lightview 1.4.1-b → 1.4.5-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 +1 -1
- package/jest-puppeteer.config.js +5 -0
- package/jest.config.json +12 -0
- package/lightview.js +147 -104
- package/package.json +7 -3
- package/test/basic.html +79 -0
- package/test/basic.test.mjs +248 -0
package/README.md
CHANGED
package/jest.config.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"moduleFileExtensions": ["mjs", "js", "jsx", "ts", "tsx", "json", "node"],
|
|
3
|
+
"testMatch": [
|
|
4
|
+
"**/?(*.)test.?js"
|
|
5
|
+
],
|
|
6
|
+
"verbose": true,
|
|
7
|
+
"transform": {},
|
|
8
|
+
"testEnvironment": "jest-environment-node",
|
|
9
|
+
"globalSetup": "jest-environment-puppeteer/setup",
|
|
10
|
+
"globalTeardown": "jest-environment-puppeteer/teardown",
|
|
11
|
+
"testEnvironment": "jest-environment-puppeteer"
|
|
12
|
+
}
|
package/lightview.js
CHANGED
|
@@ -30,8 +30,8 @@ const {observe} = (() => {
|
|
|
30
30
|
let CURRENTOBSERVER;
|
|
31
31
|
const parser = new DOMParser();
|
|
32
32
|
|
|
33
|
-
const addListener = (node,eventName,callback) => {
|
|
34
|
-
node.addEventListener(eventName,callback); // just used to make code footprint smaller
|
|
33
|
+
const addListener = (node, eventName, callback) => {
|
|
34
|
+
node.addEventListener(eventName, callback); // just used to make code footprint smaller
|
|
35
35
|
}
|
|
36
36
|
const anchorHandler = async (event) => {
|
|
37
37
|
event.preventDefault();
|
|
@@ -73,6 +73,7 @@ const {observe} = (() => {
|
|
|
73
73
|
if (toType === "number") return parseFloat(value + "");
|
|
74
74
|
if (toType === "boolean") {
|
|
75
75
|
if (["on", "checked", "selected"].includes(value)) return true;
|
|
76
|
+
if(value==null || value==="") return false;
|
|
76
77
|
try {
|
|
77
78
|
const parsed = JSON.parse(value + "");
|
|
78
79
|
if (typeof (parsed) === "boolean") return parsed;
|
|
@@ -93,9 +94,18 @@ const {observe} = (() => {
|
|
|
93
94
|
if (isfunction) {
|
|
94
95
|
const instance = toType === Date ? new Date() : Object.create(toType.prototype);
|
|
95
96
|
if (instance instanceof Array) {
|
|
96
|
-
|
|
97
|
-
if (!Array.isArray(parsed))
|
|
98
|
-
|
|
97
|
+
let parsed = tryParse(value.startsWith("[") ? value : `[${value}]`);
|
|
98
|
+
if (!Array.isArray(parsed)) {
|
|
99
|
+
if(value.includes(",")) parsed = value.split(",");
|
|
100
|
+
else {
|
|
101
|
+
parsed = tryParse(`["${value}"]`);
|
|
102
|
+
if(!Array.isArray(parsed) || parsed[0]!==value && parsed.length!==1) parsed = null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!Array.isArray(parsed)) {
|
|
106
|
+
throw new TypeError(`Expected an Array for parsed data`)
|
|
107
|
+
}
|
|
108
|
+
instance.push(...parsed);
|
|
99
109
|
} else if (instance instanceof Date) {
|
|
100
110
|
instance.setTime(Date.parse(value));
|
|
101
111
|
} else {
|
|
@@ -131,7 +141,7 @@ const {observe} = (() => {
|
|
|
131
141
|
return [...target];
|
|
132
142
|
}
|
|
133
143
|
if (property === "toString") return function toString() {
|
|
134
|
-
return JSON.stringify(target);
|
|
144
|
+
return JSON.stringify([...target]);
|
|
135
145
|
}
|
|
136
146
|
}
|
|
137
147
|
let value = target[property];
|
|
@@ -218,14 +228,20 @@ const {observe} = (() => {
|
|
|
218
228
|
}
|
|
219
229
|
});
|
|
220
230
|
}
|
|
221
|
-
const createObserver = (domNode) => {
|
|
231
|
+
const createObserver = (domNode, framed) => {
|
|
222
232
|
const observer = new MutationObserver((mutations) => {
|
|
223
233
|
mutations.forEach((mutation) => {
|
|
224
234
|
if (mutation.type === "attributes") {
|
|
235
|
+
if (framed) debugger;
|
|
225
236
|
const name = mutation.attributeName,
|
|
226
|
-
target = mutation.target
|
|
237
|
+
target = mutation.target,
|
|
238
|
+
value = target.getAttribute(name);
|
|
239
|
+
if (framed && name === "message" && target instanceof IFrameElement) {
|
|
240
|
+
if (value) console.log("message", value);
|
|
241
|
+
target.removeAttribute(name);
|
|
242
|
+
target.dispatchEvent(new CustomEvent("message", {detail: JSON.parse(value)}))
|
|
243
|
+
}
|
|
227
244
|
if (target.observedAttributes && target.observedAttributes.includes(name)) {
|
|
228
|
-
const value = target.getAttribute(name);
|
|
229
245
|
if (value !== mutation.oldValue) {
|
|
230
246
|
target.setValue(name, value);
|
|
231
247
|
if (target.attributeChangedCallback) target.attributeChangedCallback(name, value, mutation.oldValue);
|
|
@@ -264,7 +280,7 @@ const {observe} = (() => {
|
|
|
264
280
|
nodes.push(node);
|
|
265
281
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
266
282
|
let skip;
|
|
267
|
-
if(node.getAttribute("type")==="radio") nodes.push(node);
|
|
283
|
+
if (node.getAttribute("type") === "radio") nodes.push(node);
|
|
268
284
|
[...node.attributes].forEach((attr) => {
|
|
269
285
|
if (attr.value.includes("${")) {
|
|
270
286
|
attr.template ||= attr.value;
|
|
@@ -302,7 +318,7 @@ const {observe} = (() => {
|
|
|
302
318
|
}
|
|
303
319
|
const inputTypeToType = (inputType) => {
|
|
304
320
|
if (!inputType) return "any"
|
|
305
|
-
if (["text", "tel", "email", "url", "search", "radio","color","password"].includes(inputType)) return "string";
|
|
321
|
+
if (["text", "tel", "email", "url", "search", "radio", "color", "password"].includes(inputType)) return "string";
|
|
306
322
|
if (["number", "range"].includes(inputType)) return "number";
|
|
307
323
|
if (["datetime"].includes(inputType)) return Date;
|
|
308
324
|
if (["checkbox"].includes(inputType)) return "boolean";
|
|
@@ -311,44 +327,45 @@ const {observe} = (() => {
|
|
|
311
327
|
const _importAnchors = (node, component) => {
|
|
312
328
|
[...node.querySelectorAll('a[href][target^="#"]')].forEach((node) => {
|
|
313
329
|
node.removeEventListener("click", anchorHandler);
|
|
314
|
-
addListener(node,"click", anchorHandler);
|
|
330
|
+
addListener(node, "click", anchorHandler);
|
|
315
331
|
})
|
|
316
332
|
}
|
|
317
|
-
const bindInput = (input, name, component) => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
333
|
+
const bindInput = (input, name, component,value) => {
|
|
334
|
+
const inputtype = input.tagName === "SELECT" || input.tagName === "TEXTAREA" ? "text" : input.getAttribute("type"),
|
|
335
|
+
type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
|
|
336
|
+
deflt = input.getAttribute("default");
|
|
337
|
+
value ||= input.getAttribute("value");
|
|
338
|
+
let variable = component.vars[name] || {type};
|
|
339
|
+
if (type !== variable.type) {
|
|
340
|
+
if (variable.type === "any" || variable.type === "unknown") variable.type = type;
|
|
341
|
+
else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
|
|
342
|
+
}
|
|
343
|
+
component.variables({[name]: type});
|
|
344
|
+
component.setValue(name,value);
|
|
345
|
+
let eventname = "change";
|
|
346
|
+
if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
|
|
347
|
+
eventname = "input";
|
|
348
|
+
}
|
|
349
|
+
addListener(input, eventname, (event) => {
|
|
350
|
+
event.stopImmediatePropagation();
|
|
351
|
+
const target = event.target;
|
|
352
|
+
let value = target.value;
|
|
353
|
+
if (inputtype === "checkbox") {
|
|
354
|
+
value = input.checked
|
|
355
|
+
} else if (target.tagName === "SELECT") {
|
|
356
|
+
if (target.hasAttribute("multiple")) {
|
|
357
|
+
value = [...target.querySelectorAll("option")]
|
|
358
|
+
.filter((option) => option.selected || resolveNode(option.attributes.value, component) == value || option.innerText == value)
|
|
359
|
+
.map((option) => option.getAttribute("value") || option.innerText);
|
|
344
360
|
}
|
|
345
|
-
|
|
346
|
-
|
|
361
|
+
}
|
|
362
|
+
component.varsProxy[name] = coerce(value, type);
|
|
363
|
+
})
|
|
347
364
|
}
|
|
348
365
|
const tryParse = (value) => {
|
|
349
366
|
try {
|
|
350
367
|
return JSON.parse(value);
|
|
351
|
-
} catch(e) {
|
|
368
|
+
} catch (e) {
|
|
352
369
|
return value;
|
|
353
370
|
}
|
|
354
371
|
}
|
|
@@ -362,7 +379,7 @@ const {observe} = (() => {
|
|
|
362
379
|
exported: {value: true, constant: true},
|
|
363
380
|
imported: {value: true, constant: true}
|
|
364
381
|
};
|
|
365
|
-
const createClass = (domElementNode, {observer,
|
|
382
|
+
const createClass = (domElementNode, {observer, importAnchors, framed}) => {
|
|
366
383
|
const instances = new Set(),
|
|
367
384
|
dom = domElementNode.tagName === "TEMPLATE"
|
|
368
385
|
? domElementNode.content.cloneNode(true)
|
|
@@ -376,7 +393,7 @@ const {observe} = (() => {
|
|
|
376
393
|
constructor() {
|
|
377
394
|
super();
|
|
378
395
|
instances.add(this);
|
|
379
|
-
observer ||= createObserver(this);
|
|
396
|
+
observer ||= createObserver(this, framed);
|
|
380
397
|
const currentComponent = this,
|
|
381
398
|
shadow = this.attachShadow({mode: "open"}),
|
|
382
399
|
eventlisteners = {};
|
|
@@ -406,6 +423,7 @@ const {observe} = (() => {
|
|
|
406
423
|
};
|
|
407
424
|
this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
|
|
408
425
|
this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
|
|
426
|
+
if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported: true});
|
|
409
427
|
["getElementById", "querySelector", "querySelectorAll"]
|
|
410
428
|
.forEach((fname) => {
|
|
411
429
|
Object.defineProperty(this, fname, {
|
|
@@ -465,51 +483,49 @@ const {observe} = (() => {
|
|
|
465
483
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
466
484
|
// resolve the value before all else;
|
|
467
485
|
const attr = node.attributes.value;
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
render(!!attr.template,() => {
|
|
486
|
+
if (attr && attr.template) {
|
|
487
|
+
render(!!attr.template, () => {
|
|
471
488
|
const value = resolveNode(attr, this),
|
|
472
|
-
eltype = resolveNode(node.attributes.type,ctx);
|
|
473
|
-
if(
|
|
474
|
-
|
|
475
|
-
|
|
489
|
+
eltype = resolveNode(node.attributes.type, ctx);
|
|
490
|
+
if(node.attributes.value) {
|
|
491
|
+
const template = attr.template;
|
|
492
|
+
if(/\$\{[a-zA-z_]+\}/g.test(template)) {
|
|
493
|
+
const name = template.substring(2,template.length-1);
|
|
494
|
+
if(!name.includes(" ")) bindInput(node,name,this,value);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (eltype === "checkbox") {
|
|
498
|
+
if (coerce(value, "boolean") === true) {
|
|
499
|
+
node.setAttribute("checked", "");
|
|
476
500
|
node.checked = true;
|
|
477
501
|
} else {
|
|
478
502
|
node.removeAttribute("checked");
|
|
479
503
|
node.checked = false;
|
|
480
504
|
}
|
|
481
|
-
const vname = resolveNode(node.attributes.name,ctx);
|
|
482
|
-
if(vname) ctx.setValue(vname,node.checked,{coerceTo:"boolean"});
|
|
505
|
+
const vname = resolveNode(node.attributes.name, ctx);
|
|
506
|
+
if (vname) ctx.setValue(vname, node.checked, {coerceTo: "boolean"});
|
|
483
507
|
}
|
|
484
|
-
if(node.tagName==="SELECT") {
|
|
508
|
+
if (node.tagName === "SELECT") {
|
|
485
509
|
let values = [value];
|
|
486
|
-
if(node.hasAttribute("multiple")) values = coerce(value,Array);
|
|
510
|
+
if (node.hasAttribute("multiple")) values = coerce(value, Array);
|
|
487
511
|
[...node.querySelectorAll("option")].forEach((option) => {
|
|
488
|
-
if(option.hasAttribute("value")) {
|
|
512
|
+
if (option.hasAttribute("value")) {
|
|
489
513
|
if (values.includes(resolveNode(option.attributes.value, ctx))) {
|
|
490
514
|
option.setAttribute("selected", "");
|
|
491
515
|
option.selected = true;
|
|
492
516
|
}
|
|
493
|
-
} else if(option.innerText.trim()===value) {
|
|
494
|
-
option.setAttribute("selected","");
|
|
517
|
+
} else if (option.innerText.trim() === value) {
|
|
518
|
+
option.setAttribute("selected", "");
|
|
495
519
|
option.selected = true;
|
|
496
520
|
}
|
|
497
521
|
})
|
|
498
522
|
}
|
|
499
523
|
});
|
|
500
|
-
let name;
|
|
501
|
-
for(const vname of this.getVariableNames()) {
|
|
502
|
-
if("${" + vname + "}" === attr.template) {
|
|
503
|
-
name = vname;
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if(name) bindInput(node,name,ctx);
|
|
508
524
|
}
|
|
509
525
|
[...node.attributes].forEach((attr) => {
|
|
510
|
-
if(attr.name==="value") return;
|
|
526
|
+
if (attr.name === "value") return;
|
|
511
527
|
const {name, value} = attr;
|
|
512
|
-
if(name==="type") {
|
|
528
|
+
if (name === "type") {
|
|
513
529
|
if (value === "radio") {
|
|
514
530
|
const name = resolveNode(node.attributes.name, ctx);
|
|
515
531
|
for (const vname of this.getVariableNames()) {
|
|
@@ -517,7 +533,7 @@ const {observe} = (() => {
|
|
|
517
533
|
render(true, () => {
|
|
518
534
|
const name = resolveNode(node.attributes.name, ctx),
|
|
519
535
|
varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
|
|
520
|
-
if (varvalue == resolveNode(node.attributes.value,ctx)) {
|
|
536
|
+
if (varvalue == resolveNode(node.attributes.value, ctx)) {
|
|
521
537
|
node.setAttribute("checked", "");
|
|
522
538
|
node.checked = true;
|
|
523
539
|
} else {
|
|
@@ -532,17 +548,17 @@ const {observe} = (() => {
|
|
|
532
548
|
}
|
|
533
549
|
}
|
|
534
550
|
|
|
535
|
-
|
|
551
|
+
const [type, ...params] = name.split(":");
|
|
536
552
|
if (type === "") { // name is :something
|
|
537
553
|
render(!!attr.template, () => {
|
|
538
554
|
const value = attr.value,
|
|
539
|
-
elvalue = resolveNode(node.attributes.value,ctx),
|
|
540
|
-
eltype = resolveNode(node.attributes.type,ctx),
|
|
541
|
-
elname = resolveNode(node.attributes.name,ctx);
|
|
555
|
+
elvalue = resolveNode(node.attributes.value, ctx),
|
|
556
|
+
eltype = resolveNode(node.attributes.type, ctx),
|
|
557
|
+
elname = resolveNode(node.attributes.name, ctx);
|
|
542
558
|
if (params[0]) {
|
|
543
559
|
if (value === "true") node.setAttribute(params[0], "")
|
|
544
560
|
else node.removeAttribute(params[0]);
|
|
545
|
-
} else if (eltype=== "checkbox" || node.tagName === "OPTION") {
|
|
561
|
+
} else if (eltype === "checkbox" || node.tagName === "OPTION") {
|
|
546
562
|
if (value === "true") node.setAttribute("checked", "")
|
|
547
563
|
else node.removeAttribute("checked");
|
|
548
564
|
}
|
|
@@ -553,7 +569,7 @@ const {observe} = (() => {
|
|
|
553
569
|
const value = resolveNode(attr, this);
|
|
554
570
|
if (listener) node.removeEventListener(params[0], listener);
|
|
555
571
|
listener = this[value] || window[value] || Function(value);
|
|
556
|
-
addListener(node,params[0], listener);
|
|
572
|
+
addListener(node, params[0], listener);
|
|
557
573
|
})
|
|
558
574
|
} else if (type === "l-if") {
|
|
559
575
|
render(!!attr.template, () => {
|
|
@@ -627,7 +643,7 @@ const {observe} = (() => {
|
|
|
627
643
|
|
|
628
644
|
getVariableNames() {
|
|
629
645
|
return Object.keys(this.vars).filter((name) => {
|
|
630
|
-
return !(name in reserved) && !["self","addEventListener","postEvent"].includes(name)
|
|
646
|
+
return !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name)
|
|
631
647
|
})
|
|
632
648
|
}
|
|
633
649
|
|
|
@@ -725,13 +741,17 @@ const {observe} = (() => {
|
|
|
725
741
|
}
|
|
726
742
|
}
|
|
727
743
|
}
|
|
728
|
-
const createComponent = (name, node, {observer, importAnchors} = {}) => {
|
|
744
|
+
const createComponent = (name, node, {observer, importAnchors, framed} = {}) => {
|
|
729
745
|
let ctor = customElements.get(name);
|
|
730
746
|
if (ctor) {
|
|
731
|
-
|
|
747
|
+
if (framed && !ctor.lightviewFramed) {
|
|
748
|
+
ctor.lightviewFramed = true;
|
|
749
|
+
} else {
|
|
750
|
+
console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
|
|
751
|
+
}
|
|
732
752
|
return ctor;
|
|
733
753
|
}
|
|
734
|
-
ctor = createClass(node, {observer, importAnchors});
|
|
754
|
+
ctor = createClass(node, {observer, importAnchors, framed});
|
|
735
755
|
customElements.define(name, ctor);
|
|
736
756
|
return ctor;
|
|
737
757
|
}
|
|
@@ -760,9 +780,9 @@ const {observe} = (() => {
|
|
|
760
780
|
}
|
|
761
781
|
}
|
|
762
782
|
|
|
763
|
-
const bodyAsComponent = ({as = "x-body", unhide, importAnchors} = {}) => {
|
|
783
|
+
const bodyAsComponent = ({as = "x-body", unhide, importAnchors, framed} = {}) => {
|
|
764
784
|
const parent = document.body.parentElement;
|
|
765
|
-
createComponent(as, document.body, {importAnchors});
|
|
785
|
+
createComponent(as, document.body, {importAnchors, framed});
|
|
766
786
|
const component = document.createElement(as);
|
|
767
787
|
parent.replaceChild(component, document.body);
|
|
768
788
|
Object.defineProperty(document, "body", {
|
|
@@ -804,7 +824,8 @@ const {observe} = (() => {
|
|
|
804
824
|
|
|
805
825
|
const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
|
|
806
826
|
let domContentLoadedEvent;
|
|
807
|
-
addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
|
|
827
|
+
if (!domContentLoadedEvent) addListener(window, "DOMContentLoaded", (event) => domContentLoadedEvent = event);
|
|
828
|
+
let OBSERVER;
|
|
808
829
|
const loader = async (whenFramed) => {
|
|
809
830
|
if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
|
|
810
831
|
const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
|
|
@@ -812,10 +833,10 @@ const {observe} = (() => {
|
|
|
812
833
|
isolated = !!document.querySelector('meta[name="l-isolate"]'),
|
|
813
834
|
enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
|
|
814
835
|
if (whenFramed) {
|
|
815
|
-
whenFramed({unhide, importAnchors, isolated, enableFrames});
|
|
836
|
+
whenFramed({unhide, importAnchors, isolated, enableFrames, framed: true});
|
|
816
837
|
if (!isolated) {
|
|
817
838
|
postMessage.enabled = true;
|
|
818
|
-
addListener(window,"message", ({data}) => {
|
|
839
|
+
addListener(window, "message", ({data}) => {
|
|
819
840
|
const {type, argsList} = JSON.parse(data);
|
|
820
841
|
if (type === "framed") {
|
|
821
842
|
const resize = () => {
|
|
@@ -851,7 +872,7 @@ const {observe} = (() => {
|
|
|
851
872
|
}
|
|
852
873
|
if (enableFrames) {
|
|
853
874
|
postMessage.enabled = true;
|
|
854
|
-
addListener(window,"message", (message) => {
|
|
875
|
+
addListener(window, "message", (message) => {
|
|
855
876
|
const {type, iframeId, argsList, href} = JSON.parse(message.data),
|
|
856
877
|
iframe = document.getElementById(iframeId);
|
|
857
878
|
if (iframe) {
|
|
@@ -876,7 +897,9 @@ const {observe} = (() => {
|
|
|
876
897
|
}
|
|
877
898
|
if (type === "setAttribute") {
|
|
878
899
|
const [name, value] = [...argsList];
|
|
879
|
-
if (iframe.getAttribute(name) !== value + "")
|
|
900
|
+
if (iframe.getAttribute(name) !== value + "") {
|
|
901
|
+
iframe.setAttribute(name, value);
|
|
902
|
+
}
|
|
880
903
|
return;
|
|
881
904
|
}
|
|
882
905
|
if (type === "removeAttribute") {
|
|
@@ -886,32 +909,52 @@ const {observe} = (() => {
|
|
|
886
909
|
}
|
|
887
910
|
console.warn("iframe posted a message without providing an id", message);
|
|
888
911
|
});
|
|
889
|
-
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
|
|
912
|
+
if (!OBSERVER) {
|
|
913
|
+
const mutationCallback = (mutationsList) => {
|
|
914
|
+
const console = document.getElementById("console");
|
|
915
|
+
for (const {target, attributeName, oldValue} of mutationsList) {
|
|
893
916
|
const value = target.getAttribute(attributeName);
|
|
894
|
-
if (!
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
917
|
+
if (!["height", "width", "message"].includes(attributeName)) {
|
|
918
|
+
if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
|
|
919
|
+
else if (value !== oldValue) {
|
|
920
|
+
postMessage({
|
|
921
|
+
type: "setAttribute",
|
|
922
|
+
argsList: [attributeName, value]
|
|
923
|
+
}, iframe)
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (attributeName === "message") {
|
|
927
|
+
if (value) {
|
|
928
|
+
target.removeAttribute("message");
|
|
929
|
+
target.dispatchEvent(new CustomEvent("message", {target, detail: JSON.parse(value)}))
|
|
930
|
+
}
|
|
931
|
+
} else {
|
|
932
|
+
target.dispatchEvent(new CustomEvent("attribute.changed", {
|
|
933
|
+
target,
|
|
934
|
+
detail: {attributeName, value, oldValue}
|
|
935
|
+
}))
|
|
936
|
+
}
|
|
899
937
|
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
iframe
|
|
904
|
-
|
|
938
|
+
};
|
|
939
|
+
const observer = OBSERVER = new MutationObserver(mutationCallback),
|
|
940
|
+
iframe = document.getElementById("myframe");
|
|
941
|
+
observer.observe(iframe, {attributes: true, attributeOldValue: true});
|
|
942
|
+
}
|
|
905
943
|
}
|
|
906
944
|
}
|
|
907
945
|
const whenFramed = (f, {isolated} = {}) => {
|
|
908
|
-
|
|
946
|
+
// loads for framed content
|
|
947
|
+
addListener(document, "DOMContentLoaded", (event) => loader(f));
|
|
909
948
|
}
|
|
910
949
|
Lightview.whenFramed = whenFramed;
|
|
911
950
|
//Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
|
|
912
|
-
if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
|
|
913
|
-
|
|
951
|
+
if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
|
|
952
|
+
// loads for unframed content
|
|
953
|
+
// CodePen mucks with window.parent
|
|
954
|
+
addListener(document, "DOMContentLoaded", () => loader())
|
|
914
955
|
}
|
|
915
956
|
|
|
916
957
|
return {observe}
|
|
917
|
-
})();
|
|
958
|
+
})();
|
|
959
|
+
|
|
960
|
+
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightview",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.5b",
|
|
4
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
|
-
"test": "
|
|
7
|
+
"test": "set NODE_OPTIONS=--experimental-vm-modules && jest ./test"
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -28,5 +28,9 @@
|
|
|
28
28
|
"bugs": {
|
|
29
29
|
"url": "https://github.com/anywhichway/lightview/issues"
|
|
30
30
|
},
|
|
31
|
-
"homepage": "https://github.com/anywhichway/lightview#readme"
|
|
31
|
+
"homepage": "https://github.com/anywhichway/lightview#readme",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"jest": "^27.5.1",
|
|
34
|
+
"jest-puppeteer": "^6.1.0"
|
|
35
|
+
}
|
|
32
36
|
}
|
package/test/basic.html
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Basic</title>
|
|
6
|
+
<template id="x-test" name="joe" open="true" count=1 children='["mary"]' l-on:click="bump">
|
|
7
|
+
<span id="name">${name}</span>
|
|
8
|
+
<span id="open">${open}</span>
|
|
9
|
+
<span id="count">${count}</span>
|
|
10
|
+
<span id="children">${children}</span>
|
|
11
|
+
<span id="color">${color}</span>
|
|
12
|
+
<span id="checked">${checked}</span>
|
|
13
|
+
<span id="age">${age}</span>
|
|
14
|
+
<span id="hamburger">${hamburger}</span>
|
|
15
|
+
|
|
16
|
+
<input id="iuntyped" value="${iuntyped}">
|
|
17
|
+
<input id="itext" type="text" value="${itext}">
|
|
18
|
+
<input id="itel" type="tel" value="${itel}">
|
|
19
|
+
<input id="iemail" type="email" value="${iemail}">
|
|
20
|
+
<input id="iurl" type="url" value="${iurl}">
|
|
21
|
+
<input id="isearch" type="search" value="${isearch}">
|
|
22
|
+
<input id="iradio" type="radio" value="${iradio}">
|
|
23
|
+
<input id="icolor" type="color" value="${icolor}">
|
|
24
|
+
<input id="ipassword" type="password" value="${ipassword}">
|
|
25
|
+
|
|
26
|
+
<input id="inumber" type="number" value="${inumber}">
|
|
27
|
+
<input id="irange" type="range" value="${irange}">
|
|
28
|
+
|
|
29
|
+
<input id="idatetime" type="datetime" value="${idatetime}">
|
|
30
|
+
|
|
31
|
+
<input id="icheckbox" type="checkbox" value="${icheckbox}">
|
|
32
|
+
|
|
33
|
+
<script type="lightview/module">
|
|
34
|
+
debugger;
|
|
35
|
+
self.variables({name:string,open:boolean,count:number,children:Array},{imported,reactive});
|
|
36
|
+
self.variables({color:string,checked:boolean,age:number,hamburger:Array},{exported,reactive});
|
|
37
|
+
self.variables({counter:number},{reactive});
|
|
38
|
+
self.variables({myshare:number},{shared});
|
|
39
|
+
|
|
40
|
+
color = "green";
|
|
41
|
+
checked = true;
|
|
42
|
+
age = 27;
|
|
43
|
+
hamburger = ["lettuce"];
|
|
44
|
+
counter = 0;
|
|
45
|
+
myshare = 1;
|
|
46
|
+
|
|
47
|
+
iuntyped = "test";
|
|
48
|
+
itext = "test";
|
|
49
|
+
itel = "test";
|
|
50
|
+
iemail = "test";
|
|
51
|
+
iurl = "test";
|
|
52
|
+
isearch = "test";
|
|
53
|
+
iradio = "test";
|
|
54
|
+
icolor = "test";
|
|
55
|
+
ipassword = "test";
|
|
56
|
+
|
|
57
|
+
inumber = 1;
|
|
58
|
+
irange = 1;
|
|
59
|
+
|
|
60
|
+
idatetime = new Date();
|
|
61
|
+
|
|
62
|
+
icheckbox = true;
|
|
63
|
+
|
|
64
|
+
self.bump = () => {
|
|
65
|
+
counter++;
|
|
66
|
+
};
|
|
67
|
+
</script>
|
|
68
|
+
</template>
|
|
69
|
+
<script src="../lightview.js"></script>
|
|
70
|
+
<script>
|
|
71
|
+
Lightview.createComponent("x-test",document.getElementById("x-test"));
|
|
72
|
+
</script>
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
<p><x-test id="test"></x-test></p>
|
|
76
|
+
|
|
77
|
+
<p><x-test id="test1"></x-test></p>
|
|
78
|
+
</body>
|
|
79
|
+
</html>
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import 'expect-puppeteer';
|
|
2
|
+
|
|
3
|
+
describe('Google', () => {
|
|
4
|
+
beforeAll(async () => {
|
|
5
|
+
await page.goto('https://google.com');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test('should be titled "Google"', async () => {
|
|
9
|
+
await expect(page.title()).resolves.toMatch('Google');
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('Lightview', () => {
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
await page.goto('http://localhost:8080/test/basic.html');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should be titled "Basic"', async () => {
|
|
19
|
+
await expect(page.title()).resolves.toMatch('Basic');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('boolean - open should be imported', async () => {
|
|
23
|
+
const result = await page.evaluate(() => {
|
|
24
|
+
const el = document.getElementById("test");
|
|
25
|
+
return JSON.parse(el.getValue("open"));
|
|
26
|
+
});
|
|
27
|
+
expect(result).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('number - count should be imported', async () => {
|
|
31
|
+
const result = await page.evaluate(() => {
|
|
32
|
+
const el = document.getElementById("test");
|
|
33
|
+
return JSON.parse(el.getValue("count"));
|
|
34
|
+
});
|
|
35
|
+
expect(result).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('string - name should be imported', async () => {
|
|
39
|
+
const result = await page.evaluate(() => {
|
|
40
|
+
const el = document.getElementById("test");
|
|
41
|
+
return el.getValue("name");
|
|
42
|
+
});
|
|
43
|
+
expect(result).toBe("joe");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('object - children should be imported', async () => {
|
|
47
|
+
const result = await page.evaluate(() => {
|
|
48
|
+
const el = document.getElementById("test");
|
|
49
|
+
return el.getValue("children").toJSON();
|
|
50
|
+
});
|
|
51
|
+
expect(Array.isArray(result)).toBe(true);
|
|
52
|
+
expect(result[0]).toBe("mary");
|
|
53
|
+
expect(result.length).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('boolean - checked should be exported', async () => {
|
|
57
|
+
const result = await page.evaluate(() => {
|
|
58
|
+
const el = document.getElementById("test");
|
|
59
|
+
return JSON.parse(el.getAttribute("checked"));
|
|
60
|
+
});
|
|
61
|
+
expect(result).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('number - age should be exported', async () => {
|
|
65
|
+
const result = await page.evaluate(() => {
|
|
66
|
+
const el = document.getElementById("test");
|
|
67
|
+
return JSON.parse(el.getAttribute("age"));
|
|
68
|
+
});
|
|
69
|
+
expect(result).toBe(27);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('string - color should be exported', async () => {
|
|
73
|
+
const result = await page.evaluate(() => {
|
|
74
|
+
const el = document.getElementById("test");
|
|
75
|
+
return el.getAttribute("color");
|
|
76
|
+
});
|
|
77
|
+
expect(result).toBe("green");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('object - hamburger should be exported', async () => {
|
|
81
|
+
const result = await page.evaluate(() => {
|
|
82
|
+
const el = document.getElementById("test");
|
|
83
|
+
return JSON.parse(el.getAttribute("hamburger"));
|
|
84
|
+
});
|
|
85
|
+
expect(Array.isArray(result)).toBe(true);
|
|
86
|
+
expect(result[0]).toBe("lettuce");
|
|
87
|
+
expect(result.length).toBe(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('boolean - open should be rendered', async () => {
|
|
91
|
+
const result = await page.evaluate(() => {
|
|
92
|
+
const el = document.getElementById("test"),
|
|
93
|
+
result = el.getElementById("open");
|
|
94
|
+
return JSON.parse(result.innerText);
|
|
95
|
+
});
|
|
96
|
+
expect(result).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('number - count should be rendered', async () => {
|
|
100
|
+
const result = await page.evaluate(() => {
|
|
101
|
+
const el = document.getElementById("test"),
|
|
102
|
+
result = el.getElementById("count");
|
|
103
|
+
return JSON.parse(result.innerText);
|
|
104
|
+
});
|
|
105
|
+
expect(result).toBe(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('string - name should be rendered', async () => {
|
|
109
|
+
const result = await page.evaluate(() => {
|
|
110
|
+
const el = document.getElementById("test"),
|
|
111
|
+
result = el.getElementById("name");
|
|
112
|
+
return result.innerText;
|
|
113
|
+
});
|
|
114
|
+
expect(result).toBe("joe");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('object - children should be rendered', async () => {
|
|
118
|
+
const result = await page.evaluate(() => {
|
|
119
|
+
const el = document.getElementById("test"),
|
|
120
|
+
result = el.getElementById("children");
|
|
121
|
+
return JSON.parse(result.innerText);
|
|
122
|
+
});
|
|
123
|
+
expect(Array.isArray(result)).toBe(true);
|
|
124
|
+
expect(result[0]).toBe("mary");
|
|
125
|
+
expect(result.length).toBe(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('boolean - checked should be rendered', async () => {
|
|
129
|
+
const result = await page.evaluate(() => {
|
|
130
|
+
const el = document.getElementById("test"),
|
|
131
|
+
result = el.getElementById("checked");
|
|
132
|
+
return JSON.parse(result.innerText);
|
|
133
|
+
});
|
|
134
|
+
expect(result).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('number - age should be rendered', async () => {
|
|
138
|
+
const result = await page.evaluate(() => {
|
|
139
|
+
const el = document.getElementById("test"),
|
|
140
|
+
result = el.getElementById("age");
|
|
141
|
+
return JSON.parse(result.innerText);
|
|
142
|
+
});
|
|
143
|
+
expect(result).toBe(27);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('string - color should be rendered', async () => {
|
|
147
|
+
const result = await page.evaluate(() => {
|
|
148
|
+
const el = document.getElementById("test"),
|
|
149
|
+
result = el.getElementById("color");
|
|
150
|
+
return result.innerText;
|
|
151
|
+
});
|
|
152
|
+
expect(result).toBe("green");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('object - hamburger should be rendered', async () => {
|
|
156
|
+
const result = await page.evaluate(() => {
|
|
157
|
+
const el = document.getElementById("test"),
|
|
158
|
+
result = el.getElementById("hamburger");
|
|
159
|
+
return JSON.parse(result.innerText);
|
|
160
|
+
});
|
|
161
|
+
expect(Array.isArray(result)).toBe(true);
|
|
162
|
+
expect(result[0]).toBe("lettuce");
|
|
163
|
+
expect(result.length).toBe(1);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('shared - myshare should be same', async () => {
|
|
167
|
+
const result = await page.evaluate(async () => {
|
|
168
|
+
const el0 = document.getElementById("test"),
|
|
169
|
+
el1 = document.getElementById("test1")
|
|
170
|
+
return [el0.getValue("myshare"),el1.getValue("myshare")];
|
|
171
|
+
});
|
|
172
|
+
expect(Array.isArray(result)).toBe(true);
|
|
173
|
+
expect(result[0]).toBe(result[1]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('untyped input - iuntyped should be "test"', async () => {
|
|
177
|
+
const result = await page.evaluate(async () => {
|
|
178
|
+
const el = document.getElementById("test"),
|
|
179
|
+
result = el.getElementById("iuntyped")
|
|
180
|
+
return result.getAttribute("value");
|
|
181
|
+
});
|
|
182
|
+
expect(result).toBe("test");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// "tel", "email", "url", "search", "radio", "color", "password"
|
|
186
|
+
["text","tel","email", "url", "search", "radio", "color", "password"].forEach((type) => {
|
|
187
|
+
const f = Function(`return async () => {
|
|
188
|
+
const result = await page.evaluate(async () => {
|
|
189
|
+
const el = document.getElementById("test"),
|
|
190
|
+
result = el.getElementById("i${type}");
|
|
191
|
+
return {value:result.getAttribute("value"),variable:el.vars["i${type}"]};
|
|
192
|
+
});
|
|
193
|
+
const {value,variable} = result;
|
|
194
|
+
expect(value).toBe("test");
|
|
195
|
+
expect(variable.name).toBe("i${type}");
|
|
196
|
+
expect(variable.type).toBe("string");
|
|
197
|
+
expect(variable.value).toBe(value);
|
|
198
|
+
}`)();
|
|
199
|
+
test(`${type} input - i${type} should be "test"`,f);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('number input - inumber should be 1', async () => {
|
|
203
|
+
const result = await page.evaluate(async () => {
|
|
204
|
+
const el = document.getElementById("test"),
|
|
205
|
+
result = el.getElementById("inumber")
|
|
206
|
+
return JSON.parse(result.getAttribute("value"));
|
|
207
|
+
});
|
|
208
|
+
expect(result).toBe(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('range input - irange should be 1', async () => {
|
|
212
|
+
const result = await page.evaluate(async () => {
|
|
213
|
+
const el = document.getElementById("test"),
|
|
214
|
+
result = el.getElementById("irange")
|
|
215
|
+
return JSON.parse(result.getAttribute("value"));
|
|
216
|
+
});
|
|
217
|
+
expect(result).toBe(1);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('datetime input - idatetime should be current date', async () => {
|
|
221
|
+
const result = await page.evaluate(async () => {
|
|
222
|
+
const el = document.getElementById("test"),
|
|
223
|
+
result = el.getElementById("idatetime")
|
|
224
|
+
return result.getAttribute("value");
|
|
225
|
+
});
|
|
226
|
+
const dt = new Date(result);
|
|
227
|
+
expect(dt).toBeInstanceOf(Date);
|
|
228
|
+
expect(dt.toString()).toBe(result);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('checkbox input - icheckbox should be true', async () => {
|
|
232
|
+
const result = await page.evaluate(async () => {
|
|
233
|
+
const el = document.getElementById("test"),
|
|
234
|
+
result = el.getElementById("icheckbox")
|
|
235
|
+
return JSON.parse(result.getAttribute("value"));
|
|
236
|
+
});
|
|
237
|
+
expect(result).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('on:<handler> - count should be bumped', async () => {
|
|
241
|
+
await page.click("#test",{waitUntil:"load"});
|
|
242
|
+
const result = await page.evaluate(async () => {
|
|
243
|
+
const el = document.getElementById("test");
|
|
244
|
+
return JSON.parse(el.getValue("counter"));
|
|
245
|
+
});
|
|
246
|
+
expect(result).toBe(1);
|
|
247
|
+
});
|
|
248
|
+
});
|