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.
- package/package.json +1 -1
- package/template/src/style.css +8 -6
- package/template/src/components/ui/accordion/index.ts +0 -121
- package/template/src/components/ui/calendar/index.ts +0 -157
- package/template/src/components/ui/combobox/index.ts +0 -181
- package/template/src/components/ui/dialog/index.ts +0 -68
- package/template/src/components/ui/drawer/index.ts +0 -53
- package/template/src/components/ui/dropdown/index.ts +0 -57
- package/template/src/components/ui/dropzone/index.ts +0 -114
- package/template/src/components/ui/env.ts +0 -4
- package/template/src/components/ui/index.ts +0 -13
- package/template/src/components/ui/popover/index.ts +0 -185
- package/template/src/components/ui/runtime.ts +0 -514
- package/template/src/components/ui/tabs/index.ts +0 -128
- package/template/src/components/ui/toast/index.ts +0 -144
- package/template/src/components/ui/ui-types.ts +0 -238
- package/template/src/components/ui/validate.ts +0 -83
|
@@ -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,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
|
-
}
|