lightview 1.4.10-b → 1.6.3-b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lightview.js CHANGED
@@ -85,7 +85,7 @@ const {observe} = (() => {
85
85
  return observer;
86
86
  }
87
87
  const coerce = (value, toType) => {
88
- if (value + "" === "null" || value + "" === "undefined") return value;
88
+ if (value + "" === "null" || value + "" === "undefined" || toType==="any") return value;
89
89
  const type = typeof (value);
90
90
  if (type === toType) return value;
91
91
  if (toType === "number") return parseFloat(value + "");
@@ -103,7 +103,7 @@ const {observe} = (() => {
103
103
  if (toType === "string") return value + "";
104
104
  const isfunction = typeof (toType) === "function";
105
105
  if ((toType === "object" || isfunction)) {
106
- if (type === "object") {
106
+ if (type === "object" && isfunction) {
107
107
  if (value instanceof toType) return value;
108
108
  }
109
109
  if (type === "string") {
@@ -164,6 +164,9 @@ const {observe} = (() => {
164
164
  return JSON.stringify([...target]);
165
165
  }
166
166
  }
167
+ if(target instanceof Date) {
168
+ return Reflect.get(target,property);
169
+ }
167
170
  let value = target[property];
168
171
  const type = typeof (value);
169
172
  if (CURRENTOBSERVER && typeof (property) !== "symbol" && type !== "function") {
@@ -220,12 +223,14 @@ const {observe} = (() => {
220
223
  const createVarsProxy = (vars, component, constructor) => {
221
224
  return new Proxy(vars, {
222
225
  get(target, property) {
226
+ if(target instanceof Date) {
227
+ return Reflect.get(target,property);
228
+ }
223
229
  let {value} = target[property] || {};
224
230
  if (typeof (value) === "function") return value.bind(target);
225
231
  return value;
226
232
  },
227
233
  set(target, property, newValue) {
228
- //if(newValue && typeof(newValue)==="object" && newValue instanceof Promise) newValue = await newValue;
229
234
  const event = new VariableEvent({variableName: property, value: newValue});
230
235
  if (target[property] === undefined) {
231
236
  target[property] = {type: "any", value: newValue}; // should we allow this, do first to prevent loops
@@ -233,19 +238,21 @@ const {observe} = (() => {
233
238
  if (event.defaultPrevented) delete target[property].value;
234
239
  return true;
235
240
  }
236
- const {type, value, shared, exported, constant, reactive, remote} = target[property];
241
+ const variable = target[property],
242
+ {type, value, shared, exported, constant, reactive, remote} = variable;
237
243
  if (constant) throw new TypeError(`${property}:${type} is a constant`);
244
+ newValue = type.validate ? type.validate(newValue,target[property]) : coerce(newValue,type);
238
245
  const newtype = typeof (newValue),
239
246
  typetype = typeof (type);
240
- if (newValue == null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
247
+ if (newValue == null || type === "any" || (newtype === type && typetype==="string") || (typetype === "function" && (newValue && newtype === "object" && newValue instanceof type) || variable.validityState?.valid)) {
241
248
  if (value !== newValue) {
242
249
  event.oldValue = value;
243
250
  target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
244
251
  target.postEvent.value("change", event);
245
252
  if (event.defaultPrevented) {
246
253
  target[property].value = value;
247
- } else if(remote) {
248
- handleRemote({variable:target[property],remote,reactive},true);
254
+ } else if(remote && remote.put) {
255
+ remote.handleRemote({variable,config:remote.config,reactive},true);
249
256
  }
250
257
  }
251
258
  return true;
@@ -261,22 +268,21 @@ const {observe} = (() => {
261
268
  });
262
269
  }
263
270
  const createObserver = (domNode, framed) => {
264
- const observer = new MutationObserver((mutations) => {
271
+ const mobserver = new MutationObserver((mutations) => {
265
272
  mutations.forEach((mutation) => {
273
+ const target = mutation.target;
266
274
  if (mutation.type === "attributes") {
267
275
  //if (framed) debugger;
268
276
  const name = mutation.attributeName,
269
- target = mutation.target,
270
277
  value = target.getAttribute(name);
271
278
  if (framed && name === "message" && target instanceof IFrameElement) {
272
- if (value) console.log("message", value);
279
+ //if (value) console.log("message", value);
273
280
  target.removeAttribute(name);
274
281
  target.dispatchEvent(new CustomEvent("message", {detail: JSON.parse(value)}))
275
282
  }
276
283
  if (target.observedAttributes && target.observedAttributes.includes(name)) {
277
284
  if (value !== mutation.oldValue) {
278
- target.setValue(name, value);
279
- if (target.attributeChangedCallback) target.attributeChangedCallback(name, value, mutation.oldValue);
285
+ target.setVariableValue(name, value);
280
286
  }
281
287
  }
282
288
  } else if (mutation.type === "childList") {
@@ -286,11 +292,13 @@ const {observe} = (() => {
286
292
  for (const target of mutation.addedNodes) {
287
293
  if (target.connectedCallback) target.connectedCallback();
288
294
  }
295
+ } else if(mutation.type === "characterData") {
296
+ if(target.characterDataMutationCallback) target.characterDataMutationCallback(target,mutation.oldValue,target.textContent);
289
297
  }
290
298
  });
291
299
  });
292
- observer.observe(domNode, {subtree: true, childList: true});
293
- return observer;
300
+ mobserver.observe(domNode, {subtree: true, childList: true});
301
+ return mobserver;
294
302
  }
295
303
  const querySelectorAll = (node, selector) => {
296
304
  const nodes = [...node.querySelectorAll(selector)],
@@ -313,17 +321,17 @@ const {observe} = (() => {
313
321
  nodes.push(node);
314
322
  } else if (node.nodeType === Node.ELEMENT_NODE) {
315
323
  let skip, pushed;
316
- [...node.attributes].forEach((attr) => {
317
- if (attr.value.includes("${")) {
318
- attr.template ||= attr.value;
319
- pushed = true;
320
- nodes.push(node);
321
- } else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
322
- skip = attr.name.includes("l-for:");
323
- pushed = true;
324
- nodes.push(node)
325
- }
326
- })
324
+ [...node.attributes].forEach((attr) => {
325
+ if (attr.value.includes("${")) {
326
+ attr.template ||= attr.value;
327
+ pushed = true;
328
+ nodes.push(node);
329
+ } else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
330
+ skip = attr.name.includes("l-for:");
331
+ pushed = true;
332
+ nodes.push(node)
333
+ }
334
+ })
327
335
  if (!pushed && node.getAttribute("type") === "radio") nodes.push(node);
328
336
  if (!skip && !node.shadowRoot) nodes.push(...getNodes(node));
329
337
  }
@@ -339,24 +347,16 @@ const {observe} = (() => {
339
347
  try {
340
348
  let value = Function("context", "with(context) { return `" + Lightview.sanitizeTemplate(template) + "` }")(component.varsProxy);
341
349
  value = node.nodeType === Node.TEXT_NODE || !safe ? value : Lightview.escapeHTML(value);
342
- if (type === "string") return value;
350
+ if (type === "string") return value==="undefined" ? undefined : value;
343
351
  node.nodeValue = value == "null" || value == "undefined" ? "" : value;
344
352
  } catch (e) {
345
- console.warn(e);
353
+ //console.warn(e);
346
354
  if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
355
+ return undefined;
347
356
  }
348
357
  }
349
358
  return node?.nodeValue;
350
359
  }
351
- const render = (hasTemplate, render) => {
352
- let observer;
353
- if (hasTemplate) {
354
- if (observer) observer.cancel();
355
- observer = observe(render)
356
- } else {
357
- render();
358
- }
359
- }
360
360
  const inputTypeToType = (inputType) => {
361
361
  if (!inputType) return "any"
362
362
  if (["text", "tel", "email", "url", "search", "radio", "color", "password"].includes(inputType)) return "string";
@@ -376,16 +376,18 @@ const {observe} = (() => {
376
376
  if (bound.has(input)) return;
377
377
  bound.add(input);
378
378
  const inputtype = input.tagName === "SELECT" || input.tagName === "TEXTAREA" ? "text" : input.getAttribute("type"),
379
- type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
380
- deflt = input.getAttribute("default");
379
+ type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype);
381
380
  value ||= input.getAttribute("value");
382
381
  let variable = component.vars[variableName] || {type};
383
382
  if (type !== variable.type) {
384
383
  if (variable.type === "any" || variable.type === "unknown") variable.type = type;
385
384
  else throw new TypeError(`Attempt to bind <input name="${variableName}" type="${type}"> to variable ${variableName}:${variable.type}`)
386
385
  }
387
- component.variables({[variableName]: type});
388
- if(inputtype!=="radio") component.setValue(variableName, value);
386
+ component.variables({[variableName]: type},{reactive:true});
387
+ if(inputtype!=="radio") {
388
+ if(value.includes("${")) input.attributes.value.value = "";
389
+ else component.setVariableValue(variableName, coerce(value,type));
390
+ }
389
391
  let eventname = "change";
390
392
  if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
391
393
  eventname = "input";
@@ -415,23 +417,19 @@ const {observe} = (() => {
415
417
  }
416
418
  }
417
419
  let reserved = {
418
- any: {value: "any", constant: true},
419
- boolean: {value: "boolean", constant: true},
420
- string: {value: "string", constant: true},
421
- number: {value: "number", constant: true},
422
- object: {value: "object", constant: true},
423
420
  observed: {value: true, constant: true},
424
421
  reactive: {value: true, constant: true},
425
422
  shared: {value: true, constant: true},
426
423
  exported: {value: true, constant: true},
427
- imported: {value: true, constant: true},
428
- remote: {}
424
+ imported: {value: true, constant: true}
429
425
  };
430
426
  const createClass = (domElementNode, {observer, framed}) => {
431
427
  const instances = new Set(),
432
428
  dom = domElementNode.tagName === "TEMPLATE"
433
429
  ? domElementNode.content.cloneNode(true)
434
- : domElementNode.cloneNode(true);
430
+ : domElementNode.cloneNode(true),
431
+ observedAttributes = [];
432
+ observedAttributes.add = function(name) { observedAttributes.includes(name) || observedAttributes.push(name); }
435
433
  if (domElementNode.tagName === "TEMPLATE") domElementNode = domElementNode.cloneNode(true);
436
434
  return class CustomElement extends HTMLElement {
437
435
  static get instances() {
@@ -441,23 +439,20 @@ const {observe} = (() => {
441
439
  constructor() {
442
440
  super();
443
441
  instances.add(this);
444
- observer ||= createObserver(this, framed);
445
442
  const currentComponent = this,
446
443
  shadow = this.attachShadow({mode: "open"}),
447
444
  eventlisteners = {};
445
+ // needs to be local to the instance
446
+ Object.defineProperty(this,"changeListener",{value:
447
+ function({variableName, value}) {
448
+ if (currentComponent.changeListener.targets.has(variableName)) {
449
+ value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
450
+ if (value == null) removeComponentAttribute(currentComponent, variableName);
451
+ else setComponentAttribute(currentComponent, variableName, value);
452
+ }
453
+ }});
448
454
  this.vars = {
449
455
  ...reserved,
450
- changeListener: {
451
- value: ({variableName, value}) => {
452
- if (currentComponent.vars.changeListener.value.targets.has(variableName)) {
453
- value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
454
- if (value == null) removeComponentAttribute(this, variableName);
455
- else setComponentAttribute(this, variableName, value);
456
- }
457
- },
458
- type: "function",
459
- constant: true
460
- },
461
456
  addEventListener: {
462
457
  value: (eventName, listener) => {
463
458
  const listeners = eventlisteners[eventName] ||= new Set();
@@ -470,9 +465,10 @@ const {observe} = (() => {
470
465
  constant: true
471
466
  },
472
467
  postEvent: {
473
- value: (eventName, event) => {
468
+ value: (eventName, event = {}) => {
474
469
  //event = {...event}
475
470
  event.type = eventName;
471
+ event.target = currentComponent;
476
472
  eventlisteners[eventName]?.forEach((f) => f(event));
477
473
  },
478
474
  type: "function",
@@ -482,8 +478,8 @@ const {observe} = (() => {
482
478
  };
483
479
  this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
484
480
  this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
485
- this.vars.changeListener.value.targets = new Set();
486
- this.varsProxy.addEventListener("change", this.varsProxy.changeListener);
481
+ this.changeListener.targets = new Set();
482
+ this.varsProxy.addEventListener("change", this.changeListener);
487
483
  if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported: true});
488
484
  ["getElementById", "querySelector", "querySelectorAll"]
489
485
  .forEach((fname) => {
@@ -514,104 +510,113 @@ const {observe} = (() => {
514
510
  shadow = ctx.shadowRoot;
515
511
  for (const attr of this.defaultAttributes) this.hasAttribute(attr.name) || this.setAttribute(attr.name, attr.value);
516
512
  const scripts = shadow.querySelectorAll("script"),
517
- promises = [];
518
- for (const script of scripts) {
513
+ promises = [],
514
+ scriptpromises = [];
515
+ for (const script of [...scripts]) {
519
516
  if (script.attributes.src?.value?.includes("/lightview.js")) continue;
520
- if (script.className !== "lightview" && !((script.getAttribute("type") || "").includes("lightview/"))) continue;
521
- const scriptid = Math.random() + "",
522
- currentScript = document.createElement("script");
517
+ const currentScript = document.createElement("script");
518
+ if (script.className !== "lightview" && !((script.attributes.type?.value || "").includes("lightview/"))) {
519
+ for (const attr of script.attributes) currentScript.setAttribute(attr.name,attr.value);
520
+ scriptpromises.push(new Promise((resolve) => {
521
+ currentScript.onload = () => resolve();
522
+ }))
523
+ shadow.appendChild(currentScript);
524
+ currentScript.remove();
525
+ continue;
526
+ };
527
+ const scriptid = Math.random() + "";
523
528
  for (const attr of script.attributes) {
524
529
  currentScript.setAttribute(attr.name, attr.name === "type" ? attr.value.replace("lightview/", "") : attr.value);
525
530
  }
526
531
  currentScript.classList.remove("lightview");
527
532
  const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
528
- currentScript.innerHTML = `Object.getPrototypeOf(async function(){}).constructor('if(window["${scriptid}"]?.ctx) { with(window["${scriptid}"].ctx) { ${text}; } window["${scriptid}"](); }')(); `;
533
+ currentScript.innerHTML = `Object.getPrototypeOf(async function(){}).constructor('if(window["${scriptid}"]?.ctx) { const ctx = window["${scriptid}"].ctx; { with(ctx) { ${text}; } } }')().then(() => window["${scriptid}"]()); `;
529
534
  let resolver;
530
- promises.push(new Promise((resolve) => resolver = resolve));
531
535
  window[scriptid] = () => {
532
536
  delete window[scriptid];
533
537
  currentScript.remove();
534
538
  resolver();
535
539
  }
536
540
  window[scriptid].ctx = ctx.varsProxy;
537
- ctx.appendChild(currentScript);
541
+ promises.push(new Promise((resolve) => {
542
+ resolver = resolve;
543
+ // wait for all regular scripts to load first
544
+ Promise.all(scriptpromises).then(() => shadow.appendChild(currentScript));
545
+ }));
538
546
  }
539
547
  Promise.all(promises).then(() => {
540
548
  const nodes = getNodes(ctx);
541
549
  nodes.forEach((node) => {
542
550
  if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
543
- render(!!node.template, () => resolveNodeOrText(node, this))
551
+ observe(() => resolveNodeOrText(node, this));
544
552
  } else if (node.nodeType === Node.ELEMENT_NODE) {
545
553
  // resolve the value before all else;
546
- const attr = node.attributes.value;
547
- if (attr && attr.template) {
548
- render(!!attr.template, () => {
549
- const value = resolveNodeOrText(attr, this),
554
+ const attr = node.attributes.value,
555
+ template = attr?.template;
556
+ if (attr && template) {
557
+ let value = resolveNodeOrText(attr, this),
550
558
  eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
551
- if (node.attributes.value) {
552
- const template = attr.template;
559
+ const template = attr.template;
560
+ if (template) {
553
561
  if (/\$\{[a-zA-z_]+\}/g.test(template)) {
554
562
  const name = template.substring(2, template.length - 1);
555
- bindInput(node, name, this, value);
556
- }
557
- }
558
- if (eltype === "checkbox") {
559
- if (coerce(value, "boolean") === true) {
560
- node.setAttribute("checked", "");
561
- node.checked = true;
562
- } else {
563
- node.removeAttribute("checked");
564
- node.checked = false;
565
- }
566
- const vname = resolveNodeOrText(node.attributes.name, ctx);
567
- if (vname) ctx.setValue(vname, node.checked, {coerceTo: "boolean"});
568
- }
569
- if (node.tagName === "SELECT") {
570
- let values = [value];
571
- if (node.hasAttribute("multiple")) values = coerce(value, Array);
572
- [...node.querySelectorAll("option")].forEach(async (option) => {
573
- if (option.hasAttribute("value")) {
574
- if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
575
- option.setAttribute("selected", "");
576
- option.selected = true;
577
- }
578
- } else if (values.includes(resolveNodeOrText(option.innerText, ctx))) {
579
- option.setAttribute("selected", "");
580
- option.selected = true;
563
+ if(!this.vars[name] || this.vars[name].reactive) {
564
+ bindInput(node, name, this, value);
581
565
  }
582
- })
583
- }
584
- });
585
- }
586
- [...node.attributes].forEach(async (attr) => {
587
- if (attr.name === "value" && attr.template) return;
588
- const {name, value} = attr;
589
- if (name === "type") {
590
- if (value === "radio" && node.attributes.name) {
591
- const name = resolveNodeOrText(node.attributes.name, ctx);
592
- for (const vname of this.getVariableNames()) {
593
- if (vname === name) {
594
- render(true, () => {
595
- const name = resolveNodeOrText(node.attributes.name, ctx),
596
- varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
597
- if (node.attributes.value && varvalue == resolveNodeOrText(node.attributes.value, ctx)) {
566
+ }
567
+ observe(() => {
568
+ const value = resolveNodeOrText(template, ctx);
569
+ if(value!==undefined) {
570
+ if (eltype === "checkbox") {
571
+ if (coerce(value, "boolean") === true) {
598
572
  node.setAttribute("checked", "");
599
573
  node.checked = true;
600
574
  } else {
601
575
  node.removeAttribute("checked");
602
576
  node.checked = false;
603
577
  }
604
- });
605
- bindInput(node, name, ctx);
606
- break;
578
+ } else if (node.tagName === "SELECT") {
579
+ let values = [value];
580
+ if (node.hasAttribute("multiple")) values = coerce(value, Array);
581
+ [...node.querySelectorAll("option")].forEach(async (option) => {
582
+ if (option.hasAttribute("value")) {
583
+ if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
584
+ option.setAttribute("selected", "");
585
+ option.selected = true;
586
+ }
587
+ } else if (values.includes(resolveNodeOrText(option.innerText, ctx))) {
588
+ option.setAttribute("selected", "");
589
+ option.selected = true;
590
+ }
591
+ })
592
+ } else if (eltype!=="radio") {
593
+ attr.value = value;
594
+ }
607
595
  }
608
- }
596
+ });
609
597
  }
598
+ }
599
+ [...node.attributes].forEach(async (attr) => {
600
+ if (attr.name === "value" && attr.template) return;
601
+ const {name, value} = attr,
602
+ vname = node.attributes.name?.value;
603
+ if (name === "type" && value=="radio" && vname) {
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
+ });
610
615
  }
611
616
 
612
617
  const [type, ...params] = name.split(":");
613
618
  if (type === "") { // name is :something
614
- render(!!attr.template, () => {
619
+ observe(() => {
615
620
  const value = attr.value;
616
621
  if (params[0]) {
617
622
  if (value === "true") node.setAttribute(params[0], "")
@@ -627,20 +632,20 @@ const {observe} = (() => {
627
632
  })
628
633
  } else if (type === "l-on") {
629
634
  let listener;
630
- render(!!attr.template, () => {
635
+ observe(() => {
631
636
  const value = resolveNodeOrText(attr, this);
632
637
  if (listener) node.removeEventListener(params[0], listener);
633
638
  listener = this[value] || window[value] || Function(value);
634
639
  addListener(node, params[0], listener);
635
640
  })
636
641
  } else if (type === "l-if") {
637
- render(!!attr.template, () => {
642
+ observe(() => {
638
643
  const value = resolveNodeOrText(attr, this);
639
644
  node.style.setProperty("display", value === "true" ? "revert" : "none");
640
645
  })
641
646
  } else if (type === "l-for") {
642
647
  node.template ||= node.innerHTML;
643
- render(!!attr.template, () => {
648
+ observe(() => {
644
649
  const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
645
650
  value = resolveNodeOrText(attr, this),
646
651
  coerced = coerce(value, what === "each" ? Array : "object"),
@@ -667,61 +672,53 @@ const {observe} = (() => {
667
672
  else node.appendChild(parsed.body.firstChild);
668
673
  }
669
674
  })
670
- } else if (attr.template) {
671
- render(!!attr.template, () => resolveNodeOrText(attr, this));
675
+ } else if(attr.template) {
676
+ observe(() => {
677
+ resolveNodeOrText(attr, this);
678
+ })
672
679
  }
673
680
  })
674
681
  }
675
682
  })
676
683
  shadow.normalize();
677
- observer.observe(ctx, {attributeOldValue: true});
678
- if (ctx.hasOwnProperty("connectedCallback")) ctx.connectedCallback();
684
+ observer ||= createObserver(ctx, framed);
685
+ observer.observe(ctx, {attributeOldValue: true, subtree:true, characterData:true, characterDataOldValue:true});
686
+ ctx.vars.postEvent.value("connected");
679
687
  })
680
688
  }
681
-
682
- adopted(value) {
683
- this.adoptedCallback = value;
684
- //Object.defineProperty(this, "adoptedCallback", {configurable: true, writable: true, value});
689
+ adoptedCallback(callback) {
690
+ this.vars.postEvent.value("adopted");
685
691
  }
686
-
687
- connected(callback) {
688
- this.connectedCallback = callback;
689
- //Object.defineProperty(this, "connectedCallback", {configurable: true, writable: true, value});
692
+ disconnectedCallback() {
693
+ this.vars.postEvent.value("disconnected");
690
694
  }
691
-
692
- attributeChanged(callback) {
693
- this.attributeChangedCallback = callback;
694
- //Object.defineProperty(this, "attributeChangedCallback", {configurable: true, writable: true, value});
695
+ get observedAttributes() {
696
+ return CustomElement.observedAttributes;
695
697
  }
696
-
697
- disconnected(callback) {
698
- Object.defineProperty(this, "disconnectedCallback", {
699
- configurable: true,
700
- writable: true,
701
- value: () => {
702
- value();
703
- super.disconnectedCallback(callback);
704
- }
705
- });
698
+ static get observedAttributes() {
699
+ return observedAttributes;
706
700
  }
707
701
 
708
702
  getVariableNames() {
709
- return Object.keys(this.vars).filter((name) => {
710
- return !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name)
711
- })
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;
712
709
  }
713
710
 
714
- setValue(variableName, value, {coerceTo = typeof (value)} = {}) {
711
+ setVariableValue(variableName, value, {coerceTo = typeof (value)} = {}) {
715
712
  if (!this.isConnected) {
716
713
  instances.delete(this);
717
714
  return false;
718
715
  }
719
716
  let {type} = this.vars[variableName] || {};
720
717
  if (type) {
721
- value = coerce(value, type);
722
718
  if (this.varsProxy[variableName] !== value) {
723
719
  const variable = this.vars[variableName];
724
720
  if (variable.shared) {
721
+ value = type.validate ? type.validate(value,variable) : coerce(value,coerceTo);
725
722
  const event = new VariableEvent({
726
723
  variableName: variableName,
727
724
  value: value,
@@ -740,20 +737,29 @@ const {observe} = (() => {
740
737
  return false;
741
738
  }
742
739
 
743
- getValue(variableName) {
740
+ getVariableValue(variableName) {
744
741
  return this.vars[variableName]?.value;
745
742
  }
746
743
 
747
- 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}
748
745
  const addEventListener = this.varsProxy.addEventListener;
749
746
  if (variables !== undefined) {
750
747
  Object.entries(variables)
751
748
  .forEach(([key, type]) => {
752
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
+ }
753
756
  if (observed || imported) {
754
757
  variable.value = this.hasAttribute(key) ? coerce(this.getAttribute(key), variable.type) : variable.value;
755
- variable.observed = observed;
756
758
  variable.imported = imported;
759
+ if(variable.observed) {
760
+ variable.observed = observed;
761
+ this.observedAttributes.add(key);
762
+ }
757
763
  }
758
764
  if (reactive) {
759
765
  variable.reactive = true;
@@ -762,21 +768,21 @@ const {observe} = (() => {
762
768
  if (shared) {
763
769
  variable.shared = true;
764
770
  addEventListener("change", ({variableName, value}) => {
765
- if (this.vars[variableName]?.shared) {
766
- this.siblings.forEach((instance) => instance.setValue(variableName, value))
767
- }
771
+ if (this.vars[variableName]?.shared) this.siblings.forEach((instance) => instance.setVariableValue(variableName, value))
768
772
  })
769
773
  }
770
774
  if (exported) {
771
775
  variable.exported = true;
772
776
  // in case the export goes up to an iframe
773
777
  if (variable.value != null) setComponentAttribute(this, key, variable.value);
774
- this.vars.changeListener.value.targets.add(key);
778
+ this.changeListener.targets.add(key);
775
779
  }
776
780
  if (remote) {
781
+ if(typeof(remote)==="function") remote = remote(`./${key}`);
777
782
  variable.remote = remote;
778
- handleRemote({variable, remote, reactive});
783
+ remote.handleRemote({variable, config:remote.config, reactive,component:this});
779
784
  }
785
+ if(type.validate) type.validate(variable.value,variable);
780
786
  });
781
787
  }
782
788
  return Object.entries(this.vars)
@@ -785,163 +791,14 @@ const {observe} = (() => {
785
791
  return result;
786
792
  }, {});
787
793
  }
788
-
789
- constants(variables) {
790
- if (variables !== undefined) {
791
- Object.entries(variables)
792
- .forEach(([key, value]) => {
793
- const type = typeof (value) === "function" ? value : typeof (value),
794
- variable = this.vars[key];
795
- if (variable !== undefined) throw new TypeError(`${variable.constant ? "const" : "let"} ${key}:${variable.type} already declared.`);
796
- if (value === undefined) throw new TypeError(`const ${key}:undefined must be initialized.`);
797
- this.vars[key] = {type, value, constant: true};
798
- })
799
- }
800
- return Object.entries(this.vars)
801
- .reduce((result, [key, variable]) => {
802
- if (variable.constant) result[key] = {...variable};
803
- return result;
804
- }, {});
805
- }
806
- }
807
- }
808
-
809
- const remoteProxy = ({json, variable,remote, reactive}) => {
810
- const type = typeof (remote);
811
- return new Proxy(json, {
812
- get(target,property) {
813
- if(property==="__remoteProxytarget__") return json;
814
- return target[property];
815
- },
816
- async set(target, property, value) {
817
- if(value && typeof(value)==="object" && value instanceof Promise) value = await value;
818
- const oldValue = target[property];
819
- if (oldValue !== value) {
820
- let remotevalue;
821
- if (type === "string") {
822
- const href = new URL(remote,window.location.href).href;
823
- remotevalue = patch({target,property,value,oldValue},href);
824
- } else if(remote && type==="object") {
825
- let href;
826
- if(remote.path) href = new URL(remote.path,window.location.href).href;
827
- if(!remote.patch) {
828
- if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
829
- remote.patch = patch;
830
- }
831
- remotevalue = remote.patch({target,property,value,oldValue},href);
832
- }
833
- if(remotevalue) {
834
- await remotevalue.then((newjson) => {
835
- if (newjson && typeof (newjson) === "object" && reactive) {
836
- const target = variable.value?.__reactorProxyTarget__ ? json : variable.value;
837
- Object.entries(newjson).forEach(([key,newValue]) => {
838
- if(target[key]!==newValue) {
839
- target[key] = newValue;
840
- }
841
- })
842
- Object.keys(target).forEach((key) => {
843
- if(!(key in newjson)) {
844
- delete target[key];
845
- }
846
- });
847
- if(variable.value?.__reactorProxyTarget__) {
848
- const dependents = variable.value.__dependents__,
849
- observers = dependents[property] || [];
850
- [...observers].forEach((f) => {
851
- if (f.cancelled) dependents[property].delete(f);
852
- else f();
853
- })
854
- }
855
- } else {
856
- variable.value = json;
857
- }
858
- })
859
- }
860
- }
861
- return true;
862
- }
863
- })
864
- }
865
-
866
- const patch = ({target,property,value,oldValue},href) => {
867
- return fetch(href, {
868
- method: "PATCH",
869
- body: JSON.stringify({property,value,oldValue}),
870
- headers: {
871
- "Content-Type": "application/json"
872
- }
873
- }).then((response) => {
874
- if (response.status < 400) return response.json();
875
- })
876
- }
877
-
878
- const get = ({variable,remote,reactive},path) => {
879
- return fetch(path)
880
- .then((response) => {
881
- if (response.status < 400) return response.json();
882
- })
883
- }
884
-
885
- const put = ({variable,remote,reactive},href) => {
886
- return fetch(href, {
887
- method: "PUT",
888
- body: JSON.stringify(variable.value),
889
- headers: {
890
- "Content-Type": "application/json"
891
- }
892
- }).then((response) => {
893
- if (response.status === 200) {
894
- return response.json();
895
- }
896
- })
897
- }
898
-
899
- const handleRemote = async ({variable, remote, reactive},doput) => {
900
- const type = typeof (remote);
901
- let value;
902
- if (type === "string") {
903
- const href = new URL(remote,window.location.href).href;
904
- value = (doput
905
- ? put({variable, remote, reactive},href)
906
- : get({variable, remote, reactive},href));
907
- if(variable.value===undefined) variable.value = value;
908
- } else if (remote && type === "object") {
909
- let href;
910
- if(remote.path) href = new URL(remote.path,window.location.href).href;
911
- if(!remote.get || !remote.put) {
912
- if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
913
- if(!remote.get) remote.get = get;
914
- if(!remote.put) remote.put = put;
915
- }
916
- value = (doput
917
- ? remote.put({variable, remote, reactive},href)
918
- : remote.get({variable, remote, reactive},href));
919
- if(remote.ttl && !doput && !remote.intervalId) {
920
- remote.intervalId = setInterval(async () => {
921
- await handleRemote({variable, remote, reactive});
922
- })
923
- }
924
- if(variable.value===undefined) variable.value = value;
925
- }
926
- if(value) {
927
- variable.value = await value.then((json) => {
928
- if (json && typeof (json) === "object" && reactive) {
929
- return remoteProxy({json, variable,remote, reactive})
930
- } else {
931
- return json;
932
- }
933
- })
934
794
  }
935
795
  }
936
796
 
937
797
  const createComponent = (name, node, {framed, observer} = {}) => {
938
798
  let ctor = customElements.get(name);
939
799
  if (ctor) {
940
- if (framed && !ctor.lightviewFramed) {
941
- ctor.lightviewFramed = true;
942
- } else {
943
- console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
944
- }
800
+ if (framed && !ctor.lightviewFramed) ctor.lightviewFramed = true;
801
+ else console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
945
802
  return ctor;
946
803
  }
947
804
  ctor = createClass(node, {observer, framed});
@@ -951,7 +808,6 @@ const {observe} = (() => {
951
808
  }
952
809
  Lightview.customElements = new Map();
953
810
  Lightview.createComponent = createComponent;
954
- //Object.defineProperty(Lightview, "createComponent", {writable: true, configurable: true, value: createComponent})
955
811
  const importLink = async (link, observer) => {
956
812
  const url = (new URL(link.getAttribute("href"), window.location.href)),
957
813
  as = link.getAttribute("as") || getNameFromPath(url.pathname);
@@ -1046,21 +902,19 @@ const {observe} = (() => {
1046
902
  postMessage({type: "setAttribute", argsList: ["height", height + 20]});
1047
903
  }
1048
904
  resize();
1049
- onresize(document.body, () => {
1050
- resize();
1051
- });
905
+ onresize(document.body, () => resize());
1052
906
  return
1053
907
  }
1054
908
  if (type === "setAttribute") {
1055
909
  const [name, value] = [...argsList],
1056
910
  variable = document.body.vars[name];
1057
- if (variable && variable.imported) document.body.setValue(name, value);
911
+ if (variable && variable.imported) document.body.setVariableValue(name, value);
1058
912
  return;
1059
913
  }
1060
914
  if (type === "removeAttribute") {
1061
915
  const [name] = argsList[0],
1062
916
  variable = document.body.vars[name];
1063
- if (variable && variable.imported) document.body.setValue(name, undefined);
917
+ if (variable && variable.imported) document.body.setVariableValue(name, undefined);
1064
918
 
1065
919
  }
1066
920
  });
@@ -1098,9 +952,7 @@ const {observe} = (() => {
1098
952
  }
1099
953
  if (type === "setAttribute") {
1100
954
  const [name, value] = [...argsList];
1101
- if (iframe.getAttribute(name) !== value + "") {
1102
- iframe.setAttribute(name, value);
1103
- }
955
+ if (iframe.getAttribute(name) !== value + "") iframe.setAttribute(name, value);
1104
956
  return;
1105
957
  }
1106
958
  if (type === "removeAttribute") {