dalila 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/componentes/ui/accordion/index.d.ts +2 -0
  2. package/dist/componentes/ui/accordion/index.js +114 -0
  3. package/dist/componentes/ui/calendar/index.d.ts +2 -0
  4. package/dist/componentes/ui/calendar/index.js +132 -0
  5. package/dist/componentes/ui/combobox/index.d.ts +2 -0
  6. package/dist/componentes/ui/combobox/index.js +161 -0
  7. package/dist/componentes/ui/dialog/index.d.ts +10 -0
  8. package/dist/componentes/ui/dialog/index.js +54 -0
  9. package/dist/componentes/ui/drawer/index.d.ts +2 -0
  10. package/dist/componentes/ui/drawer/index.js +41 -0
  11. package/dist/componentes/ui/dropdown/index.d.ts +2 -0
  12. package/dist/componentes/ui/dropdown/index.js +48 -0
  13. package/dist/componentes/ui/dropzone/index.d.ts +2 -0
  14. package/dist/componentes/ui/dropzone/index.js +92 -0
  15. package/dist/componentes/ui/env.d.ts +1 -0
  16. package/dist/componentes/ui/env.js +2 -0
  17. package/dist/componentes/ui/index.d.ts +13 -0
  18. package/dist/componentes/ui/index.js +12 -0
  19. package/dist/componentes/ui/popover/index.d.ts +2 -0
  20. package/dist/componentes/ui/popover/index.js +156 -0
  21. package/dist/componentes/ui/runtime.d.ts +20 -0
  22. package/dist/componentes/ui/runtime.js +421 -0
  23. package/dist/componentes/ui/tabs/index.d.ts +3 -0
  24. package/dist/componentes/ui/tabs/index.js +101 -0
  25. package/dist/componentes/ui/toast/index.d.ts +3 -0
  26. package/dist/componentes/ui/toast/index.js +115 -0
  27. package/dist/componentes/ui/ui-types.d.ts +175 -0
  28. package/dist/componentes/ui/ui-types.js +1 -0
  29. package/dist/componentes/ui/validate.d.ts +7 -0
  30. package/dist/componentes/ui/validate.js +71 -0
  31. package/dist/components/ui/accordion/index.d.ts +2 -0
  32. package/dist/components/ui/accordion/index.js +114 -0
  33. package/dist/components/ui/calendar/index.d.ts +2 -0
  34. package/dist/components/ui/calendar/index.js +132 -0
  35. package/dist/components/ui/combobox/index.d.ts +2 -0
  36. package/dist/components/ui/combobox/index.js +161 -0
  37. package/dist/components/ui/dialog/index.d.ts +10 -0
  38. package/dist/components/ui/dialog/index.js +54 -0
  39. package/dist/components/ui/drawer/index.d.ts +2 -0
  40. package/dist/components/ui/drawer/index.js +41 -0
  41. package/dist/components/ui/dropdown/index.d.ts +2 -0
  42. package/dist/components/ui/dropdown/index.js +48 -0
  43. package/dist/components/ui/dropzone/index.d.ts +2 -0
  44. package/dist/components/ui/dropzone/index.js +92 -0
  45. package/dist/components/ui/env.d.ts +1 -0
  46. package/dist/components/ui/env.js +2 -0
  47. package/dist/components/ui/index.d.ts +13 -0
  48. package/dist/components/ui/index.js +12 -0
  49. package/dist/components/ui/popover/index.d.ts +2 -0
  50. package/dist/components/ui/popover/index.js +156 -0
  51. package/dist/components/ui/runtime.d.ts +20 -0
  52. package/dist/components/ui/runtime.js +421 -0
  53. package/dist/components/ui/tabs/index.d.ts +3 -0
  54. package/dist/components/ui/tabs/index.js +101 -0
  55. package/dist/components/ui/toast/index.d.ts +3 -0
  56. package/dist/components/ui/toast/index.js +115 -0
  57. package/dist/components/ui/ui-types.d.ts +175 -0
  58. package/dist/components/ui/ui-types.js +1 -0
  59. package/dist/components/ui/validate.d.ts +7 -0
  60. package/dist/components/ui/validate.js +71 -0
  61. package/dist/ui/accordion.d.ts +2 -0
  62. package/dist/ui/accordion.js +114 -0
  63. package/dist/ui/calendar.d.ts +2 -0
  64. package/dist/ui/calendar.js +132 -0
  65. package/dist/ui/combobox.d.ts +2 -0
  66. package/dist/ui/combobox.js +161 -0
  67. package/dist/ui/dialog.d.ts +10 -0
  68. package/dist/ui/dialog.js +54 -0
  69. package/dist/ui/drawer.d.ts +2 -0
  70. package/dist/ui/drawer.js +41 -0
  71. package/dist/ui/dropdown.d.ts +2 -0
  72. package/dist/ui/dropdown.js +48 -0
  73. package/dist/ui/dropzone.d.ts +2 -0
  74. package/dist/ui/dropzone.js +92 -0
  75. package/dist/ui/env.d.ts +1 -0
  76. package/dist/ui/env.js +2 -0
  77. package/dist/ui/index.d.ts +13 -0
  78. package/dist/ui/index.js +12 -0
  79. package/dist/ui/popover.d.ts +2 -0
  80. package/dist/ui/popover.js +156 -0
  81. package/dist/ui/runtime.d.ts +20 -0
  82. package/dist/ui/runtime.js +421 -0
  83. package/dist/ui/tabs.d.ts +3 -0
  84. package/dist/ui/tabs.js +101 -0
  85. package/dist/ui/toast.d.ts +3 -0
  86. package/dist/ui/toast.js +115 -0
  87. package/dist/ui/ui-types.d.ts +175 -0
  88. package/dist/ui/ui-types.js +1 -0
  89. package/dist/ui/validate.d.ts +7 -0
  90. package/dist/ui/validate.js +71 -0
  91. package/package.json +56 -2
  92. package/src/components/ui/accordion/accordion.css +90 -0
  93. package/src/components/ui/alert/alert.css +78 -0
  94. package/src/components/ui/avatar/avatar.css +45 -0
  95. package/src/components/ui/badge/badge.css +71 -0
  96. package/src/components/ui/breadcrumb/breadcrumb.css +41 -0
  97. package/src/components/ui/button/button.css +135 -0
  98. package/src/components/ui/calendar/calendar.css +96 -0
  99. package/src/components/ui/card/card.css +93 -0
  100. package/src/components/ui/checkbox/checkbox.css +57 -0
  101. package/src/components/ui/chip/chip.css +62 -0
  102. package/src/components/ui/collapsible/collapsible.css +61 -0
  103. package/src/components/ui/combobox/combobox.css +85 -0
  104. package/src/components/ui/dalila/dalila.css +42 -0
  105. package/src/components/ui/dalila-core/dalila-core.css +14 -0
  106. package/src/components/ui/dialog/dialog.css +125 -0
  107. package/src/components/ui/drawer/drawer.css +122 -0
  108. package/src/components/ui/dropdown/dropdown.css +87 -0
  109. package/src/components/ui/dropzone/dropzone.css +47 -0
  110. package/src/components/ui/empty-state/empty-state.css +33 -0
  111. package/src/components/ui/form/form.css +44 -0
  112. package/src/components/ui/input/input.css +106 -0
  113. package/src/components/ui/layout/layout.css +62 -0
  114. package/src/components/ui/pagination/pagination.css +55 -0
  115. package/src/components/ui/popover/popover.css +55 -0
  116. package/src/components/ui/radio/radio.css +56 -0
  117. package/src/components/ui/separator/separator.css +38 -0
  118. package/src/components/ui/skeleton/skeleton.css +57 -0
  119. package/src/components/ui/slider/slider.css +60 -0
  120. package/src/components/ui/spinner/spinner.css +38 -0
  121. package/src/components/ui/table/table.css +54 -0
  122. package/src/components/ui/tabs/tabs.css +74 -0
  123. package/src/components/ui/toast/toast.css +100 -0
  124. package/src/components/ui/toggle/toggle.css +90 -0
  125. package/src/components/ui/tokens/tokens.css +161 -0
  126. package/src/components/ui/tooltip/tooltip.css +53 -0
  127. package/src/components/ui/typography/typography.css +81 -0
@@ -0,0 +1,2 @@
1
+ import type { Accordion, AccordionOptions } from "../ui-types.js";
2
+ export declare function createAccordion(options?: AccordionOptions): Accordion;
@@ -0,0 +1,114 @@
1
+ import { signal, computed } from "../../../core/signal.js";
2
+ import { getCurrentScope } from "../../../core/scope.js";
3
+ export function createAccordion(options = {}) {
4
+ const { single = false, initial = [] } = options;
5
+ const hasInitial = Object.prototype.hasOwnProperty.call(options, "initial");
6
+ const seededInitial = single ? initial.slice(0, 1) : initial;
7
+ const openItems = signal(new Set(seededInitial));
8
+ const toggle = (itemId) => {
9
+ openItems.update((current) => {
10
+ const next = new Set(current);
11
+ if (next.has(itemId)) {
12
+ next.delete(itemId);
13
+ }
14
+ else {
15
+ if (single)
16
+ next.clear();
17
+ next.add(itemId);
18
+ }
19
+ return next;
20
+ });
21
+ };
22
+ const open = (itemId) => {
23
+ openItems.update((current) => {
24
+ const next = new Set(current);
25
+ if (single)
26
+ next.clear();
27
+ next.add(itemId);
28
+ return next;
29
+ });
30
+ };
31
+ const close = (itemId) => {
32
+ openItems.update((current) => {
33
+ const next = new Set(current);
34
+ next.delete(itemId);
35
+ return next;
36
+ });
37
+ };
38
+ const _isOpenCache = new Map();
39
+ const isOpen = (itemId) => {
40
+ let sig = _isOpenCache.get(itemId);
41
+ if (!sig) {
42
+ sig = computed(() => openItems().has(itemId));
43
+ _isOpenCache.set(itemId, sig);
44
+ }
45
+ return sig;
46
+ };
47
+ const _attachTo = (el) => {
48
+ const scope = getCurrentScope();
49
+ let syncing = false;
50
+ const allDetails = () => Array.from(el.querySelectorAll("details[data-accordion]"));
51
+ const syncDOMFromSignal = (set) => {
52
+ syncing = true;
53
+ for (const details of allDetails()) {
54
+ const itemId = details.dataset.accordion;
55
+ if (!itemId)
56
+ continue;
57
+ details.open = set.has(itemId);
58
+ }
59
+ syncing = false;
60
+ };
61
+ const syncSignalFromDOM = () => {
62
+ const next = new Set();
63
+ for (const details of allDetails()) {
64
+ if (!details.open)
65
+ continue;
66
+ const itemId = details.dataset.accordion;
67
+ if (!itemId)
68
+ continue;
69
+ if (single) {
70
+ next.clear();
71
+ }
72
+ next.add(itemId);
73
+ }
74
+ openItems.set(next);
75
+ };
76
+ const onToggle = (ev) => {
77
+ const details = ev.target;
78
+ if (syncing)
79
+ return;
80
+ const itemId = details.dataset.accordion;
81
+ if (!itemId)
82
+ return;
83
+ openItems.update((current) => {
84
+ const next = new Set(current);
85
+ if (details.open) {
86
+ if (single)
87
+ next.clear();
88
+ next.add(itemId);
89
+ }
90
+ else {
91
+ next.delete(itemId);
92
+ }
93
+ return next;
94
+ });
95
+ };
96
+ const unsub = openItems.on((set) => {
97
+ if (!syncing)
98
+ syncDOMFromSignal(set);
99
+ });
100
+ // If initial was not provided, respect current DOM open state.
101
+ if (!hasInitial)
102
+ syncSignalFromDOM();
103
+ else
104
+ syncDOMFromSignal(openItems());
105
+ el.addEventListener("toggle", onToggle, true);
106
+ if (scope) {
107
+ scope.onCleanup(() => {
108
+ unsub();
109
+ el.removeEventListener("toggle", onToggle, true);
110
+ });
111
+ }
112
+ };
113
+ return { openItems, toggle, open, close, isOpen, _attachTo };
114
+ }
@@ -0,0 +1,2 @@
1
+ import type { Calendar, CalendarOptions } from "../ui-types.js";
2
+ export declare function createCalendar(options?: CalendarOptions): Calendar;
@@ -0,0 +1,132 @@
1
+ import { signal, computed } from "../../../core/signal.js";
2
+ import { batch } from "../../../core/scheduler.js";
3
+ import { validateCalendarOptions } from "../validate.js";
4
+ const DEFAULT_DAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
5
+ const DEFAULT_MONTH_LABELS = [
6
+ "January", "February", "March", "April", "May", "June",
7
+ "July", "August", "September", "October", "November", "December",
8
+ ];
9
+ function isSameDay(a, b) {
10
+ return (a.getFullYear() === b.getFullYear() &&
11
+ a.getMonth() === b.getMonth() &&
12
+ a.getDate() === b.getDate());
13
+ }
14
+ export function createCalendar(options = {}) {
15
+ validateCalendarOptions(options);
16
+ const { initial, min, max, dayLabels: customDayLabels, monthLabels: customMonthLabels, } = options;
17
+ const now = initial ?? new Date();
18
+ const monthLabels = customMonthLabels ?? DEFAULT_MONTH_LABELS;
19
+ const calDayLabels = customDayLabels ?? DEFAULT_DAY_LABELS;
20
+ const year = signal(now.getFullYear());
21
+ const month = signal(now.getMonth());
22
+ const selected = signal(initial ?? null);
23
+ const title = computed(() => `${monthLabels[month()]} ${year()}`);
24
+ const days = computed(() => {
25
+ const y = year();
26
+ const m = month();
27
+ const sel = selected();
28
+ const today = new Date();
29
+ const firstDow = new Date(y, m, 1).getDay();
30
+ const daysInMonth = new Date(y, m + 1, 0).getDate();
31
+ const prevMonthDays = new Date(y, m, 0).getDate();
32
+ const grid = [];
33
+ const GRID_SIZE = 42;
34
+ // Previous month days
35
+ for (let i = firstDow - 1; i >= 0; i--) {
36
+ const date = prevMonthDays - i;
37
+ const fullDate = new Date(y, m - 1, date);
38
+ grid.push({
39
+ date,
40
+ month: "prev",
41
+ fullDate,
42
+ isToday: isSameDay(fullDate, today),
43
+ isSelected: sel !== null && isSameDay(fullDate, sel),
44
+ disabled: isOutOfBounds(fullDate),
45
+ });
46
+ }
47
+ // Current month days
48
+ for (let d = 1; d <= daysInMonth; d++) {
49
+ const fullDate = new Date(y, m, d);
50
+ grid.push({
51
+ date: d,
52
+ month: "current",
53
+ fullDate,
54
+ isToday: isSameDay(fullDate, today),
55
+ isSelected: sel !== null && isSameDay(fullDate, sel),
56
+ disabled: isOutOfBounds(fullDate),
57
+ });
58
+ }
59
+ // Next month days
60
+ const remaining = GRID_SIZE - grid.length;
61
+ for (let d = 1; d <= remaining; d++) {
62
+ const fullDate = new Date(y, m + 1, d);
63
+ grid.push({
64
+ date: d,
65
+ month: "next",
66
+ fullDate,
67
+ isToday: isSameDay(fullDate, today),
68
+ isSelected: sel !== null && isSameDay(fullDate, sel),
69
+ disabled: isOutOfBounds(fullDate),
70
+ });
71
+ }
72
+ return grid;
73
+ });
74
+ function isOutOfBounds(date) {
75
+ if (min && date < min)
76
+ return true;
77
+ if (max && date > max)
78
+ return true;
79
+ return false;
80
+ }
81
+ const prev = () => {
82
+ batch(() => {
83
+ if (month() === 0) {
84
+ month.set(11);
85
+ year.update((y) => y - 1);
86
+ }
87
+ else {
88
+ month.update((m) => m - 1);
89
+ }
90
+ });
91
+ };
92
+ const next = () => {
93
+ batch(() => {
94
+ if (month() === 11) {
95
+ month.set(0);
96
+ year.update((y) => y + 1);
97
+ }
98
+ else {
99
+ month.update((m) => m + 1);
100
+ }
101
+ });
102
+ };
103
+ const select = (date) => {
104
+ if (isOutOfBounds(date))
105
+ return;
106
+ selected.set(date);
107
+ };
108
+ const handleDayClick = (ev) => {
109
+ const target = ev.target.closest("[data-date]");
110
+ if (!target)
111
+ return;
112
+ const dateStr = target.dataset.date;
113
+ if (!dateStr)
114
+ return;
115
+ const date = new Date(dateStr);
116
+ if (isNaN(date.getTime()))
117
+ return;
118
+ select(date);
119
+ };
120
+ return {
121
+ year,
122
+ month,
123
+ selected,
124
+ title,
125
+ days,
126
+ dayLabels: calDayLabels,
127
+ prev,
128
+ next,
129
+ select,
130
+ handleDayClick,
131
+ };
132
+ }
@@ -0,0 +1,2 @@
1
+ import type { Combobox, ComboboxOptions } from "../ui-types.js";
2
+ export declare function createCombobox(options: ComboboxOptions): Combobox;
@@ -0,0 +1,161 @@
1
+ import { signal, computed } from "../../../core/signal.js";
2
+ import { getCurrentScope } from "../../../core/scope.js";
3
+ import { validateComboboxOptions } from "../validate.js";
4
+ import { isBrowser } from "../env.js";
5
+ let comboboxUid = 0;
6
+ export function createCombobox(options) {
7
+ validateComboboxOptions(options);
8
+ const { options: items, placeholder = "", name: fieldName } = options;
9
+ const open = signal(false);
10
+ const query = signal("");
11
+ const value = signal("");
12
+ const label = signal("");
13
+ const highlightedIndex = signal(-1);
14
+ const filtered = computed(() => {
15
+ const q = query().toLowerCase();
16
+ if (!q)
17
+ return items;
18
+ return items.filter((opt) => opt.label.toLowerCase().includes(q));
19
+ });
20
+ const show = () => open.set(true);
21
+ const close = () => {
22
+ open.set(false);
23
+ highlightedIndex.set(-1);
24
+ };
25
+ const toggle = () => open.update((v) => !v);
26
+ const handleInput = (ev) => {
27
+ const input = ev.target;
28
+ query.set(input.value);
29
+ open.set(true);
30
+ highlightedIndex.set(-1);
31
+ };
32
+ const handleSelect = (ev) => {
33
+ const target = ev.target.closest("[data-value]");
34
+ if (!target)
35
+ return;
36
+ const val = target.dataset.value ?? "";
37
+ const lbl = target.textContent?.trim() ?? val;
38
+ value.set(val);
39
+ label.set(lbl);
40
+ query.set(lbl);
41
+ close();
42
+ };
43
+ const handleKeydown = (ev) => {
44
+ const list = filtered();
45
+ switch (ev.key) {
46
+ case "ArrowDown":
47
+ ev.preventDefault();
48
+ if (!open()) {
49
+ show();
50
+ }
51
+ else {
52
+ highlightedIndex.update((i) => i < list.length - 1 ? i + 1 : 0);
53
+ }
54
+ break;
55
+ case "ArrowUp":
56
+ ev.preventDefault();
57
+ if (open()) {
58
+ highlightedIndex.update((i) => i > 0 ? i - 1 : list.length - 1);
59
+ }
60
+ break;
61
+ case "Enter":
62
+ ev.preventDefault();
63
+ if (open() && highlightedIndex() >= 0) {
64
+ const opt = list[highlightedIndex()];
65
+ if (opt) {
66
+ value.set(opt.value);
67
+ label.set(opt.label);
68
+ query.set(opt.label);
69
+ close();
70
+ }
71
+ }
72
+ break;
73
+ case "Escape":
74
+ close();
75
+ break;
76
+ }
77
+ };
78
+ const _attachTo = (el) => {
79
+ const scope = getCurrentScope();
80
+ const uid = ++comboboxUid;
81
+ // ARIA setup
82
+ const input = el.querySelector("input");
83
+ const list = el.querySelector("ul, [data-d-tag='d-combobox-list'], .d-combobox-list");
84
+ if (input) {
85
+ if (placeholder)
86
+ input.setAttribute("placeholder", placeholder);
87
+ input.setAttribute("role", "combobox");
88
+ input.setAttribute("aria-autocomplete", "list");
89
+ input.setAttribute("aria-expanded", "false");
90
+ }
91
+ if (list) {
92
+ const listId = list.id || `d-combobox-list-${uid}`;
93
+ if (!list.id)
94
+ list.id = listId;
95
+ list.setAttribute("role", "listbox");
96
+ if (input)
97
+ input.setAttribute("aria-controls", listId);
98
+ }
99
+ const unsubExpanded = open.on((isOpen) => {
100
+ input?.setAttribute("aria-expanded", String(isOpen));
101
+ });
102
+ const unsubHighlight = highlightedIndex.on((idx) => {
103
+ if (idx >= 0 && list) {
104
+ const opts = list.querySelectorAll("[data-value]");
105
+ const active = opts[idx];
106
+ if (active) {
107
+ const activeId = active.id || `d-combobox-opt-${uid}-${idx}`;
108
+ if (!active.id)
109
+ active.id = activeId;
110
+ active.setAttribute("role", "option");
111
+ input?.setAttribute("aria-activedescendant", activeId);
112
+ }
113
+ }
114
+ else {
115
+ input?.removeAttribute("aria-activedescendant");
116
+ }
117
+ });
118
+ // Hidden input for form submission
119
+ let hidden = el.querySelector('input[type="hidden"]');
120
+ if (!hidden) {
121
+ hidden = el.ownerDocument.createElement("input");
122
+ hidden.type = "hidden";
123
+ hidden.name = fieldName || input?.getAttribute("name") || "";
124
+ el.appendChild(hidden);
125
+ }
126
+ const unsubValue = value.on((v) => {
127
+ if (hidden)
128
+ hidden.value = v;
129
+ });
130
+ const onDocClick = (e) => {
131
+ if (!el.contains(e.target))
132
+ close();
133
+ };
134
+ if (isBrowser)
135
+ document.addEventListener("click", onDocClick);
136
+ if (scope) {
137
+ scope.onCleanup(() => {
138
+ unsubExpanded();
139
+ unsubHighlight();
140
+ unsubValue();
141
+ if (isBrowser)
142
+ document.removeEventListener("click", onDocClick);
143
+ });
144
+ }
145
+ };
146
+ return {
147
+ open,
148
+ query,
149
+ value,
150
+ label,
151
+ filtered,
152
+ highlightedIndex,
153
+ show,
154
+ close,
155
+ toggle,
156
+ handleInput,
157
+ handleSelect,
158
+ handleKeydown,
159
+ _attachTo,
160
+ };
161
+ }
@@ -0,0 +1,10 @@
1
+ import { type Signal } from "../../../core/signal.js";
2
+ import type { Dialog, DialogOptions } from "../ui-types.js";
3
+ /**
4
+ * Shared dialog behavior — used by both createDialog and createDrawer.
5
+ */
6
+ export declare function _attachDialogBehavior(el: HTMLDialogElement, open: Signal<boolean>, closeFn: () => void, opts: {
7
+ closeOnBackdrop: boolean;
8
+ closeOnEscape: boolean;
9
+ }): void;
10
+ export declare function createDialog(options?: DialogOptions): Dialog;
@@ -0,0 +1,54 @@
1
+ import { signal } from "../../../core/signal.js";
2
+ import { getCurrentScope } from "../../../core/scope.js";
3
+ import { validateDialogOptions } from "../validate.js";
4
+ /**
5
+ * Shared dialog behavior — used by both createDialog and createDrawer.
6
+ */
7
+ export function _attachDialogBehavior(el, open, closeFn, opts) {
8
+ const scope = getCurrentScope();
9
+ // Sync signal → native dialog
10
+ const unsub = open.on((isOpen) => {
11
+ if (isOpen && !el.open)
12
+ el.showModal();
13
+ else if (!isOpen && el.open)
14
+ el.close();
15
+ });
16
+ // Native close event → sync signal
17
+ const onClose = () => open.set(false);
18
+ el.addEventListener("close", onClose);
19
+ // Backdrop click
20
+ const onBackdropClick = (e) => {
21
+ if (opts.closeOnBackdrop && e.target === el)
22
+ closeFn();
23
+ };
24
+ el.addEventListener("click", onBackdropClick);
25
+ // Escape key
26
+ if (!opts.closeOnEscape) {
27
+ const onCancel = (e) => e.preventDefault();
28
+ el.addEventListener("cancel", onCancel);
29
+ if (scope) {
30
+ scope.onCleanup(() => el.removeEventListener("cancel", onCancel));
31
+ }
32
+ }
33
+ // ARIA
34
+ el.setAttribute("aria-modal", "true");
35
+ if (scope) {
36
+ scope.onCleanup(() => {
37
+ unsub();
38
+ el.removeEventListener("close", onClose);
39
+ el.removeEventListener("click", onBackdropClick);
40
+ });
41
+ }
42
+ }
43
+ export function createDialog(options = {}) {
44
+ validateDialogOptions(options);
45
+ const { closeOnBackdrop = true, closeOnEscape = true } = options;
46
+ const open = signal(false);
47
+ const show = () => open.set(true);
48
+ const close = () => open.set(false);
49
+ const toggle = () => open.update((v) => !v);
50
+ const _attachTo = (el) => {
51
+ _attachDialogBehavior(el, open, close, { closeOnBackdrop, closeOnEscape });
52
+ };
53
+ return { open, show, close, toggle, _attachTo };
54
+ }
@@ -0,0 +1,2 @@
1
+ import type { Drawer, DrawerOptions } from "../ui-types.js";
2
+ export declare function createDrawer(options?: DrawerOptions): Drawer;
@@ -0,0 +1,41 @@
1
+ import { signal } from "../../../core/signal.js";
2
+ import { getCurrentScope } from "../../../core/scope.js";
3
+ import { _attachDialogBehavior } from "../dialog/index.js";
4
+ import { validateDrawerOptions } from "../validate.js";
5
+ const SIDE_CLASSES = {
6
+ right: "",
7
+ left: "d-drawer-left",
8
+ bottom: "d-sheet",
9
+ };
10
+ export function createDrawer(options = {}) {
11
+ validateDrawerOptions(options);
12
+ const { closeOnBackdrop = true, closeOnEscape = true, side: initialSide = "right", } = options;
13
+ const open = signal(false);
14
+ const side = signal(initialSide);
15
+ const show = () => open.set(true);
16
+ const close = () => open.set(false);
17
+ const toggle = () => open.update((v) => !v);
18
+ const _attachTo = (el) => {
19
+ const scope = getCurrentScope();
20
+ // Shared dialog behavior (open sync, backdrop, escape, ARIA)
21
+ _attachDialogBehavior(el, open, close, { closeOnBackdrop, closeOnEscape });
22
+ // Apply initial side class
23
+ const initial = SIDE_CLASSES[side()];
24
+ if (initial)
25
+ el.classList.add(initial);
26
+ // React to side changes
27
+ const unsub = side.on((s) => {
28
+ for (const cls of Object.values(SIDE_CLASSES)) {
29
+ if (cls)
30
+ el.classList.remove(cls);
31
+ }
32
+ const cls = SIDE_CLASSES[s];
33
+ if (cls)
34
+ el.classList.add(cls);
35
+ });
36
+ if (scope) {
37
+ scope.onCleanup(() => unsub());
38
+ }
39
+ };
40
+ return { open, side, show, close, toggle, _attachTo };
41
+ }
@@ -0,0 +1,2 @@
1
+ import type { Dropdown, DropdownOptions } from "../ui-types.js";
2
+ export declare function createDropdown(options?: DropdownOptions): Dropdown;
@@ -0,0 +1,48 @@
1
+ import { signal } from "../../../core/signal.js";
2
+ import { getCurrentScope } from "../../../core/scope.js";
3
+ import { isBrowser } from "../env.js";
4
+ export function createDropdown(options = {}) {
5
+ const { closeOnSelect = true } = options;
6
+ const open = signal(false);
7
+ const toggle = (ev) => {
8
+ if (ev)
9
+ ev.stopPropagation();
10
+ open.update((v) => !v);
11
+ };
12
+ const close = () => open.set(false);
13
+ const select = (ev) => {
14
+ if (ev)
15
+ ev.stopPropagation();
16
+ if (closeOnSelect)
17
+ close();
18
+ };
19
+ const _attachTo = (el) => {
20
+ const scope = getCurrentScope();
21
+ // ARIA
22
+ const trigger = el.querySelector("button, [role='button'], [data-d-tag='d-button']");
23
+ const menu = el.querySelector("[data-d-tag='d-menu'], .d-menu");
24
+ if (menu)
25
+ menu.setAttribute("role", "menu");
26
+ if (trigger) {
27
+ trigger.setAttribute("aria-haspopup", "true");
28
+ trigger.setAttribute("aria-expanded", "false");
29
+ }
30
+ const unsubAria = open.on((isOpen) => {
31
+ trigger?.setAttribute("aria-expanded", String(isOpen));
32
+ });
33
+ const onDocClick = (e) => {
34
+ if (!el.contains(e.target))
35
+ close();
36
+ };
37
+ if (isBrowser)
38
+ document.addEventListener("click", onDocClick);
39
+ if (scope) {
40
+ scope.onCleanup(() => {
41
+ unsubAria();
42
+ if (isBrowser)
43
+ document.removeEventListener("click", onDocClick);
44
+ });
45
+ }
46
+ };
47
+ return { open, toggle, close, select, _attachTo };
48
+ }
@@ -0,0 +1,2 @@
1
+ import type { Dropzone, DropzoneOptions } from "../ui-types.js";
2
+ export declare function createDropzone(options?: DropzoneOptions): Dropzone;
@@ -0,0 +1,92 @@
1
+ import { signal } from "../../../core/signal.js";
2
+ import { getCurrentScope } from "../../../core/scope.js";
3
+ import { validateDropzoneOptions } from "../validate.js";
4
+ export function createDropzone(options = {}) {
5
+ validateDropzoneOptions(options);
6
+ const { accept, multiple = true, maxFiles, maxSize } = options;
7
+ const dragging = signal(false);
8
+ const files = signal([]);
9
+ let inputEl = null;
10
+ const filterFiles = (fileList) => {
11
+ let result = fileList;
12
+ if (accept) {
13
+ const types = accept.split(",").map((t) => t.trim().toLowerCase());
14
+ result = result.filter((f) => {
15
+ const ext = "." + f.name.split(".").pop()?.toLowerCase();
16
+ const mime = f.type.toLowerCase();
17
+ return types.some((t) => t === ext || t === mime || (t.endsWith("/*") && mime.startsWith(t.slice(0, -1))));
18
+ });
19
+ }
20
+ if (maxSize) {
21
+ result = result.filter((f) => f.size <= maxSize);
22
+ }
23
+ if (maxFiles) {
24
+ result = result.slice(0, maxFiles);
25
+ }
26
+ return result;
27
+ };
28
+ const addFiles = (newFiles) => {
29
+ const filtered = filterFiles(newFiles);
30
+ if (multiple) {
31
+ files.update((current) => {
32
+ const combined = [...current, ...filtered];
33
+ return maxFiles ? combined.slice(0, maxFiles) : combined;
34
+ });
35
+ }
36
+ else {
37
+ files.set(filtered.slice(0, 1));
38
+ }
39
+ };
40
+ const browse = () => {
41
+ inputEl?.click();
42
+ };
43
+ const handleClick = () => browse();
44
+ const handleDragover = (ev) => {
45
+ ev.preventDefault();
46
+ dragging.set(true);
47
+ };
48
+ const handleDragleave = () => {
49
+ dragging.set(false);
50
+ };
51
+ const handleDrop = (ev) => {
52
+ ev.preventDefault();
53
+ dragging.set(false);
54
+ if (ev.dataTransfer?.files) {
55
+ addFiles(Array.from(ev.dataTransfer.files));
56
+ }
57
+ };
58
+ const _attachTo = (el) => {
59
+ const scope = getCurrentScope();
60
+ // ARIA
61
+ el.setAttribute("role", "button");
62
+ el.setAttribute("tabindex", "0");
63
+ inputEl = el.querySelector('input[type="file"]');
64
+ if (inputEl) {
65
+ if (accept)
66
+ inputEl.accept = accept;
67
+ inputEl.multiple = multiple;
68
+ const onInputChange = () => {
69
+ if (inputEl?.files) {
70
+ addFiles(Array.from(inputEl.files));
71
+ inputEl.value = "";
72
+ }
73
+ };
74
+ inputEl.addEventListener("change", onInputChange);
75
+ if (scope) {
76
+ scope.onCleanup(() => {
77
+ inputEl?.removeEventListener("change", onInputChange);
78
+ });
79
+ }
80
+ }
81
+ };
82
+ return {
83
+ dragging,
84
+ files,
85
+ browse,
86
+ handleClick,
87
+ handleDragover,
88
+ handleDragleave,
89
+ handleDrop,
90
+ _attachTo,
91
+ };
92
+ }
@@ -0,0 +1 @@
1
+ export declare const isBrowser: boolean;
@@ -0,0 +1,2 @@
1
+ // ── Environment Detection ───────────────────────────────────────────
2
+ export const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";