create-dalila 1.2.0 → 1.2.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.
@@ -1,114 +0,0 @@
1
- import { signal } from "../../../core/signal.js";
2
- import { getCurrentScope } from "../../../core/scope.js";
3
- import type { Dropzone, DropzoneOptions } from "../ui-types.js";
4
- import { validateDropzoneOptions } from "../validate.js";
5
-
6
- export function createDropzone(options: DropzoneOptions = {}): Dropzone {
7
- validateDropzoneOptions(options as Record<string, unknown>);
8
- const { accept, multiple = true, maxFiles, maxSize } = options;
9
-
10
- const dragging = signal(false);
11
- const files = signal<File[]>([]);
12
-
13
- let inputEl: HTMLInputElement | null = null;
14
-
15
- const filterFiles = (fileList: File[]): File[] => {
16
- let result = fileList;
17
-
18
- if (accept) {
19
- const types = accept.split(",").map((t) => t.trim().toLowerCase());
20
- result = result.filter((f) => {
21
- const ext = "." + f.name.split(".").pop()?.toLowerCase();
22
- const mime = f.type.toLowerCase();
23
- return types.some(
24
- (t) => t === ext || t === mime || (t.endsWith("/*") && mime.startsWith(t.slice(0, -1)))
25
- );
26
- });
27
- }
28
-
29
- if (maxSize) {
30
- result = result.filter((f) => f.size <= maxSize);
31
- }
32
-
33
- if (maxFiles) {
34
- result = result.slice(0, maxFiles);
35
- }
36
-
37
- return result;
38
- };
39
-
40
- const addFiles = (newFiles: File[]) => {
41
- const filtered = filterFiles(newFiles);
42
- if (multiple) {
43
- files.update((current) => {
44
- const combined = [...current, ...filtered];
45
- return maxFiles ? combined.slice(0, maxFiles) : combined;
46
- });
47
- } else {
48
- files.set(filtered.slice(0, 1));
49
- }
50
- };
51
-
52
- const browse = () => {
53
- inputEl?.click();
54
- };
55
-
56
- const handleClick = () => browse();
57
-
58
- const handleDragover = (ev: DragEvent) => {
59
- ev.preventDefault();
60
- dragging.set(true);
61
- };
62
-
63
- const handleDragleave = () => {
64
- dragging.set(false);
65
- };
66
-
67
- const handleDrop = (ev: DragEvent) => {
68
- ev.preventDefault();
69
- dragging.set(false);
70
- if (ev.dataTransfer?.files) {
71
- addFiles(Array.from(ev.dataTransfer.files));
72
- }
73
- };
74
-
75
- const _attachTo = (el: HTMLElement) => {
76
- const scope = getCurrentScope();
77
-
78
- // ARIA
79
- el.setAttribute("role", "button");
80
- el.setAttribute("tabindex", "0");
81
-
82
- inputEl = el.querySelector<HTMLInputElement>('input[type="file"]');
83
-
84
- if (inputEl) {
85
- if (accept) inputEl.accept = accept;
86
- inputEl.multiple = multiple;
87
-
88
- const onInputChange = () => {
89
- if (inputEl?.files) {
90
- addFiles(Array.from(inputEl.files));
91
- inputEl.value = "";
92
- }
93
- };
94
- inputEl.addEventListener("change", onInputChange);
95
-
96
- if (scope) {
97
- scope.onCleanup(() => {
98
- inputEl?.removeEventListener("change", onInputChange);
99
- });
100
- }
101
- }
102
- };
103
-
104
- return {
105
- dragging,
106
- files,
107
- browse,
108
- handleClick,
109
- handleDragover,
110
- handleDragleave,
111
- handleDrop,
112
- _attachTo,
113
- };
114
- }
@@ -1,4 +0,0 @@
1
- // ── Environment Detection ───────────────────────────────────────────
2
-
3
- export const isBrowser =
4
- typeof window !== "undefined" && typeof document !== "undefined";
@@ -1,13 +0,0 @@
1
- export * from "./ui-types.js";
2
- export { createDialog, _attachDialogBehavior } from "./dialog/index.js";
3
- export { createDrawer } from "./drawer/index.js";
4
- export { createToast, toastIcon } from "./toast/index.js";
5
- export { createTabs, tabBindings } from "./tabs/index.js";
6
- export { createDropdown } from "./dropdown/index.js";
7
- export { createCombobox } from "./combobox/index.js";
8
- export { createAccordion } from "./accordion/index.js";
9
- export { createCalendar } from "./calendar/index.js";
10
- export { createDropzone } from "./dropzone/index.js";
11
- export { createPopover } from "./popover/index.js";
12
- export { mountUI } from "./runtime.js";
13
- export type { MountUIOptions } from "./runtime.js";
@@ -1,185 +0,0 @@
1
- import { signal } from "../../../core/signal.js";
2
- import { getCurrentScope } from "../../../core/scope.js";
3
- import type { Popover, PopoverOptions, PopoverPlacement } from "../ui-types.js";
4
- import { validatePopoverOptions } from "../validate.js";
5
- import { isBrowser } from "../env.js";
6
-
7
- let popoverUid = 0;
8
-
9
- function computePosition(
10
- trigger: HTMLElement,
11
- popoverEl: HTMLElement,
12
- placement: PopoverPlacement,
13
- gap: number,
14
- viewportPadding: number
15
- ): { top: number; left: number } {
16
- const triggerRect = trigger.getBoundingClientRect();
17
- const popRect = popoverEl.getBoundingClientRect();
18
- const vw = isBrowser ? window.innerWidth : 1024;
19
- const vh = isBrowser ? window.innerHeight : 768;
20
-
21
- // ── Flip when not enough space ──
22
- let effective = placement as string;
23
-
24
- if (effective.startsWith("bottom") && triggerRect.bottom + gap + popRect.height > vh - viewportPadding) {
25
- if (triggerRect.top - gap - popRect.height >= viewportPadding) {
26
- effective = effective.replace("bottom", "top");
27
- }
28
- } else if (effective.startsWith("top") && triggerRect.top - gap - popRect.height < viewportPadding) {
29
- if (triggerRect.bottom + gap + popRect.height <= vh - viewportPadding) {
30
- effective = effective.replace("top", "bottom");
31
- }
32
- } else if (effective === "left" && triggerRect.left - gap - popRect.width < viewportPadding) {
33
- if (triggerRect.right + gap + popRect.width <= vw - viewportPadding) {
34
- effective = "right";
35
- }
36
- } else if (effective === "right" && triggerRect.right + gap + popRect.width > vw - viewportPadding) {
37
- if (triggerRect.left - gap - popRect.width >= viewportPadding) {
38
- effective = "left";
39
- }
40
- }
41
-
42
- let top = 0;
43
- let left = 0;
44
-
45
- switch (effective) {
46
- case "bottom":
47
- top = triggerRect.bottom + gap;
48
- left = triggerRect.left + (triggerRect.width - popRect.width) / 2;
49
- break;
50
- case "bottom-start":
51
- top = triggerRect.bottom + gap;
52
- left = triggerRect.left;
53
- break;
54
- case "top":
55
- top = triggerRect.top - popRect.height - gap;
56
- left = triggerRect.left + (triggerRect.width - popRect.width) / 2;
57
- break;
58
- case "top-start":
59
- top = triggerRect.top - popRect.height - gap;
60
- left = triggerRect.left;
61
- break;
62
- case "left":
63
- top = triggerRect.top + (triggerRect.height - popRect.height) / 2;
64
- left = triggerRect.left - popRect.width - gap;
65
- break;
66
- case "right":
67
- top = triggerRect.top + (triggerRect.height - popRect.height) / 2;
68
- left = triggerRect.right + gap;
69
- break;
70
- }
71
-
72
- // Viewport clamping
73
- const maxLeft = vw - popRect.width - viewportPadding;
74
- left = Math.min(Math.max(viewportPadding, left), Math.max(viewportPadding, maxLeft));
75
-
76
- const maxTop = vh - popRect.height - viewportPadding;
77
- top = Math.min(Math.max(viewportPadding, top), Math.max(viewportPadding, maxTop));
78
-
79
- return { top, left };
80
- }
81
-
82
- export function createPopover(options: PopoverOptions = {}): Popover {
83
- validatePopoverOptions(options as Record<string, unknown>);
84
- const {
85
- placement: initialPlacement = "bottom",
86
- gap = 8,
87
- viewportPadding = 12,
88
- } = options;
89
-
90
- const open = signal(false);
91
- const placement = signal<PopoverPlacement>(initialPlacement);
92
-
93
- const show = () => open.set(true);
94
- const hide = () => open.set(false);
95
- const toggle = () => open.update((v) => !v);
96
-
97
- const position = (trigger: HTMLElement, popoverEl: HTMLElement) => {
98
- const { top, left } = computePosition(
99
- trigger,
100
- popoverEl,
101
- placement(),
102
- gap,
103
- viewportPadding
104
- );
105
- popoverEl.style.position = "fixed";
106
- popoverEl.style.top = `${top}px`;
107
- popoverEl.style.left = `${left}px`;
108
- };
109
-
110
- const _attachTo = (trigger: HTMLElement, popoverEl: HTMLElement) => {
111
- const scope = getCurrentScope();
112
-
113
- // Ensure popover attribute for native API
114
- if (!popoverEl.hasAttribute("popover")) {
115
- popoverEl.setAttribute("popover", "manual");
116
- }
117
-
118
- const reposition = () => {
119
- try {
120
- if (!popoverEl.matches(":popover-open")) return;
121
- } catch {
122
- return;
123
- }
124
- position(trigger, popoverEl);
125
- };
126
-
127
- // Sync open signal → native popover
128
- const unsub = open.on((isOpen) => {
129
- try {
130
- const isNativeOpen = popoverEl.matches(":popover-open");
131
- if (isOpen && !isNativeOpen) {
132
- popoverEl.showPopover();
133
- reposition();
134
- } else if (!isOpen && isNativeOpen) {
135
- popoverEl.hidePopover();
136
- }
137
- } catch {
138
- // Popover API not supported or element not connected
139
- }
140
- });
141
-
142
- // Sync native toggle → signal
143
- const onToggle = (ev: Event) => {
144
- const state = (ev as ToggleEvent).newState;
145
- if (state === "open") {
146
- if (!open.peek()) open.set(true);
147
- reposition();
148
- } else {
149
- if (open.peek()) open.set(false);
150
- }
151
- };
152
- popoverEl.addEventListener("toggle", onToggle);
153
-
154
- // Reposition on scroll/resize
155
- if (isBrowser) {
156
- window.addEventListener("resize", reposition);
157
- window.addEventListener("scroll", reposition, { passive: true });
158
- }
159
-
160
- // ARIA
161
- const popoverId = popoverEl.id || `d-popover-${++popoverUid}`;
162
- if (!popoverEl.id) popoverEl.id = popoverId;
163
- trigger.setAttribute("aria-controls", popoverId);
164
- trigger.setAttribute("aria-expanded", "false");
165
- trigger.setAttribute("aria-haspopup", "true");
166
-
167
- const unsubAria = open.on((isOpen) => {
168
- trigger.setAttribute("aria-expanded", String(isOpen));
169
- });
170
-
171
- if (scope) {
172
- scope.onCleanup(() => {
173
- unsub();
174
- unsubAria();
175
- popoverEl.removeEventListener("toggle", onToggle);
176
- if (isBrowser) {
177
- window.removeEventListener("resize", reposition);
178
- window.removeEventListener("scroll", reposition);
179
- }
180
- });
181
- }
182
- };
183
-
184
- return { open, show, hide, toggle, placement, position, _attachTo };
185
- }