@vertz/ui-primitives 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@
2
2
  import { signal } from "@vertz/ui";
3
3
 
4
4
  // src/utils/aria.ts
5
+ import { onAnimationsComplete } from "@vertz/ui/internals";
6
+ var hideGeneration = new WeakMap;
5
7
  function setExpanded(el, expanded) {
6
8
  el.setAttribute("aria-expanded", String(expanded));
7
9
  }
@@ -12,6 +14,21 @@ function setHidden(el, hidden) {
12
14
  el.setAttribute("aria-hidden", String(hidden));
13
15
  el.style.display = hidden ? "none" : "";
14
16
  }
17
+ function setHiddenAnimated(el, hidden) {
18
+ const gen = (hideGeneration.get(el) ?? 0) + 1;
19
+ hideGeneration.set(el, gen);
20
+ if (!hidden) {
21
+ el.setAttribute("aria-hidden", "false");
22
+ el.style.display = "";
23
+ return;
24
+ }
25
+ el.setAttribute("aria-hidden", "true");
26
+ onAnimationsComplete(el, () => {
27
+ if (hideGeneration.get(el) === gen) {
28
+ el.style.display = "none";
29
+ }
30
+ });
31
+ }
15
32
  function setChecked(el, checked) {
16
33
  el.setAttribute("aria-checked", String(checked));
17
34
  }
@@ -27,6 +44,9 @@ function setLabelledBy(el, labelId) {
27
44
  function setDescribedBy(el, descriptionId) {
28
45
  el.setAttribute("aria-describedby", descriptionId);
29
46
  }
47
+ function setPressed(el, pressed) {
48
+ el.setAttribute("aria-pressed", String(pressed));
49
+ }
30
50
  function setValueRange(el, now, min, max) {
31
51
  el.setAttribute("aria-valuenow", String(now));
32
52
  el.setAttribute("aria-valuemin", String(min));
@@ -111,10 +131,29 @@ var Accordion = {
111
131
  const { multiple = false, defaultValue = [], onValueChange } = options;
112
132
  const state = { value: signal([...defaultValue]) };
113
133
  const triggers = [];
134
+ const itemMap = new Map;
114
135
  const root = document.createElement("div");
115
136
  root.setAttribute("data-orientation", "vertical");
137
+ function updateItemState(val, open) {
138
+ const entry = itemMap.get(val);
139
+ if (!entry)
140
+ return;
141
+ const { trigger: t, content: c } = entry;
142
+ if (open) {
143
+ setHidden(c, false);
144
+ }
145
+ const height = c.scrollHeight;
146
+ c.style.setProperty("--accordion-content-height", `${height}px`);
147
+ setExpanded(t, open);
148
+ setDataState(t, open ? "open" : "closed");
149
+ setDataState(c, open ? "open" : "closed");
150
+ if (!open) {
151
+ setHiddenAnimated(c, true);
152
+ }
153
+ }
116
154
  function toggleItem(value) {
117
- const current = [...state.value.peek()];
155
+ const prev = [...state.value.peek()];
156
+ const current = [...prev];
118
157
  const idx = current.indexOf(value);
119
158
  if (idx >= 0) {
120
159
  current.splice(idx, 1);
@@ -128,6 +167,16 @@ var Accordion = {
128
167
  }
129
168
  state.value.value = current;
130
169
  onValueChange?.(current);
170
+ for (const v of prev) {
171
+ if (!current.includes(v)) {
172
+ updateItemState(v, false);
173
+ }
174
+ }
175
+ for (const v of current) {
176
+ if (!prev.includes(v)) {
177
+ updateItemState(v, true);
178
+ }
179
+ }
131
180
  }
132
181
  root.addEventListener("keydown", (event) => {
133
182
  if (isKey(event, Keys.ArrowUp, Keys.ArrowDown, Keys.Home, Keys.End)) {
@@ -156,16 +205,18 @@ var Accordion = {
156
205
  setDataState(content, isOpen ? "open" : "closed");
157
206
  trigger.addEventListener("click", () => {
158
207
  toggleItem(value);
159
- const nowOpen = state.value.peek().includes(value);
160
- setExpanded(trigger, nowOpen);
161
- setHidden(content, !nowOpen);
162
- setDataState(trigger, nowOpen ? "open" : "closed");
163
- setDataState(content, nowOpen ? "open" : "closed");
164
208
  });
209
+ itemMap.set(value, { trigger, content });
165
210
  triggers.push(trigger);
166
211
  item.appendChild(trigger);
167
212
  item.appendChild(content);
168
213
  root.appendChild(item);
214
+ if (isOpen) {
215
+ requestAnimationFrame(() => {
216
+ const height = content.scrollHeight;
217
+ content.style.setProperty("--accordion-content-height", `${height}px`);
218
+ });
219
+ }
169
220
  return { item, trigger, content };
170
221
  }
171
222
  return { root, state, Item };
@@ -212,8 +263,363 @@ var Button = {
212
263
  return { root, state };
213
264
  }
214
265
  };
215
- // src/checkbox/checkbox.ts
266
+ // src/calendar/calendar.ts
216
267
  import { signal as signal3 } from "@vertz/ui";
268
+ var MONTH_NAMES = [
269
+ "January",
270
+ "February",
271
+ "March",
272
+ "April",
273
+ "May",
274
+ "June",
275
+ "July",
276
+ "August",
277
+ "September",
278
+ "October",
279
+ "November",
280
+ "December"
281
+ ];
282
+ var DAY_NAMES = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
283
+ function getDaysInMonth(year, month) {
284
+ return new Date(year, month + 1, 0).getDate();
285
+ }
286
+ function isSameDay(a, b) {
287
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
288
+ }
289
+ function addDays(date, days) {
290
+ const result = new Date(date);
291
+ result.setDate(result.getDate() + days);
292
+ return result;
293
+ }
294
+ function addMonths(date, months) {
295
+ const result = new Date(date);
296
+ result.setMonth(result.getMonth() + months);
297
+ return result;
298
+ }
299
+ var Calendar = {
300
+ Root(options = {}) {
301
+ const now = new Date;
302
+ const defaultMonth = options.defaultMonth ?? now;
303
+ const weekStartsOn = options.weekStartsOn ?? 0;
304
+ const mode = options.mode ?? "single";
305
+ const state = {
306
+ value: signal3(options.defaultValue ?? null),
307
+ focusedDate: signal3(defaultMonth),
308
+ displayMonth: signal3(defaultMonth)
309
+ };
310
+ const root = document.createElement("div");
311
+ const header = document.createElement("div");
312
+ const title = document.createElement("div");
313
+ const prevButton = document.createElement("button");
314
+ prevButton.setAttribute("type", "button");
315
+ const nextButton = document.createElement("button");
316
+ nextButton.setAttribute("type", "button");
317
+ const grid = document.createElement("table");
318
+ grid.setAttribute("role", "grid");
319
+ function updateTitle() {
320
+ const month = state.displayMonth.peek();
321
+ title.textContent = `${MONTH_NAMES[month.getMonth()]} ${month.getFullYear()}`;
322
+ }
323
+ function isDateDisabled(date) {
324
+ if (options.disabled?.(date))
325
+ return true;
326
+ if (options.minDate && date < options.minDate && !isSameDay(date, options.minDate)) {
327
+ return true;
328
+ }
329
+ if (options.maxDate && date > options.maxDate && !isSameDay(date, options.maxDate)) {
330
+ return true;
331
+ }
332
+ return false;
333
+ }
334
+ function isSelected(date) {
335
+ const val = state.value.peek();
336
+ if (val === null)
337
+ return false;
338
+ if (val instanceof Date)
339
+ return isSameDay(val, date);
340
+ if (Array.isArray(val))
341
+ return val.some((d) => isSameDay(d, date));
342
+ if ("from" in val && "to" in val) {
343
+ return isSameDay(val.from, date) || isSameDay(val.to, date);
344
+ }
345
+ return false;
346
+ }
347
+ function isInRange(date) {
348
+ const val = state.value.peek();
349
+ if (val === null || !("from" in val))
350
+ return false;
351
+ const range = val;
352
+ return date > range.from && date < range.to;
353
+ }
354
+ function selectDate(date) {
355
+ if (isDateDisabled(date))
356
+ return;
357
+ if (mode === "single") {
358
+ state.value.value = date;
359
+ } else if (mode === "multiple") {
360
+ const current = state.value.peek() ?? [];
361
+ const existing = current.findIndex((d) => isSameDay(d, date));
362
+ if (existing >= 0) {
363
+ const next = [...current];
364
+ next.splice(existing, 1);
365
+ state.value.value = next;
366
+ } else {
367
+ state.value.value = [...current, date];
368
+ }
369
+ } else if (mode === "range") {
370
+ const current = state.value.peek();
371
+ if (!current || "to" in current && current.to) {
372
+ state.value.value = { from: date, to: date };
373
+ } else {
374
+ if (date < current.from) {
375
+ state.value.value = { from: date, to: current.from };
376
+ } else {
377
+ state.value.value = { from: current.from, to: date };
378
+ }
379
+ }
380
+ }
381
+ options.onValueChange?.(state.value.peek());
382
+ }
383
+ function buildGrid() {
384
+ grid.innerHTML = "";
385
+ const display = state.displayMonth.peek();
386
+ const year = display.getFullYear();
387
+ const month = display.getMonth();
388
+ const daysInMonth = getDaysInMonth(year, month);
389
+ const thead = document.createElement("thead");
390
+ const headerRow = document.createElement("tr");
391
+ for (let i = 0;i < 7; i++) {
392
+ const dayIndex = (weekStartsOn + i) % 7;
393
+ const th = document.createElement("th");
394
+ th.setAttribute("scope", "col");
395
+ th.textContent = DAY_NAMES[dayIndex] ?? "";
396
+ headerRow.appendChild(th);
397
+ }
398
+ thead.appendChild(headerRow);
399
+ grid.appendChild(thead);
400
+ const tbody = document.createElement("tbody");
401
+ const firstDay = new Date(year, month, 1);
402
+ const firstDayOfWeek = firstDay.getDay();
403
+ const offset = (firstDayOfWeek - weekStartsOn + 7) % 7;
404
+ const startDate = addDays(firstDay, -offset);
405
+ let currentDate = startDate;
406
+ const totalCells = offset + daysInMonth;
407
+ const totalRows = Math.ceil(totalCells / 7);
408
+ for (let row = 0;row < totalRows; row++) {
409
+ const tr = document.createElement("tr");
410
+ for (let col = 0;col < 7; col++) {
411
+ const td = document.createElement("td");
412
+ td.setAttribute("role", "gridcell");
413
+ const btn = document.createElement("button");
414
+ btn.setAttribute("type", "button");
415
+ const cellDate = new Date(currentDate);
416
+ btn.textContent = String(cellDate.getDate());
417
+ btn.setAttribute("data-date", cellDate.toISOString().split("T")[0] ?? "");
418
+ const isOutside = cellDate.getMonth() !== month;
419
+ if (isOutside) {
420
+ btn.setAttribute("data-outside-month", "true");
421
+ }
422
+ if (isSameDay(cellDate, now)) {
423
+ btn.setAttribute("data-today", "true");
424
+ }
425
+ if (isDateDisabled(cellDate)) {
426
+ btn.setAttribute("aria-disabled", "true");
427
+ }
428
+ if (isSelected(cellDate)) {
429
+ btn.setAttribute("aria-selected", "true");
430
+ }
431
+ if (mode === "range") {
432
+ const val = state.value.peek();
433
+ if (val && "from" in val) {
434
+ if (isSameDay(cellDate, val.from)) {
435
+ btn.setAttribute("data-range-start", "true");
436
+ }
437
+ if (isSameDay(cellDate, val.to)) {
438
+ btn.setAttribute("data-range-end", "true");
439
+ }
440
+ if (isInRange(cellDate)) {
441
+ btn.setAttribute("data-in-range", "true");
442
+ }
443
+ }
444
+ }
445
+ btn.addEventListener("click", () => {
446
+ selectDate(cellDate);
447
+ rebuildGrid();
448
+ });
449
+ td.appendChild(btn);
450
+ tr.appendChild(td);
451
+ currentDate = addDays(currentDate, 1);
452
+ }
453
+ tbody.appendChild(tr);
454
+ }
455
+ grid.appendChild(tbody);
456
+ }
457
+ function rebuildGrid() {
458
+ updateTitle();
459
+ buildGrid();
460
+ }
461
+ function navigateMonth(delta) {
462
+ state.displayMonth.value = addMonths(state.displayMonth.peek(), delta);
463
+ options.onMonthChange?.(state.displayMonth.peek());
464
+ rebuildGrid();
465
+ }
466
+ prevButton.addEventListener("click", () => navigateMonth(-1));
467
+ nextButton.addEventListener("click", () => navigateMonth(1));
468
+ grid.addEventListener("keydown", (event) => {
469
+ const active = document.activeElement;
470
+ if (!active || active.tagName !== "BUTTON")
471
+ return;
472
+ const dateStr = active.getAttribute("data-date");
473
+ if (!dateStr)
474
+ return;
475
+ const focused = new Date(dateStr + "T00:00:00");
476
+ let next = null;
477
+ if (event.key === "ArrowLeft") {
478
+ event.preventDefault();
479
+ next = addDays(focused, -1);
480
+ } else if (event.key === "ArrowRight") {
481
+ event.preventDefault();
482
+ next = addDays(focused, 1);
483
+ } else if (event.key === "ArrowUp") {
484
+ event.preventDefault();
485
+ next = addDays(focused, -7);
486
+ } else if (event.key === "ArrowDown") {
487
+ event.preventDefault();
488
+ next = addDays(focused, 7);
489
+ } else if (event.key === "Home") {
490
+ event.preventDefault();
491
+ const dayOfWeek = (focused.getDay() - weekStartsOn + 7) % 7;
492
+ next = addDays(focused, -dayOfWeek);
493
+ } else if (event.key === "End") {
494
+ event.preventDefault();
495
+ const dayOfWeek = (focused.getDay() - weekStartsOn + 7) % 7;
496
+ next = addDays(focused, 6 - dayOfWeek);
497
+ } else if (event.key === "PageUp") {
498
+ event.preventDefault();
499
+ next = event.shiftKey ? addMonths(focused, -12) : addMonths(focused, -1);
500
+ } else if (event.key === "PageDown") {
501
+ event.preventDefault();
502
+ next = event.shiftKey ? addMonths(focused, 12) : addMonths(focused, 1);
503
+ } else if (event.key === "Enter" || event.key === " ") {
504
+ event.preventDefault();
505
+ selectDate(focused);
506
+ rebuildGrid();
507
+ return;
508
+ }
509
+ if (next) {
510
+ state.focusedDate.value = next;
511
+ if (next.getMonth() !== state.displayMonth.peek().getMonth() || next.getFullYear() !== state.displayMonth.peek().getFullYear()) {
512
+ state.displayMonth.value = new Date(next.getFullYear(), next.getMonth(), 1);
513
+ options.onMonthChange?.(state.displayMonth.peek());
514
+ rebuildGrid();
515
+ }
516
+ const dateKey = next.toISOString().split("T")[0];
517
+ const btn = grid.querySelector(`button[data-date="${dateKey}"]`);
518
+ btn?.focus();
519
+ }
520
+ });
521
+ updateTitle();
522
+ buildGrid();
523
+ root.appendChild(header);
524
+ root.appendChild(grid);
525
+ header.appendChild(prevButton);
526
+ header.appendChild(title);
527
+ header.appendChild(nextButton);
528
+ return { root, header, title, prevButton, nextButton, grid, state };
529
+ }
530
+ };
531
+ // src/carousel/carousel.ts
532
+ import { signal as signal4 } from "@vertz/ui";
533
+ var Carousel = {
534
+ Root(options = {}) {
535
+ const { orientation = "horizontal", loop = false, defaultIndex = 0, onSlideChange } = options;
536
+ const state = {
537
+ currentIndex: signal4(defaultIndex),
538
+ slideCount: signal4(0)
539
+ };
540
+ const slides = [];
541
+ const root = document.createElement("div");
542
+ root.setAttribute("role", "region");
543
+ root.setAttribute("aria-roledescription", "carousel");
544
+ root.setAttribute("data-orientation", orientation);
545
+ const viewport = document.createElement("div");
546
+ viewport.style.overflow = "hidden";
547
+ const prevButton = document.createElement("button");
548
+ prevButton.setAttribute("type", "button");
549
+ prevButton.setAttribute("aria-label", "Previous slide");
550
+ const nextButton = document.createElement("button");
551
+ nextButton.setAttribute("type", "button");
552
+ nextButton.setAttribute("aria-label", "Next slide");
553
+ function updateSlideVisibility() {
554
+ const current = state.currentIndex.peek();
555
+ for (let i = 0;i < slides.length; i++) {
556
+ const slide = slides[i];
557
+ if (!slide)
558
+ continue;
559
+ slide.setAttribute("aria-hidden", String(i !== current));
560
+ slide.setAttribute("aria-label", `Slide ${i + 1} of ${slides.length}`);
561
+ setDataState(slide, i === current ? "active" : "inactive");
562
+ }
563
+ if (!loop) {
564
+ prevButton.disabled = current <= 0;
565
+ nextButton.disabled = current >= slides.length - 1;
566
+ }
567
+ const translateProp = orientation === "horizontal" ? "translateX" : "translateY";
568
+ viewport.style.transform = `${translateProp}(-${current * 100}%)`;
569
+ }
570
+ function goTo(index) {
571
+ const total = slides.length;
572
+ if (total === 0)
573
+ return;
574
+ let next = index;
575
+ if (loop) {
576
+ next = (index % total + total) % total;
577
+ } else {
578
+ next = Math.max(0, Math.min(total - 1, index));
579
+ }
580
+ if (next === state.currentIndex.peek())
581
+ return;
582
+ state.currentIndex.value = next;
583
+ updateSlideVisibility();
584
+ onSlideChange?.(next);
585
+ }
586
+ function goNext() {
587
+ goTo(state.currentIndex.peek() + 1);
588
+ }
589
+ function goPrev() {
590
+ goTo(state.currentIndex.peek() - 1);
591
+ }
592
+ prevButton.addEventListener("click", goPrev);
593
+ nextButton.addEventListener("click", goNext);
594
+ root.addEventListener("keydown", (event) => {
595
+ const prevKey = orientation === "horizontal" ? Keys.ArrowLeft : Keys.ArrowUp;
596
+ const nextKey = orientation === "horizontal" ? Keys.ArrowRight : Keys.ArrowDown;
597
+ if (isKey(event, prevKey)) {
598
+ event.preventDefault();
599
+ goPrev();
600
+ }
601
+ if (isKey(event, nextKey)) {
602
+ event.preventDefault();
603
+ goNext();
604
+ }
605
+ });
606
+ function Slide() {
607
+ const slide = document.createElement("div");
608
+ slide.setAttribute("role", "group");
609
+ slide.setAttribute("aria-roledescription", "slide");
610
+ slides.push(slide);
611
+ state.slideCount.value = slides.length;
612
+ viewport.appendChild(slide);
613
+ updateSlideVisibility();
614
+ return slide;
615
+ }
616
+ root.appendChild(viewport);
617
+ updateSlideVisibility();
618
+ return { root, viewport, prevButton, nextButton, state, Slide, goTo, goNext, goPrev };
619
+ }
620
+ };
621
+ // src/checkbox/checkbox.ts
622
+ import { signal as signal5 } from "@vertz/ui";
217
623
  function dataStateFor(checked) {
218
624
  if (checked === "mixed")
219
625
  return "indeterminate";
@@ -223,8 +629,8 @@ var Checkbox = {
223
629
  Root(options = {}) {
224
630
  const { defaultChecked = false, disabled = false, onCheckedChange } = options;
225
631
  const state = {
226
- checked: signal3(defaultChecked),
227
- disabled: signal3(disabled)
632
+ checked: signal5(defaultChecked),
633
+ disabled: signal5(disabled)
228
634
  };
229
635
  const root = document.createElement("button");
230
636
  root.setAttribute("type", "button");
@@ -256,17 +662,66 @@ var Checkbox = {
256
662
  return { root, state };
257
663
  }
258
664
  };
665
+ // src/collapsible/collapsible.ts
666
+ import { signal as signal6 } from "@vertz/ui";
667
+ var Collapsible = {
668
+ Root(options = {}) {
669
+ const { defaultOpen = false, disabled = false, onOpenChange } = options;
670
+ const ids = linkedIds("collapsible");
671
+ const state = {
672
+ open: signal6(defaultOpen),
673
+ disabled: signal6(disabled)
674
+ };
675
+ const root = document.createElement("div");
676
+ const trigger = document.createElement("button");
677
+ trigger.setAttribute("type", "button");
678
+ trigger.id = ids.triggerId;
679
+ trigger.setAttribute("aria-controls", ids.contentId);
680
+ setExpanded(trigger, defaultOpen);
681
+ setDataState(trigger, defaultOpen ? "open" : "closed");
682
+ if (disabled) {
683
+ trigger.disabled = true;
684
+ trigger.setAttribute("aria-disabled", "true");
685
+ }
686
+ const content = document.createElement("div");
687
+ content.id = ids.contentId;
688
+ setHidden(content, !defaultOpen);
689
+ setDataState(content, defaultOpen ? "open" : "closed");
690
+ function toggle() {
691
+ if (state.disabled.peek())
692
+ return;
693
+ const next = !state.open.peek();
694
+ state.open.value = next;
695
+ if (next) {
696
+ setHidden(content, false);
697
+ }
698
+ const height = content.scrollHeight;
699
+ content.style.setProperty("--collapsible-content-height", `${height}px`);
700
+ setExpanded(trigger, next);
701
+ setDataState(trigger, next ? "open" : "closed");
702
+ setDataState(content, next ? "open" : "closed");
703
+ if (!next) {
704
+ setHiddenAnimated(content, true);
705
+ }
706
+ onOpenChange?.(next);
707
+ }
708
+ trigger.addEventListener("click", toggle);
709
+ root.appendChild(trigger);
710
+ root.appendChild(content);
711
+ return { root, trigger, content, state };
712
+ }
713
+ };
259
714
  // src/combobox/combobox.ts
260
- import { signal as signal4 } from "@vertz/ui";
715
+ import { signal as signal7 } from "@vertz/ui";
261
716
  var Combobox = {
262
717
  Root(options = {}) {
263
718
  const { defaultValue = "", onValueChange, onInputChange } = options;
264
719
  const ids = linkedIds("combobox");
265
720
  const state = {
266
- open: signal4(false),
267
- value: signal4(defaultValue),
268
- inputValue: signal4(defaultValue),
269
- activeIndex: signal4(-1)
721
+ open: signal7(false),
722
+ value: signal7(defaultValue),
723
+ inputValue: signal7(defaultValue),
724
+ activeIndex: signal7(-1)
270
725
  };
271
726
  const optionElements = [];
272
727
  const input = document.createElement("input");
@@ -293,8 +748,8 @@ var Combobox = {
293
748
  state.open.value = false;
294
749
  state.activeIndex.value = -1;
295
750
  setExpanded(input, false);
296
- setHidden(listbox, true);
297
751
  setDataState(listbox, "closed");
752
+ setHiddenAnimated(listbox, true);
298
753
  updateActiveDescendant(-1);
299
754
  }
300
755
  function selectOption(value) {
@@ -387,77 +842,678 @@ var Combobox = {
387
842
  return { input, listbox, state, Option };
388
843
  }
389
844
  };
390
- // src/dialog/dialog.ts
391
- import { signal as signal5 } from "@vertz/ui";
392
-
393
- // src/utils/focus.ts
394
- var FOCUSABLE_SELECTOR = [
395
- "a[href]",
396
- "button:not([disabled])",
397
- "input:not([disabled])",
398
- "select:not([disabled])",
399
- "textarea:not([disabled])",
400
- '[tabindex]:not([tabindex="-1"])',
401
- "[contenteditable]"
402
- ].join(", ");
403
- function getFocusableElements(container) {
404
- return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
405
- }
406
- function trapFocus(container) {
407
- function handleKeyDown(event) {
408
- if (event.key !== "Tab")
409
- return;
410
- const focusable = getFocusableElements(container);
411
- if (focusable.length === 0)
412
- return;
413
- const first = focusable[0];
414
- const last = focusable[focusable.length - 1];
415
- if (!first || !last)
416
- return;
417
- if (event.shiftKey) {
418
- if (document.activeElement === first) {
845
+ // src/command/command.ts
846
+ import { signal as signal8 } from "@vertz/ui";
847
+ var Command = {
848
+ Root(options = {}) {
849
+ const { filter: customFilter, onSelect, onInputChange, placeholder } = options;
850
+ const listId = uniqueId("command-list");
851
+ const state = {
852
+ inputValue: signal8(""),
853
+ activeIndex: signal8(0)
854
+ };
855
+ const allItems = [];
856
+ const groups = new Map;
857
+ const root = document.createElement("div");
858
+ const input = document.createElement("input");
859
+ input.setAttribute("type", "text");
860
+ input.setAttribute("role", "combobox");
861
+ input.setAttribute("aria-autocomplete", "list");
862
+ input.setAttribute("aria-expanded", "true");
863
+ input.setAttribute("aria-controls", listId);
864
+ if (placeholder)
865
+ input.placeholder = placeholder;
866
+ const list = document.createElement("div");
867
+ list.setAttribute("role", "listbox");
868
+ list.id = listId;
869
+ const empty = document.createElement("div");
870
+ setHidden(empty, true);
871
+ const defaultFilter = (value, search) => {
872
+ return value.toLowerCase().includes(search.toLowerCase());
873
+ };
874
+ const filterFn = customFilter ?? defaultFilter;
875
+ function getVisibleItems() {
876
+ return allItems.filter((item) => item.getAttribute("aria-hidden") !== "true");
877
+ }
878
+ function updateActiveItem() {
879
+ const visible = getVisibleItems();
880
+ const activeIdx = state.activeIndex.peek();
881
+ for (const item of allItems) {
882
+ item.setAttribute("aria-selected", "false");
883
+ }
884
+ if (visible.length > 0 && activeIdx >= 0 && activeIdx < visible.length) {
885
+ visible[activeIdx]?.setAttribute("aria-selected", "true");
886
+ }
887
+ }
888
+ function runFilter() {
889
+ const search = state.inputValue.peek();
890
+ let visibleCount = 0;
891
+ for (const item of allItems) {
892
+ const value = item.getAttribute("data-value") ?? "";
893
+ const text = item.textContent ?? "";
894
+ const keywords = item.getAttribute("data-keywords") ?? "";
895
+ const searchable = `${value} ${text} ${keywords}`;
896
+ const matches = search === "" || filterFn(searchable, search);
897
+ setHidden(item, !matches);
898
+ if (matches)
899
+ visibleCount++;
900
+ }
901
+ for (const [groupEl, group] of groups) {
902
+ const hasVisible = group.items.some((item) => item.getAttribute("aria-hidden") !== "true");
903
+ setHidden(group.heading, !hasVisible);
904
+ if (!hasVisible) {
905
+ groupEl.style.display = "none";
906
+ } else {
907
+ groupEl.style.display = "";
908
+ }
909
+ }
910
+ setHidden(empty, visibleCount > 0);
911
+ state.activeIndex.value = 0;
912
+ updateActiveItem();
913
+ }
914
+ input.addEventListener("input", () => {
915
+ state.inputValue.value = input.value;
916
+ onInputChange?.(input.value);
917
+ runFilter();
918
+ });
919
+ input.addEventListener("keydown", (event) => {
920
+ const visible = getVisibleItems();
921
+ if (isKey(event, Keys.ArrowDown)) {
419
922
  event.preventDefault();
420
- last.focus();
923
+ const next = Math.min(state.activeIndex.peek() + 1, visible.length - 1);
924
+ state.activeIndex.value = next;
925
+ updateActiveItem();
926
+ return;
421
927
  }
422
- } else {
423
- if (document.activeElement === last) {
928
+ if (isKey(event, Keys.ArrowUp)) {
424
929
  event.preventDefault();
425
- first.focus();
930
+ const prev = Math.max(state.activeIndex.peek() - 1, 0);
931
+ state.activeIndex.value = prev;
932
+ updateActiveItem();
933
+ return;
934
+ }
935
+ if (isKey(event, Keys.Enter)) {
936
+ event.preventDefault();
937
+ const active = visible[state.activeIndex.peek()];
938
+ if (active) {
939
+ const val = active.getAttribute("data-value");
940
+ if (val !== null) {
941
+ onSelect?.(val);
942
+ }
943
+ }
944
+ return;
945
+ }
946
+ if (isKey(event, Keys.Escape)) {
947
+ event.preventDefault();
948
+ input.value = "";
949
+ state.inputValue.value = "";
950
+ onInputChange?.("");
951
+ runFilter();
952
+ }
953
+ });
954
+ function createItem(value, label, keywords, parent) {
955
+ const item = document.createElement("div");
956
+ item.setAttribute("role", "option");
957
+ item.setAttribute("data-value", value);
958
+ item.setAttribute("aria-selected", "false");
959
+ item.textContent = label ?? value;
960
+ if (keywords && keywords.length > 0) {
961
+ item.setAttribute("data-keywords", keywords.join(" "));
426
962
  }
963
+ item.addEventListener("click", () => {
964
+ onSelect?.(value);
965
+ });
966
+ allItems.push(item);
967
+ (parent ?? list).appendChild(item);
968
+ updateActiveItem();
969
+ return item;
970
+ }
971
+ function Item(value, label, keywords) {
972
+ return createItem(value, label, keywords);
973
+ }
974
+ function Group(label) {
975
+ const headingId = uniqueId("command-group");
976
+ const el = document.createElement("div");
977
+ el.setAttribute("role", "group");
978
+ el.setAttribute("aria-labelledby", headingId);
979
+ const heading = document.createElement("div");
980
+ heading.id = headingId;
981
+ heading.textContent = label;
982
+ el.appendChild(heading);
983
+ const groupItems = [];
984
+ groups.set(el, { heading, items: groupItems });
985
+ list.appendChild(el);
986
+ return {
987
+ el,
988
+ Item: (value, itemLabel, keywords) => {
989
+ const item = createItem(value, itemLabel, keywords, el);
990
+ groupItems.push(item);
991
+ return item;
992
+ }
993
+ };
427
994
  }
995
+ function Separator() {
996
+ const hr = document.createElement("hr");
997
+ hr.setAttribute("role", "separator");
998
+ list.appendChild(hr);
999
+ return hr;
1000
+ }
1001
+ root.appendChild(input);
1002
+ root.appendChild(list);
1003
+ root.appendChild(empty);
1004
+ return { root, input, list, empty, state, Item, Group, Separator };
428
1005
  }
429
- container.addEventListener("keydown", handleKeyDown);
430
- return () => {
431
- container.removeEventListener("keydown", handleKeyDown);
1006
+ };
1007
+ // src/context-menu/context-menu.ts
1008
+ import { signal as signal9 } from "@vertz/ui";
1009
+
1010
+ // src/utils/dismiss.ts
1011
+ function createDismiss(options) {
1012
+ const { onDismiss, insideElements, escapeKey = true, clickOutside = true } = options;
1013
+ function handlePointerDown(event) {
1014
+ const target = event.target;
1015
+ const isInside = insideElements.some((el) => el.contains(target));
1016
+ if (!isInside) {
1017
+ onDismiss();
1018
+ }
1019
+ }
1020
+ function handleKeyDown(event) {
1021
+ if (event.key === "Escape") {
1022
+ onDismiss();
1023
+ }
1024
+ }
1025
+ if (clickOutside) {
1026
+ document.addEventListener("pointerdown", handlePointerDown, true);
1027
+ }
1028
+ if (escapeKey) {
1029
+ document.addEventListener("keydown", handleKeyDown);
1030
+ }
1031
+ return function cleanup() {
1032
+ if (clickOutside) {
1033
+ document.removeEventListener("pointerdown", handlePointerDown, true);
1034
+ }
1035
+ if (escapeKey) {
1036
+ document.removeEventListener("keydown", handleKeyDown);
1037
+ }
432
1038
  };
433
1039
  }
434
- function focusFirst(container) {
435
- const focusable = getFocusableElements(container);
436
- if (focusable.length > 0) {
437
- focusable[0]?.focus();
1040
+
1041
+ // src/utils/floating.ts
1042
+ import { autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
1043
+ function createFloatingPosition(reference, floating, options = {}) {
1044
+ const {
1045
+ placement = "bottom-start",
1046
+ strategy = "fixed",
1047
+ offset: offsetValue = 4,
1048
+ flip: enableFlip = true,
1049
+ shift: enableShift = true,
1050
+ middleware: extraMiddleware = [],
1051
+ matchReferenceWidth = false,
1052
+ portal = false
1053
+ } = options;
1054
+ if (portal && floating.parentElement !== document.body) {
1055
+ document.body.appendChild(floating);
1056
+ }
1057
+ const mw = [];
1058
+ mw.push(offset(offsetValue));
1059
+ if (enableFlip)
1060
+ mw.push(flip());
1061
+ if (enableShift)
1062
+ mw.push(shift());
1063
+ if (matchReferenceWidth) {
1064
+ mw.push({
1065
+ name: "matchReferenceWidth",
1066
+ fn({ rects }) {
1067
+ floating.style.minWidth = `${rects.reference.width}px`;
1068
+ return {};
1069
+ }
1070
+ });
1071
+ }
1072
+ mw.push(...extraMiddleware);
1073
+ function updatePosition() {
1074
+ computePosition(reference, floating, {
1075
+ placement,
1076
+ strategy,
1077
+ middleware: mw
1078
+ }).then((result) => {
1079
+ floating.style.position = result.strategy;
1080
+ floating.style.left = `${result.x}px`;
1081
+ floating.style.top = `${result.y}px`;
1082
+ const [side = "bottom", align = "center"] = result.placement.split("-");
1083
+ floating.setAttribute("data-side", side);
1084
+ floating.setAttribute("data-align", align);
1085
+ });
438
1086
  }
1087
+ const cleanupAutoUpdate = autoUpdate(reference, floating, updatePosition, {
1088
+ animationFrame: true
1089
+ });
1090
+ return {
1091
+ cleanup: cleanupAutoUpdate,
1092
+ update() {
1093
+ updatePosition();
1094
+ return Promise.resolve();
1095
+ }
1096
+ };
439
1097
  }
440
- function saveFocus() {
441
- const previously = document.activeElement;
442
- return () => {
443
- if (previously && typeof previously.focus === "function") {
444
- previously.focus();
1098
+ function virtualElement(x, y) {
1099
+ return {
1100
+ getBoundingClientRect() {
1101
+ return {
1102
+ x,
1103
+ y,
1104
+ top: y,
1105
+ left: x,
1106
+ bottom: y,
1107
+ right: x,
1108
+ width: 0,
1109
+ height: 0,
1110
+ toJSON() {
1111
+ return this;
1112
+ }
1113
+ };
445
1114
  }
446
1115
  };
447
1116
  }
448
- function setRovingTabindex(items, activeIndex) {
449
- for (let i = 0;i < items.length; i++) {
1117
+
1118
+ // src/context-menu/context-menu.ts
1119
+ var ContextMenu = {
1120
+ Root(options = {}) {
1121
+ const { onSelect, positioning } = options;
1122
+ const state = {
1123
+ open: signal9(false),
1124
+ activeIndex: signal9(-1)
1125
+ };
1126
+ const items = [];
1127
+ let floatingCleanup = null;
1128
+ let dismissCleanup = null;
1129
+ const trigger = document.createElement("div");
1130
+ const contentId = uniqueId("ctx-menu");
1131
+ const content = document.createElement("div");
1132
+ content.setAttribute("role", "menu");
1133
+ content.id = contentId;
1134
+ content.style.position = "fixed";
1135
+ setHidden(content, true);
1136
+ setDataState(content, "closed");
1137
+ function handleClickOutside(event) {
1138
+ const target = event.target;
1139
+ if (!trigger.contains(target) && !content.contains(target)) {
1140
+ close();
1141
+ }
1142
+ }
1143
+ function open(x, y) {
1144
+ state.open.value = true;
1145
+ setHidden(content, false);
1146
+ setDataState(content, "open");
1147
+ if (positioning) {
1148
+ const result = createFloatingPosition(virtualElement(x, y), content, {
1149
+ strategy: "fixed",
1150
+ ...positioning
1151
+ });
1152
+ floatingCleanup = result.cleanup;
1153
+ dismissCleanup = createDismiss({
1154
+ onDismiss: close,
1155
+ insideElements: [trigger, content],
1156
+ escapeKey: false
1157
+ });
1158
+ } else {
1159
+ content.style.left = `${x}px`;
1160
+ content.style.top = `${y}px`;
1161
+ document.addEventListener("mousedown", handleClickOutside);
1162
+ }
1163
+ state.activeIndex.value = 0;
1164
+ updateActiveItem(0);
1165
+ items[0]?.focus();
1166
+ }
1167
+ function close() {
1168
+ state.open.value = false;
1169
+ setDataState(content, "closed");
1170
+ setHiddenAnimated(content, true);
1171
+ if (positioning) {
1172
+ floatingCleanup?.();
1173
+ floatingCleanup = null;
1174
+ dismissCleanup?.();
1175
+ dismissCleanup = null;
1176
+ } else {
1177
+ document.removeEventListener("mousedown", handleClickOutside);
1178
+ }
1179
+ }
1180
+ function updateActiveItem(index) {
1181
+ for (let i = 0;i < items.length; i++) {
1182
+ items[i]?.setAttribute("tabindex", i === index ? "0" : "-1");
1183
+ }
1184
+ }
1185
+ trigger.addEventListener("contextmenu", (event) => {
1186
+ event.preventDefault();
1187
+ if (state.open.peek()) {
1188
+ close();
1189
+ }
1190
+ open(event.clientX, event.clientY);
1191
+ });
1192
+ content.addEventListener("keydown", (event) => {
1193
+ if (isKey(event, Keys.Escape)) {
1194
+ event.preventDefault();
1195
+ close();
1196
+ return;
1197
+ }
1198
+ if (isKey(event, Keys.Enter, Keys.Space)) {
1199
+ event.preventDefault();
1200
+ const active = items[state.activeIndex.peek()];
1201
+ if (active) {
1202
+ const val = active.getAttribute("data-value");
1203
+ if (val !== null) {
1204
+ onSelect?.(val);
1205
+ close();
1206
+ }
1207
+ }
1208
+ return;
1209
+ }
1210
+ const result = handleListNavigation(event, items, { orientation: "vertical" });
1211
+ if (result) {
1212
+ const idx = items.indexOf(result);
1213
+ if (idx >= 0) {
1214
+ state.activeIndex.value = idx;
1215
+ updateActiveItem(idx);
1216
+ }
1217
+ return;
1218
+ }
1219
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
1220
+ const char = event.key.toLowerCase();
1221
+ const match = items.find((item) => item.textContent?.toLowerCase().startsWith(char));
1222
+ if (match) {
1223
+ const idx = items.indexOf(match);
1224
+ state.activeIndex.value = idx;
1225
+ updateActiveItem(idx);
1226
+ match.focus();
1227
+ }
1228
+ }
1229
+ });
1230
+ function createItem(value, label, parent) {
1231
+ const item = document.createElement("div");
1232
+ item.setAttribute("role", "menuitem");
1233
+ item.setAttribute("data-value", value);
1234
+ item.setAttribute("tabindex", "-1");
1235
+ item.textContent = label ?? value;
1236
+ item.addEventListener("click", () => {
1237
+ onSelect?.(value);
1238
+ close();
1239
+ });
1240
+ items.push(item);
1241
+ (parent ?? content).appendChild(item);
1242
+ return item;
1243
+ }
1244
+ function Item(value, label) {
1245
+ return createItem(value, label);
1246
+ }
1247
+ function Group(label) {
1248
+ const el = document.createElement("div");
1249
+ el.setAttribute("role", "group");
1250
+ el.setAttribute("aria-label", label);
1251
+ content.appendChild(el);
1252
+ return {
1253
+ el,
1254
+ Item: (value, itemLabel) => createItem(value, itemLabel, el)
1255
+ };
1256
+ }
1257
+ function Separator() {
1258
+ const hr = document.createElement("hr");
1259
+ hr.setAttribute("role", "separator");
1260
+ content.appendChild(hr);
1261
+ return hr;
1262
+ }
1263
+ function Label(text) {
1264
+ const el = document.createElement("div");
1265
+ el.setAttribute("role", "none");
1266
+ el.textContent = text;
1267
+ content.appendChild(el);
1268
+ return el;
1269
+ }
1270
+ return { trigger, content, state, Item, Group, Separator, Label };
1271
+ }
1272
+ };
1273
+ // src/date-picker/date-picker.ts
1274
+ import { signal as signal11 } from "@vertz/ui";
1275
+
1276
+ // src/popover/popover.ts
1277
+ import { signal as signal10 } from "@vertz/ui";
1278
+
1279
+ // src/utils/focus.ts
1280
+ var FOCUSABLE_SELECTOR = [
1281
+ "a[href]",
1282
+ "button:not([disabled])",
1283
+ "input:not([disabled])",
1284
+ "select:not([disabled])",
1285
+ "textarea:not([disabled])",
1286
+ '[tabindex]:not([tabindex="-1"])',
1287
+ "[contenteditable]"
1288
+ ].join(", ");
1289
+ function getFocusableElements(container) {
1290
+ return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
1291
+ }
1292
+ function trapFocus(container) {
1293
+ function handleKeyDown(event) {
1294
+ if (event.key !== "Tab")
1295
+ return;
1296
+ const focusable = getFocusableElements(container);
1297
+ if (focusable.length === 0)
1298
+ return;
1299
+ const first = focusable[0];
1300
+ const last = focusable[focusable.length - 1];
1301
+ if (!first || !last)
1302
+ return;
1303
+ if (event.shiftKey) {
1304
+ if (document.activeElement === first) {
1305
+ event.preventDefault();
1306
+ last.focus();
1307
+ }
1308
+ } else {
1309
+ if (document.activeElement === last) {
1310
+ event.preventDefault();
1311
+ first.focus();
1312
+ }
1313
+ }
1314
+ }
1315
+ container.addEventListener("keydown", handleKeyDown);
1316
+ return () => {
1317
+ container.removeEventListener("keydown", handleKeyDown);
1318
+ };
1319
+ }
1320
+ function focusFirst(container) {
1321
+ const focusable = getFocusableElements(container);
1322
+ if (focusable.length > 0) {
1323
+ focusable[0]?.focus();
1324
+ }
1325
+ }
1326
+ function saveFocus() {
1327
+ const previously = document.activeElement;
1328
+ return () => {
1329
+ if (previously && typeof previously.focus === "function") {
1330
+ previously.focus();
1331
+ }
1332
+ };
1333
+ }
1334
+ function setRovingTabindex(items, activeIndex) {
1335
+ for (let i = 0;i < items.length; i++) {
450
1336
  items[i]?.setAttribute("tabindex", i === activeIndex ? "0" : "-1");
451
1337
  }
452
1338
  }
453
1339
 
1340
+ // src/popover/popover.ts
1341
+ var Popover = {
1342
+ Root(options = {}) {
1343
+ const { defaultOpen = false, onOpenChange, positioning } = options;
1344
+ const ids = linkedIds("popover");
1345
+ const state = { open: signal10(defaultOpen) };
1346
+ let restoreFocus = null;
1347
+ let floatingCleanup = null;
1348
+ let dismissCleanup = null;
1349
+ const trigger = document.createElement("button");
1350
+ trigger.setAttribute("type", "button");
1351
+ trigger.id = ids.triggerId;
1352
+ trigger.setAttribute("aria-controls", ids.contentId);
1353
+ trigger.setAttribute("aria-haspopup", "dialog");
1354
+ setExpanded(trigger, defaultOpen);
1355
+ setDataState(trigger, defaultOpen ? "open" : "closed");
1356
+ const content = document.createElement("div");
1357
+ content.setAttribute("role", "dialog");
1358
+ content.id = ids.contentId;
1359
+ setHidden(content, !defaultOpen);
1360
+ setDataState(content, defaultOpen ? "open" : "closed");
1361
+ function open() {
1362
+ state.open.value = true;
1363
+ setExpanded(trigger, true);
1364
+ setHidden(content, false);
1365
+ setDataState(trigger, "open");
1366
+ setDataState(content, "open");
1367
+ restoreFocus = saveFocus();
1368
+ queueMicrotask(() => focusFirst(content));
1369
+ if (positioning) {
1370
+ const result = createFloatingPosition(trigger, content, positioning);
1371
+ floatingCleanup = result.cleanup;
1372
+ dismissCleanup = createDismiss({
1373
+ onDismiss: close,
1374
+ insideElements: [trigger, content],
1375
+ escapeKey: false
1376
+ });
1377
+ }
1378
+ onOpenChange?.(true);
1379
+ }
1380
+ function close() {
1381
+ state.open.value = false;
1382
+ setExpanded(trigger, false);
1383
+ setDataState(trigger, "closed");
1384
+ setDataState(content, "closed");
1385
+ setHiddenAnimated(content, true);
1386
+ floatingCleanup?.();
1387
+ floatingCleanup = null;
1388
+ dismissCleanup?.();
1389
+ dismissCleanup = null;
1390
+ restoreFocus?.();
1391
+ restoreFocus = null;
1392
+ onOpenChange?.(false);
1393
+ }
1394
+ trigger.addEventListener("click", () => {
1395
+ if (state.open.peek()) {
1396
+ close();
1397
+ } else {
1398
+ open();
1399
+ }
1400
+ });
1401
+ content.addEventListener("keydown", (event) => {
1402
+ if (isKey(event, Keys.Escape)) {
1403
+ event.preventDefault();
1404
+ close();
1405
+ }
1406
+ });
1407
+ return { trigger, content, state };
1408
+ }
1409
+ };
1410
+
1411
+ // src/date-picker/date-picker.ts
1412
+ function defaultFormatDate(date) {
1413
+ return date.toLocaleDateString();
1414
+ }
1415
+ function formatRangeDisplay(value, fmt) {
1416
+ return `${fmt(value.from)} – ${fmt(value.to)}`;
1417
+ }
1418
+ var DatePicker = {
1419
+ Root(options = {}) {
1420
+ const {
1421
+ mode = "single",
1422
+ defaultValue = null,
1423
+ minDate,
1424
+ maxDate,
1425
+ disabled,
1426
+ formatDate = defaultFormatDate,
1427
+ placeholder = "Pick a date",
1428
+ onValueChange,
1429
+ onOpenChange
1430
+ } = options;
1431
+ const defaultMonth = options.defaultMonth ?? (defaultValue instanceof Date ? defaultValue : defaultValue && ("from" in defaultValue) ? defaultValue.from : new Date);
1432
+ const calendarMode = mode === "range" ? "range" : "single";
1433
+ const calendarDefaultValue = defaultValue instanceof Date ? defaultValue : defaultValue && ("from" in defaultValue) ? defaultValue : undefined;
1434
+ const popover = Popover.Root({
1435
+ onOpenChange(open) {
1436
+ state.open.value = open;
1437
+ onOpenChange?.(open);
1438
+ }
1439
+ });
1440
+ const calendarResult = Calendar.Root({
1441
+ mode: calendarMode,
1442
+ defaultValue: calendarDefaultValue,
1443
+ defaultMonth,
1444
+ minDate,
1445
+ maxDate,
1446
+ disabled,
1447
+ onValueChange(value) {
1448
+ if (mode === "single" && value instanceof Date) {
1449
+ state.value.value = value;
1450
+ updateTriggerText();
1451
+ onValueChange?.(value);
1452
+ hide();
1453
+ } else if (mode === "range" && value && "from" in value) {
1454
+ const range = value;
1455
+ state.value.value = range;
1456
+ updateTriggerText();
1457
+ onValueChange?.(range);
1458
+ if (range.from && range.to && range.from.getTime() !== range.to.getTime()) {
1459
+ hide();
1460
+ }
1461
+ }
1462
+ }
1463
+ });
1464
+ popover.content.appendChild(calendarResult.root);
1465
+ const state = {
1466
+ open: signal11(false),
1467
+ value: signal11(defaultValue),
1468
+ displayMonth: calendarResult.state.displayMonth
1469
+ };
1470
+ function updateTriggerText() {
1471
+ const val = state.value.peek();
1472
+ if (val === null) {
1473
+ popover.trigger.textContent = placeholder;
1474
+ popover.trigger.setAttribute("data-placeholder", "true");
1475
+ } else if (val instanceof Date) {
1476
+ popover.trigger.textContent = formatDate(val);
1477
+ popover.trigger.removeAttribute("data-placeholder");
1478
+ } else if ("from" in val) {
1479
+ popover.trigger.textContent = formatRangeDisplay(val, formatDate);
1480
+ popover.trigger.removeAttribute("data-placeholder");
1481
+ }
1482
+ }
1483
+ function show() {
1484
+ popover.trigger.click();
1485
+ }
1486
+ function hide() {
1487
+ if (state.open.peek()) {
1488
+ popover.trigger.click();
1489
+ }
1490
+ }
1491
+ updateTriggerText();
1492
+ return {
1493
+ trigger: popover.trigger,
1494
+ content: popover.content,
1495
+ calendar: {
1496
+ root: calendarResult.root,
1497
+ header: calendarResult.header,
1498
+ title: calendarResult.title,
1499
+ prevButton: calendarResult.prevButton,
1500
+ nextButton: calendarResult.nextButton,
1501
+ grid: calendarResult.grid
1502
+ },
1503
+ state,
1504
+ show,
1505
+ hide
1506
+ };
1507
+ }
1508
+ };
454
1509
  // src/dialog/dialog.ts
1510
+ import { signal as signal12 } from "@vertz/ui";
455
1511
  var Dialog = {
456
1512
  Root(options = {}) {
457
1513
  const { modal = true, defaultOpen = false, onOpenChange } = options;
458
1514
  const ids = linkedIds("dialog");
459
1515
  const titleId = `${ids.contentId}-title`;
460
- const state = { open: signal5(defaultOpen) };
1516
+ const state = { open: signal12(defaultOpen) };
461
1517
  let restoreFocus = null;
462
1518
  let removeTrap = null;
463
1519
  const trigger = document.createElement("button");
@@ -468,11 +1524,6 @@ var Dialog = {
468
1524
  setDataState(trigger, defaultOpen ? "open" : "closed");
469
1525
  const overlay = document.createElement("div");
470
1526
  overlay.setAttribute("data-dialog-overlay", "");
471
- if (modal) {
472
- overlay.style.position = "fixed";
473
- overlay.style.inset = "0";
474
- overlay.style.zIndex = "49";
475
- }
476
1527
  setHidden(overlay, !defaultOpen);
477
1528
  setDataState(overlay, defaultOpen ? "open" : "closed");
478
1529
  const content = document.createElement("div");
@@ -480,11 +1531,6 @@ var Dialog = {
480
1531
  content.id = ids.contentId;
481
1532
  if (modal) {
482
1533
  content.setAttribute("aria-modal", "true");
483
- content.style.position = "fixed";
484
- content.style.top = "50%";
485
- content.style.left = "50%";
486
- content.style.transform = "translate(-50%, -50%)";
487
- content.style.zIndex = "50";
488
1534
  }
489
1535
  setLabelledBy(content, titleId);
490
1536
  setHidden(content, !defaultOpen);
@@ -512,11 +1558,11 @@ var Dialog = {
512
1558
  function closeDialog() {
513
1559
  state.open.value = false;
514
1560
  setExpanded(trigger, false);
515
- setHidden(overlay, true);
516
- setHidden(content, true);
517
1561
  setDataState(trigger, "closed");
518
1562
  setDataState(overlay, "closed");
519
1563
  setDataState(content, "closed");
1564
+ setHiddenAnimated(overlay, true);
1565
+ setHiddenAnimated(content, true);
520
1566
  removeTrap?.();
521
1567
  removeTrap = null;
522
1568
  restoreFocus?.();
@@ -543,20 +1589,22 @@ var Dialog = {
543
1589
  closeDialog();
544
1590
  }
545
1591
  });
546
- return { trigger, overlay, content, title, close, state };
1592
+ return { trigger, overlay, content, title, close, state, show: openDialog, hide: closeDialog };
547
1593
  }
548
1594
  };
549
1595
  // src/menu/menu.ts
550
- import { signal as signal6 } from "@vertz/ui";
1596
+ import { signal as signal13 } from "@vertz/ui";
551
1597
  var Menu = {
552
1598
  Root(options = {}) {
553
- const { onSelect } = options;
1599
+ const { onSelect, positioning } = options;
554
1600
  const ids = linkedIds("menu");
555
1601
  const state = {
556
- open: signal6(false),
557
- activeIndex: signal6(-1)
1602
+ open: signal13(false),
1603
+ activeIndex: signal13(-1)
558
1604
  };
559
1605
  const items = [];
1606
+ let floatingCleanup = null;
1607
+ let dismissCleanup = null;
560
1608
  const trigger = document.createElement("button");
561
1609
  trigger.setAttribute("type", "button");
562
1610
  trigger.id = ids.triggerId;
@@ -566,25 +1614,58 @@ var Menu = {
566
1614
  setDataState(trigger, "closed");
567
1615
  const content = document.createElement("div");
568
1616
  content.setAttribute("role", "menu");
1617
+ content.setAttribute("tabindex", "-1");
569
1618
  content.id = ids.contentId;
570
1619
  setHidden(content, true);
571
1620
  setDataState(content, "closed");
572
- function open() {
1621
+ function handleClickOutside(event) {
1622
+ const target = event.target;
1623
+ if (!trigger.contains(target) && !content.contains(target)) {
1624
+ close();
1625
+ }
1626
+ }
1627
+ function open(activateFirst = false) {
573
1628
  state.open.value = true;
574
1629
  setExpanded(trigger, true);
575
1630
  setHidden(content, false);
576
1631
  setDataState(trigger, "open");
577
1632
  setDataState(content, "open");
578
- state.activeIndex.value = 0;
579
- updateActiveItem(0);
580
- items[0]?.focus();
1633
+ if (positioning) {
1634
+ const ref = positioning.referenceElement ?? trigger;
1635
+ const result = createFloatingPosition(ref, content, positioning);
1636
+ floatingCleanup = result.cleanup;
1637
+ dismissCleanup = createDismiss({
1638
+ onDismiss: close,
1639
+ insideElements: [ref, trigger, content],
1640
+ escapeKey: false
1641
+ });
1642
+ } else {
1643
+ document.addEventListener("mousedown", handleClickOutside);
1644
+ }
1645
+ if (activateFirst && items.length > 0) {
1646
+ state.activeIndex.value = 0;
1647
+ updateActiveItem(0);
1648
+ items[0]?.focus();
1649
+ } else {
1650
+ state.activeIndex.value = -1;
1651
+ updateActiveItem(-1);
1652
+ content.focus();
1653
+ }
581
1654
  }
582
1655
  function close() {
583
1656
  state.open.value = false;
584
1657
  setExpanded(trigger, false);
585
- setHidden(content, true);
586
1658
  setDataState(trigger, "closed");
587
1659
  setDataState(content, "closed");
1660
+ setHiddenAnimated(content, true);
1661
+ if (positioning) {
1662
+ floatingCleanup?.();
1663
+ floatingCleanup = null;
1664
+ dismissCleanup?.();
1665
+ dismissCleanup = null;
1666
+ } else {
1667
+ document.removeEventListener("mousedown", handleClickOutside);
1668
+ }
588
1669
  trigger.focus();
589
1670
  }
590
1671
  function updateActiveItem(index) {
@@ -603,7 +1684,7 @@ var Menu = {
603
1684
  if (isKey(event, Keys.ArrowDown, Keys.Enter, Keys.Space)) {
604
1685
  event.preventDefault();
605
1686
  if (!state.open.peek())
606
- open();
1687
+ open(true);
607
1688
  }
608
1689
  });
609
1690
  content.addEventListener("keydown", (event) => {
@@ -624,6 +1705,23 @@ var Menu = {
624
1705
  }
625
1706
  return;
626
1707
  }
1708
+ if (state.activeIndex.peek() === -1) {
1709
+ if (isKey(event, Keys.ArrowDown)) {
1710
+ event.preventDefault();
1711
+ state.activeIndex.value = 0;
1712
+ updateActiveItem(0);
1713
+ items[0]?.focus();
1714
+ return;
1715
+ }
1716
+ if (isKey(event, Keys.ArrowUp)) {
1717
+ event.preventDefault();
1718
+ const last = items.length - 1;
1719
+ state.activeIndex.value = last;
1720
+ updateActiveItem(last);
1721
+ items[last]?.focus();
1722
+ return;
1723
+ }
1724
+ }
627
1725
  const result = handleListNavigation(event, items, { orientation: "vertical" });
628
1726
  if (result) {
629
1727
  const idx = items.indexOf(result);
@@ -631,87 +1729,571 @@ var Menu = {
631
1729
  state.activeIndex.value = idx;
632
1730
  updateActiveItem(idx);
633
1731
  }
634
- }
635
- });
636
- function Item(value, label) {
637
- const item = document.createElement("div");
638
- item.setAttribute("role", "menuitem");
639
- item.setAttribute("data-value", value);
640
- item.setAttribute("tabindex", "-1");
641
- item.textContent = label ?? value;
642
- item.addEventListener("click", () => {
643
- onSelect?.(value);
644
- close();
1732
+ return;
1733
+ }
1734
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
1735
+ const char = event.key.toLowerCase();
1736
+ const match = items.find((item) => item.textContent?.toLowerCase().startsWith(char));
1737
+ if (match) {
1738
+ const idx = items.indexOf(match);
1739
+ state.activeIndex.value = idx;
1740
+ updateActiveItem(idx);
1741
+ match.focus();
1742
+ }
1743
+ }
1744
+ });
1745
+ function createItem(value, label, parent) {
1746
+ const item = document.createElement("div");
1747
+ item.setAttribute("role", "menuitem");
1748
+ item.setAttribute("data-value", value);
1749
+ item.setAttribute("tabindex", "-1");
1750
+ item.textContent = label ?? value;
1751
+ item.addEventListener("click", () => {
1752
+ onSelect?.(value);
1753
+ close();
1754
+ });
1755
+ items.push(item);
1756
+ (parent ?? content).appendChild(item);
1757
+ return item;
1758
+ }
1759
+ function Item(value, label) {
1760
+ return createItem(value, label);
1761
+ }
1762
+ function Group(label) {
1763
+ const el = document.createElement("div");
1764
+ el.setAttribute("role", "group");
1765
+ el.setAttribute("aria-label", label);
1766
+ content.appendChild(el);
1767
+ return {
1768
+ el,
1769
+ Item: (value, itemLabel) => createItem(value, itemLabel, el)
1770
+ };
1771
+ }
1772
+ function Separator() {
1773
+ const hr = document.createElement("hr");
1774
+ hr.setAttribute("role", "separator");
1775
+ content.appendChild(hr);
1776
+ return hr;
1777
+ }
1778
+ function Label(text) {
1779
+ const el = document.createElement("div");
1780
+ el.setAttribute("role", "none");
1781
+ el.textContent = text;
1782
+ content.appendChild(el);
1783
+ return el;
1784
+ }
1785
+ return { trigger, content, state, Item, Group, Separator, Label };
1786
+ }
1787
+ };
1788
+
1789
+ // src/dropdown-menu/dropdown-menu.ts
1790
+ var DropdownMenu = {
1791
+ Root(options = {}) {
1792
+ const { positioning, ...rest } = options;
1793
+ return Menu.Root({
1794
+ ...rest,
1795
+ positioning: {
1796
+ placement: "bottom-start",
1797
+ ...positioning
1798
+ }
1799
+ });
1800
+ }
1801
+ };
1802
+ // src/hover-card/hover-card.ts
1803
+ import { signal as signal14 } from "@vertz/ui";
1804
+ var HoverCard = {
1805
+ Root(options = {}) {
1806
+ const { openDelay = 700, closeDelay = 300, onOpenChange, positioning } = options;
1807
+ const contentId = uniqueId("hovercard");
1808
+ const state = { open: signal14(false) };
1809
+ let openTimeout = null;
1810
+ let closeTimeout = null;
1811
+ let floatingCleanup = null;
1812
+ const trigger = document.createElement("span");
1813
+ trigger.setAttribute("aria-haspopup", "dialog");
1814
+ trigger.setAttribute("aria-expanded", "false");
1815
+ const content = document.createElement("div");
1816
+ content.setAttribute("role", "dialog");
1817
+ content.id = contentId;
1818
+ setHidden(content, true);
1819
+ setDataState(content, "closed");
1820
+ function cancelTimers() {
1821
+ if (openTimeout) {
1822
+ clearTimeout(openTimeout);
1823
+ openTimeout = null;
1824
+ }
1825
+ if (closeTimeout) {
1826
+ clearTimeout(closeTimeout);
1827
+ closeTimeout = null;
1828
+ }
1829
+ }
1830
+ function show() {
1831
+ cancelTimers();
1832
+ if (state.open.peek())
1833
+ return;
1834
+ openTimeout = setTimeout(() => {
1835
+ state.open.value = true;
1836
+ setExpanded(trigger, true);
1837
+ setHidden(content, false);
1838
+ setDataState(content, "open");
1839
+ if (positioning) {
1840
+ const effectivePlacement = positioning.placement ?? "bottom";
1841
+ const result = createFloatingPosition(trigger, content, {
1842
+ ...positioning,
1843
+ placement: effectivePlacement
1844
+ });
1845
+ floatingCleanup = result.cleanup;
1846
+ }
1847
+ onOpenChange?.(true);
1848
+ openTimeout = null;
1849
+ }, openDelay);
1850
+ }
1851
+ function hide() {
1852
+ cancelTimers();
1853
+ if (!state.open.peek())
1854
+ return;
1855
+ closeTimeout = setTimeout(() => {
1856
+ state.open.value = false;
1857
+ setExpanded(trigger, false);
1858
+ setDataState(content, "closed");
1859
+ setHiddenAnimated(content, true);
1860
+ floatingCleanup?.();
1861
+ floatingCleanup = null;
1862
+ onOpenChange?.(false);
1863
+ closeTimeout = null;
1864
+ }, closeDelay);
1865
+ }
1866
+ trigger.addEventListener("mouseenter", show);
1867
+ trigger.addEventListener("mouseleave", hide);
1868
+ trigger.addEventListener("focus", () => {
1869
+ cancelTimers();
1870
+ state.open.value = true;
1871
+ setExpanded(trigger, true);
1872
+ setHidden(content, false);
1873
+ setDataState(content, "open");
1874
+ if (positioning) {
1875
+ floatingCleanup?.();
1876
+ const effectivePlacement = positioning.placement ?? "bottom";
1877
+ const result = createFloatingPosition(trigger, content, {
1878
+ ...positioning,
1879
+ placement: effectivePlacement
1880
+ });
1881
+ floatingCleanup = result.cleanup;
1882
+ }
1883
+ onOpenChange?.(true);
1884
+ });
1885
+ trigger.addEventListener("blur", (event) => {
1886
+ const related = event.relatedTarget;
1887
+ if (related && (trigger.contains(related) || content.contains(related)))
1888
+ return;
1889
+ hide();
1890
+ });
1891
+ content.addEventListener("mouseenter", () => {
1892
+ if (closeTimeout) {
1893
+ clearTimeout(closeTimeout);
1894
+ closeTimeout = null;
1895
+ }
1896
+ });
1897
+ content.addEventListener("mouseleave", hide);
1898
+ content.addEventListener("focusin", () => {
1899
+ if (closeTimeout) {
1900
+ clearTimeout(closeTimeout);
1901
+ closeTimeout = null;
1902
+ }
1903
+ });
1904
+ content.addEventListener("focusout", (event) => {
1905
+ const related = event.relatedTarget;
1906
+ if (related && (trigger.contains(related) || content.contains(related)))
1907
+ return;
1908
+ hide();
1909
+ });
1910
+ function hideImmediate() {
1911
+ cancelTimers();
1912
+ state.open.value = false;
1913
+ setExpanded(trigger, false);
1914
+ setDataState(content, "closed");
1915
+ setHiddenAnimated(content, true);
1916
+ floatingCleanup?.();
1917
+ floatingCleanup = null;
1918
+ onOpenChange?.(false);
1919
+ }
1920
+ content.addEventListener("keydown", (event) => {
1921
+ if (isKey(event, Keys.Escape)) {
1922
+ hideImmediate();
1923
+ trigger.focus();
1924
+ }
1925
+ });
1926
+ trigger.addEventListener("keydown", (event) => {
1927
+ if (isKey(event, Keys.Escape) && state.open.peek()) {
1928
+ hideImmediate();
1929
+ }
1930
+ });
1931
+ return { trigger, content, state };
1932
+ }
1933
+ };
1934
+ // src/menubar/menubar.ts
1935
+ import { signal as signal15 } from "@vertz/ui";
1936
+ var Menubar = {
1937
+ Root(options = {}) {
1938
+ const { onSelect, positioning } = options;
1939
+ const state = { activeMenu: signal15(null) };
1940
+ const triggers = [];
1941
+ const menus = new Map;
1942
+ let floatingCleanup = null;
1943
+ let dismissCleanup = null;
1944
+ const root = document.createElement("div");
1945
+ root.setAttribute("role", "menubar");
1946
+ function closeAll() {
1947
+ for (const [, menu] of menus) {
1948
+ setExpanded(menu.trigger, false);
1949
+ setDataState(menu.trigger, "closed");
1950
+ setDataState(menu.content, "closed");
1951
+ setHiddenAnimated(menu.content, true);
1952
+ }
1953
+ state.activeMenu.value = null;
1954
+ if (positioning) {
1955
+ floatingCleanup?.();
1956
+ floatingCleanup = null;
1957
+ dismissCleanup?.();
1958
+ dismissCleanup = null;
1959
+ } else {
1960
+ document.removeEventListener("mousedown", handleClickOutside);
1961
+ }
1962
+ }
1963
+ function openMenu(value) {
1964
+ const current = state.activeMenu.peek();
1965
+ if (current && current !== value) {
1966
+ const prev = menus.get(current);
1967
+ if (prev) {
1968
+ setExpanded(prev.trigger, false);
1969
+ setDataState(prev.trigger, "closed");
1970
+ setDataState(prev.content, "closed");
1971
+ setHiddenAnimated(prev.content, true);
1972
+ }
1973
+ if (positioning) {
1974
+ floatingCleanup?.();
1975
+ floatingCleanup = null;
1976
+ }
1977
+ }
1978
+ const menu = menus.get(value);
1979
+ if (!menu)
1980
+ return;
1981
+ state.activeMenu.value = value;
1982
+ setExpanded(menu.trigger, true);
1983
+ setHidden(menu.content, false);
1984
+ setDataState(menu.trigger, "open");
1985
+ setDataState(menu.content, "open");
1986
+ if (positioning) {
1987
+ const result = createFloatingPosition(menu.trigger, menu.content, positioning);
1988
+ floatingCleanup = result.cleanup;
1989
+ if (!dismissCleanup) {
1990
+ dismissCleanup = createDismiss({
1991
+ onDismiss: closeAll,
1992
+ insideElements: [root],
1993
+ escapeKey: false
1994
+ });
1995
+ }
1996
+ } else {
1997
+ document.addEventListener("mousedown", handleClickOutside);
1998
+ }
1999
+ const firstItem = menu.items[0];
2000
+ if (firstItem) {
2001
+ firstItem.setAttribute("tabindex", "0");
2002
+ firstItem.focus();
2003
+ }
2004
+ }
2005
+ function handleClickOutside(event) {
2006
+ const target = event.target;
2007
+ if (!root.contains(target)) {
2008
+ closeAll();
2009
+ }
2010
+ }
2011
+ root.addEventListener("keydown", (event) => {
2012
+ if (isKey(event, Keys.ArrowLeft, Keys.ArrowRight, Keys.Home, Keys.End)) {
2013
+ const focused = document.activeElement;
2014
+ const triggerIndex = triggers.indexOf(focused);
2015
+ if (triggerIndex >= 0) {
2016
+ const result = handleListNavigation(event, triggers, { orientation: "horizontal" });
2017
+ if (result && state.activeMenu.peek()) {
2018
+ const newTrigger = result;
2019
+ const menuValue = newTrigger.getAttribute("data-value");
2020
+ if (menuValue)
2021
+ openMenu(menuValue);
2022
+ }
2023
+ }
2024
+ }
2025
+ });
2026
+ function Menu2(value, label) {
2027
+ const ids = linkedIds("menubar-menu");
2028
+ const items = [];
2029
+ const trigger = document.createElement("button");
2030
+ trigger.setAttribute("type", "button");
2031
+ trigger.setAttribute("role", "menuitem");
2032
+ trigger.id = ids.triggerId;
2033
+ trigger.setAttribute("aria-controls", ids.contentId);
2034
+ trigger.setAttribute("aria-haspopup", "menu");
2035
+ trigger.setAttribute("data-value", value);
2036
+ trigger.textContent = label ?? value;
2037
+ setExpanded(trigger, false);
2038
+ setDataState(trigger, "closed");
2039
+ setRovingTabindex(triggers.concat(trigger), triggers.length);
2040
+ const content = document.createElement("div");
2041
+ content.setAttribute("role", "menu");
2042
+ content.id = ids.contentId;
2043
+ setHidden(content, true);
2044
+ setDataState(content, "closed");
2045
+ trigger.addEventListener("click", () => {
2046
+ if (state.activeMenu.peek() === value) {
2047
+ closeAll();
2048
+ } else {
2049
+ openMenu(value);
2050
+ }
2051
+ });
2052
+ trigger.addEventListener("keydown", (event) => {
2053
+ if (isKey(event, Keys.ArrowDown, Keys.Enter, Keys.Space)) {
2054
+ event.preventDefault();
2055
+ openMenu(value);
2056
+ }
2057
+ });
2058
+ content.addEventListener("keydown", (event) => {
2059
+ if (isKey(event, Keys.Escape)) {
2060
+ event.preventDefault();
2061
+ event.stopPropagation();
2062
+ closeAll();
2063
+ trigger.focus();
2064
+ return;
2065
+ }
2066
+ if (isKey(event, Keys.Enter, Keys.Space)) {
2067
+ event.preventDefault();
2068
+ const active = document.activeElement;
2069
+ const activeItem = items.find((item) => item === active);
2070
+ if (activeItem) {
2071
+ const val = activeItem.getAttribute("data-value");
2072
+ if (val !== null) {
2073
+ onSelect?.(val);
2074
+ closeAll();
2075
+ trigger.focus();
2076
+ }
2077
+ }
2078
+ return;
2079
+ }
2080
+ if (isKey(event, Keys.ArrowLeft, Keys.ArrowRight)) {
2081
+ event.preventDefault();
2082
+ const triggerIdx = triggers.indexOf(trigger);
2083
+ let nextIdx;
2084
+ if (isKey(event, Keys.ArrowRight)) {
2085
+ nextIdx = (triggerIdx + 1) % triggers.length;
2086
+ } else {
2087
+ nextIdx = (triggerIdx - 1 + triggers.length) % triggers.length;
2088
+ }
2089
+ const nextTrigger = triggers[nextIdx];
2090
+ if (nextTrigger) {
2091
+ nextTrigger.focus();
2092
+ const nextValue = nextTrigger.getAttribute("data-value");
2093
+ if (nextValue)
2094
+ openMenu(nextValue);
2095
+ }
2096
+ return;
2097
+ }
2098
+ handleListNavigation(event, items, { orientation: "vertical" });
645
2099
  });
646
- items.push(item);
647
- content.appendChild(item);
648
- return item;
2100
+ function createItem(val, itemLabel, parent) {
2101
+ const item = document.createElement("div");
2102
+ item.setAttribute("role", "menuitem");
2103
+ item.setAttribute("data-value", val);
2104
+ item.setAttribute("tabindex", "-1");
2105
+ item.textContent = itemLabel ?? val;
2106
+ item.addEventListener("click", () => {
2107
+ onSelect?.(val);
2108
+ closeAll();
2109
+ trigger.focus();
2110
+ });
2111
+ items.push(item);
2112
+ (parent ?? content).appendChild(item);
2113
+ return item;
2114
+ }
2115
+ function Item(val, itemLabel) {
2116
+ return createItem(val, itemLabel);
2117
+ }
2118
+ function Group(groupLabel) {
2119
+ const el = document.createElement("div");
2120
+ el.setAttribute("role", "group");
2121
+ el.setAttribute("aria-label", groupLabel);
2122
+ content.appendChild(el);
2123
+ return {
2124
+ el,
2125
+ Item: (val, l) => createItem(val, l, el)
2126
+ };
2127
+ }
2128
+ function Separator() {
2129
+ const hr = document.createElement("hr");
2130
+ hr.setAttribute("role", "separator");
2131
+ content.appendChild(hr);
2132
+ return hr;
2133
+ }
2134
+ triggers.push(trigger);
2135
+ setRovingTabindex(triggers, 0);
2136
+ menus.set(value, { trigger, content, items });
2137
+ root.appendChild(trigger);
2138
+ return { trigger, content, Item, Group, Separator };
649
2139
  }
650
- return { trigger, content, state, Item };
2140
+ return { root, state, Menu: Menu2 };
651
2141
  }
652
2142
  };
653
- // src/popover/popover.ts
654
- import { signal as signal7 } from "@vertz/ui";
655
- var Popover = {
2143
+ // src/navigation-menu/navigation-menu.ts
2144
+ import { signal as signal16 } from "@vertz/ui";
2145
+ var NavigationMenu = {
656
2146
  Root(options = {}) {
657
- const { defaultOpen = false, onOpenChange } = options;
658
- const ids = linkedIds("popover");
659
- const state = { open: signal7(defaultOpen) };
660
- let restoreFocus = null;
661
- const trigger = document.createElement("button");
662
- trigger.setAttribute("type", "button");
663
- trigger.id = ids.triggerId;
664
- trigger.setAttribute("aria-controls", ids.contentId);
665
- trigger.setAttribute("aria-haspopup", "dialog");
666
- setExpanded(trigger, defaultOpen);
667
- setDataState(trigger, defaultOpen ? "open" : "closed");
668
- const content = document.createElement("div");
669
- content.setAttribute("role", "dialog");
670
- content.id = ids.contentId;
671
- setHidden(content, !defaultOpen);
672
- setDataState(content, defaultOpen ? "open" : "closed");
673
- function open() {
674
- state.open.value = true;
675
- setExpanded(trigger, true);
676
- setHidden(content, false);
677
- setDataState(trigger, "open");
678
- setDataState(content, "open");
679
- restoreFocus = saveFocus();
680
- queueMicrotask(() => focusFirst(content));
681
- onOpenChange?.(true);
2147
+ const { orientation = "horizontal", delayOpen = 200, delayClose = 300 } = options;
2148
+ const state = { activeItem: signal16(null) };
2149
+ const triggers = [];
2150
+ const items = new Map;
2151
+ let openTimeout = null;
2152
+ let closeTimeout = null;
2153
+ const root = document.createElement("nav");
2154
+ const list = document.createElement("div");
2155
+ const viewport = document.createElement("div");
2156
+ function cancelTimers() {
2157
+ if (openTimeout) {
2158
+ clearTimeout(openTimeout);
2159
+ openTimeout = null;
2160
+ }
2161
+ if (closeTimeout) {
2162
+ clearTimeout(closeTimeout);
2163
+ closeTimeout = null;
2164
+ }
682
2165
  }
683
- function close() {
684
- state.open.value = false;
2166
+ function openItem(value) {
2167
+ cancelTimers();
2168
+ const current = state.activeItem.peek();
2169
+ if (current && current !== value) {
2170
+ const prev = items.get(current);
2171
+ if (prev) {
2172
+ setExpanded(prev.trigger, false);
2173
+ setDataState(prev.trigger, "closed");
2174
+ setDataState(prev.content, "closed");
2175
+ setHiddenAnimated(prev.content, true);
2176
+ }
2177
+ }
2178
+ const item = items.get(value);
2179
+ if (!item)
2180
+ return;
2181
+ state.activeItem.value = value;
2182
+ setExpanded(item.trigger, true);
2183
+ setHidden(item.content, false);
2184
+ setDataState(item.trigger, "open");
2185
+ setDataState(item.content, "open");
2186
+ }
2187
+ function closeAll() {
2188
+ cancelTimers();
2189
+ const current = state.activeItem.peek();
2190
+ if (current) {
2191
+ const item = items.get(current);
2192
+ if (item) {
2193
+ setExpanded(item.trigger, false);
2194
+ setDataState(item.trigger, "closed");
2195
+ setDataState(item.content, "closed");
2196
+ setHiddenAnimated(item.content, true);
2197
+ }
2198
+ }
2199
+ state.activeItem.value = null;
2200
+ }
2201
+ function Item(value, label) {
2202
+ const ids = linkedIds("nav-menu");
2203
+ const trigger = document.createElement("button");
2204
+ trigger.setAttribute("type", "button");
2205
+ trigger.id = ids.triggerId;
2206
+ trigger.setAttribute("aria-controls", ids.contentId);
2207
+ trigger.setAttribute("data-value", value);
2208
+ trigger.textContent = label ?? value;
685
2209
  setExpanded(trigger, false);
686
- setHidden(content, true);
687
2210
  setDataState(trigger, "closed");
2211
+ const content = document.createElement("div");
2212
+ content.id = ids.contentId;
2213
+ setHidden(content, true);
688
2214
  setDataState(content, "closed");
689
- restoreFocus?.();
690
- restoreFocus = null;
691
- onOpenChange?.(false);
2215
+ trigger.addEventListener("click", () => {
2216
+ if (state.activeItem.peek() === value) {
2217
+ closeAll();
2218
+ } else {
2219
+ openItem(value);
2220
+ }
2221
+ });
2222
+ trigger.addEventListener("mouseenter", () => {
2223
+ cancelTimers();
2224
+ openTimeout = setTimeout(() => {
2225
+ openItem(value);
2226
+ openTimeout = null;
2227
+ }, delayOpen);
2228
+ });
2229
+ trigger.addEventListener("mouseleave", () => {
2230
+ cancelTimers();
2231
+ closeTimeout = setTimeout(() => {
2232
+ closeAll();
2233
+ closeTimeout = null;
2234
+ }, delayClose);
2235
+ });
2236
+ content.addEventListener("mouseenter", () => {
2237
+ cancelTimers();
2238
+ });
2239
+ content.addEventListener("mouseleave", () => {
2240
+ cancelTimers();
2241
+ closeTimeout = setTimeout(() => {
2242
+ closeAll();
2243
+ closeTimeout = null;
2244
+ }, delayClose);
2245
+ });
2246
+ trigger.addEventListener("keydown", (event) => {
2247
+ if (isKey(event, Keys.Enter, Keys.Space)) {
2248
+ event.preventDefault();
2249
+ openItem(value);
2250
+ queueMicrotask(() => focusFirst(content));
2251
+ }
2252
+ if (isKey(event, Keys.Escape)) {
2253
+ event.preventDefault();
2254
+ closeAll();
2255
+ }
2256
+ });
2257
+ content.addEventListener("keydown", (event) => {
2258
+ if (isKey(event, Keys.Escape)) {
2259
+ event.preventDefault();
2260
+ event.stopPropagation();
2261
+ closeAll();
2262
+ trigger.focus();
2263
+ }
2264
+ });
2265
+ triggers.push(trigger);
2266
+ setRovingTabindex(triggers, 0);
2267
+ items.set(value, { trigger, content });
2268
+ list.appendChild(trigger);
2269
+ viewport.appendChild(content);
2270
+ return { trigger, content };
692
2271
  }
693
- trigger.addEventListener("click", () => {
694
- if (state.open.peek()) {
695
- close();
696
- } else {
697
- open();
698
- }
699
- });
700
- content.addEventListener("keydown", (event) => {
701
- if (isKey(event, Keys.Escape)) {
702
- event.preventDefault();
703
- close();
2272
+ function Link(href, label) {
2273
+ const a = document.createElement("a");
2274
+ a.href = href;
2275
+ a.textContent = label;
2276
+ list.appendChild(a);
2277
+ return a;
2278
+ }
2279
+ list.addEventListener("keydown", (event) => {
2280
+ if (isKey(event, Keys.ArrowLeft, Keys.ArrowRight, Keys.Home, Keys.End)) {
2281
+ handleListNavigation(event, triggers, {
2282
+ orientation: orientation === "horizontal" ? "horizontal" : "vertical"
2283
+ });
704
2284
  }
705
2285
  });
706
- return { trigger, content, state };
2286
+ root.appendChild(list);
2287
+ root.appendChild(viewport);
2288
+ return { root, list, viewport, state, Item, Link };
707
2289
  }
708
2290
  };
709
2291
  // src/progress/progress.ts
710
- import { signal as signal8 } from "@vertz/ui";
2292
+ import { signal as signal17 } from "@vertz/ui";
711
2293
  var Progress = {
712
2294
  Root(options = {}) {
713
2295
  const { defaultValue = 0, min = 0, max = 100 } = options;
714
- const state = { value: signal8(defaultValue) };
2296
+ const state = { value: signal17(defaultValue) };
715
2297
  const root = document.createElement("div");
716
2298
  root.setAttribute("role", "progressbar");
717
2299
  root.id = uniqueId("progress");
@@ -746,11 +2328,11 @@ var Progress = {
746
2328
  }
747
2329
  };
748
2330
  // src/radio/radio.ts
749
- import { signal as signal9 } from "@vertz/ui";
2331
+ import { signal as signal18 } from "@vertz/ui";
750
2332
  var Radio = {
751
2333
  Root(options = {}) {
752
2334
  const { defaultValue = "", onValueChange } = options;
753
- const state = { value: signal9(defaultValue) };
2335
+ const state = { value: signal18(defaultValue) };
754
2336
  const items = [];
755
2337
  const itemValues = [];
756
2338
  const root = document.createElement("div");
@@ -802,18 +2384,275 @@ var Radio = {
802
2384
  return { root, state, Item };
803
2385
  }
804
2386
  };
2387
+ // src/resizable-panel/resizable-panel.ts
2388
+ import { signal as signal19 } from "@vertz/ui";
2389
+ var ResizablePanel = {
2390
+ Root(options = {}) {
2391
+ const { orientation = "horizontal", onResize } = options;
2392
+ const state = { sizes: signal19([]) };
2393
+ const panels = [];
2394
+ const handles = [];
2395
+ const root = document.createElement("div");
2396
+ root.style.display = "flex";
2397
+ root.style.flexDirection = orientation === "horizontal" ? "row" : "column";
2398
+ root.setAttribute("data-orientation", orientation);
2399
+ function updateSizes(newSizes) {
2400
+ state.sizes.value = [...newSizes];
2401
+ for (let i = 0;i < panels.length; i++) {
2402
+ const panel = panels[i];
2403
+ if (!panel)
2404
+ continue;
2405
+ const size = newSizes[i] ?? 0;
2406
+ panel.el.style.flex = `0 0 ${size}%`;
2407
+ }
2408
+ for (let i = 0;i < handles.length; i++) {
2409
+ const handle = handles[i];
2410
+ const leftPanel = panels[i];
2411
+ if (handle && leftPanel) {
2412
+ const size = newSizes[i] ?? 0;
2413
+ setValueRange(handle, Math.round(size), Math.round(leftPanel.minSize), Math.round(leftPanel.maxSize));
2414
+ }
2415
+ }
2416
+ onResize?.(newSizes);
2417
+ }
2418
+ function Panel(panelOptions = {}) {
2419
+ const { defaultSize, minSize = 0, maxSize = 100 } = panelOptions;
2420
+ const el = document.createElement("div");
2421
+ el.setAttribute("data-panel", "");
2422
+ panels.push({ el, minSize, maxSize });
2423
+ const sizes = state.sizes.peek();
2424
+ if (defaultSize != null) {
2425
+ sizes.push(defaultSize);
2426
+ } else {
2427
+ const equalSize = 100 / panels.length;
2428
+ sizes.length = 0;
2429
+ for (let i = 0;i < panels.length; i++) {
2430
+ sizes.push(equalSize);
2431
+ }
2432
+ }
2433
+ updateSizes(sizes);
2434
+ root.appendChild(el);
2435
+ return el;
2436
+ }
2437
+ function Handle() {
2438
+ const handleIndex = handles.length;
2439
+ const handle = document.createElement("div");
2440
+ handle.setAttribute("role", "separator");
2441
+ handle.setAttribute("tabindex", "0");
2442
+ handle.setAttribute("data-orientation", orientation);
2443
+ setDataState(handle, "idle");
2444
+ handles.push(handle);
2445
+ const STEP = 5;
2446
+ handle.addEventListener("keydown", (event) => {
2447
+ const sizes = [...state.sizes.peek()];
2448
+ const leftIdx = handleIndex;
2449
+ const rightIdx = handleIndex + 1;
2450
+ const leftPanel = panels[leftIdx];
2451
+ const rightPanel = panels[rightIdx];
2452
+ if (!leftPanel || !rightPanel)
2453
+ return;
2454
+ let leftSize = sizes[leftIdx] ?? 0;
2455
+ let rightSize = sizes[rightIdx] ?? 0;
2456
+ const growKey = orientation === "horizontal" ? Keys.ArrowRight : Keys.ArrowDown;
2457
+ const shrinkKey = orientation === "horizontal" ? Keys.ArrowLeft : Keys.ArrowUp;
2458
+ if (isKey(event, growKey)) {
2459
+ event.preventDefault();
2460
+ const delta = Math.min(STEP, rightSize - rightPanel.minSize, leftPanel.maxSize - leftSize);
2461
+ leftSize += delta;
2462
+ rightSize -= delta;
2463
+ } else if (isKey(event, shrinkKey)) {
2464
+ event.preventDefault();
2465
+ const delta = Math.min(STEP, leftSize - leftPanel.minSize, rightPanel.maxSize - rightSize);
2466
+ leftSize -= delta;
2467
+ rightSize += delta;
2468
+ } else if (isKey(event, Keys.Home)) {
2469
+ event.preventDefault();
2470
+ const delta = leftSize - leftPanel.minSize;
2471
+ leftSize -= delta;
2472
+ rightSize += delta;
2473
+ } else if (isKey(event, Keys.End)) {
2474
+ event.preventDefault();
2475
+ const delta = rightSize - rightPanel.minSize;
2476
+ leftSize += delta;
2477
+ rightSize -= delta;
2478
+ } else {
2479
+ return;
2480
+ }
2481
+ sizes[leftIdx] = leftSize;
2482
+ sizes[rightIdx] = rightSize;
2483
+ updateSizes(sizes);
2484
+ });
2485
+ handle.addEventListener("pointerdown", (event) => {
2486
+ event.preventDefault();
2487
+ handle.setPointerCapture(event.pointerId);
2488
+ setDataState(handle, "dragging");
2489
+ const startPos = orientation === "horizontal" ? event.clientX : event.clientY;
2490
+ const rootSize = orientation === "horizontal" ? root.offsetWidth : root.offsetHeight;
2491
+ const startSizes = [...state.sizes.peek()];
2492
+ function onMove(e) {
2493
+ const currentPos = orientation === "horizontal" ? e.clientX : e.clientY;
2494
+ const delta = (currentPos - startPos) / rootSize * 100;
2495
+ const sizes = [...startSizes];
2496
+ const leftIdx = handleIndex;
2497
+ const rightIdx = handleIndex + 1;
2498
+ const leftPanel = panels[leftIdx];
2499
+ const rightPanel = panels[rightIdx];
2500
+ if (!leftPanel || !rightPanel)
2501
+ return;
2502
+ let newLeft = (startSizes[leftIdx] ?? 0) + delta;
2503
+ let newRight = (startSizes[rightIdx] ?? 0) - delta;
2504
+ newLeft = Math.max(leftPanel.minSize, Math.min(leftPanel.maxSize, newLeft));
2505
+ newRight = Math.max(rightPanel.minSize, Math.min(rightPanel.maxSize, newRight));
2506
+ sizes[leftIdx] = newLeft;
2507
+ sizes[rightIdx] = newRight;
2508
+ updateSizes(sizes);
2509
+ }
2510
+ function onUp(e) {
2511
+ handle.releasePointerCapture(e.pointerId);
2512
+ setDataState(handle, "idle");
2513
+ handle.removeEventListener("pointermove", onMove);
2514
+ handle.removeEventListener("pointerup", onUp);
2515
+ }
2516
+ handle.addEventListener("pointermove", onMove);
2517
+ handle.addEventListener("pointerup", onUp);
2518
+ });
2519
+ root.appendChild(handle);
2520
+ return handle;
2521
+ }
2522
+ return { root, state, Panel, Handle };
2523
+ }
2524
+ };
2525
+ // src/scroll-area/scroll-area.ts
2526
+ import { signal as signal20 } from "@vertz/ui";
2527
+ var ScrollArea = {
2528
+ Root(options = {}) {
2529
+ const { orientation = "vertical" } = options;
2530
+ const state = {
2531
+ scrollTop: signal20(0),
2532
+ scrollLeft: signal20(0)
2533
+ };
2534
+ const root = document.createElement("div");
2535
+ root.style.position = "relative";
2536
+ root.style.overflow = "hidden";
2537
+ const viewport = document.createElement("div");
2538
+ viewport.style.overflow = "scroll";
2539
+ viewport.style.scrollbarWidth = "none";
2540
+ const content = document.createElement("div");
2541
+ const scrollbarY = document.createElement("div");
2542
+ scrollbarY.setAttribute("aria-hidden", "true");
2543
+ scrollbarY.setAttribute("data-orientation", "vertical");
2544
+ const thumbY = document.createElement("div");
2545
+ scrollbarY.appendChild(thumbY);
2546
+ const scrollbarX = document.createElement("div");
2547
+ scrollbarX.setAttribute("aria-hidden", "true");
2548
+ scrollbarX.setAttribute("data-orientation", "horizontal");
2549
+ const thumbX = document.createElement("div");
2550
+ scrollbarX.appendChild(thumbX);
2551
+ function syncThumbY() {
2552
+ const { scrollTop, scrollHeight, clientHeight } = viewport;
2553
+ if (scrollHeight <= clientHeight) {
2554
+ thumbY.style.height = "0";
2555
+ return;
2556
+ }
2557
+ const ratio = clientHeight / scrollHeight;
2558
+ thumbY.style.height = `${ratio * 100}%`;
2559
+ const scrollRatio = scrollTop / (scrollHeight - clientHeight);
2560
+ thumbY.style.transform = `translateY(${scrollRatio * (1 / ratio - 1) * 100}%)`;
2561
+ state.scrollTop.value = scrollTop;
2562
+ }
2563
+ function syncThumbX() {
2564
+ const { scrollLeft, scrollWidth, clientWidth } = viewport;
2565
+ if (scrollWidth <= clientWidth) {
2566
+ thumbX.style.width = "0";
2567
+ return;
2568
+ }
2569
+ const ratio = clientWidth / scrollWidth;
2570
+ thumbX.style.width = `${ratio * 100}%`;
2571
+ const scrollRatio = scrollLeft / (scrollWidth - clientWidth);
2572
+ thumbX.style.transform = `translateX(${scrollRatio * (1 / ratio - 1) * 100}%)`;
2573
+ state.scrollLeft.value = scrollLeft;
2574
+ }
2575
+ viewport.addEventListener("scroll", () => {
2576
+ if (orientation === "vertical" || orientation === "both")
2577
+ syncThumbY();
2578
+ if (orientation === "horizontal" || orientation === "both")
2579
+ syncThumbX();
2580
+ });
2581
+ let isDraggingY = false;
2582
+ let startY = 0;
2583
+ let startScrollTop = 0;
2584
+ thumbY.addEventListener("pointerdown", (e) => {
2585
+ isDraggingY = true;
2586
+ startY = e.clientY;
2587
+ startScrollTop = viewport.scrollTop;
2588
+ thumbY.setPointerCapture(e.pointerId);
2589
+ e.preventDefault();
2590
+ });
2591
+ thumbY.addEventListener("pointermove", (e) => {
2592
+ if (!isDraggingY)
2593
+ return;
2594
+ const delta = e.clientY - startY;
2595
+ const scrollbarHeight = scrollbarY.clientHeight;
2596
+ const scrollRange = viewport.scrollHeight - viewport.clientHeight;
2597
+ if (scrollbarHeight > 0) {
2598
+ viewport.scrollTop = startScrollTop + delta / scrollbarHeight * scrollRange;
2599
+ }
2600
+ });
2601
+ thumbY.addEventListener("pointerup", (e) => {
2602
+ isDraggingY = false;
2603
+ thumbY.releasePointerCapture(e.pointerId);
2604
+ });
2605
+ let isDraggingX = false;
2606
+ let startX = 0;
2607
+ let startScrollLeft = 0;
2608
+ thumbX.addEventListener("pointerdown", (e) => {
2609
+ isDraggingX = true;
2610
+ startX = e.clientX;
2611
+ startScrollLeft = viewport.scrollLeft;
2612
+ thumbX.setPointerCapture(e.pointerId);
2613
+ e.preventDefault();
2614
+ });
2615
+ thumbX.addEventListener("pointermove", (e) => {
2616
+ if (!isDraggingX)
2617
+ return;
2618
+ const delta = e.clientX - startX;
2619
+ const scrollbarWidth = scrollbarX.clientWidth;
2620
+ const scrollRange = viewport.scrollWidth - viewport.clientWidth;
2621
+ if (scrollbarWidth > 0) {
2622
+ viewport.scrollLeft = startScrollLeft + delta / scrollbarWidth * scrollRange;
2623
+ }
2624
+ });
2625
+ thumbX.addEventListener("pointerup", (e) => {
2626
+ isDraggingX = false;
2627
+ thumbX.releasePointerCapture(e.pointerId);
2628
+ });
2629
+ function update() {
2630
+ syncThumbY();
2631
+ syncThumbX();
2632
+ }
2633
+ viewport.appendChild(content);
2634
+ root.appendChild(viewport);
2635
+ if (orientation === "vertical" || orientation === "both")
2636
+ root.appendChild(scrollbarY);
2637
+ if (orientation === "horizontal" || orientation === "both")
2638
+ root.appendChild(scrollbarX);
2639
+ return { root, viewport, content, scrollbarY, thumbY, scrollbarX, thumbX, state, update };
2640
+ }
2641
+ };
805
2642
  // src/select/select.ts
806
- import { signal as signal10 } from "@vertz/ui";
2643
+ import { signal as signal21 } from "@vertz/ui";
807
2644
  var Select = {
808
2645
  Root(options = {}) {
809
- const { defaultValue = "", onValueChange } = options;
2646
+ const { defaultValue = "", placeholder = "", onValueChange, positioning } = options;
810
2647
  const ids = linkedIds("select");
811
2648
  const state = {
812
- open: signal10(false),
813
- value: signal10(defaultValue),
814
- activeIndex: signal10(-1)
2649
+ open: signal21(false),
2650
+ value: signal21(defaultValue),
2651
+ activeIndex: signal21(-1)
815
2652
  };
816
2653
  const items = [];
2654
+ let floatingCleanup = null;
2655
+ let dismissCleanup = null;
817
2656
  const trigger = document.createElement("button");
818
2657
  trigger.setAttribute("type", "button");
819
2658
  trigger.setAttribute("role", "combobox");
@@ -822,8 +2661,13 @@ var Select = {
822
2661
  trigger.setAttribute("aria-haspopup", "listbox");
823
2662
  setExpanded(trigger, false);
824
2663
  setDataState(trigger, "closed");
2664
+ const triggerText = document.createElement("span");
2665
+ triggerText.setAttribute("data-part", "value");
2666
+ triggerText.textContent = defaultValue || placeholder;
2667
+ trigger.appendChild(triggerText);
825
2668
  const content = document.createElement("div");
826
2669
  content.setAttribute("role", "listbox");
2670
+ content.setAttribute("tabindex", "-1");
827
2671
  content.id = ids.contentId;
828
2672
  setHidden(content, true);
829
2673
  setDataState(content, "closed");
@@ -833,18 +2677,40 @@ var Select = {
833
2677
  setHidden(content, false);
834
2678
  setDataState(trigger, "open");
835
2679
  setDataState(content, "open");
2680
+ if (positioning) {
2681
+ const result = createFloatingPosition(trigger, content, positioning);
2682
+ floatingCleanup = result.cleanup;
2683
+ dismissCleanup = createDismiss({
2684
+ onDismiss: close,
2685
+ insideElements: [trigger, content],
2686
+ escapeKey: false
2687
+ });
2688
+ } else {
2689
+ const rect = trigger.getBoundingClientRect();
2690
+ const side = window.innerHeight - rect.bottom >= rect.top ? "bottom" : "top";
2691
+ content.setAttribute("data-side", side);
2692
+ }
836
2693
  const selectedIdx = items.findIndex((item) => item.getAttribute("data-value") === state.value.peek());
837
- const focusIdx = selectedIdx >= 0 ? selectedIdx : 0;
838
- state.activeIndex.value = focusIdx;
839
- updateActiveItem(focusIdx);
840
- items[focusIdx]?.focus();
2694
+ if (selectedIdx >= 0) {
2695
+ state.activeIndex.value = selectedIdx;
2696
+ updateActiveItem(selectedIdx);
2697
+ items[selectedIdx]?.focus();
2698
+ } else {
2699
+ state.activeIndex.value = -1;
2700
+ updateActiveItem(-1);
2701
+ content.focus();
2702
+ }
841
2703
  }
842
2704
  function close() {
843
2705
  state.open.value = false;
844
2706
  setExpanded(trigger, false);
845
- setHidden(content, true);
846
2707
  setDataState(trigger, "closed");
847
2708
  setDataState(content, "closed");
2709
+ setHiddenAnimated(content, true);
2710
+ floatingCleanup?.();
2711
+ floatingCleanup = null;
2712
+ dismissCleanup?.();
2713
+ dismissCleanup = null;
848
2714
  trigger.focus();
849
2715
  }
850
2716
  function selectItem(value) {
@@ -853,6 +2719,9 @@ var Select = {
853
2719
  const isActive = item.getAttribute("data-value") === value;
854
2720
  setSelected(item, isActive);
855
2721
  setDataState(item, isActive ? "active" : "inactive");
2722
+ if (isActive) {
2723
+ triggerText.textContent = item.textContent ?? value;
2724
+ }
856
2725
  }
857
2726
  onValueChange?.(value);
858
2727
  close();
@@ -893,6 +2762,23 @@ var Select = {
893
2762
  }
894
2763
  return;
895
2764
  }
2765
+ if (state.activeIndex.peek() === -1) {
2766
+ if (isKey(event, Keys.ArrowDown)) {
2767
+ event.preventDefault();
2768
+ state.activeIndex.value = 0;
2769
+ updateActiveItem(0);
2770
+ items[0]?.focus();
2771
+ return;
2772
+ }
2773
+ if (isKey(event, Keys.ArrowUp)) {
2774
+ event.preventDefault();
2775
+ const last = items.length - 1;
2776
+ state.activeIndex.value = last;
2777
+ updateActiveItem(last);
2778
+ items[last]?.focus();
2779
+ return;
2780
+ }
2781
+ }
896
2782
  const result = handleListNavigation(event, items, { orientation: "vertical" });
897
2783
  if (result) {
898
2784
  const idx = items.indexOf(result);
@@ -900,9 +2786,20 @@ var Select = {
900
2786
  state.activeIndex.value = idx;
901
2787
  updateActiveItem(idx);
902
2788
  }
2789
+ return;
2790
+ }
2791
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
2792
+ const char = event.key.toLowerCase();
2793
+ const match = items.find((item) => item.textContent?.toLowerCase().startsWith(char));
2794
+ if (match) {
2795
+ const idx = items.indexOf(match);
2796
+ state.activeIndex.value = idx;
2797
+ updateActiveItem(idx);
2798
+ match.focus();
2799
+ }
903
2800
  }
904
2801
  });
905
- function Item(value, label) {
2802
+ function createItem(value, label, parent) {
906
2803
  const item = document.createElement("div");
907
2804
  item.setAttribute("role", "option");
908
2805
  item.setAttribute("data-value", value);
@@ -911,18 +2808,40 @@ var Select = {
911
2808
  const isSelected = value === defaultValue;
912
2809
  setSelected(item, isSelected);
913
2810
  setDataState(item, isSelected ? "active" : "inactive");
2811
+ if (isSelected) {
2812
+ triggerText.textContent = item.textContent ?? value;
2813
+ }
914
2814
  item.addEventListener("click", () => {
915
2815
  selectItem(value);
916
2816
  });
917
2817
  items.push(item);
918
- content.appendChild(item);
2818
+ (parent ?? content).appendChild(item);
919
2819
  return item;
920
2820
  }
921
- return { trigger, content, state, Item };
2821
+ function Item(value, label) {
2822
+ return createItem(value, label);
2823
+ }
2824
+ function Group(label) {
2825
+ const el = document.createElement("div");
2826
+ el.setAttribute("role", "group");
2827
+ el.setAttribute("aria-label", label);
2828
+ content.appendChild(el);
2829
+ return {
2830
+ el,
2831
+ Item: (value, itemLabel) => createItem(value, itemLabel, el)
2832
+ };
2833
+ }
2834
+ function Separator() {
2835
+ const hr = document.createElement("hr");
2836
+ hr.setAttribute("role", "separator");
2837
+ content.appendChild(hr);
2838
+ return hr;
2839
+ }
2840
+ return { trigger, content, state, Item, Group, Separator };
922
2841
  }
923
2842
  };
924
2843
  // src/slider/slider.ts
925
- import { signal as signal11 } from "@vertz/ui";
2844
+ import { signal as signal22 } from "@vertz/ui";
926
2845
  var Slider = {
927
2846
  Root(options = {}) {
928
2847
  const {
@@ -934,8 +2853,8 @@ var Slider = {
934
2853
  onValueChange
935
2854
  } = options;
936
2855
  const state = {
937
- value: signal11(defaultValue),
938
- disabled: signal11(disabled)
2856
+ value: signal22(defaultValue),
2857
+ disabled: signal22(disabled)
939
2858
  };
940
2859
  const root = document.createElement("div");
941
2860
  root.id = uniqueId("slider");
@@ -953,6 +2872,13 @@ var Slider = {
953
2872
  function clamp(val) {
954
2873
  return Math.min(max, Math.max(min, val));
955
2874
  }
2875
+ const fill = document.createElement("div");
2876
+ fill.setAttribute("data-part", "fill");
2877
+ fill.style.cssText = "position: absolute; height: 100%; border-radius: inherit;";
2878
+ function updatePosition(pct2) {
2879
+ thumb.style.left = `${pct2}%`;
2880
+ fill.style.width = `${pct2}%`;
2881
+ }
956
2882
  function setValue(val) {
957
2883
  if (state.disabled.peek())
958
2884
  return;
@@ -960,10 +2886,16 @@ var Slider = {
960
2886
  state.value.value = clamped;
961
2887
  setValueRange(thumb, clamped, min, max);
962
2888
  const pct2 = (clamped - min) / (max - min) * 100;
963
- thumb.style.left = `${pct2}%`;
2889
+ updatePosition(pct2);
964
2890
  setDataState(root, "active");
965
2891
  onValueChange?.(clamped);
966
2892
  }
2893
+ function valueFromPointer(event) {
2894
+ const rect = track.getBoundingClientRect();
2895
+ const pct2 = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
2896
+ const raw = min + pct2 * (max - min);
2897
+ return Math.round(raw / step) * step;
2898
+ }
967
2899
  thumb.addEventListener("keydown", (event) => {
968
2900
  if (state.disabled.peek())
969
2901
  return;
@@ -982,21 +2914,41 @@ var Slider = {
982
2914
  setValue(max);
983
2915
  }
984
2916
  });
2917
+ root.addEventListener("pointerdown", (event) => {
2918
+ if (state.disabled.peek())
2919
+ return;
2920
+ event.preventDefault();
2921
+ setValue(valueFromPointer(event));
2922
+ thumb.focus();
2923
+ function onMove(e) {
2924
+ setValue(valueFromPointer(e));
2925
+ }
2926
+ function onUp() {
2927
+ document.removeEventListener("pointermove", onMove);
2928
+ document.removeEventListener("pointerup", onUp);
2929
+ }
2930
+ document.addEventListener("pointermove", onMove);
2931
+ document.addEventListener("pointerup", onUp);
2932
+ });
2933
+ thumb.style.position = "absolute";
2934
+ thumb.style.transform = "translate(-50%, -50%)";
2935
+ track.style.position = "relative";
2936
+ track.appendChild(fill);
985
2937
  track.appendChild(thumb);
986
2938
  root.appendChild(track);
987
2939
  const pct = (defaultValue - min) / (max - min) * 100;
988
- thumb.style.left = `${pct}%`;
2940
+ updatePosition(pct);
989
2941
  return { root, thumb, track, state };
990
2942
  }
991
2943
  };
992
2944
  // src/switch/switch.ts
993
- import { signal as signal12 } from "@vertz/ui";
2945
+ import { signal as signal23 } from "@vertz/ui";
994
2946
  var Switch = {
995
2947
  Root(options = {}) {
996
2948
  const { defaultChecked = false, disabled = false, onCheckedChange } = options;
997
2949
  const state = {
998
- checked: signal12(defaultChecked),
999
- disabled: signal12(disabled)
2950
+ checked: signal23(defaultChecked),
2951
+ disabled: signal23(disabled)
1000
2952
  };
1001
2953
  const root = document.createElement("button");
1002
2954
  root.setAttribute("type", "button");
@@ -1028,17 +2980,20 @@ var Switch = {
1028
2980
  }
1029
2981
  };
1030
2982
  // src/tabs/tabs.ts
1031
- import { signal as signal13 } from "@vertz/ui";
2983
+ import { signal as signal24 } from "@vertz/ui";
1032
2984
  var Tabs = {
1033
2985
  Root(options = {}) {
1034
- const { defaultValue = "", onValueChange } = options;
1035
- const state = { value: signal13(defaultValue) };
2986
+ const { defaultValue = "", orientation = "horizontal", onValueChange } = options;
2987
+ const state = { value: signal24(defaultValue) };
1036
2988
  const triggers = [];
1037
2989
  const panels = [];
1038
2990
  const tabValues = [];
1039
2991
  const root = document.createElement("div");
1040
2992
  const list = document.createElement("div");
1041
2993
  list.setAttribute("role", "tablist");
2994
+ if (orientation === "vertical") {
2995
+ list.setAttribute("aria-orientation", "vertical");
2996
+ }
1042
2997
  root.appendChild(list);
1043
2998
  function selectTab(value) {
1044
2999
  state.value.value = value;
@@ -1058,7 +3013,7 @@ var Tabs = {
1058
3013
  }
1059
3014
  list.addEventListener("keydown", (event) => {
1060
3015
  const result = handleListNavigation(event, triggers, {
1061
- orientation: "horizontal"
3016
+ orientation
1062
3017
  });
1063
3018
  if (result) {
1064
3019
  const idx = triggers.indexOf(result);
@@ -1106,11 +3061,11 @@ var Tabs = {
1106
3061
  }
1107
3062
  };
1108
3063
  // src/toast/toast.ts
1109
- import { signal as signal14 } from "@vertz/ui";
3064
+ import { signal as signal25 } from "@vertz/ui";
1110
3065
  var Toast = {
1111
3066
  Root(options = {}) {
1112
3067
  const { duration = 5000, politeness = "polite" } = options;
1113
- const state = { messages: signal14([]) };
3068
+ const state = { messages: signal25([]) };
1114
3069
  const region = document.createElement("div");
1115
3070
  region.setAttribute("role", "status");
1116
3071
  region.setAttribute("aria-live", politeness);
@@ -1148,14 +3103,130 @@ var Toast = {
1148
3103
  return { region, state, announce, dismiss };
1149
3104
  }
1150
3105
  };
3106
+ // src/toggle/toggle.ts
3107
+ import { signal as signal26 } from "@vertz/ui";
3108
+ var Toggle = {
3109
+ Root(options = {}) {
3110
+ const { defaultPressed = false, disabled = false, onPressedChange } = options;
3111
+ const state = {
3112
+ pressed: signal26(defaultPressed),
3113
+ disabled: signal26(disabled)
3114
+ };
3115
+ const root = document.createElement("button");
3116
+ root.setAttribute("type", "button");
3117
+ root.id = uniqueId("toggle");
3118
+ setPressed(root, defaultPressed);
3119
+ setDataState(root, defaultPressed ? "on" : "off");
3120
+ if (disabled) {
3121
+ root.disabled = true;
3122
+ root.setAttribute("aria-disabled", "true");
3123
+ }
3124
+ function toggle() {
3125
+ if (state.disabled.peek())
3126
+ return;
3127
+ const next = !state.pressed.peek();
3128
+ state.pressed.value = next;
3129
+ setPressed(root, next);
3130
+ setDataState(root, next ? "on" : "off");
3131
+ onPressedChange?.(next);
3132
+ }
3133
+ root.addEventListener("click", toggle);
3134
+ root.addEventListener("keydown", (event) => {
3135
+ if (isKey(event, Keys.Space)) {
3136
+ event.preventDefault();
3137
+ toggle();
3138
+ }
3139
+ });
3140
+ return { root, state };
3141
+ }
3142
+ };
3143
+ // src/toggle-group/toggle-group.ts
3144
+ import { signal as signal27 } from "@vertz/ui";
3145
+ var ToggleGroup = {
3146
+ Root(options = {}) {
3147
+ const {
3148
+ type = "single",
3149
+ defaultValue = [],
3150
+ orientation = "horizontal",
3151
+ disabled = false,
3152
+ onValueChange
3153
+ } = options;
3154
+ const state = {
3155
+ value: signal27([...defaultValue]),
3156
+ disabled: signal27(disabled)
3157
+ };
3158
+ const items = [];
3159
+ const root = document.createElement("div");
3160
+ root.setAttribute("role", "group");
3161
+ root.setAttribute("data-orientation", orientation);
3162
+ function toggleValue(itemValue) {
3163
+ if (state.disabled.peek())
3164
+ return;
3165
+ const current = [...state.value.peek()];
3166
+ const idx = current.indexOf(itemValue);
3167
+ if (type === "single") {
3168
+ if (idx >= 0) {
3169
+ current.length = 0;
3170
+ } else {
3171
+ current.length = 0;
3172
+ current.push(itemValue);
3173
+ }
3174
+ } else {
3175
+ if (idx >= 0) {
3176
+ current.splice(idx, 1);
3177
+ } else {
3178
+ current.push(itemValue);
3179
+ }
3180
+ }
3181
+ state.value.value = current;
3182
+ onValueChange?.(current);
3183
+ for (const item of items) {
3184
+ const val = item.getAttribute("data-value") ?? "";
3185
+ const isOn = current.includes(val);
3186
+ setPressed(item, isOn);
3187
+ setDataState(item, isOn ? "on" : "off");
3188
+ }
3189
+ }
3190
+ root.addEventListener("keydown", (event) => {
3191
+ if (isKey(event, Keys.ArrowLeft, Keys.ArrowRight, Keys.ArrowUp, Keys.ArrowDown, Keys.Home, Keys.End)) {
3192
+ const result = handleListNavigation(event, items, { orientation });
3193
+ if (result) {
3194
+ const idx = items.indexOf(result);
3195
+ if (idx >= 0) {
3196
+ setRovingTabindex(items, idx);
3197
+ }
3198
+ }
3199
+ }
3200
+ });
3201
+ function Item(value) {
3202
+ const item = document.createElement("button");
3203
+ item.setAttribute("type", "button");
3204
+ item.setAttribute("data-value", value);
3205
+ const isOn = state.value.peek().includes(value);
3206
+ setPressed(item, isOn);
3207
+ setDataState(item, isOn ? "on" : "off");
3208
+ if (state.disabled.peek()) {
3209
+ item.disabled = true;
3210
+ item.setAttribute("aria-disabled", "true");
3211
+ }
3212
+ item.addEventListener("click", () => toggleValue(value));
3213
+ items.push(item);
3214
+ setRovingTabindex(items, 0);
3215
+ root.appendChild(item);
3216
+ return item;
3217
+ }
3218
+ return { root, state, Item };
3219
+ }
3220
+ };
1151
3221
  // src/tooltip/tooltip.ts
1152
- import { signal as signal15 } from "@vertz/ui";
3222
+ import { signal as signal28 } from "@vertz/ui";
1153
3223
  var Tooltip = {
1154
3224
  Root(options = {}) {
1155
- const { delay = 300, onOpenChange } = options;
3225
+ const { delay = 300, onOpenChange, positioning } = options;
1156
3226
  const contentId = uniqueId("tooltip");
1157
- const state = { open: signal15(false) };
3227
+ const state = { open: signal28(false) };
1158
3228
  let showTimeout = null;
3229
+ let floatingCleanup = null;
1159
3230
  const trigger = document.createElement("span");
1160
3231
  setDescribedBy(trigger, contentId);
1161
3232
  const content = document.createElement("div");
@@ -1170,6 +3241,14 @@ var Tooltip = {
1170
3241
  state.open.value = true;
1171
3242
  setHidden(content, false);
1172
3243
  setDataState(content, "open");
3244
+ if (positioning) {
3245
+ const effectivePlacement = positioning.placement ?? "top";
3246
+ const result = createFloatingPosition(trigger, content, {
3247
+ ...positioning,
3248
+ placement: effectivePlacement
3249
+ });
3250
+ floatingCleanup = result.cleanup;
3251
+ }
1173
3252
  onOpenChange?.(true);
1174
3253
  showTimeout = null;
1175
3254
  }, delay);
@@ -1180,8 +3259,10 @@ var Tooltip = {
1180
3259
  showTimeout = null;
1181
3260
  }
1182
3261
  state.open.value = false;
1183
- setHidden(content, true);
1184
3262
  setDataState(content, "closed");
3263
+ setHiddenAnimated(content, true);
3264
+ floatingCleanup?.();
3265
+ floatingCleanup = null;
1185
3266
  onOpenChange?.(false);
1186
3267
  }
1187
3268
  trigger.addEventListener("mouseenter", show);
@@ -1198,18 +3279,32 @@ var Tooltip = {
1198
3279
  };
1199
3280
  export {
1200
3281
  Tooltip,
3282
+ ToggleGroup,
3283
+ Toggle,
1201
3284
  Toast,
1202
3285
  Tabs,
1203
3286
  Switch,
1204
3287
  Slider,
1205
3288
  Select,
3289
+ ScrollArea,
3290
+ ResizablePanel,
1206
3291
  Radio,
1207
3292
  Progress,
1208
3293
  Popover,
3294
+ NavigationMenu,
3295
+ Menubar,
1209
3296
  Menu,
3297
+ HoverCard,
3298
+ DropdownMenu,
1210
3299
  Dialog,
3300
+ DatePicker,
3301
+ ContextMenu,
3302
+ Command,
1211
3303
  Combobox,
3304
+ Collapsible,
1212
3305
  Checkbox,
3306
+ Carousel,
3307
+ Calendar,
1213
3308
  Button,
1214
3309
  Accordion
1215
3310
  };