lightview 2.2.2 → 2.3.4
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/cDOMIntro.md +279 -0
- package/docs/about.html +15 -12
- package/docs/api/computed.html +1 -1
- package/docs/api/effects.html +1 -1
- package/docs/api/elements.html +56 -25
- package/docs/api/enhance.html +1 -1
- package/docs/api/hypermedia.html +1 -1
- package/docs/api/index.html +1 -1
- package/docs/api/nav.html +28 -3
- package/docs/api/signals.html +1 -1
- package/docs/api/state.html +283 -85
- package/docs/assets/js/examplify.js +2 -1
- package/docs/cdom-nav.html +3 -2
- package/docs/cdom.html +383 -114
- package/jprx/README.md +112 -71
- package/jprx/helpers/state.js +21 -0
- package/jprx/package.json +1 -1
- package/jprx/parser.js +136 -86
- package/jprx/specs/expressions.json +71 -0
- package/jprx/specs/helpers.json +150 -0
- package/lightview-all.js +618 -431
- package/lightview-cdom.js +311 -605
- package/lightview-router.js +6 -0
- package/lightview-x.js +226 -54
- package/lightview.js +351 -42
- package/package.json +2 -1
- package/src/lightview-cdom.js +211 -315
- package/src/lightview-router.js +10 -0
- package/src/lightview-x.js +121 -1
- package/src/lightview.js +88 -16
- package/src/reactivity/signal.js +73 -29
- package/src/reactivity/state.js +84 -21
- package/tests/cdom/fixtures/helpers.cdomc +24 -24
- package/tests/cdom/helpers.test.js +28 -28
- package/tests/cdom/parser.test.js +39 -114
- package/tests/cdom/reactivity.test.js +32 -29
- package/tests/jprx/spec.test.js +99 -0
- package/tests/cdom/loader.test.js +0 -125
package/lightview.js
CHANGED
|
@@ -3,27 +3,45 @@
|
|
|
3
3
|
const _LV = globalThis.__LIGHTVIEW_INTERNALS__ || (globalThis.__LIGHTVIEW_INTERNALS__ = {
|
|
4
4
|
currentEffect: null,
|
|
5
5
|
registry: /* @__PURE__ */ new Map(),
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// Global name -> Signal/Proxy
|
|
7
|
+
localRegistries: /* @__PURE__ */ new WeakMap(),
|
|
8
|
+
// Object/Element -> Map(name -> Signal/Proxy)
|
|
9
|
+
futureSignals: /* @__PURE__ */ new Map(),
|
|
10
|
+
// name -> Set of (signal) => void
|
|
11
|
+
schemas: /* @__PURE__ */ new Map(),
|
|
12
|
+
// name -> Schema (Draft 7+ or Shorthand)
|
|
13
|
+
parents: /* @__PURE__ */ new WeakMap(),
|
|
14
|
+
// Proxy -> Parent (Proxy/Element)
|
|
15
|
+
helpers: /* @__PURE__ */ new Map(),
|
|
16
|
+
// name -> function (used for transforms and expressions)
|
|
17
|
+
hooks: {
|
|
18
|
+
validate: (value, schema) => true
|
|
19
|
+
// Hook for extensions (like JPRX) to provide full validation
|
|
20
|
+
}
|
|
8
21
|
});
|
|
22
|
+
const lookup = (name, scope) => {
|
|
23
|
+
let current = scope;
|
|
24
|
+
while (current && typeof current === "object") {
|
|
25
|
+
const registry2 = _LV.localRegistries.get(current);
|
|
26
|
+
if (registry2 && registry2.has(name)) return registry2.get(name);
|
|
27
|
+
current = current.parentElement || _LV.parents.get(current);
|
|
28
|
+
}
|
|
29
|
+
return _LV.registry.get(name);
|
|
30
|
+
};
|
|
9
31
|
const signal = (initialValue, optionsOrName) => {
|
|
10
|
-
|
|
32
|
+
const name = typeof optionsOrName === "string" ? optionsOrName : optionsOrName == null ? void 0 : optionsOrName.name;
|
|
11
33
|
const storage = optionsOrName == null ? void 0 : optionsOrName.storage;
|
|
34
|
+
const scope = optionsOrName == null ? void 0 : optionsOrName.scope;
|
|
12
35
|
if (name && storage) {
|
|
13
36
|
try {
|
|
14
37
|
const stored = storage.getItem(name);
|
|
15
|
-
if (stored !== null)
|
|
16
|
-
initialValue = JSON.parse(stored);
|
|
17
|
-
}
|
|
38
|
+
if (stored !== null) initialValue = JSON.parse(stored);
|
|
18
39
|
} catch (e) {
|
|
19
40
|
}
|
|
20
41
|
}
|
|
21
42
|
let value = initialValue;
|
|
22
43
|
const subscribers = /* @__PURE__ */ new Set();
|
|
23
|
-
const f = (...args) =>
|
|
24
|
-
if (args.length === 0) return f.value;
|
|
25
|
-
f.value = args[0];
|
|
26
|
-
};
|
|
44
|
+
const f = (...args) => args.length === 0 ? f.value : f.value = args[0];
|
|
27
45
|
Object.defineProperty(f, "value", {
|
|
28
46
|
get() {
|
|
29
47
|
if (_LV.currentEffect) {
|
|
@@ -46,21 +64,39 @@
|
|
|
46
64
|
}
|
|
47
65
|
});
|
|
48
66
|
if (name) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
const registry2 = scope && typeof scope === "object" ? _LV.localRegistries.get(scope) || _LV.localRegistries.set(scope, /* @__PURE__ */ new Map()).get(scope) : _LV.registry;
|
|
68
|
+
if (registry2 && registry2.has(name) && registry2.get(name) !== f) {
|
|
69
|
+
throw new Error(`Lightview: A signal or state with the name "${name}" is already registered.`);
|
|
70
|
+
}
|
|
71
|
+
if (registry2) registry2.set(name, f);
|
|
72
|
+
const futures = _LV.futureSignals.get(name);
|
|
73
|
+
if (futures) {
|
|
74
|
+
futures.forEach((resolve) => resolve(f));
|
|
55
75
|
}
|
|
56
76
|
}
|
|
57
77
|
return f;
|
|
58
78
|
};
|
|
59
|
-
const getSignal = (name,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
const getSignal = (name, defaultValueOrOptions) => {
|
|
80
|
+
const options = typeof defaultValueOrOptions === "object" && defaultValueOrOptions !== null ? defaultValueOrOptions : { defaultValue: defaultValueOrOptions };
|
|
81
|
+
const { scope, defaultValue } = options;
|
|
82
|
+
const existing = lookup(name, scope);
|
|
83
|
+
if (existing) return existing;
|
|
84
|
+
if (defaultValue !== void 0) return signal(defaultValue, { name, scope });
|
|
85
|
+
const future = signal(void 0);
|
|
86
|
+
const handler = (realSignal) => {
|
|
87
|
+
const hasValue = realSignal && (typeof realSignal === "object" || typeof realSignal === "function") && "value" in realSignal;
|
|
88
|
+
if (hasValue) {
|
|
89
|
+
future.value = realSignal.value;
|
|
90
|
+
effect(() => {
|
|
91
|
+
future.value = realSignal.value;
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
future.value = realSignal;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
if (!_LV.futureSignals.has(name)) _LV.futureSignals.set(name, /* @__PURE__ */ new Set());
|
|
98
|
+
_LV.futureSignals.get(name).add(handler);
|
|
99
|
+
return future;
|
|
64
100
|
};
|
|
65
101
|
signal.get = getSignal;
|
|
66
102
|
const effect = (fn) => {
|
|
@@ -96,9 +132,66 @@
|
|
|
96
132
|
return sig;
|
|
97
133
|
};
|
|
98
134
|
const getRegistry = () => _LV.registry;
|
|
135
|
+
const internals = _LV;
|
|
136
|
+
const stateCache = /* @__PURE__ */ new WeakMap();
|
|
137
|
+
const stateSignals = /* @__PURE__ */ new WeakMap();
|
|
138
|
+
const stateSchemas = /* @__PURE__ */ new WeakMap();
|
|
139
|
+
const { parents, schemas, hooks } = internals;
|
|
140
|
+
const validate = (target, prop, value, schema) => {
|
|
141
|
+
var _a, _b;
|
|
142
|
+
const current = target[prop];
|
|
143
|
+
const type = typeof current;
|
|
144
|
+
const isNew = !(prop in target);
|
|
145
|
+
let behavior = schema;
|
|
146
|
+
if (typeof schema === "object" && schema !== null) behavior = schema.type;
|
|
147
|
+
if (behavior === "auto" && isNew) throw new Error(`Lightview: Cannot add new property "${prop}" to fixed 'auto' state.`);
|
|
148
|
+
if (behavior === "polymorphic" || typeof behavior === "object" && (behavior == null ? void 0 : behavior.coerce)) {
|
|
149
|
+
if (type === "number") return Number(value);
|
|
150
|
+
if (type === "boolean") return Boolean(value);
|
|
151
|
+
if (type === "string") return String(value);
|
|
152
|
+
} else if (behavior === "auto" || behavior === "dynamic") {
|
|
153
|
+
if (!isNew && typeof value !== type) {
|
|
154
|
+
throw new Error(`Lightview: Type mismatch for "${prop}". Expected ${type}, got ${typeof value}.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (typeof schema === "object" && schema !== null && schema.transform) {
|
|
158
|
+
const trans = schema.transform;
|
|
159
|
+
const transformFn = typeof trans === "function" ? trans : internals.helpers.get(trans) || ((_b = (_a = globalThis.Lightview) == null ? void 0 : _a.helpers) == null ? void 0 : _b[trans]);
|
|
160
|
+
if (transformFn) value = transformFn(value);
|
|
161
|
+
}
|
|
162
|
+
if (hooks.validate(value, schema) === false) {
|
|
163
|
+
throw new Error(`Lightview: Validation failed for "${prop}".`);
|
|
164
|
+
}
|
|
165
|
+
return value;
|
|
166
|
+
};
|
|
99
167
|
const protoMethods = (proto, test) => Object.getOwnPropertyNames(proto).filter((k) => typeof proto[k] === "function" && test(k));
|
|
100
|
-
protoMethods(Date.prototype, (k) => /^(to|get|valueOf)/.test(k));
|
|
101
|
-
protoMethods(Date.prototype, (k) => /^set/.test(k));
|
|
168
|
+
const DATE_TRACKING = protoMethods(Date.prototype, (k) => /^(to|get|valueOf)/.test(k));
|
|
169
|
+
const DATE_MUTATING = protoMethods(Date.prototype, (k) => /^set/.test(k));
|
|
170
|
+
const ARRAY_TRACKING = [
|
|
171
|
+
"map",
|
|
172
|
+
"forEach",
|
|
173
|
+
"filter",
|
|
174
|
+
"find",
|
|
175
|
+
"findIndex",
|
|
176
|
+
"some",
|
|
177
|
+
"every",
|
|
178
|
+
"reduce",
|
|
179
|
+
"reduceRight",
|
|
180
|
+
"includes",
|
|
181
|
+
"indexOf",
|
|
182
|
+
"lastIndexOf",
|
|
183
|
+
"join",
|
|
184
|
+
"slice",
|
|
185
|
+
"concat",
|
|
186
|
+
"flat",
|
|
187
|
+
"flatMap",
|
|
188
|
+
"at",
|
|
189
|
+
"entries",
|
|
190
|
+
"keys",
|
|
191
|
+
"values"
|
|
192
|
+
];
|
|
193
|
+
const ARRAY_MUTATING = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse", "fill", "copyWithin"];
|
|
194
|
+
const ARRAY_ITERATION = ["map", "forEach", "filter", "find", "findIndex", "some", "every", "flatMap"];
|
|
102
195
|
const getOrSet = (map, key, factory) => {
|
|
103
196
|
let v = map.get(key);
|
|
104
197
|
if (!v) {
|
|
@@ -107,6 +200,169 @@
|
|
|
107
200
|
}
|
|
108
201
|
return v;
|
|
109
202
|
};
|
|
203
|
+
const proxyGet = (target, prop, receiver, signals) => {
|
|
204
|
+
if (prop === "__parent__") return parents.get(receiver);
|
|
205
|
+
if (!signals.has(prop)) {
|
|
206
|
+
signals.set(prop, signal(Reflect.get(target, prop, receiver)));
|
|
207
|
+
}
|
|
208
|
+
const signal$1 = signals.get(prop);
|
|
209
|
+
const val = signal$1.value;
|
|
210
|
+
if (typeof val === "object" && val !== null) {
|
|
211
|
+
const childProxy = state(val);
|
|
212
|
+
parents.set(childProxy, receiver);
|
|
213
|
+
return childProxy;
|
|
214
|
+
}
|
|
215
|
+
return val;
|
|
216
|
+
};
|
|
217
|
+
const proxySet = (target, prop, value, receiver, signals) => {
|
|
218
|
+
const schema = stateSchemas.get(receiver);
|
|
219
|
+
const validatedValue = schema ? validate(target, prop, value, schema) : value;
|
|
220
|
+
if (!signals.has(prop)) {
|
|
221
|
+
signals.set(prop, signal(Reflect.get(target, prop, receiver)));
|
|
222
|
+
}
|
|
223
|
+
const success = Reflect.set(target, prop, validatedValue, receiver);
|
|
224
|
+
const signal$1 = signals.get(prop);
|
|
225
|
+
if (success && signal$1) signal$1.value = validatedValue;
|
|
226
|
+
return success;
|
|
227
|
+
};
|
|
228
|
+
const createSpecialProxy = (obj, monitor, trackingProps = []) => {
|
|
229
|
+
const signals = getOrSet(stateSignals, obj, () => /* @__PURE__ */ new Map());
|
|
230
|
+
if (!signals.has(monitor)) {
|
|
231
|
+
const initialValue = typeof obj[monitor] === "function" ? obj[monitor].call(obj) : obj[monitor];
|
|
232
|
+
signals.set(monitor, signal(initialValue));
|
|
233
|
+
}
|
|
234
|
+
const isDate = obj instanceof Date;
|
|
235
|
+
const isArray = Array.isArray(obj);
|
|
236
|
+
const trackingMethods = isDate ? DATE_TRACKING : isArray ? ARRAY_TRACKING : trackingProps;
|
|
237
|
+
const mutatingMethods = isDate ? DATE_MUTATING : isArray ? ARRAY_MUTATING : [];
|
|
238
|
+
return new Proxy(obj, {
|
|
239
|
+
get(target, prop, receiver) {
|
|
240
|
+
if (prop === "__parent__") return parents.get(receiver);
|
|
241
|
+
const value = target[prop];
|
|
242
|
+
if (typeof value === "function") {
|
|
243
|
+
const isTracking = trackingMethods.includes(prop);
|
|
244
|
+
const isMutating = mutatingMethods.includes(prop);
|
|
245
|
+
return function(...args) {
|
|
246
|
+
if (isTracking) {
|
|
247
|
+
const sig = signals.get(monitor);
|
|
248
|
+
if (sig) void sig.value;
|
|
249
|
+
}
|
|
250
|
+
const startValue = typeof target[monitor] === "function" ? target[monitor].call(target) : target[monitor];
|
|
251
|
+
if (isArray && ARRAY_ITERATION.includes(prop) && typeof args[0] === "function") {
|
|
252
|
+
const originalCallback = args[0];
|
|
253
|
+
args[0] = function(element2, index, array) {
|
|
254
|
+
const wrappedElement = typeof element2 === "object" && element2 !== null ? state(element2) : element2;
|
|
255
|
+
if (wrappedElement && typeof wrappedElement === "object") {
|
|
256
|
+
parents.set(wrappedElement, receiver);
|
|
257
|
+
}
|
|
258
|
+
return originalCallback.call(this, wrappedElement, index, array);
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const result = value.apply(target, args);
|
|
262
|
+
const endValue = typeof target[monitor] === "function" ? target[monitor].call(target) : target[monitor];
|
|
263
|
+
if (startValue !== endValue || isMutating) {
|
|
264
|
+
const sig = signals.get(monitor);
|
|
265
|
+
if (sig && sig.value !== endValue) {
|
|
266
|
+
sig.value = endValue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return result;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (prop === monitor) {
|
|
273
|
+
const sig = signals.get(monitor);
|
|
274
|
+
return sig ? sig.value : Reflect.get(target, prop, receiver);
|
|
275
|
+
}
|
|
276
|
+
if (isArray && !isNaN(parseInt(prop))) {
|
|
277
|
+
const monitorSig = signals.get(monitor);
|
|
278
|
+
if (monitorSig) void monitorSig.value;
|
|
279
|
+
}
|
|
280
|
+
return proxyGet(target, prop, receiver, signals);
|
|
281
|
+
},
|
|
282
|
+
set(target, prop, value, receiver) {
|
|
283
|
+
if (prop === monitor) {
|
|
284
|
+
const success = Reflect.set(target, prop, value, receiver);
|
|
285
|
+
if (success) {
|
|
286
|
+
const sig = signals.get(monitor);
|
|
287
|
+
if (sig) sig.value = value;
|
|
288
|
+
}
|
|
289
|
+
return success;
|
|
290
|
+
}
|
|
291
|
+
return proxySet(target, prop, value, receiver, signals);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
const state = (obj, optionsOrName) => {
|
|
296
|
+
if (typeof obj !== "object" || obj === null) return obj;
|
|
297
|
+
const name = typeof optionsOrName === "string" ? optionsOrName : optionsOrName == null ? void 0 : optionsOrName.name;
|
|
298
|
+
const storage = optionsOrName == null ? void 0 : optionsOrName.storage;
|
|
299
|
+
const scope = optionsOrName == null ? void 0 : optionsOrName.scope;
|
|
300
|
+
const schema = optionsOrName == null ? void 0 : optionsOrName.schema;
|
|
301
|
+
if (name && storage) {
|
|
302
|
+
try {
|
|
303
|
+
const item = storage.getItem(name);
|
|
304
|
+
if (item) {
|
|
305
|
+
const loaded = JSON.parse(item);
|
|
306
|
+
Array.isArray(obj) && Array.isArray(loaded) ? (obj.length = 0, obj.push(...loaded)) : Object.assign(obj, loaded);
|
|
307
|
+
}
|
|
308
|
+
} catch (e) {
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
let proxy = stateCache.get(obj);
|
|
312
|
+
if (!proxy) {
|
|
313
|
+
const isArray = Array.isArray(obj), isDate = obj instanceof Date;
|
|
314
|
+
const isSpecial = isArray || isDate;
|
|
315
|
+
const monitor = isArray ? "length" : isDate ? "getTime" : null;
|
|
316
|
+
if (isSpecial || !(obj instanceof RegExp || obj instanceof Map || obj instanceof Set || obj instanceof WeakMap || obj instanceof WeakSet)) {
|
|
317
|
+
proxy = isSpecial ? createSpecialProxy(obj, monitor) : new Proxy(obj, {
|
|
318
|
+
get(t, p, r) {
|
|
319
|
+
if (p === "__parent__") return parents.get(r);
|
|
320
|
+
return proxyGet(t, p, r, getOrSet(stateSignals, t, () => /* @__PURE__ */ new Map()));
|
|
321
|
+
},
|
|
322
|
+
set(t, p, v, r) {
|
|
323
|
+
return proxySet(t, p, v, r, getOrSet(stateSignals, t, () => /* @__PURE__ */ new Map()));
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
stateCache.set(obj, proxy);
|
|
327
|
+
} else return obj;
|
|
328
|
+
}
|
|
329
|
+
if (schema) stateSchemas.set(proxy, schema);
|
|
330
|
+
if (name && storage) {
|
|
331
|
+
effect(() => {
|
|
332
|
+
try {
|
|
333
|
+
storage.setItem(name, JSON.stringify(proxy));
|
|
334
|
+
} catch (e) {
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (name) {
|
|
339
|
+
const registry2 = scope && typeof scope === "object" ? internals.localRegistries.get(scope) || internals.localRegistries.set(scope, /* @__PURE__ */ new Map()).get(scope) : getRegistry();
|
|
340
|
+
if (registry2 && registry2.has(name) && registry2.get(name) !== proxy) {
|
|
341
|
+
throw new Error(`Lightview: A signal or state with the name "${name}" is already registered.`);
|
|
342
|
+
}
|
|
343
|
+
if (registry2) registry2.set(name, proxy);
|
|
344
|
+
const futures = internals.futureSignals.get(name);
|
|
345
|
+
if (futures) {
|
|
346
|
+
futures.forEach((resolve) => resolve(proxy));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return proxy;
|
|
350
|
+
};
|
|
351
|
+
const getState = (name, defaultValueOrOptions) => {
|
|
352
|
+
const options = typeof defaultValueOrOptions === "object" && defaultValueOrOptions !== null ? defaultValueOrOptions : { defaultValue: defaultValueOrOptions };
|
|
353
|
+
const { scope, defaultValue } = options;
|
|
354
|
+
const existing = lookup(name, scope);
|
|
355
|
+
if (existing) return existing;
|
|
356
|
+
if (defaultValue !== void 0) return state(defaultValue, { name, scope });
|
|
357
|
+
const future = signal(void 0);
|
|
358
|
+
const handler = (realState) => {
|
|
359
|
+
future.value = realState;
|
|
360
|
+
};
|
|
361
|
+
if (!internals.futureSignals.has(name)) internals.futureSignals.set(name, /* @__PURE__ */ new Set());
|
|
362
|
+
internals.futureSignals.get(name).add(handler);
|
|
363
|
+
return future;
|
|
364
|
+
};
|
|
365
|
+
state.get = getState;
|
|
110
366
|
const core = {
|
|
111
367
|
get currentEffect() {
|
|
112
368
|
return (globalThis.__LIGHTVIEW_INTERNALS__ || (globalThis.__LIGHTVIEW_INTERNALS__ = {})).currentEffect;
|
|
@@ -115,10 +371,42 @@
|
|
|
115
371
|
const nodeState = /* @__PURE__ */ new WeakMap();
|
|
116
372
|
const nodeStateFactory = () => ({ effects: [], onmount: null, onunmount: null });
|
|
117
373
|
const registry = getRegistry();
|
|
374
|
+
const scrollMemory = /* @__PURE__ */ new Map();
|
|
375
|
+
const initScrollMemory = () => {
|
|
376
|
+
if (typeof document === "undefined") return;
|
|
377
|
+
document.addEventListener("scroll", (e) => {
|
|
378
|
+
const el = e.target;
|
|
379
|
+
if (el === document || el === document.documentElement) return;
|
|
380
|
+
const key = el.id || el.getAttribute && el.getAttribute("data-preserve-scroll");
|
|
381
|
+
if (key) {
|
|
382
|
+
scrollMemory.set(key, { top: el.scrollTop, left: el.scrollLeft });
|
|
383
|
+
}
|
|
384
|
+
}, true);
|
|
385
|
+
};
|
|
386
|
+
if (typeof document !== "undefined") {
|
|
387
|
+
if (document.readyState === "loading") {
|
|
388
|
+
document.addEventListener("DOMContentLoaded", initScrollMemory);
|
|
389
|
+
} else {
|
|
390
|
+
initScrollMemory();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const saveScrolls = () => new Map(scrollMemory);
|
|
394
|
+
const restoreScrolls = (map, root = document) => {
|
|
395
|
+
if (!map || map.size === 0) return;
|
|
396
|
+
requestAnimationFrame(() => {
|
|
397
|
+
map.forEach((pos, key) => {
|
|
398
|
+
const node = document.getElementById(key) || document.querySelector(`[data-preserve-scroll="${key}"]`);
|
|
399
|
+
if (node) {
|
|
400
|
+
node.scrollTop = pos.top;
|
|
401
|
+
node.scrollLeft = pos.left;
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
};
|
|
118
406
|
const trackEffect = (node, effectFn) => {
|
|
119
|
-
const
|
|
120
|
-
if (!
|
|
121
|
-
|
|
407
|
+
const state2 = getOrSet(nodeState, node, nodeStateFactory);
|
|
408
|
+
if (!state2.effects) state2.effects = [];
|
|
409
|
+
state2.effects.push(effectFn);
|
|
122
410
|
};
|
|
123
411
|
const SHADOW_DOM_MARKER = Symbol("lightview.shadowDOM");
|
|
124
412
|
const createShadowDOMMarker = (attributes, children) => ({
|
|
@@ -299,8 +587,8 @@
|
|
|
299
587
|
const reactiveAttrs = {};
|
|
300
588
|
for (let [key, value] of Object.entries(attributes)) {
|
|
301
589
|
if (key === "onmount" || key === "onunmount") {
|
|
302
|
-
const
|
|
303
|
-
|
|
590
|
+
const state2 = getOrSet(nodeState, domNode, nodeStateFactory);
|
|
591
|
+
state2[key] = value;
|
|
304
592
|
if (key === "onmount" && domNode.isConnected) {
|
|
305
593
|
value(domNode);
|
|
306
594
|
}
|
|
@@ -312,6 +600,26 @@
|
|
|
312
600
|
domNode.setAttribute(key, value);
|
|
313
601
|
}
|
|
314
602
|
reactiveAttrs[key] = value;
|
|
603
|
+
} else if (typeof value === "object" && value !== null && Lightview.hooks.processAttribute) {
|
|
604
|
+
const processed = Lightview.hooks.processAttribute(domNode, key, value);
|
|
605
|
+
if (processed !== void 0) {
|
|
606
|
+
reactiveAttrs[key] = processed;
|
|
607
|
+
} else if (key === "style") {
|
|
608
|
+
Object.entries(value).forEach(([styleKey, styleValue]) => {
|
|
609
|
+
if (typeof styleValue === "function") {
|
|
610
|
+
const runner = effect(() => {
|
|
611
|
+
domNode.style[styleKey] = styleValue();
|
|
612
|
+
});
|
|
613
|
+
trackEffect(domNode, runner);
|
|
614
|
+
} else {
|
|
615
|
+
domNode.style[styleKey] = styleValue;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
reactiveAttrs[key] = value;
|
|
619
|
+
} else {
|
|
620
|
+
setAttributeValue(domNode, key, value);
|
|
621
|
+
reactiveAttrs[key] = value;
|
|
622
|
+
}
|
|
315
623
|
} else if (typeof value === "function") {
|
|
316
624
|
const runner = effect(() => {
|
|
317
625
|
const result = value();
|
|
@@ -323,18 +631,6 @@
|
|
|
323
631
|
});
|
|
324
632
|
trackEffect(domNode, runner);
|
|
325
633
|
reactiveAttrs[key] = value;
|
|
326
|
-
} else if (key === "style" && typeof value === "object") {
|
|
327
|
-
Object.entries(value).forEach(([styleKey, styleValue]) => {
|
|
328
|
-
if (typeof styleValue === "function") {
|
|
329
|
-
const runner = effect(() => {
|
|
330
|
-
domNode.style[styleKey] = styleValue();
|
|
331
|
-
});
|
|
332
|
-
trackEffect(domNode, runner);
|
|
333
|
-
} else {
|
|
334
|
-
domNode.style[styleKey] = styleValue;
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
reactiveAttrs[key] = value;
|
|
338
634
|
} else {
|
|
339
635
|
setAttributeValue(domNode, key, value);
|
|
340
636
|
reactiveAttrs[key] = value;
|
|
@@ -513,6 +809,9 @@
|
|
|
513
809
|
}
|
|
514
810
|
});
|
|
515
811
|
const Lightview = {
|
|
812
|
+
state,
|
|
813
|
+
getState,
|
|
814
|
+
registerSchema: (name, definition) => internals.schemas.set(name, definition),
|
|
516
815
|
signal,
|
|
517
816
|
get: signal.get,
|
|
518
817
|
computed,
|
|
@@ -527,14 +826,24 @@
|
|
|
527
826
|
hooks: {
|
|
528
827
|
onNonStandardHref: null,
|
|
529
828
|
processChild: null,
|
|
530
|
-
|
|
829
|
+
processAttribute: null,
|
|
830
|
+
validateUrl: null,
|
|
831
|
+
validate: (value, schema) => internals.hooks.validate(value, schema)
|
|
531
832
|
},
|
|
532
833
|
// Internals exposed for extensions
|
|
533
834
|
internals: {
|
|
534
835
|
core,
|
|
535
836
|
domToElement,
|
|
536
837
|
wrapDomElement,
|
|
537
|
-
setupChildren
|
|
838
|
+
setupChildren,
|
|
839
|
+
trackEffect,
|
|
840
|
+
saveScrolls,
|
|
841
|
+
restoreScrolls,
|
|
842
|
+
localRegistries: internals.localRegistries,
|
|
843
|
+
futureSignals: internals.futureSignals,
|
|
844
|
+
schemas: internals.schemas,
|
|
845
|
+
parents: internals.parents,
|
|
846
|
+
hooks: internals.hooks
|
|
538
847
|
}
|
|
539
848
|
};
|
|
540
849
|
if (typeof module !== "undefined" && module.exports) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightview",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.4",
|
|
4
4
|
"description": "A lightweight reactive UI library with features of Bau, Juris, and HTMX",
|
|
5
5
|
"main": "lightview.js",
|
|
6
6
|
"workspaces": [
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"wrangler": "^4.54.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"jprx": "^1.2.0",
|
|
38
39
|
"linkedom": "^0.18.12",
|
|
39
40
|
"marked": "^17.0.1"
|
|
40
41
|
}
|