lightview 1.6.5-b → 1.6.6-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,8 +1,8 @@
1
- # lightview v1.6.5b (BETA)
1
+ # lightview v1.6.6b (BETA)
2
2
 
3
3
  Small, simple, powerful web UI and micro front end creation ...
4
4
 
5
- Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 7K (minified/gzipped).
5
+ Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 7.5K (minified/gzipped).
6
6
 
7
7
  See the docs and examples at [https://lightview.dev](https://lightview.dev).
8
8
 
@@ -12,24 +12,38 @@
12
12
  The component below is loaded from an alternate domain and running in a child iframe.
13
13
  The logging console is below the component in this frame.
14
14
  </p>
15
- <iframe id="myframe" src="https://lightview.dev/foreignform.html?id=myframe"></iframe>
15
+ <p>New Order:<span id="orderclip"></span></p>
16
+ <iframe id="myframe"
17
+ class="l-remote" style="border-width:2px;border-style:dashed;"
18
+ src="https://lightview.dev/examples/foreignform.html?id=myframe"></iframe>
16
19
  <div id="console" style="max-height:250px;scroll:auto"></div>
17
20
  <script>
18
- const mutationCallback = (mutationsList) => {
19
- const console = document.getElementById("console");
20
- for (const {target,attributeName,oldValue} of mutationsList) {
21
- const line = document.createElement("div"),
22
- event = {attributeName,oldValue,value:target.getAttribute(attributeName)};
23
- line.innerText = JSON.stringify(event);
24
- console.appendChild(line);
25
- }
26
- };
27
- const observer = new MutationObserver(mutationCallback),
28
- iframe = document.getElementById("myframe");
29
- observer.observe(iframe, { attributes:true, attributeOldValue: true });
21
+ const iframe = document.getElementById("myframe");
30
22
  iframe.addEventListener("DOMContentLoaded",(event) => {
23
+ // modify the line below, or remove this event listener
24
+ // based on the needs of your application
31
25
  console.log(event);
32
26
  });
27
+ iframe.addEventListener("message",({detail}) => {
28
+ // modify the lines below, or remove this event listener
29
+ // based on the needs of your application
30
+ const orderclip = document.getElementById("orderclip");
31
+ orderclip.innerText = JSON.stringify(detail);
32
+ });
33
+
34
+ iframe.addEventListener("attribute.changed",(event) => {
35
+ const {target,detail} = event,
36
+ {attributeName,value,oldValue} = detail;
37
+ event.stopImmediatePropagation();
38
+ if(target.getAttribute(attributeName)!==oldValue) {
39
+ // modify the lines below, or remove this event listener
40
+ // based on the needs of your application
41
+ const console = document.getElementById("console"),
42
+ line = document.createElement("div");
43
+ line.innerText = JSON.stringify(detail);
44
+ console.appendChild(line);
45
+ }
46
+ });
33
47
  </script>
34
48
  </body>
35
49
 
@@ -27,7 +27,9 @@
27
27
  <option>tomato</option>
28
28
  <option>cheese</option>
29
29
  </select>
30
+ <br><button l-on:click="placeOrder">Place Order</button>
30
31
  </p>
32
+ Expose: <input type="checkbox" value="${checked}">
31
33
  <p l-if="${checked}">
32
34
  Now you've done it. You've exposed me.
33
35
  </p>
@@ -45,24 +47,43 @@
45
47
  </p>
46
48
  </div>
47
49
  <script type="lightview/module">
50
+ const orders = [];
51
+ self.variables({
52
+ checked: "boolean"
53
+ }, {
54
+ reactive
55
+ });
48
56
  self.variables({
49
57
  color: "string",
50
- checked: "boolean",
51
58
  hamburger: Array
52
59
  }, {
53
- reactive
60
+ reactive, exported
61
+ });
62
+ self.addEventListener("connected",() => {
63
+ color = "green";
64
+ checked = true;
65
+ hamburger = ["lettuce"];
54
66
  });
55
- color = "green";
56
- checked = true;
57
- hamburger = ["lettuce"];
67
+ self.placeOrder = () => {
68
+ orders.push(hamburger);
69
+ message = {hamburger};
70
+ };
58
71
  // demo instrumentation
59
72
  const variableValues = () => {
60
73
  const el = self.getElementById("variables");
61
74
  while (el.lastElementChild) el.lastElementChild.remove();
62
75
  self.getVariableNames().forEach((name) => {
63
- const line = document.createElement("div");
64
- line.innerText = `${name} = ${JSON.stringify(self.getVariableValue(name))}`;
65
- el.appendChild(line);
76
+ const line = document.createElement("div");
77
+ line.innerText = `${name} = ${JSON.stringify(self.getVariableValue(name))}`;
78
+ el.appendChild(line);
79
+ });
80
+ const line = document.createElement("div");
81
+ line.innerText = "Previous Orders";
82
+ el.appendChild(line);
83
+ orders.forEach((order) => {
84
+ const line = document.createElement("div");
85
+ line.innerText = JSON.stringify(order);
86
+ el.appendChild(line);
66
87
  });
67
88
  };
68
89
  variableValues();
@@ -38,7 +38,7 @@
38
38
  addEventListener("change", () => {
39
39
  variableValues()
40
40
  });
41
- addEventListener("connected",() => {
41
+ self.addEventListener("connected",() => {
42
42
  color = "yellow";
43
43
  checked = true;
44
44
  hamburger = ["cheese"];
@@ -21,7 +21,7 @@
21
21
  <!--
22
22
  set the initial value 0 for all components in a relaxed JSON5 configuration data block
23
23
  -->
24
- <r-chart id="dashboard" style="display:inline-block" type="Gauge" title="Server Status">
24
+ <r-chart id="dashboard" style="display:inline-block" type="Gauge" title="Server Status" hidden l-unhide>
25
25
  [
26
26
  ['Label', 'Value'], // gauge will always take two columns, Label and Value
27
27
  ['Memory', 0],
@@ -1 +1 @@
1
- {"name":"joe","age":30}
1
+ {"name":"joe","age":40}
@@ -16,6 +16,7 @@
16
16
  </div>
17
17
  <script type="lightview/module">
18
18
  const {string} = await import("../types.js");
19
+ debugger;
19
20
  self.run = () => {
20
21
  self.variables({
21
22
  err: Error,
package/lightview.js CHANGED
@@ -40,14 +40,16 @@ const {observe} = (() => {
40
40
  Lightview.sanitizeTemplate = templateSanitizer;
41
41
 
42
42
  const escaper = document.createElement('textarea');
43
-
44
43
  function escapeHTML(html) {
45
44
  escaper.textContent = html;
46
45
  return escaper.innerHTML;
47
46
  }
48
-
49
47
  Lightview.escapeHTML = escapeHTML;
50
48
 
49
+ const isArrowFunction = (f) => {
50
+ return typeof(f)==="function" && (f+"").match(/\(*.*\)*\s*=>/g)
51
+ }
52
+
51
53
  const addListener = (node, eventName, callback) => {
52
54
  node.addEventListener(eventName, callback); // just used to make code footprint smaller
53
55
  }
@@ -226,7 +228,8 @@ const {observe} = (() => {
226
228
  if(target instanceof Date) {
227
229
  return Reflect.get(target,property);
228
230
  }
229
- let {value} = target[property] || {};
231
+ let {value,get} = target[property] || {};
232
+ if(get) return target[property].value = get.call(target[property]);
230
233
  if (typeof (value) === "function") return value.bind(target);
231
234
  return value;
232
235
  },
@@ -239,7 +242,8 @@ const {observe} = (() => {
239
242
  return true;
240
243
  }
241
244
  const variable = target[property],
242
- {type, value, shared, exported, constant, reactive, remote} = variable;
245
+ {value, shared, exported, constant, reactive, remote} = variable;
246
+ let type = variable.type;
243
247
  if (constant) throw new TypeError(`${property}:${type} is a constant`);
244
248
  if(newValue!=null || type.required) newValue = type.validate ? type.validate(newValue,target[property]) : coerce(newValue,type);
245
249
  const newtype = typeof (newValue),
@@ -254,8 +258,10 @@ const {observe} = (() => {
254
258
  target.postEvent.value("change", event);
255
259
  if (event.defaultPrevented) {
256
260
  target[property].value = value;
257
- } else if(remote && remote.put) {
258
- remote.handleRemote({variable,config:remote.config,reactive},true);
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);
259
265
  }
260
266
  }
261
267
  return true;
@@ -386,7 +392,8 @@ const {observe} = (() => {
386
392
  if (variable.type === "any" || variable.type === "unknown") variable.type = type;
387
393
  else throw new TypeError(`Attempt to bind <input name="${variableName}" type="${type}"> to variable ${variableName}:${variable.type}`)
388
394
  }
389
- component.variables({[variableName]: type},{reactive:true});
395
+ const existing = component.vars[variableName];
396
+ if(!existing || existing.type!==type || !existing.reactive) component.variables({[variableName]: type},{reactive});
390
397
  if(inputtype!=="radio") {
391
398
  if(value.includes("${")) input.attributes.value.value = "";
392
399
  else component.setVariableValue(variableName, coerce(value,type));
@@ -419,12 +426,88 @@ const {observe} = (() => {
419
426
  return value;
420
427
  }
421
428
  }
429
+ const observed = () => {
430
+ return {
431
+ init({variable, component}) {
432
+ const name = variable.name;
433
+ variable.value = component.hasAttribute(name) ? coerce(component.getAttribute(name), variable.type) : variable.value;
434
+ variable.observed = true;
435
+ component.observedAttributes.add(variable.name);
436
+ }
437
+ }
438
+ }
439
+ const reactive = () => {
440
+ return {
441
+ init({variable, component}) {
442
+ variable.reactive = true;
443
+ component.vars[variable.name] = Reactor(variable);
444
+ }
445
+ }
446
+ }
447
+ const shared = () => {
448
+ return {
449
+ init({variable, component}) {
450
+ variable.shared = true;
451
+ /*addEventListener("change", ({variableName, value}) => {
452
+ if (variableName===variable.name && component.vars[variableName]?.shared) component.siblings.forEach((instance) => instance.setVariableValue(variableName, value))
453
+ })*/
454
+ variable.set = function(newValue) {
455
+ if(component.vars[this.name]?.shared) component.siblings.forEach((instance) => instance.setVariableValue(this.name, newValue));
456
+ }
457
+ }
458
+ }
459
+ }
460
+ const exported = () => {
461
+ return {
462
+ init({variable, component}) {
463
+ const name = variable.name;
464
+ variable.exported = true;
465
+ variable.set = (newValue) => {
466
+ if(variable.exported) {
467
+ if(newValue==null) {
468
+ removeComponentAttribute(component, name);
469
+ } else {
470
+ newValue = typeof (newValue) === "string" ? newValue : JSON.stringify(newValue);
471
+ setComponentAttribute(component, name, newValue);
472
+ }
473
+ }
474
+ }
475
+ variable.set(variable.value);
476
+ //component.changeListener.targets.add(name);
477
+ }
478
+ }
479
+ }
480
+ const imported = () => {
481
+ return {
482
+ init({variable, component}) {
483
+ const name = variable.name;
484
+ variable.imported = true;
485
+ variable.value = component.hasAttribute(name) ? coerce(component.getAttribute(name), variable.type) : variable.value;
486
+ }
487
+ }
488
+ }
489
+
422
490
  let reserved = {
423
- observed: {value: true, constant: true},
424
- reactive: {value: true, constant: true},
425
- shared: {value: true, constant: true},
426
- exported: {value: true, constant: true},
427
- imported: {value: true, constant: true}
491
+ observed: {
492
+ constant: true,
493
+ value: observed
494
+ },
495
+ reactive: {
496
+ constant: true,
497
+ value: reactive
498
+ },
499
+ shared: {
500
+ constant: true,
501
+ value: shared
502
+ },
503
+ exported: {
504
+ constant: true,
505
+ value: exported
506
+ },
507
+ imported: {
508
+ constant: true,
509
+ value: imported
510
+ }
428
511
  };
429
512
  const createClass = (domElementNode, {observer, framed}) => {
430
513
  const instances = new Set(),
@@ -446,14 +529,14 @@ const {observe} = (() => {
446
529
  shadow = this.attachShadow({mode: "open"}),
447
530
  eventlisteners = {};
448
531
  // needs to be local to the instance
449
- Object.defineProperty(this,"changeListener",{value:
532
+ /*Object.defineProperty(this,"changeListener",{value:
450
533
  function({variableName, value}) {
451
- if (currentComponent.changeListener.targets.has(variableName)) {
534
+ //if (currentComponent.changeListener.targets.has(variableName)) {
452
535
  value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
453
536
  if (value == null) removeComponentAttribute(currentComponent, variableName);
454
537
  else setComponentAttribute(currentComponent, variableName, value);
455
- }
456
- }});
538
+ // }
539
+ }});*/
457
540
  this.vars = {
458
541
  ...reserved,
459
542
  addEventListener: {
@@ -481,9 +564,9 @@ const {observe} = (() => {
481
564
  };
482
565
  this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
483
566
  this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
484
- this.changeListener.targets = new Set();
485
- this.varsProxy.addEventListener("change", this.changeListener);
486
- if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported: true});
567
+ //this.changeListener.targets = new Set();
568
+ //this.varsProxy.addEventListener("change", this.changeListener);
569
+ if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported});
487
570
  ["getElementById", "querySelector", "querySelectorAll"]
488
571
  .forEach((fname) => {
489
572
  Object.defineProperty(this, fname, {
@@ -514,15 +597,19 @@ const {observe} = (() => {
514
597
  for (const attr of this.defaultAttributes) this.hasAttribute(attr.name) || this.setAttribute(attr.name, attr.value);
515
598
  const scripts = shadow.querySelectorAll("script"),
516
599
  promises = [];
517
- // scriptpromises = [];
518
600
  for (const script of [...scripts]) {
519
601
  if (script.attributes.src?.value?.includes("/lightview.js")) continue;
602
+ // remove comments;
603
+ const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
520
604
  const currentScript = document.createElement("script");
521
605
  if (script.className !== "lightview" && !((script.attributes.type?.value || "").includes("lightview/"))) {
522
606
  for (const attr of script.attributes) currentScript.setAttribute(attr.name,attr.value);
607
+ currentScript.innerHTML = text;
523
608
  shadow.appendChild(currentScript);
524
609
  await new Promise((resolve) => {
610
+ const timeout = setTimeout(() => resolve(),500);
525
611
  currentScript.onload = () => {
612
+ clearTimeout(timeout);
526
613
  currentScript.remove();
527
614
  resolve();
528
615
  }
@@ -534,7 +621,6 @@ const {observe} = (() => {
534
621
  currentScript.setAttribute(attr.name, attr.name === "type" ? attr.value.replace("lightview/", "") : attr.value);
535
622
  }
536
623
  currentScript.classList.remove("lightview");
537
- const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
538
624
  currentScript.innerHTML = `Object.getPrototypeOf(async function(){}).constructor('if(window["${scriptid}"]?.ctx) { const ctx = window["${scriptid}"].ctx; { with(ctx) { ${text}; } } }')().then(() => window["${scriptid}"]()); `;
539
625
  await new Promise((resolve) => {
540
626
  window[scriptid] = () => {
@@ -685,6 +771,7 @@ const {observe} = (() => {
685
771
  shadow.normalize();
686
772
  observer ||= createObserver(ctx, framed);
687
773
  observer.observe(ctx, {attributeOldValue: true, subtree:true, characterData:true, characterDataOldValue:true});
774
+ if(this.hasAttribute("l-unhide")) this.removeAttribute("hidden");
688
775
  //ctx.vars.postEvent.value("connected");
689
776
  this.dispatchEvent(new Event("connected"));
690
777
  // })
@@ -746,11 +833,13 @@ const {observe} = (() => {
746
833
  return this.vars[variableName]?.value;
747
834
  }
748
835
 
749
- variables(variables, {observed, reactive, shared, exported, imported, remote, constant,set} = {}) { // options = {observed,reactive,shared,exported,imported}
750
- const addEventListener = this.varsProxy.addEventListener;
836
+ variables(variables, {remote, constant,set,...rest} = {}) { // options = {observed,reactive,shared,exported,imported}
837
+ const options = {remote, constant,...rest},
838
+ addEventListener = this.varsProxy.addEventListener;
751
839
  if (variables !== undefined) {
752
840
  Object.entries(variables)
753
841
  .forEach(([key, type]) => {
842
+ if(isArrowFunction(type)) type = type();
754
843
  const variable = this.vars[key] ||= {name: key, type};
755
844
  if(set!==undefined && constant!==undefined) throw new TypeError(`${key} has the constant value ${constant} and can't be set to ${set}`);
756
845
  variable.value = set;
@@ -758,35 +847,19 @@ const {observe} = (() => {
758
847
  variable.constant = true;
759
848
  variable.value = constant;
760
849
  }
761
- if (observed || imported) {
762
- variable.value = this.hasAttribute(key) ? coerce(this.getAttribute(key), variable.type) : variable.value;
763
- variable.imported = imported;
764
- if(variable.observed) {
765
- variable.observed = observed;
766
- this.observedAttributes.add(key);
767
- }
768
- }
769
- if (reactive) {
770
- variable.reactive = true;
771
- this.vars[key] = Reactor(variable);
772
- }
773
- if (shared) {
774
- variable.shared = true;
775
- addEventListener("change", ({variableName, value}) => {
776
- if (this.vars[variableName]?.shared) this.siblings.forEach((instance) => instance.setVariableValue(variableName, value))
777
- })
778
- }
779
- if (exported) {
780
- variable.exported = true;
781
- // in case the export goes up to an iframe
782
- if (variable.value != null) setComponentAttribute(this, key, variable.value);
783
- this.changeListener.targets.add(key);
784
- }
785
850
  if (remote) {
786
851
  if(typeof(remote)==="function") remote = remote(`./${key}`);
787
852
  variable.remote = remote;
788
- remote.handleRemote({variable, config:remote.config, reactive,component:this});
853
+ remote.handleRemote({variable, config:remote.config,component:this});
789
854
  }
855
+ // todo: handle custom functional types, remote should actually be handled this way
856
+ Object.entries(rest).forEach(([type,f]) => {
857
+ const functionalType = variable[type] = typeof(f)==="function" ? f() : f;
858
+ if(functionalType.init) functionalType.init({variable,options,component:this});
859
+ if((rest.get!==undefined || rest.set!==undefined) && constant!==undefined) throw new TypeError(`${key} has the constant value ${constant} and can't have a getter or setter`);
860
+ variable.set != functionalType.set;
861
+ variable.get != functionalType.get;
862
+ });
790
863
  if(type.validate && variable.value!==undefined) type.validate(variable.value,variable);
791
864
  });
792
865
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightview",
3
- "version": "1.6.5b",
3
+ "version": "1.6.6b",
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": {
@@ -14,15 +14,18 @@ self.variables({strictboolean:boolean},{set:true});
14
14
  self.variables({strictnumber:number},{set:0});
15
15
  self.variables({strictobject:object},{set:{}});
16
16
  self.variables({strictstring:string},{set:"test"});
17
+ self.variables({extendedarray:array({required:true,minlength:2,maxlength:3})});
18
+ self.variables({extendedboolean:boolean({required:true})});
19
+ self.variables({extendednumber:number({required:true,min:1,max:4,step:2,allowNaN:false})});
20
+ self.variables({extendedobject:object({required:true})});
21
+ self.variables({extendedstring:string({required:true,minlength:2,maxlength:4})});
17
22
 
23
+ self.variables({allowNaNnumber:number({min:null,max:null,step:null})});
24
+ self.variables({noNaNnumber:number({allowNaN:false})});
18
25
 
19
- self.variables({requiredarray:array({required:true})});
20
- self.variables({requiredboolean:boolean({required:true})});
21
- self.variables({requirednumber:number({required:true})});
22
- self.variables({requiredobject:object({required:true})});
23
- self.variables({requiredstring:string({required:true})});
24
-
25
- //self.setVariableValue("requirednumber",null);
26
+ //debugger;
27
+ //self.setVariableValue("extendedarray",[]);
28
+ //console.log(typeof(extendedarray),extendedarray);
26
29
 
27
30
  </script>
28
31
  </body>
@@ -253,18 +253,196 @@ describe('Lightview - Variables', () => {
253
253
  expect(validityState.value).toBe(0);
254
254
  });
255
255
 
256
- test('requirednumber - should throw and not allow null', async () => {
256
+ test('extendedarray - should respect minlength', async () => {
257
257
  const result = await page.evaluate(() => {
258
258
  try {
259
- document.body.setVariableValue("requirednumber",null);
259
+ document.body.setVariableValue("extendedarray",[]);
260
260
  } catch(e) {
261
261
  return e.message;
262
262
  }
263
- return document.body.getVariableValue("requirednumber")
263
+ return document.body.getVariableValue("extendedarray")
264
264
  });
265
265
  const {name,validityState} = JSON.parse(result,reviver);
266
- expect(name).toBe("requirednumber");
266
+ expect(name).toBe("extendedarray");
267
+ expect(validityState.tooShort).toBe(true);
268
+ });
269
+
270
+ test('extendedarray - should respect maxlength', async () => {
271
+ const result = await page.evaluate(() => {
272
+ try {
273
+ document.body.setVariableValue("extendedarray",[1,2,3,4,5]);
274
+ } catch(e) {
275
+ return e.message;
276
+ }
277
+ return document.body.getVariableValue("extendedarray")
278
+ });
279
+ const {name,validityState} = JSON.parse(result,reviver);
280
+ expect(name).toBe("extendedarray");
281
+ expect(validityState.tooLong).toBe(true);
282
+ });
283
+
284
+ test('extendedarray - should throw and not allow null', async () => {
285
+ const result = await page.evaluate(() => {
286
+ try {
287
+ document.body.setVariableValue("extendedarray",null);
288
+ } catch(e) {
289
+ return e.message;
290
+ }
291
+ return document.body.getVariableValue("extendedarray")
292
+ });
293
+ const {name,validityState} = JSON.parse(result,reviver);
294
+ expect(name).toBe("extendedarray");
267
295
  expect(validityState.valueMissing).toBe(true);
296
+ });
297
+
298
+ test('extendednumber - should throw and not allow null', async () => {
299
+ const result = await page.evaluate(() => {
300
+ try {
301
+ document.body.setVariableValue("extendednumber",null);
302
+ } catch(e) {
303
+ return e.message;
304
+ }
305
+ return document.body.getVariableValue("extendednumber")
306
+ });
307
+ const {name,validityState} = JSON.parse(result,reviver);
308
+ expect(name).toBe("extendednumber");
309
+ expect(validityState.valueMissing).toBe(true);
310
+ });
311
+
312
+ test('extendednumber - should throw and not allow NaN', async () => {
313
+ const result = await page.evaluate(() => {
314
+ try {
315
+ document.body.setVariableValue("extendednumber",NaN);
316
+ } catch(e) {
317
+ return e.message;
318
+ }
319
+ return document.body.getVariableValue("extendednumber")
320
+ });
321
+ const {name,validityState} = JSON.parse(result,reviver);
322
+ expect(name).toBe("extendednumber");
323
+ expect(validityState.badInput).toBe(true);
268
324
  //expect(validityState.value).toBe(null);
269
325
  });
326
+
327
+ test('extendednumber - should respect min', async () => {
328
+ const result = await page.evaluate(() => {
329
+ try {
330
+ document.body.setVariableValue("extendednumber",0);
331
+ } catch(e) {
332
+ return e.message;
333
+ }
334
+ return document.body.getVariableValue("extendednumber")
335
+ });
336
+ const {name,validityState} = JSON.parse(result,reviver);
337
+ expect(name).toBe("extendednumber");
338
+ expect(validityState.rangeUnderflow).toBe(true);
339
+ });
340
+
341
+ test('extendednumber - should allow between and allow step', async () => {
342
+ const result = await page.evaluate(() => {
343
+ try {
344
+ document.body.setVariableValue("extendednumber",4);
345
+ } catch(e) {
346
+ return e.message;
347
+ }
348
+ return document.body.getVariableValue("extendednumber")
349
+ });
350
+ expect(result).toBe(4);
351
+ });
352
+
353
+ test('extendednumber - should respect max', async () => {
354
+ const result = await page.evaluate(() => {
355
+ try {
356
+ document.body.setVariableValue("extendednumber",5);
357
+ } catch(e) {
358
+ return e.message;
359
+ }
360
+ return document.body.getVariableValue("extendednumber")
361
+ });
362
+ const {name,validityState} = JSON.parse(result,reviver);
363
+ expect(name).toBe("extendednumber");
364
+ expect(validityState.rangeOverflow).toBe(true);
365
+ });
366
+
367
+ test('extendednumber - should respect step', async () => {
368
+ const result = await page.evaluate(() => {
369
+ try {
370
+ document.body.setVariableValue("extendednumber",3);
371
+ } catch(e) {
372
+ return e.message;
373
+ }
374
+ return document.body.getVariableValue("extendednumber")
375
+ });
376
+ const {name,validityState} = JSON.parse(result,reviver);
377
+ expect(name).toBe("extendednumber");
378
+ expect(validityState.rangeUnderflow).toBe(true);
379
+ });
380
+
381
+ test('allowNaNnumber - should allow NaN', async () => {
382
+ const result = await page.evaluate(() => {
383
+ try {
384
+ document.body.setVariableValue("allowNaNnumber",NaN);
385
+ } catch(e) {
386
+ return e.message;
387
+ }
388
+ return document.body.getVariableValue("allowNaNnumber")
389
+ });
390
+ expect(typeof(result)==="number" && isNaN(result)).toBe(true);
391
+ });
392
+
393
+ test('noNaNnumber - should not allow NaN', async () => {
394
+ const result = await page.evaluate(() => {
395
+ try {
396
+ document.body.setVariableValue("noNaNnumber",NaN);
397
+ } catch(e) {
398
+ return e.message;
399
+ }
400
+ return document.body.getVariableValue("noNaNnumber")
401
+ });
402
+ const {name,validityState} = JSON.parse(result,reviver);
403
+ expect(name).toBe("noNaNnumber");
404
+ expect(validityState.badInput).toBe(true);
405
+ });
406
+
407
+ test('extendedstring - should respect minlength', async () => {
408
+ const result = await page.evaluate(() => {
409
+ try {
410
+ document.body.setVariableValue("extendedstring","a");
411
+ } catch(e) {
412
+ return e.message;
413
+ }
414
+ return document.body.getVariableValue("extendedstring")
415
+ });
416
+ const {name,validityState} = JSON.parse(result,reviver);
417
+ expect(name).toBe("extendedstring");
418
+ expect(validityState.tooShort).toBe(true);
419
+ });
420
+
421
+ test('extendedstring - should respect maxlength', async () => {
422
+ const result = await page.evaluate(() => {
423
+ try {
424
+ document.body.setVariableValue("extendedstring","abcdefg");
425
+ } catch(e) {
426
+ return e.message;
427
+ }
428
+ return document.body.getVariableValue("extendedstring")
429
+ });
430
+ const {name,validityState} = JSON.parse(result,reviver);
431
+ expect(name).toBe("extendedstring");
432
+ expect(validityState.tooLong).toBe(true);
433
+ });
434
+
435
+ test('extendedstring - should throw and not allow null', async () => {
436
+ const result = await page.evaluate(() => {
437
+ try {
438
+ document.body.setVariableValue("extendedstring",null);
439
+ } catch(e) {
440
+ return e.message;
441
+ }
442
+ return document.body.getVariableValue("extendedstring")
443
+ });
444
+ const {name,validityState} = JSON.parse(result,reviver);
445
+ expect(name).toBe("extendedstring");
446
+ expect(validityState.valueMissing).toBe(true);
447
+ });
270
448
  })
package/types.js CHANGED
@@ -108,7 +108,7 @@ const validateArray = function(value,variable) {
108
108
  }
109
109
  return this.whenInvalid(variable,value);
110
110
  }
111
- const array = ({coerce=false, required = false,whenInvalid = ifInvalid,maxlength=Infinity,minlength=0,...rest}) => {
111
+ const array = ({coerce=false, required = false,whenInvalid = ifInvalid,maxlength=Infinity,minlength=0,...rest}={}) => {
112
112
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
113
113
  if(typeof(required)!=="boolean") throw new TypeError(`required, ${JSON.stringify(required)}, must be a boolean`);
114
114
  if(typeof(whenInvalid)!=="function") throw new TypeError(`whenInvalid, ${whenInvalid}, must be a function`);
@@ -126,10 +126,7 @@ const array = ({coerce=false, required = false,whenInvalid = ifInvalid,maxlength
126
126
  validate: validateArray
127
127
  }
128
128
  }
129
- array.validate = validateArray;
130
- array.whenInvalid = ifInvalid;
131
- array.coerce = false;
132
- array.required = false;
129
+
133
130
 
134
131
  const validateBoolean = function(value,variable) {
135
132
  if(value===undefined && variable.value===undefined) {
@@ -149,7 +146,7 @@ const validateBoolean = function(value,variable) {
149
146
  }
150
147
  return this.whenInvalid(variable,value);
151
148
  }
152
- const boolean = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest}) =>{
149
+ const boolean = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest}={}) =>{
153
150
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
154
151
  if(typeof(required)!=="boolean") throw new TypeError(`required, ${JSON.stringify(required)}, must be a boolean`);
155
152
  if(typeof(whenInvalid)!=="function") throw new TypeError(`whenInvalid, ${whenInvalid}, must be a function`);
@@ -163,10 +160,7 @@ const boolean = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest})
163
160
  validate: validateBoolean
164
161
  }
165
162
  }
166
- boolean.validate = validateBoolean;
167
- boolean.whenInvalid = ifInvalid;
168
- boolean.coerce = false;
169
- boolean.required = false;
163
+
170
164
 
171
165
  const validateNumber = function(value,variable) {
172
166
  if(value===undefined && variable.value===undefined) {
@@ -178,13 +172,13 @@ const validateNumber = function(value,variable) {
178
172
  const result = this.coerce ? tryParse(value) : value;
179
173
  if(typeof(result)!=="number") {
180
174
  variable.validityState = ValidityState({typeMismatch:true,value});
181
- } else if(isNaN(result) && !allowNaN) {
175
+ } else if(isNaN(result) && !this.allowNaN) {
182
176
  variable.validityState = ValidityState({badInput:true,value});
183
- } else if(result<this.min) {
177
+ } else if(this.min!=null && result<this.min) {
184
178
  variable.validityState = ValidityState({rangeUnderflow:true,value});
185
- } else if(result>this.max) {
179
+ } else if(this.max!=null && result>this.max) {
186
180
  variable.validityState = ValidityState({rangeOverflow:true,value});
187
- } else if((result % this.step)!==0) {
181
+ } else if(this.step!==null && (result % this.step)!==0) {
188
182
  variable.validityState = ValidityState({rangeUnderflow:true,value});
189
183
  } else {
190
184
  variable.validityState = ValidityState({valid:true});
@@ -193,13 +187,13 @@ const validateNumber = function(value,variable) {
193
187
  }
194
188
  return this.whenInvalid(variable,value);
195
189
  }
196
- const number = ({coerce=false,required = false,whenInvalid = ifInvalid,min=-Infinity,max=Infinity,step = 1,allowNaN = true,...rest}) => {
190
+ const number = ({coerce=false,required = false,whenInvalid = ifInvalid,min=-Infinity,max=Infinity,step = 1,allowNaN = true,...rest}={}) => {
197
191
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
198
192
  if(typeof(required)!=="boolean") throw new TypeError(`required, ${JSON.stringify(required)}, must be a boolean`);
199
193
  if(typeof(whenInvalid)!=="function") throw new TypeError(`whenInvalid, ${whenInvalid}, must be a function`);
200
- if(typeof(min)!=="number") throw new TypeError(`min, ${JSON.stringify(min)}, must be a number`);
201
- if(typeof(max)!=="number") throw new TypeError(`max, ${JSON.stringify(max)}, must be a number`);
202
- if(typeof(step)!=="number") throw new TypeError(`step, ${JSON.stringify(step)}, must be a number`);
194
+ if(min!=null && typeof(min)!=="number") throw new TypeError(`min, ${JSON.stringify(min)}, must be a number`);
195
+ if(max!=null && typeof(max)!=="number") throw new TypeError(`max, ${JSON.stringify(max)}, must be a number`);
196
+ if(step!=null && typeof(step)!=="number") throw new TypeError(`step, ${JSON.stringify(step)}, must be a number`);
203
197
  if(typeof(allowNaN)!=="boolean") throw new TypeError(`step, ${JSON.stringify(allowNaN)}, must be a boolean`);
204
198
  if(rest.default!==undefined && typeof(rest.default)!=="number") throw new TypeError(`default, ${rest.default}, must be a number`);
205
199
  return {
@@ -215,14 +209,7 @@ const number = ({coerce=false,required = false,whenInvalid = ifInvalid,min=-Infi
215
209
  validate: validateNumber
216
210
  }
217
211
  }
218
- number.validate = validateNumber;
219
- number.whenInvalid = ifInvalid;
220
- number.min = -Infinity;
221
- number.max = Infinity;
222
- number.coerce = false;
223
- number.required = false;
224
- number.allowNaN = true;
225
- number.step = 1;
212
+
226
213
 
227
214
  const validateObject = function(value,variable) {
228
215
  if(value===undefined && variable.value===undefined) {
@@ -241,7 +228,7 @@ const validateObject = function(value,variable) {
241
228
  }
242
229
  return this.whenInvalid(variable,value);
243
230
  }
244
- const object = ({coerce=false, required = false,whenInvalid = ifInvalid,...rest}) => {
231
+ const object = ({coerce=false, required = false,whenInvalid = ifInvalid,...rest}={}) => {
245
232
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
246
233
  if(typeof(required)!=="boolean") throw new TypeError(`required, ${JSON.stringify(required)}, must be a boolean`);
247
234
  if(typeof(whenInvalid)!=="function") throw new TypeError(`whenInvalid, ${whenInvalid}, must be a function`);
@@ -255,10 +242,7 @@ const object = ({coerce=false, required = false,whenInvalid = ifInvalid,...rest}
255
242
  validate: validateObject
256
243
  }
257
244
  }
258
- object.validate = validateObject;
259
- object.whenInvalid = ifInvalid;
260
- object.coerce = false;
261
- object.required = false;
245
+
262
246
 
263
247
  const validateString = function(value,variable) {
264
248
  if(value===undefined && variable.value===undefined) {
@@ -281,7 +265,7 @@ const validateString = function(value,variable) {
281
265
  }
282
266
  return this.whenInvalid(variable,value);
283
267
  }
284
- const string = ({coerce=false, required = false,whenInvalid = ifInvalid, maxlength = Infinity, minlength = 0, pattern, ...rest}) => {
268
+ const string = ({coerce=false, required = false,whenInvalid = ifInvalid, maxlength = Infinity, minlength = 0, pattern,...rest}={}) => {
285
269
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
286
270
  if(typeof(required)!=="boolean") throw new TypeError(`required, ${JSON.stringify(required)}, must be a boolean`);
287
271
  if(typeof(whenInvalid)!=="function") throw new TypeError(`whenInvalid, ${whenInvalid}, must be a function`);
@@ -300,12 +284,7 @@ const string = ({coerce=false, required = false,whenInvalid = ifInvalid, maxleng
300
284
  validate: validateString
301
285
  }
302
286
  }
303
- string.validate = validateString;
304
- string.whenInvalid = ifInvalid;
305
- string.coerce = false;
306
- string.required = false;
307
- string.maxlength = Infinity;
308
- string.minlength = 0;
287
+
309
288
 
310
289
  const validateSymbol = function(value,variable) {
311
290
  if(value===undefined && variable.value===undefined) {
@@ -324,7 +303,7 @@ const validateSymbol = function(value,variable) {
324
303
  }
325
304
  return this.whenInvalid(variable,value);
326
305
  }
327
- const symbol = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest}) =>{
306
+ const symbol = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest}={}) =>{
328
307
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
329
308
  if(typeof(required)!=="boolean") throw new TypeError(`required, ${JSON.stringify(required)}, must be a boolean`);
330
309
  if(typeof(whenInvalid)!=="function") throw new TypeError(`whenInvalid, ${whenInvalid}, must be a function`);
@@ -338,10 +317,6 @@ const symbol = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest})
338
317
  validate: validateSymbol
339
318
  }
340
319
  }
341
- symbol.validate = validateSymbol;
342
- symbol.whenInvalid = ifInvalid;
343
- symbol.coerce = false;
344
- symbol.required = false;
345
320
 
346
321
  const remoteProxy = ({json, variable,config, reactive, component}) => {
347
322
  const type = typeof (config);
@@ -428,7 +403,7 @@ const put = (href,variable) => {
428
403
  })
429
404
  }
430
405
 
431
- const handleRemote = async ({variable, config, reactive, component},doput) => {
406
+ const handleRemote = async ({variable, functionalType, config=functionalType, component},doput) => {
432
407
  const type = typeof (config);
433
408
  let value;
434
409
  if (type === "string") {
@@ -444,14 +419,14 @@ const handleRemote = async ({variable, config, reactive, component},doput) => {
444
419
  if(!config.get || !config.put) {
445
420
  if(!href) throw new Error(`A remote path is required if no put function is provided for remote data`)
446
421
  if(!config.get) config.get = get;
447
- if(!config.put && reactive) config.put = put;
422
+ if(!config.put && variable.reactive) config.put = put;
448
423
  }
449
424
  value = (doput
450
425
  ? config.put(href,variable)
451
426
  : config.get(href,variable));
452
427
  if(config.ttl && !doput && !config.intervalId) {
453
428
  config.intervalId = setInterval(async () => {
454
- await handleRemote({variable, config, reactive, component});
429
+ await handleRemote({variable, config, component});
455
430
  //schedule();
456
431
  },config.ttl);
457
432
  }
@@ -459,13 +434,11 @@ const handleRemote = async ({variable, config, reactive, component},doput) => {
459
434
  }
460
435
  if(value) {
461
436
  const json = await value;
462
- //value.then((json) => {
463
- if (json && typeof (json) === "object" && reactive) {
464
- variable.value = remoteProxy({json, variable,config, reactive, component});
437
+ if (json && typeof (json) === "object" && variable.reactive) {
438
+ variable.value = remoteProxy({json, variable,config, component});
465
439
  } else {
466
440
  component.setVariableValue(variable.name,json);
467
441
  }
468
- //})
469
442
  }
470
443
  }
471
444
 
@@ -476,4 +449,6 @@ const remote = (config) => {
476
449
  }
477
450
  }
478
451
 
479
- export {ValidityState,any,array,boolean,number,object,string,symbol,remote,reviver}
452
+ const remoteGenerator = handleRemote;
453
+
454
+ export {ValidityState,any,array,boolean,number,object,remote,remoteGenerator,string,symbol,reviver}