elements-kit 0.18.2 → 0.20.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 +14 -0
- package/dist/await.d.mts +53 -0
- package/dist/await.mjs +70 -0
- package/dist/custom-elements.d.mts +1 -1
- package/dist/{element-xQwq4ulc.mjs → element-D3om9yA6.mjs} +71 -4
- package/dist/for.d.mts +20 -26
- package/dist/for.mjs +29 -4
- package/dist/fragment-ROPRBJQd.mjs +61 -0
- package/dist/hydrate/index.d.mts +41 -0
- package/dist/hydrate/index.mjs +2 -0
- package/dist/hydrate-6Qx4n4Su.mjs +380 -0
- package/dist/integrations/astro-client.d.mts +17 -0
- package/dist/integrations/astro-client.mjs +23 -0
- package/dist/integrations/astro-server.d.mts +15 -0
- package/dist/integrations/astro-server.mjs +39 -0
- package/dist/integrations/astro-slots.d.mts +12 -0
- package/dist/integrations/astro-slots.mjs +30 -0
- package/dist/integrations/astro.d.mts +46 -0
- package/dist/integrations/astro.mjs +64 -0
- package/dist/integrations/react.d.mts +1 -1
- package/dist/integrations/react.mjs +2 -2
- package/dist/jsx-runtime/index.d.mts +1 -1
- package/dist/jsx-runtime/index.mjs +2 -20
- package/dist/{lib-Dkc7cV5F.mjs → lib-DYypKhxk.mjs} +63 -5
- package/dist/render.mjs +1 -1
- package/dist/{scope-CnEvL9Nd.mjs → scope-zbEG299F.mjs} +1 -1
- package/dist/server/index.d.mts +43 -0
- package/dist/server/index.mjs +2 -0
- package/dist/server-iFSng50i.mjs +385 -0
- package/dist/signals/index.d.mts +2 -2
- package/dist/signals/index.mjs +5 -3
- package/dist/{slot-B8y0aEoz.d.mts → slot-BFWXeFRl.d.mts} +6 -0
- package/dist/{slot-Cne__au7.mjs → slot-Dvf30O0B.mjs} +12 -1
- package/dist/slot.d.mts +1 -1
- package/dist/slot.mjs +1 -1
- package/dist/{infer-B7T5hi_D.d.mts → types-ncUK9rki.d.mts} +140 -141
- package/dist/ui/overlay/index.css +70 -0
- package/dist/ui/overlay/index.d.mts +209 -0
- package/dist/ui/overlay/index.mjs +1001 -0
- package/dist/ui/overlay/overlay.css +332 -0
- package/dist/utilities/_observe.mjs +1 -1
- package/dist/utilities/active-element.d.mts +1 -1
- package/dist/utilities/active-element.mjs +1 -1
- package/dist/utilities/async.d.mts +19 -2
- package/dist/utilities/async.mjs +49 -2
- package/dist/utilities/context.mjs +1 -1
- package/dist/utilities/debounced.d.mts +1 -1
- package/dist/utilities/debounced.mjs +1 -1
- package/dist/utilities/dom-lifecycle.bench.mjs +3255 -1
- package/dist/utilities/dom-lifecycle.mjs +1 -1
- package/dist/utilities/element-rect.d.mts +1 -1
- package/dist/utilities/element-rect.mjs +1 -1
- package/dist/utilities/element-scroll.d.mts +1 -1
- package/dist/utilities/event-driven.d.mts +1 -1
- package/dist/utilities/event-driven.mjs +1 -1
- package/dist/utilities/event-listener.d.mts +1 -1
- package/dist/utilities/event-listener.mjs +1 -1
- package/dist/utilities/focus-within.d.mts +1 -1
- package/dist/utilities/focus-within.mjs +1 -1
- package/dist/utilities/form-object.d.mts +96 -0
- package/dist/utilities/form-object.mjs +285 -0
- package/dist/utilities/hover.d.mts +1 -1
- package/dist/utilities/hover.mjs +1 -1
- package/dist/utilities/interval.d.mts +1 -1
- package/dist/utilities/interval.mjs +1 -1
- package/dist/utilities/location.d.mts +1 -1
- package/dist/utilities/location.mjs +1 -1
- package/dist/utilities/media-devices.d.mts +1 -1
- package/dist/utilities/media-devices.mjs +1 -1
- package/dist/utilities/media-player.d.mts +1 -1
- package/dist/utilities/media-query.d.mts +1 -1
- package/dist/utilities/media-query.mjs +1 -1
- package/dist/utilities/network.d.mts +1 -1
- package/dist/utilities/network.mjs +1 -1
- package/dist/utilities/orientation.d.mts +1 -1
- package/dist/utilities/orientation.mjs +1 -1
- package/dist/utilities/previous.d.mts +1 -1
- package/dist/utilities/previous.mjs +1 -1
- package/dist/utilities/promise.d.mts +19 -2
- package/dist/utilities/promise.mjs +33 -3
- package/dist/utilities/retry.mjs +1 -1
- package/dist/utilities/routing.d.mts +1 -1
- package/dist/utilities/routing.mjs +1 -1
- package/dist/utilities/search-params.d.mts +1 -1
- package/dist/utilities/storage.d.mts +1 -1
- package/dist/utilities/throttled.d.mts +1 -1
- package/dist/utilities/throttled.mjs +1 -1
- package/dist/utilities/timeout.d.mts +1 -1
- package/dist/utilities/timeout.mjs +1 -1
- package/dist/utilities/window-focus.d.mts +1 -1
- package/dist/utilities/window-focus.mjs +1 -1
- package/dist/utilities/window-size.d.mts +1 -1
- package/dist/utilities/window-size.mjs +1 -1
- package/package.json +20 -5
- package/dist/benchmark.CX_oY03V-CSLAXL5f.mjs +0 -5145
- package/dist/magic-string.es-CXefOmkB.mjs +0 -1011
- package/dist/test.BmQO5GaM-Ddz9cDxc.mjs +0 -8266
- package/dist/utilities/active-element.test.d.mts +0 -1
- package/dist/utilities/active-element.test.mjs +0 -25
- package/dist/utilities/async.test.d.mts +0 -1
- package/dist/utilities/async.test.mjs +0 -275
- package/dist/utilities/context.test.d.mts +0 -1
- package/dist/utilities/context.test.mjs +0 -171
- package/dist/utilities/debounced.test.d.mts +0 -1
- package/dist/utilities/debounced.test.mjs +0 -68
- package/dist/utilities/dom-lifecycle.test.d.mts +0 -1
- package/dist/utilities/dom-lifecycle.test.mjs +0 -228
- package/dist/utilities/element-rect.test.d.mts +0 -1
- package/dist/utilities/element-rect.test.mjs +0 -101
- package/dist/utilities/element-scroll.test.d.mts +0 -1
- package/dist/utilities/element-scroll.test.mjs +0 -27
- package/dist/utilities/event-listener.test.d.mts +0 -1
- package/dist/utilities/event-listener.test.mjs +0 -60
- package/dist/utilities/focus-within.test.d.mts +0 -1
- package/dist/utilities/focus-within.test.mjs +0 -51
- package/dist/utilities/hover.test.d.mts +0 -1
- package/dist/utilities/hover.test.mjs +0 -54
- package/dist/utilities/intersection-observer.test.d.mts +0 -1
- package/dist/utilities/intersection-observer.test.mjs +0 -46
- package/dist/utilities/interval.test.d.mts +0 -1
- package/dist/utilities/interval.test.mjs +0 -115
- package/dist/utilities/location.test.d.mts +0 -1
- package/dist/utilities/location.test.mjs +0 -61
- package/dist/utilities/long-press.test.d.mts +0 -1
- package/dist/utilities/long-press.test.mjs +0 -54
- package/dist/utilities/media-devices.test.d.mts +0 -1
- package/dist/utilities/media-devices.test.mjs +0 -44
- package/dist/utilities/media-player.test.d.mts +0 -1
- package/dist/utilities/media-player.test.mjs +0 -102
- package/dist/utilities/mutation-observer.test.d.mts +0 -1
- package/dist/utilities/mutation-observer.test.mjs +0 -48
- package/dist/utilities/network.test.d.mts +0 -1
- package/dist/utilities/network.test.mjs +0 -30
- package/dist/utilities/on-click-outside.test.d.mts +0 -1
- package/dist/utilities/on-click-outside.test.mjs +0 -45
- package/dist/utilities/previous.test.d.mts +0 -1
- package/dist/utilities/previous.test.mjs +0 -90
- package/dist/utilities/promise.test.d.mts +0 -1
- package/dist/utilities/promise.test.mjs +0 -158
- package/dist/utilities/retry.test.d.mts +0 -1
- package/dist/utilities/retry.test.mjs +0 -85
- package/dist/utilities/routing.test.d.mts +0 -1
- package/dist/utilities/routing.test.mjs +0 -129
- package/dist/utilities/search-params.test.d.mts +0 -1
- package/dist/utilities/search-params.test.mjs +0 -37
- package/dist/utilities/ssr.test.d.mts +0 -1
- package/dist/utilities/ssr.test.mjs +0 -65
- package/dist/utilities/storage.test.d.mts +0 -1
- package/dist/utilities/storage.test.mjs +0 -139
- package/dist/utilities/throttled.test.d.mts +0 -1
- package/dist/utilities/throttled.test.mjs +0 -58
- package/dist/utilities/timeout.test.d.mts +0 -1
- package/dist/utilities/timeout.test.mjs +0 -121
- package/dist/utilities/window-size.test.d.mts +0 -1
- package/dist/utilities/window-size.test.mjs +0 -43
- /package/dist/{custom-elements-D5CgfNKc.d.mts → custom-elements-DK5VUcWA.d.mts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as trigger, _ as onCleanup, l as computed, s as SIGNAL, x as signal } from "../lib-DYypKhxk.mjs";
|
|
2
2
|
import "../signals/index.mjs";
|
|
3
3
|
//#region src/utilities/event-driven.ts
|
|
4
4
|
/**
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//#region src/utilities/form-object.d.ts
|
|
2
|
+
/** Leaf value a single control contributes to the extracted object. */
|
|
3
|
+
type FormObjectValue = string | number | boolean | File | null;
|
|
4
|
+
/** A nested plain object built from a form's dot-notation field names. */
|
|
5
|
+
type FormValues = {
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
/** Named controls FormObject reads from and writes to. */
|
|
9
|
+
type NamedControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
10
|
+
/**
|
|
11
|
+
* One control's contribution, as seen by transforms before the nested object
|
|
12
|
+
* is assembled. `value` is the raw extracted value; `checked` is present only
|
|
13
|
+
* for checkbox / radio controls.
|
|
14
|
+
*/
|
|
15
|
+
interface Field {
|
|
16
|
+
control: NamedControl;
|
|
17
|
+
/** Dot-notation path. Transforms may rewrite it to remap the output key. */
|
|
18
|
+
name: string;
|
|
19
|
+
/** `string | string[] | File | File[] | boolean` depending on control type. */
|
|
20
|
+
value: unknown;
|
|
21
|
+
/** Whether the control is checked — checkbox / radio only. */
|
|
22
|
+
checked?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Per-field transform. Return the (possibly modified) field to keep it, or
|
|
26
|
+
* `null` to drop it from the output. Run in order during {@link FormObject.toObject}.
|
|
27
|
+
*/
|
|
28
|
+
type FormFieldTransform = (field: Field) => Field | null;
|
|
29
|
+
interface FormObjectOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Pipeline applied to each field during `toObject()`. Defaults to
|
|
32
|
+
* {@link defaultTransforms}. Providing this **replaces** the defaults —
|
|
33
|
+
* spread `...defaultTransforms` to extend them.
|
|
34
|
+
*/
|
|
35
|
+
transforms?: FormFieldTransform[];
|
|
36
|
+
}
|
|
37
|
+
/** Drop disabled controls (mirrors native `FormData`). */
|
|
38
|
+
declare const skipDisabled: FormFieldTransform;
|
|
39
|
+
/** Drop submit / reset / button / image controls and `<button>` elements. */
|
|
40
|
+
declare const skipButtons: FormFieldTransform;
|
|
41
|
+
/** Drop fields whose control is an unchecked checkbox / radio. */
|
|
42
|
+
declare const dropUnchecked: FormFieldTransform;
|
|
43
|
+
/**
|
|
44
|
+
* Include unchecked **checkboxes** with the given `value` instead of dropping
|
|
45
|
+
* them. Unchecked **radios** are still dropped (only the selected radio is
|
|
46
|
+
* meaningful). Use this **in place of** {@link dropUnchecked}.
|
|
47
|
+
*
|
|
48
|
+
* @example transforms: [skipDisabled, skipButtons, uncheckedAs(false)]
|
|
49
|
+
*/
|
|
50
|
+
declare function uncheckedAs(value: unknown): FormFieldTransform;
|
|
51
|
+
/** Drop fields whose value is empty: `""`, `null`, or an empty array. */
|
|
52
|
+
declare const skipEmpty: FormFieldTransform;
|
|
53
|
+
/** Default pipeline: skip disabled controls, buttons, and unchecked checkboxes/radios. */
|
|
54
|
+
declare const defaultTransforms: FormFieldTransform[];
|
|
55
|
+
/**
|
|
56
|
+
* Extracts an {@link HTMLFormElement} into a nested plain object (and writes one
|
|
57
|
+
* back) using dot-notation field names — `name="user.address.city"` nests into
|
|
58
|
+
* objects, numeric segments like `name="tags.0"` build arrays. A static snapshot:
|
|
59
|
+
* values are read/written at call time, no reactivity.
|
|
60
|
+
*
|
|
61
|
+
* Extraction runs each field through a composable {@link FormFieldTransform}
|
|
62
|
+
* pipeline ({@link defaultTransforms} by default), which decides inclusion and
|
|
63
|
+
* may rewrite names/values. Mirrors the ergonomics of the native `FormData`
|
|
64
|
+
* constructor.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* import { FormObject, defaultTransforms, uncheckedAs }
|
|
69
|
+
* from "elements-kit/utilities/form-object";
|
|
70
|
+
*
|
|
71
|
+
* // <input name="user.name"> <input name="tags.0"> <input name="tags.1">
|
|
72
|
+
* const data = new FormObject(form).toObject();
|
|
73
|
+
* // => { user: { name: "..." }, tags: ["...", "..."] }
|
|
74
|
+
*
|
|
75
|
+
* // Replace the pipeline — include unchecked checkboxes as false
|
|
76
|
+
* new FormObject(form, {
|
|
77
|
+
* transforms: [...defaultTransforms.slice(0, 2), uncheckedAs(false)],
|
|
78
|
+
* }).toObject();
|
|
79
|
+
*
|
|
80
|
+
* new FormObject(form).fromObject({ user: { name: "Wael" }, tags: ["a", "b"] });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare class FormObject {
|
|
84
|
+
#private;
|
|
85
|
+
constructor(form: HTMLFormElement, options?: FormObjectOptions);
|
|
86
|
+
/** Snapshot the form into a nested object built from dot-notation names. */
|
|
87
|
+
toObject(): FormValues;
|
|
88
|
+
/** Alias for {@link toObject} — lets `JSON.stringify(instance)` work. */
|
|
89
|
+
toJSON(): FormValues;
|
|
90
|
+
/** Write values from a nested object back onto the form's named controls. */
|
|
91
|
+
fromObject(data: FormValues): this;
|
|
92
|
+
/** Reset every named control to its empty/default state. */
|
|
93
|
+
clear(): this;
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { Field, FormFieldTransform, FormObject, FormObjectOptions, FormObjectValue, FormValues, NamedControl, defaultTransforms, dropUnchecked, skipButtons, skipDisabled, skipEmpty, uncheckedAs };
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
//#region src/utilities/form-object.ts
|
|
2
|
+
/** True when the control is a button / submit / reset / image. */
|
|
3
|
+
function isButton(control) {
|
|
4
|
+
if (control instanceof HTMLButtonElement) return true;
|
|
5
|
+
if (control instanceof HTMLInputElement) return control.type === "submit" || control.type === "reset" || control.type === "button" || control.type === "image";
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
/** Drop disabled controls (mirrors native `FormData`). */
|
|
9
|
+
const skipDisabled = (field) => field.control.disabled ? null : field;
|
|
10
|
+
/** Drop submit / reset / button / image controls and `<button>` elements. */
|
|
11
|
+
const skipButtons = (field) => isButton(field.control) ? null : field;
|
|
12
|
+
/** Drop fields whose control is an unchecked checkbox / radio. */
|
|
13
|
+
const dropUnchecked = (field) => field.checked === false ? null : field;
|
|
14
|
+
/**
|
|
15
|
+
* Include unchecked **checkboxes** with the given `value` instead of dropping
|
|
16
|
+
* them. Unchecked **radios** are still dropped (only the selected radio is
|
|
17
|
+
* meaningful). Use this **in place of** {@link dropUnchecked}.
|
|
18
|
+
*
|
|
19
|
+
* @example transforms: [skipDisabled, skipButtons, uncheckedAs(false)]
|
|
20
|
+
*/
|
|
21
|
+
function uncheckedAs(value) {
|
|
22
|
+
return (field) => {
|
|
23
|
+
if (field.checked !== false) return field;
|
|
24
|
+
if (field.control instanceof HTMLInputElement && field.control.type === "radio") return null;
|
|
25
|
+
return {
|
|
26
|
+
...field,
|
|
27
|
+
value,
|
|
28
|
+
checked: void 0
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Drop fields whose value is empty: `""`, `null`, or an empty array. */
|
|
33
|
+
const skipEmpty = (field) => {
|
|
34
|
+
const v = field.value;
|
|
35
|
+
if (v === "" || v === null) return null;
|
|
36
|
+
if (Array.isArray(v) && v.length === 0) return null;
|
|
37
|
+
return field;
|
|
38
|
+
};
|
|
39
|
+
/** Default pipeline: skip disabled controls, buttons, and unchecked checkboxes/radios. */
|
|
40
|
+
const defaultTransforms = [
|
|
41
|
+
skipDisabled,
|
|
42
|
+
skipButtons,
|
|
43
|
+
dropUnchecked
|
|
44
|
+
];
|
|
45
|
+
/** A non-negative integer string (e.g. "0", "12") — denotes an array index. */
|
|
46
|
+
function isIndex(segment) {
|
|
47
|
+
return /^(0|[1-9]\d*)$/.test(segment);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A trailing `[]` marks an auto-append array field (PHP / form-data-json
|
|
51
|
+
* convention) — `colors[]` appends to the `colors` array, `a.b[]` to `a.b`.
|
|
52
|
+
* Returns the dot-path without the suffix, or `null` when absent.
|
|
53
|
+
*/
|
|
54
|
+
function arrayFieldPath(name) {
|
|
55
|
+
return name.endsWith("[]") ? name.slice(0, -2) : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Segments that could walk into an object's prototype. Writing through them
|
|
59
|
+
* enables prototype-pollution; reading them leaks internal objects. Paths
|
|
60
|
+
* containing any of these are ignored entirely.
|
|
61
|
+
*/
|
|
62
|
+
function hasUnsafeKey(keys) {
|
|
63
|
+
return keys.some((k) => k === "__proto__" || k === "prototype" || k === "constructor");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Write `value` into `target` at the dot-notation `path`. Object segments
|
|
67
|
+
* create plain objects; integer segments create/extend arrays. Paths
|
|
68
|
+
* containing prototype-polluting segments are ignored.
|
|
69
|
+
*
|
|
70
|
+
* @example setPath(o, "user.tags.0", "a") // { user: { tags: ["a"] } }
|
|
71
|
+
*/
|
|
72
|
+
function setPath(target, path, value) {
|
|
73
|
+
const keys = path.split(".");
|
|
74
|
+
if (hasUnsafeKey(keys)) return;
|
|
75
|
+
let node = target;
|
|
76
|
+
for (let i = 0; i < keys.length; i++) {
|
|
77
|
+
const key = keys[i];
|
|
78
|
+
if (i === keys.length - 1) {
|
|
79
|
+
node[key] = value;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const nextKey = keys[i + 1];
|
|
83
|
+
const childShouldBeArray = isIndex(nextKey);
|
|
84
|
+
const existing = node[key];
|
|
85
|
+
if (existing === void 0 || typeof existing !== "object" || existing === null) node[key] = childShouldBeArray ? [] : {};
|
|
86
|
+
node = node[key];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Read the value at the dot-notation `path` from `source`, or `undefined`. */
|
|
90
|
+
function getPath(source, path) {
|
|
91
|
+
const keys = path.split(".");
|
|
92
|
+
if (hasUnsafeKey(keys)) return void 0;
|
|
93
|
+
let node = source;
|
|
94
|
+
for (const key of keys) {
|
|
95
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
96
|
+
node = node[key];
|
|
97
|
+
}
|
|
98
|
+
return node;
|
|
99
|
+
}
|
|
100
|
+
/** Extract one raw {@link Field} from a named control, pre-transform. */
|
|
101
|
+
function extractField(control) {
|
|
102
|
+
if (control instanceof HTMLInputElement) {
|
|
103
|
+
const type = control.type;
|
|
104
|
+
if (type === "checkbox") return {
|
|
105
|
+
control,
|
|
106
|
+
name: control.name,
|
|
107
|
+
value: control.value || "on",
|
|
108
|
+
checked: control.checked
|
|
109
|
+
};
|
|
110
|
+
if (type === "radio") return {
|
|
111
|
+
control,
|
|
112
|
+
name: control.name,
|
|
113
|
+
value: control.value,
|
|
114
|
+
checked: control.checked
|
|
115
|
+
};
|
|
116
|
+
if (type === "file") {
|
|
117
|
+
const value = control.multiple ? Array.from(control.files ?? []) : control.files?.[0] ?? null;
|
|
118
|
+
return {
|
|
119
|
+
control,
|
|
120
|
+
name: control.name,
|
|
121
|
+
value
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
control,
|
|
126
|
+
name: control.name,
|
|
127
|
+
value: control.value
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (control instanceof HTMLSelectElement && control.multiple) return {
|
|
131
|
+
control,
|
|
132
|
+
name: control.name,
|
|
133
|
+
value: Array.from(control.selectedOptions, (o) => o.value)
|
|
134
|
+
};
|
|
135
|
+
return {
|
|
136
|
+
control,
|
|
137
|
+
name: control.name,
|
|
138
|
+
value: control.value
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Extracts an {@link HTMLFormElement} into a nested plain object (and writes one
|
|
143
|
+
* back) using dot-notation field names — `name="user.address.city"` nests into
|
|
144
|
+
* objects, numeric segments like `name="tags.0"` build arrays. A static snapshot:
|
|
145
|
+
* values are read/written at call time, no reactivity.
|
|
146
|
+
*
|
|
147
|
+
* Extraction runs each field through a composable {@link FormFieldTransform}
|
|
148
|
+
* pipeline ({@link defaultTransforms} by default), which decides inclusion and
|
|
149
|
+
* may rewrite names/values. Mirrors the ergonomics of the native `FormData`
|
|
150
|
+
* constructor.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* import { FormObject, defaultTransforms, uncheckedAs }
|
|
155
|
+
* from "elements-kit/utilities/form-object";
|
|
156
|
+
*
|
|
157
|
+
* // <input name="user.name"> <input name="tags.0"> <input name="tags.1">
|
|
158
|
+
* const data = new FormObject(form).toObject();
|
|
159
|
+
* // => { user: { name: "..." }, tags: ["...", "..."] }
|
|
160
|
+
*
|
|
161
|
+
* // Replace the pipeline — include unchecked checkboxes as false
|
|
162
|
+
* new FormObject(form, {
|
|
163
|
+
* transforms: [...defaultTransforms.slice(0, 2), uncheckedAs(false)],
|
|
164
|
+
* }).toObject();
|
|
165
|
+
*
|
|
166
|
+
* new FormObject(form).fromObject({ user: { name: "Wael" }, tags: ["a", "b"] });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
var FormObject = class {
|
|
170
|
+
#form;
|
|
171
|
+
#transforms;
|
|
172
|
+
constructor(form, options = {}) {
|
|
173
|
+
this.#form = form;
|
|
174
|
+
this.#transforms = options.transforms ?? defaultTransforms;
|
|
175
|
+
}
|
|
176
|
+
/** Run a field through the transform pipeline; `null` if any transform drops it. */
|
|
177
|
+
#applyTransforms(field) {
|
|
178
|
+
let current = field;
|
|
179
|
+
for (const transform of this.#transforms) {
|
|
180
|
+
if (current === null) return null;
|
|
181
|
+
current = transform(current);
|
|
182
|
+
}
|
|
183
|
+
return current;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Place a field into `result`. Array-ness is explicit, like form-data-json:
|
|
187
|
+
* a name ending in `[]` appends to an array at that path (so a single
|
|
188
|
+
* `colors[]` still yields `["x"]` and an empty one stays `[]`), while a bare
|
|
189
|
+
* name is scalar — if several controls share it, the **last value wins**.
|
|
190
|
+
* Use explicit indices (`tags.0`, `tags.1`) or `[]` to build arrays.
|
|
191
|
+
*/
|
|
192
|
+
#assign(result, field) {
|
|
193
|
+
const { name, value } = field;
|
|
194
|
+
const arrayPath = arrayFieldPath(name);
|
|
195
|
+
if (arrayPath !== null) {
|
|
196
|
+
let arr = getPath(result, arrayPath);
|
|
197
|
+
if (!Array.isArray(arr)) {
|
|
198
|
+
arr = [];
|
|
199
|
+
setPath(result, arrayPath, arr);
|
|
200
|
+
}
|
|
201
|
+
arr.push(value);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
setPath(result, name, value);
|
|
205
|
+
}
|
|
206
|
+
/** Snapshot the form into a nested object built from dot-notation names. */
|
|
207
|
+
toObject() {
|
|
208
|
+
const result = {};
|
|
209
|
+
for (const el of this.#form.elements) {
|
|
210
|
+
const control = el;
|
|
211
|
+
if (!control.name) continue;
|
|
212
|
+
const path = arrayFieldPath(control.name);
|
|
213
|
+
if (path !== null && getPath(result, path) === void 0) setPath(result, path, []);
|
|
214
|
+
}
|
|
215
|
+
for (const el of this.#form.elements) {
|
|
216
|
+
if (!el.name) continue;
|
|
217
|
+
const field = this.#applyTransforms(extractField(el));
|
|
218
|
+
if (field === null) continue;
|
|
219
|
+
this.#assign(result, field);
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
/** Alias for {@link toObject} — lets `JSON.stringify(instance)` work. */
|
|
224
|
+
toJSON() {
|
|
225
|
+
return this.toObject();
|
|
226
|
+
}
|
|
227
|
+
/** True when the control can be written/cleared (named and not disabled). */
|
|
228
|
+
#isWritable(el) {
|
|
229
|
+
const control = el;
|
|
230
|
+
return !!control.name && !control.disabled;
|
|
231
|
+
}
|
|
232
|
+
/** Write values from a nested object back onto the form's named controls. */
|
|
233
|
+
fromObject(data) {
|
|
234
|
+
for (const el of this.#form.elements) {
|
|
235
|
+
if (!this.#isWritable(el)) continue;
|
|
236
|
+
const control = el;
|
|
237
|
+
const value = getPath(data, arrayFieldPath(control.name) ?? control.name);
|
|
238
|
+
if (value === void 0) continue;
|
|
239
|
+
if (control instanceof HTMLInputElement) {
|
|
240
|
+
const type = control.type;
|
|
241
|
+
if (type === "checkbox") {
|
|
242
|
+
if (Array.isArray(value)) control.checked = value.includes(control.value);
|
|
243
|
+
else if (typeof value === "boolean") control.checked = value;
|
|
244
|
+
else control.checked = value === control.value || value === "on";
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (type === "radio") {
|
|
248
|
+
control.checked = control.value === value;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (type === "file") continue;
|
|
252
|
+
control.value = value == null ? "" : String(value);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (control instanceof HTMLSelectElement && control.multiple) {
|
|
256
|
+
const values = (Array.isArray(value) ? value : [value]).map(String);
|
|
257
|
+
for (const option of control.options) option.selected = values.includes(option.value);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
control.value = value == null ? "" : String(value);
|
|
261
|
+
}
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
/** Reset every named control to its empty/default state. */
|
|
265
|
+
clear() {
|
|
266
|
+
for (const el of this.#form.elements) {
|
|
267
|
+
if (!this.#isWritable(el)) continue;
|
|
268
|
+
const control = el;
|
|
269
|
+
if (control instanceof HTMLInputElement) {
|
|
270
|
+
const type = control.type;
|
|
271
|
+
if (type === "checkbox" || type === "radio") control.checked = false;
|
|
272
|
+
else control.value = "";
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (control instanceof HTMLSelectElement) {
|
|
276
|
+
for (const option of control.options) option.selected = false;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
control.value = "";
|
|
280
|
+
}
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
//#endregion
|
|
285
|
+
export { FormObject, defaultTransforms, dropUnchecked, skipButtons, skipDisabled, skipEmpty, uncheckedAs };
|
package/dist/utilities/hover.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as trigger, _ as onCleanup, l as computed, x as signal } from "../lib-DYypKhxk.mjs";
|
|
2
2
|
import "../signals/index.mjs";
|
|
3
3
|
import { fromEvent } from "./event-driven.mjs";
|
|
4
4
|
import { isBrowser } from "./environment.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { y as Computed } from "../
|
|
1
|
+
import { D as CLAIM, j as SEED, y as Computed } from "../types-ncUK9rki.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/utilities/promise.d.ts
|
|
4
4
|
/**
|
|
@@ -27,7 +27,24 @@ declare class ReactivePromise<T, E = unknown> extends Promise<T> {
|
|
|
27
27
|
get result(): T | E | undefined;
|
|
28
28
|
constructor(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void);
|
|
29
29
|
static from<T, E = unknown>(p: Promise<T>): ReactivePromise<T, E>;
|
|
30
|
+
/**
|
|
31
|
+
* @internal Hydration seeding: settle the reactive state from a serialized
|
|
32
|
+
* server snapshot. The underlying promise is untouched — when it actually
|
|
33
|
+
* resolves or rejects, its handlers overwrite the seeded state
|
|
34
|
+
* (stale-while-revalidate). `await` still waits for the real settlement.
|
|
35
|
+
*/
|
|
36
|
+
[SEED](value: unknown): void;
|
|
37
|
+
/**
|
|
38
|
+
* @internal Hydrate claim protocol: seed from the server record while the
|
|
39
|
+
* underlying promise is still pending. The promise fired at construction —
|
|
40
|
+
* there is no run to skip here (see `Async[CLAIM]` for that).
|
|
41
|
+
*/
|
|
42
|
+
[CLAIM](record: {
|
|
43
|
+
value: unknown;
|
|
44
|
+
} | undefined): void;
|
|
30
45
|
}
|
|
46
|
+
/** @internal Cross-instance-safe `ReactivePromise` detection. */
|
|
47
|
+
declare function isReactivePromiseLike(value: unknown): value is ReactivePromise<unknown, unknown>;
|
|
31
48
|
/**
|
|
32
49
|
* A {@link ReactivePromise} that is also callable as a `Computed<T | E | undefined>`.
|
|
33
50
|
*
|
|
@@ -59,4 +76,4 @@ declare function promise<T, E = unknown>(p: ReactivePromise<T>): ComputedPromise
|
|
|
59
76
|
declare function promise<T, E = unknown>(p: Promise<T>): ComputedPromise<T, E>;
|
|
60
77
|
declare function promise<T, E = unknown>(executor: Executor<T, E>): ComputedPromise<T, E>;
|
|
61
78
|
//#endregion
|
|
62
|
-
export { ComputedPromise, ReactivePromise, promise };
|
|
79
|
+
export { ComputedPromise, ReactivePromise, isReactivePromiseLike, promise };
|