luxen-ui 0.17.0 → 0.18.0
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/cdn/custom-elements.json +336 -181
- package/cdn/elements/alert/alert.d.ts +0 -5
- package/cdn/elements/alert/alert.d.ts.map +1 -1
- package/cdn/elements/alert/alert.js.map +1 -1
- package/cdn/elements/alert-dialog/alert-dialog.d.ts.map +1 -1
- package/cdn/elements/alert-dialog/alert-dialog.js +8 -8
- package/cdn/elements/alert-dialog/alert-dialog.js.map +1 -1
- package/cdn/elements/avatar/avatar.js +3 -3
- package/cdn/elements/dropdown-item/dropdown-item.d.ts +4 -0
- package/cdn/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js +2 -2
- package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
- package/cdn/elements/form-field/form-field.d.ts.map +1 -1
- package/cdn/elements/form-field/form-field.js +1 -1
- package/cdn/elements/form-field/form-field.js.map +1 -1
- package/cdn/elements/input-otp/input-otp.d.ts.map +1 -1
- package/cdn/elements/input-otp/input-otp.js +1 -1
- package/cdn/elements/input-otp/input-otp.js.map +1 -1
- package/cdn/elements/input-stepper/input-stepper.js +1 -1
- package/cdn/elements/input-stepper/input-stepper.js.map +1 -1
- package/cdn/elements/prose-editor/prose-editor.d.ts +1 -0
- package/cdn/elements/prose-editor/prose-editor.d.ts.map +1 -1
- package/cdn/elements/prose-editor/prose-editor.js +8 -8
- package/cdn/elements/prose-editor/prose-editor.js.map +1 -1
- package/cdn/elements/select/select.js +1 -1
- package/cdn/elements/slider/slider.d.ts +0 -5
- package/cdn/elements/slider/slider.d.ts.map +1 -1
- package/cdn/elements/slider/slider.js.map +1 -1
- package/cdn/elements/stories-viewer/stories-viewer.d.ts.map +1 -1
- package/cdn/elements/stories-viewer/stories-viewer.js +13 -13
- package/cdn/elements/stories-viewer/stories-viewer.js.map +1 -1
- package/cdn/elements/story/story.d.ts.map +1 -1
- package/cdn/elements/story/story.js +11 -11
- package/cdn/elements/story/story.js.map +1 -1
- package/cdn/elements/tag/tag.d.ts +51 -2
- package/cdn/elements/tag/tag.d.ts.map +1 -1
- package/cdn/elements/tag/tag.js +53 -17
- package/cdn/elements/tag/tag.js.map +1 -1
- package/cdn/elements/tree-item/tree-item.js +6 -6
- package/cdn/elements/tree-item/tree-item.js.map +1 -1
- package/cdn/shared/styles/button-core.styles.js.map +1 -1
- package/cdn/shared/styles/checkbox-appearance.styles.js +1 -1
- package/cdn/shared/styles/checkbox-appearance.styles.js.map +1 -1
- package/cdn/standalone.css +277 -24
- package/cdn/styles/elements/button.css +6 -0
- package/cdn/styles/elements/checkbox.css +49 -0
- package/cdn/styles/elements/form-field.css +50 -0
- package/cdn/styles/elements/input-otp.css +7 -2
- package/cdn/styles/elements/input-stepper/default.css +10 -4
- package/cdn/styles/elements/input-stepper/rounded.css +6 -2
- package/cdn/styles/elements/input.css +19 -1
- package/cdn/styles/elements/prose-editor.css +46 -0
- package/cdn/styles/elements/radio.css +23 -1
- package/cdn/styles/elements/segmented-control.css +26 -7
- package/cdn/styles/elements/switch.css +28 -2
- package/cdn/styles/preset.css +7 -5
- package/cdn/styles/tokens/forms.css +7 -5
- package/cdn/styles/tokens.css +7 -5
- package/dist/css/elements/button.css +6 -0
- package/dist/css/elements/checkbox.css +49 -0
- package/dist/css/elements/form-field.css +50 -0
- package/dist/css/elements/input-otp.css +7 -2
- package/dist/css/elements/input-stepper/default.css +10 -4
- package/dist/css/elements/input-stepper/rounded.css +6 -2
- package/dist/css/elements/input.css +19 -1
- package/dist/css/elements/prose-editor.css +46 -0
- package/dist/css/elements/radio.css +23 -1
- package/dist/css/elements/segmented-control.css +26 -7
- package/dist/css/elements/switch.css +28 -2
- package/dist/css/preset.css +7 -5
- package/dist/css/tokens/forms.css +7 -5
- package/dist/css/tokens.css +7 -5
- package/dist/custom-elements.json +336 -181
- package/dist/elements/alert/alert.d.ts +0 -5
- package/dist/elements/alert/alert.d.ts.map +1 -1
- package/dist/elements/alert-dialog/alert-dialog.d.ts.map +1 -1
- package/dist/elements/alert-dialog/alert-dialog.js +15 -3
- package/dist/elements/dropdown-item/dropdown-item.css +18 -0
- package/dist/elements/dropdown-item/dropdown-item.d.ts +4 -0
- package/dist/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
- package/dist/elements/dropdown-item/dropdown-item.js +11 -2
- package/dist/elements/form-field/form-field.d.ts.map +1 -1
- package/dist/elements/form-field/form-field.js +5 -3
- package/dist/elements/input-otp/input-otp.d.ts.map +1 -1
- package/dist/elements/input-otp/input-otp.js +4 -3
- package/dist/elements/input-stepper/input-stepper.js +4 -4
- package/dist/elements/prose-editor/prose-editor.css +31 -0
- package/dist/elements/prose-editor/prose-editor.d.ts +1 -0
- package/dist/elements/prose-editor/prose-editor.d.ts.map +1 -1
- package/dist/elements/prose-editor/prose-editor.js +1 -0
- package/dist/elements/slider/slider.css +6 -0
- package/dist/elements/slider/slider.d.ts +0 -5
- package/dist/elements/slider/slider.d.ts.map +1 -1
- package/dist/elements/stories-viewer/stories-viewer.d.ts.map +1 -1
- package/dist/elements/stories-viewer/stories-viewer.js +4 -1
- package/dist/elements/story/story.d.ts.map +1 -1
- package/dist/elements/story/story.js +9 -4
- package/dist/elements/tag/tag.css +202 -16
- package/dist/elements/tag/tag.d.ts +51 -2
- package/dist/elements/tag/tag.d.ts.map +1 -1
- package/dist/elements/tag/tag.js +151 -22
- package/dist/elements/tree-item/tree-item.js +2 -2
- package/dist/metadata/dropdown-item.json +16 -1
- package/dist/metadata/index.json +91 -5
- package/dist/metadata/prose-editor.json +5 -0
- package/dist/metadata/tag.json +69 -3
- package/dist/shared/styles/button-core.css +6 -0
- package/dist/shared/styles/checkbox-appearance.css +49 -0
- package/dist/templates/elements/checkbox.md +2 -0
- package/dist/templates/elements/dropdown.md +52 -0
- package/dist/templates/elements/form-field.md +33 -0
- package/dist/templates/elements/input.md +1 -1
- package/dist/templates/elements/prose-editor.md +2 -1
- package/dist/templates/elements/radio.md +2 -0
- package/dist/templates/elements/select.md +14 -0
- package/dist/templates/elements/tag.md +225 -8
- package/dist/templates/elements/textarea.md +1 -1
- package/package.json +1 -1
- package/vite-plugin.ts +7 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","names":[],"sources":["../../../src/html/elements/alert/alert.ts"],"sourcesContent":["import 'iconify-icon';\nimport type { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls } from '../../registry.js';\nimport { HideEvent, AfterHideEvent } from '../../events/index.js';\n\nexport type AlertVariant = 'info' | 'success' | 'warning' | 'danger';\n\n/** Default Iconify icon per variant (neutral falls back to the info glyph). */\nconst DEFAULT_ICONS: Record<string, string> = {\n neutral: 'lucide:info',\n info: 'lucide:info',\n success: 'lucide:circle-check',\n warning: 'lucide:triangle-alert',\n danger: 'lucide:octagon-alert',\n};\n\n/**\n * @summary A callout box that highlights contextual information with a semantic color and a leading icon.\n * @customElement l-alert\n *\n * @event hide - Emitted when a dismissible alert is about to close. Cancelable — call `event.preventDefault()` to keep it open.\n * @event after-hide - Emitted after the dismiss animation completes and the alert is removed from the DOM. Not cancelable.\n *\n * @cssproperty [--gap=0.75rem] - Gap between the icon, content, and close button.\n * @cssproperty [--padding=1rem] - Inner padding.\n * @cssproperty [--border-radius=8px] - Corner radius.\n *\n * @cssClass .l-alert-icon - The leading icon, colored by variant (auto-injected).\n * @cssClass .l-alert-content - Wrapper the authored children are moved into; stacks them vertically (auto-injected).\n * @cssClass .l-alert-title - Optional bold heading placed above the body content. Use a real heading element (e.g. `<h3 class=\"l-alert-title\">`) when the alert is a section callout so it is reachable by heading navigation.\n *\n * @example\n * ```html\n * <l-alert variant=\"warning\" dismissible>\n * <span class=\"l-alert-title\">Heads up</span>\n * This action cannot be undone.\n * </l-alert>\n * ```\n */\nexport class Alert extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n /** Semantic variant: `info`, `success`, `warning`, or `danger`. Default is neutral. */\n @property({ reflect: true })\n variant?: AlertVariant;\n\n /** Iconify icon name (e.g. `lucide:bell`) overriding the variant's default icon. */\n @property()\n icon?: string;\n\n /** Hide the leading icon entirely. */\n @property({ attribute: 'without-icon', type: Boolean })\n withoutIcon = false;\n\n /** Show a close button that dismisses the alert when clicked. */\n @property({ type: Boolean, reflect: true })\n dismissible = false;\n\n private _localize = new LocalizeController(this);\n private _dismissTimer?: ReturnType<typeof setTimeout>;\n // Re-home children the parser (upgrade-before-parse) or the consumer\n // (imperative `append`) adds directly to the host after a sync. Lit's\n // `updated()` only fires on reactive-property changes, so without this an\n // appended text-node body would escape `.l-alert-content`.\n private _observer = new MutationObserver(() => this._sync());\n\n override connectedCallback() {\n super.connectedCallback();\n this._sync();\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._observer.disconnect();\n if (this._dismissTimer) clearTimeout(this._dismissTimer);\n }\n\n override updated(_changed: PropertyValues<this>) {\n this._sync();\n }\n\n /** Idempotently reconcile the injected icon, content wrapper, and close button. */\n private _sync() {\n // Pause the observer so our own DOM moves below don't re-trigger it.\n this._observer.disconnect();\n this._syncIcon();\n this._syncContent();\n this._syncClose();\n if (this.isConnected) this._observer.observe(this, { childList: true });\n }\n\n private _iconName(): string | null {\n if (this.withoutIcon) return null;\n if (this.icon) return this.icon;\n return DEFAULT_ICONS[this.variant ?? 'neutral'];\n }\n\n private _syncIcon() {\n const iconClass = cls('alert-icon');\n let iconEl = this.querySelector<HTMLElement>(`:scope > .${iconClass}`);\n const name = this._iconName();\n\n if (!name) {\n iconEl?.remove();\n return;\n }\n\n if (!iconEl) {\n iconEl = document.createElement('iconify-icon');\n iconEl.className = iconClass;\n iconEl.setAttribute('aria-hidden', 'true');\n this.prepend(iconEl);\n } else if (this.firstElementChild !== iconEl) {\n this.prepend(iconEl);\n }\n\n if (iconEl.getAttribute('icon') !== name) iconEl.setAttribute('icon', name);\n }\n\n /**\n * Move the authored children into a `.l-alert-content` wrapper so they stack\n * as one column regardless of node type. A wrapper (rather than placing bare\n * children directly) is required because text nodes — a plain string body —\n * cannot be targeted by CSS, so they would otherwise escape the layout.\n */\n private _syncContent() {\n const contentClass = cls('alert-content');\n const iconClass = cls('alert-icon');\n const closeClass = cls('close');\n\n let content = this.querySelector<HTMLElement>(`:scope > .${contentClass}`);\n if (!content) {\n content = document.createElement('div');\n content.className = contentClass;\n this.append(content);\n }\n\n for (const node of Array.from(this.childNodes)) {\n if (node === content) continue;\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as Element;\n if (el.classList.contains(iconClass) || el.classList.contains(closeClass)) continue;\n } else if (node.nodeType !== Node.TEXT_NODE) {\n // Leave comment/marker nodes in place — notably Lit's empty\n // ChildPart marker (`<!---->`) from the default light-DOM render.\n continue;\n }\n content.append(node);\n }\n }\n\n private _syncClose() {\n const closeClass = cls('close');\n let btn = this.querySelector<HTMLButtonElement>(`:scope > .${closeClass}[data-alert-close]`);\n\n if (!this.dismissible) {\n btn?.remove();\n return;\n }\n\n if (!btn) {\n btn = document.createElement('button');\n btn.type = 'button';\n btn.className = closeClass;\n btn.dataset.appearance = 'ring';\n btn.dataset.alertClose = '';\n btn.addEventListener('click', this._dismiss);\n this.append(btn);\n } else if (this.lastElementChild !== btn) {\n this.append(btn);\n }\n\n btn.setAttribute('aria-label', this._localize.term('close'));\n }\n\n private _dismiss = () => {\n // Ignore repeat activations (fast double-click / double AT activation)\n // once a dismissal is already animating out.\n if (this.hasAttribute('data-dismissing')) return;\n if (!this.dispatchEvent(new HideEvent())) return;\n // A `hide` listener may have removed the alert itself; don't animate a\n // node that is no longer in the document.\n if (this.isConnected) this._animateOut();\n };\n\n private _animateOut() {\n this.setAttribute('data-dismissing', '');\n\n let done = false;\n const finish = () => {\n if (done) return;\n done = true;\n this.removeEventListener('transitionend', onEnd);\n if (this._dismissTimer) clearTimeout(this._dismissTimer);\n this.dispatchEvent(new AfterHideEvent());\n this.remove();\n };\n const onEnd = (e: TransitionEvent) => {\n if (e.target !== this) return;\n finish();\n };\n\n const duration = parseFloat(getComputedStyle(this).transitionDuration) * 1000;\n if (!duration) {\n finish();\n return;\n }\n\n this.addEventListener('transitionend', onEnd);\n // Fallback: hidden Preview tabs never fire `transitionend`. Tracked so\n // `disconnectedCallback` can cancel it if the alert is removed mid-animation.\n this._dismissTimer = setTimeout(finish, duration + 50);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'l-alert': Alert;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"alert.js","names":[],"sources":["../../../src/html/elements/alert/alert.ts"],"sourcesContent":["import 'iconify-icon';\nimport type { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls } from '../../registry.js';\nimport { HideEvent, AfterHideEvent } from '../../events/index.js';\n\nexport type AlertVariant = 'info' | 'success' | 'warning' | 'danger';\n\n/** Default Iconify icon per variant (neutral falls back to the info glyph). */\nconst DEFAULT_ICONS: Record<string, string> = {\n neutral: 'lucide:info',\n info: 'lucide:info',\n success: 'lucide:circle-check',\n warning: 'lucide:triangle-alert',\n danger: 'lucide:octagon-alert',\n};\n\n/**\n * @summary A callout box that highlights contextual information with a semantic color and a leading icon.\n * @customElement l-alert\n *\n * @event hide - Emitted when a dismissible alert is about to close. Cancelable — call `event.preventDefault()` to keep it open.\n * @event after-hide - Emitted after the dismiss animation completes and the alert is removed from the DOM. Not cancelable.\n *\n * @cssproperty [--gap=0.75rem] - Gap between the icon, content, and close button.\n * @cssproperty [--padding=1rem] - Inner padding.\n * @cssproperty [--border-radius=8px] - Corner radius.\n *\n * @cssClass .l-alert-icon - The leading icon, colored by variant (auto-injected).\n * @cssClass .l-alert-content - Wrapper the authored children are moved into; stacks them vertically (auto-injected).\n * @cssClass .l-alert-title - Optional bold heading placed above the body content. Use a real heading element (e.g. `<h3 class=\"l-alert-title\">`) when the alert is a section callout so it is reachable by heading navigation.\n *\n * @example\n * ```html\n * <l-alert variant=\"warning\" dismissible>\n * <span class=\"l-alert-title\">Heads up</span>\n * This action cannot be undone.\n * </l-alert>\n * ```\n */\nexport class Alert extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n /** Semantic variant: `info`, `success`, `warning`, or `danger`. Default is neutral. */\n @property({ reflect: true })\n variant?: AlertVariant;\n\n /** Iconify icon name (e.g. `lucide:bell`) overriding the variant's default icon. */\n @property()\n icon?: string;\n\n /** Hide the leading icon entirely. */\n @property({ attribute: 'without-icon', type: Boolean })\n withoutIcon = false;\n\n /** Show a close button that dismisses the alert when clicked. */\n @property({ type: Boolean, reflect: true })\n dismissible = false;\n\n private _localize = new LocalizeController(this);\n private _dismissTimer?: ReturnType<typeof setTimeout>;\n // Re-home children the parser (upgrade-before-parse) or the consumer\n // (imperative `append`) adds directly to the host after a sync. Lit's\n // `updated()` only fires on reactive-property changes, so without this an\n // appended text-node body would escape `.l-alert-content`.\n private _observer = new MutationObserver(() => this._sync());\n\n override connectedCallback() {\n super.connectedCallback();\n this._sync();\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._observer.disconnect();\n if (this._dismissTimer) clearTimeout(this._dismissTimer);\n }\n\n override updated(_changed: PropertyValues<this>) {\n this._sync();\n }\n\n /** Idempotently reconcile the injected icon, content wrapper, and close button. */\n private _sync() {\n // Pause the observer so our own DOM moves below don't re-trigger it.\n this._observer.disconnect();\n this._syncIcon();\n this._syncContent();\n this._syncClose();\n if (this.isConnected) this._observer.observe(this, { childList: true });\n }\n\n private _iconName(): string | null {\n if (this.withoutIcon) return null;\n if (this.icon) return this.icon;\n return DEFAULT_ICONS[this.variant ?? 'neutral'];\n }\n\n private _syncIcon() {\n const iconClass = cls('alert-icon');\n let iconEl = this.querySelector<HTMLElement>(`:scope > .${iconClass}`);\n const name = this._iconName();\n\n if (!name) {\n iconEl?.remove();\n return;\n }\n\n if (!iconEl) {\n iconEl = document.createElement('iconify-icon');\n iconEl.className = iconClass;\n iconEl.setAttribute('aria-hidden', 'true');\n this.prepend(iconEl);\n } else if (this.firstElementChild !== iconEl) {\n this.prepend(iconEl);\n }\n\n if (iconEl.getAttribute('icon') !== name) iconEl.setAttribute('icon', name);\n }\n\n /**\n * Move the authored children into a `.l-alert-content` wrapper so they stack\n * as one column regardless of node type. A wrapper (rather than placing bare\n * children directly) is required because text nodes — a plain string body —\n * cannot be targeted by CSS, so they would otherwise escape the layout.\n */\n private _syncContent() {\n const contentClass = cls('alert-content');\n const iconClass = cls('alert-icon');\n const closeClass = cls('close');\n\n let content = this.querySelector<HTMLElement>(`:scope > .${contentClass}`);\n if (!content) {\n content = document.createElement('div');\n content.className = contentClass;\n this.append(content);\n }\n\n for (const node of Array.from(this.childNodes)) {\n if (node === content) continue;\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as Element;\n if (el.classList.contains(iconClass) || el.classList.contains(closeClass)) continue;\n } else if (node.nodeType !== Node.TEXT_NODE) {\n // Leave comment/marker nodes in place — notably Lit's empty\n // ChildPart marker (`<!---->`) from the default light-DOM render.\n continue;\n }\n content.append(node);\n }\n }\n\n private _syncClose() {\n const closeClass = cls('close');\n let btn = this.querySelector<HTMLButtonElement>(`:scope > .${closeClass}[data-alert-close]`);\n\n if (!this.dismissible) {\n btn?.remove();\n return;\n }\n\n if (!btn) {\n btn = document.createElement('button');\n btn.type = 'button';\n btn.className = closeClass;\n btn.dataset.appearance = 'ring';\n btn.dataset.alertClose = '';\n btn.addEventListener('click', this._dismiss);\n this.append(btn);\n } else if (this.lastElementChild !== btn) {\n this.append(btn);\n }\n\n btn.setAttribute('aria-label', this._localize.term('close'));\n }\n\n private _dismiss = () => {\n // Ignore repeat activations (fast double-click / double AT activation)\n // once a dismissal is already animating out.\n if (this.hasAttribute('data-dismissing')) return;\n if (!this.dispatchEvent(new HideEvent())) return;\n // A `hide` listener may have removed the alert itself; don't animate a\n // node that is no longer in the document.\n if (this.isConnected) this._animateOut();\n };\n\n private _animateOut() {\n this.setAttribute('data-dismissing', '');\n\n let done = false;\n const finish = () => {\n if (done) return;\n done = true;\n this.removeEventListener('transitionend', onEnd);\n if (this._dismissTimer) clearTimeout(this._dismissTimer);\n this.dispatchEvent(new AfterHideEvent());\n this.remove();\n };\n const onEnd = (e: TransitionEvent) => {\n if (e.target !== this) return;\n finish();\n };\n\n const duration = parseFloat(getComputedStyle(this).transitionDuration) * 1000;\n if (!duration) {\n finish();\n return;\n }\n\n this.addEventListener('transitionend', onEnd);\n // Fallback: hidden Preview tabs never fire `transitionend`. Tracked so\n // `disconnectedCallback` can cancel it if the alert is removed mid-animation.\n this._dismissTimer = setTimeout(finish, duration + 50);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert-dialog.d.ts","sourceRoot":"","sources":["../../../src/html/elements/alert-dialog/alert-dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"alert-dialog.d.ts","sourceRoot":"","sources":["../../../src/html/elements/alert-dialog/alert-dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAUpE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAI7C,OAAO,qBAAqB,CAAC;AAU7B;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,KAAK;;CAItC;AAED;;;;;GAKG;AACH,qBAAa,WAAY,SAAQ,KAAK;;CAIrC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,qBAAa,WAAY,SAAQ,MAAM;IACrC,OAAgB,MAAM,4BAAyD;IAE/E,oCAAoC;IAEpC,WAAW,SAAa;IAExB,mCAAmC;IAEnC,UAAU,SAAY;IAEtB,uEAAuE;IAEvE,IAAI,CAAC,EAAE,QAAQ,CAAC;IAEhB;2DACuD;IAEvD,OAAO,UAAS;IAEhB,cAAuB,KAAK,WAE3B;IAID,OAAO,CAAC,YAAY,CAA4C;IAChE,cAAuB,OAAO,uBAE7B;cAKkB,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;cAOxC,SAAS,CAAC,CAAC,EAAE,KAAK;cAUlB,aAAa;IAehC,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc,CASpB;cAEiB,YAAY;CAqChC;AAMD,MAAM,WAAW,WAAW;IAC1B,gBAAgB,CACd,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,KAAK,IAAI,EACvD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,gBAAgB,CACd,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,KAAK,IAAI,EACtD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,KAAK,IAAI,EACvD,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,KAAK,IAAI,EACtD,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;CACT"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{cls as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{i as n,t as r}from"../../chunks/decorate.js";import{a as i,t as a}from"../../chunks/lit-html.js";import{r as o}from"../../chunks/static-html.js";import{staticTag as s}from"../../static-tag.js";import c from"../../shared/styles/host.styles.js";import{HasSlotController as l}from"../../shared/controllers/has-slot-controller.js";import u from"../dialog/dialog.styles.js";import{Dialog as d}from"../dialog/dialog.js";import f from"../../shared/styles/button-core.styles.js";var p=t(`:host{--backdrop:var(--l-backdrop-strong);--backdrop-blur:2px}[part=header]{padding-block-end:.5rem}[part=body]{color:var(--l-color-text-secondary);font-size:var(--l-text-sm);padding-block-end:var(--padding);line-height:1.5}[part=footer]{padding:1rem var(--padding);border-top:1px solid var(--l-color-border);background:var(--l-color-bg-fill-neutral-subtle);border-end-end-radius:var(--border-radius);border-end-start-radius:var(--border-radius)}`),m=class extends Event{constructor(){super(`confirm`,{bubbles:!1,composed:!1,cancelable:!0})}},h=class extends Event{constructor(){super(`cancel`,{bubbles:!1,composed:!1,cancelable:!0})}},g=class extends d{constructor(...e){super(...e),this.confirmText=`Confirm`,this.cancelText=`Cancel`,this.loading=!1,this._bodyContent=new l(this,`[default]`),this._onFooterClick=e=>{for(let t of e.composedPath()){if(t===e.currentTarget)break;if(t instanceof Element){if(t.matches(`[slot="confirm"], [data-confirm]`))return this._activate(t,new m);if(t.matches(`[slot="cancel"], [data-cancel]`))return this._activate(t,new h)}}}}static{this.styles=[c,u,f,p]}get _role(){return`alertdialog`}get _bodyId(){return this._bodyContent.test(`[default]`)?`description`:``}willUpdate(e){super.willUpdate(e),this.lightDismiss&&=!1}_onCancel(e){this.dispatchEvent(new h)||e.preventDefault()}_focusInitial(){let e=[this.querySelector(`[autofocus]`),this.querySelector(`[slot="cancel"]`),this.renderRoot.querySelector(`[data-cancel]`)];for(let t of e)if(!(!t||t.matches(`[disabled], [aria-disabled="true"]`))&&(t.focus({preventScroll:!0}),t===document.activeElement||t.contains(document.activeElement)))return}_close(){this.dialog.open&&this.dialog.close()}_activate(e,t){e.hasAttribute(`data-confirm`)&&this.loading||e.matches(`[disabled], [aria-disabled="true"]`)||this.dispatchEvent(t)&&this._close()}renderFooter(){let t=this.tone===`danger`?`destructive`:`primary`,n=s(`spinner`);return i`
|
|
2
2
|
<footer
|
|
3
3
|
part="footer"
|
|
4
4
|
@click=${this._onFooterClick}
|
|
@@ -6,7 +6,7 @@ import{i as e}from"../../chunks/lit.js";import{i as t,t as n}from"../../chunks/d
|
|
|
6
6
|
<slot name="cancel">
|
|
7
7
|
<button
|
|
8
8
|
part="button cancel"
|
|
9
|
-
class
|
|
9
|
+
class=${e(`button`)}
|
|
10
10
|
type="button"
|
|
11
11
|
data-cancel
|
|
12
12
|
>
|
|
@@ -16,17 +16,17 @@ import{i as e}from"../../chunks/lit.js";import{i as t,t as n}from"../../chunks/d
|
|
|
16
16
|
<slot name="confirm">
|
|
17
17
|
<button
|
|
18
18
|
part="button confirm"
|
|
19
|
-
class
|
|
19
|
+
class=${e(`button`)}
|
|
20
20
|
type="button"
|
|
21
21
|
data-confirm
|
|
22
|
-
data-variant=${
|
|
23
|
-
aria-disabled=${this.loading?`true`:
|
|
24
|
-
aria-busy=${this.loading?`true`:
|
|
22
|
+
data-variant=${t}
|
|
23
|
+
aria-disabled=${this.loading?`true`:a}
|
|
24
|
+
aria-busy=${this.loading?`true`:a}
|
|
25
25
|
>
|
|
26
|
-
${this.loading?
|
|
26
|
+
${this.loading?o`<${n} aria-hidden="true"></${n}>`:a}
|
|
27
27
|
${this.confirmText}
|
|
28
28
|
</button>
|
|
29
29
|
</slot>
|
|
30
30
|
</footer>
|
|
31
|
-
`}};
|
|
31
|
+
`}};r([n({attribute:`confirm-text`})],g.prototype,`confirmText`,void 0),r([n({attribute:`cancel-text`})],g.prototype,`cancelText`,void 0),r([n({reflect:!0})],g.prototype,`tone`,void 0),r([n({type:Boolean,reflect:!0})],g.prototype,`loading`,void 0);export{g as AlertDialog,h as CancelEvent,m as ConfirmEvent};
|
|
32
32
|
//# sourceMappingURL=alert-dialog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert-dialog.js","names":[],"sources":["../../../src/html/elements/alert-dialog/alert-dialog.css?inline","../../../src/html/elements/alert-dialog/alert-dialog.ts"],"sourcesContent":["/*\n * Layout for l-alert-dialog. Unlayered to match dialog.css (the inherited\n * shared styles) so these overrides win — inside a shadow root there's no host\n * cascade to guard against, and layered rules can't override dialog.css's\n * unlayered ones. The `.l-button` appearance comes from the shared button-core\n * (imported in alert-dialog.ts), not from here.\n *\n * Visual rhythm: a confirmation reads better with the title and description\n * grouped tight and the actions set off in their own bar.\n */\n\n/* Interruptive by nature: a stronger backdrop (the token meant for immersive\n overlays) plus a touch of blur pulls focus firmly onto the confirmation.\n Consumers can still retune --backdrop / --backdrop-blur. */\n:host {\n --backdrop: var(--l-backdrop-strong);\n --backdrop-blur: 2px;\n}\n\n/* Pull the description up close under the title (title keeps the inherited\n 1.125rem / 600 from dialog.css). */\n[part='header'] {\n padding-block-end: 0.5rem;\n}\n\n/* Muted, smaller description with a comfortable reading measure, plus its own\n block-end gap so the footer divider isn't crowded. */\n[part='body'] {\n padding-block-end: var(--padding);\n color: var(--l-color-text-secondary);\n font-size: var(--l-text-sm);\n line-height: 1.5;\n}\n\n/* Footer as a distinct action bar: subtle fill + top divider, corners rounded\n to match the dialog (which doesn't clip its overflow). */\n[part='footer'] {\n padding: 1rem var(--padding);\n border-top: 1px solid var(--l-color-border);\n background: var(--l-color-bg-fill-neutral-subtle);\n border-end-start-radius: var(--border-radius);\n border-end-end-radius: var(--border-radius);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport { HasSlotController } from '../../shared/controllers/has-slot-controller.js';\nimport { Dialog } from '../dialog/dialog.js';\nimport dialogStyles from '../dialog/dialog.styles.js';\nimport buttonStyles from '../../shared/styles/button-core.styles.js';\nimport rawAlertStyles from './alert-dialog.css?inline';\nimport '../spinner/index.js';\n\n// The built-in actions render as `.l-button`, styled inside the shadow root from\n// the SAME shared button-core as the light-DOM `.l-button` (single source of\n// truth — no copy to drift). alert-dialog.css adds only the dialog layout.\nconst alertStyles = unsafeCSS(rawAlertStyles);\n\n/**\n * Fired when the user accepts an alert dialog (the confirm action). Cancelable —\n * call `event.preventDefault()` to keep the dialog open (e.g. to await an async\n * task); close it yourself afterwards by setting `open = false`.\n *\n * Does not bubble and is not composed: listen on the element itself.\n */\nexport class ConfirmEvent extends Event {\n constructor() {\n super('confirm', { bubbles: false, composed: false, cancelable: true });\n }\n}\n\n/**\n * Fired when the user dismisses an alert dialog (the cancel action, Escape, or a\n * close request). Cancelable — call `event.preventDefault()` to keep it open.\n *\n * Does not bubble and is not composed: listen on the element itself.\n */\nexport class CancelEvent extends Event {\n constructor() {\n super('cancel', { bubbles: false, composed: false, cancelable: true });\n }\n}\n\n/**\n * An interruptive confirmation dialog built on `<l-dialog>`. Renders its own\n * cancel/confirm actions and exposes `role=\"alertdialog\"`. Use it to confirm a\n * consequential action (delete, publish, discard) — not for generic content.\n *\n * Open and close by toggling the `open` property (or the `--show` / `--hide`\n * Invoker commands). The dialog closes automatically when the user picks an\n * action, unless the matching `confirm` / `cancel` event is canceled.\n *\n * @slot - Description text. Provides the dialog's accessible description.\n * @slot title - Custom heading element. Overrides the default `<h2>` rendered from the `title` property. Also provides the dialog's accessible name.\n * @slot cancel - Replaces the built-in cancel action (e.g. a link). Keep it focusable.\n * @slot confirm - Replaces the built-in confirm action.\n *\n * @csspart dialog - The native `<dialog>` element.\n * @csspart header - The header wrapper containing the title.\n * @csspart title - The dialog title heading.\n * @csspart body - The body wrapper around the description.\n * @csspart footer - The footer wrapper around the actions.\n * @csspart button - Both built-in action buttons.\n * @csspart cancel - The built-in cancel button.\n * @csspart confirm - The built-in confirm button.\n *\n * @cssproperty [--width=31rem] - Dialog width.\n * @cssproperty [--border-radius=6px] - Dialog border radius.\n * @cssproperty [--padding=1.5rem] - Padding applied to the header, footer, and inline-padding of the body.\n * @cssproperty [--show-duration=200ms] - Open transition duration.\n * @cssproperty [--hide-duration=200ms] - Close transition duration.\n * @cssproperty --backdrop - Backdrop color.\n * @cssproperty [--backdrop-blur=0] - Backdrop blur amount (any CSS length).\n *\n * @event confirm - Fired when the user accepts. Cancelable — `preventDefault()` keeps it open for async work.\n * @event cancel - Fired when the user dismisses (cancel action, Escape). Cancelable — `preventDefault()` keeps it open.\n * @event show - Fired when the dialog is about to open. Cancelable.\n * @event hide - Fired when the dialog is about to close via the `open` property. Cancelable. (The confirm, cancel, and Escape paths close natively and emit `confirm`/`cancel` instead of `hide`.)\n * @event after-show - Fired after the open animation completes. Not cancelable.\n * @event after-hide - Fired after the close animation completes. Not cancelable.\n *\n * @command --show - Sets `open = true`.\n * @command --hide - Sets `open = false`.\n *\n * @customElement l-alert-dialog\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below.\nexport class AlertDialog extends Dialog {\n static override styles = [hostStyles, dialogStyles, buttonStyles, alertStyles];\n\n /** Label for the confirm action. */\n @property({ attribute: 'confirm-text' })\n confirmText = 'Confirm';\n\n /** Label for the cancel action. */\n @property({ attribute: 'cancel-text' })\n cancelText = 'Cancel';\n\n /** Visual tone. `danger` renders the confirm action as destructive. */\n @property({ reflect: true })\n tone?: 'danger';\n\n /** Show a busy state on the confirm action (spinner + `aria-disabled`); the\n * cancel action stays operable as an escape hatch. */\n @property({ type: Boolean, reflect: true })\n loading = false;\n\n protected override get _role() {\n return 'alertdialog';\n }\n\n // Wire aria-describedby to the body only when it actually has content, so a\n // title-only confirmation doesn't advertise an empty description.\n private _bodyContent = new HasSlotController(this, '[default]');\n protected override get _bodyId() {\n return this._bodyContent.test('[default]') ? 'description' : '';\n }\n\n // Alert dialogs are interruptive: they are never dismissable by clicking\n // outside, so the inherited `light-dismiss` is forced off (a vetoed dismissal\n // gesture must go through an explicit action).\n protected override willUpdate(changed: PropertyValues<this>) {\n super.willUpdate(changed);\n if (this.lightDismiss) this.lightDismiss = false;\n }\n\n // Native Escape / close request → cancel intent. A vetoed `cancel` keeps the\n // dialog open; otherwise the native close proceeds (emitting `after-hide`).\n protected override _onCancel(e: Event) {\n if (!this.dispatchEvent(new CancelEvent())) e.preventDefault();\n }\n\n // Focus the least destructive action on open: an author `[autofocus]`, else\n // the slotted cancel action, else the built-in cancel button (in shadow DOM,\n // so the base light-DOM lookup can't reach it). Try each in order and keep the\n // first that actually takes focus — this skips a non-focusable slotted cancel\n // without rejecting a focusable one that reports `tabIndex === -1` (a custom\n // element with `delegatesFocus`, `<div role=\"button\" tabindex=\"0\">`, etc.).\n protected override _focusInitial() {\n const candidates = [\n this.querySelector<HTMLElement>('[autofocus]'),\n this.querySelector<HTMLElement>('[slot=\"cancel\"]'),\n this.renderRoot.querySelector<HTMLElement>('[data-cancel]'),\n ];\n for (const el of candidates) {\n if (!el || el.matches('[disabled], [aria-disabled=\"true\"]')) continue;\n el.focus({ preventScroll: true });\n // `document.activeElement` resolves to the shadow host when focus lands\n // inside an open shadow root, so this also covers delegatesFocus.\n if (el === document.activeElement || el.contains(document.activeElement)) return;\n }\n }\n\n private _close() {\n // Close natively rather than via `open = false` so we don't also emit the\n // inherited cancelable `hide` — `confirm` / `cancel` are the intent events.\n if (this.dialog.open) this.dialog.close();\n }\n\n // Activate an action unless its control is disabled. The built-in confirm also\n // respects `loading` via the property — immune to render timing, so a fast\n // second click in the window before `aria-disabled` is flushed can't fire a\n // duplicate confirm mid-async. A slotted action the consumer drives carries no\n // disabled state unless the author adds one, so it stays operable.\n private _activate(el: Element, event: ConfirmEvent | CancelEvent) {\n if (el.hasAttribute('data-confirm') && this.loading) return;\n if (el.matches('[disabled], [aria-disabled=\"true\"]')) return;\n if (this.dispatchEvent(event)) this._close();\n }\n\n // Single delegated handler covering both the built-in buttons (marked with\n // `data-confirm` / `data-cancel`) and slotted replacements (`slot=\"confirm\"` /\n // `slot=\"cancel\"`). `composedPath()` flattens across the slot boundary, so a\n // click on light-DOM slotted content still resolves here.\n private _onFooterClick = (e: Event) => {\n for (const node of e.composedPath()) {\n if (node === e.currentTarget) break;\n if (!(node instanceof Element)) continue;\n if (node.matches('[slot=\"confirm\"], [data-confirm]'))\n return this._activate(node, new ConfirmEvent());\n if (node.matches('[slot=\"cancel\"], [data-cancel]'))\n return this._activate(node, new CancelEvent());\n }\n };\n\n protected override renderFooter() {\n const confirmVariant = this.tone === 'danger' ? 'destructive' : 'primary';\n return html`\n <footer\n part=\"footer\"\n @click=${this._onFooterClick}\n >\n <slot name=\"cancel\">\n <button\n part=\"button cancel\"\n class=\"l-button\"\n type=\"button\"\n data-cancel\n >\n ${this.cancelText}\n </button>\n </slot>\n <slot name=\"confirm\">\n <button\n part=\"button confirm\"\n class=\"l-button\"\n type=\"button\"\n data-confirm\n data-variant=${confirmVariant}\n aria-disabled=${this.loading ? 'true' : nothing}\n aria-busy=${this.loading ? 'true' : nothing}\n >\n ${this.loading ? html`<l-spinner aria-hidden=\"true\"></l-spinner>` : nothing}\n ${this.confirmText}\n </button>\n </slot>\n </footer>\n `;\n }\n}\n\n// `confirm` and `cancel` collide with lib.dom's event maps, so we cannot augment\n// the global map (as the shared lifecycle events do). Declaration-merge typed\n// listener overloads onto the element instead — must come after the class so the\n// CEM analyzer reads the class first.\nexport interface AlertDialog {\n addEventListener(\n type: 'confirm',\n listener: (this: AlertDialog, ev: ConfirmEvent) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: 'cancel',\n listener: (this: AlertDialog, ev: CancelEvent) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener(\n type: 'confirm',\n listener: (this: AlertDialog, ev: ConfirmEvent) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: 'cancel',\n listener: (this: AlertDialog, ev: CancelEvent) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"saCaA,IAAM,EAAc,EAAU,gcAAc,EAS/B,EAAb,cAAkC,KAAM,CACtC,aAAc,CACZ,MAAM,UAAW,CAAE,QAAS,GAAO,SAAU,GAAO,WAAY,EAAK,CAAC,CACxE,CACF,EAQa,EAAb,cAAiC,KAAM,CACrC,aAAc,CACZ,MAAM,SAAU,CAAE,QAAS,GAAO,SAAU,GAAO,WAAY,EAAK,CAAC,CACvE,CACF,EA8Ca,EAAb,cAAiC,CAAO,gDAKxB,0BAID,sBASH,qBAQa,IAAI,EAAkB,KAAM,WAAW,sBA6DpC,GAAa,CACrC,IAAK,IAAM,KAAQ,EAAE,aAAa,EAAG,CACnC,GAAI,IAAS,EAAE,cAAe,MACxB,gBAAgB,QACtB,IAAI,EAAK,QAAQ,kCAAkC,EACjD,OAAO,KAAK,UAAU,EAAM,IAAI,CAAc,EAChD,GAAI,EAAK,QAAQ,gCAAgC,EAC/C,OAAO,KAAK,UAAU,EAAM,IAAI,CAAa,CAFC,CAGlD,CACF,qBA/FyB,CAAC,EAAY,EAAc,EAAc,CAAW,EAmB7E,IAAuB,OAAQ,CAC7B,MAAO,aACT,CAKA,IAAuB,SAAU,CAC/B,OAAO,KAAK,aAAa,KAAK,WAAW,EAAI,cAAgB,EAC/D,CAKA,WAA8B,EAA+B,CAC3D,MAAM,WAAW,CAAO,EACxB,AAAuB,KAAK,eAAe,EAC7C,CAIA,UAA6B,EAAU,CAChC,KAAK,cAAc,IAAI,CAAa,GAAG,EAAE,eAAe,CAC/D,CAQA,eAAmC,CACjC,IAAM,EAAa,CACjB,KAAK,cAA2B,aAAa,EAC7C,KAAK,cAA2B,iBAAiB,EACjD,KAAK,WAAW,cAA2B,eAAe,CAC5D,EACA,IAAK,IAAM,KAAM,EACX,MAAC,GAAM,EAAG,QAAQ,oCAAoC,KAC1D,EAAG,MAAM,CAAE,cAAe,EAAK,CAAC,EAG5B,IAAO,SAAS,eAAiB,EAAG,SAAS,SAAS,aAAa,GAAG,MAE9E,CAEA,QAAiB,CAGX,KAAK,OAAO,MAAM,KAAK,OAAO,MAAM,CAC1C,CAOA,UAAkB,EAAa,EAAmC,CAC5D,EAAG,aAAa,cAAc,GAAK,KAAK,SACxC,EAAG,QAAQ,oCAAoC,GAC/C,KAAK,cAAc,CAAK,GAAG,KAAK,OAAO,CAC7C,CAiBA,cAAkC,CAChC,IAAM,EAAiB,KAAK,OAAS,SAAW,cAAgB,UAChE,MAAO,EAAI;;;iBAGE,KAAK,eAAe;;;;;;;;;cASvB,KAAK,WAAW;;;;;;;;;2BASH,EAAe;4BACd,KAAK,QAAU,OAAS,EAAQ;wBACpC,KAAK,QAAU,OAAS,EAAQ;;cAE1C,KAAK,QAAU,CAAI,6CAA+C,EAAQ;cAC1E,KAAK,YAAY;;;;KAK7B,CACF,KAhIG,EAAS,CAAE,UAAW,cAAe,CAAC,CAAA,EAAA,EAAA,UAAA,cAAA,IAAA,EAAA,KAItC,EAAS,CAAE,UAAW,aAAc,CAAC,CAAA,EAAA,EAAA,UAAA,aAAA,IAAA,EAAA,KAIrC,EAAS,CAAE,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,KAK1B,EAAS,CAAE,KAAM,QAAS,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,UAAA,IAAA,EAAA"}
|
|
1
|
+
{"version":3,"file":"alert-dialog.js","names":[],"sources":["../../../src/html/elements/alert-dialog/alert-dialog.css?inline","../../../src/html/elements/alert-dialog/alert-dialog.ts"],"sourcesContent":["/*\n * Layout for l-alert-dialog. Unlayered to match dialog.css (the inherited\n * shared styles) so these overrides win — inside a shadow root there's no host\n * cascade to guard against, and layered rules can't override dialog.css's\n * unlayered ones. The `.l-button` appearance comes from the shared button-core\n * (imported in alert-dialog.ts), not from here.\n *\n * Visual rhythm: a confirmation reads better with the title and description\n * grouped tight and the actions set off in their own bar.\n */\n\n/* Interruptive by nature: a stronger backdrop (the token meant for immersive\n overlays) plus a touch of blur pulls focus firmly onto the confirmation.\n Consumers can still retune --backdrop / --backdrop-blur. */\n:host {\n --backdrop: var(--l-backdrop-strong);\n --backdrop-blur: 2px;\n}\n\n/* Pull the description up close under the title (title keeps the inherited\n 1.125rem / 600 from dialog.css). */\n[part='header'] {\n padding-block-end: 0.5rem;\n}\n\n/* Muted, smaller description with a comfortable reading measure, plus its own\n block-end gap so the footer divider isn't crowded. */\n[part='body'] {\n padding-block-end: var(--padding);\n color: var(--l-color-text-secondary);\n font-size: var(--l-text-sm);\n line-height: 1.5;\n}\n\n/* Footer as a distinct action bar: subtle fill + top divider, corners rounded\n to match the dialog (which doesn't clip its overflow). */\n[part='footer'] {\n padding: 1rem var(--padding);\n border-top: 1px solid var(--l-color-border);\n background: var(--l-color-bg-fill-neutral-subtle);\n border-end-start-radius: var(--border-radius);\n border-end-end-radius: var(--border-radius);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\n// Only the spinner fragment needs the static tag function; the outer template\n// stays on lit's `html` so `renderFooter()` keeps the return type `Dialog`\n// declares. A static-html result nests inside a regular one just fine.\nimport { html as staticHtml } from 'lit/static-html.js';\nimport { property } from 'lit/decorators.js';\nimport { cls } from '../../registry.js';\nimport { staticTag } from '../../static-tag.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport { HasSlotController } from '../../shared/controllers/has-slot-controller.js';\nimport { Dialog } from '../dialog/dialog.js';\nimport dialogStyles from '../dialog/dialog.styles.js';\nimport buttonStyles from '../../shared/styles/button-core.styles.js';\nimport rawAlertStyles from './alert-dialog.css?inline';\nimport '../spinner/index.js';\n\n// The built-in actions render as `.l-button`, styled inside the shadow root from\n// the SAME shared button-core as the light-DOM `.l-button` (single source of\n// truth — no copy to drift). alert-dialog.css adds only the dialog layout.\n// The class is emitted through `cls('button')` so it tracks a renamed\n// `cssPrefix` — button-core.css ships as a real CSS file the consumer's postcss\n// re-prefixes, and a static literal would desync from it (see button-core.css).\nconst alertStyles = unsafeCSS(rawAlertStyles);\n\n/**\n * Fired when the user accepts an alert dialog (the confirm action). Cancelable —\n * call `event.preventDefault()` to keep the dialog open (e.g. to await an async\n * task); close it yourself afterwards by setting `open = false`.\n *\n * Does not bubble and is not composed: listen on the element itself.\n */\nexport class ConfirmEvent extends Event {\n constructor() {\n super('confirm', { bubbles: false, composed: false, cancelable: true });\n }\n}\n\n/**\n * Fired when the user dismisses an alert dialog (the cancel action, Escape, or a\n * close request). Cancelable — call `event.preventDefault()` to keep it open.\n *\n * Does not bubble and is not composed: listen on the element itself.\n */\nexport class CancelEvent extends Event {\n constructor() {\n super('cancel', { bubbles: false, composed: false, cancelable: true });\n }\n}\n\n/**\n * An interruptive confirmation dialog built on `<l-dialog>`. Renders its own\n * cancel/confirm actions and exposes `role=\"alertdialog\"`. Use it to confirm a\n * consequential action (delete, publish, discard) — not for generic content.\n *\n * Open and close by toggling the `open` property (or the `--show` / `--hide`\n * Invoker commands). The dialog closes automatically when the user picks an\n * action, unless the matching `confirm` / `cancel` event is canceled.\n *\n * @slot - Description text. Provides the dialog's accessible description.\n * @slot title - Custom heading element. Overrides the default `<h2>` rendered from the `title` property. Also provides the dialog's accessible name.\n * @slot cancel - Replaces the built-in cancel action (e.g. a link). Keep it focusable.\n * @slot confirm - Replaces the built-in confirm action.\n *\n * @csspart dialog - The native `<dialog>` element.\n * @csspart header - The header wrapper containing the title.\n * @csspart title - The dialog title heading.\n * @csspart body - The body wrapper around the description.\n * @csspart footer - The footer wrapper around the actions.\n * @csspart button - Both built-in action buttons.\n * @csspart cancel - The built-in cancel button.\n * @csspart confirm - The built-in confirm button.\n *\n * @cssproperty [--width=31rem] - Dialog width.\n * @cssproperty [--border-radius=6px] - Dialog border radius.\n * @cssproperty [--padding=1.5rem] - Padding applied to the header, footer, and inline-padding of the body.\n * @cssproperty [--show-duration=200ms] - Open transition duration.\n * @cssproperty [--hide-duration=200ms] - Close transition duration.\n * @cssproperty --backdrop - Backdrop color.\n * @cssproperty [--backdrop-blur=0] - Backdrop blur amount (any CSS length).\n *\n * @event confirm - Fired when the user accepts. Cancelable — `preventDefault()` keeps it open for async work.\n * @event cancel - Fired when the user dismisses (cancel action, Escape). Cancelable — `preventDefault()` keeps it open.\n * @event show - Fired when the dialog is about to open. Cancelable.\n * @event hide - Fired when the dialog is about to close via the `open` property. Cancelable. (The confirm, cancel, and Escape paths close natively and emit `confirm`/`cancel` instead of `hide`.)\n * @event after-show - Fired after the open animation completes. Not cancelable.\n * @event after-hide - Fired after the close animation completes. Not cancelable.\n *\n * @command --show - Sets `open = true`.\n * @command --hide - Sets `open = false`.\n *\n * @customElement l-alert-dialog\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below.\nexport class AlertDialog extends Dialog {\n static override styles = [hostStyles, dialogStyles, buttonStyles, alertStyles];\n\n /** Label for the confirm action. */\n @property({ attribute: 'confirm-text' })\n confirmText = 'Confirm';\n\n /** Label for the cancel action. */\n @property({ attribute: 'cancel-text' })\n cancelText = 'Cancel';\n\n /** Visual tone. `danger` renders the confirm action as destructive. */\n @property({ reflect: true })\n tone?: 'danger';\n\n /** Show a busy state on the confirm action (spinner + `aria-disabled`); the\n * cancel action stays operable as an escape hatch. */\n @property({ type: Boolean, reflect: true })\n loading = false;\n\n protected override get _role() {\n return 'alertdialog';\n }\n\n // Wire aria-describedby to the body only when it actually has content, so a\n // title-only confirmation doesn't advertise an empty description.\n private _bodyContent = new HasSlotController(this, '[default]');\n protected override get _bodyId() {\n return this._bodyContent.test('[default]') ? 'description' : '';\n }\n\n // Alert dialogs are interruptive: they are never dismissable by clicking\n // outside, so the inherited `light-dismiss` is forced off (a vetoed dismissal\n // gesture must go through an explicit action).\n protected override willUpdate(changed: PropertyValues<this>) {\n super.willUpdate(changed);\n if (this.lightDismiss) this.lightDismiss = false;\n }\n\n // Native Escape / close request → cancel intent. A vetoed `cancel` keeps the\n // dialog open; otherwise the native close proceeds (emitting `after-hide`).\n protected override _onCancel(e: Event) {\n if (!this.dispatchEvent(new CancelEvent())) e.preventDefault();\n }\n\n // Focus the least destructive action on open: an author `[autofocus]`, else\n // the slotted cancel action, else the built-in cancel button (in shadow DOM,\n // so the base light-DOM lookup can't reach it). Try each in order and keep the\n // first that actually takes focus — this skips a non-focusable slotted cancel\n // without rejecting a focusable one that reports `tabIndex === -1` (a custom\n // element with `delegatesFocus`, `<div role=\"button\" tabindex=\"0\">`, etc.).\n protected override _focusInitial() {\n const candidates = [\n this.querySelector<HTMLElement>('[autofocus]'),\n this.querySelector<HTMLElement>('[slot=\"cancel\"]'),\n this.renderRoot.querySelector<HTMLElement>('[data-cancel]'),\n ];\n for (const el of candidates) {\n if (!el || el.matches('[disabled], [aria-disabled=\"true\"]')) continue;\n el.focus({ preventScroll: true });\n // `document.activeElement` resolves to the shadow host when focus lands\n // inside an open shadow root, so this also covers delegatesFocus.\n if (el === document.activeElement || el.contains(document.activeElement)) return;\n }\n }\n\n private _close() {\n // Close natively rather than via `open = false` so we don't also emit the\n // inherited cancelable `hide` — `confirm` / `cancel` are the intent events.\n if (this.dialog.open) this.dialog.close();\n }\n\n // Activate an action unless its control is disabled. The built-in confirm also\n // respects `loading` via the property — immune to render timing, so a fast\n // second click in the window before `aria-disabled` is flushed can't fire a\n // duplicate confirm mid-async. A slotted action the consumer drives carries no\n // disabled state unless the author adds one, so it stays operable.\n private _activate(el: Element, event: ConfirmEvent | CancelEvent) {\n if (el.hasAttribute('data-confirm') && this.loading) return;\n if (el.matches('[disabled], [aria-disabled=\"true\"]')) return;\n if (this.dispatchEvent(event)) this._close();\n }\n\n // Single delegated handler covering both the built-in buttons (marked with\n // `data-confirm` / `data-cancel`) and slotted replacements (`slot=\"confirm\"` /\n // `slot=\"cancel\"`). `composedPath()` flattens across the slot boundary, so a\n // click on light-DOM slotted content still resolves here.\n private _onFooterClick = (e: Event) => {\n for (const node of e.composedPath()) {\n if (node === e.currentTarget) break;\n if (!(node instanceof Element)) continue;\n if (node.matches('[slot=\"confirm\"], [data-confirm]'))\n return this._activate(node, new ConfirmEvent());\n if (node.matches('[slot=\"cancel\"], [data-cancel]'))\n return this._activate(node, new CancelEvent());\n }\n };\n\n protected override renderFooter() {\n const confirmVariant = this.tone === 'danger' ? 'destructive' : 'primary';\n const spinnerTag = staticTag('spinner');\n return html`\n <footer\n part=\"footer\"\n @click=${this._onFooterClick}\n >\n <slot name=\"cancel\">\n <button\n part=\"button cancel\"\n class=${cls('button')}\n type=\"button\"\n data-cancel\n >\n ${this.cancelText}\n </button>\n </slot>\n <slot name=\"confirm\">\n <button\n part=\"button confirm\"\n class=${cls('button')}\n type=\"button\"\n data-confirm\n data-variant=${confirmVariant}\n aria-disabled=${this.loading ? 'true' : nothing}\n aria-busy=${this.loading ? 'true' : nothing}\n >\n ${this.loading\n ? staticHtml`<${spinnerTag} aria-hidden=\"true\"></${spinnerTag}>`\n : nothing}\n ${this.confirmText}\n </button>\n </slot>\n </footer>\n `;\n }\n}\n\n// `confirm` and `cancel` collide with lib.dom's event maps, so we cannot augment\n// the global map (as the shared lifecycle events do). Declaration-merge typed\n// listener overloads onto the element instead — must come after the class so the\n// CEM analyzer reads the class first.\nexport interface AlertDialog {\n addEventListener(\n type: 'confirm',\n listener: (this: AlertDialog, ev: ConfirmEvent) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: 'cancel',\n listener: (this: AlertDialog, ev: CancelEvent) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener(\n type: 'confirm',\n listener: (this: AlertDialog, ev: ConfirmEvent) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: 'cancel',\n listener: (this: AlertDialog, ev: CancelEvent) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"8iBCsBA,IAAM,EAAc,EAAU,gcAAc,EAS/B,EAAb,cAAkC,KAAM,CACtC,aAAc,CACZ,MAAM,UAAW,CAAE,QAAS,GAAO,SAAU,GAAO,WAAY,EAAK,CAAC,CACxE,CACF,EAQa,EAAb,cAAiC,KAAM,CACrC,aAAc,CACZ,MAAM,SAAU,CAAE,QAAS,GAAO,SAAU,GAAO,WAAY,EAAK,CAAC,CACvE,CACF,EA8Ca,EAAb,cAAiC,CAAO,gDAKxB,0BAID,sBASH,qBAQa,IAAI,EAAkB,KAAM,WAAW,sBA6DpC,GAAa,CACrC,IAAK,IAAM,KAAQ,EAAE,aAAa,EAAG,CACnC,GAAI,IAAS,EAAE,cAAe,MACxB,gBAAgB,QACtB,IAAI,EAAK,QAAQ,kCAAkC,EACjD,OAAO,KAAK,UAAU,EAAM,IAAI,CAAc,EAChD,GAAI,EAAK,QAAQ,gCAAgC,EAC/C,OAAO,KAAK,UAAU,EAAM,IAAI,CAAa,CAFC,CAGlD,CACF,qBA/FyB,CAAC,EAAY,EAAc,EAAc,CAAW,EAmB7E,IAAuB,OAAQ,CAC7B,MAAO,aACT,CAKA,IAAuB,SAAU,CAC/B,OAAO,KAAK,aAAa,KAAK,WAAW,EAAI,cAAgB,EAC/D,CAKA,WAA8B,EAA+B,CAC3D,MAAM,WAAW,CAAO,EACxB,AAAuB,KAAK,eAAe,EAC7C,CAIA,UAA6B,EAAU,CAChC,KAAK,cAAc,IAAI,CAAa,GAAG,EAAE,eAAe,CAC/D,CAQA,eAAmC,CACjC,IAAM,EAAa,CACjB,KAAK,cAA2B,aAAa,EAC7C,KAAK,cAA2B,iBAAiB,EACjD,KAAK,WAAW,cAA2B,eAAe,CAC5D,EACA,IAAK,IAAM,KAAM,EACX,MAAC,GAAM,EAAG,QAAQ,oCAAoC,KAC1D,EAAG,MAAM,CAAE,cAAe,EAAK,CAAC,EAG5B,IAAO,SAAS,eAAiB,EAAG,SAAS,SAAS,aAAa,GAAG,MAE9E,CAEA,QAAiB,CAGX,KAAK,OAAO,MAAM,KAAK,OAAO,MAAM,CAC1C,CAOA,UAAkB,EAAa,EAAmC,CAC5D,EAAG,aAAa,cAAc,GAAK,KAAK,SACxC,EAAG,QAAQ,oCAAoC,GAC/C,KAAK,cAAc,CAAK,GAAG,KAAK,OAAO,CAC7C,CAiBA,cAAkC,CAChC,IAAM,EAAiB,KAAK,OAAS,SAAW,cAAgB,UAC1D,EAAa,EAAU,SAAS,EACtC,MAAO,EAAI;;;iBAGE,KAAK,eAAe;;;;;oBAKjB,EAAI,QAAQ,EAAE;;;;cAIpB,KAAK,WAAW;;;;;;oBAMV,EAAI,QAAQ,EAAE;;;2BAGP,EAAe;4BACd,KAAK,QAAU,OAAS,EAAQ;wBACpC,KAAK,QAAU,OAAS,EAAQ;;cAE1C,KAAK,QACH,CAAU,IAAI,EAAW,wBAAwB,EAAW,GAC5D,EAAQ;cACV,KAAK,YAAY;;;;KAK7B,CACF,KAnIG,EAAS,CAAE,UAAW,cAAe,CAAC,CAAA,EAAA,EAAA,UAAA,cAAA,IAAA,EAAA,KAItC,EAAS,CAAE,UAAW,aAAc,CAAC,CAAA,EAAA,EAAA,UAAA,aAAA,IAAA,EAAA,KAIrC,EAAS,CAAE,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,OAAA,IAAA,EAAA,KAK1B,EAAS,CAAE,KAAM,QAAS,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,UAAA,IAAA,EAAA"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{i as e}from"../../chunks/lit.js";import{i as t,r as n,t as r}from"../../chunks/decorate.js";import{a as i,o as a,t as o}from"../../chunks/lit-html.js";import{LuxenElement as s}from"../../shared/luxen-element.js";import
|
|
1
|
+
import{i as e}from"../../chunks/lit.js";import{i as t,r as n,t as r}from"../../chunks/decorate.js";import{a as i,o as a,t as o}from"../../chunks/lit-html.js";import{LuxenElement as s}from"../../shared/luxen-element.js";import{r as c,t as l}from"../../chunks/static-html.js";import u from"../../shared/styles/host.styles.js";var d=e(`:host{--size:40px;--_font-size:calc(var(--size) * .4);width:var(--size);height:var(--size);border-radius:calc(var(--size) * .15);isolation:isolate;vertical-align:middle;color:var(--l-color-text-secondary);flex-shrink:0;justify-content:center;align-items:center;display:inline-flex;position:relative}:host([size=xs]){--size:24px;--_font-size:.75rem}:host([size=sm]){--size:32px;--_font-size:.875rem}:host([size=lg]){--size:48px;--_font-size:1.25rem}:host([size=xl]){--size:56px;--_font-size:1.25rem}img{object-fit:cover;border-radius:inherit;width:100%;height:100%}.initials{margin:auto}.icon{color:currentColor;width:60%;height:60%}.badge{z-index:1;min-width:12px;height:12px;box-shadow:0 0 0 2px var(--l-color-surface);background-color:var(--l-color-surface);font-variant-numeric:tabular-nums;color:var(--l-color-text-primary);border-radius:3px;justify-content:center;align-items:center;padding-inline:2px;font-size:.625rem;font-weight:500;line-height:1;display:flex;position:absolute;bottom:0;right:0;overflow:hidden}.base:after{content:"";border-radius:inherit;pointer-events:none;position:absolute;inset:0;box-shadow:inset 0 0 0 1px var(--lightningcss-light,oklch(0% 0 0/.07))var(--lightningcss-dark,oklch(100% 0 0/.1))}.base{border-radius:calc(var(--size) * .15);background-color:var(--color,var(--l-color-bg-fill-neutral-soft));--_on:calc(255 * ( 1 - sign(.2126 * (r / 255) * (r / 255) + .7152 * (g / 255) * (g / 255) + .0722 * (b / 255) * (b / 255) - .2) ) / 2);width:100%;height:100%;color:var(--text-color,rgb(from var(--color) var(--_on) var(--_on) var(--_on)));font-size:var(--_font-size);text-transform:uppercase;cursor:inherit;border:none;justify-content:center;align-items:center;padding:0;font-weight:500;line-height:1;display:inline-flex}@supports (color:contrast-color(red)){.base{color:var(--text-color,contrast-color(var(--color)))}}button.base{cursor:pointer}button.base:focus-visible{outline:2px solid var(--l-focus-ring);outline-offset:2px}button.base:hover{box-shadow:inset 0 0 0 1.5px var(--l-color-border-interactive)}@container style(--appearance:circle){.base{border-radius:50%}}`);function f(e=``){let t=e.normalize(`NFC`).trim().split(/\s+/).filter(Boolean);if(t.length===0)return``;let n=e=>[...e][0];return(n(t[0])+(t.length>1?n(t[t.length-1]):``)).toUpperCase()}var p=a`<svg class="icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
2
2
|
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />
|
|
3
|
-
</svg>`,m=class extends s{constructor(...e){super(...e),this.src=``,this.name=``,this.size=`md`,this.badge=0,this.interactive=!1,this._hasError=!1,this._onError=()=>{this._hasError=!0}}static{this.styles=[
|
|
3
|
+
</svg>`,m=class extends s{constructor(...e){super(...e),this.src=``,this.name=``,this.size=`md`,this.badge=0,this.interactive=!1,this._hasError=!1,this._onError=()=>{this._hasError=!0}}static{this.styles=[u,d]}willUpdate(e){e.has(`src`)&&(this._hasError=!1)}connectedCallback(){super.connectedCallback(),!this.interactive&&!this.hasAttribute(`role`)&&this.setAttribute(`role`,`img`)}updated(){this.interactive?(this.getAttribute(`role`)===`img`&&this.removeAttribute(`role`),this.removeAttribute(`aria-label`)):(this.hasAttribute(`role`)||this.setAttribute(`role`,`img`),this.name&&this.setAttribute(`aria-label`,this.name));let e=getComputedStyle(this).getPropertyValue(`--appearance`).trim()===`circle`;this.style.borderRadius=e?`50%`:``}get _tag(){return this.interactive?l`button`:l`div`}render(){let e=this.src&&!this._hasError?i`<img
|
|
4
4
|
src=${this.src}
|
|
5
5
|
alt=${this.name||``}
|
|
6
6
|
@error=${this._onError}
|
|
7
|
-
/>`:this.name?i`<span class="initials">${f(this.name)}</span>`:i`<slot>${p}</slot>`;return
|
|
7
|
+
/>`:this.name?i`<span class="initials">${f(this.name)}</span>`:i`<slot>${p}</slot>`;return c`
|
|
8
8
|
<${this._tag}
|
|
9
9
|
class="base"
|
|
10
10
|
part="base"
|
|
@@ -12,6 +12,8 @@ import { LuxenElement } from '../../shared/luxen-element.js';
|
|
|
12
12
|
*
|
|
13
13
|
* @cssproperty --color - Text color.
|
|
14
14
|
*
|
|
15
|
+
* @attribute variant - destructive — Renders the item as a destructive action (red), for delete-style entries.
|
|
16
|
+
*
|
|
15
17
|
* @customElement l-dropdown-item
|
|
16
18
|
*/
|
|
17
19
|
export declare class DropdownItem extends LuxenElement {
|
|
@@ -23,6 +25,8 @@ export declare class DropdownItem extends LuxenElement {
|
|
|
23
25
|
accessor disabled: boolean;
|
|
24
26
|
/** The type of item: `normal` or `checkbox`. */
|
|
25
27
|
accessor type: 'normal' | 'checkbox';
|
|
28
|
+
/** Visual variant. `destructive` marks a delete-style action in red. */
|
|
29
|
+
accessor variant: 'destructive' | undefined;
|
|
26
30
|
/** Whether the checkbox item is checked. */
|
|
27
31
|
accessor checked: boolean;
|
|
28
32
|
/** Whether this item's submenu is open. Managed by the parent `l-dropdown`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-item.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dropdown-item/dropdown-item.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAU7D
|
|
1
|
+
{"version":3,"file":"dropdown-item.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dropdown-item/dropdown-item.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAU7D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAgB,MAAM,4BAAwB;IAE9C,OAAO,CAAC,gBAAgB,CAIrB;IAEH,2CAA2C;IAE3C,QAAQ,CAAC,KAAK,SAAM;IAEpB,yBAAyB;IAEzB,QAAQ,CAAC,QAAQ,UAAS;IAE1B,gDAAgD;IAEhD,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAY;IAEhD,wEAAwE;IAExE,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAa;IAExD,4CAA4C;IAE5C,QAAQ,CAAC,OAAO,UAAS;IAEzB,+EAA+E;IAE/E,QAAQ,CAAC,WAAW,UAAS;IAG7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,2DAA2D;IAC3D,IAAI,UAAU,IAAI,OAAO,CAExB;IAEQ,iBAAiB;IAU1B,OAAO,KAAK,UAAU,GAErB;IAED,2EAA2E;IAC3E,YAAY,IAAI,MAAM;IAUtB,uFAAuF;IACvF,eAAe,IAAI,YAAY,EAAE;IAQjC,oEAAoE;IACpE,WAAW;IAKX,4EAA4E;IAC5E,YAAY;IAKH,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAM9C,OAAO,CAAC,YAAY;YAKN,wBAAwB;IA2BtC,OAAO,CAAC,oBAAoB,CAK1B;IAEO,MAAM;CAiEhB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{tagName as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{i as n,r,t as i}from"../../chunks/decorate.js";import{a,t as o}from"../../chunks/lit-html.js";import{LuxenElement as s}from"../../shared/luxen-element.js";import c from"../../shared/styles/host.styles.js";import{PopoverController as l}from"../../shared/controllers/popover.js";var u=t(`:host{display:block}:host([disabled]){pointer-events:none;opacity:.5}.item{box-sizing:border-box;cursor:pointer;min-block-size:36px;color:var(--l-color-text-primary);white-space:nowrap;text-align:start;border-radius:4px;outline:none;align-items:center;gap:8px;padding:.375rem .5rem;font-size:.875rem;line-height:1.5;display:flex}.item:focus-visible{background:var(--l-color-bg-state-hover);outline:2px solid var(--l-focus-ring);outline-offset:-2px}@media (hover:hover){.item:hover{background:var(--l-color-bg-state-hover)}}:host([submenu-open]) .item{background:var(--l-color-bg-state-hover)}.check{flex-shrink:0;width:16px;display:flex}.submenu-indicator{background-color:currentColor;flex-shrink:0;block-size:16px;inline-size:16px;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 50%/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 50%/contain no-repeat}:host(:dir(rtl)) .submenu-indicator{scale:-1 1}.submenu{inset:unset;box-sizing:border-box;width:max-content;padding:var(--padding,.25rem);border:1px solid var(--l-color-border-overlay);border-radius:var(--border-radius,6px);background:var(--background,var(--l-color-surface-overlay));color:var(--l-color-text-primary);box-shadow:var(--shadow,0 4px 6px -1px #00000014, 0 2px 4px -2px #0000000f);margin:0;font-size:.875rem;line-height:1.5;overflow:visible}slot[name=submenu]::slotted(hr){height:1px;margin-block:var(--padding,.25rem);margin-inline:calc(var(--padding,.25rem) * -1);background:var(--l-color-divider);border:0;display:block}:host(:not([checked])) .check svg{visibility:hidden}::slotted([slot=prefix]),::slotted([slot=suffix]){flex-shrink:0;display:flex}.label{flex:1}`),d=class extends s{constructor(...t){super(...t),this._submenuFloating=new l(this,{getTriggerElement:()=>this,getFloatingElement:()=>this._submenuEl,getArrowElement:()=>null}),this.#e=``,this.#t=!1,this.#n=`normal`,this.#r
|
|
1
|
+
import{tagName as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{i as n,r,t as i}from"../../chunks/decorate.js";import{a,t as o}from"../../chunks/lit-html.js";import{LuxenElement as s}from"../../shared/luxen-element.js";import c from"../../shared/styles/host.styles.js";import{PopoverController as l}from"../../shared/controllers/popover.js";var u=t(`:host{display:block}:host([disabled]){pointer-events:none;opacity:.5}.item{box-sizing:border-box;cursor:pointer;min-block-size:36px;color:var(--l-color-text-primary);white-space:nowrap;text-align:start;border-radius:4px;outline:none;align-items:center;gap:8px;padding:.375rem .5rem;font-size:.875rem;line-height:1.5;display:flex}.item:focus-visible{background:var(--l-color-bg-state-hover);outline:2px solid var(--l-focus-ring);outline-offset:-2px}@media (hover:hover){.item:hover{background:var(--l-color-bg-state-hover)}}:host([submenu-open]) .item{background:var(--l-color-bg-state-hover)}:host([variant=destructive]) .item{color:var(--l-color-text-danger)}:host([variant=destructive]) .item:focus-visible{background:var(--l-color-bg-fill-danger-soft);outline-color:var(--l-color-text-danger)}@media (hover:hover){:host([variant=destructive]) .item:hover{background:var(--l-color-bg-fill-danger-soft)}}.check{flex-shrink:0;width:16px;display:flex}.submenu-indicator{background-color:currentColor;flex-shrink:0;block-size:16px;inline-size:16px;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 50%/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 50%/contain no-repeat}:host(:dir(rtl)) .submenu-indicator{scale:-1 1}.submenu{inset:unset;box-sizing:border-box;width:max-content;padding:var(--padding,.25rem);border:1px solid var(--l-color-border-overlay);border-radius:var(--border-radius,6px);background:var(--background,var(--l-color-surface-overlay));color:var(--l-color-text-primary);box-shadow:var(--shadow,0 4px 6px -1px #00000014, 0 2px 4px -2px #0000000f);margin:0;font-size:.875rem;line-height:1.5;overflow:visible}slot[name=submenu]::slotted(hr){height:1px;margin-block:var(--padding,.25rem);margin-inline:calc(var(--padding,.25rem) * -1);background:var(--l-color-divider);border:0;display:block}:host(:not([checked])) .check svg{visibility:hidden}::slotted([slot=prefix]),::slotted([slot=suffix]){flex-shrink:0;display:flex}.label{flex:1}`),d=class extends s{constructor(...t){super(...t),this._submenuFloating=new l(this,{getTriggerElement:()=>this,getFloatingElement:()=>this._submenuEl,getArrowElement:()=>null}),this.#e=``,this.#t=!1,this.#n=`normal`,this.#r=void 0,this.#i=!1,this.#a=!1,this.#o=!1,this._onSubmenuSlotChange=t=>{let n=t.target;this._hasSubmenu=n.assignedElements().some(t=>t.tagName===e(`dropdown-item`).toUpperCase())}}static{this.styles=[c,u]}#e;get value(){return this.#e}set value(e){this.#e=e}#t;get disabled(){return this.#t}set disabled(e){this.#t=e}#n;get type(){return this.#n}set type(e){this.#n=e}#r;get variant(){return this.#r}set variant(e){this.#r=e}#i;get checked(){return this.#i}set checked(e){this.#i=e}#a;get submenuOpen(){return this.#a}set submenuOpen(e){this.#a=e}#o;get _hasSubmenu(){return this.#o}set _hasSubmenu(e){this.#o=e}get hasSubmenu(){return this._hasSubmenu}connectedCallback(){super.connectedCallback(),this.hasAttribute(`role`)||this.setAttribute(`role`,`none`)}get _submenuEl(){return this.shadowRoot.querySelector(`.submenu`)}getTextLabel(){let e=this.shadowRoot?.querySelector(`slot:not([name])`);return e?e.assignedNodes({flatten:!0}).map(e=>e.textContent??``).join(``).trim():(this.textContent??``).trim()}getSubmenuItems(){let t=this.shadowRoot?.querySelector(`slot[name="submenu"]`);return t?t.assignedElements().filter(t=>t.tagName===e(`dropdown-item`).toUpperCase()&&!t.disabled):[]}openSubmenu(){!this._hasSubmenu||this.disabled||this.submenuOpen||(this.submenuOpen=!0)}closeSubmenu(){for(let e of this.getSubmenuItems())e.closeSubmenu();this.submenuOpen&&=!1}updated(e){e.has(`submenuOpen`)&&this._handleSubmenuOpenChange()}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}async _handleSubmenuOpenChange(){let e=this._submenuEl;if(!e)return;let t={placement:getComputedStyle(this).direction===`rtl`?`left-start`:`right-start`,distance:0,skidding:-parseFloat(getComputedStyle(e).paddingTop||`0`)};if(this.submenuOpen){if(!this.isConnected||(e.showPopover(),await this._submenuFloating.updatePosition(t),!this.submenuOpen))return;await this._submenuFloating.animateShow(e,this._getDuration(`--show-duration`)),this._submenuFloating.startPositioning(t)}else{if(!e.matches(`:popover-open`))return;this._submenuFloating.stopPositioning(),await this._submenuFloating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover()}}render(){let e=this.type===`checkbox`;return a`
|
|
2
2
|
<div
|
|
3
3
|
class="item"
|
|
4
4
|
role=${e?`menuitemcheckbox`:`menuitem`}
|
|
@@ -54,5 +54,5 @@ import{tagName as e}from"../../registry.js";import{i as t}from"../../chunks/lit.
|
|
|
54
54
|
@slotchange=${this._onSubmenuSlotChange}
|
|
55
55
|
></slot>
|
|
56
56
|
</div>
|
|
57
|
-
`}};i([n()],d.prototype,`value`,null),i([n({type:Boolean,reflect:!0})],d.prototype,`disabled`,null),i([n()],d.prototype,`type`,null),i([n({type:Boolean,reflect:!0})],d.prototype,`checked`,null),i([n({type:Boolean,reflect:!0,attribute:`submenu-open`})],d.prototype,`submenuOpen`,null),i([r()],d.prototype,`_hasSubmenu`,null);export{d as DropdownItem};
|
|
57
|
+
`}};i([n()],d.prototype,`value`,null),i([n({type:Boolean,reflect:!0})],d.prototype,`disabled`,null),i([n()],d.prototype,`type`,null),i([n({reflect:!0})],d.prototype,`variant`,null),i([n({type:Boolean,reflect:!0})],d.prototype,`checked`,null),i([n({type:Boolean,reflect:!0,attribute:`submenu-open`})],d.prototype,`submenuOpen`,null),i([r()],d.prototype,`_hasSubmenu`,null);export{d as DropdownItem};
|
|
58
58
|
//# sourceMappingURL=dropdown-item.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-item.js","names":[],"sources":["../../../src/html/elements/dropdown-item/dropdown-item.css?inline","../../../src/html/elements/dropdown-item/dropdown-item.ts"],"sourcesContent":[":host {\n display: block;\n}\n\n:host([disabled]) {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n min-block-size: 36px;\n padding: 0.375rem 0.5rem;\n cursor: pointer;\n outline: none;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--l-color-text-primary);\n white-space: nowrap;\n text-align: start;\n}\n\n.item:focus-visible {\n /* A ring distinct from the hover tint (which is the same low-contrast fill).\n Negative offset keeps the 2px ring inside the row so it never collides with\n the panel edge or a neighboring item. */\n background: var(--l-color-bg-state-hover);\n outline: 2px solid var(--l-focus-ring);\n outline-offset: -2px;\n}\n\n@media (hover: hover) {\n .item:hover {\n background: var(--l-color-bg-state-hover);\n }\n}\n\n/* Parent item stays highlighted while its submenu is open. */\n:host([submenu-open]) .item {\n background: var(--l-color-bg-state-hover);\n}\n\n.check {\n display: flex;\n width: 16px;\n flex-shrink: 0;\n}\n\n/* Inline-end chevron marking an item that opens a submenu. */\n.submenu-indicator {\n inline-size: 16px;\n block-size: 16px;\n flex-shrink: 0;\n background-color: currentColor;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\")\n no-repeat center / contain;\n}\n\n:host(:dir(rtl)) .submenu-indicator {\n scale: -1 1;\n}\n\n/* Submenu panel — mirrors the l-dropdown panel. The public custom properties\n (--padding, --background, --border-radius, --shadow) inherit from the\n ancestor l-dropdown through the light DOM, so a themed dropdown themes its\n submenus too; their fallbacks repeat the dropdown defaults for standalone use. */\n.submenu {\n inset: unset;\n overflow: visible;\n box-sizing: border-box;\n width: max-content;\n padding: var(--padding, 0.25rem);\n margin: 0;\n border: 1px solid var(--l-color-border-overlay);\n border-radius: var(--border-radius, 6px);\n background: var(--background, var(--l-color-surface-overlay));\n color: var(--l-color-text-primary);\n box-shadow: var(--shadow, 0 4px 6px -1px rgb(0 0 0 / 8%), 0 2px 4px -2px rgb(0 0 0 / 6%));\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n/* Lightweight separator alternative to <l-divider> — same dimensions, no extra import. */\nslot[name='submenu']::slotted(hr) {\n display: block;\n height: 1px;\n margin-block: var(--padding, 0.25rem);\n margin-inline: calc(var(--padding, 0.25rem) * -1);\n border: 0;\n background: var(--l-color-divider);\n}\n\n:host(:not([checked])) .check svg {\n visibility: hidden;\n}\n\n::slotted([slot='prefix']),\n::slotted([slot='suffix']) {\n display: flex;\n flex-shrink: 0;\n}\n\n.label {\n flex: 1;\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { property, state } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { tagName } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './dropdown-item.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A menu item for use inside `<l-dropdown>`.\n *\n * @slot - Label text.\n * @slot prefix - Leading content (e.g. icon).\n * @slot suffix - Trailing content.\n * @slot submenu - Nested `l-dropdown-item` elements rendered in a submenu panel anchored to this item. Drop an `<hr>` between them for a separator.\n *\n * @csspart submenu - The floating submenu panel.\n *\n * @cssproperty --color - Text color.\n *\n * @customElement l-dropdown-item\n */\nexport class DropdownItem extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _submenuFloating = new PopoverController(this, {\n getTriggerElement: () => this,\n getFloatingElement: () => this._submenuEl,\n getArrowElement: () => null,\n });\n\n /** The value associated with this item. */\n @property()\n accessor value = '';\n\n /** Disables the item. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /** The type of item: `normal` or `checkbox`. */\n @property()\n accessor type: 'normal' | 'checkbox' = 'normal';\n\n /** Whether the checkbox item is checked. */\n @property({ type: Boolean, reflect: true })\n accessor checked = false;\n\n /** Whether this item's submenu is open. Managed by the parent `l-dropdown`. */\n @property({ type: Boolean, reflect: true, attribute: 'submenu-open' })\n accessor submenuOpen = false;\n\n @state()\n private accessor _hasSubmenu = false;\n\n /** Whether this item has nested `slot=\"submenu\"` items. */\n get hasSubmenu(): boolean {\n return this._hasSubmenu;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n // The host is a generic wrapper between the `role=\"menu\"` panel and the\n // `role=\"menuitem\"` row in its shadow root; `none` removes it from the\n // accessibility tree so the menu owns its menuitems directly (ARIA\n // required-owned-elements). The inner row keeps its menuitem role.\n // Guard preserves a consumer-set role.\n if (!this.hasAttribute('role')) this.setAttribute('role', 'none');\n }\n\n private get _submenuEl(): HTMLElement | null {\n return this.shadowRoot!.querySelector<HTMLElement>('.submenu');\n }\n\n /** Returns the text label of this item (excludes nested submenu items). */\n getTextLabel(): string {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!slot) return (this.textContent ?? '').trim();\n return slot\n .assignedNodes({ flatten: true })\n .map((node) => node.textContent ?? '')\n .join('')\n .trim();\n }\n\n /** Returns the enabled `l-dropdown-item` elements slotted into this item's submenu. */\n getSubmenuItems(): DropdownItem[] {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"submenu\"]');\n if (!slot) return [];\n return (slot.assignedElements() as DropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase() && !el.disabled,\n );\n }\n\n /** Opens this item's submenu panel. No-op without submenu items. */\n openSubmenu() {\n if (!this._hasSubmenu || this.disabled || this.submenuOpen) return;\n this.submenuOpen = true;\n }\n\n /** Closes this item's submenu panel, including any open nested submenus. */\n closeSubmenu() {\n for (const child of this.getSubmenuItems()) child.closeSubmenu();\n if (this.submenuOpen) this.submenuOpen = false;\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('submenuOpen')) {\n void this._handleSubmenuOpenChange();\n }\n }\n\n private _getDuration(prop: '--show-duration' | '--hide-duration'): number {\n const parsed = parseFloat(getComputedStyle(this).getPropertyValue(prop));\n return Number.isNaN(parsed) ? 150 : parsed;\n }\n\n private async _handleSubmenuOpenChange() {\n const panel = this._submenuEl;\n if (!panel) return;\n\n const isRTL = getComputedStyle(this).direction === 'rtl';\n const posOpts = {\n placement: (isRTL ? 'left-start' : 'right-start') as Placement,\n distance: 0,\n // Pull the panel up by its own padding so its first item lines up with this item.\n skidding: -parseFloat(getComputedStyle(panel).paddingTop || '0'),\n };\n\n if (this.submenuOpen) {\n if (!this.isConnected) return;\n panel.showPopover();\n await this._submenuFloating.updatePosition(posOpts);\n if (!this.submenuOpen) return;\n await this._submenuFloating.animateShow(panel, this._getDuration('--show-duration'));\n this._submenuFloating.startPositioning(posOpts);\n } else {\n if (!panel.matches(':popover-open')) return;\n this._submenuFloating.stopPositioning();\n await this._submenuFloating.animateHide(panel, this._getDuration('--hide-duration'));\n if (panel.matches(':popover-open')) panel.hidePopover();\n }\n }\n\n private _onSubmenuSlotChange = (e: Event) => {\n const slot = e.target as HTMLSlotElement;\n this._hasSubmenu = slot\n .assignedElements()\n .some((el) => el.tagName === tagName('dropdown-item').toUpperCase());\n };\n\n override render() {\n const isCheckbox = this.type === 'checkbox';\n\n return html`\n <div\n class=\"item\"\n role=${isCheckbox ? 'menuitemcheckbox' : 'menuitem'}\n aria-checked=${isCheckbox ? String(this.checked) : nothing}\n aria-disabled=${this.disabled ? 'true' : nothing}\n aria-haspopup=${this._hasSubmenu ? 'menu' : nothing}\n aria-expanded=${this._hasSubmenu ? String(this.submenuOpen) : nothing}\n aria-controls=${this._hasSubmenu ? 'submenu' : nothing}\n tabindex=\"-1\"\n >\n ${isCheckbox\n ? html`\n <span\n class=\"check\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n `\n : html` <slot name=\"prefix\"></slot> `}\n <span\n class=\"label\"\n id=\"label\"\n ><slot></slot\n ></span>\n <slot name=\"suffix\"></slot>\n ${this._hasSubmenu\n ? html`<span\n class=\"submenu-indicator\"\n aria-hidden=\"true\"\n ></span>`\n : nothing}\n </div>\n <div\n class=\"submenu\"\n part=\"submenu\"\n id=\"submenu\"\n popover=\"manual\"\n role=\"menu\"\n aria-labelledby=\"label\"\n >\n <slot\n name=\"submenu\"\n @slotchange=${this._onSubmenuSlotChange}\n ></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dropdown-item.js","names":[],"sources":["../../../src/html/elements/dropdown-item/dropdown-item.css?inline","../../../src/html/elements/dropdown-item/dropdown-item.ts"],"sourcesContent":[":host {\n display: block;\n}\n\n:host([disabled]) {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n min-block-size: 36px;\n padding: 0.375rem 0.5rem;\n cursor: pointer;\n outline: none;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--l-color-text-primary);\n white-space: nowrap;\n text-align: start;\n}\n\n.item:focus-visible {\n /* A ring distinct from the hover tint (which is the same low-contrast fill).\n Negative offset keeps the 2px ring inside the row so it never collides with\n the panel edge or a neighboring item. */\n background: var(--l-color-bg-state-hover);\n outline: 2px solid var(--l-focus-ring);\n outline-offset: -2px;\n}\n\n@media (hover: hover) {\n .item:hover {\n background: var(--l-color-bg-state-hover);\n }\n}\n\n/* Parent item stays highlighted while its submenu is open. */\n:host([submenu-open]) .item {\n background: var(--l-color-bg-state-hover);\n}\n\n/* Destructive variant — a delete-style action. Red label at rest, danger-tinted\n fill and focus ring on hover/focus so the highlight reads as \"danger\" rather\n than the neutral hover tint. */\n:host([variant='destructive']) .item {\n color: var(--l-color-text-danger);\n}\n\n:host([variant='destructive']) .item:focus-visible {\n background: var(--l-color-bg-fill-danger-soft);\n outline-color: var(--l-color-text-danger);\n}\n\n@media (hover: hover) {\n :host([variant='destructive']) .item:hover {\n background: var(--l-color-bg-fill-danger-soft);\n }\n}\n\n.check {\n display: flex;\n width: 16px;\n flex-shrink: 0;\n}\n\n/* Inline-end chevron marking an item that opens a submenu. */\n.submenu-indicator {\n inline-size: 16px;\n block-size: 16px;\n flex-shrink: 0;\n background-color: currentColor;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\")\n no-repeat center / contain;\n}\n\n:host(:dir(rtl)) .submenu-indicator {\n scale: -1 1;\n}\n\n/* Submenu panel — mirrors the l-dropdown panel. The public custom properties\n (--padding, --background, --border-radius, --shadow) inherit from the\n ancestor l-dropdown through the light DOM, so a themed dropdown themes its\n submenus too; their fallbacks repeat the dropdown defaults for standalone use. */\n.submenu {\n inset: unset;\n overflow: visible;\n box-sizing: border-box;\n width: max-content;\n padding: var(--padding, 0.25rem);\n margin: 0;\n border: 1px solid var(--l-color-border-overlay);\n border-radius: var(--border-radius, 6px);\n background: var(--background, var(--l-color-surface-overlay));\n color: var(--l-color-text-primary);\n box-shadow: var(--shadow, 0 4px 6px -1px rgb(0 0 0 / 8%), 0 2px 4px -2px rgb(0 0 0 / 6%));\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n/* Lightweight separator alternative to <l-divider> — same dimensions, no extra import. */\nslot[name='submenu']::slotted(hr) {\n display: block;\n height: 1px;\n margin-block: var(--padding, 0.25rem);\n margin-inline: calc(var(--padding, 0.25rem) * -1);\n border: 0;\n background: var(--l-color-divider);\n}\n\n:host(:not([checked])) .check svg {\n visibility: hidden;\n}\n\n::slotted([slot='prefix']),\n::slotted([slot='suffix']) {\n display: flex;\n flex-shrink: 0;\n}\n\n.label {\n flex: 1;\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { property, state } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { tagName } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './dropdown-item.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A menu item for use inside `<l-dropdown>`.\n *\n * @slot - Label text.\n * @slot prefix - Leading content (e.g. icon).\n * @slot suffix - Trailing content.\n * @slot submenu - Nested `l-dropdown-item` elements rendered in a submenu panel anchored to this item. Drop an `<hr>` between them for a separator.\n *\n * @csspart submenu - The floating submenu panel.\n *\n * @cssproperty --color - Text color.\n *\n * @attribute variant - destructive — Renders the item as a destructive action (red), for delete-style entries.\n *\n * @customElement l-dropdown-item\n */\nexport class DropdownItem extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _submenuFloating = new PopoverController(this, {\n getTriggerElement: () => this,\n getFloatingElement: () => this._submenuEl,\n getArrowElement: () => null,\n });\n\n /** The value associated with this item. */\n @property()\n accessor value = '';\n\n /** Disables the item. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /** The type of item: `normal` or `checkbox`. */\n @property()\n accessor type: 'normal' | 'checkbox' = 'normal';\n\n /** Visual variant. `destructive` marks a delete-style action in red. */\n @property({ reflect: true })\n accessor variant: 'destructive' | undefined = undefined;\n\n /** Whether the checkbox item is checked. */\n @property({ type: Boolean, reflect: true })\n accessor checked = false;\n\n /** Whether this item's submenu is open. Managed by the parent `l-dropdown`. */\n @property({ type: Boolean, reflect: true, attribute: 'submenu-open' })\n accessor submenuOpen = false;\n\n @state()\n private accessor _hasSubmenu = false;\n\n /** Whether this item has nested `slot=\"submenu\"` items. */\n get hasSubmenu(): boolean {\n return this._hasSubmenu;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n // The host is a generic wrapper between the `role=\"menu\"` panel and the\n // `role=\"menuitem\"` row in its shadow root; `none` removes it from the\n // accessibility tree so the menu owns its menuitems directly (ARIA\n // required-owned-elements). The inner row keeps its menuitem role.\n // Guard preserves a consumer-set role.\n if (!this.hasAttribute('role')) this.setAttribute('role', 'none');\n }\n\n private get _submenuEl(): HTMLElement | null {\n return this.shadowRoot!.querySelector<HTMLElement>('.submenu');\n }\n\n /** Returns the text label of this item (excludes nested submenu items). */\n getTextLabel(): string {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!slot) return (this.textContent ?? '').trim();\n return slot\n .assignedNodes({ flatten: true })\n .map((node) => node.textContent ?? '')\n .join('')\n .trim();\n }\n\n /** Returns the enabled `l-dropdown-item` elements slotted into this item's submenu. */\n getSubmenuItems(): DropdownItem[] {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"submenu\"]');\n if (!slot) return [];\n return (slot.assignedElements() as DropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase() && !el.disabled,\n );\n }\n\n /** Opens this item's submenu panel. No-op without submenu items. */\n openSubmenu() {\n if (!this._hasSubmenu || this.disabled || this.submenuOpen) return;\n this.submenuOpen = true;\n }\n\n /** Closes this item's submenu panel, including any open nested submenus. */\n closeSubmenu() {\n for (const child of this.getSubmenuItems()) child.closeSubmenu();\n if (this.submenuOpen) this.submenuOpen = false;\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('submenuOpen')) {\n void this._handleSubmenuOpenChange();\n }\n }\n\n private _getDuration(prop: '--show-duration' | '--hide-duration'): number {\n const parsed = parseFloat(getComputedStyle(this).getPropertyValue(prop));\n return Number.isNaN(parsed) ? 150 : parsed;\n }\n\n private async _handleSubmenuOpenChange() {\n const panel = this._submenuEl;\n if (!panel) return;\n\n const isRTL = getComputedStyle(this).direction === 'rtl';\n const posOpts = {\n placement: (isRTL ? 'left-start' : 'right-start') as Placement,\n distance: 0,\n // Pull the panel up by its own padding so its first item lines up with this item.\n skidding: -parseFloat(getComputedStyle(panel).paddingTop || '0'),\n };\n\n if (this.submenuOpen) {\n if (!this.isConnected) return;\n panel.showPopover();\n await this._submenuFloating.updatePosition(posOpts);\n if (!this.submenuOpen) return;\n await this._submenuFloating.animateShow(panel, this._getDuration('--show-duration'));\n this._submenuFloating.startPositioning(posOpts);\n } else {\n if (!panel.matches(':popover-open')) return;\n this._submenuFloating.stopPositioning();\n await this._submenuFloating.animateHide(panel, this._getDuration('--hide-duration'));\n if (panel.matches(':popover-open')) panel.hidePopover();\n }\n }\n\n private _onSubmenuSlotChange = (e: Event) => {\n const slot = e.target as HTMLSlotElement;\n this._hasSubmenu = slot\n .assignedElements()\n .some((el) => el.tagName === tagName('dropdown-item').toUpperCase());\n };\n\n override render() {\n const isCheckbox = this.type === 'checkbox';\n\n return html`\n <div\n class=\"item\"\n role=${isCheckbox ? 'menuitemcheckbox' : 'menuitem'}\n aria-checked=${isCheckbox ? String(this.checked) : nothing}\n aria-disabled=${this.disabled ? 'true' : nothing}\n aria-haspopup=${this._hasSubmenu ? 'menu' : nothing}\n aria-expanded=${this._hasSubmenu ? String(this.submenuOpen) : nothing}\n aria-controls=${this._hasSubmenu ? 'submenu' : nothing}\n tabindex=\"-1\"\n >\n ${isCheckbox\n ? html`\n <span\n class=\"check\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n `\n : html` <slot name=\"prefix\"></slot> `}\n <span\n class=\"label\"\n id=\"label\"\n ><slot></slot\n ></span>\n <slot name=\"suffix\"></slot>\n ${this._hasSubmenu\n ? html`<span\n class=\"submenu-indicator\"\n aria-hidden=\"true\"\n ></span>`\n : nothing}\n </div>\n <div\n class=\"submenu\"\n part=\"submenu\"\n id=\"submenu\"\n popover=\"manual\"\n role=\"menu\"\n aria-labelledby=\"label\"\n >\n <slot\n name=\"submenu\"\n @slotchange=${this._onSubmenuSlotChange}\n ></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-field.d.ts","sourceRoot":"","sources":["../../../src/html/elements/form-field/form-field.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAU7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,SAAU,SAAQ,YAAY;IAChC,gBAAgB;IAIzB,4HAA4H;IAE5H,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAE9B,uEAAuE;IAEvE,QAAQ,UAAS;IAEjB,kDAAkD;IAElD,QAAQ,UAAS;IAEjB,mGAAmG;IAEnG,OAAO,UAAS;IAEhB,yEAAyE;IAEzE,QAAQ,UAAS;IAEjB,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,MAAM,CAAS;IACvB,+DAA+D;IAC/D,OAAO,CAAC,oBAAoB,CAAgB;IAEnC,iBAAiB;IAoBjB,oBAAoB;IAM7B,OAAO,CAAC,MAAM;
|
|
1
|
+
{"version":3,"file":"form-field.d.ts","sourceRoot":"","sources":["../../../src/html/elements/form-field/form-field.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAU7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,SAAU,SAAQ,YAAY;IAChC,gBAAgB;IAIzB,4HAA4H;IAE5H,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAE9B,uEAAuE;IAEvE,QAAQ,UAAS;IAEjB,kDAAkD;IAElD,QAAQ,UAAS;IAEjB,mGAAmG;IAEnG,OAAO,UAAS;IAEhB,yEAAyE;IAEzE,QAAQ,UAAS;IAEjB,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,MAAM,CAAS;IACvB,+DAA+D;IAC/D,OAAO,CAAC,oBAAoB,CAAgB;IAEnC,iBAAiB;IAoBjB,oBAAoB;IAM7B,OAAO,CAAC,MAAM;IA8Dd;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,UAAU,CAKhB;IAEF,OAAO,CAAC,WAAW,CAGjB;IAEF,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,kBAAkB;CAS3B"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{uniqueId as
|
|
1
|
+
import{cls as e,uniqueId as t}from"../../registry.js";import{i as n,t as r}from"../../chunks/decorate.js";import{LuxenElement as i}from"../../shared/luxen-element.js";var a=`input, select, textarea`,o=class extends i{constructor(...e){super(...e),this.required=!1,this.optional=!1,this.invalid=!1,this.unstyled=!1,this._control=null,this._hint=null,this._error=null,this._hasInteracted=!1,this._wired=!1,this._externalDescribedby=[],this._onInvalid=e=>{e.preventDefault(),this._hasInteracted=!0,this._updateValidity()},this._onInteract=()=>{this._hasInteracted=!0,this._updateValidity()}}createRenderRoot(){return this}connectedCallback(){if(super.connectedCallback(),!this._setup()){setTimeout(()=>this._setup(),0);for(let e of this.querySelectorAll(`*`)){let t=e.localName;t.includes(`-`)&&!customElements.get(t)&&customElements.whenDefined(t).then(()=>this._setup())}}}disconnectedCallback(){super.disconnectedCallback(),this._teardown(),this._wired=!1}_setup(){if(this._wired)return!0;if(this._control=this._findControl(),!this._control)return!1;this._wired=!0;let n=this._control;this._hint=this.querySelector(`.${e(`hint`)}`),this._error=this.querySelector(`.${e(`error`)}`),this._error&&!this._error.hasAttribute(`role`)&&this._error.setAttribute(`role`,`alert`),n.id||=t(`field`);let r=this.querySelector(`label`);r&&!r.htmlFor&&!r.contains(n)&&(r.htmlFor=n.id),this._hint&&!this._hint.id&&(this._hint.id=t(`hint`)),this._error&&!this._error.id&&(this._error.id=t(`error`));let i=new Set([this._hint?.id,this._error?.id].filter(Boolean));if(this._externalDescribedby=(n.getAttribute(`aria-describedby`)??``).split(/\s+/).filter(e=>e&&!i.has(e)),!this.layout){let e=n.tagName===`INPUT`&&([`checkbox`,`radio`].includes(n.type)||n.getAttribute(`role`)===`switch`);this.layout=e?`inline`:`stacked`}this.required=n.hasAttribute(`required`),this.optional=!this.required;let a=this.invalid||n.getAttribute(`aria-invalid`)===`true`;return this._hasInteracted=a,n.addEventListener(`invalid`,this._onInvalid),n.addEventListener(`blur`,this._onInteract),n.addEventListener(`change`,this._onInteract),n.addEventListener(`input`,this._onInteract),this._updateValidity(),!0}_findControl(){let e=this.querySelector(a);if(e)return e;for(let e of this.querySelectorAll(`*`))if(e.constructor.formAssociated)return e;return null}_teardown(){let e=this._control;e&&(e.removeEventListener(`invalid`,this._onInvalid),e.removeEventListener(`blur`,this._onInteract),e.removeEventListener(`change`,this._onInteract),e.removeEventListener(`input`,this._onInteract))}_updateValidity(){let e=this._control;if(!e)return;let t=this._hasInteracted&&!e.checkValidity();this.invalid=t,t?e.setAttribute(`aria-invalid`,`true`):e.removeAttribute(`aria-invalid`),this._updateDescribedby()}_updateDescribedby(){let e=this._control;if(!e)return;let t=[...this._externalDescribedby];this._hint?.id&&t.push(this._hint.id),this._error&&this.invalid&&this._error.id&&t.push(this._error.id),t.length?e.setAttribute(`aria-describedby`,t.join(` `)):e.removeAttribute(`aria-describedby`)}};r([n({reflect:!0})],o.prototype,`layout`,void 0),r([n({type:Boolean,reflect:!0})],o.prototype,`required`,void 0),r([n({type:Boolean,reflect:!0})],o.prototype,`optional`,void 0),r([n({type:Boolean,reflect:!0})],o.prototype,`invalid`,void 0),r([n({type:Boolean,reflect:!0})],o.prototype,`unstyled`,void 0);export{o as FormField};
|
|
2
2
|
//# sourceMappingURL=form-field.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-field.js","names":[],"sources":["../../../src/html/elements/form-field/form-field.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { uniqueId } from '../../registry.js';\n\ntype NativeControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;\n/** A form-associated custom element (e.g. `l-slider`) exposes the same constraint\n * surface as a native control but doesn't match the native selector. */\ntype FormControl = NativeControl | (HTMLElement & { checkValidity(): boolean });\n\nconst CONTROL_SELECTOR = 'input, select, textarea';\n\n/**\n * @summary Progressively enhances a label + control + messages group: wires the\n * accessibility plumbing (`id`/`for`, `aria-describedby`, `aria-invalid`,\n * required marker) and picks an inline or stacked layout from the control type.\n *\n * Styling stays on the control (`.l-checkbox`, …); inside a field a bare control\n * is auto-styled by the element CSS. Add `unstyled` to keep the ARIA wiring while\n * opting out of that auto-styling (e.g. for a third-party control).\n *\n * @example\n * ```html\n * <l-form-field>\n * <label>Subscribe to the newsletter</label>\n * <input type=\"checkbox\" />\n * <p class=\"l-hint\">One email a month, unsubscribe anytime.</p>\n * <p class=\"l-error\">Please make a choice to continue.</p>\n * </l-form-field>\n * ```\n *\n * @slot - A `<label>`, one form control (a native `input` / `select` /\n * `textarea`, or a form-associated custom element such as `l-slider`), and\n * optional `.l-hint` / `.l-error` message elements, in any order.\n *\n * @cssClass .l-hint - Helper text element. Always visible; linked via `aria-describedby`.\n * @cssClass .l-error - Error message element. Hidden until invalid, then revealed with `role=\"alert\"` and linked via `aria-describedby`.\n *\n * @customElement l-form-field\n */\nexport class FormField extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n /** Layout, derived from the control type (`inline` for checkbox/radio/switch, otherwise `stacked`). Set one to force it. */\n @property({ reflect: true })\n layout?: 'inline' | 'stacked';\n\n /** Reflected when the control is required. Drives the label marker. */\n @property({ type: Boolean, reflect: true })\n required = false;\n\n /** Reflected when the control is not required. */\n @property({ type: Boolean, reflect: true })\n optional = false;\n\n /** Reflected once the control is invalid after interaction (or set to force the invalid state). */\n @property({ type: Boolean, reflect: true })\n invalid = false;\n\n /** Opt out of auto-styling the control; the ARIA wiring is preserved. */\n @property({ type: Boolean, reflect: true })\n unstyled = false;\n\n private _control: FormControl | null = null;\n private _hint: HTMLElement | null = null;\n private _error: HTMLElement | null = null;\n private _hasInteracted = false;\n private _wired = false;\n /** describedby ids set by the author that we must preserve. */\n private _externalDescribedby: string[] = [];\n\n override connectedCallback() {\n super.connectedCallback();\n // Run synchronously when the children are already parsed (the common case:\n // the element is defined after its markup). Fall back to a macrotask — which,\n // unlike requestAnimationFrame, still fires in a hidden tab — if the element\n // upgrades mid-parse before its children exist.\n if (this._setup()) return;\n setTimeout(() => this._setup(), 0);\n // A custom-element control (e.g. l-slider) can upgrade after us — its module\n // may load lazily — so the macrotask retry can still miss it. Re-attempt once\n // each pending custom-element child is defined. `_setup()` is idempotent via\n // `_wired`, so the earliest successful attempt wins.\n for (const el of this.querySelectorAll('*')) {\n const tag = el.localName;\n if (tag.includes('-') && !customElements.get(tag)) {\n void customElements.whenDefined(tag).then(() => this._setup());\n }\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._teardown();\n this._wired = false;\n }\n\n private _setup(): boolean {\n if (this._wired) return true;\n this._control = this._findControl();\n if (!this._control) return false;\n this._wired = true;\n const control = this._control;\n\n this._hint = this.querySelector('.l-hint');\n this._error = this.querySelector('.l-error');\n // The error is announced when revealed; the field owns this (the message is\n // a plain element with no JS of its own).\n if (this._error && !this._error.hasAttribute('role')) {\n this._error.setAttribute('role', 'alert');\n }\n\n // id / label[for]\n if (!control.id) control.id = uniqueId('field');\n const label = this.querySelector('label');\n if (label && !label.htmlFor && !label.contains(control)) {\n label.htmlFor = control.id;\n }\n\n // Ensure message elements have ids for aria-describedby.\n if (this._hint && !this._hint.id) this._hint.id = uniqueId('hint');\n if (this._error && !this._error.id) this._error.id = uniqueId('error');\n\n // Capture author-provided describedby ids (minus our managed ones).\n const managed = new Set([this._hint?.id, this._error?.id].filter(Boolean) as string[]);\n this._externalDescribedby = (control.getAttribute('aria-describedby') ?? '')\n .split(/\\s+/)\n .filter((id) => id && !managed.has(id));\n\n // Layout from control type (unless explicitly forced).\n if (!this.layout) {\n const isToggle =\n control.tagName === 'INPUT' &&\n (['checkbox', 'radio'].includes((control as HTMLInputElement).type) ||\n control.getAttribute('role') === 'switch');\n this.layout = isToggle ? 'inline' : 'stacked';\n }\n\n // Required marker (control is the source of truth).\n this.required = control.hasAttribute('required');\n this.optional = !this.required;\n\n // The error stays hidden until the field is invalid after interaction (or is\n // already flagged invalid by the author / server). Visibility is owned by CSS\n // off the reflected `invalid` state — the field never toggles `hidden`.\n const preInvalid = this.invalid || control.getAttribute('aria-invalid') === 'true';\n this._hasInteracted = preInvalid;\n\n control.addEventListener('invalid', this._onInvalid);\n control.addEventListener('blur', this._onInteract);\n control.addEventListener('change', this._onInteract);\n control.addEventListener('input', this._onInteract);\n\n this._updateValidity();\n return true;\n }\n\n /**\n * The field's control: a native `input`/`select`/`textarea`, or — when none is\n * present — the first form-associated custom element (e.g. `l-slider`). Those\n * expose the same `checkValidity()` / `invalid` event surface but don't match\n * the native selector, so without this fallback the field never wires up and\n * the `l-error` message stays visible on load.\n */\n private _findControl(): FormControl | null {\n const native = this.querySelector<NativeControl>(CONTROL_SELECTOR);\n if (native) return native;\n for (const el of this.querySelectorAll<HTMLElement>('*')) {\n if ((el.constructor as { formAssociated?: boolean }).formAssociated) {\n return el as FormControl;\n }\n }\n return null;\n }\n\n private _teardown() {\n const control = this._control;\n if (!control) return;\n control.removeEventListener('invalid', this._onInvalid);\n control.removeEventListener('blur', this._onInteract);\n control.removeEventListener('change', this._onInteract);\n control.removeEventListener('input', this._onInteract);\n }\n\n private _onInvalid = (event: Event) => {\n // Suppress the native validation bubble in favour of `l-error`.\n event.preventDefault();\n this._hasInteracted = true;\n this._updateValidity();\n };\n\n private _onInteract = () => {\n this._hasInteracted = true;\n this._updateValidity();\n };\n\n private _updateValidity() {\n const control = this._control;\n if (!control) return;\n const showError = this._hasInteracted && !control.checkValidity();\n\n // `invalid` reflects to the host; CSS reveals `.l-error` from there.\n this.invalid = showError;\n if (showError) control.setAttribute('aria-invalid', 'true');\n else control.removeAttribute('aria-invalid');\n\n this._updateDescribedby();\n }\n\n private _updateDescribedby() {\n const control = this._control;\n if (!control) return;\n const ids = [...this._externalDescribedby];\n if (this._hint?.id) ids.push(this._hint.id);\n if (this._error && this.invalid && this._error.id) ids.push(this._error.id);\n if (ids.length) control.setAttribute('aria-describedby', ids.join(' '));\n else control.removeAttribute('aria-describedby');\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"form-field.js","names":[],"sources":["../../../src/html/elements/form-field/form-field.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { cls, uniqueId } from '../../registry.js';\n\ntype NativeControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;\n/** A form-associated custom element (e.g. `l-slider`) exposes the same constraint\n * surface as a native control but doesn't match the native selector. */\ntype FormControl = NativeControl | (HTMLElement & { checkValidity(): boolean });\n\nconst CONTROL_SELECTOR = 'input, select, textarea';\n\n/**\n * @summary Progressively enhances a label + control + messages group: wires the\n * accessibility plumbing (`id`/`for`, `aria-describedby`, `aria-invalid`,\n * required marker) and picks an inline or stacked layout from the control type.\n *\n * Styling stays on the control (`.l-checkbox`, …); inside a field a bare control\n * is auto-styled by the element CSS. Add `unstyled` to keep the ARIA wiring while\n * opting out of that auto-styling (e.g. for a third-party control).\n *\n * @example\n * ```html\n * <l-form-field>\n * <label>Subscribe to the newsletter</label>\n * <input type=\"checkbox\" />\n * <p class=\"l-hint\">One email a month, unsubscribe anytime.</p>\n * <p class=\"l-error\">Please make a choice to continue.</p>\n * </l-form-field>\n * ```\n *\n * @slot - A `<label>`, one form control (a native `input` / `select` /\n * `textarea`, or a form-associated custom element such as `l-slider`), and\n * optional `.l-hint` / `.l-error` message elements, in any order.\n *\n * @cssClass .l-hint - Helper text element. Always visible; linked via `aria-describedby`.\n * @cssClass .l-error - Error message element. Hidden until invalid, then revealed with `role=\"alert\"` and linked via `aria-describedby`.\n *\n * @customElement l-form-field\n */\nexport class FormField extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n /** Layout, derived from the control type (`inline` for checkbox/radio/switch, otherwise `stacked`). Set one to force it. */\n @property({ reflect: true })\n layout?: 'inline' | 'stacked';\n\n /** Reflected when the control is required. Drives the label marker. */\n @property({ type: Boolean, reflect: true })\n required = false;\n\n /** Reflected when the control is not required. */\n @property({ type: Boolean, reflect: true })\n optional = false;\n\n /** Reflected once the control is invalid after interaction (or set to force the invalid state). */\n @property({ type: Boolean, reflect: true })\n invalid = false;\n\n /** Opt out of auto-styling the control; the ARIA wiring is preserved. */\n @property({ type: Boolean, reflect: true })\n unstyled = false;\n\n private _control: FormControl | null = null;\n private _hint: HTMLElement | null = null;\n private _error: HTMLElement | null = null;\n private _hasInteracted = false;\n private _wired = false;\n /** describedby ids set by the author that we must preserve. */\n private _externalDescribedby: string[] = [];\n\n override connectedCallback() {\n super.connectedCallback();\n // Run synchronously when the children are already parsed (the common case:\n // the element is defined after its markup). Fall back to a macrotask — which,\n // unlike requestAnimationFrame, still fires in a hidden tab — if the element\n // upgrades mid-parse before its children exist.\n if (this._setup()) return;\n setTimeout(() => this._setup(), 0);\n // A custom-element control (e.g. l-slider) can upgrade after us — its module\n // may load lazily — so the macrotask retry can still miss it. Re-attempt once\n // each pending custom-element child is defined. `_setup()` is idempotent via\n // `_wired`, so the earliest successful attempt wins.\n for (const el of this.querySelectorAll('*')) {\n const tag = el.localName;\n if (tag.includes('-') && !customElements.get(tag)) {\n void customElements.whenDefined(tag).then(() => this._setup());\n }\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._teardown();\n this._wired = false;\n }\n\n private _setup(): boolean {\n if (this._wired) return true;\n this._control = this._findControl();\n if (!this._control) return false;\n this._wired = true;\n const control = this._control;\n\n // Consumer-authored markup, so these carry the consumer's configured\n // `cssPrefix` — match on `cls()`, not a hardcoded `.l-hint` / `.l-error`.\n this._hint = this.querySelector(`.${cls('hint')}`);\n this._error = this.querySelector(`.${cls('error')}`);\n // The error is announced when revealed; the field owns this (the message is\n // a plain element with no JS of its own).\n if (this._error && !this._error.hasAttribute('role')) {\n this._error.setAttribute('role', 'alert');\n }\n\n // id / label[for]\n if (!control.id) control.id = uniqueId('field');\n const label = this.querySelector('label');\n if (label && !label.htmlFor && !label.contains(control)) {\n label.htmlFor = control.id;\n }\n\n // Ensure message elements have ids for aria-describedby.\n if (this._hint && !this._hint.id) this._hint.id = uniqueId('hint');\n if (this._error && !this._error.id) this._error.id = uniqueId('error');\n\n // Capture author-provided describedby ids (minus our managed ones).\n const managed = new Set([this._hint?.id, this._error?.id].filter(Boolean) as string[]);\n this._externalDescribedby = (control.getAttribute('aria-describedby') ?? '')\n .split(/\\s+/)\n .filter((id) => id && !managed.has(id));\n\n // Layout from control type (unless explicitly forced).\n if (!this.layout) {\n const isToggle =\n control.tagName === 'INPUT' &&\n (['checkbox', 'radio'].includes((control as HTMLInputElement).type) ||\n control.getAttribute('role') === 'switch');\n this.layout = isToggle ? 'inline' : 'stacked';\n }\n\n // Required marker (control is the source of truth).\n this.required = control.hasAttribute('required');\n this.optional = !this.required;\n\n // The error stays hidden until the field is invalid after interaction (or is\n // already flagged invalid by the author / server). Visibility is owned by CSS\n // off the reflected `invalid` state — the field never toggles `hidden`.\n const preInvalid = this.invalid || control.getAttribute('aria-invalid') === 'true';\n this._hasInteracted = preInvalid;\n\n control.addEventListener('invalid', this._onInvalid);\n control.addEventListener('blur', this._onInteract);\n control.addEventListener('change', this._onInteract);\n control.addEventListener('input', this._onInteract);\n\n this._updateValidity();\n return true;\n }\n\n /**\n * The field's control: a native `input`/`select`/`textarea`, or — when none is\n * present — the first form-associated custom element (e.g. `l-slider`). Those\n * expose the same `checkValidity()` / `invalid` event surface but don't match\n * the native selector, so without this fallback the field never wires up and\n * the `l-error` message stays visible on load.\n */\n private _findControl(): FormControl | null {\n const native = this.querySelector<NativeControl>(CONTROL_SELECTOR);\n if (native) return native;\n for (const el of this.querySelectorAll<HTMLElement>('*')) {\n if ((el.constructor as { formAssociated?: boolean }).formAssociated) {\n return el as FormControl;\n }\n }\n return null;\n }\n\n private _teardown() {\n const control = this._control;\n if (!control) return;\n control.removeEventListener('invalid', this._onInvalid);\n control.removeEventListener('blur', this._onInteract);\n control.removeEventListener('change', this._onInteract);\n control.removeEventListener('input', this._onInteract);\n }\n\n private _onInvalid = (event: Event) => {\n // Suppress the native validation bubble in favour of `l-error`.\n event.preventDefault();\n this._hasInteracted = true;\n this._updateValidity();\n };\n\n private _onInteract = () => {\n this._hasInteracted = true;\n this._updateValidity();\n };\n\n private _updateValidity() {\n const control = this._control;\n if (!control) return;\n const showError = this._hasInteracted && !control.checkValidity();\n\n // `invalid` reflects to the host; CSS reveals `.l-error` from there.\n this.invalid = showError;\n if (showError) control.setAttribute('aria-invalid', 'true');\n else control.removeAttribute('aria-invalid');\n\n this._updateDescribedby();\n }\n\n private _updateDescribedby() {\n const control = this._control;\n if (!control) return;\n const ids = [...this._externalDescribedby];\n if (this._hint?.id) ids.push(this._hint.id);\n if (this._error && this.invalid && this._error.id) ids.push(this._error.id);\n if (ids.length) control.setAttribute('aria-describedby', ids.join(' '));\n else control.removeAttribute('aria-describedby');\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-otp.d.ts","sourceRoot":"","sources":["../../../src/html/elements/input-otp/input-otp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"input-otp.d.ts","sourceRoot":"","sources":["../../../src/html/elements/input-otp/input-otp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAG7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,QAAS,SAAQ,YAAY;IAC/B,gBAAgB;IAIzB,sFAAsF;IAEtF,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAK;IAEf,iBAAiB;IAUjB,oBAAoB;IAM7B,mFAAmF;IACnF,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,MAAM;IAgEd,OAAO,CAAC,SAAS;IAoBjB,OAAO,CAAC,YAAY,CAyBlB;IAEF,OAAO,CAAC,WAAW,CAIjB;IAEF,OAAO,CAAC,oBAAoB,CAE1B;CACH"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{i as
|
|
1
|
+
import{cls as e}from"../../registry.js";import{i as t,t as n}from"../../chunks/decorate.js";import{LuxenElement as r}from"../../shared/luxen-element.js";var i=class extends r{constructor(...e){super(...e),this._cells=[],this._separatorEl=null,this._initialized=!1,this._setupTimer=0,this._updateCells=()=>{let e=this._input.value,t=this._input.maxLength||6,n=Math.min(this._input.selectionStart??0,t-1),r=document.activeElement===this._input;for(let t=0;t<this._cells.length;t++){let i=this._cells[t],a=i.firstElementChild,o=e[t]??``;a.textContent=o,o?i.setAttribute(`data-filled`,``):i.removeAttribute(`data-filled`),r&&t===n?i.setAttribute(`data-active`,``):i.removeAttribute(`data-active`)}},this._clearCells=()=>{for(let e of this._cells)e.removeAttribute(`data-active`)},this._scheduleUpdateCells=()=>{requestAnimationFrame(this._updateCells)}}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this._trySetup()||(this._setupTimer=window.setTimeout(()=>this._trySetup(),0))}disconnectedCallback(){super.disconnectedCallback(),clearTimeout(this._setupTimer),this._teardown()}_trySetup(){return this._initialized||!this.isConnected?!0:this.querySelector(`input`)?(this._setup(),!0):!1}_setup(){let t=this.querySelector(`input`);if(!t)return;this._input=t;let n=Number(getComputedStyle(this).getPropertyValue(`--digits`).trim())||6,r={type:`text`,inputmode:`numeric`,autocomplete:`one-time-code`,maxlength:String(n),pattern:String.raw`\d{${n}}`};for(let[e,t]of Object.entries(r))this._input.hasAttribute(e)||this._input.setAttribute(e,t);this._container=document.createElement(`div`),this._container.className=e(`input-otp-cells`);for(let t=0;t<n;t++){let r=document.createElement(`div`);r.className=e(`input-otp-cell`),r.setAttribute(`aria-hidden`,`true`),r.appendChild(document.createElement(`span`)),this._cells.push(r),this._container.appendChild(r),this.separatorAfter&&t===this.separatorAfter-1&&t<n-1&&(this._separatorEl=document.createElement(`span`),this._separatorEl.className=e(`input-otp-separator`),this._separatorEl.setAttribute(`aria-hidden`,`true`),this._container.appendChild(this._separatorEl))}this._input.replaceWith(this._container),this._container.appendChild(this._input),this._initialized=!0,this._updateCells(),this._input.addEventListener(`input`,this._updateCells),this._input.addEventListener(`click`,this._updateCells),this._input.addEventListener(`keyup`,this._updateCells),this._input.addEventListener(`focus`,this._scheduleUpdateCells),this._input.addEventListener(`blur`,this._clearCells)}_teardown(){this._initialized&&=(this._input.removeEventListener(`input`,this._updateCells),this._input.removeEventListener(`click`,this._updateCells),this._input.removeEventListener(`keyup`,this._updateCells),this._input.removeEventListener(`focus`,this._scheduleUpdateCells),this._input.removeEventListener(`blur`,this._clearCells),this._container.replaceWith(this._input),this._separatorEl?.remove(),this._cells=[],this._separatorEl=null,!1)}};n([t({type:Number,reflect:!0,attribute:`separator-after`})],i.prototype,`separatorAfter`,void 0);export{i as InputOtp};
|
|
2
2
|
//# sourceMappingURL=input-otp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-otp.js","names":[],"sources":["../../../src/html/elements/input-otp/input-otp.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\n\n/**\n * Enhances a child `<input>` with visual digit cells (Stripe-style OTP input).\n *\n * A single hidden `<input>` handles keyboard, paste, and autocomplete.\n * Visual cells are rendered as real DOM elements with individual borders and focus ring.\n *\n * @summary Stripe-style OTP input with visual digit cells over a hidden native input.\n * @customElement l-input-otp\n *\n * @attribute size - sm | lg — Cell size. Default is md.\n *\n * @cssproperty --digits - Number of digit boxes (default: 6). Must match input's maxlength.\n * @cssproperty --cell-size - Cell width and height (default: 2.75rem). Font size scales automatically.\n * @cssproperty --cell-gap - Space between cells (default: 0.5rem).\n * @cssproperty --cell-bg-color - Cell background color.\n * @cssproperty --cell-border-color - Cell border color.\n * @cssproperty --cell-border-radius - Cell border-radius.\n * @cssproperty --cell-focus-color - Border + ring color of the active (focused) cell.\n * @cssproperty --cell-focus-ring - `box-shadow` of the active cell ring (defaults to a 1px solid ring; set to `none` to disable).\n */\nexport class InputOtp extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n /** Position after which to insert a visual separator (e.g., 3 for a 3-3 grouping). */\n @property({ type: Number, reflect: true, attribute: 'separator-after' })\n separatorAfter?: number;\n\n private _input!: HTMLInputElement;\n private _container!: HTMLDivElement;\n private _cells: HTMLDivElement[] = [];\n private _separatorEl: HTMLSpanElement | null = null;\n private _initialized = false;\n private _setupTimer = 0;\n\n override connectedCallback() {\n super.connectedCallback();\n // Children may not be parsed yet when the element upgrades mid-parse: try\n // synchronously, then retry once on a macrotask. setTimeout, not rAF — rAF\n // is suspended in hidden documents, so the element would stay inert there.\n if (!this._trySetup()) {\n this._setupTimer = window.setTimeout(() => this._trySetup(), 0);\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n clearTimeout(this._setupTimer);\n this._teardown();\n }\n\n /** @returns true when setup ran or was already done; false to schedule a retry. */\n private _trySetup(): boolean {\n if (this._initialized || !this.isConnected) return true;\n if (!this.querySelector('input')) return false;\n this._setup();\n return true;\n }\n\n // --- Setup / Teardown ---\n\n private _setup() {\n const input = this.querySelector<HTMLInputElement>('input');\n if (!input) return;\n\n this._input = input;\n\n // Derive digit count from --digits CSS custom property (default 6)\n const digits = Number(getComputedStyle(this).getPropertyValue('--digits').trim()) || 6;\n\n // Set sensible defaults — author can still override via HTML attributes\n const defaults: Record<string, string> = {\n type: 'text',\n inputmode: 'numeric',\n autocomplete: 'one-time-code',\n maxlength: String(digits),\n pattern: String.raw`\\d{${digits}}`,\n };\n for (const [attr, value] of Object.entries(defaults)) {\n if (!this._input.hasAttribute(attr)) {\n this._input.setAttribute(attr, value);\n }\n }\n\n // Build visual cells container. The real <input> is moved INTO this\n // container (below), so the container itself must stay exposed to AT —\n // aria-hidden goes on each decorative cell/separator, never on the wrapper\n // (a focusable element inside an aria-hidden subtree violates WCAG 4.1.2).\n this._container = document.createElement('div');\n this._container.className = '
|
|
1
|
+
{"version":3,"file":"input-otp.js","names":[],"sources":["../../../src/html/elements/input-otp/input-otp.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { cls } from '../../registry.js';\n\n/**\n * Enhances a child `<input>` with visual digit cells (Stripe-style OTP input).\n *\n * A single hidden `<input>` handles keyboard, paste, and autocomplete.\n * Visual cells are rendered as real DOM elements with individual borders and focus ring.\n *\n * @summary Stripe-style OTP input with visual digit cells over a hidden native input.\n * @customElement l-input-otp\n *\n * @attribute size - sm | lg — Cell size. Default is md.\n *\n * @cssproperty --digits - Number of digit boxes (default: 6). Must match input's maxlength.\n * @cssproperty --cell-size - Cell width and height (default: 2.75rem). Font size scales automatically.\n * @cssproperty --cell-gap - Space between cells (default: 0.5rem).\n * @cssproperty --cell-bg-color - Cell background color.\n * @cssproperty --cell-border-color - Cell border color.\n * @cssproperty --cell-border-radius - Cell border-radius.\n * @cssproperty --cell-focus-color - Border + ring color of the active (focused) cell.\n * @cssproperty --cell-focus-ring - `box-shadow` of the active cell ring (defaults to a 1px solid ring; set to `none` to disable).\n */\nexport class InputOtp extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n /** Position after which to insert a visual separator (e.g., 3 for a 3-3 grouping). */\n @property({ type: Number, reflect: true, attribute: 'separator-after' })\n separatorAfter?: number;\n\n private _input!: HTMLInputElement;\n private _container!: HTMLDivElement;\n private _cells: HTMLDivElement[] = [];\n private _separatorEl: HTMLSpanElement | null = null;\n private _initialized = false;\n private _setupTimer = 0;\n\n override connectedCallback() {\n super.connectedCallback();\n // Children may not be parsed yet when the element upgrades mid-parse: try\n // synchronously, then retry once on a macrotask. setTimeout, not rAF — rAF\n // is suspended in hidden documents, so the element would stay inert there.\n if (!this._trySetup()) {\n this._setupTimer = window.setTimeout(() => this._trySetup(), 0);\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n clearTimeout(this._setupTimer);\n this._teardown();\n }\n\n /** @returns true when setup ran or was already done; false to schedule a retry. */\n private _trySetup(): boolean {\n if (this._initialized || !this.isConnected) return true;\n if (!this.querySelector('input')) return false;\n this._setup();\n return true;\n }\n\n // --- Setup / Teardown ---\n\n private _setup() {\n const input = this.querySelector<HTMLInputElement>('input');\n if (!input) return;\n\n this._input = input;\n\n // Derive digit count from --digits CSS custom property (default 6)\n const digits = Number(getComputedStyle(this).getPropertyValue('--digits').trim()) || 6;\n\n // Set sensible defaults — author can still override via HTML attributes\n const defaults: Record<string, string> = {\n type: 'text',\n inputmode: 'numeric',\n autocomplete: 'one-time-code',\n maxlength: String(digits),\n pattern: String.raw`\\d{${digits}}`,\n };\n for (const [attr, value] of Object.entries(defaults)) {\n if (!this._input.hasAttribute(attr)) {\n this._input.setAttribute(attr, value);\n }\n }\n\n // Build visual cells container. The real <input> is moved INTO this\n // container (below), so the container itself must stay exposed to AT —\n // aria-hidden goes on each decorative cell/separator, never on the wrapper\n // (a focusable element inside an aria-hidden subtree violates WCAG 4.1.2).\n this._container = document.createElement('div');\n this._container.className = cls('input-otp-cells');\n\n for (let i = 0; i < digits; i++) {\n const cell = document.createElement('div');\n cell.className = cls('input-otp-cell');\n cell.setAttribute('aria-hidden', 'true');\n cell.appendChild(document.createElement('span'));\n this._cells.push(cell);\n this._container.appendChild(cell);\n\n // Insert separator after the specified position\n if (this.separatorAfter && i === this.separatorAfter - 1 && i < digits - 1) {\n this._separatorEl = document.createElement('span');\n this._separatorEl.className = cls('input-otp-separator');\n this._separatorEl.setAttribute('aria-hidden', 'true');\n this._container.appendChild(this._separatorEl);\n }\n }\n\n // Wrap: insert container before input, then move input inside\n this._input.replaceWith(this._container);\n this._container.appendChild(this._input);\n this._initialized = true;\n\n // Populate cells if input already has a value (e.g. disabled with prefilled value)\n this._updateCells();\n\n // Events — focus is deferred so it runs after the click that triggered it\n // (otherwise selectionStart is stale and the active cell flickers).\n this._input.addEventListener('input', this._updateCells);\n this._input.addEventListener('click', this._updateCells);\n this._input.addEventListener('keyup', this._updateCells);\n this._input.addEventListener('focus', this._scheduleUpdateCells);\n this._input.addEventListener('blur', this._clearCells);\n }\n\n private _teardown() {\n if (!this._initialized) return;\n\n this._input.removeEventListener('input', this._updateCells);\n this._input.removeEventListener('click', this._updateCells);\n this._input.removeEventListener('keyup', this._updateCells);\n this._input.removeEventListener('focus', this._scheduleUpdateCells);\n this._input.removeEventListener('blur', this._clearCells);\n\n // Restore input to direct child\n this._container.replaceWith(this._input);\n this._separatorEl?.remove();\n\n this._cells = [];\n this._separatorEl = null;\n this._initialized = false;\n }\n\n // --- Cell updates ---\n\n private _updateCells = (): void => {\n const value = this._input.value;\n const maxLen = this._input.maxLength || 6;\n const pos = Math.min(this._input.selectionStart ?? 0, maxLen - 1);\n const isFocused = document.activeElement === this._input;\n\n for (let i = 0; i < this._cells.length; i++) {\n const cell = this._cells[i];\n const span = cell.firstElementChild as HTMLSpanElement;\n const char = value[i] ?? '';\n\n span.textContent = char;\n\n if (char) {\n cell.setAttribute('data-filled', '');\n } else {\n cell.removeAttribute('data-filled');\n }\n\n if (isFocused && i === pos) {\n cell.setAttribute('data-active', '');\n } else {\n cell.removeAttribute('data-active');\n }\n }\n };\n\n private _clearCells = (): void => {\n for (const cell of this._cells) {\n cell.removeAttribute('data-active');\n }\n };\n\n private _scheduleUpdateCells = (): void => {\n requestAnimationFrame(this._updateCells);\n };\n}\n"],"mappings":"yJAwBA,IAAa,EAAb,cAA8B,CAAa,2CAWN,CAAC,oBACW,uBACxB,oBACD,wBAgHa,CACjC,IAAM,EAAQ,KAAK,OAAO,MACpB,EAAS,KAAK,OAAO,WAAa,EAClC,EAAM,KAAK,IAAI,KAAK,OAAO,gBAAkB,EAAG,EAAS,CAAC,EAC1D,EAAY,SAAS,gBAAkB,KAAK,OAElD,IAAK,IAAI,EAAI,EAAG,EAAI,KAAK,OAAO,OAAQ,IAAK,CAC3C,IAAM,EAAO,KAAK,OAAO,GACnB,EAAO,EAAK,kBACZ,EAAO,EAAM,IAAM,GAEzB,EAAK,YAAc,EAEf,EACF,EAAK,aAAa,cAAe,EAAE,EAEnC,EAAK,gBAAgB,aAAa,EAGhC,GAAa,IAAM,EACrB,EAAK,aAAa,cAAe,EAAE,EAEnC,EAAK,gBAAgB,aAAa,CAEtC,CACF,uBAEkC,CAChC,IAAK,IAAM,KAAQ,KAAK,OACtB,EAAK,gBAAgB,aAAa,CAEtC,gCAE2C,CACzC,sBAAsB,KAAK,YAAY,CACzC,EAhKA,kBAA4B,CAC1B,OAAO,IACT,CAaA,mBAA6B,CAC3B,MAAM,kBAAkB,EAInB,KAAK,UAAU,IAClB,KAAK,YAAc,OAAO,eAAiB,KAAK,UAAU,EAAG,CAAC,EAElE,CAEA,sBAAgC,CAC9B,MAAM,qBAAqB,EAC3B,aAAa,KAAK,WAAW,EAC7B,KAAK,UAAU,CACjB,CAGA,WAA6B,CAI3B,OAHI,KAAK,cAAgB,CAAC,KAAK,YAAoB,GAC9C,KAAK,cAAc,OAAO,GAC/B,KAAK,OAAO,EACL,IAFkC,EAG3C,CAIA,QAAiB,CACf,IAAM,EAAQ,KAAK,cAAgC,OAAO,EAC1D,GAAI,CAAC,EAAO,OAEZ,KAAK,OAAS,EAGd,IAAM,EAAS,OAAO,iBAAiB,IAAI,CAAC,CAAC,iBAAiB,UAAU,CAAC,CAAC,KAAK,CAAC,GAAK,EAG/E,EAAmC,CACvC,KAAM,OACN,UAAW,UACX,aAAc,gBACd,UAAW,OAAO,CAAM,EACxB,QAAS,OAAO,GAAG,MAAM,EAAO,EAClC,EACA,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAQ,EAC5C,KAAK,OAAO,aAAa,CAAI,GAChC,KAAK,OAAO,aAAa,EAAM,CAAK,EAQxC,KAAK,WAAa,SAAS,cAAc,KAAK,EAC9C,KAAK,WAAW,UAAY,EAAI,iBAAiB,EAEjD,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,IAAK,CAC/B,IAAM,EAAO,SAAS,cAAc,KAAK,EACzC,EAAK,UAAY,EAAI,gBAAgB,EACrC,EAAK,aAAa,cAAe,MAAM,EACvC,EAAK,YAAY,SAAS,cAAc,MAAM,CAAC,EAC/C,KAAK,OAAO,KAAK,CAAI,EACrB,KAAK,WAAW,YAAY,CAAI,EAG5B,KAAK,gBAAkB,IAAM,KAAK,eAAiB,GAAK,EAAI,EAAS,IACvE,KAAK,aAAe,SAAS,cAAc,MAAM,EACjD,KAAK,aAAa,UAAY,EAAI,qBAAqB,EACvD,KAAK,aAAa,aAAa,cAAe,MAAM,EACpD,KAAK,WAAW,YAAY,KAAK,YAAY,EAEjD,CAGA,KAAK,OAAO,YAAY,KAAK,UAAU,EACvC,KAAK,WAAW,YAAY,KAAK,MAAM,EACvC,KAAK,aAAe,GAGpB,KAAK,aAAa,EAIlB,KAAK,OAAO,iBAAiB,QAAS,KAAK,YAAY,EACvD,KAAK,OAAO,iBAAiB,QAAS,KAAK,YAAY,EACvD,KAAK,OAAO,iBAAiB,QAAS,KAAK,YAAY,EACvD,KAAK,OAAO,iBAAiB,QAAS,KAAK,oBAAoB,EAC/D,KAAK,OAAO,iBAAiB,OAAQ,KAAK,WAAW,CACvD,CAEA,WAAoB,CACb,AAcL,KAAK,gBAZL,KAAK,OAAO,oBAAoB,QAAS,KAAK,YAAY,EAC1D,KAAK,OAAO,oBAAoB,QAAS,KAAK,YAAY,EAC1D,KAAK,OAAO,oBAAoB,QAAS,KAAK,YAAY,EAC1D,KAAK,OAAO,oBAAoB,QAAS,KAAK,oBAAoB,EAClE,KAAK,OAAO,oBAAoB,OAAQ,KAAK,WAAW,EAGxD,KAAK,WAAW,YAAY,KAAK,MAAM,EACvC,KAAK,cAAc,OAAO,EAE1B,KAAK,OAAS,CAAC,EACf,KAAK,aAAe,KACA,GACtB,CAwCF,KA5JG,EAAS,CAAE,KAAM,OAAQ,QAAS,GAAM,UAAW,iBAAkB,CAAC,CAAA,EAAA,EAAA,UAAA,iBAAA,IAAA,EAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{tagName as
|
|
1
|
+
import{cls as e,tagName as t}from"../../registry.js";import{i as n,t as r}from"../../chunks/decorate.js";import{LuxenElement as i}from"../../shared/luxen-element.js";import{LocalizeController as a}from"../../shared/localize.js";var o=class extends Event{constructor(e){super(`change`,{bubbles:!0,composed:!1,cancelable:!1}),this.value=e}},s=class extends i{constructor(...e){super(...e),this._localize=new a(this),this.size=`md`,this.withRoller=!1,this.decrementIcon=`lucide:minus`,this.incrementIcon=`lucide:plus`,this._initialized=!1,this._setupTimer=0,this._input=null,this._decrementBtn=null,this._incrementBtn=null,this._valueWrapper=null,this._trackDisplay=null,this._track=null,this._observer=null,this._skipTransition=!1,this._onDecrement=()=>this.decrement(),this._onIncrement=()=>this.increment(),this._onInputChange=e=>{if(!this._input)return;e.stopPropagation();let{min:t,max:n}=this._getConstraints(),r=Math.min(Math.max(this._input.valueAsNumber,t),n);this._input.value=String(r),this._skipTransition=!0,this._updateButtonStates(),this._updateTrack(),this.dispatchEvent(new o(r))}}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this._trySetup()||(this._setupTimer=window.setTimeout(()=>this._trySetup(),0))}disconnectedCallback(){super.disconnectedCallback(),clearTimeout(this._setupTimer),this._teardown()}_trySetup(){return this._initialized||!this.isConnected?!0:this.querySelector(`input[type="number"]`)?(this._setup(),!0):!1}updated(e){this._input&&((e.has(`min`)||e.has(`max`)||e.has(`step`))&&(this._syncConstraints(),this._updateButtonStates()),this._decrementBtn?.setAttribute(`aria-label`,this._localize.term(`decreaseValue`)),this._incrementBtn?.setAttribute(`aria-label`,this._localize.term(`increaseValue`)))}decrement(){if(!this._input)return;let{min:e,step:t}=this._getConstraints(),n=Math.max(this._input.valueAsNumber-t,e);this._applyValue(n)}increment(){if(!this._input)return;let{max:e,step:t}=this._getConstraints(),n=Math.min(this._input.valueAsNumber+t,e);this._applyValue(n)}_setup(){this._input=this.querySelector(`input[type="number"]`),this._input&&(this._initialized=!0,this._input.inputMode=`numeric`,this._valueWrapper=document.createElement(`div`),this._valueWrapper.className=e(`input-stepper-value`),this._input.replaceWith(this._valueWrapper),this._valueWrapper.appendChild(this._input),this.withRoller&&this._buildTrack(),this._decrementBtn=this._createButton(this.decrementIcon,this._localize.term(`decreaseValue`)),this._incrementBtn=this._createButton(this.incrementIcon,this._localize.term(`increaseValue`)),this._valueWrapper.before(this._decrementBtn),this._valueWrapper.after(this._incrementBtn),this._decrementBtn.addEventListener(`click`,this._onDecrement),this._incrementBtn.addEventListener(`click`,this._onIncrement),this._input.addEventListener(`change`,this._onInputChange),this._observer=new MutationObserver(()=>this._updateButtonStates()),this._observer.observe(this._input,{attributes:!0,attributeFilter:[`disabled`]}),this._syncConstraints(),this._updateButtonStates(),this._updateTrack())}_teardown(){this._decrementBtn?.removeEventListener(`click`,this._onDecrement),this._incrementBtn?.removeEventListener(`click`,this._onIncrement),this._input?.removeEventListener(`change`,this._onInputChange),this._observer?.disconnect(),this._input&&this._valueWrapper&&this._valueWrapper.replaceWith(this._input),this._decrementBtn?.remove(),this._incrementBtn?.remove(),this._input=null,this._decrementBtn=null,this._incrementBtn=null,this._valueWrapper=null,this._trackDisplay=null,this._track=null,this._observer=null,this._initialized=!1}_createButton(e,n){let r=document.createElement(`button`);r.type=`button`,r.setAttribute(`aria-label`,n);let i=document.createElement(t(`icon`));return i.setAttribute(`name`,e),r.append(i),r}_buildTrack(){this._valueWrapper&&(this._trackDisplay=document.createElement(`div`),this._trackDisplay.className=e(`input-stepper-track-display`),this._track=document.createElement(`div`),this._track.className=e(`input-stepper-track`),this._track.setAttribute(`aria-hidden`,`true`),this._rebuildTrackNumbers(),this._trackDisplay.appendChild(this._track),this._valueWrapper.appendChild(this._trackDisplay))}_rebuildTrackNumbers(){if(!this._track)return;let{min:e,max:t,step:n}=this._getConstraints(),r=Number.isFinite(t)?t:(this._input?.valueAsNumber??0)+100;this._track.innerHTML=``;for(let t=e;t<=r;t+=n){let e=document.createElement(`div`);e.textContent=String(t),this._track.appendChild(e)}}_getConstraints(){return{min:this.min??(Number(this._input?.getAttribute(`min`))||0),max:this.max??(this._input?.hasAttribute(`max`)?Number(this._input.getAttribute(`max`)):1/0),step:this.step??(Number(this._input?.getAttribute(`step`))||1)}}_syncConstraints(){if(!this._input)return;let{min:e,max:t,step:n}=this._getConstraints();this._input.min=String(e),Number.isFinite(t)&&(this._input.max=String(t)),this._input.step=String(n),this._rebuildTrackNumbers(),this._updateTrack()}_applyValue(e){this._input&&(this._input.value=String(e),this._updateButtonStates(),this._updateTrack(),this.dispatchEvent(new o(e)))}_updateTrack(){if(!this._track)return;let{min:e,step:t}=this._getConstraints(),n=(e-(this._input?.valueAsNumber??e))/t;this._track.style.translate=`0 calc(${n} * var(--_button-size))`,this._skipTransition&&=(this._track.style.transition=`none`,this._track.offsetHeight,this._track.style.transition=``,!1)}_updateButtonStates(){if(!this._input||!this._decrementBtn||!this._incrementBtn)return;let{min:e,max:t}=this._getConstraints(),n=this._input.valueAsNumber,r=this._input.disabled;this._decrementBtn.disabled=r||n<=e,this._incrementBtn.disabled=r||n>=t}};r([n({type:Number})],s.prototype,`min`,void 0),r([n({type:Number})],s.prototype,`max`,void 0),r([n({type:Number})],s.prototype,`step`,void 0),r([n({reflect:!0})],s.prototype,`size`,void 0),r([n({type:Boolean,reflect:!0,attribute:`with-roller`})],s.prototype,`withRoller`,void 0),r([n({attribute:`decrement-icon`})],s.prototype,`decrementIcon`,void 0),r([n({attribute:`increment-icon`})],s.prototype,`incrementIcon`,void 0);export{s as InputStepper,o as StepperChangeEvent};
|
|
2
2
|
//# sourceMappingURL=input-stepper.js.map
|