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.
- package/README.md +47 -0
- package/dist/componentes/ui/accordion/index.d.ts +2 -0
- package/dist/componentes/ui/accordion/index.js +114 -0
- package/dist/componentes/ui/calendar/index.d.ts +2 -0
- package/dist/componentes/ui/calendar/index.js +132 -0
- package/dist/componentes/ui/combobox/index.d.ts +2 -0
- package/dist/componentes/ui/combobox/index.js +161 -0
- package/dist/componentes/ui/dialog/index.d.ts +10 -0
- package/dist/componentes/ui/dialog/index.js +54 -0
- package/dist/componentes/ui/drawer/index.d.ts +2 -0
- package/dist/componentes/ui/drawer/index.js +41 -0
- package/dist/componentes/ui/dropdown/index.d.ts +2 -0
- package/dist/componentes/ui/dropdown/index.js +48 -0
- package/dist/componentes/ui/dropzone/index.d.ts +2 -0
- package/dist/componentes/ui/dropzone/index.js +92 -0
- package/dist/componentes/ui/env.d.ts +1 -0
- package/dist/componentes/ui/env.js +2 -0
- package/dist/componentes/ui/index.d.ts +13 -0
- package/dist/componentes/ui/index.js +12 -0
- package/dist/componentes/ui/popover/index.d.ts +2 -0
- package/dist/componentes/ui/popover/index.js +156 -0
- package/dist/componentes/ui/runtime.d.ts +20 -0
- package/dist/componentes/ui/runtime.js +421 -0
- package/dist/componentes/ui/tabs/index.d.ts +3 -0
- package/dist/componentes/ui/tabs/index.js +101 -0
- package/dist/componentes/ui/toast/index.d.ts +3 -0
- package/dist/componentes/ui/toast/index.js +115 -0
- package/dist/componentes/ui/ui-types.d.ts +175 -0
- package/dist/componentes/ui/ui-types.js +1 -0
- package/dist/componentes/ui/validate.d.ts +7 -0
- package/dist/componentes/ui/validate.js +71 -0
- package/dist/components/ui/accordion/index.d.ts +2 -0
- package/dist/components/ui/accordion/index.js +114 -0
- package/dist/components/ui/calendar/index.d.ts +2 -0
- package/dist/components/ui/calendar/index.js +132 -0
- package/dist/components/ui/combobox/index.d.ts +2 -0
- package/dist/components/ui/combobox/index.js +161 -0
- package/dist/components/ui/dialog/index.d.ts +10 -0
- package/dist/components/ui/dialog/index.js +54 -0
- package/dist/components/ui/drawer/index.d.ts +2 -0
- package/dist/components/ui/drawer/index.js +41 -0
- package/dist/components/ui/dropdown/index.d.ts +2 -0
- package/dist/components/ui/dropdown/index.js +48 -0
- package/dist/components/ui/dropzone/index.d.ts +2 -0
- package/dist/components/ui/dropzone/index.js +92 -0
- package/dist/components/ui/env.d.ts +1 -0
- package/dist/components/ui/env.js +2 -0
- package/dist/components/ui/index.d.ts +13 -0
- package/dist/components/ui/index.js +12 -0
- package/dist/components/ui/popover/index.d.ts +2 -0
- package/dist/components/ui/popover/index.js +156 -0
- package/dist/components/ui/runtime.d.ts +20 -0
- package/dist/components/ui/runtime.js +421 -0
- package/dist/components/ui/tabs/index.d.ts +3 -0
- package/dist/components/ui/tabs/index.js +101 -0
- package/dist/components/ui/toast/index.d.ts +3 -0
- package/dist/components/ui/toast/index.js +115 -0
- package/dist/components/ui/ui-types.d.ts +175 -0
- package/dist/components/ui/ui-types.js +1 -0
- package/dist/components/ui/validate.d.ts +7 -0
- package/dist/components/ui/validate.js +71 -0
- package/dist/form/form-types.d.ts +181 -0
- package/dist/form/form-types.js +4 -0
- package/dist/form/form.d.ts +71 -0
- package/dist/form/form.js +1073 -0
- package/dist/form/index.d.ts +2 -0
- package/dist/form/index.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/runtime/bind.js +567 -9
- package/dist/ui/accordion.d.ts +2 -0
- package/dist/ui/accordion.js +114 -0
- package/dist/ui/calendar.d.ts +2 -0
- package/dist/ui/calendar.js +132 -0
- package/dist/ui/combobox.d.ts +2 -0
- package/dist/ui/combobox.js +161 -0
- package/dist/ui/dialog.d.ts +10 -0
- package/dist/ui/dialog.js +54 -0
- package/dist/ui/drawer.d.ts +2 -0
- package/dist/ui/drawer.js +41 -0
- package/dist/ui/dropdown.d.ts +2 -0
- package/dist/ui/dropdown.js +48 -0
- package/dist/ui/dropzone.d.ts +2 -0
- package/dist/ui/dropzone.js +92 -0
- package/dist/ui/env.d.ts +1 -0
- package/dist/ui/env.js +2 -0
- package/dist/ui/index.d.ts +13 -0
- package/dist/ui/index.js +12 -0
- package/dist/ui/popover.d.ts +2 -0
- package/dist/ui/popover.js +156 -0
- package/dist/ui/runtime.d.ts +20 -0
- package/dist/ui/runtime.js +421 -0
- package/dist/ui/tabs.d.ts +3 -0
- package/dist/ui/tabs.js +101 -0
- package/dist/ui/toast.d.ts +3 -0
- package/dist/ui/toast.js +115 -0
- package/dist/ui/ui-types.d.ts +175 -0
- package/dist/ui/ui-types.js +1 -0
- package/dist/ui/validate.d.ts +7 -0
- package/dist/ui/validate.js +71 -0
- package/package.json +60 -2
- package/src/components/ui/accordion/accordion.css +90 -0
- package/src/components/ui/alert/alert.css +78 -0
- package/src/components/ui/avatar/avatar.css +45 -0
- package/src/components/ui/badge/badge.css +71 -0
- package/src/components/ui/breadcrumb/breadcrumb.css +41 -0
- package/src/components/ui/button/button.css +135 -0
- package/src/components/ui/calendar/calendar.css +96 -0
- package/src/components/ui/card/card.css +93 -0
- package/src/components/ui/checkbox/checkbox.css +57 -0
- package/src/components/ui/chip/chip.css +62 -0
- package/src/components/ui/collapsible/collapsible.css +61 -0
- package/src/components/ui/combobox/combobox.css +85 -0
- package/src/components/ui/dalila/dalila.css +42 -0
- package/src/components/ui/dalila-core/dalila-core.css +14 -0
- package/src/components/ui/dialog/dialog.css +125 -0
- package/src/components/ui/drawer/drawer.css +122 -0
- package/src/components/ui/dropdown/dropdown.css +87 -0
- package/src/components/ui/dropzone/dropzone.css +47 -0
- package/src/components/ui/empty-state/empty-state.css +33 -0
- package/src/components/ui/form/form.css +44 -0
- package/src/components/ui/input/input.css +106 -0
- package/src/components/ui/layout/layout.css +62 -0
- package/src/components/ui/pagination/pagination.css +55 -0
- package/src/components/ui/popover/popover.css +55 -0
- package/src/components/ui/radio/radio.css +56 -0
- package/src/components/ui/separator/separator.css +38 -0
- package/src/components/ui/skeleton/skeleton.css +57 -0
- package/src/components/ui/slider/slider.css +60 -0
- package/src/components/ui/spinner/spinner.css +38 -0
- package/src/components/ui/table/table.css +54 -0
- package/src/components/ui/tabs/tabs.css +74 -0
- package/src/components/ui/toast/toast.css +100 -0
- package/src/components/ui/toggle/toggle.css +90 -0
- package/src/components/ui/tokens/tokens.css +161 -0
- package/src/components/ui/tooltip/tooltip.css +53 -0
- 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,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type utilities for path-based access
|
|
3
|
+
*/
|
|
4
|
+
export type FieldErrors = Record<string, string>;
|
|
5
|
+
export interface FormSubmitContext {
|
|
6
|
+
signal: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export interface FormOptions<T> {
|
|
9
|
+
/**
|
|
10
|
+
* Default values for the form.
|
|
11
|
+
* Can be a static object, a function returning values, or a promise.
|
|
12
|
+
*/
|
|
13
|
+
defaultValues?: Partial<T> | (() => Partial<T>) | (() => Promise<Partial<T>>);
|
|
14
|
+
/**
|
|
15
|
+
* Custom parser for FormData → T.
|
|
16
|
+
* If not provided, uses the built-in parser with dot/bracket notation.
|
|
17
|
+
*/
|
|
18
|
+
parse?: (formEl: HTMLFormElement, fd: FormData) => T;
|
|
19
|
+
/**
|
|
20
|
+
* Client-side validation function.
|
|
21
|
+
* Returns { fieldErrors?, formError? } or just fieldErrors.
|
|
22
|
+
*/
|
|
23
|
+
validate?: (data: T) => FieldErrors | {
|
|
24
|
+
fieldErrors?: FieldErrors;
|
|
25
|
+
formError?: string;
|
|
26
|
+
} | void;
|
|
27
|
+
/**
|
|
28
|
+
* When to run validation:
|
|
29
|
+
* - "submit" (default): only on submit
|
|
30
|
+
* - "blur": on field blur after first submit
|
|
31
|
+
* - "change": on every change after first submit
|
|
32
|
+
*/
|
|
33
|
+
validateOn?: 'submit' | 'blur' | 'change';
|
|
34
|
+
/**
|
|
35
|
+
* Transform server errors into form errors.
|
|
36
|
+
* Useful for mapping backend error formats to field paths.
|
|
37
|
+
*/
|
|
38
|
+
transformServerErrors?: (error: unknown) => {
|
|
39
|
+
fieldErrors?: FieldErrors;
|
|
40
|
+
formError?: string;
|
|
41
|
+
} | void;
|
|
42
|
+
}
|
|
43
|
+
export interface Form<T> {
|
|
44
|
+
/**
|
|
45
|
+
* Creates a submit handler that:
|
|
46
|
+
* - prevents default
|
|
47
|
+
* - collects FormData
|
|
48
|
+
* - parses to T
|
|
49
|
+
* - validates (if configured)
|
|
50
|
+
* - calls handler with data and AbortSignal
|
|
51
|
+
* - cancels previous submit if re-submitted
|
|
52
|
+
*/
|
|
53
|
+
handleSubmit(handler: (data: T, ctx: FormSubmitContext) => Promise<unknown> | unknown): (ev: SubmitEvent) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Reset form to initial/new defaults
|
|
56
|
+
*/
|
|
57
|
+
reset(nextDefaults?: Partial<T>): void;
|
|
58
|
+
/**
|
|
59
|
+
* Set error for a specific field
|
|
60
|
+
*/
|
|
61
|
+
setError(path: string, message: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* Set form-level error
|
|
64
|
+
*/
|
|
65
|
+
setFormError(message: string): void;
|
|
66
|
+
/**
|
|
67
|
+
* Clear errors (all or by prefix)
|
|
68
|
+
*/
|
|
69
|
+
clearErrors(prefix?: string): void;
|
|
70
|
+
/**
|
|
71
|
+
* Get error message for a field
|
|
72
|
+
*/
|
|
73
|
+
error(path: string): string | null;
|
|
74
|
+
/**
|
|
75
|
+
* Get form-level error
|
|
76
|
+
*/
|
|
77
|
+
formError(): string | null;
|
|
78
|
+
/**
|
|
79
|
+
* Check if field has been touched
|
|
80
|
+
*/
|
|
81
|
+
touched(path: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Check if field is dirty (value differs from default)
|
|
84
|
+
*/
|
|
85
|
+
dirty(path: string): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Check if form is currently submitting
|
|
88
|
+
*/
|
|
89
|
+
submitting(): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Get submit count
|
|
92
|
+
*/
|
|
93
|
+
submitCount(): number;
|
|
94
|
+
/**
|
|
95
|
+
* Focus first error field (or specific field)
|
|
96
|
+
*/
|
|
97
|
+
focus(path?: string): void;
|
|
98
|
+
/**
|
|
99
|
+
* Internal: register a field element
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
_registerField(path: string, element: HTMLElement): () => void;
|
|
103
|
+
/**
|
|
104
|
+
* Internal: get form element
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
_getFormElement(): HTMLFormElement | null;
|
|
108
|
+
/**
|
|
109
|
+
* Internal: set form element
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
_setFormElement(form: HTMLFormElement): void;
|
|
113
|
+
/**
|
|
114
|
+
* Create or get a field array
|
|
115
|
+
*/
|
|
116
|
+
fieldArray<TItem = unknown>(path: string): FieldArray<TItem>;
|
|
117
|
+
}
|
|
118
|
+
export interface FieldArrayItem<T = unknown> {
|
|
119
|
+
key: string;
|
|
120
|
+
value?: T;
|
|
121
|
+
}
|
|
122
|
+
export interface FieldArray<TItem = unknown> {
|
|
123
|
+
/**
|
|
124
|
+
* Get array of items with stable keys
|
|
125
|
+
*/
|
|
126
|
+
fields(): FieldArrayItem<TItem>[];
|
|
127
|
+
/**
|
|
128
|
+
* Append item(s) to the end
|
|
129
|
+
*/
|
|
130
|
+
append(value: TItem | TItem[]): void;
|
|
131
|
+
/**
|
|
132
|
+
* Remove item by key
|
|
133
|
+
*/
|
|
134
|
+
remove(key: string): void;
|
|
135
|
+
/**
|
|
136
|
+
* Remove item by index
|
|
137
|
+
*/
|
|
138
|
+
removeAt(index: number): void;
|
|
139
|
+
/**
|
|
140
|
+
* Insert item at index
|
|
141
|
+
*/
|
|
142
|
+
insert(index: number, value: TItem): void;
|
|
143
|
+
/**
|
|
144
|
+
* Move item from one index to another
|
|
145
|
+
*/
|
|
146
|
+
move(fromIndex: number, toIndex: number): void;
|
|
147
|
+
/**
|
|
148
|
+
* Swap two items by index
|
|
149
|
+
*/
|
|
150
|
+
swap(indexA: number, indexB: number): void;
|
|
151
|
+
/**
|
|
152
|
+
* Replace entire array
|
|
153
|
+
*/
|
|
154
|
+
replace(values: TItem[]): void;
|
|
155
|
+
/**
|
|
156
|
+
* Update a specific item by key
|
|
157
|
+
*/
|
|
158
|
+
update(key: string, value: TItem): void;
|
|
159
|
+
/**
|
|
160
|
+
* Update a specific item by index
|
|
161
|
+
*/
|
|
162
|
+
updateAt(index: number, value: TItem): void;
|
|
163
|
+
/**
|
|
164
|
+
* Clear all items
|
|
165
|
+
*/
|
|
166
|
+
clear(): void;
|
|
167
|
+
/**
|
|
168
|
+
* Get current length
|
|
169
|
+
*/
|
|
170
|
+
length(): number;
|
|
171
|
+
/**
|
|
172
|
+
* Internal: translate index-based path to key-based path
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
_translatePath(path: string): string | null;
|
|
176
|
+
/**
|
|
177
|
+
* Internal: get current index for a key
|
|
178
|
+
* @internal
|
|
179
|
+
*/
|
|
180
|
+
_getIndex(key: string): number;
|
|
181
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dalila Forms - DOM-first reactive form management
|
|
3
|
+
*
|
|
4
|
+
* Design principles:
|
|
5
|
+
* - Values live in the DOM (uncontrolled by default)
|
|
6
|
+
* - Meta-state in memory (errors, touched, dirty, submitting)
|
|
7
|
+
* - Declarative HTML via directives
|
|
8
|
+
* - Scope-safe with automatic cleanup
|
|
9
|
+
* - Race-safe submits with AbortController
|
|
10
|
+
* - Field arrays with stable keys
|
|
11
|
+
*/
|
|
12
|
+
import type { Form, FormOptions } from './form-types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Symbol to mark handlers that have been wrapped by handleSubmit().
|
|
15
|
+
* Used by bindForm to avoid double-wrapping.
|
|
16
|
+
*/
|
|
17
|
+
export declare const WRAPPED_HANDLER: unique symbol;
|
|
18
|
+
/**
|
|
19
|
+
* Parse FormData into a nested object structure.
|
|
20
|
+
*
|
|
21
|
+
* Supports:
|
|
22
|
+
* - Simple fields: "email" → { email: "..." }
|
|
23
|
+
* - Nested objects: "user.name" → { user: { name: "..." } }
|
|
24
|
+
* - Arrays: "phones[0].number" → { phones: [{ number: "..." }] }
|
|
25
|
+
* - Checkboxes: single = boolean, multiple = array of values
|
|
26
|
+
* - Select multiple: array of selected values
|
|
27
|
+
* - Radio: single value
|
|
28
|
+
* - Files: File object
|
|
29
|
+
*
|
|
30
|
+
* ## Checkbox Parsing Contract
|
|
31
|
+
*
|
|
32
|
+
* HTML FormData omits unchecked checkboxes entirely. To resolve this ambiguity,
|
|
33
|
+
* parseFormData() inspects the DOM to distinguish between "field missing" vs "checkbox unchecked".
|
|
34
|
+
*
|
|
35
|
+
* ### Single Checkbox (one input with unique name)
|
|
36
|
+
* When there is exactly ONE checkbox with a given name:
|
|
37
|
+
* - Checked (with or without value) → `true`
|
|
38
|
+
* - Unchecked → `false`
|
|
39
|
+
* - Value attribute is ignored (always returns boolean)
|
|
40
|
+
*
|
|
41
|
+
* Example:
|
|
42
|
+
* ```html
|
|
43
|
+
* <input type="checkbox" name="agree" />
|
|
44
|
+
* ```
|
|
45
|
+
* Result: `{ agree: false }` (unchecked) or `{ agree: true }` (checked)
|
|
46
|
+
*
|
|
47
|
+
* ### Multiple Checkboxes (same name, multiple inputs)
|
|
48
|
+
* When there are MULTIPLE checkboxes with the same name:
|
|
49
|
+
* - Result is ALWAYS an array
|
|
50
|
+
* - Some checked → `["value1", "value2"]`
|
|
51
|
+
* - None checked → `[]`
|
|
52
|
+
* - One checked → `["value1"]` (still an array!)
|
|
53
|
+
*
|
|
54
|
+
* Example:
|
|
55
|
+
* ```html
|
|
56
|
+
* <input type="checkbox" name="colors" value="red" checked />
|
|
57
|
+
* <input type="checkbox" name="colors" value="blue" />
|
|
58
|
+
* <input type="checkbox" name="colors" value="green" checked />
|
|
59
|
+
* ```
|
|
60
|
+
* Result: `{ colors: ["red", "green"] }`
|
|
61
|
+
*
|
|
62
|
+
* ### Edge Cases
|
|
63
|
+
* - Radio buttons: Unchecked radio → field absent (standard HTML behavior)
|
|
64
|
+
* - Select multiple: Always returns array (like multiple checkboxes)
|
|
65
|
+
*
|
|
66
|
+
* @param form - The form element to parse (used for DOM inspection)
|
|
67
|
+
* @param fd - FormData instance from the form
|
|
68
|
+
* @returns Parsed form data with nested structure
|
|
69
|
+
*/
|
|
70
|
+
export declare function parseFormData<T = unknown>(form: HTMLFormElement, fd: FormData): T;
|
|
71
|
+
export declare function createForm<T = unknown>(options?: FormOptions<T>): Form<T>;
|