import { PktElementWithSlot } from '@/base-elements/element-with-slot' import { slotContent } from '@/directives/slot-content' import { ifDefined } from 'lit/directives/if-defined.js' import { html, nothing, PropertyValues } from 'lit' import { unsafeHTML } from 'lit/directives/unsafe-html.js' import { classMap } from 'lit/directives/class-map.js' import { customElement, property } from 'lit/decorators.js' import { ElementProps } from '@/types/typeUtils' import { uuidish } from 'shared-utils/utils' import { booleanishConverter, type Booleanish } from 'shared-types' import { FocusModalityController } from '@/controllers/focus-modality-controller' import specs from 'componentSpecs/input-wrapper.json' import '@/components/helptext' import '@/components/icon' import '@/components/alert' type TCounterPosition = 'top' | 'bottom' type TInputWrapperSize = 'small' | 'medium' | 'xsmall' type Props = ElementProps< PktInputWrapper, | 'forId' | 'label' | 'helptext' | 'helptextDropdown' | 'helptextDropdownButton' | 'counter' | 'counterCurrent' | 'counterMaxLength' | 'optionalTag' | 'optionalText' | 'requiredTag' | 'requiredText' | 'tagText' | 'hasError' | 'errorMessage' | 'disabled' | 'inline' | 'ariaDescribedby' | 'hasFieldset' | 'useWrapper' | 'role' > export class PktInputWrapper extends PktElementWithSlot { /** * Element attributes */ @property({ type: String }) forId: string = uuidish() @property({ type: String }) label: string = '' @property({ type: String }) helptext: string | null = null @property({ type: String }) helptextDropdown: string | null = null @property({ type: String }) helptextDropdownButton: string | null = null @property({ type: Boolean }) counter: boolean = specs.props.counter.default @property({ type: Number }) counterCurrent: number = 0 @property({ type: Number }) counterMaxLength: number = 0 @property({ type: String }) counterError: string | null = null @property({ type: String, reflect: false }) counterPosition: TCounterPosition = 'bottom' @property({ type: Boolean }) optionalTag: boolean = specs.props.optionalTag.default @property({ type: String }) optionalText: string = specs.props.optionalText.default @property({ type: Boolean }) requiredTag: boolean = specs.props.requiredTag.default @property({ type: String }) requiredText: string = specs.props.requiredText.default @property({ type: String }) tagText: string | null = null @property({ type: Boolean }) hasError: boolean = specs.props.hasError.default @property({ type: String }) errorMessage: string = '' @property({ type: Boolean }) disabled: boolean = specs.props.disabled.default @property({ type: Boolean }) inline: boolean = specs.props.inline.default @property({ type: String }) ariaDescribedby: string | undefined = undefined @property({ type: Boolean }) hasFieldset: boolean = specs.props.hasFieldset.default @property({ type: String, reflect: true }) role: string | null = 'group' @property({ type: Boolean, reflect: true, converter: booleanishConverter }) useWrapper: Booleanish = specs.props.useWrapper.default @property({ type: String, attribute: 'input-wrapper-size' }) size: TInputWrapperSize = 'medium' protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties) } // Speiler navigeringsmodus til den interne pkt-inputwrapper-div-en som // data-navtype="pointer" (eller fjerner attributtet) så CSS kan skille // dempet fokus (klikk) fra kraftig fokus (tab). Controlleren registrerer // seg selv på hosten i konstruktøren, så vi trenger ikke beholde referansen. focusModality = new FocusModalityController(this, '.pkt-inputwrapper') render() { const classes = { 'pkt-inputwrapper': true, 'pkt-inputwrapper--error': this.hasError, 'pkt-inputwrapper--disabled': this.disabled, 'pkt-inputwrapper--inline': this.inline, 'pkt-inputwrapper--small': this.size === 'small', 'pkt-inputwrapper--xsmall': this.size === 'xsmall', } const tagClasses = 'pkt-tag pkt-tag--small pkt-tag--thin-text' const describedBy = this.ariaDescribedby ? this.ariaDescribedby : this.helptext ? `${this.forId}-helptext` : nothing const tagElement = () => { return html` ${this.tagText ? html`${this.tagText}` : nothing} ${this.optionalTag ? html`${this.optionalText}` : nothing} ${this.requiredTag ? html`${this.requiredText}` : nothing} ` } const labelElement = () => { if (this.useWrapper) { if (this.hasFieldset) { return html` ${this.label} ${tagElement()} ` } else { return html`` } } else { return html`` } } const helptextElement = () => { return html` { this.toggleDropdown(e) }} >${slotContent(this, 'helptext')} ` } const counterElement = () => { if (this.counter) { return html`
${this.counterError ? this.counterError : nothing} ${this.counterCurrent || 0} ${this.counterMaxLength ? `/${this.counterMaxLength}` : nothing}
` } else { return nothing } } const errorElement = () => { if (this.hasError && this.errorMessage) { const errorAlertSize = this.size === 'medium' ? 'small' : 'xsmall' return html`
${unsafeHTML(this.errorMessage)}
` } else { return nothing } } const inputContent = () => { //prettier-ignore return html` ${labelElement()} ${helptextElement()} ${this.counterPosition === 'top' ? counterElement() : nothing}
${slotContent(this)}
${this.counterPosition === 'bottom' ? counterElement() : nothing} ${errorElement()} ` } const wrapperElement = () => { return this.hasFieldset ? html`
${inputContent()}
` : html`
${inputContent()}
` } return html`
${wrapperElement()}
` } private toggleDropdown(e: CustomEvent) { this.dispatchEvent( new CustomEvent('toggleHelpText', { bubbles: false, detail: { isOpen: e.detail.isOpen }, }), ) } private handleLabelClick() { this.dispatchEvent( new CustomEvent('labelClick', { bubbles: true, composed: true, detail: 'label clicked', }), ) } } try { customElement('pkt-input-wrapper')(PktInputWrapper) } catch (e) { console.warn('Forsøker å definere , men den er allerede definert') }