@jsswift/core 1.0.41

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.
@@ -0,0 +1,575 @@
1
+ /* ===============================
2
+ _h hyperscript (usa JSswift.reactive.effect)
3
+ =============================== */
4
+
5
+ const SVG_NS = "http://www.w3.org/2000/svg";
6
+ const SVG_TAGS = new Set([
7
+ "svg",
8
+ "g",
9
+ "path",
10
+ "circle",
11
+ "ellipse",
12
+ "line",
13
+ "rect",
14
+ "polygon",
15
+ "polyline",
16
+ "text",
17
+ "tspan",
18
+ "defs",
19
+ "linearGradient",
20
+ "radialGradient",
21
+ "stop",
22
+ "use",
23
+ "symbol",
24
+ "clipPath",
25
+ "mask",
26
+ "pattern",
27
+ "filter",
28
+ "feGaussianBlur",
29
+ "feOffset",
30
+ "feBlend",
31
+ "feColorMatrix"
32
+ ]);
33
+
34
+ function isContentProp(key) {
35
+ return key === "innerHTML" || key === "innerText" || key === "textContent" || key === "value";
36
+ }
37
+
38
+ function createElement(tag, ...args) {
39
+ const isSVG = SVG_TAGS.has(tag);
40
+ const el = tag === "text" && !isSVG
41
+ ? document.createTextNode("")
42
+ : isSVG
43
+ ? document.createElementNS(SVG_NS, tag)
44
+ : document.createElement(tag);
45
+
46
+ const isRod = (v) => !!v && v.type === "rod";
47
+ const interpolationCursor = createRodInterpolationCursor();
48
+ const childHelpers = createRendererChildHelpers(el, { isRod, interpolationCursor });
49
+ const {
50
+ appendRodText,
51
+ appendInterpolatedText,
52
+ appendDynamicChild,
53
+ appendChildValue
54
+ } = childHelpers;
55
+ const domBridge = createDomPropBridge(el, { isSVG, normalizeClass, isContentProp });
56
+ const {
57
+ isBooleanDomProp,
58
+ isBindingValueKey,
59
+ applyBindingValue,
60
+ setStyleEntry,
61
+ setProp,
62
+ setClassValue
63
+ } = domBridge;
64
+
65
+ function registerNodeEffect(run) {
66
+ const stop = JSswift.reactive.effect(run);
67
+ JSswift._registerCleanup(el, stop);
68
+ return stop;
69
+ }
70
+
71
+ function isNumericValueControl(control) {
72
+ if (String(control?.tagName || "").toLowerCase() !== "input") return false;
73
+ const inputType = String(control?.type || "").toLowerCase();
74
+ return inputType === "range" || inputType === "number";
75
+ }
76
+
77
+ function getValueControlType(control) {
78
+ if (String(control?.tagName || "").toLowerCase() !== "input") return "";
79
+ return String(control?.type || "").toLowerCase();
80
+ }
81
+
82
+ function isSingleSelectControl(control) {
83
+ return String(control?.tagName || "").toLowerCase() === "select" && !control.multiple;
84
+ }
85
+
86
+ function isFileInputControl(control) {
87
+ return String(control?.tagName || "").toLowerCase() === "input"
88
+ && String(control?.type || "").toLowerCase() === "file";
89
+ }
90
+
91
+ function bindRodValueControl(control, rodValue, eventName) {
92
+ registerNodeEffect(() => {
93
+ const next = rodValue.value ?? "";
94
+ if (control.value !== String(next)) setProp("value", next);
95
+ });
96
+
97
+ const onValueChange = () => {
98
+ let next = control.value;
99
+ const controlType = getValueControlType(control);
100
+ const isNumericRod = typeof rodValue.value === "number" || rodValue.value == null;
101
+ if (isSingleSelectControl(control) && control.value === "") {
102
+ next = null;
103
+ } else if (isNumericValueControl(control) && isNumericRod) {
104
+ if (controlType === "number" && control.value === "") {
105
+ next = null;
106
+ } else {
107
+ const numeric = typeof control.valueAsNumber === "number" && !Number.isNaN(control.valueAsNumber)
108
+ ? control.valueAsNumber
109
+ : Number(control.value);
110
+ next = Number.isNaN(numeric) ? (controlType === "number" ? null : 0) : numeric;
111
+ }
112
+ }
113
+ if (rodValue.value !== next) rodValue.value = next;
114
+ };
115
+
116
+ control.addEventListener(eventName, onValueChange);
117
+ JSswift._registerCleanup(control, () => {
118
+ control.removeEventListener(eventName, onValueChange);
119
+ });
120
+ }
121
+
122
+ function bindRodCheckedControl(control, rodValue) {
123
+ registerNodeEffect(() => {
124
+ const next = !!rodValue.value;
125
+ if (!!control.checked !== next) setProp("checked", next);
126
+ });
127
+
128
+ const onCheckedChange = () => {
129
+ const next = !!control.checked;
130
+ if (rodValue.value !== next) rodValue.value = next;
131
+ };
132
+
133
+ control.addEventListener("change", onCheckedChange);
134
+ JSswift._registerCleanup(control, () => {
135
+ control.removeEventListener("change", onCheckedChange);
136
+ });
137
+ }
138
+
139
+ function bindRodRadioControl(control, rodValue) {
140
+ const readOwnValue = () => String(control.value ?? "on");
141
+
142
+ registerNodeEffect(() => {
143
+ const next = rodValue.value == null ? null : String(rodValue.value);
144
+ const shouldCheck = next === readOwnValue();
145
+ if (!!control.checked !== shouldCheck) setProp("checked", shouldCheck);
146
+ });
147
+
148
+ const onCheckedChange = () => {
149
+ if (!control.checked) return;
150
+ const next = readOwnValue();
151
+ if (rodValue.value !== next) rodValue.value = next;
152
+ };
153
+
154
+ control.addEventListener("change", onCheckedChange);
155
+ JSswift._registerCleanup(control, () => {
156
+ control.removeEventListener("change", onCheckedChange);
157
+ });
158
+ }
159
+
160
+ function bindRodFilesControl(control, rodValue) {
161
+ const readFiles = () => {
162
+ const files = control.files;
163
+ if (files == null) return [];
164
+ if (Array.isArray(files)) return files.slice();
165
+ if (typeof files.length === "number") return Array.from(files);
166
+ return [files];
167
+ };
168
+
169
+ registerNodeEffect(() => {
170
+ const next = rodValue.value;
171
+ const shouldClear = next == null || (Array.isArray(next) && next.length === 0);
172
+ if (!shouldClear) return;
173
+ if (control.value !== "") setProp("value", "");
174
+ if (Array.isArray(control.files)) control.files = [];
175
+ });
176
+
177
+ const onFilesChange = () => {
178
+ const next = readFiles();
179
+ rodValue.value = next;
180
+ };
181
+
182
+ control.addEventListener("change", onFilesChange);
183
+ JSswift._registerCleanup(control, () => {
184
+ control.removeEventListener("change", onFilesChange);
185
+ });
186
+ }
187
+
188
+ function bindRodSelectMultiple(control, rodValue) {
189
+ const readValues = () => Array.from(control.childNodes || [])
190
+ .filter((node) => node?.tagName === "OPTION" && node.selected)
191
+ .map((node) => String(node.value ?? ""));
192
+
193
+ const applyFromRod = () => {
194
+ const nextValues = Array.isArray(rodValue.value)
195
+ ? rodValue.value.map((item) => String(item))
196
+ : [];
197
+ const nextSet = new Set(nextValues);
198
+ Array.from(control.childNodes || []).forEach((node) => {
199
+ if (node?.tagName !== "OPTION") return;
200
+ const shouldSelect = nextSet.has(String(node.value ?? ""));
201
+ if (!!node.selected === shouldSelect) return;
202
+ node.selected = shouldSelect;
203
+ if (shouldSelect) node.setAttribute("selected", "");
204
+ else node.removeAttribute("selected");
205
+ });
206
+ };
207
+
208
+ registerNodeEffect(applyFromRod);
209
+ control._cmsApplySelectedValues = applyFromRod;
210
+ queueMicrotask(applyFromRod);
211
+
212
+ const onValueChange = () => {
213
+ const next = readValues();
214
+ const current = Array.isArray(rodValue.value) ? rodValue.value.map((item) => String(item)) : [];
215
+ if (current.length === next.length && current.every((item, index) => item === next[index])) return;
216
+ rodValue.value = next;
217
+ };
218
+
219
+ control.addEventListener("change", onValueChange);
220
+ JSswift._registerCleanup(control, () => {
221
+ control.removeEventListener("change", onValueChange);
222
+ });
223
+ }
224
+
225
+ function bindRodOptionSelected(optionEl, rodValue) {
226
+ registerNodeEffect(() => {
227
+ const next = !!rodValue.value;
228
+ if (!!optionEl.selected !== next) setProp("selected", next);
229
+ });
230
+
231
+ let parentSelect = null;
232
+ const onParentChange = () => {
233
+ const next = !!optionEl.selected;
234
+ if (rodValue.value !== next) rodValue.value = next;
235
+ };
236
+
237
+ const attachParent = () => {
238
+ if (parentSelect || optionEl.parentNode?.tagName !== "SELECT") return;
239
+ parentSelect = optionEl.parentNode;
240
+ parentSelect.addEventListener("change", onParentChange);
241
+ };
242
+
243
+ queueMicrotask(attachParent);
244
+ JSswift._registerCleanup(optionEl, () => {
245
+ parentSelect?.removeEventListener("change", onParentChange);
246
+ });
247
+ }
248
+
249
+ function bindProp(key, value) {
250
+ if (isEventProp(key)) {
251
+ bindEventProp(el, key, value, isRod);
252
+ return;
253
+ }
254
+ if (key === "class") {
255
+ if (hasDynamicClassValue(value)) {
256
+ registerNodeEffect(() => {
257
+ setClassValue(normalizeClass(value));
258
+ });
259
+ return;
260
+ }
261
+ setClassValue(normalizeClass(value));
262
+ return;
263
+ }
264
+ if (key === "style" && value && typeof value === "object") {
265
+ const styleApplier = createStyleObjectApplier(setStyleEntry);
266
+ if (hasDynamicStyleValue(value, isRod)) {
267
+ registerNodeEffect(() => {
268
+ styleApplier.apply(value, isRod);
269
+ });
270
+ return;
271
+ }
272
+ styleApplier.apply(value, isRod);
273
+ return;
274
+ }
275
+ if (key === "style" && (typeof value === "function" || isRod(value))) {
276
+ const styleApplier = createStyleObjectApplier(setStyleEntry);
277
+ registerNodeEffect(() => {
278
+ styleApplier.apply(value, isRod);
279
+ });
280
+ return;
281
+ }
282
+ if (isBindingValueKey(key) && typeof value === "function") {
283
+ registerNodeEffect(() => {
284
+ applyBindingValue(key, value());
285
+ });
286
+ return;
287
+ }
288
+ if (isBindingValueKey(key) && !isRod(value)) {
289
+ applyBindingValue(key, value);
290
+ return;
291
+ }
292
+ if (typeof value === "function") {
293
+ registerNodeEffect(() => {
294
+ setProp(key, value());
295
+ });
296
+ return;
297
+ }
298
+ if (key === "value" && isRod(value) && (tag === "input" || tag === "textarea" || tag === "select")) {
299
+ if (tag === "select" && !!el.multiple) {
300
+ bindRodSelectMultiple(el, value);
301
+ return;
302
+ }
303
+ bindRodValueControl(el, value, tag === "select" ? "change" : "input");
304
+ return;
305
+ }
306
+ if (key === "checked" && isRod(value) && tag === "input") {
307
+ if (String(el.type || "").toLowerCase() === "radio") {
308
+ bindRodRadioControl(el, value);
309
+ return;
310
+ }
311
+ bindRodCheckedControl(el, value);
312
+ return;
313
+ }
314
+ if (key === "files" && isRod(value) && isFileInputControl(el)) {
315
+ bindRodFilesControl(el, value);
316
+ return;
317
+ }
318
+ if (key === "selected" && isRod(value) && tag === "option") {
319
+ bindRodOptionSelected(el, value);
320
+ return;
321
+ }
322
+ if (isContentProp(key) && isRod(value)) {
323
+ registerNodeEffect(() => {
324
+ setProp(key, value.value);
325
+ });
326
+ return;
327
+ }
328
+ if (isRod(value)) {
329
+ const unbind = JSswift.rodBind(el, value, { key });
330
+ JSswift._registerCleanup(el, unbind);
331
+ return;
332
+ }
333
+ setProp(key, value);
334
+ }
335
+
336
+ applyRendererArgs(args, {
337
+ el,
338
+ isRod,
339
+ interpolationCursor,
340
+ appendChildValue,
341
+ appendRodText,
342
+ appendInterpolatedText,
343
+ appendDynamicChild,
344
+ bindProp
345
+ });
346
+
347
+ if (tag === "select" && typeof el._cmsApplySelectedValues === "function") {
348
+ el._cmsApplySelectedValues();
349
+ }
350
+
351
+ return el;
352
+ }
353
+
354
+ const _h = {};
355
+ const DOM_ELEMENTS = [
356
+
357
+ // ─────────────────────────────
358
+ // Document & Metadata
359
+ // ─────────────────────────────
360
+ "html",
361
+ "head",
362
+ "title",
363
+ "base",
364
+ "link",
365
+ "meta",
366
+ "style",
367
+
368
+ // ─────────────────────────────
369
+ // Sectioning
370
+ // ─────────────────────────────
371
+ "body",
372
+ "header",
373
+ "footer",
374
+ "main",
375
+ "section",
376
+ "article",
377
+ "aside",
378
+ "nav",
379
+
380
+ // ─────────────────────────────
381
+ // Headings
382
+ // ─────────────────────────────
383
+ "h1",
384
+ "h2",
385
+ "h3",
386
+ "h4",
387
+ "h5",
388
+ "h6",
389
+
390
+ // ─────────────────────────────
391
+ // Text content
392
+ // ─────────────────────────────
393
+ "p",
394
+ "div",
395
+ "span",
396
+ "pre",
397
+ "blockquote",
398
+ "hr",
399
+ "br",
400
+ "address",
401
+
402
+ // ─────────────────────────────
403
+ // Inline text semantics
404
+ // ─────────────────────────────
405
+ "a",
406
+ "abbr",
407
+ "b",
408
+ "bdi",
409
+ "bdo",
410
+ "cite",
411
+ "code",
412
+ "data",
413
+ "dfn",
414
+ "em",
415
+ "i",
416
+ "kbd",
417
+ "mark",
418
+ "q",
419
+ "rp",
420
+ "rt",
421
+ "ruby",
422
+ "s",
423
+ "samp",
424
+ "small",
425
+ "strong",
426
+ "sub",
427
+ "sup",
428
+ "time",
429
+ "u",
430
+ "var",
431
+ "wbr",
432
+
433
+ // ─────────────────────────────
434
+ // Lists
435
+ // ─────────────────────────────
436
+ "ul",
437
+ "ol",
438
+ "li",
439
+ "dl",
440
+ "dt",
441
+ "dd",
442
+
443
+ // ─────────────────────────────
444
+ // Tables
445
+ // ─────────────────────────────
446
+ "table",
447
+ "caption",
448
+ "colgroup",
449
+ "col",
450
+ "thead",
451
+ "tbody",
452
+ "tfoot",
453
+ "tr",
454
+ "th",
455
+ "td",
456
+
457
+ // ─────────────────────────────
458
+ // Forms
459
+ // ─────────────────────────────
460
+ "form",
461
+ "label",
462
+ "input",
463
+ "textarea",
464
+ "button",
465
+ "select",
466
+ "option",
467
+ "optgroup",
468
+ "fieldset",
469
+ "legend",
470
+ "datalist",
471
+ "output",
472
+ "progress",
473
+ "meter",
474
+
475
+ // ─────────────────────────────
476
+ // Embedded content
477
+ // ─────────────────────────────
478
+ "img",
479
+ "picture",
480
+ "source",
481
+ "iframe",
482
+ "embed",
483
+ "object",
484
+ "param",
485
+
486
+ // ─────────────────────────────
487
+ // Media
488
+ // ─────────────────────────────
489
+ "audio",
490
+ "video",
491
+ "track",
492
+
493
+ // ─────────────────────────────
494
+ // Interactive
495
+ // ─────────────────────────────
496
+ "details",
497
+ "summary",
498
+ "dialog",
499
+
500
+ // ─────────────────────────────
501
+ // Scripting
502
+ // ─────────────────────────────
503
+ "script",
504
+ "noscript",
505
+ "canvas",
506
+ "template",
507
+ "slot",
508
+
509
+ // ─────────────────────────────
510
+ // SVG (principali)
511
+ // ─────────────────────────────
512
+ "svg",
513
+ "g",
514
+ "path",
515
+ "circle",
516
+ "ellipse",
517
+ "line",
518
+ "rect",
519
+ "polygon",
520
+ "polyline",
521
+ "text",
522
+ "tspan",
523
+ "defs",
524
+ "linearGradient",
525
+ "radialGradient",
526
+ "stop",
527
+ "use",
528
+ "symbol",
529
+ "clipPath",
530
+ "mask",
531
+ "pattern",
532
+ "filter",
533
+ "feGaussianBlur",
534
+ "feOffset",
535
+ "feBlend",
536
+ "feColorMatrix"
537
+ ];
538
+ DOM_ELEMENTS.forEach(tag => {
539
+ window._[tag] = (...args) => createElement(tag, ...args);
540
+ });
541
+ _.DOM_ELEMENTS = DOM_ELEMENTS;
542
+
543
+ _.fragment = (...children) => children;
544
+ _.dynamic = function (renderFn) {
545
+ const anchor = document.createComment("dyn");
546
+ const parent = document.createDocumentFragment();
547
+ parent.appendChild(anchor);
548
+
549
+ let current = null;
550
+
551
+ JSswift.reactive.effect(() => {
552
+ const next = renderFn();
553
+
554
+ // rimuovi current
555
+ if (current && current.parentNode) current.parentNode.removeChild(current);
556
+ current = null;
557
+
558
+ if (next == null) return;
559
+
560
+ // normalizza: node o string
561
+ let node = next;
562
+ if (typeof next === "string" || typeof next === "number") {
563
+ node = document.createTextNode(String(next));
564
+ }
565
+
566
+ // inserisci dopo anchor
567
+ const p = anchor.parentNode;
568
+ if (p && node && node.nodeType) {
569
+ p.insertBefore(node, anchor.nextSibling);
570
+ current = node;
571
+ }
572
+ });
573
+
574
+ return parent; // fragment con anchor
575
+ };