lightview 1.4.10-b → 1.6.3-b
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 +35 -1
- package/components/chart.html +76 -0
- package/components/gauge.html +57 -0
- package/examples/chart.html +64 -0
- package/examples/counter.html +3 -10
- package/examples/counter.test.mjs +47 -0
- package/examples/directives.html +2 -2
- package/examples/foreign.html +1 -1
- package/examples/forgeinform.html +3 -3
- package/examples/form.html +9 -15
- package/examples/gauge.html +16 -0
- package/examples/invalid-template-literals.html +45 -0
- package/examples/message.html +1 -1
- package/examples/remote.html +3 -1
- package/examples/remote.json +1 -1
- package/examples/sensors/index.html +30 -0
- package/examples/sensors/sensor-server.js +30 -0
- package/examples/template.html +1 -1
- package/examples/types.html +25 -5
- package/examples/xor.html +3 -3
- package/lightview.js +179 -327
- package/package.json +36 -36
- package/test/basic.html +26 -19
- package/test/basic.test.mjs +98 -21
- package/types.js +445 -0
package/lightview.js
CHANGED
|
@@ -85,7 +85,7 @@ const {observe} = (() => {
|
|
|
85
85
|
return observer;
|
|
86
86
|
}
|
|
87
87
|
const coerce = (value, toType) => {
|
|
88
|
-
if (value + "" === "null" || value + "" === "undefined") return value;
|
|
88
|
+
if (value + "" === "null" || value + "" === "undefined" || toType==="any") return value;
|
|
89
89
|
const type = typeof (value);
|
|
90
90
|
if (type === toType) return value;
|
|
91
91
|
if (toType === "number") return parseFloat(value + "");
|
|
@@ -103,7 +103,7 @@ const {observe} = (() => {
|
|
|
103
103
|
if (toType === "string") return value + "";
|
|
104
104
|
const isfunction = typeof (toType) === "function";
|
|
105
105
|
if ((toType === "object" || isfunction)) {
|
|
106
|
-
if (type === "object") {
|
|
106
|
+
if (type === "object" && isfunction) {
|
|
107
107
|
if (value instanceof toType) return value;
|
|
108
108
|
}
|
|
109
109
|
if (type === "string") {
|
|
@@ -164,6 +164,9 @@ const {observe} = (() => {
|
|
|
164
164
|
return JSON.stringify([...target]);
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
+
if(target instanceof Date) {
|
|
168
|
+
return Reflect.get(target,property);
|
|
169
|
+
}
|
|
167
170
|
let value = target[property];
|
|
168
171
|
const type = typeof (value);
|
|
169
172
|
if (CURRENTOBSERVER && typeof (property) !== "symbol" && type !== "function") {
|
|
@@ -220,12 +223,14 @@ const {observe} = (() => {
|
|
|
220
223
|
const createVarsProxy = (vars, component, constructor) => {
|
|
221
224
|
return new Proxy(vars, {
|
|
222
225
|
get(target, property) {
|
|
226
|
+
if(target instanceof Date) {
|
|
227
|
+
return Reflect.get(target,property);
|
|
228
|
+
}
|
|
223
229
|
let {value} = target[property] || {};
|
|
224
230
|
if (typeof (value) === "function") return value.bind(target);
|
|
225
231
|
return value;
|
|
226
232
|
},
|
|
227
233
|
set(target, property, newValue) {
|
|
228
|
-
//if(newValue && typeof(newValue)==="object" && newValue instanceof Promise) newValue = await newValue;
|
|
229
234
|
const event = new VariableEvent({variableName: property, value: newValue});
|
|
230
235
|
if (target[property] === undefined) {
|
|
231
236
|
target[property] = {type: "any", value: newValue}; // should we allow this, do first to prevent loops
|
|
@@ -233,19 +238,21 @@ const {observe} = (() => {
|
|
|
233
238
|
if (event.defaultPrevented) delete target[property].value;
|
|
234
239
|
return true;
|
|
235
240
|
}
|
|
236
|
-
const
|
|
241
|
+
const variable = target[property],
|
|
242
|
+
{type, value, shared, exported, constant, reactive, remote} = variable;
|
|
237
243
|
if (constant) throw new TypeError(`${property}:${type} is a constant`);
|
|
244
|
+
newValue = type.validate ? type.validate(newValue,target[property]) : coerce(newValue,type);
|
|
238
245
|
const newtype = typeof (newValue),
|
|
239
246
|
typetype = typeof (type);
|
|
240
|
-
if (newValue == null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
|
|
247
|
+
if (newValue == null || type === "any" || (newtype === type && typetype==="string") || (typetype === "function" && (newValue && newtype === "object" && newValue instanceof type) || variable.validityState?.valid)) {
|
|
241
248
|
if (value !== newValue) {
|
|
242
249
|
event.oldValue = value;
|
|
243
250
|
target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
|
|
244
251
|
target.postEvent.value("change", event);
|
|
245
252
|
if (event.defaultPrevented) {
|
|
246
253
|
target[property].value = value;
|
|
247
|
-
} else if(remote) {
|
|
248
|
-
handleRemote({variable:
|
|
254
|
+
} else if(remote && remote.put) {
|
|
255
|
+
remote.handleRemote({variable,config:remote.config,reactive},true);
|
|
249
256
|
}
|
|
250
257
|
}
|
|
251
258
|
return true;
|
|
@@ -261,22 +268,21 @@ const {observe} = (() => {
|
|
|
261
268
|
});
|
|
262
269
|
}
|
|
263
270
|
const createObserver = (domNode, framed) => {
|
|
264
|
-
const
|
|
271
|
+
const mobserver = new MutationObserver((mutations) => {
|
|
265
272
|
mutations.forEach((mutation) => {
|
|
273
|
+
const target = mutation.target;
|
|
266
274
|
if (mutation.type === "attributes") {
|
|
267
275
|
//if (framed) debugger;
|
|
268
276
|
const name = mutation.attributeName,
|
|
269
|
-
target = mutation.target,
|
|
270
277
|
value = target.getAttribute(name);
|
|
271
278
|
if (framed && name === "message" && target instanceof IFrameElement) {
|
|
272
|
-
if (value) console.log("message", value);
|
|
279
|
+
//if (value) console.log("message", value);
|
|
273
280
|
target.removeAttribute(name);
|
|
274
281
|
target.dispatchEvent(new CustomEvent("message", {detail: JSON.parse(value)}))
|
|
275
282
|
}
|
|
276
283
|
if (target.observedAttributes && target.observedAttributes.includes(name)) {
|
|
277
284
|
if (value !== mutation.oldValue) {
|
|
278
|
-
target.
|
|
279
|
-
if (target.attributeChangedCallback) target.attributeChangedCallback(name, value, mutation.oldValue);
|
|
285
|
+
target.setVariableValue(name, value);
|
|
280
286
|
}
|
|
281
287
|
}
|
|
282
288
|
} else if (mutation.type === "childList") {
|
|
@@ -286,11 +292,13 @@ const {observe} = (() => {
|
|
|
286
292
|
for (const target of mutation.addedNodes) {
|
|
287
293
|
if (target.connectedCallback) target.connectedCallback();
|
|
288
294
|
}
|
|
295
|
+
} else if(mutation.type === "characterData") {
|
|
296
|
+
if(target.characterDataMutationCallback) target.characterDataMutationCallback(target,mutation.oldValue,target.textContent);
|
|
289
297
|
}
|
|
290
298
|
});
|
|
291
299
|
});
|
|
292
|
-
|
|
293
|
-
return
|
|
300
|
+
mobserver.observe(domNode, {subtree: true, childList: true});
|
|
301
|
+
return mobserver;
|
|
294
302
|
}
|
|
295
303
|
const querySelectorAll = (node, selector) => {
|
|
296
304
|
const nodes = [...node.querySelectorAll(selector)],
|
|
@@ -313,17 +321,17 @@ const {observe} = (() => {
|
|
|
313
321
|
nodes.push(node);
|
|
314
322
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
315
323
|
let skip, pushed;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
324
|
+
[...node.attributes].forEach((attr) => {
|
|
325
|
+
if (attr.value.includes("${")) {
|
|
326
|
+
attr.template ||= attr.value;
|
|
327
|
+
pushed = true;
|
|
328
|
+
nodes.push(node);
|
|
329
|
+
} else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
|
|
330
|
+
skip = attr.name.includes("l-for:");
|
|
331
|
+
pushed = true;
|
|
332
|
+
nodes.push(node)
|
|
333
|
+
}
|
|
334
|
+
})
|
|
327
335
|
if (!pushed && node.getAttribute("type") === "radio") nodes.push(node);
|
|
328
336
|
if (!skip && !node.shadowRoot) nodes.push(...getNodes(node));
|
|
329
337
|
}
|
|
@@ -339,24 +347,16 @@ const {observe} = (() => {
|
|
|
339
347
|
try {
|
|
340
348
|
let value = Function("context", "with(context) { return `" + Lightview.sanitizeTemplate(template) + "` }")(component.varsProxy);
|
|
341
349
|
value = node.nodeType === Node.TEXT_NODE || !safe ? value : Lightview.escapeHTML(value);
|
|
342
|
-
if (type === "string") return value;
|
|
350
|
+
if (type === "string") return value==="undefined" ? undefined : value;
|
|
343
351
|
node.nodeValue = value == "null" || value == "undefined" ? "" : value;
|
|
344
352
|
} catch (e) {
|
|
345
|
-
console.warn(e);
|
|
353
|
+
//console.warn(e);
|
|
346
354
|
if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
|
|
355
|
+
return undefined;
|
|
347
356
|
}
|
|
348
357
|
}
|
|
349
358
|
return node?.nodeValue;
|
|
350
359
|
}
|
|
351
|
-
const render = (hasTemplate, render) => {
|
|
352
|
-
let observer;
|
|
353
|
-
if (hasTemplate) {
|
|
354
|
-
if (observer) observer.cancel();
|
|
355
|
-
observer = observe(render)
|
|
356
|
-
} else {
|
|
357
|
-
render();
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
360
|
const inputTypeToType = (inputType) => {
|
|
361
361
|
if (!inputType) return "any"
|
|
362
362
|
if (["text", "tel", "email", "url", "search", "radio", "color", "password"].includes(inputType)) return "string";
|
|
@@ -376,16 +376,18 @@ const {observe} = (() => {
|
|
|
376
376
|
if (bound.has(input)) return;
|
|
377
377
|
bound.add(input);
|
|
378
378
|
const inputtype = input.tagName === "SELECT" || input.tagName === "TEXTAREA" ? "text" : input.getAttribute("type"),
|
|
379
|
-
type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype)
|
|
380
|
-
deflt = input.getAttribute("default");
|
|
379
|
+
type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype);
|
|
381
380
|
value ||= input.getAttribute("value");
|
|
382
381
|
let variable = component.vars[variableName] || {type};
|
|
383
382
|
if (type !== variable.type) {
|
|
384
383
|
if (variable.type === "any" || variable.type === "unknown") variable.type = type;
|
|
385
384
|
else throw new TypeError(`Attempt to bind <input name="${variableName}" type="${type}"> to variable ${variableName}:${variable.type}`)
|
|
386
385
|
}
|
|
387
|
-
component.variables({[variableName]: type});
|
|
388
|
-
if(inputtype!=="radio")
|
|
386
|
+
component.variables({[variableName]: type},{reactive:true});
|
|
387
|
+
if(inputtype!=="radio") {
|
|
388
|
+
if(value.includes("${")) input.attributes.value.value = "";
|
|
389
|
+
else component.setVariableValue(variableName, coerce(value,type));
|
|
390
|
+
}
|
|
389
391
|
let eventname = "change";
|
|
390
392
|
if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
|
|
391
393
|
eventname = "input";
|
|
@@ -415,23 +417,19 @@ const {observe} = (() => {
|
|
|
415
417
|
}
|
|
416
418
|
}
|
|
417
419
|
let reserved = {
|
|
418
|
-
any: {value: "any", constant: true},
|
|
419
|
-
boolean: {value: "boolean", constant: true},
|
|
420
|
-
string: {value: "string", constant: true},
|
|
421
|
-
number: {value: "number", constant: true},
|
|
422
|
-
object: {value: "object", constant: true},
|
|
423
420
|
observed: {value: true, constant: true},
|
|
424
421
|
reactive: {value: true, constant: true},
|
|
425
422
|
shared: {value: true, constant: true},
|
|
426
423
|
exported: {value: true, constant: true},
|
|
427
|
-
imported: {value: true, constant: true}
|
|
428
|
-
remote: {}
|
|
424
|
+
imported: {value: true, constant: true}
|
|
429
425
|
};
|
|
430
426
|
const createClass = (domElementNode, {observer, framed}) => {
|
|
431
427
|
const instances = new Set(),
|
|
432
428
|
dom = domElementNode.tagName === "TEMPLATE"
|
|
433
429
|
? domElementNode.content.cloneNode(true)
|
|
434
|
-
: domElementNode.cloneNode(true)
|
|
430
|
+
: domElementNode.cloneNode(true),
|
|
431
|
+
observedAttributes = [];
|
|
432
|
+
observedAttributes.add = function(name) { observedAttributes.includes(name) || observedAttributes.push(name); }
|
|
435
433
|
if (domElementNode.tagName === "TEMPLATE") domElementNode = domElementNode.cloneNode(true);
|
|
436
434
|
return class CustomElement extends HTMLElement {
|
|
437
435
|
static get instances() {
|
|
@@ -441,23 +439,20 @@ const {observe} = (() => {
|
|
|
441
439
|
constructor() {
|
|
442
440
|
super();
|
|
443
441
|
instances.add(this);
|
|
444
|
-
observer ||= createObserver(this, framed);
|
|
445
442
|
const currentComponent = this,
|
|
446
443
|
shadow = this.attachShadow({mode: "open"}),
|
|
447
444
|
eventlisteners = {};
|
|
445
|
+
// needs to be local to the instance
|
|
446
|
+
Object.defineProperty(this,"changeListener",{value:
|
|
447
|
+
function({variableName, value}) {
|
|
448
|
+
if (currentComponent.changeListener.targets.has(variableName)) {
|
|
449
|
+
value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
|
|
450
|
+
if (value == null) removeComponentAttribute(currentComponent, variableName);
|
|
451
|
+
else setComponentAttribute(currentComponent, variableName, value);
|
|
452
|
+
}
|
|
453
|
+
}});
|
|
448
454
|
this.vars = {
|
|
449
455
|
...reserved,
|
|
450
|
-
changeListener: {
|
|
451
|
-
value: ({variableName, value}) => {
|
|
452
|
-
if (currentComponent.vars.changeListener.value.targets.has(variableName)) {
|
|
453
|
-
value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
|
|
454
|
-
if (value == null) removeComponentAttribute(this, variableName);
|
|
455
|
-
else setComponentAttribute(this, variableName, value);
|
|
456
|
-
}
|
|
457
|
-
},
|
|
458
|
-
type: "function",
|
|
459
|
-
constant: true
|
|
460
|
-
},
|
|
461
456
|
addEventListener: {
|
|
462
457
|
value: (eventName, listener) => {
|
|
463
458
|
const listeners = eventlisteners[eventName] ||= new Set();
|
|
@@ -470,9 +465,10 @@ const {observe} = (() => {
|
|
|
470
465
|
constant: true
|
|
471
466
|
},
|
|
472
467
|
postEvent: {
|
|
473
|
-
value: (eventName, event) => {
|
|
468
|
+
value: (eventName, event = {}) => {
|
|
474
469
|
//event = {...event}
|
|
475
470
|
event.type = eventName;
|
|
471
|
+
event.target = currentComponent;
|
|
476
472
|
eventlisteners[eventName]?.forEach((f) => f(event));
|
|
477
473
|
},
|
|
478
474
|
type: "function",
|
|
@@ -482,8 +478,8 @@ const {observe} = (() => {
|
|
|
482
478
|
};
|
|
483
479
|
this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
|
|
484
480
|
this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
|
|
485
|
-
this.
|
|
486
|
-
this.varsProxy.addEventListener("change", this.
|
|
481
|
+
this.changeListener.targets = new Set();
|
|
482
|
+
this.varsProxy.addEventListener("change", this.changeListener);
|
|
487
483
|
if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported: true});
|
|
488
484
|
["getElementById", "querySelector", "querySelectorAll"]
|
|
489
485
|
.forEach((fname) => {
|
|
@@ -514,104 +510,113 @@ const {observe} = (() => {
|
|
|
514
510
|
shadow = ctx.shadowRoot;
|
|
515
511
|
for (const attr of this.defaultAttributes) this.hasAttribute(attr.name) || this.setAttribute(attr.name, attr.value);
|
|
516
512
|
const scripts = shadow.querySelectorAll("script"),
|
|
517
|
-
promises = []
|
|
518
|
-
|
|
513
|
+
promises = [],
|
|
514
|
+
scriptpromises = [];
|
|
515
|
+
for (const script of [...scripts]) {
|
|
519
516
|
if (script.attributes.src?.value?.includes("/lightview.js")) continue;
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
517
|
+
const currentScript = document.createElement("script");
|
|
518
|
+
if (script.className !== "lightview" && !((script.attributes.type?.value || "").includes("lightview/"))) {
|
|
519
|
+
for (const attr of script.attributes) currentScript.setAttribute(attr.name,attr.value);
|
|
520
|
+
scriptpromises.push(new Promise((resolve) => {
|
|
521
|
+
currentScript.onload = () => resolve();
|
|
522
|
+
}))
|
|
523
|
+
shadow.appendChild(currentScript);
|
|
524
|
+
currentScript.remove();
|
|
525
|
+
continue;
|
|
526
|
+
};
|
|
527
|
+
const scriptid = Math.random() + "";
|
|
523
528
|
for (const attr of script.attributes) {
|
|
524
529
|
currentScript.setAttribute(attr.name, attr.name === "type" ? attr.value.replace("lightview/", "") : attr.value);
|
|
525
530
|
}
|
|
526
531
|
currentScript.classList.remove("lightview");
|
|
527
532
|
const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
|
|
528
|
-
currentScript.innerHTML = `Object.getPrototypeOf(async function(){}).constructor('if(window["${scriptid}"]?.ctx) {
|
|
533
|
+
currentScript.innerHTML = `Object.getPrototypeOf(async function(){}).constructor('if(window["${scriptid}"]?.ctx) { const ctx = window["${scriptid}"].ctx; { with(ctx) { ${text}; } } }')().then(() => window["${scriptid}"]()); `;
|
|
529
534
|
let resolver;
|
|
530
|
-
promises.push(new Promise((resolve) => resolver = resolve));
|
|
531
535
|
window[scriptid] = () => {
|
|
532
536
|
delete window[scriptid];
|
|
533
537
|
currentScript.remove();
|
|
534
538
|
resolver();
|
|
535
539
|
}
|
|
536
540
|
window[scriptid].ctx = ctx.varsProxy;
|
|
537
|
-
|
|
541
|
+
promises.push(new Promise((resolve) => {
|
|
542
|
+
resolver = resolve;
|
|
543
|
+
// wait for all regular scripts to load first
|
|
544
|
+
Promise.all(scriptpromises).then(() => shadow.appendChild(currentScript));
|
|
545
|
+
}));
|
|
538
546
|
}
|
|
539
547
|
Promise.all(promises).then(() => {
|
|
540
548
|
const nodes = getNodes(ctx);
|
|
541
549
|
nodes.forEach((node) => {
|
|
542
550
|
if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
|
|
543
|
-
|
|
551
|
+
observe(() => resolveNodeOrText(node, this));
|
|
544
552
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
545
553
|
// resolve the value before all else;
|
|
546
|
-
const attr = node.attributes.value
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
554
|
+
const attr = node.attributes.value,
|
|
555
|
+
template = attr?.template;
|
|
556
|
+
if (attr && template) {
|
|
557
|
+
let value = resolveNodeOrText(attr, this),
|
|
550
558
|
eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
|
|
551
|
-
|
|
552
|
-
|
|
559
|
+
const template = attr.template;
|
|
560
|
+
if (template) {
|
|
553
561
|
if (/\$\{[a-zA-z_]+\}/g.test(template)) {
|
|
554
562
|
const name = template.substring(2, template.length - 1);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
if (eltype === "checkbox") {
|
|
559
|
-
if (coerce(value, "boolean") === true) {
|
|
560
|
-
node.setAttribute("checked", "");
|
|
561
|
-
node.checked = true;
|
|
562
|
-
} else {
|
|
563
|
-
node.removeAttribute("checked");
|
|
564
|
-
node.checked = false;
|
|
565
|
-
}
|
|
566
|
-
const vname = resolveNodeOrText(node.attributes.name, ctx);
|
|
567
|
-
if (vname) ctx.setValue(vname, node.checked, {coerceTo: "boolean"});
|
|
568
|
-
}
|
|
569
|
-
if (node.tagName === "SELECT") {
|
|
570
|
-
let values = [value];
|
|
571
|
-
if (node.hasAttribute("multiple")) values = coerce(value, Array);
|
|
572
|
-
[...node.querySelectorAll("option")].forEach(async (option) => {
|
|
573
|
-
if (option.hasAttribute("value")) {
|
|
574
|
-
if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
|
|
575
|
-
option.setAttribute("selected", "");
|
|
576
|
-
option.selected = true;
|
|
577
|
-
}
|
|
578
|
-
} else if (values.includes(resolveNodeOrText(option.innerText, ctx))) {
|
|
579
|
-
option.setAttribute("selected", "");
|
|
580
|
-
option.selected = true;
|
|
563
|
+
if(!this.vars[name] || this.vars[name].reactive) {
|
|
564
|
+
bindInput(node, name, this, value);
|
|
581
565
|
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const {name, value} = attr;
|
|
589
|
-
if (name === "type") {
|
|
590
|
-
if (value === "radio" && node.attributes.name) {
|
|
591
|
-
const name = resolveNodeOrText(node.attributes.name, ctx);
|
|
592
|
-
for (const vname of this.getVariableNames()) {
|
|
593
|
-
if (vname === name) {
|
|
594
|
-
render(true, () => {
|
|
595
|
-
const name = resolveNodeOrText(node.attributes.name, ctx),
|
|
596
|
-
varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
|
|
597
|
-
if (node.attributes.value && varvalue == resolveNodeOrText(node.attributes.value, ctx)) {
|
|
566
|
+
}
|
|
567
|
+
observe(() => {
|
|
568
|
+
const value = resolveNodeOrText(template, ctx);
|
|
569
|
+
if(value!==undefined) {
|
|
570
|
+
if (eltype === "checkbox") {
|
|
571
|
+
if (coerce(value, "boolean") === true) {
|
|
598
572
|
node.setAttribute("checked", "");
|
|
599
573
|
node.checked = true;
|
|
600
574
|
} else {
|
|
601
575
|
node.removeAttribute("checked");
|
|
602
576
|
node.checked = false;
|
|
603
577
|
}
|
|
604
|
-
})
|
|
605
|
-
|
|
606
|
-
|
|
578
|
+
} else if (node.tagName === "SELECT") {
|
|
579
|
+
let values = [value];
|
|
580
|
+
if (node.hasAttribute("multiple")) values = coerce(value, Array);
|
|
581
|
+
[...node.querySelectorAll("option")].forEach(async (option) => {
|
|
582
|
+
if (option.hasAttribute("value")) {
|
|
583
|
+
if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
|
|
584
|
+
option.setAttribute("selected", "");
|
|
585
|
+
option.selected = true;
|
|
586
|
+
}
|
|
587
|
+
} else if (values.includes(resolveNodeOrText(option.innerText, ctx))) {
|
|
588
|
+
option.setAttribute("selected", "");
|
|
589
|
+
option.selected = true;
|
|
590
|
+
}
|
|
591
|
+
})
|
|
592
|
+
} else if (eltype!=="radio") {
|
|
593
|
+
attr.value = value;
|
|
594
|
+
}
|
|
607
595
|
}
|
|
608
|
-
}
|
|
596
|
+
});
|
|
609
597
|
}
|
|
598
|
+
}
|
|
599
|
+
[...node.attributes].forEach(async (attr) => {
|
|
600
|
+
if (attr.name === "value" && attr.template) return;
|
|
601
|
+
const {name, value} = attr,
|
|
602
|
+
vname = node.attributes.name?.value;
|
|
603
|
+
if (name === "type" && value=="radio" && vname) {
|
|
604
|
+
bindInput(node, vname, this);
|
|
605
|
+
observe(() => {
|
|
606
|
+
const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
|
|
607
|
+
if (node.attributes.value.value == varvalue) {
|
|
608
|
+
node.setAttribute("checked", "");
|
|
609
|
+
node.checked = true;
|
|
610
|
+
} else {
|
|
611
|
+
node.removeAttribute("checked");
|
|
612
|
+
node.checked = false;
|
|
613
|
+
}
|
|
614
|
+
});
|
|
610
615
|
}
|
|
611
616
|
|
|
612
617
|
const [type, ...params] = name.split(":");
|
|
613
618
|
if (type === "") { // name is :something
|
|
614
|
-
|
|
619
|
+
observe(() => {
|
|
615
620
|
const value = attr.value;
|
|
616
621
|
if (params[0]) {
|
|
617
622
|
if (value === "true") node.setAttribute(params[0], "")
|
|
@@ -627,20 +632,20 @@ const {observe} = (() => {
|
|
|
627
632
|
})
|
|
628
633
|
} else if (type === "l-on") {
|
|
629
634
|
let listener;
|
|
630
|
-
|
|
635
|
+
observe(() => {
|
|
631
636
|
const value = resolveNodeOrText(attr, this);
|
|
632
637
|
if (listener) node.removeEventListener(params[0], listener);
|
|
633
638
|
listener = this[value] || window[value] || Function(value);
|
|
634
639
|
addListener(node, params[0], listener);
|
|
635
640
|
})
|
|
636
641
|
} else if (type === "l-if") {
|
|
637
|
-
|
|
642
|
+
observe(() => {
|
|
638
643
|
const value = resolveNodeOrText(attr, this);
|
|
639
644
|
node.style.setProperty("display", value === "true" ? "revert" : "none");
|
|
640
645
|
})
|
|
641
646
|
} else if (type === "l-for") {
|
|
642
647
|
node.template ||= node.innerHTML;
|
|
643
|
-
|
|
648
|
+
observe(() => {
|
|
644
649
|
const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
|
|
645
650
|
value = resolveNodeOrText(attr, this),
|
|
646
651
|
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
@@ -667,61 +672,53 @@ const {observe} = (() => {
|
|
|
667
672
|
else node.appendChild(parsed.body.firstChild);
|
|
668
673
|
}
|
|
669
674
|
})
|
|
670
|
-
} else if
|
|
671
|
-
|
|
675
|
+
} else if(attr.template) {
|
|
676
|
+
observe(() => {
|
|
677
|
+
resolveNodeOrText(attr, this);
|
|
678
|
+
})
|
|
672
679
|
}
|
|
673
680
|
})
|
|
674
681
|
}
|
|
675
682
|
})
|
|
676
683
|
shadow.normalize();
|
|
677
|
-
observer
|
|
678
|
-
|
|
684
|
+
observer ||= createObserver(ctx, framed);
|
|
685
|
+
observer.observe(ctx, {attributeOldValue: true, subtree:true, characterData:true, characterDataOldValue:true});
|
|
686
|
+
ctx.vars.postEvent.value("connected");
|
|
679
687
|
})
|
|
680
688
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
this.adoptedCallback = value;
|
|
684
|
-
//Object.defineProperty(this, "adoptedCallback", {configurable: true, writable: true, value});
|
|
689
|
+
adoptedCallback(callback) {
|
|
690
|
+
this.vars.postEvent.value("adopted");
|
|
685
691
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
this.connectedCallback = callback;
|
|
689
|
-
//Object.defineProperty(this, "connectedCallback", {configurable: true, writable: true, value});
|
|
692
|
+
disconnectedCallback() {
|
|
693
|
+
this.vars.postEvent.value("disconnected");
|
|
690
694
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
this.attributeChangedCallback = callback;
|
|
694
|
-
//Object.defineProperty(this, "attributeChangedCallback", {configurable: true, writable: true, value});
|
|
695
|
+
get observedAttributes() {
|
|
696
|
+
return CustomElement.observedAttributes;
|
|
695
697
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
Object.defineProperty(this, "disconnectedCallback", {
|
|
699
|
-
configurable: true,
|
|
700
|
-
writable: true,
|
|
701
|
-
value: () => {
|
|
702
|
-
value();
|
|
703
|
-
super.disconnectedCallback(callback);
|
|
704
|
-
}
|
|
705
|
-
});
|
|
698
|
+
static get observedAttributes() {
|
|
699
|
+
return observedAttributes;
|
|
706
700
|
}
|
|
707
701
|
|
|
708
702
|
getVariableNames() {
|
|
709
|
-
return Object.keys(this.vars)
|
|
710
|
-
|
|
711
|
-
|
|
703
|
+
return Object.keys(this.vars)
|
|
704
|
+
.filter(name => !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name))
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
getVariable(name) {
|
|
708
|
+
return this.vars[name] ? {...this.vars[name]} : undefined;
|
|
712
709
|
}
|
|
713
710
|
|
|
714
|
-
|
|
711
|
+
setVariableValue(variableName, value, {coerceTo = typeof (value)} = {}) {
|
|
715
712
|
if (!this.isConnected) {
|
|
716
713
|
instances.delete(this);
|
|
717
714
|
return false;
|
|
718
715
|
}
|
|
719
716
|
let {type} = this.vars[variableName] || {};
|
|
720
717
|
if (type) {
|
|
721
|
-
value = coerce(value, type);
|
|
722
718
|
if (this.varsProxy[variableName] !== value) {
|
|
723
719
|
const variable = this.vars[variableName];
|
|
724
720
|
if (variable.shared) {
|
|
721
|
+
value = type.validate ? type.validate(value,variable) : coerce(value,coerceTo);
|
|
725
722
|
const event = new VariableEvent({
|
|
726
723
|
variableName: variableName,
|
|
727
724
|
value: value,
|
|
@@ -740,20 +737,29 @@ const {observe} = (() => {
|
|
|
740
737
|
return false;
|
|
741
738
|
}
|
|
742
739
|
|
|
743
|
-
|
|
740
|
+
getVariableValue(variableName) {
|
|
744
741
|
return this.vars[variableName]?.value;
|
|
745
742
|
}
|
|
746
743
|
|
|
747
|
-
variables(variables, {observed, reactive, shared, exported, imported, remote} = {}) { // options = {observed,reactive,shared,exported,imported}
|
|
744
|
+
variables(variables, {observed, reactive, shared, exported, imported, remote, constant,set} = {}) { // options = {observed,reactive,shared,exported,imported}
|
|
748
745
|
const addEventListener = this.varsProxy.addEventListener;
|
|
749
746
|
if (variables !== undefined) {
|
|
750
747
|
Object.entries(variables)
|
|
751
748
|
.forEach(([key, type]) => {
|
|
752
749
|
const variable = this.vars[key] ||= {name: key, type};
|
|
750
|
+
if(set!==undefined && constant!==undefined) throw new TypeError(`${key} has the constant value ${constant} and can't be set to ${set}`);
|
|
751
|
+
variable.value = set;
|
|
752
|
+
if(constant!==undefined) {
|
|
753
|
+
variable.constant = true;
|
|
754
|
+
variable.value = constant;
|
|
755
|
+
}
|
|
753
756
|
if (observed || imported) {
|
|
754
757
|
variable.value = this.hasAttribute(key) ? coerce(this.getAttribute(key), variable.type) : variable.value;
|
|
755
|
-
variable.observed = observed;
|
|
756
758
|
variable.imported = imported;
|
|
759
|
+
if(variable.observed) {
|
|
760
|
+
variable.observed = observed;
|
|
761
|
+
this.observedAttributes.add(key);
|
|
762
|
+
}
|
|
757
763
|
}
|
|
758
764
|
if (reactive) {
|
|
759
765
|
variable.reactive = true;
|
|
@@ -762,21 +768,21 @@ const {observe} = (() => {
|
|
|
762
768
|
if (shared) {
|
|
763
769
|
variable.shared = true;
|
|
764
770
|
addEventListener("change", ({variableName, value}) => {
|
|
765
|
-
if (this.vars[variableName]?.shared)
|
|
766
|
-
this.siblings.forEach((instance) => instance.setValue(variableName, value))
|
|
767
|
-
}
|
|
771
|
+
if (this.vars[variableName]?.shared) this.siblings.forEach((instance) => instance.setVariableValue(variableName, value))
|
|
768
772
|
})
|
|
769
773
|
}
|
|
770
774
|
if (exported) {
|
|
771
775
|
variable.exported = true;
|
|
772
776
|
// in case the export goes up to an iframe
|
|
773
777
|
if (variable.value != null) setComponentAttribute(this, key, variable.value);
|
|
774
|
-
this.
|
|
778
|
+
this.changeListener.targets.add(key);
|
|
775
779
|
}
|
|
776
780
|
if (remote) {
|
|
781
|
+
if(typeof(remote)==="function") remote = remote(`./${key}`);
|
|
777
782
|
variable.remote = remote;
|
|
778
|
-
handleRemote({variable, remote, reactive});
|
|
783
|
+
remote.handleRemote({variable, config:remote.config, reactive,component:this});
|
|
779
784
|
}
|
|
785
|
+
if(type.validate) type.validate(variable.value,variable);
|
|
780
786
|
});
|
|
781
787
|
}
|
|
782
788
|
return Object.entries(this.vars)
|
|
@@ -785,163 +791,14 @@ const {observe} = (() => {
|
|
|
785
791
|
return result;
|
|
786
792
|
}, {});
|
|
787
793
|
}
|
|
788
|
-
|
|
789
|
-
constants(variables) {
|
|
790
|
-
if (variables !== undefined) {
|
|
791
|
-
Object.entries(variables)
|
|
792
|
-
.forEach(([key, value]) => {
|
|
793
|
-
const type = typeof (value) === "function" ? value : typeof (value),
|
|
794
|
-
variable = this.vars[key];
|
|
795
|
-
if (variable !== undefined) throw new TypeError(`${variable.constant ? "const" : "let"} ${key}:${variable.type} already declared.`);
|
|
796
|
-
if (value === undefined) throw new TypeError(`const ${key}:undefined must be initialized.`);
|
|
797
|
-
this.vars[key] = {type, value, constant: true};
|
|
798
|
-
})
|
|
799
|
-
}
|
|
800
|
-
return Object.entries(this.vars)
|
|
801
|
-
.reduce((result, [key, variable]) => {
|
|
802
|
-
if (variable.constant) result[key] = {...variable};
|
|
803
|
-
return result;
|
|
804
|
-
}, {});
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const remoteProxy = ({json, variable,remote, reactive}) => {
|
|
810
|
-
const type = typeof (remote);
|
|
811
|
-
return new Proxy(json, {
|
|
812
|
-
get(target,property) {
|
|
813
|
-
if(property==="__remoteProxytarget__") return json;
|
|
814
|
-
return target[property];
|
|
815
|
-
},
|
|
816
|
-
async set(target, property, value) {
|
|
817
|
-
if(value && typeof(value)==="object" && value instanceof Promise) value = await value;
|
|
818
|
-
const oldValue = target[property];
|
|
819
|
-
if (oldValue !== value) {
|
|
820
|
-
let remotevalue;
|
|
821
|
-
if (type === "string") {
|
|
822
|
-
const href = new URL(remote,window.location.href).href;
|
|
823
|
-
remotevalue = patch({target,property,value,oldValue},href);
|
|
824
|
-
} else if(remote && type==="object") {
|
|
825
|
-
let href;
|
|
826
|
-
if(remote.path) href = new URL(remote.path,window.location.href).href;
|
|
827
|
-
if(!remote.patch) {
|
|
828
|
-
if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
|
|
829
|
-
remote.patch = patch;
|
|
830
|
-
}
|
|
831
|
-
remotevalue = remote.patch({target,property,value,oldValue},href);
|
|
832
|
-
}
|
|
833
|
-
if(remotevalue) {
|
|
834
|
-
await remotevalue.then((newjson) => {
|
|
835
|
-
if (newjson && typeof (newjson) === "object" && reactive) {
|
|
836
|
-
const target = variable.value?.__reactorProxyTarget__ ? json : variable.value;
|
|
837
|
-
Object.entries(newjson).forEach(([key,newValue]) => {
|
|
838
|
-
if(target[key]!==newValue) {
|
|
839
|
-
target[key] = newValue;
|
|
840
|
-
}
|
|
841
|
-
})
|
|
842
|
-
Object.keys(target).forEach((key) => {
|
|
843
|
-
if(!(key in newjson)) {
|
|
844
|
-
delete target[key];
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
if(variable.value?.__reactorProxyTarget__) {
|
|
848
|
-
const dependents = variable.value.__dependents__,
|
|
849
|
-
observers = dependents[property] || [];
|
|
850
|
-
[...observers].forEach((f) => {
|
|
851
|
-
if (f.cancelled) dependents[property].delete(f);
|
|
852
|
-
else f();
|
|
853
|
-
})
|
|
854
|
-
}
|
|
855
|
-
} else {
|
|
856
|
-
variable.value = json;
|
|
857
|
-
}
|
|
858
|
-
})
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
return true;
|
|
862
|
-
}
|
|
863
|
-
})
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
const patch = ({target,property,value,oldValue},href) => {
|
|
867
|
-
return fetch(href, {
|
|
868
|
-
method: "PATCH",
|
|
869
|
-
body: JSON.stringify({property,value,oldValue}),
|
|
870
|
-
headers: {
|
|
871
|
-
"Content-Type": "application/json"
|
|
872
|
-
}
|
|
873
|
-
}).then((response) => {
|
|
874
|
-
if (response.status < 400) return response.json();
|
|
875
|
-
})
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
const get = ({variable,remote,reactive},path) => {
|
|
879
|
-
return fetch(path)
|
|
880
|
-
.then((response) => {
|
|
881
|
-
if (response.status < 400) return response.json();
|
|
882
|
-
})
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
const put = ({variable,remote,reactive},href) => {
|
|
886
|
-
return fetch(href, {
|
|
887
|
-
method: "PUT",
|
|
888
|
-
body: JSON.stringify(variable.value),
|
|
889
|
-
headers: {
|
|
890
|
-
"Content-Type": "application/json"
|
|
891
|
-
}
|
|
892
|
-
}).then((response) => {
|
|
893
|
-
if (response.status === 200) {
|
|
894
|
-
return response.json();
|
|
895
|
-
}
|
|
896
|
-
})
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
const handleRemote = async ({variable, remote, reactive},doput) => {
|
|
900
|
-
const type = typeof (remote);
|
|
901
|
-
let value;
|
|
902
|
-
if (type === "string") {
|
|
903
|
-
const href = new URL(remote,window.location.href).href;
|
|
904
|
-
value = (doput
|
|
905
|
-
? put({variable, remote, reactive},href)
|
|
906
|
-
: get({variable, remote, reactive},href));
|
|
907
|
-
if(variable.value===undefined) variable.value = value;
|
|
908
|
-
} else if (remote && type === "object") {
|
|
909
|
-
let href;
|
|
910
|
-
if(remote.path) href = new URL(remote.path,window.location.href).href;
|
|
911
|
-
if(!remote.get || !remote.put) {
|
|
912
|
-
if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
|
|
913
|
-
if(!remote.get) remote.get = get;
|
|
914
|
-
if(!remote.put) remote.put = put;
|
|
915
|
-
}
|
|
916
|
-
value = (doput
|
|
917
|
-
? remote.put({variable, remote, reactive},href)
|
|
918
|
-
: remote.get({variable, remote, reactive},href));
|
|
919
|
-
if(remote.ttl && !doput && !remote.intervalId) {
|
|
920
|
-
remote.intervalId = setInterval(async () => {
|
|
921
|
-
await handleRemote({variable, remote, reactive});
|
|
922
|
-
})
|
|
923
|
-
}
|
|
924
|
-
if(variable.value===undefined) variable.value = value;
|
|
925
|
-
}
|
|
926
|
-
if(value) {
|
|
927
|
-
variable.value = await value.then((json) => {
|
|
928
|
-
if (json && typeof (json) === "object" && reactive) {
|
|
929
|
-
return remoteProxy({json, variable,remote, reactive})
|
|
930
|
-
} else {
|
|
931
|
-
return json;
|
|
932
|
-
}
|
|
933
|
-
})
|
|
934
794
|
}
|
|
935
795
|
}
|
|
936
796
|
|
|
937
797
|
const createComponent = (name, node, {framed, observer} = {}) => {
|
|
938
798
|
let ctor = customElements.get(name);
|
|
939
799
|
if (ctor) {
|
|
940
|
-
if (framed && !ctor.lightviewFramed)
|
|
941
|
-
|
|
942
|
-
} else {
|
|
943
|
-
console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
|
|
944
|
-
}
|
|
800
|
+
if (framed && !ctor.lightviewFramed) ctor.lightviewFramed = true;
|
|
801
|
+
else console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
|
|
945
802
|
return ctor;
|
|
946
803
|
}
|
|
947
804
|
ctor = createClass(node, {observer, framed});
|
|
@@ -951,7 +808,6 @@ const {observe} = (() => {
|
|
|
951
808
|
}
|
|
952
809
|
Lightview.customElements = new Map();
|
|
953
810
|
Lightview.createComponent = createComponent;
|
|
954
|
-
//Object.defineProperty(Lightview, "createComponent", {writable: true, configurable: true, value: createComponent})
|
|
955
811
|
const importLink = async (link, observer) => {
|
|
956
812
|
const url = (new URL(link.getAttribute("href"), window.location.href)),
|
|
957
813
|
as = link.getAttribute("as") || getNameFromPath(url.pathname);
|
|
@@ -1046,21 +902,19 @@ const {observe} = (() => {
|
|
|
1046
902
|
postMessage({type: "setAttribute", argsList: ["height", height + 20]});
|
|
1047
903
|
}
|
|
1048
904
|
resize();
|
|
1049
|
-
onresize(document.body, () =>
|
|
1050
|
-
resize();
|
|
1051
|
-
});
|
|
905
|
+
onresize(document.body, () => resize());
|
|
1052
906
|
return
|
|
1053
907
|
}
|
|
1054
908
|
if (type === "setAttribute") {
|
|
1055
909
|
const [name, value] = [...argsList],
|
|
1056
910
|
variable = document.body.vars[name];
|
|
1057
|
-
if (variable && variable.imported) document.body.
|
|
911
|
+
if (variable && variable.imported) document.body.setVariableValue(name, value);
|
|
1058
912
|
return;
|
|
1059
913
|
}
|
|
1060
914
|
if (type === "removeAttribute") {
|
|
1061
915
|
const [name] = argsList[0],
|
|
1062
916
|
variable = document.body.vars[name];
|
|
1063
|
-
if (variable && variable.imported) document.body.
|
|
917
|
+
if (variable && variable.imported) document.body.setVariableValue(name, undefined);
|
|
1064
918
|
|
|
1065
919
|
}
|
|
1066
920
|
});
|
|
@@ -1098,9 +952,7 @@ const {observe} = (() => {
|
|
|
1098
952
|
}
|
|
1099
953
|
if (type === "setAttribute") {
|
|
1100
954
|
const [name, value] = [...argsList];
|
|
1101
|
-
if (iframe.getAttribute(name) !== value + "")
|
|
1102
|
-
iframe.setAttribute(name, value);
|
|
1103
|
-
}
|
|
955
|
+
if (iframe.getAttribute(name) !== value + "") iframe.setAttribute(name, value);
|
|
1104
956
|
return;
|
|
1105
957
|
}
|
|
1106
958
|
if (type === "removeAttribute") {
|