@vertz/ui 0.2.0 → 0.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.
Files changed (35) hide show
  1. package/README.md +339 -857
  2. package/dist/css/public.d.ts +24 -27
  3. package/dist/css/public.js +5 -1
  4. package/dist/form/public.d.ts +94 -38
  5. package/dist/form/public.js +5 -3
  6. package/dist/index.d.ts +696 -167
  7. package/dist/index.js +461 -84
  8. package/dist/internals.d.ts +192 -23
  9. package/dist/internals.js +151 -102
  10. package/dist/jsx-runtime/index.d.ts +44 -17
  11. package/dist/jsx-runtime/index.js +26 -7
  12. package/dist/query/public.d.ts +62 -7
  13. package/dist/query/public.js +12 -4
  14. package/dist/router/public.d.ts +186 -26
  15. package/dist/router/public.js +22 -7
  16. package/dist/shared/{chunk-f1ynwam4.js → chunk-0p5f7gmg.js} +155 -32
  17. package/dist/shared/{chunk-j8vzvne3.js → chunk-9e92w0wt.js} +4 -1
  18. package/dist/shared/{chunk-xd9d7q5p.js → chunk-cq7xg4xe.js} +59 -10
  19. package/dist/shared/chunk-g4rch80a.js +33 -0
  20. package/dist/shared/{chunk-pgymxpn1.js → chunk-hrd0mft1.js} +136 -34
  21. package/dist/shared/chunk-nmjyj8p9.js +290 -0
  22. package/dist/shared/chunk-pp3a6xbn.js +483 -0
  23. package/dist/shared/chunk-prj7nm08.js +67 -0
  24. package/dist/shared/chunk-q6cpe5k7.js +230 -0
  25. package/dist/shared/chunk-ryb49346.js +374 -0
  26. package/dist/shared/chunk-v3yyf79g.js +48 -0
  27. package/dist/shared/chunk-vx0kzack.js +103 -0
  28. package/dist/shared/chunk-wv6kkj1w.js +464 -0
  29. package/dist/test/index.d.ts +67 -6
  30. package/dist/test/index.js +4 -3
  31. package/package.json +13 -8
  32. package/dist/shared/chunk-bp3v6s9j.js +0 -62
  33. package/dist/shared/chunk-d8h2eh8d.js +0 -141
  34. package/dist/shared/chunk-tsdpgmks.js +0 -98
  35. package/dist/shared/chunk-zbbvx05f.js +0 -202
@@ -0,0 +1,230 @@
1
+ import {
2
+ computed,
3
+ signal
4
+ } from "./chunk-hrd0mft1.js";
5
+
6
+ // src/form/field-state.ts
7
+ function createFieldState(_name, initialValue) {
8
+ const error = signal(undefined);
9
+ const dirty = signal(false);
10
+ const touched = signal(false);
11
+ const value = signal(initialValue);
12
+ return {
13
+ error,
14
+ dirty,
15
+ touched,
16
+ value,
17
+ setValue(newValue) {
18
+ value.value = newValue;
19
+ dirty.value = newValue !== initialValue;
20
+ },
21
+ reset() {
22
+ value.value = initialValue;
23
+ error.value = undefined;
24
+ dirty.value = false;
25
+ touched.value = false;
26
+ }
27
+ };
28
+ }
29
+
30
+ // src/form/form-data.ts
31
+ function formDataToObject(formData, options) {
32
+ const result = {};
33
+ const coerce = options?.coerce ?? false;
34
+ for (const [key, value] of formData.entries()) {
35
+ if (typeof value !== "string") {
36
+ continue;
37
+ }
38
+ result[key] = coerce ? coerceValue(value) : value;
39
+ }
40
+ return result;
41
+ }
42
+ function coerceValue(value) {
43
+ if (value === "true")
44
+ return true;
45
+ if (value === "false")
46
+ return false;
47
+ if (value !== "" && !Number.isNaN(Number(value))) {
48
+ return Number(value);
49
+ }
50
+ return value;
51
+ }
52
+
53
+ // src/form/validation.ts
54
+ function validate(schema, data) {
55
+ const result = schema.parse(data);
56
+ if (result.ok) {
57
+ return { success: true, data: result.data, errors: {} };
58
+ }
59
+ const err = result.error;
60
+ if (err instanceof Error) {
61
+ const fieldErrors = err.fieldErrors;
62
+ if (fieldErrors && Object.keys(fieldErrors).length > 0) {
63
+ return { success: false, data: undefined, errors: fieldErrors };
64
+ }
65
+ const issues = err.issues;
66
+ if (Array.isArray(issues) && issues.length > 0) {
67
+ const errors = {};
68
+ for (const issue of issues) {
69
+ const key = Array.isArray(issue.path) && issue.path.length > 0 ? issue.path.join(".") : "_form";
70
+ if (!(key in errors)) {
71
+ errors[key] = issue.message ?? "Validation failed";
72
+ }
73
+ }
74
+ return { success: false, data: undefined, errors };
75
+ }
76
+ return { success: false, data: undefined, errors: { _form: err.message } };
77
+ }
78
+ return { success: false, data: undefined, errors: { _form: "Validation failed" } };
79
+ }
80
+
81
+ // src/form/form.ts
82
+ function form(sdkMethod, options) {
83
+ const fieldCache = new Map;
84
+ const submitting = signal(false);
85
+ const fieldGeneration = signal(0);
86
+ const dirty = computed(() => {
87
+ fieldGeneration.value;
88
+ for (const field of fieldCache.values()) {
89
+ if (field.dirty.value)
90
+ return true;
91
+ }
92
+ return false;
93
+ });
94
+ const valid = computed(() => {
95
+ fieldGeneration.value;
96
+ for (const field of fieldCache.values()) {
97
+ if (field.error.value !== undefined)
98
+ return false;
99
+ }
100
+ return true;
101
+ });
102
+ function getOrCreateField(name) {
103
+ let field = fieldCache.get(name);
104
+ if (!field) {
105
+ const initialObj = typeof options?.initial === "function" ? options.initial() : options?.initial;
106
+ const initialValue = initialObj?.[name];
107
+ field = createFieldState(name, initialValue);
108
+ fieldCache.set(name, field);
109
+ fieldGeneration.value++;
110
+ }
111
+ return field;
112
+ }
113
+ const resolvedSchema = options?.schema ?? sdkMethod.meta?.bodySchema;
114
+ async function submitPipeline(formData) {
115
+ const data = formDataToObject(formData);
116
+ if (resolvedSchema) {
117
+ const result2 = validate(resolvedSchema, data);
118
+ if (!result2.success) {
119
+ for (const [fieldName, message] of Object.entries(result2.errors)) {
120
+ getOrCreateField(fieldName).error.value = message;
121
+ }
122
+ options?.onError?.(result2.errors);
123
+ return;
124
+ }
125
+ }
126
+ for (const field of fieldCache.values()) {
127
+ field.error.value = undefined;
128
+ }
129
+ submitting.value = true;
130
+ const result = await sdkMethod(data);
131
+ if (!result.ok) {
132
+ submitting.value = false;
133
+ const message = result.error.message;
134
+ getOrCreateField("_form").error.value = message;
135
+ options?.onError?.({ _form: message });
136
+ return;
137
+ }
138
+ submitting.value = false;
139
+ options?.onSuccess?.(result.data);
140
+ if (options?.resetOnSuccess) {
141
+ resetForm();
142
+ }
143
+ }
144
+ let boundElement;
145
+ function resetForm() {
146
+ for (const field of fieldCache.values()) {
147
+ field.reset();
148
+ }
149
+ }
150
+ async function submitPipelineWithReset(formData) {
151
+ await submitPipeline(formData);
152
+ if (options?.resetOnSuccess && !submitting.peek() && boundElement) {
153
+ const hasErrors = [...fieldCache.values()].some((f) => f.error.peek() !== undefined);
154
+ if (!hasErrors) {
155
+ boundElement.reset();
156
+ }
157
+ }
158
+ }
159
+ function handleInputOrChange(e) {
160
+ const target = e.target;
161
+ if (!target?.name)
162
+ return;
163
+ const field = getOrCreateField(target.name);
164
+ field.setValue(target.value);
165
+ }
166
+ function handleFocusout(e) {
167
+ const target = e.target;
168
+ if (!target?.name)
169
+ return;
170
+ const field = getOrCreateField(target.name);
171
+ field.touched.value = true;
172
+ }
173
+ const baseProperties = {
174
+ action: sdkMethod.url,
175
+ method: sdkMethod.method,
176
+ onSubmit: async (e) => {
177
+ e.preventDefault();
178
+ const formElement = e.target;
179
+ const formData = new FormData(formElement);
180
+ await submitPipeline(formData);
181
+ if (options?.resetOnSuccess && !submitting.peek()) {
182
+ const hasErrors = [...fieldCache.values()].some((f) => f.error.peek() !== undefined);
183
+ if (!hasErrors) {
184
+ formElement.reset();
185
+ }
186
+ }
187
+ },
188
+ reset: resetForm,
189
+ setFieldError: (field, message) => {
190
+ getOrCreateField(field).error.value = message;
191
+ },
192
+ submit: async (formData) => {
193
+ if (formData) {
194
+ await submitPipeline(formData);
195
+ } else if (boundElement) {
196
+ await submitPipelineWithReset(new FormData(boundElement));
197
+ return;
198
+ }
199
+ if (options?.resetOnSuccess && formData && !submitting.peek()) {
200
+ const hasErrors = [...fieldCache.values()].some((f) => f.error.peek() !== undefined);
201
+ if (!hasErrors && boundElement) {
202
+ boundElement.reset();
203
+ }
204
+ }
205
+ },
206
+ submitting,
207
+ dirty,
208
+ valid,
209
+ __bindElement: (el) => {
210
+ boundElement = el;
211
+ el.addEventListener("input", handleInputOrChange);
212
+ el.addEventListener("change", handleInputOrChange);
213
+ el.addEventListener("focusout", handleFocusout);
214
+ }
215
+ };
216
+ const knownProperties = new Set(Object.keys(baseProperties));
217
+ return new Proxy(baseProperties, {
218
+ get(target, prop, receiver) {
219
+ if (typeof prop === "string") {
220
+ if (knownProperties.has(prop)) {
221
+ return target[prop];
222
+ }
223
+ return getOrCreateField(prop);
224
+ }
225
+ return Reflect.get(target, prop, receiver);
226
+ }
227
+ });
228
+ }
229
+
230
+ export { createFieldState, formDataToObject, validate, form };
@@ -0,0 +1,374 @@
1
+ import {
2
+ getAdapter,
3
+ isRenderNode
4
+ } from "./chunk-g4rch80a.js";
5
+ import {
6
+ domEffect
7
+ } from "./chunk-hrd0mft1.js";
8
+ import {
9
+ SVG_NS,
10
+ isSVGTag,
11
+ normalizeSVGAttr
12
+ } from "./chunk-prj7nm08.js";
13
+
14
+ // src/hydrate/hydration-context.ts
15
+ var isHydrating = false;
16
+ var currentNode = null;
17
+ var cursorStack = [];
18
+ var hydrationRoot = null;
19
+ var claimedNodes = null;
20
+ function isDebug() {
21
+ if (typeof process !== "undefined" && true) {
22
+ return true;
23
+ }
24
+ return typeof globalThis !== "undefined" && globalThis.__VERTZ_HYDRATION_DEBUG__ === true;
25
+ }
26
+ function startHydration(root) {
27
+ if (isHydrating) {
28
+ throw new Error("[hydrate] startHydration() called while hydration is already active. " + "Concurrent hydration is not supported.");
29
+ }
30
+ isHydrating = true;
31
+ currentNode = root.firstChild;
32
+ cursorStack.length = 0;
33
+ if (isDebug()) {
34
+ hydrationRoot = root;
35
+ claimedNodes = new WeakSet;
36
+ }
37
+ }
38
+ function endHydration() {
39
+ if (isDebug()) {
40
+ if (currentNode) {
41
+ console.debug("[hydrate] Hydration ended with unclaimed nodes remaining. " + "This may indicate SSR/client tree mismatch or browser extension nodes.");
42
+ }
43
+ if (cursorStack.length > 0) {
44
+ console.debug(`[hydrate] Hydration ended with unbalanced cursor stack (depth: ${cursorStack.length}). ` + "Check that __enterChildren/__exitChildren calls are paired.");
45
+ }
46
+ }
47
+ if (hydrationRoot && claimedNodes) {
48
+ const unclaimed = findUnclaimedNodes(hydrationRoot, claimedNodes);
49
+ if (unclaimed.length > 0) {
50
+ console.warn(`[hydrate] ${unclaimed.length} SSR node(s) not claimed during hydration:
51
+ ` + unclaimed.map((n) => ` - ${describeNode(n)}`).join(`
52
+ `));
53
+ }
54
+ }
55
+ hydrationRoot = null;
56
+ claimedNodes = null;
57
+ isHydrating = false;
58
+ currentNode = null;
59
+ cursorStack.length = 0;
60
+ }
61
+ function getIsHydrating() {
62
+ return isHydrating;
63
+ }
64
+ function pauseHydration() {
65
+ isHydrating = false;
66
+ }
67
+ function resumeHydration() {
68
+ isHydrating = true;
69
+ }
70
+ function claimElement(tag) {
71
+ const upperTag = tag.toUpperCase();
72
+ while (currentNode) {
73
+ if (currentNode.nodeType === Node.ELEMENT_NODE) {
74
+ const el = currentNode;
75
+ if (el.tagName === upperTag) {
76
+ if (isDebug()) {
77
+ const id = el.id ? `#${el.id}` : "";
78
+ const cls = el.className ? `.${el.className.split(" ")[0]}` : "";
79
+ console.debug(`[hydrate] claimElement(<${tag}${id}${cls}>) ✓ depth=${cursorStack.length}`);
80
+ }
81
+ if (claimedNodes)
82
+ claimedNodes.add(el);
83
+ currentNode = el.nextSibling;
84
+ return el;
85
+ }
86
+ if (isDebug()) {
87
+ console.debug(`[hydrate] Skipping non-matching node: <${el.tagName.toLowerCase()}> (expected <${tag}>)`);
88
+ }
89
+ }
90
+ currentNode = currentNode.nextSibling;
91
+ }
92
+ if (isDebug()) {
93
+ console.warn(`[hydrate] Expected <${tag}> but no matching SSR node found. Creating new element.`);
94
+ }
95
+ return null;
96
+ }
97
+ function claimText() {
98
+ while (currentNode) {
99
+ if (currentNode.nodeType === Node.TEXT_NODE) {
100
+ const text = currentNode;
101
+ if (isDebug()) {
102
+ const preview = text.data.length > 30 ? text.data.slice(0, 30) + "..." : text.data;
103
+ console.debug(`[hydrate] claimText("${preview}") ✓ depth=${cursorStack.length}`);
104
+ }
105
+ if (claimedNodes)
106
+ claimedNodes.add(text);
107
+ currentNode = text.nextSibling;
108
+ return text;
109
+ }
110
+ if (currentNode.nodeType === Node.ELEMENT_NODE) {
111
+ break;
112
+ }
113
+ currentNode = currentNode.nextSibling;
114
+ }
115
+ if (isDebug()) {
116
+ console.warn("[hydrate] Expected text node but no matching SSR node found.");
117
+ }
118
+ return null;
119
+ }
120
+ function claimComment() {
121
+ while (currentNode) {
122
+ if (currentNode.nodeType === Node.COMMENT_NODE) {
123
+ const comment = currentNode;
124
+ if (claimedNodes)
125
+ claimedNodes.add(comment);
126
+ currentNode = comment.nextSibling;
127
+ return comment;
128
+ }
129
+ currentNode = currentNode.nextSibling;
130
+ }
131
+ if (isDebug()) {
132
+ console.warn("[hydrate] Expected comment node but no matching SSR node found.");
133
+ }
134
+ return null;
135
+ }
136
+ function enterChildren(el) {
137
+ cursorStack.push(currentNode);
138
+ currentNode = el.firstChild;
139
+ }
140
+ function exitChildren() {
141
+ if (cursorStack.length === 0) {
142
+ if (isDebug()) {
143
+ console.warn("[hydrate] exitChildren() called with empty stack. " + "This likely means __exitChildren was called without a matching __enterChildren.");
144
+ }
145
+ currentNode = null;
146
+ return;
147
+ }
148
+ currentNode = cursorStack.pop() ?? null;
149
+ }
150
+ function findUnclaimedNodes(root, claimed) {
151
+ const unclaimed = [];
152
+ function walk(node) {
153
+ let child = node.firstChild;
154
+ while (child) {
155
+ if (child.nodeType === Node.ELEMENT_NODE) {
156
+ const el = child;
157
+ if (el.tagName.includes("-")) {
158
+ child = child.nextSibling;
159
+ continue;
160
+ }
161
+ }
162
+ if (child.nodeType === Node.ELEMENT_NODE && claimed.has(child) && child.tagName === "SPAN" && child.style.display === "contents") {
163
+ child = child.nextSibling;
164
+ continue;
165
+ }
166
+ if (!claimed.has(child)) {
167
+ if (child.nodeType === Node.ELEMENT_NODE || child.nodeType === Node.TEXT_NODE || child.nodeType === Node.COMMENT_NODE) {
168
+ unclaimed.push(child);
169
+ }
170
+ }
171
+ if (child.nodeType === Node.ELEMENT_NODE) {
172
+ walk(child);
173
+ }
174
+ child = child.nextSibling;
175
+ }
176
+ }
177
+ walk(root);
178
+ return unclaimed;
179
+ }
180
+ function describeNode(node) {
181
+ if (node.nodeType === Node.ELEMENT_NODE) {
182
+ const el = node;
183
+ const id = el.id ? `#${el.id}` : "";
184
+ const cls = el.className ? `.${String(el.className).split(" ")[0]}` : "";
185
+ return `<${el.tagName.toLowerCase()}${id}${cls}>`;
186
+ }
187
+ if (node.nodeType === Node.TEXT_NODE) {
188
+ const data = node.data;
189
+ const preview = data.length > 20 ? data.slice(0, 20) + "..." : data;
190
+ return `text("${preview}")`;
191
+ }
192
+ if (node.nodeType === Node.COMMENT_NODE) {
193
+ return `<!-- ${node.data} -->`;
194
+ }
195
+ return `[node type=${node.nodeType}]`;
196
+ }
197
+
198
+ // src/dom/element.ts
199
+ var MAX_THUNK_DEPTH = 100;
200
+ function resolveAndAppend(parent, value, depth = 0) {
201
+ if (depth >= MAX_THUNK_DEPTH) {
202
+ throw new Error("resolveAndAppend: max recursion depth exceeded — possible circular thunk");
203
+ }
204
+ if (value == null || typeof value === "boolean") {
205
+ return;
206
+ }
207
+ if (typeof value === "function") {
208
+ resolveAndAppend(parent, value(), depth + 1);
209
+ return;
210
+ }
211
+ if (Array.isArray(value)) {
212
+ for (const item of value) {
213
+ resolveAndAppend(parent, item, depth);
214
+ }
215
+ return;
216
+ }
217
+ if (isRenderNode(value)) {
218
+ parent.appendChild(value);
219
+ return;
220
+ }
221
+ const text = typeof value === "string" ? value : String(value);
222
+ parent.appendChild(getAdapter().createTextNode(text));
223
+ }
224
+ function __text(fn) {
225
+ if (getIsHydrating()) {
226
+ const claimed = claimText();
227
+ if (claimed) {
228
+ const node2 = claimed;
229
+ node2.dispose = domEffect(() => {
230
+ node2.data = fn();
231
+ });
232
+ return node2;
233
+ }
234
+ }
235
+ const node = getAdapter().createTextNode("");
236
+ node.dispose = domEffect(() => {
237
+ node.data = fn();
238
+ });
239
+ return node;
240
+ }
241
+ function __child(fn) {
242
+ let wrapper;
243
+ if (getIsHydrating()) {
244
+ const claimed = claimElement("span");
245
+ if (claimed) {
246
+ wrapper = claimed;
247
+ while (wrapper.firstChild) {
248
+ wrapper.removeChild(wrapper.firstChild);
249
+ }
250
+ pauseHydration();
251
+ try {
252
+ wrapper.dispose = domEffect(() => {
253
+ const value = fn();
254
+ if (isRenderNode(value) && wrapper.childNodes.length === 1 && wrapper.firstChild === value) {
255
+ return;
256
+ }
257
+ while (wrapper.firstChild) {
258
+ wrapper.removeChild(wrapper.firstChild);
259
+ }
260
+ resolveAndAppend(wrapper, value);
261
+ });
262
+ } finally {
263
+ resumeHydration();
264
+ }
265
+ return wrapper;
266
+ }
267
+ }
268
+ wrapper = getAdapter().createElement("span");
269
+ wrapper.style.display = "contents";
270
+ wrapper.dispose = domEffect(() => {
271
+ const value = fn();
272
+ if (isRenderNode(value) && wrapper.childNodes.length === 1 && wrapper.firstChild === value) {
273
+ return;
274
+ }
275
+ while (wrapper.firstChild) {
276
+ wrapper.removeChild(wrapper.firstChild);
277
+ }
278
+ resolveAndAppend(wrapper, value);
279
+ });
280
+ return wrapper;
281
+ }
282
+ function resolveAndInsert(parent, value, depth = 0) {
283
+ if (depth >= MAX_THUNK_DEPTH) {
284
+ throw new Error("__insert: max recursion depth exceeded — possible circular thunk");
285
+ }
286
+ if (value == null || typeof value === "boolean") {
287
+ return;
288
+ }
289
+ if (typeof value === "function") {
290
+ resolveAndInsert(parent, value(), depth + 1);
291
+ return;
292
+ }
293
+ if (Array.isArray(value)) {
294
+ for (const item of value) {
295
+ resolveAndInsert(parent, item, depth);
296
+ }
297
+ return;
298
+ }
299
+ insertLeaf(parent, value);
300
+ }
301
+ function insertLeaf(parent, value) {
302
+ if (getIsHydrating()) {
303
+ if (isRenderNode(value)) {
304
+ return;
305
+ }
306
+ claimText();
307
+ return;
308
+ }
309
+ if (isRenderNode(value)) {
310
+ parent.appendChild(value);
311
+ return;
312
+ }
313
+ const text = typeof value === "string" ? value : String(value);
314
+ parent.appendChild(getAdapter().createTextNode(text));
315
+ }
316
+ function __insert(parent, value) {
317
+ if (value == null || typeof value === "boolean") {
318
+ return;
319
+ }
320
+ resolveAndInsert(parent, value);
321
+ }
322
+ function __element(tag, props) {
323
+ if (getIsHydrating()) {
324
+ const claimed = claimElement(tag);
325
+ if (claimed) {
326
+ if (props && typeof process !== "undefined" && true) {
327
+ for (const [key, value] of Object.entries(props)) {
328
+ if (key === "role" || key.startsWith("aria-")) {
329
+ const actual = claimed.getAttribute(key);
330
+ if (actual !== value) {
331
+ console.warn(`[hydrate] ARIA mismatch on <${tag}>: ${key}="${actual}" (expected "${value}")`);
332
+ }
333
+ }
334
+ }
335
+ }
336
+ return claimed;
337
+ }
338
+ }
339
+ const adapter = getAdapter();
340
+ const svg = isSVGTag(tag);
341
+ const el = svg ? adapter.createElementNS(SVG_NS, tag) : adapter.createElement(tag);
342
+ if (props) {
343
+ for (const [key, value] of Object.entries(props)) {
344
+ const attrName = svg ? normalizeSVGAttr(key) : key;
345
+ el.setAttribute(attrName, value);
346
+ }
347
+ }
348
+ return el;
349
+ }
350
+ function __append(parent, child) {
351
+ if (getIsHydrating())
352
+ return;
353
+ parent.appendChild(child);
354
+ }
355
+ function __staticText(text) {
356
+ if (getIsHydrating()) {
357
+ const claimed = claimText();
358
+ if (claimed)
359
+ return claimed;
360
+ }
361
+ return getAdapter().createTextNode(text);
362
+ }
363
+ function __enterChildren(el) {
364
+ if (getIsHydrating()) {
365
+ enterChildren(el);
366
+ }
367
+ }
368
+ function __exitChildren() {
369
+ if (getIsHydrating()) {
370
+ exitChildren();
371
+ }
372
+ }
373
+
374
+ export { startHydration, endHydration, getIsHydrating, claimText, claimComment, __text, __child, __insert, __element, __append, __staticText, __enterChildren, __exitChildren };
@@ -0,0 +1,48 @@
1
+ import {
2
+ domEffect
3
+ } from "./chunk-hrd0mft1.js";
4
+
5
+ // src/dom/attributes.ts
6
+ function __attr(el, name, fn) {
7
+ return domEffect(() => {
8
+ const value = fn();
9
+ if (value == null || value === false) {
10
+ el.removeAttribute(name);
11
+ } else if (value === true) {
12
+ el.setAttribute(name, "");
13
+ } else {
14
+ el.setAttribute(name, value);
15
+ }
16
+ });
17
+ }
18
+ function __show(el, fn) {
19
+ const originalDisplay = el.style.display;
20
+ return domEffect(() => {
21
+ el.style.display = fn() ? originalDisplay : "none";
22
+ });
23
+ }
24
+ function __classList(el, classMap) {
25
+ const disposers = [];
26
+ for (const [className, fn] of Object.entries(classMap)) {
27
+ disposers.push(domEffect(() => {
28
+ if (fn()) {
29
+ el.classList.add(className);
30
+ } else {
31
+ el.classList.remove(className);
32
+ }
33
+ }));
34
+ }
35
+ return () => {
36
+ for (const dispose of disposers) {
37
+ dispose();
38
+ }
39
+ };
40
+ }
41
+
42
+ // src/dom/events.ts
43
+ function __on(el, event, handler) {
44
+ el.addEventListener(event, handler);
45
+ return () => el.removeEventListener(event, handler);
46
+ }
47
+
48
+ export { __attr, __show, __classList, __on };