@nordhealth/components 5.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +19411 -19267
- package/lib/Autocomplete.js +1 -1
- package/lib/Autocomplete.js.map +1 -1
- package/lib/Button.js +1 -1
- package/lib/Button.js.map +1 -1
- package/lib/Calendar.js +1 -1
- package/lib/Combobox.js +1 -1
- package/lib/Combobox.js.map +1 -1
- package/lib/ComboboxBase.js +1 -1
- package/lib/ComboboxBase.js.map +1 -1
- package/lib/CommandMenu.js +1 -1
- package/lib/CommandMenu.js.map +1 -1
- package/lib/DatePicker.js +1 -1
- package/lib/DatePicker.js.map +1 -1
- package/lib/DateRangePicker.js +1 -1
- package/lib/DateRangePicker.js.map +1 -1
- package/lib/Dropdown.js +1 -1
- package/lib/Dropdown.js.map +1 -1
- package/lib/DropdownSubmenu.js +1 -1
- package/lib/FilterAddButton.js +1 -1
- package/lib/FilterAddButton.js.map +1 -1
- package/lib/FilterDateRange.js +1 -1
- package/lib/FilterDateRange.js.map +1 -1
- package/lib/FilterDropdown.js +1 -1
- package/lib/FilterDropdown.js.map +1 -1
- package/lib/FilterField.js +1 -1
- package/lib/FilterField.js.map +1 -1
- package/lib/FilterInput.js +1 -1
- package/lib/FilterInput.js.map +1 -1
- package/lib/FilterResetButton.js +1 -1
- package/lib/FilterResetButton.js.map +1 -1
- package/lib/Item.js +1 -1
- package/lib/Item.js.map +1 -1
- package/lib/Layout.js +1 -1
- package/lib/Layout.js.map +1 -1
- package/lib/LocalizeController.js +1 -1
- package/lib/LocalizeController.js.map +1 -1
- package/lib/Message.js +1 -1
- package/lib/Message.js.map +1 -1
- package/lib/Modal.js +1 -1
- package/lib/Modal.js.map +1 -1
- package/lib/NavGroup.js +1 -1
- package/lib/NavGroup.js.map +1 -1
- package/lib/NavItem.js +1 -1
- package/lib/NavItem.js.map +1 -1
- package/lib/NavToggle-ROsuaJkW.js +2 -0
- package/lib/{NavToggle-BY83xpx0.js.map → NavToggle-ROsuaJkW.js.map} +1 -1
- package/lib/NavToggle.js +1 -1
- package/lib/Notification.js +1 -1
- package/lib/Notification.js.map +1 -1
- package/lib/NumberField.js +1 -1
- package/lib/NumberField.js.map +1 -1
- package/lib/OtpField.js +1 -1
- package/lib/OtpField.js.map +1 -1
- package/lib/Outline.js +1 -1
- package/lib/Outline.js.map +1 -1
- package/lib/Pagination.js +1 -1
- package/lib/Pagination.js.map +1 -1
- package/lib/Popout-B6Hirj3y.js +2 -0
- package/lib/{Popout-CFtFoCaA.js.map → Popout-B6Hirj3y.js.map} +1 -1
- package/lib/Popout.js +1 -1
- package/lib/Tag.js +1 -1
- package/lib/Tag.js.map +1 -1
- package/lib/Textarea.js +1 -1
- package/lib/Textarea.js.map +1 -1
- package/lib/TimePicker.js +1 -1
- package/lib/TimePicker.js.map +1 -1
- package/lib/bundle.js +9 -9
- package/lib/bundle.js.map +1 -1
- package/lib/en-us.js +1 -1
- package/lib/en-us.js.map +1 -1
- package/lib/fi-fi.js +1 -1
- package/lib/fi-fi.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/localization13.js +1 -1
- package/lib/localization13.js.map +1 -1
- package/lib/localization14.js +1 -1
- package/lib/localization14.js.map +1 -1
- package/lib/localization15.js +1 -1
- package/lib/localization15.js.map +1 -1
- package/lib/localization16.js +1 -1
- package/lib/localization16.js.map +1 -1
- package/lib/localization17.js +1 -1
- package/lib/localization17.js.map +1 -1
- package/lib/localization18.js +1 -1
- package/lib/localization18.js.map +1 -1
- package/lib/localization19.js +1 -1
- package/lib/localization19.js.map +1 -1
- package/lib/localization20.js +1 -1
- package/lib/localization20.js.map +1 -1
- package/lib/localization21.js +1 -1
- package/lib/localization21.js.map +1 -1
- package/lib/localization22.js +1 -1
- package/lib/localization22.js.map +1 -1
- package/lib/localization23.js +1 -1
- package/lib/localization23.js.map +1 -1
- package/lib/localization24.js +2 -0
- package/lib/localization24.js.map +1 -0
- package/lib/nav-context.js +2 -0
- package/lib/nav-context.js.map +1 -0
- package/lib/src/button/Button.d.ts +1 -0
- package/lib/src/dropdown/Dropdown.d.ts +3 -0
- package/lib/src/layout/Layout.d.ts +12 -0
- package/lib/src/layout/localization.d.ts +5 -0
- package/lib/src/localization/en-us.d.ts +4 -0
- package/lib/src/localization/translation.d.ts +4 -0
- package/lib/src/nav-group/NavGroup.d.ts +1 -0
- package/lib/src/nav-group/nav-context.d.ts +11 -0
- package/lib/src/nav-item/NavItem.d.ts +23 -0
- package/lib/translation.js +1 -1
- package/lib/translation.js.map +1 -1
- package/package.json +4 -4
- package/lib/NavToggle-BY83xpx0.js +0 -2
- package/lib/Popout-CFtFoCaA.js +0 -2
package/lib/Autocomplete.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{nothing as i}from"lit";import{property as e,state as o,customElement as s}from"lit/decorators.js";import{s as r}from"./Combobox-DBxXlbY7.js";import
|
|
1
|
+
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{nothing as i}from"lit";import{property as e,state as o,customElement as s}from"lit/decorators.js";import{s as r}from"./Combobox-DBxXlbY7.js";import l,{retagComboboxStyles as n}from"./ComboboxBase.js";import{ComboboxClearEvent as a}from"./ComboboxEvents.js";import{i as c}from"./light-dom-styles-DXsbiKU9.js";import{LocalizeController as p}from"./LocalizeController.js";import"./ActiveDescendantController-BnMqM79O.js";import"./number-Dg2vCfGd.js";import"./interface-close-small-CnpAFMO3.js";import"./interface-dropdown-small-CYvgxudU.js";import"lit/directives/class-map.js";import"lit/directives/if-defined.js";import"lit/directives/ref.js";import"lit/directives/repeat.js";import"lit/directives/style-map.js";import"lit/directives/unsafe-html.js";import"./collection-Dvg2XbxV.js";import"./FormDataController-OUt5L5uC.js";import"./LightDismissController-wcL1ziJa.js";import"./EventController-BBOmvfLa.js";import"./ShortcutController-CV5b0BZl.js";import"./tinykeys-DwvDXsEb.js";import"./ResizeController-14PUwRQy.js";import"./StateAttributesController-BIh96sXZ.js";import"./observe-D0n0zOfU.js";import"./cond-CI1KbneT.js";import"./events-Bv6wNHwJ.js";import"./AutocompleteMixin-D8eiOxvO.js";import"./DraftComponentMixin-4qug5Rky.js";import"./FieldControlMixin-BXNZJXu9.js";import"./context-consumer-BJVUulGf.js";import"./context-request-event-CCRlXoeR.js";import"./field-context.js";import"./create-context-CJ69ntlY.js";import"./SlotController-Z6eG7LSZ.js";import"./VisuallyHidden.js";import"./Popout-B6Hirj3y.js";import"./positioning-D-K8Mueq.js";import"./DirectionController-ChvNGESZ.js";import"./ScrollbarController-BFC67Y2x.js";import"./Component-BJ5O5n8k.js";import"./InputMixin-LetXsCyv.js";import"./SizeMixin-CU9cLbLC.js";import"./Icon.js";import"./IconManager.js";import"./Spinner.js";import"./translation.js";import"./en-us.js";import"./localization.js";import"./localization2.js";import"./localization3.js";import"./localization4.js";import"./localization5.js";import"./localization6.js";import"./localization7.js";import"./localization8.js";import"./localization9.js";import"./localization10.js";import"./localization11.js";import"./localization12.js";import"./localization13.js";import"./localization14.js";import"./localization15.js";import"./localization16.js";import"./localization17.js";import"./localization18.js";import"./localization19.js";import"./localization20.js";import"./localization21.js";import"./localization22.js";import"./localization23.js";import"./localization24.js";const h=n(r,"nord-autocomplete");let m=class extends l{constructor(){super(...arguments),this.value="",this.inline=!1,this.clearable=!1,this.openOnInputClick=!1,this.localize=new p(this),this.keyboardActive=!1}connectedCallback(){super.connectedCallback(),c(this,h)}get filterQuery(){return this.value}get inlineQuery(){return this.value}get hasInlineGhost(){if(!this.inline||!this.keyboardActive||!this.open)return!1;const t=this.activeOption,i=this.inlineQuery;if(!t||!i)return!1;const e=t.label;return e.length>i.length&&e.toLocaleLowerCase().startsWith(i.toLocaleLowerCase())}get displayValue(){if(this.hasInlineGhost){const t=this.inlineQuery,{label:i}=this.activeOption;return t+i.slice(t.length)}return this.value}get hasValue(){return this.value.length>0}get formValue(){return this.value||void 0}get showClear(){return this.clearable&&this.hasValue&&!this.disabled&&!this.readonly}get showToggle(){return!1}get ariaAutocomplete(){return this.inline?this.externalFiltering?"inline":"both":this.externalFiltering?"none":"list"}isSelected(){return!1}get ariaMultiselectable(){}showCheck(){return!1}get rendersCheck(){return!1}optionAriaSelected(){return i}onHide(){}onInput(t){this.keyboardActive=!1,this.value=t.value,this.commit()}onKeydown(t){switch(t.key){case"ArrowDown":case"ArrowUp":case"Home":this.open&&(this.keyboardActive=!0);break;case"ArrowRight":case"End":if(this.inline&&this.open&&this.hasInlineGhost){const i=this.inputRef.value;if(!i||i.selectionEnd===i.value.length){t.preventDefault(),this.acceptInline();break}}"End"===t.key&&this.open&&(this.keyboardActive=!0);break;case"Backspace":case"Delete":this.keyboardActive=!1}}onMove(){this.keyboardActive=!0}handleEnter(t){return this.inline&&this.hasInlineGhost&&this.activeOption?(t.preventDefault(),this.acceptInline(),!0):(this.navigated||this.autoHighlight&&this.hasQuery)&&this.activeOption?(t.preventDefault(),this.selectOption(this.activeOption),!0):(this.hide(),!0)}get hasQuery(){return this.filterQuery.length>0}onOptionHover(){this.keyboardActive=!1}onBlur(){this.inline&&this.hasInlineGhost&&this.revertInline(),this.keyboardActive=!1}afterUpdate(){if(this.hasInlineGhost){const t=this.inputRef.value;t&&t.setSelectionRange(this.inlineQuery.length,t.value.length)}}selectOption(t){t.disabled||(this.labelCache.set(t.value,t.label),this.keyboardActive=!1,this.value=t.label,this.search=t.label,this.commit(),this.hide(),this.focus())}acceptInline(){const t=this.activeOption;t&&this.selectOption(t)}revertInline(){this.keyboardActive=!1;const t=this.inputRef.value;t&&(t.value=this.value,t.setSelectionRange(this.value.length,this.value.length))}resetActiveIndex(){super.resetActiveIndex(),this.keyboardActive=!1}clear(){this.value="",this.search="",this.keyboardActive=!1,this.dispatchEvent(new a),this.commit(),this.focus()}};t([e({attribute:!1})],m.prototype,"value",void 0),t([e({reflect:!0,type:Boolean})],m.prototype,"inline",void 0),t([e({reflect:!0,type:Boolean})],m.prototype,"clearable",void 0),t([e({reflect:!0,type:Boolean,attribute:"open-on-input-click"})],m.prototype,"openOnInputClick",void 0),t([o()],m.prototype,"keyboardActive",void 0),m=t([s("nord-autocomplete")],m);var u=m;export{a as ComboboxClearEvent,u as default};
|
|
2
2
|
//# sourceMappingURL=Autocomplete.js.map
|
package/lib/Autocomplete.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Autocomplete.js","sources":["../src/autocomplete/Autocomplete.ts"],"sourcesContent":["import type { ComboboxOption } from '../combobox/ComboboxBase.js'\nimport { nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport comboboxStyle from '../combobox/Combobox.css'\nimport ComboboxBase, { retagComboboxStyles } from '../combobox/ComboboxBase.js'\nimport { ComboboxClearEvent } from '../combobox/ComboboxEvents.js'\nimport { injectLightDomStyles } from '../common/light-dom-styles.js'\nimport { LocalizeController } from '../localization/LocalizeController.js'\n\nexport type { ComboboxOption } from '../combobox/ComboboxBase.js'\nexport { ComboboxClearEvent } from '../combobox/ComboboxEvents.js'\n\n/**\n * The shared combobox stylesheet is authored once against the `nord-combobox`\n * tag (see `Combobox.css`); re-prefix it for `nord-autocomplete` so this sibling\n * light-DOM control reuses the exact same `.n-*` rules without duplicating them.\n */\nconst style = retagComboboxStyles(comboboxStyle, 'nord-autocomplete')\n\n/**\n * Autocomplete is a free-text input with suggestions: the text the user types\n * is itself the committed `value` (a string), and the options are completion\n * aids. Picking a suggestion fills the input with the option's `label` and commits\n * that label. Use it when the answer is open-ended text but you can offer\n * helpful completions (a city, a tag, a username); reach for\n * [Combobox](/components/combobox/) instead when the value must be one of a\n * fixed set of options.\n *\n * Autocomplete is a control only — it has no built-in label, hint or error.\n * Compose it inside a [Field](/components/field/) to give it a label, hint and\n * error message, the same way you would a native input.\n *\n * Base UI parity: Autocomplete is the Combobox primitive with\n * `selectionMode=\"none\"` + `fillInputOnItemPress`.\n *\n * @status draft\n * @category form\n * @lightDom true\n *\n * @slot start - Optional slot used to place an icon or prefix at the start of the control.\n * @slot end - Optional slot used to place an icon or suffix at the end of the control.\n *\n * @fires {NordEvent} change - Dispatched when the committed value changes via user interaction.\n * @fires {NordEvent} input - Dispatched as the user types into the input.\n * @fires {NordEvent} open - Dispatched when the listbox is opened.\n * @fires {NordEvent} close - Dispatched when the listbox is closed.\n * @fires {ComboboxClearEvent} clear - Dispatched when the value is cleared via the clear button or `clear()`.\n *\n * @localization clearLabel - Accessible label for the clear button.\n * @localization noResults - Message shown when no options match the query.\n *\n * @cssprop [--n-combobox-list-inline-size] - Controls the inline size, or width, of the dropdown list. Defaults to the width of the control.\n * @cssprop [--n-combobox-block-size] - Controls the block size, or height, of the control. The medium default is `var(--n-space-xl)`; the s and l sizes scale from it.\n *\n * @attr {boolean} data-invalid - Present while the control is invalid (mirrors the `invalid` property). Style hook for the whole control.\n * @attr {boolean} data-focused - Present while the control has focus. Style hook for focus-within treatments.\n */\n@customElement('nord-autocomplete')\nexport default class Autocomplete extends ComboboxBase {\n /**\n * The value of the autocomplete. This is the free text the user has typed (or\n * the `label` of a picked suggestion) — always a single string.\n */\n @property({ attribute: false }) value = ''\n\n /**\n * Enables inline ghost-text completion. While an option is highlighted with\n * the keyboard, the input shows the typed query followed by the remainder of\n * the highlighted option's label, with that remainder text-selected so\n * continued typing overwrites it. Accepting (Enter, ArrowRight/End at the end\n * of the text, or an option click) commits the full label.\n */\n @property({ reflect: true, type: Boolean }) inline = false\n\n /**\n * Shows a clear (✕) button while there is text. Pressing it resets the value\n * to `''`, then fires `clear` and `change`.\n */\n @property({ reflect: true, type: Boolean }) clearable = false\n\n /**\n * Whether clicking the input opens the suggestion list. Defaults to `false`\n * for autocomplete — matching Base UI's `openOnInputClick={false}`, the list\n * appears as the user types rather than on click. Set to `true` to also open\n * on click.\n */\n @property({ reflect: true, type: Boolean, attribute: 'open-on-input-click' }) override openOnInputClick = false\n\n protected override localize = new LocalizeController<'nord-autocomplete'>(this)\n\n /**\n * Whether the active option last changed via the keyboard (arrows/Home/End)\n * rather than pointer hover. Inline ghost completion only fills on keyboard\n * navigation, mirroring Base UI's `keyboardActiveRef`. Reactive so toggling it\n * (even when `activeIndex` is unchanged, e.g. a single filtered option)\n * re-renders the ghost.\n */\n @state() private keyboardActive = false\n\n connectedCallback() {\n super.connectedCallback()\n injectLightDomStyles(this, style)\n }\n\n /**\n * The committed free-text `value` *is* the query, so a programmatically-set\n * `value` (e.g. `el.value = 'fea'`) filters the open list without the user\n * having typed, matching the text the input already displays.\n */\n protected override get filterQuery(): string {\n return this.value\n }\n\n /** The typed query an inline ghost is composed against (the committed value). */\n private get inlineQuery(): string {\n return this.value\n }\n\n /**\n * Whether an inline ghost suffix is currently showing: only in `inline` mode,\n * when the active option (reached via the keyboard) has a label that starts\n * with the typed query and extends it.\n */\n private get hasInlineGhost(): boolean {\n if (!this.inline || !this.keyboardActive || !this.open) {\n return false\n }\n\n const option = this.activeOption\n const query = this.inlineQuery\n\n if (!option || !query) {\n return false\n }\n\n const label = option.label\n return (\n label.length > query.length\n && label.toLocaleLowerCase().startsWith(query.toLocaleLowerCase())\n )\n }\n\n /** The text shown in the input. */\n protected override get displayValue(): string {\n // While an inline ghost is showing, present the typed query plus the\n // highlighted option's remaining label (with the original casing).\n if (this.hasInlineGhost) {\n const query = this.inlineQuery\n const { label } = this.activeOption!\n return query + label.slice(query.length)\n }\n\n // Otherwise the input always reflects the committed free-text value.\n return this.value\n }\n\n /** Free text is always present once non-empty (drives the clear affordance). */\n protected override get hasValue(): boolean {\n return this.value.length > 0\n }\n\n protected override get formValue(): string | undefined {\n return this.value || undefined\n }\n\n protected override get showClear(): boolean {\n return this.clearable && this.hasValue && !this.disabled && !this.readonly\n }\n\n /**\n * Autocomplete is a search input with suggestions, not a picker — like Base\n * UI's autocomplete it renders no dropdown chevron (its `AriaCombobox` has no\n * `Trigger`). The list appears as you type, not on a toggle click.\n */\n protected override get showToggle(): boolean {\n return false\n }\n\n /**\n * The `aria-autocomplete` value for the inner input, mapping the `inline` +\n * `external-filtering` axes onto the four ARIA values:\n * - inline + internal filter → `both`\n * - inline + external-filtering → `inline` (static list + ghost)\n * - external-filtering (no inline) → `none` (static list, no ghost)\n * - otherwise → `list` (internal filter, no ghost)\n */\n protected override get ariaAutocomplete(): 'list' | 'both' | 'inline' | 'none' {\n if (this.inline) {\n return this.externalFiltering ? 'inline' : 'both'\n }\n return this.externalFiltering ? 'none' : 'list'\n }\n\n /**\n * Autocomplete renders no selection state — `aria-selected` is omitted\n * (`optionAriaSelected` → nothing) and the check is never shown\n * (`showCheck` → false) — so nothing is ever \"selected\".\n */\n protected override isSelected(): boolean {\n return false\n }\n\n /**\n * Autocomplete has no selection model, so the listbox does not advertise\n * `aria-multiselectable` at all (the base's `'false'` would imply a single-\n * select semantic it doesn't have).\n */\n protected override get ariaMultiselectable(): undefined {\n return undefined\n }\n\n /** Autocomplete never shows the selected-check icon (it is not a select). */\n protected override showCheck(): boolean {\n return false\n }\n\n /** No selection model, so no leading check gutter (avoids indenting suggestions). */\n protected override get rendersCheck(): boolean {\n return false\n }\n\n /** Autocomplete options are completion aids, not selectable values — omit aria-selected. */\n protected override optionAriaSelected(): typeof nothing {\n return nothing\n }\n\n /** Autocomplete keeps its committed free-text query when the listbox closes. */\n protected override onHide() {\n // Intentionally empty: the free-text value is the query, so it is not\n // discarded the way select mode's transient filter is.\n }\n\n /** Typing commits the typed text as the live value. */\n protected override onInput(input: HTMLInputElement): void {\n // Typing breaks any inline ghost suffix the keyboard had composed.\n this.keyboardActive = false\n // The typed text *is* the value, so it tracks the input live. `input` has\n // already fired (in the base); `commit()` emits the live `change`.\n this.value = input.value\n this.commit()\n }\n\n /**\n * Runs before the base's own key switch. Marks keyboard navigation (so the\n * inline ghost may compose) and owns the ArrowRight/End inline-accept. After\n * an accept the listbox is closed, so the base's own `End` branch (gated on\n * `this.open`) is skipped; the base still performs the moves for the arrows\n * and Home.\n */\n protected override onKeydown(e: KeyboardEvent): void {\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowUp':\n case 'Home':\n // The base performs the actual move (and opens a closed listbox); mark\n // it as keyboard navigation so the ghost composes after it.\n if (this.open) {\n this.keyboardActive = true\n }\n break\n case 'ArrowRight':\n case 'End':\n // Accept an inline ghost suffix: End always sits at the text end, and\n // ArrowRight accepts when the selection already reaches the end. When the\n // ghost suffix is selected, `selectionEnd` is the text length, so both\n // cases collapse to \"is the selection at the end?\". A bare `End` (no\n // ghost) falls through to the base's jump-to-last navigation, which we\n // mark as keyboard nav so the ghost composes for the newly-active row.\n if (this.inline && this.open && this.hasInlineGhost) {\n const input = this.inputRef.value\n const atEnd = !input || input.selectionEnd === input.value.length\n if (atEnd) {\n e.preventDefault()\n this.acceptInline()\n break\n }\n }\n if (e.key === 'End' && this.open) {\n this.keyboardActive = true\n }\n break\n case 'Backspace':\n case 'Delete':\n // A deletion breaks any inline ghost suffix.\n this.keyboardActive = false\n break\n }\n }\n\n protected override onMove(): void {\n this.keyboardActive = true\n }\n\n /**\n * Fully owns Enter when the listbox is open. An inline ghost or any\n * highlighted option fills+commits its label; otherwise the typed text is\n * already committed, so Enter just closes and does NOT preventDefault (the\n * owning form may submit). Returns `true` so the base does no further handling.\n */\n protected override handleEnter(e: KeyboardEvent): boolean {\n if (this.inline && this.hasInlineGhost && this.activeOption) {\n e.preventDefault()\n this.acceptInline()\n return true\n }\n\n // A navigated (arrow-key) highlight always commits. `autoHighlight` only\n // implicitly commits the first row when there is a query to complete — Base\n // UI does not highlight index 0 on an empty query, so a bare Enter with no\n // typed text just closes (and may submit the form).\n if ((this.navigated || (this.autoHighlight && this.hasQuery)) && this.activeOption) {\n e.preventDefault()\n this.selectOption(this.activeOption)\n return true\n }\n\n this.hide()\n return true\n }\n\n /** Whether the user has typed/committed any free text to complete against. */\n private get hasQuery(): boolean {\n return this.filterQuery.length > 0\n }\n\n /** Pointer hover never composes an inline ghost (mirrors keyboard-vs-pointer). */\n protected override onOptionHover(): void {\n this.keyboardActive = false\n }\n\n /**\n * Never silently commit an un-accepted inline suggestion: on blur drop the\n * ghost suffix and restore the input to the typed query (the actual value).\n */\n protected override onBlur(): void {\n if (this.inline && this.hasInlineGhost) {\n this.revertInline()\n }\n this.keyboardActive = false\n }\n\n /**\n * Lit binds the inner input's `.value` to the ghost-composed string, but the\n * suffix selection has to be applied imperatively after the DOM update so\n * continued typing overwrites it.\n */\n protected override afterUpdate(): void {\n if (this.hasInlineGhost) {\n const input = this.inputRef.value\n if (input) {\n input.setSelectionRange(this.inlineQuery.length, input.value.length)\n }\n }\n }\n\n /**\n * Picking a suggestion is a completion aid: fill the input with its label and\n * commit that label as the free-text value. No chips, no array, no toggle.\n */\n protected override selectOption(option: ComboboxOption): void {\n if (option.disabled) {\n return\n }\n\n // Remember the label so it survives the option leaving the result set.\n this.labelCache.set(option.value, option.label)\n\n this.keyboardActive = false\n this.value = option.label\n this.search = option.label\n this.commit()\n this.hide()\n // Restore focus to the input. Focus alone never opens the listbox.\n this.focus()\n }\n\n /** Commit the currently ghost-highlighted option's full label (inline mode). */\n private acceptInline() {\n const option = this.activeOption\n if (!option) {\n return\n }\n this.selectOption(option)\n }\n\n /**\n * Drop the transient inline ghost suffix, restoring the input to the typed\n * query (the actual committed `value`). Does not change `value`.\n */\n private revertInline() {\n this.keyboardActive = false\n\n const input = this.inputRef.value\n if (input) {\n input.value = this.value\n // Collapse any lingering suffix selection to the end of the typed text.\n input.setSelectionRange(this.value.length, this.value.length)\n }\n }\n\n /** A new query supersedes any inline ghost from the previous one. */\n protected override resetActiveIndex() {\n super.resetActiveIndex()\n this.keyboardActive = false\n }\n\n /** Clear the current value, firing `clear` then `change`. */\n override clear() {\n this.value = ''\n this.search = ''\n this.keyboardActive = false\n this.dispatchEvent(new ComboboxClearEvent())\n this.commit()\n this.focus()\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-autocomplete': Autocomplete\n }\n}\n"],"names":["style","retagComboboxStyles","comboboxStyle","Autocomplete","ComboboxBase","constructor","this","value","inline","clearable","openOnInputClick","localize","LocalizeController","keyboardActive","connectedCallback","super","injectLightDomStyles","filterQuery","inlineQuery","hasInlineGhost","open","option","activeOption","query","label","length","toLocaleLowerCase","startsWith","displayValue","slice","hasValue","formValue","undefined","showClear","disabled","readonly","showToggle","ariaAutocomplete","externalFiltering","isSelected","ariaMultiselectable","showCheck","rendersCheck","optionAriaSelected","nothing","onHide","onInput","input","commit","onKeydown","e","key","inputRef","selectionEnd","preventDefault","acceptInline","onMove","handleEnter","navigated","autoHighlight","hasQuery","selectOption","hide","onOptionHover","onBlur","revertInline","afterUpdate","setSelectionRange","labelCache","set","search","focus","resetActiveIndex","clear","dispatchEvent","ComboboxClearEvent","__decorate","property","attribute","prototype","reflect","type","Boolean","state","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Autocomplete.js","sources":["../src/autocomplete/Autocomplete.ts"],"sourcesContent":["import type { ComboboxOption } from '../combobox/ComboboxBase.js'\nimport { nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport comboboxStyle from '../combobox/Combobox.css'\nimport ComboboxBase, { retagComboboxStyles } from '../combobox/ComboboxBase.js'\nimport { ComboboxClearEvent } from '../combobox/ComboboxEvents.js'\nimport { injectLightDomStyles } from '../common/light-dom-styles.js'\nimport { LocalizeController } from '../localization/LocalizeController.js'\n\nexport type { ComboboxOption } from '../combobox/ComboboxBase.js'\nexport { ComboboxClearEvent } from '../combobox/ComboboxEvents.js'\n\n/**\n * The shared combobox stylesheet is authored once against the `nord-combobox`\n * tag (see `Combobox.css`); re-prefix it for `nord-autocomplete` so this sibling\n * light-DOM control reuses the exact same `.n-*` rules without duplicating them.\n */\nconst style = retagComboboxStyles(comboboxStyle, 'nord-autocomplete')\n\n/**\n * Autocomplete is a free-text input with suggestions: the text the user types\n * is itself the committed `value` (a string), and the options are completion\n * aids. Picking a suggestion fills the input with the option's `label` and commits\n * that label. Use it when the answer is open-ended text but you can offer\n * helpful completions (a city, a tag, a username); reach for\n * [Combobox](/components/combobox/) instead when the value must be one of a\n * fixed set of options.\n *\n * Autocomplete is a control only — it has no built-in label, hint or error.\n * Compose it inside a [Field](/components/field/) to give it a label, hint and\n * error message, the same way you would a native input.\n *\n * Base UI parity: Autocomplete is the Combobox primitive with\n * `selectionMode=\"none\"` + `fillInputOnItemPress`.\n *\n * @status draft\n * @category form\n * @lightDom true\n *\n * @slot start - Optional slot used to place an icon or prefix at the start of the control.\n * @slot end - Optional slot used to place an icon or suffix at the end of the control.\n *\n * @fires {NordEvent} change - Dispatched when the committed value changes via user interaction.\n * @fires {NordEvent} input - Dispatched as the user types into the input.\n * @fires {NordEvent} open - Dispatched when the listbox is opened.\n * @fires {NordEvent} close - Dispatched when the listbox is closed.\n * @fires {ComboboxClearEvent} clear - Dispatched when the value is cleared via the clear button or `clear()`.\n *\n * @localization clearLabel - Accessible label for the clear button.\n * @localization noResults - Message shown when no options match the query.\n *\n * @cssprop [--n-combobox-list-inline-size] - Controls the inline size, or width, of the dropdown list. Defaults to the width of the control.\n * @cssprop [--n-combobox-block-size] - Controls the block size, or height, of the control. The medium default is `var(--n-space-xl)`; the s and l sizes scale from it.\n *\n * @attr {boolean} data-invalid - Present while the control is invalid (mirrors the `invalid` property). Style hook for the whole control.\n * @attr {boolean} data-focused - Present while the control has focus. Style hook for focus-within treatments.\n */\n@customElement('nord-autocomplete')\nexport default class Autocomplete extends ComboboxBase {\n /**\n * The value of the autocomplete. This is the free text the user has typed (or\n * the `label` of a picked suggestion) — always a single string.\n */\n @property({ attribute: false }) value = ''\n\n /**\n * Enables inline ghost-text completion. While an option is highlighted with\n * the keyboard, the input shows the typed query followed by the remainder of\n * the highlighted option's label, with that remainder text-selected so\n * continued typing overwrites it. Accepting (Enter, ArrowRight/End at the end\n * of the text, or an option click) commits the full label.\n */\n @property({ reflect: true, type: Boolean }) inline = false\n\n /**\n * Shows a clear (✕) button while there is text. Pressing it resets the value\n * to `''`, then fires `clear` and `change`.\n */\n @property({ reflect: true, type: Boolean }) clearable = false\n\n /**\n * Whether clicking the input opens the suggestion list. Defaults to `false`\n * for autocomplete — matching Base UI's `openOnInputClick={false}`, the list\n * appears as the user types rather than on click. Set to `true` to also open\n * on click.\n */\n @property({ reflect: true, type: Boolean, attribute: 'open-on-input-click' }) override openOnInputClick = false\n\n protected override localize = new LocalizeController<'nord-autocomplete'>(this)\n\n /**\n * Whether the active option last changed via the keyboard (arrows/Home/End)\n * rather than pointer hover. Inline ghost completion only fills on keyboard\n * navigation, mirroring Base UI's `keyboardActiveRef`. Reactive so toggling it\n * (even when `activeIndex` is unchanged, e.g. a single filtered option)\n * re-renders the ghost.\n */\n @state() private keyboardActive = false\n\n connectedCallback() {\n super.connectedCallback()\n injectLightDomStyles(this, style)\n }\n\n /**\n * The committed free-text `value` *is* the query, so a programmatically-set\n * `value` (e.g. `el.value = 'fea'`) filters the open list without the user\n * having typed, matching the text the input already displays.\n */\n protected override get filterQuery(): string {\n return this.value\n }\n\n /** The typed query an inline ghost is composed against (the committed value). */\n private get inlineQuery(): string {\n return this.value\n }\n\n /**\n * Whether an inline ghost suffix is currently showing: only in `inline` mode,\n * when the active option (reached via the keyboard) has a label that starts\n * with the typed query and extends it.\n */\n private get hasInlineGhost(): boolean {\n if (!this.inline || !this.keyboardActive || !this.open) {\n return false\n }\n\n const option = this.activeOption\n const query = this.inlineQuery\n\n if (!option || !query) {\n return false\n }\n\n const label = option.label\n return (\n label.length > query.length\n && label.toLocaleLowerCase().startsWith(query.toLocaleLowerCase())\n )\n }\n\n /** The text shown in the input. */\n protected override get displayValue(): string {\n // While an inline ghost is showing, present the typed query plus the\n // highlighted option's remaining label (with the original casing).\n if (this.hasInlineGhost) {\n const query = this.inlineQuery\n const { label } = this.activeOption!\n return query + label.slice(query.length)\n }\n\n // Otherwise the input always reflects the committed free-text value.\n return this.value\n }\n\n /** Free text is always present once non-empty (drives the clear affordance). */\n protected override get hasValue(): boolean {\n return this.value.length > 0\n }\n\n protected override get formValue(): string | undefined {\n return this.value || undefined\n }\n\n protected override get showClear(): boolean {\n return this.clearable && this.hasValue && !this.disabled && !this.readonly\n }\n\n /**\n * Autocomplete is a search input with suggestions, not a picker — like Base\n * UI's autocomplete it renders no dropdown chevron (its `AriaCombobox` has no\n * `Trigger`). The list appears as you type, not on a toggle click.\n */\n protected override get showToggle(): boolean {\n return false\n }\n\n /**\n * The `aria-autocomplete` value for the inner input, mapping the `inline` +\n * `external-filtering` axes onto the four ARIA values:\n * - inline + internal filter → `both`\n * - inline + external-filtering → `inline` (static list + ghost)\n * - external-filtering (no inline) → `none` (static list, no ghost)\n * - otherwise → `list` (internal filter, no ghost)\n */\n protected override get ariaAutocomplete(): 'list' | 'both' | 'inline' | 'none' {\n if (this.inline) {\n return this.externalFiltering ? 'inline' : 'both'\n }\n return this.externalFiltering ? 'none' : 'list'\n }\n\n /**\n * Autocomplete renders no selection state — `aria-selected` is omitted\n * (`optionAriaSelected` → nothing) and the check is never shown\n * (`showCheck` → false) — so nothing is ever \"selected\".\n */\n protected override isSelected(): boolean {\n return false\n }\n\n /**\n * Autocomplete has no selection model, so the listbox does not advertise\n * `aria-multiselectable` at all (the base's `'false'` would imply a single-\n * select semantic it doesn't have).\n */\n protected override get ariaMultiselectable(): undefined {\n return undefined\n }\n\n /** Autocomplete never shows the selected-check icon (it is not a select). */\n protected override showCheck(): boolean {\n return false\n }\n\n /** No selection model, so no leading check gutter (avoids indenting suggestions). */\n protected override get rendersCheck(): boolean {\n return false\n }\n\n /** Autocomplete options are completion aids, not selectable values — omit aria-selected. */\n protected override optionAriaSelected(): typeof nothing {\n return nothing\n }\n\n /** Autocomplete keeps its committed free-text query when the listbox closes. */\n protected override onHide() {\n // Intentionally empty: the free-text value is the query, so it is not\n // discarded the way select mode's transient filter is.\n }\n\n /** Typing commits the typed text as the live value. */\n protected override onInput(input: HTMLInputElement): void {\n // Typing breaks any inline ghost suffix the keyboard had composed.\n this.keyboardActive = false\n // The typed text *is* the value, so it tracks the input live. `input` has\n // already fired (in the base); `commit()` emits the live `change`.\n this.value = input.value\n this.commit()\n }\n\n /**\n * Runs before the base's own key switch. Marks keyboard navigation (so the\n * inline ghost may compose) and owns the ArrowRight/End inline-accept. After\n * an accept the listbox is closed, so the base's own `End` branch (gated on\n * `this.open`) is skipped; the base still performs the moves for the arrows\n * and Home.\n */\n protected override onKeydown(e: KeyboardEvent): void {\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowUp':\n case 'Home':\n // The base performs the actual move (and opens a closed listbox); mark\n // it as keyboard navigation so the ghost composes after it.\n if (this.open) {\n this.keyboardActive = true\n }\n break\n case 'ArrowRight':\n case 'End':\n // Accept an inline ghost suffix: End always sits at the text end, and\n // ArrowRight accepts when the selection already reaches the end. When the\n // ghost suffix is selected, `selectionEnd` is the text length, so both\n // cases collapse to \"is the selection at the end?\". A bare `End` (no\n // ghost) falls through to the base's jump-to-last navigation, which we\n // mark as keyboard nav so the ghost composes for the newly-active row.\n if (this.inline && this.open && this.hasInlineGhost) {\n const input = this.inputRef.value\n const atEnd = !input || input.selectionEnd === input.value.length\n if (atEnd) {\n e.preventDefault()\n this.acceptInline()\n break\n }\n }\n if (e.key === 'End' && this.open) {\n this.keyboardActive = true\n }\n break\n case 'Backspace':\n case 'Delete':\n // A deletion breaks any inline ghost suffix.\n this.keyboardActive = false\n break\n }\n }\n\n protected override onMove(): void {\n this.keyboardActive = true\n }\n\n /**\n * Fully owns Enter when the listbox is open. An inline ghost or any\n * highlighted option fills+commits its label; otherwise the typed text is\n * already committed, so Enter just closes and does NOT preventDefault (the\n * owning form may submit). Returns `true` so the base does no further handling.\n */\n protected override handleEnter(e: KeyboardEvent): boolean {\n if (this.inline && this.hasInlineGhost && this.activeOption) {\n e.preventDefault()\n this.acceptInline()\n return true\n }\n\n // A navigated (arrow-key) highlight always commits. `autoHighlight` only\n // implicitly commits the first row when there is a query to complete — Base\n // UI does not highlight index 0 on an empty query, so a bare Enter with no\n // typed text just closes (and may submit the form).\n if ((this.navigated || (this.autoHighlight && this.hasQuery)) && this.activeOption) {\n e.preventDefault()\n this.selectOption(this.activeOption)\n return true\n }\n\n this.hide()\n return true\n }\n\n /** Whether the user has typed/committed any free text to complete against. */\n private get hasQuery(): boolean {\n return this.filterQuery.length > 0\n }\n\n /** Pointer hover never composes an inline ghost (mirrors keyboard-vs-pointer). */\n protected override onOptionHover(): void {\n this.keyboardActive = false\n }\n\n /**\n * Never silently commit an un-accepted inline suggestion: on blur drop the\n * ghost suffix and restore the input to the typed query (the actual value).\n */\n protected override onBlur(): void {\n if (this.inline && this.hasInlineGhost) {\n this.revertInline()\n }\n this.keyboardActive = false\n }\n\n /**\n * Lit binds the inner input's `.value` to the ghost-composed string, but the\n * suffix selection has to be applied imperatively after the DOM update so\n * continued typing overwrites it.\n */\n protected override afterUpdate(): void {\n if (this.hasInlineGhost) {\n const input = this.inputRef.value\n if (input) {\n input.setSelectionRange(this.inlineQuery.length, input.value.length)\n }\n }\n }\n\n /**\n * Picking a suggestion is a completion aid: fill the input with its label and\n * commit that label as the free-text value. No chips, no array, no toggle.\n */\n protected override selectOption(option: ComboboxOption): void {\n if (option.disabled) {\n return\n }\n\n // Remember the label so it survives the option leaving the result set.\n this.labelCache.set(option.value, option.label)\n\n this.keyboardActive = false\n this.value = option.label\n this.search = option.label\n this.commit()\n this.hide()\n // Restore focus to the input. Focus alone never opens the listbox.\n this.focus()\n }\n\n /** Commit the currently ghost-highlighted option's full label (inline mode). */\n private acceptInline() {\n const option = this.activeOption\n if (!option) {\n return\n }\n this.selectOption(option)\n }\n\n /**\n * Drop the transient inline ghost suffix, restoring the input to the typed\n * query (the actual committed `value`). Does not change `value`.\n */\n private revertInline() {\n this.keyboardActive = false\n\n const input = this.inputRef.value\n if (input) {\n input.value = this.value\n // Collapse any lingering suffix selection to the end of the typed text.\n input.setSelectionRange(this.value.length, this.value.length)\n }\n }\n\n /** A new query supersedes any inline ghost from the previous one. */\n protected override resetActiveIndex() {\n super.resetActiveIndex()\n this.keyboardActive = false\n }\n\n /** Clear the current value, firing `clear` then `change`. */\n override clear() {\n this.value = ''\n this.search = ''\n this.keyboardActive = false\n this.dispatchEvent(new ComboboxClearEvent())\n this.commit()\n this.focus()\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-autocomplete': Autocomplete\n }\n}\n"],"names":["style","retagComboboxStyles","comboboxStyle","Autocomplete","ComboboxBase","constructor","this","value","inline","clearable","openOnInputClick","localize","LocalizeController","keyboardActive","connectedCallback","super","injectLightDomStyles","filterQuery","inlineQuery","hasInlineGhost","open","option","activeOption","query","label","length","toLocaleLowerCase","startsWith","displayValue","slice","hasValue","formValue","undefined","showClear","disabled","readonly","showToggle","ariaAutocomplete","externalFiltering","isSelected","ariaMultiselectable","showCheck","rendersCheck","optionAriaSelected","nothing","onHide","onInput","input","commit","onKeydown","e","key","inputRef","selectionEnd","preventDefault","acceptInline","onMove","handleEnter","navigated","autoHighlight","hasQuery","selectOption","hide","onOptionHover","onBlur","revertInline","afterUpdate","setSelectionRange","labelCache","set","search","focus","resetActiveIndex","clear","dispatchEvent","ComboboxClearEvent","__decorate","property","attribute","prototype","reflect","type","Boolean","state","customElement"],"mappings":"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"}
|
package/lib/Button.js
CHANGED
|
@@ -4,5 +4,5 @@ import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{css as o,html as n,nothing as
|
|
|
4
4
|
--n-button-box-shadow,0 0 0 1px var(--n-color-surface),0 0 0 3px var(--_n-button-border-color)
|
|
5
5
|
)}:host([variant=dashed]){--_n-button-border-color:var(--n-button-border-color,var(--n-color-border-hover));--_n-button-box-shadow:var(--n-button-box-shadow,none);--_n-button-color:var(--n-button-color,var(--n-color-text-weaker));--_n-button-border-style:dashed}:host([variant=dashed]) .n-button:hover{--_n-button-color:var(--n-button-color,var(--n-color-text))}:host([variant=dashed]) .n-button:after{display:none}:host([variant=plain]){--_n-button-box-shadow:var(--n-button-box-shadow,none)}:host([variant=plain]),:host([variant=plain]) .n-button:hover{--_n-button-border-color:var(--n-button-border-color,#0000)}:host([variant=plain]) .n-button:after{display:none}:host([variant=danger]){--_n-button-box-shadow:var(--n-button-box-shadow,none);--_n-button-color:var(--n-button-color,var(--n-color-text-on-accent));--_n-button-font-weight:var(--n-button-font-weight,var(--n-font-weight-active))}:host([variant=danger]),:host([variant=danger]) .n-button:hover{--_n-button-border-color:var(--n-button-border-color,#0000);--_n-button-background-color:var(--n-button-background-color,var(--n-color-status-danger))}:host([variant=danger]) .n-button:hover{filter:brightness(93%)}:host([variant=danger]) .n-button:focus{--_n-button-border-color:var(--n-button-border-color,var(--n-color-status-danger));--_n-button-box-shadow:var(
|
|
6
6
|
--n-button-box-shadow,0 0 0 1px var(--n-color-surface),0 0 0 3px var(--n-color-status-danger)
|
|
7
|
-
)}:host([disabled]){--_n-button-border-color:var(--n-button-border-color,var(--_n-button-background-color));--_n-button-background-color:var(--n-button-background-color,var(--n-color-border));--_n-button-box-shadow:var(--n-button-box-shadow,none);--_n-button-color:var(--n-button-color,var(--n-color-text-weak));--_n-button-opacity:0.5;pointer-events:none}:host([disabled]) .n-button:after{display:none}.n-button-spinner{inset-block-start:50%;inset-inline-start:50%;position:absolute;transform:translateX(-50%) translateY(-50%)}:host([loading]:not([href])){pointer-events:none}:host([loading]:not([href])) .n-content,:host([loading]:not([href])) ::slotted([slot=end]),:host([loading]:not([href])) ::slotted([slot=start]){opacity:0}:host([size=s]){--_n-button-gap:var(--n-button-gap,var(--n-space-xs));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-s) + 1px));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-s));--_n-button-min-block-size:var(--n-button-min-block-size,calc(var(--n-space-l) + var(--n-space-xs)));--_n-button-padding-block:calc(var(--n-space-xs) - 1px);--_n-button-icon-size:var(--n-size-icon-xs)}:host([size=l]){--_n-button-border-radius:var(--n-button-border-radius,var(--n-border-radius));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-l)/1.3));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-l));--_n-button-font-weight:var(--n-button-font-weight,var(--n-font-weight-active));--_n-button-min-block-size:var(--n-button-min-block-size,calc(var(--n-space-xxl) - var(--n-space-l)));--_n-button-icon-size:var(--n-size-icon-m)}:host([square]){--_n-button-inline-size:var(--_n-button-min-block-size);--_n-button-padding-block:0;--_n-button-padding-inline:0}:host([square]) .n-content{align-items:center;display:flex;justify-content:center}::slotted(*){color:inherit;pointer-events:none}::slotted(svg){color:var(--n-color-icon)}::slotted(button[slot=proxy]){display:none}:host(:not([variant=primary],[variant=danger])) ::slotted(nord-icon){color:var(--n-color-icon)}:host(:not([variant=primary],[variant=danger])) .n-toggle-icon{color:var(--_n-button-toggle-icon-color)}:host(:not([square])) slot:not([name])::slotted(nord-icon){transform:translateY(1px)}::slotted(nord-icon:not([size])),nord-icon{--_n-icon-size:var(--_n-button-icon-size)}`;let f=class extends(h(v(e))){constructor(){super(...arguments),this.defaultSlot=new c(this),this.buttonRef=s(),this.events=new b(this),this.lightDom=new d(this,{render:()=>this.renderLightDom()}),this.variant="default",this.type="submit",this.size="m",this.download=!1,this.target="_self",this.expand=!1,this.square=!1,this.loading=!1,this.hideDropdownIcon=!1,this.handleOuterClick=t=>{t.composedPath().some((t=>t===this.focusableRef.value||t===this.buttonRef.value))||t.stopPropagation()}}connectedCallback(){super.connectedCallback(),this.events.listen(this,"click",this.handleOuterClick,!0)}render(){var t;const o="toggle"===(null===(t=this.assignedSlot)||void 0===t?void 0:t.name),e=this.defaultSlot.assigned.some((t=>"nord-icon"===t.localName)),a=o&&!this.hideDropdownIcon&&!e&&!this.href,i=n`<slot name="start"></slot><div class="n-content"><slot></slot></div><nord-spinner class="n-button-spinner" color="currentColor" ?hidden="${!this.loading||Boolean(this.href)}"></nord-spinner><slot name="end">${a?n`<nord-icon name="interface-dropdown-small" class="n-toggle-icon"></nord-icon>`:r}</slot>`;return this.href?this.renderLink(i):this.renderButton(i)}renderLink(t){return n`<a ${l(this.focusableRef)} class="n-button" target="${this.target}" ?download="${this.download}" href="${u(this.disabled,r,this.href)}" tabindex="${u(this.disabled,"-1")}" aria-disabled="${u(this.disabled,"true")}" role="${u(this.disabled,"link")}">${t}</a>`}renderButton(t){return n`<slot name="proxy" @slotchange="${this.handleProxyChange}"></slot><button ${l(this.focusableRef)} class="n-button" ?disabled="${this.disabled}" name="${u(this.name)}" value="${u(this.value)}" @click="${this.handleClick}" aria-disabled="${u(this.loading,"true")}" aria-expanded="${u(this.accessibleExpanded)}" aria-haspopup="${u(this.accessibleHasPopup)}">${t}</button>`}renderLightDom(){return this.href||!this.form?r:n`<button ${l(this.buttonRef)} slot="proxy" name="${u(this.name)}" value="${u(this.value)}" ?disabled="${this.disabled}" form="${u(this._formId)}" type="${this.type}"></button>`}handleClick(t){this.buttonRef.value&&(t.stopPropagation(),this.buttonRef.value.click())}handleProxyChange(t){const o=t.target,n=this.buttonRef.value;n&&n.assignedSlot!==o&&this.appendChild(n)}};f.shadowRootOptions={...e.shadowRootOptions,delegatesFocus:!0},f.styles=[p,g],t([a({reflect:!0})],f.prototype,"variant",void 0),t([a({reflect:!0})],f.prototype,"type",void 0),t([a({reflect:!0})],f.prototype,"size",void 0),t([a({attribute:"aria-expanded"})],f.prototype,"accessibleExpanded",void 0),t([a({attribute:"aria-haspopup"})],f.prototype,"accessibleHasPopup",void 0),t([a({reflect:!0})],f.prototype,"href",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"download",void 0),t([a()],f.prototype,"target",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"expand",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"square",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"loading",void 0),t([a({reflect:!0,type:Boolean,attribute:"hide-dropdown-icon"})],f.prototype,"hideDropdownIcon",void 0),f=t([i("nord-button")],f);var _=f;export{_ as default};
|
|
7
|
+
)}:host([disabled]){--_n-button-border-color:var(--n-button-border-color,var(--_n-button-background-color));--_n-button-background-color:var(--n-button-background-color,var(--n-color-border));--_n-button-box-shadow:var(--n-button-box-shadow,none);--_n-button-color:var(--n-button-color,var(--n-color-text-weak));--_n-button-opacity:0.5;pointer-events:none}:host([disabled]) .n-button:after{display:none}.n-button-spinner{inset-block-start:50%;inset-inline-start:50%;position:absolute;transform:translateX(-50%) translateY(-50%)}:host([loading]:not([href])){pointer-events:none}:host([loading]:not([href])) .n-content,:host([loading]:not([href])) ::slotted([slot=end]),:host([loading]:not([href])) ::slotted([slot=start]){opacity:0}:host([size=s]){--_n-button-gap:var(--n-button-gap,var(--n-space-xs));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-s) + 1px));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-s));--_n-button-min-block-size:var(--n-button-min-block-size,calc(var(--n-space-l) + var(--n-space-xs)));--_n-button-padding-block:calc(var(--n-space-xs) - 1px);--_n-button-icon-size:var(--n-size-icon-xs)}:host([size=l]){--_n-button-border-radius:var(--n-button-border-radius,var(--n-border-radius));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-l)/1.3));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-l));--_n-button-font-weight:var(--n-button-font-weight,var(--n-font-weight-active));--_n-button-min-block-size:var(--n-button-min-block-size,calc(var(--n-space-xxl) - var(--n-space-l)));--_n-button-icon-size:var(--n-size-icon-m)}:host([square]){--_n-button-inline-size:var(--_n-button-min-block-size);--_n-button-padding-block:0;--_n-button-padding-inline:0}:host([square]) .n-content{align-items:center;display:flex;justify-content:center}::slotted(*){color:inherit;pointer-events:none}::slotted(svg){color:var(--n-color-icon)}::slotted(button[slot=proxy]){display:none}:host(:not([variant=primary],[variant=danger])) ::slotted(nord-icon){color:var(--n-color-icon)}:host(:not([variant=primary],[variant=danger])) .n-toggle-icon{color:var(--_n-button-toggle-icon-color)}:host(:not([square])) slot:not([name])::slotted(nord-icon){transform:translateY(1px)}::slotted(nord-icon:not([size])),nord-icon{--_n-icon-size:var(--_n-button-icon-size)}`;let f=class extends(h(v(e))){constructor(){super(...arguments),this.defaultSlot=new c(this),this.buttonRef=s(),this.events=new b(this),this.lightDom=new d(this,{render:()=>this.renderLightDom()}),this.variant="default",this.type="submit",this.size="m",this.download=!1,this.target="_self",this.expand=!1,this.square=!1,this.loading=!1,this.hideDropdownIcon=!1,this.handleOuterClick=t=>{t.composedPath().some((t=>t===this.focusableRef.value||t===this.buttonRef.value))||t.stopPropagation()}}connectedCallback(){super.connectedCallback(),this.events.listen(this,"click",this.handleOuterClick,!0)}render(){var t;const o="toggle"===(null===(t=this.assignedSlot)||void 0===t?void 0:t.name),e=this.defaultSlot.assigned.some((t=>"nord-icon"===t.localName)),a=o&&!this.hideDropdownIcon&&!e&&!this.href,i=n`<slot name="start"></slot><div class="n-content"><slot></slot></div><nord-spinner class="n-button-spinner" color="currentColor" ?hidden="${!this.loading||Boolean(this.href)}"></nord-spinner><slot name="end">${a?n`<nord-icon name="interface-dropdown-small" class="n-toggle-icon"></nord-icon>`:r}</slot>`;return this.href?this.renderLink(i):this.renderButton(i)}get isHostNonFocusable(){return"-1"===this.getAttribute("tabindex")}renderLink(t){return n`<a ${l(this.focusableRef)} class="n-button" target="${this.target}" ?download="${this.download}" href="${u(this.disabled,r,this.href)}" tabindex="${u(this.disabled||this.isHostNonFocusable,"-1")}" aria-disabled="${u(this.disabled,"true")}" role="${u(this.disabled,"link")}">${t}</a>`}renderButton(t){return n`<slot name="proxy" @slotchange="${this.handleProxyChange}"></slot><button ${l(this.focusableRef)} class="n-button" ?disabled="${this.disabled}" name="${u(this.name)}" value="${u(this.value)}" tabindex="${u(this.isHostNonFocusable,"-1")}" @click="${this.handleClick}" aria-disabled="${u(this.loading,"true")}" aria-expanded="${u(this.accessibleExpanded)}" aria-haspopup="${u(this.accessibleHasPopup)}">${t}</button>`}renderLightDom(){return this.href||!this.form?r:n`<button ${l(this.buttonRef)} slot="proxy" name="${u(this.name)}" value="${u(this.value)}" ?disabled="${this.disabled}" form="${u(this._formId)}" type="${this.type}"></button>`}handleClick(t){this.buttonRef.value&&(t.stopPropagation(),this.buttonRef.value.click())}handleProxyChange(t){const o=t.target,n=this.buttonRef.value;n&&n.assignedSlot!==o&&this.appendChild(n)}};f.shadowRootOptions={...e.shadowRootOptions,delegatesFocus:!0},f.styles=[p,g],t([a({reflect:!0})],f.prototype,"variant",void 0),t([a({reflect:!0})],f.prototype,"type",void 0),t([a({reflect:!0})],f.prototype,"size",void 0),t([a({attribute:"aria-expanded"})],f.prototype,"accessibleExpanded",void 0),t([a({attribute:"aria-haspopup"})],f.prototype,"accessibleHasPopup",void 0),t([a({reflect:!0})],f.prototype,"href",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"download",void 0),t([a()],f.prototype,"target",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"expand",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"square",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"loading",void 0),t([a({reflect:!0,type:Boolean,attribute:"hide-dropdown-icon"})],f.prototype,"hideDropdownIcon",void 0),f=t([i("nord-button")],f);var _=f;export{_ as default};
|
|
8
8
|
//# sourceMappingURL=Button.js.map
|
package/lib/Button.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","sources":["../src/button/Button.ts"],"sourcesContent":["/* eslint-disable lit-a11y/accessible-name */\nimport type { TemplateResult } from 'lit'\nimport { html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { createRef, ref } from 'lit/directives/ref.js'\nimport { EventController } from '../common/controllers/EventController.js'\nimport { LightDomController } from '../common/controllers/LightDomController.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\n\nimport { cond } from '../common/directives/cond.js'\n\nimport { FocusableMixin } from '../common/mixins/FocusableMixin.js'\nimport { InputMixin } from '../common/mixins/InputMixin.js'\nimport componentStyle from '../common/styles/Component.css'\nimport style from './Button.css'\nimport '../spinner/Spinner.js'\n\n/**\n * Buttons are used for interface actions. Primary style should be\n * used only once per section for main call-to-action, while other\n * styles can appear more frequently.\n *\n * @status ready\n * @category action\n * @slot - The button content\n * @slot start - Used to place content at the start of button text. Typically used for icons.\n * @slot end - Used to place content at the end of button text. Typically used for icons.\n *\n * @cssprop [--n-button-border-radius=var(--n-border-radius-s)] - Controls the rounded corners of the button, using [border radius tokens](/tokens/#border-radius).\n * @cssprop [--n-button-gap=var(--n-space-s)] - Controls the spacing between items within the button, using our [spacing tokens](/tokens/#space).\n * @cssprop [--n-button-gradient=linear-gradient(to bottom, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.013) 100%))] - Controls the overlayed gradient background on the button.\n * @cssprop [--n-button-background-color=var(--n-color-button)] - Controls the background color of the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-border-color=var(--n-color-border-strong)] - Controls the border color of the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-text-align=center] - Controls the text alignment for the text in the button.\n * @cssprop [--n-button-box-shadow=var(--n-box-shadow)] - Controls the surrounding shadow, using our [box shadow tokens](/tokens/#box-shadow).\n * @cssprop [--n-button-color=var(--n-color-text)] - Controls the color of the text within the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-padding-inline=calc(var(--n-space-m) / 1.2)] - Controls the inline, or left and right, padding of the button.\n * @cssprop [--n-button-font-size=var(--n-font-size-m)] - Controls the size of the text within the button, using our [font tokens](/tokens/#font).\n * @cssprop [--n-button-font-weight=var(--n-font-weight)] - Controls the weight of the text within the button, using our [font tokens](/tokens/#font).\n * @cssprop [--n-button-min-block-size=var(--n-space-xl)] - Controls the minimum block size, or height, of the button using our [spacing tokens](/tokens/#space).\n * @cssprop [--n-button-toggle-icon-color=var(--n-color-icon)] - Controls the color of toggle icon that gets rendered when the button is used as a [Dropdown](/components/dropdown/) toggle.\n * @cssprop [--n-button-user-select=none] - Controls the text selection behavior of the button text.\n */\n@customElement('nord-button')\nexport default class Button extends InputMixin(FocusableMixin(LitElement)) {\n static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n\n static styles = [componentStyle, style]\n\n private defaultSlot = new SlotController(this)\n private buttonRef = createRef<HTMLButtonElement>()\n private events = new EventController(this)\n private lightDom = new LightDomController(this, {\n render: () => this.renderLightDom(),\n })\n\n /**\n * The style variant of the button.\n */\n @property({ reflect: true }) variant: 'default' | 'primary' | 'dashed' | 'plain' | 'danger' = 'default'\n\n /**\n * The type of the button.\n */\n @property({ reflect: true }) type: 'button' | 'submit' | 'reset' = 'submit'\n\n /**\n * The size of the button.\n * This affects font-size and padding.\n */\n @property({ reflect: true }) size: 's' | 'm' | 'l' = 'm'\n\n /**\n * This does not need to be documented,\n * since it is only for forwarding the aria-expanded attribute\n * to the internal button element.\n * @private\n */\n @property({ attribute: 'aria-expanded' }) accessibleExpanded?: 'true' | 'false'\n\n /**\n * This does not need to be documented,\n * since it is only for forwarding the aria-haspopup attribute\n * to the internal button element.\n * @private\n */\n @property({ attribute: 'aria-haspopup' }) accessibleHasPopup?:\n | 'false'\n | 'true'\n | 'menu'\n | 'listbox'\n | 'tree'\n | 'grid'\n | 'dialog'\n\n /**\n * When provided, renders the button as a link,\n * with its href attribute set to the given value.\n */\n @property({ reflect: true }) href?: string\n\n /**\n * When provided together with a href property, the button will\n * trigger a file download instead of a page visit.\n */\n @property({ reflect: true, type: Boolean }) download = false\n\n /**\n * When provided together with a href property, determines where\n * to open the linked URL. The keywords have special meanings for\n * where to load the URL: “_self” means the current browsing context,\n * “_blank” usually a new tab but users can configure browsers this to\n * open a new window instead, “_parent” means the parent browsing\n * context of the current one, but if no parent exists, behaves as\n * _self, and finally “top” means the topmost browsing context.\n */\n @property() target: '_self' | '_blank' | '_parent' | '_top' = '_self'\n\n /**\n * Controls whether the button expands to fill the width of its container.\n */\n @property({ reflect: true, type: Boolean }) expand = false\n\n /**\n * When provided, the button is rendered as a square button. Use this for icon only buttons.\n */\n @property({ reflect: true, type: Boolean }) square = false\n\n /**\n * Controls whether the button is in loading state. Please note that the spinner\n * is hidden from assistive technologies, so you need to make sure to announce\n * the loading state to e.g. screen reader users. We also recommend disabling\n * all user interactions on the button itself while in loading state.\n */\n @property({ reflect: true, type: Boolean }) loading = false\n\n /**\n * Controls whether the `interface-dropdown-small` icon from Nordicons is hidden\n * in the `end` slot when used as a dropdown toggle.\n */\n @property({ reflect: true, type: Boolean, attribute: 'hide-dropdown-icon' }) hideDropdownIcon = false\n\n connectedCallback() {\n super.connectedCallback()\n this.events.listen(this, 'click', this.handleOuterClick, true)\n }\n\n render() {\n const isDropdownToggle = this.assignedSlot?.name === 'toggle'\n const isIconButton = this.defaultSlot.assigned.some(node => node.localName === 'nord-icon')\n const shouldShowDropdownIcon = isDropdownToggle && !this.hideDropdownIcon && !isIconButton && !this.href\n\n const innards = html`\n <slot name=\"start\"></slot>\n <div class=\"n-content\">\n <slot></slot>\n </div>\n <nord-spinner\n class=\"n-button-spinner\"\n color=\"currentColor\"\n ?hidden=${!this.loading || Boolean(this.href)}\n ></nord-spinner>\n <slot name=\"end\">\n ${shouldShowDropdownIcon\n ? html`<nord-icon name=\"interface-dropdown-small\" class=\"n-toggle-icon\"></nord-icon>`\n : nothing}\n </slot>\n `\n\n return this.href ? this.renderLink(innards) : this.renderButton(innards)\n }\n\n /**\n * We jump through some hoops here to ensure the link is treated correctly when \"disabled\".\n * Links cannot be disabled natively, so we need to rely on some aria magic to get the correct semantics.\n * Along with the advice in the article below, we also set tabindex to \"-1\", so it is taken out of tab order.\n *\n * @see https://www.scottohara.me/blog/2021/05/28/disabled-links.html\n */\n private renderLink(innards: TemplateResult) {\n return html`\n <a\n ${ref(this.focusableRef)}\n class=\"n-button\"\n target=${this.target}\n ?download=${this.download}\n href=${cond(this.disabled, nothing, this.href)}\n tabindex=${cond(this.disabled, '-1')}\n aria-disabled=${cond(this.disabled, 'true')}\n role=${cond(this.disabled, 'link')}\n >${innards}\n </a>\n `\n }\n\n private renderButton(innards: TemplateResult) {\n return html`\n <slot name=\"proxy\" @slotchange=${this.handleProxyChange}></slot>\n <button\n ${ref(this.focusableRef)}\n class=\"n-button\"\n ?disabled=${this.disabled}\n name=${cond(this.name)}\n value=${cond(this.value)}\n @click=${this.handleClick}\n aria-disabled=${cond(this.loading, 'true')}\n aria-expanded=${cond(this.accessibleExpanded)}\n aria-haspopup=${cond(this.accessibleHasPopup)}\n >\n ${innards}\n </button>\n `\n }\n\n private renderLightDom() {\n if (this.href || !this.form) {\n return nothing\n }\n\n return html`\n <button\n ${ref(this.buttonRef)}\n slot=\"proxy\"\n name=${cond(this.name)}\n value=${cond(this.value)}\n ?disabled=${this.disabled}\n form=${cond(this._formId)}\n type=${this.type}\n ></button>\n `\n }\n\n private handleOuterClick = (e: MouseEvent) => {\n // we want to avoid emitting click events when a click\n // happens in blank space in the host, but not on the button\n // so we stop propagation of any events if click didn't happen on the internal or proxy button\n const isInternalButton = e\n .composedPath()\n .some(node => node === this.focusableRef.value || node === this.buttonRef.value)\n\n if (!isInternalButton) {\n e.stopPropagation()\n }\n }\n\n private handleClick(e: Event) {\n if (this.buttonRef.value) {\n // prevents two events: one from internal button, one from proxy button\n e.stopPropagation()\n this.buttonRef.value.click()\n }\n }\n\n /**\n * React/Vue etc may remove our proxy button when updating button text, since they are unaware of its existence.\n * So we listen for a slotchange event, and if the element is no longer connected to the DOM we add it back in.\n */\n private handleProxyChange(e: Event) {\n const slot = e.target as HTMLSlotElement\n const proxyButton = this.buttonRef.value\n\n if (proxyButton && proxyButton.assignedSlot !== slot) {\n this.appendChild(proxyButton)\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-button': Button\n }\n}\n"],"names":["Button","InputMixin","FocusableMixin","LitElement","constructor","this","defaultSlot","SlotController","buttonRef","createRef","events","EventController","lightDom","LightDomController","render","renderLightDom","variant","type","size","download","target","expand","square","loading","hideDropdownIcon","handleOuterClick","e","composedPath","some","node","focusableRef","value","stopPropagation","connectedCallback","super","listen","isDropdownToggle","_a","assignedSlot","name","isIconButton","assigned","localName","shouldShowDropdownIcon","href","innards","html","Boolean","nothing","renderLink","renderButton","ref","cond","disabled","handleProxyChange","handleClick","accessibleExpanded","accessibleHasPopup","form","_formId","click","slot","proxyButton","appendChild","shadowRootOptions","delegatesFocus","styles","componentStyle","style","__decorate","property","reflect","prototype","attribute","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Button.js","sources":["../src/button/Button.ts"],"sourcesContent":["/* eslint-disable lit-a11y/accessible-name */\nimport type { TemplateResult } from 'lit'\nimport { html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { createRef, ref } from 'lit/directives/ref.js'\nimport { EventController } from '../common/controllers/EventController.js'\nimport { LightDomController } from '../common/controllers/LightDomController.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\n\nimport { cond } from '../common/directives/cond.js'\n\nimport { FocusableMixin } from '../common/mixins/FocusableMixin.js'\nimport { InputMixin } from '../common/mixins/InputMixin.js'\nimport componentStyle from '../common/styles/Component.css'\nimport style from './Button.css'\nimport '../spinner/Spinner.js'\n\n/**\n * Buttons are used for interface actions. Primary style should be\n * used only once per section for main call-to-action, while other\n * styles can appear more frequently.\n *\n * @status ready\n * @category action\n * @slot - The button content\n * @slot start - Used to place content at the start of button text. Typically used for icons.\n * @slot end - Used to place content at the end of button text. Typically used for icons.\n *\n * @cssprop [--n-button-border-radius=var(--n-border-radius-s)] - Controls the rounded corners of the button, using [border radius tokens](/tokens/#border-radius).\n * @cssprop [--n-button-gap=var(--n-space-s)] - Controls the spacing between items within the button, using our [spacing tokens](/tokens/#space).\n * @cssprop [--n-button-gradient=linear-gradient(to bottom, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.013) 100%))] - Controls the overlayed gradient background on the button.\n * @cssprop [--n-button-background-color=var(--n-color-button)] - Controls the background color of the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-border-color=var(--n-color-border-strong)] - Controls the border color of the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-text-align=center] - Controls the text alignment for the text in the button.\n * @cssprop [--n-button-box-shadow=var(--n-box-shadow)] - Controls the surrounding shadow, using our [box shadow tokens](/tokens/#box-shadow).\n * @cssprop [--n-button-color=var(--n-color-text)] - Controls the color of the text within the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-padding-inline=calc(var(--n-space-m) / 1.2)] - Controls the inline, or left and right, padding of the button.\n * @cssprop [--n-button-font-size=var(--n-font-size-m)] - Controls the size of the text within the button, using our [font tokens](/tokens/#font).\n * @cssprop [--n-button-font-weight=var(--n-font-weight)] - Controls the weight of the text within the button, using our [font tokens](/tokens/#font).\n * @cssprop [--n-button-min-block-size=var(--n-space-xl)] - Controls the minimum block size, or height, of the button using our [spacing tokens](/tokens/#space).\n * @cssprop [--n-button-toggle-icon-color=var(--n-color-icon)] - Controls the color of toggle icon that gets rendered when the button is used as a [Dropdown](/components/dropdown/) toggle.\n * @cssprop [--n-button-user-select=none] - Controls the text selection behavior of the button text.\n */\n@customElement('nord-button')\nexport default class Button extends InputMixin(FocusableMixin(LitElement)) {\n static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n\n static styles = [componentStyle, style]\n\n private defaultSlot = new SlotController(this)\n private buttonRef = createRef<HTMLButtonElement>()\n private events = new EventController(this)\n private lightDom = new LightDomController(this, {\n render: () => this.renderLightDom(),\n })\n\n /**\n * The style variant of the button.\n */\n @property({ reflect: true }) variant: 'default' | 'primary' | 'dashed' | 'plain' | 'danger' = 'default'\n\n /**\n * The type of the button.\n */\n @property({ reflect: true }) type: 'button' | 'submit' | 'reset' = 'submit'\n\n /**\n * The size of the button.\n * This affects font-size and padding.\n */\n @property({ reflect: true }) size: 's' | 'm' | 'l' = 'm'\n\n /**\n * This does not need to be documented,\n * since it is only for forwarding the aria-expanded attribute\n * to the internal button element.\n * @private\n */\n @property({ attribute: 'aria-expanded' }) accessibleExpanded?: 'true' | 'false'\n\n /**\n * This does not need to be documented,\n * since it is only for forwarding the aria-haspopup attribute\n * to the internal button element.\n * @private\n */\n @property({ attribute: 'aria-haspopup' }) accessibleHasPopup?:\n | 'false'\n | 'true'\n | 'menu'\n | 'listbox'\n | 'tree'\n | 'grid'\n | 'dialog'\n\n /**\n * When provided, renders the button as a link,\n * with its href attribute set to the given value.\n */\n @property({ reflect: true }) href?: string\n\n /**\n * When provided together with a href property, the button will\n * trigger a file download instead of a page visit.\n */\n @property({ reflect: true, type: Boolean }) download = false\n\n /**\n * When provided together with a href property, determines where\n * to open the linked URL. The keywords have special meanings for\n * where to load the URL: “_self” means the current browsing context,\n * “_blank” usually a new tab but users can configure browsers this to\n * open a new window instead, “_parent” means the parent browsing\n * context of the current one, but if no parent exists, behaves as\n * _self, and finally “top” means the topmost browsing context.\n */\n @property() target: '_self' | '_blank' | '_parent' | '_top' = '_self'\n\n /**\n * Controls whether the button expands to fill the width of its container.\n */\n @property({ reflect: true, type: Boolean }) expand = false\n\n /**\n * When provided, the button is rendered as a square button. Use this for icon only buttons.\n */\n @property({ reflect: true, type: Boolean }) square = false\n\n /**\n * Controls whether the button is in loading state. Please note that the spinner\n * is hidden from assistive technologies, so you need to make sure to announce\n * the loading state to e.g. screen reader users. We also recommend disabling\n * all user interactions on the button itself while in loading state.\n */\n @property({ reflect: true, type: Boolean }) loading = false\n\n /**\n * Controls whether the `interface-dropdown-small` icon from Nordicons is hidden\n * in the `end` slot when used as a dropdown toggle.\n */\n @property({ reflect: true, type: Boolean, attribute: 'hide-dropdown-icon' }) hideDropdownIcon = false\n\n connectedCallback() {\n super.connectedCallback()\n this.events.listen(this, 'click', this.handleOuterClick, true)\n }\n\n render() {\n const isDropdownToggle = this.assignedSlot?.name === 'toggle'\n const isIconButton = this.defaultSlot.assigned.some(node => node.localName === 'nord-icon')\n const shouldShowDropdownIcon = isDropdownToggle && !this.hideDropdownIcon && !isIconButton && !this.href\n\n const innards = html`\n <slot name=\"start\"></slot>\n <div class=\"n-content\">\n <slot></slot>\n </div>\n <nord-spinner\n class=\"n-button-spinner\"\n color=\"currentColor\"\n ?hidden=${!this.loading || Boolean(this.href)}\n ></nord-spinner>\n <slot name=\"end\">\n ${shouldShowDropdownIcon\n ? html`<nord-icon name=\"interface-dropdown-small\" class=\"n-toggle-icon\"></nord-icon>`\n : nothing}\n </slot>\n `\n\n return this.href ? this.renderLink(innards) : this.renderButton(innards)\n }\n\n /**\n * We jump through some hoops here to ensure the link is treated correctly when \"disabled\".\n * Links cannot be disabled natively, so we need to rely on some aria magic to get the correct semantics.\n * Along with the advice in the article below, we also set tabindex to \"-1\", so it is taken out of tab order.\n *\n * @see https://www.scottohara.me/blog/2021/05/28/disabled-links.html\n */\n // When the host is explicitly removed from the tab order (`tabindex=\"-1\"`,\n // e.g. a decorative/`aria-hidden` button), forward that to the internal\n // control. `delegatesFocus` otherwise keeps the shadow button focusable, which\n // fails the `aria-hidden-focus` a11y rule (focusable descendant of a hidden\n // element). Only `-1` is forwarded — positive/zero values are left to\n // `delegatesFocus`.\n private get isHostNonFocusable(): boolean {\n return this.getAttribute('tabindex') === '-1'\n }\n\n private renderLink(innards: TemplateResult) {\n return html`\n <a\n ${ref(this.focusableRef)}\n class=\"n-button\"\n target=${this.target}\n ?download=${this.download}\n href=${cond(this.disabled, nothing, this.href)}\n tabindex=${cond(this.disabled || this.isHostNonFocusable, '-1')}\n aria-disabled=${cond(this.disabled, 'true')}\n role=${cond(this.disabled, 'link')}\n >${innards}\n </a>\n `\n }\n\n private renderButton(innards: TemplateResult) {\n return html`\n <slot name=\"proxy\" @slotchange=${this.handleProxyChange}></slot>\n <button\n ${ref(this.focusableRef)}\n class=\"n-button\"\n ?disabled=${this.disabled}\n name=${cond(this.name)}\n value=${cond(this.value)}\n tabindex=${cond(this.isHostNonFocusable, '-1')}\n @click=${this.handleClick}\n aria-disabled=${cond(this.loading, 'true')}\n aria-expanded=${cond(this.accessibleExpanded)}\n aria-haspopup=${cond(this.accessibleHasPopup)}\n >\n ${innards}\n </button>\n `\n }\n\n private renderLightDom() {\n if (this.href || !this.form) {\n return nothing\n }\n\n return html`\n <button\n ${ref(this.buttonRef)}\n slot=\"proxy\"\n name=${cond(this.name)}\n value=${cond(this.value)}\n ?disabled=${this.disabled}\n form=${cond(this._formId)}\n type=${this.type}\n ></button>\n `\n }\n\n private handleOuterClick = (e: MouseEvent) => {\n // we want to avoid emitting click events when a click\n // happens in blank space in the host, but not on the button\n // so we stop propagation of any events if click didn't happen on the internal or proxy button\n const isInternalButton = e\n .composedPath()\n .some(node => node === this.focusableRef.value || node === this.buttonRef.value)\n\n if (!isInternalButton) {\n e.stopPropagation()\n }\n }\n\n private handleClick(e: Event) {\n if (this.buttonRef.value) {\n // prevents two events: one from internal button, one from proxy button\n e.stopPropagation()\n this.buttonRef.value.click()\n }\n }\n\n /**\n * React/Vue etc may remove our proxy button when updating button text, since they are unaware of its existence.\n * So we listen for a slotchange event, and if the element is no longer connected to the DOM we add it back in.\n */\n private handleProxyChange(e: Event) {\n const slot = e.target as HTMLSlotElement\n const proxyButton = this.buttonRef.value\n\n if (proxyButton && proxyButton.assignedSlot !== slot) {\n this.appendChild(proxyButton)\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-button': Button\n }\n}\n"],"names":["Button","InputMixin","FocusableMixin","LitElement","constructor","this","defaultSlot","SlotController","buttonRef","createRef","events","EventController","lightDom","LightDomController","render","renderLightDom","variant","type","size","download","target","expand","square","loading","hideDropdownIcon","handleOuterClick","e","composedPath","some","node","focusableRef","value","stopPropagation","connectedCallback","super","listen","isDropdownToggle","_a","assignedSlot","name","isIconButton","assigned","localName","shouldShowDropdownIcon","href","innards","html","Boolean","nothing","renderLink","renderButton","isHostNonFocusable","getAttribute","ref","cond","disabled","handleProxyChange","handleClick","accessibleExpanded","accessibleHasPopup","form","_formId","click","slot","proxyButton","appendChild","shadowRootOptions","delegatesFocus","styles","componentStyle","style","__decorate","property","reflect","prototype","attribute","customElement"],"mappings":"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"}
|
package/lib/Calendar.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./tslib.es6-CmLYFWVC.js";export{C as default}from"./Calendar-D47b20IT.js";import"lit";import"lit/decorators.js";import"lit/directives/class-map.js";import"lit/directives/repeat.js";import"./tinykeys-DwvDXsEb.js";import"./collection-Dvg2XbxV.js";import"./DirectionController-ChvNGESZ.js";import"./dates-C-ZvzXaC.js";import"./observe-D0n0zOfU.js";import"./cond-CI1KbneT.js";import"./number-Dg2vCfGd.js";import"./Component-BJ5O5n8k.js";import"./Icon.js";import"./LocalizeController.js";import"./DateRangeSelectEvent.js";import"./DateSelectEvent.js";import"./Button.js";import"./VisuallyHidden.js";import"./EventController-BBOmvfLa.js";import"lit/directives/if-defined.js";import"lit/directives/unsafe-html.js";import"./IconManager.js";import"./translation.js";import"./en-us.js";import"./localization.js";import"./localization2.js";import"./localization3.js";import"./localization4.js";import"./localization5.js";import"./localization6.js";import"./localization7.js";import"./localization8.js";import"./localization9.js";import"./localization10.js";import"./localization11.js";import"./localization12.js";import"./localization13.js";import"./localization14.js";import"./localization15.js";import"./localization16.js";import"./localization17.js";import"./localization18.js";import"./localization19.js";import"./localization20.js";import"./localization21.js";import"./localization22.js";import"./localization23.js";import"./events-Bv6wNHwJ.js";import"lit/directives/ref.js";import"./LightDomController-DIwtVelV.js";import"./SlotController-Z6eG7LSZ.js";import"./FocusableMixin-BlQLNPdJ.js";import"./InputMixin-LetXsCyv.js";import"./Spinner.js";
|
|
1
|
+
import"./tslib.es6-CmLYFWVC.js";export{C as default}from"./Calendar-D47b20IT.js";import"lit";import"lit/decorators.js";import"lit/directives/class-map.js";import"lit/directives/repeat.js";import"./tinykeys-DwvDXsEb.js";import"./collection-Dvg2XbxV.js";import"./DirectionController-ChvNGESZ.js";import"./dates-C-ZvzXaC.js";import"./observe-D0n0zOfU.js";import"./cond-CI1KbneT.js";import"./number-Dg2vCfGd.js";import"./Component-BJ5O5n8k.js";import"./Icon.js";import"./LocalizeController.js";import"./DateRangeSelectEvent.js";import"./DateSelectEvent.js";import"./Button.js";import"./VisuallyHidden.js";import"./EventController-BBOmvfLa.js";import"lit/directives/if-defined.js";import"lit/directives/unsafe-html.js";import"./IconManager.js";import"./translation.js";import"./en-us.js";import"./localization.js";import"./localization2.js";import"./localization3.js";import"./localization4.js";import"./localization5.js";import"./localization6.js";import"./localization7.js";import"./localization8.js";import"./localization9.js";import"./localization10.js";import"./localization11.js";import"./localization12.js";import"./localization13.js";import"./localization14.js";import"./localization15.js";import"./localization16.js";import"./localization17.js";import"./localization18.js";import"./localization19.js";import"./localization20.js";import"./localization21.js";import"./localization22.js";import"./localization23.js";import"./localization24.js";import"./events-Bv6wNHwJ.js";import"lit/directives/ref.js";import"./LightDomController-DIwtVelV.js";import"./SlotController-Z6eG7LSZ.js";import"./FocusableMixin-BlQLNPdJ.js";import"./InputMixin-LetXsCyv.js";import"./Spinner.js";
|
|
2
2
|
//# sourceMappingURL=Calendar.js.map
|
package/lib/Combobox.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{i}from"./context-provider-D8lrPLVi.js";import{a as e}from"./interface-add-small-B-hv2v9J.js";import{nothing as o,html as s}from"lit";import{property as r,state as l,customElement as a}from"lit/decorators.js";import{classMap as n}from"lit/directives/class-map.js";import{repeat as p}from"lit/directives/repeat.js";import{i as c}from"./light-dom-styles-DXsbiKU9.js";import m from"./Icon.js";import{comboboxRegistryContext as h}from"./combobox-context.js";import{s as d}from"./Combobox-DBxXlbY7.js";import u from"./ComboboxBase.js";import{ComboboxClearEvent as j,ComboboxCreateEvent as v}from"./ComboboxEvents.js";import"./Kbd.js";import"./Tag.js";import"./ComboboxOption.js";import"./ComboboxGroup.js";import"./context-request-event-CCRlXoeR.js";import"lit/directives/if-defined.js";import"lit/directives/unsafe-html.js";import"./observe-D0n0zOfU.js";import"./cond-CI1KbneT.js";import"./Component-BJ5O5n8k.js";import"./IconManager.js";import"./create-context-CJ69ntlY.js";import"./ActiveDescendantController-BnMqM79O.js";import"./number-Dg2vCfGd.js";import"./interface-close-small-CnpAFMO3.js";import"./interface-dropdown-small-CYvgxudU.js";import"lit/directives/ref.js";import"lit/directives/style-map.js";import"./collection-Dvg2XbxV.js";import"./FormDataController-OUt5L5uC.js";import"./LightDismissController-wcL1ziJa.js";import"./EventController-BBOmvfLa.js";import"./ShortcutController-CV5b0BZl.js";import"./tinykeys-DwvDXsEb.js";import"./ResizeController-14PUwRQy.js";import"./StateAttributesController-BIh96sXZ.js";import"./events-Bv6wNHwJ.js";import"./AutocompleteMixin-D8eiOxvO.js";import"./DraftComponentMixin-4qug5Rky.js";import"./FieldControlMixin-BXNZJXu9.js";import"./context-consumer-BJVUulGf.js";import"./field-context.js";import"./SlotController-Z6eG7LSZ.js";import"./VisuallyHidden.js";import"./Popout-
|
|
1
|
+
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{i}from"./context-provider-D8lrPLVi.js";import{a as e}from"./interface-add-small-B-hv2v9J.js";import{nothing as o,html as s}from"lit";import{property as r,state as l,customElement as a}from"lit/decorators.js";import{classMap as n}from"lit/directives/class-map.js";import{repeat as p}from"lit/directives/repeat.js";import{i as c}from"./light-dom-styles-DXsbiKU9.js";import m from"./Icon.js";import{comboboxRegistryContext as h}from"./combobox-context.js";import{s as d}from"./Combobox-DBxXlbY7.js";import u from"./ComboboxBase.js";import{ComboboxClearEvent as j,ComboboxCreateEvent as v}from"./ComboboxEvents.js";import"./Kbd.js";import"./Tag.js";import"./ComboboxOption.js";import"./ComboboxGroup.js";import"./context-request-event-CCRlXoeR.js";import"lit/directives/if-defined.js";import"lit/directives/unsafe-html.js";import"./observe-D0n0zOfU.js";import"./cond-CI1KbneT.js";import"./Component-BJ5O5n8k.js";import"./IconManager.js";import"./create-context-CJ69ntlY.js";import"./ActiveDescendantController-BnMqM79O.js";import"./number-Dg2vCfGd.js";import"./interface-close-small-CnpAFMO3.js";import"./interface-dropdown-small-CYvgxudU.js";import"lit/directives/ref.js";import"lit/directives/style-map.js";import"./collection-Dvg2XbxV.js";import"./FormDataController-OUt5L5uC.js";import"./LightDismissController-wcL1ziJa.js";import"./EventController-BBOmvfLa.js";import"./ShortcutController-CV5b0BZl.js";import"./tinykeys-DwvDXsEb.js";import"./ResizeController-14PUwRQy.js";import"./StateAttributesController-BIh96sXZ.js";import"./events-Bv6wNHwJ.js";import"./AutocompleteMixin-D8eiOxvO.js";import"./DraftComponentMixin-4qug5Rky.js";import"./FieldControlMixin-BXNZJXu9.js";import"./context-consumer-BJVUulGf.js";import"./field-context.js";import"./SlotController-Z6eG7LSZ.js";import"./VisuallyHidden.js";import"./Popout-B6Hirj3y.js";import"./positioning-D-K8Mueq.js";import"./DirectionController-ChvNGESZ.js";import"./ScrollbarController-BFC67Y2x.js";import"./InputMixin-LetXsCyv.js";import"./SizeMixin-CU9cLbLC.js";import"./LocalizeController.js";import"./translation.js";import"./en-us.js";import"./localization.js";import"./localization2.js";import"./localization3.js";import"./localization4.js";import"./localization5.js";import"./localization6.js";import"./localization7.js";import"./localization8.js";import"./localization9.js";import"./localization10.js";import"./localization11.js";import"./localization12.js";import"./localization13.js";import"./localization14.js";import"./localization15.js";import"./localization16.js";import"./localization17.js";import"./localization18.js";import"./localization19.js";import"./localization20.js";import"./localization21.js";import"./localization22.js";import"./localization23.js";import"./localization24.js";import"./Spinner.js";import"./interface-checked-small-BQmkNgCI.js";import"./FocusableMixin-BlQLNPdJ.js";m.registerIcon(e);let b=class extends u{constructor(){super(...arguments),this.value="",this.multiple=!1,this.clearable=!0,this.creatable=!1,this.slottedOptions=[],this.createdOptions=[],this.optionElements=new Set,this.optionRegistry={add:t=>{this.optionElements.add(t),this.rebuildSlottedOptions()},remove:t=>{this.optionElements.delete(t),this.rebuildSlottedOptions()},notify:()=>this.rebuildSlottedOptions()},this.registryProvider=new i(this,{context:h,initialValue:this.optionRegistry})}connectedCallback(){super.connectedCallback(),c(this,d)}readOptionElement(t){var i,e,o,s,r;const l=(null===(i=t.closest("nord-combobox-group"))||void 0===i?void 0:i.getAttribute("label"))||void 0,a=null!==(s=null!==(e=t.getAttribute("label"))&&void 0!==e?e:null===(o=t.textContent)||void 0===o?void 0:o.trim())&&void 0!==s?s:"",n={value:null!==(r=t.getAttribute("value"))&&void 0!==r?r:"",label:a,disabled:t.hasAttribute("disabled")};return l&&(n.group=l),t.children.length>0&&(n.html=t.innerHTML),n}rebuildSlottedOptions(){const t=[...this.optionElements].sort(((t,i)=>t.compareDocumentPosition(i)&Node.DOCUMENT_POSITION_FOLLOWING?-1:1));this.slottedOptions=t.map((t=>this.readOptionElement(t)))}get resolvedOptions(){const t=this.options.length?this.options:this.slottedOptions,i=this.createdOptions,e=this.resolvedCache;if(e&&e.options===this.options&&e.slotted===this.slottedOptions&&e.created===i)return e.result;const o=i.length?[...t,...i]:t;return this.resolvedCache={options:this.options,slotted:this.slottedOptions,created:i,result:o},o}get isMultiple(){return this.multiple}get showsQueryInInput(){return this.isMultiple||this.queryDirty}get inputPlaceholder(){return this.isMultiple&&this.hasValue?void 0:this.placeholder}get createIndex(){return this.canCreate?this.filteredOptions.length:-1}get canCreate(){const t=this.search.trim();return!(!this.creatable||!t)&&!this.resolvedOptions.some((i=>i.label.toLocaleLowerCase()===t.toLocaleLowerCase()))}get showClear(){return this.clearable&&this.hasValue&&!this.disabled&&!this.readonly}get ariaMultiselectable(){return this.isMultiple?"true":"false"}clear(){this.value=this.isMultiple?[]:"",this.search="",this.queryDirty=!1,this.dispatchEvent(new j),this.commit(),this.focus()}selectOption(t){if(!this.isMultiple)return super.selectOption(t);if(t.disabled)return;this.labelCache.set(t.value,t.label);const i=this.values.includes(t.value)?this.values.filter((i=>i!==t.value)):[...this.values,t.value];this.value=i,this.search="",this.queryDirty=!1,this.commit(),this.focus()}onInput(t){!this.isMultiple&&""===t.value&&this.hasValue&&(this.value="",this.commit())}emitCreate(){const t=this.search.trim();if(!t)return;if(!this.dispatchEvent(new v(t)))return;const i={value:t,label:t};this.resolvedOptions.some((t=>t.value===i.value))||(this.createdOptions=[...this.createdOptions,i]),this.labelCache.set(i.value,i.label),this.selectOption(i)}onUnhandledKeydown(t){"Backspace"===t.key&&this.isMultiple&&!this.search&&this.values.length&&this.removeValue(this.values[this.values.length-1])}removeValue(t){this.disabled||(this.value=this.values.filter((i=>i!==t)),this.commit(),this.focus())}renderLeadingControl(){return this.isMultiple?p(this.values,(t=>t),(t=>s`<nord-tag class="n-chip" size="s" variant="removable" ?disabled="${this.disabled}" @remove="${()=>this.removeValue(t)}" @click="${t=>t.stopPropagation()}">${this.labelFor(t)}</nord-tag>`)):o}renderCreate(){const t=this.activeDescendant.activeIndex===this.createIndex;return s`<div id="${this.createId}" class="${n({"n-option":!0,"n-create":!0,"is-active":t})}" role="option" aria-selected="false" @click="${this.emitCreate}" @mousemove="${()=>this.activeDescendant.setActiveIndex(this.createIndex)}"><nord-icon class="n-create-icon" name="interface-add-small"></nord-icon><span class="n-option-label">${this.localize.term("createLabel",this.search.trim())}</span><nord-kbd class="n-create-kbd" aria-hidden="true">⏎</nord-kbd></div>`}};t([r({attribute:!1})],b.prototype,"value",void 0),t([r({reflect:!0,type:Boolean})],b.prototype,"multiple",void 0),t([r({reflect:!0,type:Boolean})],b.prototype,"clearable",void 0),t([r({reflect:!0,type:Boolean})],b.prototype,"creatable",void 0),t([l()],b.prototype,"slottedOptions",void 0),t([l()],b.prototype,"createdOptions",void 0),b=t([a("nord-combobox")],b);var f=b;export{j as ComboboxClearEvent,v as ComboboxCreateEvent,f as default};
|
|
2
2
|
//# sourceMappingURL=Combobox.js.map
|
package/lib/Combobox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Combobox.js","sources":["../src/combobox/Combobox.ts"],"sourcesContent":["import type { ComboboxRegistry } from './combobox-context.js'\nimport type { ComboboxOption } from './ComboboxBase.js'\nimport { ContextProvider } from '@lit/context'\nimport * as addIcon from '@nordhealth/icons/lib/assets/interface-add-small.js'\nimport { html, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { repeat } from 'lit/directives/repeat.js'\n\nimport { injectLightDomStyles } from '../common/light-dom-styles.js'\nimport Icon from '../icon/Icon.js'\nimport { comboboxRegistryContext } from './combobox-context.js'\nimport style from './Combobox.css'\nimport ComboboxBase from './ComboboxBase.js'\nimport { ComboboxClearEvent, ComboboxCreateEvent } from './ComboboxEvents.js'\nimport '../kbd/Kbd.js'\nimport '../tag/Tag.js'\nimport './ComboboxOption.js'\nimport './ComboboxGroup.js'\n\nIcon.registerIcon(addIcon)\n\nexport type { ComboboxOption } from './ComboboxBase.js'\nexport { ComboboxClearEvent, ComboboxCreateEvent } from './ComboboxEvents.js'\n\n/**\n * Combobox lets users choose one option (or several) from a list by typing to\n * filter, then picking with the keyboard or pointer. Use it when a [Select](/components/select/)\n * would be unwieldy because there are many options to scan.\n *\n * Combobox is a control only — it has no built-in label, hint or error. Compose\n * it inside a [Field](/components/field/) to give it a label, hint and error\n * message, the same way you would a native input.\n *\n * The committed `value` is an option's `value` (or an array of them with\n * `multiple`); the input is a filter/search field whose text is discarded on\n * commit. For a free-text input with suggestions where the typed text is the\n * value, use [Autocomplete](/components/autocomplete/) instead.\n *\n * @status draft\n * @category form\n * @lightDom true\n *\n * @slot start - Optional slot used to place an icon or prefix at the start of the control.\n * @slot end - Optional slot used to place an icon or suffix at the end of the control.\n *\n * @fires {NordEvent} change - Dispatched when the committed value changes via user interaction.\n * @fires {NordEvent} input - Dispatched as the user types into the search input.\n * @fires {NordEvent} open - Dispatched when the listbox is opened.\n * @fires {NordEvent} close - Dispatched when the listbox is closed.\n * @fires {ComboboxClearEvent} clear - Dispatched when the value is cleared via the clear button or `clear()`.\n * @fires {ComboboxCreateEvent} create - Dispatched when the user chooses the \"Create\" affordance in a `creatable` combobox.\n *\n * @localization clearLabel - Accessible label for the clear button.\n * @localization createLabel - Label for the \"Create\" affordance, given the current query.\n * @localization noResults - Message shown when no options match the query.\n *\n * @cssprop [--n-combobox-list-inline-size] - Controls the inline size, or width, of the dropdown list. Defaults to the width of the combobox control.\n * @cssprop [--n-combobox-block-size] - Controls the block size, or height, of the control. The medium default is `var(--n-space-xl)`; the s and l sizes scale from it.\n *\n * @attr {boolean} data-invalid - Present while the control is invalid (mirrors the `invalid` property). Style hook for the whole control.\n * @attr {boolean} data-focused - Present while the control has focus. Style hook for focus-within treatments.\n */\n@customElement('nord-combobox')\nexport default class Combobox extends ComboboxBase {\n /**\n * The value of the combobox. A single string when `multiple` is `false`,\n * or an array of strings when `multiple` is `true`.\n */\n @property({ attribute: false })\n // @ts-expect-error - intentionally widens the inherited `value: string` to support multiple selection\n value: string | string[] = ''\n\n /**\n * Allows selecting more than one option. Selected options are shown as\n * removable chips and the listbox stays open after each selection.\n */\n @property({ reflect: true, type: Boolean }) multiple = false\n\n /**\n * Shows a clear (✕) button while there is a value. Pressing it resets the\n * value to `''` (or `[]` when `multiple`), then fires `clear` and `change`.\n * Defaults to `true`.\n *\n * This prop only toggles the button. A single-select value can always be\n * cleared by deleting the input text, regardless of this prop (Base UI\n * parity).\n */\n @property({ reflect: true, type: Boolean }) clearable = true\n\n /**\n * Offers a \"Create\" affordance when the query does not exactly match an\n * existing option. Choosing it (click or Enter) adds the typed value as an\n * option and selects it (in `multiple` mode it is appended as a chip), and\n * fires a cancelable `create` event. Call `event.preventDefault()` to opt out\n * and handle creation yourself (e.g. to map the value to a different `value`).\n */\n @property({ reflect: true, type: Boolean }) creatable = false\n\n /** Options contributed by declarative `<nord-combobox-option>` children. */\n @state() private slottedOptions: ComboboxOption[] = []\n\n /** Options the combobox created itself via the auto-add `creatable` flow. */\n @state() private createdOptions: ComboboxOption[] = []\n\n /** Registered declarative option elements (rebuilt into `slottedOptions`). */\n private optionElements = new Set<Element>()\n\n /** The registry the option/group children use to self-register (provide/inject). */\n private optionRegistry: ComboboxRegistry = {\n add: (option) => {\n this.optionElements.add(option)\n this.rebuildSlottedOptions()\n },\n remove: (option) => {\n this.optionElements.delete(option)\n this.rebuildSlottedOptions()\n },\n notify: () => this.rebuildSlottedOptions(),\n }\n\n private registryProvider = new ContextProvider(this, {\n context: comboboxRegistryContext,\n initialValue: this.optionRegistry,\n })\n\n connectedCallback() {\n super.connectedCallback()\n injectLightDomStyles(this, style)\n }\n\n /** Read a registered option element into a plain `ComboboxOption`. */\n private readOptionElement(el: Element): ComboboxOption {\n const group = el.closest('nord-combobox-group')?.getAttribute('label') || undefined\n const label = el.getAttribute('label') ?? el.textContent?.trim() ?? ''\n const option: ComboboxOption = {\n value: el.getAttribute('value') ?? '',\n label,\n disabled: el.hasAttribute('disabled'),\n }\n if (group) {\n option.group = group\n }\n // Custom markup (e.g. an avatar) becomes the rendered row content.\n if (el.children.length > 0) {\n option.html = el.innerHTML\n }\n return option\n }\n\n /** Rebuild `slottedOptions` from the registered elements, in DOM order. */\n private rebuildSlottedOptions() {\n const ordered = [...this.optionElements].sort((a, b) =>\n\n a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1,\n )\n this.slottedOptions = ordered.map(el => this.readOptionElement(el))\n }\n\n /** Cache for the merged option list so `filtered`'s identity check stays stable. */\n private resolvedCache?: {\n options: ComboboxOption[]\n slotted: ComboboxOption[]\n created: ComboboxOption[]\n result: ComboboxOption[]\n }\n\n /**\n * The options to render: the `.options` property when set (dynamic/async data),\n * otherwise the declarative `<nord-combobox-option>` children, with any\n * auto-created options merged on top.\n */\n protected override get resolvedOptions(): ComboboxOption[] {\n const base = this.options.length ? this.options : this.slottedOptions\n const created = this.createdOptions\n const cache = this.resolvedCache\n if (cache && cache.options === this.options && cache.slotted === this.slottedOptions && cache.created === created) {\n return cache.result\n }\n const result = created.length ? [...base, ...created] : base\n this.resolvedCache = { options: this.options, slotted: this.slottedOptions, created, result }\n return result\n }\n\n /** Whether chip/array logic applies — i.e. `multiple` selection. */\n private get isMultiple(): boolean {\n return this.multiple\n }\n\n /**\n * In `multiple` mode the input is always a pure filter (selections live in\n * chips). In single-select it shows the live query only once the user types;\n * until then it keeps the selected option's label visible and editable (Base\n * UI parity — see `ComboboxBase.showsQueryInInput`).\n */\n protected override get showsQueryInInput(): boolean {\n return this.isMultiple || this.queryDirty\n }\n\n protected override get inputPlaceholder(): string | undefined {\n return this.isMultiple && this.hasValue ? undefined : this.placeholder\n }\n\n /** The index of the create row, or `-1` when it isn't shown. */\n protected override get createIndex(): number {\n return this.canCreate ? this.filteredOptions.length : -1\n }\n\n /** Whether the \"Create\" affordance should be offered for the current query. */\n protected override get canCreate(): boolean {\n const query = this.search.trim()\n\n if (!this.creatable || !query) {\n return false\n }\n\n // Don't offer to create something that already exists exactly.\n return !this.resolvedOptions.some(\n option => option.label.toLocaleLowerCase() === query.toLocaleLowerCase(),\n )\n }\n\n protected override get showClear(): boolean {\n return this.clearable && this.hasValue && !this.disabled && !this.readonly\n }\n\n protected override get ariaMultiselectable(): 'true' | 'false' {\n return this.isMultiple ? 'true' : 'false'\n }\n\n /**\n * Clear the current value, firing `clear` then `change`.\n */\n override clear() {\n this.value = this.isMultiple ? [] : ''\n this.search = ''\n this.queryDirty = false\n this.dispatchEvent(new ComboboxClearEvent())\n this.commit()\n this.focus()\n }\n\n protected override selectOption(option: ComboboxOption) {\n // Single-select behaves exactly like the base (set value, close, restore\n // focus); only the multiple case — toggle membership, keep the popup open —\n // differs, so handle that here and delegate the rest.\n if (!this.isMultiple) {\n return super.selectOption(option)\n }\n\n if (option.disabled) {\n return\n }\n\n // Remember the label so it survives the option leaving the result set.\n this.labelCache.set(option.value, option.label)\n\n const next = this.values.includes(option.value)\n ? this.values.filter(value => value !== option.value)\n : [...this.values, option.value]\n\n this.value = next\n this.search = ''\n this.queryDirty = false\n this.commit()\n // Keep the popup open after selection in multiple mode.\n this.focus()\n }\n\n protected override onInput(input: HTMLInputElement): void {\n // Base UI parity: fully deleting the single-select input text clears the\n // committed selection (not just the filter). This is always available,\n // independent of the `clearable` button. In multiple mode the selections\n // live in chips, so an empty filter is not a clear (Backspace removes the\n // last chip instead — see `onUnhandledKeydown`).\n if (!this.isMultiple && input.value === '' && this.hasValue) {\n this.value = ''\n this.commit()\n }\n }\n\n protected override emitCreate() {\n const query = this.search.trim()\n\n if (!query) {\n return\n }\n\n /**\n * Fired when the user chooses the create affordance, carrying the typed\n * query. By default the combobox then adds the value as an option and selects\n * it; call `event.preventDefault()` to opt out and handle creation yourself.\n */\n const proceed = this.dispatchEvent(new ComboboxCreateEvent(query))\n if (!proceed) {\n // Consumer opted out — leave options/value untouched (legacy behaviour).\n return\n }\n\n const option: ComboboxOption = { value: query, label: query }\n\n // Add to our own additive list so it survives `.options` reassignment and\n // works regardless of the source (property, slotted, or external-filtering).\n if (!this.resolvedOptions.some(o => o.value === option.value)) {\n this.createdOptions = [...this.createdOptions, option]\n }\n this.labelCache.set(option.value, option.label)\n\n // Select it: single sets the value and closes; multiple appends a chip,\n // clears the query and keeps the popup open — same as picking any option.\n this.selectOption(option)\n }\n\n protected override onUnhandledKeydown(e: KeyboardEvent) {\n // In multiple mode, an empty query Backspace removes the last chip.\n if (e.key === 'Backspace' && this.isMultiple && !this.search && this.values.length) {\n this.removeValue(this.values[this.values.length - 1])\n }\n }\n\n private removeValue(value: string) {\n if (this.disabled) {\n return\n }\n\n this.value = this.values.filter(v => v !== value)\n this.commit()\n this.focus()\n }\n\n /** Multiple mode renders selected options as removable chips before the input. */\n protected override renderLeadingControl() {\n if (!this.isMultiple) {\n return nothing\n }\n\n return repeat(\n this.values,\n value => value,\n (value) => {\n return html`\n <nord-tag\n class=\"n-chip\"\n size=\"s\"\n variant=\"removable\"\n ?disabled=${this.disabled}\n @remove=${() => this.removeValue(value)}\n @click=${(e: Event) => e.stopPropagation()}\n >\n ${this.labelFor(value)}\n </nord-tag>\n `\n },\n )\n }\n\n protected override renderCreate() {\n const active = this.activeDescendant.activeIndex === this.createIndex\n\n return html`\n <div\n id=${this.createId}\n class=${classMap({ 'n-option': true, 'n-create': true, 'is-active': active })}\n role=\"option\"\n aria-selected=\"false\"\n @click=${this.emitCreate}\n @mousemove=${() => this.activeDescendant.setActiveIndex(this.createIndex)}\n >\n <nord-icon class=\"n-create-icon\" name=\"interface-add-small\"></nord-icon>\n <span class=\"n-option-label\">${this.localize.term('createLabel', this.search.trim())}</span>\n <nord-kbd class=\"n-create-kbd\" aria-hidden=\"true\">⏎</nord-kbd>\n </div>\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-combobox': Combobox\n }\n}\n"],"names":["Icon","registerIcon","addIcon","Combobox","ComboboxBase","constructor","this","value","multiple","clearable","creatable","slottedOptions","createdOptions","optionElements","Set","optionRegistry","add","option","rebuildSlottedOptions","remove","delete","notify","registryProvider","ContextProvider","context","comboboxRegistryContext","initialValue","connectedCallback","super","injectLightDomStyles","style","readOptionElement","el","group","_a","closest","getAttribute","undefined","label","_d","_b","_c","textContent","trim","_e","disabled","hasAttribute","children","length","html","innerHTML","ordered","sort","a","b","compareDocumentPosition","Node","DOCUMENT_POSITION_FOLLOWING","map","resolvedOptions","base","options","created","cache","resolvedCache","slotted","result","isMultiple","showsQueryInInput","queryDirty","inputPlaceholder","hasValue","placeholder","createIndex","canCreate","filteredOptions","query","search","some","toLocaleLowerCase","showClear","readonly","ariaMultiselectable","clear","dispatchEvent","ComboboxClearEvent","commit","focus","selectOption","labelCache","set","next","values","includes","filter","onInput","input","emitCreate","ComboboxCreateEvent","o","onUnhandledKeydown","e","key","removeValue","v","renderLeadingControl","repeat","stopPropagation","labelFor","nothing","renderCreate","active","activeDescendant","activeIndex","createId","classMap","setActiveIndex","localize","term","__decorate","property","attribute","prototype","reflect","type","Boolean","state","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Combobox.js","sources":["../src/combobox/Combobox.ts"],"sourcesContent":["import type { ComboboxRegistry } from './combobox-context.js'\nimport type { ComboboxOption } from './ComboboxBase.js'\nimport { ContextProvider } from '@lit/context'\nimport * as addIcon from '@nordhealth/icons/lib/assets/interface-add-small.js'\nimport { html, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { repeat } from 'lit/directives/repeat.js'\n\nimport { injectLightDomStyles } from '../common/light-dom-styles.js'\nimport Icon from '../icon/Icon.js'\nimport { comboboxRegistryContext } from './combobox-context.js'\nimport style from './Combobox.css'\nimport ComboboxBase from './ComboboxBase.js'\nimport { ComboboxClearEvent, ComboboxCreateEvent } from './ComboboxEvents.js'\nimport '../kbd/Kbd.js'\nimport '../tag/Tag.js'\nimport './ComboboxOption.js'\nimport './ComboboxGroup.js'\n\nIcon.registerIcon(addIcon)\n\nexport type { ComboboxOption } from './ComboboxBase.js'\nexport { ComboboxClearEvent, ComboboxCreateEvent } from './ComboboxEvents.js'\n\n/**\n * Combobox lets users choose one option (or several) from a list by typing to\n * filter, then picking with the keyboard or pointer. Use it when a [Select](/components/select/)\n * would be unwieldy because there are many options to scan.\n *\n * Combobox is a control only — it has no built-in label, hint or error. Compose\n * it inside a [Field](/components/field/) to give it a label, hint and error\n * message, the same way you would a native input.\n *\n * The committed `value` is an option's `value` (or an array of them with\n * `multiple`); the input is a filter/search field whose text is discarded on\n * commit. For a free-text input with suggestions where the typed text is the\n * value, use [Autocomplete](/components/autocomplete/) instead.\n *\n * @status draft\n * @category form\n * @lightDom true\n *\n * @slot start - Optional slot used to place an icon or prefix at the start of the control.\n * @slot end - Optional slot used to place an icon or suffix at the end of the control.\n *\n * @fires {NordEvent} change - Dispatched when the committed value changes via user interaction.\n * @fires {NordEvent} input - Dispatched as the user types into the search input.\n * @fires {NordEvent} open - Dispatched when the listbox is opened.\n * @fires {NordEvent} close - Dispatched when the listbox is closed.\n * @fires {ComboboxClearEvent} clear - Dispatched when the value is cleared via the clear button or `clear()`.\n * @fires {ComboboxCreateEvent} create - Dispatched when the user chooses the \"Create\" affordance in a `creatable` combobox.\n *\n * @localization clearLabel - Accessible label for the clear button.\n * @localization createLabel - Label for the \"Create\" affordance, given the current query.\n * @localization noResults - Message shown when no options match the query.\n *\n * @cssprop [--n-combobox-list-inline-size] - Controls the inline size, or width, of the dropdown list. Defaults to the width of the combobox control.\n * @cssprop [--n-combobox-block-size] - Controls the block size, or height, of the control. The medium default is `var(--n-space-xl)`; the s and l sizes scale from it.\n *\n * @attr {boolean} data-invalid - Present while the control is invalid (mirrors the `invalid` property). Style hook for the whole control.\n * @attr {boolean} data-focused - Present while the control has focus. Style hook for focus-within treatments.\n */\n@customElement('nord-combobox')\nexport default class Combobox extends ComboboxBase {\n /**\n * The value of the combobox. A single string when `multiple` is `false`,\n * or an array of strings when `multiple` is `true`.\n */\n @property({ attribute: false })\n // @ts-expect-error - intentionally widens the inherited `value: string` to support multiple selection\n value: string | string[] = ''\n\n /**\n * Allows selecting more than one option. Selected options are shown as\n * removable chips and the listbox stays open after each selection.\n */\n @property({ reflect: true, type: Boolean }) multiple = false\n\n /**\n * Shows a clear (✕) button while there is a value. Pressing it resets the\n * value to `''` (or `[]` when `multiple`), then fires `clear` and `change`.\n * Defaults to `true`.\n *\n * This prop only toggles the button. A single-select value can always be\n * cleared by deleting the input text, regardless of this prop (Base UI\n * parity).\n */\n @property({ reflect: true, type: Boolean }) clearable = true\n\n /**\n * Offers a \"Create\" affordance when the query does not exactly match an\n * existing option. Choosing it (click or Enter) adds the typed value as an\n * option and selects it (in `multiple` mode it is appended as a chip), and\n * fires a cancelable `create` event. Call `event.preventDefault()` to opt out\n * and handle creation yourself (e.g. to map the value to a different `value`).\n */\n @property({ reflect: true, type: Boolean }) creatable = false\n\n /** Options contributed by declarative `<nord-combobox-option>` children. */\n @state() private slottedOptions: ComboboxOption[] = []\n\n /** Options the combobox created itself via the auto-add `creatable` flow. */\n @state() private createdOptions: ComboboxOption[] = []\n\n /** Registered declarative option elements (rebuilt into `slottedOptions`). */\n private optionElements = new Set<Element>()\n\n /** The registry the option/group children use to self-register (provide/inject). */\n private optionRegistry: ComboboxRegistry = {\n add: (option) => {\n this.optionElements.add(option)\n this.rebuildSlottedOptions()\n },\n remove: (option) => {\n this.optionElements.delete(option)\n this.rebuildSlottedOptions()\n },\n notify: () => this.rebuildSlottedOptions(),\n }\n\n private registryProvider = new ContextProvider(this, {\n context: comboboxRegistryContext,\n initialValue: this.optionRegistry,\n })\n\n connectedCallback() {\n super.connectedCallback()\n injectLightDomStyles(this, style)\n }\n\n /** Read a registered option element into a plain `ComboboxOption`. */\n private readOptionElement(el: Element): ComboboxOption {\n const group = el.closest('nord-combobox-group')?.getAttribute('label') || undefined\n const label = el.getAttribute('label') ?? el.textContent?.trim() ?? ''\n const option: ComboboxOption = {\n value: el.getAttribute('value') ?? '',\n label,\n disabled: el.hasAttribute('disabled'),\n }\n if (group) {\n option.group = group\n }\n // Custom markup (e.g. an avatar) becomes the rendered row content.\n if (el.children.length > 0) {\n option.html = el.innerHTML\n }\n return option\n }\n\n /** Rebuild `slottedOptions` from the registered elements, in DOM order. */\n private rebuildSlottedOptions() {\n const ordered = [...this.optionElements].sort((a, b) =>\n\n a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1,\n )\n this.slottedOptions = ordered.map(el => this.readOptionElement(el))\n }\n\n /** Cache for the merged option list so `filtered`'s identity check stays stable. */\n private resolvedCache?: {\n options: ComboboxOption[]\n slotted: ComboboxOption[]\n created: ComboboxOption[]\n result: ComboboxOption[]\n }\n\n /**\n * The options to render: the `.options` property when set (dynamic/async data),\n * otherwise the declarative `<nord-combobox-option>` children, with any\n * auto-created options merged on top.\n */\n protected override get resolvedOptions(): ComboboxOption[] {\n const base = this.options.length ? this.options : this.slottedOptions\n const created = this.createdOptions\n const cache = this.resolvedCache\n if (cache && cache.options === this.options && cache.slotted === this.slottedOptions && cache.created === created) {\n return cache.result\n }\n const result = created.length ? [...base, ...created] : base\n this.resolvedCache = { options: this.options, slotted: this.slottedOptions, created, result }\n return result\n }\n\n /** Whether chip/array logic applies — i.e. `multiple` selection. */\n private get isMultiple(): boolean {\n return this.multiple\n }\n\n /**\n * In `multiple` mode the input is always a pure filter (selections live in\n * chips). In single-select it shows the live query only once the user types;\n * until then it keeps the selected option's label visible and editable (Base\n * UI parity — see `ComboboxBase.showsQueryInInput`).\n */\n protected override get showsQueryInInput(): boolean {\n return this.isMultiple || this.queryDirty\n }\n\n protected override get inputPlaceholder(): string | undefined {\n return this.isMultiple && this.hasValue ? undefined : this.placeholder\n }\n\n /** The index of the create row, or `-1` when it isn't shown. */\n protected override get createIndex(): number {\n return this.canCreate ? this.filteredOptions.length : -1\n }\n\n /** Whether the \"Create\" affordance should be offered for the current query. */\n protected override get canCreate(): boolean {\n const query = this.search.trim()\n\n if (!this.creatable || !query) {\n return false\n }\n\n // Don't offer to create something that already exists exactly.\n return !this.resolvedOptions.some(\n option => option.label.toLocaleLowerCase() === query.toLocaleLowerCase(),\n )\n }\n\n protected override get showClear(): boolean {\n return this.clearable && this.hasValue && !this.disabled && !this.readonly\n }\n\n protected override get ariaMultiselectable(): 'true' | 'false' {\n return this.isMultiple ? 'true' : 'false'\n }\n\n /**\n * Clear the current value, firing `clear` then `change`.\n */\n override clear() {\n this.value = this.isMultiple ? [] : ''\n this.search = ''\n this.queryDirty = false\n this.dispatchEvent(new ComboboxClearEvent())\n this.commit()\n this.focus()\n }\n\n protected override selectOption(option: ComboboxOption) {\n // Single-select behaves exactly like the base (set value, close, restore\n // focus); only the multiple case — toggle membership, keep the popup open —\n // differs, so handle that here and delegate the rest.\n if (!this.isMultiple) {\n return super.selectOption(option)\n }\n\n if (option.disabled) {\n return\n }\n\n // Remember the label so it survives the option leaving the result set.\n this.labelCache.set(option.value, option.label)\n\n const next = this.values.includes(option.value)\n ? this.values.filter(value => value !== option.value)\n : [...this.values, option.value]\n\n this.value = next\n this.search = ''\n this.queryDirty = false\n this.commit()\n // Keep the popup open after selection in multiple mode.\n this.focus()\n }\n\n protected override onInput(input: HTMLInputElement): void {\n // Base UI parity: fully deleting the single-select input text clears the\n // committed selection (not just the filter). This is always available,\n // independent of the `clearable` button. In multiple mode the selections\n // live in chips, so an empty filter is not a clear (Backspace removes the\n // last chip instead — see `onUnhandledKeydown`).\n if (!this.isMultiple && input.value === '' && this.hasValue) {\n this.value = ''\n this.commit()\n }\n }\n\n protected override emitCreate() {\n const query = this.search.trim()\n\n if (!query) {\n return\n }\n\n /**\n * Fired when the user chooses the create affordance, carrying the typed\n * query. By default the combobox then adds the value as an option and selects\n * it; call `event.preventDefault()` to opt out and handle creation yourself.\n */\n const proceed = this.dispatchEvent(new ComboboxCreateEvent(query))\n if (!proceed) {\n // Consumer opted out — leave options/value untouched (legacy behaviour).\n return\n }\n\n const option: ComboboxOption = { value: query, label: query }\n\n // Add to our own additive list so it survives `.options` reassignment and\n // works regardless of the source (property, slotted, or external-filtering).\n if (!this.resolvedOptions.some(o => o.value === option.value)) {\n this.createdOptions = [...this.createdOptions, option]\n }\n this.labelCache.set(option.value, option.label)\n\n // Select it: single sets the value and closes; multiple appends a chip,\n // clears the query and keeps the popup open — same as picking any option.\n this.selectOption(option)\n }\n\n protected override onUnhandledKeydown(e: KeyboardEvent) {\n // In multiple mode, an empty query Backspace removes the last chip.\n if (e.key === 'Backspace' && this.isMultiple && !this.search && this.values.length) {\n this.removeValue(this.values[this.values.length - 1])\n }\n }\n\n private removeValue(value: string) {\n if (this.disabled) {\n return\n }\n\n this.value = this.values.filter(v => v !== value)\n this.commit()\n this.focus()\n }\n\n /** Multiple mode renders selected options as removable chips before the input. */\n protected override renderLeadingControl() {\n if (!this.isMultiple) {\n return nothing\n }\n\n return repeat(\n this.values,\n value => value,\n (value) => {\n return html`\n <nord-tag\n class=\"n-chip\"\n size=\"s\"\n variant=\"removable\"\n ?disabled=${this.disabled}\n @remove=${() => this.removeValue(value)}\n @click=${(e: Event) => e.stopPropagation()}\n >\n ${this.labelFor(value)}\n </nord-tag>\n `\n },\n )\n }\n\n protected override renderCreate() {\n const active = this.activeDescendant.activeIndex === this.createIndex\n\n return html`\n <div\n id=${this.createId}\n class=${classMap({ 'n-option': true, 'n-create': true, 'is-active': active })}\n role=\"option\"\n aria-selected=\"false\"\n @click=${this.emitCreate}\n @mousemove=${() => this.activeDescendant.setActiveIndex(this.createIndex)}\n >\n <nord-icon class=\"n-create-icon\" name=\"interface-add-small\"></nord-icon>\n <span class=\"n-option-label\">${this.localize.term('createLabel', this.search.trim())}</span>\n <nord-kbd class=\"n-create-kbd\" aria-hidden=\"true\">⏎</nord-kbd>\n </div>\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-combobox': Combobox\n }\n}\n"],"names":["Icon","registerIcon","addIcon","Combobox","ComboboxBase","constructor","this","value","multiple","clearable","creatable","slottedOptions","createdOptions","optionElements","Set","optionRegistry","add","option","rebuildSlottedOptions","remove","delete","notify","registryProvider","ContextProvider","context","comboboxRegistryContext","initialValue","connectedCallback","super","injectLightDomStyles","style","readOptionElement","el","group","_a","closest","getAttribute","undefined","label","_d","_b","_c","textContent","trim","_e","disabled","hasAttribute","children","length","html","innerHTML","ordered","sort","a","b","compareDocumentPosition","Node","DOCUMENT_POSITION_FOLLOWING","map","resolvedOptions","base","options","created","cache","resolvedCache","slotted","result","isMultiple","showsQueryInInput","queryDirty","inputPlaceholder","hasValue","placeholder","createIndex","canCreate","filteredOptions","query","search","some","toLocaleLowerCase","showClear","readonly","ariaMultiselectable","clear","dispatchEvent","ComboboxClearEvent","commit","focus","selectOption","labelCache","set","next","values","includes","filter","onInput","input","emitCreate","ComboboxCreateEvent","o","onUnhandledKeydown","e","key","removeValue","v","renderLeadingControl","repeat","stopPropagation","labelFor","nothing","renderCreate","active","activeDescendant","activeIndex","createId","classMap","setActiveIndex","localize","term","__decorate","property","attribute","prototype","reflect","type","Boolean","state","customElement"],"mappings":"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"}
|