lightview 1.4.4-b → 1.4.8-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/codepen-no-tabs-embed.css +2 -0
- package/{counter.html → examples/counter.html} +1 -0
- package/{directives.html → examples/directives.html} +1 -1
- package/examples/form.html +65 -0
- package/examples/message.html +13 -0
- package/examples/nested.html +11 -0
- package/{remote.html → examples/remote.html} +1 -1
- package/{remoteform.html → examples/remoteform.html} +1 -1
- package/{scratch.html → examples/scratch.html} +1 -1
- package/examples/top.html +10 -0
- package/{xor.html → examples/xor.html} +1 -1
- package/lightview.js +129 -94
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# lightview v1.4.
|
|
1
|
+
# lightview v1.4.8b (BETA)
|
|
2
2
|
|
|
3
3
|
Small, simple, powerful web UI and micro front end creation ...
|
|
4
4
|
|
|
5
|
-
Great ideas from Svelte, React, Vue and Riot combined into one small tool: <
|
|
5
|
+
Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 7K (minified/gzipped).
|
|
6
6
|
|
|
7
7
|
See the docs and examples at [https://lightview.dev](https://lightview.dev).
|
|
8
8
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<title>Form</title>
|
|
6
|
+
<script src="../lightview.js?as=x-body"></script>
|
|
7
|
+
</head>
|
|
8
|
+
|
|
9
|
+
<body>
|
|
10
|
+
<div style="margin:20px;padding:5px;border:1px;border-style:solid;border-color:${color}">
|
|
11
|
+
<p>
|
|
12
|
+
<input type="text" value="${color}">
|
|
13
|
+
<input type="radio" name="color" value="red">
|
|
14
|
+
<input type="radio" name="color" value="yellow">
|
|
15
|
+
<input type="radio" name="color" value="green">
|
|
16
|
+
<select value="${color}">
|
|
17
|
+
<option value="red">red</option>
|
|
18
|
+
<option>yellow</option>
|
|
19
|
+
<option> green</option>
|
|
20
|
+
</select>
|
|
21
|
+
<div>Hamburger options:</div>
|
|
22
|
+
<select value="${hamburger}" multiple>
|
|
23
|
+
<option value="lettuce">lettuce</option>
|
|
24
|
+
<option value="tomato">tomato</option>
|
|
25
|
+
<option>cheese</option>
|
|
26
|
+
</select>
|
|
27
|
+
</p>
|
|
28
|
+
Expose: <input type="checkbox" value="${checked}">
|
|
29
|
+
<p l-if="${checked}">
|
|
30
|
+
Now you've done it. You've exposed me.
|
|
31
|
+
</p>
|
|
32
|
+
<p id="variables">
|
|
33
|
+
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
36
|
+
<script type="lightview/module">
|
|
37
|
+
self.variables({
|
|
38
|
+
color: string,
|
|
39
|
+
checked: boolean,
|
|
40
|
+
hamburger: Array
|
|
41
|
+
}, {
|
|
42
|
+
reactive
|
|
43
|
+
});
|
|
44
|
+
color = "red";
|
|
45
|
+
checked = true;
|
|
46
|
+
hamburger = ["cheese"];
|
|
47
|
+
|
|
48
|
+
// demo instrumentation
|
|
49
|
+
const variableValues = () => {
|
|
50
|
+
const el = self.getElementById("variables");
|
|
51
|
+
while (el.lastElementChild) el.lastElementChild.remove();
|
|
52
|
+
self.getVariableNames().forEach((name) => {
|
|
53
|
+
const line = document.createElement("div");
|
|
54
|
+
line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
|
|
55
|
+
el.appendChild(line);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
variableValues();
|
|
59
|
+
addEventListener("change", () => {
|
|
60
|
+
variableValues()
|
|
61
|
+
});
|
|
62
|
+
</script>
|
|
63
|
+
</body>
|
|
64
|
+
|
|
65
|
+
</html>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>Nested</title>
|
|
4
|
+
<link href="./message.html" rel="module">
|
|
5
|
+
<script src="../lightview.js?as=x-body"></script>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<l-message value="Hello One"></l-message>
|
|
9
|
+
<l-message value="Hello Two"></l-message>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<head>
|
|
4
4
|
<title>Form</title>
|
|
5
|
-
<script src="
|
|
5
|
+
<script src="../lightview.js?as=x-body"></script>
|
|
6
6
|
<script>Lightview.whenFramed(({as,unhide,importAnchors,isolated,enableFrames}) => {
|
|
7
7
|
Lightview.bodyAsComponent({as,unhide,importAnchors,isolated,enableFrames});
|
|
8
8
|
})</script>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<title>Scratch</title>
|
|
6
|
-
<script src="
|
|
6
|
+
<script src="../lightview.js?as=x-body"></script>
|
|
7
7
|
</head>
|
|
8
8
|
<body>
|
|
9
9
|
<div style="margin:20px;padding:5px;border:1px;border-style:solid;border-color:${color}">
|
package/lightview.js
CHANGED
|
@@ -30,6 +30,22 @@ const {observe} = (() => {
|
|
|
30
30
|
let CURRENTOBSERVER;
|
|
31
31
|
const parser = new DOMParser();
|
|
32
32
|
|
|
33
|
+
const templateSanitizer = (string) => {
|
|
34
|
+
return string.replace(/function\s+/g,"")
|
|
35
|
+
.replace(/function\(/g,"")
|
|
36
|
+
.replace(/=\s*>/g,"")
|
|
37
|
+
.replace(/(while|do|for|alert)\s*\(/g,"")
|
|
38
|
+
.replace(/console\.[a-zA-Z$]+\s*\(/g,"");
|
|
39
|
+
}
|
|
40
|
+
Lightview.sanitizeTemplate = templateSanitizer;
|
|
41
|
+
|
|
42
|
+
const escaper = document.createElement('textarea');
|
|
43
|
+
function escapeHTML(html) {
|
|
44
|
+
escaper.textContent = html;
|
|
45
|
+
return escaper.innerHTML;
|
|
46
|
+
}
|
|
47
|
+
Lightview.escapeHTML = escapeHTML;
|
|
48
|
+
|
|
33
49
|
const addListener = (node, eventName, callback) => {
|
|
34
50
|
node.addEventListener(eventName, callback); // just used to make code footprint smaller
|
|
35
51
|
}
|
|
@@ -73,6 +89,7 @@ const {observe} = (() => {
|
|
|
73
89
|
if (toType === "number") return parseFloat(value + "");
|
|
74
90
|
if (toType === "boolean") {
|
|
75
91
|
if (["on", "checked", "selected"].includes(value)) return true;
|
|
92
|
+
if(value==null || value==="") return false;
|
|
76
93
|
try {
|
|
77
94
|
const parsed = JSON.parse(value + "");
|
|
78
95
|
if (typeof (parsed) === "boolean") return parsed;
|
|
@@ -127,7 +144,7 @@ const {observe} = (() => {
|
|
|
127
144
|
}
|
|
128
145
|
throw new TypeError(`Unable to coerce ${value} to ${toType}`)
|
|
129
146
|
}
|
|
130
|
-
const Reactor = (value
|
|
147
|
+
const Reactor = (value) => {
|
|
131
148
|
if (value && typeof (value) === "object") {
|
|
132
149
|
if (value.__isReactor__) return value;
|
|
133
150
|
const childReactors = [],
|
|
@@ -155,7 +172,7 @@ const {observe} = (() => {
|
|
|
155
172
|
return value;
|
|
156
173
|
}
|
|
157
174
|
if (value && type === "object") {
|
|
158
|
-
value = Reactor(value
|
|
175
|
+
value = Reactor(value);
|
|
159
176
|
childReactors.push(value);
|
|
160
177
|
}
|
|
161
178
|
target[property] = value;
|
|
@@ -165,7 +182,7 @@ const {observe} = (() => {
|
|
|
165
182
|
const type = typeof (value);
|
|
166
183
|
if (target[property] !== value) {
|
|
167
184
|
if (value && type === "object") {
|
|
168
|
-
value = Reactor(value
|
|
185
|
+
value = Reactor(value);
|
|
169
186
|
childReactors.push(value);
|
|
170
187
|
}
|
|
171
188
|
target[property] = value;
|
|
@@ -211,7 +228,7 @@ const {observe} = (() => {
|
|
|
211
228
|
if (newValue == null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
|
|
212
229
|
if (value !== newValue) {
|
|
213
230
|
event.oldValue = value;
|
|
214
|
-
target[property].value = reactive ? Reactor(newValue
|
|
231
|
+
target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
|
|
215
232
|
target.postEvent.value("change", event);
|
|
216
233
|
if (event.defaultPrevented) target[property].value = value;
|
|
217
234
|
}
|
|
@@ -231,7 +248,7 @@ const {observe} = (() => {
|
|
|
231
248
|
const observer = new MutationObserver((mutations) => {
|
|
232
249
|
mutations.forEach((mutation) => {
|
|
233
250
|
if (mutation.type === "attributes") {
|
|
234
|
-
if (framed) debugger;
|
|
251
|
+
//if (framed) debugger;
|
|
235
252
|
const name = mutation.attributeName,
|
|
236
253
|
target = mutation.target,
|
|
237
254
|
value = target.getAttribute(name);
|
|
@@ -274,6 +291,7 @@ const {observe} = (() => {
|
|
|
274
291
|
nodes.push(root, ...getNodes(root.shadowRoot))
|
|
275
292
|
} else {
|
|
276
293
|
for (const node of root.childNodes) {
|
|
294
|
+
if(node.tagName==="SCRIPT") continue;
|
|
277
295
|
if (node.nodeType === Node.TEXT_NODE && node.nodeValue?.includes("${")) {
|
|
278
296
|
node.template ||= node.nodeValue;
|
|
279
297
|
nodes.push(node);
|
|
@@ -295,12 +313,17 @@ const {observe} = (() => {
|
|
|
295
313
|
}
|
|
296
314
|
return nodes;
|
|
297
315
|
}
|
|
298
|
-
const
|
|
299
|
-
|
|
316
|
+
const resolveNodeOrText = (node, component,safe) => {
|
|
317
|
+
const type = typeof(node),
|
|
318
|
+
template = type === "string" ? node.trim() : node.template;
|
|
319
|
+
if (template) {
|
|
300
320
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
321
|
+
let value = Function("context", "with(context) { return `" + Lightview.sanitizeTemplate(template) + "` }")(component.varsProxy);
|
|
322
|
+
value = node.nodeType===Node.TEXT_NODE || !safe ? value : Lightview.escapeHTML(value);
|
|
323
|
+
if(type==="string") return value;
|
|
324
|
+
node.nodeValue = value=="null" || value=="undefined" ? "" : value;
|
|
303
325
|
} catch (e) {
|
|
326
|
+
console.warn(e);
|
|
304
327
|
if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
|
|
305
328
|
}
|
|
306
329
|
}
|
|
@@ -323,43 +346,47 @@ const {observe} = (() => {
|
|
|
323
346
|
if (["checkbox"].includes(inputType)) return "boolean";
|
|
324
347
|
return "any";
|
|
325
348
|
}
|
|
326
|
-
const
|
|
327
|
-
[...node.querySelectorAll('a[href][target^="#"]')].forEach((node) => {
|
|
349
|
+
const importAnchors = (node, component) => {
|
|
350
|
+
[...node.querySelectorAll('a[href$=".html"][target^="#"]')].forEach((node) => {
|
|
328
351
|
node.removeEventListener("click", anchorHandler);
|
|
329
352
|
addListener(node, "click", anchorHandler);
|
|
330
353
|
})
|
|
331
354
|
}
|
|
332
|
-
const
|
|
355
|
+
const bound = new WeakSet();
|
|
356
|
+
const bindInput = (input, variableName, component,value) => {
|
|
357
|
+
if(bound.has(input)) return;
|
|
358
|
+
bound.add(input);
|
|
333
359
|
const inputtype = input.tagName === "SELECT" || input.tagName === "TEXTAREA" ? "text" : input.getAttribute("type"),
|
|
334
360
|
type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
|
|
335
361
|
deflt = input.getAttribute("default");
|
|
336
362
|
value ||= input.getAttribute("value");
|
|
337
|
-
let variable = component.vars[
|
|
363
|
+
let variable = component.vars[variableName] || {type};
|
|
338
364
|
if (type !== variable.type) {
|
|
339
365
|
if (variable.type === "any" || variable.type === "unknown") variable.type = type;
|
|
340
|
-
else throw new TypeError(`Attempt to bind <input name="${
|
|
366
|
+
else throw new TypeError(`Attempt to bind <input name="${variableName}" type="${type}"> to variable ${variableName}:${variable.type}`)
|
|
341
367
|
}
|
|
342
|
-
component.variables({[
|
|
343
|
-
component.setValue(
|
|
368
|
+
component.variables({[variableName]: type});
|
|
369
|
+
if(inputtype!=="radio") component.setValue(variableName,value);
|
|
344
370
|
let eventname = "change";
|
|
345
371
|
if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
|
|
346
372
|
eventname = "input";
|
|
347
373
|
}
|
|
348
|
-
|
|
349
|
-
event.stopImmediatePropagation();
|
|
350
|
-
|
|
351
|
-
let value = target.value;
|
|
374
|
+
const listener = (event) => {
|
|
375
|
+
if(event) event.stopImmediatePropagation();
|
|
376
|
+
let value = input.value;
|
|
352
377
|
if (inputtype === "checkbox") {
|
|
353
378
|
value = input.checked
|
|
354
|
-
} else if (
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
379
|
+
} else if (input.tagName === "SELECT") {
|
|
380
|
+
if (input.hasAttribute("multiple")) {
|
|
381
|
+
const varvalue = component.varsProxy[variableName];
|
|
382
|
+
value = [...input.querySelectorAll("option")]
|
|
383
|
+
.filter((option) => option.selected || resolveNodeOrText(option.attributes.value||option.innerText,component)===value)
|
|
358
384
|
.map((option) => option.getAttribute("value") || option.innerText);
|
|
359
385
|
}
|
|
360
386
|
}
|
|
361
|
-
component.varsProxy[
|
|
362
|
-
}
|
|
387
|
+
component.varsProxy[variableName] = coerce(value, type);
|
|
388
|
+
};
|
|
389
|
+
addListener(input, eventname, listener);
|
|
363
390
|
}
|
|
364
391
|
const tryParse = (value) => {
|
|
365
392
|
try {
|
|
@@ -369,16 +396,18 @@ const {observe} = (() => {
|
|
|
369
396
|
}
|
|
370
397
|
}
|
|
371
398
|
let reserved = {
|
|
399
|
+
any: {value: "any",constant: true},
|
|
372
400
|
boolean: {value: "boolean", constant: true},
|
|
373
401
|
string: {value: "string", constant: true},
|
|
374
402
|
number: {value: "number", constant: true},
|
|
403
|
+
object: {value: "object", constant: true},
|
|
375
404
|
observed: {value: true, constant: true},
|
|
376
405
|
reactive: {value: true, constant: true},
|
|
377
406
|
shared: {value: true, constant: true},
|
|
378
407
|
exported: {value: true, constant: true},
|
|
379
408
|
imported: {value: true, constant: true}
|
|
380
409
|
};
|
|
381
|
-
const createClass = (domElementNode, {observer,
|
|
410
|
+
const createClass = (domElementNode, {observer, framed}) => {
|
|
382
411
|
const instances = new Set(),
|
|
383
412
|
dom = domElementNode.tagName === "TEMPLATE"
|
|
384
413
|
? domElementNode.content.cloneNode(true)
|
|
@@ -432,7 +461,7 @@ const {observe} = (() => {
|
|
|
432
461
|
})
|
|
433
462
|
});
|
|
434
463
|
[...dom.childNodes].forEach((child) => shadow.appendChild(child.cloneNode(true)));
|
|
435
|
-
|
|
464
|
+
importAnchors(shadow, this);
|
|
436
465
|
}
|
|
437
466
|
|
|
438
467
|
get siblings() {
|
|
@@ -478,15 +507,21 @@ const {observe} = (() => {
|
|
|
478
507
|
const nodes = getNodes(ctx);
|
|
479
508
|
nodes.forEach((node) => {
|
|
480
509
|
if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
|
|
481
|
-
render(!!node.template, () =>
|
|
510
|
+
render(!!node.template, () => resolveNodeOrText(node, this))
|
|
482
511
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
483
512
|
// resolve the value before all else;
|
|
484
513
|
const attr = node.attributes.value;
|
|
485
|
-
let name;
|
|
486
514
|
if (attr && attr.template) {
|
|
487
515
|
render(!!attr.template, () => {
|
|
488
|
-
const value =
|
|
489
|
-
eltype =
|
|
516
|
+
const value = resolveNodeOrText(attr, this),
|
|
517
|
+
eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
|
|
518
|
+
if(node.attributes.value) {
|
|
519
|
+
const template = attr.template;
|
|
520
|
+
if(/\$\{[a-zA-z_]+\}/g.test(template)) {
|
|
521
|
+
const name = template.substring(2,template.length-1);
|
|
522
|
+
bindInput(node,name,this,value);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
490
525
|
if (eltype === "checkbox") {
|
|
491
526
|
if (coerce(value, "boolean") === true) {
|
|
492
527
|
node.setAttribute("checked", "");
|
|
@@ -495,7 +530,7 @@ const {observe} = (() => {
|
|
|
495
530
|
node.removeAttribute("checked");
|
|
496
531
|
node.checked = false;
|
|
497
532
|
}
|
|
498
|
-
const vname =
|
|
533
|
+
const vname = resolveNodeOrText(node.attributes.name, ctx);
|
|
499
534
|
if (vname) ctx.setValue(vname, node.checked, {coerceTo: "boolean"});
|
|
500
535
|
}
|
|
501
536
|
if (node.tagName === "SELECT") {
|
|
@@ -503,38 +538,30 @@ const {observe} = (() => {
|
|
|
503
538
|
if (node.hasAttribute("multiple")) values = coerce(value, Array);
|
|
504
539
|
[...node.querySelectorAll("option")].forEach((option) => {
|
|
505
540
|
if (option.hasAttribute("value")) {
|
|
506
|
-
if (values.includes(
|
|
541
|
+
if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
|
|
507
542
|
option.setAttribute("selected", "");
|
|
508
543
|
option.selected = true;
|
|
509
544
|
}
|
|
510
|
-
} else if (option.innerText
|
|
545
|
+
} else if (values.includes(resolveNodeOrText(option.innerText,ctx))) {
|
|
511
546
|
option.setAttribute("selected", "");
|
|
512
547
|
option.selected = true;
|
|
513
548
|
}
|
|
514
549
|
})
|
|
515
550
|
}
|
|
516
|
-
if(node.attributes.value) {
|
|
517
|
-
const valueattr = node.attributes.value,
|
|
518
|
-
template = valueattr.template || valueattr.template.value;
|
|
519
|
-
if(template.startsWith("${") && template.endsWith("}")) {
|
|
520
|
-
const name = template.substring(2,template.length-1);
|
|
521
|
-
if(!name.includes(" ")) bindInput(node,name,this,value);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
551
|
});
|
|
525
552
|
}
|
|
526
553
|
[...node.attributes].forEach((attr) => {
|
|
527
|
-
if (attr.name === "value") return;
|
|
554
|
+
if (attr.name === "value" && attr.template) return;
|
|
528
555
|
const {name, value} = attr;
|
|
529
556
|
if (name === "type") {
|
|
530
557
|
if (value === "radio") {
|
|
531
|
-
const name =
|
|
558
|
+
const name = resolveNodeOrText(node.attributes.name, ctx);
|
|
532
559
|
for (const vname of this.getVariableNames()) {
|
|
533
560
|
if (vname === name) {
|
|
534
561
|
render(true, () => {
|
|
535
|
-
const name =
|
|
562
|
+
const name = resolveNodeOrText(node.attributes.name, ctx),
|
|
536
563
|
varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
|
|
537
|
-
if (varvalue ==
|
|
564
|
+
if (varvalue == resolveNodeOrText(node.attributes.value, ctx)) {
|
|
538
565
|
node.setAttribute("checked", "");
|
|
539
566
|
node.checked = true;
|
|
540
567
|
} else {
|
|
@@ -553,9 +580,9 @@ const {observe} = (() => {
|
|
|
553
580
|
if (type === "") { // name is :something
|
|
554
581
|
render(!!attr.template, () => {
|
|
555
582
|
const value = attr.value,
|
|
556
|
-
elvalue =
|
|
557
|
-
eltype =
|
|
558
|
-
elname =
|
|
583
|
+
elvalue = resolveNodeOrText(node.attributes.value, ctx),
|
|
584
|
+
eltype = resolveNodeOrText(node.attributes.type, ctx),
|
|
585
|
+
elname = resolveNodeOrText(node.attributes.name, ctx);
|
|
559
586
|
if (params[0]) {
|
|
560
587
|
if (value === "true") node.setAttribute(params[0], "")
|
|
561
588
|
else node.removeAttribute(params[0]);
|
|
@@ -567,29 +594,31 @@ const {observe} = (() => {
|
|
|
567
594
|
} else if (type === "l-on") {
|
|
568
595
|
let listener;
|
|
569
596
|
render(!!attr.template, () => {
|
|
570
|
-
const value =
|
|
597
|
+
const value = resolveNodeOrText(attr, this);
|
|
571
598
|
if (listener) node.removeEventListener(params[0], listener);
|
|
572
599
|
listener = this[value] || window[value] || Function(value);
|
|
573
600
|
addListener(node, params[0], listener);
|
|
574
601
|
})
|
|
575
602
|
} else if (type === "l-if") {
|
|
576
603
|
render(!!attr.template, () => {
|
|
577
|
-
const value =
|
|
604
|
+
const value = resolveNodeOrText(attr, this);
|
|
578
605
|
node.style.setProperty("display", value === "true" ? "revert" : "none");
|
|
579
606
|
})
|
|
580
607
|
} else if (type === "l-for") {
|
|
581
608
|
node.template ||= node.innerHTML;
|
|
582
609
|
render(!!attr.template, () => {
|
|
583
610
|
const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
|
|
584
|
-
value =
|
|
611
|
+
value = resolveNodeOrText(attr, this),
|
|
585
612
|
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
586
613
|
target = what === "each" ? coerced : Object[what](coerced),
|
|
587
614
|
html = target.reduce((html, item, i, target) => {
|
|
588
|
-
return html += Function("context", "with(context) { return `" + node.template + "` }")(
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
615
|
+
return html += Function("vars","context", "with(vars) { with(context) { return `" + node.template + "` }}")(
|
|
616
|
+
ctx.varsProxy,
|
|
617
|
+
{
|
|
618
|
+
[vname]: item,
|
|
619
|
+
[index]: i,
|
|
620
|
+
[array]: target
|
|
621
|
+
})
|
|
593
622
|
}, ""),
|
|
594
623
|
parsed = parser.parseFromString(html, "text/html");
|
|
595
624
|
if (!window.lightviewDebug) {
|
|
@@ -605,7 +634,7 @@ const {observe} = (() => {
|
|
|
605
634
|
}
|
|
606
635
|
})
|
|
607
636
|
} else if (attr.template) {
|
|
608
|
-
render(!!attr.template, () =>
|
|
637
|
+
render(!!attr.template, () => resolveNodeOrText(attr, this));
|
|
609
638
|
}
|
|
610
639
|
})
|
|
611
640
|
}
|
|
@@ -621,23 +650,23 @@ const {observe} = (() => {
|
|
|
621
650
|
//Object.defineProperty(this, "adoptedCallback", {configurable: true, writable: true, value});
|
|
622
651
|
}
|
|
623
652
|
|
|
624
|
-
connected(
|
|
625
|
-
this.connectedCallback =
|
|
653
|
+
connected(callback) {
|
|
654
|
+
this.connectedCallback = callback;
|
|
626
655
|
//Object.defineProperty(this, "connectedCallback", {configurable: true, writable: true, value});
|
|
627
656
|
}
|
|
628
657
|
|
|
629
|
-
attributeChanged(
|
|
630
|
-
this.attributeChangedCallback =
|
|
658
|
+
attributeChanged(callback) {
|
|
659
|
+
this.attributeChangedCallback = callback;
|
|
631
660
|
//Object.defineProperty(this, "attributeChangedCallback", {configurable: true, writable: true, value});
|
|
632
661
|
}
|
|
633
662
|
|
|
634
|
-
disconnected(
|
|
663
|
+
disconnected(callback) {
|
|
635
664
|
Object.defineProperty(this, "disconnectedCallback", {
|
|
636
665
|
configurable: true,
|
|
637
666
|
writable: true,
|
|
638
667
|
value: () => {
|
|
639
668
|
value();
|
|
640
|
-
super.disconnectedCallback(
|
|
669
|
+
super.disconnectedCallback(callback);
|
|
641
670
|
}
|
|
642
671
|
});
|
|
643
672
|
}
|
|
@@ -648,19 +677,19 @@ const {observe} = (() => {
|
|
|
648
677
|
})
|
|
649
678
|
}
|
|
650
679
|
|
|
651
|
-
setValue(
|
|
680
|
+
setValue(variableName, value, {coerceTo = typeof (value)} = {}) {
|
|
652
681
|
if (!this.isConnected) {
|
|
653
682
|
instances.delete(this);
|
|
654
683
|
return false;
|
|
655
684
|
}
|
|
656
|
-
let {type} = this.vars[
|
|
685
|
+
let {type} = this.vars[variableName] || {};
|
|
657
686
|
if (type) {
|
|
658
687
|
value = coerce(value, type);
|
|
659
|
-
if (this.varsProxy[
|
|
660
|
-
const variable = this.vars[
|
|
688
|
+
if (this.varsProxy[variableName] !== value) {
|
|
689
|
+
const variable = this.vars[variableName];
|
|
661
690
|
if (variable.shared) {
|
|
662
691
|
const event = new VariableEvent({
|
|
663
|
-
variableName:
|
|
692
|
+
variableName: variableName,
|
|
664
693
|
value: value,
|
|
665
694
|
oldValue: variable.value
|
|
666
695
|
});
|
|
@@ -668,12 +697,12 @@ const {observe} = (() => {
|
|
|
668
697
|
this.vars.postEvent.value("change", event);
|
|
669
698
|
if (event.defaultPrevented) variable.value = value;
|
|
670
699
|
} else {
|
|
671
|
-
this.varsProxy[
|
|
700
|
+
this.varsProxy[variableName] = value;
|
|
672
701
|
}
|
|
673
702
|
}
|
|
674
703
|
return true;
|
|
675
704
|
}
|
|
676
|
-
this.vars[
|
|
705
|
+
this.vars[variableName] = {name, type: coerceTo, value: coerce(value, coerceTo)};
|
|
677
706
|
return false;
|
|
678
707
|
}
|
|
679
708
|
|
|
@@ -694,7 +723,7 @@ const {observe} = (() => {
|
|
|
694
723
|
}
|
|
695
724
|
if (reactive) {
|
|
696
725
|
variable.reactive = true;
|
|
697
|
-
this.vars[key] = Reactor(variable
|
|
726
|
+
this.vars[key] = Reactor(variable);
|
|
698
727
|
}
|
|
699
728
|
if (shared) {
|
|
700
729
|
variable.shared = true;
|
|
@@ -742,7 +771,7 @@ const {observe} = (() => {
|
|
|
742
771
|
}
|
|
743
772
|
}
|
|
744
773
|
}
|
|
745
|
-
const createComponent = (name, node, {observer
|
|
774
|
+
const createComponent = (name, node, {framed,observer} = {}) => {
|
|
746
775
|
let ctor = customElements.get(name);
|
|
747
776
|
if (ctor) {
|
|
748
777
|
if (framed && !ctor.lightviewFramed) {
|
|
@@ -752,38 +781,47 @@ const {observe} = (() => {
|
|
|
752
781
|
}
|
|
753
782
|
return ctor;
|
|
754
783
|
}
|
|
755
|
-
ctor = createClass(node, {observer,
|
|
784
|
+
ctor = createClass(node, {observer, framed});
|
|
756
785
|
customElements.define(name, ctor);
|
|
786
|
+
Lightview.customElements.set(name,ctor);
|
|
757
787
|
return ctor;
|
|
758
788
|
}
|
|
789
|
+
Lightview.customElements = new Map();
|
|
759
790
|
Lightview.createComponent = createComponent;
|
|
760
791
|
//Object.defineProperty(Lightview, "createComponent", {writable: true, configurable: true, value: createComponent})
|
|
761
792
|
const importLink = async (link, observer) => {
|
|
762
793
|
const url = (new URL(link.getAttribute("href"), window.location.href)),
|
|
763
794
|
as = link.getAttribute("as") || getNameFromPath(url.pathname);
|
|
764
|
-
if (url.hostname !== window.location.hostname) {
|
|
765
|
-
throw new URIError(`importLink:HTML imports must be from same domain: ${url.hostname}!=${location.hostname}
|
|
795
|
+
if (url.hostname !== window.location.hostname && !link.getAttribute("crossorigin")) {
|
|
796
|
+
throw new URIError(`importLink:HTML imports must be from same domain: ${url.hostname}!=${location.hostname} unless 'crossorigin' attribute is set.`)
|
|
766
797
|
}
|
|
767
798
|
if (!customElements.get(as)) {
|
|
768
799
|
const html = await (await fetch(url.href)).text(),
|
|
769
800
|
dom = parser.parseFromString(html, "text/html"),
|
|
770
|
-
|
|
771
|
-
|
|
801
|
+
unhide = !!dom.head.querySelector('meta[name="l-unhide"]'),
|
|
802
|
+
links = dom.head.querySelectorAll('link[href$=".html"][rel=module]');
|
|
803
|
+
for(const childlink of links) {
|
|
804
|
+
const href = childlink.getAttribute("href"),
|
|
805
|
+
childurl = new URL(href,url.href);
|
|
806
|
+
childlink.setAttribute("href",childurl.href);
|
|
807
|
+
if(link.hasAttribute("crossorigin")) childlink.setAttribute("crossorigin",link.getAttribute("crossorigin"))
|
|
808
|
+
await importLink(childlink,observer);
|
|
809
|
+
}
|
|
772
810
|
if (unhide) dom.body.removeAttribute("hidden");
|
|
773
|
-
createComponent(as, dom.body, {observer
|
|
811
|
+
createComponent(as, dom.body, {observer});
|
|
774
812
|
}
|
|
775
813
|
return {as};
|
|
776
814
|
}
|
|
777
815
|
const importLinks = async () => {
|
|
778
816
|
const observer = createObserver(document.body);
|
|
779
|
-
for (const link of [...document.querySelectorAll(
|
|
780
|
-
await importLink(link);
|
|
817
|
+
for (const link of [...document.querySelectorAll(`link[href$=".html"][rel=module]`)]) {
|
|
818
|
+
await importLink(link,observer);
|
|
781
819
|
}
|
|
782
820
|
}
|
|
783
821
|
|
|
784
|
-
const bodyAsComponent = ({as = "x-body", unhide,
|
|
822
|
+
const bodyAsComponent = ({as = "x-body", unhide, framed} = {}) => {
|
|
785
823
|
const parent = document.body.parentElement;
|
|
786
|
-
createComponent(as, document.body, {
|
|
824
|
+
createComponent(as, document.body, {framed});
|
|
787
825
|
const component = document.createElement(as);
|
|
788
826
|
parent.replaceChild(component, document.body);
|
|
789
827
|
Object.defineProperty(document, "body", {
|
|
@@ -828,13 +866,12 @@ const {observe} = (() => {
|
|
|
828
866
|
if (!domContentLoadedEvent) addListener(window, "DOMContentLoaded", (event) => domContentLoadedEvent = event);
|
|
829
867
|
let OBSERVER;
|
|
830
868
|
const loader = async (whenFramed) => {
|
|
831
|
-
|
|
832
|
-
const
|
|
833
|
-
unhide = !!document.querySelector('meta[name="l-unhide"]'),
|
|
869
|
+
await importLinks();
|
|
870
|
+
const unhide = !!document.querySelector('meta[name="l-unhide"]'),
|
|
834
871
|
isolated = !!document.querySelector('meta[name="l-isolate"]'),
|
|
835
872
|
enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
|
|
836
873
|
if (whenFramed) {
|
|
837
|
-
whenFramed({unhide,
|
|
874
|
+
whenFramed({unhide, isolated, enableFrames, framed: true});
|
|
838
875
|
if (!isolated) {
|
|
839
876
|
postMessage.enabled = true;
|
|
840
877
|
addListener(window, "message", ({data}) => {
|
|
@@ -869,7 +906,7 @@ const {observe} = (() => {
|
|
|
869
906
|
postMessage({type: "DOMContentLoaded"})
|
|
870
907
|
}
|
|
871
908
|
} else if (url.searchParams.has("as")) {
|
|
872
|
-
bodyAsComponent({as: url.searchParams.get("as"), unhide
|
|
909
|
+
bodyAsComponent({as: url.searchParams.get("as"), unhide});
|
|
873
910
|
}
|
|
874
911
|
if (enableFrames) {
|
|
875
912
|
postMessage.enabled = true;
|
|
@@ -943,9 +980,9 @@ const {observe} = (() => {
|
|
|
943
980
|
}
|
|
944
981
|
}
|
|
945
982
|
}
|
|
946
|
-
const whenFramed = (
|
|
983
|
+
const whenFramed = (callback, {isolated} = {}) => {
|
|
947
984
|
// loads for framed content
|
|
948
|
-
addListener(document, "DOMContentLoaded", (event) => loader(
|
|
985
|
+
addListener(document, "DOMContentLoaded", (event) => loader(callback));
|
|
949
986
|
}
|
|
950
987
|
Lightview.whenFramed = whenFramed;
|
|
951
988
|
//Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
|
|
@@ -956,6 +993,4 @@ const {observe} = (() => {
|
|
|
956
993
|
}
|
|
957
994
|
|
|
958
995
|
return {observe}
|
|
959
|
-
})();
|
|
960
|
-
|
|
961
|
-
|
|
996
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightview",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"description": "Small, simple, powerful web UI and micro front end creation ...
|
|
3
|
+
"version": "1.4.8b",
|
|
4
|
+
"description": "Small, simple, powerful web UI and micro front end creation ... Great ideas from Svelte, React, Vue and Riot combined.",
|
|
5
5
|
"main": "lightview.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "set NODE_OPTIONS=--experimental-vm-modules && jest ./test"
|