lightview 1.4.0-b → 1.4.4-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 +152 -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();
|
|
@@ -93,9 +93,18 @@ const {observe} = (() => {
|
|
|
93
93
|
if (isfunction) {
|
|
94
94
|
const instance = toType === Date ? new Date() : Object.create(toType.prototype);
|
|
95
95
|
if (instance instanceof Array) {
|
|
96
|
-
|
|
97
|
-
if (!Array.isArray(parsed))
|
|
98
|
-
|
|
96
|
+
let parsed = tryParse(value.startsWith("[") ? value : `[${value}]`);
|
|
97
|
+
if (!Array.isArray(parsed)) {
|
|
98
|
+
if(value.includes(",")) parsed = value.split(",");
|
|
99
|
+
else {
|
|
100
|
+
parsed = tryParse(`["${value}"]`);
|
|
101
|
+
if(!Array.isArray(parsed) || parsed[0]!==value && parsed.length!==1) parsed = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!Array.isArray(parsed)) {
|
|
105
|
+
throw new TypeError(`Expected an Array for parsed data`)
|
|
106
|
+
}
|
|
107
|
+
instance.push(...parsed);
|
|
99
108
|
} else if (instance instanceof Date) {
|
|
100
109
|
instance.setTime(Date.parse(value));
|
|
101
110
|
} else {
|
|
@@ -118,7 +127,7 @@ const {observe} = (() => {
|
|
|
118
127
|
}
|
|
119
128
|
throw new TypeError(`Unable to coerce ${value} to ${toType}`)
|
|
120
129
|
}
|
|
121
|
-
const Reactor = (value) => {
|
|
130
|
+
const Reactor = (value,component) => {
|
|
122
131
|
if (value && typeof (value) === "object") {
|
|
123
132
|
if (value.__isReactor__) return value;
|
|
124
133
|
const childReactors = [],
|
|
@@ -131,7 +140,7 @@ const {observe} = (() => {
|
|
|
131
140
|
return [...target];
|
|
132
141
|
}
|
|
133
142
|
if (property === "toString") return function toString() {
|
|
134
|
-
return JSON.stringify(target);
|
|
143
|
+
return JSON.stringify([...target]);
|
|
135
144
|
}
|
|
136
145
|
}
|
|
137
146
|
let value = target[property];
|
|
@@ -146,7 +155,7 @@ const {observe} = (() => {
|
|
|
146
155
|
return value;
|
|
147
156
|
}
|
|
148
157
|
if (value && type === "object") {
|
|
149
|
-
value = Reactor(value);
|
|
158
|
+
value = Reactor(value,component);
|
|
150
159
|
childReactors.push(value);
|
|
151
160
|
}
|
|
152
161
|
target[property] = value;
|
|
@@ -156,7 +165,7 @@ const {observe} = (() => {
|
|
|
156
165
|
const type = typeof (value);
|
|
157
166
|
if (target[property] !== value) {
|
|
158
167
|
if (value && type === "object") {
|
|
159
|
-
value = Reactor(value);
|
|
168
|
+
value = Reactor(value,component);
|
|
160
169
|
childReactors.push(value);
|
|
161
170
|
}
|
|
162
171
|
target[property] = value;
|
|
@@ -202,7 +211,7 @@ const {observe} = (() => {
|
|
|
202
211
|
if (newValue == null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
|
|
203
212
|
if (value !== newValue) {
|
|
204
213
|
event.oldValue = value;
|
|
205
|
-
target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
|
|
214
|
+
target[property].value = reactive ? Reactor(newValue,component) : newValue; // do first to prevent loops
|
|
206
215
|
target.postEvent.value("change", event);
|
|
207
216
|
if (event.defaultPrevented) target[property].value = value;
|
|
208
217
|
}
|
|
@@ -218,14 +227,20 @@ const {observe} = (() => {
|
|
|
218
227
|
}
|
|
219
228
|
});
|
|
220
229
|
}
|
|
221
|
-
const createObserver = (domNode) => {
|
|
230
|
+
const createObserver = (domNode, framed) => {
|
|
222
231
|
const observer = new MutationObserver((mutations) => {
|
|
223
232
|
mutations.forEach((mutation) => {
|
|
224
233
|
if (mutation.type === "attributes") {
|
|
234
|
+
if (framed) debugger;
|
|
225
235
|
const name = mutation.attributeName,
|
|
226
|
-
target = mutation.target
|
|
236
|
+
target = mutation.target,
|
|
237
|
+
value = target.getAttribute(name);
|
|
238
|
+
if (framed && name === "message" && target instanceof IFrameElement) {
|
|
239
|
+
if (value) console.log("message", value);
|
|
240
|
+
target.removeAttribute(name);
|
|
241
|
+
target.dispatchEvent(new CustomEvent("message", {detail: JSON.parse(value)}))
|
|
242
|
+
}
|
|
227
243
|
if (target.observedAttributes && target.observedAttributes.includes(name)) {
|
|
228
|
-
const value = target.getAttribute(name);
|
|
229
244
|
if (value !== mutation.oldValue) {
|
|
230
245
|
target.setValue(name, value);
|
|
231
246
|
if (target.attributeChangedCallback) target.attributeChangedCallback(name, value, mutation.oldValue);
|
|
@@ -264,7 +279,7 @@ const {observe} = (() => {
|
|
|
264
279
|
nodes.push(node);
|
|
265
280
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
266
281
|
let skip;
|
|
267
|
-
if(node.getAttribute("type")==="radio") nodes.push(node);
|
|
282
|
+
if (node.getAttribute("type") === "radio") nodes.push(node);
|
|
268
283
|
[...node.attributes].forEach((attr) => {
|
|
269
284
|
if (attr.value.includes("${")) {
|
|
270
285
|
attr.template ||= attr.value;
|
|
@@ -302,7 +317,7 @@ const {observe} = (() => {
|
|
|
302
317
|
}
|
|
303
318
|
const inputTypeToType = (inputType) => {
|
|
304
319
|
if (!inputType) return "any"
|
|
305
|
-
if (["text", "tel", "email", "url", "search", "radio"].includes(inputType)) return "string";
|
|
320
|
+
if (["text", "tel", "email", "url", "search", "radio", "color", "password"].includes(inputType)) return "string";
|
|
306
321
|
if (["number", "range"].includes(inputType)) return "number";
|
|
307
322
|
if (["datetime"].includes(inputType)) return Date;
|
|
308
323
|
if (["checkbox"].includes(inputType)) return "boolean";
|
|
@@ -311,40 +326,45 @@ const {observe} = (() => {
|
|
|
311
326
|
const _importAnchors = (node, component) => {
|
|
312
327
|
[...node.querySelectorAll('a[href][target^="#"]')].forEach((node) => {
|
|
313
328
|
node.removeEventListener("click", anchorHandler);
|
|
314
|
-
addListener(node,"click", anchorHandler);
|
|
329
|
+
addListener(node, "click", anchorHandler);
|
|
315
330
|
})
|
|
316
331
|
}
|
|
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
|
-
|
|
332
|
+
const bindInput = (input, name, component,value) => {
|
|
333
|
+
const inputtype = input.tagName === "SELECT" || input.tagName === "TEXTAREA" ? "text" : input.getAttribute("type"),
|
|
334
|
+
type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
|
|
335
|
+
deflt = input.getAttribute("default");
|
|
336
|
+
value ||= input.getAttribute("value");
|
|
337
|
+
let variable = component.vars[name] || {type};
|
|
338
|
+
if (type !== variable.type) {
|
|
339
|
+
if (variable.type === "any" || variable.type === "unknown") variable.type = type;
|
|
340
|
+
else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
|
|
341
|
+
}
|
|
342
|
+
component.variables({[name]: type});
|
|
343
|
+
component.setValue(name,value);
|
|
344
|
+
let eventname = "change";
|
|
345
|
+
if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
|
|
346
|
+
eventname = "input";
|
|
347
|
+
}
|
|
348
|
+
addListener(input, eventname, (event) => {
|
|
349
|
+
event.stopImmediatePropagation();
|
|
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 || resolveNode(option.attributes.value, component) == value || option.innerText == value)
|
|
358
|
+
.map((option) => option.getAttribute("value") || option.innerText);
|
|
340
359
|
}
|
|
341
|
-
|
|
342
|
-
|
|
360
|
+
}
|
|
361
|
+
component.varsProxy[name] = coerce(value, type);
|
|
362
|
+
})
|
|
343
363
|
}
|
|
344
364
|
const tryParse = (value) => {
|
|
345
365
|
try {
|
|
346
366
|
return JSON.parse(value);
|
|
347
|
-
} catch(e) {
|
|
367
|
+
} catch (e) {
|
|
348
368
|
return value;
|
|
349
369
|
}
|
|
350
370
|
}
|
|
@@ -358,7 +378,7 @@ const {observe} = (() => {
|
|
|
358
378
|
exported: {value: true, constant: true},
|
|
359
379
|
imported: {value: true, constant: true}
|
|
360
380
|
};
|
|
361
|
-
const createClass = (domElementNode, {observer,
|
|
381
|
+
const createClass = (domElementNode, {observer, importAnchors, framed}) => {
|
|
362
382
|
const instances = new Set(),
|
|
363
383
|
dom = domElementNode.tagName === "TEMPLATE"
|
|
364
384
|
? domElementNode.content.cloneNode(true)
|
|
@@ -372,7 +392,7 @@ const {observe} = (() => {
|
|
|
372
392
|
constructor() {
|
|
373
393
|
super();
|
|
374
394
|
instances.add(this);
|
|
375
|
-
observer ||= createObserver(this);
|
|
395
|
+
observer ||= createObserver(this, framed);
|
|
376
396
|
const currentComponent = this,
|
|
377
397
|
shadow = this.attachShadow({mode: "open"}),
|
|
378
398
|
eventlisteners = {};
|
|
@@ -402,6 +422,7 @@ const {observe} = (() => {
|
|
|
402
422
|
};
|
|
403
423
|
this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
|
|
404
424
|
this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
|
|
425
|
+
if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported: true});
|
|
405
426
|
["getElementById", "querySelector", "querySelectorAll"]
|
|
406
427
|
.forEach((fname) => {
|
|
407
428
|
Object.defineProperty(this, fname, {
|
|
@@ -462,50 +483,50 @@ const {observe} = (() => {
|
|
|
462
483
|
// resolve the value before all else;
|
|
463
484
|
const attr = node.attributes.value;
|
|
464
485
|
let name;
|
|
465
|
-
if(attr && attr.template) {
|
|
466
|
-
render(!!attr.template,() => {
|
|
486
|
+
if (attr && attr.template) {
|
|
487
|
+
render(!!attr.template, () => {
|
|
467
488
|
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","");
|
|
489
|
+
eltype = resolveNode(node.attributes.type, ctx);
|
|
490
|
+
if (eltype === "checkbox") {
|
|
491
|
+
if (coerce(value, "boolean") === true) {
|
|
492
|
+
node.setAttribute("checked", "");
|
|
472
493
|
node.checked = true;
|
|
473
494
|
} else {
|
|
474
495
|
node.removeAttribute("checked");
|
|
475
496
|
node.checked = false;
|
|
476
497
|
}
|
|
477
|
-
const vname = resolveNode(node.attributes.name,ctx);
|
|
478
|
-
if(vname) ctx.setValue(vname,node.checked,{coerceTo:"boolean"});
|
|
498
|
+
const vname = resolveNode(node.attributes.name, ctx);
|
|
499
|
+
if (vname) ctx.setValue(vname, node.checked, {coerceTo: "boolean"});
|
|
479
500
|
}
|
|
480
|
-
if(node.tagName==="SELECT") {
|
|
501
|
+
if (node.tagName === "SELECT") {
|
|
481
502
|
let values = [value];
|
|
482
|
-
if(node.hasAttribute("multiple")) values = coerce(value,Array);
|
|
503
|
+
if (node.hasAttribute("multiple")) values = coerce(value, Array);
|
|
483
504
|
[...node.querySelectorAll("option")].forEach((option) => {
|
|
484
|
-
if(option.hasAttribute("value")) {
|
|
505
|
+
if (option.hasAttribute("value")) {
|
|
485
506
|
if (values.includes(resolveNode(option.attributes.value, ctx))) {
|
|
486
507
|
option.setAttribute("selected", "");
|
|
487
508
|
option.selected = true;
|
|
488
509
|
}
|
|
489
|
-
} else if(option.innerText.trim()===value) {
|
|
490
|
-
option.setAttribute("selected","");
|
|
510
|
+
} else if (option.innerText.trim() === value) {
|
|
511
|
+
option.setAttribute("selected", "");
|
|
491
512
|
option.selected = true;
|
|
492
513
|
}
|
|
493
514
|
})
|
|
494
515
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
516
|
+
if(node.attributes.value) {
|
|
517
|
+
const valueattr = node.attributes.value,
|
|
518
|
+
template = valueattr.template || valueattr.template.value;
|
|
519
|
+
if(template.startsWith("${") && template.endsWith("}")) {
|
|
520
|
+
const name = template.substring(2,template.length-1);
|
|
521
|
+
if(!name.includes(" ")) bindInput(node,name,this,value);
|
|
522
|
+
}
|
|
501
523
|
}
|
|
502
|
-
}
|
|
503
|
-
if(name) bindInput(node,name,ctx);
|
|
524
|
+
});
|
|
504
525
|
}
|
|
505
526
|
[...node.attributes].forEach((attr) => {
|
|
506
|
-
if(attr.name==="value") return;
|
|
527
|
+
if (attr.name === "value") return;
|
|
507
528
|
const {name, value} = attr;
|
|
508
|
-
if(name==="type") {
|
|
529
|
+
if (name === "type") {
|
|
509
530
|
if (value === "radio") {
|
|
510
531
|
const name = resolveNode(node.attributes.name, ctx);
|
|
511
532
|
for (const vname of this.getVariableNames()) {
|
|
@@ -513,7 +534,7 @@ const {observe} = (() => {
|
|
|
513
534
|
render(true, () => {
|
|
514
535
|
const name = resolveNode(node.attributes.name, ctx),
|
|
515
536
|
varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
|
|
516
|
-
if (varvalue == resolveNode(node.attributes.value,ctx)) {
|
|
537
|
+
if (varvalue == resolveNode(node.attributes.value, ctx)) {
|
|
517
538
|
node.setAttribute("checked", "");
|
|
518
539
|
node.checked = true;
|
|
519
540
|
} else {
|
|
@@ -528,17 +549,17 @@ const {observe} = (() => {
|
|
|
528
549
|
}
|
|
529
550
|
}
|
|
530
551
|
|
|
531
|
-
|
|
552
|
+
const [type, ...params] = name.split(":");
|
|
532
553
|
if (type === "") { // name is :something
|
|
533
554
|
render(!!attr.template, () => {
|
|
534
555
|
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);
|
|
556
|
+
elvalue = resolveNode(node.attributes.value, ctx),
|
|
557
|
+
eltype = resolveNode(node.attributes.type, ctx),
|
|
558
|
+
elname = resolveNode(node.attributes.name, ctx);
|
|
538
559
|
if (params[0]) {
|
|
539
560
|
if (value === "true") node.setAttribute(params[0], "")
|
|
540
561
|
else node.removeAttribute(params[0]);
|
|
541
|
-
} else if (eltype=== "checkbox" || node.tagName === "OPTION") {
|
|
562
|
+
} else if (eltype === "checkbox" || node.tagName === "OPTION") {
|
|
542
563
|
if (value === "true") node.setAttribute("checked", "")
|
|
543
564
|
else node.removeAttribute("checked");
|
|
544
565
|
}
|
|
@@ -549,7 +570,7 @@ const {observe} = (() => {
|
|
|
549
570
|
const value = resolveNode(attr, this);
|
|
550
571
|
if (listener) node.removeEventListener(params[0], listener);
|
|
551
572
|
listener = this[value] || window[value] || Function(value);
|
|
552
|
-
addListener(node,params[0], listener);
|
|
573
|
+
addListener(node, params[0], listener);
|
|
553
574
|
})
|
|
554
575
|
} else if (type === "l-if") {
|
|
555
576
|
render(!!attr.template, () => {
|
|
@@ -623,7 +644,7 @@ const {observe} = (() => {
|
|
|
623
644
|
|
|
624
645
|
getVariableNames() {
|
|
625
646
|
return Object.keys(this.vars).filter((name) => {
|
|
626
|
-
return !(name in reserved) && !["self","addEventListener","postEvent"].includes(name)
|
|
647
|
+
return !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name)
|
|
627
648
|
})
|
|
628
649
|
}
|
|
629
650
|
|
|
@@ -673,7 +694,7 @@ const {observe} = (() => {
|
|
|
673
694
|
}
|
|
674
695
|
if (reactive) {
|
|
675
696
|
variable.reactive = true;
|
|
676
|
-
this.vars[key] = Reactor(variable);
|
|
697
|
+
this.vars[key] = Reactor(variable,this);
|
|
677
698
|
}
|
|
678
699
|
if (shared) {
|
|
679
700
|
variable.shared = true;
|
|
@@ -721,13 +742,17 @@ const {observe} = (() => {
|
|
|
721
742
|
}
|
|
722
743
|
}
|
|
723
744
|
}
|
|
724
|
-
const createComponent = (name, node, {observer, importAnchors} = {}) => {
|
|
745
|
+
const createComponent = (name, node, {observer, importAnchors, framed} = {}) => {
|
|
725
746
|
let ctor = customElements.get(name);
|
|
726
747
|
if (ctor) {
|
|
727
|
-
|
|
748
|
+
if (framed && !ctor.lightviewFramed) {
|
|
749
|
+
ctor.lightviewFramed = true;
|
|
750
|
+
} else {
|
|
751
|
+
console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
|
|
752
|
+
}
|
|
728
753
|
return ctor;
|
|
729
754
|
}
|
|
730
|
-
ctor = createClass(node, {observer, importAnchors});
|
|
755
|
+
ctor = createClass(node, {observer, importAnchors, framed});
|
|
731
756
|
customElements.define(name, ctor);
|
|
732
757
|
return ctor;
|
|
733
758
|
}
|
|
@@ -756,9 +781,9 @@ const {observe} = (() => {
|
|
|
756
781
|
}
|
|
757
782
|
}
|
|
758
783
|
|
|
759
|
-
const bodyAsComponent = ({as = "x-body", unhide, importAnchors} = {}) => {
|
|
784
|
+
const bodyAsComponent = ({as = "x-body", unhide, importAnchors, framed} = {}) => {
|
|
760
785
|
const parent = document.body.parentElement;
|
|
761
|
-
createComponent(as, document.body, {importAnchors});
|
|
786
|
+
createComponent(as, document.body, {importAnchors, framed});
|
|
762
787
|
const component = document.createElement(as);
|
|
763
788
|
parent.replaceChild(component, document.body);
|
|
764
789
|
Object.defineProperty(document, "body", {
|
|
@@ -800,7 +825,8 @@ const {observe} = (() => {
|
|
|
800
825
|
|
|
801
826
|
const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
|
|
802
827
|
let domContentLoadedEvent;
|
|
803
|
-
addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
|
|
828
|
+
if (!domContentLoadedEvent) addListener(window, "DOMContentLoaded", (event) => domContentLoadedEvent = event);
|
|
829
|
+
let OBSERVER;
|
|
804
830
|
const loader = async (whenFramed) => {
|
|
805
831
|
if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
|
|
806
832
|
const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
|
|
@@ -808,10 +834,10 @@ const {observe} = (() => {
|
|
|
808
834
|
isolated = !!document.querySelector('meta[name="l-isolate"]'),
|
|
809
835
|
enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
|
|
810
836
|
if (whenFramed) {
|
|
811
|
-
whenFramed({unhide, importAnchors, isolated, enableFrames});
|
|
837
|
+
whenFramed({unhide, importAnchors, isolated, enableFrames, framed: true});
|
|
812
838
|
if (!isolated) {
|
|
813
839
|
postMessage.enabled = true;
|
|
814
|
-
addListener(window,"message", ({data}) => {
|
|
840
|
+
addListener(window, "message", ({data}) => {
|
|
815
841
|
const {type, argsList} = JSON.parse(data);
|
|
816
842
|
if (type === "framed") {
|
|
817
843
|
const resize = () => {
|
|
@@ -847,7 +873,7 @@ const {observe} = (() => {
|
|
|
847
873
|
}
|
|
848
874
|
if (enableFrames) {
|
|
849
875
|
postMessage.enabled = true;
|
|
850
|
-
addListener(window,"message", (message) => {
|
|
876
|
+
addListener(window, "message", (message) => {
|
|
851
877
|
const {type, iframeId, argsList, href} = JSON.parse(message.data),
|
|
852
878
|
iframe = document.getElementById(iframeId);
|
|
853
879
|
if (iframe) {
|
|
@@ -872,7 +898,9 @@ const {observe} = (() => {
|
|
|
872
898
|
}
|
|
873
899
|
if (type === "setAttribute") {
|
|
874
900
|
const [name, value] = [...argsList];
|
|
875
|
-
if (iframe.getAttribute(name) !== value + "")
|
|
901
|
+
if (iframe.getAttribute(name) !== value + "") {
|
|
902
|
+
iframe.setAttribute(name, value);
|
|
903
|
+
}
|
|
876
904
|
return;
|
|
877
905
|
}
|
|
878
906
|
if (type === "removeAttribute") {
|
|
@@ -882,32 +910,52 @@ const {observe} = (() => {
|
|
|
882
910
|
}
|
|
883
911
|
console.warn("iframe posted a message without providing an id", message);
|
|
884
912
|
});
|
|
885
|
-
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
|
|
913
|
+
if (!OBSERVER) {
|
|
914
|
+
const mutationCallback = (mutationsList) => {
|
|
915
|
+
const console = document.getElementById("console");
|
|
916
|
+
for (const {target, attributeName, oldValue} of mutationsList) {
|
|
889
917
|
const value = target.getAttribute(attributeName);
|
|
890
|
-
if (!
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
918
|
+
if (!["height", "width", "message"].includes(attributeName)) {
|
|
919
|
+
if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
|
|
920
|
+
else if (value !== oldValue) {
|
|
921
|
+
postMessage({
|
|
922
|
+
type: "setAttribute",
|
|
923
|
+
argsList: [attributeName, value]
|
|
924
|
+
}, iframe)
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (attributeName === "message") {
|
|
928
|
+
if (value) {
|
|
929
|
+
target.removeAttribute("message");
|
|
930
|
+
target.dispatchEvent(new CustomEvent("message", {target, detail: JSON.parse(value)}))
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
target.dispatchEvent(new CustomEvent("attribute.changed", {
|
|
934
|
+
target,
|
|
935
|
+
detail: {attributeName, value, oldValue}
|
|
936
|
+
}))
|
|
937
|
+
}
|
|
895
938
|
}
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
iframe
|
|
900
|
-
|
|
939
|
+
};
|
|
940
|
+
const observer = OBSERVER = new MutationObserver(mutationCallback),
|
|
941
|
+
iframe = document.getElementById("myframe");
|
|
942
|
+
observer.observe(iframe, {attributes: true, attributeOldValue: true});
|
|
943
|
+
}
|
|
901
944
|
}
|
|
902
945
|
}
|
|
903
946
|
const whenFramed = (f, {isolated} = {}) => {
|
|
904
|
-
|
|
947
|
+
// loads for framed content
|
|
948
|
+
addListener(document, "DOMContentLoaded", (event) => loader(f));
|
|
905
949
|
}
|
|
906
950
|
Lightview.whenFramed = whenFramed;
|
|
907
951
|
//Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
|
|
908
|
-
if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
|
|
909
|
-
|
|
952
|
+
if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
|
|
953
|
+
// loads for unframed content
|
|
954
|
+
// CodePen mucks with window.parent
|
|
955
|
+
addListener(document, "DOMContentLoaded", () => loader())
|
|
910
956
|
}
|
|
911
957
|
|
|
912
958
|
return {observe}
|
|
913
|
-
})();
|
|
959
|
+
})();
|
|
960
|
+
|
|
961
|
+
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightview",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4b",
|
|
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
|
+
});
|