lightview 1.0.0-b → 1.2.0-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,4 +1,4 @@
1
- # lightview v1.0.0.b (BETA)
1
+ # lightview v1.2.0.b (BETA)
2
2
 
3
3
  Small, simple, powerful web UI creation ...
4
4
 
package/lightview.js CHANGED
@@ -190,7 +190,7 @@ const {observe} = (() => {
190
190
  if (constant) throw new TypeError(`${property}:${type} is a constant`);
191
191
  const newtype = typeof (newValue),
192
192
  typetype = typeof (type);
193
- if (type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
193
+ if (newValue==null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
194
194
  if (value !== newValue) {
195
195
  event.oldValue = value;
196
196
  target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
@@ -276,9 +276,10 @@ const {observe} = (() => {
276
276
  const resolveNode = (node, component) => {
277
277
  if (node.template) {
278
278
  try {
279
- node.nodeValue = Function("context", "with(context) { return `" + node.template + "` }")(component.varsProxy);
279
+ const value = Function("context", "with(context) { return `" + node.template + "` }")(component.varsProxy);
280
+ node.nodeValue = value==="null" || value==="undefined" ? "" : value;
280
281
  } catch (e) {
281
- if (!e.message.includes("defined")) throw e;
282
+ if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
282
283
  }
283
284
  }
284
285
  return node.nodeValue;
@@ -608,11 +609,17 @@ const {observe} = (() => {
608
609
  }
609
610
  if (exported) {
610
611
  variable.exported = true;
612
+ // in case the export goes up to at iframe
613
+ setComponentAttribute(this,key,variable.value);
611
614
  addEventListener("change",({variableName,value}) => {
612
615
  // Array.isArray will not work here, Proxies mess up JSON.stringify for Arrays
613
616
  //value = value && typeof (value) === "object" ? (value instanceof Array ? JSON.stringify([...value]) : JSON.stringify(value)) : value+"";
614
- value = typeof(value)==="string" ? value : JSON.stringify(value);
615
- this.setAttribute(variableName,value);
617
+ value = typeof(value)==="string" || !value ? value : JSON.stringify(value);
618
+ if(value==null) {
619
+ removeComponentAttribute(this,variableName);
620
+ } else {
621
+ setComponentAttribute(this,variableName,value);
622
+ }
616
623
  })
617
624
  }
618
625
  });
@@ -624,13 +631,17 @@ const {observe} = (() => {
624
631
  } else {
625
632
  // Array.isArray will not work here, Proxies mess up JSON.stringify for Arrays
626
633
  //value = value && typeof (value) === "object" ? (value instanceof Array ? JSON.stringify([...value]) : JSON.stringify(value)) : value+"";
627
- value = typeof(value)==="string" ? value : JSON.stringify(value);
634
+ value = typeof(value)==="string" || value==null ? value : JSON.stringify(value);
628
635
  const oldvalue = input.getAttribute("value")||"";
629
636
  if(oldvalue!==value) {
630
- input.setAttribute("value",value);
637
+ if(value==null) {
638
+ input.removeAttribute("value");
639
+ } else {
640
+ input.setAttribute("value",value);
641
+ }
631
642
  try {
632
- input.setSelectionRange(0, Math.max(oldvalue.length,value.length)); // shadowDom sometimes fails to rerender unless this is done;
633
- input.setRangeText(value,0, Math.max(oldvalue.length,value.length));
643
+ input.setSelectionRange(0, Math.max(oldvalue.length,value ? value.length : 0)); // shadowDom sometimes fails to rerender unless this is done;
644
+ input.setRangeText(value||"",0, Math.max(oldvalue.length,value ? value.length : 0));
634
645
  } catch(e) {
635
646
 
636
647
  }
@@ -666,7 +677,14 @@ const {observe} = (() => {
666
677
  }
667
678
  }
668
679
  const createComponent = (name, node, {observer,bindForms,importAnchors}={}) => {
669
- if (!customElements.get(name)) customElements.define(name, createClass(node, {observer,bindForms,importAnchors}));
680
+ let ctor = customElements.get(name);
681
+ if(ctor) {
682
+ console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
683
+ return ctor;
684
+ }
685
+ ctor = createClass(node, {observer,bindForms,importAnchors});
686
+ customElements.define(name, ctor);
687
+ return ctor;
670
688
  }
671
689
  Object.defineProperty(Lightview,"createComponent",{writable:true,configurable:true,value:createComponent})
672
690
  const importLink = async (link, observer) => {
@@ -693,22 +711,124 @@ const {observe} = (() => {
693
711
  }
694
712
  }
695
713
 
696
- const bodyAsComponent = (as, {unhide,importAnchors,bindForms}={}) => {
714
+ const bodyAsComponent = ({as="x-body",unhide,importAnchors,bindForms}={}) => {
697
715
  const parent = document.body.parentElement;
698
716
  createComponent(as, document.body,{importAnchors,bindForms});
699
717
  const component = document.createElement(as);
700
718
  parent.replaceChild(component, document.body);
719
+ Object.defineProperty(document,"body",{enumerable:true,configurable:true,get() { return component; }});
701
720
  if (unhide) component.removeAttribute("hidden");
702
721
  }
722
+ Lightview.bodyAsComponent = bodyAsComponent;
723
+ const postMessage = (data,target=window.parent) => {
724
+ if(postMessage.enabled) {
725
+ if(target instanceof HTMLIFrameElement) {
726
+ data = {...data,href:window.location.href};
727
+ target.contentWindow.postMessage(JSON.stringify(data),"*");
728
+ } else {
729
+ data = {...data,iframeId:document.lightviewId,href:window.location.href};
730
+ target.postMessage(JSON.stringify(data),"*");
731
+ }
732
+ }
733
+ }
734
+ const setComponentAttribute = (node,name,value) => {
735
+ if(node.getAttribute(name)!==value) node.setAttribute(name,value);
736
+ postMessage({type:"setAttribute",argsList:[name,value]});
737
+ }
738
+ const removeComponentAttribute = (node,name,value) => {
739
+ node.removeAttribute(name);
740
+ postMessage({type:"removeAttribute",argsList:[name]});
741
+ }
742
+ const getNodePath = (node,path=[]) => {
743
+ path.unshift(node);
744
+ if(node.parentNode && node.parentNode!==node.parentNode) getNodePath(node.parentNode,path);
745
+ return path;
746
+ }
747
+ const onresize = (node, callback) => {
748
+ const resizeObserver = new ResizeObserver(() => callback() );
749
+ resizeObserver.observe(node);
750
+ };
703
751
 
704
752
  const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
705
- document.addEventListener("DOMContentLoaded", async () => {
706
- if (url.searchParams.has("importLinks")) await importLinks();
707
- const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
708
- bindForms = !!document.querySelector('meta[name="l-bindForms"]'),
709
- unhide = !!document.querySelector('meta[name="l-unhide"]');
710
- if (url.searchParams.has("as")) bodyAsComponent(url.searchParams.get("as"), {unhide,importAnchors,bindForms});
711
- });
753
+ let domContentLoadedEvent;
754
+ window.addEventListener("DOMContentLoaded",(event) => domContentLoadedEvent = event);
755
+ const loader = async (whenFramed) => {
756
+ if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
757
+ const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
758
+ bindForms = !!document.querySelector('meta[name="l-bindForms"]'),
759
+ unhide = !!document.querySelector('meta[name="l-unhide"]'),
760
+ isolated = !!document.querySelector('meta[name="l-isolate"]'),
761
+ enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
762
+ if(whenFramed) {
763
+ whenFramed({unhide,importAnchors,bindForms,isolated,enableFrames});
764
+ if(!isolated) {
765
+ postMessage.enabled = true;
766
+ window.addEventListener("message",({data}) => {
767
+ data = JSON.parse(data);
768
+ if(data.type==="framed") {
769
+ const resize = () => {
770
+ const {width,height} = document.body.getBoundingClientRect();
771
+ postMessage({type:"setAttribute",argsList:["width",width]})
772
+ postMessage({type:"setAttribute",argsList:["height",height+20]});
773
+ }
774
+ resize();
775
+ onresize(document.body,() => {
776
+ resize();
777
+ })
778
+ }
779
+ const event = new CustomEvent(data.type,{detail:data});
780
+
781
+ });
782
+ /* window.addEventListener("resize",() => {
783
+ const {width,height} = document.body.getBoundingClientRect();
784
+ postMessage({type:"setAttribute",argsList:["width",width]})
785
+ postMessage({type:"setAttribute",argsList:["height",height]})
786
+ })*/
787
+ const url = new URL(window.location.href);
788
+ document.lightviewId = url.searchParams.get("id");
789
+ postMessage({type:"DOMContentLoaded"})
790
+ }
791
+ } else if (url.searchParams.has("as")) {
792
+ bodyAsComponent({as:url.searchParams.get("as"),unhide,importAnchors,bindForms});
793
+ }
794
+ if(enableFrames) {
795
+ postMessage.enabled = true;
796
+ window.addEventListener("message",(message) => {
797
+ const {type,iframeId,argsList,href} = JSON.parse(message.data),
798
+ iframe = document.getElementById(iframeId);
799
+ if(iframe) {
800
+ if(type==="DOMContentLoaded") {
801
+ postMessage({type:"framed",href:window.location.href},iframe);
802
+ Object.defineProperty(domContentLoadedEvent,"currentTarget",{enumerable:false,configurable:true,value:iframe});
803
+ domContentLoadedEvent.href = href;
804
+ domContentLoadedEvent.srcElement = iframe;
805
+ domContentLoadedEvent.bubbles = false;
806
+ domContentLoadedEvent.path = getNodePath(iframe);
807
+ Object.defineProperty(domContentLoadedEvent,"timeStamp",{enumerable:false,configurable:true,value:performance.now()})
808
+ iframe.dispatchEvent(domContentLoadedEvent);
809
+ return;
810
+ }
811
+ if(type==="setAttribute") {
812
+ const [name,value] = [...argsList];
813
+ if(iframe.getAttribute(name)!==value+"") iframe.setAttribute(name,value);
814
+ return;
815
+ }
816
+ if(type==="removeAttribute") {
817
+ iframe.removeAttribute(...argsList);
818
+ return;
819
+ }
820
+ }
821
+ console.warn("iframe posted a message without providing an id",message);
822
+ });
823
+ }
824
+ }
825
+ const whenFramed = (f,{isolated}={}) => {
826
+ document.addEventListener("DOMContentLoaded",(event) => loader(f));
827
+ }
828
+ Object.defineProperty(Lightview,"whenFramed",{configurable:true,writable:true,value:whenFramed});
829
+ if(window.location===window.parent.location || !(window.parent instanceof Window)) { // CodePen mucks with window.parent
830
+ document.addEventListener("DOMContentLoaded",() => loader())
831
+ }
712
832
 
713
833
  return {observe}
714
834
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightview",
3
- "version": "1.0.0b",
3
+ "version": "1.2.0b",
4
4
  "description": "Small, simple, powerful web UI creation ...",
5
5
  "main": "lightview.js",
6
6
  "scripts": {
package/remote.html ADDED
@@ -0,0 +1,35 @@
1
+ <!DOCTYPE html>
2
+
3
+ <head>
4
+ <title>Remote</title>
5
+ <link type="module" src="">
6
+ <meta name="l-enableFrames">
7
+ <script src="./lightview.js"></script>
8
+ </head>
9
+
10
+ <body>
11
+ <p>
12
+ The component below is loaded from an alternate domain and running in an iframe.
13
+ </p>
14
+ <iframe id="myframe" src="https://lightview.dev/remoteform.html?id=myframe"></iframe>
15
+ <div id="console" style="max-height:250px;scroll:auto"></div>
16
+ <script>
17
+ const mutationCallback = (mutationsList) => {
18
+ const console = document.getElementById("console");
19
+ for (const {target,attributeName,oldValue} of mutationsList) {
20
+ const line = document.createElement("div"),
21
+ event = {attributeName,oldValue,value:target.getAttribute(attributeName)};
22
+ line.innerText = JSON.stringify(event);
23
+ console.appendChild(line);
24
+ }
25
+ };
26
+ const observer = new MutationObserver(mutationCallback),
27
+ iframe = document.getElementById("myframe");
28
+ observer.observe(iframe, { attributes:true, attributeOldValue: true });
29
+ iframe.addEventListener("DOMContentLoaded",(event) => {
30
+ console.log(event);
31
+ });
32
+ </script>
33
+ </body>
34
+
35
+ </html>
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+
3
+ <head>
4
+ <title>Form</title>
5
+ <meta name="l-bindForms">
6
+ <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});
9
+ })</script>
10
+ </head>
11
+
12
+ <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>
21
+ <script type="lightview/module">
22
+ self.variables({name:string,age:number},{exported});
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");
38
+ };
39
+ const action = (name) => {
40
+ const div = document.createElement("div");
41
+ div.innerText = name;
42
+ self.getElementById("console").appendChild(div);
43
+ }
44
+ </script>
45
+ </body>
46
+
47
+ </html>