lightview 1.2.1-b → 1.4.0-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 +4 -2
- package/counter.html +30 -0
- package/directives.html +77 -0
- package/lightview.js +288 -234
- package/package.json +9 -3
- package/remote.html +2 -1
- package/remoteform.html +60 -32
- package/scratch.html +69 -0
- package/xor.html +60 -0
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# lightview v1.
|
|
1
|
+
# lightview v1.4.0b (BETA)
|
|
2
2
|
|
|
3
|
-
Small, simple, powerful web UI creation ...
|
|
3
|
+
Small, simple, powerful web UI and micro front end creation ...
|
|
4
|
+
|
|
5
|
+
Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 6K (minified/gzipped).
|
|
4
6
|
|
|
5
7
|
See the docs and examples at [https://lightview.dev](https://lightview.dev).
|
|
6
8
|
|
package/counter.html
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<head>
|
|
2
|
+
<title>Counter</title>
|
|
3
|
+
<script src="../lightview.js?as=x-body"></script>
|
|
4
|
+
</head>
|
|
5
|
+
|
|
6
|
+
<body>
|
|
7
|
+
<p>
|
|
8
|
+
<button l-on:click="bump">Click count:${count}</button>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<script type="lightview/module">
|
|
12
|
+
self.variables({
|
|
13
|
+
count: number
|
|
14
|
+
}, {
|
|
15
|
+
reactive
|
|
16
|
+
});
|
|
17
|
+
debugger;
|
|
18
|
+
count = 0;
|
|
19
|
+
self.bump = () => count++;
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<style>
|
|
23
|
+
button {
|
|
24
|
+
margin: 20px;
|
|
25
|
+
background: gray
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
28
|
+
</body>
|
|
29
|
+
|
|
30
|
+
</html>
|
package/directives.html
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<head>
|
|
4
|
+
<title>Directives</title>
|
|
5
|
+
<script src="./lightview.js?as=x-body"></script>
|
|
6
|
+
</head>
|
|
7
|
+
|
|
8
|
+
<body>
|
|
9
|
+
|
|
10
|
+
<div style="margin:20px">
|
|
11
|
+
<p>
|
|
12
|
+
Show: <input type="checkbox" value="${on}">
|
|
13
|
+
<div l-if="${on}">
|
|
14
|
+
Now you've done it. You've exposed me.
|
|
15
|
+
</div>
|
|
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>
|
|
53
|
+
<script type="lightview/module">
|
|
54
|
+
self.variables({on:boolean,options:Array},{reactive});
|
|
55
|
+
|
|
56
|
+
on = true;
|
|
57
|
+
options = ["lettuce"];
|
|
58
|
+
|
|
59
|
+
// demo instrumentation
|
|
60
|
+
const variableValues = () => {
|
|
61
|
+
const el = self.getElementById("variables");
|
|
62
|
+
while(el.lastElementChild) el.lastElementChild.remove();
|
|
63
|
+
self.getVariableNames().forEach((name) => {
|
|
64
|
+
const line = document.createElement("div");
|
|
65
|
+
line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
|
|
66
|
+
el.appendChild(line);
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
variableValues();
|
|
70
|
+
addEventListener("change",()=> {
|
|
71
|
+
variableValues()
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
</script>
|
|
75
|
+
</body>
|
|
76
|
+
|
|
77
|
+
</html>
|
package/lightview.js
CHANGED
|
@@ -29,6 +29,10 @@ const Lightview = {};
|
|
|
29
29
|
const {observe} = (() => {
|
|
30
30
|
let CURRENTOBSERVER;
|
|
31
31
|
const parser = new DOMParser();
|
|
32
|
+
|
|
33
|
+
const addListener = (node,eventName,callback) => {
|
|
34
|
+
node.addEventListener(eventName,callback); // just used to make code footprint smaller
|
|
35
|
+
}
|
|
32
36
|
const anchorHandler = async (event) => {
|
|
33
37
|
event.preventDefault();
|
|
34
38
|
const target = event.target;
|
|
@@ -57,21 +61,22 @@ const {observe} = (() => {
|
|
|
57
61
|
}
|
|
58
62
|
CURRENTOBSERVER = null;
|
|
59
63
|
}
|
|
64
|
+
|
|
60
65
|
observer.cancel = () => observer.cancelled = true;
|
|
61
66
|
observer();
|
|
62
67
|
return observer;
|
|
63
68
|
}
|
|
64
69
|
const coerce = (value, toType) => {
|
|
65
|
-
if(value+""==="null" || value+""==="undefined") return value;
|
|
70
|
+
if (value + "" === "null" || value + "" === "undefined") return value;
|
|
66
71
|
const type = typeof (value);
|
|
67
72
|
if (type === toType) return value;
|
|
68
73
|
if (toType === "number") return parseFloat(value + "");
|
|
69
74
|
if (toType === "boolean") {
|
|
70
|
-
if(["on","checked","selected"].includes(value)) return true;
|
|
75
|
+
if (["on", "checked", "selected"].includes(value)) return true;
|
|
71
76
|
try {
|
|
72
77
|
const parsed = JSON.parse(value + "");
|
|
73
78
|
if (typeof (parsed) === "boolean") return parsed;
|
|
74
|
-
return [1,"on","checked","selected"].includes(parsed);
|
|
79
|
+
return [1, "on", "checked", "selected"].includes(parsed);
|
|
75
80
|
} catch (e) {
|
|
76
81
|
throw new TypeError(`Unable to convert ${value} into 'boolean'`);
|
|
77
82
|
}
|
|
@@ -79,10 +84,10 @@ const {observe} = (() => {
|
|
|
79
84
|
if (toType === "string") return value + "";
|
|
80
85
|
const isfunction = typeof (toType) === "function";
|
|
81
86
|
if ((toType === "object" || isfunction)) {
|
|
82
|
-
if(type==="object")
|
|
83
|
-
if(value instanceof toType) return value;
|
|
87
|
+
if (type === "object") {
|
|
88
|
+
if (value instanceof toType) return value;
|
|
84
89
|
}
|
|
85
|
-
if(type === "string") {
|
|
90
|
+
if (type === "string") {
|
|
86
91
|
value = value.trim();
|
|
87
92
|
try {
|
|
88
93
|
if (isfunction) {
|
|
@@ -121,9 +126,13 @@ const {observe} = (() => {
|
|
|
121
126
|
proxy = new Proxy(value, {
|
|
122
127
|
get(target, property) {
|
|
123
128
|
if (property === "__isReactor__") return true;
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
if (target instanceof Array) {
|
|
130
|
+
if (property === "toJSON") return function toJSON() {
|
|
131
|
+
return [...target];
|
|
132
|
+
}
|
|
133
|
+
if (property === "toString") return function toString() {
|
|
134
|
+
return JSON.stringify(target);
|
|
135
|
+
}
|
|
127
136
|
}
|
|
128
137
|
let value = target[property];
|
|
129
138
|
const type = typeof (value);
|
|
@@ -183,19 +192,19 @@ const {observe} = (() => {
|
|
|
183
192
|
if (target[property] === undefined) {
|
|
184
193
|
target[property] = {type: "any", value: newValue}; // should we allow this, do first to prevent loops
|
|
185
194
|
target.postEvent.value("change", event);
|
|
186
|
-
if(event.defaultPrevented) delete target[property].value;
|
|
195
|
+
if (event.defaultPrevented) delete target[property].value;
|
|
187
196
|
return true;
|
|
188
197
|
}
|
|
189
|
-
const {type, value, shared, exported, constant,reactive} = target[property];
|
|
198
|
+
const {type, value, shared, exported, constant, reactive} = target[property];
|
|
190
199
|
if (constant) throw new TypeError(`${property}:${type} is a constant`);
|
|
191
200
|
const newtype = typeof (newValue),
|
|
192
201
|
typetype = typeof (type);
|
|
193
|
-
if (newValue==null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
|
|
202
|
+
if (newValue == null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
|
|
194
203
|
if (value !== newValue) {
|
|
195
204
|
event.oldValue = value;
|
|
196
205
|
target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
|
|
197
206
|
target.postEvent.value("change", event);
|
|
198
|
-
if(event.defaultPrevented) target[property].value = value;
|
|
207
|
+
if (event.defaultPrevented) target[property].value = value;
|
|
199
208
|
}
|
|
200
209
|
return true;
|
|
201
210
|
}
|
|
@@ -218,7 +227,7 @@ const {observe} = (() => {
|
|
|
218
227
|
if (target.observedAttributes && target.observedAttributes.includes(name)) {
|
|
219
228
|
const value = target.getAttribute(name);
|
|
220
229
|
if (value !== mutation.oldValue) {
|
|
221
|
-
target.
|
|
230
|
+
target.setValue(name, value);
|
|
222
231
|
if (target.attributeChangedCallback) target.attributeChangedCallback(name, value, mutation.oldValue);
|
|
223
232
|
}
|
|
224
233
|
}
|
|
@@ -255,38 +264,36 @@ const {observe} = (() => {
|
|
|
255
264
|
nodes.push(node);
|
|
256
265
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
257
266
|
let skip;
|
|
267
|
+
if(node.getAttribute("type")==="radio") nodes.push(node);
|
|
258
268
|
[...node.attributes].forEach((attr) => {
|
|
259
269
|
if (attr.value.includes("${")) {
|
|
260
270
|
attr.template ||= attr.value;
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
if (attr.name.includes(":") || attr.name.startsWith("l-")) {
|
|
271
|
+
nodes.push(node);
|
|
272
|
+
} else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
|
|
264
273
|
skip = attr.name.includes("l-for:");
|
|
265
|
-
|
|
274
|
+
nodes.push(node)
|
|
266
275
|
}
|
|
267
276
|
})
|
|
268
|
-
if (!skip)
|
|
269
|
-
if (!node.shadowRoot) nodes.push(...getNodes(node));
|
|
270
|
-
}
|
|
277
|
+
if (!skip && !node.shadowRoot) nodes.push(...getNodes(node));
|
|
271
278
|
}
|
|
272
279
|
}
|
|
273
280
|
}
|
|
274
281
|
return nodes;
|
|
275
282
|
}
|
|
276
283
|
const resolveNode = (node, component) => {
|
|
277
|
-
if (node
|
|
284
|
+
if (node?.template) {
|
|
278
285
|
try {
|
|
279
286
|
const value = Function("context", "with(context) { return `" + node.template + "` }")(component.varsProxy);
|
|
280
|
-
node.nodeValue = value==="null" || value==="undefined" ? "" : value;
|
|
287
|
+
node.nodeValue = value === "null" || value === "undefined" ? "" : value;
|
|
281
288
|
} catch (e) {
|
|
282
289
|
if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
|
|
283
290
|
}
|
|
284
291
|
}
|
|
285
|
-
return node
|
|
292
|
+
return node?.nodeValue;
|
|
286
293
|
}
|
|
287
|
-
const render = (
|
|
294
|
+
const render = (hasTemplate, render) => {
|
|
288
295
|
let observer;
|
|
289
|
-
if (
|
|
296
|
+
if (hasTemplate) {
|
|
290
297
|
if (observer) observer.cancel();
|
|
291
298
|
observer = observe(render)
|
|
292
299
|
} else {
|
|
@@ -298,57 +305,65 @@ const {observe} = (() => {
|
|
|
298
305
|
if (["text", "tel", "email", "url", "search", "radio"].includes(inputType)) return "string";
|
|
299
306
|
if (["number", "range"].includes(inputType)) return "number";
|
|
300
307
|
if (["datetime"].includes(inputType)) return Date;
|
|
301
|
-
if(["checkbox"].includes(inputType)) return "boolean";
|
|
308
|
+
if (["checkbox"].includes(inputType)) return "boolean";
|
|
302
309
|
return "any";
|
|
303
310
|
}
|
|
304
|
-
const _importAnchors = (node,component) => {
|
|
311
|
+
const _importAnchors = (node, component) => {
|
|
305
312
|
[...node.querySelectorAll('a[href][target^="#"]')].forEach((node) => {
|
|
306
313
|
node.removeEventListener("click", anchorHandler);
|
|
307
|
-
node
|
|
308
|
-
})
|
|
309
|
-
}
|
|
310
|
-
const _bindForms = (node, component) => {
|
|
311
|
-
[...node.querySelectorAll("input")].forEach((input) => {
|
|
312
|
-
bindInput(input,component);
|
|
314
|
+
addListener(node,"click", anchorHandler);
|
|
313
315
|
})
|
|
314
316
|
}
|
|
315
|
-
const bindInput = (input,component) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (name) {
|
|
319
|
-
if(!input.hasAttribute("l-bind")) input.setAttribute("l-bind",vname)
|
|
320
|
-
const type = inputTypeToType(input.getAttribute("type")),
|
|
317
|
+
const bindInput = (input, name, component) => {
|
|
318
|
+
const inputtype = input.tagName === "SELECT" ? "text" : input.getAttribute("type"),
|
|
319
|
+
type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
|
|
321
320
|
deflt = input.getAttribute("default"),
|
|
322
321
|
value = input.getAttribute("value");
|
|
323
|
-
let variable = component.vars[
|
|
324
|
-
if(type!==variable.type) {
|
|
325
|
-
if(variable.type==="any" || variable.type==="unknown") variable.type = type;
|
|
326
|
-
else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${
|
|
322
|
+
let variable = component.vars[name] || {type};
|
|
323
|
+
if (type !== variable.type) {
|
|
324
|
+
if (variable.type === "any" || variable.type === "unknown") variable.type = type;
|
|
325
|
+
else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
|
|
327
326
|
}
|
|
328
|
-
component.variables({[
|
|
329
|
-
|
|
330
|
-
if (value || deflt) {
|
|
331
|
-
if (value && !value.includes("${")) {
|
|
332
|
-
variable.value = coerce(value, type);
|
|
333
|
-
input.setAttribute("value", `\${${name}}`);
|
|
334
|
-
}
|
|
335
|
-
if (deflt && !deflt.includes("${")) {
|
|
336
|
-
variable.value = coerce(deflt, type);
|
|
337
|
-
input.setAttribute("default", `\${${name}}`);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
input.addEventListener("change",(event) => {
|
|
327
|
+
component.variables({[name]: type});
|
|
328
|
+
addListener(input,"change", (event) => {
|
|
341
329
|
event.stopImmediatePropagation();
|
|
342
|
-
|
|
330
|
+
const target = event.target;
|
|
331
|
+
let value = target.value;
|
|
332
|
+
if (inputtype === "checkbox") {
|
|
333
|
+
value = input.checked
|
|
334
|
+
} else if (target.tagName === "SELECT") {
|
|
335
|
+
if (target.hasAttribute("multiple")) {
|
|
336
|
+
value = [...target.querySelectorAll("option")]
|
|
337
|
+
.filter((option) => option.selected || resolveNode(option.attributes.value,component)==value || option.innerText == value)
|
|
338
|
+
.map((option) => option.getAttribute("value") || option.innerText);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
component.varsProxy[name] = coerce(value, type);
|
|
343
342
|
})
|
|
343
|
+
}
|
|
344
|
+
const tryParse = (value) => {
|
|
345
|
+
try {
|
|
346
|
+
return JSON.parse(value);
|
|
347
|
+
} catch(e) {
|
|
348
|
+
return value;
|
|
344
349
|
}
|
|
345
350
|
}
|
|
346
|
-
|
|
351
|
+
let reserved = {
|
|
352
|
+
boolean: {value: "boolean", constant: true},
|
|
353
|
+
string: {value: "string", constant: true},
|
|
354
|
+
number: {value: "number", constant: true},
|
|
355
|
+
observed: {value: true, constant: true},
|
|
356
|
+
reactive: {value: true, constant: true},
|
|
357
|
+
shared: {value: true, constant: true},
|
|
358
|
+
exported: {value: true, constant: true},
|
|
359
|
+
imported: {value: true, constant: true}
|
|
360
|
+
};
|
|
361
|
+
const createClass = (domElementNode, {observer, importAnchors}) => {
|
|
347
362
|
const instances = new Set(),
|
|
348
|
-
dom = domElementNode.tagName==="TEMPLATE"
|
|
363
|
+
dom = domElementNode.tagName === "TEMPLATE"
|
|
349
364
|
? domElementNode.content.cloneNode(true)
|
|
350
365
|
: domElementNode.cloneNode(true);
|
|
351
|
-
if(domElementNode.tagName==="TEMPLATE") domElementNode = domElementNode.cloneNode(true);
|
|
366
|
+
if (domElementNode.tagName === "TEMPLATE") domElementNode = domElementNode.cloneNode(true);
|
|
352
367
|
return class CustomElement extends HTMLElement {
|
|
353
368
|
static get instances() {
|
|
354
369
|
return instances;
|
|
@@ -362,6 +377,7 @@ const {observe} = (() => {
|
|
|
362
377
|
shadow = this.attachShadow({mode: "open"}),
|
|
363
378
|
eventlisteners = {};
|
|
364
379
|
this.vars = {
|
|
380
|
+
...reserved,
|
|
365
381
|
addEventListener: {
|
|
366
382
|
value: (eventName, listener) => {
|
|
367
383
|
const listeners = eventlisteners[eventName] ||= new Set();
|
|
@@ -382,17 +398,9 @@ const {observe} = (() => {
|
|
|
382
398
|
type: "function",
|
|
383
399
|
constant: true
|
|
384
400
|
},
|
|
385
|
-
self: {value: currentComponent, type: CustomElement, constant: true}
|
|
386
|
-
boolean: {value: "boolean", constant: true},
|
|
387
|
-
string: {value: "string", constant: true},
|
|
388
|
-
number: {value: "number", constant: true},
|
|
389
|
-
observed: {value: true, constant: true},
|
|
390
|
-
reactive: {value: true, constant: true},
|
|
391
|
-
shared: {value: true, constant: true},
|
|
392
|
-
exported: {value: true, constant: true},
|
|
393
|
-
imported: {value: true, constant: true}
|
|
401
|
+
self: {value: currentComponent, type: CustomElement, constant: true}
|
|
394
402
|
};
|
|
395
|
-
this.defaultAttributes = domElementNode.tagName==="TEMPLATE" ? domElementNode.attributes : dom.attributes;
|
|
403
|
+
this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
|
|
396
404
|
this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
|
|
397
405
|
["getElementById", "querySelector", "querySelectorAll"]
|
|
398
406
|
.forEach((fname) => {
|
|
@@ -402,15 +410,12 @@ const {observe} = (() => {
|
|
|
402
410
|
value: (...args) => this.shadowRoot[fname](...args)
|
|
403
411
|
})
|
|
404
412
|
});
|
|
405
|
-
[...dom.childNodes].forEach((child) =>
|
|
406
|
-
|
|
407
|
-
})
|
|
408
|
-
if (bindForms) _bindForms(shadow, this);
|
|
409
|
-
if(importAnchors) _importAnchors(shadow,this);
|
|
413
|
+
[...dom.childNodes].forEach((child) => shadow.appendChild(child.cloneNode(true)));
|
|
414
|
+
if (importAnchors) _importAnchors(shadow, this);
|
|
410
415
|
}
|
|
411
416
|
|
|
412
417
|
get siblings() {
|
|
413
|
-
return [...CustomElement.instances].filter((sibling) => sibling!=this);
|
|
418
|
+
return [...CustomElement.instances].filter((sibling) => sibling != this);
|
|
414
419
|
}
|
|
415
420
|
|
|
416
421
|
adoptedCallback() {
|
|
@@ -439,9 +444,7 @@ const {observe} = (() => {
|
|
|
439
444
|
const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
|
|
440
445
|
currentScript.innerHTML = `Function('if(window["${scriptid}"]?.ctx) { with(window["${scriptid}"].ctx) { ${text}; } window["${scriptid}"](); }')(); `;
|
|
441
446
|
let resolver;
|
|
442
|
-
promises.push(new Promise((resolve) =>
|
|
443
|
-
resolver = resolve;
|
|
444
|
-
}));
|
|
447
|
+
promises.push(new Promise((resolve) => resolver = resolve));
|
|
445
448
|
window[scriptid] = () => {
|
|
446
449
|
delete window[scriptid];
|
|
447
450
|
currentScript.remove();
|
|
@@ -451,47 +454,102 @@ const {observe} = (() => {
|
|
|
451
454
|
ctx.appendChild(currentScript);
|
|
452
455
|
}
|
|
453
456
|
Promise.all(promises).then(() => {
|
|
454
|
-
const inputs = [...ctx.shadowRoot.querySelectorAll("input[l-bind]")];
|
|
455
|
-
inputs.forEach((input) => {
|
|
456
|
-
bindInput(input,ctx);
|
|
457
|
-
})
|
|
458
457
|
const nodes = getNodes(ctx);
|
|
459
458
|
nodes.forEach((node) => {
|
|
460
459
|
if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
|
|
461
460
|
render(!!node.template, () => resolveNode(node, this))
|
|
462
461
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
462
|
+
// resolve the value before all else;
|
|
463
|
+
const attr = node.attributes.value;
|
|
464
|
+
let name;
|
|
465
|
+
if(attr && attr.template) {
|
|
466
|
+
render(!!attr.template,() => {
|
|
467
|
+
const value = resolveNode(attr, this),
|
|
468
|
+
eltype = resolveNode(node.attributes.type,ctx);
|
|
469
|
+
if(eltype==="checkbox") {
|
|
470
|
+
if(coerce(value,"boolean")===true) {
|
|
471
|
+
node.setAttribute("checked","");
|
|
472
|
+
node.checked = true;
|
|
473
|
+
} else {
|
|
474
|
+
node.removeAttribute("checked");
|
|
475
|
+
node.checked = false;
|
|
476
|
+
}
|
|
477
|
+
const vname = resolveNode(node.attributes.name,ctx);
|
|
478
|
+
if(vname) ctx.setValue(vname,node.checked,{coerceTo:"boolean"});
|
|
479
|
+
}
|
|
480
|
+
if(node.tagName==="SELECT") {
|
|
481
|
+
let values = [value];
|
|
482
|
+
if(node.hasAttribute("multiple")) values = coerce(value,Array);
|
|
483
|
+
[...node.querySelectorAll("option")].forEach((option) => {
|
|
484
|
+
if(option.hasAttribute("value")) {
|
|
485
|
+
if (values.includes(resolveNode(option.attributes.value, ctx))) {
|
|
486
|
+
option.setAttribute("selected", "");
|
|
487
|
+
option.selected = true;
|
|
488
|
+
}
|
|
489
|
+
} else if(option.innerText.trim()===value) {
|
|
490
|
+
option.setAttribute("selected","");
|
|
491
|
+
option.selected = true;
|
|
492
|
+
}
|
|
493
|
+
})
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
let name;
|
|
497
|
+
for(const vname of this.getVariableNames()) {
|
|
498
|
+
if("${" + vname + "}" === attr.template) {
|
|
499
|
+
name = vname;
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if(name) bindInput(node,name,ctx);
|
|
504
|
+
}
|
|
463
505
|
[...node.attributes].forEach((attr) => {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
506
|
+
if(attr.name==="value") return;
|
|
507
|
+
const {name, value} = attr;
|
|
508
|
+
if(name==="type") {
|
|
509
|
+
if (value === "radio") {
|
|
510
|
+
const name = resolveNode(node.attributes.name, ctx);
|
|
511
|
+
for (const vname of this.getVariableNames()) {
|
|
512
|
+
if (vname === name) {
|
|
513
|
+
render(true, () => {
|
|
514
|
+
const name = resolveNode(node.attributes.name, ctx),
|
|
515
|
+
varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
|
|
516
|
+
if (varvalue == resolveNode(node.attributes.value,ctx)) {
|
|
517
|
+
node.setAttribute("checked", "");
|
|
518
|
+
node.checked = true;
|
|
519
|
+
} else {
|
|
520
|
+
node.removeAttribute("checked");
|
|
521
|
+
node.checked = false;
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
bindInput(node, name, ctx);
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const [type, ...params] = name.split(":");
|
|
532
|
+
if (type === "") { // name is :something
|
|
473
533
|
render(!!attr.template, () => {
|
|
474
|
-
const value =
|
|
475
|
-
|
|
476
|
-
|
|
534
|
+
const value = attr.value,
|
|
535
|
+
elvalue = resolveNode(node.attributes.value,ctx),
|
|
536
|
+
eltype = resolveNode(node.attributes.type,ctx),
|
|
537
|
+
elname = resolveNode(node.attributes.name,ctx);
|
|
538
|
+
if (params[0]) {
|
|
539
|
+
if (value === "true") node.setAttribute(params[0], "")
|
|
477
540
|
else node.removeAttribute(params[0]);
|
|
478
|
-
} else {
|
|
479
|
-
if(value
|
|
541
|
+
} else if (eltype=== "checkbox" || node.tagName === "OPTION") {
|
|
542
|
+
if (value === "true") node.setAttribute("checked", "")
|
|
543
|
+
else node.removeAttribute("checked");
|
|
480
544
|
}
|
|
481
545
|
})
|
|
482
|
-
} else if (["checked","selected"].includes(name)) {
|
|
483
|
-
render(!!attr.template, () => {
|
|
484
|
-
const value = resolveNode(attr, this);
|
|
485
|
-
if (value === "true") node.setAttribute(name, "");
|
|
486
|
-
else node.removeAttribute(name);
|
|
487
|
-
})
|
|
488
546
|
} else if (type === "l-on") {
|
|
489
547
|
let listener;
|
|
490
548
|
render(!!attr.template, () => {
|
|
491
549
|
const value = resolveNode(attr, this);
|
|
492
550
|
if (listener) node.removeEventListener(params[0], listener);
|
|
493
551
|
listener = this[value] || window[value] || Function(value);
|
|
494
|
-
node
|
|
552
|
+
addListener(node,params[0], listener);
|
|
495
553
|
})
|
|
496
554
|
} else if (type === "l-if") {
|
|
497
555
|
render(!!attr.template, () => {
|
|
@@ -501,7 +559,7 @@ const {observe} = (() => {
|
|
|
501
559
|
} else if (type === "l-for") {
|
|
502
560
|
node.template ||= node.innerHTML;
|
|
503
561
|
render(!!attr.template, () => {
|
|
504
|
-
const [what = "each", vname = "
|
|
562
|
+
const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
|
|
505
563
|
value = resolveNode(attr, this),
|
|
506
564
|
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
507
565
|
target = what === "each" ? coerced : Object[what](coerced),
|
|
@@ -538,27 +596,39 @@ const {observe} = (() => {
|
|
|
538
596
|
}
|
|
539
597
|
|
|
540
598
|
adopted(value) {
|
|
541
|
-
|
|
599
|
+
this.adoptedCallback = value;
|
|
600
|
+
//Object.defineProperty(this, "adoptedCallback", {configurable: true, writable: true, value});
|
|
542
601
|
}
|
|
543
602
|
|
|
544
603
|
connected(value) {
|
|
545
|
-
|
|
604
|
+
this.connectedCallback = value;
|
|
605
|
+
//Object.defineProperty(this, "connectedCallback", {configurable: true, writable: true, value});
|
|
546
606
|
}
|
|
547
607
|
|
|
548
608
|
attributeChanged(value) {
|
|
549
|
-
|
|
609
|
+
this.attributeChangedCallback = value;
|
|
610
|
+
//Object.defineProperty(this, "attributeChangedCallback", {configurable: true, writable: true, value});
|
|
550
611
|
}
|
|
551
612
|
|
|
552
613
|
disconnected(value) {
|
|
553
614
|
Object.defineProperty(this, "disconnectedCallback", {
|
|
554
615
|
configurable: true,
|
|
555
616
|
writable: true,
|
|
556
|
-
value:() => {
|
|
617
|
+
value: () => {
|
|
618
|
+
value();
|
|
619
|
+
super.disconnectedCallback(value);
|
|
620
|
+
}
|
|
557
621
|
});
|
|
558
622
|
}
|
|
559
623
|
|
|
560
|
-
|
|
561
|
-
|
|
624
|
+
getVariableNames() {
|
|
625
|
+
return Object.keys(this.vars).filter((name) => {
|
|
626
|
+
return !(name in reserved) && !["self","addEventListener","postEvent"].includes(name)
|
|
627
|
+
})
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
setValue(name, value, {shared, coerceTo = typeof (value)} = {}) {
|
|
631
|
+
if (!this.isConnected) {
|
|
562
632
|
instances.delete(this);
|
|
563
633
|
return false;
|
|
564
634
|
}
|
|
@@ -567,27 +637,35 @@ const {observe} = (() => {
|
|
|
567
637
|
value = coerce(value, type);
|
|
568
638
|
if (this.varsProxy[name] !== value) {
|
|
569
639
|
const variable = this.vars[name];
|
|
570
|
-
if(variable.shared) {
|
|
571
|
-
const event = new VariableEvent({
|
|
640
|
+
if (variable.shared) {
|
|
641
|
+
const event = new VariableEvent({
|
|
642
|
+
variableName: name,
|
|
643
|
+
value: value,
|
|
644
|
+
oldValue: variable.value
|
|
645
|
+
});
|
|
572
646
|
variable.value = value;
|
|
573
|
-
this.vars.postEvent.value("change",event);
|
|
574
|
-
if(event.defaultPrevented)
|
|
647
|
+
this.vars.postEvent.value("change", event);
|
|
648
|
+
if (event.defaultPrevented) variable.value = value;
|
|
575
649
|
} else {
|
|
576
650
|
this.varsProxy[name] = value;
|
|
577
651
|
}
|
|
578
652
|
}
|
|
579
653
|
return true;
|
|
580
654
|
}
|
|
581
|
-
this.vars[name] = {type:coerceTo, value: coerce(value, coerceTo)};
|
|
655
|
+
this.vars[name] = {name, type: coerceTo, value: coerce(value, coerceTo)};
|
|
582
656
|
return false;
|
|
583
657
|
}
|
|
584
658
|
|
|
659
|
+
getValue(variableName) {
|
|
660
|
+
return this.vars[variableName]?.value;
|
|
661
|
+
}
|
|
662
|
+
|
|
585
663
|
variables(variables, {observed, reactive, shared, exported, imported} = {}) { // options = {observed,reactive,shared,exported,imported}
|
|
586
664
|
const addEventListener = this.varsProxy.addEventListener;
|
|
587
665
|
if (variables !== undefined) {
|
|
588
666
|
Object.entries(variables)
|
|
589
667
|
.forEach(([key, type]) => {
|
|
590
|
-
const variable = this.vars[key] ||= {type};
|
|
668
|
+
const variable = this.vars[key] ||= {name: key, type};
|
|
591
669
|
if (observed || imported) {
|
|
592
670
|
variable.value = this.hasAttribute(key) ? coerce(this.getAttribute(key), variable.type) : variable.value;
|
|
593
671
|
variable.observed = observed;
|
|
@@ -599,56 +677,23 @@ const {observe} = (() => {
|
|
|
599
677
|
}
|
|
600
678
|
if (shared) {
|
|
601
679
|
variable.shared = true;
|
|
602
|
-
addEventListener("change",({variableName,value}) => {
|
|
603
|
-
if(this.vars[variableName]?.shared) {
|
|
604
|
-
this.siblings.forEach((instance) =>
|
|
605
|
-
instance.setVariable(variableName, value);
|
|
606
|
-
})
|
|
680
|
+
addEventListener("change", ({variableName, value}) => {
|
|
681
|
+
if (this.vars[variableName]?.shared) {
|
|
682
|
+
this.siblings.forEach((instance) => instance.setValue(variableName, value))
|
|
607
683
|
}
|
|
608
684
|
})
|
|
609
685
|
}
|
|
610
686
|
if (exported) {
|
|
611
687
|
variable.exported = true;
|
|
612
|
-
// in case the export goes up to
|
|
613
|
-
if(variable.value!=null) setComponentAttribute(this,key,variable.value);
|
|
614
|
-
addEventListener("change",({variableName,value}) => {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if(value==null) {
|
|
619
|
-
removeComponentAttribute(this,variableName);
|
|
620
|
-
} else {
|
|
621
|
-
setComponentAttribute(this,variableName,value);
|
|
622
|
-
}
|
|
688
|
+
// in case the export goes up to an iframe
|
|
689
|
+
if (variable.value != null) setComponentAttribute(this, key, variable.value);
|
|
690
|
+
addEventListener("change", ({variableName, value}) => {
|
|
691
|
+
value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
|
|
692
|
+
if (value == null) removeComponentAttribute(this, variableName);
|
|
693
|
+
else setComponentAttribute(this, variableName, value);
|
|
623
694
|
})
|
|
624
695
|
}
|
|
625
696
|
});
|
|
626
|
-
addEventListener("change",({variableName,value}) => {
|
|
627
|
-
[...this.shadowRoot.querySelectorAll(`input[l-bind=${variableName}]`)].forEach((input) => {
|
|
628
|
-
if(input.getAttribute("type")==="checkbox") { // at el option selected
|
|
629
|
-
if(!value) input.removeAttribute("checked");
|
|
630
|
-
input.checked = value;
|
|
631
|
-
} else {
|
|
632
|
-
// Array.isArray will not work here, Proxies mess up JSON.stringify for Arrays
|
|
633
|
-
//value = value && typeof (value) === "object" ? (value instanceof Array ? JSON.stringify([...value]) : JSON.stringify(value)) : value+"";
|
|
634
|
-
value = typeof(value)==="string" || value==null ? value : JSON.stringify(value);
|
|
635
|
-
const oldvalue = input.getAttribute("value")||"";
|
|
636
|
-
if(oldvalue!==value) {
|
|
637
|
-
if(value==null) {
|
|
638
|
-
input.removeAttribute("value");
|
|
639
|
-
} else {
|
|
640
|
-
input.setAttribute("value",value);
|
|
641
|
-
}
|
|
642
|
-
try {
|
|
643
|
-
input.setSelectionRange(0, Math.max(oldvalue.length,value ? value.length : 0)); // shadowDom sometimes fails to rerender unless this is done;
|
|
644
|
-
input.setRangeText(value||"",0, Math.max(oldvalue.length,value ? value.length : 0));
|
|
645
|
-
} catch(e) {
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
})
|
|
651
|
-
})
|
|
652
697
|
}
|
|
653
698
|
return Object.entries(this.vars)
|
|
654
699
|
.reduce((result, [key, variable]) => {
|
|
@@ -676,17 +721,18 @@ const {observe} = (() => {
|
|
|
676
721
|
}
|
|
677
722
|
}
|
|
678
723
|
}
|
|
679
|
-
const createComponent = (name, node, {observer,
|
|
724
|
+
const createComponent = (name, node, {observer, importAnchors} = {}) => {
|
|
680
725
|
let ctor = customElements.get(name);
|
|
681
|
-
if(ctor) {
|
|
726
|
+
if (ctor) {
|
|
682
727
|
console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
|
|
683
728
|
return ctor;
|
|
684
729
|
}
|
|
685
|
-
ctor = createClass(node, {observer,
|
|
730
|
+
ctor = createClass(node, {observer, importAnchors});
|
|
686
731
|
customElements.define(name, ctor);
|
|
687
732
|
return ctor;
|
|
688
733
|
}
|
|
689
|
-
|
|
734
|
+
Lightview.createComponent = createComponent;
|
|
735
|
+
//Object.defineProperty(Lightview, "createComponent", {writable: true, configurable: true, value: createComponent})
|
|
690
736
|
const importLink = async (link, observer) => {
|
|
691
737
|
const url = (new URL(link.getAttribute("href"), window.location.href)),
|
|
692
738
|
as = link.getAttribute("as") || getNameFromPath(url.pathname);
|
|
@@ -697,10 +743,9 @@ const {observe} = (() => {
|
|
|
697
743
|
const html = await (await fetch(url.href)).text(),
|
|
698
744
|
dom = parser.parseFromString(html, "text/html"),
|
|
699
745
|
importAnchors = !!dom.head.querySelector('meta[name="l-importAnchors"]'),
|
|
700
|
-
bindForms = !!dom.head.querySelector('meta[name="l-bindForms"]'),
|
|
701
746
|
unhide = !!dom.head.querySelector('meta[name="l-unhide"]');
|
|
702
|
-
if(unhide) dom.body.removeAttribute("hidden");
|
|
703
|
-
createComponent(as, dom.body, {observer,importAnchors
|
|
747
|
+
if (unhide) dom.body.removeAttribute("hidden");
|
|
748
|
+
createComponent(as, dom.body, {observer, importAnchors});
|
|
704
749
|
}
|
|
705
750
|
return {as};
|
|
706
751
|
}
|
|
@@ -711,149 +756,158 @@ const {observe} = (() => {
|
|
|
711
756
|
}
|
|
712
757
|
}
|
|
713
758
|
|
|
714
|
-
const bodyAsComponent = ({as="x-body",unhide,importAnchors
|
|
759
|
+
const bodyAsComponent = ({as = "x-body", unhide, importAnchors} = {}) => {
|
|
715
760
|
const parent = document.body.parentElement;
|
|
716
|
-
createComponent(as, document.body,{importAnchors
|
|
761
|
+
createComponent(as, document.body, {importAnchors});
|
|
717
762
|
const component = document.createElement(as);
|
|
718
763
|
parent.replaceChild(component, document.body);
|
|
719
|
-
Object.defineProperty(document,"body",
|
|
764
|
+
Object.defineProperty(document, "body", {
|
|
765
|
+
enumerable: true, configurable: true, get() {
|
|
766
|
+
return component;
|
|
767
|
+
}
|
|
768
|
+
});
|
|
720
769
|
if (unhide) component.removeAttribute("hidden");
|
|
721
770
|
}
|
|
722
771
|
Lightview.bodyAsComponent = bodyAsComponent;
|
|
723
|
-
const postMessage = (data,target=window.parent) => {
|
|
724
|
-
if(postMessage.enabled) {
|
|
725
|
-
if(target instanceof HTMLIFrameElement) {
|
|
726
|
-
data = {...data,href:window.location.href};
|
|
727
|
-
target.contentWindow.postMessage(JSON.stringify(data),"*");
|
|
772
|
+
const postMessage = (data, target = window.parent) => {
|
|
773
|
+
if (postMessage.enabled) {
|
|
774
|
+
if (target instanceof HTMLIFrameElement) {
|
|
775
|
+
data = {...data, href: window.location.href};
|
|
776
|
+
target.contentWindow.postMessage(JSON.stringify(data), "*");
|
|
728
777
|
} else {
|
|
729
|
-
data = {...data,iframeId:document.lightviewId,href:window.location.href};
|
|
730
|
-
target.postMessage(JSON.stringify(data),"*");
|
|
778
|
+
data = {...data, iframeId: document.lightviewId, href: window.location.href};
|
|
779
|
+
target.postMessage(JSON.stringify(data), "*");
|
|
731
780
|
}
|
|
732
781
|
}
|
|
733
782
|
}
|
|
734
|
-
const setComponentAttribute = (node,name,value) => {
|
|
735
|
-
if(node.getAttribute(name)!==value) node.setAttribute(name,value);
|
|
736
|
-
postMessage({type:"setAttribute",argsList:[name,value]});
|
|
783
|
+
const setComponentAttribute = (node, name, value) => {
|
|
784
|
+
if (node.getAttribute(name) !== value) node.setAttribute(name, value);
|
|
785
|
+
postMessage({type: "setAttribute", argsList: [name, value]});
|
|
737
786
|
}
|
|
738
|
-
const removeComponentAttribute = (node,name,value) => {
|
|
787
|
+
const removeComponentAttribute = (node, name, value) => {
|
|
739
788
|
node.removeAttribute(name);
|
|
740
|
-
postMessage({type:"removeAttribute",argsList:[name]});
|
|
789
|
+
postMessage({type: "removeAttribute", argsList: [name]});
|
|
741
790
|
}
|
|
742
|
-
const getNodePath = (node,path=[]) => {
|
|
791
|
+
const getNodePath = (node, path = []) => {
|
|
743
792
|
path.unshift(node);
|
|
744
|
-
if(node.parentNode && node.parentNode!==node.parentNode) getNodePath(node.parentNode,path);
|
|
793
|
+
if (node.parentNode && node.parentNode !== node.parentNode) getNodePath(node.parentNode, path);
|
|
745
794
|
return path;
|
|
746
795
|
}
|
|
747
796
|
const onresize = (node, callback) => {
|
|
748
|
-
const resizeObserver = new ResizeObserver(() => callback()
|
|
797
|
+
const resizeObserver = new ResizeObserver(() => callback());
|
|
749
798
|
resizeObserver.observe(node);
|
|
750
799
|
};
|
|
751
800
|
|
|
752
801
|
const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
|
|
753
802
|
let domContentLoadedEvent;
|
|
754
|
-
window
|
|
803
|
+
addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
|
|
755
804
|
const loader = async (whenFramed) => {
|
|
756
805
|
if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
|
|
757
806
|
const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
|
|
758
|
-
bindForms = !!document.querySelector('meta[name="l-bindForms"]'),
|
|
759
807
|
unhide = !!document.querySelector('meta[name="l-unhide"]'),
|
|
760
808
|
isolated = !!document.querySelector('meta[name="l-isolate"]'),
|
|
761
809
|
enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
|
|
762
|
-
if(whenFramed) {
|
|
763
|
-
whenFramed({unhide,importAnchors,
|
|
764
|
-
if(!isolated) {
|
|
810
|
+
if (whenFramed) {
|
|
811
|
+
whenFramed({unhide, importAnchors, isolated, enableFrames});
|
|
812
|
+
if (!isolated) {
|
|
765
813
|
postMessage.enabled = true;
|
|
766
|
-
window
|
|
767
|
-
const {type,argsList} = JSON.parse(data);
|
|
768
|
-
if(type==="framed") {
|
|
814
|
+
addListener(window,"message", ({data}) => {
|
|
815
|
+
const {type, argsList} = JSON.parse(data);
|
|
816
|
+
if (type === "framed") {
|
|
769
817
|
const resize = () => {
|
|
770
|
-
const {width,height} = document.body.getBoundingClientRect();
|
|
771
|
-
postMessage({type:"setAttribute",argsList:["width",width]})
|
|
772
|
-
postMessage({type:"setAttribute",argsList:["height",height+20]});
|
|
818
|
+
const {width, height} = document.body.getBoundingClientRect();
|
|
819
|
+
postMessage({type: "setAttribute", argsList: ["width", width]})
|
|
820
|
+
postMessage({type: "setAttribute", argsList: ["height", height + 20]});
|
|
773
821
|
}
|
|
774
822
|
resize();
|
|
775
|
-
onresize(document.body,() => {
|
|
823
|
+
onresize(document.body, () => {
|
|
776
824
|
resize();
|
|
777
825
|
});
|
|
778
826
|
return
|
|
779
827
|
}
|
|
780
|
-
if(type==="setAttribute") {
|
|
781
|
-
const [name,value] = [...argsList],
|
|
828
|
+
if (type === "setAttribute") {
|
|
829
|
+
const [name, value] = [...argsList],
|
|
782
830
|
variable = document.body.vars[name];
|
|
783
|
-
if(variable && variable.imported)
|
|
784
|
-
document.body.setVariable(name,value);
|
|
785
|
-
}
|
|
831
|
+
if (variable && variable.imported) document.body.setValue(name, value);
|
|
786
832
|
return;
|
|
787
833
|
}
|
|
788
|
-
if(type==="removeAttribute") {
|
|
834
|
+
if (type === "removeAttribute") {
|
|
789
835
|
const [name] = argsList[0],
|
|
790
836
|
variable = document.body.vars[name];
|
|
791
|
-
if(variable && variable.imported)
|
|
792
|
-
|
|
793
|
-
}
|
|
794
|
-
return;
|
|
837
|
+
if (variable && variable.imported) document.body.setValue(name, undefined);
|
|
838
|
+
|
|
795
839
|
}
|
|
796
840
|
});
|
|
797
841
|
const url = new URL(window.location.href);
|
|
798
842
|
document.lightviewId = url.searchParams.get("id");
|
|
799
|
-
postMessage({type:"DOMContentLoaded"})
|
|
843
|
+
postMessage({type: "DOMContentLoaded"})
|
|
800
844
|
}
|
|
801
845
|
} else if (url.searchParams.has("as")) {
|
|
802
|
-
bodyAsComponent({as:url.searchParams.get("as"),unhide,importAnchors
|
|
846
|
+
bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors});
|
|
803
847
|
}
|
|
804
|
-
if(enableFrames) {
|
|
848
|
+
if (enableFrames) {
|
|
805
849
|
postMessage.enabled = true;
|
|
806
|
-
window
|
|
807
|
-
const {type,iframeId,argsList,href} = JSON.parse(message.data),
|
|
850
|
+
addListener(window,"message", (message) => {
|
|
851
|
+
const {type, iframeId, argsList, href} = JSON.parse(message.data),
|
|
808
852
|
iframe = document.getElementById(iframeId);
|
|
809
|
-
if(iframe) {
|
|
810
|
-
if(type==="DOMContentLoaded") {
|
|
811
|
-
postMessage({type:"framed",href:window.location.href},iframe);
|
|
812
|
-
Object.defineProperty(domContentLoadedEvent,"currentTarget",{
|
|
853
|
+
if (iframe) {
|
|
854
|
+
if (type === "DOMContentLoaded") {
|
|
855
|
+
postMessage({type: "framed", href: window.location.href}, iframe);
|
|
856
|
+
Object.defineProperty(domContentLoadedEvent, "currentTarget", {
|
|
857
|
+
enumerable: false,
|
|
858
|
+
configurable: true,
|
|
859
|
+
value: iframe
|
|
860
|
+
});
|
|
813
861
|
domContentLoadedEvent.href = href;
|
|
814
862
|
domContentLoadedEvent.srcElement = iframe;
|
|
815
863
|
domContentLoadedEvent.bubbles = false;
|
|
816
864
|
domContentLoadedEvent.path = getNodePath(iframe);
|
|
817
|
-
Object.defineProperty(domContentLoadedEvent,"timeStamp",{
|
|
865
|
+
Object.defineProperty(domContentLoadedEvent, "timeStamp", {
|
|
866
|
+
enumerable: false,
|
|
867
|
+
configurable: true,
|
|
868
|
+
value: performance.now()
|
|
869
|
+
})
|
|
818
870
|
iframe.dispatchEvent(domContentLoadedEvent);
|
|
819
871
|
return;
|
|
820
872
|
}
|
|
821
|
-
if(type==="setAttribute") {
|
|
822
|
-
const [name,value] = [...argsList];
|
|
823
|
-
if(iframe.getAttribute(name)!==value+"") iframe.setAttribute(name,value);
|
|
873
|
+
if (type === "setAttribute") {
|
|
874
|
+
const [name, value] = [...argsList];
|
|
875
|
+
if (iframe.getAttribute(name) !== value + "") iframe.setAttribute(name, value);
|
|
824
876
|
return;
|
|
825
877
|
}
|
|
826
|
-
if(type==="removeAttribute") {
|
|
878
|
+
if (type === "removeAttribute") {
|
|
827
879
|
iframe.removeAttribute(...argsList);
|
|
828
880
|
return;
|
|
829
881
|
}
|
|
830
882
|
}
|
|
831
|
-
console.warn("iframe posted a message without providing an id",message);
|
|
883
|
+
console.warn("iframe posted a message without providing an id", message);
|
|
832
884
|
});
|
|
833
885
|
const mutationCallback = (mutationsList) => {
|
|
834
886
|
const console = document.getElementById("console");
|
|
835
|
-
for (const {target,attributeName,oldValue} of mutationsList) {
|
|
836
|
-
if(!["height","width"].includes(attributeName)) {
|
|
887
|
+
for (const {target, attributeName, oldValue} of mutationsList) {
|
|
888
|
+
if (!["height", "width"].includes(attributeName)) {
|
|
837
889
|
const value = target.getAttribute(attributeName);
|
|
838
890
|
if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
|
|
839
|
-
else if (value !== oldValue) postMessage({
|
|
891
|
+
else if (value !== oldValue) postMessage({
|
|
892
|
+
type: "setAttribute",
|
|
893
|
+
argsList: [attributeName, value]
|
|
894
|
+
}, iframe)
|
|
840
895
|
}
|
|
841
896
|
}
|
|
842
897
|
};
|
|
843
898
|
const observer = new MutationObserver(mutationCallback),
|
|
844
899
|
iframe = document.getElementById("myframe");
|
|
845
|
-
observer.observe(iframe, {
|
|
900
|
+
observer.observe(iframe, {attributes: true, attributeOldValue: true});
|
|
846
901
|
}
|
|
847
902
|
}
|
|
848
|
-
const whenFramed = (f,{isolated}={}) => {
|
|
849
|
-
document
|
|
903
|
+
const whenFramed = (f, {isolated} = {}) => {
|
|
904
|
+
addListener(document,"DOMContentLoaded", (event) => loader(f));
|
|
850
905
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
906
|
+
Lightview.whenFramed = whenFramed;
|
|
907
|
+
//Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
|
|
908
|
+
if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) { // CodePen mucks with window.parent
|
|
909
|
+
addListener(document,"DOMContentLoaded", () => loader())
|
|
854
910
|
}
|
|
855
911
|
|
|
856
912
|
return {observe}
|
|
857
|
-
})();
|
|
858
|
-
|
|
859
|
-
|
|
913
|
+
})();
|
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.0b",
|
|
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
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<template id="audiostream">
|
|
6
|
+
<p>${name}</p>
|
|
7
|
+
<p>
|
|
8
|
+
Play: <input type="checkbox" value="${run}">
|
|
9
|
+
</p>
|
|
10
|
+
<script type="lightview/module">
|
|
11
|
+
self.variables({
|
|
12
|
+
run: boolean
|
|
13
|
+
},{reactive});
|
|
14
|
+
self.variables({
|
|
15
|
+
name: string
|
|
16
|
+
}, {
|
|
17
|
+
imported
|
|
18
|
+
});
|
|
19
|
+
addEventListener("change", ({
|
|
20
|
+
variableName,
|
|
21
|
+
value
|
|
22
|
+
}) => {
|
|
23
|
+
if (variableName === "run" && value === true) {
|
|
24
|
+
self.siblings.forEach((sibling) => {
|
|
25
|
+
sibling.setValue(variableName, false);
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
</script>
|
|
30
|
+
</template>
|
|
31
|
+
<title>Form</title>
|
|
32
|
+
<script src="./lightview.js"></script>
|
|
33
|
+
<script>
|
|
34
|
+
Lightview.createComponent("x-audiostream", document.getElementById("audiostream"))
|
|
35
|
+
</script>
|
|
36
|
+
</head>
|
|
37
|
+
|
|
38
|
+
<body>
|
|
39
|
+
<div style="margin:20px">
|
|
40
|
+
<table>
|
|
41
|
+
<th>
|
|
42
|
+
<td colspan="3">Audio Streams</td>
|
|
43
|
+
</th>
|
|
44
|
+
<tr>
|
|
45
|
+
<td style="width:33%;text-align:center">
|
|
46
|
+
<x-audiostream name="Classical"></x-audiostream>
|
|
47
|
+
</td>
|
|
48
|
+
<td style="width:33%;text-align:center">
|
|
49
|
+
<x-audiostream name="Country"></x-audiostream>
|
|
50
|
+
</td>
|
|
51
|
+
<td style="width:33%;text-align:center">
|
|
52
|
+
<x-audiostream name="Classic Rock"></x-audiostream>
|
|
53
|
+
</td>
|
|
54
|
+
</tr>
|
|
55
|
+
</table>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
</body>
|
|
59
|
+
|
|
60
|
+
</html>
|