lightview 1.3.0-b → 1.4.1-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 +113 -127
- package/package.json +9 -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.1b (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
|
@@ -264,14 +264,14 @@ const {observe} = (() => {
|
|
|
264
264
|
nodes.push(node);
|
|
265
265
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
266
266
|
let skip;
|
|
267
|
+
if(node.getAttribute("type")==="radio") nodes.push(node);
|
|
267
268
|
[...node.attributes].forEach((attr) => {
|
|
268
269
|
if (attr.value.includes("${")) {
|
|
269
270
|
attr.template ||= attr.value;
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
if (attr.name.includes(":") || attr.name.startsWith("l-")) {
|
|
271
|
+
nodes.push(node);
|
|
272
|
+
} else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
|
|
273
273
|
skip = attr.name.includes("l-for:");
|
|
274
|
-
|
|
274
|
+
nodes.push(node)
|
|
275
275
|
}
|
|
276
276
|
})
|
|
277
277
|
if (!skip && !node.shadowRoot) nodes.push(...getNodes(node));
|
|
@@ -281,7 +281,7 @@ const {observe} = (() => {
|
|
|
281
281
|
return nodes;
|
|
282
282
|
}
|
|
283
283
|
const resolveNode = (node, component) => {
|
|
284
|
-
if (node
|
|
284
|
+
if (node?.template) {
|
|
285
285
|
try {
|
|
286
286
|
const value = Function("context", "with(context) { return `" + node.template + "` }")(component.varsProxy);
|
|
287
287
|
node.nodeValue = value === "null" || value === "undefined" ? "" : value;
|
|
@@ -289,11 +289,11 @@ const {observe} = (() => {
|
|
|
289
289
|
if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
|
-
return node
|
|
292
|
+
return node?.nodeValue;
|
|
293
293
|
}
|
|
294
|
-
const render = (
|
|
294
|
+
const render = (hasTemplate, render) => {
|
|
295
295
|
let observer;
|
|
296
|
-
if (
|
|
296
|
+
if (hasTemplate) {
|
|
297
297
|
if (observer) observer.cancel();
|
|
298
298
|
observer = observe(render)
|
|
299
299
|
} else {
|
|
@@ -302,7 +302,7 @@ const {observe} = (() => {
|
|
|
302
302
|
}
|
|
303
303
|
const inputTypeToType = (inputType) => {
|
|
304
304
|
if (!inputType) return "any"
|
|
305
|
-
if (["text", "tel", "email", "url", "search", "radio"].includes(inputType)) return "string";
|
|
305
|
+
if (["text", "tel", "email", "url", "search", "radio","color","password"].includes(inputType)) return "string";
|
|
306
306
|
if (["number", "range"].includes(inputType)) return "number";
|
|
307
307
|
if (["datetime"].includes(inputType)) return Date;
|
|
308
308
|
if (["checkbox"].includes(inputType)) return "boolean";
|
|
@@ -314,38 +314,22 @@ const {observe} = (() => {
|
|
|
314
314
|
addListener(node,"click", anchorHandler);
|
|
315
315
|
})
|
|
316
316
|
}
|
|
317
|
-
const
|
|
318
|
-
[...node.querySelectorAll("input")].forEach((input) => bindInput(input, component))
|
|
319
|
-
}
|
|
320
|
-
const bindInput = (input, component) => {
|
|
321
|
-
let name = input.getAttribute("name"),
|
|
322
|
-
vname = input.getAttribute("l-bind") || name;
|
|
323
|
-
name ||= vname;
|
|
324
|
-
if (name) {
|
|
325
|
-
if (!input.hasAttribute("l-bind")) input.setAttribute("l-bind", vname)
|
|
317
|
+
const bindInput = (input, name, component) => {
|
|
326
318
|
const inputtype = input.tagName === "SELECT" ? "text" : input.getAttribute("type"),
|
|
327
319
|
type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
|
|
328
320
|
deflt = input.getAttribute("default"),
|
|
329
321
|
value = input.getAttribute("value");
|
|
330
|
-
let variable = component.vars[
|
|
322
|
+
let variable = component.vars[name] || {type};
|
|
331
323
|
if (type !== variable.type) {
|
|
332
324
|
if (variable.type === "any" || variable.type === "unknown") variable.type = type;
|
|
333
|
-
else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${
|
|
325
|
+
else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
|
|
334
326
|
}
|
|
335
|
-
component.variables({[
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (value && !value.includes("${")) {
|
|
340
|
-
variable.value = coerce(value, type);
|
|
341
|
-
//input.setAttribute("value", `\${${name}}`);
|
|
342
|
-
} else if (deflt && !deflt.includes("${")) {
|
|
343
|
-
variable.value = coerce(deflt, type);
|
|
344
|
-
//input.setAttribute("default", `\${${name}}`);
|
|
345
|
-
}
|
|
327
|
+
component.variables({[name]: type});
|
|
328
|
+
let eventname = "change";
|
|
329
|
+
if(input.tagName!=="SELECT" && (!inputtype || ["text","number","tel","email","url","search","password"].includes(inputtype))) {
|
|
330
|
+
eventname = "input";
|
|
346
331
|
}
|
|
347
|
-
|
|
348
|
-
addListener(input,"change", (event) => {
|
|
332
|
+
addListener(input,eventname, (event) => {
|
|
349
333
|
event.stopImmediatePropagation();
|
|
350
334
|
const target = event.target;
|
|
351
335
|
let value = target.value;
|
|
@@ -354,12 +338,18 @@ const {observe} = (() => {
|
|
|
354
338
|
} else if (target.tagName === "SELECT") {
|
|
355
339
|
if (target.hasAttribute("multiple")) {
|
|
356
340
|
value = [...target.querySelectorAll("option")]
|
|
357
|
-
.filter((option) => option.selected || option.
|
|
341
|
+
.filter((option) => option.selected || resolveNode(option.attributes.value,component)==value || option.innerText == value)
|
|
358
342
|
.map((option) => option.getAttribute("value") || option.innerText);
|
|
359
343
|
}
|
|
360
344
|
}
|
|
361
|
-
component.varsProxy[
|
|
345
|
+
component.varsProxy[name] = coerce(value, type);
|
|
362
346
|
})
|
|
347
|
+
}
|
|
348
|
+
const tryParse = (value) => {
|
|
349
|
+
try {
|
|
350
|
+
return JSON.parse(value);
|
|
351
|
+
} catch(e) {
|
|
352
|
+
return value;
|
|
363
353
|
}
|
|
364
354
|
}
|
|
365
355
|
let reserved = {
|
|
@@ -372,7 +362,7 @@ const {observe} = (() => {
|
|
|
372
362
|
exported: {value: true, constant: true},
|
|
373
363
|
imported: {value: true, constant: true}
|
|
374
364
|
};
|
|
375
|
-
const createClass = (domElementNode, {observer,
|
|
365
|
+
const createClass = (domElementNode, {observer, importAnchors}) => {
|
|
376
366
|
const instances = new Set(),
|
|
377
367
|
dom = domElementNode.tagName === "TEMPLATE"
|
|
378
368
|
? domElementNode.content.cloneNode(true)
|
|
@@ -425,7 +415,6 @@ const {observe} = (() => {
|
|
|
425
415
|
})
|
|
426
416
|
});
|
|
427
417
|
[...dom.childNodes].forEach((child) => shadow.appendChild(child.cloneNode(true)));
|
|
428
|
-
if (bindForms) _bindForms(shadow, this);
|
|
429
418
|
if (importAnchors) _importAnchors(shadow, this);
|
|
430
419
|
}
|
|
431
420
|
|
|
@@ -459,9 +448,7 @@ const {observe} = (() => {
|
|
|
459
448
|
const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
|
|
460
449
|
currentScript.innerHTML = `Function('if(window["${scriptid}"]?.ctx) { with(window["${scriptid}"].ctx) { ${text}; } window["${scriptid}"](); }')(); `;
|
|
461
450
|
let resolver;
|
|
462
|
-
promises.push(new Promise((resolve) =>
|
|
463
|
-
resolver = resolve;
|
|
464
|
-
}));
|
|
451
|
+
promises.push(new Promise((resolve) => resolver = resolve));
|
|
465
452
|
window[scriptid] = () => {
|
|
466
453
|
delete window[scriptid];
|
|
467
454
|
currentScript.remove();
|
|
@@ -471,49 +458,93 @@ const {observe} = (() => {
|
|
|
471
458
|
ctx.appendChild(currentScript);
|
|
472
459
|
}
|
|
473
460
|
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
461
|
const nodes = getNodes(ctx);
|
|
479
462
|
nodes.forEach((node) => {
|
|
480
463
|
if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
|
|
481
464
|
render(!!node.template, () => resolveNode(node, this))
|
|
482
465
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
466
|
+
// resolve the value before all else;
|
|
467
|
+
const attr = node.attributes.value;
|
|
468
|
+
let name;
|
|
469
|
+
if(attr && attr.template) {
|
|
470
|
+
render(!!attr.template,() => {
|
|
471
|
+
const value = resolveNode(attr, this),
|
|
472
|
+
eltype = resolveNode(node.attributes.type,ctx);
|
|
473
|
+
if(eltype==="checkbox") {
|
|
474
|
+
if(coerce(value,"boolean")===true) {
|
|
475
|
+
node.setAttribute("checked","");
|
|
476
|
+
node.checked = true;
|
|
477
|
+
} else {
|
|
478
|
+
node.removeAttribute("checked");
|
|
479
|
+
node.checked = false;
|
|
480
|
+
}
|
|
481
|
+
const vname = resolveNode(node.attributes.name,ctx);
|
|
482
|
+
if(vname) ctx.setValue(vname,node.checked,{coerceTo:"boolean"});
|
|
483
|
+
}
|
|
484
|
+
if(node.tagName==="SELECT") {
|
|
485
|
+
let values = [value];
|
|
486
|
+
if(node.hasAttribute("multiple")) values = coerce(value,Array);
|
|
487
|
+
[...node.querySelectorAll("option")].forEach((option) => {
|
|
488
|
+
if(option.hasAttribute("value")) {
|
|
489
|
+
if (values.includes(resolveNode(option.attributes.value, ctx))) {
|
|
490
|
+
option.setAttribute("selected", "");
|
|
491
|
+
option.selected = true;
|
|
492
|
+
}
|
|
493
|
+
} else if(option.innerText.trim()===value) {
|
|
494
|
+
option.setAttribute("selected","");
|
|
495
|
+
option.selected = true;
|
|
496
|
+
}
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
let name;
|
|
501
|
+
for(const vname of this.getVariableNames()) {
|
|
502
|
+
if("${" + vname + "}" === attr.template) {
|
|
503
|
+
name = vname;
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if(name) bindInput(node,name,ctx);
|
|
508
|
+
}
|
|
483
509
|
[...node.attributes].forEach((attr) => {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if
|
|
510
|
+
if(attr.name==="value") return;
|
|
511
|
+
const {name, value} = attr;
|
|
512
|
+
if(name==="type") {
|
|
513
|
+
if (value === "radio") {
|
|
514
|
+
const name = resolveNode(node.attributes.name, ctx);
|
|
515
|
+
for (const vname of this.getVariableNames()) {
|
|
516
|
+
if (vname === name) {
|
|
517
|
+
render(true, () => {
|
|
518
|
+
const name = resolveNode(node.attributes.name, ctx),
|
|
519
|
+
varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
|
|
520
|
+
if (varvalue == resolveNode(node.attributes.value,ctx)) {
|
|
521
|
+
node.setAttribute("checked", "");
|
|
522
|
+
node.checked = true;
|
|
523
|
+
} else {
|
|
524
|
+
node.removeAttribute("checked");
|
|
525
|
+
node.checked = false;
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
bindInput(node, name, ctx);
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const [type, ...params] = name.split(":");
|
|
536
|
+
if (type === "") { // name is :something
|
|
487
537
|
render(!!attr.template, () => {
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
elname = node.
|
|
538
|
+
const value = attr.value,
|
|
539
|
+
elvalue = resolveNode(node.attributes.value,ctx),
|
|
540
|
+
eltype = resolveNode(node.attributes.type,ctx),
|
|
541
|
+
elname = resolveNode(node.attributes.name,ctx);
|
|
492
542
|
if (params[0]) {
|
|
493
543
|
if (value === "true") node.setAttribute(params[0], "")
|
|
494
544
|
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
|
-
})
|
|
545
|
+
} else if (eltype=== "checkbox" || node.tagName === "OPTION") {
|
|
546
|
+
if (value === "true") node.setAttribute("checked", "")
|
|
547
|
+
else node.removeAttribute("checked");
|
|
517
548
|
}
|
|
518
549
|
})
|
|
519
550
|
} else if (type === "l-on") {
|
|
@@ -532,7 +563,7 @@ const {observe} = (() => {
|
|
|
532
563
|
} else if (type === "l-for") {
|
|
533
564
|
node.template ||= node.innerHTML;
|
|
534
565
|
render(!!attr.template, () => {
|
|
535
|
-
const [what = "each", vname = "
|
|
566
|
+
const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
|
|
536
567
|
value = resolveNode(attr, this),
|
|
537
568
|
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
538
569
|
target = what === "each" ? coerced : Object[what](coerced),
|
|
@@ -652,9 +683,7 @@ const {observe} = (() => {
|
|
|
652
683
|
variable.shared = true;
|
|
653
684
|
addEventListener("change", ({variableName, value}) => {
|
|
654
685
|
if (this.vars[variableName]?.shared) {
|
|
655
|
-
this.siblings.forEach((instance) =>
|
|
656
|
-
instance.setValue(variableName, value);
|
|
657
|
-
})
|
|
686
|
+
this.siblings.forEach((instance) => instance.setValue(variableName, value))
|
|
658
687
|
}
|
|
659
688
|
})
|
|
660
689
|
}
|
|
@@ -669,47 +698,6 @@ const {observe} = (() => {
|
|
|
669
698
|
})
|
|
670
699
|
}
|
|
671
700
|
});
|
|
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
701
|
}
|
|
714
702
|
return Object.entries(this.vars)
|
|
715
703
|
.reduce((result, [key, variable]) => {
|
|
@@ -737,13 +725,13 @@ const {observe} = (() => {
|
|
|
737
725
|
}
|
|
738
726
|
}
|
|
739
727
|
}
|
|
740
|
-
const createComponent = (name, node, {observer,
|
|
728
|
+
const createComponent = (name, node, {observer, importAnchors} = {}) => {
|
|
741
729
|
let ctor = customElements.get(name);
|
|
742
730
|
if (ctor) {
|
|
743
731
|
console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
|
|
744
732
|
return ctor;
|
|
745
733
|
}
|
|
746
|
-
ctor = createClass(node, {observer,
|
|
734
|
+
ctor = createClass(node, {observer, importAnchors});
|
|
747
735
|
customElements.define(name, ctor);
|
|
748
736
|
return ctor;
|
|
749
737
|
}
|
|
@@ -759,10 +747,9 @@ const {observe} = (() => {
|
|
|
759
747
|
const html = await (await fetch(url.href)).text(),
|
|
760
748
|
dom = parser.parseFromString(html, "text/html"),
|
|
761
749
|
importAnchors = !!dom.head.querySelector('meta[name="l-importAnchors"]'),
|
|
762
|
-
bindForms = !!dom.head.querySelector('meta[name="l-bindForms"]'),
|
|
763
750
|
unhide = !!dom.head.querySelector('meta[name="l-unhide"]');
|
|
764
751
|
if (unhide) dom.body.removeAttribute("hidden");
|
|
765
|
-
createComponent(as, dom.body, {observer, importAnchors
|
|
752
|
+
createComponent(as, dom.body, {observer, importAnchors});
|
|
766
753
|
}
|
|
767
754
|
return {as};
|
|
768
755
|
}
|
|
@@ -773,9 +760,9 @@ const {observe} = (() => {
|
|
|
773
760
|
}
|
|
774
761
|
}
|
|
775
762
|
|
|
776
|
-
const bodyAsComponent = ({as = "x-body", unhide, importAnchors
|
|
763
|
+
const bodyAsComponent = ({as = "x-body", unhide, importAnchors} = {}) => {
|
|
777
764
|
const parent = document.body.parentElement;
|
|
778
|
-
createComponent(as, document.body, {importAnchors
|
|
765
|
+
createComponent(as, document.body, {importAnchors});
|
|
779
766
|
const component = document.createElement(as);
|
|
780
767
|
parent.replaceChild(component, document.body);
|
|
781
768
|
Object.defineProperty(document, "body", {
|
|
@@ -821,12 +808,11 @@ const {observe} = (() => {
|
|
|
821
808
|
const loader = async (whenFramed) => {
|
|
822
809
|
if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
|
|
823
810
|
const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
|
|
824
|
-
bindForms = !!document.querySelector('meta[name="l-bindForms"]'),
|
|
825
811
|
unhide = !!document.querySelector('meta[name="l-unhide"]'),
|
|
826
812
|
isolated = !!document.querySelector('meta[name="l-isolate"]'),
|
|
827
813
|
enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
|
|
828
814
|
if (whenFramed) {
|
|
829
|
-
whenFramed({unhide, importAnchors,
|
|
815
|
+
whenFramed({unhide, importAnchors, isolated, enableFrames});
|
|
830
816
|
if (!isolated) {
|
|
831
817
|
postMessage.enabled = true;
|
|
832
818
|
addListener(window,"message", ({data}) => {
|
|
@@ -861,7 +847,7 @@ const {observe} = (() => {
|
|
|
861
847
|
postMessage({type: "DOMContentLoaded"})
|
|
862
848
|
}
|
|
863
849
|
} else if (url.searchParams.has("as")) {
|
|
864
|
-
bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors
|
|
850
|
+
bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors});
|
|
865
851
|
}
|
|
866
852
|
if (enableFrames) {
|
|
867
853
|
postMessage.enabled = true;
|
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 ...",
|
|
3
|
+
"version": "1.4.1b",
|
|
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"
|
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
"react",
|
|
16
16
|
"angular",
|
|
17
17
|
"riot",
|
|
18
|
-
"vue"
|
|
18
|
+
"vue",
|
|
19
|
+
"moon",
|
|
20
|
+
"hyperapp",
|
|
21
|
+
"hyperhtml",
|
|
22
|
+
"micro front end",
|
|
23
|
+
"custom elements",
|
|
24
|
+
"web components"
|
|
19
25
|
],
|
|
20
26
|
"author": "Simon Y. Blackwell",
|
|
21
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
|
|