dalila 1.5.13 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +47 -0
  2. package/dist/componentes/ui/accordion/index.d.ts +2 -0
  3. package/dist/componentes/ui/accordion/index.js +114 -0
  4. package/dist/componentes/ui/calendar/index.d.ts +2 -0
  5. package/dist/componentes/ui/calendar/index.js +132 -0
  6. package/dist/componentes/ui/combobox/index.d.ts +2 -0
  7. package/dist/componentes/ui/combobox/index.js +161 -0
  8. package/dist/componentes/ui/dialog/index.d.ts +10 -0
  9. package/dist/componentes/ui/dialog/index.js +54 -0
  10. package/dist/componentes/ui/drawer/index.d.ts +2 -0
  11. package/dist/componentes/ui/drawer/index.js +41 -0
  12. package/dist/componentes/ui/dropdown/index.d.ts +2 -0
  13. package/dist/componentes/ui/dropdown/index.js +48 -0
  14. package/dist/componentes/ui/dropzone/index.d.ts +2 -0
  15. package/dist/componentes/ui/dropzone/index.js +92 -0
  16. package/dist/componentes/ui/env.d.ts +1 -0
  17. package/dist/componentes/ui/env.js +2 -0
  18. package/dist/componentes/ui/index.d.ts +13 -0
  19. package/dist/componentes/ui/index.js +12 -0
  20. package/dist/componentes/ui/popover/index.d.ts +2 -0
  21. package/dist/componentes/ui/popover/index.js +156 -0
  22. package/dist/componentes/ui/runtime.d.ts +20 -0
  23. package/dist/componentes/ui/runtime.js +421 -0
  24. package/dist/componentes/ui/tabs/index.d.ts +3 -0
  25. package/dist/componentes/ui/tabs/index.js +101 -0
  26. package/dist/componentes/ui/toast/index.d.ts +3 -0
  27. package/dist/componentes/ui/toast/index.js +115 -0
  28. package/dist/componentes/ui/ui-types.d.ts +175 -0
  29. package/dist/componentes/ui/ui-types.js +1 -0
  30. package/dist/componentes/ui/validate.d.ts +7 -0
  31. package/dist/componentes/ui/validate.js +71 -0
  32. package/dist/components/ui/accordion/index.d.ts +2 -0
  33. package/dist/components/ui/accordion/index.js +114 -0
  34. package/dist/components/ui/calendar/index.d.ts +2 -0
  35. package/dist/components/ui/calendar/index.js +132 -0
  36. package/dist/components/ui/combobox/index.d.ts +2 -0
  37. package/dist/components/ui/combobox/index.js +161 -0
  38. package/dist/components/ui/dialog/index.d.ts +10 -0
  39. package/dist/components/ui/dialog/index.js +54 -0
  40. package/dist/components/ui/drawer/index.d.ts +2 -0
  41. package/dist/components/ui/drawer/index.js +41 -0
  42. package/dist/components/ui/dropdown/index.d.ts +2 -0
  43. package/dist/components/ui/dropdown/index.js +48 -0
  44. package/dist/components/ui/dropzone/index.d.ts +2 -0
  45. package/dist/components/ui/dropzone/index.js +92 -0
  46. package/dist/components/ui/env.d.ts +1 -0
  47. package/dist/components/ui/env.js +2 -0
  48. package/dist/components/ui/index.d.ts +13 -0
  49. package/dist/components/ui/index.js +12 -0
  50. package/dist/components/ui/popover/index.d.ts +2 -0
  51. package/dist/components/ui/popover/index.js +156 -0
  52. package/dist/components/ui/runtime.d.ts +20 -0
  53. package/dist/components/ui/runtime.js +421 -0
  54. package/dist/components/ui/tabs/index.d.ts +3 -0
  55. package/dist/components/ui/tabs/index.js +101 -0
  56. package/dist/components/ui/toast/index.d.ts +3 -0
  57. package/dist/components/ui/toast/index.js +115 -0
  58. package/dist/components/ui/ui-types.d.ts +175 -0
  59. package/dist/components/ui/ui-types.js +1 -0
  60. package/dist/components/ui/validate.d.ts +7 -0
  61. package/dist/components/ui/validate.js +71 -0
  62. package/dist/form/form-types.d.ts +181 -0
  63. package/dist/form/form-types.js +4 -0
  64. package/dist/form/form.d.ts +71 -0
  65. package/dist/form/form.js +1073 -0
  66. package/dist/form/index.d.ts +2 -0
  67. package/dist/form/index.js +2 -0
  68. package/dist/index.d.ts +1 -0
  69. package/dist/index.js +1 -0
  70. package/dist/runtime/bind.js +567 -9
  71. package/dist/ui/accordion.d.ts +2 -0
  72. package/dist/ui/accordion.js +114 -0
  73. package/dist/ui/calendar.d.ts +2 -0
  74. package/dist/ui/calendar.js +132 -0
  75. package/dist/ui/combobox.d.ts +2 -0
  76. package/dist/ui/combobox.js +161 -0
  77. package/dist/ui/dialog.d.ts +10 -0
  78. package/dist/ui/dialog.js +54 -0
  79. package/dist/ui/drawer.d.ts +2 -0
  80. package/dist/ui/drawer.js +41 -0
  81. package/dist/ui/dropdown.d.ts +2 -0
  82. package/dist/ui/dropdown.js +48 -0
  83. package/dist/ui/dropzone.d.ts +2 -0
  84. package/dist/ui/dropzone.js +92 -0
  85. package/dist/ui/env.d.ts +1 -0
  86. package/dist/ui/env.js +2 -0
  87. package/dist/ui/index.d.ts +13 -0
  88. package/dist/ui/index.js +12 -0
  89. package/dist/ui/popover.d.ts +2 -0
  90. package/dist/ui/popover.js +156 -0
  91. package/dist/ui/runtime.d.ts +20 -0
  92. package/dist/ui/runtime.js +421 -0
  93. package/dist/ui/tabs.d.ts +3 -0
  94. package/dist/ui/tabs.js +101 -0
  95. package/dist/ui/toast.d.ts +3 -0
  96. package/dist/ui/toast.js +115 -0
  97. package/dist/ui/ui-types.d.ts +175 -0
  98. package/dist/ui/ui-types.js +1 -0
  99. package/dist/ui/validate.d.ts +7 -0
  100. package/dist/ui/validate.js +71 -0
  101. package/package.json +60 -2
  102. package/src/components/ui/accordion/accordion.css +90 -0
  103. package/src/components/ui/alert/alert.css +78 -0
  104. package/src/components/ui/avatar/avatar.css +45 -0
  105. package/src/components/ui/badge/badge.css +71 -0
  106. package/src/components/ui/breadcrumb/breadcrumb.css +41 -0
  107. package/src/components/ui/button/button.css +135 -0
  108. package/src/components/ui/calendar/calendar.css +96 -0
  109. package/src/components/ui/card/card.css +93 -0
  110. package/src/components/ui/checkbox/checkbox.css +57 -0
  111. package/src/components/ui/chip/chip.css +62 -0
  112. package/src/components/ui/collapsible/collapsible.css +61 -0
  113. package/src/components/ui/combobox/combobox.css +85 -0
  114. package/src/components/ui/dalila/dalila.css +42 -0
  115. package/src/components/ui/dalila-core/dalila-core.css +14 -0
  116. package/src/components/ui/dialog/dialog.css +125 -0
  117. package/src/components/ui/drawer/drawer.css +122 -0
  118. package/src/components/ui/dropdown/dropdown.css +87 -0
  119. package/src/components/ui/dropzone/dropzone.css +47 -0
  120. package/src/components/ui/empty-state/empty-state.css +33 -0
  121. package/src/components/ui/form/form.css +44 -0
  122. package/src/components/ui/input/input.css +106 -0
  123. package/src/components/ui/layout/layout.css +62 -0
  124. package/src/components/ui/pagination/pagination.css +55 -0
  125. package/src/components/ui/popover/popover.css +55 -0
  126. package/src/components/ui/radio/radio.css +56 -0
  127. package/src/components/ui/separator/separator.css +38 -0
  128. package/src/components/ui/skeleton/skeleton.css +57 -0
  129. package/src/components/ui/slider/slider.css +60 -0
  130. package/src/components/ui/spinner/spinner.css +38 -0
  131. package/src/components/ui/table/table.css +54 -0
  132. package/src/components/ui/tabs/tabs.css +74 -0
  133. package/src/components/ui/toast/toast.css +100 -0
  134. package/src/components/ui/toggle/toggle.css +90 -0
  135. package/src/components/ui/tokens/tokens.css +161 -0
  136. package/src/components/ui/tooltip/tooltip.css +53 -0
  137. package/src/components/ui/typography/typography.css +81 -0
@@ -0,0 +1,175 @@
1
+ import type { Signal } from "../../core/signal.js";
2
+ export interface DialogOptions {
3
+ closeOnBackdrop?: boolean;
4
+ closeOnEscape?: boolean;
5
+ }
6
+ export interface Dialog {
7
+ open: Signal<boolean>;
8
+ show(): void;
9
+ close(): void;
10
+ toggle(): void;
11
+ _attachTo(el: HTMLDialogElement): void;
12
+ }
13
+ export type DrawerSide = "right" | "left" | "bottom";
14
+ export interface DrawerOptions extends DialogOptions {
15
+ side?: DrawerSide;
16
+ }
17
+ export interface Drawer extends Dialog {
18
+ side: Signal<DrawerSide>;
19
+ }
20
+ export type ToastVariant = "success" | "error" | "warning" | "info";
21
+ export type ToastPosition = "top-right" | "top-left" | "bottom-right" | "bottom-left" | "top-center" | "bottom-center";
22
+ export interface ToastItem {
23
+ id: string;
24
+ variant: ToastVariant;
25
+ title: string;
26
+ text?: string;
27
+ variantClass: string;
28
+ icon: string;
29
+ }
30
+ export interface ToastOptions {
31
+ position?: ToastPosition;
32
+ duration?: number;
33
+ maxToasts?: number;
34
+ }
35
+ export interface Toast {
36
+ items: Signal<ToastItem[]>;
37
+ activeVariant: Signal<ToastVariant | "idle">;
38
+ containerClass: Signal<string>;
39
+ show(variant: ToastVariant, title: string, text?: string, duration?: number): string;
40
+ success(title: string, text?: string): string;
41
+ error(title: string, text?: string): string;
42
+ warning(title: string, text?: string): string;
43
+ info(title: string, text?: string): string;
44
+ dismiss(id: string): void;
45
+ clear(): void;
46
+ }
47
+ export interface TabsOptions {
48
+ initial?: string;
49
+ orientation?: "horizontal" | "vertical";
50
+ }
51
+ export interface Tabs {
52
+ active: Signal<string>;
53
+ select(tabId: string): void;
54
+ isActive(tabId: string): boolean;
55
+ handleClick(ev: Event): void;
56
+ _attachTo(el: HTMLElement): void;
57
+ }
58
+ export interface TabBindings {
59
+ tabClass: Signal<string>;
60
+ selected: Signal<string>;
61
+ visible: Signal<boolean>;
62
+ }
63
+ export interface DropdownOptions {
64
+ closeOnSelect?: boolean;
65
+ }
66
+ export interface Dropdown {
67
+ open: Signal<boolean>;
68
+ toggle(ev?: Event): void;
69
+ close(): void;
70
+ select(ev?: Event): void;
71
+ _attachTo(el: HTMLElement): void;
72
+ }
73
+ export interface ComboboxOption {
74
+ value: string;
75
+ label: string;
76
+ }
77
+ export interface ComboboxOptions {
78
+ options: ComboboxOption[];
79
+ placeholder?: string;
80
+ name?: string;
81
+ }
82
+ export interface Combobox {
83
+ open: Signal<boolean>;
84
+ query: Signal<string>;
85
+ value: Signal<string>;
86
+ label: Signal<string>;
87
+ filtered: Signal<ComboboxOption[]>;
88
+ highlightedIndex: Signal<number>;
89
+ show(): void;
90
+ close(): void;
91
+ toggle(): void;
92
+ handleInput(ev: Event): void;
93
+ handleSelect(ev: Event): void;
94
+ handleKeydown(ev: KeyboardEvent): void;
95
+ _attachTo(el: HTMLElement): void;
96
+ }
97
+ export interface AccordionOptions {
98
+ single?: boolean;
99
+ initial?: string[];
100
+ }
101
+ export interface Accordion {
102
+ openItems: Signal<Set<string>>;
103
+ toggle(itemId: string): void;
104
+ open(itemId: string): void;
105
+ close(itemId: string): void;
106
+ isOpen(itemId: string): Signal<boolean>;
107
+ _attachTo(el: HTMLElement): void;
108
+ }
109
+ export interface CalendarDay {
110
+ date: number;
111
+ month: "prev" | "current" | "next";
112
+ fullDate: Date;
113
+ isToday: boolean;
114
+ isSelected: boolean;
115
+ disabled: boolean;
116
+ }
117
+ export interface CalendarOptions {
118
+ initial?: Date;
119
+ min?: Date;
120
+ max?: Date;
121
+ dayLabels?: string[];
122
+ monthLabels?: string[];
123
+ }
124
+ export interface Calendar {
125
+ year: Signal<number>;
126
+ month: Signal<number>;
127
+ selected: Signal<Date | null>;
128
+ title: Signal<string>;
129
+ days: Signal<CalendarDay[]>;
130
+ dayLabels: string[];
131
+ prev(): void;
132
+ next(): void;
133
+ select(date: Date): void;
134
+ handleDayClick(ev: Event): void;
135
+ }
136
+ export interface DropzoneOptions {
137
+ accept?: string;
138
+ multiple?: boolean;
139
+ maxFiles?: number;
140
+ maxSize?: number;
141
+ }
142
+ export interface Dropzone {
143
+ dragging: Signal<boolean>;
144
+ files: Signal<File[]>;
145
+ browse(): void;
146
+ handleClick(): void;
147
+ handleDragover(ev: DragEvent): void;
148
+ handleDragleave(): void;
149
+ handleDrop(ev: DragEvent): void;
150
+ _attachTo(el: HTMLElement): void;
151
+ }
152
+ export type PopoverPlacement = "top" | "bottom" | "left" | "right" | "top-start" | "bottom-start";
153
+ export interface PopoverOptions {
154
+ placement?: PopoverPlacement;
155
+ gap?: number;
156
+ viewportPadding?: number;
157
+ }
158
+ export interface Popover {
159
+ open: Signal<boolean>;
160
+ placement: Signal<PopoverPlacement>;
161
+ show(): void;
162
+ hide(): void;
163
+ toggle(): void;
164
+ position(trigger: HTMLElement, popoverEl: HTMLElement): void;
165
+ _attachTo(trigger: HTMLElement, popoverEl: HTMLElement): void;
166
+ }
167
+ export interface TabsMount {
168
+ api: Tabs;
169
+ bindings: [string, string][];
170
+ }
171
+ export interface PopoverMount {
172
+ api: Popover;
173
+ triggerId?: string;
174
+ panelId?: string;
175
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export declare function validateDialogOptions(opts: Record<string, unknown>): void;
2
+ export declare function validateDrawerOptions(opts: Record<string, unknown>): void;
3
+ export declare function validateToastOptions(opts: Record<string, unknown>): void;
4
+ export declare function validatePopoverOptions(opts: Record<string, unknown>): void;
5
+ export declare function validateDropzoneOptions(opts: Record<string, unknown>): void;
6
+ export declare function validateCalendarOptions(opts: Record<string, unknown>): void;
7
+ export declare function validateComboboxOptions(opts: Record<string, unknown>): void;
@@ -0,0 +1,71 @@
1
+ // ── Prop Validation ─────────────────────────────────────────────────
2
+ const VALID_TOAST_POSITIONS = new Set([
3
+ "top-right", "top-left", "bottom-right", "bottom-left", "top-center", "bottom-center",
4
+ ]);
5
+ const VALID_POPOVER_PLACEMENTS = new Set([
6
+ "top", "bottom", "left", "right", "top-start", "bottom-start",
7
+ ]);
8
+ const VALID_DRAWER_SIDES = new Set(["right", "left", "bottom"]);
9
+ function warn(component, message) {
10
+ console.warn(`[Dalila] ${component}: ${message}`);
11
+ }
12
+ function warnInvalidType(component, prop, expected, got) {
13
+ warn(component, `"${prop}" expected ${expected}, got ${typeof got} (${String(got)})`);
14
+ }
15
+ export function validateDialogOptions(opts) {
16
+ if (opts.closeOnBackdrop !== undefined && typeof opts.closeOnBackdrop !== "boolean") {
17
+ warnInvalidType("createDialog", "closeOnBackdrop", "boolean", opts.closeOnBackdrop);
18
+ }
19
+ if (opts.closeOnEscape !== undefined && typeof opts.closeOnEscape !== "boolean") {
20
+ warnInvalidType("createDialog", "closeOnEscape", "boolean", opts.closeOnEscape);
21
+ }
22
+ }
23
+ export function validateDrawerOptions(opts) {
24
+ validateDialogOptions(opts);
25
+ if (opts.side !== undefined && !VALID_DRAWER_SIDES.has(opts.side)) {
26
+ warn("createDrawer", `"side" must be one of "right"|"left"|"bottom", got "${String(opts.side)}"`);
27
+ }
28
+ }
29
+ export function validateToastOptions(opts) {
30
+ if (opts.position !== undefined && !VALID_TOAST_POSITIONS.has(opts.position)) {
31
+ warn("createToast", `"position" must be a valid ToastPosition, got "${String(opts.position)}"`);
32
+ }
33
+ if (opts.duration !== undefined && (typeof opts.duration !== "number" || opts.duration < 0)) {
34
+ warn("createToast", `"duration" must be a number >= 0, got ${String(opts.duration)}`);
35
+ }
36
+ if (opts.maxToasts !== undefined && (typeof opts.maxToasts !== "number" || opts.maxToasts <= 0)) {
37
+ warn("createToast", `"maxToasts" must be a number > 0, got ${String(opts.maxToasts)}`);
38
+ }
39
+ }
40
+ export function validatePopoverOptions(opts) {
41
+ if (opts.placement !== undefined && !VALID_POPOVER_PLACEMENTS.has(opts.placement)) {
42
+ warn("createPopover", `"placement" must be a valid PopoverPlacement, got "${String(opts.placement)}"`);
43
+ }
44
+ if (opts.gap !== undefined && (typeof opts.gap !== "number" || opts.gap < 0)) {
45
+ warn("createPopover", `"gap" must be a number >= 0, got ${String(opts.gap)}`);
46
+ }
47
+ if (opts.viewportPadding !== undefined && (typeof opts.viewportPadding !== "number" || opts.viewportPadding < 0)) {
48
+ warn("createPopover", `"viewportPadding" must be a number >= 0, got ${String(opts.viewportPadding)}`);
49
+ }
50
+ }
51
+ export function validateDropzoneOptions(opts) {
52
+ if (opts.maxFiles !== undefined && (typeof opts.maxFiles !== "number" || opts.maxFiles <= 0)) {
53
+ warn("createDropzone", `"maxFiles" must be a number > 0, got ${String(opts.maxFiles)}`);
54
+ }
55
+ if (opts.maxSize !== undefined && (typeof opts.maxSize !== "number" || opts.maxSize <= 0)) {
56
+ warn("createDropzone", `"maxSize" must be a number > 0, got ${String(opts.maxSize)}`);
57
+ }
58
+ }
59
+ export function validateCalendarOptions(opts) {
60
+ const min = opts.min;
61
+ const max = opts.max;
62
+ if (min && max && min > max) {
63
+ warn("createCalendar", `"min" must be before "max"`);
64
+ }
65
+ }
66
+ export function validateComboboxOptions(opts) {
67
+ const options = opts.options;
68
+ if (!Array.isArray(options) || options.length === 0) {
69
+ warn("createCombobox", `"options" must be a non-empty array`);
70
+ }
71
+ }
@@ -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;