lightview 1.6.5-b → 1.7.2-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 -3
- package/components/chart/chart.html +15 -0
- package/components/chart/example.html +32 -0
- package/components/chart.html +22 -18
- package/components/components.js +93 -0
- package/components/gantt/example.html +27 -0
- package/components/gantt/gantt.html +34 -0
- package/components/gauge/example.html +28 -0
- package/components/gauge/guage.html +19 -0
- package/components/timeline.html +81 -0
- package/examples/counter.html +1 -1
- package/examples/foreign.html +27 -13
- package/examples/forgeinform.html +29 -8
- package/examples/form.html +1 -1
- package/examples/invalid-template-literals.html +1 -4
- package/examples/medium/remote.html +2 -1
- package/examples/message.html +0 -1
- package/examples/object-bound-form.html +32 -0
- package/examples/remote.json +1 -1
- package/examples/timeline.html +21 -0
- package/examples/todo.html +38 -0
- package/examples/types.html +3 -2
- package/lightview.js +376 -243
- package/package.json +1 -1
- package/test/basic.html +8 -4
- package/test/extended.html +10 -7
- package/test/extended.test.mjs +182 -4
- package/types.js +98 -52
package/lightview.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
MIT License
|
|
3
3
|
|
|
4
|
-
Copyright (c)
|
|
4
|
+
Copyright (c) 2022 AnyWhichWay, LLC - Lightview Small, simple, powerful UI creation ...
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -23,6 +23,10 @@ SOFTWARE.
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
// <script src="https://000686818.codepen.website/lightview.js?as=x-body"></script>
|
|
26
|
+
/*
|
|
27
|
+
self.variables({name:"string"})
|
|
28
|
+
imported(x) => exported(x) => reactive(x) => remote(x,{path:".
|
|
29
|
+
*/
|
|
26
30
|
|
|
27
31
|
const Lightview = {};
|
|
28
32
|
|
|
@@ -40,17 +44,33 @@ const {observe} = (() => {
|
|
|
40
44
|
Lightview.sanitizeTemplate = templateSanitizer;
|
|
41
45
|
|
|
42
46
|
const escaper = document.createElement('textarea');
|
|
43
|
-
|
|
44
47
|
function escapeHTML(html) {
|
|
45
48
|
escaper.textContent = html;
|
|
46
49
|
return escaper.innerHTML;
|
|
47
50
|
}
|
|
48
|
-
|
|
49
51
|
Lightview.escapeHTML = escapeHTML;
|
|
50
52
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
+
const isArrowFunction = (f) => typeof(f)==="function" && (f+"").match(/\(*.*\)*\s*=>/g);
|
|
54
|
+
|
|
55
|
+
const getTemplateVariableName = (template) => {
|
|
56
|
+
if(template && /^\$\{[a-zA-z_.]*\}$/g.test(template)) return template.substring(2, template.length - 1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const walk = (target,path,depth=path.length-1,create) => {
|
|
60
|
+
for(let i=0;i<=depth;i++) {
|
|
61
|
+
target = (target[path[i]]==null && create ? target[path[i]] = (typeof(create)==="function" ? Object.create(create.prototype) : {}) : target[path[i]]);
|
|
62
|
+
if(target===undefined) return;
|
|
63
|
+
}
|
|
64
|
+
return target;
|
|
53
65
|
}
|
|
66
|
+
|
|
67
|
+
const addListener = (node, eventName, callback, self) => {
|
|
68
|
+
node.addEventListener(eventName, (event) => {
|
|
69
|
+
if(self) event.self = self;
|
|
70
|
+
callback(event);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
54
74
|
const anchorHandler = async (event) => {
|
|
55
75
|
event.preventDefault();
|
|
56
76
|
const target = event.target;
|
|
@@ -103,9 +123,7 @@ const {observe} = (() => {
|
|
|
103
123
|
if (toType === "string") return value + "";
|
|
104
124
|
const isfunction = typeof (toType) === "function";
|
|
105
125
|
if ((toType === "object" || isfunction)) {
|
|
106
|
-
if (type === "object" && isfunction)
|
|
107
|
-
if (value instanceof toType) return value;
|
|
108
|
-
}
|
|
126
|
+
if (type === "object" && isfunction && value instanceof toType) return value;
|
|
109
127
|
if (type === "string") {
|
|
110
128
|
value = value.trim();
|
|
111
129
|
try {
|
|
@@ -157,16 +175,10 @@ const {observe} = (() => {
|
|
|
157
175
|
if(property=== "__dependents__") return dependents;
|
|
158
176
|
if(property=== "__reactorProxyTarget__") return data;
|
|
159
177
|
if (target instanceof Array) {
|
|
160
|
-
if (property === "toJSON") return function toJSON() {
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
if (property === "toString") return function toString() {
|
|
164
|
-
return JSON.stringify([...target]);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if(target instanceof Date) {
|
|
168
|
-
return Reflect.get(target,property);
|
|
178
|
+
if (property === "toJSON") return function toJSON() { return [...target]; }
|
|
179
|
+
if (property === "toString") return function toString() { return JSON.stringify([...target]); }
|
|
169
180
|
}
|
|
181
|
+
if(target instanceof Date) return Reflect.get(target,property);
|
|
170
182
|
let value = target[property];
|
|
171
183
|
const type = typeof (value);
|
|
172
184
|
if (CURRENTOBSERVER && typeof (property) !== "symbol" && type !== "function") {
|
|
@@ -191,9 +203,7 @@ const {observe} = (() => {
|
|
|
191
203
|
console.warn(`Setting ${property} = ${value} on a Promise in Reactor`);
|
|
192
204
|
}
|
|
193
205
|
const type = typeof (value);
|
|
194
|
-
if(value && type==="object" && value instanceof Promise)
|
|
195
|
-
value = await value;
|
|
196
|
-
}
|
|
206
|
+
if(value && type==="object" && value instanceof Promise) value = await value;
|
|
197
207
|
if (target[property] !== value) {
|
|
198
208
|
if (value && type === "object") {
|
|
199
209
|
value = Reactor(value);
|
|
@@ -223,10 +233,9 @@ const {observe} = (() => {
|
|
|
223
233
|
const createVarsProxy = (vars, component, constructor) => {
|
|
224
234
|
return new Proxy(vars, {
|
|
225
235
|
get(target, property) {
|
|
226
|
-
if(target instanceof Date)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
let {value} = target[property] || {};
|
|
236
|
+
if(target instanceof Date) return Reflect.get(target,property);
|
|
237
|
+
let {value,get} = target[property] || {};
|
|
238
|
+
if(get) return target[property].value = get.call(target[property]);
|
|
230
239
|
if (typeof (value) === "function") return value.bind(target);
|
|
231
240
|
return value;
|
|
232
241
|
},
|
|
@@ -239,7 +248,8 @@ const {observe} = (() => {
|
|
|
239
248
|
return true;
|
|
240
249
|
}
|
|
241
250
|
const variable = target[property],
|
|
242
|
-
{
|
|
251
|
+
{value, shared, exported, constant, reactive, remote} = variable;
|
|
252
|
+
let type = variable.type;
|
|
243
253
|
if (constant) throw new TypeError(`${property}:${type} is a constant`);
|
|
244
254
|
if(newValue!=null || type.required) newValue = type.validate ? type.validate(newValue,target[property]) : coerce(newValue,type);
|
|
245
255
|
const newtype = typeof (newValue),
|
|
@@ -252,11 +262,9 @@ const {observe} = (() => {
|
|
|
252
262
|
event.oldValue = value;
|
|
253
263
|
target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
|
|
254
264
|
target.postEvent.value("change", event);
|
|
255
|
-
if (event.defaultPrevented)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
remote.handleRemote({variable,config:remote.config,reactive},true);
|
|
259
|
-
}
|
|
265
|
+
if (event.defaultPrevented) target[property].value = value;
|
|
266
|
+
else if(remote && (variable.reactive || remote.put)) remote.handleRemote({variable,config:remote.config},true);
|
|
267
|
+
else if(variable.set) variable.set(newValue);
|
|
260
268
|
}
|
|
261
269
|
return true;
|
|
262
270
|
}
|
|
@@ -283,10 +291,8 @@ const {observe} = (() => {
|
|
|
283
291
|
target.removeAttribute(name);
|
|
284
292
|
target.dispatchEvent(new CustomEvent("message", {detail: JSON.parse(value)}))
|
|
285
293
|
}
|
|
286
|
-
if (target.observedAttributes && target.observedAttributes.includes(name)) {
|
|
287
|
-
|
|
288
|
-
target.setVariableValue(name, value);
|
|
289
|
-
}
|
|
294
|
+
if (target.observedAttributes && target.observedAttributes.includes(name) && value !== mutation.oldValue) {
|
|
295
|
+
target.setVariableValue(name, value);
|
|
290
296
|
}
|
|
291
297
|
} else if (mutation.type === "childList") {
|
|
292
298
|
for (const target of mutation.removedNodes) {
|
|
@@ -313,45 +319,54 @@ const {observe} = (() => {
|
|
|
313
319
|
return nodes;
|
|
314
320
|
}
|
|
315
321
|
const getNodes = (root) => {
|
|
316
|
-
const nodes =
|
|
322
|
+
const nodes = new Set();
|
|
317
323
|
if (root.shadowRoot) {
|
|
318
|
-
nodes.
|
|
324
|
+
nodes.add(root);
|
|
325
|
+
getNodes(root.shadowRoot).forEach((node) => nodes.add(node))
|
|
319
326
|
} else {
|
|
320
327
|
for (const node of root.childNodes) {
|
|
321
328
|
if (node.tagName === "SCRIPT") continue;
|
|
322
329
|
if (node.nodeType === Node.TEXT_NODE && node.nodeValue?.includes("${")) {
|
|
323
330
|
node.template ||= node.nodeValue;
|
|
324
|
-
nodes.
|
|
331
|
+
nodes.add(node);
|
|
325
332
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
326
|
-
let skip
|
|
333
|
+
let skip;
|
|
327
334
|
[...node.attributes].forEach((attr) => {
|
|
328
335
|
if (attr.value.includes("${")) {
|
|
329
336
|
attr.template ||= attr.value;
|
|
330
|
-
|
|
331
|
-
nodes.push(node);
|
|
337
|
+
nodes.add(node);
|
|
332
338
|
} else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
|
|
333
339
|
skip = attr.name.includes("l-for:");
|
|
334
|
-
|
|
335
|
-
nodes.push(node)
|
|
340
|
+
nodes.add(node)
|
|
336
341
|
}
|
|
337
342
|
})
|
|
338
|
-
if (
|
|
339
|
-
if (!skip && !node.shadowRoot)
|
|
343
|
+
if (node.getAttribute("type") === "radio") nodes.add(node);
|
|
344
|
+
if (!skip && !node.shadowRoot) getNodes(node).forEach((node) => nodes.add(node));
|
|
340
345
|
}
|
|
341
346
|
}
|
|
342
347
|
}
|
|
343
348
|
return nodes;
|
|
344
349
|
}
|
|
345
350
|
|
|
346
|
-
const resolveNodeOrText = (node, component, safe) => {
|
|
351
|
+
const resolveNodeOrText = (node, component, safe,extras=node.extras||{}) => {
|
|
347
352
|
const type = typeof (node),
|
|
348
353
|
template = type === "string" ? node.trim() : node.template;
|
|
349
354
|
if (template) {
|
|
355
|
+
const name = getTemplateVariableName(template);
|
|
350
356
|
try {
|
|
351
|
-
let value =
|
|
352
|
-
|
|
357
|
+
let value = (name
|
|
358
|
+
? component[name] || walk(extras,name.split(".")) || walk(component.varsProxy,name.split("."))
|
|
359
|
+
: Function("context", "extras", "with(context) { with(extras) { return `" + (safe ? template : Lightview.sanitizeTemplate(template)) + "` } }")(component.varsProxy,extras));
|
|
360
|
+
//let value = Function("context", "with(context) { return `" + Lightview.sanitizeTemplate(template) + "` }")(component.varsProxy);
|
|
361
|
+
if(typeof(value)==="function") return value;
|
|
362
|
+
value = (name || node.nodeType === Node.TEXT_NODE || safe ? value : Lightview.escapeHTML(value));
|
|
353
363
|
if (type === "string") return value==="undefined" ? undefined : value;
|
|
354
|
-
|
|
364
|
+
if(name) {
|
|
365
|
+
node.nodeValue = value==null ? "" : typeof(value)==="string" ? value : JSON.stringify(value);
|
|
366
|
+
} else {
|
|
367
|
+
node.nodeValue = value == "null" || value == "undefined" ? "" : value;
|
|
368
|
+
}
|
|
369
|
+
return value;
|
|
355
370
|
} catch (e) {
|
|
356
371
|
//console.warn(e);
|
|
357
372
|
if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
|
|
@@ -375,24 +390,36 @@ const {observe} = (() => {
|
|
|
375
390
|
})
|
|
376
391
|
}
|
|
377
392
|
const bound = new WeakSet();
|
|
378
|
-
const bindInput = (input, variableName, component, value) => {
|
|
393
|
+
const bindInput = (input, variableName, component, value, object) => {
|
|
379
394
|
if (bound.has(input)) return;
|
|
380
395
|
bound.add(input);
|
|
381
396
|
const inputtype = input.tagName === "SELECT" || input.tagName === "TEXTAREA" ? "text" : input.getAttribute("type"),
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
397
|
+
nameparts = variableName.split(".");
|
|
398
|
+
let type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype);
|
|
399
|
+
const variable = walk(component.vars,nameparts) || {type};
|
|
400
|
+
if(type==="any") type = variable.type;
|
|
401
|
+
if(value==null) value = input.getAttribute("value");
|
|
402
|
+
if(object && nameparts.length>1) {
|
|
403
|
+
const [root,...path] = nameparts;
|
|
404
|
+
object = walk(object,path,path.length-2,true);
|
|
405
|
+
const key = path[path.length-1];
|
|
406
|
+
object[key] = coerce(value,type);
|
|
407
|
+
} else {
|
|
408
|
+
if (type !== variable.type) {
|
|
409
|
+
if (variable.type === "any" || variable.type === "unknown") variable.type = type;
|
|
410
|
+
else throw new TypeError(`Attempt to bind <input name="${variableName}" type="${type}"> to variable ${variableName}:${variable.type}`)
|
|
411
|
+
}
|
|
412
|
+
const existing = component.vars[variableName];
|
|
413
|
+
if(!existing || existing.type!==type || !existing.reactive) component.variables({[variableName]: type},{reactive});
|
|
414
|
+
if(inputtype!=="radio") {
|
|
415
|
+
if(typeof(value)==="string" && value.includes("${")) input.attributes.value.value = "";
|
|
416
|
+
else component.setVariableValue(variableName, coerce(value,type));
|
|
417
|
+
}
|
|
393
418
|
}
|
|
394
419
|
let eventname = "change";
|
|
395
|
-
if
|
|
420
|
+
if(input.tagName==="FORM") {
|
|
421
|
+
eventname = "submit"
|
|
422
|
+
} else if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
|
|
396
423
|
eventname = "input";
|
|
397
424
|
}
|
|
398
425
|
const listener = (event) => {
|
|
@@ -400,17 +427,21 @@ const {observe} = (() => {
|
|
|
400
427
|
let value = input.value;
|
|
401
428
|
if (inputtype === "checkbox") {
|
|
402
429
|
value = input.checked
|
|
403
|
-
} else if (input.tagName === "SELECT") {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
430
|
+
} else if (input.tagName === "SELECT" && input.hasAttribute("multiple")) {
|
|
431
|
+
value = [...input.querySelectorAll("option")]
|
|
432
|
+
.filter((option) => option.selected || resolveNodeOrText(option.attributes.value || option.innerText, component) === value)
|
|
433
|
+
.map((option) => option.getAttribute("value") || option.innerText);
|
|
434
|
+
}
|
|
435
|
+
if(object) {
|
|
436
|
+
const [root,...path] = nameparts;
|
|
437
|
+
object = walk(object,nameparts,path.length-2,true);
|
|
438
|
+
} else {
|
|
439
|
+
object = walk(component.varsProxy,nameparts,nameparts.length-2,true);
|
|
410
440
|
}
|
|
411
|
-
|
|
441
|
+
const key = nameparts[nameparts.length-1];
|
|
442
|
+
object[key] = coerce(value,type);
|
|
412
443
|
};
|
|
413
|
-
addListener(input, eventname, listener);
|
|
444
|
+
addListener(input, eventname, listener,component);
|
|
414
445
|
}
|
|
415
446
|
const tryParse = (value) => {
|
|
416
447
|
try {
|
|
@@ -419,14 +450,89 @@ const {observe} = (() => {
|
|
|
419
450
|
return value;
|
|
420
451
|
}
|
|
421
452
|
}
|
|
453
|
+
const observed = () => {
|
|
454
|
+
return {
|
|
455
|
+
init({variable, component}) {
|
|
456
|
+
const name = variable.name;
|
|
457
|
+
variable.value = component.hasAttribute(name) ? coerce(component.getAttribute(name), variable.type) : variable.value;
|
|
458
|
+
variable.observed = true;
|
|
459
|
+
component.observedAttributes.add(variable.name);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const reactive = () => {
|
|
464
|
+
return {
|
|
465
|
+
init({variable, component}) {
|
|
466
|
+
variable.reactive = true;
|
|
467
|
+
component.vars[variable.name] = Reactor(variable);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const shared = () => {
|
|
472
|
+
return {
|
|
473
|
+
init({variable, component}) {
|
|
474
|
+
variable.shared = true;
|
|
475
|
+
/*addEventListener("change", ({variableName, value}) => {
|
|
476
|
+
if (variableName===variable.name && component.vars[variableName]?.shared) component.siblings.forEach((instance) => instance.setVariableValue(variableName, value))
|
|
477
|
+
})*/
|
|
478
|
+
variable.set = function(newValue) {
|
|
479
|
+
if(component.vars[this.name]?.shared) component.siblings.forEach((instance) => instance.setVariableValue(this.name, newValue));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const exported = () => {
|
|
485
|
+
return {
|
|
486
|
+
init({variable, component}) {
|
|
487
|
+
const name = variable.name;
|
|
488
|
+
variable.exported = true;
|
|
489
|
+
variable.set = (newValue) => {
|
|
490
|
+
if(variable.exported) {
|
|
491
|
+
if(newValue==null) {
|
|
492
|
+
removeComponentAttribute(component, name);
|
|
493
|
+
} else {
|
|
494
|
+
newValue = typeof (newValue) === "string" ? newValue : JSON.stringify(newValue);
|
|
495
|
+
setComponentAttribute(component, name, newValue);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
variable.set(variable.value);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const imported = () => {
|
|
504
|
+
return {
|
|
505
|
+
init({variable, component}) {
|
|
506
|
+
const name = variable.name;
|
|
507
|
+
variable.imported = true;
|
|
508
|
+
variable.value = component.hasAttribute(name) ? coerce(component.getAttribute(name), variable.type) : variable.value;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
422
513
|
let reserved = {
|
|
423
|
-
observed: {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
514
|
+
observed: {
|
|
515
|
+
constant: true,
|
|
516
|
+
value: observed
|
|
517
|
+
},
|
|
518
|
+
reactive: {
|
|
519
|
+
constant: true,
|
|
520
|
+
value: reactive
|
|
521
|
+
},
|
|
522
|
+
shared: {
|
|
523
|
+
constant: true,
|
|
524
|
+
value: shared
|
|
525
|
+
},
|
|
526
|
+
exported: {
|
|
527
|
+
constant: true,
|
|
528
|
+
value: exported
|
|
529
|
+
},
|
|
530
|
+
imported: {
|
|
531
|
+
constant: true,
|
|
532
|
+
value: imported
|
|
533
|
+
}
|
|
428
534
|
};
|
|
429
|
-
const createClass = (domElementNode, {observer, framed}) => {
|
|
535
|
+
const createClass = (domElementNode, {observer, framed, href}) => {
|
|
430
536
|
const instances = new Set(),
|
|
431
537
|
dom = domElementNode.tagName === "TEMPLATE"
|
|
432
538
|
? domElementNode.content.cloneNode(true)
|
|
@@ -438,24 +544,19 @@ const {observe} = (() => {
|
|
|
438
544
|
static get instances() {
|
|
439
545
|
return instances;
|
|
440
546
|
}
|
|
441
|
-
|
|
442
547
|
constructor() {
|
|
443
548
|
super();
|
|
444
549
|
instances.add(this);
|
|
445
550
|
const currentComponent = this,
|
|
446
551
|
shadow = this.attachShadow({mode: "open"}),
|
|
447
552
|
eventlisteners = {};
|
|
448
|
-
// needs to be local to the instance
|
|
449
|
-
Object.defineProperty(this,"changeListener",{value:
|
|
450
|
-
function({variableName, value}) {
|
|
451
|
-
if (currentComponent.changeListener.targets.has(variableName)) {
|
|
452
|
-
value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
|
|
453
|
-
if (value == null) removeComponentAttribute(currentComponent, variableName);
|
|
454
|
-
else setComponentAttribute(currentComponent, variableName, value);
|
|
455
|
-
}
|
|
456
|
-
}});
|
|
457
553
|
this.vars = {
|
|
458
554
|
...reserved,
|
|
555
|
+
observe: {
|
|
556
|
+
value: (...args) => observe(...args),
|
|
557
|
+
type: "function",
|
|
558
|
+
constant: true
|
|
559
|
+
},
|
|
459
560
|
addEventListener: {
|
|
460
561
|
value: (eventName, listener) => {
|
|
461
562
|
const listeners = eventlisteners[eventName] ||= new Set();
|
|
@@ -481,9 +582,7 @@ const {observe} = (() => {
|
|
|
481
582
|
};
|
|
482
583
|
this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
|
|
483
584
|
this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
|
|
484
|
-
this.
|
|
485
|
-
this.varsProxy.addEventListener("change", this.changeListener);
|
|
486
|
-
if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported: true});
|
|
585
|
+
if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported});
|
|
487
586
|
["getElementById", "querySelector", "querySelectorAll"]
|
|
488
587
|
.forEach((fname) => {
|
|
489
588
|
Object.defineProperty(this, fname, {
|
|
@@ -514,15 +613,22 @@ const {observe} = (() => {
|
|
|
514
613
|
for (const attr of this.defaultAttributes) this.hasAttribute(attr.name) || this.setAttribute(attr.name, attr.value);
|
|
515
614
|
const scripts = shadow.querySelectorAll("script"),
|
|
516
615
|
promises = [];
|
|
517
|
-
// scriptpromises = [];
|
|
518
616
|
for (const script of [...scripts]) {
|
|
519
617
|
if (script.attributes.src?.value?.includes("/lightview.js")) continue;
|
|
618
|
+
const text = script.innerHTML
|
|
619
|
+
.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1") // remove comments;
|
|
620
|
+
.replaceAll(/\r?\n/g, "") // remove \n
|
|
621
|
+
.replaceAll(/import\s*\((\s*["'][\.\/].*["'])\)/g,`import(new URL($1,"${href ? href : window.location.href}").href)`) // handle relative paths
|
|
622
|
+
.replaceAll(/'(([^'\\]|\\.)*)'/g,"\\'$1\\'"); // handle quotes
|
|
520
623
|
const currentScript = document.createElement("script");
|
|
521
624
|
if (script.className !== "lightview" && !((script.attributes.type?.value || "").includes("lightview/"))) {
|
|
522
625
|
for (const attr of script.attributes) currentScript.setAttribute(attr.name,attr.value);
|
|
626
|
+
currentScript.innerHTML = text;
|
|
523
627
|
shadow.appendChild(currentScript);
|
|
524
628
|
await new Promise((resolve) => {
|
|
629
|
+
const timeout = setTimeout(() => resolve(),500);
|
|
525
630
|
currentScript.onload = () => {
|
|
631
|
+
clearTimeout(timeout);
|
|
526
632
|
currentScript.remove();
|
|
527
633
|
resolve();
|
|
528
634
|
}
|
|
@@ -534,12 +640,12 @@ const {observe} = (() => {
|
|
|
534
640
|
currentScript.setAttribute(attr.name, attr.name === "type" ? attr.value.replace("lightview/", "") : attr.value);
|
|
535
641
|
}
|
|
536
642
|
currentScript.classList.remove("lightview");
|
|
537
|
-
const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
|
|
538
643
|
currentScript.innerHTML = `Object.getPrototypeOf(async function(){}).constructor('if(window["${scriptid}"]?.ctx) { const ctx = window["${scriptid}"].ctx; { with(ctx) { ${text}; } } }')().then(() => window["${scriptid}"]()); `;
|
|
539
644
|
await new Promise((resolve) => {
|
|
540
645
|
window[scriptid] = () => {
|
|
541
646
|
delete window[scriptid];
|
|
542
647
|
currentScript.remove();
|
|
648
|
+
script.remove();
|
|
543
649
|
resolve();
|
|
544
650
|
}
|
|
545
651
|
window[scriptid].ctx = ctx.varsProxy;
|
|
@@ -547,155 +653,196 @@ const {observe} = (() => {
|
|
|
547
653
|
})
|
|
548
654
|
}
|
|
549
655
|
// Promise.all(promises).then(() => {
|
|
550
|
-
const nodes = getNodes(ctx)
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
656
|
+
const nodes = getNodes(ctx),
|
|
657
|
+
processNodes = (nodes,object) => {
|
|
658
|
+
nodes.forEach((node) => {
|
|
659
|
+
if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
|
|
660
|
+
observe(() => resolveNodeOrText(node, this,true,node.extras));
|
|
661
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
662
|
+
// resolve the value before all else;
|
|
663
|
+
const attr = node.attributes.value,
|
|
664
|
+
template = attr?.template;
|
|
665
|
+
if (attr && template) {
|
|
666
|
+
//let value = resolveNodeOrText(attr, this),
|
|
667
|
+
// ;
|
|
668
|
+
const eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx, false,node.extras) : null,
|
|
669
|
+
template = attr.template;
|
|
670
|
+
if (template) {
|
|
671
|
+
const name = getTemplateVariableName(template);
|
|
672
|
+
if (name) {
|
|
673
|
+
const nameparts = name.split(".");
|
|
674
|
+
if(node.extras && node.extras[nameparts[0]]) {
|
|
675
|
+
object = node.extras[nameparts[0]];
|
|
676
|
+
}
|
|
677
|
+
if(!this.vars[nameparts[0]] || this.vars[nameparts[0]].reactive || object) {
|
|
678
|
+
bindInput(node, name, this, resolveNodeOrText(attr, this,false,node.extras), object);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
observe(() => {
|
|
682
|
+
const value = resolveNodeOrText(template, ctx,false,node.extras);
|
|
683
|
+
if(value!==undefined) {
|
|
684
|
+
if (eltype === "checkbox") {
|
|
685
|
+
if (coerce(value, "boolean") === true) {
|
|
686
|
+
node.setAttribute("checked", "");
|
|
687
|
+
node.checked = true;
|
|
688
|
+
} else {
|
|
689
|
+
node.removeAttribute("checked");
|
|
690
|
+
node.checked = false;
|
|
691
|
+
}
|
|
692
|
+
} else if (node.tagName === "SELECT") {
|
|
693
|
+
let values = [value];
|
|
694
|
+
if (node.hasAttribute("multiple")) values = coerce(value, Array);
|
|
695
|
+
[...node.querySelectorAll("option")].forEach(async (option) => {
|
|
696
|
+
if (option.hasAttribute("value")) {
|
|
697
|
+
if (values.includes(resolveNodeOrText(option.attributes.value, ctx,false,node.extras))) {
|
|
698
|
+
option.setAttribute("selected", "");
|
|
699
|
+
option.selected = true;
|
|
700
|
+
}
|
|
701
|
+
} else if (values.includes(resolveNodeOrText(option.innerText, ctx,false,node.extras))) {
|
|
702
|
+
option.setAttribute("selected", "");
|
|
703
|
+
option.selected = true;
|
|
704
|
+
}
|
|
705
|
+
})
|
|
706
|
+
} else if (eltype!=="radio") {
|
|
707
|
+
attr.value = value;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
});
|
|
567
711
|
}
|
|
568
712
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
713
|
+
[...node.attributes].forEach(async (attr) => {
|
|
714
|
+
if (attr.name === "value" && attr.template) return;
|
|
715
|
+
const {name, value} = attr,
|
|
716
|
+
vname = node.attributes.name?.value;
|
|
717
|
+
if (name === "type" && value=="radio" && vname) {
|
|
718
|
+
bindInput(node, vname, this, undefined, object);
|
|
719
|
+
observe(() => {
|
|
720
|
+
const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
|
|
721
|
+
if (node.attributes.value.value == varvalue) {
|
|
574
722
|
node.setAttribute("checked", "");
|
|
575
723
|
node.checked = true;
|
|
576
724
|
} else {
|
|
577
725
|
node.removeAttribute("checked");
|
|
578
726
|
node.checked = false;
|
|
579
727
|
}
|
|
580
|
-
}
|
|
581
|
-
let values = [value];
|
|
582
|
-
if (node.hasAttribute("multiple")) values = coerce(value, Array);
|
|
583
|
-
[...node.querySelectorAll("option")].forEach(async (option) => {
|
|
584
|
-
if (option.hasAttribute("value")) {
|
|
585
|
-
if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
|
|
586
|
-
option.setAttribute("selected", "");
|
|
587
|
-
option.selected = true;
|
|
588
|
-
}
|
|
589
|
-
} else if (values.includes(resolveNodeOrText(option.innerText, ctx))) {
|
|
590
|
-
option.setAttribute("selected", "");
|
|
591
|
-
option.selected = true;
|
|
592
|
-
}
|
|
593
|
-
})
|
|
594
|
-
} else if (eltype!=="radio") {
|
|
595
|
-
attr.value = value;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
[...node.attributes].forEach(async (attr) => {
|
|
602
|
-
if (attr.name === "value" && attr.template) return;
|
|
603
|
-
const {name, value} = attr,
|
|
604
|
-
vname = node.attributes.name?.value;
|
|
605
|
-
if (name === "type" && value=="radio" && vname) {
|
|
606
|
-
bindInput(node, vname, this);
|
|
607
|
-
observe(() => {
|
|
608
|
-
const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
|
|
609
|
-
if (node.attributes.value.value == varvalue) {
|
|
610
|
-
node.setAttribute("checked", "");
|
|
611
|
-
node.checked = true;
|
|
612
|
-
} else {
|
|
613
|
-
node.removeAttribute("checked");
|
|
614
|
-
node.checked = false;
|
|
728
|
+
});
|
|
615
729
|
}
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
730
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
731
|
+
const [type, ...params] = name.split(":");
|
|
732
|
+
if (type === "") { // name is :something
|
|
733
|
+
observe(() => {
|
|
734
|
+
const value = attr.value;
|
|
735
|
+
if (params[0]) {
|
|
736
|
+
if (value === "true") node.setAttribute(params[0], "")
|
|
737
|
+
else node.removeAttribute(params[0]);
|
|
738
|
+
} else {
|
|
739
|
+
const elvalue = node.attributes.value ? resolveNodeOrText(node.attributes.value, ctx,false,node.extras) : null,
|
|
740
|
+
eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx,false,node.extras) : null;
|
|
741
|
+
if (eltype === "checkbox" || node.tagName === "OPTION") {
|
|
742
|
+
if (elvalue === true) node.setAttribute("checked", "")
|
|
743
|
+
else node.removeAttribute("checked");
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
})
|
|
747
|
+
} else if (type === "l-on") {
|
|
748
|
+
let listener;
|
|
749
|
+
observe(() => {
|
|
750
|
+
const value = resolveNodeOrText(attr, this,true,node.extras);
|
|
751
|
+
if (listener) node.removeEventListener(params[0], listener);
|
|
752
|
+
listener = null;
|
|
753
|
+
if(typeof(value)==="function") {
|
|
754
|
+
listener = value;
|
|
755
|
+
} else {
|
|
756
|
+
try {
|
|
757
|
+
listener = Function("return " + value)();
|
|
758
|
+
} catch(e) {
|
|
759
|
+
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if(listener) addListener(node, params[0], listener,ctx);
|
|
763
|
+
})
|
|
764
|
+
} else if (type === "l-if") {
|
|
765
|
+
observe(() => {
|
|
766
|
+
const value = resolveNodeOrText(attr, this,true,node.extras);
|
|
767
|
+
node.style.setProperty("display", value == true ? "revert" : "none");
|
|
768
|
+
})
|
|
769
|
+
} else if (type === "l-for") {
|
|
770
|
+
node.template ||= node.innerHTML;
|
|
771
|
+
node.clone ||= node.cloneNode(true);
|
|
772
|
+
observe(() => {
|
|
773
|
+
const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
|
|
774
|
+
value = resolveNodeOrText(attr, this,false,node.extras),
|
|
775
|
+
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
776
|
+
target = what === "each" ? coerced : Object[what](coerced),
|
|
777
|
+
children = target.reduce((children,item,i,target) => {
|
|
778
|
+
const clone = node.clone.cloneNode(true),
|
|
779
|
+
extras = node.extras = {
|
|
780
|
+
[vname]: item,
|
|
781
|
+
[index]: i,
|
|
782
|
+
[array]: target
|
|
783
|
+
},
|
|
784
|
+
nodes = [...getNodes(clone)].map((node) => {
|
|
785
|
+
node.extras = extras;
|
|
786
|
+
return node;
|
|
787
|
+
});
|
|
788
|
+
processNodes(nodes);
|
|
789
|
+
children.push(...clone.childNodes);
|
|
790
|
+
return children;
|
|
791
|
+
},[]);
|
|
792
|
+
if (!window.lightviewDebug) {
|
|
793
|
+
if (after) {
|
|
794
|
+
node.style.setProperty("display", "none")
|
|
795
|
+
} else {
|
|
796
|
+
while (node.lastElementChild) node.lastElementChild.remove();
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
//const nodes = getNodes(parsed.body);
|
|
800
|
+
children.forEach((child) => {
|
|
801
|
+
//while (parsed.body.firstChild) {
|
|
802
|
+
//const child = parsed.body.firstChild;
|
|
803
|
+
if (after) node.parentElement.insertBefore(child, node);
|
|
804
|
+
else node.appendChild(child);
|
|
805
|
+
})
|
|
806
|
+
//processNodes(nodes);
|
|
807
|
+
})
|
|
808
|
+
} else if(attr.template) {
|
|
809
|
+
observe(() => {
|
|
810
|
+
resolveNodeOrText(attr, this,false,node.extras);
|
|
811
|
+
})
|
|
675
812
|
}
|
|
676
813
|
})
|
|
677
|
-
} else if(attr.template) {
|
|
678
|
-
observe(() => {
|
|
679
|
-
resolveNodeOrText(attr, this);
|
|
680
|
-
})
|
|
681
814
|
}
|
|
682
815
|
})
|
|
816
|
+
};
|
|
817
|
+
nodes.forEach((node) => {
|
|
818
|
+
if(node.tagName==="FORM") {
|
|
819
|
+
const value = node.getAttribute("value"),
|
|
820
|
+
name = getTemplateVariableName(value);
|
|
821
|
+
if(name) {
|
|
822
|
+
const childnodes = [...nodes].filter((childnode) => node!==childnode && node.contains(childnode));
|
|
823
|
+
childnodes.forEach((node) => nodes.delete(node));
|
|
824
|
+
const variable = ctx.vars[name] ||= {type: "object", reactive:true, value: Reactor({})};
|
|
825
|
+
if(variable.type !== "object" || !variable.reactive || !variable.value || typeof(variable.value)!=="object") {
|
|
826
|
+
throw new TypeError(`Can't bind form ${node.getAttribute("id")} to non-object variable ${name}`);
|
|
827
|
+
}
|
|
828
|
+
processNodes(childnodes,variable.value);
|
|
829
|
+
}
|
|
683
830
|
}
|
|
684
831
|
})
|
|
832
|
+
processNodes(nodes);
|
|
685
833
|
shadow.normalize();
|
|
686
834
|
observer ||= createObserver(ctx, framed);
|
|
687
835
|
observer.observe(ctx, {attributeOldValue: true, subtree:true, characterData:true, characterDataOldValue:true});
|
|
836
|
+
if(this.hasAttribute("l-unhide")) this.removeAttribute("hidden");
|
|
688
837
|
//ctx.vars.postEvent.value("connected");
|
|
689
838
|
this.dispatchEvent(new Event("connected"));
|
|
690
839
|
// })
|
|
691
840
|
}
|
|
692
841
|
adoptedCallback(callback) {
|
|
693
842
|
this.dispatchEvent(new Event("adopted"));
|
|
694
|
-
//this.vars.postEvent.value("adopted");
|
|
695
843
|
}
|
|
696
844
|
disconnectedCallback() {
|
|
697
845
|
this.dispatchEvent(new Event("disconnected"));
|
|
698
|
-
//this.vars.postEvent.value("disconnected");
|
|
699
846
|
}
|
|
700
847
|
get observedAttributes() {
|
|
701
848
|
return CustomElement.observedAttributes;
|
|
@@ -706,7 +853,7 @@ const {observe} = (() => {
|
|
|
706
853
|
|
|
707
854
|
getVariableNames() {
|
|
708
855
|
return Object.keys(this.vars)
|
|
709
|
-
.filter(name => !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name))
|
|
856
|
+
.filter(name => !(name in reserved) && !["self", "addEventListener", "postEvent","observe"].includes(name))
|
|
710
857
|
}
|
|
711
858
|
|
|
712
859
|
getVariable(name) {
|
|
@@ -746,11 +893,13 @@ const {observe} = (() => {
|
|
|
746
893
|
return this.vars[variableName]?.value;
|
|
747
894
|
}
|
|
748
895
|
|
|
749
|
-
variables(variables, {
|
|
750
|
-
const
|
|
896
|
+
variables(variables, {remote, constant,set,...rest} = {}) { // options = {observed,reactive,shared,exported,imported}
|
|
897
|
+
const options = {remote, constant,...rest},
|
|
898
|
+
addEventListener = this.varsProxy.addEventListener;
|
|
751
899
|
if (variables !== undefined) {
|
|
752
900
|
Object.entries(variables)
|
|
753
901
|
.forEach(([key, type]) => {
|
|
902
|
+
if(isArrowFunction(type)) type = type();
|
|
754
903
|
const variable = this.vars[key] ||= {name: key, type};
|
|
755
904
|
if(set!==undefined && constant!==undefined) throw new TypeError(`${key} has the constant value ${constant} and can't be set to ${set}`);
|
|
756
905
|
variable.value = set;
|
|
@@ -758,35 +907,19 @@ const {observe} = (() => {
|
|
|
758
907
|
variable.constant = true;
|
|
759
908
|
variable.value = constant;
|
|
760
909
|
}
|
|
761
|
-
if (observed || imported) {
|
|
762
|
-
variable.value = this.hasAttribute(key) ? coerce(this.getAttribute(key), variable.type) : variable.value;
|
|
763
|
-
variable.imported = imported;
|
|
764
|
-
if(variable.observed) {
|
|
765
|
-
variable.observed = observed;
|
|
766
|
-
this.observedAttributes.add(key);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
if (reactive) {
|
|
770
|
-
variable.reactive = true;
|
|
771
|
-
this.vars[key] = Reactor(variable);
|
|
772
|
-
}
|
|
773
|
-
if (shared) {
|
|
774
|
-
variable.shared = true;
|
|
775
|
-
addEventListener("change", ({variableName, value}) => {
|
|
776
|
-
if (this.vars[variableName]?.shared) this.siblings.forEach((instance) => instance.setVariableValue(variableName, value))
|
|
777
|
-
})
|
|
778
|
-
}
|
|
779
|
-
if (exported) {
|
|
780
|
-
variable.exported = true;
|
|
781
|
-
// in case the export goes up to an iframe
|
|
782
|
-
if (variable.value != null) setComponentAttribute(this, key, variable.value);
|
|
783
|
-
this.changeListener.targets.add(key);
|
|
784
|
-
}
|
|
785
910
|
if (remote) {
|
|
786
911
|
if(typeof(remote)==="function") remote = remote(`./${key}`);
|
|
787
912
|
variable.remote = remote;
|
|
788
|
-
remote.handleRemote({variable, config:remote.config,
|
|
913
|
+
remote.handleRemote({variable, config:remote.config,component:this});
|
|
789
914
|
}
|
|
915
|
+
// todo: handle custom functional types, remote should actually be handled this way
|
|
916
|
+
Object.entries(rest).forEach(([type,f]) => {
|
|
917
|
+
const functionalType = variable[type] = typeof(f)==="function" ? f() : f;
|
|
918
|
+
if(functionalType.init) functionalType.init({variable,options,component:this});
|
|
919
|
+
if((rest.get!==undefined || rest.set!==undefined) && constant!==undefined) throw new TypeError(`${key} has the constant value ${constant} and can't have a getter or setter`);
|
|
920
|
+
variable.set != functionalType.set;
|
|
921
|
+
variable.get != functionalType.get;
|
|
922
|
+
});
|
|
790
923
|
if(type.validate && variable.value!==undefined) type.validate(variable.value,variable);
|
|
791
924
|
});
|
|
792
925
|
}
|
|
@@ -799,14 +932,14 @@ const {observe} = (() => {
|
|
|
799
932
|
}
|
|
800
933
|
}
|
|
801
934
|
|
|
802
|
-
const createComponent = (name, node, {framed, observer} = {}) => {
|
|
935
|
+
const createComponent = (name, node, {framed, observer, href} = {}) => {
|
|
803
936
|
let ctor = customElements.get(name);
|
|
804
937
|
if (ctor) {
|
|
805
938
|
if (framed && !ctor.lightviewFramed) ctor.lightviewFramed = true;
|
|
806
939
|
else console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
|
|
807
940
|
return ctor;
|
|
808
941
|
}
|
|
809
|
-
ctor = createClass(node, {observer, framed});
|
|
942
|
+
ctor = createClass(node, {observer, framed, href});
|
|
810
943
|
customElements.define(name, ctor);
|
|
811
944
|
Lightview.customElements.set(name, ctor);
|
|
812
945
|
return ctor;
|
|
@@ -832,7 +965,7 @@ const {observe} = (() => {
|
|
|
832
965
|
await importLink(childlink, observer);
|
|
833
966
|
}
|
|
834
967
|
if (unhide) dom.body.removeAttribute("hidden");
|
|
835
|
-
createComponent(as, dom.body, {observer});
|
|
968
|
+
createComponent(as, dom.body, {observer,href:url.href});
|
|
836
969
|
}
|
|
837
970
|
return {as};
|
|
838
971
|
}
|