@nuykon/basecoat-react 0.0.0-stage → 1.0.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.
@@ -0,0 +1,872 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ attachFloating: () => attachFloating,
24
+ attachWidget: () => attachWidget,
25
+ createTypeahead: () => createTypeahead,
26
+ cx: () => cx,
27
+ dismissableLayer: () => dismissableLayer,
28
+ floatingPosition: () => floatingPosition,
29
+ focusFirst: () => focusFirst,
30
+ formReset: () => formReset,
31
+ lockScroll: () => lockScroll,
32
+ nextIndex: () => nextIndex,
33
+ percentage: () => percentage,
34
+ primitiveDefinitions: () => primitiveDefinitions,
35
+ scrollWithin: () => scrollWithin,
36
+ stylePacks: () => stylePacks
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/chart.ts
41
+ function attachCanvasChart(canvas, config) {
42
+ const doc = canvas.ownerDocument, win = doc.defaultView, ctx = canvas.getContext("2d");
43
+ if (!ctx) return () => {
44
+ };
45
+ const data = config.data ?? [], series = Object.entries(config.series ?? {}), labelKey = config.labelKey ?? "label";
46
+ const color = (value) => value.startsWith("var(") ? win.getComputedStyle(canvas).getPropertyValue(value.slice(4, -1)).trim() || "#888" : value;
47
+ const legend = doc.createElement("ul");
48
+ legend.className = "chart-legend";
49
+ if (config.legend) {
50
+ for (const [index, [key, options]] of series.entries()) {
51
+ const item = doc.createElement("li"), content = doc.createElement("span"), indicator = doc.createElement("span");
52
+ content.className = "chart-legend-item";
53
+ indicator.className = "chart-legend-indicator";
54
+ indicator.style.setProperty(
55
+ "--chart-indicator-color",
56
+ options.color ?? `var(--chart-${index + 1})`
57
+ );
58
+ content.append(indicator, doc.createTextNode(options.label ?? key));
59
+ item.append(content);
60
+ legend.append(item);
61
+ }
62
+ canvas.after(legend);
63
+ }
64
+ canvas.style.width = "100%";
65
+ canvas.style.aspectRatio = "16 / 9";
66
+ canvas.style.display = "block";
67
+ const draw = () => {
68
+ const width = Math.max(200, canvas.parentElement?.clientWidth ?? 400), height = width * 9 / 16, ratio = win.devicePixelRatio || 1;
69
+ canvas.width = Math.round(width * ratio);
70
+ canvas.height = Math.round(height * ratio);
71
+ canvas.style.height = `${height}px`;
72
+ ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
73
+ ctx.clearRect(0, 0, width, height);
74
+ const padding = { left: 30, top: 10, right: 10, bottom: 30 }, w = width - padding.left - padding.right, h = height - padding.top - padding.bottom;
75
+ const max = Math.max(
76
+ 1,
77
+ ...data.map(
78
+ (row) => config.options?.scales?.y?.stacked ? series.reduce((sum, [key]) => sum + Number(row[key] ?? 0), 0) : Math.max(...series.map(([key]) => Number(row[key] ?? 0)))
79
+ )
80
+ );
81
+ const x = (index) => padding.left + index / Math.max(1, data.length - 1) * w, y = (value) => padding.top + h - value / max * h;
82
+ ctx.font = '12px "Geist Sans",system-ui';
83
+ ctx.fillStyle = color("var(--muted-foreground)");
84
+ ctx.strokeStyle = color("var(--border)");
85
+ ctx.lineWidth = 0.5;
86
+ if (config.type !== "doughnut" && config.type !== "radar") {
87
+ for (let index = 0; index <= 4; index++) {
88
+ const top = padding.top + index * h / 4;
89
+ ctx.beginPath();
90
+ ctx.moveTo(padding.left, top);
91
+ ctx.lineTo(width - padding.right, top);
92
+ ctx.stroke();
93
+ ctx.fillText(String(Math.round(max * (4 - index) / 4)), 0, top + 4);
94
+ }
95
+ for (let index = 0; index < data.length; index++)
96
+ if (index % Math.max(1, Math.ceil(data.length / 6)) === 0)
97
+ ctx.fillText(String(data[index][labelKey] ?? ""), x(index) - 10, height - 8);
98
+ }
99
+ if (config.type === "doughnut") {
100
+ const key = series[0]?.[0] ?? "value", values = data.map((row) => Number(row[key] ?? 0)), total = values.reduce((a, b) => a + b, 0) || 1, radius = Math.min(w, h) * 0.45, cx2 = width / 2, cy = height / 2;
101
+ let start = -Math.PI / 2;
102
+ values.forEach((value, index) => {
103
+ const end = start + value / total * Math.PI * 2;
104
+ ctx.beginPath();
105
+ ctx.arc(cx2, cy, radius, start, end);
106
+ ctx.arc(cx2, cy, radius * 0.62, end, start, true);
107
+ ctx.closePath();
108
+ ctx.fillStyle = color(`var(--chart-${index % 5 + 1})`);
109
+ ctx.fill();
110
+ start = end;
111
+ });
112
+ } else if (config.type === "radar") {
113
+ const radius = Math.min(w, h) * 0.4, cx2 = width / 2, cy = height / 2;
114
+ const point = (index, scale) => {
115
+ const angle = index / Math.max(1, data.length) * Math.PI * 2 - Math.PI / 2;
116
+ return [
117
+ cx2 + Math.cos(angle) * radius * scale,
118
+ cy + Math.sin(angle) * radius * scale
119
+ ];
120
+ };
121
+ for (let ring = 1; ring <= 4; ring++) {
122
+ ctx.beginPath();
123
+ data.forEach((_, i) => {
124
+ const [x2, y2] = point(i, ring / 4);
125
+ if (i === 0) ctx.moveTo(x2, y2);
126
+ else ctx.lineTo(x2, y2);
127
+ });
128
+ ctx.closePath();
129
+ ctx.stroke();
130
+ }
131
+ series.forEach(([key, options], index) => {
132
+ ctx.beginPath();
133
+ data.forEach((row, i) => {
134
+ const [x2, y2] = point(i, Number(row[key] ?? 0) / max);
135
+ if (i === 0) ctx.moveTo(x2, y2);
136
+ else ctx.lineTo(x2, y2);
137
+ });
138
+ ctx.closePath();
139
+ ctx.strokeStyle = color(options.color ?? `var(--chart-${index + 1})`);
140
+ ctx.stroke();
141
+ ctx.globalAlpha = 0.2;
142
+ ctx.fillStyle = ctx.strokeStyle;
143
+ ctx.fill();
144
+ ctx.globalAlpha = 1;
145
+ });
146
+ ctx.fillStyle = color("var(--muted-foreground)");
147
+ data.forEach((row, i) => {
148
+ const [x2, y2] = point(i, 1.15);
149
+ ctx.fillText(String(row[labelKey] ?? ""), x2 - 15, y2);
150
+ });
151
+ } else {
152
+ const stacks2 = data.map(() => 0);
153
+ series.forEach(([key, options], seriesIndex) => {
154
+ const fill = color(options.color ?? `var(--chart-${seriesIndex + 1})`);
155
+ ctx.strokeStyle = fill;
156
+ ctx.fillStyle = fill;
157
+ ctx.lineWidth = 1.5;
158
+ if (config.type === "bar")
159
+ data.forEach((row, index) => {
160
+ const value = Number(row[key] ?? 0), stacked = config.options?.scales?.y?.stacked, group = w / Math.max(1, data.length), barWidth = group * 0.65 / (stacked ? 1 : series.length), left = padding.left + index * group + group * 0.15 + (stacked ? 0 : seriesIndex * barWidth), base = stacks2[index];
161
+ ctx.beginPath();
162
+ ctx.roundRect(
163
+ left,
164
+ y(value + base),
165
+ Math.max(1, barWidth - 2),
166
+ Math.max(0, y(base) - y(value + base)),
167
+ 3
168
+ );
169
+ ctx.fill();
170
+ if (stacked) stacks2[index] += value;
171
+ });
172
+ else {
173
+ ctx.beginPath();
174
+ data.forEach((row, index) => {
175
+ const top = y(Number(row[key] ?? 0));
176
+ if (index === 0) ctx.moveTo(x(index), top);
177
+ else if (options.dataset?.stepped) {
178
+ ctx.lineTo(x(index), y(Number(data[index - 1][key] ?? 0)));
179
+ ctx.lineTo(x(index), top);
180
+ } else ctx.lineTo(x(index), top);
181
+ });
182
+ ctx.stroke();
183
+ if (options.dataset?.fill || options.surface === "gradient") {
184
+ ctx.lineTo(x(data.length - 1), y(0));
185
+ ctx.lineTo(x(0), y(0));
186
+ ctx.closePath();
187
+ const gradient = ctx.createLinearGradient(0, 0, 0, height);
188
+ gradient.addColorStop(0, fill);
189
+ gradient.addColorStop(1, "transparent");
190
+ ctx.globalAlpha = 0.3;
191
+ ctx.fillStyle = gradient;
192
+ ctx.fill();
193
+ ctx.globalAlpha = 1;
194
+ }
195
+ }
196
+ });
197
+ }
198
+ };
199
+ const resize = new win.ResizeObserver(draw), themes = new win.MutationObserver(draw);
200
+ resize.observe(canvas.parentElement ?? canvas);
201
+ themes.observe(doc.documentElement, {
202
+ attributes: true,
203
+ attributeFilter: ["class", "data-style-variant"]
204
+ });
205
+ doc.addEventListener("basecoat:stylechange", draw);
206
+ draw();
207
+ return () => {
208
+ resize.disconnect();
209
+ themes.disconnect();
210
+ legend.remove();
211
+ doc.removeEventListener("basecoat:stylechange", draw);
212
+ };
213
+ }
214
+
215
+ // src/widgets.ts
216
+ function attachWidget(root) {
217
+ const abort = new AbortController(), signal = abort.signal, cleanups = [];
218
+ const on = (element, name, fn) => element?.addEventListener(name, fn, { signal });
219
+ const emit = (detail) => root.dispatchEvent(new CustomEvent("basecoat:change", { bubbles: true, detail }));
220
+ if (root instanceof HTMLCanvasElement && root.dataset.chartConfig)
221
+ cleanups.push(attachCanvasChart(root, JSON.parse(root.dataset.chartConfig)));
222
+ if (root.classList.contains("sidebar")) {
223
+ const media = root.ownerDocument.defaultView.matchMedia("(min-width: 768px)");
224
+ root.dataset.sidebarInitialized = "true";
225
+ const set = (open) => root.setAttribute("aria-hidden", String(!open));
226
+ const toggle = () => set(root.getAttribute("aria-hidden") === "true");
227
+ const sidebar = root;
228
+ sidebar.toggle = toggle;
229
+ set(media.matches);
230
+ on(media, "change", () => set(media.matches));
231
+ on(root, "click", (event) => {
232
+ if (event.target === root && !media.matches) set(false);
233
+ });
234
+ on(root.ownerDocument, "keydown", (event) => {
235
+ if (event.key === "Escape" && !media.matches) set(false);
236
+ });
237
+ cleanups.push(() => {
238
+ delete sidebar.toggle;
239
+ delete root.dataset.sidebarInitialized;
240
+ });
241
+ }
242
+ if (root.classList.contains("toaster")) {
243
+ const timers = /* @__PURE__ */ new Set();
244
+ const toast = (message) => {
245
+ const item = root.ownerDocument.createElement("div");
246
+ item.className = "toast";
247
+ item.setAttribute("role", "status");
248
+ const content = root.ownerDocument.createElement("div");
249
+ content.className = "toast-content";
250
+ const section = root.ownerDocument.createElement("section"), heading = root.ownerDocument.createElement("h2");
251
+ heading.textContent = message.title ?? "";
252
+ section.append(heading);
253
+ if (message.description) {
254
+ const description = root.ownerDocument.createElement("p");
255
+ description.textContent = message.description;
256
+ section.append(description);
257
+ }
258
+ const footer = root.ownerDocument.createElement("footer"), close = root.ownerDocument.createElement("button");
259
+ close.type = "button";
260
+ close.className = "btn";
261
+ close.dataset.variant = "outline";
262
+ close.dataset.toastCancel = "";
263
+ close.textContent = message.cancel?.label ?? "Dismiss";
264
+ close.addEventListener("click", () => item.remove(), { signal });
265
+ footer.append(close);
266
+ if (message.action) {
267
+ const action = root.ownerDocument.createElement("button");
268
+ action.type = "button";
269
+ action.className = "btn";
270
+ action.textContent = message.action.label ?? "Action";
271
+ action.addEventListener(
272
+ "click",
273
+ () => {
274
+ message.action?.onclick?.();
275
+ item.remove();
276
+ },
277
+ { signal }
278
+ );
279
+ footer.append(action);
280
+ }
281
+ content.append(section, footer);
282
+ item.append(content);
283
+ root.append(item);
284
+ if ((message.duration ?? 5e3) > 0) {
285
+ const timer = setTimeout(() => {
286
+ item.remove();
287
+ timers.delete(timer);
288
+ }, message.duration ?? 5e3);
289
+ timers.add(timer);
290
+ }
291
+ return item;
292
+ };
293
+ root.toast = toast;
294
+ on(root, "click", (event) => {
295
+ const button = event.target.closest("[data-toast-cancel]");
296
+ button?.closest(".toast")?.remove();
297
+ });
298
+ cleanups.push(() => {
299
+ timers.forEach(clearTimeout);
300
+ delete root.toast;
301
+ });
302
+ }
303
+ if (root.classList.contains("tabs")) {
304
+ const tabs = [...root.querySelectorAll("[role=tab]")].filter(
305
+ (tab) => tab.closest(".tabs") === root
306
+ );
307
+ const set = (target) => {
308
+ if (target.disabled || target.getAttribute("aria-disabled") === "true") return;
309
+ for (const tab of tabs) {
310
+ const active = tab === target;
311
+ tab.setAttribute("aria-selected", String(active));
312
+ tab.tabIndex = active ? 0 : -1;
313
+ const panel = root.ownerDocument.getElementById(tab.getAttribute("aria-controls") ?? "");
314
+ if (panel) {
315
+ panel.hidden = !active;
316
+ panel.setAttribute("aria-selected", String(active));
317
+ }
318
+ }
319
+ emit(target.id);
320
+ };
321
+ for (const [index, tab] of tabs.entries()) {
322
+ on(tab, "click", () => set(tab));
323
+ on(tab, "keydown", (event) => {
324
+ const key = event.key, vertical = tab.parentElement?.getAttribute("aria-orientation") === "vertical";
325
+ if (!(vertical ? ["ArrowUp", "ArrowDown"] : ["ArrowLeft", "ArrowRight"]).includes(key) && !["Home", "End"].includes(key))
326
+ return;
327
+ event.preventDefault();
328
+ const target = tabs[nextIndex(
329
+ tabs.map((item) => ({
330
+ disabled: item.disabled || item.getAttribute("aria-disabled") === "true"
331
+ })),
332
+ index,
333
+ key
334
+ )];
335
+ target?.focus();
336
+ if (target) set(target);
337
+ });
338
+ }
339
+ }
340
+ if (root.classList.contains("accordion")) {
341
+ const summaries = [...root.querySelectorAll("summary")];
342
+ on(root, "keydown", (event) => {
343
+ const index = summaries.indexOf(event.target), key = event.key;
344
+ if (index >= 0 && ["ArrowUp", "ArrowDown", "Home", "End"].includes(key)) {
345
+ event.preventDefault();
346
+ summaries[nextIndex(
347
+ summaries.map((el) => ({ disabled: el.getAttribute("aria-disabled") === "true" })),
348
+ index,
349
+ key
350
+ )]?.focus();
351
+ }
352
+ });
353
+ on(root, "click", (event) => {
354
+ const summary = event.target.closest("summary");
355
+ if (summary?.getAttribute("aria-disabled") === "true" || summary?.parentElement?.getAttribute("aria-disabled") === "true")
356
+ event.preventDefault();
357
+ });
358
+ }
359
+ const kind = ["select", "combobox", "dropdown-menu", "popover"].find(
360
+ (kind2) => root.classList.contains(kind2)
361
+ );
362
+ if (kind) {
363
+ const trigger = root.querySelector(
364
+ ":scope > button, :scope > input[role=combobox], :scope > .input-group input[role=combobox]"
365
+ ), popup = root.querySelector(":scope > [data-popover]");
366
+ const search = root.querySelector(
367
+ "[data-popover] input, input[role=combobox]"
368
+ );
369
+ if (trigger && popup) {
370
+ let open = false, active = -1, disposePosition, disposeLayer;
371
+ const selector = kind === "dropdown-menu" ? "[role=menuitem], [role=menuitemcheckbox], [role=menuitemradio]" : "[role=option]";
372
+ const items = () => [...popup.querySelectorAll(selector)].filter(
373
+ (item) => !item.hidden && item.getAttribute("aria-hidden") !== "true"
374
+ );
375
+ const disabled = (item) => item.matches(":disabled") || item.getAttribute("aria-disabled") === "true";
376
+ const cursor = () => items().map((item) => ({
377
+ value: item.dataset.value ?? item.id,
378
+ label: item.textContent?.trim() ?? "",
379
+ disabled: disabled(item)
380
+ }));
381
+ const typeahead = createTypeahead();
382
+ const highlight = (index) => {
383
+ active = index;
384
+ const all = items();
385
+ for (const [i, item2] of all.entries()) {
386
+ item2.classList.toggle("active", i === index);
387
+ if (kind === "dropdown-menu") item2.tabIndex = i === index ? 0 : -1;
388
+ }
389
+ const item = all[index];
390
+ if (item) {
391
+ if (kind === "dropdown-menu") item.focus();
392
+ else {
393
+ const list = popup.querySelector("[role=listbox]");
394
+ if (list) {
395
+ list.setAttribute("aria-activedescendant", item.id);
396
+ scrollWithin(list, item);
397
+ }
398
+ search?.setAttribute("aria-activedescendant", item.id);
399
+ }
400
+ }
401
+ };
402
+ const close = (restore = false) => {
403
+ if (!open) return;
404
+ open = false;
405
+ disposeLayer?.();
406
+ disposePosition?.();
407
+ trigger.setAttribute("aria-expanded", "false");
408
+ popup.setAttribute("aria-hidden", "true");
409
+ popup.hidden = true;
410
+ if (restore) trigger.focus();
411
+ };
412
+ const show = (last = false) => {
413
+ if (open || disabled(trigger)) return;
414
+ open = true;
415
+ popup.hidden = false;
416
+ popup.setAttribute("aria-hidden", "false");
417
+ popup.setAttribute("popover", "manual");
418
+ popup.classList.add("bc-floating");
419
+ trigger.setAttribute("aria-expanded", "true");
420
+ disposePosition = attachFloating(trigger, popup, {
421
+ side: popup.dataset.side,
422
+ align: popup.dataset.align,
423
+ matchWidth: kind === "select" || kind === "combobox"
424
+ });
425
+ disposeLayer = dismissableLayer(root.ownerDocument, {
426
+ elements: () => [trigger, popup],
427
+ dismiss: (reason) => close(reason === "escape")
428
+ });
429
+ const list = popup.querySelector("[role=listbox],[role=menu]");
430
+ if (list) {
431
+ list.tabIndex = -1;
432
+ (search ?? list).focus();
433
+ }
434
+ if (kind === "popover") focusFirst(popup);
435
+ else highlight(nextIndex(cursor(), -1, last ? "End" : "Home"));
436
+ };
437
+ const choose = (item) => {
438
+ if (disabled(item)) return;
439
+ if (kind === "select" || kind === "combobox") {
440
+ const multiple = popup.querySelector("[aria-multiselectable=true]") !== null;
441
+ if (!multiple)
442
+ items().forEach(
443
+ (option) => option.setAttribute("aria-selected", String(option === item))
444
+ );
445
+ else
446
+ item.setAttribute(
447
+ "aria-selected",
448
+ String(item.getAttribute("aria-selected") !== "true")
449
+ );
450
+ const selected = items().filter(
451
+ (option) => option.getAttribute("aria-selected") === "true"
452
+ ), values = selected.map(
453
+ (option) => option.dataset.value ?? option.textContent?.trim() ?? ""
454
+ );
455
+ if (trigger instanceof HTMLInputElement) trigger.value = item.textContent?.trim() ?? "";
456
+ else {
457
+ const label = trigger.querySelector("span");
458
+ if (label)
459
+ label.textContent = selected.map((option) => option.textContent?.trim()).join(", ");
460
+ }
461
+ const hidden = root.querySelector("input[type=hidden]");
462
+ if (hidden) {
463
+ hidden.value = multiple ? JSON.stringify(values) : values[0] ?? "";
464
+ hidden.dispatchEvent(new Event("change", { bubbles: true }));
465
+ }
466
+ emit(multiple ? values : values[0]);
467
+ if (!multiple) close(true);
468
+ } else if (kind === "dropdown-menu") {
469
+ if (item.getAttribute("role") === "menuitemcheckbox") {
470
+ item.setAttribute("aria-checked", String(item.getAttribute("aria-checked") !== "true"));
471
+ emit(item.id);
472
+ } else close(true);
473
+ }
474
+ };
475
+ popup.hidden = popup.getAttribute("aria-hidden") !== "false";
476
+ on(trigger, "click", () => open ? close() : show());
477
+ on(trigger, "keydown", (event) => {
478
+ const key = event.key;
479
+ if (!open && ["ArrowDown", "ArrowUp", "Enter", " "].includes(key)) {
480
+ event.preventDefault();
481
+ show(key === "ArrowUp");
482
+ }
483
+ });
484
+ on(popup, "keydown", (event) => {
485
+ const keyboard = event;
486
+ if (["ArrowDown", "ArrowUp", "Home", "End"].includes(keyboard.key)) {
487
+ event.preventDefault();
488
+ highlight(nextIndex(cursor(), active, keyboard.key));
489
+ } else if (keyboard.key === "Enter" || !search && keyboard.key === " ") {
490
+ event.preventDefault();
491
+ const item = items()[active];
492
+ if (item) {
493
+ choose(item);
494
+ if (item instanceof HTMLAnchorElement) item.click();
495
+ }
496
+ } else if (keyboard.key === "Tab") close();
497
+ else if (!search && keyboard.key.length === 1 && !keyboard.ctrlKey && !keyboard.metaKey) {
498
+ event.preventDefault();
499
+ highlight(typeahead(keyboard.key, cursor(), active));
500
+ }
501
+ });
502
+ on(popup, "click", (event) => {
503
+ const item = event.target.closest(selector);
504
+ if (item) {
505
+ if (disabled(item)) event.preventDefault();
506
+ else choose(item);
507
+ }
508
+ });
509
+ on(popup, "pointermove", (event) => {
510
+ const pointer = event, item = event.target.closest(selector);
511
+ if (item && !disabled(item) && (pointer.movementX || pointer.movementY))
512
+ highlight(items().indexOf(item));
513
+ });
514
+ on(search, "input", () => {
515
+ for (const item of popup.querySelectorAll(selector)) {
516
+ const hidden = !item.textContent?.toLocaleLowerCase().includes(search.value.toLocaleLowerCase());
517
+ item.hidden = hidden;
518
+ item.setAttribute("aria-hidden", String(hidden));
519
+ }
520
+ if (!open) show();
521
+ highlight(nextIndex(cursor(), -1, "Home"));
522
+ });
523
+ cleanups.push(() => close());
524
+ }
525
+ }
526
+ if (root.classList.contains("command")) {
527
+ const input = root.querySelector("input"), list = root.querySelector("[role=menu],[role=listbox]");
528
+ let active = 0;
529
+ const items = () => [...list?.querySelectorAll("[role=menuitem],[role=option]") ?? []].filter(
530
+ (item) => !item.hidden
531
+ );
532
+ const refresh = () => {
533
+ const all = items();
534
+ all.forEach((item, index) => item.classList.toggle("active", index === active));
535
+ const current = all[active];
536
+ if (current) input?.setAttribute("aria-activedescendant", current.id);
537
+ };
538
+ on(input, "input", () => {
539
+ list?.querySelectorAll("[role=menuitem],[role=option]").forEach((item) => {
540
+ item.hidden = !item.textContent?.toLocaleLowerCase().includes(input.value.toLocaleLowerCase());
541
+ item.setAttribute("aria-hidden", String(item.hidden));
542
+ });
543
+ active = 0;
544
+ refresh();
545
+ });
546
+ on(input, "keydown", (event) => {
547
+ const key = event.key;
548
+ if (["ArrowUp", "ArrowDown", "Home", "End"].includes(key)) {
549
+ event.preventDefault();
550
+ active = nextIndex(
551
+ items().map((item) => ({ disabled: item.getAttribute("aria-disabled") === "true" })),
552
+ active,
553
+ key
554
+ );
555
+ refresh();
556
+ } else if (key === "Enter") {
557
+ event.preventDefault();
558
+ items()[active]?.click();
559
+ }
560
+ });
561
+ root.refresh = refresh;
562
+ }
563
+ if (root.matches("input[type=range]")) {
564
+ const range = root, update = () => range.style.setProperty(
565
+ "--slider-value",
566
+ `${percentage(range.valueAsNumber, Number(range.min || 0), Number(range.max || 100))}%`
567
+ );
568
+ on(range, "input", update);
569
+ update();
570
+ }
571
+ return () => {
572
+ abort.abort();
573
+ cleanups.forEach((cleanup) => cleanup());
574
+ };
575
+ }
576
+
577
+ // src/index.ts
578
+ var stylePacks = [
579
+ "vega",
580
+ "nova",
581
+ "maia",
582
+ "lyra",
583
+ "mira",
584
+ "luma",
585
+ "sera",
586
+ "rhea"
587
+ ];
588
+ function cx(...values) {
589
+ return values.filter(Boolean).join(" ");
590
+ }
591
+ function percentage(value, min = 0, max = 100) {
592
+ return Number.isFinite(value) && max > min ? Math.min(100, Math.max(0, (value - min) / (max - min) * 100)) : 0;
593
+ }
594
+ function nextIndex(items, current, key, loop = true) {
595
+ const enabled = items.map((item, index) => item.disabled ? -1 : index).filter((index) => index >= 0);
596
+ if (!enabled.length) return -1;
597
+ if (key === "Home") return enabled[0];
598
+ if (key === "End") return enabled[enabled.length - 1];
599
+ const direction = key === "ArrowUp" || key === "ArrowLeft" ? -1 : 1;
600
+ const position = enabled.indexOf(current);
601
+ if (position < 0) return direction < 0 ? enabled[enabled.length - 1] : enabled[0];
602
+ const next = position + direction;
603
+ return enabled[loop ? (next + enabled.length) % enabled.length : Math.min(enabled.length - 1, Math.max(0, next))];
604
+ }
605
+ function createTypeahead() {
606
+ let buffer = "", last = 0;
607
+ return (key, items, current) => {
608
+ const now = Date.now();
609
+ buffer = now - last > 700 ? key : buffer + key;
610
+ last = now;
611
+ const query = [...buffer].every((char) => char === buffer[0]) ? buffer[0] : buffer;
612
+ for (let offset = 1; offset <= items.length; offset++) {
613
+ const index = (current + offset + items.length) % items.length;
614
+ if (!items[index].disabled && items[index].label.toLocaleLowerCase().startsWith(query.toLocaleLowerCase()))
615
+ return index;
616
+ }
617
+ return current;
618
+ };
619
+ }
620
+ var stacks = /* @__PURE__ */ new WeakMap();
621
+ function dismissableLayer(doc, layer) {
622
+ let stack = stacks.get(doc);
623
+ if (!stack) {
624
+ const layers = [];
625
+ const pointer = (event) => {
626
+ const top = layers.at(-1);
627
+ if (top && !top.elements().some((el) => el && event.composedPath().includes(el)))
628
+ top.dismiss("outside");
629
+ };
630
+ const keyboard = (event) => {
631
+ if (event.key === "Escape" && !event.defaultPrevented && layers.length) {
632
+ event.preventDefault();
633
+ event.stopPropagation();
634
+ layers.at(-1)?.dismiss("escape");
635
+ }
636
+ };
637
+ doc.addEventListener("pointerdown", pointer, true);
638
+ doc.addEventListener("keydown", keyboard);
639
+ stack = {
640
+ layers,
641
+ dispose: () => {
642
+ doc.removeEventListener("pointerdown", pointer, true);
643
+ doc.removeEventListener("keydown", keyboard);
644
+ }
645
+ };
646
+ stacks.set(doc, stack);
647
+ }
648
+ stack.layers.push(layer);
649
+ return () => {
650
+ const index = stack.layers.indexOf(layer);
651
+ if (index >= 0) stack.layers.splice(index, 1);
652
+ if (!stack.layers.length) {
653
+ stack.dispose();
654
+ stacks.delete(doc);
655
+ }
656
+ };
657
+ }
658
+ function floatingPosition(anchor, popup, width, height, options = {}) {
659
+ let side = options.side ?? "bottom";
660
+ const align = options.align ?? "start", offset = options.offset ?? 6, padding = 8;
661
+ const available = {
662
+ bottom: height - anchor.bottom,
663
+ top: anchor.top,
664
+ right: width - anchor.right,
665
+ left: anchor.left
666
+ };
667
+ const opposite = {
668
+ top: "bottom",
669
+ bottom: "top",
670
+ left: "right",
671
+ right: "left"
672
+ };
673
+ const required = (side === "top" || side === "bottom" ? popup.height : popup.width) + offset + padding;
674
+ if (available[side] < required && available[opposite[side]] > available[side])
675
+ side = opposite[side];
676
+ const vertical = side === "top" || side === "bottom";
677
+ let x = vertical ? anchor.left : side === "left" ? anchor.left - popup.width - offset : anchor.right + offset;
678
+ let y = vertical ? side === "top" ? anchor.top - popup.height - offset : anchor.bottom + offset : anchor.top;
679
+ if (vertical)
680
+ x += align === "end" ? anchor.width - popup.width : align === "center" ? (anchor.width - popup.width) / 2 : 0;
681
+ else
682
+ y += align === "end" ? anchor.height - popup.height : align === "center" ? (anchor.height - popup.height) / 2 : 0;
683
+ return {
684
+ x: Math.max(padding, Math.min(x, width - popup.width - padding)),
685
+ y: Math.max(padding, Math.min(y, height - popup.height - padding)),
686
+ side,
687
+ maxHeight: Math.max(40, available[side] - offset - padding)
688
+ };
689
+ }
690
+ function attachFloating(anchor, popup, options = {}) {
691
+ const win = anchor.ownerDocument.defaultView;
692
+ if (typeof popup.showPopover === "function") popup.showPopover();
693
+ const update = () => {
694
+ const rect = anchor.getBoundingClientRect();
695
+ if (options.matchWidth) popup.style.minWidth = `${rect.width}px`;
696
+ const viewport = win.visualViewport;
697
+ const result = floatingPosition(
698
+ rect,
699
+ popup.getBoundingClientRect(),
700
+ viewport?.width ?? win.innerWidth,
701
+ viewport?.height ?? win.innerHeight,
702
+ options
703
+ );
704
+ Object.assign(popup.style, {
705
+ left: `${result.x + (viewport?.offsetLeft ?? 0)}px`,
706
+ top: `${result.y + (viewport?.offsetTop ?? 0)}px`,
707
+ maxHeight: `${result.maxHeight}px`
708
+ });
709
+ popup.dataset.side = result.side;
710
+ };
711
+ let frame = 0;
712
+ const schedule = () => {
713
+ win.cancelAnimationFrame(frame);
714
+ frame = win.requestAnimationFrame(update);
715
+ };
716
+ const observer = typeof win.ResizeObserver === "function" ? new win.ResizeObserver(schedule) : null;
717
+ observer?.observe(anchor);
718
+ observer?.observe(popup);
719
+ win.addEventListener("resize", schedule);
720
+ win.addEventListener("scroll", schedule, true);
721
+ win.visualViewport?.addEventListener("resize", schedule);
722
+ win.visualViewport?.addEventListener("scroll", schedule);
723
+ update();
724
+ return () => {
725
+ win.cancelAnimationFrame(frame);
726
+ observer?.disconnect();
727
+ win.removeEventListener("resize", schedule);
728
+ win.removeEventListener("scroll", schedule, true);
729
+ win.visualViewport?.removeEventListener("resize", schedule);
730
+ win.visualViewport?.removeEventListener("scroll", schedule);
731
+ if (typeof popup.hidePopover === "function" && popup.matches(":popover-open"))
732
+ popup.hidePopover();
733
+ };
734
+ }
735
+ var scrollLocks = /* @__PURE__ */ new WeakMap();
736
+ function lockScroll(doc) {
737
+ const record = scrollLocks.get(doc) ?? { count: 0, overflow: doc.documentElement.style.overflow };
738
+ record.count++;
739
+ scrollLocks.set(doc, record);
740
+ doc.documentElement.style.overflow = "hidden";
741
+ let disposed = false;
742
+ return () => {
743
+ if (disposed) return;
744
+ disposed = true;
745
+ if (--record.count === 0) {
746
+ doc.documentElement.style.overflow = record.overflow;
747
+ scrollLocks.delete(doc);
748
+ }
749
+ };
750
+ }
751
+ function focusFirst(root) {
752
+ const target = root.querySelector(
753
+ '[autofocus],button:not(:disabled),[href],input:not(:disabled),select:not(:disabled),textarea:not(:disabled),[tabindex="0"]'
754
+ );
755
+ (target ?? root).focus({ preventScroll: true });
756
+ }
757
+ function scrollWithin(container, item) {
758
+ const parent = container.scrollHeight > container.clientHeight ? container : container.parentElement ?? container;
759
+ const outer = parent.getBoundingClientRect(), inner = item.getBoundingClientRect();
760
+ if (inner.top < outer.top) parent.scrollTop -= outer.top - inner.top;
761
+ else if (inner.bottom > outer.bottom) parent.scrollTop += inner.bottom - outer.bottom;
762
+ }
763
+ function formReset(element, reset) {
764
+ const form = element.closest("form");
765
+ let timer;
766
+ const listener = (event) => {
767
+ clearTimeout(timer);
768
+ timer = setTimeout(() => {
769
+ if (!event.defaultPrevented) reset();
770
+ }, 0);
771
+ };
772
+ form?.addEventListener("reset", listener);
773
+ return () => {
774
+ clearTimeout(timer);
775
+ form?.removeEventListener("reset", listener);
776
+ };
777
+ }
778
+ var primitiveDefinitions = {
779
+ ChartRoot: ["canvas", "chart-canvas"],
780
+ SelectRoot: ["div", "select"],
781
+ ComboboxRoot: ["div", "combobox"],
782
+ DropdownMenuRoot: ["div", "dropdown-menu"],
783
+ PopoverRoot: ["div", "popover"],
784
+ TabsRoot: ["div", "tabs"],
785
+ AccordionRoot: ["div", "accordion"],
786
+ CommandRoot: ["div", "command"],
787
+ SidebarRoot: ["div", "sidebar"],
788
+ DialogRoot: ["dialog", "dialog"],
789
+ DrawerRoot: ["dialog", "drawer"],
790
+ AlertDialogRoot: ["dialog", "alert-dialog"],
791
+ Button: ["button", "btn"],
792
+ Badge: ["span", "badge"],
793
+ Card: ["article", "card"],
794
+ CardHeader: ["header", ""],
795
+ CardTitle: ["h3", "card-title"],
796
+ CardDescription: ["p", ""],
797
+ CardContent: ["section", ""],
798
+ CardFooter: ["footer", ""],
799
+ CardAction: ["div", "card-action"],
800
+ Alert: ["div", "alert"],
801
+ AlertTitle: ["h2", ""],
802
+ AlertDescription: ["section", ""],
803
+ Avatar: ["span", "avatar"],
804
+ AvatarImage: ["img", ""],
805
+ AvatarFallback: ["span", ""],
806
+ Breadcrumb: ["nav", "breadcrumb"],
807
+ BreadcrumbList: ["ol", ""],
808
+ BreadcrumbItem: ["li", ""],
809
+ BreadcrumbLink: ["a", ""],
810
+ BreadcrumbSeparator: ["li", ""],
811
+ ButtonGroup: ["div", "button-group"],
812
+ Field: ["div", "field"],
813
+ FieldLabel: ["label", "label"],
814
+ FieldDescription: ["p", ""],
815
+ FieldError: ["p", ""],
816
+ Label: ["label", "label"],
817
+ Input: ["input", "input"],
818
+ Textarea: ["textarea", "textarea"],
819
+ NativeSelect: ["select", "select"],
820
+ InputGroup: ["div", "input-group"],
821
+ InputGroupAddon: ["div", ""],
822
+ Empty: ["div", "empty"],
823
+ EmptyHeader: ["header", ""],
824
+ EmptyTitle: ["h3", ""],
825
+ EmptyDescription: ["p", ""],
826
+ EmptyContent: ["section", ""],
827
+ Item: ["div", "item"],
828
+ ItemContent: ["section", ""],
829
+ ItemTitle: ["h3", ""],
830
+ ItemDescription: ["p", ""],
831
+ ItemActions: ["div", ""],
832
+ Kbd: ["kbd", "kbd"],
833
+ ScrollArea: ["div", "scrollbar"],
834
+ Skeleton: ["div", "skeleton"],
835
+ Spinner: ["span", "bc-spinner"],
836
+ Separator: ["hr", ""],
837
+ Table: ["table", "table"],
838
+ TableHeader: ["thead", ""],
839
+ TableBody: ["tbody", ""],
840
+ TableFooter: ["tfoot", ""],
841
+ TableRow: ["tr", ""],
842
+ TableHead: ["th", ""],
843
+ TableCell: ["td", ""],
844
+ TableCaption: ["caption", ""],
845
+ DialogHeader: ["header", ""],
846
+ DialogTitle: ["h2", ""],
847
+ DialogDescription: ["p", ""],
848
+ DialogContent: ["section", ""],
849
+ DialogFooter: ["footer", ""],
850
+ SidebarHeader: ["header", ""],
851
+ SidebarContent: ["section", ""],
852
+ SidebarFooter: ["footer", ""],
853
+ SidebarGroup: ["section", ""],
854
+ SidebarItem: ["a", ""]
855
+ };
856
+ // Annotate the CommonJS export names for ESM import in node:
857
+ 0 && (module.exports = {
858
+ attachFloating,
859
+ attachWidget,
860
+ createTypeahead,
861
+ cx,
862
+ dismissableLayer,
863
+ floatingPosition,
864
+ focusFirst,
865
+ formReset,
866
+ lockScroll,
867
+ nextIndex,
868
+ percentage,
869
+ primitiveDefinitions,
870
+ scrollWithin,
871
+ stylePacks
872
+ });