@sken-ds/primitives 0.4.6 → 0.6.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.
Files changed (52) hide show
  1. package/dist/catalog-y40FMic1-DY2BFupW.js +153 -0
  2. package/dist/catalog-y40FMic1-DY2BFupW.js.map +1 -0
  3. package/dist/{decorate-D4FKzyDw.js → decorate-C1e2sg49.js} +1 -1
  4. package/dist/index.js +16 -12
  5. package/dist/ix-bridge.css +53 -191
  6. package/dist/primitives.css +2 -0
  7. package/dist/rolldown-runtime-BRfZIaNm.js +17 -0
  8. package/dist/sken-button.js +3 -3
  9. package/dist/sken-button.js.map +1 -1
  10. package/dist/sken-checkbox.js +14 -4
  11. package/dist/sken-checkbox.js.map +1 -1
  12. package/dist/sken-combobox-group.js +1 -1
  13. package/dist/sken-combobox-group.js.map +1 -1
  14. package/dist/sken-combobox-item.js +1 -1
  15. package/dist/sken-combobox-item.js.map +1 -1
  16. package/dist/sken-combobox.js +120 -99
  17. package/dist/sken-combobox.js.map +1 -1
  18. package/dist/{sken-datatable-CEXcvhM-.js → sken-datatable-DSXSyrNf.js} +219 -251
  19. package/dist/sken-datatable-DSXSyrNf.js.map +1 -0
  20. package/dist/sken-datatable.js +1 -1
  21. package/dist/sken-date-picker.js +1 -1
  22. package/dist/sken-date-picker.js.map +1 -1
  23. package/dist/sken-dialog.js +251 -2
  24. package/dist/sken-dialog.js.map +1 -0
  25. package/dist/sken-filter-chip.js +1 -1
  26. package/dist/sken-filter-chip.js.map +1 -1
  27. package/dist/sken-input.js +69 -44
  28. package/dist/sken-input.js.map +1 -1
  29. package/dist/sken-layout-region.js +1 -1
  30. package/dist/sken-layout-region.js.map +1 -1
  31. package/dist/sken-layout.js +35 -32
  32. package/dist/sken-layout.js.map +1 -1
  33. package/dist/sken-nav-group.js +306 -0
  34. package/dist/sken-nav-group.js.map +1 -0
  35. package/dist/sken-nav-item.js +224 -0
  36. package/dist/sken-nav-item.js.map +1 -0
  37. package/dist/sken-nav.js +233 -0
  38. package/dist/sken-nav.js.map +1 -0
  39. package/dist/sken-number-input.js +15 -7
  40. package/dist/sken-number-input.js.map +1 -1
  41. package/dist/sken-switch.js +27 -7
  42. package/dist/sken-switch.js.map +1 -1
  43. package/dist/sken-textarea.js +108 -52
  44. package/dist/sken-textarea.js.map +1 -1
  45. package/dist/sken-tooltip.js +70 -0
  46. package/dist/sken-tooltip.js.map +1 -0
  47. package/package.json +35 -16
  48. package/dist/directive-BcLtPdVO.js +0 -263
  49. package/dist/directive-BcLtPdVO.js.map +0 -1
  50. package/dist/sken-datatable-CEXcvhM-.js.map +0 -1
  51. package/dist/sken-dialog-BvRZxwSk.js +0 -258
  52. package/dist/sken-dialog-BvRZxwSk.js.map +0 -1
@@ -0,0 +1,233 @@
1
+ import { t as e } from "./decorate-C1e2sg49.js";
2
+ import "./sken-nav-group.js";
3
+ import "./sken-nav-item.js";
4
+ import { LitElement as t, css as n, html as r } from "lit";
5
+ import { customElement as i, property as a, query as o, state as s } from "lit/decorators.js";
6
+ //#region src/components/sken-nav.ts
7
+ var c = 0;
8
+ function l() {
9
+ return typeof crypto < "u" && typeof crypto.randomUUID == "function" ? crypto.randomUUID() : `fallback-${++c}`;
10
+ }
11
+ var u = class extends t {
12
+ constructor(...e) {
13
+ super(...e), this.ariaLabel = "Main", this.i18nNavigationHint = "Use arrow keys to navigate", this.expanded = !0, this.items = [], this.groups = [], this.activeIndex = -1, this.focusableItems = [], this.#e = (e, t) => {
14
+ if (t.disabled) {
15
+ e.preventDefault();
16
+ return;
17
+ }
18
+ this.dispatchEvent(new CustomEvent("sken-nav-change", {
19
+ detail: {
20
+ item: t,
21
+ group: this.#h(t)
22
+ },
23
+ bubbles: !0,
24
+ composed: !0
25
+ }));
26
+ }, this.#t = (e) => {
27
+ this.dispatchEvent(new CustomEvent("sken-nav-group-toggle", {
28
+ detail: {
29
+ group: e,
30
+ expanded: e.expanded
31
+ },
32
+ bubbles: !0,
33
+ composed: !0
34
+ })), this.#m();
35
+ }, this.#n = (e) => {
36
+ if (this.focusableItems.length !== 0) {
37
+ switch (e.key) {
38
+ case "ArrowDown":
39
+ e.preventDefault(), this.#i(1);
40
+ break;
41
+ case "ArrowUp":
42
+ e.preventDefault(), this.#i(-1);
43
+ break;
44
+ case "Home":
45
+ e.preventDefault(), this.#a(0);
46
+ break;
47
+ case "End":
48
+ e.preventDefault(), this.#a(this.focusableItems.length - 1);
49
+ break;
50
+ default: return;
51
+ }
52
+ this.#o();
53
+ }
54
+ }, this.#r = (e) => {
55
+ let t = e.target.closest("sken-nav-item");
56
+ if (!t) return;
57
+ let n = this.focusableItems.indexOf(t);
58
+ n >= 0 && n !== this.activeIndex && (this.activeIndex = n);
59
+ }, this.#g = () => {
60
+ this.#c();
61
+ }, this.#_ = `sken-nav-hint-${l()}`;
62
+ }
63
+ static {
64
+ this.styles = n`
65
+ :host {
66
+ display: block;
67
+ inline-size: 100%;
68
+ font-family: var(--sken-family-sans, system-ui, sans-serif);
69
+ color: var(--sken-foreground, #152b48);
70
+ }
71
+
72
+ :host([expanded='false']) {
73
+ inline-size: max-content;
74
+ min-inline-size: var(--sken-12, 3rem);
75
+ }
76
+
77
+ .visually-hidden {
78
+ position: absolute;
79
+ inline-size: 1px;
80
+ block-size: 1px;
81
+ padding: 0;
82
+ margin: -1px;
83
+ overflow: hidden;
84
+ clip: rect(0, 0, 0, 0);
85
+ white-space: nowrap;
86
+ border: 0;
87
+ }
88
+
89
+ nav {
90
+ display: block;
91
+ }
92
+ ul {
93
+ list-style: none;
94
+ margin: 0;
95
+ padding: 0;
96
+ display: flex;
97
+ flex-direction: column;
98
+ gap: var(--sken-1, 0.25rem);
99
+ }
100
+ :host([expanded='false']) ul {
101
+ align-items: center;
102
+ inline-size: max-content;
103
+ }
104
+ `;
105
+ }
106
+ #e;
107
+ #t;
108
+ #n;
109
+ #r;
110
+ #i(e) {
111
+ if (this.focusableItems.length === 0) return;
112
+ let t = this.activeIndex < 0 ? 0 : this.activeIndex;
113
+ for (let n = 0; n < this.focusableItems.length && (t = (t + e + this.focusableItems.length) % this.focusableItems.length, this.focusableItems[t].disabled); n++);
114
+ this.activeIndex = t;
115
+ }
116
+ #a(e) {
117
+ if (e < 0 || e >= this.focusableItems.length) return;
118
+ let t = e;
119
+ for (let e = 0; e < this.focusableItems.length; e++) {
120
+ if (!this.focusableItems[t].disabled) {
121
+ this.activeIndex = t;
122
+ return;
123
+ }
124
+ t = (t + 1) % this.focusableItems.length;
125
+ }
126
+ }
127
+ #o() {
128
+ let e = this.focusableItems[this.activeIndex];
129
+ e && (this.requestUpdate(), queueMicrotask(() => {
130
+ e.focus();
131
+ }));
132
+ }
133
+ #s() {
134
+ for (let e = 0; e < this.focusableItems.length; e++) {
135
+ let t = this.focusableItems[e];
136
+ t.tabIndex = e === this.activeIndex ? 0 : -1;
137
+ }
138
+ }
139
+ firstUpdated() {
140
+ this.#c(), this.#u();
141
+ }
142
+ #c() {
143
+ let e = Array.from(this.querySelectorAll("sken-nav-item")), t = Array.from(this.querySelectorAll("sken-nav-group")), n = new Set(t.flatMap((e) => Array.from(e.querySelectorAll("sken-nav-item"))));
144
+ this.items = e.filter((e) => !n.has(e)), this.groups = t, this.#m(), this.#l();
145
+ }
146
+ #l() {
147
+ for (let e of [...this.items, ...this.flatGroupChildren()]) e.__skenWired || (e.__skenWired = !0, e.addEventListener("click", (t) => {
148
+ this.#e(t, e);
149
+ }));
150
+ for (let e of this.groups) e.__skenGroupWired || (e.__skenGroupWired = !0, e.addEventListener("sken-nav-group-native-toggle", (e) => {
151
+ let t = e;
152
+ this.#t(t.detail.group);
153
+ }));
154
+ }
155
+ flatGroupChildren() {
156
+ return this.groups.flatMap((e) => Array.from(e.querySelectorAll("sken-nav-item")));
157
+ }
158
+ #u() {
159
+ for (let e of this.items) e.expanded !== this.expanded && (e.expanded = this.expanded);
160
+ for (let e of this.groups) e.expandedNav !== this.expanded && (e.expandedNav = this.expanded);
161
+ this.expanded ? this.#f() : this.#d();
162
+ }
163
+ #d() {
164
+ let e = this.#p();
165
+ for (let t of e) {
166
+ if (t.inFlyout) continue;
167
+ let e = t.nextElementSibling;
168
+ e && e.tagName.toLowerCase() === "sken-tooltip" && (e.getAttribute("for") ?? "").replace(/^#/, "") === t.id && e.remove();
169
+ let n = t.tooltip || t.label;
170
+ if (!n) continue;
171
+ let r = document.createElement("sken-tooltip");
172
+ r.setAttribute("for", `#${t.id}`), r.setAttribute("content", n), r.setAttribute("placement", "right"), t.insertAdjacentElement("afterend", r);
173
+ }
174
+ }
175
+ #f() {
176
+ for (let e of this.#p()) {
177
+ let t = e.nextElementSibling;
178
+ if (!t || t.tagName.toLowerCase() !== "sken-tooltip") continue;
179
+ let n = t.getAttribute("for") ?? "";
180
+ (n.startsWith("#") ? n.slice(1) : n) === e.id && t.remove();
181
+ }
182
+ }
183
+ #p() {
184
+ let e = [];
185
+ for (let t of this.items) e.push(t);
186
+ for (let t of this.groups) if (t.expanded) for (let n of Array.from(t.querySelectorAll("sken-nav-item"))) e.push(n);
187
+ return e;
188
+ }
189
+ willUpdate(e) {
190
+ e.has("expanded") && this.#u();
191
+ }
192
+ updated() {
193
+ this.#s();
194
+ }
195
+ #m() {
196
+ let e = [];
197
+ for (let t of this.items) e.push(t);
198
+ for (let t of this.groups) if (t.expanded) for (let n of Array.from(t.querySelectorAll("sken-nav-item"))) e.push(n);
199
+ if (this.focusableItems = e, this.activeIndex < 0 || this.activeIndex >= this.focusableItems.length) {
200
+ let e = this.focusableItems.findIndex((e) => e.current);
201
+ this.activeIndex = e >= 0 ? e : this.focusableItems.length > 0 ? 0 : -1;
202
+ }
203
+ }
204
+ #h(e) {
205
+ return e.closest("sken-nav-group") ?? void 0;
206
+ }
207
+ render() {
208
+ return r`
209
+ <nav
210
+ part="nav"
211
+ aria-label=${this.ariaLabel}
212
+ aria-describedby=${this.#_}
213
+ @keydown=${this.#n}
214
+ @focusin=${this.#r}
215
+ >
216
+ <span id=${this.#_} class="visually-hidden"> ${this.i18nNavigationHint} </span>
217
+ <ul part="list" role="list">
218
+ <slot @slotchange=${this.#g}></slot>
219
+ </ul>
220
+ </nav>
221
+ `;
222
+ }
223
+ #g;
224
+ #_;
225
+ };
226
+ e([a({ attribute: "aria-label" })], u.prototype, "ariaLabel", void 0), e([a({ attribute: "i18n-navigation-hint" })], u.prototype, "i18nNavigationHint", void 0), e([a({
227
+ type: Boolean,
228
+ reflect: !0
229
+ })], u.prototype, "expanded", void 0), e([s()], u.prototype, "items", void 0), e([s()], u.prototype, "groups", void 0), e([s()], u.prototype, "activeIndex", void 0), e([s()], u.prototype, "focusableItems", void 0), e([o("nav")], u.prototype, "navEl", void 0), u = e([i("sken-nav")], u);
230
+ //#endregion
231
+ export { u as SkenNav };
232
+
233
+ //# sourceMappingURL=sken-nav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sken-nav.js","names":[],"sources":["../src/components/sken-nav.ts"],"sourcesContent":["import { LitElement, css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport './sken-nav-group.js'\nimport type { SkenNavGroup } from './sken-nav-group.js'\nimport './sken-nav-item.js'\nimport type { SkenNavItem } from './sken-nav-item.js'\n\nlet _fallbackCounter = 0\nfunction generateUniqueId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n return `fallback-${++_fallbackCounter}`\n}\n\n@customElement('sken-nav')\nexport class SkenNav extends LitElement {\n static override styles = css`\n :host {\n display: block;\n inline-size: 100%;\n font-family: var(--sken-family-sans, system-ui, sans-serif);\n color: var(--sken-foreground, #152b48);\n }\n\n :host([expanded='false']) {\n inline-size: max-content;\n min-inline-size: var(--sken-12, 3rem);\n }\n\n .visually-hidden {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n nav {\n display: block;\n }\n ul {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--sken-1, 0.25rem);\n }\n :host([expanded='false']) ul {\n align-items: center;\n inline-size: max-content;\n }\n `\n\n @property({ attribute: 'aria-label' })\n ariaLabel = 'Main'\n\n @property({ attribute: 'i18n-navigation-hint' })\n i18nNavigationHint = 'Use arrow keys to navigate'\n\n @property({ type: Boolean, reflect: true })\n expanded = true\n\n @state() private items: SkenNavItem[] = []\n @state() private groups: SkenNavGroup[] = []\n\n @state() private activeIndex = -1\n\n @state() private focusableItems: SkenNavItem[] = []\n\n @query('nav') private navEl?: HTMLElement\n\n // ── Event re-emission ────────────────────────────────────────\n\n #onItemClick = (event: MouseEvent, item: SkenNavItem) => {\n if (item.disabled) {\n event.preventDefault()\n return\n }\n this.dispatchEvent(\n new CustomEvent('sken-nav-change', {\n detail: { item, group: this.#enclosingGroup(item) },\n bubbles: true,\n composed: true,\n }),\n )\n }\n\n #onGroupToggle = (group: SkenNavGroup) => {\n this.dispatchEvent(\n new CustomEvent('sken-nav-group-toggle', {\n detail: { group, expanded: group.expanded },\n bubbles: true,\n composed: true,\n }),\n )\n this.#recomputeFocusableItems()\n }\n\n // ── Roving tabindex ──────────────────────────────────────────\n\n #handleKeydown = (event: KeyboardEvent) => {\n if (this.focusableItems.length === 0) return\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n this.#moveActive(1)\n break\n case 'ArrowUp':\n event.preventDefault()\n this.#moveActive(-1)\n break\n case 'Home':\n event.preventDefault()\n this.#setActive(0)\n break\n case 'End':\n event.preventDefault()\n this.#setActive(this.focusableItems.length - 1)\n break\n default:\n return\n }\n this.#focusActive()\n }\n\n #handleFocusIn = (event: FocusEvent) => {\n const target = event.target as HTMLElement\n const item = target.closest<SkenNavItem>('sken-nav-item')\n if (!item) return\n const index = this.focusableItems.indexOf(item)\n if (index >= 0 && index !== this.activeIndex) {\n this.activeIndex = index\n }\n }\n\n #moveActive(direction: 1 | -1): void {\n if (this.focusableItems.length === 0) return\n const start = this.activeIndex < 0 ? 0 : this.activeIndex\n let next = start\n for (let i = 0; i < this.focusableItems.length; i++) {\n next = (next + direction + this.focusableItems.length) % this.focusableItems.length\n if (!this.focusableItems[next]!.disabled) break\n }\n this.activeIndex = next\n }\n\n #setActive(index: number): void {\n if (index < 0 || index >= this.focusableItems.length) return\n let next = index\n for (let i = 0; i < this.focusableItems.length; i++) {\n if (!this.focusableItems[next]!.disabled) {\n this.activeIndex = next\n return\n }\n next = (next + 1) % this.focusableItems.length\n }\n }\n\n #focusActive(): void {\n const item = this.focusableItems[this.activeIndex]\n if (!item) return\n this.requestUpdate()\n queueMicrotask(() => {\n item.focus()\n })\n }\n\n #applyRovingTabindex(): void {\n for (let i = 0; i < this.focusableItems.length; i++) {\n const item = this.focusableItems[i]!\n item.tabIndex = i === this.activeIndex ? 0 : -1\n }\n }\n\n // ── Introspection ────────────────────────────────────────────\n\n protected override firstUpdated(): void {\n this.#introspectChildren()\n this.#propagateExpanded()\n }\n\n #introspectChildren(): void {\n const items = Array.from(this.querySelectorAll<SkenNavItem>('sken-nav-item'))\n const groups = Array.from(this.querySelectorAll<SkenNavGroup>('sken-nav-group'))\n const itemsInGroups = new Set(\n groups.flatMap((group) => Array.from(group.querySelectorAll('sken-nav-item'))),\n )\n this.items = items.filter((item) => !itemsInGroups.has(item))\n this.groups = groups\n this.#recomputeFocusableItems()\n this.#wireItemClicks()\n }\n\n #wireItemClicks(): void {\n for (const item of [...this.items, ...this.flatGroupChildren()]) {\n if ((item as unknown as { __skenWired?: boolean }).__skenWired) continue\n ;(item as unknown as { __skenWired?: boolean }).__skenWired = true\n item.addEventListener('click', (event) => {\n this.#onItemClick(event as MouseEvent, item)\n })\n }\n for (const group of this.groups) {\n if ((group as unknown as { __skenGroupWired?: boolean }).__skenGroupWired) continue\n ;(group as unknown as { __skenGroupWired?: boolean }).__skenGroupWired = true\n group.addEventListener('sken-nav-group-native-toggle', (event) => {\n const ev = event as CustomEvent<{ group: SkenNavGroup; expanded: boolean }>\n this.#onGroupToggle(ev.detail.group)\n })\n }\n }\n\n flatGroupChildren(): SkenNavItem[] {\n return this.groups.flatMap((g) => Array.from(g.querySelectorAll<SkenNavItem>('sken-nav-item')))\n }\n\n #propagateExpanded(): void {\n for (const item of this.items) {\n if (item.expanded !== this.expanded) item.expanded = this.expanded\n }\n for (const group of this.groups) {\n if ((group as unknown as { expandedNav?: boolean }).expandedNav !== this.expanded) {\n ;(group as unknown as { expandedNav?: boolean }).expandedNav = this.expanded\n }\n }\n if (this.expanded) {\n this.#unmountItemTooltips()\n } else {\n this.#mountItemTooltips()\n }\n }\n\n #mountItemTooltips(): void {\n const all = this.#allReachableItems()\n for (const item of all) {\n if (item.inFlyout) continue\n\n const stale = item.nextElementSibling\n if (\n stale &&\n stale.tagName.toLowerCase() === 'sken-tooltip' &&\n (stale.getAttribute('for') ?? '').replace(/^#/, '') === item.id\n ) {\n stale.remove()\n }\n const text = item.tooltip || item.label\n if (!text) continue\n const tt = document.createElement('sken-tooltip')\n tt.setAttribute('for', `#${item.id}`)\n tt.setAttribute('content', text)\n tt.setAttribute('placement', 'right')\n item.insertAdjacentElement('afterend', tt)\n }\n }\n\n #unmountItemTooltips(): void {\n for (const item of this.#allReachableItems()) {\n const sibling = item.nextElementSibling\n if (!sibling) continue\n if (sibling.tagName.toLowerCase() !== 'sken-tooltip') continue\n const target = sibling.getAttribute('for') ?? ''\n const id = target.startsWith('#') ? target.slice(1) : target\n if (id === item.id) sibling.remove()\n }\n }\n\n #allReachableItems(): SkenNavItem[] {\n const out: SkenNavItem[] = []\n for (const item of this.items) out.push(item)\n for (const group of this.groups) {\n if (group.expanded) {\n for (const child of Array.from(group.querySelectorAll<SkenNavItem>('sken-nav-item'))) {\n out.push(child)\n }\n }\n }\n return out\n }\n\n protected override willUpdate(changed: Map<string, unknown>): void {\n if (changed.has('expanded')) {\n this.#propagateExpanded()\n }\n }\n\n protected override updated(): void {\n this.#applyRovingTabindex()\n }\n\n #recomputeFocusableItems(): void {\n const focusable: SkenNavItem[] = []\n for (const item of this.items) {\n focusable.push(item)\n }\n for (const group of this.groups) {\n if (group.expanded) {\n for (const item of Array.from(group.querySelectorAll<SkenNavItem>('sken-nav-item'))) {\n focusable.push(item)\n }\n }\n }\n this.focusableItems = focusable\n if (this.activeIndex < 0 || this.activeIndex >= this.focusableItems.length) {\n const currentIndex = this.focusableItems.findIndex((item) => item.current)\n this.activeIndex = currentIndex >= 0 ? currentIndex : this.focusableItems.length > 0 ? 0 : -1\n }\n }\n\n #enclosingGroup(item: SkenNavItem): SkenNavGroup | undefined {\n const group = item.closest<SkenNavGroup>('sken-nav-group')\n return group ?? undefined\n }\n\n // ── Render ───────────────────────────────────────────────────\n\n protected override render() {\n return html`\n <nav\n part=\"nav\"\n aria-label=${this.ariaLabel}\n aria-describedby=${this.#hintId}\n @keydown=${this.#handleKeydown}\n @focusin=${this.#handleFocusIn}\n >\n <span id=${this.#hintId} class=\"visually-hidden\"> ${this.i18nNavigationHint} </span>\n <ul part=\"list\" role=\"list\">\n <slot @slotchange=${this.#onSlotChange}></slot>\n </ul>\n </nav>\n `\n }\n\n #onSlotChange = (): void => {\n this.#introspectChildren()\n }\n\n #hintId = `sken-nav-hint-${generateUniqueId()}`\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sken-nav': SkenNav\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "./decorate-D4FKzyDw.js";
1
+ import { t as e } from "./decorate-C1e2sg49.js";
2
2
  import { LitElement as t, css as n, html as r, nothing as i } from "lit";
3
3
  import { customElement as a, property as o } from "lit/decorators.js";
4
4
  //#region src/components/sken-number-input.ts
@@ -8,7 +8,7 @@ var s = 1, c = !0, l = class extends t {
8
8
  }
9
9
  #e;
10
10
  constructor() {
11
- super(), this.#e = null, this.defaultValue = void 0, this.value = void 0, this.min = void 0, this.max = void 0, this.step = s, this.showStepperButtons = c, this.placeholder = void 0, this.label = void 0, this.required = !1, this.disabled = !1, this.readonly = !1, this.helperText = void 0, this.infoText = void 0, this.warningText = void 0, this.validText = void 0, this.invalidText = void 0, this.textAlignment = "end", this.size = "md", this.invalid = !1, this.describedById = void 0, this.name = void 0, this.#t = "", this.#r = "0.5rem", this.#i = () => {
11
+ super(), this.#e = void 0, this.defaultValue = void 0, this.value = void 0, this.min = void 0, this.max = void 0, this.step = s, this.showStepperButtons = c, this.placeholder = void 0, this.label = void 0, this.required = !1, this.disabled = !1, this.readonly = !1, this.helperText = void 0, this.infoText = void 0, this.warningText = void 0, this.validText = void 0, this.invalidText = void 0, this.textAlignment = "end", this.size = "md", this.invalid = !1, this.describedById = void 0, this.name = void 0, this.ariaLabel = null, this.#t = "", this.#r = "0.5rem", this.#i = () => {
12
12
  let e = this.renderRoot.querySelector("input"), t = this.renderRoot.querySelector(".steppers"), n = this.renderRoot.querySelector(".slot-end");
13
13
  !e || !n || requestAnimationFrame(() => {
14
14
  if (!e.isConnected) return;
@@ -17,7 +17,7 @@ var s = 1, c = !0, l = class extends t {
17
17
  let c = s + i, l = Math.max(o, c) + a;
18
18
  e.style.paddingInlineEnd = `${l}px`;
19
19
  });
20
- }, this.#a = null, this.#o = null, this.#c = (e) => {
20
+ }, this.#a = void 0, this.#o = void 0, this.#c = (e) => {
21
21
  let t = e.target;
22
22
  this.#t = t.value;
23
23
  let n = this.#v(t.value);
@@ -63,7 +63,7 @@ var s = 1, c = !0, l = class extends t {
63
63
  try {
64
64
  this.#e = this.attachInternals();
65
65
  } catch {
66
- this.#e = null;
66
+ this.#e = void 0;
67
67
  }
68
68
  }
69
69
  #t;
@@ -73,6 +73,9 @@ var s = 1, c = !0, l = class extends t {
73
73
  disconnectedCallback() {
74
74
  super.disconnectedCallback(), this.#a?.disconnect(), this.#o?.disconnect();
75
75
  }
76
+ willUpdate(e) {
77
+ e.has("value") && (this.value === void 0 ? this.#t = this.defaultValue === void 0 ? "" : String(this.defaultValue) : this.#v(this.#t) !== this.value && (this.#t = String(this.value)));
78
+ }
76
79
  formResetCallback() {
77
80
  if (this.value === void 0) {
78
81
  this.#t = this.defaultValue === void 0 ? "" : String(this.defaultValue);
@@ -111,17 +114,21 @@ var s = 1, c = !0, l = class extends t {
111
114
 
112
115
  .field {
113
116
  display: grid;
114
- gap: 0.375rem;
117
+ gap: var(--sken-1, 0.25rem);
115
118
  }
116
119
 
117
120
  .label {
118
- font-size: 0.8125rem;
121
+ font-size: var(--sken-size-xs, 0.75rem);
119
122
  font-weight: 500;
120
123
  color: var(--sken-foreground);
121
124
  }
122
125
 
123
126
  .label[data-required='true']::after {
124
127
  content: ' *';
128
+ }
129
+
130
+ :host([invalid]) .label[data-required='true'],
131
+ :host([invalid]) .label[data-required='true']::after {
125
132
  color: var(--sken-destructive);
126
133
  }
127
134
 
@@ -431,6 +438,7 @@ var s = 1, c = !0, l = class extends t {
431
438
  step=${this.step}
432
439
  aria-invalid=${this.invalid ? "true" : "false"}
433
440
  aria-describedby=${this.describedById ?? ""}
441
+ aria-label=${this.ariaLabel ?? ""}
434
442
  name=${this.name ?? ""}
435
443
  style="text-align: ${this.textAlignment}"
436
444
  @input=${this.#c}
@@ -533,7 +541,7 @@ e([o({
533
541
  })], l.prototype, "readonly", void 0), e([o({ attribute: "helper-text" })], l.prototype, "helperText", void 0), e([o({ attribute: "info-text" })], l.prototype, "infoText", void 0), e([o({ attribute: "warning-text" })], l.prototype, "warningText", void 0), e([o({ attribute: "valid-text" })], l.prototype, "validText", void 0), e([o({ attribute: "invalid-text" })], l.prototype, "invalidText", void 0), e([o({ attribute: "text-alignment" })], l.prototype, "textAlignment", void 0), e([o({ reflect: !0 })], l.prototype, "size", void 0), e([o({
534
542
  reflect: !0,
535
543
  type: Boolean
536
- })], l.prototype, "invalid", void 0), e([o({ attribute: "described-by-id" })], l.prototype, "describedById", void 0), e([o()], l.prototype, "name", void 0), l = e([a("sken-number-input")], l);
544
+ })], l.prototype, "invalid", void 0), e([o({ attribute: "described-by-id" })], l.prototype, "describedById", void 0), e([o()], l.prototype, "name", void 0), e([o({ attribute: "aria-label" })], l.prototype, "ariaLabel", void 0), l = e([a("sken-number-input")], l);
537
545
  //#endregion
538
546
  export { l as SkenNumberInput };
539
547
 
@@ -1 +1 @@
1
- {"version":3,"file":"sken-number-input.js","names":["#rawText","#parseNumber","#syncFormValue","#clamp","#step","#commitValue","#internals","#slotEndObserver","#visibilityObserver","#adjustLayout","#renderMessages","#handleInput","#handleChange","#handleFocus","#handleBlur","#handleKeydown","#handleStepUp","#handleStepDown"],"sources":["../src/components/sken-number-input.ts"],"sourcesContent":["// ── <sken-number-input> — Sken-owned Web Component ─────────────────\n// Lit 3 primitive for numeric input with visible ± stepper buttons,\n// min/max clamping, and the same start/end slot system as\n// <sken-input>. Promoted from the SkenNumberInput PoC story (which\n// was a thin wrap of <ix-number-input>). The PoC proved the UX;\n// this primitive owns the contract.\n\nimport type { SkenNumberInputSize } from '@sken-ds/contracts'\nimport { LitElement, css, html, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\n/**\n * Default step when the consumer does not pass one. Matches the\n * native `<input type=\"number\" step>` default.\n */\nconst DEFAULT_STEP = 1\n\n/**\n * Default value for `showStepperButtons`. The PoC validated that\n * the iX default of `false` is wrong for our use case: consumers\n * expect ± buttons for the numeric fields they use most (Alma\n * demo: quantity, temperature, score). The Sken primitive flips\n * the default to `true`; consumers who want a bare input pass\n * `showStepperButtons={false}` explicitly.\n */\nconst DEFAULT_SHOW_STEPPER_BUTTONS = true\n\n@customElement('sken-number-input')\nexport class SkenNumberInput extends LitElement {\n // Form-associated custom element. Same pattern as SkenInput /\n // SkenTextarea. The numeric value is stringified for\n // FormData (FormData is text-only); undefined becomes the\n // empty string. The implicit-submit-on-Enter rule still\n // applies, so a form with a single number input and a\n // submit button will submit on Enter.\n // https://lit.dev/docs/components/form/\n static formAssociated = true\n\n // ElementInternals handle. Created in the constructor.\n // Nullable to gracefully degrade in test environments.\n #internals: ElementInternals | null = null\n\n constructor() {\n super()\n try {\n this.#internals = this.attachInternals()\n } catch {\n this.#internals = null\n }\n }\n\n // ── Props ─────────────────────────────────────────────────────\n @property({ attribute: 'default-value', type: Number }) defaultValue: number | undefined =\n undefined\n @property({ type: Number }) value: number | undefined = undefined\n @property({ type: Number }) min: number | undefined = undefined\n @property({ type: Number }) max: number | undefined = undefined\n @property({ type: Number }) step: number = DEFAULT_STEP\n @property({ attribute: 'show-stepper-buttons', reflect: true, type: Boolean })\n showStepperButtons = DEFAULT_SHOW_STEPPER_BUTTONS\n @property() placeholder: string | undefined = undefined\n @property() label: string | undefined = undefined\n @property({ reflect: true, type: Boolean }) required = false\n @property({ reflect: true, type: Boolean }) disabled = false\n @property({ reflect: true, type: Boolean }) readonly = false\n @property({ attribute: 'helper-text' }) helperText: string | undefined = undefined\n @property({ attribute: 'info-text' }) infoText: string | undefined = undefined\n @property({ attribute: 'warning-text' }) warningText: string | undefined = undefined\n @property({ attribute: 'valid-text' }) validText: string | undefined = undefined\n @property({ attribute: 'invalid-text' }) invalidText: string | undefined = undefined\n @property({ attribute: 'text-alignment' }) textAlignment: 'start' | 'end' = 'end'\n @property({ reflect: true }) size: SkenNumberInputSize = 'md'\n @property({ reflect: true, type: Boolean }) invalid = false\n @property({ attribute: 'described-by-id' }) describedById: string | undefined = undefined\n @property() name: string | undefined = undefined\n\n // ── Internal state ────────────────────────────────────────────\n /**\n * The raw text in the input. The DOM input is the source of\n * truth for the text (so the user can type a partial value like\n * \"-\", which is not a valid number yet). The numeric `value` is\n * derived from this on commit (blur, Enter, stepper).\n */\n #rawText: string = ''\n\n // ── DOM refs (queried on demand) ────────────────────────────\n\n // ── Lifecycle ─────────────────────────────────────────────────\n override connectedCallback(): void {\n super.connectedCallback()\n // Seed the raw text from the controlled value or the default.\n // We do NOT trigger a re-render here; the property is only\n // set after Lit has applied the host attributes, and the\n // first render will read the right value through this.value.\n if (this.value === undefined) {\n this.#rawText = this.defaultValue !== undefined ? String(this.defaultValue) : ''\n } else {\n this.#rawText = String(this.value)\n }\n // Publish the initial value to the surrounding form.\n this.#syncFormValue()\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n this.#slotEndObserver?.disconnect()\n this.#visibilityObserver?.disconnect()\n }\n\n // ── Form-association callbacks ─────────────────────────────\n // formResetCallback: restore uncontrolled seed; re-render\n // controlled so Lit re-publishes the controlled value.\n formResetCallback(): void {\n if (this.value === undefined) {\n this.#rawText = this.defaultValue !== undefined ? String(this.defaultValue) : ''\n const inputEl = this.renderRoot.querySelector('input') as HTMLInputElement | null\n if (inputEl) inputEl.value = this.#rawText\n this.#syncFormValue()\n } else {\n this.requestUpdate()\n }\n }\n\n // formDisabledCallback: mirror the form's :disabled state\n // onto the prop. Lit re-renders; the inner <input> picks up\n // ?disabled=${this.disabled} in the template.\n formDisabledCallback(isDisabled: boolean): void {\n this.disabled = isDisabled\n }\n\n // Publish the current value to the form. FormData is\n // text-only, so we stringify the number (or empty string\n // for undefined). The string roundtrip preserves precision\n // for integers and finite decimals; consumers that need\n // BigInt or arbitrary precision should re-parse on the\n // server side.\n #syncFormValue(): void {\n if (!this.#internals) return\n const value = this.value !== undefined ? String(this.value) : ''\n this.#internals.setFormValue(value)\n }\n\n /**\n * Set up the dynamic layout once the shadow root has the\n * element children we need to measure. Same pattern as\n * SkenInput: MutationObserver on the slot container to\n * catch children being added/removed, IntersectionObserver\n * to catch the case where the input is hidden at first\n * connect (e.g. inside a closed <details> or an off-screen\n * tab), and slotchange as belt-and-braces.\n */\n protected override firstUpdated(): void {\n const endContainer = this.renderRoot.querySelector('.slot-end') as HTMLElement | null\n const endSlot = this.renderRoot.querySelector('slot[name=\"end\"]') as HTMLSlotElement | null\n if (endContainer && endSlot) {\n this.#slotEndObserver = new MutationObserver(() => this.#adjustLayout())\n this.#slotEndObserver.observe(endContainer, {\n childList: true,\n subtree: true,\n attributes: true,\n })\n endSlot.addEventListener('slotchange', this.#adjustLayout)\n }\n this.#visibilityObserver = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) this.#adjustLayout()\n }\n })\n this.#visibilityObserver.observe(this)\n this.#adjustLayout()\n }\n\n /**\n * The gap between a slot's edge and the input's text. 0.5rem\n * is what the SkenInput primitive uses (mirrors the iX rule).\n * The number input needs an extra \"air\" margin because the\n * stepper column is also on the right edge and we don't want\n * the value text to crash into the slot.\n */\n #SLOT_AIR = '0.5rem'\n\n /**\n * Measure the stepper column width and the end slot width, then\n * apply:\n * - The input's padding-inline-end so the value text never\n * overlaps the slot OR the stepper.\n * - The end slot's inset-inline-end so it sits to the LEFT of\n * the stepper (or at the right edge when no stepper).\n *\n * The math:\n * endInset = stepperReserve + air (slot's right edge)\n * paddingInline = max(endInset, stepperReserve) + air\n * If the end slot is empty, its width is 0 and the calc\n * falls back to the stepper-only reservation.\n */\n #adjustLayout = (): void => {\n const inputEl = this.renderRoot.querySelector('input') as HTMLInputElement | null\n const stepperEl = this.renderRoot.querySelector('.steppers') as HTMLElement | null\n const slotEndEl = this.renderRoot.querySelector('.slot-end') as HTMLElement | null\n if (!inputEl || !slotEndEl) return\n\n requestAnimationFrame(() => {\n if (!inputEl.isConnected) return\n const stepperWidth =\n stepperEl && this.showStepperButtons ? stepperEl.getBoundingClientRect().width : 0\n const endWidth = slotEndEl.getBoundingClientRect().width\n const air = parseFloat(getComputedStyle(inputEl).fontSize) * 0.5 // 0.5em in px\n const stepperReserve = stepperWidth\n // The slot's right edge sits at stepperReserve + air from\n // the input's right edge, so it never overlaps the stepper\n // column.\n const endInset = stepperReserve + air\n slotEndEl.style.insetInlineEnd = endInset > 0 ? `${endInset}px` : '0.5rem'\n // The input's padding-inline-end has to be at least the\n // wider of (stepperReserve, endInset + endWidth) so the\n // value text never overlaps either.\n const textReserve = endInset + endWidth\n const padEnd = Math.max(stepperReserve, textReserve) + air\n inputEl.style.paddingInlineEnd = `${padEnd}px`\n })\n }\n\n #slotEndObserver: MutationObserver | null = null\n #visibilityObserver: IntersectionObserver | null = null\n\n // ── Styles ────────────────────────────────────────────────────\n static styles = css`\n :host {\n display: inline-block;\n inline-size: 100%;\n }\n\n .field {\n display: grid;\n gap: 0.375rem;\n }\n\n .label {\n font-size: 0.8125rem;\n font-weight: 500;\n color: var(--sken-foreground);\n }\n\n .label[data-required='true']::after {\n content: ' *';\n color: var(--sken-destructive);\n }\n\n .input-wrapper {\n position: relative;\n display: flex;\n align-items: center;\n inline-size: 100%;\n }\n\n input {\n box-sizing: border-box;\n inline-size: 100%;\n border: 1px solid var(--sken-border);\n border-radius: var(--sken-md);\n background: var(--sken-input);\n color: var(--sken-foreground);\n font-family: var(--sken-family-sans);\n font-size: var(--sken-size-md);\n line-height: 1.25;\n text-overflow: ellipsis;\n padding-block: var(--sken-2);\n padding-inline: var(--sken-3);\n /* When the ± steppers are visible, reserve the right edge\n for them so the value text never overlaps. 1.5rem (button)\n + 2 * 0.25rem (insets) + 0.25rem (air) = 2.25rem. */\n padding-inline-end: var(--sken-3);\n min-block-size: 2.25rem;\n transition:\n border-color 120ms ease,\n box-shadow 120ms ease;\n /* Hide the native steppers: we render our own. */\n -moz-appearance: textfield;\n }\n\n input::-webkit-outer-spin-button,\n input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n }\n\n input::placeholder {\n color: var(--sken-muted-foreground);\n opacity: 1;\n }\n\n /* ── Stepper buttons ───────────────────────────────────── */\n /*\n * The stepper column is a vertical pair of + and − buttons\n * anchored to the right edge of the input. Sizes scale with\n * the host's [size] attribute (sm / md / lg) so the buttons\n * match the input's vertical rhythm. Default (md) is 24x20\n * per button. Glyph color is --sken-foreground; hover swaps\n * to --sken-primary. Disabled state uses --sken-disabled.\n */\n .steppers {\n position: absolute;\n inset-block: 0.25rem;\n inset-inline-end: 0.25rem;\n display: flex;\n flex-direction: column;\n gap: 2px;\n align-items: center;\n justify-content: center;\n }\n\n .stepper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n inline-size: 1.5rem;\n block-size: 1.25rem;\n background: transparent;\n border: 0;\n padding: 0;\n color: var(--sken-foreground);\n cursor: pointer;\n font-family: inherit;\n font-size: 1rem;\n line-height: 1;\n font-weight: 600;\n border-radius: var(--sken-sm, 0.25rem);\n /* The parent .steppers is a flex column; prevent the\n buttons from being squashed when the host's vertical\n rhythm is tight (sm). The explicit block-size wins. */\n flex-shrink: 0;\n transition:\n color 120ms ease,\n background-color 120ms ease;\n }\n\n .stepper:hover:not(:disabled) {\n color: var(--sken-primary);\n /* No background fill on hover. A background would paint\n over the input's right border, hiding it. The color\n change to --sken-primary is enough affordance for a\n 20x24px icon button. */\n background: transparent;\n }\n\n .stepper:focus-visible {\n outline: 2px solid var(--sken-ring, currentColor);\n outline-offset: 1px;\n }\n\n .stepper:disabled {\n cursor: not-allowed;\n opacity: var(--sken-disabled);\n }\n\n /* When the ± steppers are visible, reserve the right edge\n of the input so the value text never overlaps. The\n reservation is 2rem for the md size (default): 1.5rem\n (button) + 2 * 0.25rem (column insets). The sm / lg\n variants below override the button size, so the\n reservation needs to scale too. */\n :host([show-stepper-buttons]) input {\n padding-inline-end: 2rem;\n }\n\n /* ── Stepper sizes ─────────────────────────────────────── */\n :host([size='sm']) .steppers {\n inset-block: 0.125rem;\n inset-inline-end: 0.125rem;\n gap: 1px;\n }\n :host([size='sm']) .stepper {\n inline-size: 1.125rem;\n block-size: 0.875rem;\n font-size: 0.75rem;\n }\n /* sm: 1.125rem (button) + 2 * 0.125rem (insets) = 1.375rem,\n plus 0.125rem of breathing air = 1.5rem. */\n :host([size='sm'][show-stepper-buttons]) input {\n padding-inline-end: 1.5rem;\n }\n\n :host([size='lg']) .steppers {\n inset-block: 0.375rem;\n inset-inline-end: 0.375rem;\n gap: 3px;\n }\n :host([size='lg']) .stepper {\n inline-size: 1.75rem;\n block-size: 1.5rem;\n font-size: 1.125rem;\n }\n /* lg: 1.75rem (button) + 2 * 0.375rem (insets) = 2.5rem,\n plus 0.25rem of breathing air = 2.75rem. */\n :host([size='lg'][show-stepper-buttons]) input {\n padding-inline-end: 2.75rem;\n }\n\n /* Hide steppers on touch devices where they would be hard\n to hit accurately. The user can still use ↑/↓ keys or\n type directly. */\n @media (hover: none) {\n .steppers {\n display: none;\n }\n input {\n padding-inline-end: var(--sken-3);\n }\n :host([size='sm']) input {\n padding-inline-end: var(--sken-2);\n }\n :host([size='lg']) input {\n padding-inline-end: var(--sken-4);\n }\n }\n\n /* ── Slot containers ────────────────────────────────────── */\n .slot {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1;\n pointer-events: none;\n color: var(--sken-muted-foreground);\n }\n\n .slot ::slotted(*) {\n pointer-events: auto;\n }\n\n .slot-start {\n inset-inline-start: 0.5rem;\n }\n\n /*\n * The end slot's position is set in JS via the inline style\n * (see #adjustLayout). The rule below is a fallback for the\n * initial render before JS has measured the slot, and a\n * sensible default when the slot is empty.\n */\n .slot-end {\n inset-inline-end: 0.5rem;\n }\n\n /* ── States ─────────────────────────────────────────────── */\n .input-wrapper:hover input:not(:disabled):not(:read-only) {\n border-color: var(--sken-foreground);\n }\n\n .input-wrapper:focus-within input {\n outline: 2px solid var(--sken-ring, currentColor);\n outline-offset: 2px;\n border-color: var(--sken-primary);\n }\n\n input:disabled {\n cursor: not-allowed;\n opacity: var(--sken-disabled);\n }\n\n input:read-only {\n background: var(--sken-muted);\n }\n\n :host([invalid]) input {\n border-color: var(--sken-destructive);\n }\n\n :host([invalid]) .input-wrapper:focus-within input {\n outline-color: var(--sken-destructive);\n }\n\n /* ── Sizes ──────────────────────────────────────────────── */\n :host([size='sm']) input {\n padding-block: var(--sken-1);\n padding-inline: var(--sken-2);\n font-size: var(--sken-size-sm);\n /* 2 × 0.875rem (buttons) + 1px (gap) + 2 × 0.125rem\n (insets) = ~2.0625rem. Round up to 2.25rem for visual\n breathing room. */\n min-block-size: 2.25rem;\n }\n :host([size='md']) input {\n /* Default styles above. */\n }\n :host([size='lg']) input {\n padding-block: var(--sken-3);\n padding-inline: var(--sken-4);\n font-size: var(--sken-size-lg);\n /* 2 × 1.5rem (buttons) + 3px (gap) + 2 × 0.375rem\n (insets) = ~3.9375rem. Round up to 4rem. */\n min-block-size: 4rem;\n }\n\n /* ── Validation messages ─────────────────────────────────── */\n .messages {\n display: grid;\n gap: 0.25rem;\n }\n\n .message {\n margin: 0;\n font-size: 0.75rem;\n line-height: 1.4;\n }\n\n .message[data-tone='info'] {\n color: var(--sken-info, #0082ff);\n }\n\n .message[data-tone='warning'] {\n color: var(--sken-warning, #f59e0b);\n }\n\n .message[data-tone='valid'] {\n color: var(--sken-success, #10b981);\n }\n\n .message[data-tone='invalid'] {\n color: var(--sken-destructive, #dc2626);\n }\n\n .message[data-tone='helper'] {\n color: var(--sken-muted-foreground);\n }\n `\n\n // ── Render ────────────────────────────────────────────────────\n protected override render() {\n const messages = this.#renderMessages()\n return html`\n <div class=\"field\" part=\"field\">\n ${\n this.label\n ? html`\n <label class=\"label\" part=\"label\" data-required=${this.required}>\n ${this.label}\n </label>\n `\n : nothing\n }\n <div class=\"input-wrapper\" part=\"wrapper\">\n <div class=\"slot slot-start\" part=\"slot-start\">\n <slot name=\"start\"></slot>\n </div>\n <input\n part=\"input\"\n type=\"number\"\n .value=${this.#rawText}\n placeholder=${this.placeholder ?? ''}\n ?disabled=${this.disabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n min=${this.min ?? ''}\n max=${this.max ?? ''}\n step=${this.step}\n aria-invalid=${this.invalid ? 'true' : 'false'}\n aria-describedby=${this.describedById ?? ''}\n name=${this.name ?? ''}\n style=\"text-align: ${this.textAlignment}\"\n @input=${this.#handleInput}\n @change=${this.#handleChange}\n @focus=${this.#handleFocus}\n @blur=${this.#handleBlur}\n @keydown=${this.#handleKeydown}\n />\n <div class=\"slot slot-end\" part=\"slot-end\">\n <slot name=\"end\"></slot>\n </div>\n ${\n this.showStepperButtons\n ? html`\n <div class=\"steppers\" part=\"steppers\">\n <button\n class=\"stepper\"\n part=\"stepper stepper-up\"\n type=\"button\"\n aria-label=\"Increment\"\n ?disabled=${\n this.disabled ||\n this.readonly ||\n (this.value !== undefined &&\n this.max !== undefined &&\n this.value >= this.max)\n }\n @click=${this.#handleStepUp}\n >\n +\n </button>\n <button\n class=\"stepper\"\n part=\"stepper stepper-down\"\n type=\"button\"\n aria-label=\"Decrement\"\n ?disabled=${\n this.disabled ||\n this.readonly ||\n (this.value !== undefined &&\n this.min !== undefined &&\n this.value <= this.min)\n }\n @click=${this.#handleStepDown}\n >\n −\n </button>\n </div>\n `\n : nothing\n }\n </div>\n ${\n messages.length > 0\n ? html` <div class=\"messages\" part=\"messages\">${messages}</div> `\n : nothing\n }\n </div>\n `\n }\n\n #renderMessages() {\n const messages: ReturnType<typeof html>[] = []\n if (this.invalidText) {\n messages.push(\n html`<p class=\"message\" data-tone=\"invalid\" part=\"message-invalid\">${this.invalidText}</p>`,\n )\n }\n if (this.warningText) {\n messages.push(\n html`<p class=\"message\" data-tone=\"warning\" part=\"message-warning\">${this.warningText}</p>`,\n )\n }\n if (this.infoText) {\n messages.push(\n html`<p class=\"message\" data-tone=\"info\" part=\"message-info\">${this.infoText}</p>`,\n )\n }\n if (this.validText) {\n messages.push(\n html`<p class=\"message\" data-tone=\"valid\" part=\"message-valid\">${this.validText}</p>`,\n )\n }\n if (this.helperText && messages.length === 0) {\n messages.push(\n html`<p class=\"message\" data-tone=\"helper\" part=\"message-helper\">${this.helperText}</p>`,\n )\n }\n return messages\n }\n\n // ── Event handlers ───────────────────────────────────────────\n #handleInput = (event: Event) => {\n const target = event.target as HTMLInputElement\n this.#rawText = target.value\n // Emit the parsed number (or undefined if empty / invalid).\n const parsed = this.#parseNumber(target.value)\n if (this.value === undefined) {\n // Uncontrolled: store internally, do not mutate this.value\n // because the contract is \"controlled by the consumer\".\n this.#syncFormValue()\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-input', {\n detail: parsed,\n bubbles: true,\n composed: true,\n }),\n )\n } else {\n this.#syncFormValue()\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-input', {\n detail: parsed,\n bubbles: true,\n composed: true,\n }),\n )\n }\n }\n\n #handleChange = (event: Event) => {\n const target = event.target as HTMLInputElement\n const parsed = this.#parseNumber(target.value)\n // Clamp on commit. If the user typed 150 with max=100, we\n // commit 100 and update the input text.\n const clamped = this.#clamp(parsed)\n if (clamped !== parsed) {\n this.#rawText = clamped !== undefined ? String(clamped) : ''\n target.value = this.#rawText\n }\n this.#syncFormValue()\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-change', {\n detail: clamped,\n bubbles: true,\n composed: true,\n }),\n )\n }\n\n #handleFocus = (_event: FocusEvent) => {\n this.dispatchEvent(new CustomEvent('sken-focus', { bubbles: true, composed: true }))\n }\n\n #handleBlur = (_event: FocusEvent) => {\n this.dispatchEvent(new CustomEvent('sken-blur', { bubbles: true, composed: true }))\n }\n\n #handleKeydown = (event: KeyboardEvent) => {\n if (this.disabled || this.readonly) return\n // Shift+Arrow and PageUp/PageDown apply a 10x multiplier to\n // the step. This is the standard \"fast forward\" pattern in\n // numeric inputs across Mature DS (iX, Material, AntD). It\n // lets the consumer reach the target faster when the step\n // is small relative to the typical range.\n const multiplier = event.shiftKey || event.key === 'PageUp' || event.key === 'PageDown' ? 10 : 1\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n this.#step(+1, multiplier)\n } else if (event.key === 'ArrowDown') {\n event.preventDefault()\n this.#step(-1, multiplier)\n } else if (event.key === 'PageUp') {\n event.preventDefault()\n this.#step(+1, 10)\n } else if (event.key === 'PageDown') {\n event.preventDefault()\n this.#step(-1, 10)\n } else if (event.key === 'Home' && this.min !== undefined) {\n event.preventDefault()\n this.#commitValue(this.min)\n } else if (event.key === 'End' && this.max !== undefined) {\n event.preventDefault()\n this.#commitValue(this.max)\n } else if (event.key === 'Enter') {\n // The browser fires a `change` event on Enter for\n // <input type=\"number\">, so we don't need to commit\n // anything manually. We DO need to bridge the shadow\n // boundary so the surrounding <form> receives a submit\n // event — the browser's implicit submission algorithm\n // does not see the inner input. Mirrors SkenInput and\n // SkenTextarea. `isComposing` guards IME composition.\n if (event.isComposing) return\n // preventDefault runs even without ElementInternals,\n // keeping the observable behaviour consistent.\n event.preventDefault()\n const form = this.#internals?.form\n if (!form) return\n form.requestSubmit()\n }\n }\n\n #handleStepUp = () => this.#step(+1)\n #handleStepDown = () => this.#step(-1)\n\n // ── Helpers ───────────────────────────────────────────────────\n /**\n * Increment / decrement the value by `step * multiplier`,\n * clamped at [min, max]. Emits `sken-input` and `sken-change`\n * (we treat the stepper as a commit, not a keystroke). The\n * `multiplier` defaults to 1; the keyboard handler passes 10\n * for Shift+Arrow and PageUp/PageDown (the \"fast forward\"\n * pattern).\n *\n * The current value is read from the rendered <input>, NOT\n * from `this.value`. Reading from the input is robust for\n * both modes:\n * - Controlled: the consumer is async (Vue's reactive update\n * applies on the next tick). By the time the second click\n * arrives, the consumer's `value` prop may still be the\n * pre-click value, but the input's `.value` is already\n * updated by #commitValue from the first click.\n * - Uncontrolled: the primitive owns the value. The input's\n * `.value` is the source of truth; `this.value` is the\n * initial seed and not maintained by the primitive in this\n * mode.\n */\n #step(direction: 1 | -1, multiplier: number = 1) {\n if (this.disabled || this.readonly) return\n const inputEl = this.renderRoot.querySelector('input') as HTMLInputElement | null\n const text = inputEl?.value ?? ''\n const current = text === '' ? (this.value ?? 0) : Number(text)\n if (!Number.isFinite(current)) return\n const next = current + direction * this.step * multiplier\n this.#commitValue(next)\n }\n\n /**\n * Commit a value: clamp, update internal state, emit events.\n * Same path used by the stepper buttons and the keyboard\n * Home / End shortcuts.\n */\n #commitValue(raw: number) {\n const clamped = this.#clamp(raw)\n this.#rawText = clamped !== undefined ? String(clamped) : ''\n // Update the DOM input so the next @input sees the new value\n const inputEl = this.renderRoot.querySelector('input') as HTMLInputElement | null\n if (inputEl) inputEl.value = this.#rawText\n this.#syncFormValue()\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-input', {\n detail: clamped,\n bubbles: true,\n composed: true,\n }),\n )\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-change', {\n detail: clamped,\n bubbles: true,\n composed: true,\n }),\n )\n }\n\n #clamp(value: number | undefined): number | undefined {\n if (value === undefined || Number.isNaN(value)) return undefined\n let v = value\n if (this.min !== undefined && v < this.min) v = this.min\n if (this.max !== undefined && v > this.max) v = this.max\n return v\n }\n\n #parseNumber(text: string): number | undefined {\n if (text === '' || text === '-') return undefined\n const n = Number(text)\n if (Number.isNaN(n)) return undefined\n return n\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sken-number-input': SkenNumberInput\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sken-number-input.js","names":[],"sources":["../src/components/sken-number-input.ts"],"sourcesContent":["// ── <sken-number-input> — Sken-owned Web Component ─────────────────\n\nimport type { SkenNumberInputSize } from '@sken-ds/contracts'\nimport { LitElement, css, html, nothing, type PropertyValues } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nconst DEFAULT_STEP = 1\n\nconst DEFAULT_SHOW_STEPPER_BUTTONS = true\n\n@customElement('sken-number-input')\nexport class SkenNumberInput extends LitElement {\n static formAssociated = true\n\n #internals: ElementInternals | undefined = undefined\n\n constructor() {\n super()\n try {\n this.#internals = this.attachInternals()\n } catch {\n this.#internals = undefined\n }\n }\n\n // ── Props ─────────────────────────────────────────────────────\n @property({ attribute: 'default-value', type: Number }) defaultValue: number | undefined =\n undefined\n @property({ type: Number }) value: number | undefined = undefined\n @property({ type: Number }) min: number | undefined = undefined\n @property({ type: Number }) max: number | undefined = undefined\n @property({ type: Number }) step: number = DEFAULT_STEP\n @property({ attribute: 'show-stepper-buttons', reflect: true, type: Boolean })\n showStepperButtons = DEFAULT_SHOW_STEPPER_BUTTONS\n @property() placeholder: string | undefined = undefined\n @property() label: string | undefined = undefined\n @property({ reflect: true, type: Boolean }) required = false\n @property({ reflect: true, type: Boolean }) disabled = false\n @property({ reflect: true, type: Boolean }) readonly = false\n @property({ attribute: 'helper-text' }) helperText: string | undefined = undefined\n @property({ attribute: 'info-text' }) infoText: string | undefined = undefined\n @property({ attribute: 'warning-text' }) warningText: string | undefined = undefined\n @property({ attribute: 'valid-text' }) validText: string | undefined = undefined\n @property({ attribute: 'invalid-text' }) invalidText: string | undefined = undefined\n @property({ attribute: 'text-alignment' }) textAlignment: 'start' | 'end' = 'end'\n @property({ reflect: true }) size: SkenNumberInputSize = 'md'\n @property({ reflect: true, type: Boolean }) invalid = false\n @property({ attribute: 'described-by-id' }) describedById: string | undefined = undefined\n @property() name: string | undefined = undefined\n @property({ attribute: 'aria-label' }) override ariaLabel: string | null = null\n\n // ── Internal state ────────────────────────────────────────────\n #rawText: string = ''\n\n // ── DOM refs (queried on demand) ────────────────────────────\n\n // ── Lifecycle ─────────────────────────────────────────────────\n override connectedCallback(): void {\n super.connectedCallback()\n if (this.value === undefined) {\n this.#rawText = this.defaultValue !== undefined ? String(this.defaultValue) : ''\n } else {\n this.#rawText = String(this.value)\n }\n this.#syncFormValue()\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n this.#slotEndObserver?.disconnect()\n this.#visibilityObserver?.disconnect()\n }\n\n protected override willUpdate(changedProperties: PropertyValues<this>): void {\n if (!changedProperties.has('value')) return\n if (this.value === undefined) {\n this.#rawText = this.defaultValue !== undefined ? String(this.defaultValue) : ''\n } else if (this.#parseNumber(this.#rawText) !== this.value) {\n this.#rawText = String(this.value)\n }\n }\n\n // ── Form-association callbacks ─────────────────────────────\n formResetCallback(): void {\n if (this.value === undefined) {\n this.#rawText = this.defaultValue !== undefined ? String(this.defaultValue) : ''\n const inputEl = this.renderRoot.querySelector('input') as HTMLInputElement | null\n if (inputEl) inputEl.value = this.#rawText\n this.#syncFormValue()\n } else {\n this.requestUpdate()\n }\n }\n\n formDisabledCallback(isDisabled: boolean): void {\n this.disabled = isDisabled\n }\n\n #syncFormValue(): void {\n if (!this.#internals) return\n const value = this.value !== undefined ? String(this.value) : ''\n this.#internals.setFormValue(value)\n }\n\n protected override firstUpdated(): void {\n const endContainer = this.renderRoot.querySelector('.slot-end') as HTMLElement | null\n const endSlot = this.renderRoot.querySelector('slot[name=\"end\"]') as HTMLSlotElement | null\n if (endContainer && endSlot) {\n this.#slotEndObserver = new MutationObserver(() => this.#adjustLayout())\n this.#slotEndObserver.observe(endContainer, {\n childList: true,\n subtree: true,\n attributes: true,\n })\n endSlot.addEventListener('slotchange', this.#adjustLayout)\n }\n this.#visibilityObserver = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) this.#adjustLayout()\n }\n })\n this.#visibilityObserver.observe(this)\n this.#adjustLayout()\n }\n\n #SLOT_AIR = '0.5rem'\n\n #adjustLayout = (): void => {\n const inputEl = this.renderRoot.querySelector('input') as HTMLInputElement | null\n const stepperEl = this.renderRoot.querySelector('.steppers') as HTMLElement | null\n const slotEndEl = this.renderRoot.querySelector('.slot-end') as HTMLElement | null\n if (!inputEl || !slotEndEl) return\n\n requestAnimationFrame(() => {\n if (!inputEl.isConnected) return\n const stepperWidth =\n stepperEl && this.showStepperButtons ? stepperEl.getBoundingClientRect().width : 0\n const endWidth = slotEndEl.getBoundingClientRect().width\n const air = parseFloat(getComputedStyle(inputEl).fontSize) * 0.5 // 0.5em in px\n const stepperReserve = stepperWidth\n const endInset = stepperReserve + air\n slotEndEl.style.insetInlineEnd = endInset > 0 ? `${endInset}px` : '0.5rem'\n const textReserve = endInset + endWidth\n const padEnd = Math.max(stepperReserve, textReserve) + air\n inputEl.style.paddingInlineEnd = `${padEnd}px`\n })\n }\n\n #slotEndObserver: MutationObserver | undefined = undefined\n #visibilityObserver: IntersectionObserver | undefined = undefined\n\n // ── Styles ────────────────────────────────────────────────────\n static styles = css`\n :host {\n display: inline-block;\n inline-size: 100%;\n }\n\n .field {\n display: grid;\n gap: var(--sken-1, 0.25rem);\n }\n\n .label {\n font-size: var(--sken-size-xs, 0.75rem);\n font-weight: 500;\n color: var(--sken-foreground);\n }\n\n .label[data-required='true']::after {\n content: ' *';\n }\n\n :host([invalid]) .label[data-required='true'],\n :host([invalid]) .label[data-required='true']::after {\n color: var(--sken-destructive);\n }\n\n .input-wrapper {\n position: relative;\n display: flex;\n align-items: center;\n inline-size: 100%;\n }\n\n input {\n box-sizing: border-box;\n inline-size: 100%;\n border: 1px solid var(--sken-border);\n border-radius: var(--sken-md);\n background: var(--sken-input);\n color: var(--sken-foreground);\n font-family: var(--sken-family-sans);\n font-size: var(--sken-size-md);\n line-height: 1.25;\n text-overflow: ellipsis;\n padding-block: var(--sken-2);\n padding-inline: var(--sken-3);\n /* When the ± steppers are visible, reserve the right edge\n for them so the value text never overlaps. 1.5rem (button)\n + 2 * 0.25rem (insets) + 0.25rem (air) = 2.25rem. */\n padding-inline-end: var(--sken-3);\n min-block-size: 2.25rem;\n transition:\n border-color 120ms ease,\n box-shadow 120ms ease;\n /* Hide the native steppers: we render our own. */\n -moz-appearance: textfield;\n }\n\n input::-webkit-outer-spin-button,\n input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n }\n\n input::placeholder {\n color: var(--sken-muted-foreground);\n opacity: 1;\n }\n\n /* ── Stepper buttons ───────────────────────────────────── */\n /*\n * The stepper column is a vertical pair of + and − buttons\n * anchored to the right edge of the input. Sizes scale with\n * the host's [size] attribute (sm / md / lg) so the buttons\n * match the input's vertical rhythm. Default (md) is 24x20\n * per button. Glyph color is --sken-foreground; hover swaps\n * to --sken-primary. Disabled state uses --sken-disabled.\n */\n .steppers {\n position: absolute;\n inset-block: 0.25rem;\n inset-inline-end: 0.25rem;\n display: flex;\n flex-direction: column;\n gap: 2px;\n align-items: center;\n justify-content: center;\n }\n\n .stepper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n inline-size: 1.5rem;\n block-size: 1.25rem;\n background: transparent;\n border: 0;\n padding: 0;\n color: var(--sken-foreground);\n cursor: pointer;\n font-family: inherit;\n font-size: 1rem;\n line-height: 1;\n font-weight: 600;\n border-radius: var(--sken-sm, 0.25rem);\n /* The parent .steppers is a flex column; prevent the\n buttons from being squashed when the host's vertical\n rhythm is tight (sm). The explicit block-size wins. */\n flex-shrink: 0;\n transition:\n color 120ms ease,\n background-color 120ms ease;\n }\n\n .stepper:hover:not(:disabled) {\n color: var(--sken-primary);\n /* No background fill on hover. A background would paint\n over the input's right border, hiding it. The color\n change to --sken-primary is enough affordance for a\n 20x24px icon button. */\n background: transparent;\n }\n\n .stepper:focus-visible {\n outline: 2px solid var(--sken-ring, currentColor);\n outline-offset: 1px;\n }\n\n .stepper:disabled {\n cursor: not-allowed;\n opacity: var(--sken-disabled);\n }\n\n /* When the ± steppers are visible, reserve the right edge\n of the input so the value text never overlaps. The\n reservation is 2rem for the md size (default): 1.5rem\n (button) + 2 * 0.25rem (column insets). The sm / lg\n variants below override the button size, so the\n reservation needs to scale too. */\n :host([show-stepper-buttons]) input {\n padding-inline-end: 2rem;\n }\n\n /* ── Stepper sizes ─────────────────────────────────────── */\n :host([size='sm']) .steppers {\n inset-block: 0.125rem;\n inset-inline-end: 0.125rem;\n gap: 1px;\n }\n :host([size='sm']) .stepper {\n inline-size: 1.125rem;\n block-size: 0.875rem;\n font-size: 0.75rem;\n }\n /* sm: 1.125rem (button) + 2 * 0.125rem (insets) = 1.375rem,\n plus 0.125rem of breathing air = 1.5rem. */\n :host([size='sm'][show-stepper-buttons]) input {\n padding-inline-end: 1.5rem;\n }\n\n :host([size='lg']) .steppers {\n inset-block: 0.375rem;\n inset-inline-end: 0.375rem;\n gap: 3px;\n }\n :host([size='lg']) .stepper {\n inline-size: 1.75rem;\n block-size: 1.5rem;\n font-size: 1.125rem;\n }\n /* lg: 1.75rem (button) + 2 * 0.375rem (insets) = 2.5rem,\n plus 0.25rem of breathing air = 2.75rem. */\n :host([size='lg'][show-stepper-buttons]) input {\n padding-inline-end: 2.75rem;\n }\n\n /* Hide steppers on touch devices where they would be hard\n to hit accurately. The user can still use ↑/↓ keys or\n type directly. */\n @media (hover: none) {\n .steppers {\n display: none;\n }\n input {\n padding-inline-end: var(--sken-3);\n }\n :host([size='sm']) input {\n padding-inline-end: var(--sken-2);\n }\n :host([size='lg']) input {\n padding-inline-end: var(--sken-4);\n }\n }\n\n /* ── Slot containers ────────────────────────────────────── */\n .slot {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1;\n pointer-events: none;\n color: var(--sken-muted-foreground);\n }\n\n .slot ::slotted(*) {\n pointer-events: auto;\n }\n\n .slot-start {\n inset-inline-start: 0.5rem;\n }\n\n /*\n * The end slot's position is set in JS via the inline style\n * (see #adjustLayout). The rule below is a fallback for the\n * initial render before JS has measured the slot, and a\n * sensible default when the slot is empty.\n */\n .slot-end {\n inset-inline-end: 0.5rem;\n }\n\n /* ── States ─────────────────────────────────────────────── */\n .input-wrapper:hover input:not(:disabled):not(:read-only) {\n border-color: var(--sken-foreground);\n }\n\n .input-wrapper:focus-within input {\n outline: 2px solid var(--sken-ring, currentColor);\n outline-offset: 2px;\n border-color: var(--sken-primary);\n }\n\n input:disabled {\n cursor: not-allowed;\n opacity: var(--sken-disabled);\n }\n\n input:read-only {\n background: var(--sken-muted);\n }\n\n :host([invalid]) input {\n border-color: var(--sken-destructive);\n }\n\n :host([invalid]) .input-wrapper:focus-within input {\n outline-color: var(--sken-destructive);\n }\n\n /* ── Sizes ──────────────────────────────────────────────── */\n :host([size='sm']) input {\n padding-block: var(--sken-1);\n padding-inline: var(--sken-2);\n font-size: var(--sken-size-sm);\n /* 2 × 0.875rem (buttons) + 1px (gap) + 2 × 0.125rem\n (insets) = ~2.0625rem. Round up to 2.25rem for visual\n breathing room. */\n min-block-size: 2.25rem;\n }\n :host([size='md']) input {\n /* Default styles above. */\n }\n :host([size='lg']) input {\n padding-block: var(--sken-3);\n padding-inline: var(--sken-4);\n font-size: var(--sken-size-lg);\n /* 2 × 1.5rem (buttons) + 3px (gap) + 2 × 0.375rem\n (insets) = ~3.9375rem. Round up to 4rem. */\n min-block-size: 4rem;\n }\n\n /* ── Validation messages ─────────────────────────────────── */\n .messages {\n display: grid;\n gap: 0.25rem;\n }\n\n .message {\n margin: 0;\n font-size: 0.75rem;\n line-height: 1.4;\n }\n\n .message[data-tone='info'] {\n color: var(--sken-info, #0082ff);\n }\n\n .message[data-tone='warning'] {\n color: var(--sken-warning, #f59e0b);\n }\n\n .message[data-tone='valid'] {\n color: var(--sken-success, #10b981);\n }\n\n .message[data-tone='invalid'] {\n color: var(--sken-destructive, #dc2626);\n }\n\n .message[data-tone='helper'] {\n color: var(--sken-muted-foreground);\n }\n `\n\n // ── Render ────────────────────────────────────────────────────\n protected override render() {\n const messages = this.#renderMessages()\n return html`\n <div class=\"field\" part=\"field\">\n ${\n this.label\n ? html`\n <label class=\"label\" part=\"label\" data-required=${this.required}>\n ${this.label}\n </label>\n `\n : nothing\n }\n <div class=\"input-wrapper\" part=\"wrapper\">\n <div class=\"slot slot-start\" part=\"slot-start\">\n <slot name=\"start\"></slot>\n </div>\n <input\n part=\"input\"\n type=\"number\"\n .value=${this.#rawText}\n placeholder=${this.placeholder ?? ''}\n ?disabled=${this.disabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n min=${this.min ?? ''}\n max=${this.max ?? ''}\n step=${this.step}\n aria-invalid=${this.invalid ? 'true' : 'false'}\n aria-describedby=${this.describedById ?? ''}\n aria-label=${this.ariaLabel ?? ''}\n name=${this.name ?? ''}\n style=\"text-align: ${this.textAlignment}\"\n @input=${this.#handleInput}\n @change=${this.#handleChange}\n @focus=${this.#handleFocus}\n @blur=${this.#handleBlur}\n @keydown=${this.#handleKeydown}\n />\n <div class=\"slot slot-end\" part=\"slot-end\">\n <slot name=\"end\"></slot>\n </div>\n ${\n this.showStepperButtons\n ? html`\n <div class=\"steppers\" part=\"steppers\">\n <button\n class=\"stepper\"\n part=\"stepper stepper-up\"\n type=\"button\"\n aria-label=\"Increment\"\n ?disabled=${\n this.disabled ||\n this.readonly ||\n (this.value !== undefined &&\n this.max !== undefined &&\n this.value >= this.max)\n }\n @click=${this.#handleStepUp}\n >\n +\n </button>\n <button\n class=\"stepper\"\n part=\"stepper stepper-down\"\n type=\"button\"\n aria-label=\"Decrement\"\n ?disabled=${\n this.disabled ||\n this.readonly ||\n (this.value !== undefined &&\n this.min !== undefined &&\n this.value <= this.min)\n }\n @click=${this.#handleStepDown}\n >\n −\n </button>\n </div>\n `\n : nothing\n }\n </div>\n ${\n messages.length > 0\n ? html` <div class=\"messages\" part=\"messages\">${messages}</div> `\n : nothing\n }\n </div>\n `\n }\n\n #renderMessages() {\n const messages: ReturnType<typeof html>[] = []\n if (this.invalidText) {\n messages.push(\n html`<p class=\"message\" data-tone=\"invalid\" part=\"message-invalid\">${this.invalidText}</p>`,\n )\n }\n if (this.warningText) {\n messages.push(\n html`<p class=\"message\" data-tone=\"warning\" part=\"message-warning\">${this.warningText}</p>`,\n )\n }\n if (this.infoText) {\n messages.push(\n html`<p class=\"message\" data-tone=\"info\" part=\"message-info\">${this.infoText}</p>`,\n )\n }\n if (this.validText) {\n messages.push(\n html`<p class=\"message\" data-tone=\"valid\" part=\"message-valid\">${this.validText}</p>`,\n )\n }\n if (this.helperText && messages.length === 0) {\n messages.push(\n html`<p class=\"message\" data-tone=\"helper\" part=\"message-helper\">${this.helperText}</p>`,\n )\n }\n return messages\n }\n\n // ── Event handlers ───────────────────────────────────────────\n #handleInput = (event: Event) => {\n const target = event.target as HTMLInputElement\n this.#rawText = target.value\n const parsed = this.#parseNumber(target.value)\n if (this.value === undefined) {\n this.#syncFormValue()\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-input', {\n detail: parsed,\n bubbles: true,\n composed: true,\n }),\n )\n } else {\n this.#syncFormValue()\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-input', {\n detail: parsed,\n bubbles: true,\n composed: true,\n }),\n )\n }\n }\n\n #handleChange = (event: Event) => {\n const target = event.target as HTMLInputElement\n const parsed = this.#parseNumber(target.value)\n const clamped = this.#clamp(parsed)\n if (clamped !== parsed) {\n this.#rawText = clamped !== undefined ? String(clamped) : ''\n target.value = this.#rawText\n }\n this.#syncFormValue()\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-change', {\n detail: clamped,\n bubbles: true,\n composed: true,\n }),\n )\n }\n\n #handleFocus = (_event: FocusEvent) => {\n this.dispatchEvent(new CustomEvent('sken-focus', { bubbles: true, composed: true }))\n }\n\n #handleBlur = (_event: FocusEvent) => {\n this.dispatchEvent(new CustomEvent('sken-blur', { bubbles: true, composed: true }))\n }\n\n #handleKeydown = (event: KeyboardEvent) => {\n if (this.disabled || this.readonly) return\n const multiplier = event.shiftKey || event.key === 'PageUp' || event.key === 'PageDown' ? 10 : 1\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n this.#step(+1, multiplier)\n } else if (event.key === 'ArrowDown') {\n event.preventDefault()\n this.#step(-1, multiplier)\n } else if (event.key === 'PageUp') {\n event.preventDefault()\n this.#step(+1, 10)\n } else if (event.key === 'PageDown') {\n event.preventDefault()\n this.#step(-1, 10)\n } else if (event.key === 'Home' && this.min !== undefined) {\n event.preventDefault()\n this.#commitValue(this.min)\n } else if (event.key === 'End' && this.max !== undefined) {\n event.preventDefault()\n this.#commitValue(this.max)\n } else if (event.key === 'Enter') {\n if (event.isComposing) return\n event.preventDefault()\n const form = this.#internals?.form\n if (!form) return\n form.requestSubmit()\n }\n }\n\n #handleStepUp = () => this.#step(+1)\n #handleStepDown = () => this.#step(-1)\n\n // ── Helpers ───────────────────────────────────────────────────\n #step(direction: 1 | -1, multiplier: number = 1) {\n if (this.disabled || this.readonly) return\n const inputEl = this.renderRoot.querySelector('input') as HTMLInputElement | null\n const text = inputEl?.value ?? ''\n const current = text === '' ? (this.value ?? 0) : Number(text)\n if (!Number.isFinite(current)) return\n const next = current + direction * this.step * multiplier\n this.#commitValue(next)\n }\n\n #commitValue(raw: number) {\n const clamped = this.#clamp(raw)\n this.#rawText = clamped !== undefined ? String(clamped) : ''\n const inputEl = this.renderRoot.querySelector('input') as HTMLInputElement | null\n if (inputEl) inputEl.value = this.#rawText\n this.#syncFormValue()\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-input', {\n detail: clamped,\n bubbles: true,\n composed: true,\n }),\n )\n this.dispatchEvent(\n new CustomEvent<number | undefined>('sken-change', {\n detail: clamped,\n bubbles: true,\n composed: true,\n }),\n )\n }\n\n #clamp(value: number | undefined): number | undefined {\n if (value === undefined || Number.isNaN(value)) return undefined\n let v = value\n if (this.min !== undefined && v < this.min) v = this.min\n if (this.max !== undefined && v > this.max) v = this.max\n return v\n }\n\n #parseNumber(text: string): number | undefined {\n if (text === '' || text === '-') return undefined\n const n = Number(text)\n if (Number.isNaN(n)) return undefined\n return n\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sken-number-input': SkenNumberInput\n }\n}\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
- import { t as e } from "./decorate-D4FKzyDw.js";
1
+ import { t as e } from "./decorate-C1e2sg49.js";
2
2
  import { LitElement as t, css as n, html as r } from "lit";
3
3
  import { customElement as i, property as a, state as o } from "lit/decorators.js";
4
4
  //#region src/components/sken-switch.ts
5
5
  var s = class extends t {
6
6
  constructor(...e) {
7
- super(...e), this._on = void 0, this._onSet = !1, this.defaultOn = void 0, this.disabled = !1, this.readonly = !1, this.required = !1, this.describedById = void 0, this.name = void 0, this.ariaLabel = null, this._uncontrolled = !1, this.#t = (e) => {
7
+ super(...e), this._on = void 0, this._onSet = !1, this.defaultOn = void 0, this.disabled = !1, this.readonly = !1, this.required = !1, this.invalid = !1, this.describedById = void 0, this.name = void 0, this.ariaLabel = null, this._uncontrolled = !1, this.#t = (e) => {
8
8
  let t = e.target, n = t.checked;
9
9
  if (this.disabled) {
10
10
  t.checked = this.#e ? this.on === !0 : this._uncontrolled;
@@ -64,7 +64,7 @@ var s = class extends t {
64
64
  cursor: pointer;
65
65
  color: var(--sken-foreground);
66
66
  font-family: var(--sken-family-sans);
67
- font-size: var(--sken-size-md);
67
+ font-size: var(--sken-size-xs, 0.75rem);
68
68
  line-height: 1.25;
69
69
  }
70
70
 
@@ -91,15 +91,18 @@ var s = class extends t {
91
91
  inline-size: 2.5rem;
92
92
  block-size: 1.375rem;
93
93
  padding: 2px;
94
+ border: 1px solid transparent;
94
95
  border-radius: 999px;
95
96
  background: var(--sken-border);
96
- transition: background-color 160ms ease;
97
+ transition:
98
+ background-color 160ms ease,
99
+ border-color 160ms ease;
97
100
  }
98
101
 
99
102
  .thumb {
100
103
  display: block;
101
- inline-size: calc(1.375rem - 4px);
102
- block-size: calc(1.375rem - 4px);
104
+ inline-size: calc(1.375rem - 6px);
105
+ block-size: calc(1.375rem - 6px);
103
106
  border-radius: 50%;
104
107
  background: var(--sken-input);
105
108
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
@@ -122,9 +125,22 @@ var s = class extends t {
122
125
  outline: none;
123
126
  }
124
127
 
128
+ :host([invalid]) .track {
129
+ border-color: var(--sken-destructive);
130
+ }
131
+
125
132
  .label {
126
133
  user-select: none;
127
134
  }
135
+
136
+ :host([required]) .label::after {
137
+ content: ' *';
138
+ }
139
+
140
+ :host([required][invalid]) .label,
141
+ :host([required][invalid]) .label::after {
142
+ color: var(--sken-destructive);
143
+ }
128
144
  `;
129
145
  }
130
146
  connectedCallback() {
@@ -147,6 +163,7 @@ var s = class extends t {
147
163
  ?disabled=${this.disabled}
148
164
  ?readonly=${this.readonly}
149
165
  ?required=${this.required}
166
+ aria-invalid=${this.invalid ? "true" : "false"}
150
167
  aria-describedby=${this.describedById ?? ""}
151
168
  aria-label=${this.ariaLabel ?? ""}
152
169
  name=${this.name ?? ""}
@@ -189,7 +206,10 @@ e([a({
189
206
  })], s.prototype, "readonly", void 0), e([a({
190
207
  reflect: !0,
191
208
  type: Boolean
192
- })], s.prototype, "required", void 0), e([a({ attribute: "described-by-id" })], s.prototype, "describedById", void 0), e([a()], s.prototype, "name", void 0), e([a({ attribute: "aria-label" })], s.prototype, "ariaLabel", void 0), e([o()], s.prototype, "_uncontrolled", void 0), s = e([i("sken-switch")], s);
209
+ })], s.prototype, "required", void 0), e([a({
210
+ reflect: !0,
211
+ type: Boolean
212
+ })], s.prototype, "invalid", void 0), e([a({ attribute: "described-by-id" })], s.prototype, "describedById", void 0), e([a()], s.prototype, "name", void 0), e([a({ attribute: "aria-label" })], s.prototype, "ariaLabel", void 0), e([o()], s.prototype, "_uncontrolled", void 0), s = e([i("sken-switch")], s);
193
213
  //#endregion
194
214
  export { s as SkenSwitch };
195
215