luxen-ui 0.2.0 → 0.3.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 +495 -337
- package/cdn/elements/dialog/dialog.d.ts +3 -0
- package/cdn/elements/dialog/dialog.d.ts.map +1 -1
- package/cdn/elements/dialog/dialog.js +10 -6
- package/cdn/elements/dialog/dialog.js.map +1 -1
- package/cdn/elements/dialog/dialog.styles.js +1 -1
- package/cdn/elements/dialog/dialog.styles.js.map +1 -1
- package/cdn/elements/dropdown/dropdown.js +1 -1
- package/cdn/elements/dropdown/dropdown.js.map +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
- package/cdn/elements/input-otp/input-otp.d.ts +8 -2
- 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/popover/popover.js +1 -1
- package/cdn/elements/popover/popover.js.map +1 -1
- package/cdn/styles/elements/button.css +1 -1
- package/cdn/styles/elements/input-otp.css +63 -29
- package/cdn/styles/index.css +15 -2
- package/dist/css/elements/button.css +1 -1
- package/dist/css/elements/input-otp.css +63 -29
- package/dist/css/index.css +15 -2
- package/dist/custom-elements.json +495 -337
- package/dist/elements/dialog/dialog.css +10 -0
- package/dist/elements/dialog/dialog.d.ts +3 -0
- package/dist/elements/dialog/dialog.d.ts.map +1 -1
- package/dist/elements/dialog/dialog.js +16 -4
- package/dist/elements/dropdown/dropdown.css +4 -4
- package/dist/elements/dropdown-item/dropdown-item.css +2 -1
- package/dist/elements/input-otp/input-otp.d.ts +8 -2
- package/dist/elements/input-otp/input-otp.d.ts.map +1 -1
- package/dist/elements/input-otp/input-otp.js +14 -5
- package/dist/elements/popover/popover.css +3 -4
- package/dist/skills/luxen-ui/references/dialog.md +79 -1
- package/dist/skills/luxen-ui/references/drawer.md +8 -0
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { LuxenElement } from '../../shared/luxen-element';
|
|
|
7
7
|
* Invoker commands). There are no public `show()` / `close()` methods.
|
|
8
8
|
*
|
|
9
9
|
* @slot - Body content.
|
|
10
|
+
* @slot title - Custom heading element. Overrides the default `<h2>` rendered from the `title` property.
|
|
10
11
|
* @slot close - Close button (typically `<button class="l-close">`).
|
|
11
12
|
* @slot footer - Footer actions.
|
|
12
13
|
*
|
|
@@ -37,6 +38,8 @@ export declare class LuxenDialog extends LuxenElement {
|
|
|
37
38
|
open: boolean;
|
|
38
39
|
/** Close when the backdrop is clicked. */
|
|
39
40
|
lightDismiss: boolean;
|
|
41
|
+
/** Hide the header entirely (title and close slot). */
|
|
42
|
+
withoutHeader: boolean;
|
|
40
43
|
dialog: HTMLDialogElement;
|
|
41
44
|
private _mouseDownTarget;
|
|
42
45
|
private _commandListener;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dialog/dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAwB1D
|
|
1
|
+
{"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dialog/dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAwB1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,WAAY,SAAQ,YAAY;IAC3C,MAAM,CAAC,MAAM,4BAAwB;IAErC,2CAA2C;IAE3C,KAAK,SAAM;IAEX,kCAAkC;IAElC,IAAI,UAAS;IAEb,0CAA0C;IAE1C,YAAY,UAAS;IAErB,uDAAuD;IAEvD,aAAa,UAAS;IAGtB,MAAM,EAAG,iBAAiB,CAAC;IAE3B,OAAO,CAAC,gBAAgB,CAA4B;IAEpD,OAAO,CAAC,gBAAgB,CAEtB;IAIF,iBAAiB;IAKjB,oBAAoB;IAKpB,YAAY;IASZ,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IA0BrC,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,eAAe,CAAC,CAAY;IAEpC,OAAO,CAAC,aAAa;YAcP,UAAU;IAUxB,OAAO,CAAC,qBAAqB;IAK7B,MAAM;CA0BP"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import{i as e,n as t}from"../../chunks/lit.js";import{LuxenElement as n}from"../../shared/luxen-element.js";import{a as r,r as i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";import s from"./dialog.styles.js";var c=typeof HTMLDialogElement<`u`&&`closedBy`in HTMLDialogElement.prototype,l=Symbol.for(`luxen-dialog-scroll-lock`);if(typeof document<`u`&&!(l in document)){let e=new CSSStyleSheet;e.replaceSync(`html:has([data-modal]) { overflow: hidden; scrollbar-gutter: stable; }`),document.adoptedStyleSheets.push(e),Object.defineProperty(document,l,{value:e})}var u=class extends n{constructor(...e){super(...e),this.title=``,this.open=!1,this.lightDismiss=!1,this._mouseDownTarget=null,this._commandListener={handleEvent:e=>this._onCommand(e)}}static{this.styles=[o,s]}connectedCallback(){super.connectedCallback(),this.addEventListener(`command`,this._commandListener)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(`command`,this._commandListener)}firstUpdated(){this.dialog.addEventListener(`cancel`,e=>this._onCancel(e)),this.dialog.addEventListener(`close`,()=>this._onNativeClose()),this.dialog.addEventListener(`mousedown`,e=>{this._mouseDownTarget=e.target}),this.dialog.addEventListener(`click`,e=>this._onDialogClick(e))}updated(e){if(e.has(`open`)){if(this.open&&!this.dialog.open)this.emit(`show`),this.toggleAttribute(`data-modal`,!0),this.dialog.showModal(),this._focusAutofocusTarget(),this._emitAfter(`after-show`);else if(!this.open&&this.dialog.open){if(!this.emit(`hide`,{cancelable:!0})){this.open=!0;return}this.dialog.close()}}}_onCommand(e){switch(e.command){case`--hide`:this.open=!1;break;case`--show`:this.open=!0;break}}_onCancel(e){this.emit(`hide`,{cancelable:!0})||e.preventDefault()}_onNativeClose(){this.open=!1,this.removeAttribute(`data-modal`),this._emitAfter(`after-hide`)}_onDialogClick(e){let t=e.target===this.dialog&&this._mouseDownTarget===this.dialog;if(this._mouseDownTarget=null,t){if(this.lightDismiss){c||(this.open=!1);return}this._nudgeDismiss()}}_nudgeDismiss(){matchMedia(`(prefers-reduced-motion: reduce)`).matches||(this._nudgeAnimation?.cancel(),this._nudgeAnimation=this.dialog.animate([{transform:`scale(1)`},{transform:`scale(1.02)`},{transform:`scale(1)`}],{duration:250,easing:`ease-in-out`}))}async _emitAfter(e){await new Promise(e=>requestAnimationFrame(()=>e(null)));let t=this.dialog.getAnimations({subtree:!1});await Promise.all(t.map(e=>e.finished.catch(()=>{}))),e===`after-show`===this.open&&this.emit(e)}_focusAutofocusTarget(){this.querySelector(`[autofocus]`)?.focus({preventScroll:!0})}render(){return e`
|
|
1
|
+
import{i as e,n as t}from"../../chunks/lit.js";import{LuxenElement as n}from"../../shared/luxen-element.js";import{a as r,r as i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";import s from"./dialog.styles.js";var c=typeof HTMLDialogElement<`u`&&`closedBy`in HTMLDialogElement.prototype,l=Symbol.for(`luxen-dialog-scroll-lock`);if(typeof document<`u`&&!(l in document)){let e=new CSSStyleSheet;e.replaceSync(`html:has([data-modal]) { overflow: hidden; scrollbar-gutter: stable; }`),document.adoptedStyleSheets.push(e),Object.defineProperty(document,l,{value:e})}var u=class extends n{constructor(...e){super(...e),this.title=``,this.open=!1,this.lightDismiss=!1,this.withoutHeader=!1,this._mouseDownTarget=null,this._commandListener={handleEvent:e=>this._onCommand(e)}}static{this.styles=[o,s]}connectedCallback(){super.connectedCallback(),this.addEventListener(`command`,this._commandListener)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(`command`,this._commandListener)}firstUpdated(){this.dialog.addEventListener(`cancel`,e=>this._onCancel(e)),this.dialog.addEventListener(`close`,()=>this._onNativeClose()),this.dialog.addEventListener(`mousedown`,e=>{this._mouseDownTarget=e.target}),this.dialog.addEventListener(`click`,e=>this._onDialogClick(e))}updated(e){if(e.has(`open`)){if(this.open&&!this.dialog.open)this.emit(`show`),this.toggleAttribute(`data-modal`,!0),this.dialog.showModal(),this._focusAutofocusTarget(),this._emitAfter(`after-show`);else if(!this.open&&this.dialog.open){if(!this.emit(`hide`,{cancelable:!0})){this.open=!0;return}this.dialog.close()}}}_onCommand(e){switch(e.command){case`--hide`:this.open=!1;break;case`--show`:this.open=!0;break}}_onCancel(e){this.emit(`hide`,{cancelable:!0})||e.preventDefault()}_onNativeClose(){this.open=!1,this.removeAttribute(`data-modal`),this._emitAfter(`after-hide`)}_onDialogClick(e){let t=e.target===this.dialog&&this._mouseDownTarget===this.dialog;if(this._mouseDownTarget=null,t){if(this.lightDismiss){c||(this.open=!1);return}this._nudgeDismiss()}}_nudgeDismiss(){matchMedia(`(prefers-reduced-motion: reduce)`).matches||(this._nudgeAnimation?.cancel(),this._nudgeAnimation=this.dialog.animate([{transform:`scale(1)`},{transform:`scale(1.02)`},{transform:`scale(1)`}],{duration:250,easing:`ease-in-out`}))}async _emitAfter(e){await new Promise(e=>requestAnimationFrame(()=>e(null)));let t=this.dialog.getAnimations({subtree:!1});await Promise.all(t.map(e=>e.finished.catch(()=>{}))),e===`after-show`===this.open&&this.emit(e)}_focusAutofocusTarget(){this.querySelector(`[autofocus]`)?.focus({preventScroll:!0})}render(){return e`
|
|
2
2
|
<dialog
|
|
3
3
|
part="dialog"
|
|
4
4
|
closedby=${this.lightDismiss&&c?`any`:t}
|
|
5
5
|
>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
${this.withoutHeader?t:e`
|
|
7
|
+
<header part="header">
|
|
8
|
+
<slot name="title">
|
|
9
|
+
${this.title?e`<h2 part="title">${this.title}</h2>`:t}
|
|
10
|
+
</slot>
|
|
11
|
+
<slot name="close"></slot>
|
|
12
|
+
</header>
|
|
13
|
+
`}
|
|
10
14
|
<div part="body">
|
|
11
15
|
<slot></slot>
|
|
12
16
|
</div>
|
|
@@ -14,5 +18,5 @@ import{i as e,n as t}from"../../chunks/lit.js";import{LuxenElement as n}from"../
|
|
|
14
18
|
<slot name="footer"></slot>
|
|
15
19
|
</footer>
|
|
16
20
|
</dialog>
|
|
17
|
-
`}};a([r()],u.prototype,`title`,void 0),a([r({type:Boolean,reflect:!0})],u.prototype,`open`,void 0),a([r({type:Boolean,reflect:!0,attribute:`light-dismiss`})],u.prototype,`lightDismiss`,void 0),a([i(`dialog`)],u.prototype,`dialog`,void 0);export{u as LuxenDialog};
|
|
21
|
+
`}};a([r()],u.prototype,`title`,void 0),a([r({type:Boolean,reflect:!0})],u.prototype,`open`,void 0),a([r({type:Boolean,reflect:!0,attribute:`light-dismiss`})],u.prototype,`lightDismiss`,void 0),a([r({type:Boolean,reflect:!0,attribute:`without-header`})],u.prototype,`withoutHeader`,void 0),a([i(`dialog`)],u.prototype,`dialog`,void 0);export{u as LuxenDialog};
|
|
18
22
|
//# sourceMappingURL=dialog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","names":[],"sources":["../../../src/html/elements/dialog/dialog.ts"],"sourcesContent":["import { html, nothing, type PropertyValues } from 'lit';\nimport { property, query } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport hostStyles from '../../shared/styles/host.styles';\nimport styles from './dialog.styles';\n\ninterface CommandEventLike extends Event {\n command: string;\n source: Element | null;\n}\n\nconst supportsClosedBy =\n typeof HTMLDialogElement !== 'undefined' && 'closedBy' in HTMLDialogElement.prototype;\n\n// Native <dialog> doesn't lock body scroll. Inject a global rule that uses\n// `:has()` to freeze the root scroll container whenever any modal l-dialog\n// is open. Purely declarative — no manual lock/unlock bookkeeping.\n// Symbol guard makes the injection idempotent across HMR reloads.\nconst SCROLL_LOCK_SHEET = Symbol.for('luxen-dialog-scroll-lock');\nif (typeof document !== 'undefined' && !(SCROLL_LOCK_SHEET in document)) {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(`html:has([data-modal]) { overflow: hidden; scrollbar-gutter: stable; }`);\n document.adoptedStyleSheets.push(sheet);\n Object.defineProperty(document, SCROLL_LOCK_SHEET, { value: sheet });\n}\n\n/**\n * A modal dialog rendered in the top layer via the native `<dialog>` element.\n *\n * Open and close by toggling the `open` property (or the `--show` / `--hide`\n * Invoker commands). There are no public `show()` / `close()` methods.\n *\n * @slot - Body content.\n * @slot close - Close button (typically `<button class=\"l-close\">`).\n * @slot footer - Footer actions.\n *\n * @csspart dialog - The native `<dialog>` element.\n * @csspart header - The header wrapper containing the title and close slot.\n * @csspart title - The dialog title heading.\n * @csspart body - The body wrapper around the default slot.\n * @csspart footer - The footer wrapper around the footer slot.\n *\n * @cssproperty --width - Dialog width. Default `31rem`.\n * @cssproperty --border-radius - Dialog border radius. Default `6px`.\n * @cssproperty --padding - Padding applied to the header, footer, and inline-padding of the body. Default `1.5rem`. Set to `0` to remove all internal spacing (e.g. for edge-to-edge media).\n * @cssproperty --show-duration - Open transition duration. Default `200ms`.\n * @cssproperty --hide-duration - Close transition duration. Default `200ms`.\n * @cssproperty --backdrop - Backdrop color.\n * @cssproperty --backdrop-blur - Backdrop blur amount (any CSS length). Default `0` (no blur). Set to e.g. `4px` for a subtle frost.\n *\n * @event show - Fired when the dialog opens. Not cancelable.\n * @event after-show - Fired after the open animation completes.\n * @event hide - Fired when the dialog is about to close. Cancelable — call `event.preventDefault()` to keep it open.\n * @event after-hide - Fired after the close animation completes.\n */\nexport class LuxenDialog extends LuxenElement {\n static styles = [hostStyles, styles];\n\n /** Dialog title rendered in the header. */\n @property()\n title = '';\n\n /** Whether the dialog is open. */\n @property({ type: Boolean, reflect: true })\n open = false;\n\n /** Close when the backdrop is clicked. */\n @property({ type: Boolean, reflect: true, attribute: 'light-dismiss' })\n lightDismiss = false;\n\n @query('dialog')\n dialog!: HTMLDialogElement;\n\n private _mouseDownTarget: EventTarget | null = null;\n\n private _commandListener: EventListenerObject = {\n handleEvent: (e: Event) => this._onCommand(e as CommandEventLike),\n };\n\n // --- Lifecycle ---\n\n connectedCallback() {\n super.connectedCallback();\n this.addEventListener('command', this._commandListener);\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.removeEventListener('command', this._commandListener);\n }\n\n firstUpdated() {\n this.dialog.addEventListener('cancel', (e) => this._onCancel(e));\n this.dialog.addEventListener('close', () => this._onNativeClose());\n this.dialog.addEventListener('mousedown', (e) => {\n this._mouseDownTarget = e.target;\n });\n this.dialog.addEventListener('click', (e) => this._onDialogClick(e));\n }\n\n updated(changed: PropertyValues<this>) {\n if (!changed.has('open')) return;\n\n if (this.open && !this.dialog.open) {\n // Opening — not cancelable.\n this.emit('show');\n this.toggleAttribute('data-modal', true);\n this.dialog.showModal();\n this._focusAutofocusTarget();\n this._emitAfter('after-show');\n } else if (!this.open && this.dialog.open) {\n // Closing — cancelable. Revert the property if consumer prevents.\n if (!this.emit('hide', { cancelable: true })) {\n this.open = true;\n return;\n }\n this.dialog.close();\n // `after-hide` is emitted from `_onNativeClose` (runs for every close path).\n }\n }\n\n // --- Event handlers ---\n\n // Custom commands on a custom element must start with `--`.\n // Built-in commands like \"close\" are reserved for native elements\n // and won't fire here.\n private _onCommand(e: CommandEventLike) {\n switch (e.command) {\n case '--hide':\n this.open = false;\n break;\n case '--show':\n this.open = true;\n break;\n }\n }\n\n // Fires on Escape and on `closedby=\"any\"` close requests.\n // Does NOT fire when script calls `.close()`, so `updated()`'s cancelable\n // `hide` emit doesn't collide with this one.\n private _onCancel(e: Event) {\n if (!this.emit('hide', { cancelable: true })) {\n e.preventDefault();\n }\n }\n\n private _onNativeClose() {\n this.open = false;\n this.removeAttribute('data-modal');\n this._emitAfter('after-hide');\n }\n\n private _onDialogClick(e: MouseEvent) {\n // With `dialog { padding: 0 }`, `e.target === this.dialog` only fires\n // for backdrop clicks. The mousedown guard prevents drag-out dismissal.\n const clickedBackdrop = e.target === this.dialog && this._mouseDownTarget === this.dialog;\n this._mouseDownTarget = null;\n if (!clickedBackdrop) return;\n\n if (this.lightDismiss) {\n // When `supportsClosedBy`, the native `closedby=\"any\"` already closed.\n if (!supportsClosedBy) this.open = false;\n return;\n }\n this._nudgeDismiss();\n }\n\n private _nudgeAnimation?: Animation;\n\n private _nudgeDismiss() {\n if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n this._nudgeAnimation?.cancel();\n this._nudgeAnimation = this.dialog.animate(\n [{ transform: 'scale(1)' }, { transform: 'scale(1.02)' }, { transform: 'scale(1)' }],\n { duration: 250, easing: 'ease-in-out' },\n );\n }\n\n // Awaits every active animation on the dialog (transitions + @keyframes)\n // and then emits. Resolves immediately when no animations are running,\n // which covers `prefers-reduced-motion` and consumers that zero the\n // duration custom properties — cases where `transitionend` never fires.\n // Waits one frame first so @starting-style transitions have registered.\n private async _emitAfter(name: 'after-show' | 'after-hide') {\n await new Promise((r) => requestAnimationFrame(() => r(null)));\n const anims = this.dialog.getAnimations({ subtree: false });\n await Promise.all(anims.map((a) => a.finished.catch(() => {})));\n if ((name === 'after-show') !== this.open) return;\n this.emit(name);\n }\n\n // Firefox/Safari don't reliably resolve `[autofocus]` when `<dialog>`\n // is in shadow DOM and the target is in light DOM. Resolve it manually.\n private _focusAutofocusTarget() {\n const target = this.querySelector<HTMLElement>('[autofocus]');\n target?.focus({ preventScroll: true });\n }\n\n render() {\n const closedby = this.lightDismiss && supportsClosedBy ? 'any' : nothing;\n return html`\n <dialog\n part=\"dialog\"\n closedby=${closedby}\n >\n <header part=\"header\">\n <h2 part=\"title\">${this.title}</h2>\n <slot name=\"close\"></slot>\n </header>\n <div part=\"body\">\n <slot></slot>\n </div>\n <footer part=\"footer\">\n <slot name=\"footer\"></slot>\n </footer>\n </dialog>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dialog.js","names":[],"sources":["../../../src/html/elements/dialog/dialog.ts"],"sourcesContent":["import { html, nothing, type PropertyValues } from 'lit';\nimport { property, query } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport hostStyles from '../../shared/styles/host.styles';\nimport styles from './dialog.styles';\n\ninterface CommandEventLike extends Event {\n command: string;\n source: Element | null;\n}\n\nconst supportsClosedBy =\n typeof HTMLDialogElement !== 'undefined' && 'closedBy' in HTMLDialogElement.prototype;\n\n// Native <dialog> doesn't lock body scroll. Inject a global rule that uses\n// `:has()` to freeze the root scroll container whenever any modal l-dialog\n// is open. Purely declarative — no manual lock/unlock bookkeeping.\n// Symbol guard makes the injection idempotent across HMR reloads.\nconst SCROLL_LOCK_SHEET = Symbol.for('luxen-dialog-scroll-lock');\nif (typeof document !== 'undefined' && !(SCROLL_LOCK_SHEET in document)) {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(`html:has([data-modal]) { overflow: hidden; scrollbar-gutter: stable; }`);\n document.adoptedStyleSheets.push(sheet);\n Object.defineProperty(document, SCROLL_LOCK_SHEET, { value: sheet });\n}\n\n/**\n * A modal dialog rendered in the top layer via the native `<dialog>` element.\n *\n * Open and close by toggling the `open` property (or the `--show` / `--hide`\n * Invoker commands). There are no public `show()` / `close()` methods.\n *\n * @slot - Body content.\n * @slot title - Custom heading element. Overrides the default `<h2>` rendered from the `title` property.\n * @slot close - Close button (typically `<button class=\"l-close\">`).\n * @slot footer - Footer actions.\n *\n * @csspart dialog - The native `<dialog>` element.\n * @csspart header - The header wrapper containing the title and close slot.\n * @csspart title - The dialog title heading.\n * @csspart body - The body wrapper around the default slot.\n * @csspart footer - The footer wrapper around the footer slot.\n *\n * @cssproperty --width - Dialog width. Default `31rem`.\n * @cssproperty --border-radius - Dialog border radius. Default `6px`.\n * @cssproperty --padding - Padding applied to the header, footer, and inline-padding of the body. Default `1.5rem`. Set to `0` to remove all internal spacing (e.g. for edge-to-edge media).\n * @cssproperty --show-duration - Open transition duration. Default `200ms`.\n * @cssproperty --hide-duration - Close transition duration. Default `200ms`.\n * @cssproperty --backdrop - Backdrop color.\n * @cssproperty --backdrop-blur - Backdrop blur amount (any CSS length). Default `0` (no blur). Set to e.g. `4px` for a subtle frost.\n *\n * @event show - Fired when the dialog opens. Not cancelable.\n * @event after-show - Fired after the open animation completes.\n * @event hide - Fired when the dialog is about to close. Cancelable — call `event.preventDefault()` to keep it open.\n * @event after-hide - Fired after the close animation completes.\n */\nexport class LuxenDialog extends LuxenElement {\n static styles = [hostStyles, styles];\n\n /** Dialog title rendered in the header. */\n @property()\n title = '';\n\n /** Whether the dialog is open. */\n @property({ type: Boolean, reflect: true })\n open = false;\n\n /** Close when the backdrop is clicked. */\n @property({ type: Boolean, reflect: true, attribute: 'light-dismiss' })\n lightDismiss = false;\n\n /** Hide the header entirely (title and close slot). */\n @property({ type: Boolean, reflect: true, attribute: 'without-header' })\n withoutHeader = false;\n\n @query('dialog')\n dialog!: HTMLDialogElement;\n\n private _mouseDownTarget: EventTarget | null = null;\n\n private _commandListener: EventListenerObject = {\n handleEvent: (e: Event) => this._onCommand(e as CommandEventLike),\n };\n\n // --- Lifecycle ---\n\n connectedCallback() {\n super.connectedCallback();\n this.addEventListener('command', this._commandListener);\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.removeEventListener('command', this._commandListener);\n }\n\n firstUpdated() {\n this.dialog.addEventListener('cancel', (e) => this._onCancel(e));\n this.dialog.addEventListener('close', () => this._onNativeClose());\n this.dialog.addEventListener('mousedown', (e) => {\n this._mouseDownTarget = e.target;\n });\n this.dialog.addEventListener('click', (e) => this._onDialogClick(e));\n }\n\n updated(changed: PropertyValues<this>) {\n if (!changed.has('open')) return;\n\n if (this.open && !this.dialog.open) {\n // Opening — not cancelable.\n this.emit('show');\n this.toggleAttribute('data-modal', true);\n this.dialog.showModal();\n this._focusAutofocusTarget();\n this._emitAfter('after-show');\n } else if (!this.open && this.dialog.open) {\n // Closing — cancelable. Revert the property if consumer prevents.\n if (!this.emit('hide', { cancelable: true })) {\n this.open = true;\n return;\n }\n this.dialog.close();\n // `after-hide` is emitted from `_onNativeClose` (runs for every close path).\n }\n }\n\n // --- Event handlers ---\n\n // Custom commands on a custom element must start with `--`.\n // Built-in commands like \"close\" are reserved for native elements\n // and won't fire here.\n private _onCommand(e: CommandEventLike) {\n switch (e.command) {\n case '--hide':\n this.open = false;\n break;\n case '--show':\n this.open = true;\n break;\n }\n }\n\n // Fires on Escape and on `closedby=\"any\"` close requests.\n // Does NOT fire when script calls `.close()`, so `updated()`'s cancelable\n // `hide` emit doesn't collide with this one.\n private _onCancel(e: Event) {\n if (!this.emit('hide', { cancelable: true })) {\n e.preventDefault();\n }\n }\n\n private _onNativeClose() {\n this.open = false;\n this.removeAttribute('data-modal');\n this._emitAfter('after-hide');\n }\n\n private _onDialogClick(e: MouseEvent) {\n // With `dialog { padding: 0 }`, `e.target === this.dialog` only fires\n // for backdrop clicks. The mousedown guard prevents drag-out dismissal.\n const clickedBackdrop = e.target === this.dialog && this._mouseDownTarget === this.dialog;\n this._mouseDownTarget = null;\n if (!clickedBackdrop) return;\n\n if (this.lightDismiss) {\n // When `supportsClosedBy`, the native `closedby=\"any\"` already closed.\n if (!supportsClosedBy) this.open = false;\n return;\n }\n this._nudgeDismiss();\n }\n\n private _nudgeAnimation?: Animation;\n\n private _nudgeDismiss() {\n if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n this._nudgeAnimation?.cancel();\n this._nudgeAnimation = this.dialog.animate(\n [{ transform: 'scale(1)' }, { transform: 'scale(1.02)' }, { transform: 'scale(1)' }],\n { duration: 250, easing: 'ease-in-out' },\n );\n }\n\n // Awaits every active animation on the dialog (transitions + @keyframes)\n // and then emits. Resolves immediately when no animations are running,\n // which covers `prefers-reduced-motion` and consumers that zero the\n // duration custom properties — cases where `transitionend` never fires.\n // Waits one frame first so @starting-style transitions have registered.\n private async _emitAfter(name: 'after-show' | 'after-hide') {\n await new Promise((r) => requestAnimationFrame(() => r(null)));\n const anims = this.dialog.getAnimations({ subtree: false });\n await Promise.all(anims.map((a) => a.finished.catch(() => {})));\n if ((name === 'after-show') !== this.open) return;\n this.emit(name);\n }\n\n // Firefox/Safari don't reliably resolve `[autofocus]` when `<dialog>`\n // is in shadow DOM and the target is in light DOM. Resolve it manually.\n private _focusAutofocusTarget() {\n const target = this.querySelector<HTMLElement>('[autofocus]');\n target?.focus({ preventScroll: true });\n }\n\n render() {\n const closedby = this.lightDismiss && supportsClosedBy ? 'any' : nothing;\n return html`\n <dialog\n part=\"dialog\"\n closedby=${closedby}\n >\n ${this.withoutHeader\n ? nothing\n : html`\n <header part=\"header\">\n <slot name=\"title\">\n ${this.title ? html`<h2 part=\"title\">${this.title}</h2>` : nothing}\n </slot>\n <slot name=\"close\"></slot>\n </header>\n `}\n <div part=\"body\">\n <slot></slot>\n </div>\n <footer part=\"footer\">\n <slot name=\"footer\"></slot>\n </footer>\n </dialog>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{c as e}from"../../chunks/lit.js";var t=e(`:host{--width:31rem;--border-radius:6px;--padding:1.5rem;--show-duration:.2s;--hide-duration:.2s;--backdrop:var(--l-backdrop);--backdrop-blur:0;display:contents}dialog{box-sizing:border-box;width:var(--width);max-inline-size:min(90vw, var(--width));border-radius:var(--border-radius);background-color:var(--l-color-surface-overlay);max-block-size:min(80dvb,100%);color:var(--l-color-text-primary);opacity:0;transition-property:opacity,display,overlay;transition-duration:var(--hide-duration);transition-behavior:allow-discrete;border:0;margin:auto;padding:0;position:fixed;inset:0}dialog::backdrop{background:var(--backdrop);-webkit-backdrop-filter:blur(var(--backdrop-blur));backdrop-filter:blur(var(--backdrop-blur))}dialog[open]{opacity:1;transition-duration:var(--show-duration);grid-template-rows:auto minmax(0,1fr) auto;display:grid}@starting-style{dialog[open]{opacity:0}}[part=header]{padding:var(--padding);justify-content:space-between;align-items:center;gap:1rem;display:flex}[part=title]{margin:0;font-size:1.125rem;font-weight:600;line-height:1.4}[part=body]{padding-inline:var(--padding);overflow-y:auto}[part=footer]{padding:var(--padding);place-content:end;gap:.5rem;display:flex}::slotted(menu[slot=footer]){display:contents}@media (prefers-reduced-motion:reduce){:host{--show-duration:0s;--hide-duration:0s}}`);export{t as default};
|
|
1
|
+
import{c as e}from"../../chunks/lit.js";var t=e(`:host{--width:31rem;--border-radius:6px;--padding:1.5rem;--show-duration:.2s;--hide-duration:.2s;--backdrop:var(--l-backdrop);--backdrop-blur:0;display:contents}dialog{box-sizing:border-box;width:var(--width);max-inline-size:min(90vw, var(--width));border-radius:var(--border-radius);background-color:var(--l-color-surface-overlay);max-block-size:min(80dvb,100%);color:var(--l-color-text-primary);opacity:0;transition-property:opacity,display,overlay;transition-duration:var(--hide-duration);transition-behavior:allow-discrete;border:0;margin:auto;padding:0;position:fixed;inset:0}dialog::backdrop{background:var(--backdrop);-webkit-backdrop-filter:blur(var(--backdrop-blur));backdrop-filter:blur(var(--backdrop-blur))}dialog[open]{opacity:1;transition-duration:var(--show-duration);grid-template-rows:auto minmax(0,1fr) auto;display:grid}@starting-style{dialog[open]{opacity:0}}[part=header]{padding:var(--padding);justify-content:space-between;align-items:center;gap:1rem;display:flex}[part=title]{margin:0;font-size:1.125rem;font-weight:600;line-height:1.4}[part=body]{padding-inline:var(--padding);grid-row:2;overflow-y:auto}[part=footer]{padding:var(--padding);grid-row:3;place-content:end;gap:.5rem;display:flex}:host([without-header]) [part=body]{padding-block-start:var(--padding)}::slotted(menu[slot=footer]){display:contents}@media (prefers-reduced-motion:reduce){:host{--show-duration:0s;--hide-duration:0s}}`);export{t as default};
|
|
2
2
|
//# sourceMappingURL=dialog.styles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.styles.js","names":[],"sources":["../../../src/html/elements/dialog/dialog.css?inline","../../../src/html/elements/dialog/dialog.styles.ts"],"sourcesContent":[":host {\n --width: 31rem;\n --border-radius: 6px;\n --padding: 1.5rem;\n --show-duration: 200ms;\n --hide-duration: 200ms;\n --backdrop: var(--l-backdrop);\n --backdrop-blur: 0;\n\n display: contents;\n}\n\ndialog {\n position: fixed;\n inset: 0;\n box-sizing: border-box;\n width: var(--width);\n max-inline-size: min(90vw, var(--width));\n max-block-size: min(80dvb, 100%);\n margin: auto;\n padding: 0;\n border: 0;\n border-radius: var(--border-radius);\n background-color: var(--l-color-surface-overlay);\n color: var(--l-color-text-primary);\n\n /* EXIT STATE */\n opacity: 0;\n\n transition-property: opacity, display, overlay;\n transition-duration: var(--hide-duration);\n transition-behavior: allow-discrete;\n\n &::backdrop {\n background: var(--backdrop);\n backdrop-filter: blur(var(--backdrop-blur));\n }\n\n /* OPEN STATE */\n /* grid layout pins header/footer; the middle row (body) scrolls via\n overflow on [part='body'] and minmax(0, 1fr) allowing it to shrink. */\n &[open] {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr) auto;\n opacity: 1;\n transition-duration: var(--show-duration);\n }\n\n /* BEFORE-OPEN STATE */\n @starting-style {\n &[open] {\n opacity: 0;\n }\n }\n}\n\n[part='header'] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 1rem;\n padding: var(--padding);\n}\n\n[part='title'] {\n margin: 0;\n font-size: 1.125rem;\n font-weight: 600;\n line-height: 1.4;\n}\n\n[part='body'] {\n padding-inline: var(--padding);\n overflow-y: auto;\n}\n\n[part='footer'] {\n display: flex;\n place-content: end;\n gap: 0.5rem;\n padding: var(--padding);\n}\n\n::slotted(menu[slot='footer']) {\n display: contents;\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host {\n --show-duration: 0ms;\n --hide-duration: 0ms;\n }\n}\n","import { unsafeCSS } from 'lit';\nimport raw from './dialog.css?inline';\n\n/**\n * Wrapper module: imported by both `dialog.ts` and `drawer.ts`.\n * `unsafeCSS()` is called once here so both importers share the same\n * `CSSResult` instance (one constructed `CSSStyleSheet`, not two).\n */\nexport default unsafeCSS(raw);\n"],"mappings":"wCCQA,IAAA,EAAe,
|
|
1
|
+
{"version":3,"file":"dialog.styles.js","names":[],"sources":["../../../src/html/elements/dialog/dialog.css?inline","../../../src/html/elements/dialog/dialog.styles.ts"],"sourcesContent":[":host {\n --width: 31rem;\n --border-radius: 6px;\n --padding: 1.5rem;\n --show-duration: 200ms;\n --hide-duration: 200ms;\n --backdrop: var(--l-backdrop);\n --backdrop-blur: 0;\n\n display: contents;\n}\n\ndialog {\n position: fixed;\n inset: 0;\n box-sizing: border-box;\n width: var(--width);\n max-inline-size: min(90vw, var(--width));\n max-block-size: min(80dvb, 100%);\n margin: auto;\n padding: 0;\n border: 0;\n border-radius: var(--border-radius);\n background-color: var(--l-color-surface-overlay);\n color: var(--l-color-text-primary);\n\n /* EXIT STATE */\n opacity: 0;\n\n transition-property: opacity, display, overlay;\n transition-duration: var(--hide-duration);\n transition-behavior: allow-discrete;\n\n &::backdrop {\n background: var(--backdrop);\n backdrop-filter: blur(var(--backdrop-blur));\n }\n\n /* OPEN STATE */\n /* grid layout pins header/footer; the middle row (body) scrolls via\n overflow on [part='body'] and minmax(0, 1fr) allowing it to shrink. */\n &[open] {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr) auto;\n opacity: 1;\n transition-duration: var(--show-duration);\n }\n\n /* BEFORE-OPEN STATE */\n @starting-style {\n &[open] {\n opacity: 0;\n }\n }\n}\n\n[part='header'] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 1rem;\n padding: var(--padding);\n}\n\n[part='title'] {\n margin: 0;\n font-size: 1.125rem;\n font-weight: 600;\n line-height: 1.4;\n}\n\n/* Pin body and footer so the layout stays correct when [without-header]\n removes the header and only two children auto-place into the grid. */\n[part='body'] {\n grid-row: 2;\n padding-inline: var(--padding);\n overflow-y: auto;\n}\n\n[part='footer'] {\n grid-row: 3;\n display: flex;\n place-content: end;\n gap: 0.5rem;\n padding: var(--padding);\n}\n\n/* Without a header, the body has to provide its own block-start padding\n (normally inherited visually from the header padding above it). */\n:host([without-header]) [part='body'] {\n padding-block-start: var(--padding);\n}\n\n::slotted(menu[slot='footer']) {\n display: contents;\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host {\n --show-duration: 0ms;\n --hide-duration: 0ms;\n }\n}\n","import { unsafeCSS } from 'lit';\nimport raw from './dialog.css?inline';\n\n/**\n * Wrapper module: imported by both `dialog.ts` and `drawer.ts`.\n * `unsafeCSS()` is called once here so both importers share the same\n * `CSSResult` instance (one constructed `CSSStyleSheet`, not two).\n */\nexport default unsafeCSS(raw);\n"],"mappings":"wCCQA,IAAA,EAAe,g5CAAc"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{tagName as e}from"../../registry.js";import{c as t,i as n}from"../../chunks/lit.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{a as i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";import{PopoverController as s}from"../../shared/controllers/popover.js";var c=t(`:host{--background:var(--l-color-bg-surface,Canvas);--radius:
|
|
1
|
+
import{tagName as e}from"../../registry.js";import{c as t,i as n}from"../../chunks/lit.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{a as i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";import{PopoverController as s}from"../../shared/controllers/popover.js";var c=t(`:host{--background:var(--l-color-bg-surface,Canvas);--radius:6px;--shadow:0 4px 6px -1px #00000014, 0 2px 4px -2px #0000000f;--show-duration:150;--hide-duration:150;display:inline-block;position:relative}.trigger{display:contents}[popover]{inset:unset;box-sizing:border-box;width:max-content;min-width:anchor-size(width);border:1px solid var(--l-color-border-overlay,var(--lightningcss-light,#e5e7eb)var(--lightningcss-dark,#374151));border-radius:var(--radius);background:var(--background);color:var(--l-color-text-primary,CanvasText);box-shadow:var(--shadow);margin:0;padding:.25rem;font-size:.875rem;line-height:1.5;overflow:visible}`),l=class extends r{constructor(...t){super(...t),this._floating=new s(this,{getTriggerElement:()=>this._triggerEl,getFloatingElement:()=>this._panelEl,getArrowElement:()=>null}),this._typeaheadBuffer=``,this._typeaheadTimeout=0,this.#e=!1,this.#t=`bottom-start`,this.#n=4,this.#r=!1,this._onTriggerClick=()=>{this.disabled||this.toggle()},this._onTriggerKeyDown=e=>{this.disabled||(e.key===`ArrowDown`?(e.preventDefault(),this.show(),requestAnimationFrame(()=>this._focusFirstItem())):e.key===`ArrowUp`&&(e.preventDefault(),this.show(),requestAnimationFrame(()=>this._focusLastItem())))},this._onPanelKeyDown=e=>{switch(e.key){case`ArrowDown`:e.preventDefault(),this._focusNextItem();break;case`ArrowUp`:e.preventDefault(),this._focusPreviousItem();break;case`Home`:e.preventDefault(),this._focusFirstItem();break;case`End`:e.preventDefault(),this._focusLastItem();break;case`Escape`:e.preventDefault(),this.hide(),this._triggerEl?.focus();break;case`Enter`:case` `:e.preventDefault(),this._selectCurrentItem();break;default:e.key.length===1&&!e.ctrlKey&&!e.metaKey&&this._handleTypeahead(e.key)}},this._onItemClick=t=>{let n=t.target.closest(e(`dropdown-item`));n&&!n.disabled&&this._selectItem(n)},this._onToggle=e=>{e.newState===`closed`&&this.open&&(this.open=!1,this._triggerEl?.setAttribute(`aria-expanded`,`false`))}}static{this.styles=[o,c]}#e;get open(){return this.#e}set open(e){this.#e=e}#t;get placement(){return this.#t}set placement(e){this.#t=e}#n;get distance(){return this.#n}set distance(e){this.#n=e}#r;get disabled(){return this.#r}set disabled(e){this.#r=e}get _triggerEl(){return this.shadowRoot.querySelector(`.trigger slot`)?.assignedElements()[0]??null}get _panelEl(){return this.shadowRoot.querySelector(`[popover]`)}_getItems(){let t=this.shadowRoot.querySelector(`slot:not([name])`);return t?t.assignedElements().filter(t=>t.tagName===e(`dropdown-item`).toUpperCase()&&!t.disabled):[]}_getAllItems(){let t=this.shadowRoot.querySelector(`slot:not([name])`);return t?t.assignedElements().filter(t=>t.tagName===e(`dropdown-item`).toUpperCase()):[]}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}show(){this.open||this.disabled||this.emit(`show`,{cancelable:!0})&&(this.open=!0)}hide(){this.open&&this.emit(`hide`,{cancelable:!0})&&(this.open=!1)}toggle(){this.open?this.hide():this.show()}updated(e){e.has(`open`)&&this._handleOpenChange()}async _handleOpenChange(){let e=this._panelEl;if(!e)return;let t={placement:this.placement,distance:this.distance};if(this.open){if(e.showPopover(),await this._floating.updatePosition(t),!this.open)return;await this._floating.animateShow(e,this._getDuration(`--show-duration`)),this._floating.startPositioning(t),this._triggerEl?.setAttribute(`aria-expanded`,`true`),this.emit(`after-show`)}else this._floating.stopPositioning(),this._triggerEl?.setAttribute(`aria-expanded`,`false`),await this._floating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover(),this.emit(`after-hide`)}_setActiveItem(e){let t=e.shadowRoot.querySelector(`.item`);if(t){for(let e of this._getAllItems())e.shadowRoot.querySelector(`.item`)?.setAttribute(`tabindex`,`-1`);t.setAttribute(`tabindex`,`0`),t.focus()}}_focusFirstItem(){let e=this._getItems();e.length&&this._setActiveItem(e[0])}_focusLastItem(){let e=this._getItems();e.length&&this._setActiveItem(e[e.length-1])}_getCurrentItem(){return this._getItems().find(e=>{let t=e.shadowRoot.querySelector(`.item`);return t?.getAttribute(`tabindex`)===`0`&&e.shadowRoot.activeElement===t})??null}_focusNextItem(){let e=this._getItems(),t=this._getCurrentItem(),n=e[((t?e.indexOf(t):-1)+1)%e.length];n&&this._setActiveItem(n)}_focusPreviousItem(){let e=this._getItems(),t=this._getCurrentItem(),n=e[((t?e.indexOf(t):0)-1+e.length)%e.length];n&&this._setActiveItem(n)}_handleTypeahead(e){clearTimeout(this._typeaheadTimeout),this._typeaheadBuffer+=e.toLowerCase(),this._typeaheadTimeout=window.setTimeout(()=>{this._typeaheadBuffer=``},500);let t=this._getItems().find(e=>e.getTextLabel().toLowerCase().startsWith(this._typeaheadBuffer));t&&this._setActiveItem(t)}_selectCurrentItem(){let e=this._getCurrentItem();e&&this._selectItem(e)}_selectItem(e){e.type===`checkbox`&&(e.checked=!e.checked),this.emit(`select`,{detail:{item:e}}),e.type!==`checkbox`&&(this.hide(),this._triggerEl?.focus())}render(){return n`
|
|
2
2
|
<div
|
|
3
3
|
class="trigger"
|
|
4
4
|
@click=${this._onTriggerClick}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown.js","names":[],"sources":["../../../src/html/elements/dropdown/dropdown.css?inline","../../../src/html/elements/dropdown/dropdown.ts"],"sourcesContent":[":host {\n --background: var(--l-color-bg-surface, Canvas);\n --radius: 8px;\n --shadow: 0 4px 16px rgb(0 0 0 / 12%);\n --show-duration: 150;\n --hide-duration: 150;\n\n display: inline-block;\n position: relative;\n}\n\n.trigger {\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n box-sizing: border-box;\n width: max-content;\n min-width: anchor-size(width);\n padding: 4px 0;\n margin: 0;\n border: 1px solid var(--l-color-border, light-dark(#e5e7eb, #374151));\n border-radius: var(--radius);\n background: var(--background);\n color: var(--l-color-text-primary, CanvasText);\n box-shadow: var(--shadow);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n","import { html, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover';\nimport { tagName } from '../../registry';\nimport type { LuxenDropdownItem } from '../dropdown-item/dropdown-item';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './dropdown.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A dropdown menu anchored to a trigger element.\n *\n * @slot trigger - The element that triggers the dropdown.\n * @slot - Menu content (`l-dropdown-item` elements).\n *\n * @csspart panel - The floating menu container.\n *\n * @cssproperty --background - Panel background color.\n * @cssproperty --radius - Panel border radius. Default `8px`.\n * @cssproperty --shadow - Panel box shadow.\n * @cssproperty --show-duration - Show animation duration in ms. Default `150`.\n * @cssproperty --hide-duration - Hide animation duration in ms. Default `150`.\n *\n * @event show - Fired before the dropdown opens. Cancelable.\n * @event after-show - Fired after the open animation completes.\n * @event hide - Fired before the dropdown closes. Cancelable.\n * @event after-hide - Fired after the close animation completes.\n * @event select - Fired when an item is selected. Detail: `{ item: LuxenDropdownItem }`.\n */\nexport class LuxenDropdown extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._triggerEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _typeaheadBuffer = '';\n private _typeaheadTimeout = 0;\n\n /** Whether the dropdown is open. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Preferred placement of the panel. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Distance in pixels from the trigger. */\n @property({ type: Number })\n accessor distance = 4;\n\n /** Disables the dropdown trigger. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n private get _triggerEl(): HTMLElement | null {\n const slot = this.shadowRoot!.querySelector<HTMLSlotElement>('.trigger slot');\n return (slot?.assignedElements()[0] as HTMLElement) ?? null;\n }\n\n private get _panelEl(): HTMLElement | null {\n return this.shadowRoot!.querySelector<HTMLElement>('[popover]');\n }\n\n private _getItems(): LuxenDropdownItem[] {\n const menuSlot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!menuSlot) return [];\n return (menuSlot.assignedElements() as LuxenDropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase() && !el.disabled,\n );\n }\n\n private _getAllItems(): LuxenDropdownItem[] {\n const menuSlot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!menuSlot) return [];\n return (menuSlot.assignedElements() as LuxenDropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase(),\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 // --- Public API ---\n\n show() {\n if (this.open || this.disabled) return;\n if (this.emit('show', { cancelable: true })) this.open = true;\n }\n\n hide() {\n if (!this.open) return;\n if (this.emit('hide', { cancelable: true })) this.open = false;\n }\n\n toggle() {\n if (this.open) this.hide();\n else this.show();\n }\n\n // --- Lifecycle ---\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('open')) {\n this._handleOpenChange();\n }\n }\n\n // --- Open/Close ---\n\n private async _handleOpenChange() {\n const panel = this._panelEl;\n if (!panel) return;\n\n const posOpts = { placement: this.placement, distance: this.distance };\n\n if (this.open) {\n panel.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(panel, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._triggerEl?.setAttribute('aria-expanded', 'true');\n this.emit('after-show');\n } else {\n this._floating.stopPositioning();\n this._triggerEl?.setAttribute('aria-expanded', 'false');\n await this._floating.animateHide(panel, this._getDuration('--hide-duration'));\n if (panel.matches(':popover-open')) panel.hidePopover();\n this.emit('after-hide');\n }\n }\n\n // --- Focus management ---\n\n private _setActiveItem(item: LuxenDropdownItem) {\n const itemEl = item.shadowRoot!.querySelector<HTMLElement>('.item');\n if (!itemEl) return;\n\n // Reset all items\n for (const i of this._getAllItems()) {\n const el = i.shadowRoot!.querySelector<HTMLElement>('.item');\n el?.setAttribute('tabindex', '-1');\n }\n\n itemEl.setAttribute('tabindex', '0');\n itemEl.focus();\n }\n\n private _focusFirstItem() {\n const items = this._getItems();\n if (items.length) this._setActiveItem(items[0]);\n }\n\n private _focusLastItem() {\n const items = this._getItems();\n if (items.length) this._setActiveItem(items[items.length - 1]);\n }\n\n private _getCurrentItem(): LuxenDropdownItem | null {\n const items = this._getItems();\n return (\n items.find((item) => {\n const el = item.shadowRoot!.querySelector<HTMLElement>('.item');\n return el?.getAttribute('tabindex') === '0' && item.shadowRoot!.activeElement === el;\n }) ?? null\n );\n }\n\n private _focusNextItem() {\n const items = this._getItems();\n const current = this._getCurrentItem();\n const index = current ? items.indexOf(current) : -1;\n const next = items[(index + 1) % items.length];\n if (next) this._setActiveItem(next);\n }\n\n private _focusPreviousItem() {\n const items = this._getItems();\n const current = this._getCurrentItem();\n const index = current ? items.indexOf(current) : 0;\n const prev = items[(index - 1 + items.length) % items.length];\n if (prev) this._setActiveItem(prev);\n }\n\n // --- Typeahead ---\n\n private _handleTypeahead(key: string) {\n clearTimeout(this._typeaheadTimeout);\n this._typeaheadBuffer += key.toLowerCase();\n this._typeaheadTimeout = window.setTimeout(() => {\n this._typeaheadBuffer = '';\n }, 500);\n\n const items = this._getItems();\n const match = items.find((item) =>\n item.getTextLabel().toLowerCase().startsWith(this._typeaheadBuffer),\n );\n if (match) this._setActiveItem(match);\n }\n\n // --- Event handlers ---\n\n private _onTriggerClick = () => {\n if (!this.disabled) this.toggle();\n };\n\n private _onTriggerKeyDown = (e: KeyboardEvent) => {\n if (this.disabled) return;\n\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n this.show();\n requestAnimationFrame(() => this._focusFirstItem());\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n this.show();\n requestAnimationFrame(() => this._focusLastItem());\n }\n };\n\n private _onPanelKeyDown = (e: KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n this._focusNextItem();\n break;\n case 'ArrowUp':\n e.preventDefault();\n this._focusPreviousItem();\n break;\n case 'Home':\n e.preventDefault();\n this._focusFirstItem();\n break;\n case 'End':\n e.preventDefault();\n this._focusLastItem();\n break;\n case 'Escape':\n e.preventDefault();\n this.hide();\n this._triggerEl?.focus();\n break;\n case 'Enter':\n case ' ':\n e.preventDefault();\n this._selectCurrentItem();\n break;\n default:\n if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {\n this._handleTypeahead(e.key);\n }\n }\n };\n\n private _onItemClick = (e: Event) => {\n const item = (e.target as HTMLElement).closest<LuxenDropdownItem>(tagName('dropdown-item'));\n if (item && !item.disabled) {\n this._selectItem(item);\n }\n };\n\n private _selectCurrentItem() {\n const current = this._getCurrentItem();\n if (current) this._selectItem(current);\n }\n\n private _selectItem(item: LuxenDropdownItem) {\n if (item.type === 'checkbox') {\n item.checked = !item.checked;\n }\n this.emit('select', { detail: { item } });\n if (item.type !== 'checkbox') {\n this.hide();\n this._triggerEl?.focus();\n }\n }\n\n /** Sync `open` when popover=\"auto\" light-dismiss fires. */\n private _onToggle = (e: Event) => {\n const toggleEvent = e as ToggleEvent;\n if (toggleEvent.newState === 'closed' && this.open) {\n this.open = false;\n this._triggerEl?.setAttribute('aria-expanded', 'false');\n }\n };\n\n override render() {\n return html`\n <div\n class=\"trigger\"\n @click=${this._onTriggerClick}\n @keydown=${this._onTriggerKeyDown}\n >\n <slot name=\"trigger\"></slot>\n </div>\n <div\n popover=\"auto\"\n part=\"panel\"\n role=\"menu\"\n @keydown=${this._onPanelKeyDown}\n @click=${this._onItemClick}\n @toggle=${this._onToggle}\n >\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dropdown.js","names":[],"sources":["../../../src/html/elements/dropdown/dropdown.css?inline","../../../src/html/elements/dropdown/dropdown.ts"],"sourcesContent":[":host {\n --background: var(--l-color-bg-surface, Canvas);\n --radius: 6px;\n --shadow: 0 4px 6px -1px rgb(0 0 0 / 8%), 0 2px 4px -2px rgb(0 0 0 / 6%);\n --show-duration: 150;\n --hide-duration: 150;\n\n display: inline-block;\n position: relative;\n}\n\n.trigger {\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n box-sizing: border-box;\n width: max-content;\n min-width: anchor-size(width);\n padding: 0.25rem;\n margin: 0;\n border: 1px solid var(--l-color-border-overlay, light-dark(#e5e7eb, #374151));\n border-radius: var(--radius);\n background: var(--background);\n color: var(--l-color-text-primary, CanvasText);\n box-shadow: var(--shadow);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n","import { html, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover';\nimport { tagName } from '../../registry';\nimport type { LuxenDropdownItem } from '../dropdown-item/dropdown-item';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './dropdown.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A dropdown menu anchored to a trigger element.\n *\n * @slot trigger - The element that triggers the dropdown.\n * @slot - Menu content (`l-dropdown-item` elements).\n *\n * @csspart panel - The floating menu container.\n *\n * @cssproperty --background - Panel background color.\n * @cssproperty --radius - Panel border radius. Default `8px`.\n * @cssproperty --shadow - Panel box shadow.\n * @cssproperty --show-duration - Show animation duration in ms. Default `150`.\n * @cssproperty --hide-duration - Hide animation duration in ms. Default `150`.\n *\n * @event show - Fired before the dropdown opens. Cancelable.\n * @event after-show - Fired after the open animation completes.\n * @event hide - Fired before the dropdown closes. Cancelable.\n * @event after-hide - Fired after the close animation completes.\n * @event select - Fired when an item is selected. Detail: `{ item: LuxenDropdownItem }`.\n */\nexport class LuxenDropdown extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._triggerEl,\n getFloatingElement: () => this._panelEl,\n getArrowElement: () => null,\n });\n\n private _typeaheadBuffer = '';\n private _typeaheadTimeout = 0;\n\n /** Whether the dropdown is open. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Preferred placement of the panel. */\n @property()\n accessor placement: Placement = 'bottom-start';\n\n /** Distance in pixels from the trigger. */\n @property({ type: Number })\n accessor distance = 4;\n\n /** Disables the dropdown trigger. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n private get _triggerEl(): HTMLElement | null {\n const slot = this.shadowRoot!.querySelector<HTMLSlotElement>('.trigger slot');\n return (slot?.assignedElements()[0] as HTMLElement) ?? null;\n }\n\n private get _panelEl(): HTMLElement | null {\n return this.shadowRoot!.querySelector<HTMLElement>('[popover]');\n }\n\n private _getItems(): LuxenDropdownItem[] {\n const menuSlot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!menuSlot) return [];\n return (menuSlot.assignedElements() as LuxenDropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase() && !el.disabled,\n );\n }\n\n private _getAllItems(): LuxenDropdownItem[] {\n const menuSlot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!menuSlot) return [];\n return (menuSlot.assignedElements() as LuxenDropdownItem[]).filter(\n (el) => el.tagName === tagName('dropdown-item').toUpperCase(),\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 // --- Public API ---\n\n show() {\n if (this.open || this.disabled) return;\n if (this.emit('show', { cancelable: true })) this.open = true;\n }\n\n hide() {\n if (!this.open) return;\n if (this.emit('hide', { cancelable: true })) this.open = false;\n }\n\n toggle() {\n if (this.open) this.hide();\n else this.show();\n }\n\n // --- Lifecycle ---\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('open')) {\n this._handleOpenChange();\n }\n }\n\n // --- Open/Close ---\n\n private async _handleOpenChange() {\n const panel = this._panelEl;\n if (!panel) return;\n\n const posOpts = { placement: this.placement, distance: this.distance };\n\n if (this.open) {\n panel.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(panel, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._triggerEl?.setAttribute('aria-expanded', 'true');\n this.emit('after-show');\n } else {\n this._floating.stopPositioning();\n this._triggerEl?.setAttribute('aria-expanded', 'false');\n await this._floating.animateHide(panel, this._getDuration('--hide-duration'));\n if (panel.matches(':popover-open')) panel.hidePopover();\n this.emit('after-hide');\n }\n }\n\n // --- Focus management ---\n\n private _setActiveItem(item: LuxenDropdownItem) {\n const itemEl = item.shadowRoot!.querySelector<HTMLElement>('.item');\n if (!itemEl) return;\n\n // Reset all items\n for (const i of this._getAllItems()) {\n const el = i.shadowRoot!.querySelector<HTMLElement>('.item');\n el?.setAttribute('tabindex', '-1');\n }\n\n itemEl.setAttribute('tabindex', '0');\n itemEl.focus();\n }\n\n private _focusFirstItem() {\n const items = this._getItems();\n if (items.length) this._setActiveItem(items[0]);\n }\n\n private _focusLastItem() {\n const items = this._getItems();\n if (items.length) this._setActiveItem(items[items.length - 1]);\n }\n\n private _getCurrentItem(): LuxenDropdownItem | null {\n const items = this._getItems();\n return (\n items.find((item) => {\n const el = item.shadowRoot!.querySelector<HTMLElement>('.item');\n return el?.getAttribute('tabindex') === '0' && item.shadowRoot!.activeElement === el;\n }) ?? null\n );\n }\n\n private _focusNextItem() {\n const items = this._getItems();\n const current = this._getCurrentItem();\n const index = current ? items.indexOf(current) : -1;\n const next = items[(index + 1) % items.length];\n if (next) this._setActiveItem(next);\n }\n\n private _focusPreviousItem() {\n const items = this._getItems();\n const current = this._getCurrentItem();\n const index = current ? items.indexOf(current) : 0;\n const prev = items[(index - 1 + items.length) % items.length];\n if (prev) this._setActiveItem(prev);\n }\n\n // --- Typeahead ---\n\n private _handleTypeahead(key: string) {\n clearTimeout(this._typeaheadTimeout);\n this._typeaheadBuffer += key.toLowerCase();\n this._typeaheadTimeout = window.setTimeout(() => {\n this._typeaheadBuffer = '';\n }, 500);\n\n const items = this._getItems();\n const match = items.find((item) =>\n item.getTextLabel().toLowerCase().startsWith(this._typeaheadBuffer),\n );\n if (match) this._setActiveItem(match);\n }\n\n // --- Event handlers ---\n\n private _onTriggerClick = () => {\n if (!this.disabled) this.toggle();\n };\n\n private _onTriggerKeyDown = (e: KeyboardEvent) => {\n if (this.disabled) return;\n\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n this.show();\n requestAnimationFrame(() => this._focusFirstItem());\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n this.show();\n requestAnimationFrame(() => this._focusLastItem());\n }\n };\n\n private _onPanelKeyDown = (e: KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n this._focusNextItem();\n break;\n case 'ArrowUp':\n e.preventDefault();\n this._focusPreviousItem();\n break;\n case 'Home':\n e.preventDefault();\n this._focusFirstItem();\n break;\n case 'End':\n e.preventDefault();\n this._focusLastItem();\n break;\n case 'Escape':\n e.preventDefault();\n this.hide();\n this._triggerEl?.focus();\n break;\n case 'Enter':\n case ' ':\n e.preventDefault();\n this._selectCurrentItem();\n break;\n default:\n if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {\n this._handleTypeahead(e.key);\n }\n }\n };\n\n private _onItemClick = (e: Event) => {\n const item = (e.target as HTMLElement).closest<LuxenDropdownItem>(tagName('dropdown-item'));\n if (item && !item.disabled) {\n this._selectItem(item);\n }\n };\n\n private _selectCurrentItem() {\n const current = this._getCurrentItem();\n if (current) this._selectItem(current);\n }\n\n private _selectItem(item: LuxenDropdownItem) {\n if (item.type === 'checkbox') {\n item.checked = !item.checked;\n }\n this.emit('select', { detail: { item } });\n if (item.type !== 'checkbox') {\n this.hide();\n this._triggerEl?.focus();\n }\n }\n\n /** Sync `open` when popover=\"auto\" light-dismiss fires. */\n private _onToggle = (e: Event) => {\n const toggleEvent = e as ToggleEvent;\n if (toggleEvent.newState === 'closed' && this.open) {\n this.open = false;\n this._triggerEl?.setAttribute('aria-expanded', 'false');\n }\n };\n\n override render() {\n return html`\n <div\n class=\"trigger\"\n @click=${this._onTriggerClick}\n @keydown=${this._onTriggerKeyDown}\n >\n <slot name=\"trigger\"></slot>\n </div>\n <div\n popover=\"auto\"\n part=\"panel\"\n role=\"menu\"\n @keydown=${this._onPanelKeyDown}\n @click=${this._onItemClick}\n @toggle=${this._onToggle}\n >\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as e,i as t,n}from"../../chunks/lit.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{a as i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";var s=e(`:host{display:block}:host([disabled]){pointer-events:none;opacity:.5}.item{cursor:pointer;color:var(--l-color-text-primary,CanvasText);white-space:nowrap;outline:none;align-items:center;gap:8px;padding
|
|
1
|
+
import{c as e,i as t,n}from"../../chunks/lit.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{a as i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";var s=e(`:host{display:block}:host([disabled]){pointer-events:none;opacity:.5}.item{cursor:pointer;color:var(--l-color-text-primary,CanvasText);white-space:nowrap;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)}@media (hover:hover){.item:hover{background:var(--l-color-bg-state-hover)}}.check{flex-shrink:0;width:16px;display:flex}:host(:not([checked])) .check svg{visibility:hidden}::slotted([slot=prefix]),::slotted([slot=suffix]){flex-shrink:0;display:flex}.label{flex:1}`),c=class extends r{static{this.styles=[o,s]}#e=``;get value(){return this.#e}set value(e){this.#e=e}#t=!1;get disabled(){return this.#t}set disabled(e){this.#t=e}#n=`normal`;get type(){return this.#n}set type(e){this.#n=e}#r=!1;get checked(){return this.#r}set checked(e){this.#r=e}getTextLabel(){return(this.textContent??``).trim()}render(){let e=this.type===`checkbox`;return t`
|
|
2
2
|
<div
|
|
3
3
|
class="item"
|
|
4
4
|
role=${e?`menuitemcheckbox`:`menuitem`}
|
|
@@ -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 padding:
|
|
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 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, CanvasText);\n white-space: nowrap;\n}\n\n.item:focus-visible {\n background: var(--l-color-bg-state-hover);\n}\n\n@media (hover: hover) {\n .item:hover {\n background: var(--l-color-bg-state-hover);\n }\n}\n\n.check {\n display: flex;\n width: 16px;\n flex-shrink: 0;\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 } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { property } from 'lit/decorators.js';\nimport hostStyles from '../../shared/styles/host.styles';\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 *\n * @cssproperty --color - Text color.\n */\nexport class LuxenDropdownItem extends LuxenElement {\n static override styles = [hostStyles, styles];\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 /** Returns the text label of this item. */\n getTextLabel(): string {\n return (this.textContent ?? '').trim();\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 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 class=\"label\"><slot></slot></span>\n <slot name=\"suffix\"></slot>\n </div>\n `;\n }\n}\n"],"mappings":"oNCMA,IAAM,EAAS,kmBAAoB,CAWtB,EAAb,cAAuC,CAAa,oBACzB,CAAC,EAAY,EAAO,IAI5B,OAAR,OAAA,0CAIW,OAAX,UAAA,6CAI8B,aAA9B,MAAA,yCAIU,OAAV,SAAA,yCAGT,cAAuB,CACrB,OAAQ,KAAK,aAAe,IAAI,MAAM,CAGxC,QAAkB,CAChB,IAAM,EAAa,KAAK,OAAS,WAEjC,MAAO,EAAI;;;eAGA,EAAa,mBAAqB,WAAW;uBACrC,EAAa,OAAO,KAAK,QAAQ,CAAG,EAAQ;wBAC3C,KAAK,SAAW,OAAS,EAAQ;;;UAG/C,EACE,CAAI;;;;;;;;;;;;;;;;;;;;cAqBJ,CAAI,gCAAgC;;;;WArD7C,GAAU,CAAA,CAAA,EAAA,UAAA,QAAA,KAAA,IAIV,EAAS,CAAE,KAAM,QAAS,QAAS,GAAM,CAAC,CAAA,CAAA,EAAA,UAAA,WAAA,KAAA,IAI1C,GAAU,CAAA,CAAA,EAAA,UAAA,OAAA,KAAA,IAIV,EAAS,CAAE,KAAM,QAAS,QAAS,GAAM,CAAC,CAAA,CAAA,EAAA,UAAA,UAAA,KAAA"}
|
|
@@ -9,8 +9,13 @@ import { LuxenElement } from '../../shared/luxen-element';
|
|
|
9
9
|
* @customElement l-input-otp
|
|
10
10
|
*
|
|
11
11
|
* @cssproperty --digits - Number of digit boxes (default: 6). Must match input's maxlength.
|
|
12
|
-
* @cssproperty --size - Cell width and height (default: 2.75rem). Font size scales automatically.
|
|
13
|
-
* @cssproperty --gap - Space between cells (default: 0.5rem).
|
|
12
|
+
* @cssproperty --cell-size - Cell width and height (default: 2.75rem). Font size scales automatically.
|
|
13
|
+
* @cssproperty --cell-gap - Space between cells (default: 0.5rem).
|
|
14
|
+
* @cssproperty --cell-bg-color - Cell background color.
|
|
15
|
+
* @cssproperty --cell-border-color - Cell border color.
|
|
16
|
+
* @cssproperty --cell-border-radius - Cell border-radius.
|
|
17
|
+
* @cssproperty --cell-focus-color - Border + ring color of the active (focused) cell.
|
|
18
|
+
* @cssproperty --cell-focus-ring - `box-shadow` of the active cell ring (defaults to a 1px solid ring; set to `none` to disable).
|
|
14
19
|
*/
|
|
15
20
|
export declare class LuxenInputOtp extends LuxenElement {
|
|
16
21
|
createRenderRoot(): this;
|
|
@@ -27,5 +32,6 @@ export declare class LuxenInputOtp extends LuxenElement {
|
|
|
27
32
|
private _teardown;
|
|
28
33
|
private _updateCells;
|
|
29
34
|
private _clearCells;
|
|
35
|
+
private _scheduleUpdateCells;
|
|
30
36
|
}
|
|
31
37
|
//# sourceMappingURL=input-otp.d.ts.map
|
|
@@ -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,4BAA4B,CAAC;AAE1D
|
|
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,4BAA4B,CAAC;AAE1D;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,aAAc,SAAQ,YAAY;IACpC,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;IAEpB,iBAAiB;IAKjB,oBAAoB;IAO7B,OAAO,CAAC,MAAM;IA4Dd,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{LuxenElement as e}from"../../shared/luxen-element.js";import{a as t,t as n}from"../../chunks/decorate.js";var r=class extends e{constructor(...e){super(...e),this._cells=[],this._separatorEl=null,this._initialized=!1,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`)}}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),requestAnimationFrame(()=>this._setup())}disconnectedCallback(){super.disconnectedCallback(),this._teardown()}_setup(){let e=this.querySelector(`input`);if(!e)return;this._input=e;let t=Number(getComputedStyle(this).getPropertyValue(`--digits`).trim())||6,n={type:`text`,inputmode:`numeric`,autocomplete:`one-time-code`,maxlength:String(t),pattern:String.raw`\d{${t}}`};for(let[e,t]of Object.entries(n))this._input.hasAttribute(e)||this._input.setAttribute(e,t);this._container=document.createElement(`div`),this._container.className=`l-input-otp-cells`,this._container.setAttribute(`aria-hidden`,`true`);for(let e=0;e<t;e++){let n=document.createElement(`div`);n.className=`l-input-otp-cell`,n.appendChild(document.createElement(`span`)),this._cells.push(n),this._container.appendChild(n),this.separatorAfter&&e===this.separatorAfter-1&&e<t-1&&(this._separatorEl=document.createElement(`span`),this._separatorEl.className=`l-input-otp-separator`,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.
|
|
1
|
+
import{LuxenElement as e}from"../../shared/luxen-element.js";import{a as t,t as n}from"../../chunks/decorate.js";var r=class extends e{constructor(...e){super(...e),this._cells=[],this._separatorEl=null,this._initialized=!1,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(),requestAnimationFrame(()=>this._setup())}disconnectedCallback(){super.disconnectedCallback(),this._teardown()}_setup(){let e=this.querySelector(`input`);if(!e)return;this._input=e;let t=Number(getComputedStyle(this).getPropertyValue(`--digits`).trim())||6,n={type:`text`,inputmode:`numeric`,autocomplete:`one-time-code`,maxlength:String(t),pattern:String.raw`\d{${t}}`};for(let[e,t]of Object.entries(n))this._input.hasAttribute(e)||this._input.setAttribute(e,t);this._container=document.createElement(`div`),this._container.className=`l-input-otp-cells`,this._container.setAttribute(`aria-hidden`,`true`);for(let e=0;e<t;e++){let n=document.createElement(`div`);n.className=`l-input-otp-cell`,n.appendChild(document.createElement(`span`)),this._cells.push(n),this._container.appendChild(n),this.separatorAfter&&e===this.separatorAfter-1&&e<t-1&&(this._separatorEl=document.createElement(`span`),this._separatorEl.className=`l-input-otp-separator`,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`})],r.prototype,`separatorAfter`,void 0);export{r as LuxenInputOtp};
|
|
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';\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 * @cssproperty --digits - Number of digit boxes (default: 6). Must match input's maxlength.\n * @cssproperty --size - Cell width and height (default: 2.75rem). Font size scales automatically.\n * @cssproperty --gap - Space between cells (default: 0.5rem).\n */\nexport class LuxenInputOtp 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\n override connectedCallback() {\n super.connectedCallback();\n requestAnimationFrame(() => this._setup());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._teardown();\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\n this._container = document.createElement('div');\n this._container.className = 'l-input-otp-cells';\n this._container.setAttribute('aria-hidden', 'true');\n\n for (let i = 0; i < digits; i++) {\n const cell = document.createElement('div');\n cell.className = 'l-input-otp-cell';\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 = 'l-input-otp-separator';\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\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.
|
|
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';\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 * @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 LuxenInputOtp 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\n override connectedCallback() {\n super.connectedCallback();\n requestAnimationFrame(() => this._setup());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._teardown();\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\n this._container = document.createElement('div');\n this._container.className = 'l-input-otp-cells';\n this._container.setAttribute('aria-hidden', 'true');\n\n for (let i = 0; i < digits; i++) {\n const cell = document.createElement('div');\n cell.className = 'l-input-otp-cell';\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 = 'l-input-otp-separator';\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":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{uniqueId as e}from"../../registry.js";import{c as t,i as n,n as r}from"../../chunks/lit.js";import{LuxenElement as i}from"../../shared/luxen-element.js";import{a,t as o}from"../../chunks/decorate.js";import s from"../../shared/styles/host.styles.js";import{PopoverController as c}from"../../shared/controllers/popover.js";var l=t(`:host{--background:var(--l-color-bg-surface,Canvas);--color:inherit;--border-radius:
|
|
1
|
+
import{uniqueId as e}from"../../registry.js";import{c as t,i as n,n as r}from"../../chunks/lit.js";import{LuxenElement as i}from"../../shared/luxen-element.js";import{a,t as o}from"../../chunks/decorate.js";import s from"../../shared/styles/host.styles.js";import{PopoverController as c}from"../../shared/controllers/popover.js";var l=t(`:host{--background:var(--l-color-bg-surface,Canvas);--color:inherit;--border-radius:6px;--max-width:320px;--shadow:0 4px 6px -1px #00000014, 0 2px 4px -2px #0000000f;--arrow-size:8px;--show-duration:.15s;--hide-duration:.15s;--_border-color:var(--l-color-border-overlay,var(--lightningcss-light,#e5e7eb)var(--lightningcss-dark,#374151));display:contents}[popover]{inset:unset;isolation:isolate;box-sizing:border-box;width:max-content;max-width:var(--max-width);border:1px solid var(--_border-color);border-radius:var(--border-radius);background:var(--background);color:var(--color);box-shadow:var(--shadow);font-size:.875rem;line-height:1.5;overflow:visible}:host([full-width]) [popover]{width:100vw;max-width:none}i{width:var(--arrow-size);height:var(--arrow-size);background:var(--background);z-index:-1;display:block;position:absolute;transform:rotate(45deg)}:host([data-placement^=top]) i{border-right:1px solid var(--_border-color);border-bottom:1px solid var(--_border-color)}:host([data-placement^=bottom]) i{border-top:1px solid var(--_border-color);border-left:1px solid var(--_border-color)}:host([data-placement^=left]) i{border-right:1px solid var(--_border-color);border-top:1px solid var(--_border-color)}:host([data-placement^=right]) i{border-left:1px solid var(--_border-color);border-bottom:1px solid var(--_border-color)}`),u=class extends i{constructor(...t){super(...t),this._popoverId=e(`popover`),this._floating=new c(this,{getTriggerElement:()=>this._trigger,getFloatingElement:()=>this._popoverEl,getArrowElement:()=>this._arrowEl,onPlacementChange:e=>{this.dataset.placement=e}}),this.#e=``,this.#t=`bottom`,this.#n=8,this.#r=!1,this.#i=!1,this.#a=!1,this.#o=`click`,this._onPointerEnter=()=>{this._hasTrigger(`hover`)&&(this._floating.cleanupSafePolygon(),this.show())},this._onPointerLeave=e=>{!this._hasTrigger(`hover`)||!this.open||this._floating.handlePointerLeave(e,()=>this.hide())},this._onFocusIn=()=>{this._hasTrigger(`focus`)&&this.show()},this._onFocusOut=()=>{this._hasTrigger(`focus`)&&this.hide()},this._onClick=()=>{this._hasTrigger(`click`)&&this.toggle()},this._onKeyDown=e=>{this.open&&e.key===`Escape`&&(e.stopPropagation(),this.hide())},this._onToggle=e=>{e.newState===`closed`&&this.open&&(this.open=!1)}}static{this.styles=[s,l]}#e;get for(){return this.#e}set for(e){this.#e=e}#t;get placement(){return this.#t}set placement(e){this.#t=e}#n;get distance(){return this.#n}set distance(e){this.#n=e}#r;get open(){return this.#r}set open(e){this.#r=e}#i;get withoutArrow(){return this.#i}set withoutArrow(e){this.#i=e}#a;get fullWidth(){return this.#a}set fullWidth(e){this.#a=e}#o;get trigger(){return this.#o}set trigger(e){this.#o=e}_hasTrigger(e){return this.trigger.split(` `).includes(e)}get _trigger(){return this.for?this.getRootNode().getElementById(this.for):null}get _popoverEl(){return this.shadowRoot.querySelector(`[popover]`)}get _arrowEl(){return this.withoutArrow?null:this.shadowRoot.querySelector(`i`)}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}connectedCallback(){super.connectedCallback(),requestAnimationFrame(()=>this._addTriggerListeners())}disconnectedCallback(){super.disconnectedCallback(),this._removeTriggerListeners()}updated(e){e.has(`open`)&&this._handleOpenChange(),e.has(`for`)&&(this._removeTriggerListeners(e.get(`for`)),this._addTriggerListeners())}show(){this.open||=!0}hide(){this.open&&=!1}toggle(){this.open=!this.open}async _handleOpenChange(){let e=this._popoverEl;if(!e)return;let t={placement:this.placement,distance:this.distance,fullWidth:this.fullWidth};if(this.open){if(e.showPopover(),await this._floating.updatePosition(t),!this.open)return;await this._floating.animateShow(e,this._getDuration(`--show-duration`)),this._floating.startPositioning(t),this._trigger?.setAttribute(`aria-expanded`,`true`),this._trigger?.setAttribute(`aria-controls`,this._popoverId)}else this._floating.stopPositioning(),this._floating.cleanupSafePolygon(),this._trigger?.setAttribute(`aria-expanded`,`false`),this._trigger?.removeAttribute(`aria-controls`),await this._floating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover()}_addTriggerListeners(){this._floating.addTriggerListeners({onPointerEnter:this._onPointerEnter,onPointerLeave:this._onPointerLeave,onFocusIn:this._onFocusIn,onFocusOut:this._onFocusOut,onClick:this._onClick,onKeyDown:this._onKeyDown})}_removeTriggerListeners(e){let t=e?this.getRootNode().getElementById(e):void 0;this._floating.removeTriggerListeners(t)}render(){return n`
|
|
2
2
|
<div
|
|
3
3
|
id=${this._popoverId}
|
|
4
4
|
popover="auto"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"popover.js","names":[],"sources":["../../../src/html/elements/popover/popover.css?inline","../../../src/html/elements/popover/popover.ts"],"sourcesContent":[":host {\n --background: var(--l-color-bg-surface, Canvas);\n --color: inherit;\n --border-radius: 8px;\n --max-width: 320px;\n --shadow: 0 4px 16px rgb(0 0 0 / 12%);\n --arrow-size: 8px;\n --show-duration: 150ms;\n --hide-duration: 150ms;\n\n --_border-color: var(--l-color-border, light-dark(#e5e7eb, #374151));\n\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n isolation: isolate;\n box-sizing: border-box;\n width: max-content;\n max-width: var(--max-width);\n padding: 12px 16px;\n border: 1px solid var(--_border-color);\n border-radius: var(--border-radius);\n background: var(--background);\n color: var(--color);\n font-size: 0.875rem;\n line-height: 1.5;\n box-shadow: var(--shadow);\n}\n\n:host([full-width]) [popover] {\n width: 100vw;\n max-width: none;\n}\n\ni {\n position: absolute;\n display: block;\n width: var(--arrow-size);\n height: var(--arrow-size);\n background: var(--background);\n transform: rotate(45deg);\n z-index: -1;\n}\n\n:host([data-placement^='top']) i {\n border-right: 1px solid var(--_border-color);\n border-bottom: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='bottom']) i {\n border-top: 1px solid var(--_border-color);\n border-left: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='left']) i {\n border-right: 1px solid var(--_border-color);\n border-top: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='right']) i {\n border-left: 1px solid var(--_border-color);\n border-bottom: 1px solid var(--_border-color);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover';\nimport { uniqueId } from '../../registry';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './popover.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * @summary A popover that displays interactive content anchored to a trigger.\n * @customElement l-popover\n *\n * @slot - Popover content.\n *\n * @csspart body - The popover container.\n * @csspart arrow - The directional arrow element.\n *\n * @cssproperty --background - Background color. Default: `Canvas`.\n * @cssproperty --color - Text color. Default: inherited.\n * @cssproperty --border-radius - Border radius. Default `8px`.\n * @cssproperty --max-width - Maximum width. Default `320px`.\n * @cssproperty --shadow - Box shadow.\n * @cssproperty --arrow-size - Arrow size. Default `8px`.\n * @cssproperty --show-duration - Show animation duration. Default `150ms`.\n * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.\n */\nexport class LuxenPopover extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _popoverId = uniqueId('popover');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._trigger,\n getFloatingElement: () => this._popoverEl,\n getArrowElement: () => this._arrowEl,\n onPlacementChange: (p) => {\n this.dataset.placement = p;\n },\n });\n\n /** The HTML id of the element triggering the popover. */\n @property()\n accessor for = '';\n\n /** The preferred placement of the popover. */\n @property()\n accessor placement: Placement = 'bottom';\n\n /** The distance in pixels from the target element. */\n @property({ type: Number })\n accessor distance = 8;\n\n /** Whether or not the popover is visible. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Hide the directional arrow. */\n @property({ type: Boolean, reflect: true, attribute: 'without-arrow' })\n accessor withoutArrow = false;\n\n /** Stretch the popover to the viewport width. Useful for mega menus. */\n @property({ type: Boolean, reflect: true, attribute: 'full-width' })\n accessor fullWidth = false;\n\n /** Space-separated list of trigger modes: `click`, `hover`, `focus`, `manual`. */\n @property()\n accessor trigger = 'click';\n\n private _hasTrigger(type: string) {\n return this.trigger.split(' ').includes(type);\n }\n\n private get _trigger(): HTMLElement | null {\n return this.for ? (this.getRootNode() as Document | ShadowRoot).getElementById(this.for) : null;\n }\n\n private get _popoverEl(): HTMLElement {\n return this.shadowRoot!.querySelector('[popover]')!;\n }\n\n private get _arrowEl(): HTMLElement | null {\n return this.withoutArrow ? null : this.shadowRoot!.querySelector('i');\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 override connectedCallback() {\n super.connectedCallback();\n requestAnimationFrame(() => this._addTriggerListeners());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._removeTriggerListeners();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('open')) {\n this._handleOpenChange();\n }\n if (changed.has('for')) {\n this._removeTriggerListeners(changed.get('for') as string);\n this._addTriggerListeners();\n }\n }\n\n show() {\n if (!this.open) this.open = true;\n }\n\n hide() {\n if (this.open) this.open = false;\n }\n\n toggle() {\n this.open = !this.open;\n }\n\n private async _handleOpenChange() {\n const popover = this._popoverEl;\n if (!popover) return;\n\n const posOpts = {\n placement: this.placement,\n distance: this.distance,\n fullWidth: this.fullWidth,\n };\n\n if (this.open) {\n popover.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(popover, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._trigger?.setAttribute('aria-expanded', 'true');\n this._trigger?.setAttribute('aria-controls', this._popoverId);\n } else {\n this._floating.stopPositioning();\n this._floating.cleanupSafePolygon();\n this._trigger?.setAttribute('aria-expanded', 'false');\n this._trigger?.removeAttribute('aria-controls');\n await this._floating.animateHide(popover, this._getDuration('--hide-duration'));\n if (popover.matches(':popover-open')) popover.hidePopover();\n }\n }\n\n // --- Trigger event handlers ---\n\n private _onPointerEnter = () => {\n if (!this._hasTrigger('hover')) return;\n this._floating.cleanupSafePolygon();\n this.show();\n };\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (!this._hasTrigger('hover') || !this.open) return;\n this._floating.handlePointerLeave(e, () => this.hide());\n };\n\n private _onFocusIn = () => {\n if (this._hasTrigger('focus')) this.show();\n };\n private _onFocusOut = () => {\n if (this._hasTrigger('focus')) this.hide();\n };\n private _onClick = () => {\n if (this._hasTrigger('click')) this.toggle();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (this.open && e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n\n /** Sync `open` when popover=\"auto\" light-dismiss fires. */\n private _onToggle = (e: Event) => {\n const toggleEvent = e as ToggleEvent;\n if (toggleEvent.newState === 'closed' && this.open) {\n this.open = false;\n }\n };\n\n private _addTriggerListeners() {\n this._floating.addTriggerListeners({\n onPointerEnter: this._onPointerEnter,\n onPointerLeave: this._onPointerLeave,\n onFocusIn: this._onFocusIn,\n onFocusOut: this._onFocusOut,\n onClick: this._onClick,\n onKeyDown: this._onKeyDown,\n });\n }\n\n private _removeTriggerListeners(forId?: string) {\n const trigger = forId\n ? (this.getRootNode() as Document | ShadowRoot).getElementById(forId)\n : undefined;\n this._floating.removeTriggerListeners(trigger);\n }\n\n override render() {\n return html`\n <div\n id=${this._popoverId}\n popover=\"auto\"\n part=\"body\"\n @toggle=${this._onToggle}\n >\n ${this.withoutArrow\n ? nothing\n : html`\n <i\n part=\"arrow\"\n role=\"presentation\"\n ></i>\n `}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"popover.js","names":[],"sources":["../../../src/html/elements/popover/popover.css?inline","../../../src/html/elements/popover/popover.ts"],"sourcesContent":[":host {\n --background: var(--l-color-bg-surface, Canvas);\n --color: inherit;\n --border-radius: 6px;\n --max-width: 320px;\n --shadow: 0 4px 6px -1px rgb(0 0 0 / 8%), 0 2px 4px -2px rgb(0 0 0 / 6%);\n --arrow-size: 8px;\n --show-duration: 150ms;\n --hide-duration: 150ms;\n\n --_border-color: var(--l-color-border-overlay, light-dark(#e5e7eb, #374151));\n\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n isolation: isolate;\n box-sizing: border-box;\n width: max-content;\n max-width: var(--max-width);\n border: 1px solid var(--_border-color);\n border-radius: var(--border-radius);\n background: var(--background);\n color: var(--color);\n font-size: 0.875rem;\n line-height: 1.5;\n box-shadow: var(--shadow);\n}\n\n:host([full-width]) [popover] {\n width: 100vw;\n max-width: none;\n}\n\ni {\n position: absolute;\n display: block;\n width: var(--arrow-size);\n height: var(--arrow-size);\n background: var(--background);\n transform: rotate(45deg);\n z-index: -1;\n}\n\n:host([data-placement^='top']) i {\n border-right: 1px solid var(--_border-color);\n border-bottom: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='bottom']) i {\n border-top: 1px solid var(--_border-color);\n border-left: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='left']) i {\n border-right: 1px solid var(--_border-color);\n border-top: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='right']) i {\n border-left: 1px solid var(--_border-color);\n border-bottom: 1px solid var(--_border-color);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover';\nimport { uniqueId } from '../../registry';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './popover.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * @summary A popover that displays interactive content anchored to a trigger.\n * @customElement l-popover\n *\n * @slot - Popover content.\n *\n * @csspart body - The popover container.\n * @csspart arrow - The directional arrow element.\n *\n * @cssproperty --background - Background color. Default: `Canvas`.\n * @cssproperty --color - Text color. Default: inherited.\n * @cssproperty --border-radius - Border radius. Default `8px`.\n * @cssproperty --max-width - Maximum width. Default `320px`.\n * @cssproperty --shadow - Box shadow.\n * @cssproperty --arrow-size - Arrow size. Default `8px`.\n * @cssproperty --show-duration - Show animation duration. Default `150ms`.\n * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.\n */\nexport class LuxenPopover extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _popoverId = uniqueId('popover');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._trigger,\n getFloatingElement: () => this._popoverEl,\n getArrowElement: () => this._arrowEl,\n onPlacementChange: (p) => {\n this.dataset.placement = p;\n },\n });\n\n /** The HTML id of the element triggering the popover. */\n @property()\n accessor for = '';\n\n /** The preferred placement of the popover. */\n @property()\n accessor placement: Placement = 'bottom';\n\n /** The distance in pixels from the target element. */\n @property({ type: Number })\n accessor distance = 8;\n\n /** Whether or not the popover is visible. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Hide the directional arrow. */\n @property({ type: Boolean, reflect: true, attribute: 'without-arrow' })\n accessor withoutArrow = false;\n\n /** Stretch the popover to the viewport width. Useful for mega menus. */\n @property({ type: Boolean, reflect: true, attribute: 'full-width' })\n accessor fullWidth = false;\n\n /** Space-separated list of trigger modes: `click`, `hover`, `focus`, `manual`. */\n @property()\n accessor trigger = 'click';\n\n private _hasTrigger(type: string) {\n return this.trigger.split(' ').includes(type);\n }\n\n private get _trigger(): HTMLElement | null {\n return this.for ? (this.getRootNode() as Document | ShadowRoot).getElementById(this.for) : null;\n }\n\n private get _popoverEl(): HTMLElement {\n return this.shadowRoot!.querySelector('[popover]')!;\n }\n\n private get _arrowEl(): HTMLElement | null {\n return this.withoutArrow ? null : this.shadowRoot!.querySelector('i');\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 override connectedCallback() {\n super.connectedCallback();\n requestAnimationFrame(() => this._addTriggerListeners());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._removeTriggerListeners();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('open')) {\n this._handleOpenChange();\n }\n if (changed.has('for')) {\n this._removeTriggerListeners(changed.get('for') as string);\n this._addTriggerListeners();\n }\n }\n\n show() {\n if (!this.open) this.open = true;\n }\n\n hide() {\n if (this.open) this.open = false;\n }\n\n toggle() {\n this.open = !this.open;\n }\n\n private async _handleOpenChange() {\n const popover = this._popoverEl;\n if (!popover) return;\n\n const posOpts = {\n placement: this.placement,\n distance: this.distance,\n fullWidth: this.fullWidth,\n };\n\n if (this.open) {\n popover.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(popover, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._trigger?.setAttribute('aria-expanded', 'true');\n this._trigger?.setAttribute('aria-controls', this._popoverId);\n } else {\n this._floating.stopPositioning();\n this._floating.cleanupSafePolygon();\n this._trigger?.setAttribute('aria-expanded', 'false');\n this._trigger?.removeAttribute('aria-controls');\n await this._floating.animateHide(popover, this._getDuration('--hide-duration'));\n if (popover.matches(':popover-open')) popover.hidePopover();\n }\n }\n\n // --- Trigger event handlers ---\n\n private _onPointerEnter = () => {\n if (!this._hasTrigger('hover')) return;\n this._floating.cleanupSafePolygon();\n this.show();\n };\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (!this._hasTrigger('hover') || !this.open) return;\n this._floating.handlePointerLeave(e, () => this.hide());\n };\n\n private _onFocusIn = () => {\n if (this._hasTrigger('focus')) this.show();\n };\n private _onFocusOut = () => {\n if (this._hasTrigger('focus')) this.hide();\n };\n private _onClick = () => {\n if (this._hasTrigger('click')) this.toggle();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (this.open && e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n\n /** Sync `open` when popover=\"auto\" light-dismiss fires. */\n private _onToggle = (e: Event) => {\n const toggleEvent = e as ToggleEvent;\n if (toggleEvent.newState === 'closed' && this.open) {\n this.open = false;\n }\n };\n\n private _addTriggerListeners() {\n this._floating.addTriggerListeners({\n onPointerEnter: this._onPointerEnter,\n onPointerLeave: this._onPointerLeave,\n onFocusIn: this._onFocusIn,\n onFocusOut: this._onFocusOut,\n onClick: this._onClick,\n onKeyDown: this._onKeyDown,\n });\n }\n\n private _removeTriggerListeners(forId?: string) {\n const trigger = forId\n ? (this.getRootNode() as Document | ShadowRoot).getElementById(forId)\n : undefined;\n this._floating.removeTriggerListeners(trigger);\n }\n\n override render() {\n return html`\n <div\n id=${this._popoverId}\n popover=\"auto\"\n part=\"body\"\n @toggle=${this._onToggle}\n >\n ${this.withoutArrow\n ? nothing\n : html`\n <i\n part=\"arrow\"\n role=\"presentation\"\n ></i>\n `}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
var(--l-color-text-primary)
|
|
25
25
|
);
|
|
26
26
|
--background-color-hover: var(--background-color-active);
|
|
27
|
-
--font-size: var(--text-
|
|
27
|
+
--font-size: var(--text-sm);
|
|
28
28
|
--text-color: var(--l-color-text-primary);
|
|
29
29
|
--text-color-hover: var(--text-color);
|
|
30
30
|
--border-color: var(--l-color-border);
|