lightview 1.3.1-b → 1.4.2-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 +2 -2
- package/directives.html +50 -53
- package/lightview.js +182 -167
- package/package.json +6 -3
- package/remote.html +2 -1
- package/remoteform.html +60 -32
- package/scratch.html +69 -0
- package/xor.html +3 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# lightview v1.
|
|
1
|
+
# lightview v1.4.2b (BETA)
|
|
2
2
|
|
|
3
|
-
Small, simple, powerful web UI creation ...
|
|
3
|
+
Small, simple, powerful web UI and micro front end creation ...
|
|
4
4
|
|
|
5
5
|
Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 6K (minified/gzipped).
|
|
6
6
|
|
package/directives.html
CHANGED
|
@@ -7,65 +7,57 @@
|
|
|
7
7
|
|
|
8
8
|
<body>
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
<p>
|
|
12
|
-
Show: <input type="checkbox"
|
|
10
|
+
<div style="margin:20px">
|
|
11
|
+
<p>
|
|
12
|
+
Show: <input type="checkbox" value="${on}">
|
|
13
13
|
<div l-if="${on}">
|
|
14
|
-
|
|
14
|
+
Now you've done it. You've exposed me.
|
|
15
15
|
</div>
|
|
16
|
-
</p>
|
|
17
|
-
<p>
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
</
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
</
|
|
53
|
-
<ul l-for:entries:entry="${children}">
|
|
54
|
-
<li>${entry[0]}:${entry[1]}</li>
|
|
55
|
-
</ul>
|
|
56
|
-
|
|
57
|
-
Variable Values
|
|
58
|
-
<p id="variables"></p>
|
|
59
|
-
|
|
16
|
+
</p>
|
|
17
|
+
<p>
|
|
18
|
+
|
|
19
|
+
<p>
|
|
20
|
+
How would you like that burger?<br>
|
|
21
|
+
<select value="${options}" multiple>
|
|
22
|
+
<option>lettuce</option>
|
|
23
|
+
<option>tomato</option>
|
|
24
|
+
<option>cheese</option>
|
|
25
|
+
</select>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
For (defaults to each)
|
|
30
|
+
<ul l-for:each="${options}">
|
|
31
|
+
<li>${index}:${item}</li>
|
|
32
|
+
</ul>
|
|
33
|
+
For Each
|
|
34
|
+
<ul l-for:each="${options}">
|
|
35
|
+
<li>${index}:${item}</li>
|
|
36
|
+
</ul>
|
|
37
|
+
For Values
|
|
38
|
+
<ul l-for:values="${options}">
|
|
39
|
+
<li>${item}:${index}</li>
|
|
40
|
+
</ul>
|
|
41
|
+
For Keys
|
|
42
|
+
<ul l-for:keys="${options}">
|
|
43
|
+
<li>${item}</li>
|
|
44
|
+
</ul>
|
|
45
|
+
For Entries
|
|
46
|
+
<ul l-for:entries="${options}">
|
|
47
|
+
<li>${item[0]}:${item[1]}</li>
|
|
48
|
+
</ul>
|
|
49
|
+
|
|
50
|
+
Variable Values
|
|
51
|
+
<p id="variables"></p>
|
|
52
|
+
</div>
|
|
60
53
|
<script type="lightview/module">
|
|
61
|
-
self.variables({on:boolean,
|
|
54
|
+
self.variables({on:boolean,options:Array},{reactive});
|
|
62
55
|
|
|
63
56
|
on = true;
|
|
64
|
-
|
|
65
|
-
children = ["John","Mary","Jane"];
|
|
66
|
-
options = ["tomato"];
|
|
57
|
+
options = ["lettuce"];
|
|
67
58
|
|
|
68
|
-
|
|
59
|
+
// demo instrumentation
|
|
60
|
+
const variableValues = () => {
|
|
69
61
|
const el = self.getElementById("variables");
|
|
70
62
|
while(el.lastElementChild) el.lastElementChild.remove();
|
|
71
63
|
self.getVariableNames().forEach((name) => {
|
|
@@ -73,7 +65,12 @@ addEventListener("change",()=> {
|
|
|
73
65
|
line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
|
|
74
66
|
el.appendChild(line);
|
|
75
67
|
});
|
|
68
|
+
};
|
|
69
|
+
variableValues();
|
|
70
|
+
addEventListener("change",()=> {
|
|
71
|
+
variableValues()
|
|
76
72
|
});
|
|
73
|
+
|
|
77
74
|
</script>
|
|
78
75
|
</body>
|
|
79
76
|
|
package/lightview.js
CHANGED
|
@@ -218,14 +218,20 @@ const {observe} = (() => {
|
|
|
218
218
|
}
|
|
219
219
|
});
|
|
220
220
|
}
|
|
221
|
-
const createObserver = (domNode) => {
|
|
221
|
+
const createObserver = (domNode,framed) => {
|
|
222
222
|
const observer = new MutationObserver((mutations) => {
|
|
223
223
|
mutations.forEach((mutation) => {
|
|
224
224
|
if (mutation.type === "attributes") {
|
|
225
|
+
if(framed) debugger;
|
|
225
226
|
const name = mutation.attributeName,
|
|
226
|
-
target = mutation.target
|
|
227
|
+
target = mutation.target,
|
|
228
|
+
value = target.getAttribute(name);
|
|
229
|
+
if(framed && name==="message" && target instanceof IFrameElement) {
|
|
230
|
+
if(value) console.log("message",value);
|
|
231
|
+
target.removeAttribute(name);
|
|
232
|
+
target.dispatchEvent("message",new CustomEvent("message",{detail:JSON.parse(value)}))
|
|
233
|
+
}
|
|
227
234
|
if (target.observedAttributes && target.observedAttributes.includes(name)) {
|
|
228
|
-
const value = target.getAttribute(name);
|
|
229
235
|
if (value !== mutation.oldValue) {
|
|
230
236
|
target.setValue(name, value);
|
|
231
237
|
if (target.attributeChangedCallback) target.attributeChangedCallback(name, value, mutation.oldValue);
|
|
@@ -264,14 +270,14 @@ const {observe} = (() => {
|
|
|
264
270
|
nodes.push(node);
|
|
265
271
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
266
272
|
let skip;
|
|
273
|
+
if(node.getAttribute("type")==="radio") nodes.push(node);
|
|
267
274
|
[...node.attributes].forEach((attr) => {
|
|
268
275
|
if (attr.value.includes("${")) {
|
|
269
276
|
attr.template ||= attr.value;
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
if (attr.name.includes(":") || attr.name.startsWith("l-")) {
|
|
277
|
+
nodes.push(node);
|
|
278
|
+
} else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
|
|
273
279
|
skip = attr.name.includes("l-for:");
|
|
274
|
-
|
|
280
|
+
nodes.push(node)
|
|
275
281
|
}
|
|
276
282
|
})
|
|
277
283
|
if (!skip && !node.shadowRoot) nodes.push(...getNodes(node));
|
|
@@ -281,7 +287,7 @@ const {observe} = (() => {
|
|
|
281
287
|
return nodes;
|
|
282
288
|
}
|
|
283
289
|
const resolveNode = (node, component) => {
|
|
284
|
-
if (node
|
|
290
|
+
if (node?.template) {
|
|
285
291
|
try {
|
|
286
292
|
const value = Function("context", "with(context) { return `" + node.template + "` }")(component.varsProxy);
|
|
287
293
|
node.nodeValue = value === "null" || value === "undefined" ? "" : value;
|
|
@@ -289,11 +295,11 @@ const {observe} = (() => {
|
|
|
289
295
|
if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
|
|
290
296
|
}
|
|
291
297
|
}
|
|
292
|
-
return node
|
|
298
|
+
return node?.nodeValue;
|
|
293
299
|
}
|
|
294
|
-
const render = (
|
|
300
|
+
const render = (hasTemplate, render) => {
|
|
295
301
|
let observer;
|
|
296
|
-
if (
|
|
302
|
+
if (hasTemplate) {
|
|
297
303
|
if (observer) observer.cancel();
|
|
298
304
|
observer = observe(render)
|
|
299
305
|
} else {
|
|
@@ -302,7 +308,7 @@ const {observe} = (() => {
|
|
|
302
308
|
}
|
|
303
309
|
const inputTypeToType = (inputType) => {
|
|
304
310
|
if (!inputType) return "any"
|
|
305
|
-
if (["text", "tel", "email", "url", "search", "radio"].includes(inputType)) return "string";
|
|
311
|
+
if (["text", "tel", "email", "url", "search", "radio","color","password"].includes(inputType)) return "string";
|
|
306
312
|
if (["number", "range"].includes(inputType)) return "number";
|
|
307
313
|
if (["datetime"].includes(inputType)) return Date;
|
|
308
314
|
if (["checkbox"].includes(inputType)) return "boolean";
|
|
@@ -314,52 +320,42 @@ const {observe} = (() => {
|
|
|
314
320
|
addListener(node,"click", anchorHandler);
|
|
315
321
|
})
|
|
316
322
|
}
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (
|
|
339
|
-
if (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
variable.value = coerce(deflt, type);
|
|
344
|
-
//input.setAttribute("default", `\${${name}}`);
|
|
323
|
+
const bindInput = (input, name, component) => {
|
|
324
|
+
const inputtype = input.tagName === "SELECT" ? "text" : input.getAttribute("type"),
|
|
325
|
+
type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
|
|
326
|
+
deflt = input.getAttribute("default"),
|
|
327
|
+
value = input.getAttribute("value");
|
|
328
|
+
let variable = component.vars[name] || {type};
|
|
329
|
+
if (type !== variable.type) {
|
|
330
|
+
if (variable.type === "any" || variable.type === "unknown") variable.type = type;
|
|
331
|
+
else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
|
|
332
|
+
}
|
|
333
|
+
component.variables({[name]: type});
|
|
334
|
+
let eventname = "change";
|
|
335
|
+
if(input.tagName!=="SELECT" && (!inputtype || ["text","number","tel","email","url","search","password"].includes(inputtype))) {
|
|
336
|
+
eventname = "input";
|
|
337
|
+
}
|
|
338
|
+
addListener(input,eventname, (event) => {
|
|
339
|
+
event.stopImmediatePropagation();
|
|
340
|
+
const target = event.target;
|
|
341
|
+
let value = target.value;
|
|
342
|
+
if (inputtype === "checkbox") {
|
|
343
|
+
value = input.checked
|
|
344
|
+
} else if (target.tagName === "SELECT") {
|
|
345
|
+
if (target.hasAttribute("multiple")) {
|
|
346
|
+
value = [...target.querySelectorAll("option")]
|
|
347
|
+
.filter((option) => option.selected || resolveNode(option.attributes.value,component)==value || option.innerText == value)
|
|
348
|
+
.map((option) => option.getAttribute("value") || option.innerText);
|
|
345
349
|
}
|
|
346
350
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (target.hasAttribute("multiple")) {
|
|
356
|
-
value = [...target.querySelectorAll("option")]
|
|
357
|
-
.filter((option) => option.selected || option.getAttribute("value") == value || option.innerText == value)
|
|
358
|
-
.map((option) => option.getAttribute("value") || option.innerText);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
component.varsProxy[vname] = coerce(value, type);
|
|
362
|
-
})
|
|
351
|
+
component.varsProxy[name] = coerce(value, type);
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
const tryParse = (value) => {
|
|
355
|
+
try {
|
|
356
|
+
return JSON.parse(value);
|
|
357
|
+
} catch(e) {
|
|
358
|
+
return value;
|
|
363
359
|
}
|
|
364
360
|
}
|
|
365
361
|
let reserved = {
|
|
@@ -372,7 +368,7 @@ const {observe} = (() => {
|
|
|
372
368
|
exported: {value: true, constant: true},
|
|
373
369
|
imported: {value: true, constant: true}
|
|
374
370
|
};
|
|
375
|
-
const createClass = (domElementNode, {observer,
|
|
371
|
+
const createClass = (domElementNode, {observer, importAnchors, framed}) => {
|
|
376
372
|
const instances = new Set(),
|
|
377
373
|
dom = domElementNode.tagName === "TEMPLATE"
|
|
378
374
|
? domElementNode.content.cloneNode(true)
|
|
@@ -386,7 +382,7 @@ const {observe} = (() => {
|
|
|
386
382
|
constructor() {
|
|
387
383
|
super();
|
|
388
384
|
instances.add(this);
|
|
389
|
-
observer ||= createObserver(this);
|
|
385
|
+
observer ||= createObserver(this,framed);
|
|
390
386
|
const currentComponent = this,
|
|
391
387
|
shadow = this.attachShadow({mode: "open"}),
|
|
392
388
|
eventlisteners = {};
|
|
@@ -416,6 +412,7 @@ const {observe} = (() => {
|
|
|
416
412
|
};
|
|
417
413
|
this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
|
|
418
414
|
this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
|
|
415
|
+
if(framed || CustomElement.lightviewFramed) this.variables({message:Object},{exported:true});
|
|
419
416
|
["getElementById", "querySelector", "querySelectorAll"]
|
|
420
417
|
.forEach((fname) => {
|
|
421
418
|
Object.defineProperty(this, fname, {
|
|
@@ -425,7 +422,6 @@ const {observe} = (() => {
|
|
|
425
422
|
})
|
|
426
423
|
});
|
|
427
424
|
[...dom.childNodes].forEach((child) => shadow.appendChild(child.cloneNode(true)));
|
|
428
|
-
if (bindForms) _bindForms(shadow, this);
|
|
429
425
|
if (importAnchors) _importAnchors(shadow, this);
|
|
430
426
|
}
|
|
431
427
|
|
|
@@ -459,9 +455,7 @@ const {observe} = (() => {
|
|
|
459
455
|
const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
|
|
460
456
|
currentScript.innerHTML = `Function('if(window["${scriptid}"]?.ctx) { with(window["${scriptid}"].ctx) { ${text}; } window["${scriptid}"](); }')(); `;
|
|
461
457
|
let resolver;
|
|
462
|
-
promises.push(new Promise((resolve) =>
|
|
463
|
-
resolver = resolve;
|
|
464
|
-
}));
|
|
458
|
+
promises.push(new Promise((resolve) => resolver = resolve));
|
|
465
459
|
window[scriptid] = () => {
|
|
466
460
|
delete window[scriptid];
|
|
467
461
|
currentScript.remove();
|
|
@@ -471,49 +465,93 @@ const {observe} = (() => {
|
|
|
471
465
|
ctx.appendChild(currentScript);
|
|
472
466
|
}
|
|
473
467
|
Promise.all(promises).then(() => {
|
|
474
|
-
const inputs = [...ctx.shadowRoot.querySelectorAll("input[l-bind]"), ...ctx.shadowRoot.querySelectorAll("select[l-bind]")];
|
|
475
|
-
inputs.forEach((input) => {
|
|
476
|
-
bindInput(input, ctx);
|
|
477
|
-
})
|
|
478
468
|
const nodes = getNodes(ctx);
|
|
479
469
|
nodes.forEach((node) => {
|
|
480
470
|
if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
|
|
481
471
|
render(!!node.template, () => resolveNode(node, this))
|
|
482
472
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
473
|
+
// resolve the value before all else;
|
|
474
|
+
const attr = node.attributes.value;
|
|
475
|
+
let name;
|
|
476
|
+
if(attr && attr.template) {
|
|
477
|
+
render(!!attr.template,() => {
|
|
478
|
+
const value = resolveNode(attr, this),
|
|
479
|
+
eltype = resolveNode(node.attributes.type,ctx);
|
|
480
|
+
if(eltype==="checkbox") {
|
|
481
|
+
if(coerce(value,"boolean")===true) {
|
|
482
|
+
node.setAttribute("checked","");
|
|
483
|
+
node.checked = true;
|
|
484
|
+
} else {
|
|
485
|
+
node.removeAttribute("checked");
|
|
486
|
+
node.checked = false;
|
|
487
|
+
}
|
|
488
|
+
const vname = resolveNode(node.attributes.name,ctx);
|
|
489
|
+
if(vname) ctx.setValue(vname,node.checked,{coerceTo:"boolean"});
|
|
490
|
+
}
|
|
491
|
+
if(node.tagName==="SELECT") {
|
|
492
|
+
let values = [value];
|
|
493
|
+
if(node.hasAttribute("multiple")) values = coerce(value,Array);
|
|
494
|
+
[...node.querySelectorAll("option")].forEach((option) => {
|
|
495
|
+
if(option.hasAttribute("value")) {
|
|
496
|
+
if (values.includes(resolveNode(option.attributes.value, ctx))) {
|
|
497
|
+
option.setAttribute("selected", "");
|
|
498
|
+
option.selected = true;
|
|
499
|
+
}
|
|
500
|
+
} else if(option.innerText.trim()===value) {
|
|
501
|
+
option.setAttribute("selected","");
|
|
502
|
+
option.selected = true;
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
let name;
|
|
508
|
+
for(const vname of this.getVariableNames()) {
|
|
509
|
+
if("${" + vname + "}" === attr.template) {
|
|
510
|
+
name = vname;
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if(name) bindInput(node,name,ctx);
|
|
515
|
+
}
|
|
483
516
|
[...node.attributes].forEach((attr) => {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if
|
|
517
|
+
if(attr.name==="value") return;
|
|
518
|
+
const {name, value} = attr;
|
|
519
|
+
if(name==="type") {
|
|
520
|
+
if (value === "radio") {
|
|
521
|
+
const name = resolveNode(node.attributes.name, ctx);
|
|
522
|
+
for (const vname of this.getVariableNames()) {
|
|
523
|
+
if (vname === name) {
|
|
524
|
+
render(true, () => {
|
|
525
|
+
const name = resolveNode(node.attributes.name, ctx),
|
|
526
|
+
varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
|
|
527
|
+
if (varvalue == resolveNode(node.attributes.value,ctx)) {
|
|
528
|
+
node.setAttribute("checked", "");
|
|
529
|
+
node.checked = true;
|
|
530
|
+
} else {
|
|
531
|
+
node.removeAttribute("checked");
|
|
532
|
+
node.checked = false;
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
bindInput(node, name, ctx);
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const [type, ...params] = name.split(":");
|
|
543
|
+
if (type === "") { // name is :something
|
|
487
544
|
render(!!attr.template, () => {
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
elname = node.
|
|
545
|
+
const value = attr.value,
|
|
546
|
+
elvalue = resolveNode(node.attributes.value,ctx),
|
|
547
|
+
eltype = resolveNode(node.attributes.type,ctx),
|
|
548
|
+
elname = resolveNode(node.attributes.name,ctx);
|
|
492
549
|
if (params[0]) {
|
|
493
550
|
if (value === "true") node.setAttribute(params[0], "")
|
|
494
551
|
else node.removeAttribute(params[0]);
|
|
495
|
-
} else if (
|
|
496
|
-
if (value === "true")
|
|
497
|
-
|
|
498
|
-
} else {
|
|
499
|
-
node.removeAttribute("checked");
|
|
500
|
-
}
|
|
501
|
-
} else if (attrtype === "radio") {
|
|
502
|
-
if (elvalue === value) {
|
|
503
|
-
node.setAttribute("checked", "");
|
|
504
|
-
}
|
|
505
|
-
} else if (name === "value" && node.tagName === "SELECT") {
|
|
506
|
-
node.setAttribute("value", value);
|
|
507
|
-
const values = value[0] === "[" ? JSON.parse(value) : value.split(","); // handle multiselect
|
|
508
|
-
[...node.querySelectorAll("option")].forEach((option) => {
|
|
509
|
-
if (option.hasAttribute("value")) {
|
|
510
|
-
if (values.includes(option.getAttribute("value"))) {
|
|
511
|
-
option.setAttribute("selected", true);
|
|
512
|
-
}
|
|
513
|
-
} else if (values.includes(option.innerText)) {
|
|
514
|
-
option.setAttribute("selected", true);
|
|
515
|
-
}
|
|
516
|
-
})
|
|
552
|
+
} else if (eltype=== "checkbox" || node.tagName === "OPTION") {
|
|
553
|
+
if (value === "true") node.setAttribute("checked", "")
|
|
554
|
+
else node.removeAttribute("checked");
|
|
517
555
|
}
|
|
518
556
|
})
|
|
519
557
|
} else if (type === "l-on") {
|
|
@@ -532,7 +570,7 @@ const {observe} = (() => {
|
|
|
532
570
|
} else if (type === "l-for") {
|
|
533
571
|
node.template ||= node.innerHTML;
|
|
534
572
|
render(!!attr.template, () => {
|
|
535
|
-
const [what = "each", vname = "
|
|
573
|
+
const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
|
|
536
574
|
value = resolveNode(attr, this),
|
|
537
575
|
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
538
576
|
target = what === "each" ? coerced : Object[what](coerced),
|
|
@@ -652,9 +690,7 @@ const {observe} = (() => {
|
|
|
652
690
|
variable.shared = true;
|
|
653
691
|
addEventListener("change", ({variableName, value}) => {
|
|
654
692
|
if (this.vars[variableName]?.shared) {
|
|
655
|
-
this.siblings.forEach((instance) =>
|
|
656
|
-
instance.setValue(variableName, value);
|
|
657
|
-
})
|
|
693
|
+
this.siblings.forEach((instance) => instance.setValue(variableName, value))
|
|
658
694
|
}
|
|
659
695
|
})
|
|
660
696
|
}
|
|
@@ -669,47 +705,6 @@ const {observe} = (() => {
|
|
|
669
705
|
})
|
|
670
706
|
}
|
|
671
707
|
});
|
|
672
|
-
addEventListener("change", ({variableName, value}) => {
|
|
673
|
-
[...this.shadowRoot.querySelectorAll(`input[l-bind=${variableName}]`),
|
|
674
|
-
...this.shadowRoot.querySelectorAll(`select[l-bind=${variableName}]`)]
|
|
675
|
-
.forEach((input) => {
|
|
676
|
-
const eltype = input.getAttribute("type");
|
|
677
|
-
if (eltype === "checkbox") { // at el option selected
|
|
678
|
-
if(!!value) {
|
|
679
|
-
input.setAttribute("checked", "");
|
|
680
|
-
} else {
|
|
681
|
-
input.removeAttribute("checked");
|
|
682
|
-
}
|
|
683
|
-
input.checked = !!value;
|
|
684
|
-
} else if (eltype === "radio") {
|
|
685
|
-
if (input.getAttribute("value") === value) {
|
|
686
|
-
input.setAttribute("checked", "");
|
|
687
|
-
input.checked = true;
|
|
688
|
-
}
|
|
689
|
-
} else if (input.tagName === "SELECT") {
|
|
690
|
-
const values = value && typeof (value) === "object" && value instanceof Array ? value : [value];
|
|
691
|
-
[...input.querySelectorAll("option")].forEach((option) => {
|
|
692
|
-
if (values.includes(option.getAttribute("value") || option.innerText)) {
|
|
693
|
-
option.setAttribute("selected", "");
|
|
694
|
-
option.selected = true;
|
|
695
|
-
}
|
|
696
|
-
})
|
|
697
|
-
} else if (!eltype || eltype === "text") {
|
|
698
|
-
value = typeof (value) === "string" || value == null ? value : JSON.stringify(value);
|
|
699
|
-
const oldvalue = input.getAttribute("value") || "";
|
|
700
|
-
if (oldvalue !== value) {
|
|
701
|
-
if (value == null) input.removeAttribute("value");
|
|
702
|
-
else input.setAttribute("value", value);
|
|
703
|
-
try {
|
|
704
|
-
input.setSelectionRange(0, Math.max(oldvalue.length, value ? value.length : 0)); // shadowDom sometimes fails to rerender unless this is done;
|
|
705
|
-
input.setRangeText(value || "", 0, Math.max(oldvalue.length, value ? value.length : 0));
|
|
706
|
-
} catch (e) {
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
})
|
|
712
|
-
})
|
|
713
708
|
}
|
|
714
709
|
return Object.entries(this.vars)
|
|
715
710
|
.reduce((result, [key, variable]) => {
|
|
@@ -737,13 +732,17 @@ const {observe} = (() => {
|
|
|
737
732
|
}
|
|
738
733
|
}
|
|
739
734
|
}
|
|
740
|
-
const createComponent = (name, node, {observer,
|
|
735
|
+
const createComponent = (name, node, {observer, importAnchors,framed} = {}) => {
|
|
741
736
|
let ctor = customElements.get(name);
|
|
742
737
|
if (ctor) {
|
|
743
|
-
|
|
738
|
+
if(framed && !ctor.lightviewFramed) {
|
|
739
|
+
ctor.lightviewFramed = true;
|
|
740
|
+
} else {
|
|
741
|
+
console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
|
|
742
|
+
}
|
|
744
743
|
return ctor;
|
|
745
744
|
}
|
|
746
|
-
ctor = createClass(node, {observer,
|
|
745
|
+
ctor = createClass(node, {observer, importAnchors,framed});
|
|
747
746
|
customElements.define(name, ctor);
|
|
748
747
|
return ctor;
|
|
749
748
|
}
|
|
@@ -759,10 +758,9 @@ const {observe} = (() => {
|
|
|
759
758
|
const html = await (await fetch(url.href)).text(),
|
|
760
759
|
dom = parser.parseFromString(html, "text/html"),
|
|
761
760
|
importAnchors = !!dom.head.querySelector('meta[name="l-importAnchors"]'),
|
|
762
|
-
bindForms = !!dom.head.querySelector('meta[name="l-bindForms"]'),
|
|
763
761
|
unhide = !!dom.head.querySelector('meta[name="l-unhide"]');
|
|
764
762
|
if (unhide) dom.body.removeAttribute("hidden");
|
|
765
|
-
createComponent(as, dom.body, {observer, importAnchors
|
|
763
|
+
createComponent(as, dom.body, {observer, importAnchors});
|
|
766
764
|
}
|
|
767
765
|
return {as};
|
|
768
766
|
}
|
|
@@ -773,9 +771,9 @@ const {observe} = (() => {
|
|
|
773
771
|
}
|
|
774
772
|
}
|
|
775
773
|
|
|
776
|
-
const bodyAsComponent = ({as = "x-body", unhide, importAnchors,
|
|
774
|
+
const bodyAsComponent = ({as = "x-body", unhide, importAnchors,framed} = {}) => {
|
|
777
775
|
const parent = document.body.parentElement;
|
|
778
|
-
createComponent(as, document.body, {importAnchors,
|
|
776
|
+
createComponent(as, document.body, {importAnchors,framed});
|
|
779
777
|
const component = document.createElement(as);
|
|
780
778
|
parent.replaceChild(component, document.body);
|
|
781
779
|
Object.defineProperty(document, "body", {
|
|
@@ -817,16 +815,16 @@ const {observe} = (() => {
|
|
|
817
815
|
|
|
818
816
|
const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
|
|
819
817
|
let domContentLoadedEvent;
|
|
820
|
-
addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
|
|
818
|
+
if(!domContentLoadedEvent) addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
|
|
819
|
+
let OBSERVER;
|
|
821
820
|
const loader = async (whenFramed) => {
|
|
822
821
|
if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
|
|
823
822
|
const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
|
|
824
|
-
bindForms = !!document.querySelector('meta[name="l-bindForms"]'),
|
|
825
823
|
unhide = !!document.querySelector('meta[name="l-unhide"]'),
|
|
826
824
|
isolated = !!document.querySelector('meta[name="l-isolate"]'),
|
|
827
825
|
enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
|
|
828
826
|
if (whenFramed) {
|
|
829
|
-
whenFramed({unhide, importAnchors,
|
|
827
|
+
whenFramed({unhide, importAnchors, isolated, enableFrames, framed:true});
|
|
830
828
|
if (!isolated) {
|
|
831
829
|
postMessage.enabled = true;
|
|
832
830
|
addListener(window,"message", ({data}) => {
|
|
@@ -861,7 +859,7 @@ const {observe} = (() => {
|
|
|
861
859
|
postMessage({type: "DOMContentLoaded"})
|
|
862
860
|
}
|
|
863
861
|
} else if (url.searchParams.has("as")) {
|
|
864
|
-
bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors
|
|
862
|
+
bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors});
|
|
865
863
|
}
|
|
866
864
|
if (enableFrames) {
|
|
867
865
|
postMessage.enabled = true;
|
|
@@ -890,7 +888,9 @@ const {observe} = (() => {
|
|
|
890
888
|
}
|
|
891
889
|
if (type === "setAttribute") {
|
|
892
890
|
const [name, value] = [...argsList];
|
|
893
|
-
if (iframe.getAttribute(name) !== value + "")
|
|
891
|
+
if (iframe.getAttribute(name) !== value + "") {
|
|
892
|
+
iframe.setAttribute(name, value);
|
|
893
|
+
}
|
|
894
894
|
return;
|
|
895
895
|
}
|
|
896
896
|
if (type === "removeAttribute") {
|
|
@@ -900,30 +900,45 @@ const {observe} = (() => {
|
|
|
900
900
|
}
|
|
901
901
|
console.warn("iframe posted a message without providing an id", message);
|
|
902
902
|
});
|
|
903
|
-
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
903
|
+
if(!OBSERVER) {
|
|
904
|
+
const mutationCallback = (mutationsList) => {
|
|
905
|
+
const console = document.getElementById("console");
|
|
906
|
+
for (const {target, attributeName, oldValue} of mutationsList) {
|
|
907
907
|
const value = target.getAttribute(attributeName);
|
|
908
|
-
if (!
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
908
|
+
if (!["height", "width", "message"].includes(attributeName)) {
|
|
909
|
+
if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
|
|
910
|
+
else if (value !== oldValue) {
|
|
911
|
+
postMessage({
|
|
912
|
+
type: "setAttribute",
|
|
913
|
+
argsList: [attributeName, value]
|
|
914
|
+
}, iframe)
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if(attributeName==="message") {
|
|
918
|
+
if(value) {
|
|
919
|
+
target.removeAttribute("message");
|
|
920
|
+
target.dispatchEvent(new CustomEvent("message",{target,detail:JSON.parse(value)}))
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
target.dispatchEvent(new CustomEvent("attribute.changed",{target,detail:{attributeName,value,oldValue}}))
|
|
924
|
+
}
|
|
913
925
|
}
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
iframe
|
|
918
|
-
|
|
926
|
+
};
|
|
927
|
+
const observer = OBSERVER = new MutationObserver(mutationCallback),
|
|
928
|
+
iframe = document.getElementById("myframe");
|
|
929
|
+
observer.observe(iframe, {attributes: true, attributeOldValue: true});
|
|
930
|
+
}
|
|
919
931
|
}
|
|
920
932
|
}
|
|
921
933
|
const whenFramed = (f, {isolated} = {}) => {
|
|
934
|
+
// loads for framed content
|
|
922
935
|
addListener(document,"DOMContentLoaded", (event) => loader(f));
|
|
923
936
|
}
|
|
924
937
|
Lightview.whenFramed = whenFramed;
|
|
925
938
|
//Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
|
|
926
|
-
if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
|
|
939
|
+
if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
|
|
940
|
+
// loads for unframed content
|
|
941
|
+
// CodePen mucks with window.parent
|
|
927
942
|
addListener(document,"DOMContentLoaded", () => loader())
|
|
928
943
|
}
|
|
929
944
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightview",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Small, simple, powerful web UI creation ... imagine a blend of Svelte, React, Vue, Riot and more.",
|
|
3
|
+
"version": "1.4.2b",
|
|
4
|
+
"description": "Small, simple, powerful web UI and micro front end creation ... imagine a blend of Svelte, React, Vue, Riot and more.",
|
|
5
5
|
"main": "lightview.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
"vue",
|
|
19
19
|
"moon",
|
|
20
20
|
"hyperapp",
|
|
21
|
-
"hyperhtml"
|
|
21
|
+
"hyperhtml",
|
|
22
|
+
"micro front end",
|
|
23
|
+
"custom elements",
|
|
24
|
+
"web components"
|
|
22
25
|
],
|
|
23
26
|
"author": "Simon Y. Blackwell",
|
|
24
27
|
"license": "MIT",
|
package/remote.html
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
<body>
|
|
11
11
|
<p>
|
|
12
|
-
The component below is loaded from an alternate domain and running in
|
|
12
|
+
The component below is loaded from an alternate domain and running in a child iframe.
|
|
13
|
+
The logging console is below the component in this frame.
|
|
13
14
|
</p>
|
|
14
15
|
<iframe id="myframe" src="https://lightview.dev/remoteform.html?id=myframe"></iframe>
|
|
15
16
|
<div id="console" style="max-height:250px;scroll:auto"></div>
|
package/remoteform.html
CHANGED
|
@@ -2,45 +2,73 @@
|
|
|
2
2
|
|
|
3
3
|
<head>
|
|
4
4
|
<title>Form</title>
|
|
5
|
-
<meta name="l-bindForms">
|
|
6
5
|
<script src="./lightview.js?as=x-body"></script>
|
|
7
|
-
<script>Lightview.whenFramed(({as,unhide,importAnchors,
|
|
8
|
-
Lightview.bodyAsComponent({as,unhide,importAnchors,
|
|
6
|
+
<script>Lightview.whenFramed(({as,unhide,importAnchors,isolated,enableFrames}) => {
|
|
7
|
+
Lightview.bodyAsComponent({as,unhide,importAnchors,isolated,enableFrames});
|
|
9
8
|
})</script>
|
|
10
9
|
</head>
|
|
11
10
|
|
|
12
11
|
<body style="height:fit-content;width:fit-content;display:flex;flex-direction:column;max-height:100%;overflow:auto;">
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<
|
|
12
|
+
<div style="margin:20px">
|
|
13
|
+
<p>
|
|
14
|
+
<input type="text" value="${color}">
|
|
15
|
+
<input type="checkbox" value="${checked}">
|
|
16
|
+
<input type="radio" name="color" value="red">
|
|
17
|
+
<input type="radio" name="color" value="yellow">
|
|
18
|
+
<input type="radio" name="color" value="green">
|
|
19
|
+
<select value="${color}">
|
|
20
|
+
<option value="red">red</option>
|
|
21
|
+
<option>yellow</option>
|
|
22
|
+
<option> green </option>
|
|
23
|
+
</select>
|
|
24
|
+
<div>Hamburger options:</div>
|
|
25
|
+
<select value="${hamburger}" multiple>
|
|
26
|
+
<option value="lettuce">lettuce</option>
|
|
27
|
+
<option>tomato</option>
|
|
28
|
+
<option>cheese</option>
|
|
29
|
+
</select>
|
|
30
|
+
</p>
|
|
31
|
+
<p l-if="${checked}">
|
|
32
|
+
Now you've done it. You've exposed me.
|
|
33
|
+
</p>
|
|
34
|
+
<ul l-for="${hamburger}">
|
|
35
|
+
<li>${item}</li>
|
|
36
|
+
</ul>
|
|
37
|
+
<ul l-for:entries="${hamburger}">
|
|
38
|
+
<li>${item[0]}:${item[1]}</li>
|
|
39
|
+
</ul>
|
|
40
|
+
<ul l-for:values="${hamburger}">
|
|
41
|
+
<li>${item}</li>
|
|
42
|
+
</ul>
|
|
43
|
+
<p id="variables">
|
|
44
|
+
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
21
47
|
<script type="lightview/module">
|
|
22
|
-
self.variables({
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
self.variables({
|
|
49
|
+
color: string,
|
|
50
|
+
checked: boolean,
|
|
51
|
+
hamburger: Array
|
|
52
|
+
}, {
|
|
53
|
+
reactive
|
|
54
|
+
});
|
|
55
|
+
color = "green";
|
|
56
|
+
checked = true;
|
|
57
|
+
hamburger = ["lettuce"];
|
|
58
|
+
// demo instrumentation
|
|
59
|
+
const variableValues = () => {
|
|
60
|
+
const el = self.getElementById("variables");
|
|
61
|
+
while (el.lastElementChild) el.lastElementChild.remove();
|
|
62
|
+
self.getVariableNames().forEach((name) => {
|
|
63
|
+
const line = document.createElement("div");
|
|
64
|
+
line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
|
|
65
|
+
el.appendChild(line);
|
|
66
|
+
});
|
|
38
67
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
68
|
+
variableValues();
|
|
69
|
+
addEventListener("change", () => {
|
|
70
|
+
variableValues()
|
|
71
|
+
});
|
|
44
72
|
</script>
|
|
45
73
|
</body>
|
|
46
74
|
|
package/scratch.html
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Scratch</title>
|
|
6
|
+
<script src="./lightview.js?as=x-body"></script>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div style="margin:20px;padding:5px;border:1px;border-style:solid;border-color:${color}">
|
|
10
|
+
<p>
|
|
11
|
+
<input type="text" value="${color}">
|
|
12
|
+
<input type="radio" name="color" value="red">
|
|
13
|
+
<input type="radio" name="color" value="yellow">
|
|
14
|
+
<input type="radio" name="color" value="green">
|
|
15
|
+
<select value="${color}">
|
|
16
|
+
<option value="red">red</option>
|
|
17
|
+
<option>yellow</option>
|
|
18
|
+
<option> green</option>
|
|
19
|
+
</select>
|
|
20
|
+
<div>Hamburger options:</div>
|
|
21
|
+
<select value="${hamburger}" multiple>
|
|
22
|
+
<option value="lettuce">lettuce</option>
|
|
23
|
+
<option>tomato</option>
|
|
24
|
+
<option>cheese</option>
|
|
25
|
+
</select>
|
|
26
|
+
</p>
|
|
27
|
+
Expose: <input type="checkbox" value="${checked}">
|
|
28
|
+
<p l-if="${checked}">
|
|
29
|
+
Now you've done it. You've exposed me.
|
|
30
|
+
</p>
|
|
31
|
+
<ul l-for="${hamburger}">
|
|
32
|
+
<li>${item}</li>
|
|
33
|
+
</ul>
|
|
34
|
+
<ul l-for:entries="${hamburger}">
|
|
35
|
+
<li>${item[0]}:${item[1]}</li>
|
|
36
|
+
</ul>
|
|
37
|
+
<ul l-for:values="${hamburger}">
|
|
38
|
+
<li>${item}</li>
|
|
39
|
+
</ul>
|
|
40
|
+
<p id="variables">
|
|
41
|
+
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
<script type="lightview/module">
|
|
45
|
+
self.variables({color:string,checked:boolean,hamburger:Array},{reactive});
|
|
46
|
+
|
|
47
|
+
color = "green";
|
|
48
|
+
checked = true;
|
|
49
|
+
hamburger = ["lettuce"];
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
// demo instrumentation
|
|
53
|
+
const variableValues = () => {
|
|
54
|
+
const el = self.getElementById("variables");
|
|
55
|
+
while (el.lastElementChild) el.lastElementChild.remove();
|
|
56
|
+
self.getVariableNames().forEach((name) => {
|
|
57
|
+
const line = document.createElement("div");
|
|
58
|
+
line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
|
|
59
|
+
el.appendChild(line);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
variableValues();
|
|
63
|
+
addEventListener("change", () => {
|
|
64
|
+
variableValues()
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
</script>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
package/xor.html
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<template id="audiostream">
|
|
6
6
|
<p>${name}</p>
|
|
7
7
|
<p>
|
|
8
|
-
Play: <input
|
|
8
|
+
Play: <input type="checkbox" value="${run}">
|
|
9
9
|
</p>
|
|
10
10
|
<script type="lightview/module">
|
|
11
11
|
self.variables({
|
|
12
12
|
run: boolean
|
|
13
|
-
});
|
|
13
|
+
},{reactive});
|
|
14
14
|
self.variables({
|
|
15
15
|
name: string
|
|
16
16
|
}, {
|
|
@@ -31,9 +31,7 @@
|
|
|
31
31
|
<title>Form</title>
|
|
32
32
|
<script src="./lightview.js"></script>
|
|
33
33
|
<script>
|
|
34
|
-
Lightview.createComponent("x-audiostream", document.getElementById("audiostream")
|
|
35
|
-
bindForms: true
|
|
36
|
-
})
|
|
34
|
+
Lightview.createComponent("x-audiostream", document.getElementById("audiostream"))
|
|
37
35
|
</script>
|
|
38
36
|
</head>
|
|
39
37
|
|