@jsswift/core 1.0.52 → 1.0.53
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/dist/cms.js +172 -161
- package/dist/cms.min.js +1 -1
- package/dist/min-cms.js +1 -1
- package/package.json +1 -1
- package/src/10-overlay.js +3 -4
- package/src/31-rod-model.js +169 -157
package/src/31-rod-model.js
CHANGED
|
@@ -1,157 +1,169 @@
|
|
|
1
|
-
// ===============================
|
|
2
|
-
// Two-way binding: input <-> rod
|
|
3
|
-
// ===============================
|
|
4
|
-
// ===============================
|
|
5
|
-
// Two-way binding avanzato
|
|
6
|
-
// input / checkbox / radio / select
|
|
7
|
-
// ===============================
|
|
8
|
-
|
|
9
|
-
function getInputKind(el) {
|
|
10
|
-
if (!el || !el.tagName) return null;
|
|
11
|
-
|
|
12
|
-
const tag = el.tagName.toLowerCase();
|
|
13
|
-
|
|
14
|
-
if (tag === "input") {
|
|
15
|
-
const type = (el.type || "text").toLowerCase();
|
|
16
|
-
if (type === "checkbox") return "checkbox";
|
|
17
|
-
if (type === "radio") return "radio";
|
|
18
|
-
if (type === "number") return "number";
|
|
19
|
-
return "text";
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (tag === "select") {
|
|
23
|
-
return el.multiple ? "select-multiple" : "select";
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
JSswift.rodModel = function (target, rodObj, opts = {}) {
|
|
30
|
-
if (!rodObj || rodObj.type !== "rod") {
|
|
31
|
-
throw new Error("[JSswift.rodModel] rodObj deve essere un rod");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Supporta singolo elemento o lista (radio)
|
|
35
|
-
const elements = Array.isArray(target) || target instanceof NodeList
|
|
36
|
-
? Array.from(target)
|
|
37
|
-
: [target];
|
|
38
|
-
|
|
39
|
-
const eventName = opts.event ?? "change";
|
|
40
|
-
const parse = opts.parse ?? (v => v);
|
|
41
|
-
const format = opts.format ?? (v => v);
|
|
42
|
-
|
|
43
|
-
let syncing = false;
|
|
44
|
-
const disposers = [];
|
|
45
|
-
|
|
46
|
-
// === ROD → DOM ===
|
|
47
|
-
const updateDOM = (value) => {
|
|
48
|
-
for (const el of elements) {
|
|
49
|
-
if (!el) continue;
|
|
50
|
-
const kind = getInputKind(el);
|
|
51
|
-
|
|
52
|
-
switch (kind) {
|
|
53
|
-
case "checkbox":
|
|
54
|
-
el.checked = !!value;
|
|
55
|
-
break;
|
|
56
|
-
|
|
57
|
-
case "radio":
|
|
58
|
-
el.checked = el.value == value;
|
|
59
|
-
break;
|
|
60
|
-
|
|
61
|
-
case "select":
|
|
62
|
-
el.value = value ?? "";
|
|
63
|
-
break;
|
|
64
|
-
|
|
65
|
-
case "select-multiple":
|
|
66
|
-
const arr = Array.isArray(value) ? value.map(String) : [];
|
|
67
|
-
for (const opt of el.options) {
|
|
68
|
-
opt.selected = arr.includes(opt.value);
|
|
69
|
-
}
|
|
70
|
-
break;
|
|
71
|
-
|
|
72
|
-
case "number":
|
|
73
|
-
case "text":
|
|
74
|
-
default:
|
|
75
|
-
el.value = value ?? "";
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// bind rod → DOM (usa rod.action)
|
|
81
|
-
|
|
82
|
-
if (syncing) return;
|
|
83
|
-
syncing = true;
|
|
84
|
-
try {
|
|
85
|
-
updateDOM(format(v));
|
|
86
|
-
} finally {
|
|
87
|
-
syncing = false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
1
|
+
// ===============================
|
|
2
|
+
// Two-way binding: input <-> rod
|
|
3
|
+
// ===============================
|
|
4
|
+
// ===============================
|
|
5
|
+
// Two-way binding avanzato
|
|
6
|
+
// input / checkbox / radio / select
|
|
7
|
+
// ===============================
|
|
8
|
+
|
|
9
|
+
function getInputKind(el) {
|
|
10
|
+
if (!el || !el.tagName) return null;
|
|
11
|
+
|
|
12
|
+
const tag = el.tagName.toLowerCase();
|
|
13
|
+
|
|
14
|
+
if (tag === "input") {
|
|
15
|
+
const type = (el.type || "text").toLowerCase();
|
|
16
|
+
if (type === "checkbox") return "checkbox";
|
|
17
|
+
if (type === "radio") return "radio";
|
|
18
|
+
if (type === "number") return "number";
|
|
19
|
+
return "text";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (tag === "select") {
|
|
23
|
+
return el.multiple ? "select-multiple" : "select";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
JSswift.rodModel = function (target, rodObj, opts = {}) {
|
|
30
|
+
if (!rodObj || rodObj.type !== "rod") {
|
|
31
|
+
throw new Error("[JSswift.rodModel] rodObj deve essere un rod");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Supporta singolo elemento o lista (radio)
|
|
35
|
+
const elements = Array.isArray(target) || target instanceof NodeList
|
|
36
|
+
? Array.from(target)
|
|
37
|
+
: [target];
|
|
38
|
+
|
|
39
|
+
const eventName = opts.event ?? "change";
|
|
40
|
+
const parse = opts.parse ?? (v => v);
|
|
41
|
+
const format = opts.format ?? (v => v);
|
|
42
|
+
|
|
43
|
+
let syncing = false;
|
|
44
|
+
const disposers = [];
|
|
45
|
+
|
|
46
|
+
// === ROD → DOM ===
|
|
47
|
+
const updateDOM = (value) => {
|
|
48
|
+
for (const el of elements) {
|
|
49
|
+
if (!el) continue;
|
|
50
|
+
const kind = getInputKind(el);
|
|
51
|
+
|
|
52
|
+
switch (kind) {
|
|
53
|
+
case "checkbox":
|
|
54
|
+
el.checked = !!value;
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
case "radio":
|
|
58
|
+
el.checked = el.value == value;
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case "select":
|
|
62
|
+
el.value = value ?? "";
|
|
63
|
+
break;
|
|
64
|
+
|
|
65
|
+
case "select-multiple":
|
|
66
|
+
const arr = Array.isArray(value) ? value.map(String) : [];
|
|
67
|
+
for (const opt of el.options) {
|
|
68
|
+
opt.selected = arr.includes(opt.value);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case "number":
|
|
73
|
+
case "text":
|
|
74
|
+
default:
|
|
75
|
+
el.value = value ?? "";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// bind rod → DOM (usa rod.action)
|
|
81
|
+
const onValue = (v) => {
|
|
82
|
+
if (syncing) return;
|
|
83
|
+
syncing = true;
|
|
84
|
+
try {
|
|
85
|
+
updateDOM(format(v));
|
|
86
|
+
} finally {
|
|
87
|
+
syncing = false;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
rodObj.action(onValue);
|
|
91
|
+
|
|
92
|
+
// sync iniziale
|
|
93
|
+
updateDOM(format(rodObj.value));
|
|
94
|
+
|
|
95
|
+
// === DOM → ROD ===
|
|
96
|
+
for (const el of elements) {
|
|
97
|
+
if (!el) continue;
|
|
98
|
+
|
|
99
|
+
const kind = getInputKind(el);
|
|
100
|
+
|
|
101
|
+
const handler = () => {
|
|
102
|
+
if (syncing) return;
|
|
103
|
+
|
|
104
|
+
let next;
|
|
105
|
+
|
|
106
|
+
switch (kind) {
|
|
107
|
+
case "checkbox":
|
|
108
|
+
next = el.checked;
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
case "radio":
|
|
112
|
+
if (!el.checked) return;
|
|
113
|
+
next = el.value;
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case "select":
|
|
117
|
+
next = el.value;
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case "select-multiple":
|
|
121
|
+
next = Array.from(el.selectedOptions).map(o => o.value);
|
|
122
|
+
break;
|
|
123
|
+
|
|
124
|
+
case "number":
|
|
125
|
+
next = parse(el.value === "" ? null : Number(el.value));
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
case "text":
|
|
129
|
+
default:
|
|
130
|
+
next = parse(el.value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
syncing = true;
|
|
134
|
+
try {
|
|
135
|
+
if (rodObj.value !== next) rodObj.value = next;
|
|
136
|
+
} finally {
|
|
137
|
+
syncing = false;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
el.addEventListener(eventName, handler);
|
|
142
|
+
disposers.push(() => el.removeEventListener(eventName, handler));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Release both directions without disposing the caller-owned rod.
|
|
146
|
+
let disposed = false;
|
|
147
|
+
const dispose = () => {
|
|
148
|
+
if (disposed) return;
|
|
149
|
+
disposed = true;
|
|
150
|
+
disposers.splice(0).forEach(fn => fn());
|
|
151
|
+
const actionIndex = rodObj._actions?.indexOf(onValue) ?? -1;
|
|
152
|
+
if (actionIndex >= 0) rodObj._actions.splice(actionIndex, 1);
|
|
153
|
+
const hookIndex = rodObj._disposeHooks?.indexOf(dispose) ?? -1;
|
|
154
|
+
if (hookIndex >= 0 && !rodObj._disposed) rodObj._disposeHooks.splice(hookIndex, 1);
|
|
155
|
+
};
|
|
156
|
+
if (typeof rodObj.onDispose === "function") rodObj.onDispose(dispose);
|
|
157
|
+
return dispose;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
// input <-> signal (sugar)
|
|
162
|
+
JSswift.signalModel = function (inputEl, get, set, opts = {}) {
|
|
163
|
+
const r = JSswift.rodFromSignal(get, set);
|
|
164
|
+
const stopBinding = JSswift.rodModel(inputEl, r, opts);
|
|
165
|
+
return () => {
|
|
166
|
+
stopBinding();
|
|
167
|
+
r.dispose();
|
|
168
|
+
};
|
|
169
|
+
};
|