luxen-ui 0.2.1 → 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 +54 -5
- package/cdn/elements/dialog/dialog.styles.js +1 -1
- package/cdn/elements/dialog/dialog.styles.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/styles/elements/input-otp.css +63 -29
- package/cdn/styles/index.css +10 -0
- package/dist/css/elements/input-otp.css +63 -29
- package/dist/css/index.css +10 -0
- package/dist/custom-elements.json +54 -5
- package/dist/elements/dialog/dialog.css +10 -0
- 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/skills/luxen-ui/references/dialog.md +76 -0
- package/dist/skills/luxen-ui/references/drawer.md +8 -0
- package/package.json +1 -1
package/cdn/custom-elements.json
CHANGED
|
@@ -2240,7 +2240,7 @@
|
|
|
2240
2240
|
{
|
|
2241
2241
|
"kind": "variable",
|
|
2242
2242
|
"name": "r",
|
|
2243
|
-
"default": "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.
|
|
2243
|
+
"default": "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)}}"
|
|
2244
2244
|
}
|
|
2245
2245
|
],
|
|
2246
2246
|
"exports": [
|
|
@@ -4638,11 +4638,31 @@
|
|
|
4638
4638
|
},
|
|
4639
4639
|
{
|
|
4640
4640
|
"description": "Cell width and height (default: 2.75rem). Font size scales automatically.",
|
|
4641
|
-
"name": "--size"
|
|
4641
|
+
"name": "--cell-size"
|
|
4642
4642
|
},
|
|
4643
4643
|
{
|
|
4644
4644
|
"description": "Space between cells (default: 0.5rem).",
|
|
4645
|
-
"name": "--gap"
|
|
4645
|
+
"name": "--cell-gap"
|
|
4646
|
+
},
|
|
4647
|
+
{
|
|
4648
|
+
"description": "Cell background color.",
|
|
4649
|
+
"name": "--cell-bg-color"
|
|
4650
|
+
},
|
|
4651
|
+
{
|
|
4652
|
+
"description": "Cell border color.",
|
|
4653
|
+
"name": "--cell-border-color"
|
|
4654
|
+
},
|
|
4655
|
+
{
|
|
4656
|
+
"description": "Cell border-radius.",
|
|
4657
|
+
"name": "--cell-border-radius"
|
|
4658
|
+
},
|
|
4659
|
+
{
|
|
4660
|
+
"description": "Border + ring color of the active (focused) cell.",
|
|
4661
|
+
"name": "--cell-focus-color"
|
|
4662
|
+
},
|
|
4663
|
+
{
|
|
4664
|
+
"description": "`box-shadow` of the active cell ring (defaults to a 1px solid ring; set to `none` to disable).",
|
|
4665
|
+
"name": "--cell-focus-ring"
|
|
4646
4666
|
}
|
|
4647
4667
|
],
|
|
4648
4668
|
"members": [
|
|
@@ -4686,6 +4706,10 @@
|
|
|
4686
4706
|
"kind": "field",
|
|
4687
4707
|
"name": "_clearCells"
|
|
4688
4708
|
},
|
|
4709
|
+
{
|
|
4710
|
+
"kind": "field",
|
|
4711
|
+
"name": "_scheduleUpdateCells"
|
|
4712
|
+
},
|
|
4689
4713
|
{
|
|
4690
4714
|
"kind": "method",
|
|
4691
4715
|
"name": "emit",
|
|
@@ -10762,11 +10786,31 @@
|
|
|
10762
10786
|
},
|
|
10763
10787
|
{
|
|
10764
10788
|
"description": "Cell width and height (default: 2.75rem). Font size scales automatically.",
|
|
10765
|
-
"name": "--size"
|
|
10789
|
+
"name": "--cell-size"
|
|
10766
10790
|
},
|
|
10767
10791
|
{
|
|
10768
10792
|
"description": "Space between cells (default: 0.5rem).",
|
|
10769
|
-
"name": "--gap"
|
|
10793
|
+
"name": "--cell-gap"
|
|
10794
|
+
},
|
|
10795
|
+
{
|
|
10796
|
+
"description": "Cell background color.",
|
|
10797
|
+
"name": "--cell-bg-color"
|
|
10798
|
+
},
|
|
10799
|
+
{
|
|
10800
|
+
"description": "Cell border color.",
|
|
10801
|
+
"name": "--cell-border-color"
|
|
10802
|
+
},
|
|
10803
|
+
{
|
|
10804
|
+
"description": "Cell border-radius.",
|
|
10805
|
+
"name": "--cell-border-radius"
|
|
10806
|
+
},
|
|
10807
|
+
{
|
|
10808
|
+
"description": "Border + ring color of the active (focused) cell.",
|
|
10809
|
+
"name": "--cell-focus-color"
|
|
10810
|
+
},
|
|
10811
|
+
{
|
|
10812
|
+
"description": "`box-shadow` of the active cell ring (defaults to a 1px solid ring; set to `none` to disable).",
|
|
10813
|
+
"name": "--cell-focus-ring"
|
|
10770
10814
|
}
|
|
10771
10815
|
],
|
|
10772
10816
|
"members": [
|
|
@@ -10843,6 +10887,11 @@
|
|
|
10843
10887
|
"name": "_clearCells",
|
|
10844
10888
|
"privacy": "private"
|
|
10845
10889
|
},
|
|
10890
|
+
{
|
|
10891
|
+
"kind": "field",
|
|
10892
|
+
"name": "_scheduleUpdateCells",
|
|
10893
|
+
"privacy": "private"
|
|
10894
|
+
},
|
|
10846
10895
|
{
|
|
10847
10896
|
"kind": "method",
|
|
10848
10897
|
"name": "emit",
|
|
@@ -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"}
|
|
@@ -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":"iHAqBA,IAAa,EAAb,cAAmC,CAAa,2CAWX,EAAE,mBACU,uBACxB,yBA8FY,CACjC,IAAM,EAAQ,KAAK,OAAO,MACpB,EAAS,KAAK,OAAO,WAAa,EAClC,EAAM,KAAK,IAAI,KAAK,OAAO,gBAAkB,EAAG,EAAS,EAAE,CAC3D,EAAY,SAAS,gBAAkB,KAAK,OAElD,IAAK,IAAI,EAAI,EAAG,EAAI,KAAK,OAAO,OAAQ,IAAK,CAC3C,IAAM,EAAO,KAAK,OAAO,GACnB,EAAO,EAAK,kBACZ,EAAO,EAAM,IAAM,GAEzB,EAAK,YAAc,EAEf,EACF,EAAK,aAAa,cAAe,GAAG,CAEpC,EAAK,gBAAgB,cAAc,CAGjC,GAAa,IAAM,EACrB,EAAK,aAAa,cAAe,GAAG,CAEpC,EAAK,gBAAgB,cAAc,wBAKP,CAChC,IAAK,IAAM,KAAQ,KAAK,OACtB,EAAK,gBAAgB,cAAc,gCAII,CACzC,sBAAsB,KAAK,aAAa,EA5I1C,kBAA4B,CAC1B,OAAO,KAaT,mBAA6B,CAC3B,MAAM,mBAAmB,CACzB,0BAA4B,KAAK,QAAQ,CAAC,CAG5C,sBAAgC,CAC9B,MAAM,sBAAsB,CAC5B,KAAK,WAAW,CAKlB,QAAiB,CACf,IAAM,EAAQ,KAAK,cAAgC,QAAQ,CAC3D,GAAI,CAAC,EAAO,OAEZ,KAAK,OAAS,EAGd,IAAM,EAAS,OAAO,iBAAiB,KAAK,CAAC,iBAAiB,WAAW,CAAC,MAAM,CAAC,EAAI,EAG/E,EAAmC,CACvC,KAAM,OACN,UAAW,UACX,aAAc,gBACd,UAAW,OAAO,EAAO,CACzB,QAAS,OAAO,GAAG,MAAM,EAAO,GACjC,CACD,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,EAAS,CAC7C,KAAK,OAAO,aAAa,EAAK,EACjC,KAAK,OAAO,aAAa,EAAM,EAAM,CAKzC,KAAK,WAAa,SAAS,cAAc,MAAM,CAC/C,KAAK,WAAW,UAAY,oBAC5B,KAAK,WAAW,aAAa,cAAe,OAAO,CAEnD,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,IAAK,CAC/B,IAAM,EAAO,SAAS,cAAc,MAAM,CAC1C,EAAK,UAAY,mBACjB,EAAK,YAAY,SAAS,cAAc,OAAO,CAAC,CAChD,KAAK,OAAO,KAAK,EAAK,CACtB,KAAK,WAAW,YAAY,EAAK,CAG7B,KAAK,gBAAkB,IAAM,KAAK,eAAiB,GAAK,EAAI,EAAS,IACvE,KAAK,aAAe,SAAS,cAAc,OAAO,CAClD,KAAK,aAAa,UAAY,wBAC9B,KAAK,WAAW,YAAY,KAAK,aAAa,EAKlD,KAAK,OAAO,YAAY,KAAK,WAAW,CACxC,KAAK,WAAW,YAAY,KAAK,OAAO,CACxC,KAAK,aAAe,GAGpB,KAAK,cAAc,CAInB,KAAK,OAAO,iBAAiB,QAAS,KAAK,aAAa,CACxD,KAAK,OAAO,iBAAiB,QAAS,KAAK,aAAa,CACxD,KAAK,OAAO,iBAAiB,QAAS,KAAK,aAAa,CACxD,KAAK,OAAO,iBAAiB,QAAS,KAAK,qBAAqB,CAChE,KAAK,OAAO,iBAAiB,OAAQ,KAAK,YAAY,CAGxD,WAAoB,CACb,AAcL,KAAK,gBAZL,KAAK,OAAO,oBAAoB,QAAS,KAAK,aAAa,CAC3D,KAAK,OAAO,oBAAoB,QAAS,KAAK,aAAa,CAC3D,KAAK,OAAO,oBAAoB,QAAS,KAAK,aAAa,CAC3D,KAAK,OAAO,oBAAoB,QAAS,KAAK,qBAAqB,CACnE,KAAK,OAAO,oBAAoB,OAAQ,KAAK,YAAY,CAGzD,KAAK,WAAW,YAAY,KAAK,OAAO,CACxC,KAAK,cAAc,QAAQ,CAE3B,KAAK,OAAS,EAAE,CAChB,KAAK,aAAe,KACA,SAhGrB,EAAS,CAAE,KAAM,OAAQ,QAAS,GAAM,UAAW,kBAAmB,CAAC,CAAA,CAAA,EAAA,UAAA,iBAAA,IAAA,GAAA"}
|
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
|
|
16
16
|
l-input-otp {
|
|
17
17
|
--digits: 6;
|
|
18
|
-
--size: 2.75rem;
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
18
|
+
--cell-size: 2.75rem;
|
|
19
|
+
--cell-bg-color: color-mix(in oklab, var(--l-color-text-primary) 4%, var(--l-color-surface));
|
|
20
|
+
--cell-border-color: var(--l-color-border);
|
|
21
|
+
--cell-border-radius: var(--radius-md);
|
|
22
|
+
--cell-focus-color: var(--l-focus-ring);
|
|
23
|
+
--cell-focus-ring: 0 0 0 1px var(--cell-focus-color);
|
|
23
24
|
|
|
24
|
-
display: inline-
|
|
25
|
+
display: inline-block;
|
|
25
26
|
position: relative;
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
.l-input-otp-cells {
|
|
35
36
|
display: inline-flex;
|
|
36
37
|
align-items: center;
|
|
37
|
-
gap: var(--gap, 0.5rem);
|
|
38
|
+
gap: var(--cell-gap, 0.5rem);
|
|
38
39
|
position: relative;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -48,15 +49,15 @@
|
|
|
48
49
|
display: flex;
|
|
49
50
|
align-items: center;
|
|
50
51
|
justify-content: center;
|
|
51
|
-
inline-size: var(--size);
|
|
52
|
-
block-size: var(--size);
|
|
53
|
-
border: 1px solid var(--
|
|
54
|
-
border-radius: var(--radius
|
|
55
|
-
background: var(--
|
|
52
|
+
inline-size: var(--cell-size);
|
|
53
|
+
block-size: var(--cell-size);
|
|
54
|
+
border: 1px solid var(--cell-border-color);
|
|
55
|
+
border-radius: var(--cell-border-radius);
|
|
56
|
+
background: var(--cell-bg-color);
|
|
56
57
|
font-family:
|
|
57
58
|
ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
|
|
58
59
|
monospace;
|
|
59
|
-
font-size: var(--
|
|
60
|
+
font-size: calc(var(--cell-size) * 0.45);
|
|
60
61
|
font-variant-numeric: tabular-nums;
|
|
61
62
|
line-height: 1;
|
|
62
63
|
color: var(--l-color-text-primary);
|
|
@@ -73,8 +74,37 @@
|
|
|
73
74
|
*/
|
|
74
75
|
|
|
75
76
|
l-input-otp:focus-within .l-input-otp-cell[data-active] {
|
|
76
|
-
border-color: var(--
|
|
77
|
-
box-shadow:
|
|
77
|
+
border-color: var(--cell-focus-color);
|
|
78
|
+
box-shadow: var(--cell-focus-ring);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
83
|
+
🅲🅰🆁🅴🆃 (fake blinking caret in active empty cell)
|
|
84
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
85
|
+
The native caret is hidden via `caret-color: transparent`; this stand-in
|
|
86
|
+
gives the missing point-of-insertion cue inside the active cell. Only
|
|
87
|
+
visible while empty — once a digit is typed, it replaces the caret.
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
l-input-otp:focus-within .l-input-otp-cell[data-active]:not([data-filled])::after {
|
|
91
|
+
content: '';
|
|
92
|
+
inline-size: 1px;
|
|
93
|
+
block-size: 1em;
|
|
94
|
+
background: currentColor;
|
|
95
|
+
animation: l-input-otp-caret 1s steps(2, jump-none) infinite;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@keyframes l-input-otp-caret {
|
|
99
|
+
50% {
|
|
100
|
+
opacity: 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@media (prefers-reduced-motion: reduce) {
|
|
105
|
+
l-input-otp:focus-within .l-input-otp-cell[data-active]:not([data-filled])::after {
|
|
106
|
+
animation: none;
|
|
107
|
+
}
|
|
78
108
|
}
|
|
79
109
|
|
|
80
110
|
/*
|
|
@@ -122,11 +152,11 @@
|
|
|
122
152
|
*/
|
|
123
153
|
|
|
124
154
|
l-input-otp[size='sm'] {
|
|
125
|
-
--size: 2rem;
|
|
155
|
+
--cell-size: 2rem;
|
|
126
156
|
}
|
|
127
157
|
|
|
128
158
|
l-input-otp[size='lg'] {
|
|
129
|
-
--size: 3.5rem;
|
|
159
|
+
--cell-size: 3.5rem;
|
|
130
160
|
}
|
|
131
161
|
|
|
132
162
|
/*
|
|
@@ -145,20 +175,24 @@
|
|
|
145
175
|
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
146
176
|
🅿🆁🅾🅶🆁🅴🆂🆂🅸🆅🅴 🅴🅽🅷🅰🅽🅲🅴🅼🅴🅽🆃 🅵🅰🅻🅻🅱🅰🅲🅺
|
|
147
177
|
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
148
|
-
|
|
178
|
+
Pre-upgrade reserves the exact box the cells will occupy — width scales
|
|
179
|
+
with --digits / --cell-size / --cell-gap so layout doesn't shift on hydration.
|
|
180
|
+
Single soft-tinted rectangle (not per-cell): matches any custom appearance
|
|
181
|
+
cleanly since it doesn't pretend to redraw the cell layout.
|
|
149
182
|
*/
|
|
150
183
|
|
|
151
|
-
l-input-otp:not(:defined)
|
|
152
|
-
all: unset;
|
|
184
|
+
l-input-otp:not(:defined) {
|
|
153
185
|
display: inline-block;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
186
|
+
flex-shrink: 0;
|
|
187
|
+
inline-size: calc(
|
|
188
|
+
var(--cell-size) * var(--digits) + var(--cell-gap, 0.5rem) * (var(--digits) - 1)
|
|
189
|
+
);
|
|
190
|
+
block-size: var(--cell-size);
|
|
191
|
+
background: var(--cell-bg-color);
|
|
192
|
+
border-radius: var(--cell-border-radius);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
l-input-otp:not(:defined) > input {
|
|
196
|
+
display: none;
|
|
163
197
|
}
|
|
164
198
|
}
|
package/cdn/styles/index.css
CHANGED
|
@@ -707,6 +707,16 @@ In dark mode, mixes the base color with black (default 15% black).
|
|
|
707
707
|
white-space: nowrap;
|
|
708
708
|
border: 0;
|
|
709
709
|
}
|
|
710
|
+
|
|
711
|
+
/* Hide Shadow-DOM overlay elements until their custom element upgrades.
|
|
712
|
+
Without this, slotted/inner content flashes inline before the upgrade. */
|
|
713
|
+
l-dialog:not(:defined),
|
|
714
|
+
l-drawer:not(:defined),
|
|
715
|
+
l-dropdown:not(:defined),
|
|
716
|
+
l-popover:not(:defined),
|
|
717
|
+
l-tooltip:not(:defined) {
|
|
718
|
+
display: none;
|
|
719
|
+
}
|
|
710
720
|
}
|
|
711
721
|
|
|
712
722
|
/* https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/theme.css */
|
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
|
|
16
16
|
l-input-otp {
|
|
17
17
|
--digits: 6;
|
|
18
|
-
--size: 2.75rem;
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
18
|
+
--cell-size: 2.75rem;
|
|
19
|
+
--cell-bg-color: color-mix(in oklab, var(--l-color-text-primary) 4%, var(--l-color-surface));
|
|
20
|
+
--cell-border-color: var(--l-color-border);
|
|
21
|
+
--cell-border-radius: var(--radius-md);
|
|
22
|
+
--cell-focus-color: var(--l-focus-ring);
|
|
23
|
+
--cell-focus-ring: 0 0 0 1px var(--cell-focus-color);
|
|
23
24
|
|
|
24
|
-
display: inline-
|
|
25
|
+
display: inline-block;
|
|
25
26
|
position: relative;
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
.l-input-otp-cells {
|
|
35
36
|
display: inline-flex;
|
|
36
37
|
align-items: center;
|
|
37
|
-
gap: var(--gap, 0.5rem);
|
|
38
|
+
gap: var(--cell-gap, 0.5rem);
|
|
38
39
|
position: relative;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -48,15 +49,15 @@
|
|
|
48
49
|
display: flex;
|
|
49
50
|
align-items: center;
|
|
50
51
|
justify-content: center;
|
|
51
|
-
inline-size: var(--size);
|
|
52
|
-
block-size: var(--size);
|
|
53
|
-
border: 1px solid var(--
|
|
54
|
-
border-radius: var(--radius
|
|
55
|
-
background: var(--
|
|
52
|
+
inline-size: var(--cell-size);
|
|
53
|
+
block-size: var(--cell-size);
|
|
54
|
+
border: 1px solid var(--cell-border-color);
|
|
55
|
+
border-radius: var(--cell-border-radius);
|
|
56
|
+
background: var(--cell-bg-color);
|
|
56
57
|
font-family:
|
|
57
58
|
ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
|
|
58
59
|
monospace;
|
|
59
|
-
font-size: var(--
|
|
60
|
+
font-size: calc(var(--cell-size) * 0.45);
|
|
60
61
|
font-variant-numeric: tabular-nums;
|
|
61
62
|
line-height: 1;
|
|
62
63
|
color: var(--l-color-text-primary);
|
|
@@ -73,8 +74,37 @@
|
|
|
73
74
|
*/
|
|
74
75
|
|
|
75
76
|
l-input-otp:focus-within .l-input-otp-cell[data-active] {
|
|
76
|
-
border-color: var(--
|
|
77
|
-
box-shadow:
|
|
77
|
+
border-color: var(--cell-focus-color);
|
|
78
|
+
box-shadow: var(--cell-focus-ring);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
83
|
+
🅲🅰🆁🅴🆃 (fake blinking caret in active empty cell)
|
|
84
|
+
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
85
|
+
The native caret is hidden via `caret-color: transparent`; this stand-in
|
|
86
|
+
gives the missing point-of-insertion cue inside the active cell. Only
|
|
87
|
+
visible while empty — once a digit is typed, it replaces the caret.
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
l-input-otp:focus-within .l-input-otp-cell[data-active]:not([data-filled])::after {
|
|
91
|
+
content: '';
|
|
92
|
+
inline-size: 1px;
|
|
93
|
+
block-size: 1em;
|
|
94
|
+
background: currentColor;
|
|
95
|
+
animation: l-input-otp-caret 1s steps(2, jump-none) infinite;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@keyframes l-input-otp-caret {
|
|
99
|
+
50% {
|
|
100
|
+
opacity: 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@media (prefers-reduced-motion: reduce) {
|
|
105
|
+
l-input-otp:focus-within .l-input-otp-cell[data-active]:not([data-filled])::after {
|
|
106
|
+
animation: none;
|
|
107
|
+
}
|
|
78
108
|
}
|
|
79
109
|
|
|
80
110
|
/*
|
|
@@ -122,11 +152,11 @@
|
|
|
122
152
|
*/
|
|
123
153
|
|
|
124
154
|
l-input-otp[size='sm'] {
|
|
125
|
-
--size: 2rem;
|
|
155
|
+
--cell-size: 2rem;
|
|
126
156
|
}
|
|
127
157
|
|
|
128
158
|
l-input-otp[size='lg'] {
|
|
129
|
-
--size: 3.5rem;
|
|
159
|
+
--cell-size: 3.5rem;
|
|
130
160
|
}
|
|
131
161
|
|
|
132
162
|
/*
|
|
@@ -145,20 +175,24 @@
|
|
|
145
175
|
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
146
176
|
🅿🆁🅾🅶🆁🅴🆂🆂🅸🆅🅴 🅴🅽🅷🅰🅽🅲🅴🅼🅴🅽🆃 🅵🅰🅻🅻🅱🅰🅲🅺
|
|
147
177
|
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
148
|
-
|
|
178
|
+
Pre-upgrade reserves the exact box the cells will occupy — width scales
|
|
179
|
+
with --digits / --cell-size / --cell-gap so layout doesn't shift on hydration.
|
|
180
|
+
Single soft-tinted rectangle (not per-cell): matches any custom appearance
|
|
181
|
+
cleanly since it doesn't pretend to redraw the cell layout.
|
|
149
182
|
*/
|
|
150
183
|
|
|
151
|
-
l-input-otp:not(:defined)
|
|
152
|
-
all: unset;
|
|
184
|
+
l-input-otp:not(:defined) {
|
|
153
185
|
display: inline-block;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
186
|
+
flex-shrink: 0;
|
|
187
|
+
inline-size: calc(
|
|
188
|
+
var(--cell-size) * var(--digits) + var(--cell-gap, 0.5rem) * (var(--digits) - 1)
|
|
189
|
+
);
|
|
190
|
+
block-size: var(--cell-size);
|
|
191
|
+
background: var(--cell-bg-color);
|
|
192
|
+
border-radius: var(--cell-border-radius);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
l-input-otp:not(:defined) > input {
|
|
196
|
+
display: none;
|
|
163
197
|
}
|
|
164
198
|
}
|
package/dist/css/index.css
CHANGED
|
@@ -707,6 +707,16 @@ In dark mode, mixes the base color with black (default 15% black).
|
|
|
707
707
|
white-space: nowrap;
|
|
708
708
|
border: 0;
|
|
709
709
|
}
|
|
710
|
+
|
|
711
|
+
/* Hide Shadow-DOM overlay elements until their custom element upgrades.
|
|
712
|
+
Without this, slotted/inner content flashes inline before the upgrade. */
|
|
713
|
+
l-dialog:not(:defined),
|
|
714
|
+
l-drawer:not(:defined),
|
|
715
|
+
l-dropdown:not(:defined),
|
|
716
|
+
l-popover:not(:defined),
|
|
717
|
+
l-tooltip:not(:defined) {
|
|
718
|
+
display: none;
|
|
719
|
+
}
|
|
710
720
|
}
|
|
711
721
|
|
|
712
722
|
/* https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/theme.css */
|
|
@@ -2240,7 +2240,7 @@
|
|
|
2240
2240
|
{
|
|
2241
2241
|
"kind": "variable",
|
|
2242
2242
|
"name": "r",
|
|
2243
|
-
"default": "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.
|
|
2243
|
+
"default": "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)}}"
|
|
2244
2244
|
}
|
|
2245
2245
|
],
|
|
2246
2246
|
"exports": [
|
|
@@ -4638,11 +4638,31 @@
|
|
|
4638
4638
|
},
|
|
4639
4639
|
{
|
|
4640
4640
|
"description": "Cell width and height (default: 2.75rem). Font size scales automatically.",
|
|
4641
|
-
"name": "--size"
|
|
4641
|
+
"name": "--cell-size"
|
|
4642
4642
|
},
|
|
4643
4643
|
{
|
|
4644
4644
|
"description": "Space between cells (default: 0.5rem).",
|
|
4645
|
-
"name": "--gap"
|
|
4645
|
+
"name": "--cell-gap"
|
|
4646
|
+
},
|
|
4647
|
+
{
|
|
4648
|
+
"description": "Cell background color.",
|
|
4649
|
+
"name": "--cell-bg-color"
|
|
4650
|
+
},
|
|
4651
|
+
{
|
|
4652
|
+
"description": "Cell border color.",
|
|
4653
|
+
"name": "--cell-border-color"
|
|
4654
|
+
},
|
|
4655
|
+
{
|
|
4656
|
+
"description": "Cell border-radius.",
|
|
4657
|
+
"name": "--cell-border-radius"
|
|
4658
|
+
},
|
|
4659
|
+
{
|
|
4660
|
+
"description": "Border + ring color of the active (focused) cell.",
|
|
4661
|
+
"name": "--cell-focus-color"
|
|
4662
|
+
},
|
|
4663
|
+
{
|
|
4664
|
+
"description": "`box-shadow` of the active cell ring (defaults to a 1px solid ring; set to `none` to disable).",
|
|
4665
|
+
"name": "--cell-focus-ring"
|
|
4646
4666
|
}
|
|
4647
4667
|
],
|
|
4648
4668
|
"members": [
|
|
@@ -4686,6 +4706,10 @@
|
|
|
4686
4706
|
"kind": "field",
|
|
4687
4707
|
"name": "_clearCells"
|
|
4688
4708
|
},
|
|
4709
|
+
{
|
|
4710
|
+
"kind": "field",
|
|
4711
|
+
"name": "_scheduleUpdateCells"
|
|
4712
|
+
},
|
|
4689
4713
|
{
|
|
4690
4714
|
"kind": "method",
|
|
4691
4715
|
"name": "emit",
|
|
@@ -10762,11 +10786,31 @@
|
|
|
10762
10786
|
},
|
|
10763
10787
|
{
|
|
10764
10788
|
"description": "Cell width and height (default: 2.75rem). Font size scales automatically.",
|
|
10765
|
-
"name": "--size"
|
|
10789
|
+
"name": "--cell-size"
|
|
10766
10790
|
},
|
|
10767
10791
|
{
|
|
10768
10792
|
"description": "Space between cells (default: 0.5rem).",
|
|
10769
|
-
"name": "--gap"
|
|
10793
|
+
"name": "--cell-gap"
|
|
10794
|
+
},
|
|
10795
|
+
{
|
|
10796
|
+
"description": "Cell background color.",
|
|
10797
|
+
"name": "--cell-bg-color"
|
|
10798
|
+
},
|
|
10799
|
+
{
|
|
10800
|
+
"description": "Cell border color.",
|
|
10801
|
+
"name": "--cell-border-color"
|
|
10802
|
+
},
|
|
10803
|
+
{
|
|
10804
|
+
"description": "Cell border-radius.",
|
|
10805
|
+
"name": "--cell-border-radius"
|
|
10806
|
+
},
|
|
10807
|
+
{
|
|
10808
|
+
"description": "Border + ring color of the active (focused) cell.",
|
|
10809
|
+
"name": "--cell-focus-color"
|
|
10810
|
+
},
|
|
10811
|
+
{
|
|
10812
|
+
"description": "`box-shadow` of the active cell ring (defaults to a 1px solid ring; set to `none` to disable).",
|
|
10813
|
+
"name": "--cell-focus-ring"
|
|
10770
10814
|
}
|
|
10771
10815
|
],
|
|
10772
10816
|
"members": [
|
|
@@ -10843,6 +10887,11 @@
|
|
|
10843
10887
|
"name": "_clearCells",
|
|
10844
10888
|
"privacy": "private"
|
|
10845
10889
|
},
|
|
10890
|
+
{
|
|
10891
|
+
"kind": "field",
|
|
10892
|
+
"name": "_scheduleUpdateCells",
|
|
10893
|
+
"privacy": "private"
|
|
10894
|
+
},
|
|
10846
10895
|
{
|
|
10847
10896
|
"kind": "method",
|
|
10848
10897
|
"name": "emit",
|
|
@@ -69,18 +69,28 @@ dialog {
|
|
|
69
69
|
line-height: 1.4;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/* Pin body and footer so the layout stays correct when [without-header]
|
|
73
|
+
removes the header and only two children auto-place into the grid. */
|
|
72
74
|
[part='body'] {
|
|
75
|
+
grid-row: 2;
|
|
73
76
|
padding-inline: var(--padding);
|
|
74
77
|
overflow-y: auto;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
[part='footer'] {
|
|
81
|
+
grid-row: 3;
|
|
78
82
|
display: flex;
|
|
79
83
|
place-content: end;
|
|
80
84
|
gap: 0.5rem;
|
|
81
85
|
padding: var(--padding);
|
|
82
86
|
}
|
|
83
87
|
|
|
88
|
+
/* Without a header, the body has to provide its own block-start padding
|
|
89
|
+
(normally inherited visually from the header padding above it). */
|
|
90
|
+
:host([without-header]) [part='body'] {
|
|
91
|
+
padding-block-start: var(--padding);
|
|
92
|
+
}
|
|
93
|
+
|
|
84
94
|
::slotted(menu[slot='footer']) {
|
|
85
95
|
display: contents;
|
|
86
96
|
}
|
|
@@ -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"}
|
|
@@ -16,8 +16,13 @@ import { LuxenElement } from '../../shared/luxen-element';
|
|
|
16
16
|
* @customElement l-input-otp
|
|
17
17
|
*
|
|
18
18
|
* @cssproperty --digits - Number of digit boxes (default: 6). Must match input's maxlength.
|
|
19
|
-
* @cssproperty --size - Cell width and height (default: 2.75rem). Font size scales automatically.
|
|
20
|
-
* @cssproperty --gap - Space between cells (default: 0.5rem).
|
|
19
|
+
* @cssproperty --cell-size - Cell width and height (default: 2.75rem). Font size scales automatically.
|
|
20
|
+
* @cssproperty --cell-gap - Space between cells (default: 0.5rem).
|
|
21
|
+
* @cssproperty --cell-bg-color - Cell background color.
|
|
22
|
+
* @cssproperty --cell-border-color - Cell border color.
|
|
23
|
+
* @cssproperty --cell-border-radius - Cell border-radius.
|
|
24
|
+
* @cssproperty --cell-focus-color - Border + ring color of the active (focused) cell.
|
|
25
|
+
* @cssproperty --cell-focus-ring - `box-shadow` of the active cell ring (defaults to a 1px solid ring; set to `none` to disable).
|
|
21
26
|
*/
|
|
22
27
|
export class LuxenInputOtp extends LuxenElement {
|
|
23
28
|
constructor() {
|
|
@@ -55,6 +60,9 @@ export class LuxenInputOtp extends LuxenElement {
|
|
|
55
60
|
cell.removeAttribute('data-active');
|
|
56
61
|
}
|
|
57
62
|
};
|
|
63
|
+
this._scheduleUpdateCells = () => {
|
|
64
|
+
requestAnimationFrame(this._updateCells);
|
|
65
|
+
};
|
|
58
66
|
}
|
|
59
67
|
createRenderRoot() {
|
|
60
68
|
return this;
|
|
@@ -111,11 +119,12 @@ export class LuxenInputOtp extends LuxenElement {
|
|
|
111
119
|
this._initialized = true;
|
|
112
120
|
// Populate cells if input already has a value (e.g. disabled with prefilled value)
|
|
113
121
|
this._updateCells();
|
|
114
|
-
// Events
|
|
122
|
+
// Events — focus is deferred so it runs after the click that triggered it
|
|
123
|
+
// (otherwise selectionStart is stale and the active cell flickers).
|
|
115
124
|
this._input.addEventListener('input', this._updateCells);
|
|
116
125
|
this._input.addEventListener('click', this._updateCells);
|
|
117
126
|
this._input.addEventListener('keyup', this._updateCells);
|
|
118
|
-
this._input.addEventListener('focus', this.
|
|
127
|
+
this._input.addEventListener('focus', this._scheduleUpdateCells);
|
|
119
128
|
this._input.addEventListener('blur', this._clearCells);
|
|
120
129
|
}
|
|
121
130
|
_teardown() {
|
|
@@ -124,7 +133,7 @@ export class LuxenInputOtp extends LuxenElement {
|
|
|
124
133
|
this._input.removeEventListener('input', this._updateCells);
|
|
125
134
|
this._input.removeEventListener('click', this._updateCells);
|
|
126
135
|
this._input.removeEventListener('keyup', this._updateCells);
|
|
127
|
-
this._input.removeEventListener('focus', this.
|
|
136
|
+
this._input.removeEventListener('focus', this._scheduleUpdateCells);
|
|
128
137
|
this._input.removeEventListener('blur', this._clearCells);
|
|
129
138
|
// Restore input to direct child
|
|
130
139
|
this._container.replaceWith(this._input);
|
|
@@ -345,6 +345,74 @@ Add `autofocus` to any focusable element inside the dialog to focus it automatic
|
|
|
345
345
|
</l-dialog>
|
|
346
346
|
```
|
|
347
347
|
|
|
348
|
+
### Without header
|
|
349
|
+
|
|
350
|
+
Add `without-header` to drop the header row entirely (title and close slot). Useful for confirmation prompts where the body already carries the heading. Provide an accessible heading inside the body and rely on `Escape` or a footer action to close.
|
|
351
|
+
|
|
352
|
+
```html
|
|
353
|
+
<button
|
|
354
|
+
type="button"
|
|
355
|
+
class="l-button"
|
|
356
|
+
command="--show"
|
|
357
|
+
commandfor="dialog-without-header"
|
|
358
|
+
>
|
|
359
|
+
Delete account
|
|
360
|
+
</button>
|
|
361
|
+
|
|
362
|
+
<l-dialog
|
|
363
|
+
id="dialog-without-header"
|
|
364
|
+
without-header
|
|
365
|
+
>
|
|
366
|
+
<div class="grid justify-items-center gap-3 pb-2 text-center">
|
|
367
|
+
<span
|
|
368
|
+
class="flex size-12 items-center justify-center rounded-full bg-red-100 text-red-700 dark:bg-red-950 dark:text-red-300"
|
|
369
|
+
>
|
|
370
|
+
<svg
|
|
371
|
+
class="size-6"
|
|
372
|
+
viewBox="0 0 24 24"
|
|
373
|
+
fill="none"
|
|
374
|
+
stroke="currentColor"
|
|
375
|
+
stroke-width="2"
|
|
376
|
+
stroke-linecap="round"
|
|
377
|
+
stroke-linejoin="round"
|
|
378
|
+
aria-hidden="true"
|
|
379
|
+
>
|
|
380
|
+
<path d="M12 9v4" />
|
|
381
|
+
<path d="M12 17h.01" />
|
|
382
|
+
<path
|
|
383
|
+
d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
|
384
|
+
/>
|
|
385
|
+
</svg>
|
|
386
|
+
</span>
|
|
387
|
+
<h2 class="text-lg font-semibold">Delete your account?</h2>
|
|
388
|
+
<p class="text-sm">
|
|
389
|
+
This action is permanent and cannot be undone. All your data, including projects and billing
|
|
390
|
+
history, will be erased.
|
|
391
|
+
</p>
|
|
392
|
+
</div>
|
|
393
|
+
<menu slot="footer">
|
|
394
|
+
<button
|
|
395
|
+
autofocus
|
|
396
|
+
type="button"
|
|
397
|
+
class="l-button"
|
|
398
|
+
command="--hide"
|
|
399
|
+
commandfor="dialog-without-header"
|
|
400
|
+
>
|
|
401
|
+
Cancel
|
|
402
|
+
</button>
|
|
403
|
+
<button
|
|
404
|
+
type="button"
|
|
405
|
+
class="l-button"
|
|
406
|
+
data-variant="destructive"
|
|
407
|
+
command="--hide"
|
|
408
|
+
commandfor="dialog-without-header"
|
|
409
|
+
>
|
|
410
|
+
Delete account
|
|
411
|
+
</button>
|
|
412
|
+
</menu>
|
|
413
|
+
</l-dialog>
|
|
414
|
+
```
|
|
415
|
+
|
|
348
416
|
## Accessibility
|
|
349
417
|
|
|
350
418
|
### Criteria
|
|
@@ -392,6 +460,14 @@ import 'luxen-ui/dialog';
|
|
|
392
460
|
|
|
393
461
|
Open and close the dialog by toggling its `open` property, or via the [Invoker Commands API](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API) from any light-DOM button. Custom commands must start with `--`.
|
|
394
462
|
|
|
463
|
+
>
|
|
464
|
+
> The Invoker Commands API is [✓ Baseline Newly Available (since 2025-12-12)](https://web-platform-dx.github.io/web-features-explorer/features/invoker-commands/). For older browser versions, load the [`invokers-polyfill`](https://npmx.dev/package/invokers-polyfill) once at app startup:
|
|
465
|
+
>
|
|
466
|
+
> ```js
|
|
467
|
+
> import 'invokers-polyfill';
|
|
468
|
+
> ```
|
|
469
|
+
>
|
|
470
|
+
|
|
395
471
|
<ApiTable :data="[
|
|
396
472
|
{ Command: '--show', Description: 'Sets `open = true`' },
|
|
397
473
|
{ Command: '--hide', Description: 'Sets `open = false`' },
|
|
@@ -357,6 +357,14 @@ import 'luxen-ui/drawer';
|
|
|
357
357
|
|
|
358
358
|
Open and close the drawer by toggling its `open` property, or via the [Invoker Commands API](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API) from any light-DOM button. Custom commands must start with `--`.
|
|
359
359
|
|
|
360
|
+
>
|
|
361
|
+
> The Invoker Commands API is [✓ Baseline Newly Available (since 2025-12-12)](https://web-platform-dx.github.io/web-features-explorer/features/invoker-commands/). For older browser versions, load the [`invokers-polyfill`](https://npmx.dev/package/invokers-polyfill) once at app startup:
|
|
362
|
+
>
|
|
363
|
+
> ```js
|
|
364
|
+
> import 'invokers-polyfill';
|
|
365
|
+
> ```
|
|
366
|
+
>
|
|
367
|
+
|
|
360
368
|
<ApiTable :data="[
|
|
361
369
|
{ Command: '--show', Description: 'Sets `open = true`' },
|
|
362
370
|
{ Command: '--hide', Description: 'Sets `open = false`' },
|
package/package.json
CHANGED