lightview 1.5.1-b → 1.6.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.
@@ -1,5 +1,5 @@
1
1
  <head>
2
- <title>Counter</title>
2
+ <title>Lightview:Examples:Counter</title>
3
3
  <script src="../lightview.js?as=x-body"></script>
4
4
  </head>
5
5
 
@@ -9,15 +9,9 @@
9
9
  </p>
10
10
 
11
11
  <script type="lightview/module">
12
- debugger;
13
- self.variables({
14
- count: "number"
15
- }, {
16
- reactive
17
- });
18
- count = 0;
12
+ self.variables({count: "number",}, {reactive,set:0});
19
13
  self.bump = () => count++;
20
- </script>
14
+ </script>
21
15
 
22
16
  <style>
23
17
  button {
@@ -0,0 +1,47 @@
1
+ import 'expect-puppeteer';
2
+
3
+ describe('Lightview:Example:Counter', () => {
4
+ beforeAll(async () => {
5
+ await page.goto('http://localhost:8080/examples/counter.html');
6
+ });
7
+
8
+ test('should be titled "Lightview:Examples:Counter"', async () => {
9
+ await expect(page.title()).resolves.toMatch("Lightview:Examples:Counter");
10
+ });
11
+
12
+ test('count should be a variable', async () => {
13
+ const result = await page.evaluate(async () => {
14
+ return document.body.getVariable("count");
15
+ });
16
+ expect(result).toBeDefined();
17
+ const {name,type,value,reactive} = result;
18
+ expect(name).toBe("count");
19
+ expect(type).toBe("number");
20
+ expect(value).toBe(0);
21
+ expect(reactive).toBe(true);
22
+ });
23
+
24
+ test('bump should be called', async () => {
25
+ const result = await page.evaluate(async () => {
26
+ document.body.bump();
27
+ return document.body.getVariableValue("count");
28
+ });
29
+ expect(result).toBe(1);
30
+ });
31
+
32
+ test('click should bump', async () => {
33
+ const buttonHandle = await page.evaluateHandle('document.body.querySelector("button")');
34
+ await buttonHandle.click();
35
+ const result = await page.evaluate(async () => {
36
+ return document.body.getVariableValue("count");
37
+ });
38
+ expect(result).toBe(2);
39
+ });
40
+
41
+ test("should be custom element", async() => {
42
+ const result = await page.evaluate(async () => {
43
+ return document.body.constructor.name;
44
+ });
45
+ expect(result).toBe("CustomElement");
46
+ })
47
+ })
@@ -0,0 +1,45 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Invalid Template Literals</title>
5
+ <script src="../lightview.js?as=x-body"></script>
6
+ </head>
7
+ <body>
8
+ <p>
9
+ <button l-on:click="bump">Click count:${count}</button>
10
+ </p>
11
+ <div style="margin:20px">
12
+ <p>
13
+ ${"<h1>"+(test++)+"</h1>"}
14
+ </p>
15
+ <p>
16
+ ${(while (test)<10 { test++}; test)}
17
+ </p>
18
+ <p>
19
+ ${(() =>test)()}
20
+ </p>
21
+ <p>
22
+ ${(() = >test)()}
23
+ </p>
24
+ <p>
25
+ ${function(){return \${test}})()}
26
+ </p>
27
+ <p>
28
+ ${window.alert("ok")}
29
+ </p>
30
+ </div>
31
+
32
+ <script type="lightview/module">
33
+ self.variables({count: "number",test:"number"}, {reactive,set:0});
34
+ self.bump = () => count++;
35
+ </script>
36
+
37
+ <style>
38
+ button {
39
+ margin: 20px;
40
+ background: gray
41
+ }
42
+ </style>
43
+ </body>
44
+
45
+ </html>
@@ -13,7 +13,9 @@
13
13
 
14
14
 
15
15
  <script type="lightview/module">
16
- self.variables({myRemote:"object"},{reactive,remote:"http://localhost:8000/remote.json"});
16
+ const {remote} = await import("../types.js");
17
+
18
+ self.variables({myRemote:"object"},{reactive,remote:remote("http://localhost:8000/remote.json")});
17
19
 
18
20
  await myRemote; // must await remotes before the first time they are used, e.g. before HTML is rendered
19
21
 
@@ -1 +1 @@
1
- {"name":"joe","age":20}
1
+ {"name":"joe","age":30}
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <head>
3
+ <title>Lightview:Sensor Demo</title>
4
+ <link href="../../components/gauge.html" rel="module">
5
+ <script src="../../lightview.js?as=x-body"></script>
6
+ </head>
7
+ <body>
8
+ <div style="width:100%;text-align:center">
9
+ <div style="display:inline-block">
10
+ <l-gauge id="sensor1" type="Gauge" label="Sensor One" value="50"></l-gauge>
11
+ </div>
12
+
13
+ <div style="display:inline-block">
14
+ <l-gauge id="sensor2" type="Gauge" label="Sensor Two" value="50"></l-gauge>
15
+ </div>
16
+ </div>
17
+
18
+ <script type="lightview/module">
19
+ const {remote} = await import("../../types.js");
20
+ self.variables({sensor1:"number"},{remote:remote({ttl:5000,path:"https://lightview.dev/sensors/sensor1"})});
21
+ self.variables({sensor2:"number"},{remote:remote({ttl:7500,path:"https://lightview.dev/sensors/sensor2"})});
22
+ await sensor1;
23
+ await sensor2;
24
+ addEventListener("change",({variableName,value}) => {
25
+ const sensor = document.body.getElementById(variableName);
26
+ sensor.setValue(value);
27
+ });
28
+ </script>
29
+ </body>
30
+ </html>
@@ -0,0 +1,30 @@
1
+ const http = require("http"),
2
+ fs = require("fs"),
3
+ host = 'localhost',
4
+ port = 8000,
5
+ requestListener = async function (req, res) {
6
+ const path = `.${req.url}`;
7
+ res.setHeader("Access-Control-Allow-Origin","*");
8
+ res.setHeader("Access-Control-Allow-Methods", "GET,OPTIONS");
9
+ res.setHeader("Access-Control-Allow-Headers", "*");
10
+ res.setHeader("Content-Type", "application/json");
11
+ if(req.method==="OPTIONS") {
12
+ res.end();
13
+ return;
14
+ }
15
+ if(req.method==="GET") {
16
+ const value = `${40 + Math.round(60 * Math.random())}`;
17
+ console.log("GET",req.url,"<-",value);
18
+ res.setHeader("Content-Length", value.length);
19
+ res.write(value);
20
+ res.end();
21
+ return;
22
+ }
23
+ console.log(req.method);
24
+ },
25
+ server = http.createServer(requestListener);
26
+ server.listen(port, host, () => {
27
+ console.log(`Server is running on http://${host}:${port}`);
28
+ });
29
+
30
+
package/lightview.js CHANGED
@@ -251,8 +251,8 @@ const {observe} = (() => {
251
251
  target.postEvent.value("change", event);
252
252
  if (event.defaultPrevented) {
253
253
  target[property].value = value;
254
- } else if(remote) {
255
- handleRemote({variable,remote,reactive},true);
254
+ } else if(remote && remote.put) {
255
+ remote.handleRemote({variable,config:remote.config,reactive},true);
256
256
  }
257
257
  }
258
258
  return true;
@@ -357,15 +357,6 @@ const {observe} = (() => {
357
357
  }
358
358
  return node?.nodeValue;
359
359
  }
360
- const render = (hasTemplate, render) => {
361
- let observer;
362
- if (hasTemplate) {
363
- if (observer) observer.cancel();
364
- observer = observe(render)
365
- } else {
366
- render();
367
- }
368
- }
369
360
  const inputTypeToType = (inputType) => {
370
361
  if (!inputType) return "any"
371
362
  if (["text", "tel", "email", "url", "search", "radio", "color", "password"].includes(inputType)) return "string";
@@ -430,8 +421,7 @@ const {observe} = (() => {
430
421
  reactive: {value: true, constant: true},
431
422
  shared: {value: true, constant: true},
432
423
  exported: {value: true, constant: true},
433
- imported: {value: true, constant: true},
434
- remote: {}
424
+ imported: {value: true, constant: true}
435
425
  };
436
426
  const createClass = (domElementNode, {observer, framed}) => {
437
427
  const instances = new Set(),
@@ -475,7 +465,7 @@ const {observe} = (() => {
475
465
  constant: true
476
466
  },
477
467
  postEvent: {
478
- value: (eventName, event={}) => {
468
+ value: (eventName, event = {}) => {
479
469
  //event = {...event}
480
470
  event.type = eventName;
481
471
  event.target = currentComponent;
@@ -558,13 +548,12 @@ const {observe} = (() => {
558
548
  const nodes = getNodes(ctx);
559
549
  nodes.forEach((node) => {
560
550
  if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
561
- render(!!node.template, () => resolveNodeOrText(node, this))
551
+ observe(() => resolveNodeOrText(node, this));
562
552
  } else if (node.nodeType === Node.ELEMENT_NODE) {
563
553
  // resolve the value before all else;
564
554
  const attr = node.attributes.value,
565
555
  template = attr?.template;
566
556
  if (attr && template) {
567
- //render(!!template, () => {
568
557
  let value = resolveNodeOrText(attr, this),
569
558
  eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
570
559
  const template = attr.template;
@@ -612,22 +601,22 @@ const {observe} = (() => {
612
601
  const {name, value} = attr,
613
602
  vname = node.attributes.name?.value;
614
603
  if (name === "type" && value=="radio" && vname) {
615
- bindInput(node, vname, this);
616
- render(true, () => {
617
- const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
618
- if (node.attributes.value.value == varvalue) {
619
- node.setAttribute("checked", "");
620
- node.checked = true;
621
- } else {
622
- node.removeAttribute("checked");
623
- node.checked = false;
624
- }
625
- });
604
+ bindInput(node, vname, this);
605
+ observe(() => {
606
+ const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
607
+ if (node.attributes.value.value == varvalue) {
608
+ node.setAttribute("checked", "");
609
+ node.checked = true;
610
+ } else {
611
+ node.removeAttribute("checked");
612
+ node.checked = false;
613
+ }
614
+ });
626
615
  }
627
616
 
628
617
  const [type, ...params] = name.split(":");
629
618
  if (type === "") { // name is :something
630
- render(!!attr.template, () => {
619
+ observe(() => {
631
620
  const value = attr.value;
632
621
  if (params[0]) {
633
622
  if (value === "true") node.setAttribute(params[0], "")
@@ -643,20 +632,20 @@ const {observe} = (() => {
643
632
  })
644
633
  } else if (type === "l-on") {
645
634
  let listener;
646
- render(!!attr.template, () => {
635
+ observe(() => {
647
636
  const value = resolveNodeOrText(attr, this);
648
637
  if (listener) node.removeEventListener(params[0], listener);
649
638
  listener = this[value] || window[value] || Function(value);
650
639
  addListener(node, params[0], listener);
651
640
  })
652
641
  } else if (type === "l-if") {
653
- render(!!attr.template, () => {
642
+ observe(() => {
654
643
  const value = resolveNodeOrText(attr, this);
655
644
  node.style.setProperty("display", value === "true" ? "revert" : "none");
656
645
  })
657
646
  } else if (type === "l-for") {
658
647
  node.template ||= node.innerHTML;
659
- render(!!attr.template, () => {
648
+ observe(() => {
660
649
  const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
661
650
  value = resolveNodeOrText(attr, this),
662
651
  coerced = coerce(value, what === "each" ? Array : "object"),
@@ -683,8 +672,8 @@ const {observe} = (() => {
683
672
  else node.appendChild(parsed.body.firstChild);
684
673
  }
685
674
  })
686
- } else {
687
- render(!!attr.template, () => {
675
+ } else if(attr.template) {
676
+ observe(() => {
688
677
  resolveNodeOrText(attr, this);
689
678
  })
690
679
  }
@@ -699,11 +688,9 @@ const {observe} = (() => {
699
688
  }
700
689
  adoptedCallback(callback) {
701
690
  this.vars.postEvent.value("adopted");
702
- super.adoptedCallback();
703
691
  }
704
692
  disconnectedCallback() {
705
693
  this.vars.postEvent.value("disconnected");
706
- super.disconnectedCallback();
707
694
  }
708
695
  get observedAttributes() {
709
696
  return CustomElement.observedAttributes;
@@ -713,9 +700,12 @@ const {observe} = (() => {
713
700
  }
714
701
 
715
702
  getVariableNames() {
716
- return Object.keys(this.vars).filter((name) => {
717
- return !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name)
718
- })
703
+ return Object.keys(this.vars)
704
+ .filter(name => !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name))
705
+ }
706
+
707
+ getVariable(name) {
708
+ return this.vars[name] ? {...this.vars[name]} : undefined;
719
709
  }
720
710
 
721
711
  setVariableValue(variableName, value, {coerceTo = typeof (value)} = {}) {
@@ -751,17 +741,25 @@ const {observe} = (() => {
751
741
  return this.vars[variableName]?.value;
752
742
  }
753
743
 
754
- variables(variables, {observed, reactive, shared, exported, imported, remote} = {}) { // options = {observed,reactive,shared,exported,imported}
744
+ variables(variables, {observed, reactive, shared, exported, imported, remote, constant,set} = {}) { // options = {observed,reactive,shared,exported,imported}
755
745
  const addEventListener = this.varsProxy.addEventListener;
756
746
  if (variables !== undefined) {
757
747
  Object.entries(variables)
758
748
  .forEach(([key, type]) => {
759
749
  const variable = this.vars[key] ||= {name: key, type};
750
+ if(set!==undefined && constant!==undefined) throw new TypeError(`${key} has the constant value ${constant} and can't be set to ${set}`);
751
+ variable.value = set;
752
+ if(constant!==undefined) {
753
+ variable.constant = true;
754
+ variable.value = constant;
755
+ }
760
756
  if (observed || imported) {
761
757
  variable.value = this.hasAttribute(key) ? coerce(this.getAttribute(key), variable.type) : variable.value;
762
- variable.observed = observed;
763
758
  variable.imported = imported;
764
- if(variable.observed) this.observedAttributes.add(key);
759
+ if(variable.observed) {
760
+ variable.observed = observed;
761
+ this.observedAttributes.add(key);
762
+ }
765
763
  }
766
764
  if (reactive) {
767
765
  variable.reactive = true;
@@ -780,10 +778,11 @@ const {observe} = (() => {
780
778
  this.changeListener.targets.add(key);
781
779
  }
782
780
  if (remote) {
781
+ if(typeof(remote)==="function") remote = remote(`./${key}`);
783
782
  variable.remote = remote;
784
- handleRemote({variable, remote, reactive});
783
+ remote.handleRemote({variable, config:remote.config, reactive,component:this});
785
784
  }
786
- if(type.validate) type.validate(undefined,variable);
785
+ if(type.validate) type.validate(variable.value,variable);
787
786
  });
788
787
  }
789
788
  return Object.entries(this.vars)
@@ -792,146 +791,6 @@ const {observe} = (() => {
792
791
  return result;
793
792
  }, {});
794
793
  }
795
-
796
- constants(variables) {
797
- if (variables !== undefined) {
798
- Object.entries(variables)
799
- .forEach(([key, value]) => {
800
- const type = typeof (value) === "function" ? value : typeof (value),
801
- variable = this.vars[key];
802
- if (variable !== undefined) throw new TypeError(`${variable.constant ? "const" : "let"} ${key}:${variable.type} already declared.`);
803
- if (value === undefined) throw new TypeError(`const ${key}:undefined must be initialized.`);
804
- this.vars[key] = {type, value, constant: true};
805
- })
806
- }
807
- return Object.entries(this.vars)
808
- .reduce((result, [key, variable]) => {
809
- if (variable.constant) result[key] = {...variable};
810
- return result;
811
- }, {});
812
- }
813
- }
814
- }
815
-
816
- const remoteProxy = ({json, variable,remote, reactive}) => {
817
- const type = typeof (remote);
818
- return new Proxy(json, {
819
- get(target,property) {
820
- if(property==="__remoteProxytarget__") return json;
821
- return target[property];
822
- },
823
- async set(target, property, value) {
824
- if(value && typeof(value)==="object" && value instanceof Promise) value = await value;
825
- const oldValue = target[property];
826
- if (oldValue !== value) {
827
- let remotevalue;
828
- if (type === "string") {
829
- const href = new URL(remote,window.location.href).href;
830
- remotevalue = patch({target,property,value,oldValue},href,variable);
831
- } else if(remote && type==="object") {
832
- let href;
833
- if(remote.path) href = new URL(remote.path,window.location.href).href;
834
- if(!remote.patch) {
835
- if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
836
- remote.patch = patch;
837
- }
838
- remotevalue = remote.patch({target,property,value,oldValue},href,variable);
839
- }
840
- if(remotevalue) {
841
- await remotevalue.then((newjson) => {
842
- if (newjson && typeof (newjson) === "object" && reactive) {
843
- const target = variable.value?.__reactorProxyTarget__ ? json : variable.value;
844
- Object.entries(newjson).forEach(([key,newValue]) => {
845
- if(target[key]!==newValue) target[key] = newValue;
846
- })
847
- Object.keys(target).forEach((key) => {
848
- if(!(key in newjson)) delete target[key];
849
- });
850
- if(variable.value?.__reactorProxyTarget__) {
851
- const dependents = variable.value.__dependents__,
852
- observers = dependents[property] || [];
853
- [...observers].forEach((f) => {
854
- if (f.cancelled) dependents[property].delete(f);
855
- else f();
856
- })
857
- }
858
- } else {
859
- variable.value = json;
860
- }
861
- })
862
- }
863
- }
864
- return true;
865
- }
866
- })
867
- }
868
-
869
- const patch = ({target,property,value,oldValue},href,variable) => {
870
- return fetch(href, {
871
- method: "PATCH",
872
- body: JSON.stringify({property,value,oldValue}),
873
- headers: {
874
- "Content-Type": "application/json"
875
- }
876
- }).then((response) => {
877
- if (response.status < 400) return response.json();
878
- })
879
- }
880
-
881
- const get = (href,variable) => {
882
- return fetch(href)
883
- .then((response) => {
884
- if (response.status < 400) return response.json();
885
- })
886
- }
887
-
888
- const put = (href,variable) => {
889
- return fetch(href, {
890
- method: "PUT",
891
- body: JSON.stringify(variable.value),
892
- headers: {
893
- "Content-Type": "application/json"
894
- }
895
- }).then((response) => {
896
- if (response.status === 200) return response.json();
897
- })
898
- }
899
-
900
- const handleRemote = async ({variable, remote, reactive},doput) => {
901
- const type = typeof (remote);
902
- let value;
903
- if (type === "string") {
904
- const href = new URL(remote,window.location.href).href;
905
- value = (doput
906
- ? put(href,variable)
907
- : get(href,variable));
908
- if(variable.value===undefined) variable.value = value;
909
- } else if (remote && type === "object") {
910
- let href;
911
- if(remote.path) href = new URL(remote.path,window.location.href).href;
912
- if(!remote.get || !remote.put) {
913
- if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
914
- if(!remote.get) remote.get = get;
915
- if(!remote.put) remote.put = put;
916
- }
917
- value = (doput
918
- ? remote.put(href,variable)
919
- : remote.get(href,variable));
920
- if(remote.ttl && !doput && !remote.intervalId) {
921
- remote.intervalId = setInterval(async () => {
922
- await handleRemote({variable, remote, reactive});
923
- })
924
- }
925
- if(variable.value===undefined) variable.value = value;
926
- }
927
- if(value) {
928
- variable.value = await value.then((json) => {
929
- if (json && typeof (json) === "object" && reactive) {
930
- return remoteProxy({json, variable,remote, reactive})
931
- } else {
932
- return json;
933
- }
934
- })
935
794
  }
936
795
  }
937
796
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightview",
3
- "version": "1.5.1b",
3
+ "version": "1.6.2b",
4
4
  "description": "Small, simple, powerful web UI and micro front end creation ... Great ideas from Svelte, React, Vue and Riot combined.",
5
5
  "main": "lightview.js",
6
6
  "scripts": {
package/test/basic.html CHANGED
@@ -59,7 +59,7 @@
59
59
 
60
60
  inumber = 1;
61
61
  irange = 1;
62
- debugger;
62
+
63
63
  idatetime = new Date();
64
64
 
65
65
  icheckbox = true;
package/types.js CHANGED
@@ -40,7 +40,7 @@ const validateAny = function(value,variable) {
40
40
  variable.validityState = ValidityState({valid:true});
41
41
  return value;
42
42
  }
43
- return this.whenInvalid(variable);
43
+ return this.whenInvalid(variable,value);
44
44
  }
45
45
  const any = ({required=false,whenInvalid = ifInvalid,...rest}) => { // ...rest allows use of property "default", which is otherwise reserved
46
46
  if(typeof(required)!=="boolean") throw new TypeError(`required, ${JSON.stringify(required)}, must be a boolean`);
@@ -78,7 +78,7 @@ const validateArray = function(value,variable) {
78
78
  return result;
79
79
  }
80
80
  }
81
- return this.whenInvalid(variable);
81
+ return this.whenInvalid(variable,value);
82
82
  }
83
83
  const array = ({coerce=false, required = false,whenInvalid = ifInvalid,maxlength=Infinity,minlength=0,...rest}) => {
84
84
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
@@ -118,7 +118,7 @@ const validateBoolean = function(value,variable) {
118
118
  return result;
119
119
  }
120
120
  }
121
- return this.whenInvalid(variable);
121
+ return this.whenInvalid(variable,value);
122
122
  }
123
123
  const boolean = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest}) =>{
124
124
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
@@ -161,7 +161,7 @@ const validateNumber = function(value,variable) {
161
161
  return result;
162
162
  }
163
163
  }
164
- return this.whenInvalid(variable);
164
+ return this.whenInvalid(variable,value);
165
165
  }
166
166
  const number = ({coerce=false,required = false,whenInvalid = ifInvalid,min=-Infinity,max=Infinity,step = 1,allowNaN = true,...rest}) => {
167
167
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
@@ -208,7 +208,7 @@ const validateObject = function(value,variable) {
208
208
  return result;
209
209
  }
210
210
  }
211
- return this.whenInvalid(variable);
211
+ return this.whenInvalid(variable,value);
212
212
  }
213
213
  const object = ({coerce=false, required = false,whenInvalid = ifInvalid,...rest}) => {
214
214
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
@@ -247,7 +247,7 @@ const validateString = function(value,variable) {
247
247
  return result;
248
248
  }
249
249
  }
250
- return this.whenInvalid(variable);
250
+ return this.whenInvalid(variable,value);
251
251
  }
252
252
  const string = ({coerce=false, required = false,whenInvalid = ifInvalid, maxlength = Infinity, minlength = 0, pattern, ...rest}) => {
253
253
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
@@ -289,7 +289,7 @@ const validateSymbol = function(value,variable) {
289
289
  return result;
290
290
  }
291
291
  }
292
- return this.whenInvalid(variable);
292
+ return this.whenInvalid(variable,value);
293
293
  }
294
294
  const symbol = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest}) =>{
295
295
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
@@ -309,4 +309,137 @@ symbol.validate = validateSymbol;
309
309
  symbol.coerce = false;
310
310
  symbol.required = false;
311
311
 
312
- export {ValidityState,any,array,boolean,number,string,symbol}
312
+ const remoteProxy = ({json, variable,config, reactive, component}) => {
313
+ const type = typeof (config);
314
+ return new Proxy(json, {
315
+ get(target,property) {
316
+ if(property==="__remoteProxytarget__") return json;
317
+ return target[property];
318
+ },
319
+ async set(target, property, value) {
320
+ if(value && typeof(value)==="object" && value instanceof Promise) value = await value;
321
+ const oldValue = target[property];
322
+ if (oldValue !== value) {
323
+ let remotevalue;
324
+ if (type === "string") {
325
+ const href = new URL(config,window.location.href).href;
326
+ remotevalue = patch({target,property,value,oldValue},href,variable);
327
+ } else if(config && type==="object") {
328
+ let href;
329
+ if(config.path) href = new URL(config.path,window.location.href).href;
330
+ if(!config.patch) {
331
+ if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
332
+ config.patch = patch;
333
+ }
334
+ remotevalue = config.patch({target,property,value,oldValue},href,variable);
335
+ }
336
+ if(remotevalue) {
337
+ await remotevalue.then((newjson) => {
338
+ if (newjson && typeof (newjson) === "object" && reactive) {
339
+ const target = variable.value?.__reactorProxyTarget__ ? json : variable.value;
340
+ Object.entries(newjson).forEach(([key,newValue]) => {
341
+ if(target[key]!==newValue) target[key] = newValue;
342
+ })
343
+ Object.keys(target).forEach((key) => {
344
+ if(!(key in newjson)) delete target[key];
345
+ });
346
+ if(variable.value?.__reactorProxyTarget__) {
347
+ const dependents = variable.value.__dependents__,
348
+ observers = dependents[property] || [];
349
+ [...observers].forEach((f) => {
350
+ if (f.cancelled) dependents[property].delete(f);
351
+ else f();
352
+ })
353
+ }
354
+ } else {
355
+ component.setVariableValue(variable.name,newjson)
356
+ //variable.value = json;
357
+ }
358
+ })
359
+ }
360
+ }
361
+ return true;
362
+ }
363
+ })
364
+ }
365
+
366
+ const patch = ({target,property,value,oldValue},href,variable) => {
367
+ return fetch(href, {
368
+ method: "PATCH",
369
+ body: JSON.stringify({property,value,oldValue}),
370
+ headers: {
371
+ "Content-Type": "application/json"
372
+ }
373
+ }).then((response) => {
374
+ if (response.status < 400) return response.json();
375
+ })
376
+ }
377
+
378
+ const get = (href,variable) => {
379
+ return fetch(href)
380
+ .then((response) => {
381
+ if (response.status < 400) return response.json();
382
+ })
383
+ }
384
+
385
+ const put = (href,variable) => {
386
+ return fetch(href, {
387
+ method: "PUT",
388
+ body: JSON.stringify(variable.value),
389
+ headers: {
390
+ "Content-Type": "application/json"
391
+ }
392
+ }).then((response) => {
393
+ if (response.status === 200) return response.json();
394
+ })
395
+ }
396
+
397
+ const handleRemote = async ({variable, config, reactive, component},doput) => {
398
+ const type = typeof (config);
399
+ let value;
400
+ if (type === "string") {
401
+ const href = new URL(config,window.location.href).href;
402
+ value = (doput
403
+ ? put(href,variable)
404
+ : get(href,variable));
405
+ if(variable.value===undefined) variable.value = value; // do not await here
406
+ } else if (remote && type === "object") {
407
+ let href;
408
+ if(!config.path) config.path = `./${variable.name}`;
409
+ if(config.path) href = new URL(config.path,window.location.href).href;
410
+ if(!config.get || !config.put) {
411
+ if(!href) throw new Error(`A remote path is required if no put function is provided for remote data`)
412
+ if(!config.get) config.get = get;
413
+ if(!config.put && reactive) config.put = put;
414
+ }
415
+ value = (doput
416
+ ? config.put(href,variable)
417
+ : config.get(href,variable));
418
+ if(config.ttl && !doput && !config.intervalId) {
419
+ config.intervalId = setInterval(async () => {
420
+ await handleRemote({variable, config, reactive, component});
421
+ //schedule();
422
+ },config.ttl);
423
+ }
424
+ if(variable.value===undefined) variable.value = value;
425
+ }
426
+ if(value) {
427
+ const json = await value;
428
+ //value.then((json) => {
429
+ if (json && typeof (json) === "object" && reactive) {
430
+ variable.value = remoteProxy({json, variable,config, reactive, component});
431
+ } else {
432
+ component.setVariableValue(variable.name,json);
433
+ }
434
+ //})
435
+ }
436
+ }
437
+
438
+ const remote = (config) => {
439
+ return {
440
+ config,
441
+ handleRemote
442
+ }
443
+ }
444
+
445
+ export {ValidityState,any,array,boolean,number,string,symbol,remote}