lightview 1.6.6-b → 1.7.1-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/lightview.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  MIT License
3
3
 
4
- Copyright (c) 2020 Simon Y. Blackwell - Lightview Small, simple, powerful UI creation ...
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
 
@@ -46,13 +50,27 @@ const {observe} = (() => {
46
50
  }
47
51
  Lightview.escapeHTML = escapeHTML;
48
52
 
49
- const isArrowFunction = (f) => {
50
- return typeof(f)==="function" && (f+"").match(/\(*.*\)*\s*=>/g)
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);
51
57
  }
52
58
 
53
- const addListener = (node, eventName, callback) => {
54
- node.addEventListener(eventName, callback); // just used to make code footprint smaller
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;
55
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
+
56
74
  const anchorHandler = async (event) => {
57
75
  event.preventDefault();
58
76
  const target = event.target;
@@ -105,9 +123,7 @@ const {observe} = (() => {
105
123
  if (toType === "string") return value + "";
106
124
  const isfunction = typeof (toType) === "function";
107
125
  if ((toType === "object" || isfunction)) {
108
- if (type === "object" && isfunction) {
109
- if (value instanceof toType) return value;
110
- }
126
+ if (type === "object" && isfunction && value instanceof toType) return value;
111
127
  if (type === "string") {
112
128
  value = value.trim();
113
129
  try {
@@ -127,17 +143,19 @@ const {observe} = (() => {
127
143
  }
128
144
  instance.push(...parsed);
129
145
  } else if (instance instanceof Date) {
130
- instance.setTime(Date.parse(value));
146
+ const time = Date.parse(value);
147
+ if(!isNaN(time)) instance.setTime(time);
148
+ else return;
131
149
  } else {
132
150
  Object.assign(instance, JSON.parse(value));
133
151
  }
134
- if (toType !== Date) {
152
+ /*if (toType !== Date) {
135
153
  Object.defineProperty(instance, "constructor", {
136
154
  configurable: true,
137
155
  writable: true,
138
156
  value: toType.prototype.constructor || toType
139
157
  });
140
- }
158
+ }*/
141
159
  return instance;
142
160
  }
143
161
  return JSON.parse(value);
@@ -159,17 +177,13 @@ const {observe} = (() => {
159
177
  if(property=== "__dependents__") return dependents;
160
178
  if(property=== "__reactorProxyTarget__") return data;
161
179
  if (target instanceof Array) {
162
- if (property === "toJSON") return function toJSON() {
163
- return [...target];
164
- }
165
- if (property === "toString") return function toString() {
166
- return JSON.stringify([...target]);
167
- }
168
- }
169
- if(target instanceof Date) {
170
- return Reflect.get(target,property);
180
+ if (property === "toJSON") return function toJSON() { return [...target]; }
181
+ if (property === "toString") return function toString() { return JSON.stringify([...target]); }
171
182
  }
172
183
  let value = target[property];
184
+ if(target instanceof Date && typeof(value)==="function") {
185
+ value = value.bind(target);
186
+ }
173
187
  const type = typeof (value);
174
188
  if (CURRENTOBSERVER && typeof (property) !== "symbol" && type !== "function") {
175
189
  const observers = dependents[property] ||= new Set();
@@ -193,9 +207,7 @@ const {observe} = (() => {
193
207
  console.warn(`Setting ${property} = ${value} on a Promise in Reactor`);
194
208
  }
195
209
  const type = typeof (value);
196
- if(value && type==="object" && value instanceof Promise) {
197
- value = await value;
198
- }
210
+ if(value && type==="object" && value instanceof Promise) value = await value;
199
211
  if (target[property] !== value) {
200
212
  if (value && type === "object") {
201
213
  value = Reactor(value);
@@ -225,9 +237,7 @@ const {observe} = (() => {
225
237
  const createVarsProxy = (vars, component, constructor) => {
226
238
  return new Proxy(vars, {
227
239
  get(target, property) {
228
- if(target instanceof Date) {
229
- return Reflect.get(target,property);
230
- }
240
+ if(target instanceof Date) return Reflect.get(target,property);
231
241
  let {value,get} = target[property] || {};
232
242
  if(get) return target[property].value = get.call(target[property]);
233
243
  if (typeof (value) === "function") return value.bind(target);
@@ -256,13 +266,9 @@ const {observe} = (() => {
256
266
  event.oldValue = value;
257
267
  target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
258
268
  target.postEvent.value("change", event);
259
- if (event.defaultPrevented) {
260
- target[property].value = value;
261
- } else if(remote && (variable.reactive || remote.put)) {
262
- remote.handleRemote({variable,config:remote.config},true);
263
- } else if(variable.set) {
264
- variable.set(newValue);
265
- }
269
+ if (event.defaultPrevented) target[property].value = value;
270
+ else if(remote && (variable.reactive || remote.put)) remote.handleRemote({variable,config:remote.config},true);
271
+ else if(variable.set) variable.set(newValue);
266
272
  }
267
273
  return true;
268
274
  }
@@ -270,9 +276,6 @@ const {observe} = (() => {
270
276
  throw new TypeError(`Can't assign instance of '${newValue.constructor.name}' to variable '${property}:${type.name.replace("bound ", "")}'`)
271
277
  }
272
278
  throw new TypeError(`Can't assign '${typeof (newValue)} ${newtype === "string" ? '"' + newValue + '"' : newValue}' to variable '${property}:${typetype === "function" ? type.name.replace("bound ", "") : type} ${type.required ? "required" : ""}'`)
273
- },
274
- keys() {
275
- return [...Object.keys(vars)];
276
279
  }
277
280
  });
278
281
  }
@@ -289,10 +292,8 @@ const {observe} = (() => {
289
292
  target.removeAttribute(name);
290
293
  target.dispatchEvent(new CustomEvent("message", {detail: JSON.parse(value)}))
291
294
  }
292
- if (target.observedAttributes && target.observedAttributes.includes(name)) {
293
- if (value !== mutation.oldValue) {
294
- target.setVariableValue(name, value);
295
- }
295
+ if (target.observedAttributes && target.observedAttributes.includes(name) && value !== mutation.oldValue) {
296
+ target.setVariableValue(name, value);
296
297
  }
297
298
  } else if (mutation.type === "childList") {
298
299
  for (const target of mutation.removedNodes) {
@@ -319,45 +320,54 @@ const {observe} = (() => {
319
320
  return nodes;
320
321
  }
321
322
  const getNodes = (root) => {
322
- const nodes = [];
323
+ const nodes = new Set();
323
324
  if (root.shadowRoot) {
324
- nodes.push(root, ...getNodes(root.shadowRoot))
325
+ nodes.add(root);
326
+ getNodes(root.shadowRoot).forEach((node) => nodes.add(node))
325
327
  } else {
326
328
  for (const node of root.childNodes) {
327
329
  if (node.tagName === "SCRIPT") continue;
328
330
  if (node.nodeType === Node.TEXT_NODE && node.nodeValue?.includes("${")) {
329
331
  node.template ||= node.nodeValue;
330
- nodes.push(node);
332
+ nodes.add(node);
331
333
  } else if (node.nodeType === Node.ELEMENT_NODE) {
332
- let skip, pushed;
334
+ let skip;
333
335
  [...node.attributes].forEach((attr) => {
334
336
  if (attr.value.includes("${")) {
335
337
  attr.template ||= attr.value;
336
- pushed = true;
337
- nodes.push(node);
338
+ nodes.add(node);
338
339
  } else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
339
340
  skip = attr.name.includes("l-for:");
340
- pushed = true;
341
- nodes.push(node)
341
+ nodes.add(node)
342
342
  }
343
343
  })
344
- if (!pushed && node.getAttribute("type") === "radio") nodes.push(node);
345
- if (!skip && !node.shadowRoot) nodes.push(...getNodes(node));
344
+ if (node.getAttribute("type") === "radio") nodes.add(node);
345
+ if (!skip && !node.shadowRoot) getNodes(node).forEach((node) => nodes.add(node));
346
346
  }
347
347
  }
348
348
  }
349
349
  return nodes;
350
350
  }
351
351
 
352
- const resolveNodeOrText = (node, component, safe) => {
352
+ const resolveNodeOrText = (node, component, safe,extras=node.extras||{}) => {
353
353
  const type = typeof (node),
354
354
  template = type === "string" ? node.trim() : node.template;
355
355
  if (template) {
356
+ const name = getTemplateVariableName(template);
356
357
  try {
357
- let value = Function("context", "with(context) { return `" + Lightview.sanitizeTemplate(template) + "` }")(component.varsProxy);
358
- value = node.nodeType === Node.TEXT_NODE || !safe ? value : Lightview.escapeHTML(value);
358
+ let value = (name
359
+ ? walk(extras,name.split(".")) || walk(component.varsProxy,name.split(".")) || component[name]
360
+ : Function("context", "extras", "with(context) { with(extras) { return `" + (safe ? template : Lightview.sanitizeTemplate(template)) + "` } }")(component.varsProxy,extras));
361
+ //let value = Function("context", "with(context) { return `" + Lightview.sanitizeTemplate(template) + "` }")(component.varsProxy);
362
+ if(typeof(value)==="function") return value;
363
+ value = (name || node.nodeType === Node.TEXT_NODE || safe ? value : Lightview.escapeHTML(value));
359
364
  if (type === "string") return value==="undefined" ? undefined : value;
360
- node.nodeValue = value == "null" || value == "undefined" ? "" : value;
365
+ if(name) {
366
+ node.nodeValue = value==null ? "" : typeof(value)==="string" ? value : JSON.stringify(value);
367
+ } else {
368
+ node.nodeValue = value == "null" || value == "undefined" ? "" : value;
369
+ }
370
+ return value;
361
371
  } catch (e) {
362
372
  //console.warn(e);
363
373
  if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
@@ -381,25 +391,36 @@ const {observe} = (() => {
381
391
  })
382
392
  }
383
393
  const bound = new WeakSet();
384
- const bindInput = (input, variableName, component, value) => {
394
+ const bindInput = (input, variableName, component, value, object) => {
385
395
  if (bound.has(input)) return;
386
396
  bound.add(input);
387
397
  const inputtype = input.tagName === "SELECT" || input.tagName === "TEXTAREA" ? "text" : input.getAttribute("type"),
388
- type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype);
389
- value ||= input.getAttribute("value");
390
- let variable = component.vars[variableName] || {type};
391
- if (type !== variable.type) {
392
- if (variable.type === "any" || variable.type === "unknown") variable.type = type;
393
- else throw new TypeError(`Attempt to bind <input name="${variableName}" type="${type}"> to variable ${variableName}:${variable.type}`)
394
- }
395
- const existing = component.vars[variableName];
396
- if(!existing || existing.type!==type || !existing.reactive) component.variables({[variableName]: type},{reactive});
397
- if(inputtype!=="radio") {
398
- if(value.includes("${")) input.attributes.value.value = "";
399
- else component.setVariableValue(variableName, coerce(value,type));
398
+ nameparts = variableName.split(".");
399
+ let type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype);
400
+ const variable = walk(component.vars,nameparts) || {type};
401
+ if(type==="any") type = variable.type;
402
+ if(value==null) value = input.getAttribute("value");
403
+ if(object && nameparts.length>1) {
404
+ const [root,...path] = nameparts;
405
+ object = walk(object,path,path.length-2,true);
406
+ const key = path[path.length-1];
407
+ object[key] = coerce(value,type);
408
+ } else {
409
+ if (type !== variable.type) {
410
+ if (variable.type === "any" || variable.type === "unknown") variable.type = type;
411
+ else throw new TypeError(`Attempt to bind <input name="${variableName}" type="${type}"> to variable ${variableName}:${variable.type}`)
412
+ }
413
+ const existing = component.vars[variableName];
414
+ if(!existing || existing.type!==type || !existing.reactive) component.variables({[variableName]: type},{reactive});
415
+ if(inputtype!=="radio") {
416
+ if(typeof(value)==="string" && value.includes("${")) input.attributes.value.value = "";
417
+ else if(value!=="") component.setVariableValue(variableName, coerce(value,type));
418
+ }
400
419
  }
401
420
  let eventname = "change";
402
- if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
421
+ if(input.tagName==="FORM") {
422
+ eventname = "submit"
423
+ } else if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
403
424
  eventname = "input";
404
425
  }
405
426
  const listener = (event) => {
@@ -407,17 +428,21 @@ const {observe} = (() => {
407
428
  let value = input.value;
408
429
  if (inputtype === "checkbox") {
409
430
  value = input.checked
410
- } else if (input.tagName === "SELECT") {
411
- if (input.hasAttribute("multiple")) {
412
- const varvalue = component.varsProxy[variableName];
413
- value = [...input.querySelectorAll("option")]
414
- .filter((option) => option.selected || resolveNodeOrText(option.attributes.value || option.innerText, component) === value) //todo make sync comopat
415
- .map((option) => option.getAttribute("value") || option.innerText);
416
- }
431
+ } else if (input.tagName === "SELECT" && input.hasAttribute("multiple")) {
432
+ value = [...input.querySelectorAll("option")]
433
+ .filter((option) => option.selected || resolveNodeOrText(option.attributes.value || option.innerText, component) === value)
434
+ .map((option) => option.getAttribute("value") || option.innerText);
417
435
  }
418
- component.varsProxy[variableName] = coerce(value, type);
436
+ if(object) {
437
+ const [root,...path] = nameparts;
438
+ object = walk(object,nameparts,path.length-2,true);
439
+ } else {
440
+ object = walk(component.varsProxy,nameparts,nameparts.length-2,true);
441
+ }
442
+ const key = nameparts[nameparts.length-1];
443
+ object[key] = coerce(value,type);
419
444
  };
420
- addListener(input, eventname, listener);
445
+ addListener(input, eventname, listener,component);
421
446
  }
422
447
  const tryParse = (value) => {
423
448
  try {
@@ -473,7 +498,6 @@ const {observe} = (() => {
473
498
  }
474
499
  }
475
500
  variable.set(variable.value);
476
- //component.changeListener.targets.add(name);
477
501
  }
478
502
  }
479
503
  }
@@ -521,24 +545,19 @@ const {observe} = (() => {
521
545
  static get instances() {
522
546
  return instances;
523
547
  }
524
-
525
548
  constructor() {
526
549
  super();
527
550
  instances.add(this);
528
551
  const currentComponent = this,
529
552
  shadow = this.attachShadow({mode: "open"}),
530
553
  eventlisteners = {};
531
- // needs to be local to the instance
532
- /*Object.defineProperty(this,"changeListener",{value:
533
- function({variableName, value}) {
534
- //if (currentComponent.changeListener.targets.has(variableName)) {
535
- value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
536
- if (value == null) removeComponentAttribute(currentComponent, variableName);
537
- else setComponentAttribute(currentComponent, variableName, value);
538
- // }
539
- }});*/
540
554
  this.vars = {
541
555
  ...reserved,
556
+ observe: {
557
+ value: (...args) => observe(...args),
558
+ type: "function",
559
+ constant: true
560
+ },
542
561
  addEventListener: {
543
562
  value: (eventName, listener) => {
544
563
  const listeners = eventlisteners[eventName] ||= new Set();
@@ -564,8 +583,6 @@ const {observe} = (() => {
564
583
  };
565
584
  this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
566
585
  this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
567
- //this.changeListener.targets = new Set();
568
- //this.varsProxy.addEventListener("change", this.changeListener);
569
586
  if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported});
570
587
  ["getElementById", "querySelector", "querySelectorAll"]
571
588
  .forEach((fname) => {
@@ -600,7 +617,10 @@ const {observe} = (() => {
600
617
  for (const script of [...scripts]) {
601
618
  if (script.attributes.src?.value?.includes("/lightview.js")) continue;
602
619
  // remove comments;
603
- const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
620
+ const text = script.innerHTML
621
+ .replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1")
622
+ .replaceAll(/\r?\n/g, "")
623
+ .replaceAll(/'(([^'\\]|\\.)*)'/g,"\\'$1\\'");
604
624
  const currentScript = document.createElement("script");
605
625
  if (script.className !== "lightview" && !((script.attributes.type?.value || "").includes("lightview/"))) {
606
626
  for (const attr of script.attributes) currentScript.setAttribute(attr.name,attr.value);
@@ -626,6 +646,7 @@ const {observe} = (() => {
626
646
  window[scriptid] = () => {
627
647
  delete window[scriptid];
628
648
  currentScript.remove();
649
+ script.remove();
629
650
  resolve();
630
651
  }
631
652
  window[scriptid].ctx = ctx.varsProxy;
@@ -633,141 +654,180 @@ const {observe} = (() => {
633
654
  })
634
655
  }
635
656
  // Promise.all(promises).then(() => {
636
- const nodes = getNodes(ctx);
637
- nodes.forEach((node) => {
638
- if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
639
- observe(() => resolveNodeOrText(node, this));
640
- } else if (node.nodeType === Node.ELEMENT_NODE) {
641
- // resolve the value before all else;
642
- const attr = node.attributes.value,
643
- template = attr?.template;
644
- if (attr && template) {
645
- let value = resolveNodeOrText(attr, this),
646
- eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
647
- const template = attr.template;
648
- if (template) {
649
- if (/\$\{[a-zA-z_]+\}/g.test(template)) {
650
- const name = template.substring(2, template.length - 1);
651
- if(!this.vars[name] || this.vars[name].reactive) {
652
- bindInput(node, name, this, value);
657
+ const nodes = getNodes(ctx),
658
+ processNodes = (nodes,object) => {
659
+ nodes.forEach((node) => {
660
+ if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
661
+ observe(() => resolveNodeOrText(node, this,true,node.extras));
662
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
663
+ // resolve the value before all else;
664
+ const attr = node.attributes.value,
665
+ template = attr?.template;
666
+ if (attr && template) {
667
+ //let value = resolveNodeOrText(attr, this),
668
+ // ;
669
+ const eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx, false,node.extras) : null,
670
+ template = attr.template;
671
+ if (template) {
672
+ const name = getTemplateVariableName(template);
673
+ if (name) {
674
+ const nameparts = name.split(".");
675
+ if(node.extras && node.extras[nameparts[0]]) {
676
+ object = node.extras[nameparts[0]];
677
+ }
678
+ if(!this.vars[nameparts[0]] || this.vars[nameparts[0]].reactive || object) {
679
+ bindInput(node, name, this, resolveNodeOrText(attr, this,false,node.extras), object);
680
+ }
681
+ }
682
+ observe(() => {
683
+ const value = resolveNodeOrText(template, ctx,false,node.extras);
684
+ if(value!==undefined) {
685
+ if (eltype === "checkbox") {
686
+ if (coerce(value, "boolean") === true) {
687
+ node.setAttribute("checked", "");
688
+ node.checked = true;
689
+ } else {
690
+ node.removeAttribute("checked");
691
+ node.checked = false;
692
+ }
693
+ } else if (node.tagName === "SELECT") {
694
+ let values = [value];
695
+ if (node.hasAttribute("multiple")) values = coerce(value, Array);
696
+ [...node.querySelectorAll("option")].forEach(async (option) => {
697
+ if (option.hasAttribute("value")) {
698
+ if (values.includes(resolveNodeOrText(option.attributes.value, ctx,false,node.extras))) {
699
+ option.setAttribute("selected", "");
700
+ option.selected = true;
701
+ }
702
+ } else if (values.includes(resolveNodeOrText(option.innerText, ctx,false,node.extras))) {
703
+ option.setAttribute("selected", "");
704
+ option.selected = true;
705
+ }
706
+ })
707
+ } else if (eltype!=="radio") {
708
+ attr.value = value;
709
+ }
710
+ }
711
+ });
653
712
  }
654
713
  }
655
- observe(() => {
656
- const value = resolveNodeOrText(template, ctx);
657
- if(value!==undefined) {
658
- if (eltype === "checkbox") {
659
- if (coerce(value, "boolean") === true) {
714
+ [...node.attributes].forEach(async (attr) => {
715
+ if (attr.name === "value" && attr.template) return;
716
+ attr.template ||= attr.nodeValue?.includes("${") ? attr.nodeValue : undefined;
717
+ const {name, value} = attr,
718
+ vname = node.attributes.name?.value;
719
+ if (name === "type" && value=="radio" && vname) {
720
+ bindInput(node, vname, this, undefined, object);
721
+ observe(() => {
722
+ const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
723
+ if (node.attributes.value.value == varvalue) {
660
724
  node.setAttribute("checked", "");
661
725
  node.checked = true;
662
726
  } else {
663
727
  node.removeAttribute("checked");
664
728
  node.checked = false;
665
729
  }
666
- } else if (node.tagName === "SELECT") {
667
- let values = [value];
668
- if (node.hasAttribute("multiple")) values = coerce(value, Array);
669
- [...node.querySelectorAll("option")].forEach(async (option) => {
670
- if (option.hasAttribute("value")) {
671
- if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
672
- option.setAttribute("selected", "");
673
- option.selected = true;
674
- }
675
- } else if (values.includes(resolveNodeOrText(option.innerText, ctx))) {
676
- option.setAttribute("selected", "");
677
- option.selected = true;
678
- }
679
- })
680
- } else if (eltype!=="radio") {
681
- attr.value = value;
682
- }
730
+ });
683
731
  }
684
- });
685
- }
686
- }
687
- [...node.attributes].forEach(async (attr) => {
688
- if (attr.name === "value" && attr.template) return;
689
- const {name, value} = attr,
690
- vname = node.attributes.name?.value;
691
- if (name === "type" && value=="radio" && vname) {
692
- bindInput(node, vname, this);
693
- observe(() => {
694
- const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
695
- if (node.attributes.value.value == varvalue) {
696
- node.setAttribute("checked", "");
697
- node.checked = true;
698
- } else {
699
- node.removeAttribute("checked");
700
- node.checked = false;
701
- }
702
- });
703
- }
704
732
 
705
- const [type, ...params] = name.split(":");
706
- if (type === "") { // name is :something
707
- observe(() => {
708
- const value = attr.value;
709
- if (params[0]) {
710
- if (value === "true") node.setAttribute(params[0], "")
711
- else node.removeAttribute(params[0]);
712
- } else {
713
- const elvalue = node.attributes.value ? resolveNodeOrText(node.attributes.value, ctx) : null,
714
- eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
715
- if (eltype === "checkbox" || node.tagName === "OPTION") {
716
- if (elvalue === "true") node.setAttribute("checked", "")
717
- else node.removeAttribute("checked");
718
- }
719
- }
720
- })
721
- } else if (type === "l-on") {
722
- let listener;
723
- observe(() => {
724
- const value = resolveNodeOrText(attr, this);
725
- if (listener) node.removeEventListener(params[0], listener);
726
- listener = this[value] || window[value] || Function(value);
727
- addListener(node, params[0], listener);
728
- })
729
- } else if (type === "l-if") {
730
- observe(() => {
731
- const value = resolveNodeOrText(attr, this);
732
- node.style.setProperty("display", value === "true" ? "revert" : "none");
733
- })
734
- } else if (type === "l-for") {
735
- node.template ||= node.innerHTML;
736
- observe(() => {
737
- const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
738
- value = resolveNodeOrText(attr, this),
739
- coerced = coerce(value, what === "each" ? Array : "object"),
740
- target = what === "each" ? coerced : Object[what](coerced),
741
- html = target.reduce( (html, item, i, target) => {
742
- return html += Function("vars", "context", "with(vars) { with(context) { return `" + node.template + "` }}")(
743
- ctx.varsProxy,
744
- {
745
- [vname]: item,
746
- [index]: i,
747
- [array]: target
748
- })
749
- }, ""),
750
- parsed = parser.parseFromString(html, "text/html");
751
- if (!window.lightviewDebug) {
752
- if (after) {
753
- node.style.setProperty("display", "none")
754
- } else {
755
- while (node.lastElementChild) node.lastElementChild.remove();
756
- }
757
- }
758
- while (parsed.body.firstChild) {
759
- if (after) node.parentElement.insertBefore(parsed.body.firstChild, node);
760
- else node.appendChild(parsed.body.firstChild);
733
+ const [type, ...params] = name.split(":");
734
+ if (type === "") { // name is :something
735
+ observe(() => {
736
+ const value = attr.value;
737
+ if (params[0]) {
738
+ if (value === "true") node.setAttribute(params[0], "")
739
+ else node.removeAttribute(params[0]);
740
+ } else {
741
+ const elvalue = node.attributes.value ? resolveNodeOrText(node.attributes.value, ctx,false,node.extras) : null,
742
+ eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx,false,node.extras) : null;
743
+ if (eltype === "checkbox" || node.tagName === "OPTION") {
744
+ if (elvalue === true) node.setAttribute("checked", "")
745
+ else node.removeAttribute("checked");
746
+ }
747
+ }
748
+ })
749
+ } else if (type === "l-on") {
750
+ let listener;
751
+ observe(() => {
752
+ const value = resolveNodeOrText(attr, this,true,node.extras);
753
+ if (listener) node.removeEventListener(params[0], listener);
754
+ listener = null;
755
+ if(typeof(value)==="function") {
756
+ listener = value;
757
+ } else {
758
+ try {
759
+ listener = Function("return " + value)();
760
+ } catch(e) {
761
+
762
+ }
763
+ }
764
+ if(listener) addListener(node, params[0], listener,ctx);
765
+ })
766
+ } else if (type === "l-if") {
767
+ observe(() => {
768
+ const value = resolveNodeOrText(attr, this,true,node.extras);
769
+ node.style.setProperty("display", value == true ? "revert" : "none");
770
+ })
771
+ } else if (type === "l-for") {
772
+ node.template ||= node.innerHTML;
773
+ node.clone ||= node.cloneNode(true);
774
+ observe(() => {
775
+ const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
776
+ value = resolveNodeOrText(attr, this,false,node.extras),
777
+ coerced = coerce(value, what === "each" ? Array : "object"),
778
+ target = what === "each" ? coerced : Object[what](coerced),
779
+ children = target.reduce((children,item,i,target) => {
780
+ const clone = node.clone.cloneNode(true),
781
+ extras = node.extras = {
782
+ [vname]: item,
783
+ [index]: i,
784
+ [array]: target
785
+ },
786
+ nodes = [...getNodes(clone)].map((node) => {
787
+ node.extras = extras;
788
+ return node;
789
+ });
790
+ processNodes(nodes);
791
+ children.push(...clone.childNodes);
792
+ return children;
793
+ },[]);
794
+ if (!window.lightviewDebug) {
795
+ if (after) {
796
+ node.style.setProperty("display", "none")
797
+ } else {
798
+ while (node.lastElementChild) node.lastElementChild.remove();
799
+ }
800
+ }
801
+ children.forEach((child) => {
802
+ if (after) node.parentElement.insertBefore(child, node);
803
+ else node.appendChild(child);
804
+ })
805
+ })
806
+ } else if(attr.template) {
807
+ observe(() => {
808
+ resolveNodeOrText(attr, this,false,node.extras);
809
+ })
761
810
  }
762
811
  })
763
- } else if(attr.template) {
764
- observe(() => {
765
- resolveNodeOrText(attr, this);
766
- })
767
812
  }
768
813
  })
814
+ };
815
+ nodes.forEach((node) => {
816
+ if(node.tagName==="FORM") {
817
+ const value = node.getAttribute("value"),
818
+ name = getTemplateVariableName(value);
819
+ if(name) {
820
+ const childnodes = [...nodes].filter((childnode) => node!==childnode && node.contains(childnode));
821
+ childnodes.forEach((node) => nodes.delete(node));
822
+ const variable = ctx.vars[name] ||= {type: "object", reactive:true, value: Reactor({})};
823
+ if(variable.type !== "object" || !variable.reactive || !variable.value || typeof(variable.value)!=="object") {
824
+ throw new TypeError(`Can't bind form ${node.getAttribute("id")} to non-object variable ${name}`);
825
+ }
826
+ processNodes(childnodes,variable.value);
827
+ }
769
828
  }
770
829
  })
830
+ processNodes(nodes);
771
831
  shadow.normalize();
772
832
  observer ||= createObserver(ctx, framed);
773
833
  observer.observe(ctx, {attributeOldValue: true, subtree:true, characterData:true, characterDataOldValue:true});
@@ -778,11 +838,9 @@ const {observe} = (() => {
778
838
  }
779
839
  adoptedCallback(callback) {
780
840
  this.dispatchEvent(new Event("adopted"));
781
- //this.vars.postEvent.value("adopted");
782
841
  }
783
842
  disconnectedCallback() {
784
843
  this.dispatchEvent(new Event("disconnected"));
785
- //this.vars.postEvent.value("disconnected");
786
844
  }
787
845
  get observedAttributes() {
788
846
  return CustomElement.observedAttributes;
@@ -793,7 +851,7 @@ const {observe} = (() => {
793
851
 
794
852
  getVariableNames() {
795
853
  return Object.keys(this.vars)
796
- .filter(name => !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name))
854
+ .filter(name => !(name in reserved) && !["self", "addEventListener", "postEvent","observe"].includes(name))
797
855
  }
798
856
 
799
857
  getVariable(name) {