lightview 1.0.0-b → 1.3.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 +3 -1
- package/counter.html +30 -0
- package/directives.html +80 -0
- package/lightview.js +408 -193
- package/package.json +1 -1
- package/remote.html +35 -0
- package/remoteform.html +47 -0
- package/xor.html +62 -0
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# lightview v1.
|
|
1
|
+
# lightview v1.3.0b (BETA)
|
|
2
2
|
|
|
3
3
|
Small, simple, powerful web UI creation ...
|
|
4
4
|
|
|
5
|
+
Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 6K (minified/gzipped).
|
|
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,80 @@
|
|
|
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
|
+
|
|
11
|
+
<p>
|
|
12
|
+
Show: <input type="checkbox" :="${on}" l-bind="on">
|
|
13
|
+
<div l-if="${on}">
|
|
14
|
+
Show is true
|
|
15
|
+
</div>
|
|
16
|
+
</p>
|
|
17
|
+
<p>
|
|
18
|
+
|
|
19
|
+
<input id="red" type="radio" name="myradio" value="red" :="${color}" l-bind="color"> Red
|
|
20
|
+
<input id="yellow" type="radio" name="myradio" value="yellow" :="${color}" l-bind="color"> Yellow
|
|
21
|
+
<input id="green" type="radio" name="myradio" value="green" :="${color}" l-bind="color"> Green
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p>
|
|
25
|
+
<select l-bind="color" value="${color}">
|
|
26
|
+
<option value="red">Red</option>
|
|
27
|
+
<option value="yellow">Yellow</option>
|
|
28
|
+
<option value="green">Green</option>
|
|
29
|
+
</select>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
<p>
|
|
34
|
+
How would you like that burger?<br>
|
|
35
|
+
<select l-bind="options" value="${options}" multiple>
|
|
36
|
+
<option>lettuce</option>
|
|
37
|
+
<option>tomato</option>
|
|
38
|
+
<option>cheese</option>
|
|
39
|
+
</select>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
<ul l-for:each="${children}">
|
|
45
|
+
<li>${index}:${element}</li>
|
|
46
|
+
</ul>
|
|
47
|
+
<ul l-for:values:value:index='{"1":"v1","2":"v2","3":"v3"}'>
|
|
48
|
+
<li>${value}:${index}</li>
|
|
49
|
+
</ul>
|
|
50
|
+
<ul l-for:keys:key='{"name":"joe","age":27}'>
|
|
51
|
+
<li>${key}</li>
|
|
52
|
+
</ul>
|
|
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
|
+
|
|
60
|
+
<script type="lightview/module">
|
|
61
|
+
self.variables({on:boolean,off:boolean,color:string,children:Array,options:Array},{reactive});
|
|
62
|
+
|
|
63
|
+
on = true;
|
|
64
|
+
color = "yellow";
|
|
65
|
+
children = ["John","Mary","Jane"];
|
|
66
|
+
options = ["tomato"];
|
|
67
|
+
|
|
68
|
+
addEventListener("change",()=> {
|
|
69
|
+
const el = self.getElementById("variables");
|
|
70
|
+
while(el.lastElementChild) el.lastElementChild.remove();
|
|
71
|
+
self.getVariableNames().forEach((name) => {
|
|
72
|
+
const line = document.createElement("div");
|
|
73
|
+
line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
|
|
74
|
+
el.appendChild(line);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
78
|
+
</body>
|
|
79
|
+
|
|
80
|
+
</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;
|
|
@@ -63,15 +67,16 @@ const {observe} = (() => {
|
|
|
63
67
|
return observer;
|
|
64
68
|
}
|
|
65
69
|
const coerce = (value, toType) => {
|
|
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 (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
|
}
|
|
@@ -265,9 +274,7 @@ const {observe} = (() => {
|
|
|
265
274
|
if (!nodes.includes(node)) 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
|
}
|
|
@@ -276,9 +283,10 @@ const {observe} = (() => {
|
|
|
276
283
|
const resolveNode = (node, component) => {
|
|
277
284
|
if (node.template) {
|
|
278
285
|
try {
|
|
279
|
-
|
|
286
|
+
const value = Function("context", "with(context) { return `" + node.template + "` }")(component.varsProxy);
|
|
287
|
+
node.nodeValue = value === "null" || value === "undefined" ? "" : value;
|
|
280
288
|
} catch (e) {
|
|
281
|
-
if (!e.message.includes("defined")) throw e;
|
|
289
|
+
if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
|
|
282
290
|
}
|
|
283
291
|
}
|
|
284
292
|
return node.nodeValue;
|
|
@@ -297,57 +305,79 @@ const {observe} = (() => {
|
|
|
297
305
|
if (["text", "tel", "email", "url", "search", "radio"].includes(inputType)) return "string";
|
|
298
306
|
if (["number", "range"].includes(inputType)) return "number";
|
|
299
307
|
if (["datetime"].includes(inputType)) return Date;
|
|
300
|
-
if(["checkbox"].includes(inputType)) return "boolean";
|
|
308
|
+
if (["checkbox"].includes(inputType)) return "boolean";
|
|
301
309
|
return "any";
|
|
302
310
|
}
|
|
303
|
-
const _importAnchors = (node,component) => {
|
|
311
|
+
const _importAnchors = (node, component) => {
|
|
304
312
|
[...node.querySelectorAll('a[href][target^="#"]')].forEach((node) => {
|
|
305
313
|
node.removeEventListener("click", anchorHandler);
|
|
306
|
-
node
|
|
314
|
+
addListener(node,"click", anchorHandler);
|
|
307
315
|
})
|
|
308
316
|
}
|
|
309
317
|
const _bindForms = (node, component) => {
|
|
310
|
-
[...node.querySelectorAll("input")].forEach((input) =>
|
|
311
|
-
bindInput(input,component);
|
|
312
|
-
})
|
|
318
|
+
[...node.querySelectorAll("input")].forEach((input) => bindInput(input, component))
|
|
313
319
|
}
|
|
314
|
-
const bindInput = (input,component) => {
|
|
315
|
-
|
|
316
|
-
vname = input.getAttribute("l-bind")||name;
|
|
320
|
+
const bindInput = (input, component) => {
|
|
321
|
+
let name = input.getAttribute("name"),
|
|
322
|
+
vname = input.getAttribute("l-bind") || name;
|
|
323
|
+
name ||= vname;
|
|
317
324
|
if (name) {
|
|
318
|
-
if(!input.hasAttribute("l-bind")) input.setAttribute("l-bind",vname)
|
|
319
|
-
const
|
|
325
|
+
if (!input.hasAttribute("l-bind")) input.setAttribute("l-bind", vname)
|
|
326
|
+
const inputtype = input.tagName === "SELECT" ? "text" : input.getAttribute("type"),
|
|
327
|
+
type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
|
|
320
328
|
deflt = input.getAttribute("default"),
|
|
321
329
|
value = input.getAttribute("value");
|
|
322
330
|
let variable = component.vars[vname] || {type};
|
|
323
|
-
if(type!==variable.type) {
|
|
324
|
-
if(variable.type==="any" || variable.type==="unknown") variable.type = type;
|
|
331
|
+
if (type !== variable.type) {
|
|
332
|
+
if (variable.type === "any" || variable.type === "unknown") variable.type = type;
|
|
325
333
|
else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${vname}:${variable.type}`)
|
|
326
334
|
}
|
|
327
|
-
component.variables({[vname]:type});
|
|
328
|
-
variable = component.vars[vname]
|
|
329
|
-
if (value || deflt) {
|
|
335
|
+
component.variables({[vname]: type});
|
|
336
|
+
variable = component.vars[vname];
|
|
337
|
+
//if (value || deflt) {
|
|
338
|
+
if (inputtype !== "radio") {
|
|
330
339
|
if (value && !value.includes("${")) {
|
|
331
340
|
variable.value = coerce(value, type);
|
|
332
|
-
input.setAttribute("value", `\${${name}}`);
|
|
333
|
-
}
|
|
334
|
-
if (deflt && !deflt.includes("${")) {
|
|
341
|
+
//input.setAttribute("value", `\${${name}}`);
|
|
342
|
+
} else if (deflt && !deflt.includes("${")) {
|
|
335
343
|
variable.value = coerce(deflt, type);
|
|
336
|
-
input.setAttribute("default", `\${${name}}`);
|
|
344
|
+
//input.setAttribute("default", `\${${name}}`);
|
|
337
345
|
}
|
|
338
346
|
}
|
|
339
|
-
|
|
347
|
+
//}
|
|
348
|
+
addListener(input,"change", (event) => {
|
|
340
349
|
event.stopImmediatePropagation();
|
|
341
|
-
|
|
350
|
+
const target = event.target;
|
|
351
|
+
let value = target.value;
|
|
352
|
+
if (inputtype === "checkbox") {
|
|
353
|
+
value = input.checked
|
|
354
|
+
} else if (target.tagName === "SELECT") {
|
|
355
|
+
if (target.hasAttribute("multiple")) {
|
|
356
|
+
value = [...target.querySelectorAll("option")]
|
|
357
|
+
.filter((option) => option.selected || option.getAttribute("value") == value || option.innerText == value)
|
|
358
|
+
.map((option) => option.getAttribute("value") || option.innerText);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
component.varsProxy[vname] = coerce(value, type);
|
|
342
362
|
})
|
|
343
363
|
}
|
|
344
364
|
}
|
|
345
|
-
|
|
365
|
+
let reserved = {
|
|
366
|
+
boolean: {value: "boolean", constant: true},
|
|
367
|
+
string: {value: "string", constant: true},
|
|
368
|
+
number: {value: "number", constant: true},
|
|
369
|
+
observed: {value: true, constant: true},
|
|
370
|
+
reactive: {value: true, constant: true},
|
|
371
|
+
shared: {value: true, constant: true},
|
|
372
|
+
exported: {value: true, constant: true},
|
|
373
|
+
imported: {value: true, constant: true}
|
|
374
|
+
};
|
|
375
|
+
const createClass = (domElementNode, {observer, bindForms, importAnchors}) => {
|
|
346
376
|
const instances = new Set(),
|
|
347
|
-
dom = domElementNode.tagName==="TEMPLATE"
|
|
377
|
+
dom = domElementNode.tagName === "TEMPLATE"
|
|
348
378
|
? domElementNode.content.cloneNode(true)
|
|
349
379
|
: domElementNode.cloneNode(true);
|
|
350
|
-
if(domElementNode.tagName==="TEMPLATE") domElementNode = domElementNode.cloneNode(true);
|
|
380
|
+
if (domElementNode.tagName === "TEMPLATE") domElementNode = domElementNode.cloneNode(true);
|
|
351
381
|
return class CustomElement extends HTMLElement {
|
|
352
382
|
static get instances() {
|
|
353
383
|
return instances;
|
|
@@ -361,6 +391,7 @@ const {observe} = (() => {
|
|
|
361
391
|
shadow = this.attachShadow({mode: "open"}),
|
|
362
392
|
eventlisteners = {};
|
|
363
393
|
this.vars = {
|
|
394
|
+
...reserved,
|
|
364
395
|
addEventListener: {
|
|
365
396
|
value: (eventName, listener) => {
|
|
366
397
|
const listeners = eventlisteners[eventName] ||= new Set();
|
|
@@ -381,19 +412,11 @@ const {observe} = (() => {
|
|
|
381
412
|
type: "function",
|
|
382
413
|
constant: true
|
|
383
414
|
},
|
|
384
|
-
self: {value: currentComponent, type: CustomElement, constant: true}
|
|
385
|
-
boolean: {value: "boolean", constant: true},
|
|
386
|
-
string: {value: "string", constant: true},
|
|
387
|
-
number: {value: "number", constant: true},
|
|
388
|
-
observed: {value: true, constant: true},
|
|
389
|
-
reactive: {value: true, constant: true},
|
|
390
|
-
shared: {value: true, constant: true},
|
|
391
|
-
exported: {value: true, constant: true},
|
|
392
|
-
imported: {value: true, constant: true}
|
|
415
|
+
self: {value: currentComponent, type: CustomElement, constant: true}
|
|
393
416
|
};
|
|
394
|
-
this.defaultAttributes = domElementNode.tagName==="TEMPLATE" ? domElementNode.attributes : dom.attributes;
|
|
417
|
+
this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
|
|
395
418
|
this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
|
|
396
|
-
|
|
419
|
+
["getElementById", "querySelector", "querySelectorAll"]
|
|
397
420
|
.forEach((fname) => {
|
|
398
421
|
Object.defineProperty(this, fname, {
|
|
399
422
|
configurable: true,
|
|
@@ -401,15 +424,13 @@ const {observe} = (() => {
|
|
|
401
424
|
value: (...args) => this.shadowRoot[fname](...args)
|
|
402
425
|
})
|
|
403
426
|
});
|
|
404
|
-
[...dom.childNodes].forEach((child) =>
|
|
405
|
-
shadow.appendChild(child.cloneNode(true));
|
|
406
|
-
})
|
|
427
|
+
[...dom.childNodes].forEach((child) => shadow.appendChild(child.cloneNode(true)));
|
|
407
428
|
if (bindForms) _bindForms(shadow, this);
|
|
408
|
-
if(importAnchors) _importAnchors(shadow,this);
|
|
429
|
+
if (importAnchors) _importAnchors(shadow, this);
|
|
409
430
|
}
|
|
410
431
|
|
|
411
432
|
get siblings() {
|
|
412
|
-
return [...CustomElement.instances].filter((sibling) => sibling!=this);
|
|
433
|
+
return [...CustomElement.instances].filter((sibling) => sibling != this);
|
|
413
434
|
}
|
|
414
435
|
|
|
415
436
|
adoptedCallback() {
|
|
@@ -450,114 +471,137 @@ const {observe} = (() => {
|
|
|
450
471
|
ctx.appendChild(currentScript);
|
|
451
472
|
}
|
|
452
473
|
Promise.all(promises).then(() => {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
+
const nodes = getNodes(ctx);
|
|
479
|
+
nodes.forEach((node) => {
|
|
480
|
+
if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
|
|
481
|
+
render(!!node.template, () => resolveNode(node, this))
|
|
482
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
483
|
+
[...node.attributes].forEach((attr) => {
|
|
484
|
+
const {name, value} = attr,
|
|
485
|
+
[type, ...params] = name.split(":");
|
|
486
|
+
if (type === "" || type=="checked" || node.tagName === "SELECT") { // name is :something
|
|
487
|
+
render(!!attr.template, () => {
|
|
488
|
+
const attrtype = node.getAttribute("type"),
|
|
489
|
+
value = resolveNode(attr, this),
|
|
490
|
+
elvalue = node.getAttribute("value"),
|
|
491
|
+
elname = node.getAttribute("name");
|
|
492
|
+
if (params[0]) {
|
|
493
|
+
if (value === "true") node.setAttribute(params[0], "")
|
|
494
|
+
else node.removeAttribute(params[0]);
|
|
495
|
+
} else if (attrtype === "checkbox" || node.tagName === "OPTION") {
|
|
496
|
+
if (value === "true") {
|
|
497
|
+
node.setAttribute("checked", "");
|
|
477
498
|
} else {
|
|
478
|
-
|
|
499
|
+
node.removeAttribute("checked");
|
|
479
500
|
}
|
|
480
|
-
})
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const value = resolveNode(attr, this);
|
|
484
|
-
if (value === "true") node.setAttribute(name, "");
|
|
485
|
-
else node.removeAttribute(name);
|
|
486
|
-
})
|
|
487
|
-
} else if (type === "l-on") {
|
|
488
|
-
let listener;
|
|
489
|
-
render(!!attr.template, () => {
|
|
490
|
-
const value = resolveNode(attr, this);
|
|
491
|
-
if (listener) node.removeEventListener(params[0], listener);
|
|
492
|
-
listener = this[value] || window[value] || Function(value);
|
|
493
|
-
node.addEventListener(params[0], listener);
|
|
494
|
-
})
|
|
495
|
-
} else if (type === "l-if") {
|
|
496
|
-
render(!!attr.template, () => {
|
|
497
|
-
const value = resolveNode(attr, this);
|
|
498
|
-
node.style.setProperty("display", value === "true" ? "revert" : "none");
|
|
499
|
-
})
|
|
500
|
-
} else if (type === "l-for") {
|
|
501
|
-
node.template ||= node.innerHTML;
|
|
502
|
-
render(!!attr.template, () => {
|
|
503
|
-
const [what = "each", vname = "element", index = "index", array = "array", after = false] = params,
|
|
504
|
-
value = resolveNode(attr, this),
|
|
505
|
-
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
506
|
-
target = what === "each" ? coerced : Object[what](coerced),
|
|
507
|
-
html = target.reduce((html, item, i, target) => {
|
|
508
|
-
return html += Function("context", "with(context) { return `" + node.template + "` }")({
|
|
509
|
-
[vname]: item,
|
|
510
|
-
[index]: i,
|
|
511
|
-
[array]: target
|
|
512
|
-
})
|
|
513
|
-
}, ""),
|
|
514
|
-
parsed = parser.parseFromString(html, "text/html");
|
|
515
|
-
if (!window.lightviewDebug) {
|
|
516
|
-
if (after) {
|
|
517
|
-
node.style.setProperty("display", "none")
|
|
518
|
-
} else {
|
|
519
|
-
while (node.lastElementChild) node.lastElementChild.remove();
|
|
520
|
-
}
|
|
501
|
+
} else if (attrtype === "radio") {
|
|
502
|
+
if (elvalue === value) {
|
|
503
|
+
node.setAttribute("checked", "");
|
|
521
504
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
+
})
|
|
517
|
+
}
|
|
518
|
+
})
|
|
519
|
+
} else if (type === "l-on") {
|
|
520
|
+
let listener;
|
|
521
|
+
render(!!attr.template, () => {
|
|
522
|
+
const value = resolveNode(attr, this);
|
|
523
|
+
if (listener) node.removeEventListener(params[0], listener);
|
|
524
|
+
listener = this[value] || window[value] || Function(value);
|
|
525
|
+
addListener(node,params[0], listener);
|
|
526
|
+
})
|
|
527
|
+
} else if (type === "l-if") {
|
|
528
|
+
render(!!attr.template, () => {
|
|
529
|
+
const value = resolveNode(attr, this);
|
|
530
|
+
node.style.setProperty("display", value === "true" ? "revert" : "none");
|
|
531
|
+
})
|
|
532
|
+
} else if (type === "l-for") {
|
|
533
|
+
node.template ||= node.innerHTML;
|
|
534
|
+
render(!!attr.template, () => {
|
|
535
|
+
const [what = "each", vname = "element", index = "index", array = "array", after = false] = params,
|
|
536
|
+
value = resolveNode(attr, this),
|
|
537
|
+
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
538
|
+
target = what === "each" ? coerced : Object[what](coerced),
|
|
539
|
+
html = target.reduce((html, item, i, target) => {
|
|
540
|
+
return html += Function("context", "with(context) { return `" + node.template + "` }")({
|
|
541
|
+
[vname]: item,
|
|
542
|
+
[index]: i,
|
|
543
|
+
[array]: target
|
|
544
|
+
})
|
|
545
|
+
}, ""),
|
|
546
|
+
parsed = parser.parseFromString(html, "text/html");
|
|
547
|
+
if (!window.lightviewDebug) {
|
|
548
|
+
if (after) {
|
|
549
|
+
node.style.setProperty("display", "none")
|
|
550
|
+
} else {
|
|
551
|
+
while (node.lastElementChild) node.lastElementChild.remove();
|
|
525
552
|
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
553
|
+
}
|
|
554
|
+
while (parsed.body.firstChild) {
|
|
555
|
+
if (after) node.parentElement.insertBefore(parsed.body.firstChild, node);
|
|
556
|
+
else node.appendChild(parsed.body.firstChild);
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
} else if (attr.template) {
|
|
560
|
+
render(!!attr.template, () => resolveNode(attr, this));
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
}
|
|
536
564
|
})
|
|
537
|
-
|
|
565
|
+
shadow.normalize();
|
|
566
|
+
observer.observe(ctx, {attributeOldValue: true});
|
|
567
|
+
if (ctx.hasOwnProperty("connectedCallback")) ctx.connectedCallback();
|
|
568
|
+
})
|
|
569
|
+
}
|
|
538
570
|
|
|
539
571
|
adopted(value) {
|
|
540
|
-
|
|
572
|
+
this.adoptedCallback = value;
|
|
573
|
+
//Object.defineProperty(this, "adoptedCallback", {configurable: true, writable: true, value});
|
|
541
574
|
}
|
|
542
575
|
|
|
543
576
|
connected(value) {
|
|
544
|
-
|
|
577
|
+
this.connectedCallback = value;
|
|
578
|
+
//Object.defineProperty(this, "connectedCallback", {configurable: true, writable: true, value});
|
|
545
579
|
}
|
|
546
580
|
|
|
547
581
|
attributeChanged(value) {
|
|
548
|
-
|
|
582
|
+
this.attributeChangedCallback = value;
|
|
583
|
+
//Object.defineProperty(this, "attributeChangedCallback", {configurable: true, writable: true, value});
|
|
549
584
|
}
|
|
550
585
|
|
|
551
586
|
disconnected(value) {
|
|
552
587
|
Object.defineProperty(this, "disconnectedCallback", {
|
|
553
588
|
configurable: true,
|
|
554
589
|
writable: true,
|
|
555
|
-
value:() => {
|
|
590
|
+
value: () => {
|
|
591
|
+
value();
|
|
592
|
+
super.disconnectedCallback(value);
|
|
593
|
+
}
|
|
556
594
|
});
|
|
557
595
|
}
|
|
558
596
|
|
|
559
|
-
|
|
560
|
-
|
|
597
|
+
getVariableNames() {
|
|
598
|
+
return Object.keys(this.vars).filter((name) => {
|
|
599
|
+
return !(name in reserved) && !["self","addEventListener","postEvent"].includes(name)
|
|
600
|
+
})
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
setValue(name, value, {shared, coerceTo = typeof (value)} = {}) {
|
|
604
|
+
if (!this.isConnected) {
|
|
561
605
|
instances.delete(this);
|
|
562
606
|
return false;
|
|
563
607
|
}
|
|
@@ -566,29 +610,37 @@ const {observe} = (() => {
|
|
|
566
610
|
value = coerce(value, type);
|
|
567
611
|
if (this.varsProxy[name] !== value) {
|
|
568
612
|
const variable = this.vars[name];
|
|
569
|
-
if(variable.shared) {
|
|
570
|
-
const event = new VariableEvent({
|
|
613
|
+
if (variable.shared) {
|
|
614
|
+
const event = new VariableEvent({
|
|
615
|
+
variableName: name,
|
|
616
|
+
value: value,
|
|
617
|
+
oldValue: variable.value
|
|
618
|
+
});
|
|
571
619
|
variable.value = value;
|
|
572
|
-
this.vars.postEvent.value("change",event);
|
|
573
|
-
if(event.defaultPrevented)
|
|
620
|
+
this.vars.postEvent.value("change", event);
|
|
621
|
+
if (event.defaultPrevented) variable.value = value;
|
|
574
622
|
} else {
|
|
575
623
|
this.varsProxy[name] = value;
|
|
576
624
|
}
|
|
577
625
|
}
|
|
578
626
|
return true;
|
|
579
627
|
}
|
|
580
|
-
this.vars[name] = {type:coerceTo, value: coerce(value, coerceTo)};
|
|
628
|
+
this.vars[name] = {name, type: coerceTo, value: coerce(value, coerceTo)};
|
|
581
629
|
return false;
|
|
582
630
|
}
|
|
583
631
|
|
|
632
|
+
getValue(variableName) {
|
|
633
|
+
return this.vars[variableName]?.value;
|
|
634
|
+
}
|
|
635
|
+
|
|
584
636
|
variables(variables, {observed, reactive, shared, exported, imported} = {}) { // options = {observed,reactive,shared,exported,imported}
|
|
585
637
|
const addEventListener = this.varsProxy.addEventListener;
|
|
586
638
|
if (variables !== undefined) {
|
|
587
639
|
Object.entries(variables)
|
|
588
640
|
.forEach(([key, type]) => {
|
|
589
|
-
const variable = this.vars[key] ||= {type};
|
|
641
|
+
const variable = this.vars[key] ||= {name: key, type};
|
|
590
642
|
if (observed || imported) {
|
|
591
|
-
variable.value = coerce(this.getAttribute(key), variable.type);
|
|
643
|
+
variable.value = this.hasAttribute(key) ? coerce(this.getAttribute(key), variable.type) : variable.value;
|
|
592
644
|
variable.observed = observed;
|
|
593
645
|
variable.imported = imported;
|
|
594
646
|
}
|
|
@@ -598,45 +650,65 @@ const {observe} = (() => {
|
|
|
598
650
|
}
|
|
599
651
|
if (shared) {
|
|
600
652
|
variable.shared = true;
|
|
601
|
-
addEventListener("change",({variableName,value}) => {
|
|
602
|
-
if(this.vars[variableName]?.shared) {
|
|
653
|
+
addEventListener("change", ({variableName, value}) => {
|
|
654
|
+
if (this.vars[variableName]?.shared) {
|
|
603
655
|
this.siblings.forEach((instance) => {
|
|
604
|
-
instance.
|
|
656
|
+
instance.setValue(variableName, value);
|
|
605
657
|
})
|
|
606
658
|
}
|
|
607
659
|
})
|
|
608
660
|
}
|
|
609
661
|
if (exported) {
|
|
610
662
|
variable.exported = true;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
value = typeof(value)==="string" ? value : JSON.stringify(value);
|
|
615
|
-
|
|
663
|
+
// in case the export goes up to an iframe
|
|
664
|
+
if (variable.value != null) setComponentAttribute(this, key, variable.value);
|
|
665
|
+
addEventListener("change", ({variableName, value}) => {
|
|
666
|
+
value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
|
|
667
|
+
if (value == null) removeComponentAttribute(this, variableName);
|
|
668
|
+
else setComponentAttribute(this, variableName, value);
|
|
616
669
|
})
|
|
617
670
|
}
|
|
618
671
|
});
|
|
619
|
-
addEventListener("change",({variableName,value}) => {
|
|
620
|
-
[...this.shadowRoot.querySelectorAll(`input[l-bind=${variableName}]`)
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
input.
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
input.
|
|
634
|
-
|
|
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) {
|
|
635
707
|
|
|
708
|
+
}
|
|
636
709
|
}
|
|
637
710
|
}
|
|
638
|
-
}
|
|
639
|
-
})
|
|
711
|
+
})
|
|
640
712
|
})
|
|
641
713
|
}
|
|
642
714
|
return Object.entries(this.vars)
|
|
@@ -665,10 +737,18 @@ const {observe} = (() => {
|
|
|
665
737
|
}
|
|
666
738
|
}
|
|
667
739
|
}
|
|
668
|
-
const createComponent = (name, node, {observer,bindForms,importAnchors}={}) => {
|
|
669
|
-
|
|
740
|
+
const createComponent = (name, node, {observer, bindForms, importAnchors} = {}) => {
|
|
741
|
+
let ctor = customElements.get(name);
|
|
742
|
+
if (ctor) {
|
|
743
|
+
console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
|
|
744
|
+
return ctor;
|
|
745
|
+
}
|
|
746
|
+
ctor = createClass(node, {observer, bindForms, importAnchors});
|
|
747
|
+
customElements.define(name, ctor);
|
|
748
|
+
return ctor;
|
|
670
749
|
}
|
|
671
|
-
|
|
750
|
+
Lightview.createComponent = createComponent;
|
|
751
|
+
//Object.defineProperty(Lightview, "createComponent", {writable: true, configurable: true, value: createComponent})
|
|
672
752
|
const importLink = async (link, observer) => {
|
|
673
753
|
const url = (new URL(link.getAttribute("href"), window.location.href)),
|
|
674
754
|
as = link.getAttribute("as") || getNameFromPath(url.pathname);
|
|
@@ -681,8 +761,8 @@ const {observe} = (() => {
|
|
|
681
761
|
importAnchors = !!dom.head.querySelector('meta[name="l-importAnchors"]'),
|
|
682
762
|
bindForms = !!dom.head.querySelector('meta[name="l-bindForms"]'),
|
|
683
763
|
unhide = !!dom.head.querySelector('meta[name="l-unhide"]');
|
|
684
|
-
if(unhide) dom.body.removeAttribute("hidden");
|
|
685
|
-
createComponent(as, dom.body, {observer,importAnchors,bindForms});
|
|
764
|
+
if (unhide) dom.body.removeAttribute("hidden");
|
|
765
|
+
createComponent(as, dom.body, {observer, importAnchors, bindForms});
|
|
686
766
|
}
|
|
687
767
|
return {as};
|
|
688
768
|
}
|
|
@@ -693,24 +773,159 @@ const {observe} = (() => {
|
|
|
693
773
|
}
|
|
694
774
|
}
|
|
695
775
|
|
|
696
|
-
const bodyAsComponent = (as,
|
|
776
|
+
const bodyAsComponent = ({as = "x-body", unhide, importAnchors, bindForms} = {}) => {
|
|
697
777
|
const parent = document.body.parentElement;
|
|
698
|
-
createComponent(as, document.body,{importAnchors,bindForms});
|
|
778
|
+
createComponent(as, document.body, {importAnchors, bindForms});
|
|
699
779
|
const component = document.createElement(as);
|
|
700
780
|
parent.replaceChild(component, document.body);
|
|
781
|
+
Object.defineProperty(document, "body", {
|
|
782
|
+
enumerable: true, configurable: true, get() {
|
|
783
|
+
return component;
|
|
784
|
+
}
|
|
785
|
+
});
|
|
701
786
|
if (unhide) component.removeAttribute("hidden");
|
|
702
787
|
}
|
|
788
|
+
Lightview.bodyAsComponent = bodyAsComponent;
|
|
789
|
+
const postMessage = (data, target = window.parent) => {
|
|
790
|
+
if (postMessage.enabled) {
|
|
791
|
+
if (target instanceof HTMLIFrameElement) {
|
|
792
|
+
data = {...data, href: window.location.href};
|
|
793
|
+
target.contentWindow.postMessage(JSON.stringify(data), "*");
|
|
794
|
+
} else {
|
|
795
|
+
data = {...data, iframeId: document.lightviewId, href: window.location.href};
|
|
796
|
+
target.postMessage(JSON.stringify(data), "*");
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
const setComponentAttribute = (node, name, value) => {
|
|
801
|
+
if (node.getAttribute(name) !== value) node.setAttribute(name, value);
|
|
802
|
+
postMessage({type: "setAttribute", argsList: [name, value]});
|
|
803
|
+
}
|
|
804
|
+
const removeComponentAttribute = (node, name, value) => {
|
|
805
|
+
node.removeAttribute(name);
|
|
806
|
+
postMessage({type: "removeAttribute", argsList: [name]});
|
|
807
|
+
}
|
|
808
|
+
const getNodePath = (node, path = []) => {
|
|
809
|
+
path.unshift(node);
|
|
810
|
+
if (node.parentNode && node.parentNode !== node.parentNode) getNodePath(node.parentNode, path);
|
|
811
|
+
return path;
|
|
812
|
+
}
|
|
813
|
+
const onresize = (node, callback) => {
|
|
814
|
+
const resizeObserver = new ResizeObserver(() => callback());
|
|
815
|
+
resizeObserver.observe(node);
|
|
816
|
+
};
|
|
703
817
|
|
|
704
818
|
const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
|
|
705
|
-
|
|
706
|
-
|
|
819
|
+
let domContentLoadedEvent;
|
|
820
|
+
addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
|
|
821
|
+
const loader = async (whenFramed) => {
|
|
822
|
+
if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
|
|
707
823
|
const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
|
|
708
824
|
bindForms = !!document.querySelector('meta[name="l-bindForms"]'),
|
|
709
|
-
unhide = !!document.querySelector('meta[name="l-unhide"]')
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
825
|
+
unhide = !!document.querySelector('meta[name="l-unhide"]'),
|
|
826
|
+
isolated = !!document.querySelector('meta[name="l-isolate"]'),
|
|
827
|
+
enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
|
|
828
|
+
if (whenFramed) {
|
|
829
|
+
whenFramed({unhide, importAnchors, bindForms, isolated, enableFrames});
|
|
830
|
+
if (!isolated) {
|
|
831
|
+
postMessage.enabled = true;
|
|
832
|
+
addListener(window,"message", ({data}) => {
|
|
833
|
+
const {type, argsList} = JSON.parse(data);
|
|
834
|
+
if (type === "framed") {
|
|
835
|
+
const resize = () => {
|
|
836
|
+
const {width, height} = document.body.getBoundingClientRect();
|
|
837
|
+
postMessage({type: "setAttribute", argsList: ["width", width]})
|
|
838
|
+
postMessage({type: "setAttribute", argsList: ["height", height + 20]});
|
|
839
|
+
}
|
|
840
|
+
resize();
|
|
841
|
+
onresize(document.body, () => {
|
|
842
|
+
resize();
|
|
843
|
+
});
|
|
844
|
+
return
|
|
845
|
+
}
|
|
846
|
+
if (type === "setAttribute") {
|
|
847
|
+
const [name, value] = [...argsList],
|
|
848
|
+
variable = document.body.vars[name];
|
|
849
|
+
if (variable && variable.imported) document.body.setValue(name, value);
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (type === "removeAttribute") {
|
|
853
|
+
const [name] = argsList[0],
|
|
854
|
+
variable = document.body.vars[name];
|
|
855
|
+
if (variable && variable.imported) document.body.setValue(name, undefined);
|
|
715
856
|
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
const url = new URL(window.location.href);
|
|
860
|
+
document.lightviewId = url.searchParams.get("id");
|
|
861
|
+
postMessage({type: "DOMContentLoaded"})
|
|
862
|
+
}
|
|
863
|
+
} else if (url.searchParams.has("as")) {
|
|
864
|
+
bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors, bindForms});
|
|
865
|
+
}
|
|
866
|
+
if (enableFrames) {
|
|
867
|
+
postMessage.enabled = true;
|
|
868
|
+
addListener(window,"message", (message) => {
|
|
869
|
+
const {type, iframeId, argsList, href} = JSON.parse(message.data),
|
|
870
|
+
iframe = document.getElementById(iframeId);
|
|
871
|
+
if (iframe) {
|
|
872
|
+
if (type === "DOMContentLoaded") {
|
|
873
|
+
postMessage({type: "framed", href: window.location.href}, iframe);
|
|
874
|
+
Object.defineProperty(domContentLoadedEvent, "currentTarget", {
|
|
875
|
+
enumerable: false,
|
|
876
|
+
configurable: true,
|
|
877
|
+
value: iframe
|
|
878
|
+
});
|
|
879
|
+
domContentLoadedEvent.href = href;
|
|
880
|
+
domContentLoadedEvent.srcElement = iframe;
|
|
881
|
+
domContentLoadedEvent.bubbles = false;
|
|
882
|
+
domContentLoadedEvent.path = getNodePath(iframe);
|
|
883
|
+
Object.defineProperty(domContentLoadedEvent, "timeStamp", {
|
|
884
|
+
enumerable: false,
|
|
885
|
+
configurable: true,
|
|
886
|
+
value: performance.now()
|
|
887
|
+
})
|
|
888
|
+
iframe.dispatchEvent(domContentLoadedEvent);
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (type === "setAttribute") {
|
|
892
|
+
const [name, value] = [...argsList];
|
|
893
|
+
if (iframe.getAttribute(name) !== value + "") iframe.setAttribute(name, value);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (type === "removeAttribute") {
|
|
897
|
+
iframe.removeAttribute(...argsList);
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
console.warn("iframe posted a message without providing an id", message);
|
|
902
|
+
});
|
|
903
|
+
const mutationCallback = (mutationsList) => {
|
|
904
|
+
const console = document.getElementById("console");
|
|
905
|
+
for (const {target, attributeName, oldValue} of mutationsList) {
|
|
906
|
+
if (!["height", "width"].includes(attributeName)) {
|
|
907
|
+
const value = target.getAttribute(attributeName);
|
|
908
|
+
if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
|
|
909
|
+
else if (value !== oldValue) postMessage({
|
|
910
|
+
type: "setAttribute",
|
|
911
|
+
argsList: [attributeName, value]
|
|
912
|
+
}, iframe)
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
const observer = new MutationObserver(mutationCallback),
|
|
917
|
+
iframe = document.getElementById("myframe");
|
|
918
|
+
observer.observe(iframe, {attributes: true, attributeOldValue: true});
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
const whenFramed = (f, {isolated} = {}) => {
|
|
922
|
+
addListener(document,"DOMContentLoaded", (event) => loader(f));
|
|
923
|
+
}
|
|
924
|
+
Lightview.whenFramed = whenFramed;
|
|
925
|
+
//Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
|
|
926
|
+
if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) { // CodePen mucks with window.parent
|
|
927
|
+
addListener(document,"DOMContentLoaded", () => loader())
|
|
928
|
+
}
|
|
716
929
|
|
|
930
|
+
return {observe}
|
|
931
|
+
})();
|
package/package.json
CHANGED
package/remote.html
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<head>
|
|
4
|
+
<title>Remote</title>
|
|
5
|
+
<link type="module" src="">
|
|
6
|
+
<meta name="l-enableFrames">
|
|
7
|
+
<script src="./lightview.js"></script>
|
|
8
|
+
</head>
|
|
9
|
+
|
|
10
|
+
<body>
|
|
11
|
+
<p>
|
|
12
|
+
The component below is loaded from an alternate domain and running in an iframe.
|
|
13
|
+
</p>
|
|
14
|
+
<iframe id="myframe" src="https://lightview.dev/remoteform.html?id=myframe"></iframe>
|
|
15
|
+
<div id="console" style="max-height:250px;scroll:auto"></div>
|
|
16
|
+
<script>
|
|
17
|
+
const mutationCallback = (mutationsList) => {
|
|
18
|
+
const console = document.getElementById("console");
|
|
19
|
+
for (const {target,attributeName,oldValue} of mutationsList) {
|
|
20
|
+
const line = document.createElement("div"),
|
|
21
|
+
event = {attributeName,oldValue,value:target.getAttribute(attributeName)};
|
|
22
|
+
line.innerText = JSON.stringify(event);
|
|
23
|
+
console.appendChild(line);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const observer = new MutationObserver(mutationCallback),
|
|
27
|
+
iframe = document.getElementById("myframe");
|
|
28
|
+
observer.observe(iframe, { attributes:true, attributeOldValue: true });
|
|
29
|
+
iframe.addEventListener("DOMContentLoaded",(event) => {
|
|
30
|
+
console.log(event);
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
</body>
|
|
34
|
+
|
|
35
|
+
</html>
|
package/remoteform.html
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<head>
|
|
4
|
+
<title>Form</title>
|
|
5
|
+
<meta name="l-bindForms">
|
|
6
|
+
<script src="./lightview.js?as=x-body"></script>
|
|
7
|
+
<script>Lightview.whenFramed(({as,unhide,importAnchors,bindForms,isolated,enableFrames}) => {
|
|
8
|
+
Lightview.bodyAsComponent({as,unhide,importAnchors,bindForms,isolated,enableFrames});
|
|
9
|
+
})</script>
|
|
10
|
+
</head>
|
|
11
|
+
|
|
12
|
+
<body style="height:fit-content;width:fit-content;display:flex;flex-direction:column;max-height:100%;overflow:auto;">
|
|
13
|
+
<p>
|
|
14
|
+
<button l-on:click="run">Run</button> <button l-on:click="reset">Reset</button> <button l-on:click="setNull">Set Null</button>
|
|
15
|
+
</p>
|
|
16
|
+
<form>
|
|
17
|
+
<input name="name" type="text" value="Joe"><br>
|
|
18
|
+
<input name="age" type="number" value="20"><br>
|
|
19
|
+
</form>
|
|
20
|
+
<div id="console"></div>
|
|
21
|
+
<script type="lightview/module">
|
|
22
|
+
self.variables({name:string,age:number},{exported,imported});
|
|
23
|
+
self.run = () => {
|
|
24
|
+
name = "Bill";
|
|
25
|
+
age = 30;
|
|
26
|
+
action("run");
|
|
27
|
+
};
|
|
28
|
+
self.reset = () => {
|
|
29
|
+
name = "Joe";
|
|
30
|
+
age = 20;
|
|
31
|
+
const console = self.getElementById("console");
|
|
32
|
+
while(console.lastElementChild) console.lastElementChild.remove();
|
|
33
|
+
};
|
|
34
|
+
self.setNull = () => {
|
|
35
|
+
name = null;
|
|
36
|
+
age = undefined;
|
|
37
|
+
action("setNull");
|
|
38
|
+
};
|
|
39
|
+
const action = (name) => {
|
|
40
|
+
const div = document.createElement("div");
|
|
41
|
+
div.innerText = name;
|
|
42
|
+
self.getElementById("console").appendChild(div);
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
</body>
|
|
46
|
+
|
|
47
|
+
</html>
|
package/xor.html
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<template id="audiostream">
|
|
6
|
+
<p>${name}</p>
|
|
7
|
+
<p>
|
|
8
|
+
Play: <input name="play" type="checkbox" l-bind="run" checked="${run}">
|
|
9
|
+
</p>
|
|
10
|
+
<script type="lightview/module">
|
|
11
|
+
self.variables({
|
|
12
|
+
run: boolean
|
|
13
|
+
});
|
|
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
|
+
bindForms: true
|
|
36
|
+
})
|
|
37
|
+
</script>
|
|
38
|
+
</head>
|
|
39
|
+
|
|
40
|
+
<body>
|
|
41
|
+
<div style="margin:20px">
|
|
42
|
+
<table>
|
|
43
|
+
<th>
|
|
44
|
+
<td colspan="3">Audio Streams</td>
|
|
45
|
+
</th>
|
|
46
|
+
<tr>
|
|
47
|
+
<td style="width:33%;text-align:center">
|
|
48
|
+
<x-audiostream name="Classical"></x-audiostream>
|
|
49
|
+
</td>
|
|
50
|
+
<td style="width:33%;text-align:center">
|
|
51
|
+
<x-audiostream name="Country"></x-audiostream>
|
|
52
|
+
</td>
|
|
53
|
+
<td style="width:33%;text-align:center">
|
|
54
|
+
<x-audiostream name="Classic Rock"></x-audiostream>
|
|
55
|
+
</td>
|
|
56
|
+
</tr>
|
|
57
|
+
</table>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
</body>
|
|
61
|
+
|
|
62
|
+
</html>
|