lightview 1.3.1-b → 1.4.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 CHANGED
@@ -1,6 +1,6 @@
1
- # lightview v1.3.1b (BETA)
1
+ # lightview v1.4.2b (BETA)
2
2
 
3
- Small, simple, powerful web UI creation ...
3
+ Small, simple, powerful web UI and micro front end creation ...
4
4
 
5
5
  Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 6K (minified/gzipped).
6
6
 
package/directives.html CHANGED
@@ -7,65 +7,57 @@
7
7
 
8
8
  <body>
9
9
 
10
-
11
- <p>
12
- Show: <input type="checkbox" :="${on}" l-bind="on">
10
+ <div style="margin:20px">
11
+ <p>
12
+ Show: <input type="checkbox" value="${on}">
13
13
  <div l-if="${on}">
14
- Show is true
14
+ Now you've done it. You've exposed me.
15
15
  </div>
16
- </p>
17
- <p>
18
-
19
- <input id="red" type="radio" name="myradio" value="red" :="${color}" l-bind="color"> Red
20
- <input id="yellow" type="radio" name="myradio" value="yellow" :="${color}" l-bind="color"> Yellow
21
- <input id="green" type="radio" name="myradio" value="green" :="${color}" l-bind="color"> Green
22
- </p>
23
-
24
- <p>
25
- <select l-bind="color" value="${color}">
26
- <option value="red">Red</option>
27
- <option value="yellow">Yellow</option>
28
- <option value="green">Green</option>
29
- </select>
30
- </p>
31
-
32
-
33
- <p>
34
- How would you like that burger?<br>
35
- <select l-bind="options" value="${options}" multiple>
36
- <option>lettuce</option>
37
- <option>tomato</option>
38
- <option>cheese</option>
39
- </select>
40
- </p>
41
-
42
-
43
-
44
- <ul l-for:each="${children}">
45
- <li>${index}:${element}</li>
46
- </ul>
47
- <ul l-for:values:value:index='{"1":"v1","2":"v2","3":"v3"}'>
48
- <li>${value}:${index}</li>
49
- </ul>
50
- <ul l-for:keys:key='{"name":"joe","age":27}'>
51
- <li>${key}</li>
52
- </ul>
53
- <ul l-for:entries:entry="${children}">
54
- <li>${entry[0]}:${entry[1]}</li>
55
- </ul>
56
-
57
- Variable Values
58
- <p id="variables"></p>
59
-
16
+ </p>
17
+ <p>
18
+
19
+ <p>
20
+ How would you like that burger?<br>
21
+ <select value="${options}" multiple>
22
+ <option>lettuce</option>
23
+ <option>tomato</option>
24
+ <option>cheese</option>
25
+ </select>
26
+ </p>
27
+
28
+
29
+ For (defaults to each)
30
+ <ul l-for:each="${options}">
31
+ <li>${index}:${item}</li>
32
+ </ul>
33
+ For Each
34
+ <ul l-for:each="${options}">
35
+ <li>${index}:${item}</li>
36
+ </ul>
37
+ For Values
38
+ <ul l-for:values="${options}">
39
+ <li>${item}:${index}</li>
40
+ </ul>
41
+ For Keys
42
+ <ul l-for:keys="${options}">
43
+ <li>${item}</li>
44
+ </ul>
45
+ For Entries
46
+ <ul l-for:entries="${options}">
47
+ <li>${item[0]}:${item[1]}</li>
48
+ </ul>
49
+
50
+ Variable Values
51
+ <p id="variables"></p>
52
+ </div>
60
53
  <script type="lightview/module">
61
- self.variables({on:boolean,off:boolean,color:string,children:Array,options:Array},{reactive});
54
+ self.variables({on:boolean,options:Array},{reactive});
62
55
 
63
56
  on = true;
64
- color = "yellow";
65
- children = ["John","Mary","Jane"];
66
- options = ["tomato"];
57
+ options = ["lettuce"];
67
58
 
68
- addEventListener("change",()=> {
59
+ // demo instrumentation
60
+ const variableValues = () => {
69
61
  const el = self.getElementById("variables");
70
62
  while(el.lastElementChild) el.lastElementChild.remove();
71
63
  self.getVariableNames().forEach((name) => {
@@ -73,7 +65,12 @@ addEventListener("change",()=> {
73
65
  line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
74
66
  el.appendChild(line);
75
67
  });
68
+ };
69
+ variableValues();
70
+ addEventListener("change",()=> {
71
+ variableValues()
76
72
  });
73
+
77
74
  </script>
78
75
  </body>
79
76
 
package/lightview.js CHANGED
@@ -218,14 +218,20 @@ const {observe} = (() => {
218
218
  }
219
219
  });
220
220
  }
221
- const createObserver = (domNode) => {
221
+ const createObserver = (domNode,framed) => {
222
222
  const observer = new MutationObserver((mutations) => {
223
223
  mutations.forEach((mutation) => {
224
224
  if (mutation.type === "attributes") {
225
+ if(framed) debugger;
225
226
  const name = mutation.attributeName,
226
- target = mutation.target;
227
+ target = mutation.target,
228
+ value = target.getAttribute(name);
229
+ if(framed && name==="message" && target instanceof IFrameElement) {
230
+ if(value) console.log("message",value);
231
+ target.removeAttribute(name);
232
+ target.dispatchEvent("message",new CustomEvent("message",{detail:JSON.parse(value)}))
233
+ }
227
234
  if (target.observedAttributes && target.observedAttributes.includes(name)) {
228
- const value = target.getAttribute(name);
229
235
  if (value !== mutation.oldValue) {
230
236
  target.setValue(name, value);
231
237
  if (target.attributeChangedCallback) target.attributeChangedCallback(name, value, mutation.oldValue);
@@ -264,14 +270,14 @@ const {observe} = (() => {
264
270
  nodes.push(node);
265
271
  } else if (node.nodeType === Node.ELEMENT_NODE) {
266
272
  let skip;
273
+ if(node.getAttribute("type")==="radio") nodes.push(node);
267
274
  [...node.attributes].forEach((attr) => {
268
275
  if (attr.value.includes("${")) {
269
276
  attr.template ||= attr.value;
270
- if (!nodes.includes(node)) nodes.push(node);
271
- }
272
- if (attr.name.includes(":") || attr.name.startsWith("l-")) {
277
+ nodes.push(node);
278
+ } else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
273
279
  skip = attr.name.includes("l-for:");
274
- if (!nodes.includes(node)) nodes.push(node);
280
+ nodes.push(node)
275
281
  }
276
282
  })
277
283
  if (!skip && !node.shadowRoot) nodes.push(...getNodes(node));
@@ -281,7 +287,7 @@ const {observe} = (() => {
281
287
  return nodes;
282
288
  }
283
289
  const resolveNode = (node, component) => {
284
- if (node.template) {
290
+ if (node?.template) {
285
291
  try {
286
292
  const value = Function("context", "with(context) { return `" + node.template + "` }")(component.varsProxy);
287
293
  node.nodeValue = value === "null" || value === "undefined" ? "" : value;
@@ -289,11 +295,11 @@ const {observe} = (() => {
289
295
  if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
290
296
  }
291
297
  }
292
- return node.nodeValue;
298
+ return node?.nodeValue;
293
299
  }
294
- const render = (template, render) => {
300
+ const render = (hasTemplate, render) => {
295
301
  let observer;
296
- if (template) {
302
+ if (hasTemplate) {
297
303
  if (observer) observer.cancel();
298
304
  observer = observe(render)
299
305
  } else {
@@ -302,7 +308,7 @@ const {observe} = (() => {
302
308
  }
303
309
  const inputTypeToType = (inputType) => {
304
310
  if (!inputType) return "any"
305
- if (["text", "tel", "email", "url", "search", "radio"].includes(inputType)) return "string";
311
+ if (["text", "tel", "email", "url", "search", "radio","color","password"].includes(inputType)) return "string";
306
312
  if (["number", "range"].includes(inputType)) return "number";
307
313
  if (["datetime"].includes(inputType)) return Date;
308
314
  if (["checkbox"].includes(inputType)) return "boolean";
@@ -314,52 +320,42 @@ const {observe} = (() => {
314
320
  addListener(node,"click", anchorHandler);
315
321
  })
316
322
  }
317
- const _bindForms = (node, component) => {
318
- [...node.querySelectorAll("input")].forEach((input) => bindInput(input, component))
319
- }
320
- const bindInput = (input, component) => {
321
- let name = input.getAttribute("name"),
322
- vname = input.getAttribute("l-bind") || name;
323
- name ||= vname;
324
- if (name) {
325
- if (!input.hasAttribute("l-bind")) input.setAttribute("l-bind", vname)
326
- const inputtype = input.tagName === "SELECT" ? "text" : input.getAttribute("type"),
327
- type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
328
- deflt = input.getAttribute("default"),
329
- value = input.getAttribute("value");
330
- let variable = component.vars[vname] || {type};
331
- if (type !== variable.type) {
332
- if (variable.type === "any" || variable.type === "unknown") variable.type = type;
333
- else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${vname}:${variable.type}`)
334
- }
335
- component.variables({[vname]: type});
336
- variable = component.vars[vname];
337
- //if (value || deflt) {
338
- if (inputtype !== "radio") {
339
- if (value && !value.includes("${")) {
340
- variable.value = coerce(value, type);
341
- //input.setAttribute("value", `\${${name}}`);
342
- } else if (deflt && !deflt.includes("${")) {
343
- variable.value = coerce(deflt, type);
344
- //input.setAttribute("default", `\${${name}}`);
323
+ const bindInput = (input, name, component) => {
324
+ const inputtype = input.tagName === "SELECT" ? "text" : input.getAttribute("type"),
325
+ type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
326
+ deflt = input.getAttribute("default"),
327
+ value = input.getAttribute("value");
328
+ let variable = component.vars[name] || {type};
329
+ if (type !== variable.type) {
330
+ if (variable.type === "any" || variable.type === "unknown") variable.type = type;
331
+ else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
332
+ }
333
+ component.variables({[name]: type});
334
+ let eventname = "change";
335
+ if(input.tagName!=="SELECT" && (!inputtype || ["text","number","tel","email","url","search","password"].includes(inputtype))) {
336
+ eventname = "input";
337
+ }
338
+ addListener(input,eventname, (event) => {
339
+ event.stopImmediatePropagation();
340
+ const target = event.target;
341
+ let value = target.value;
342
+ if (inputtype === "checkbox") {
343
+ value = input.checked
344
+ } else if (target.tagName === "SELECT") {
345
+ if (target.hasAttribute("multiple")) {
346
+ value = [...target.querySelectorAll("option")]
347
+ .filter((option) => option.selected || resolveNode(option.attributes.value,component)==value || option.innerText == value)
348
+ .map((option) => option.getAttribute("value") || option.innerText);
345
349
  }
346
350
  }
347
- //}
348
- addListener(input,"change", (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 || option.getAttribute("value") == value || option.innerText == value)
358
- .map((option) => option.getAttribute("value") || option.innerText);
359
- }
360
- }
361
- component.varsProxy[vname] = coerce(value, type);
362
- })
351
+ component.varsProxy[name] = coerce(value, type);
352
+ })
353
+ }
354
+ const tryParse = (value) => {
355
+ try {
356
+ return JSON.parse(value);
357
+ } catch(e) {
358
+ return value;
363
359
  }
364
360
  }
365
361
  let reserved = {
@@ -372,7 +368,7 @@ const {observe} = (() => {
372
368
  exported: {value: true, constant: true},
373
369
  imported: {value: true, constant: true}
374
370
  };
375
- const createClass = (domElementNode, {observer, bindForms, importAnchors}) => {
371
+ const createClass = (domElementNode, {observer, importAnchors, framed}) => {
376
372
  const instances = new Set(),
377
373
  dom = domElementNode.tagName === "TEMPLATE"
378
374
  ? domElementNode.content.cloneNode(true)
@@ -386,7 +382,7 @@ const {observe} = (() => {
386
382
  constructor() {
387
383
  super();
388
384
  instances.add(this);
389
- observer ||= createObserver(this);
385
+ observer ||= createObserver(this,framed);
390
386
  const currentComponent = this,
391
387
  shadow = this.attachShadow({mode: "open"}),
392
388
  eventlisteners = {};
@@ -416,6 +412,7 @@ const {observe} = (() => {
416
412
  };
417
413
  this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
418
414
  this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
415
+ if(framed || CustomElement.lightviewFramed) this.variables({message:Object},{exported:true});
419
416
  ["getElementById", "querySelector", "querySelectorAll"]
420
417
  .forEach((fname) => {
421
418
  Object.defineProperty(this, fname, {
@@ -425,7 +422,6 @@ const {observe} = (() => {
425
422
  })
426
423
  });
427
424
  [...dom.childNodes].forEach((child) => shadow.appendChild(child.cloneNode(true)));
428
- if (bindForms) _bindForms(shadow, this);
429
425
  if (importAnchors) _importAnchors(shadow, this);
430
426
  }
431
427
 
@@ -459,9 +455,7 @@ const {observe} = (() => {
459
455
  const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
460
456
  currentScript.innerHTML = `Function('if(window["${scriptid}"]?.ctx) { with(window["${scriptid}"].ctx) { ${text}; } window["${scriptid}"](); }')(); `;
461
457
  let resolver;
462
- promises.push(new Promise((resolve) => {
463
- resolver = resolve;
464
- }));
458
+ promises.push(new Promise((resolve) => resolver = resolve));
465
459
  window[scriptid] = () => {
466
460
  delete window[scriptid];
467
461
  currentScript.remove();
@@ -471,49 +465,93 @@ const {observe} = (() => {
471
465
  ctx.appendChild(currentScript);
472
466
  }
473
467
  Promise.all(promises).then(() => {
474
- const inputs = [...ctx.shadowRoot.querySelectorAll("input[l-bind]"), ...ctx.shadowRoot.querySelectorAll("select[l-bind]")];
475
- inputs.forEach((input) => {
476
- bindInput(input, ctx);
477
- })
478
468
  const nodes = getNodes(ctx);
479
469
  nodes.forEach((node) => {
480
470
  if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
481
471
  render(!!node.template, () => resolveNode(node, this))
482
472
  } else if (node.nodeType === Node.ELEMENT_NODE) {
473
+ // resolve the value before all else;
474
+ const attr = node.attributes.value;
475
+ let name;
476
+ if(attr && attr.template) {
477
+ render(!!attr.template,() => {
478
+ const value = resolveNode(attr, this),
479
+ eltype = resolveNode(node.attributes.type,ctx);
480
+ if(eltype==="checkbox") {
481
+ if(coerce(value,"boolean")===true) {
482
+ node.setAttribute("checked","");
483
+ node.checked = true;
484
+ } else {
485
+ node.removeAttribute("checked");
486
+ node.checked = false;
487
+ }
488
+ const vname = resolveNode(node.attributes.name,ctx);
489
+ if(vname) ctx.setValue(vname,node.checked,{coerceTo:"boolean"});
490
+ }
491
+ if(node.tagName==="SELECT") {
492
+ let values = [value];
493
+ if(node.hasAttribute("multiple")) values = coerce(value,Array);
494
+ [...node.querySelectorAll("option")].forEach((option) => {
495
+ if(option.hasAttribute("value")) {
496
+ if (values.includes(resolveNode(option.attributes.value, ctx))) {
497
+ option.setAttribute("selected", "");
498
+ option.selected = true;
499
+ }
500
+ } else if(option.innerText.trim()===value) {
501
+ option.setAttribute("selected","");
502
+ option.selected = true;
503
+ }
504
+ })
505
+ }
506
+ });
507
+ let name;
508
+ for(const vname of this.getVariableNames()) {
509
+ if("${" + vname + "}" === attr.template) {
510
+ name = vname;
511
+ break;
512
+ }
513
+ }
514
+ if(name) bindInput(node,name,ctx);
515
+ }
483
516
  [...node.attributes].forEach((attr) => {
484
- const {name, value} = attr,
485
- [type, ...params] = name.split(":");
486
- if (type === "" || type=="checked" || node.tagName === "SELECT") { // name is :something
517
+ if(attr.name==="value") return;
518
+ const {name, value} = attr;
519
+ if(name==="type") {
520
+ if (value === "radio") {
521
+ const name = resolveNode(node.attributes.name, ctx);
522
+ for (const vname of this.getVariableNames()) {
523
+ if (vname === name) {
524
+ render(true, () => {
525
+ const name = resolveNode(node.attributes.name, ctx),
526
+ varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
527
+ if (varvalue == resolveNode(node.attributes.value,ctx)) {
528
+ node.setAttribute("checked", "");
529
+ node.checked = true;
530
+ } else {
531
+ node.removeAttribute("checked");
532
+ node.checked = false;
533
+ }
534
+ });
535
+ bindInput(node, name, ctx);
536
+ break;
537
+ }
538
+ }
539
+ }
540
+ }
541
+
542
+ const [type, ...params] = name.split(":");
543
+ if (type === "") { // name is :something
487
544
  render(!!attr.template, () => {
488
- const attrtype = node.getAttribute("type"),
489
- value = resolveNode(attr, this),
490
- elvalue = node.getAttribute("value"),
491
- elname = node.getAttribute("name");
545
+ const value = attr.value,
546
+ elvalue = resolveNode(node.attributes.value,ctx),
547
+ eltype = resolveNode(node.attributes.type,ctx),
548
+ elname = resolveNode(node.attributes.name,ctx);
492
549
  if (params[0]) {
493
550
  if (value === "true") node.setAttribute(params[0], "")
494
551
  else node.removeAttribute(params[0]);
495
- } else if (attrtype === "checkbox" || node.tagName === "OPTION") {
496
- if (value === "true") {
497
- node.setAttribute("checked", "");
498
- } else {
499
- node.removeAttribute("checked");
500
- }
501
- } else if (attrtype === "radio") {
502
- if (elvalue === value) {
503
- node.setAttribute("checked", "");
504
- }
505
- } else if (name === "value" && node.tagName === "SELECT") {
506
- node.setAttribute("value", value);
507
- const values = value[0] === "[" ? JSON.parse(value) : value.split(","); // handle multiselect
508
- [...node.querySelectorAll("option")].forEach((option) => {
509
- if (option.hasAttribute("value")) {
510
- if (values.includes(option.getAttribute("value"))) {
511
- option.setAttribute("selected", true);
512
- }
513
- } else if (values.includes(option.innerText)) {
514
- option.setAttribute("selected", true);
515
- }
516
- })
552
+ } else if (eltype=== "checkbox" || node.tagName === "OPTION") {
553
+ if (value === "true") node.setAttribute("checked", "")
554
+ else node.removeAttribute("checked");
517
555
  }
518
556
  })
519
557
  } else if (type === "l-on") {
@@ -532,7 +570,7 @@ const {observe} = (() => {
532
570
  } else if (type === "l-for") {
533
571
  node.template ||= node.innerHTML;
534
572
  render(!!attr.template, () => {
535
- const [what = "each", vname = "element", index = "index", array = "array", after = false] = params,
573
+ const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
536
574
  value = resolveNode(attr, this),
537
575
  coerced = coerce(value, what === "each" ? Array : "object"),
538
576
  target = what === "each" ? coerced : Object[what](coerced),
@@ -652,9 +690,7 @@ const {observe} = (() => {
652
690
  variable.shared = true;
653
691
  addEventListener("change", ({variableName, value}) => {
654
692
  if (this.vars[variableName]?.shared) {
655
- this.siblings.forEach((instance) => {
656
- instance.setValue(variableName, value);
657
- })
693
+ this.siblings.forEach((instance) => instance.setValue(variableName, value))
658
694
  }
659
695
  })
660
696
  }
@@ -669,47 +705,6 @@ const {observe} = (() => {
669
705
  })
670
706
  }
671
707
  });
672
- addEventListener("change", ({variableName, value}) => {
673
- [...this.shadowRoot.querySelectorAll(`input[l-bind=${variableName}]`),
674
- ...this.shadowRoot.querySelectorAll(`select[l-bind=${variableName}]`)]
675
- .forEach((input) => {
676
- const eltype = input.getAttribute("type");
677
- if (eltype === "checkbox") { // at el option selected
678
- if(!!value) {
679
- input.setAttribute("checked", "");
680
- } else {
681
- input.removeAttribute("checked");
682
- }
683
- input.checked = !!value;
684
- } else if (eltype === "radio") {
685
- if (input.getAttribute("value") === value) {
686
- input.setAttribute("checked", "");
687
- input.checked = true;
688
- }
689
- } else if (input.tagName === "SELECT") {
690
- const values = value && typeof (value) === "object" && value instanceof Array ? value : [value];
691
- [...input.querySelectorAll("option")].forEach((option) => {
692
- if (values.includes(option.getAttribute("value") || option.innerText)) {
693
- option.setAttribute("selected", "");
694
- option.selected = true;
695
- }
696
- })
697
- } else if (!eltype || eltype === "text") {
698
- value = typeof (value) === "string" || value == null ? value : JSON.stringify(value);
699
- const oldvalue = input.getAttribute("value") || "";
700
- if (oldvalue !== value) {
701
- if (value == null) input.removeAttribute("value");
702
- else input.setAttribute("value", value);
703
- try {
704
- input.setSelectionRange(0, Math.max(oldvalue.length, value ? value.length : 0)); // shadowDom sometimes fails to rerender unless this is done;
705
- input.setRangeText(value || "", 0, Math.max(oldvalue.length, value ? value.length : 0));
706
- } catch (e) {
707
-
708
- }
709
- }
710
- }
711
- })
712
- })
713
708
  }
714
709
  return Object.entries(this.vars)
715
710
  .reduce((result, [key, variable]) => {
@@ -737,13 +732,17 @@ const {observe} = (() => {
737
732
  }
738
733
  }
739
734
  }
740
- const createComponent = (name, node, {observer, bindForms, importAnchors} = {}) => {
735
+ const createComponent = (name, node, {observer, importAnchors,framed} = {}) => {
741
736
  let ctor = customElements.get(name);
742
737
  if (ctor) {
743
- console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
738
+ if(framed && !ctor.lightviewFramed) {
739
+ ctor.lightviewFramed = true;
740
+ } else {
741
+ console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
742
+ }
744
743
  return ctor;
745
744
  }
746
- ctor = createClass(node, {observer, bindForms, importAnchors});
745
+ ctor = createClass(node, {observer, importAnchors,framed});
747
746
  customElements.define(name, ctor);
748
747
  return ctor;
749
748
  }
@@ -759,10 +758,9 @@ const {observe} = (() => {
759
758
  const html = await (await fetch(url.href)).text(),
760
759
  dom = parser.parseFromString(html, "text/html"),
761
760
  importAnchors = !!dom.head.querySelector('meta[name="l-importAnchors"]'),
762
- bindForms = !!dom.head.querySelector('meta[name="l-bindForms"]'),
763
761
  unhide = !!dom.head.querySelector('meta[name="l-unhide"]');
764
762
  if (unhide) dom.body.removeAttribute("hidden");
765
- createComponent(as, dom.body, {observer, importAnchors, bindForms});
763
+ createComponent(as, dom.body, {observer, importAnchors});
766
764
  }
767
765
  return {as};
768
766
  }
@@ -773,9 +771,9 @@ const {observe} = (() => {
773
771
  }
774
772
  }
775
773
 
776
- const bodyAsComponent = ({as = "x-body", unhide, importAnchors, bindForms} = {}) => {
774
+ const bodyAsComponent = ({as = "x-body", unhide, importAnchors,framed} = {}) => {
777
775
  const parent = document.body.parentElement;
778
- createComponent(as, document.body, {importAnchors, bindForms});
776
+ createComponent(as, document.body, {importAnchors,framed});
779
777
  const component = document.createElement(as);
780
778
  parent.replaceChild(component, document.body);
781
779
  Object.defineProperty(document, "body", {
@@ -817,16 +815,16 @@ const {observe} = (() => {
817
815
 
818
816
  const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
819
817
  let domContentLoadedEvent;
820
- addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
818
+ if(!domContentLoadedEvent) addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
819
+ let OBSERVER;
821
820
  const loader = async (whenFramed) => {
822
821
  if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
823
822
  const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
824
- bindForms = !!document.querySelector('meta[name="l-bindForms"]'),
825
823
  unhide = !!document.querySelector('meta[name="l-unhide"]'),
826
824
  isolated = !!document.querySelector('meta[name="l-isolate"]'),
827
825
  enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
828
826
  if (whenFramed) {
829
- whenFramed({unhide, importAnchors, bindForms, isolated, enableFrames});
827
+ whenFramed({unhide, importAnchors, isolated, enableFrames, framed:true});
830
828
  if (!isolated) {
831
829
  postMessage.enabled = true;
832
830
  addListener(window,"message", ({data}) => {
@@ -861,7 +859,7 @@ const {observe} = (() => {
861
859
  postMessage({type: "DOMContentLoaded"})
862
860
  }
863
861
  } else if (url.searchParams.has("as")) {
864
- bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors, bindForms});
862
+ bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors});
865
863
  }
866
864
  if (enableFrames) {
867
865
  postMessage.enabled = true;
@@ -890,7 +888,9 @@ const {observe} = (() => {
890
888
  }
891
889
  if (type === "setAttribute") {
892
890
  const [name, value] = [...argsList];
893
- if (iframe.getAttribute(name) !== value + "") iframe.setAttribute(name, value);
891
+ if (iframe.getAttribute(name) !== value + "") {
892
+ iframe.setAttribute(name, value);
893
+ }
894
894
  return;
895
895
  }
896
896
  if (type === "removeAttribute") {
@@ -900,30 +900,45 @@ const {observe} = (() => {
900
900
  }
901
901
  console.warn("iframe posted a message without providing an id", message);
902
902
  });
903
- const mutationCallback = (mutationsList) => {
904
- const console = document.getElementById("console");
905
- for (const {target, attributeName, oldValue} of mutationsList) {
906
- if (!["height", "width"].includes(attributeName)) {
903
+ if(!OBSERVER) {
904
+ const mutationCallback = (mutationsList) => {
905
+ const console = document.getElementById("console");
906
+ for (const {target, attributeName, oldValue} of mutationsList) {
907
907
  const value = target.getAttribute(attributeName);
908
- if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
909
- else if (value !== oldValue) postMessage({
910
- type: "setAttribute",
911
- argsList: [attributeName, value]
912
- }, iframe)
908
+ if (!["height", "width", "message"].includes(attributeName)) {
909
+ if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
910
+ else if (value !== oldValue) {
911
+ postMessage({
912
+ type: "setAttribute",
913
+ argsList: [attributeName, value]
914
+ }, iframe)
915
+ }
916
+ }
917
+ if(attributeName==="message") {
918
+ if(value) {
919
+ target.removeAttribute("message");
920
+ target.dispatchEvent(new CustomEvent("message",{target,detail:JSON.parse(value)}))
921
+ }
922
+ } else {
923
+ target.dispatchEvent(new CustomEvent("attribute.changed",{target,detail:{attributeName,value,oldValue}}))
924
+ }
913
925
  }
914
- }
915
- };
916
- const observer = new MutationObserver(mutationCallback),
917
- iframe = document.getElementById("myframe");
918
- observer.observe(iframe, {attributes: true, attributeOldValue: true});
926
+ };
927
+ const observer = OBSERVER = new MutationObserver(mutationCallback),
928
+ iframe = document.getElementById("myframe");
929
+ observer.observe(iframe, {attributes: true, attributeOldValue: true});
930
+ }
919
931
  }
920
932
  }
921
933
  const whenFramed = (f, {isolated} = {}) => {
934
+ // loads for framed content
922
935
  addListener(document,"DOMContentLoaded", (event) => loader(f));
923
936
  }
924
937
  Lightview.whenFramed = whenFramed;
925
938
  //Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
926
- if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) { // CodePen mucks with window.parent
939
+ if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
940
+ // loads for unframed content
941
+ // CodePen mucks with window.parent
927
942
  addListener(document,"DOMContentLoaded", () => loader())
928
943
  }
929
944
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lightview",
3
- "version": "1.3.1b",
4
- "description": "Small, simple, powerful web UI creation ... imagine a blend of Svelte, React, Vue, Riot and more.",
3
+ "version": "1.4.2b",
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
7
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -18,7 +18,10 @@
18
18
  "vue",
19
19
  "moon",
20
20
  "hyperapp",
21
- "hyperhtml"
21
+ "hyperhtml",
22
+ "micro front end",
23
+ "custom elements",
24
+ "web components"
22
25
  ],
23
26
  "author": "Simon Y. Blackwell",
24
27
  "license": "MIT",
package/remote.html CHANGED
@@ -9,7 +9,8 @@
9
9
 
10
10
  <body>
11
11
  <p>
12
- The component below is loaded from an alternate domain and running in an iframe.
12
+ The component below is loaded from an alternate domain and running in a child iframe.
13
+ The logging console is below the component in this frame.
13
14
  </p>
14
15
  <iframe id="myframe" src="https://lightview.dev/remoteform.html?id=myframe"></iframe>
15
16
  <div id="console" style="max-height:250px;scroll:auto"></div>
package/remoteform.html CHANGED
@@ -2,45 +2,73 @@
2
2
 
3
3
  <head>
4
4
  <title>Form</title>
5
- <meta name="l-bindForms">
6
5
  <script src="./lightview.js?as=x-body"></script>
7
- <script>Lightview.whenFramed(({as,unhide,importAnchors,bindForms,isolated,enableFrames}) => {
8
- Lightview.bodyAsComponent({as,unhide,importAnchors,bindForms,isolated,enableFrames});
6
+ <script>Lightview.whenFramed(({as,unhide,importAnchors,isolated,enableFrames}) => {
7
+ Lightview.bodyAsComponent({as,unhide,importAnchors,isolated,enableFrames});
9
8
  })</script>
10
9
  </head>
11
10
 
12
11
  <body style="height:fit-content;width:fit-content;display:flex;flex-direction:column;max-height:100%;overflow:auto;">
13
- <p>
14
- <button l-on:click="run">Run</button> <button l-on:click="reset">Reset</button> <button l-on:click="setNull">Set Null</button>
15
- </p>
16
- <form>
17
- <input name="name" type="text" value="Joe"><br>
18
- <input name="age" type="number" value="20"><br>
19
- </form>
20
- <div id="console"></div>
12
+ <div style="margin:20px">
13
+ <p>
14
+ <input type="text" value="${color}">
15
+ <input type="checkbox" value="${checked}">
16
+ <input type="radio" name="color" value="red">
17
+ <input type="radio" name="color" value="yellow">
18
+ <input type="radio" name="color" value="green">
19
+ <select value="${color}">
20
+ <option value="red">red</option>
21
+ <option>yellow</option>
22
+ <option> green </option>
23
+ </select>
24
+ <div>Hamburger options:</div>
25
+ <select value="${hamburger}" multiple>
26
+ <option value="lettuce">lettuce</option>
27
+ <option>tomato</option>
28
+ <option>cheese</option>
29
+ </select>
30
+ </p>
31
+ <p l-if="${checked}">
32
+ Now you've done it. You've exposed me.
33
+ </p>
34
+ <ul l-for="${hamburger}">
35
+ <li>${item}</li>
36
+ </ul>
37
+ <ul l-for:entries="${hamburger}">
38
+ <li>${item[0]}:${item[1]}</li>
39
+ </ul>
40
+ <ul l-for:values="${hamburger}">
41
+ <li>${item}</li>
42
+ </ul>
43
+ <p id="variables">
44
+
45
+ </p>
46
+ </div>
21
47
  <script type="lightview/module">
22
- self.variables({name:string,age:number},{exported,imported});
23
- self.run = () => {
24
- name = "Bill";
25
- age = 30;
26
- action("run");
27
- };
28
- self.reset = () => {
29
- name = "Joe";
30
- age = 20;
31
- const console = self.getElementById("console");
32
- while(console.lastElementChild) console.lastElementChild.remove();
33
- };
34
- self.setNull = () => {
35
- name = null;
36
- age = undefined;
37
- action("setNull");
48
+ self.variables({
49
+ color: string,
50
+ checked: boolean,
51
+ hamburger: Array
52
+ }, {
53
+ reactive
54
+ });
55
+ color = "green";
56
+ checked = true;
57
+ hamburger = ["lettuce"];
58
+ // demo instrumentation
59
+ const variableValues = () => {
60
+ const el = self.getElementById("variables");
61
+ while (el.lastElementChild) el.lastElementChild.remove();
62
+ self.getVariableNames().forEach((name) => {
63
+ const line = document.createElement("div");
64
+ line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
65
+ el.appendChild(line);
66
+ });
38
67
  };
39
- const action = (name) => {
40
- const div = document.createElement("div");
41
- div.innerText = name;
42
- self.getElementById("console").appendChild(div);
43
- }
68
+ variableValues();
69
+ addEventListener("change", () => {
70
+ variableValues()
71
+ });
44
72
  </script>
45
73
  </body>
46
74
 
package/scratch.html ADDED
@@ -0,0 +1,69 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Scratch</title>
6
+ <script src="./lightview.js?as=x-body"></script>
7
+ </head>
8
+ <body>
9
+ <div style="margin:20px;padding:5px;border:1px;border-style:solid;border-color:${color}">
10
+ <p>
11
+ <input type="text" value="${color}">
12
+ <input type="radio" name="color" value="red">
13
+ <input type="radio" name="color" value="yellow">
14
+ <input type="radio" name="color" value="green">
15
+ <select value="${color}">
16
+ <option value="red">red</option>
17
+ <option>yellow</option>
18
+ <option> green</option>
19
+ </select>
20
+ <div>Hamburger options:</div>
21
+ <select value="${hamburger}" multiple>
22
+ <option value="lettuce">lettuce</option>
23
+ <option>tomato</option>
24
+ <option>cheese</option>
25
+ </select>
26
+ </p>
27
+ Expose: <input type="checkbox" value="${checked}">
28
+ <p l-if="${checked}">
29
+ Now you've done it. You've exposed me.
30
+ </p>
31
+ <ul l-for="${hamburger}">
32
+ <li>${item}</li>
33
+ </ul>
34
+ <ul l-for:entries="${hamburger}">
35
+ <li>${item[0]}:${item[1]}</li>
36
+ </ul>
37
+ <ul l-for:values="${hamburger}">
38
+ <li>${item}</li>
39
+ </ul>
40
+ <p id="variables">
41
+
42
+ </p>
43
+ </div>
44
+ <script type="lightview/module">
45
+ self.variables({color:string,checked:boolean,hamburger:Array},{reactive});
46
+
47
+ color = "green";
48
+ checked = true;
49
+ hamburger = ["lettuce"];
50
+
51
+
52
+ // demo instrumentation
53
+ const variableValues = () => {
54
+ const el = self.getElementById("variables");
55
+ while (el.lastElementChild) el.lastElementChild.remove();
56
+ self.getVariableNames().forEach((name) => {
57
+ const line = document.createElement("div");
58
+ line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
59
+ el.appendChild(line);
60
+ });
61
+ };
62
+ variableValues();
63
+ addEventListener("change", () => {
64
+ variableValues()
65
+ });
66
+
67
+ </script>
68
+ </body>
69
+ </html>
package/xor.html CHANGED
@@ -5,12 +5,12 @@
5
5
  <template id="audiostream">
6
6
  <p>${name}</p>
7
7
  <p>
8
- Play: <input name="play" type="checkbox" l-bind="run" checked="${run}">
8
+ Play: <input type="checkbox" value="${run}">
9
9
  </p>
10
10
  <script type="lightview/module">
11
11
  self.variables({
12
12
  run: boolean
13
- });
13
+ },{reactive});
14
14
  self.variables({
15
15
  name: string
16
16
  }, {
@@ -31,9 +31,7 @@
31
31
  <title>Form</title>
32
32
  <script src="./lightview.js"></script>
33
33
  <script>
34
- Lightview.createComponent("x-audiostream", document.getElementById("audiostream"), {
35
- bindForms: true
36
- })
34
+ Lightview.createComponent("x-audiostream", document.getElementById("audiostream"))
37
35
  </script>
38
36
  </head>
39
37