pendentive 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +88 -0
  3. package/dist/components/avatar/Avatar.d.ts +26 -0
  4. package/dist/components/badge/Badge.d.ts +25 -0
  5. package/dist/components/button/Button.d.ts +51 -0
  6. package/dist/components/button/ButtonGroup.d.ts +36 -0
  7. package/dist/components/checkbox/Checkbox.d.ts +34 -0
  8. package/dist/components/command/Command.d.ts +59 -0
  9. package/dist/components/data/List.d.ts +40 -0
  10. package/dist/components/data/Table.d.ts +37 -0
  11. package/dist/components/data/Tree.d.ts +36 -0
  12. package/dist/components/feedback/Alert.d.ts +29 -0
  13. package/dist/components/feedback/ProgressBar.d.ts +32 -0
  14. package/dist/components/feedback/Skeleton.d.ts +20 -0
  15. package/dist/components/feedback/Spinner.d.ts +20 -0
  16. package/dist/components/feedback/Toast.d.ts +30 -0
  17. package/dist/components/field/ColorField.d.ts +33 -0
  18. package/dist/components/field/NumberField.d.ts +42 -0
  19. package/dist/components/field/TextArea.d.ts +35 -0
  20. package/dist/components/field/TextField.d.ts +39 -0
  21. package/dist/components/field/shared.d.ts +9 -0
  22. package/dist/components/field/textInput.css.d.ts +5 -0
  23. package/dist/components/hovercard/HoverCard.d.ts +35 -0
  24. package/dist/components/input/ChipInput.d.ts +31 -0
  25. package/dist/components/input/DatePicker.d.ts +66 -0
  26. package/dist/components/input/FileDropzone.d.ts +26 -0
  27. package/dist/components/layout/Card.d.ts +24 -0
  28. package/dist/components/layout/Section.d.ts +33 -0
  29. package/dist/components/layout/Separator.d.ts +18 -0
  30. package/dist/components/misc/ModeCard.d.ts +35 -0
  31. package/dist/components/navigation/Accordion.d.ts +42 -0
  32. package/dist/components/navigation/Breadcrumb.d.ts +25 -0
  33. package/dist/components/navigation/Pagination.d.ts +30 -0
  34. package/dist/components/navigation/Tabs.d.ts +38 -0
  35. package/dist/components/overlay/AlertDialog.d.ts +37 -0
  36. package/dist/components/overlay/ContextMenu.d.ts +19 -0
  37. package/dist/components/overlay/Dialog.d.ts +38 -0
  38. package/dist/components/overlay/Drawer.d.ts +40 -0
  39. package/dist/components/overlay/DropdownMenu.d.ts +55 -0
  40. package/dist/components/overlay/Popover.d.ts +33 -0
  41. package/dist/components/radio/RadioGroup.d.ts +37 -0
  42. package/dist/components/select/Combobox.d.ts +42 -0
  43. package/dist/components/select/Select.d.ts +48 -0
  44. package/dist/components/slider/RangeSlider.d.ts +43 -0
  45. package/dist/components/slider/Slider.d.ts +41 -0
  46. package/dist/components/switch/Switch.d.ts +35 -0
  47. package/dist/components/toggle/Toggle.d.ts +39 -0
  48. package/dist/components/toggle/ToggleGroup.d.ts +47 -0
  49. package/dist/components/tooltip/Tooltip.d.ts +22 -0
  50. package/dist/core/classNames.d.ts +6 -0
  51. package/dist/core/content.d.ts +2 -0
  52. package/dist/core/controller.d.ts +34 -0
  53. package/dist/core/dom.d.ts +14 -0
  54. package/dist/core/overlay.d.ts +14 -0
  55. package/dist/core/styleInjector.d.ts +12 -0
  56. package/dist/core/theme.d.ts +18 -0
  57. package/dist/core/types.d.ts +10 -0
  58. package/dist/index.cjs +4054 -0
  59. package/dist/index.cjs.map +1 -0
  60. package/dist/index.d.ts +62 -0
  61. package/dist/index.js +3934 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/linteau.global.js +397 -0
  64. package/dist/linteau.global.js.map +7 -0
  65. package/dist/styles/tokens.d.ts +33 -0
  66. package/dist/svg/Icon.d.ts +19 -0
  67. package/dist/svg/create-icon.d.ts +13 -0
  68. package/dist/svg/icons/alertTriangle.d.ts +1 -0
  69. package/dist/svg/icons/bell.d.ts +1 -0
  70. package/dist/svg/icons/calendar.d.ts +1 -0
  71. package/dist/svg/icons/check.d.ts +1 -0
  72. package/dist/svg/icons/chevronDown.d.ts +1 -0
  73. package/dist/svg/icons/chevronLeft.d.ts +1 -0
  74. package/dist/svg/icons/chevronRight.d.ts +1 -0
  75. package/dist/svg/icons/chevronUp.d.ts +1 -0
  76. package/dist/svg/icons/circle.d.ts +1 -0
  77. package/dist/svg/icons/copy.d.ts +1 -0
  78. package/dist/svg/icons/download.d.ts +1 -0
  79. package/dist/svg/icons/edit.d.ts +1 -0
  80. package/dist/svg/icons/externalLink.d.ts +1 -0
  81. package/dist/svg/icons/eye.d.ts +1 -0
  82. package/dist/svg/icons/eyeOff.d.ts +1 -0
  83. package/dist/svg/icons/filter.d.ts +1 -0
  84. package/dist/svg/icons/heart.d.ts +1 -0
  85. package/dist/svg/icons/home.d.ts +1 -0
  86. package/dist/svg/icons/index.d.ts +54 -0
  87. package/dist/svg/icons/info.d.ts +1 -0
  88. package/dist/svg/icons/link.d.ts +1 -0
  89. package/dist/svg/icons/loader.d.ts +1 -0
  90. package/dist/svg/icons/lock.d.ts +1 -0
  91. package/dist/svg/icons/mail.d.ts +1 -0
  92. package/dist/svg/icons/menu.d.ts +1 -0
  93. package/dist/svg/icons/minus.d.ts +1 -0
  94. package/dist/svg/icons/moon.d.ts +1 -0
  95. package/dist/svg/icons/moreHorizontal.d.ts +1 -0
  96. package/dist/svg/icons/phone.d.ts +1 -0
  97. package/dist/svg/icons/plus.d.ts +1 -0
  98. package/dist/svg/icons/refreshCw.d.ts +1 -0
  99. package/dist/svg/icons/search.d.ts +1 -0
  100. package/dist/svg/icons/settings.d.ts +1 -0
  101. package/dist/svg/icons/star.d.ts +1 -0
  102. package/dist/svg/icons/sun.d.ts +1 -0
  103. package/dist/svg/icons/trash.d.ts +1 -0
  104. package/dist/svg/icons/upload.d.ts +1 -0
  105. package/dist/svg/icons/user.d.ts +1 -0
  106. package/dist/svg/icons/x.d.ts +1 -0
  107. package/dist/svg/index.cjs +380 -0
  108. package/dist/svg/index.cjs.map +1 -0
  109. package/dist/svg/index.d.ts +4 -0
  110. package/dist/svg/index.js +352 -0
  111. package/dist/svg/index.js.map +1 -0
  112. package/dist/svg/types.d.ts +26 -0
  113. package/package.json +81 -0
package/dist/index.js ADDED
@@ -0,0 +1,3934 @@
1
+ // src/core/dom.ts
2
+ function isBrowser() {
3
+ return typeof document !== "undefined" && typeof window !== "undefined";
4
+ }
5
+ function assertDom(componentName) {
6
+ if (!isBrowser()) throw new Error(`[linteau] "${componentName}" requires a DOM environment (a browser, or Node/Bun with a DOM shim such as 'happy-dom' or 'linkedom'). No 'document' was found.`);
7
+ }
8
+ function el(tag, className) {
9
+ const node = document.createElement(tag);
10
+ if (className) node.className = className;
11
+ return node;
12
+ }
13
+
14
+ // src/core/controller.ts
15
+ function createEmitter() {
16
+ const listeners = /* @__PURE__ */ new Map();
17
+ return {
18
+ on(event, listener) {
19
+ if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
20
+ listeners.get(event).add(listener);
21
+ return () => listeners.get(event)?.delete(listener);
22
+ },
23
+ off(event, listener) {
24
+ listeners.get(event)?.delete(listener);
25
+ },
26
+ emit(event, payload) {
27
+ listeners.get(event)?.forEach((listener) => listener(payload));
28
+ },
29
+ clear() {
30
+ listeners.clear();
31
+ }
32
+ };
33
+ }
34
+ function attachController(element, api) {
35
+ return Object.assign(element, api);
36
+ }
37
+
38
+ // src/styles/tokens.ts
39
+ var defaultTheme = {
40
+ background: "oklch(0.09 0 0)",
41
+ foreground: "oklch(0.94 0 0)",
42
+ card: "oklch(0.12 0 0)",
43
+ cardForeground: "oklch(0.94 0 0)",
44
+ primary: "oklch(0.92 0 0)",
45
+ primaryForeground: "oklch(0.1 0 0)",
46
+ secondary: "oklch(0.18 0 0)",
47
+ secondaryForeground: "oklch(0.9 0 0)",
48
+ muted: "oklch(0.16 0 0)",
49
+ mutedForeground: "oklch(0.6 0 0)",
50
+ accent: "oklch(0.2 0 0)",
51
+ accentForeground: "oklch(0.94 0 0)",
52
+ destructive: "oklch(0.58 0.22 27)",
53
+ destructiveForeground: "oklch(0.97 0 0)",
54
+ success: "oklch(0.62 0.16 145)",
55
+ successForeground: "oklch(0.97 0 0)",
56
+ warning: "oklch(0.75 0.15 85)",
57
+ warningForeground: "oklch(0.15 0 0)",
58
+ border: "oklch(0.22 0 0)",
59
+ input: "oklch(0.22 0 0)",
60
+ ring: "oklch(0.7 0 0)",
61
+ radius: "0.625rem",
62
+ radiusSm: "calc(0.625rem - 4px)",
63
+ radiusMd: "calc(0.625rem - 2px)",
64
+ radiusLg: "0.625rem",
65
+ radiusXl: "calc(0.625rem + 4px)"
66
+ };
67
+ function buildTokensCss(tokens) {
68
+ return `:root {
69
+ --linteau-background: ${tokens.background};
70
+ --linteau-foreground: ${tokens.foreground};
71
+ --linteau-card: ${tokens.card};
72
+ --linteau-card-foreground: ${tokens.cardForeground};
73
+ --linteau-primary: ${tokens.primary};
74
+ --linteau-primary-foreground: ${tokens.primaryForeground};
75
+ --linteau-secondary: ${tokens.secondary};
76
+ --linteau-secondary-foreground: ${tokens.secondaryForeground};
77
+ --linteau-muted: ${tokens.muted};
78
+ --linteau-muted-foreground: ${tokens.mutedForeground};
79
+ --linteau-accent: ${tokens.accent};
80
+ --linteau-accent-foreground: ${tokens.accentForeground};
81
+ --linteau-destructive: ${tokens.destructive};
82
+ --linteau-destructive-foreground: ${tokens.destructiveForeground};
83
+ --linteau-success: ${tokens.success};
84
+ --linteau-success-foreground: ${tokens.successForeground};
85
+ --linteau-warning: ${tokens.warning};
86
+ --linteau-warning-foreground: ${tokens.warningForeground};
87
+ --linteau-border: ${tokens.border};
88
+ --linteau-input: ${tokens.input};
89
+ --linteau-ring: ${tokens.ring};
90
+ --linteau-radius: ${tokens.radius};
91
+ --linteau-radius-sm: ${tokens.radiusSm};
92
+ --linteau-radius-md: ${tokens.radiusMd};
93
+ --linteau-radius-lg: ${tokens.radiusLg};
94
+ --linteau-radius-xl: ${tokens.radiusXl};
95
+ }`;
96
+ }
97
+
98
+ // src/core/theme.ts
99
+ var TOKENS_STYLE_ID = "linteau-tokens";
100
+ var currentTheme = { ...defaultTheme };
101
+ var emitter = createEmitter();
102
+ function applyThemeToDom() {
103
+ if (!isBrowser()) return;
104
+ let style = document.getElementById(TOKENS_STYLE_ID);
105
+ if (!style) {
106
+ style = document.createElement("style");
107
+ style.id = TOKENS_STYLE_ID;
108
+ document.head.appendChild(style);
109
+ }
110
+ style.textContent = buildTokensCss(currentTheme);
111
+ }
112
+ function getTheme() {
113
+ return currentTheme;
114
+ }
115
+ function setTheme(overrides) {
116
+ currentTheme = { ...currentTheme, ...overrides };
117
+ applyThemeToDom();
118
+ emitter.emit("change", currentTheme);
119
+ }
120
+ function resetTheme() {
121
+ currentTheme = { ...defaultTheme };
122
+ applyThemeToDom();
123
+ emitter.emit("change", currentTheme);
124
+ }
125
+ function onThemeChange(listener) {
126
+ return emitter.on("change", listener);
127
+ }
128
+ applyThemeToDom();
129
+
130
+ // src/svg/Icon.ts
131
+ var SVGNS = "http://www.w3.org/2000/svg";
132
+ function baseAttrs(props) {
133
+ const size = String(props.size ?? 16);
134
+ return {
135
+ xmlns: SVGNS,
136
+ width: size,
137
+ height: size,
138
+ viewBox: "0 0 24 24",
139
+ fill: "none",
140
+ stroke: props.color ?? "currentColor",
141
+ "stroke-width": String(props.strokeWidth ?? 2),
142
+ "stroke-linecap": "round",
143
+ "stroke-linejoin": "round"
144
+ };
145
+ }
146
+ function renderIconShape(shape, props) {
147
+ const svg = document.createElementNS(SVGNS, "svg");
148
+ const attrs = baseAttrs(props);
149
+ for (const key in attrs) svg.setAttribute(key, attrs[key]);
150
+ if (props.className) svg.setAttribute("class", props.className);
151
+ for (const [tag, childAttrs] of shape) {
152
+ const node = document.createElementNS(SVGNS, tag);
153
+ for (const key in childAttrs) node.setAttribute(key, childAttrs[key]);
154
+ svg.appendChild(node);
155
+ }
156
+ return svg;
157
+ }
158
+ function fromElement(source, props) {
159
+ const svg = source.cloneNode(true);
160
+ const size = String(props.size ?? 16);
161
+ svg.setAttribute("width", size);
162
+ svg.setAttribute("height", size);
163
+ if (props.color) svg.setAttribute("stroke", props.color);
164
+ if (props.strokeWidth != null) svg.setAttribute("stroke-width", String(props.strokeWidth));
165
+ if (props.className) svg.classList.add(...props.className.split(" ").filter(Boolean));
166
+ return svg;
167
+ }
168
+ function fromMarkup(markup, props) {
169
+ const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
170
+ return fromElement(doc.documentElement, props);
171
+ }
172
+ function Icon(input, size, props = {}) {
173
+ if (input === null || input === void 0) return null;
174
+ const resolved = { ...size !== void 0 ? { size } : {}, ...props };
175
+ if (typeof input === "function") return input(resolved);
176
+ if (typeof input === "string") return fromMarkup(input, resolved);
177
+ if (input instanceof SVGElement) return fromElement(input, resolved);
178
+ if (Array.isArray(input)) return renderIconShape(input, resolved);
179
+ return null;
180
+ }
181
+
182
+ // src/svg/create-icon.ts
183
+ function createIcon(name, shape) {
184
+ return function renderIcon(props = {}) {
185
+ const svg = renderIconShape(shape, props);
186
+ svg.setAttribute("data-icon", name);
187
+ return svg;
188
+ };
189
+ }
190
+
191
+ // src/svg/icons/alertTriangle.ts
192
+ var alertTriangle = createIcon("alert-triangle", [
193
+ ["path", { 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" }],
194
+ ["line", { x1: "12", y1: "9", x2: "12", y2: "13" }],
195
+ ["line", { x1: "12", y1: "17", x2: "12.01", y2: "17" }]
196
+ ]);
197
+
198
+ // src/svg/icons/bell.ts
199
+ var bell = createIcon("bell", [
200
+ ["path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }],
201
+ ["path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" }]
202
+ ]);
203
+
204
+ // src/svg/icons/calendar.ts
205
+ var calendar = createIcon("calendar", [
206
+ ["rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }],
207
+ ["line", { x1: "16", y1: "2", x2: "16", y2: "6" }],
208
+ ["line", { x1: "8", y1: "2", x2: "8", y2: "6" }],
209
+ ["line", { x1: "3", y1: "10", x2: "21", y2: "10" }]
210
+ ]);
211
+
212
+ // src/svg/icons/check.ts
213
+ var check = createIcon("check", [
214
+ ["polyline", { points: "20 6 9 17 4 12" }]
215
+ ]);
216
+
217
+ // src/svg/icons/chevronDown.ts
218
+ var chevronDown = createIcon("chevron-down", [
219
+ ["polyline", { points: "6 9 12 15 18 9" }]
220
+ ]);
221
+
222
+ // src/svg/icons/chevronLeft.ts
223
+ var chevronLeft = createIcon("chevron-left", [
224
+ ["polyline", { points: "15 18 9 12 15 6" }]
225
+ ]);
226
+
227
+ // src/svg/icons/chevronRight.ts
228
+ var chevronRight = createIcon("chevron-right", [
229
+ ["polyline", { points: "9 18 15 12 9 6" }]
230
+ ]);
231
+
232
+ // src/svg/icons/chevronUp.ts
233
+ var chevronUp = createIcon("chevron-up", [
234
+ ["polyline", { points: "18 15 12 9 6 15" }]
235
+ ]);
236
+
237
+ // src/svg/icons/circle.ts
238
+ var circle = createIcon("circle", [
239
+ ["circle", { cx: "12", cy: "12", r: "10" }]
240
+ ]);
241
+
242
+ // src/svg/icons/copy.ts
243
+ var copy = createIcon("copy", [
244
+ ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }],
245
+ ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }]
246
+ ]);
247
+
248
+ // src/svg/icons/download.ts
249
+ var download = createIcon("download", [
250
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }],
251
+ ["polyline", { points: "7 10 12 15 17 10" }],
252
+ ["line", { x1: "12", y1: "15", x2: "12", y2: "3" }]
253
+ ]);
254
+
255
+ // src/svg/icons/edit.ts
256
+ var edit = createIcon("edit", [
257
+ ["path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }]
258
+ ]);
259
+
260
+ // src/svg/icons/externalLink.ts
261
+ var externalLink = createIcon("external-link", [
262
+ ["path", { d: "M15 3h6v6" }],
263
+ ["path", { d: "M10 14 21 3" }],
264
+ ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }]
265
+ ]);
266
+
267
+ // src/svg/icons/eye.ts
268
+ var eye = createIcon("eye", [
269
+ ["path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }],
270
+ ["circle", { cx: "12", cy: "12", r: "3" }]
271
+ ]);
272
+
273
+ // src/svg/icons/eyeOff.ts
274
+ var eyeOff = createIcon("eye-off", [
275
+ ["path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }],
276
+ ["path", { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }],
277
+ ["path", { d: "M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" }],
278
+ ["line", { x1: "2", y1: "2", x2: "22", y2: "22" }]
279
+ ]);
280
+
281
+ // src/svg/icons/filter.ts
282
+ var filter = createIcon("filter", [
283
+ ["polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" }]
284
+ ]);
285
+
286
+ // src/svg/icons/heart.ts
287
+ var heart = createIcon("heart", [
288
+ ["path", { d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" }]
289
+ ]);
290
+
291
+ // src/svg/icons/home.ts
292
+ var home = createIcon("home", [
293
+ ["path", { d: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }],
294
+ ["polyline", { points: "9 22 9 12 15 12 15 22" }]
295
+ ]);
296
+
297
+ // src/svg/icons/info.ts
298
+ var info = createIcon("info", [
299
+ ["circle", { cx: "12", cy: "12", r: "10" }],
300
+ ["line", { x1: "12", y1: "16", x2: "12", y2: "12" }],
301
+ ["line", { x1: "12", y1: "8", x2: "12.01", y2: "8" }]
302
+ ]);
303
+
304
+ // src/svg/icons/link.ts
305
+ var link = createIcon("link", [
306
+ ["path", { d: "M9 17H7A5 5 0 0 1 7 7h2" }],
307
+ ["path", { d: "M15 7h2a5 5 0 1 1 0 10h-2" }],
308
+ ["line", { x1: "8", y1: "12", x2: "16", y2: "12" }]
309
+ ]);
310
+
311
+ // src/svg/icons/loader.ts
312
+ var loader = createIcon("loader", [
313
+ ["line", { x1: "12", y1: "2", x2: "12", y2: "6" }],
314
+ ["line", { x1: "12", y1: "18", x2: "12", y2: "22" }],
315
+ ["line", { x1: "4.9", y1: "4.9", x2: "7.8", y2: "7.8" }],
316
+ ["line", { x1: "16.2", y1: "16.2", x2: "19.1", y2: "19.1" }],
317
+ ["line", { x1: "2", y1: "12", x2: "6", y2: "12" }],
318
+ ["line", { x1: "18", y1: "12", x2: "22", y2: "12" }],
319
+ ["line", { x1: "4.9", y1: "19.1", x2: "7.8", y2: "16.2" }],
320
+ ["line", { x1: "16.2", y1: "7.8", x2: "19.1", y2: "4.9" }]
321
+ ]);
322
+
323
+ // src/svg/icons/lock.ts
324
+ var lock = createIcon("lock", [
325
+ ["rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }],
326
+ ["path", { d: "M7 11V7a5 5 0 0 1 10 0v4" }]
327
+ ]);
328
+
329
+ // src/svg/icons/mail.ts
330
+ var mail = createIcon("mail", [
331
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }],
332
+ ["path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }]
333
+ ]);
334
+
335
+ // src/svg/icons/menu.ts
336
+ var menu = createIcon("menu", [
337
+ ["line", { x1: "4", y1: "12", x2: "20", y2: "12" }],
338
+ ["line", { x1: "4", y1: "6", x2: "20", y2: "6" }],
339
+ ["line", { x1: "4", y1: "18", x2: "20", y2: "18" }]
340
+ ]);
341
+
342
+ // src/svg/icons/minus.ts
343
+ var minus = createIcon("minus", [
344
+ ["line", { x1: "5", y1: "12", x2: "19", y2: "12" }]
345
+ ]);
346
+
347
+ // src/svg/icons/moon.ts
348
+ var moon = createIcon("moon", [
349
+ ["path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }]
350
+ ]);
351
+
352
+ // src/svg/icons/moreHorizontal.ts
353
+ var moreHorizontal = createIcon("more-horizontal", [
354
+ ["circle", { cx: "5", cy: "12", r: "1" }],
355
+ ["circle", { cx: "12", cy: "12", r: "1" }],
356
+ ["circle", { cx: "19", cy: "12", r: "1" }]
357
+ ]);
358
+
359
+ // src/svg/icons/phone.ts
360
+ var phone = createIcon("phone", [
361
+ ["path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z" }]
362
+ ]);
363
+
364
+ // src/svg/icons/plus.ts
365
+ var plus = createIcon("plus", [
366
+ ["line", { x1: "12", y1: "5", x2: "12", y2: "19" }],
367
+ ["line", { x1: "5", y1: "12", x2: "19", y2: "12" }]
368
+ ]);
369
+
370
+ // src/svg/icons/refreshCw.ts
371
+ var refreshCw = createIcon("refresh-cw", [
372
+ ["path", { d: "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }],
373
+ ["path", { d: "M3 3v5h5" }],
374
+ ["path", { d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" }],
375
+ ["path", { d: "M16 16h5v5" }]
376
+ ]);
377
+
378
+ // src/svg/icons/search.ts
379
+ var search = createIcon("search", [
380
+ ["circle", { cx: "11", cy: "11", r: "8" }],
381
+ ["line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" }]
382
+ ]);
383
+
384
+ // src/svg/icons/settings.ts
385
+ var settings = createIcon("settings", [
386
+ ["path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }],
387
+ ["circle", { cx: "12", cy: "12", r: "3" }]
388
+ ]);
389
+
390
+ // src/svg/icons/star.ts
391
+ var star = createIcon("star", [
392
+ ["polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }]
393
+ ]);
394
+
395
+ // src/svg/icons/sun.ts
396
+ var sun = createIcon("sun", [
397
+ ["circle", { cx: "12", cy: "12", r: "4" }],
398
+ ["line", { x1: "12", y1: "2", x2: "12", y2: "4" }],
399
+ ["line", { x1: "12", y1: "20", x2: "12", y2: "22" }],
400
+ ["line", { x1: "4.93", y1: "4.93", x2: "6.34", y2: "6.34" }],
401
+ ["line", { x1: "17.66", y1: "17.66", x2: "19.07", y2: "19.07" }],
402
+ ["line", { x1: "2", y1: "12", x2: "4", y2: "12" }],
403
+ ["line", { x1: "20", y1: "12", x2: "22", y2: "12" }],
404
+ ["line", { x1: "6.34", y1: "17.66", x2: "4.93", y2: "19.07" }],
405
+ ["line", { x1: "19.07", y1: "4.93", x2: "17.66", y2: "6.34" }]
406
+ ]);
407
+
408
+ // src/svg/icons/trash.ts
409
+ var trash = createIcon("trash", [
410
+ ["path", { d: "M3 6h18" }],
411
+ ["path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }],
412
+ ["path", { d: "M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" }],
413
+ ["line", { x1: "10", y1: "11", x2: "10", y2: "17" }],
414
+ ["line", { x1: "14", y1: "11", x2: "14", y2: "17" }]
415
+ ]);
416
+
417
+ // src/svg/icons/upload.ts
418
+ var upload = createIcon("upload", [
419
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }],
420
+ ["polyline", { points: "17 8 12 3 7 8" }],
421
+ ["line", { x1: "12", y1: "3", x2: "12", y2: "15" }]
422
+ ]);
423
+
424
+ // src/svg/icons/user.ts
425
+ var user = createIcon("user", [
426
+ ["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }],
427
+ ["circle", { cx: "12", cy: "7", r: "4" }]
428
+ ]);
429
+
430
+ // src/svg/icons/x.ts
431
+ var x = createIcon("x", [
432
+ ["line", { x1: "18", y1: "6", x2: "6", y2: "18" }],
433
+ ["line", { x1: "6", y1: "6", x2: "18", y2: "18" }]
434
+ ]);
435
+
436
+ // src/svg/icons/index.ts
437
+ var icons = {
438
+ alertTriangle,
439
+ bell,
440
+ calendar,
441
+ check,
442
+ chevronDown,
443
+ chevronLeft,
444
+ chevronRight,
445
+ chevronUp,
446
+ circle,
447
+ copy,
448
+ download,
449
+ edit,
450
+ externalLink,
451
+ eye,
452
+ eyeOff,
453
+ filter,
454
+ heart,
455
+ home,
456
+ info,
457
+ link,
458
+ loader,
459
+ lock,
460
+ mail,
461
+ menu,
462
+ minus,
463
+ moon,
464
+ moreHorizontal,
465
+ phone,
466
+ plus,
467
+ refreshCw,
468
+ search,
469
+ settings,
470
+ star,
471
+ sun,
472
+ trash,
473
+ upload,
474
+ user,
475
+ x
476
+ };
477
+
478
+ // src/core/classNames.ts
479
+ var PREFIX = "linteau-";
480
+ function px(part) {
481
+ return `${PREFIX}${part}`;
482
+ }
483
+ function cx(...parts) {
484
+ return parts.filter(Boolean).join(" ");
485
+ }
486
+
487
+ // src/core/styleInjector.ts
488
+ var STYLE_ID = "linteau-styles";
489
+ var injectedKeys = /* @__PURE__ */ new Set();
490
+ var baseCss = `
491
+ .linteau-hidden { display: none !important }
492
+ .linteau-icon { color: var(--linteau-muted-foreground); flex-shrink: 0 }
493
+ `;
494
+ function getStyleSheet() {
495
+ if (!isBrowser) return null;
496
+ let sheet = document.getElementById(STYLE_ID);
497
+ if (!sheet) {
498
+ sheet = document.createElement("style");
499
+ sheet.id = STYLE_ID;
500
+ document.head.appendChild(sheet);
501
+ }
502
+ return sheet;
503
+ }
504
+ function ensureComponentStyles(key, css) {
505
+ const sheet = getStyleSheet();
506
+ if (!sheet) return;
507
+ if (!injectedKeys.has("base")) {
508
+ sheet.appendChild(document.createTextNode(baseCss));
509
+ injectedKeys.add("base");
510
+ }
511
+ if (injectedKeys.has(key)) return;
512
+ sheet.appendChild(document.createTextNode(css));
513
+ injectedKeys.add(key);
514
+ }
515
+
516
+ // src/components/avatar/Avatar.ts
517
+ var avatarCss = `
518
+ .linteau-avatar { display: inline-flex; align-items: center; justify-content: center; background: var(--linteau-secondary); color: var(--linteau-secondary-foreground); font-size: 12px; font-weight: 600; overflow: hidden; flex-shrink: 0 }
519
+ .linteau-avatar-circle { border-radius: 50% }
520
+ .linteau-avatar-square { border-radius: var(--linteau-radius-sm) }
521
+ .linteau-avatar img { width: 100%; height: 100%; object-fit: cover }
522
+ `;
523
+ function Avatar(options = {}) {
524
+ assertDom("Avatar");
525
+ ensureComponentStyles("avatar", avatarCss);
526
+ const size = options.size ?? 36;
527
+ const shape = options.shape ?? "circle";
528
+ const root = el("div", cx(px("avatar"), px(`avatar-${shape}`)));
529
+ root.style.width = `${size}px`;
530
+ root.style.height = `${size}px`;
531
+ function render(src) {
532
+ root.replaceChildren();
533
+ if (src) {
534
+ const img = document.createElement("img");
535
+ img.src = src;
536
+ img.alt = options.alt ?? "";
537
+ img.addEventListener("error", () => render(void 0), { once: true });
538
+ root.appendChild(img);
539
+ } else {
540
+ const fallback = document.createElement("span");
541
+ fallback.textContent = options.initials ?? "";
542
+ root.appendChild(fallback);
543
+ }
544
+ }
545
+ render(options.src);
546
+ const api = {
547
+ setSrc(src) {
548
+ render(src);
549
+ },
550
+ destroy() {
551
+ root.remove();
552
+ }
553
+ };
554
+ return attachController(root, api);
555
+ }
556
+
557
+ // src/components/badge/Badge.ts
558
+ var badgeCss = `
559
+ .linteau-badge {
560
+ display: inline-flex;
561
+ align-items: center;
562
+ gap: 4px;
563
+ padding: 2px 8px;
564
+ border-radius: 999px;
565
+ font-size: 11px;
566
+ font-weight: 600;
567
+ line-height: 1.6;
568
+ border: 1px solid transparent;
569
+ }
570
+ .linteau-badge-default { background: var(--linteau-secondary); color: var(--linteau-secondary-foreground) }
571
+ .linteau-badge-outline { background: transparent; border-color: var(--linteau-border); color: var(--linteau-foreground) }
572
+ .linteau-badge-success { background: var(--linteau-success); color: var(--linteau-success-foreground) }
573
+ .linteau-badge-warning { background: var(--linteau-warning); color: var(--linteau-warning-foreground) }
574
+ .linteau-badge-destructive { background: var(--linteau-destructive); color: var(--linteau-destructive-foreground) }
575
+ `;
576
+ function Badge(text, options = {}) {
577
+ assertDom("Badge");
578
+ ensureComponentStyles("badge", badgeCss);
579
+ let variant = options.variant ?? "default";
580
+ const span = el("span", cx(px("badge"), px(`badge-${variant}`)));
581
+ const label = document.createElement("span");
582
+ label.textContent = text;
583
+ if (options.icon) span.appendChild(Icon(options.icon, 11, { className: px("icon") }));
584
+ span.appendChild(label);
585
+ const api = {
586
+ setLabel(newText) {
587
+ label.textContent = newText;
588
+ },
589
+ setVariant(newVariant) {
590
+ span.classList.remove(px(`badge-${variant}`));
591
+ variant = newVariant;
592
+ span.classList.add(px(`badge-${variant}`));
593
+ },
594
+ destroy() {
595
+ span.remove();
596
+ }
597
+ };
598
+ return attachController(span, api);
599
+ }
600
+
601
+ // src/components/button/Button.ts
602
+ var buttonCss = `
603
+ .linteau-button {
604
+ display: inline-flex;
605
+ align-items: center;
606
+ justify-content: center;
607
+ gap: 6px;
608
+ border-radius: var(--linteau-radius-sm);
609
+ border: 1px solid transparent;
610
+ font-size: 13px;
611
+ font-weight: 500;
612
+ cursor: pointer;
613
+ font-family: inherit;
614
+ transition: background 120ms ease, border-color 120ms ease, transform 120ms ease, opacity 120ms ease;
615
+ }
616
+ .linteau-button-default { padding: 10px 14px }
617
+ .linteau-button-sm { padding: 6px 10px; font-size: 12px }
618
+ .linteau-button-full { width: 100% }
619
+ .linteau-button:active { transform: scale(0.97) }
620
+ .linteau-button:disabled { opacity: 0.5; cursor: not-allowed; transform: none }
621
+ .linteau-button-default:not(.linteau-button-outline):not(.linteau-button-ghost):not(.linteau-button-destructive) {
622
+ background: var(--linteau-primary);
623
+ color: var(--linteau-primary-foreground);
624
+ }
625
+ .linteau-button-default:not(.linteau-button-outline):not(.linteau-button-ghost):not(.linteau-button-destructive):hover:not(:disabled) {
626
+ filter: brightness(0.92);
627
+ }
628
+ .linteau-button-outline { background: transparent; border-color: var(--linteau-border); color: var(--linteau-foreground) }
629
+ .linteau-button-outline:hover:not(:disabled) { background: var(--linteau-accent) }
630
+ .linteau-button-ghost { background: transparent; color: var(--linteau-muted-foreground) }
631
+ .linteau-button-ghost:hover:not(:disabled) { background: var(--linteau-accent); color: var(--linteau-foreground) }
632
+ .linteau-button-destructive { background: var(--linteau-destructive); color: var(--linteau-destructive-foreground) }
633
+ .linteau-button-destructive:hover:not(:disabled) { filter: brightness(0.92) }
634
+ `;
635
+ function Button(text, options = {}) {
636
+ assertDom("Button");
637
+ ensureComponentStyles("button", buttonCss);
638
+ let { variant = "default", size = "default" } = options;
639
+ let { icon, iconPosition = "start", disabled = false } = options;
640
+ let onClickHandler = options.onClick;
641
+ const button = el("button", cx(px("button"), px(`button-${variant}`), px(`button-${size}`), options.fullWidth && px("button-full")));
642
+ button.type = "button";
643
+ button.disabled = disabled;
644
+ const label = document.createElement("span");
645
+ label.className = px("button-label");
646
+ label.textContent = text;
647
+ function render() {
648
+ button.replaceChildren();
649
+ const iconEl = icon ? Icon(icon, 14, { className: px("icon") }) : null;
650
+ const nodes = [];
651
+ if (iconEl && iconPosition === "start") nodes.push(iconEl);
652
+ nodes.push(label);
653
+ if (iconEl && iconPosition === "end") nodes.push(iconEl);
654
+ button.append(...nodes);
655
+ }
656
+ render();
657
+ const listener = (event) => {
658
+ if (!disabled) onClickHandler?.(event);
659
+ };
660
+ button.addEventListener("click", listener);
661
+ const api = {
662
+ setLabel(newText) {
663
+ label.textContent = newText;
664
+ },
665
+ setVariant(newVariant) {
666
+ button.classList.remove(px(`button-${variant}`));
667
+ variant = newVariant;
668
+ button.classList.add(px(`button-${variant}`));
669
+ },
670
+ setSize(newSize) {
671
+ button.classList.remove(px(`button-${size}`));
672
+ size = newSize;
673
+ button.classList.add(px(`button-${size}`));
674
+ },
675
+ setIcon(newIcon, position) {
676
+ icon = newIcon;
677
+ if (position) iconPosition = position;
678
+ render();
679
+ },
680
+ setDisabled(value) {
681
+ disabled = value;
682
+ button.disabled = value;
683
+ },
684
+ setOnClick(handler) {
685
+ onClickHandler = handler;
686
+ },
687
+ destroy() {
688
+ button.removeEventListener("click", listener);
689
+ button.remove();
690
+ }
691
+ };
692
+ return attachController(button, api);
693
+ }
694
+
695
+ // src/components/button/ButtonGroup.ts
696
+ var buttonGroupCss = `
697
+ .linteau-button-group { display: inline-flex; border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-sm); overflow: hidden }
698
+ .linteau-button-group .linteau-button { border-radius: 0; border: none; border-right: 1px solid var(--linteau-border); background: transparent; color: var(--linteau-muted-foreground) }
699
+ .linteau-button-group .linteau-button:last-child { border-right: none }
700
+ .linteau-button-group .linteau-button:hover:not(:disabled) { background: var(--linteau-accent) }
701
+ .linteau-button-group .linteau-button-group-item-active { background: var(--linteau-primary); color: var(--linteau-primary-foreground) }
702
+ `;
703
+ function ButtonGroup(options) {
704
+ assertDom("ButtonGroup");
705
+ ensureComponentStyles("button-group", buttonGroupCss);
706
+ let items = options.items;
707
+ let value = options.value;
708
+ const onChange = options.onChange;
709
+ const root = el("div", px("button-group"));
710
+ root.setAttribute("role", "group");
711
+ function render() {
712
+ root.replaceChildren();
713
+ for (const item of items) {
714
+ const isActive = item.value === value;
715
+ const button = Button(item.label, {
716
+ icon: item.icon,
717
+ variant: "ghost",
718
+ onClick: () => {
719
+ value = item.value;
720
+ render();
721
+ onChange?.(item.value);
722
+ }
723
+ });
724
+ button.classList.toggle(px("button-group-item-active"), isActive);
725
+ root.appendChild(button);
726
+ }
727
+ }
728
+ render();
729
+ const api = {
730
+ getValue() {
731
+ return value;
732
+ },
733
+ setValue(newValue) {
734
+ value = newValue;
735
+ render();
736
+ },
737
+ setItems(newItems) {
738
+ items = newItems;
739
+ render();
740
+ },
741
+ destroy() {
742
+ root.remove();
743
+ }
744
+ };
745
+ return attachController(root, api);
746
+ }
747
+
748
+ // src/components/checkbox/Checkbox.ts
749
+ var checkboxCss = `
750
+ .linteau-checkbox-row { display: flex; align-items: center; gap: 8px }
751
+ .linteau-checkbox {
752
+ width: 18px;
753
+ height: 18px;
754
+ border-radius: var(--linteau-radius-sm);
755
+ border: 1px solid var(--linteau-border);
756
+ background: var(--linteau-secondary);
757
+ display: flex;
758
+ align-items: center;
759
+ justify-content: center;
760
+ cursor: pointer;
761
+ color: var(--linteau-primary-foreground);
762
+ }
763
+ .linteau-checkbox:disabled { opacity: 0.5; cursor: not-allowed }
764
+ .linteau-checkbox.linteau-checked { background: var(--linteau-primary); border-color: var(--linteau-primary) }
765
+ .linteau-checkbox svg { display: none }
766
+ .linteau-checkbox.linteau-checked svg, .linteau-checkbox.linteau-indeterminate svg { display: block }
767
+ `;
768
+ function Checkbox(options) {
769
+ assertDom("Checkbox");
770
+ ensureComponentStyles("checkbox", checkboxCss);
771
+ let checked = options.checked ?? options.defaultChecked ?? false;
772
+ let indeterminate = options.indeterminate ?? false;
773
+ let disabled = options.disabled ?? false;
774
+ const onChange = options.onChange;
775
+ const root = el("div", px("checkbox-row"));
776
+ const box = el("button", px("checkbox"));
777
+ box.type = "button";
778
+ box.setAttribute("role", "checkbox");
779
+ box.disabled = disabled;
780
+ const checkIcon = Icon(icons.check, 12);
781
+ const dashIcon = Icon(icons.minus, 12);
782
+ box.append(checkIcon, dashIcon);
783
+ const labelText = document.createElement("span");
784
+ labelText.textContent = options.label;
785
+ function render() {
786
+ box.classList.toggle(px("checked"), checked);
787
+ box.classList.toggle(px("indeterminate"), indeterminate && !checked);
788
+ box.setAttribute("aria-checked", indeterminate ? "mixed" : String(checked));
789
+ checkIcon.style.display = checked ? "block" : "none";
790
+ dashIcon.style.display = !checked && indeterminate ? "block" : "none";
791
+ }
792
+ render();
793
+ const listener = () => {
794
+ if (disabled) return;
795
+ checked = !checked;
796
+ indeterminate = false;
797
+ render();
798
+ onChange?.(checked);
799
+ };
800
+ box.addEventListener("click", listener);
801
+ root.append(box, labelText);
802
+ const api = {
803
+ getValue() {
804
+ return checked;
805
+ },
806
+ setValue(value) {
807
+ checked = value;
808
+ indeterminate = false;
809
+ render();
810
+ },
811
+ setIndeterminate(value) {
812
+ indeterminate = value;
813
+ render();
814
+ },
815
+ setDisabled(value) {
816
+ disabled = value;
817
+ box.disabled = value;
818
+ },
819
+ destroy() {
820
+ box.removeEventListener("click", listener);
821
+ root.remove();
822
+ }
823
+ };
824
+ return attachController(root, api);
825
+ }
826
+
827
+ // src/core/overlay.ts
828
+ function onClickOutside(targets, callback) {
829
+ if (!isBrowser()) return () => {
830
+ };
831
+ const list = Array.isArray(targets) ? targets : [targets];
832
+ const listener = (event) => {
833
+ const node = event.target;
834
+ if (list.some((target) => target.contains(node))) return;
835
+ callback();
836
+ };
837
+ document.addEventListener("mousedown", listener, true);
838
+ return () => document.removeEventListener("mousedown", listener, true);
839
+ }
840
+ function onEscapeKey(callback) {
841
+ if (!isBrowser()) return () => {
842
+ };
843
+ const listener = (event) => {
844
+ if (event.key === "Escape") callback();
845
+ };
846
+ document.addEventListener("keydown", listener);
847
+ return () => document.removeEventListener("keydown", listener);
848
+ }
849
+ function lockBodyScroll() {
850
+ if (!isBrowser()) return () => {
851
+ };
852
+ const previous = document.body.style.overflow;
853
+ document.body.style.overflow = "hidden";
854
+ return () => {
855
+ document.body.style.overflow = previous;
856
+ };
857
+ }
858
+ function positionFloating(anchor, floating, placement = "bottom", gap = 6) {
859
+ if (!isBrowser()) return;
860
+ const anchorRect = anchor.getBoundingClientRect();
861
+ const floatingRect = floating.getBoundingClientRect();
862
+ let top = 0;
863
+ let left = 0;
864
+ if (placement === "bottom") {
865
+ top = anchorRect.bottom + gap;
866
+ left = anchorRect.left;
867
+ } else if (placement === "top") {
868
+ top = anchorRect.top - floatingRect.height - gap;
869
+ left = anchorRect.left;
870
+ } else if (placement === "left") {
871
+ top = anchorRect.top;
872
+ left = anchorRect.left - floatingRect.width - gap;
873
+ } else {
874
+ top = anchorRect.top;
875
+ left = anchorRect.right + gap;
876
+ }
877
+ const maxLeft = window.innerWidth - floatingRect.width - 8;
878
+ const maxTop = window.innerHeight - floatingRect.height - 8;
879
+ floating.style.position = "fixed";
880
+ floating.style.top = `${Math.max(8, Math.min(top, maxTop))}px`;
881
+ floating.style.left = `${Math.max(8, Math.min(left, maxLeft))}px`;
882
+ }
883
+
884
+ // src/components/command/Command.ts
885
+ var commandCss = `
886
+ .linteau-command-overlay { position: fixed; inset: 0; background: color-mix(in oklch, black 60%, transparent); display: flex; align-items: flex-start; justify-content: center; padding-top: 15vh; z-index: 1500; backdrop-filter: blur(4px) }
887
+ .linteau-command-panel { width: min(560px, 92vw); max-height: 60vh; display: flex; flex-direction: column; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-lg); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); overflow: hidden }
888
+ .linteau-command-input-wrap { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--linteau-border) }
889
+ .linteau-command-input { flex: 1; background: transparent; border: none; outline: none; color: var(--linteau-foreground); font-size: 14px; font-family: inherit }
890
+ .linteau-command-list { overflow-y: auto; padding: 6px; flex: 1 }
891
+ .linteau-command-group-heading { padding: 6px 8px 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--linteau-muted-foreground) }
892
+ .linteau-command-item { display: flex; align-items: center; gap: 8px; padding: 8px; border-radius: var(--linteau-radius-sm); font-size: 13px; color: var(--linteau-foreground); cursor: pointer }
893
+ .linteau-command-item-highlighted { background: var(--linteau-accent) }
894
+ .linteau-command-item-disabled { opacity: 0.5; cursor: not-allowed }
895
+ .linteau-command-empty { padding: 24px; text-align: center; font-size: 12px; color: var(--linteau-muted-foreground) }
896
+ `;
897
+ function Command(options = {}) {
898
+ assertDom("Command");
899
+ ensureComponentStyles("command", commandCss);
900
+ let groups = options.groups ?? [];
901
+ const closeOnSelect = options.closeOnSelect ?? true;
902
+ const onSelect = options.onSelect;
903
+ let query = "";
904
+ let highlighted = 0;
905
+ let isOpen = false;
906
+ let unlockScroll = null;
907
+ let unbindEscape = null;
908
+ const overlay = el("div", cx(px("command-overlay"), px("hidden")));
909
+ const panel = el("div", px("command-panel"));
910
+ panel.setAttribute("role", "dialog");
911
+ panel.setAttribute("aria-modal", "true");
912
+ const inputWrap = el("div", px("command-input-wrap"));
913
+ const searchIcon = Icon(icons.search, 14, { className: px("icon") });
914
+ if (searchIcon) inputWrap.appendChild(searchIcon);
915
+ const input = el("input", px("command-input"));
916
+ input.type = "text";
917
+ input.placeholder = options.placeholder ?? "Type a command or search...";
918
+ inputWrap.appendChild(input);
919
+ const list = el("div", px("command-list"));
920
+ panel.append(inputWrap, list);
921
+ overlay.appendChild(panel);
922
+ function filteredFlat() {
923
+ const normalized = query.trim().toLowerCase();
924
+ const all = groups.flatMap((group) => group.items);
925
+ if (!normalized) return all;
926
+ return all.filter((item) => item.label.toLowerCase().includes(normalized) || item.keywords?.some((k) => k.toLowerCase().includes(normalized)));
927
+ }
928
+ function renderList() {
929
+ list.replaceChildren();
930
+ const normalized = query.trim().toLowerCase();
931
+ const flat = filteredFlat();
932
+ if (flat.length === 0) {
933
+ const empty = el("div", px("command-empty"));
934
+ empty.textContent = options.emptyText ?? "No results found.";
935
+ list.appendChild(empty);
936
+ return;
937
+ }
938
+ let runningIndex = 0;
939
+ for (const group of groups) {
940
+ const matchingItems = group.items.filter((item) => !normalized || item.label.toLowerCase().includes(normalized) || item.keywords?.some((k) => k.toLowerCase().includes(normalized)));
941
+ if (matchingItems.length === 0) continue;
942
+ if (group.heading) {
943
+ const heading = el("div", px("command-group-heading"));
944
+ heading.textContent = group.heading;
945
+ list.appendChild(heading);
946
+ }
947
+ for (const item of matchingItems) {
948
+ const index = runningIndex++;
949
+ const itemEl = el("div", cx(px("command-item"), index === highlighted && px("command-item-highlighted"), item.disabled && px("command-item-disabled")));
950
+ if (item.icon) itemEl.appendChild(Icon(item.icon, 14, { className: px("icon") }));
951
+ const label = document.createElement("span");
952
+ label.textContent = item.label;
953
+ itemEl.appendChild(label);
954
+ itemEl.addEventListener("mousedown", (event) => {
955
+ event.preventDefault();
956
+ selectItem(item);
957
+ });
958
+ itemEl.addEventListener("mouseenter", () => {
959
+ highlighted = index;
960
+ renderList();
961
+ });
962
+ list.appendChild(itemEl);
963
+ }
964
+ }
965
+ }
966
+ function selectItem(item) {
967
+ if (item.disabled) return;
968
+ onSelect?.(item.value);
969
+ if (closeOnSelect) close();
970
+ }
971
+ function handleKeydown(event) {
972
+ const flat = filteredFlat();
973
+ if (event.key === "ArrowDown") {
974
+ event.preventDefault();
975
+ highlighted = Math.min(highlighted + 1, flat.length - 1);
976
+ renderList();
977
+ } else if (event.key === "ArrowUp") {
978
+ event.preventDefault();
979
+ highlighted = Math.max(highlighted - 1, 0);
980
+ renderList();
981
+ } else if (event.key === "Enter") {
982
+ event.preventDefault();
983
+ const item = flat[highlighted];
984
+ if (item) selectItem(item);
985
+ }
986
+ }
987
+ const inputListener = () => {
988
+ query = input.value;
989
+ highlighted = 0;
990
+ renderList();
991
+ };
992
+ input.addEventListener("input", inputListener);
993
+ input.addEventListener("keydown", handleKeydown);
994
+ const backdropListener = (event) => {
995
+ if (event.target === overlay) close();
996
+ };
997
+ overlay.addEventListener("mousedown", backdropListener);
998
+ function open() {
999
+ if (isOpen) return;
1000
+ isOpen = true;
1001
+ query = "";
1002
+ highlighted = 0;
1003
+ input.value = "";
1004
+ overlay.classList.remove(px("hidden"));
1005
+ renderList();
1006
+ unlockScroll = lockBodyScroll();
1007
+ unbindEscape = onEscapeKey(close);
1008
+ input.focus();
1009
+ }
1010
+ function close() {
1011
+ if (!isOpen) return;
1012
+ isOpen = false;
1013
+ overlay.classList.add(px("hidden"));
1014
+ unlockScroll?.();
1015
+ unlockScroll = null;
1016
+ unbindEscape?.();
1017
+ unbindEscape = null;
1018
+ }
1019
+ function toggle() {
1020
+ if (isOpen) close();
1021
+ else open();
1022
+ }
1023
+ const api = {
1024
+ open,
1025
+ close,
1026
+ toggle,
1027
+ isOpen() {
1028
+ return isOpen;
1029
+ },
1030
+ setGroups(newGroups) {
1031
+ groups = newGroups;
1032
+ renderList();
1033
+ },
1034
+ destroy() {
1035
+ input.removeEventListener("input", inputListener);
1036
+ input.removeEventListener("keydown", handleKeydown);
1037
+ overlay.removeEventListener("mousedown", backdropListener);
1038
+ unlockScroll?.();
1039
+ unbindEscape?.();
1040
+ overlay.remove();
1041
+ }
1042
+ };
1043
+ return attachController(overlay, api);
1044
+ }
1045
+
1046
+ // src/components/data/List.ts
1047
+ var listCss = `
1048
+ .linteau-list { display: flex; flex-direction: column; gap: 2px; width: 100% }
1049
+ .linteau-list-item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--linteau-radius-sm); cursor: pointer }
1050
+ .linteau-list-item:hover { background: var(--linteau-accent) }
1051
+ .linteau-list-item-selected { background: var(--linteau-accent) }
1052
+ .linteau-list-item-text { display: flex; flex-direction: column }
1053
+ .linteau-list-item-label { font-size: 13px; color: var(--linteau-foreground) }
1054
+ .linteau-list-item-description { font-size: 11px; color: var(--linteau-muted-foreground) }
1055
+ `;
1056
+ function List(options) {
1057
+ assertDom("List");
1058
+ ensureComponentStyles("list", listCss);
1059
+ let items = options.items;
1060
+ const selectable = options.selectable ?? true;
1061
+ let value = options.value;
1062
+ const onChange = options.onChange;
1063
+ const cleanupListeners = [];
1064
+ const root = el("div", px("list"));
1065
+ function render() {
1066
+ root.replaceChildren();
1067
+ cleanupListeners.splice(0).forEach((fn) => fn());
1068
+ for (const item of items) {
1069
+ const row = el("div", cx(px("list-item"), selectable && item.value === value && px("list-item-selected")));
1070
+ if (item.icon) row.appendChild(Icon(item.icon, 16, { className: px("icon") }));
1071
+ const textWrap = el("div", px("list-item-text"));
1072
+ const label = el("div", px("list-item-label"));
1073
+ label.textContent = item.label;
1074
+ textWrap.appendChild(label);
1075
+ if (item.description) {
1076
+ const description = el("div", px("list-item-description"));
1077
+ description.textContent = item.description;
1078
+ textWrap.appendChild(description);
1079
+ }
1080
+ row.appendChild(textWrap);
1081
+ if (selectable) {
1082
+ const listener = () => {
1083
+ value = item.value;
1084
+ render();
1085
+ onChange?.(item.value);
1086
+ };
1087
+ row.addEventListener("click", listener);
1088
+ cleanupListeners.push(() => row.removeEventListener("click", listener));
1089
+ }
1090
+ root.appendChild(row);
1091
+ }
1092
+ }
1093
+ render();
1094
+ const api = {
1095
+ getValue() {
1096
+ return value;
1097
+ },
1098
+ setValue(newValue) {
1099
+ value = newValue;
1100
+ render();
1101
+ },
1102
+ setItems(newItems) {
1103
+ items = newItems;
1104
+ render();
1105
+ },
1106
+ destroy() {
1107
+ cleanupListeners.splice(0).forEach((fn) => fn());
1108
+ root.remove();
1109
+ }
1110
+ };
1111
+ return attachController(root, api);
1112
+ }
1113
+
1114
+ // src/components/data/Table.ts
1115
+ var tableCss = `
1116
+ .linteau-table-wrap { width: 100%; overflow-x: auto; border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-lg) }
1117
+ .linteau-table { width: 100%; border-collapse: collapse; font-size: 12px }
1118
+ .linteau-table th { text-align: left; padding: 10px 12px; background: var(--linteau-muted); color: var(--linteau-muted-foreground); font-weight: 600; border-bottom: 1px solid var(--linteau-border); cursor: default; white-space: nowrap }
1119
+ .linteau-table th.linteau-table-sortable { cursor: pointer; user-select: none }
1120
+ .linteau-table td { padding: 10px 12px; border-bottom: 1px solid var(--linteau-border); color: var(--linteau-foreground) }
1121
+ .linteau-table tr:last-child td { border-bottom: none }
1122
+ .linteau-table-sort-icon { display: inline-block; margin-left: 4px; vertical-align: middle }
1123
+ `;
1124
+ function Table(options) {
1125
+ assertDom("Table");
1126
+ ensureComponentStyles("table", tableCss);
1127
+ let rows = options.rows;
1128
+ let sortKey = null;
1129
+ let sortDirection = "asc";
1130
+ const cleanupListeners = [];
1131
+ const wrap = el("div", px("table-wrap"));
1132
+ const table = el("table", px("table"));
1133
+ const thead = document.createElement("thead");
1134
+ const tbody = document.createElement("tbody");
1135
+ function renderHead() {
1136
+ thead.replaceChildren();
1137
+ cleanupListeners.splice(0).forEach((fn) => fn());
1138
+ const headRow = document.createElement("tr");
1139
+ for (const column of options.columns) {
1140
+ const th = document.createElement("th");
1141
+ th.className = cx(px("table-sortable-cell"), column.sortable && px("table-sortable"));
1142
+ th.textContent = column.header;
1143
+ if (column.sortable) {
1144
+ if (sortKey === column.key) {
1145
+ const sortIcon = Icon(sortDirection === "asc" ? icons.chevronUp : icons.chevronDown, 12, { className: px("table-sort-icon") });
1146
+ if (sortIcon) th.appendChild(sortIcon);
1147
+ }
1148
+ const listener = () => {
1149
+ sortDirection = sortKey === column.key && sortDirection === "asc" ? "desc" : "asc";
1150
+ sortKey = column.key;
1151
+ renderHead();
1152
+ options.onSortChange?.(column.key, sortDirection);
1153
+ };
1154
+ th.addEventListener("click", listener);
1155
+ cleanupListeners.push(() => th.removeEventListener("click", listener));
1156
+ }
1157
+ headRow.appendChild(th);
1158
+ }
1159
+ thead.appendChild(headRow);
1160
+ }
1161
+ function renderBody() {
1162
+ tbody.replaceChildren();
1163
+ for (const row of rows) {
1164
+ const tr = document.createElement("tr");
1165
+ for (const column of options.columns) {
1166
+ const td = document.createElement("td");
1167
+ const content = column.render ? column.render(row) : String(row[column.key] ?? "");
1168
+ if (content instanceof HTMLElement) td.appendChild(content);
1169
+ else td.textContent = content;
1170
+ tr.appendChild(td);
1171
+ }
1172
+ tbody.appendChild(tr);
1173
+ }
1174
+ }
1175
+ renderHead();
1176
+ renderBody();
1177
+ table.append(thead, tbody);
1178
+ wrap.appendChild(table);
1179
+ const api = {
1180
+ setRows(newRows) {
1181
+ rows = newRows;
1182
+ renderBody();
1183
+ },
1184
+ destroy() {
1185
+ cleanupListeners.splice(0).forEach((fn) => fn());
1186
+ wrap.remove();
1187
+ }
1188
+ };
1189
+ return attachController(wrap, api);
1190
+ }
1191
+
1192
+ // src/components/data/Tree.ts
1193
+ var treeCss = `
1194
+ .linteau-tree { display: flex; flex-direction: column; gap: 2px }
1195
+ .linteau-tree-row { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-radius: var(--linteau-radius-sm); cursor: pointer; font-size: 12px }
1196
+ .linteau-tree-row:hover { background: var(--linteau-accent) }
1197
+ .linteau-tree-chevron { transition: transform 150ms ease }
1198
+ .linteau-tree-chevron.linteau-open { transform: rotate(90deg) }
1199
+ .linteau-tree-children { margin-left: 18px; display: flex; flex-direction: column; gap: 2px }
1200
+ .linteau-tree-children.linteau-collapsed { display: none }
1201
+ `;
1202
+ function Tree(options) {
1203
+ assertDom("Tree");
1204
+ ensureComponentStyles("tree", treeCss);
1205
+ const expanded = new Set(options.defaultExpanded ?? []);
1206
+ const cleanupListeners = [];
1207
+ const allValues = [];
1208
+ const root = el("div", px("tree"));
1209
+ function collectValues(nodes) {
1210
+ for (const node of nodes) {
1211
+ if (node.children?.length) {
1212
+ allValues.push(node.value);
1213
+ collectValues(node.children);
1214
+ }
1215
+ }
1216
+ }
1217
+ collectValues(options.nodes);
1218
+ function renderNode(node, container) {
1219
+ const hasChildren = Boolean(node.children?.length);
1220
+ const isOpen = expanded.has(node.value);
1221
+ const row = el("div", px("tree-row"));
1222
+ if (hasChildren) {
1223
+ const chevron = Icon(icons.chevronRight, 12, { className: cx(px("tree-chevron"), isOpen && px("open")) });
1224
+ if (chevron) row.appendChild(chevron);
1225
+ }
1226
+ if (node.icon) row.appendChild(Icon(node.icon, 14, { className: px("icon") }));
1227
+ const label = document.createElement("span");
1228
+ label.textContent = node.label;
1229
+ row.appendChild(label);
1230
+ const childrenContainer = hasChildren ? el("div", cx(px("tree-children"), !isOpen && px("collapsed"))) : null;
1231
+ const listener = () => {
1232
+ if (hasChildren) {
1233
+ if (expanded.has(node.value)) expanded.delete(node.value);
1234
+ else expanded.add(node.value);
1235
+ render();
1236
+ }
1237
+ options.onSelect?.(node.value);
1238
+ };
1239
+ row.addEventListener("click", listener);
1240
+ cleanupListeners.push(() => row.removeEventListener("click", listener));
1241
+ container.appendChild(row);
1242
+ if (childrenContainer) {
1243
+ for (const child of node.children ?? []) renderNode(child, childrenContainer);
1244
+ container.appendChild(childrenContainer);
1245
+ }
1246
+ }
1247
+ function render() {
1248
+ root.replaceChildren();
1249
+ cleanupListeners.splice(0).forEach((fn) => fn());
1250
+ for (const node of options.nodes) renderNode(node, root);
1251
+ }
1252
+ render();
1253
+ const api = {
1254
+ expandAll() {
1255
+ allValues.forEach((value) => expanded.add(value));
1256
+ render();
1257
+ },
1258
+ collapseAll() {
1259
+ expanded.clear();
1260
+ render();
1261
+ },
1262
+ destroy() {
1263
+ cleanupListeners.splice(0).forEach((fn) => fn());
1264
+ root.remove();
1265
+ }
1266
+ };
1267
+ return attachController(root, api);
1268
+ }
1269
+
1270
+ // src/components/feedback/Alert.ts
1271
+ var alertCss = `
1272
+ .linteau-alert { display: flex; gap: 10px; padding: 12px; border-radius: var(--linteau-radius-lg); border: 1px solid var(--linteau-border); background: var(--linteau-muted) }
1273
+ .linteau-alert-success { border-color: var(--linteau-success) }
1274
+ .linteau-alert-warning { border-color: var(--linteau-warning) }
1275
+ .linteau-alert-destructive { border-color: var(--linteau-destructive) }
1276
+ .linteau-alert-body { flex: 1 }
1277
+ .linteau-alert-title { font-size: 13px; font-weight: 600; margin-bottom: 2px }
1278
+ .linteau-alert-description { font-size: 12px; color: var(--linteau-muted-foreground) }
1279
+ .linteau-alert-close { background: transparent; border: none; color: var(--linteau-muted-foreground); cursor: pointer; display: flex; align-self: flex-start }
1280
+ `;
1281
+ var defaultIconByVariant = {
1282
+ default: icons.info,
1283
+ success: icons.check,
1284
+ warning: icons.alertTriangle,
1285
+ destructive: icons.alertTriangle
1286
+ };
1287
+ function Alert(description, options = {}) {
1288
+ assertDom("Alert");
1289
+ ensureComponentStyles("alert", alertCss);
1290
+ const variant = options.variant ?? "default";
1291
+ const root = el("div", cx(px("alert"), px(`alert-${variant}`)));
1292
+ const iconEl = Icon(options.icon ?? defaultIconByVariant[variant], 18, { className: px("icon") });
1293
+ if (iconEl) root.appendChild(iconEl);
1294
+ const body = el("div", px("alert-body"));
1295
+ if (options.title) {
1296
+ const title = el("div", px("alert-title"));
1297
+ title.textContent = options.title;
1298
+ body.appendChild(title);
1299
+ }
1300
+ const descriptionEl = el("div", px("alert-description"));
1301
+ descriptionEl.textContent = description;
1302
+ body.appendChild(descriptionEl);
1303
+ root.appendChild(body);
1304
+ let closeListener = null;
1305
+ if (options.dismissible) {
1306
+ const closeButton = el("button", px("alert-close"));
1307
+ closeButton.type = "button";
1308
+ const closeIcon = Icon(icons.x, 14);
1309
+ if (closeIcon) closeButton.appendChild(closeIcon);
1310
+ closeListener = () => {
1311
+ options.onDismiss?.();
1312
+ root.remove();
1313
+ };
1314
+ closeButton.addEventListener("click", closeListener);
1315
+ root.appendChild(closeButton);
1316
+ }
1317
+ const api = {
1318
+ setDescription(description2) {
1319
+ descriptionEl.textContent = description2;
1320
+ },
1321
+ destroy() {
1322
+ root.remove();
1323
+ }
1324
+ };
1325
+ return attachController(root, api);
1326
+ }
1327
+
1328
+ // src/components/feedback/ProgressBar.ts
1329
+ var progressBarCss = `
1330
+ .linteau-progress-overlay { position: fixed; inset: 0; background: color-mix(in oklch, black 55%, transparent); display: flex; align-items: center; justify-content: center; z-index: 1000; backdrop-filter: blur(4px) }
1331
+ .linteau-progress-card { width: 280px; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-lg); padding: 20px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5) }
1332
+ .linteau-progress-title { font-size: 13px; font-weight: 600; margin-bottom: 12px; color: var(--linteau-foreground) }
1333
+ .linteau-progress-track { width: 100%; height: 8px; border-radius: 999px; background: var(--linteau-secondary); overflow: hidden; position: relative }
1334
+ .linteau-progress-fill { height: 100%; width: 0%; background: var(--linteau-primary); border-radius: 999px; transition: width 180ms ease }
1335
+ .linteau-progress-fill.linteau-indeterminate { width: 40% !important; animation: linteau-progress-slide 1.1s ease-in-out infinite }
1336
+ @keyframes linteau-progress-slide { 0% { transform: translateX(-100%) } 100% { transform: translateX(250%) } }
1337
+ .linteau-progress-percent { margin-top: 8px; font-size: 12px; color: var(--linteau-muted-foreground); text-align: right; font-variant-numeric: tabular-nums }
1338
+ `;
1339
+ function ProgressBar(options = {}) {
1340
+ assertDom("ProgressBar");
1341
+ ensureComponentStyles("progress-bar", progressBarCss);
1342
+ const { title = "", value = 0, indeterminate = false, visible = false } = options;
1343
+ const root = el("div", cx(px("progress-overlay"), !visible && px("hidden")));
1344
+ const card = el("div", px("progress-card"));
1345
+ const titleEl = el("div", px("progress-title"));
1346
+ titleEl.textContent = title;
1347
+ const track = el("div", px("progress-track"));
1348
+ const fill = el("div", cx(px("progress-fill"), indeterminate && px("indeterminate")));
1349
+ track.appendChild(fill);
1350
+ const percentLabel = el("div", px("progress-percent"));
1351
+ percentLabel.textContent = indeterminate ? "Working..." : `${Math.round(value)}%`;
1352
+ card.append(titleEl, track, percentLabel);
1353
+ root.appendChild(card);
1354
+ fill.style.width = `${Math.max(0, Math.min(100, value))}%`;
1355
+ const api = {
1356
+ setValue(percent, label) {
1357
+ const clamped = Math.max(0, Math.min(100, percent));
1358
+ fill.classList.remove(px("indeterminate"));
1359
+ fill.style.width = `${clamped}%`;
1360
+ percentLabel.textContent = label ?? `${Math.round(clamped)}%`;
1361
+ },
1362
+ setIndeterminate(value2) {
1363
+ fill.classList.toggle(px("indeterminate"), value2);
1364
+ if (value2) percentLabel.textContent = "Working...";
1365
+ },
1366
+ setTitle(newTitle) {
1367
+ titleEl.textContent = newTitle;
1368
+ },
1369
+ show() {
1370
+ root.classList.remove(px("hidden"));
1371
+ },
1372
+ hide() {
1373
+ root.classList.add(px("hidden"));
1374
+ },
1375
+ destroy() {
1376
+ root.remove();
1377
+ }
1378
+ };
1379
+ return attachController(root, api);
1380
+ }
1381
+
1382
+ // src/components/feedback/Skeleton.ts
1383
+ var skeletonCss = `
1384
+ .linteau-skeleton { background: linear-gradient(90deg, var(--linteau-secondary) 25%, var(--linteau-accent) 37%, var(--linteau-secondary) 63%); background-size: 400% 100%; animation: linteau-skeleton-shine 1.4s ease infinite; border-radius: var(--linteau-radius-sm) }
1385
+ @keyframes linteau-skeleton-shine { 0% { background-position: 100% 50% } 100% { background-position: 0 50% } }
1386
+ `;
1387
+ function Skeleton(options = {}) {
1388
+ assertDom("Skeleton");
1389
+ ensureComponentStyles("skeleton", skeletonCss);
1390
+ const root = el("div", px("skeleton"));
1391
+ root.style.width = typeof options.width === "number" ? `${options.width}px` : options.width ?? "100%";
1392
+ root.style.height = typeof options.height === "number" ? `${options.height}px` : options.height ?? "1em";
1393
+ if (options.radius) root.style.borderRadius = options.radius;
1394
+ const api = {
1395
+ destroy() {
1396
+ root.remove();
1397
+ }
1398
+ };
1399
+ return attachController(root, api);
1400
+ }
1401
+
1402
+ // src/components/feedback/Spinner.ts
1403
+ var spinnerCss = `
1404
+ .linteau-spinner { animation: linteau-spin 0.8s linear infinite; color: var(--linteau-muted-foreground) }
1405
+ @keyframes linteau-spin { to { transform: rotate(360deg) } }
1406
+ `;
1407
+ function Spinner(options = {}) {
1408
+ assertDom("Spinner");
1409
+ ensureComponentStyles("spinner", spinnerCss);
1410
+ const size = options.size ?? 16;
1411
+ const svg = Icon(icons.loader, size, { className: px("spinner"), color: options.color });
1412
+ const api = {
1413
+ setSize(newSize) {
1414
+ svg.setAttribute("width", String(newSize));
1415
+ svg.setAttribute("height", String(newSize));
1416
+ },
1417
+ destroy() {
1418
+ svg.remove();
1419
+ }
1420
+ };
1421
+ return attachController(svg, api);
1422
+ }
1423
+
1424
+ // src/components/feedback/Toast.ts
1425
+ var toastCss = `
1426
+ .linteau-toast-viewport { position: fixed; bottom: 16px; right: 16px; display: flex; flex-direction: column; gap: 8px; z-index: 1400; width: min(340px, 92vw) }
1427
+ .linteau-toast { display: flex; gap: 8px; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-left: 3px solid var(--linteau-primary); border-radius: var(--linteau-radius-md); padding: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4) }
1428
+ .linteau-toast-success { border-left-color: var(--linteau-success) }
1429
+ .linteau-toast-warning { border-left-color: var(--linteau-warning) }
1430
+ .linteau-toast-destructive { border-left-color: var(--linteau-destructive) }
1431
+ .linteau-toast-body { flex: 1 }
1432
+ .linteau-toast-title { font-size: 12px; font-weight: 600 }
1433
+ .linteau-toast-description { font-size: 12px; color: var(--linteau-muted-foreground); margin-top: 2px }
1434
+ .linteau-toast-close { background: transparent; border: none; color: var(--linteau-muted-foreground); cursor: pointer; display: flex; align-self: flex-start }
1435
+ `;
1436
+ var idCounter = 0;
1437
+ function createToastManager() {
1438
+ assertDom("ToastManager");
1439
+ ensureComponentStyles("toast", toastCss);
1440
+ let viewport = null;
1441
+ const timers = /* @__PURE__ */ new Map();
1442
+ function ensureViewport() {
1443
+ if (!viewport) {
1444
+ viewport = el("div", px("toast-viewport"));
1445
+ document.body.appendChild(viewport);
1446
+ }
1447
+ return viewport;
1448
+ }
1449
+ function dismiss(id) {
1450
+ const timer = timers.get(id);
1451
+ if (timer) clearTimeout(timer);
1452
+ timers.delete(id);
1453
+ document.getElementById(id)?.remove();
1454
+ }
1455
+ function show(options) {
1456
+ const id = `linteau-toast-${++idCounter}`;
1457
+ const variant = options.variant ?? "default";
1458
+ const duration = options.duration ?? 4e3;
1459
+ const toastEl = el("div", cx(px("toast"), px(`toast-${variant}`)));
1460
+ toastEl.id = id;
1461
+ const body = el("div", px("toast-body"));
1462
+ if (options.title) {
1463
+ const title = el("div", px("toast-title"));
1464
+ title.textContent = options.title;
1465
+ body.appendChild(title);
1466
+ }
1467
+ if (options.description) {
1468
+ const description = el("div", px("toast-description"));
1469
+ description.textContent = options.description;
1470
+ body.appendChild(description);
1471
+ }
1472
+ const closeButton = el("button", px("toast-close"));
1473
+ closeButton.type = "button";
1474
+ const closeIcon = Icon(icons.x, 14);
1475
+ if (closeIcon) closeButton.appendChild(closeIcon);
1476
+ closeButton.addEventListener("click", () => dismiss(id));
1477
+ toastEl.append(body, closeButton);
1478
+ ensureViewport().appendChild(toastEl);
1479
+ if (duration > 0) timers.set(id, setTimeout(() => dismiss(id), duration));
1480
+ return id;
1481
+ }
1482
+ return {
1483
+ show,
1484
+ dismiss,
1485
+ clear() {
1486
+ timers.forEach((timer) => clearTimeout(timer));
1487
+ timers.clear();
1488
+ viewport?.replaceChildren();
1489
+ },
1490
+ destroy() {
1491
+ timers.forEach((timer) => clearTimeout(timer));
1492
+ timers.clear();
1493
+ viewport?.remove();
1494
+ viewport = null;
1495
+ }
1496
+ };
1497
+ }
1498
+ var toast = createToastManager();
1499
+
1500
+ // src/components/field/shared.ts
1501
+ var fieldBaseCss = `
1502
+ .linteau-field { display: flex; flex-direction: column; gap: 6px; width: 100% }
1503
+ .linteau-field-row { display: flex; align-items: center; justify-content: space-between }
1504
+ .linteau-field-label { display: flex; align-items: center; gap: 6px; color: var(--linteau-foreground) }
1505
+ .linteau-field-value { color: var(--linteau-muted-foreground); font-variant-numeric: tabular-nums }
1506
+ .linteau-field-header { display: flex; justify-content: space-between; align-items: center; font-size: 12px }
1507
+ `;
1508
+
1509
+ // src/components/field/ColorField.ts
1510
+ var swatchCss = `
1511
+ .linteau-swatch { --linteau-swatch-color: #ffffff; width: 28px; height: 28px; border-radius: var(--linteau-radius-sm); background: var(--linteau-swatch-color); border: 1px solid var(--linteau-border); cursor: pointer; display: block; position: relative; overflow: hidden; transition: transform 120ms ease }
1512
+ .linteau-swatch:hover { transform: scale(1.06) }
1513
+ .linteau-swatch input[type='color'] { position: absolute; inset: 0; opacity: 0; cursor: pointer }
1514
+ `;
1515
+ function ColorField(options) {
1516
+ assertDom("ColorField");
1517
+ ensureComponentStyles("field-base", fieldBaseCss);
1518
+ ensureComponentStyles("swatch", swatchCss);
1519
+ let value = options.value ?? options.defaultValue ?? "#ffffff";
1520
+ const onChange = options.onChange;
1521
+ const root = el("div", px("field-row"));
1522
+ const labelWrap = el("div", px("field-label"));
1523
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
1524
+ const labelText = document.createElement("span");
1525
+ labelText.textContent = options.label;
1526
+ labelWrap.appendChild(labelText);
1527
+ const swatch = el("label", px("swatch"));
1528
+ swatch.style.setProperty("--linteau-swatch-color", value);
1529
+ const input = el("input", void 0);
1530
+ input.type = "color";
1531
+ input.value = value;
1532
+ input.disabled = options.disabled ?? false;
1533
+ const listener = () => {
1534
+ value = input.value;
1535
+ swatch.style.setProperty("--linteau-swatch-color", value);
1536
+ onChange?.(value);
1537
+ };
1538
+ input.addEventListener("input", listener);
1539
+ swatch.appendChild(input);
1540
+ root.append(labelWrap, swatch);
1541
+ const api = {
1542
+ getValue() {
1543
+ return value;
1544
+ },
1545
+ setValue(newValue) {
1546
+ value = newValue;
1547
+ input.value = newValue;
1548
+ swatch.style.setProperty("--linteau-swatch-color", newValue);
1549
+ },
1550
+ setDisabled(disabled) {
1551
+ input.disabled = disabled;
1552
+ },
1553
+ destroy() {
1554
+ input.removeEventListener("input", listener);
1555
+ root.remove();
1556
+ }
1557
+ };
1558
+ return attachController(root, api);
1559
+ }
1560
+
1561
+ // src/components/field/textInput.css.ts
1562
+ var textInputCss = `
1563
+ .linteau-text-input, .linteau-textarea {
1564
+ width: 100%;
1565
+ background: var(--linteau-secondary);
1566
+ color: var(--linteau-foreground);
1567
+ border: 1px solid var(--linteau-border);
1568
+ border-radius: var(--linteau-radius-sm);
1569
+ padding: 8px 10px;
1570
+ font-size: 12px;
1571
+ font-family: inherit;
1572
+ transition: border-color 120ms ease;
1573
+ }
1574
+ .linteau-text-input:focus, .linteau-textarea:focus {
1575
+ outline: none;
1576
+ border-color: var(--linteau-ring);
1577
+ box-shadow: 0 0 0 2px color-mix(in oklch, var(--linteau-ring) 20%, transparent);
1578
+ }
1579
+ .linteau-text-input:disabled, .linteau-textarea:disabled { opacity: 0.5; cursor: not-allowed }
1580
+ .linteau-textarea { resize: vertical; min-height: 72px }
1581
+ `;
1582
+
1583
+ // src/components/field/NumberField.ts
1584
+ function NumberField(options = {}) {
1585
+ assertDom("NumberField");
1586
+ ensureComponentStyles("field-base", fieldBaseCss);
1587
+ ensureComponentStyles("text-input", textInputCss);
1588
+ const onChange = options.onChange;
1589
+ const root = el("div", px("field"));
1590
+ if (options.label) {
1591
+ const header = el("div", px("field-header"));
1592
+ const labelWrap = el("div", px("field-label"));
1593
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
1594
+ const labelText = document.createElement("span");
1595
+ labelText.textContent = options.label;
1596
+ labelWrap.appendChild(labelText);
1597
+ header.appendChild(labelWrap);
1598
+ root.appendChild(header);
1599
+ }
1600
+ const input = el("input", px("text-input"));
1601
+ input.type = "number";
1602
+ if (options.min !== void 0) input.min = String(options.min);
1603
+ if (options.max !== void 0) input.max = String(options.max);
1604
+ input.step = String(options.step ?? 1);
1605
+ input.value = String(options.value ?? options.defaultValue ?? 0);
1606
+ input.disabled = options.disabled ?? false;
1607
+ function clamp(value) {
1608
+ let result = value;
1609
+ if (options.min !== void 0) result = Math.max(options.min, result);
1610
+ if (options.max !== void 0) result = Math.min(options.max, result);
1611
+ return result;
1612
+ }
1613
+ const listener = () => {
1614
+ const parsed = Number(input.value);
1615
+ if (!Number.isNaN(parsed)) onChange?.(parsed);
1616
+ };
1617
+ input.addEventListener("input", listener);
1618
+ root.appendChild(input);
1619
+ const api = {
1620
+ getValue() {
1621
+ return Number(input.value);
1622
+ },
1623
+ setValue(value) {
1624
+ input.value = String(clamp(value));
1625
+ },
1626
+ setDisabled(disabled) {
1627
+ input.disabled = disabled;
1628
+ },
1629
+ destroy() {
1630
+ input.removeEventListener("input", listener);
1631
+ root.remove();
1632
+ }
1633
+ };
1634
+ return attachController(root, api);
1635
+ }
1636
+
1637
+ // src/components/field/TextArea.ts
1638
+ function TextArea(options = {}) {
1639
+ assertDom("TextArea");
1640
+ ensureComponentStyles("field-base", fieldBaseCss);
1641
+ ensureComponentStyles("text-input", textInputCss);
1642
+ const onChange = options.onChange;
1643
+ const root = el("div", px("field"));
1644
+ if (options.label) {
1645
+ const header = el("div", px("field-header"));
1646
+ const labelWrap = el("div", px("field-label"));
1647
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
1648
+ const labelText = document.createElement("span");
1649
+ labelText.textContent = options.label;
1650
+ labelWrap.appendChild(labelText);
1651
+ header.appendChild(labelWrap);
1652
+ root.appendChild(header);
1653
+ }
1654
+ const textarea = el("textarea", px("textarea"));
1655
+ textarea.placeholder = options.placeholder ?? "";
1656
+ textarea.rows = options.rows ?? 3;
1657
+ textarea.value = options.value ?? options.defaultValue ?? "";
1658
+ textarea.disabled = options.disabled ?? false;
1659
+ const listener = () => onChange?.(textarea.value);
1660
+ textarea.addEventListener("input", listener);
1661
+ root.appendChild(textarea);
1662
+ const api = {
1663
+ getValue() {
1664
+ return textarea.value;
1665
+ },
1666
+ setValue(value) {
1667
+ textarea.value = value;
1668
+ },
1669
+ setDisabled(disabled) {
1670
+ textarea.disabled = disabled;
1671
+ },
1672
+ destroy() {
1673
+ textarea.removeEventListener("input", listener);
1674
+ root.remove();
1675
+ }
1676
+ };
1677
+ return attachController(root, api);
1678
+ }
1679
+
1680
+ // src/components/field/TextField.ts
1681
+ function TextField(options = {}) {
1682
+ assertDom("TextField");
1683
+ ensureComponentStyles("field-base", fieldBaseCss);
1684
+ ensureComponentStyles("text-input", textInputCss);
1685
+ const onChange = options.onChange;
1686
+ const root = el("div", px("field"));
1687
+ if (options.label) {
1688
+ const header = el("div", px("field-header"));
1689
+ const labelWrap = el("div", px("field-label"));
1690
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
1691
+ const labelText = document.createElement("span");
1692
+ labelText.textContent = options.label;
1693
+ labelWrap.appendChild(labelText);
1694
+ header.appendChild(labelWrap);
1695
+ root.appendChild(header);
1696
+ }
1697
+ const input = el("input", px("text-input"));
1698
+ input.type = options.type ?? "text";
1699
+ input.placeholder = options.placeholder ?? "";
1700
+ input.value = options.value ?? options.defaultValue ?? "";
1701
+ input.disabled = options.disabled ?? false;
1702
+ const inputListener = () => onChange?.(input.value);
1703
+ const keyListener = (event) => {
1704
+ if (event.key === "Enter") options.onEnter?.(input.value);
1705
+ };
1706
+ input.addEventListener("input", inputListener);
1707
+ input.addEventListener("keydown", keyListener);
1708
+ root.appendChild(input);
1709
+ const api = {
1710
+ getValue() {
1711
+ return input.value;
1712
+ },
1713
+ setValue(value) {
1714
+ input.value = value;
1715
+ },
1716
+ setDisabled(disabled) {
1717
+ input.disabled = disabled;
1718
+ },
1719
+ focus() {
1720
+ input.focus();
1721
+ },
1722
+ destroy() {
1723
+ input.removeEventListener("input", inputListener);
1724
+ input.removeEventListener("keydown", keyListener);
1725
+ root.remove();
1726
+ }
1727
+ };
1728
+ return attachController(root, api);
1729
+ }
1730
+
1731
+ // src/core/content.ts
1732
+ function fillContent(target, content) {
1733
+ target.replaceChildren();
1734
+ if (content instanceof HTMLElement) target.appendChild(content);
1735
+ else target.textContent = content;
1736
+ }
1737
+
1738
+ // src/components/hovercard/HoverCard.ts
1739
+ var hoverCardCss = `
1740
+ .linteau-hover-card { position: fixed; z-index: 1200; width: 280px; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-md); padding: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); font-size: 12px; color: var(--linteau-foreground) }
1741
+ `;
1742
+ function HoverCard(options) {
1743
+ assertDom("HoverCard");
1744
+ ensureComponentStyles("hover-card", hoverCardCss);
1745
+ const { anchor, placement = "bottom" } = options;
1746
+ const openDelay = options.openDelay ?? 300;
1747
+ const closeDelay = options.closeDelay ?? 150;
1748
+ let openTimer = null;
1749
+ let closeTimer = null;
1750
+ let isVisible = false;
1751
+ const card = el("div", cx(px("hover-card"), px("hidden")));
1752
+ fillContent(card, options.content);
1753
+ function clearTimers() {
1754
+ if (openTimer) clearTimeout(openTimer);
1755
+ if (closeTimer) clearTimeout(closeTimer);
1756
+ openTimer = null;
1757
+ closeTimer = null;
1758
+ }
1759
+ function showNow() {
1760
+ clearTimers();
1761
+ if (isVisible) return;
1762
+ isVisible = true;
1763
+ document.body.appendChild(card);
1764
+ card.classList.remove(px("hidden"));
1765
+ positionFloating(anchor, card, placement);
1766
+ }
1767
+ function hideNow() {
1768
+ clearTimers();
1769
+ if (!isVisible) return;
1770
+ isVisible = false;
1771
+ card.classList.add(px("hidden"));
1772
+ card.remove();
1773
+ }
1774
+ function scheduleShow() {
1775
+ if (closeTimer) clearTimeout(closeTimer);
1776
+ closeTimer = null;
1777
+ if (isVisible || openTimer) return;
1778
+ openTimer = setTimeout(showNow, openDelay);
1779
+ }
1780
+ function scheduleHide() {
1781
+ if (openTimer) clearTimeout(openTimer);
1782
+ openTimer = null;
1783
+ if (!isVisible || closeTimer) return;
1784
+ closeTimer = setTimeout(hideNow, closeDelay);
1785
+ }
1786
+ anchor.addEventListener("mouseenter", scheduleShow);
1787
+ anchor.addEventListener("mouseleave", scheduleHide);
1788
+ anchor.addEventListener("focus", scheduleShow);
1789
+ anchor.addEventListener("blur", scheduleHide);
1790
+ card.addEventListener("mouseenter", () => {
1791
+ if (closeTimer) clearTimeout(closeTimer);
1792
+ closeTimer = null;
1793
+ });
1794
+ card.addEventListener("mouseleave", scheduleHide);
1795
+ return {
1796
+ setContent(content) {
1797
+ fillContent(card, content);
1798
+ },
1799
+ open: showNow,
1800
+ close: hideNow,
1801
+ destroy() {
1802
+ clearTimers();
1803
+ anchor.removeEventListener("mouseenter", scheduleShow);
1804
+ anchor.removeEventListener("mouseleave", scheduleHide);
1805
+ anchor.removeEventListener("focus", scheduleShow);
1806
+ anchor.removeEventListener("blur", scheduleHide);
1807
+ card.remove();
1808
+ }
1809
+ };
1810
+ }
1811
+
1812
+ // src/components/input/ChipInput.ts
1813
+ var chipInputCss = `
1814
+ .linteau-chip-field { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px; background: var(--linteau-secondary); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-sm) }
1815
+ .linteau-chip-field:focus-within { border-color: var(--linteau-ring) }
1816
+ .linteau-chip { display: inline-flex; align-items: center; gap: 4px; background: var(--linteau-accent); color: var(--linteau-accent-foreground); border-radius: 999px; padding: 3px 8px; font-size: 11px }
1817
+ .linteau-chip-remove { background: transparent; border: none; color: inherit; cursor: pointer; display: flex; padding: 0 }
1818
+ .linteau-chip-input { flex: 1; min-width: 80px; background: transparent; border: none; color: var(--linteau-foreground); font-size: 12px; font-family: inherit; outline: none }
1819
+ `;
1820
+ function ChipInput(options = {}) {
1821
+ assertDom("ChipInput");
1822
+ ensureComponentStyles("field-base", fieldBaseCss);
1823
+ ensureComponentStyles("chip-input", chipInputCss);
1824
+ let chips = options.value ?? options.defaultValue ?? [];
1825
+ const onChange = options.onChange;
1826
+ const cleanupChipListeners = [];
1827
+ const root = el("div", px("field"));
1828
+ if (options.label) {
1829
+ const header = el("div", px("field-header"));
1830
+ const labelWrap = el("div", px("field-label"));
1831
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
1832
+ const labelText = document.createElement("span");
1833
+ labelText.textContent = options.label;
1834
+ labelWrap.appendChild(labelText);
1835
+ header.appendChild(labelWrap);
1836
+ root.appendChild(header);
1837
+ }
1838
+ const fieldWrap = el("div", px("chip-field"));
1839
+ const input = el("input", px("chip-input"));
1840
+ input.type = "text";
1841
+ input.placeholder = options.placeholder ?? "";
1842
+ fieldWrap.appendChild(input);
1843
+ function renderChips() {
1844
+ Array.from(fieldWrap.children).forEach((child) => {
1845
+ if (child !== input) child.remove();
1846
+ });
1847
+ cleanupChipListeners.splice(0).forEach((fn) => fn());
1848
+ chips.forEach((value, index) => {
1849
+ const chip = el("span", px("chip"));
1850
+ const label = document.createElement("span");
1851
+ label.textContent = value;
1852
+ const removeButton = el("button", px("chip-remove"));
1853
+ removeButton.type = "button";
1854
+ const removeIcon = Icon(icons.x, 10);
1855
+ if (removeIcon) removeButton.appendChild(removeIcon);
1856
+ const listener = () => {
1857
+ chips = chips.filter((_, i) => i !== index);
1858
+ renderChips();
1859
+ onChange?.(chips);
1860
+ };
1861
+ removeButton.addEventListener("click", listener);
1862
+ cleanupChipListeners.push(() => removeButton.removeEventListener("click", listener));
1863
+ chip.append(label, removeButton);
1864
+ fieldWrap.insertBefore(chip, input);
1865
+ });
1866
+ }
1867
+ renderChips();
1868
+ const keyListener = (event) => {
1869
+ if (event.key === "Enter" && input.value.trim()) {
1870
+ event.preventDefault();
1871
+ chips = [...chips, input.value.trim()];
1872
+ input.value = "";
1873
+ renderChips();
1874
+ onChange?.(chips);
1875
+ } else if (event.key === "Backspace" && !input.value && chips.length > 0) {
1876
+ chips = chips.slice(0, -1);
1877
+ renderChips();
1878
+ onChange?.(chips);
1879
+ }
1880
+ };
1881
+ input.addEventListener("keydown", keyListener);
1882
+ root.appendChild(fieldWrap);
1883
+ const api = {
1884
+ getValue() {
1885
+ return chips;
1886
+ },
1887
+ setValue(newValue) {
1888
+ chips = newValue;
1889
+ renderChips();
1890
+ },
1891
+ destroy() {
1892
+ input.removeEventListener("keydown", keyListener);
1893
+ cleanupChipListeners.splice(0).forEach((fn) => fn());
1894
+ root.remove();
1895
+ }
1896
+ };
1897
+ return attachController(root, api);
1898
+ }
1899
+
1900
+ // src/components/select/Combobox.ts
1901
+ var comboboxCss = `
1902
+ .linteau-combobox { position: relative; display: flex; align-items: center }
1903
+ .linteau-combobox-input { padding-right: 28px }
1904
+ .linteau-combobox-chevron { position: absolute; right: 8px; pointer-events: none; color: var(--linteau-muted-foreground) }
1905
+ .linteau-combobox-list { position: fixed; z-index: 1200; min-width: 180px; max-height: 220px; overflow-y: auto; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-md); padding: 4px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4) }
1906
+ .linteau-combobox-option { padding: 6px 8px; border-radius: var(--linteau-radius-sm); font-size: 12px; cursor: pointer; color: var(--linteau-foreground) }
1907
+ .linteau-combobox-option-highlighted, .linteau-combobox-option:hover { background: var(--linteau-accent) }
1908
+ `;
1909
+ function Combobox(options) {
1910
+ assertDom("Combobox");
1911
+ ensureComponentStyles("field-base", fieldBaseCss);
1912
+ ensureComponentStyles("text-input", textInputCss);
1913
+ ensureComponentStyles("combobox", comboboxCss);
1914
+ let items = options.options;
1915
+ let value = options.value ?? options.defaultValue ?? "";
1916
+ let query = items.find((item) => item.value === value)?.label ?? "";
1917
+ let isOpen = false;
1918
+ let highlighted = 0;
1919
+ let unbindOutside = null;
1920
+ const onChange = options.onChange;
1921
+ const root = el("div", px("field"));
1922
+ const header = el("div", px("field-header"));
1923
+ const labelWrap = el("div", px("field-label"));
1924
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
1925
+ const labelText = document.createElement("span");
1926
+ labelText.textContent = options.label;
1927
+ labelWrap.appendChild(labelText);
1928
+ header.appendChild(labelWrap);
1929
+ const wrap = el("div", px("combobox"));
1930
+ const input = el("input", cx(px("text-input"), px("combobox-input")));
1931
+ input.type = "text";
1932
+ input.placeholder = options.placeholder ?? "";
1933
+ input.value = query;
1934
+ input.disabled = options.disabled ?? false;
1935
+ const chevron = Icon(icons.chevronDown, 12, { className: px("combobox-chevron") });
1936
+ const list = el("div", cx(px("combobox-list"), px("hidden")));
1937
+ function filtered() {
1938
+ const normalized = query.trim().toLowerCase();
1939
+ if (!normalized) return items;
1940
+ return items.filter((item) => item.label.toLowerCase().includes(normalized));
1941
+ }
1942
+ function renderList() {
1943
+ list.replaceChildren();
1944
+ filtered().forEach((item, index) => {
1945
+ const optionEl = el("div", cx(px("combobox-option"), index === highlighted && px("combobox-option-highlighted")));
1946
+ optionEl.textContent = item.label;
1947
+ optionEl.addEventListener("mousedown", (event) => {
1948
+ event.preventDefault();
1949
+ select(item);
1950
+ });
1951
+ list.appendChild(optionEl);
1952
+ });
1953
+ }
1954
+ function select(item) {
1955
+ value = item.value;
1956
+ query = item.label;
1957
+ input.value = query;
1958
+ close();
1959
+ onChange?.(value);
1960
+ }
1961
+ function open() {
1962
+ if (isOpen || input.disabled) return;
1963
+ isOpen = true;
1964
+ highlighted = 0;
1965
+ document.body.appendChild(list);
1966
+ list.classList.remove(px("hidden"));
1967
+ renderList();
1968
+ positionFloating(wrap, list, "bottom");
1969
+ unbindOutside = onClickOutside([wrap, list], close);
1970
+ }
1971
+ function close() {
1972
+ if (!isOpen) return;
1973
+ isOpen = false;
1974
+ list.classList.add(px("hidden"));
1975
+ list.remove();
1976
+ unbindOutside?.();
1977
+ unbindOutside = null;
1978
+ }
1979
+ const inputListener = () => {
1980
+ query = input.value;
1981
+ highlighted = 0;
1982
+ if (!isOpen) open();
1983
+ else renderList();
1984
+ };
1985
+ const keyListener = (event) => {
1986
+ const results = filtered();
1987
+ if (event.key === "ArrowDown") {
1988
+ event.preventDefault();
1989
+ highlighted = Math.min(highlighted + 1, results.length - 1);
1990
+ renderList();
1991
+ } else if (event.key === "ArrowUp") {
1992
+ event.preventDefault();
1993
+ highlighted = Math.max(highlighted - 1, 0);
1994
+ renderList();
1995
+ } else if (event.key === "Enter") {
1996
+ event.preventDefault();
1997
+ const item = results[highlighted];
1998
+ if (item) select(item);
1999
+ } else if (event.key === "Escape") {
2000
+ close();
2001
+ }
2002
+ };
2003
+ const focusListener = () => open();
2004
+ input.addEventListener("input", inputListener);
2005
+ input.addEventListener("keydown", keyListener);
2006
+ input.addEventListener("focus", focusListener);
2007
+ wrap.append(input, chevron);
2008
+ root.append(header, wrap);
2009
+ const api = {
2010
+ getValue() {
2011
+ return value;
2012
+ },
2013
+ setValue(newValue) {
2014
+ value = newValue;
2015
+ query = items.find((item) => item.value === newValue)?.label ?? "";
2016
+ input.value = query;
2017
+ },
2018
+ setOptions(newOptions) {
2019
+ items = newOptions;
2020
+ if (isOpen) renderList();
2021
+ },
2022
+ open,
2023
+ close,
2024
+ destroy() {
2025
+ input.removeEventListener("input", inputListener);
2026
+ input.removeEventListener("keydown", keyListener);
2027
+ input.removeEventListener("focus", focusListener);
2028
+ unbindOutside?.();
2029
+ list.remove();
2030
+ root.remove();
2031
+ }
2032
+ };
2033
+ return attachController(root, api);
2034
+ }
2035
+
2036
+ // src/components/input/DatePicker.ts
2037
+ var calendarCss = `
2038
+ .linteau-calendar { position: fixed; z-index: 1200; width: 280px; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-lg); padding: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4) }
2039
+ .linteau-calendar-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px }
2040
+ .linteau-calendar-nav { background: transparent; border: none; color: var(--linteau-foreground); cursor: pointer; display: flex; padding: 4px; border-radius: var(--linteau-radius-sm) }
2041
+ .linteau-calendar-nav:hover:not(:disabled) { background: var(--linteau-accent) }
2042
+ .linteau-calendar-nav:disabled { opacity: 0.35; cursor: not-allowed }
2043
+ .linteau-calendar-month { font-size: 12px; font-weight: 600 }
2044
+ .linteau-calendar-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 2px }
2045
+ .linteau-calendar-weekday { height: 20px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600; color: var(--linteau-muted-foreground); text-transform: uppercase }
2046
+ .linteau-calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px }
2047
+ .linteau-calendar-day { height: 28px; display: flex; align-items: center; justify-content: center; font-size: 11px; border-radius: var(--linteau-radius-sm); background: transparent; border: none; color: var(--linteau-foreground); cursor: pointer; font-family: inherit }
2048
+ .linteau-calendar-day:hover:not(:disabled) { background: var(--linteau-accent) }
2049
+ .linteau-calendar-day:disabled { opacity: 0.35; cursor: not-allowed }
2050
+ .linteau-calendar-day:focus-visible { outline: 2px solid var(--linteau-ring); outline-offset: 1px }
2051
+ .linteau-calendar-day-outside { opacity: 0.35 }
2052
+ .linteau-calendar-day-selected { background: var(--linteau-primary); color: var(--linteau-primary-foreground) }
2053
+ .linteau-calendar-day-today:not(.linteau-calendar-day-selected) { box-shadow: inset 0 0 0 1px var(--linteau-ring) }
2054
+ .linteau-calendar-day-in-range { background: var(--linteau-accent); border-radius: 0 }
2055
+ .linteau-calendar-day-range-start { background: var(--linteau-primary); color: var(--linteau-primary-foreground); border-radius: var(--linteau-radius-sm) 0 0 var(--linteau-radius-sm) }
2056
+ .linteau-calendar-day-range-end { background: var(--linteau-primary); color: var(--linteau-primary-foreground); border-radius: 0 var(--linteau-radius-sm) var(--linteau-radius-sm) 0 }
2057
+ .linteau-calendar-footer { display: flex; gap: 6px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--linteau-border) }
2058
+ .linteau-calendar-footer-button { flex: 1; padding: 6px 8px; font-size: 11px; border-radius: var(--linteau-radius-sm); border: 1px solid var(--linteau-border); background: transparent; color: var(--linteau-foreground); cursor: pointer; font-family: inherit }
2059
+ .linteau-calendar-footer-button:hover { background: var(--linteau-accent) }
2060
+ .linteau-combobox-input-clearable { padding-right: 46px }
2061
+ .linteau-combobox-clear { position: absolute; right: 26px; color: var(--linteau-muted-foreground); background: transparent; border: none; cursor: pointer; display: flex; padding: 2px }
2062
+ .linteau-combobox-clear:hover { color: var(--linteau-foreground) }
2063
+ `;
2064
+ function isSameDay(a, b) {
2065
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2066
+ }
2067
+ function startOfMonth(date) {
2068
+ return new Date(date.getFullYear(), date.getMonth(), 1);
2069
+ }
2070
+ function addMonths(date, count) {
2071
+ return new Date(date.getFullYear(), date.getMonth() + count, 1);
2072
+ }
2073
+ function addDays(date, count) {
2074
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + count);
2075
+ }
2076
+ function isRange(value) {
2077
+ return Boolean(value) && typeof value === "object" && "from" in value;
2078
+ }
2079
+ function buildWeekdayLabels(weekStartsOn) {
2080
+ const base = new Date(2021, 0, 3);
2081
+ const labels = [];
2082
+ for (let i = 0; i < 7; i++) {
2083
+ const day = new Date(base.getFullYear(), base.getMonth(), base.getDate() + i + weekStartsOn);
2084
+ labels.push(day.toLocaleDateString(void 0, { weekday: "short" }).slice(0, 2));
2085
+ }
2086
+ return labels;
2087
+ }
2088
+ function getCalendarDays(viewDate, weekStartsOn) {
2089
+ const year = viewDate.getFullYear();
2090
+ const month = viewDate.getMonth();
2091
+ const firstOfMonth = new Date(year, month, 1);
2092
+ const lastOfMonth = new Date(year, month + 1, 0);
2093
+ const leadingOffset = (firstOfMonth.getDay() - weekStartsOn + 7) % 7;
2094
+ const trailingOffset = (weekStartsOn + 6 - lastOfMonth.getDay() + 7) % 7;
2095
+ const days = [];
2096
+ for (let i = leadingOffset; i > 0; i--) days.push({ date: new Date(year, month, 1 - i), inCurrentMonth: false });
2097
+ for (let day = 1; day <= lastOfMonth.getDate(); day++) days.push({ date: new Date(year, month, day), inCurrentMonth: true });
2098
+ for (let i = 1; i <= trailingOffset; i++) days.push({ date: new Date(year, month + 1, i), inCurrentMonth: false });
2099
+ return days;
2100
+ }
2101
+ function DatePicker(options = {}) {
2102
+ assertDom("DatePicker");
2103
+ ensureComponentStyles("field-base", fieldBaseCss);
2104
+ ensureComponentStyles("text-input", textInputCss);
2105
+ ensureComponentStyles("combobox", comboboxCss);
2106
+ ensureComponentStyles("calendar", calendarCss);
2107
+ const mode = options.mode ?? "single";
2108
+ const weekStartsOn = options.weekStartsOn ?? 0;
2109
+ const min = options.min;
2110
+ const max = options.max;
2111
+ const format = options.format ?? ((date) => date.toLocaleDateString());
2112
+ const onChange = options.onChange;
2113
+ const showTodayButton = options.showTodayButton ?? true;
2114
+ const showClearButton = options.showClearButton ?? true;
2115
+ let value = options.value ?? options.defaultValue;
2116
+ let rangeAnchor = null;
2117
+ let viewDate = (isRange(value) ? value.from : value) ?? /* @__PURE__ */ new Date();
2118
+ let focusedDate = viewDate;
2119
+ let isOpen = false;
2120
+ let unbindOutside = null;
2121
+ let unbindEscape = null;
2122
+ const dayButtonsByTime = /* @__PURE__ */ new Map();
2123
+ function formatValue(v) {
2124
+ if (!v) return "";
2125
+ if (isRange(v)) return v.to ? `${format(v.from)} - ${format(v.to)}` : format(v.from);
2126
+ return format(v);
2127
+ }
2128
+ function isDisabledDate(date) {
2129
+ return Boolean(min && date < min || max && date > max);
2130
+ }
2131
+ const root = el("div", px("field"));
2132
+ if (options.label) {
2133
+ const header = el("div", px("field-header"));
2134
+ const labelWrap = el("div", px("field-label"));
2135
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
2136
+ const labelText = document.createElement("span");
2137
+ labelText.textContent = options.label;
2138
+ labelWrap.appendChild(labelText);
2139
+ header.appendChild(labelWrap);
2140
+ root.appendChild(header);
2141
+ }
2142
+ const wrap = el("div", px("combobox"));
2143
+ const input = el("input", cx(px("text-input"), px("combobox-input")));
2144
+ input.type = "text";
2145
+ input.readOnly = true;
2146
+ input.value = formatValue(value);
2147
+ input.placeholder = options.placeholder ?? (mode === "range" ? "Select a date range" : "Select a date");
2148
+ input.disabled = options.disabled ?? false;
2149
+ const clearButton = el("button", px("combobox-clear"));
2150
+ clearButton.type = "button";
2151
+ const clearIcon = Icon(icons.x, 12);
2152
+ if (clearIcon) clearButton.appendChild(clearIcon);
2153
+ const calendarIcon = Icon(icons.calendar, 14, { className: px("combobox-chevron") });
2154
+ wrap.append(input, clearButton, calendarIcon);
2155
+ root.appendChild(wrap);
2156
+ function syncClearButton() {
2157
+ const hasValue = Boolean(value);
2158
+ clearButton.style.display = hasValue && showClearButton ? "flex" : "none";
2159
+ input.classList.toggle(px("combobox-input-clearable"), hasValue && showClearButton);
2160
+ }
2161
+ syncClearButton();
2162
+ const calendar2 = el("div", cx(px("calendar"), px("hidden")));
2163
+ calendar2.setAttribute("role", "application");
2164
+ function focusDayButton(date) {
2165
+ dayButtonsByTime.get(new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime())?.focus();
2166
+ }
2167
+ function updateHoverPreview(hovered) {
2168
+ if (mode !== "range" || !rangeAnchor) return;
2169
+ for (const [time, button] of dayButtonsByTime) {
2170
+ const date = new Date(time);
2171
+ const inPreview = hovered ? date >= rangeAnchor && date <= hovered || date <= rangeAnchor && date >= hovered : false;
2172
+ button.classList.toggle(px("calendar-day-in-range"), inPreview && !isSameDay(date, rangeAnchor));
2173
+ }
2174
+ }
2175
+ function handleDayClick(date) {
2176
+ if (isDisabledDate(date)) return;
2177
+ if (mode === "range") {
2178
+ if (!rangeAnchor) {
2179
+ rangeAnchor = date;
2180
+ value = { from: date };
2181
+ input.value = formatValue(value);
2182
+ renderCalendar();
2183
+ } else {
2184
+ const from = rangeAnchor <= date ? rangeAnchor : date;
2185
+ const to = rangeAnchor <= date ? date : rangeAnchor;
2186
+ value = { from, to };
2187
+ rangeAnchor = null;
2188
+ input.value = formatValue(value);
2189
+ syncClearButton();
2190
+ onChange?.(value);
2191
+ close();
2192
+ }
2193
+ } else {
2194
+ value = date;
2195
+ input.value = formatValue(value);
2196
+ syncClearButton();
2197
+ onChange?.(value);
2198
+ close();
2199
+ }
2200
+ }
2201
+ function renderCalendar() {
2202
+ calendar2.replaceChildren();
2203
+ dayButtonsByTime.clear();
2204
+ const header = el("div", px("calendar-header"));
2205
+ const prevButton = el("button", px("calendar-nav"));
2206
+ prevButton.type = "button";
2207
+ prevButton.setAttribute("aria-label", "Previous month");
2208
+ const prevIcon = Icon(icons.chevronLeft, 14);
2209
+ if (prevIcon) prevButton.appendChild(prevIcon);
2210
+ prevButton.disabled = Boolean(min && addMonths(viewDate, -1) < startOfMonth(min));
2211
+ const nextButton = el("button", px("calendar-nav"));
2212
+ nextButton.type = "button";
2213
+ nextButton.setAttribute("aria-label", "Next month");
2214
+ const nextIcon = Icon(icons.chevronRight, 14);
2215
+ if (nextIcon) nextButton.appendChild(nextIcon);
2216
+ nextButton.disabled = Boolean(max && addMonths(viewDate, 1) > startOfMonth(max));
2217
+ const monthLabel = el("div", px("calendar-month"));
2218
+ monthLabel.textContent = viewDate.toLocaleDateString(void 0, { month: "long", year: "numeric" });
2219
+ prevButton.addEventListener("click", () => {
2220
+ viewDate = addMonths(viewDate, -1);
2221
+ renderCalendar();
2222
+ });
2223
+ nextButton.addEventListener("click", () => {
2224
+ viewDate = addMonths(viewDate, 1);
2225
+ renderCalendar();
2226
+ });
2227
+ header.append(prevButton, monthLabel, nextButton);
2228
+ const weekdaysRow = el("div", px("calendar-weekdays"));
2229
+ for (const label of buildWeekdayLabels(weekStartsOn)) {
2230
+ const cell = el("div", px("calendar-weekday"));
2231
+ cell.textContent = label;
2232
+ weekdaysRow.appendChild(cell);
2233
+ }
2234
+ const grid = el("div", px("calendar-grid"));
2235
+ const today = /* @__PURE__ */ new Date();
2236
+ for (const { date, inCurrentMonth } of getCalendarDays(viewDate, weekStartsOn)) {
2237
+ const dayButton = el("button", px("calendar-day"));
2238
+ dayButton.type = "button";
2239
+ dayButton.textContent = String(date.getDate());
2240
+ dayButton.disabled = isDisabledDate(date);
2241
+ dayButton.tabIndex = isSameDay(date, focusedDate) ? 0 : -1;
2242
+ if (!inCurrentMonth) dayButton.classList.add(px("calendar-day-outside"));
2243
+ if (isSameDay(date, today)) dayButton.classList.add(px("calendar-day-today"));
2244
+ const currentValue = value;
2245
+ if (currentValue && !isRange(currentValue) && isSameDay(date, currentValue)) {
2246
+ dayButton.classList.add(px("calendar-day-selected"));
2247
+ dayButton.setAttribute("aria-selected", "true");
2248
+ }
2249
+ if (currentValue && isRange(currentValue)) {
2250
+ if (isSameDay(date, currentValue.from)) dayButton.classList.add(px("calendar-day-range-start"), px("calendar-day-selected"));
2251
+ if (currentValue.to && isSameDay(date, currentValue.to)) dayButton.classList.add(px("calendar-day-range-end"), px("calendar-day-selected"));
2252
+ if (currentValue.to && date > currentValue.from && date < currentValue.to) dayButton.classList.add(px("calendar-day-in-range"));
2253
+ }
2254
+ dayButton.addEventListener("click", () => {
2255
+ focusedDate = date;
2256
+ handleDayClick(date);
2257
+ });
2258
+ dayButton.addEventListener("mouseenter", () => updateHoverPreview(date));
2259
+ dayButtonsByTime.set(new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(), dayButton);
2260
+ grid.appendChild(dayButton);
2261
+ }
2262
+ calendar2.append(header, weekdaysRow, grid);
2263
+ if (showTodayButton || showClearButton) {
2264
+ const footer = el("div", px("calendar-footer"));
2265
+ if (showTodayButton) {
2266
+ const todayButton = el("button", px("calendar-footer-button"));
2267
+ todayButton.type = "button";
2268
+ todayButton.textContent = "Today";
2269
+ todayButton.addEventListener("click", () => {
2270
+ viewDate = startOfMonth(/* @__PURE__ */ new Date());
2271
+ focusedDate = /* @__PURE__ */ new Date();
2272
+ renderCalendar();
2273
+ focusDayButton(focusedDate);
2274
+ });
2275
+ footer.appendChild(todayButton);
2276
+ }
2277
+ if (showClearButton) {
2278
+ const clearFooterButton = el("button", px("calendar-footer-button"));
2279
+ clearFooterButton.type = "button";
2280
+ clearFooterButton.textContent = "Clear";
2281
+ clearFooterButton.addEventListener("click", () => {
2282
+ value = void 0;
2283
+ rangeAnchor = null;
2284
+ input.value = "";
2285
+ syncClearButton();
2286
+ onChange?.(void 0);
2287
+ renderCalendar();
2288
+ });
2289
+ footer.appendChild(clearFooterButton);
2290
+ }
2291
+ calendar2.appendChild(footer);
2292
+ }
2293
+ }
2294
+ function handleKeydown(event) {
2295
+ const deltas = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };
2296
+ if (event.key in deltas) {
2297
+ event.preventDefault();
2298
+ focusedDate = addDays(focusedDate, deltas[event.key]);
2299
+ if (focusedDate.getMonth() !== viewDate.getMonth() || focusedDate.getFullYear() !== viewDate.getFullYear()) viewDate = startOfMonth(focusedDate);
2300
+ renderCalendar();
2301
+ focusDayButton(focusedDate);
2302
+ } else if (event.key === "PageUp") {
2303
+ event.preventDefault();
2304
+ viewDate = addMonths(viewDate, -1);
2305
+ renderCalendar();
2306
+ } else if (event.key === "PageDown") {
2307
+ event.preventDefault();
2308
+ viewDate = addMonths(viewDate, 1);
2309
+ renderCalendar();
2310
+ } else if (event.key === "Enter" || event.key === " ") {
2311
+ event.preventDefault();
2312
+ handleDayClick(focusedDate);
2313
+ }
2314
+ }
2315
+ function open() {
2316
+ if (isOpen || input.disabled) return;
2317
+ isOpen = true;
2318
+ viewDate = (isRange(value) ? value.from : value) ?? viewDate;
2319
+ focusedDate = viewDate;
2320
+ document.body.appendChild(calendar2);
2321
+ calendar2.classList.remove(px("hidden"));
2322
+ renderCalendar();
2323
+ positionFloating(wrap, calendar2, "bottom");
2324
+ calendar2.addEventListener("keydown", handleKeydown);
2325
+ unbindOutside = onClickOutside([wrap, calendar2], close);
2326
+ unbindEscape = onEscapeKey(close);
2327
+ }
2328
+ function close() {
2329
+ if (!isOpen) return;
2330
+ isOpen = false;
2331
+ rangeAnchor = null;
2332
+ calendar2.classList.add(px("hidden"));
2333
+ calendar2.removeEventListener("keydown", handleKeydown);
2334
+ calendar2.remove();
2335
+ unbindOutside?.();
2336
+ unbindOutside = null;
2337
+ unbindEscape?.();
2338
+ unbindEscape = null;
2339
+ }
2340
+ const toggleListener = () => isOpen ? close() : open();
2341
+ input.addEventListener("click", toggleListener);
2342
+ calendarIcon.addEventListener("click", toggleListener);
2343
+ clearButton.addEventListener("click", (event) => {
2344
+ event.stopPropagation();
2345
+ value = void 0;
2346
+ rangeAnchor = null;
2347
+ input.value = "";
2348
+ syncClearButton();
2349
+ onChange?.(void 0);
2350
+ if (isOpen) renderCalendar();
2351
+ });
2352
+ const api = {
2353
+ getValue() {
2354
+ return value;
2355
+ },
2356
+ setValue(newValue) {
2357
+ value = newValue;
2358
+ input.value = formatValue(newValue);
2359
+ syncClearButton();
2360
+ },
2361
+ open,
2362
+ close,
2363
+ isOpen() {
2364
+ return isOpen;
2365
+ },
2366
+ destroy() {
2367
+ input.removeEventListener("click", toggleListener);
2368
+ calendarIcon.removeEventListener("click", toggleListener);
2369
+ unbindOutside?.();
2370
+ unbindEscape?.();
2371
+ calendar2.removeEventListener("keydown", handleKeydown);
2372
+ calendar2.remove();
2373
+ root.remove();
2374
+ }
2375
+ };
2376
+ return attachController(root, api);
2377
+ }
2378
+
2379
+ // src/components/input/FileDropzone.ts
2380
+ var dropzoneCss = `
2381
+ .linteau-dropzone { border: 1px dashed var(--linteau-border); border-radius: var(--linteau-radius-lg); padding: 24px; display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; cursor: pointer; color: var(--linteau-muted-foreground); transition: border-color 120ms ease, background 120ms ease }
2382
+ .linteau-dropzone:hover, .linteau-dropzone-active { border-color: var(--linteau-ring); background: var(--linteau-accent) }
2383
+ .linteau-dropzone-disabled { opacity: 0.5; cursor: not-allowed }
2384
+ .linteau-dropzone-label { font-size: 12px }
2385
+ `;
2386
+ function FileDropzone(options) {
2387
+ assertDom("FileDropzone");
2388
+ ensureComponentStyles("dropzone", dropzoneCss);
2389
+ const disabled = options.disabled ?? false;
2390
+ const root = el("div", cx(px("dropzone"), disabled && px("dropzone-disabled")));
2391
+ const icon = Icon(icons.upload, 22, { className: px("icon") });
2392
+ if (icon) root.appendChild(icon);
2393
+ const label = el("div", px("dropzone-label"));
2394
+ label.textContent = options.label ?? "Drop files here or click to browse";
2395
+ root.appendChild(label);
2396
+ const input = document.createElement("input");
2397
+ input.type = "file";
2398
+ input.style.display = "none";
2399
+ input.multiple = options.multiple ?? false;
2400
+ if (options.accept) input.accept = options.accept;
2401
+ input.disabled = disabled;
2402
+ function emit(fileList) {
2403
+ if (!fileList || fileList.length === 0) return;
2404
+ options.onFiles(Array.from(fileList));
2405
+ }
2406
+ const clickListener = () => {
2407
+ if (!disabled) input.click();
2408
+ };
2409
+ const changeListener = () => emit(input.files);
2410
+ const dragOverListener = (event) => {
2411
+ event.preventDefault();
2412
+ if (!disabled) root.classList.add(px("dropzone-active"));
2413
+ };
2414
+ const dragLeaveListener = () => root.classList.remove(px("dropzone-active"));
2415
+ const dropListener = (event) => {
2416
+ event.preventDefault();
2417
+ root.classList.remove(px("dropzone-active"));
2418
+ if (!disabled) emit(event.dataTransfer?.files ?? null);
2419
+ };
2420
+ root.addEventListener("click", clickListener);
2421
+ root.addEventListener("dragover", dragOverListener);
2422
+ root.addEventListener("dragleave", dragLeaveListener);
2423
+ root.addEventListener("drop", dropListener);
2424
+ input.addEventListener("change", changeListener);
2425
+ root.appendChild(input);
2426
+ const api = {
2427
+ reset() {
2428
+ input.value = "";
2429
+ },
2430
+ destroy() {
2431
+ root.removeEventListener("click", clickListener);
2432
+ root.removeEventListener("dragover", dragOverListener);
2433
+ root.removeEventListener("dragleave", dragLeaveListener);
2434
+ root.removeEventListener("drop", dropListener);
2435
+ input.removeEventListener("change", changeListener);
2436
+ root.remove();
2437
+ }
2438
+ };
2439
+ return attachController(root, api);
2440
+ }
2441
+
2442
+ // src/components/layout/Card.ts
2443
+ var cardCss = `
2444
+ .linteau-card { border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-lg); background: var(--linteau-card); color: var(--linteau-card-foreground); overflow: hidden }
2445
+ .linteau-card-header { padding: 14px 16px; border-bottom: 1px solid var(--linteau-border); font-weight: 600 }
2446
+ .linteau-card-body { padding: 16px }
2447
+ .linteau-card-footer { padding: 12px 16px; border-top: 1px solid var(--linteau-border) }
2448
+ `;
2449
+ function Card(options = {}) {
2450
+ assertDom("Card");
2451
+ ensureComponentStyles("card", cardCss);
2452
+ const root = el("div", px("card"));
2453
+ const headerEl = el("div", px("card-header"));
2454
+ const body = el("div", px("card-body"));
2455
+ const footerEl = el("div", px("card-footer"));
2456
+ function fill(target, content) {
2457
+ target.replaceChildren();
2458
+ if (content instanceof HTMLElement) target.appendChild(content);
2459
+ else if (typeof content === "string") target.textContent = content;
2460
+ }
2461
+ function syncVisibility(target, content) {
2462
+ target.style.display = content == null ? "none" : "";
2463
+ }
2464
+ fill(headerEl, options.header ?? null);
2465
+ fill(footerEl, options.footer ?? null);
2466
+ syncVisibility(headerEl, options.header ?? null);
2467
+ syncVisibility(footerEl, options.footer ?? null);
2468
+ root.append(headerEl, body, footerEl);
2469
+ const api = {
2470
+ body,
2471
+ setHeader(content) {
2472
+ fill(headerEl, content);
2473
+ syncVisibility(headerEl, content);
2474
+ },
2475
+ setFooter(content) {
2476
+ fill(footerEl, content);
2477
+ syncVisibility(footerEl, content);
2478
+ },
2479
+ destroy() {
2480
+ root.remove();
2481
+ }
2482
+ };
2483
+ return attachController(root, api);
2484
+ }
2485
+
2486
+ // src/components/layout/Section.ts
2487
+ var sectionCss = `
2488
+ .linteau-section { border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-lg); background: var(--linteau-muted); overflow: hidden; width: 100% }
2489
+ .linteau-section-header { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--linteau-border); color: var(--linteau-muted-foreground); cursor: default }
2490
+ .linteau-section-header.linteau-section-collapsible { cursor: pointer }
2491
+ .linteau-section-header h3 { margin: 0; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600; flex: 1 }
2492
+ .linteau-section-actions { display: flex; align-items: center; gap: 6px }
2493
+ .linteau-section-body { padding: 12px; display: flex; flex-direction: column; gap: 12px }
2494
+ .linteau-section-body.linteau-collapsed { display: none }
2495
+ .linteau-section-chevron { transition: transform 150ms ease }
2496
+ .linteau-section-chevron.linteau-collapsed { transform: rotate(-90deg) }
2497
+ `;
2498
+ function Section(title, options = {}) {
2499
+ assertDom("Section");
2500
+ ensureComponentStyles("section", sectionCss);
2501
+ const { icon, collapsible = false, defaultCollapsed = false, actions = [] } = options;
2502
+ let collapsed = defaultCollapsed;
2503
+ const root = el("div", px("section"));
2504
+ const header = el("div", cx(px("section-header"), collapsible && px("section-collapsible")));
2505
+ const titleEl = document.createElement("h3");
2506
+ titleEl.textContent = title;
2507
+ const body = el("div", cx(px("section-body"), collapsed && px("collapsed")));
2508
+ const headerIcon = icon ? Icon(icon, 14, { className: px("icon") }) : null;
2509
+ const chevron = collapsible ? Icon(icons.chevronDown, 14, { className: cx(px("icon"), px("section-chevron"), collapsed && px("collapsed")) }) : null;
2510
+ const actionsWrap = el("div", px("section-actions"));
2511
+ actionsWrap.append(...actions);
2512
+ if (headerIcon) header.appendChild(headerIcon);
2513
+ header.appendChild(titleEl);
2514
+ if (actions.length) header.appendChild(actionsWrap);
2515
+ if (chevron) header.appendChild(chevron);
2516
+ root.append(header, body);
2517
+ function applyCollapsed() {
2518
+ body.classList.toggle(px("collapsed"), collapsed);
2519
+ chevron?.classList.toggle(px("collapsed"), collapsed);
2520
+ }
2521
+ const clickListener = () => {
2522
+ collapsed = !collapsed;
2523
+ applyCollapsed();
2524
+ };
2525
+ if (collapsible) header.addEventListener("click", clickListener);
2526
+ const api = {
2527
+ header,
2528
+ body,
2529
+ setTitle(newTitle) {
2530
+ titleEl.textContent = newTitle;
2531
+ },
2532
+ setCollapsed(value) {
2533
+ collapsed = value;
2534
+ applyCollapsed();
2535
+ },
2536
+ isCollapsed() {
2537
+ return collapsed;
2538
+ },
2539
+ destroy() {
2540
+ if (collapsible) header.removeEventListener("click", clickListener);
2541
+ root.remove();
2542
+ }
2543
+ };
2544
+ return attachController(root, api);
2545
+ }
2546
+
2547
+ // src/components/layout/Separator.ts
2548
+ var separatorCss = `
2549
+ .linteau-separator { background: var(--linteau-border); border: none; flex-shrink: 0 }
2550
+ .linteau-separator-horizontal { width: 100%; height: 1px }
2551
+ .linteau-separator-vertical { width: 1px; height: 100% }
2552
+ `;
2553
+ function Separator(options = {}) {
2554
+ assertDom("Separator");
2555
+ ensureComponentStyles("separator", separatorCss);
2556
+ let orientation = options.orientation ?? "horizontal";
2557
+ const hr = el("hr", cx(px("separator"), px(`separator-${orientation}`)));
2558
+ hr.setAttribute("role", "separator");
2559
+ const api = {
2560
+ setOrientation(newOrientation) {
2561
+ hr.classList.remove(px(`separator-${orientation}`));
2562
+ orientation = newOrientation;
2563
+ hr.classList.add(px(`separator-${orientation}`));
2564
+ },
2565
+ destroy() {
2566
+ hr.remove();
2567
+ }
2568
+ };
2569
+ return attachController(hr, api);
2570
+ }
2571
+
2572
+ // src/components/misc/ModeCard.ts
2573
+ var modeCardCss = `
2574
+ .linteau-mode-card { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 14px; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-lg); color: var(--linteau-foreground); cursor: pointer; text-align: left; font-family: inherit; transition: border-color 120ms ease, transform 120ms ease }
2575
+ .linteau-mode-card:hover:not(.linteau-locked) { border-color: var(--linteau-ring); transform: translateY(-2px) }
2576
+ .linteau-mode-card.linteau-selected { border-color: var(--linteau-primary); box-shadow: 0 0 0 1px var(--linteau-primary) }
2577
+ .linteau-mode-card.linteau-locked { opacity: 0.45; cursor: not-allowed }
2578
+ .linteau-mode-card-icon { color: var(--linteau-foreground) }
2579
+ .linteau-mode-card-title { font-weight: 600; font-size: 13px }
2580
+ .linteau-mode-card-description { font-size: 11px; color: var(--linteau-muted-foreground) }
2581
+ `;
2582
+ function ModeCard(options) {
2583
+ assertDom("ModeCard");
2584
+ ensureComponentStyles("mode-card", modeCardCss);
2585
+ let locked = options.locked ?? false;
2586
+ let selected = options.selected ?? false;
2587
+ const lockedMessage = options.lockedMessage ?? "Coming in the next update";
2588
+ const root = el("button", px("mode-card"));
2589
+ root.type = "button";
2590
+ const iconWrap = el("div", px("mode-card-icon"));
2591
+ const textWrap = el("div", px("mode-card-text"));
2592
+ const titleEl = el("div", px("mode-card-title"));
2593
+ titleEl.textContent = options.label;
2594
+ const descEl = el("div", px("mode-card-description"));
2595
+ textWrap.append(titleEl, descEl);
2596
+ function render() {
2597
+ iconWrap.replaceChildren();
2598
+ const iconEl = Icon(locked ? icons.lock : options.icon, 20, { className: px("icon") });
2599
+ if (iconEl) iconWrap.appendChild(iconEl);
2600
+ descEl.textContent = locked ? lockedMessage : options.description;
2601
+ root.classList.toggle(px("locked"), locked);
2602
+ root.classList.toggle(px("selected"), selected);
2603
+ root.disabled = locked;
2604
+ }
2605
+ render();
2606
+ root.append(iconWrap, textWrap);
2607
+ const listener = () => {
2608
+ if (locked) return;
2609
+ options.onSelect?.();
2610
+ };
2611
+ root.addEventListener("click", listener);
2612
+ const api = {
2613
+ isSelected() {
2614
+ return selected;
2615
+ },
2616
+ setSelected(value) {
2617
+ selected = value;
2618
+ render();
2619
+ },
2620
+ setLocked(value) {
2621
+ locked = value;
2622
+ render();
2623
+ },
2624
+ destroy() {
2625
+ root.removeEventListener("click", listener);
2626
+ root.remove();
2627
+ }
2628
+ };
2629
+ return attachController(root, api);
2630
+ }
2631
+
2632
+ // src/components/navigation/Accordion.ts
2633
+ var accordionCss = `
2634
+ .linteau-accordion { display: flex; flex-direction: column; gap: 8px; width: 100% }
2635
+ .linteau-accordion-item { border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-lg); overflow: hidden }
2636
+ .linteau-accordion-header { display: flex; align-items: center; gap: 8px; width: 100%; padding: 12px; background: var(--linteau-muted); border: none; cursor: pointer; font-size: 13px; font-weight: 500; color: var(--linteau-foreground); font-family: inherit }
2637
+ .linteau-accordion-chevron { margin-left: auto; transition: transform 150ms ease }
2638
+ .linteau-accordion-chevron.linteau-open { transform: rotate(180deg) }
2639
+ .linteau-accordion-content { padding: 12px }
2640
+ .linteau-accordion-content.linteau-collapsed { display: none }
2641
+ `;
2642
+ function Accordion(options) {
2643
+ assertDom("Accordion");
2644
+ ensureComponentStyles("accordion", accordionCss);
2645
+ const multiple = options.multiple ?? false;
2646
+ let openValues = new Set(options.defaultOpen ?? []);
2647
+ const onChange = options.onChange;
2648
+ const cleanupListeners = [];
2649
+ const root = el("div", px("accordion"));
2650
+ function setOpenState(value, shouldOpen) {
2651
+ if (shouldOpen) {
2652
+ if (!multiple) openValues.clear();
2653
+ openValues.add(value);
2654
+ } else {
2655
+ openValues.delete(value);
2656
+ }
2657
+ render();
2658
+ onChange?.(Array.from(openValues));
2659
+ }
2660
+ function render() {
2661
+ root.replaceChildren();
2662
+ cleanupListeners.splice(0).forEach((fn) => fn());
2663
+ for (const item of options.items) {
2664
+ const isOpen = openValues.has(item.value);
2665
+ const itemEl = el("div", px("accordion-item"));
2666
+ const header = el("button", px("accordion-header"));
2667
+ header.type = "button";
2668
+ if (item.icon) header.appendChild(Icon(item.icon, 14, { className: px("icon") }));
2669
+ const label = document.createElement("span");
2670
+ label.textContent = item.label;
2671
+ const chevron = Icon(icons.chevronDown, 14, { className: cx(px("accordion-chevron"), isOpen && px("open")) });
2672
+ header.append(label, chevron);
2673
+ const content = el("div", cx(px("accordion-content"), !isOpen && px("collapsed")));
2674
+ content.appendChild(item.content);
2675
+ const listener = () => setOpenState(item.value, !isOpen);
2676
+ header.addEventListener("click", listener);
2677
+ cleanupListeners.push(() => header.removeEventListener("click", listener));
2678
+ itemEl.append(header, content);
2679
+ root.appendChild(itemEl);
2680
+ }
2681
+ }
2682
+ render();
2683
+ const api = {
2684
+ getOpen() {
2685
+ return Array.from(openValues);
2686
+ },
2687
+ open(value) {
2688
+ setOpenState(value, true);
2689
+ },
2690
+ close(value) {
2691
+ setOpenState(value, false);
2692
+ },
2693
+ toggle(value) {
2694
+ setOpenState(value, !openValues.has(value));
2695
+ },
2696
+ destroy() {
2697
+ cleanupListeners.splice(0).forEach((fn) => fn());
2698
+ root.remove();
2699
+ }
2700
+ };
2701
+ return attachController(root, api);
2702
+ }
2703
+
2704
+ // src/components/navigation/Breadcrumb.ts
2705
+ var breadcrumbCss = `
2706
+ .linteau-breadcrumb { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--linteau-muted-foreground) }
2707
+ .linteau-breadcrumb-item { background: transparent; border: none; color: var(--linteau-muted-foreground); cursor: pointer; font: inherit; padding: 0 }
2708
+ .linteau-breadcrumb-item:hover { color: var(--linteau-foreground) }
2709
+ .linteau-breadcrumb-current { color: var(--linteau-foreground); font-weight: 500 }
2710
+ `;
2711
+ function Breadcrumb(options) {
2712
+ assertDom("Breadcrumb");
2713
+ ensureComponentStyles("breadcrumb", breadcrumbCss);
2714
+ const nav = el("nav", px("breadcrumb"));
2715
+ nav.setAttribute("aria-label", "breadcrumb");
2716
+ const cleanupListeners = [];
2717
+ function render(items) {
2718
+ nav.replaceChildren();
2719
+ cleanupListeners.splice(0).forEach((fn) => fn());
2720
+ items.forEach((item, index) => {
2721
+ const isLast = index === items.length - 1;
2722
+ if (isLast) {
2723
+ const current = el("span", px("breadcrumb-current"));
2724
+ current.textContent = item.label;
2725
+ nav.appendChild(current);
2726
+ } else {
2727
+ const button = el("button", px("breadcrumb-item"));
2728
+ button.type = "button";
2729
+ button.textContent = item.label;
2730
+ const listener = () => item.onClick?.();
2731
+ button.addEventListener("click", listener);
2732
+ cleanupListeners.push(() => button.removeEventListener("click", listener));
2733
+ nav.appendChild(button);
2734
+ const separator = Icon(icons.chevronRight, 12, { className: px("icon") });
2735
+ if (separator) nav.appendChild(separator);
2736
+ }
2737
+ });
2738
+ }
2739
+ render(options.items);
2740
+ const api = {
2741
+ setItems(items) {
2742
+ render(items);
2743
+ },
2744
+ destroy() {
2745
+ cleanupListeners.splice(0).forEach((fn) => fn());
2746
+ nav.remove();
2747
+ }
2748
+ };
2749
+ return attachController(nav, api);
2750
+ }
2751
+
2752
+ // src/components/navigation/Pagination.ts
2753
+ var paginationCss = `
2754
+ .linteau-pagination { display: flex; align-items: center; gap: 4px }
2755
+ .linteau-pagination-item { min-width: 28px; height: 28px; padding: 0 6px; display: flex; align-items: center; justify-content: center; border-radius: var(--linteau-radius-sm); border: 1px solid transparent; background: transparent; color: var(--linteau-muted-foreground); font-size: 12px; cursor: pointer; font-family: inherit }
2756
+ .linteau-pagination-item:hover:not(:disabled) { background: var(--linteau-accent) }
2757
+ .linteau-pagination-item:disabled { opacity: 0.4; cursor: not-allowed }
2758
+ .linteau-pagination-item-active { background: var(--linteau-primary); color: var(--linteau-primary-foreground) }
2759
+ .linteau-pagination-ellipsis { color: var(--linteau-muted-foreground); padding: 0 4px }
2760
+ `;
2761
+ function buildRange(current, pageCount, siblingCount) {
2762
+ const totalVisible = siblingCount * 2 + 5;
2763
+ if (pageCount <= totalVisible) return Array.from({ length: pageCount }, (_, i) => i + 1);
2764
+ const leftSibling = Math.max(current - siblingCount, 1);
2765
+ const rightSibling = Math.min(current + siblingCount, pageCount);
2766
+ const showLeftEllipsis = leftSibling > 2;
2767
+ const showRightEllipsis = rightSibling < pageCount - 1;
2768
+ const range = [1];
2769
+ if (showLeftEllipsis) range.push("ellipsis");
2770
+ for (let page = Math.max(leftSibling, 2); page <= Math.min(rightSibling, pageCount - 1); page++) range.push(page);
2771
+ if (showRightEllipsis) range.push("ellipsis");
2772
+ if (pageCount > 1) range.push(pageCount);
2773
+ return range;
2774
+ }
2775
+ function Pagination(options) {
2776
+ assertDom("Pagination");
2777
+ ensureComponentStyles("pagination", paginationCss);
2778
+ let page = options.page ?? options.defaultPage ?? 1;
2779
+ let pageCount = options.pageCount;
2780
+ const siblingCount = options.siblingCount ?? 1;
2781
+ const onChange = options.onChange;
2782
+ const cleanupListeners = [];
2783
+ const root = el("div", px("pagination"));
2784
+ root.setAttribute("role", "navigation");
2785
+ function goTo(newPage) {
2786
+ page = Math.max(1, Math.min(pageCount, newPage));
2787
+ render();
2788
+ onChange?.(page);
2789
+ }
2790
+ function render() {
2791
+ root.replaceChildren();
2792
+ cleanupListeners.splice(0).forEach((fn) => fn());
2793
+ const prev = el("button", px("pagination-item"));
2794
+ prev.type = "button";
2795
+ const prevIcon = Icon(icons.chevronLeft, 14);
2796
+ if (prevIcon) prev.appendChild(prevIcon);
2797
+ prev.disabled = page <= 1;
2798
+ const prevListener = () => goTo(page - 1);
2799
+ prev.addEventListener("click", prevListener);
2800
+ cleanupListeners.push(() => prev.removeEventListener("click", prevListener));
2801
+ root.appendChild(prev);
2802
+ for (const entry of buildRange(page, pageCount, siblingCount)) {
2803
+ if (entry === "ellipsis") {
2804
+ root.appendChild(el("span", px("pagination-ellipsis")));
2805
+ continue;
2806
+ }
2807
+ const button = el("button", cx(px("pagination-item"), entry === page && px("pagination-item-active")));
2808
+ button.type = "button";
2809
+ button.textContent = String(entry);
2810
+ const listener = () => goTo(entry);
2811
+ button.addEventListener("click", listener);
2812
+ cleanupListeners.push(() => button.removeEventListener("click", listener));
2813
+ root.appendChild(button);
2814
+ }
2815
+ const next = el("button", px("pagination-item"));
2816
+ next.type = "button";
2817
+ const nextIcon = Icon(icons.chevronRight, 14);
2818
+ if (nextIcon) next.appendChild(nextIcon);
2819
+ next.disabled = page >= pageCount;
2820
+ const nextListener = () => goTo(page + 1);
2821
+ next.addEventListener("click", nextListener);
2822
+ cleanupListeners.push(() => next.removeEventListener("click", nextListener));
2823
+ root.appendChild(next);
2824
+ }
2825
+ render();
2826
+ const api = {
2827
+ getPage() {
2828
+ return page;
2829
+ },
2830
+ setPage(newPage) {
2831
+ page = Math.max(1, Math.min(pageCount, newPage));
2832
+ render();
2833
+ },
2834
+ setPageCount(newPageCount) {
2835
+ pageCount = newPageCount;
2836
+ render();
2837
+ },
2838
+ destroy() {
2839
+ cleanupListeners.splice(0).forEach((fn) => fn());
2840
+ root.remove();
2841
+ }
2842
+ };
2843
+ return attachController(root, api);
2844
+ }
2845
+
2846
+ // src/components/navigation/Tabs.ts
2847
+ var tabsCss = `
2848
+ .linteau-tabs { display: flex; flex-direction: column; gap: 12px; width: 100% }
2849
+ .linteau-tabs-list { display: flex; gap: 4px; border-bottom: 1px solid var(--linteau-border) }
2850
+ .linteau-tabs-trigger { background: transparent; border: none; border-bottom: 2px solid transparent; padding: 8px 12px; font-size: 12px; font-weight: 500; color: var(--linteau-muted-foreground); cursor: pointer; display: flex; align-items: center; gap: 6px; font-family: inherit }
2851
+ .linteau-tabs-trigger:hover { color: var(--linteau-foreground) }
2852
+ .linteau-tabs-trigger-active { color: var(--linteau-foreground); border-bottom-color: var(--linteau-primary) }
2853
+ .linteau-tabs-panel { width: 100% }
2854
+ `;
2855
+ function Tabs(options) {
2856
+ assertDom("Tabs");
2857
+ ensureComponentStyles("tabs", tabsCss);
2858
+ let value = options.value ?? options.defaultValue ?? options.items[0]?.value ?? "";
2859
+ const onChange = options.onChange;
2860
+ const cleanupListeners = [];
2861
+ const root = el("div", px("tabs"));
2862
+ const list = el("div", px("tabs-list"));
2863
+ list.setAttribute("role", "tablist");
2864
+ const panel = el("div", px("tabs-panel"));
2865
+ function renderPanel() {
2866
+ const active = options.items.find((item) => item.value === value);
2867
+ panel.replaceChildren();
2868
+ if (active) panel.appendChild(active.content);
2869
+ }
2870
+ function renderTriggers() {
2871
+ list.replaceChildren();
2872
+ cleanupListeners.splice(0).forEach((fn) => fn());
2873
+ for (const item of options.items) {
2874
+ const trigger = el("button", cx(px("tabs-trigger"), item.value === value && px("tabs-trigger-active")));
2875
+ trigger.type = "button";
2876
+ trigger.setAttribute("role", "tab");
2877
+ if (item.icon) trigger.appendChild(Icon(item.icon, 14, { className: px("icon") }));
2878
+ const label = document.createElement("span");
2879
+ label.textContent = item.label;
2880
+ trigger.appendChild(label);
2881
+ const listener = () => {
2882
+ value = item.value;
2883
+ renderTriggers();
2884
+ renderPanel();
2885
+ onChange?.(value);
2886
+ };
2887
+ trigger.addEventListener("click", listener);
2888
+ cleanupListeners.push(() => trigger.removeEventListener("click", listener));
2889
+ list.appendChild(trigger);
2890
+ }
2891
+ }
2892
+ renderTriggers();
2893
+ renderPanel();
2894
+ root.append(list, panel);
2895
+ const api = {
2896
+ getValue() {
2897
+ return value;
2898
+ },
2899
+ setValue(newValue) {
2900
+ value = newValue;
2901
+ renderTriggers();
2902
+ renderPanel();
2903
+ },
2904
+ destroy() {
2905
+ cleanupListeners.splice(0).forEach((fn) => fn());
2906
+ root.remove();
2907
+ }
2908
+ };
2909
+ return attachController(root, api);
2910
+ }
2911
+
2912
+ // src/components/overlay/Dialog.ts
2913
+ var dialogCss = `
2914
+ .linteau-dialog-overlay { position: fixed; inset: 0; background: color-mix(in oklch, black 60%, transparent); display: flex; align-items: center; justify-content: center; z-index: 1000; backdrop-filter: blur(4px) }
2915
+ .linteau-dialog-panel { width: min(480px, 92vw); max-height: 86vh; display: flex; flex-direction: column; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-lg); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); overflow: hidden }
2916
+ .linteau-dialog-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--linteau-border) }
2917
+ .linteau-dialog-title { font-size: 14px; font-weight: 600 }
2918
+ .linteau-dialog-close { background: transparent; border: none; color: var(--linteau-muted-foreground); cursor: pointer; display: flex; padding: 4px; border-radius: var(--linteau-radius-sm) }
2919
+ .linteau-dialog-close:hover { background: var(--linteau-accent); color: var(--linteau-foreground) }
2920
+ .linteau-dialog-body { padding: 16px; overflow-y: auto }
2921
+ .linteau-dialog-footer { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--linteau-border) }
2922
+ `;
2923
+ function Dialog(options = {}) {
2924
+ assertDom("Dialog");
2925
+ ensureComponentStyles("dialog", dialogCss);
2926
+ const closeOnBackdrop = options.closeOnBackdrop ?? true;
2927
+ const closeOnEscape = options.closeOnEscape ?? true;
2928
+ let isOpen = false;
2929
+ let unlockScroll = null;
2930
+ let unbindEscape = null;
2931
+ const overlay = el("div", cx(px("dialog-overlay"), px("hidden")));
2932
+ const panel = el("div", px("dialog-panel"));
2933
+ panel.setAttribute("role", "dialog");
2934
+ panel.setAttribute("aria-modal", "true");
2935
+ const header = el("div", px("dialog-header"));
2936
+ const titleEl = el("div", px("dialog-title"));
2937
+ titleEl.textContent = options.title ?? "";
2938
+ const closeButton = el("button", px("dialog-close"));
2939
+ closeButton.type = "button";
2940
+ const closeIcon = Icon(icons.x, 16);
2941
+ if (closeIcon) closeButton.appendChild(closeIcon);
2942
+ header.append(titleEl, closeButton);
2943
+ const body = el("div", px("dialog-body"));
2944
+ if (options.content) fillContent(body, options.content);
2945
+ const footer = el("div", px("dialog-footer"));
2946
+ if (options.actions?.length) footer.append(...options.actions);
2947
+ panel.append(header, body);
2948
+ if (options.actions?.length) panel.appendChild(footer);
2949
+ overlay.appendChild(panel);
2950
+ function setOpen(value) {
2951
+ isOpen = value;
2952
+ overlay.classList.toggle(px("hidden"), !value);
2953
+ if (value) {
2954
+ unlockScroll = lockBodyScroll();
2955
+ if (closeOnEscape) unbindEscape = onEscapeKey(close);
2956
+ } else {
2957
+ unlockScroll?.();
2958
+ unlockScroll = null;
2959
+ unbindEscape?.();
2960
+ unbindEscape = null;
2961
+ }
2962
+ options.onOpenChange?.(value);
2963
+ }
2964
+ function close() {
2965
+ if (isOpen) setOpen(false);
2966
+ }
2967
+ const backdropListener = (event) => {
2968
+ if (closeOnBackdrop && event.target === overlay) close();
2969
+ };
2970
+ overlay.addEventListener("mousedown", backdropListener);
2971
+ closeButton.addEventListener("click", close);
2972
+ const api = {
2973
+ body,
2974
+ open() {
2975
+ setOpen(true);
2976
+ },
2977
+ close,
2978
+ isOpen() {
2979
+ return isOpen;
2980
+ },
2981
+ setTitle(title) {
2982
+ titleEl.textContent = title;
2983
+ },
2984
+ setContent(content) {
2985
+ fillContent(body, content);
2986
+ },
2987
+ destroy() {
2988
+ overlay.removeEventListener("mousedown", backdropListener);
2989
+ closeButton.removeEventListener("click", close);
2990
+ unlockScroll?.();
2991
+ unbindEscape?.();
2992
+ overlay.remove();
2993
+ }
2994
+ };
2995
+ return attachController(overlay, api);
2996
+ }
2997
+
2998
+ // src/components/overlay/AlertDialog.ts
2999
+ function AlertDialog(options) {
3000
+ assertDom("AlertDialog");
3001
+ let resolved = false;
3002
+ const cancelButton = Button(options.cancelText ?? "Cancel", {
3003
+ variant: "outline",
3004
+ onClick: () => {
3005
+ resolved = true;
3006
+ options.onCancel?.();
3007
+ dialog.close();
3008
+ }
3009
+ });
3010
+ const confirmButton = Button(options.confirmText ?? "Continue", {
3011
+ variant: options.destructive ? "destructive" : "default",
3012
+ onClick: () => {
3013
+ resolved = true;
3014
+ options.onConfirm?.();
3015
+ dialog.close();
3016
+ }
3017
+ });
3018
+ const dialog = Dialog({
3019
+ title: options.title,
3020
+ ...options.description !== void 0 ? { content: options.description } : {},
3021
+ actions: [cancelButton, confirmButton],
3022
+ closeOnBackdrop: false,
3023
+ closeOnEscape: true,
3024
+ onOpenChange: (open) => {
3025
+ if (!open && !resolved) options.onCancel?.();
3026
+ resolved = false;
3027
+ }
3028
+ });
3029
+ document.body.appendChild(dialog);
3030
+ return {
3031
+ open() {
3032
+ dialog.open();
3033
+ },
3034
+ close() {
3035
+ dialog.close();
3036
+ },
3037
+ isOpen() {
3038
+ return dialog.isOpen();
3039
+ },
3040
+ destroy() {
3041
+ dialog.destroy();
3042
+ }
3043
+ };
3044
+ }
3045
+
3046
+ // src/components/overlay/DropdownMenu.ts
3047
+ function isSeparator(entry) {
3048
+ return "separator" in entry;
3049
+ }
3050
+ function renderMenuEntries(container, entries, onSelect, onAfterSelect) {
3051
+ container.replaceChildren();
3052
+ for (const entry of entries) {
3053
+ if (isSeparator(entry)) {
3054
+ container.appendChild(el("div", px("menu-separator")));
3055
+ continue;
3056
+ }
3057
+ const item = el("div", cx(px("menu-item"), entry.disabled && px("menu-item-disabled"), entry.destructive && px("menu-item-destructive")));
3058
+ if (entry.icon) item.appendChild(Icon(entry.icon, 14, { className: px("icon") }));
3059
+ const label = document.createElement("span");
3060
+ label.textContent = entry.label;
3061
+ item.appendChild(label);
3062
+ item.addEventListener("click", () => {
3063
+ if (entry.disabled) return;
3064
+ onSelect(entry.value);
3065
+ onAfterSelect();
3066
+ });
3067
+ container.appendChild(item);
3068
+ }
3069
+ }
3070
+ var menuCss = `
3071
+ .linteau-menu { position: fixed; z-index: 1200; min-width: 160px; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-md); padding: 4px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4) }
3072
+ .linteau-menu-item { display: flex; align-items: center; gap: 8px; padding: 7px 8px; border-radius: var(--linteau-radius-sm); font-size: 12px; color: var(--linteau-foreground); cursor: pointer }
3073
+ .linteau-menu-item:hover:not(.linteau-menu-item-disabled) { background: var(--linteau-accent) }
3074
+ .linteau-menu-item-disabled { opacity: 0.5; cursor: not-allowed }
3075
+ .linteau-menu-item-destructive { color: var(--linteau-destructive) }
3076
+ .linteau-menu-separator { height: 1px; background: var(--linteau-border); margin: 4px 0 }
3077
+ `;
3078
+ function DropdownMenu(options) {
3079
+ assertDom("DropdownMenu");
3080
+ ensureComponentStyles("menu", menuCss);
3081
+ const { anchor, placement = "bottom" } = options;
3082
+ let items = options.items;
3083
+ let isOpen = false;
3084
+ let unbindOutside = null;
3085
+ let unbindEscape = null;
3086
+ const menu2 = el("div", cx(px("menu"), px("hidden")));
3087
+ renderMenuEntries(menu2, items, (value) => options.onSelect?.(value), close);
3088
+ function open() {
3089
+ if (isOpen) return;
3090
+ isOpen = true;
3091
+ document.body.appendChild(menu2);
3092
+ menu2.classList.remove(px("hidden"));
3093
+ positionFloating(anchor, menu2, placement);
3094
+ unbindOutside = onClickOutside([anchor, menu2], close);
3095
+ unbindEscape = onEscapeKey(close);
3096
+ }
3097
+ function close() {
3098
+ if (!isOpen) return;
3099
+ isOpen = false;
3100
+ menu2.classList.add(px("hidden"));
3101
+ menu2.remove();
3102
+ unbindOutside?.();
3103
+ unbindOutside = null;
3104
+ unbindEscape?.();
3105
+ unbindEscape = null;
3106
+ }
3107
+ function toggle() {
3108
+ if (isOpen) close();
3109
+ else open();
3110
+ }
3111
+ const anchorListener = () => toggle();
3112
+ anchor.addEventListener("click", anchorListener);
3113
+ const api = {
3114
+ open,
3115
+ close,
3116
+ toggle,
3117
+ isOpen() {
3118
+ return isOpen;
3119
+ },
3120
+ setItems(newItems) {
3121
+ items = newItems;
3122
+ renderMenuEntries(menu2, items, (value) => options.onSelect?.(value), close);
3123
+ },
3124
+ destroy() {
3125
+ anchor.removeEventListener("click", anchorListener);
3126
+ unbindOutside?.();
3127
+ unbindEscape?.();
3128
+ menu2.remove();
3129
+ }
3130
+ };
3131
+ return attachController(menu2, api);
3132
+ }
3133
+
3134
+ // src/components/overlay/ContextMenu.ts
3135
+ function ContextMenu(options) {
3136
+ assertDom("ContextMenu");
3137
+ ensureComponentStyles("menu", menuCss);
3138
+ const { target } = options;
3139
+ let unbindOutside = null;
3140
+ let unbindEscape = null;
3141
+ const menu2 = el("div", px("menu"));
3142
+ renderMenuEntries(menu2, options.items, (value) => options.onSelect?.(value), close);
3143
+ menu2.style.display = "none";
3144
+ function close() {
3145
+ menu2.style.display = "none";
3146
+ menu2.remove();
3147
+ unbindOutside?.();
3148
+ unbindOutside = null;
3149
+ unbindEscape?.();
3150
+ unbindEscape = null;
3151
+ }
3152
+ const contextListener = (event) => {
3153
+ event.preventDefault();
3154
+ document.body.appendChild(menu2);
3155
+ menu2.style.display = "block";
3156
+ menu2.style.position = "fixed";
3157
+ menu2.style.top = `${event.clientY}px`;
3158
+ menu2.style.left = `${event.clientX}px`;
3159
+ unbindOutside = onClickOutside(menu2, close);
3160
+ unbindEscape = onEscapeKey(close);
3161
+ };
3162
+ target.addEventListener("contextmenu", contextListener);
3163
+ return {
3164
+ destroy() {
3165
+ target.removeEventListener("contextmenu", contextListener);
3166
+ unbindOutside?.();
3167
+ unbindEscape?.();
3168
+ menu2.remove();
3169
+ }
3170
+ };
3171
+ }
3172
+
3173
+ // src/components/overlay/Drawer.ts
3174
+ var drawerCss = `
3175
+ .linteau-drawer-overlay { position: fixed; inset: 0; background: color-mix(in oklch, black 55%, transparent); z-index: 1000 }
3176
+ .linteau-drawer-panel { position: fixed; background: var(--linteau-card); border: 1px solid var(--linteau-border); display: flex; flex-direction: column; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); transition: transform 200ms ease }
3177
+ .linteau-drawer-right { top: 0; right: 0; bottom: 0; width: min(360px, 90vw); transform: translateX(0) }
3178
+ .linteau-drawer-left { top: 0; left: 0; bottom: 0; width: min(360px, 90vw); transform: translateX(0) }
3179
+ .linteau-drawer-top { top: 0; left: 0; right: 0; height: min(320px, 80vh); transform: translateY(0) }
3180
+ .linteau-drawer-bottom { bottom: 0; left: 0; right: 0; height: min(320px, 80vh); transform: translateY(0) }
3181
+ .linteau-drawer-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--linteau-border) }
3182
+ .linteau-drawer-title { font-size: 14px; font-weight: 600 }
3183
+ .linteau-drawer-close { background: transparent; border: none; color: var(--linteau-muted-foreground); cursor: pointer; display: flex; padding: 4px; border-radius: var(--linteau-radius-sm) }
3184
+ .linteau-drawer-body { padding: 16px; overflow-y: auto; flex: 1 }
3185
+ `;
3186
+ function Drawer(options = {}) {
3187
+ assertDom("Drawer");
3188
+ ensureComponentStyles("drawer", drawerCss);
3189
+ const side = options.side ?? "right";
3190
+ const closeOnBackdrop = options.closeOnBackdrop ?? true;
3191
+ const closeOnEscape = options.closeOnEscape ?? true;
3192
+ let isOpen = false;
3193
+ let unlockScroll = null;
3194
+ let unbindEscape = null;
3195
+ const overlay = el("div", cx(px("drawer-overlay"), px("hidden")));
3196
+ const panel = el("div", cx(px("drawer-panel"), px(`drawer-${side}`)));
3197
+ panel.setAttribute("role", "dialog");
3198
+ panel.setAttribute("aria-modal", "true");
3199
+ const header = el("div", px("drawer-header"));
3200
+ const titleEl = el("div", px("drawer-title"));
3201
+ titleEl.textContent = options.title ?? "";
3202
+ const closeButton = el("button", px("drawer-close"));
3203
+ closeButton.type = "button";
3204
+ const closeIcon = Icon(icons.x, 16);
3205
+ if (closeIcon) closeButton.appendChild(closeIcon);
3206
+ header.append(titleEl, closeButton);
3207
+ const body = el("div", px("drawer-body"));
3208
+ if (options.content) fillContent(body, options.content);
3209
+ panel.append(header, body);
3210
+ overlay.appendChild(panel);
3211
+ function setOpen(value) {
3212
+ isOpen = value;
3213
+ overlay.classList.toggle(px("hidden"), !value);
3214
+ if (value) {
3215
+ unlockScroll = lockBodyScroll();
3216
+ if (closeOnEscape) unbindEscape = onEscapeKey(close);
3217
+ } else {
3218
+ unlockScroll?.();
3219
+ unlockScroll = null;
3220
+ unbindEscape?.();
3221
+ unbindEscape = null;
3222
+ }
3223
+ options.onOpenChange?.(value);
3224
+ }
3225
+ function close() {
3226
+ if (isOpen) setOpen(false);
3227
+ }
3228
+ const backdropListener = (event) => {
3229
+ if (closeOnBackdrop && event.target === overlay) close();
3230
+ };
3231
+ overlay.addEventListener("mousedown", backdropListener);
3232
+ closeButton.addEventListener("click", close);
3233
+ const api = {
3234
+ body,
3235
+ open() {
3236
+ setOpen(true);
3237
+ },
3238
+ close,
3239
+ isOpen() {
3240
+ return isOpen;
3241
+ },
3242
+ setTitle(title) {
3243
+ titleEl.textContent = title;
3244
+ },
3245
+ setContent(content) {
3246
+ fillContent(body, content);
3247
+ },
3248
+ destroy() {
3249
+ overlay.removeEventListener("mousedown", backdropListener);
3250
+ closeButton.removeEventListener("click", close);
3251
+ unlockScroll?.();
3252
+ unbindEscape?.();
3253
+ overlay.remove();
3254
+ }
3255
+ };
3256
+ return attachController(overlay, api);
3257
+ }
3258
+
3259
+ // src/components/overlay/Popover.ts
3260
+ var popoverCss = `
3261
+ .linteau-popover { position: fixed; z-index: 1200; background: var(--linteau-card); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-md); padding: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4) }
3262
+ `;
3263
+ function Popover(options) {
3264
+ assertDom("Popover");
3265
+ ensureComponentStyles("popover", popoverCss);
3266
+ const { anchor, placement = "bottom", closeOnClickOutside = true } = options;
3267
+ let isOpen = false;
3268
+ let unbindOutside = null;
3269
+ let unbindEscape = null;
3270
+ const panel = el("div", cx(px("popover"), px("hidden")));
3271
+ fillContent(panel, options.content);
3272
+ function open() {
3273
+ if (isOpen) return;
3274
+ isOpen = true;
3275
+ document.body.appendChild(panel);
3276
+ panel.classList.remove(px("hidden"));
3277
+ positionFloating(anchor, panel, placement);
3278
+ if (closeOnClickOutside) unbindOutside = onClickOutside([anchor, panel], close);
3279
+ unbindEscape = onEscapeKey(close);
3280
+ }
3281
+ function close() {
3282
+ if (!isOpen) return;
3283
+ isOpen = false;
3284
+ panel.classList.add(px("hidden"));
3285
+ panel.remove();
3286
+ unbindOutside?.();
3287
+ unbindOutside = null;
3288
+ unbindEscape?.();
3289
+ unbindEscape = null;
3290
+ }
3291
+ function toggle() {
3292
+ if (isOpen) close();
3293
+ else open();
3294
+ }
3295
+ const anchorListener = () => toggle();
3296
+ anchor.addEventListener("click", anchorListener);
3297
+ const api = {
3298
+ open,
3299
+ close,
3300
+ toggle,
3301
+ isOpen() {
3302
+ return isOpen;
3303
+ },
3304
+ setContent(content) {
3305
+ fillContent(panel, content);
3306
+ },
3307
+ destroy() {
3308
+ anchor.removeEventListener("click", anchorListener);
3309
+ unbindOutside?.();
3310
+ unbindEscape?.();
3311
+ panel.remove();
3312
+ }
3313
+ };
3314
+ return attachController(panel, api);
3315
+ }
3316
+
3317
+ // src/components/radio/RadioGroup.ts
3318
+ var radioCss = `
3319
+ .linteau-radio-group { display: flex; flex-direction: column; gap: 8px }
3320
+ .linteau-radio-row { display: flex; align-items: center; gap: 8px; cursor: pointer }
3321
+ .linteau-radio-row.linteau-disabled { opacity: 0.5; cursor: not-allowed }
3322
+ .linteau-radio { width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--linteau-border); background: var(--linteau-secondary); display: flex; align-items: center; justify-content: center; flex-shrink: 0 }
3323
+ .linteau-radio.linteau-checked { border-color: var(--linteau-primary) }
3324
+ .linteau-radio-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--linteau-primary); opacity: 0; transition: opacity 100ms ease }
3325
+ .linteau-radio.linteau-checked .linteau-radio-dot { opacity: 1 }
3326
+ `;
3327
+ function RadioGroup(options) {
3328
+ assertDom("RadioGroup");
3329
+ ensureComponentStyles("radio", radioCss);
3330
+ let value = options.value ?? options.defaultValue;
3331
+ const groupDisabled = options.disabled ?? false;
3332
+ const onChange = options.onChange;
3333
+ const cleanupListeners = [];
3334
+ const root = el("div", px("radio-group"));
3335
+ root.setAttribute("role", "radiogroup");
3336
+ function render() {
3337
+ root.replaceChildren();
3338
+ cleanupListeners.splice(0).forEach((fn) => fn());
3339
+ for (const item of options.items) {
3340
+ const itemDisabled = groupDisabled || (item.disabled ?? false);
3341
+ const row = el("div", cx(px("radio-row"), itemDisabled && px("disabled")));
3342
+ const dot = el("div", cx(px("radio"), item.value === value && px("checked")));
3343
+ dot.appendChild(el("div", px("radio-dot")));
3344
+ const label = document.createElement("span");
3345
+ label.textContent = item.label;
3346
+ row.append(dot, label);
3347
+ const listener = () => {
3348
+ if (itemDisabled) return;
3349
+ value = item.value;
3350
+ render();
3351
+ onChange?.(item.value);
3352
+ };
3353
+ row.addEventListener("click", listener);
3354
+ cleanupListeners.push(() => row.removeEventListener("click", listener));
3355
+ root.appendChild(row);
3356
+ }
3357
+ }
3358
+ render();
3359
+ const api = {
3360
+ getValue() {
3361
+ return value;
3362
+ },
3363
+ setValue(newValue) {
3364
+ value = newValue;
3365
+ render();
3366
+ },
3367
+ destroy() {
3368
+ cleanupListeners.splice(0).forEach((fn) => fn());
3369
+ root.remove();
3370
+ }
3371
+ };
3372
+ return attachController(root, api);
3373
+ }
3374
+
3375
+ // src/components/select/Select.ts
3376
+ var selectCss = `
3377
+ .linteau-select-wrap { position: relative }
3378
+ .linteau-select { appearance: none; background: var(--linteau-secondary); color: var(--linteau-foreground); border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-sm); padding: 6px 28px 6px 10px; font-size: 12px; font-family: inherit; cursor: pointer; transition: border-color 120ms ease }
3379
+ .linteau-select:disabled { opacity: 0.5; cursor: not-allowed }
3380
+ .linteau-select:focus { outline: none; border-color: var(--linteau-ring); box-shadow: 0 0 0 2px color-mix(in oklch, var(--linteau-ring) 20%, transparent) }
3381
+ .linteau-select-chevron { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); pointer-events: none }
3382
+ `;
3383
+ function Select(options) {
3384
+ assertDom("Select");
3385
+ ensureComponentStyles("field-base", fieldBaseCss);
3386
+ ensureComponentStyles("select", selectCss);
3387
+ let items = options.options;
3388
+ let value = options.value ?? options.defaultValue ?? items[0]?.value ?? "";
3389
+ const onChange = options.onChange;
3390
+ const root = el("div", px("field-row"));
3391
+ const labelWrap = el("div", px("field-label"));
3392
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
3393
+ const labelText = document.createElement("span");
3394
+ labelText.textContent = options.label;
3395
+ labelWrap.appendChild(labelText);
3396
+ const selectWrap = el("div", px("select-wrap"));
3397
+ const select = el("select", px("select"));
3398
+ select.disabled = options.disabled ?? false;
3399
+ function renderOptions() {
3400
+ select.replaceChildren();
3401
+ if (options.placeholder) {
3402
+ const placeholderOption = document.createElement("option");
3403
+ placeholderOption.value = "";
3404
+ placeholderOption.textContent = options.placeholder;
3405
+ placeholderOption.disabled = true;
3406
+ placeholderOption.selected = !value;
3407
+ select.appendChild(placeholderOption);
3408
+ }
3409
+ for (const item of items) {
3410
+ const optionEl = document.createElement("option");
3411
+ optionEl.value = item.value;
3412
+ optionEl.textContent = item.label;
3413
+ optionEl.disabled = item.disabled ?? false;
3414
+ optionEl.selected = item.value === value;
3415
+ select.appendChild(optionEl);
3416
+ }
3417
+ }
3418
+ renderOptions();
3419
+ const chevron = Icon(icons.chevronDown, 12, { className: px("select-chevron") });
3420
+ selectWrap.append(select, chevron);
3421
+ const listener = () => {
3422
+ value = select.value;
3423
+ onChange?.(value);
3424
+ };
3425
+ select.addEventListener("change", listener);
3426
+ root.append(labelWrap, selectWrap);
3427
+ const api = {
3428
+ getValue() {
3429
+ return value;
3430
+ },
3431
+ setValue(newValue) {
3432
+ value = newValue;
3433
+ select.value = newValue;
3434
+ },
3435
+ setOptions(newOptions) {
3436
+ items = newOptions;
3437
+ renderOptions();
3438
+ },
3439
+ setDisabled(disabled) {
3440
+ select.disabled = disabled;
3441
+ },
3442
+ destroy() {
3443
+ select.removeEventListener("change", listener);
3444
+ root.remove();
3445
+ }
3446
+ };
3447
+ return attachController(root, api);
3448
+ }
3449
+
3450
+ // src/components/slider/Slider.ts
3451
+ var sliderCss = `
3452
+ .linteau-slider { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; border-radius: 999px; background: linear-gradient(to right, var(--linteau-primary) 0%, var(--linteau-primary) var(--linteau-fill, 0%), var(--linteau-secondary) var(--linteau-fill, 0%), var(--linteau-secondary) 100%); outline: none; transition: background 120ms ease }
3453
+ .linteau-slider:disabled { opacity: 0.5 }
3454
+ .linteau-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: var(--linteau-primary); border: 2px solid var(--linteau-card); box-shadow: 0 0 0 1px var(--linteau-border); cursor: pointer; transition: transform 120ms ease }
3455
+ .linteau-slider::-webkit-slider-thumb:hover { transform: scale(1.15) }
3456
+ .linteau-slider::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: var(--linteau-primary); border: 2px solid var(--linteau-card); cursor: pointer }
3457
+ `;
3458
+ function Slider(options) {
3459
+ assertDom("Slider");
3460
+ ensureComponentStyles("field-base", fieldBaseCss);
3461
+ ensureComponentStyles("slider", sliderCss);
3462
+ const { min, max, step } = options;
3463
+ let value = options.value ?? options.defaultValue ?? min;
3464
+ const format = options.format ?? ((v) => v.toString());
3465
+ const onChange = options.onChange;
3466
+ const root = el("div", px("field"));
3467
+ const header = el("div", px("field-header"));
3468
+ const labelWrap = el("div", px("field-label"));
3469
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
3470
+ const labelText = document.createElement("span");
3471
+ labelText.textContent = options.label;
3472
+ labelWrap.appendChild(labelText);
3473
+ const valueEl = el("span", px("field-value"));
3474
+ valueEl.textContent = format(value);
3475
+ header.append(labelWrap, valueEl);
3476
+ const input = el("input", px("slider"));
3477
+ input.type = "range";
3478
+ input.min = String(min);
3479
+ input.max = String(max);
3480
+ input.step = String(step);
3481
+ input.value = String(value);
3482
+ input.disabled = options.disabled ?? false;
3483
+ function updateFill(v) {
3484
+ const percent = (v - min) / (max - min) * 100;
3485
+ input.style.setProperty("--linteau-fill", `${percent}%`);
3486
+ }
3487
+ updateFill(value);
3488
+ const listener = () => {
3489
+ value = Number(input.value);
3490
+ valueEl.textContent = format(value);
3491
+ updateFill(value);
3492
+ onChange?.(value);
3493
+ };
3494
+ input.addEventListener("input", listener);
3495
+ root.append(header, input);
3496
+ const api = {
3497
+ getValue() {
3498
+ return value;
3499
+ },
3500
+ setValue(newValue) {
3501
+ value = Math.max(min, Math.min(max, newValue));
3502
+ input.value = String(value);
3503
+ valueEl.textContent = format(value);
3504
+ updateFill(value);
3505
+ },
3506
+ setDisabled(disabled) {
3507
+ input.disabled = disabled;
3508
+ },
3509
+ destroy() {
3510
+ input.removeEventListener("input", listener);
3511
+ root.remove();
3512
+ }
3513
+ };
3514
+ return attachController(root, api);
3515
+ }
3516
+
3517
+ // src/components/slider/RangeSlider.ts
3518
+ var rangeSliderCss = `
3519
+ .linteau-range-track { position: relative; height: 20px; display: flex; align-items: center }
3520
+ .linteau-range-track::before { content: ''; position: absolute; left: 0; right: 0; height: 6px; border-radius: 999px; background: var(--linteau-secondary) }
3521
+ .linteau-range-track::after { content: ''; position: absolute; height: 6px; border-radius: 999px; background: var(--linteau-primary); left: var(--linteau-range-low, 0%); right: calc(100% - var(--linteau-range-high, 100%)) }
3522
+ .linteau-range-input { position: absolute; left: 0; right: 0; width: 100%; height: 20px; background: transparent; pointer-events: none; margin: 0 }
3523
+ .linteau-range-input::-webkit-slider-thumb { pointer-events: auto }
3524
+ .linteau-range-input::-moz-range-thumb { pointer-events: auto }
3525
+ `;
3526
+ function RangeSlider(options) {
3527
+ assertDom("RangeSlider");
3528
+ ensureComponentStyles("field-base", fieldBaseCss);
3529
+ ensureComponentStyles("slider", sliderCss);
3530
+ ensureComponentStyles("range-slider", rangeSliderCss);
3531
+ const { min, max, step } = options;
3532
+ let [low, high] = options.value ?? options.defaultValue ?? [min, max];
3533
+ const format = options.format ?? ((v) => v.toString());
3534
+ const onChange = options.onChange;
3535
+ const root = el("div", px("field"));
3536
+ const header = el("div", px("field-header"));
3537
+ const labelWrap = el("div", px("field-label"));
3538
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
3539
+ const labelText = document.createElement("span");
3540
+ labelText.textContent = options.label;
3541
+ labelWrap.appendChild(labelText);
3542
+ const valueEl = el("span", px("field-value"));
3543
+ header.append(labelWrap, valueEl);
3544
+ const track = el("div", px("range-track"));
3545
+ const inputLow = el("input", cx(px("slider"), px("range-input")));
3546
+ const inputHigh = el("input", cx(px("slider"), px("range-input")));
3547
+ for (const input of [inputLow, inputHigh]) {
3548
+ input.type = "range";
3549
+ input.min = String(min);
3550
+ input.max = String(max);
3551
+ input.step = String(step);
3552
+ input.disabled = options.disabled ?? false;
3553
+ }
3554
+ function syncDisplay() {
3555
+ valueEl.textContent = `${format(low)} - ${format(high)}`;
3556
+ inputLow.value = String(low);
3557
+ inputHigh.value = String(high);
3558
+ track.style.setProperty("--linteau-range-low", `${(low - min) / (max - min) * 100}%`);
3559
+ track.style.setProperty("--linteau-range-high", `${(high - min) / (max - min) * 100}%`);
3560
+ }
3561
+ syncDisplay();
3562
+ const lowListener = () => {
3563
+ low = Math.min(Number(inputLow.value), high);
3564
+ syncDisplay();
3565
+ onChange?.([low, high]);
3566
+ };
3567
+ const highListener = () => {
3568
+ high = Math.max(Number(inputHigh.value), low);
3569
+ syncDisplay();
3570
+ onChange?.([low, high]);
3571
+ };
3572
+ inputLow.addEventListener("input", lowListener);
3573
+ inputHigh.addEventListener("input", highListener);
3574
+ track.append(inputLow, inputHigh);
3575
+ root.append(header, track);
3576
+ const api = {
3577
+ getValue() {
3578
+ return [low, high];
3579
+ },
3580
+ setValue(value) {
3581
+ ;
3582
+ [low, high] = value;
3583
+ syncDisplay();
3584
+ },
3585
+ setDisabled(disabled) {
3586
+ inputLow.disabled = disabled;
3587
+ inputHigh.disabled = disabled;
3588
+ },
3589
+ destroy() {
3590
+ inputLow.removeEventListener("input", lowListener);
3591
+ inputHigh.removeEventListener("input", highListener);
3592
+ root.remove();
3593
+ }
3594
+ };
3595
+ return attachController(root, api);
3596
+ }
3597
+
3598
+ // src/components/switch/Switch.ts
3599
+ var switchCss = `
3600
+ .linteau-switch-row { display: flex; align-items: center; justify-content: space-between }
3601
+ .linteau-switch { width: 38px; height: 22px; border-radius: 999px; background: var(--linteau-secondary); border: 1px solid var(--linteau-border); padding: 2px; cursor: pointer; display: flex; align-items: center; transition: background 150ms ease }
3602
+ .linteau-switch:disabled { opacity: 0.5; cursor: not-allowed }
3603
+ .linteau-switch.linteau-checked { background: var(--linteau-primary) }
3604
+ .linteau-switch-thumb { width: 16px; height: 16px; border-radius: 50%; background: var(--linteau-card); transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1); transform: translateX(0) }
3605
+ .linteau-switch.linteau-checked .linteau-switch-thumb { transform: translateX(16px); background: var(--linteau-primary-foreground) }
3606
+ `;
3607
+ function Switch(options) {
3608
+ assertDom("Switch");
3609
+ ensureComponentStyles("field-base", fieldBaseCss);
3610
+ ensureComponentStyles("switch", switchCss);
3611
+ let checked = options.checked ?? options.defaultChecked ?? false;
3612
+ let disabled = options.disabled ?? false;
3613
+ const onChange = options.onChange;
3614
+ const root = el("div", px("switch-row"));
3615
+ const labelWrap = el("div", px("field-label"));
3616
+ if (options.icon) labelWrap.appendChild(Icon(options.icon, 14, { className: px("icon") }));
3617
+ const labelText = document.createElement("span");
3618
+ labelText.textContent = options.label;
3619
+ labelWrap.appendChild(labelText);
3620
+ const track = el("button", px("switch"));
3621
+ track.type = "button";
3622
+ track.setAttribute("role", "switch");
3623
+ track.disabled = disabled;
3624
+ const thumb = el("span", px("switch-thumb"));
3625
+ track.appendChild(thumb);
3626
+ function applyState() {
3627
+ track.classList.toggle(px("checked"), checked);
3628
+ track.setAttribute("aria-checked", String(checked));
3629
+ }
3630
+ applyState();
3631
+ const listener = () => {
3632
+ if (disabled) return;
3633
+ checked = !checked;
3634
+ applyState();
3635
+ onChange?.(checked);
3636
+ };
3637
+ track.addEventListener("click", listener);
3638
+ root.append(labelWrap, track);
3639
+ const api = {
3640
+ getValue() {
3641
+ return checked;
3642
+ },
3643
+ setValue(value) {
3644
+ checked = value;
3645
+ applyState();
3646
+ },
3647
+ setLabel(newLabel) {
3648
+ labelText.textContent = newLabel;
3649
+ },
3650
+ setDisabled(value) {
3651
+ disabled = value;
3652
+ track.disabled = value;
3653
+ },
3654
+ destroy() {
3655
+ track.removeEventListener("click", listener);
3656
+ root.remove();
3657
+ }
3658
+ };
3659
+ return attachController(root, api);
3660
+ }
3661
+
3662
+ // src/components/toggle/Toggle.ts
3663
+ var toggleCss = `
3664
+ .linteau-toggle {
3665
+ display: inline-flex;
3666
+ align-items: center;
3667
+ justify-content: center;
3668
+ gap: 6px;
3669
+ border-radius: var(--linteau-radius-sm);
3670
+ border: 1px solid transparent;
3671
+ background: transparent;
3672
+ color: var(--linteau-muted-foreground);
3673
+ font-size: 13px;
3674
+ font-weight: 500;
3675
+ cursor: pointer;
3676
+ font-family: inherit;
3677
+ transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
3678
+ }
3679
+ .linteau-toggle-default { padding: 8px 12px }
3680
+ .linteau-toggle-sm { padding: 5px 8px; font-size: 12px }
3681
+ .linteau-toggle:hover:not(:disabled) { background: var(--linteau-accent); color: var(--linteau-foreground) }
3682
+ .linteau-toggle:disabled { opacity: 0.5; cursor: not-allowed }
3683
+ .linteau-toggle-outline { border-color: var(--linteau-border) }
3684
+ .linteau-toggle.linteau-pressed { background: var(--linteau-accent); color: var(--linteau-foreground) }
3685
+ `;
3686
+ function Toggle(text, options = {}) {
3687
+ assertDom("Toggle");
3688
+ ensureComponentStyles("toggle", toggleCss);
3689
+ const variant = options.variant ?? "default";
3690
+ const size = options.size ?? "default";
3691
+ let pressed = options.pressed ?? options.defaultPressed ?? false;
3692
+ let disabled = options.disabled ?? false;
3693
+ const onPressedChange = options.onPressedChange;
3694
+ const button = el("button", cx(px("toggle"), px(`toggle-${size}`), variant === "outline" && px("toggle-outline")));
3695
+ button.type = "button";
3696
+ button.setAttribute("aria-pressed", String(pressed));
3697
+ button.disabled = disabled;
3698
+ if (options.icon) button.appendChild(Icon(options.icon, 14, { className: px("icon") }));
3699
+ const label = document.createElement("span");
3700
+ label.textContent = text;
3701
+ button.appendChild(label);
3702
+ function applyState() {
3703
+ button.classList.toggle(px("pressed"), pressed);
3704
+ button.setAttribute("aria-pressed", String(pressed));
3705
+ }
3706
+ applyState();
3707
+ const listener = () => {
3708
+ if (disabled) return;
3709
+ pressed = !pressed;
3710
+ applyState();
3711
+ onPressedChange?.(pressed);
3712
+ };
3713
+ button.addEventListener("click", listener);
3714
+ const api = {
3715
+ getPressed() {
3716
+ return pressed;
3717
+ },
3718
+ setPressed(value) {
3719
+ pressed = value;
3720
+ applyState();
3721
+ },
3722
+ setDisabled(value) {
3723
+ disabled = value;
3724
+ button.disabled = value;
3725
+ },
3726
+ destroy() {
3727
+ button.removeEventListener("click", listener);
3728
+ button.remove();
3729
+ }
3730
+ };
3731
+ return attachController(button, api);
3732
+ }
3733
+
3734
+ // src/components/toggle/ToggleGroup.ts
3735
+ var toggleGroupCss = `
3736
+ .linteau-toggle-group { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--linteau-border); border-radius: var(--linteau-radius-sm) }
3737
+ `;
3738
+ function ToggleGroup(options) {
3739
+ assertDom("ToggleGroup");
3740
+ ensureComponentStyles("toggle-group", toggleGroupCss);
3741
+ const type = options.type ?? "single";
3742
+ const variant = options.variant ?? "outline";
3743
+ const size = options.size ?? "default";
3744
+ const groupDisabled = options.disabled ?? false;
3745
+ const onChange = options.onChange;
3746
+ let selected = new Set(
3747
+ Array.isArray(options.value ?? options.defaultValue) ? options.value ?? options.defaultValue : options.value !== void 0 || options.defaultValue !== void 0 ? [options.value ?? options.defaultValue] : []
3748
+ );
3749
+ const root = el("div", px("toggle-group"));
3750
+ root.setAttribute("role", "group");
3751
+ const toggles = /* @__PURE__ */ new Map();
3752
+ function currentValue() {
3753
+ if (type === "single") return Array.from(selected)[0] ?? "";
3754
+ return Array.from(selected);
3755
+ }
3756
+ function toggleItem(value) {
3757
+ if (type === "single") {
3758
+ selected = selected.has(value) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([value]);
3759
+ } else {
3760
+ if (selected.has(value)) selected.delete(value);
3761
+ else selected.add(value);
3762
+ }
3763
+ for (const [itemValue, toggle] of toggles) toggle.setPressed(selected.has(itemValue));
3764
+ onChange?.(currentValue());
3765
+ }
3766
+ for (const item of options.items) {
3767
+ const toggle = Toggle(item.label, {
3768
+ variant,
3769
+ size,
3770
+ icon: item.icon,
3771
+ pressed: selected.has(item.value),
3772
+ disabled: groupDisabled || (item.disabled ?? false),
3773
+ onPressedChange: () => toggleItem(item.value)
3774
+ });
3775
+ toggles.set(item.value, toggle);
3776
+ root.appendChild(toggle);
3777
+ }
3778
+ const api = {
3779
+ getValue() {
3780
+ return currentValue();
3781
+ },
3782
+ setValue(value) {
3783
+ selected = new Set(Array.isArray(value) ? value : [value]);
3784
+ for (const [itemValue, toggle] of toggles) toggle.setPressed(selected.has(itemValue));
3785
+ },
3786
+ destroy() {
3787
+ for (const toggle of toggles.values()) toggle.destroy();
3788
+ root.remove();
3789
+ }
3790
+ };
3791
+ return attachController(root, api);
3792
+ }
3793
+
3794
+ // src/components/tooltip/Tooltip.ts
3795
+ var tooltipCss = `
3796
+ .linteau-tooltip { position: fixed; z-index: 1300; background: var(--linteau-foreground); color: var(--linteau-background); font-size: 11px; font-weight: 500; padding: 5px 8px; border-radius: var(--linteau-radius-sm); pointer-events: none; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3) }
3797
+ `;
3798
+ function Tooltip(anchor, content, options = {}) {
3799
+ assertDom("Tooltip");
3800
+ ensureComponentStyles("tooltip", tooltipCss);
3801
+ const placement = options.placement ?? "top";
3802
+ const delay = options.delay ?? 200;
3803
+ let text = content;
3804
+ let showTimer = null;
3805
+ const tooltip = el("div", px("tooltip"));
3806
+ tooltip.textContent = text;
3807
+ tooltip.style.display = "none";
3808
+ function show() {
3809
+ showTimer = setTimeout(() => {
3810
+ document.body.appendChild(tooltip);
3811
+ tooltip.style.display = "block";
3812
+ positionFloating(anchor, tooltip, placement);
3813
+ }, delay);
3814
+ }
3815
+ function hide() {
3816
+ if (showTimer) clearTimeout(showTimer);
3817
+ tooltip.style.display = "none";
3818
+ tooltip.remove();
3819
+ }
3820
+ anchor.addEventListener("mouseenter", show);
3821
+ anchor.addEventListener("mouseleave", hide);
3822
+ anchor.addEventListener("focus", show);
3823
+ anchor.addEventListener("blur", hide);
3824
+ return {
3825
+ setContent(newContent) {
3826
+ text = newContent;
3827
+ tooltip.textContent = text;
3828
+ },
3829
+ destroy() {
3830
+ hide();
3831
+ anchor.removeEventListener("mouseenter", show);
3832
+ anchor.removeEventListener("mouseleave", hide);
3833
+ anchor.removeEventListener("focus", show);
3834
+ anchor.removeEventListener("blur", hide);
3835
+ }
3836
+ };
3837
+ }
3838
+ export {
3839
+ Accordion,
3840
+ Alert,
3841
+ AlertDialog,
3842
+ Avatar,
3843
+ Badge,
3844
+ Breadcrumb,
3845
+ Button,
3846
+ ButtonGroup,
3847
+ Card,
3848
+ Checkbox,
3849
+ ChipInput,
3850
+ ColorField,
3851
+ Combobox,
3852
+ Command,
3853
+ ContextMenu,
3854
+ DatePicker,
3855
+ Dialog,
3856
+ Drawer,
3857
+ DropdownMenu,
3858
+ FileDropzone,
3859
+ HoverCard,
3860
+ Icon,
3861
+ List,
3862
+ ModeCard,
3863
+ NumberField,
3864
+ Pagination,
3865
+ Popover,
3866
+ ProgressBar,
3867
+ RadioGroup,
3868
+ RangeSlider,
3869
+ Section,
3870
+ Select,
3871
+ Separator,
3872
+ Skeleton,
3873
+ Slider,
3874
+ Spinner,
3875
+ Switch,
3876
+ Table,
3877
+ Tabs,
3878
+ TextArea,
3879
+ TextField,
3880
+ Toggle,
3881
+ ToggleGroup,
3882
+ Tooltip,
3883
+ Tree,
3884
+ accordionCss,
3885
+ alertCss,
3886
+ avatarCss,
3887
+ badgeCss,
3888
+ breadcrumbCss,
3889
+ buttonCss,
3890
+ buttonGroupCss,
3891
+ calendarCss,
3892
+ cardCss,
3893
+ checkboxCss,
3894
+ chipInputCss,
3895
+ comboboxCss,
3896
+ commandCss,
3897
+ createToastManager,
3898
+ dialogCss,
3899
+ drawerCss,
3900
+ dropzoneCss,
3901
+ getTheme,
3902
+ hoverCardCss,
3903
+ icons,
3904
+ isBrowser,
3905
+ listCss,
3906
+ menuCss,
3907
+ modeCardCss,
3908
+ onThemeChange,
3909
+ paginationCss,
3910
+ popoverCss,
3911
+ progressBarCss,
3912
+ radioCss,
3913
+ rangeSliderCss,
3914
+ renderMenuEntries,
3915
+ resetTheme,
3916
+ sectionCss,
3917
+ selectCss,
3918
+ separatorCss,
3919
+ setTheme,
3920
+ skeletonCss,
3921
+ sliderCss,
3922
+ spinnerCss,
3923
+ swatchCss,
3924
+ switchCss,
3925
+ tableCss,
3926
+ tabsCss,
3927
+ toast,
3928
+ toastCss,
3929
+ toggleCss,
3930
+ toggleGroupCss,
3931
+ tooltipCss,
3932
+ treeCss
3933
+ };
3934
+ //# sourceMappingURL=index.js.map