lightview 1.2.1-b → 1.4.0-b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # lightview v1.2.1.b (BETA)
1
+ # lightview v1.4.0b (BETA)
2
2
 
3
- Small, simple, powerful web UI creation ...
3
+ Small, simple, powerful web UI and micro front end creation ...
4
+
5
+ Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 6K (minified/gzipped).
4
6
 
5
7
  See the docs and examples at [https://lightview.dev](https://lightview.dev).
6
8
 
package/counter.html ADDED
@@ -0,0 +1,30 @@
1
+ <head>
2
+ <title>Counter</title>
3
+ <script src="../lightview.js?as=x-body"></script>
4
+ </head>
5
+
6
+ <body>
7
+ <p>
8
+ <button l-on:click="bump">Click count:${count}</button>
9
+ </p>
10
+
11
+ <script type="lightview/module">
12
+ self.variables({
13
+ count: number
14
+ }, {
15
+ reactive
16
+ });
17
+ debugger;
18
+ count = 0;
19
+ self.bump = () => count++;
20
+ </script>
21
+
22
+ <style>
23
+ button {
24
+ margin: 20px;
25
+ background: gray
26
+ }
27
+ </style>
28
+ </body>
29
+
30
+ </html>
@@ -0,0 +1,77 @@
1
+ <!DOCTYPE html>
2
+
3
+ <head>
4
+ <title>Directives</title>
5
+ <script src="./lightview.js?as=x-body"></script>
6
+ </head>
7
+
8
+ <body>
9
+
10
+ <div style="margin:20px">
11
+ <p>
12
+ Show: <input type="checkbox" value="${on}">
13
+ <div l-if="${on}">
14
+ Now you've done it. You've exposed me.
15
+ </div>
16
+ </p>
17
+ <p>
18
+
19
+ <p>
20
+ How would you like that burger?<br>
21
+ <select value="${options}" multiple>
22
+ <option>lettuce</option>
23
+ <option>tomato</option>
24
+ <option>cheese</option>
25
+ </select>
26
+ </p>
27
+
28
+
29
+ For (defaults to each)
30
+ <ul l-for:each="${options}">
31
+ <li>${index}:${item}</li>
32
+ </ul>
33
+ For Each
34
+ <ul l-for:each="${options}">
35
+ <li>${index}:${item}</li>
36
+ </ul>
37
+ For Values
38
+ <ul l-for:values="${options}">
39
+ <li>${item}:${index}</li>
40
+ </ul>
41
+ For Keys
42
+ <ul l-for:keys="${options}">
43
+ <li>${item}</li>
44
+ </ul>
45
+ For Entries
46
+ <ul l-for:entries="${options}">
47
+ <li>${item[0]}:${item[1]}</li>
48
+ </ul>
49
+
50
+ Variable Values
51
+ <p id="variables"></p>
52
+ </div>
53
+ <script type="lightview/module">
54
+ self.variables({on:boolean,options:Array},{reactive});
55
+
56
+ on = true;
57
+ options = ["lettuce"];
58
+
59
+ // demo instrumentation
60
+ const variableValues = () => {
61
+ const el = self.getElementById("variables");
62
+ while(el.lastElementChild) el.lastElementChild.remove();
63
+ self.getVariableNames().forEach((name) => {
64
+ const line = document.createElement("div");
65
+ line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
66
+ el.appendChild(line);
67
+ });
68
+ };
69
+ variableValues();
70
+ addEventListener("change",()=> {
71
+ variableValues()
72
+ });
73
+
74
+ </script>
75
+ </body>
76
+
77
+ </html>
package/lightview.js CHANGED
@@ -29,6 +29,10 @@ const Lightview = {};
29
29
  const {observe} = (() => {
30
30
  let CURRENTOBSERVER;
31
31
  const parser = new DOMParser();
32
+
33
+ const addListener = (node,eventName,callback) => {
34
+ node.addEventListener(eventName,callback); // just used to make code footprint smaller
35
+ }
32
36
  const anchorHandler = async (event) => {
33
37
  event.preventDefault();
34
38
  const target = event.target;
@@ -57,21 +61,22 @@ const {observe} = (() => {
57
61
  }
58
62
  CURRENTOBSERVER = null;
59
63
  }
64
+
60
65
  observer.cancel = () => observer.cancelled = true;
61
66
  observer();
62
67
  return observer;
63
68
  }
64
69
  const coerce = (value, toType) => {
65
- if(value+""==="null" || value+""==="undefined") return value;
70
+ if (value + "" === "null" || value + "" === "undefined") return value;
66
71
  const type = typeof (value);
67
72
  if (type === toType) return value;
68
73
  if (toType === "number") return parseFloat(value + "");
69
74
  if (toType === "boolean") {
70
- if(["on","checked","selected"].includes(value)) return true;
75
+ if (["on", "checked", "selected"].includes(value)) return true;
71
76
  try {
72
77
  const parsed = JSON.parse(value + "");
73
78
  if (typeof (parsed) === "boolean") return parsed;
74
- return [1,"on","checked","selected"].includes(parsed);
79
+ return [1, "on", "checked", "selected"].includes(parsed);
75
80
  } catch (e) {
76
81
  throw new TypeError(`Unable to convert ${value} into 'boolean'`);
77
82
  }
@@ -79,10 +84,10 @@ const {observe} = (() => {
79
84
  if (toType === "string") return value + "";
80
85
  const isfunction = typeof (toType) === "function";
81
86
  if ((toType === "object" || isfunction)) {
82
- if(type==="object") {
83
- if(value instanceof toType) return value;
87
+ if (type === "object") {
88
+ if (value instanceof toType) return value;
84
89
  }
85
- if(type === "string") {
90
+ if (type === "string") {
86
91
  value = value.trim();
87
92
  try {
88
93
  if (isfunction) {
@@ -121,9 +126,13 @@ const {observe} = (() => {
121
126
  proxy = new Proxy(value, {
122
127
  get(target, property) {
123
128
  if (property === "__isReactor__") return true;
124
- if (property === "toJSON" && target instanceof Array) {
125
- const toJSON = function() { return target.toJSON(); }
126
- return toJSON;
129
+ if (target instanceof Array) {
130
+ if (property === "toJSON") return function toJSON() {
131
+ return [...target];
132
+ }
133
+ if (property === "toString") return function toString() {
134
+ return JSON.stringify(target);
135
+ }
127
136
  }
128
137
  let value = target[property];
129
138
  const type = typeof (value);
@@ -183,19 +192,19 @@ const {observe} = (() => {
183
192
  if (target[property] === undefined) {
184
193
  target[property] = {type: "any", value: newValue}; // should we allow this, do first to prevent loops
185
194
  target.postEvent.value("change", event);
186
- if(event.defaultPrevented) delete target[property].value;
195
+ if (event.defaultPrevented) delete target[property].value;
187
196
  return true;
188
197
  }
189
- const {type, value, shared, exported, constant,reactive} = target[property];
198
+ const {type, value, shared, exported, constant, reactive} = target[property];
190
199
  if (constant) throw new TypeError(`${property}:${type} is a constant`);
191
200
  const newtype = typeof (newValue),
192
201
  typetype = typeof (type);
193
- if (newValue==null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
202
+ if (newValue == null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
194
203
  if (value !== newValue) {
195
204
  event.oldValue = value;
196
205
  target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
197
206
  target.postEvent.value("change", event);
198
- if(event.defaultPrevented) target[property].value = value;
207
+ if (event.defaultPrevented) target[property].value = value;
199
208
  }
200
209
  return true;
201
210
  }
@@ -218,7 +227,7 @@ const {observe} = (() => {
218
227
  if (target.observedAttributes && target.observedAttributes.includes(name)) {
219
228
  const value = target.getAttribute(name);
220
229
  if (value !== mutation.oldValue) {
221
- target.setVariable(name, value);
230
+ target.setValue(name, value);
222
231
  if (target.attributeChangedCallback) target.attributeChangedCallback(name, value, mutation.oldValue);
223
232
  }
224
233
  }
@@ -255,38 +264,36 @@ const {observe} = (() => {
255
264
  nodes.push(node);
256
265
  } else if (node.nodeType === Node.ELEMENT_NODE) {
257
266
  let skip;
267
+ if(node.getAttribute("type")==="radio") nodes.push(node);
258
268
  [...node.attributes].forEach((attr) => {
259
269
  if (attr.value.includes("${")) {
260
270
  attr.template ||= attr.value;
261
- if (!nodes.includes(node)) nodes.push(node);
262
- }
263
- if (attr.name.includes(":") || attr.name.startsWith("l-")) {
271
+ nodes.push(node);
272
+ } else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
264
273
  skip = attr.name.includes("l-for:");
265
- if (!nodes.includes(node)) nodes.push(node);
274
+ nodes.push(node)
266
275
  }
267
276
  })
268
- if (!skip) {
269
- if (!node.shadowRoot) nodes.push(...getNodes(node));
270
- }
277
+ if (!skip && !node.shadowRoot) nodes.push(...getNodes(node));
271
278
  }
272
279
  }
273
280
  }
274
281
  return nodes;
275
282
  }
276
283
  const resolveNode = (node, component) => {
277
- if (node.template) {
284
+ if (node?.template) {
278
285
  try {
279
286
  const value = Function("context", "with(context) { return `" + node.template + "` }")(component.varsProxy);
280
- node.nodeValue = value==="null" || value==="undefined" ? "" : value;
287
+ node.nodeValue = value === "null" || value === "undefined" ? "" : value;
281
288
  } catch (e) {
282
289
  if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
283
290
  }
284
291
  }
285
- return node.nodeValue;
292
+ return node?.nodeValue;
286
293
  }
287
- const render = (template, render) => {
294
+ const render = (hasTemplate, render) => {
288
295
  let observer;
289
- if (template) {
296
+ if (hasTemplate) {
290
297
  if (observer) observer.cancel();
291
298
  observer = observe(render)
292
299
  } else {
@@ -298,57 +305,65 @@ const {observe} = (() => {
298
305
  if (["text", "tel", "email", "url", "search", "radio"].includes(inputType)) return "string";
299
306
  if (["number", "range"].includes(inputType)) return "number";
300
307
  if (["datetime"].includes(inputType)) return Date;
301
- if(["checkbox"].includes(inputType)) return "boolean";
308
+ if (["checkbox"].includes(inputType)) return "boolean";
302
309
  return "any";
303
310
  }
304
- const _importAnchors = (node,component) => {
311
+ const _importAnchors = (node, component) => {
305
312
  [...node.querySelectorAll('a[href][target^="#"]')].forEach((node) => {
306
313
  node.removeEventListener("click", anchorHandler);
307
- node.addEventListener("click", anchorHandler);
308
- })
309
- }
310
- const _bindForms = (node, component) => {
311
- [...node.querySelectorAll("input")].forEach((input) => {
312
- bindInput(input,component);
314
+ addListener(node,"click", anchorHandler);
313
315
  })
314
316
  }
315
- const bindInput = (input,component) => {
316
- const name = input.getAttribute("name"),
317
- vname = input.getAttribute("l-bind")||name;
318
- if (name) {
319
- if(!input.hasAttribute("l-bind")) input.setAttribute("l-bind",vname)
320
- const type = inputTypeToType(input.getAttribute("type")),
317
+ const bindInput = (input, name, component) => {
318
+ const inputtype = input.tagName === "SELECT" ? "text" : input.getAttribute("type"),
319
+ type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
321
320
  deflt = input.getAttribute("default"),
322
321
  value = input.getAttribute("value");
323
- let variable = component.vars[vname] || {type};
324
- if(type!==variable.type) {
325
- if(variable.type==="any" || variable.type==="unknown") variable.type = type;
326
- else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${vname}:${variable.type}`)
322
+ let variable = component.vars[name] || {type};
323
+ if (type !== variable.type) {
324
+ if (variable.type === "any" || variable.type === "unknown") variable.type = type;
325
+ else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
327
326
  }
328
- component.variables({[vname]:type});
329
- variable = component.vars[vname]
330
- if (value || deflt) {
331
- if (value && !value.includes("${")) {
332
- variable.value = coerce(value, type);
333
- input.setAttribute("value", `\${${name}}`);
334
- }
335
- if (deflt && !deflt.includes("${")) {
336
- variable.value = coerce(deflt, type);
337
- input.setAttribute("default", `\${${name}}`);
338
- }
339
- }
340
- input.addEventListener("change",(event) => {
327
+ component.variables({[name]: type});
328
+ addListener(input,"change", (event) => {
341
329
  event.stopImmediatePropagation();
342
- component.varsProxy[vname] = coerce(event.target.value,type);
330
+ const target = event.target;
331
+ let value = target.value;
332
+ if (inputtype === "checkbox") {
333
+ value = input.checked
334
+ } else if (target.tagName === "SELECT") {
335
+ if (target.hasAttribute("multiple")) {
336
+ value = [...target.querySelectorAll("option")]
337
+ .filter((option) => option.selected || resolveNode(option.attributes.value,component)==value || option.innerText == value)
338
+ .map((option) => option.getAttribute("value") || option.innerText);
339
+ }
340
+ }
341
+ component.varsProxy[name] = coerce(value, type);
343
342
  })
343
+ }
344
+ const tryParse = (value) => {
345
+ try {
346
+ return JSON.parse(value);
347
+ } catch(e) {
348
+ return value;
344
349
  }
345
350
  }
346
- const createClass = (domElementNode, {observer,bindForms,importAnchors}) => {
351
+ let reserved = {
352
+ boolean: {value: "boolean", constant: true},
353
+ string: {value: "string", constant: true},
354
+ number: {value: "number", constant: true},
355
+ observed: {value: true, constant: true},
356
+ reactive: {value: true, constant: true},
357
+ shared: {value: true, constant: true},
358
+ exported: {value: true, constant: true},
359
+ imported: {value: true, constant: true}
360
+ };
361
+ const createClass = (domElementNode, {observer, importAnchors}) => {
347
362
  const instances = new Set(),
348
- dom = domElementNode.tagName==="TEMPLATE"
363
+ dom = domElementNode.tagName === "TEMPLATE"
349
364
  ? domElementNode.content.cloneNode(true)
350
365
  : domElementNode.cloneNode(true);
351
- if(domElementNode.tagName==="TEMPLATE") domElementNode = domElementNode.cloneNode(true);
366
+ if (domElementNode.tagName === "TEMPLATE") domElementNode = domElementNode.cloneNode(true);
352
367
  return class CustomElement extends HTMLElement {
353
368
  static get instances() {
354
369
  return instances;
@@ -362,6 +377,7 @@ const {observe} = (() => {
362
377
  shadow = this.attachShadow({mode: "open"}),
363
378
  eventlisteners = {};
364
379
  this.vars = {
380
+ ...reserved,
365
381
  addEventListener: {
366
382
  value: (eventName, listener) => {
367
383
  const listeners = eventlisteners[eventName] ||= new Set();
@@ -382,17 +398,9 @@ const {observe} = (() => {
382
398
  type: "function",
383
399
  constant: true
384
400
  },
385
- self: {value: currentComponent, type: CustomElement, constant: true},
386
- boolean: {value: "boolean", constant: true},
387
- string: {value: "string", constant: true},
388
- number: {value: "number", constant: true},
389
- observed: {value: true, constant: true},
390
- reactive: {value: true, constant: true},
391
- shared: {value: true, constant: true},
392
- exported: {value: true, constant: true},
393
- imported: {value: true, constant: true}
401
+ self: {value: currentComponent, type: CustomElement, constant: true}
394
402
  };
395
- this.defaultAttributes = domElementNode.tagName==="TEMPLATE" ? domElementNode.attributes : dom.attributes;
403
+ this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
396
404
  this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
397
405
  ["getElementById", "querySelector", "querySelectorAll"]
398
406
  .forEach((fname) => {
@@ -402,15 +410,12 @@ const {observe} = (() => {
402
410
  value: (...args) => this.shadowRoot[fname](...args)
403
411
  })
404
412
  });
405
- [...dom.childNodes].forEach((child) => {
406
- shadow.appendChild(child.cloneNode(true));
407
- })
408
- if (bindForms) _bindForms(shadow, this);
409
- if(importAnchors) _importAnchors(shadow,this);
413
+ [...dom.childNodes].forEach((child) => shadow.appendChild(child.cloneNode(true)));
414
+ if (importAnchors) _importAnchors(shadow, this);
410
415
  }
411
416
 
412
417
  get siblings() {
413
- return [...CustomElement.instances].filter((sibling) => sibling!=this);
418
+ return [...CustomElement.instances].filter((sibling) => sibling != this);
414
419
  }
415
420
 
416
421
  adoptedCallback() {
@@ -439,9 +444,7 @@ const {observe} = (() => {
439
444
  const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
440
445
  currentScript.innerHTML = `Function('if(window["${scriptid}"]?.ctx) { with(window["${scriptid}"].ctx) { ${text}; } window["${scriptid}"](); }')(); `;
441
446
  let resolver;
442
- promises.push(new Promise((resolve) => {
443
- resolver = resolve;
444
- }));
447
+ promises.push(new Promise((resolve) => resolver = resolve));
445
448
  window[scriptid] = () => {
446
449
  delete window[scriptid];
447
450
  currentScript.remove();
@@ -451,47 +454,102 @@ const {observe} = (() => {
451
454
  ctx.appendChild(currentScript);
452
455
  }
453
456
  Promise.all(promises).then(() => {
454
- const inputs = [...ctx.shadowRoot.querySelectorAll("input[l-bind]")];
455
- inputs.forEach((input) => {
456
- bindInput(input,ctx);
457
- })
458
457
  const nodes = getNodes(ctx);
459
458
  nodes.forEach((node) => {
460
459
  if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
461
460
  render(!!node.template, () => resolveNode(node, this))
462
461
  } else if (node.nodeType === Node.ELEMENT_NODE) {
462
+ // resolve the value before all else;
463
+ const attr = node.attributes.value;
464
+ let name;
465
+ if(attr && attr.template) {
466
+ render(!!attr.template,() => {
467
+ const value = resolveNode(attr, this),
468
+ eltype = resolveNode(node.attributes.type,ctx);
469
+ if(eltype==="checkbox") {
470
+ if(coerce(value,"boolean")===true) {
471
+ node.setAttribute("checked","");
472
+ node.checked = true;
473
+ } else {
474
+ node.removeAttribute("checked");
475
+ node.checked = false;
476
+ }
477
+ const vname = resolveNode(node.attributes.name,ctx);
478
+ if(vname) ctx.setValue(vname,node.checked,{coerceTo:"boolean"});
479
+ }
480
+ if(node.tagName==="SELECT") {
481
+ let values = [value];
482
+ if(node.hasAttribute("multiple")) values = coerce(value,Array);
483
+ [...node.querySelectorAll("option")].forEach((option) => {
484
+ if(option.hasAttribute("value")) {
485
+ if (values.includes(resolveNode(option.attributes.value, ctx))) {
486
+ option.setAttribute("selected", "");
487
+ option.selected = true;
488
+ }
489
+ } else if(option.innerText.trim()===value) {
490
+ option.setAttribute("selected","");
491
+ option.selected = true;
492
+ }
493
+ })
494
+ }
495
+ });
496
+ let name;
497
+ for(const vname of this.getVariableNames()) {
498
+ if("${" + vname + "}" === attr.template) {
499
+ name = vname;
500
+ break;
501
+ }
502
+ }
503
+ if(name) bindInput(node,name,ctx);
504
+ }
463
505
  [...node.attributes].forEach((attr) => {
464
- const {name, value} = attr,
465
- [type, ...params] = name.split(":");
466
- if (["checked","selected"].includes(type)) {
467
- render(!!attr.template, () => {
468
- const value = resolveNode(attr, this);
469
- if (value === "true") node.setAttribute(name, "");
470
- else node.removeAttribute(name);
471
- })
472
- } else if(type==="") {
506
+ if(attr.name==="value") return;
507
+ const {name, value} = attr;
508
+ if(name==="type") {
509
+ if (value === "radio") {
510
+ const name = resolveNode(node.attributes.name, ctx);
511
+ for (const vname of this.getVariableNames()) {
512
+ if (vname === name) {
513
+ render(true, () => {
514
+ const name = resolveNode(node.attributes.name, ctx),
515
+ varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
516
+ if (varvalue == resolveNode(node.attributes.value,ctx)) {
517
+ node.setAttribute("checked", "");
518
+ node.checked = true;
519
+ } else {
520
+ node.removeAttribute("checked");
521
+ node.checked = false;
522
+ }
523
+ });
524
+ bindInput(node, name, ctx);
525
+ break;
526
+ }
527
+ }
528
+ }
529
+ }
530
+
531
+ const [type, ...params] = name.split(":");
532
+ if (type === "") { // name is :something
473
533
  render(!!attr.template, () => {
474
- const value = resolveNode(attr, this);
475
- if(params[0]) {
476
- if(value==="true") node.setAttribute(params[0], "");
534
+ const value = attr.value,
535
+ elvalue = resolveNode(node.attributes.value,ctx),
536
+ eltype = resolveNode(node.attributes.type,ctx),
537
+ elname = resolveNode(node.attributes.name,ctx);
538
+ if (params[0]) {
539
+ if (value === "true") node.setAttribute(params[0], "")
477
540
  else node.removeAttribute(params[0]);
478
- } else {
479
- if(value!=="true") node.removeAttribute(name);
541
+ } else if (eltype=== "checkbox" || node.tagName === "OPTION") {
542
+ if (value === "true") node.setAttribute("checked", "")
543
+ else node.removeAttribute("checked");
480
544
  }
481
545
  })
482
- } else if (["checked","selected"].includes(name)) {
483
- render(!!attr.template, () => {
484
- const value = resolveNode(attr, this);
485
- if (value === "true") node.setAttribute(name, "");
486
- else node.removeAttribute(name);
487
- })
488
546
  } else if (type === "l-on") {
489
547
  let listener;
490
548
  render(!!attr.template, () => {
491
549
  const value = resolveNode(attr, this);
492
550
  if (listener) node.removeEventListener(params[0], listener);
493
551
  listener = this[value] || window[value] || Function(value);
494
- node.addEventListener(params[0], listener);
552
+ addListener(node,params[0], listener);
495
553
  })
496
554
  } else if (type === "l-if") {
497
555
  render(!!attr.template, () => {
@@ -501,7 +559,7 @@ const {observe} = (() => {
501
559
  } else if (type === "l-for") {
502
560
  node.template ||= node.innerHTML;
503
561
  render(!!attr.template, () => {
504
- const [what = "each", vname = "element", index = "index", array = "array", after = false] = params,
562
+ const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
505
563
  value = resolveNode(attr, this),
506
564
  coerced = coerce(value, what === "each" ? Array : "object"),
507
565
  target = what === "each" ? coerced : Object[what](coerced),
@@ -538,27 +596,39 @@ const {observe} = (() => {
538
596
  }
539
597
 
540
598
  adopted(value) {
541
- Object.defineProperty(this, "adoptedCallback", {configurable: true, writable: true, value});
599
+ this.adoptedCallback = value;
600
+ //Object.defineProperty(this, "adoptedCallback", {configurable: true, writable: true, value});
542
601
  }
543
602
 
544
603
  connected(value) {
545
- Object.defineProperty(this, "connectedCallback", {configurable: true, writable: true, value});
604
+ this.connectedCallback = value;
605
+ //Object.defineProperty(this, "connectedCallback", {configurable: true, writable: true, value});
546
606
  }
547
607
 
548
608
  attributeChanged(value) {
549
- Object.defineProperty(this, "attributeChangedCallback", {configurable: true, writable: true, value});
609
+ this.attributeChangedCallback = value;
610
+ //Object.defineProperty(this, "attributeChangedCallback", {configurable: true, writable: true, value});
550
611
  }
551
612
 
552
613
  disconnected(value) {
553
614
  Object.defineProperty(this, "disconnectedCallback", {
554
615
  configurable: true,
555
616
  writable: true,
556
- value:() => { value(); super.disconnectedCallback(value); }
617
+ value: () => {
618
+ value();
619
+ super.disconnectedCallback(value);
620
+ }
557
621
  });
558
622
  }
559
623
 
560
- setVariable(name, value, {shared,coerceTo = typeof (value)}={}) {
561
- if(!this.isConnected) {
624
+ getVariableNames() {
625
+ return Object.keys(this.vars).filter((name) => {
626
+ return !(name in reserved) && !["self","addEventListener","postEvent"].includes(name)
627
+ })
628
+ }
629
+
630
+ setValue(name, value, {shared, coerceTo = typeof (value)} = {}) {
631
+ if (!this.isConnected) {
562
632
  instances.delete(this);
563
633
  return false;
564
634
  }
@@ -567,27 +637,35 @@ const {observe} = (() => {
567
637
  value = coerce(value, type);
568
638
  if (this.varsProxy[name] !== value) {
569
639
  const variable = this.vars[name];
570
- if(variable.shared) {
571
- const event = new VariableEvent({variableName: name, value: value,oldValue:variable.value});
640
+ if (variable.shared) {
641
+ const event = new VariableEvent({
642
+ variableName: name,
643
+ value: value,
644
+ oldValue: variable.value
645
+ });
572
646
  variable.value = value;
573
- this.vars.postEvent.value("change",event);
574
- if(event.defaultPrevented) variable.value = value;
647
+ this.vars.postEvent.value("change", event);
648
+ if (event.defaultPrevented) variable.value = value;
575
649
  } else {
576
650
  this.varsProxy[name] = value;
577
651
  }
578
652
  }
579
653
  return true;
580
654
  }
581
- this.vars[name] = {type:coerceTo, value: coerce(value, coerceTo)};
655
+ this.vars[name] = {name, type: coerceTo, value: coerce(value, coerceTo)};
582
656
  return false;
583
657
  }
584
658
 
659
+ getValue(variableName) {
660
+ return this.vars[variableName]?.value;
661
+ }
662
+
585
663
  variables(variables, {observed, reactive, shared, exported, imported} = {}) { // options = {observed,reactive,shared,exported,imported}
586
664
  const addEventListener = this.varsProxy.addEventListener;
587
665
  if (variables !== undefined) {
588
666
  Object.entries(variables)
589
667
  .forEach(([key, type]) => {
590
- const variable = this.vars[key] ||= {type};
668
+ const variable = this.vars[key] ||= {name: key, type};
591
669
  if (observed || imported) {
592
670
  variable.value = this.hasAttribute(key) ? coerce(this.getAttribute(key), variable.type) : variable.value;
593
671
  variable.observed = observed;
@@ -599,56 +677,23 @@ const {observe} = (() => {
599
677
  }
600
678
  if (shared) {
601
679
  variable.shared = true;
602
- addEventListener("change",({variableName,value}) => {
603
- if(this.vars[variableName]?.shared) {
604
- this.siblings.forEach((instance) => {
605
- instance.setVariable(variableName, value);
606
- })
680
+ addEventListener("change", ({variableName, value}) => {
681
+ if (this.vars[variableName]?.shared) {
682
+ this.siblings.forEach((instance) => instance.setValue(variableName, value))
607
683
  }
608
684
  })
609
685
  }
610
686
  if (exported) {
611
687
  variable.exported = true;
612
- // in case the export goes up to at iframe
613
- if(variable.value!=null) setComponentAttribute(this,key,variable.value);
614
- addEventListener("change",({variableName,value}) => {
615
- // Array.isArray will not work here, Proxies mess up JSON.stringify for Arrays
616
- //value = value && typeof (value) === "object" ? (value instanceof Array ? JSON.stringify([...value]) : JSON.stringify(value)) : value+"";
617
- value = typeof(value)==="string" || !value ? value : JSON.stringify(value);
618
- if(value==null) {
619
- removeComponentAttribute(this,variableName);
620
- } else {
621
- setComponentAttribute(this,variableName,value);
622
- }
688
+ // in case the export goes up to an iframe
689
+ if (variable.value != null) setComponentAttribute(this, key, variable.value);
690
+ addEventListener("change", ({variableName, value}) => {
691
+ value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
692
+ if (value == null) removeComponentAttribute(this, variableName);
693
+ else setComponentAttribute(this, variableName, value);
623
694
  })
624
695
  }
625
696
  });
626
- addEventListener("change",({variableName,value}) => {
627
- [...this.shadowRoot.querySelectorAll(`input[l-bind=${variableName}]`)].forEach((input) => {
628
- if(input.getAttribute("type")==="checkbox") { // at el option selected
629
- if(!value) input.removeAttribute("checked");
630
- input.checked = value;
631
- } else {
632
- // Array.isArray will not work here, Proxies mess up JSON.stringify for Arrays
633
- //value = value && typeof (value) === "object" ? (value instanceof Array ? JSON.stringify([...value]) : JSON.stringify(value)) : value+"";
634
- value = typeof(value)==="string" || value==null ? value : JSON.stringify(value);
635
- const oldvalue = input.getAttribute("value")||"";
636
- if(oldvalue!==value) {
637
- if(value==null) {
638
- input.removeAttribute("value");
639
- } else {
640
- input.setAttribute("value",value);
641
- }
642
- try {
643
- input.setSelectionRange(0, Math.max(oldvalue.length,value ? value.length : 0)); // shadowDom sometimes fails to rerender unless this is done;
644
- input.setRangeText(value||"",0, Math.max(oldvalue.length,value ? value.length : 0));
645
- } catch(e) {
646
-
647
- }
648
- }
649
- }
650
- })
651
- })
652
697
  }
653
698
  return Object.entries(this.vars)
654
699
  .reduce((result, [key, variable]) => {
@@ -676,17 +721,18 @@ const {observe} = (() => {
676
721
  }
677
722
  }
678
723
  }
679
- const createComponent = (name, node, {observer,bindForms,importAnchors}={}) => {
724
+ const createComponent = (name, node, {observer, importAnchors} = {}) => {
680
725
  let ctor = customElements.get(name);
681
- if(ctor) {
726
+ if (ctor) {
682
727
  console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
683
728
  return ctor;
684
729
  }
685
- ctor = createClass(node, {observer,bindForms,importAnchors});
730
+ ctor = createClass(node, {observer, importAnchors});
686
731
  customElements.define(name, ctor);
687
732
  return ctor;
688
733
  }
689
- Object.defineProperty(Lightview,"createComponent",{writable:true,configurable:true,value:createComponent})
734
+ Lightview.createComponent = createComponent;
735
+ //Object.defineProperty(Lightview, "createComponent", {writable: true, configurable: true, value: createComponent})
690
736
  const importLink = async (link, observer) => {
691
737
  const url = (new URL(link.getAttribute("href"), window.location.href)),
692
738
  as = link.getAttribute("as") || getNameFromPath(url.pathname);
@@ -697,10 +743,9 @@ const {observe} = (() => {
697
743
  const html = await (await fetch(url.href)).text(),
698
744
  dom = parser.parseFromString(html, "text/html"),
699
745
  importAnchors = !!dom.head.querySelector('meta[name="l-importAnchors"]'),
700
- bindForms = !!dom.head.querySelector('meta[name="l-bindForms"]'),
701
746
  unhide = !!dom.head.querySelector('meta[name="l-unhide"]');
702
- if(unhide) dom.body.removeAttribute("hidden");
703
- createComponent(as, dom.body, {observer,importAnchors,bindForms});
747
+ if (unhide) dom.body.removeAttribute("hidden");
748
+ createComponent(as, dom.body, {observer, importAnchors});
704
749
  }
705
750
  return {as};
706
751
  }
@@ -711,149 +756,158 @@ const {observe} = (() => {
711
756
  }
712
757
  }
713
758
 
714
- const bodyAsComponent = ({as="x-body",unhide,importAnchors,bindForms}={}) => {
759
+ const bodyAsComponent = ({as = "x-body", unhide, importAnchors} = {}) => {
715
760
  const parent = document.body.parentElement;
716
- createComponent(as, document.body,{importAnchors,bindForms});
761
+ createComponent(as, document.body, {importAnchors});
717
762
  const component = document.createElement(as);
718
763
  parent.replaceChild(component, document.body);
719
- Object.defineProperty(document,"body",{enumerable:true,configurable:true,get() { return component; }});
764
+ Object.defineProperty(document, "body", {
765
+ enumerable: true, configurable: true, get() {
766
+ return component;
767
+ }
768
+ });
720
769
  if (unhide) component.removeAttribute("hidden");
721
770
  }
722
771
  Lightview.bodyAsComponent = bodyAsComponent;
723
- const postMessage = (data,target=window.parent) => {
724
- if(postMessage.enabled) {
725
- if(target instanceof HTMLIFrameElement) {
726
- data = {...data,href:window.location.href};
727
- target.contentWindow.postMessage(JSON.stringify(data),"*");
772
+ const postMessage = (data, target = window.parent) => {
773
+ if (postMessage.enabled) {
774
+ if (target instanceof HTMLIFrameElement) {
775
+ data = {...data, href: window.location.href};
776
+ target.contentWindow.postMessage(JSON.stringify(data), "*");
728
777
  } else {
729
- data = {...data,iframeId:document.lightviewId,href:window.location.href};
730
- target.postMessage(JSON.stringify(data),"*");
778
+ data = {...data, iframeId: document.lightviewId, href: window.location.href};
779
+ target.postMessage(JSON.stringify(data), "*");
731
780
  }
732
781
  }
733
782
  }
734
- const setComponentAttribute = (node,name,value) => {
735
- if(node.getAttribute(name)!==value) node.setAttribute(name,value);
736
- postMessage({type:"setAttribute",argsList:[name,value]});
783
+ const setComponentAttribute = (node, name, value) => {
784
+ if (node.getAttribute(name) !== value) node.setAttribute(name, value);
785
+ postMessage({type: "setAttribute", argsList: [name, value]});
737
786
  }
738
- const removeComponentAttribute = (node,name,value) => {
787
+ const removeComponentAttribute = (node, name, value) => {
739
788
  node.removeAttribute(name);
740
- postMessage({type:"removeAttribute",argsList:[name]});
789
+ postMessage({type: "removeAttribute", argsList: [name]});
741
790
  }
742
- const getNodePath = (node,path=[]) => {
791
+ const getNodePath = (node, path = []) => {
743
792
  path.unshift(node);
744
- if(node.parentNode && node.parentNode!==node.parentNode) getNodePath(node.parentNode,path);
793
+ if (node.parentNode && node.parentNode !== node.parentNode) getNodePath(node.parentNode, path);
745
794
  return path;
746
795
  }
747
796
  const onresize = (node, callback) => {
748
- const resizeObserver = new ResizeObserver(() => callback() );
797
+ const resizeObserver = new ResizeObserver(() => callback());
749
798
  resizeObserver.observe(node);
750
799
  };
751
800
 
752
801
  const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
753
802
  let domContentLoadedEvent;
754
- window.addEventListener("DOMContentLoaded",(event) => domContentLoadedEvent = event);
803
+ addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
755
804
  const loader = async (whenFramed) => {
756
805
  if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
757
806
  const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
758
- bindForms = !!document.querySelector('meta[name="l-bindForms"]'),
759
807
  unhide = !!document.querySelector('meta[name="l-unhide"]'),
760
808
  isolated = !!document.querySelector('meta[name="l-isolate"]'),
761
809
  enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
762
- if(whenFramed) {
763
- whenFramed({unhide,importAnchors,bindForms,isolated,enableFrames});
764
- if(!isolated) {
810
+ if (whenFramed) {
811
+ whenFramed({unhide, importAnchors, isolated, enableFrames});
812
+ if (!isolated) {
765
813
  postMessage.enabled = true;
766
- window.addEventListener("message",({data}) => {
767
- const {type,argsList} = JSON.parse(data);
768
- if(type==="framed") {
814
+ addListener(window,"message", ({data}) => {
815
+ const {type, argsList} = JSON.parse(data);
816
+ if (type === "framed") {
769
817
  const resize = () => {
770
- const {width,height} = document.body.getBoundingClientRect();
771
- postMessage({type:"setAttribute",argsList:["width",width]})
772
- postMessage({type:"setAttribute",argsList:["height",height+20]});
818
+ const {width, height} = document.body.getBoundingClientRect();
819
+ postMessage({type: "setAttribute", argsList: ["width", width]})
820
+ postMessage({type: "setAttribute", argsList: ["height", height + 20]});
773
821
  }
774
822
  resize();
775
- onresize(document.body,() => {
823
+ onresize(document.body, () => {
776
824
  resize();
777
825
  });
778
826
  return
779
827
  }
780
- if(type==="setAttribute") {
781
- const [name,value] = [...argsList],
828
+ if (type === "setAttribute") {
829
+ const [name, value] = [...argsList],
782
830
  variable = document.body.vars[name];
783
- if(variable && variable.imported) {
784
- document.body.setVariable(name,value);
785
- }
831
+ if (variable && variable.imported) document.body.setValue(name, value);
786
832
  return;
787
833
  }
788
- if(type==="removeAttribute") {
834
+ if (type === "removeAttribute") {
789
835
  const [name] = argsList[0],
790
836
  variable = document.body.vars[name];
791
- if(variable && variable.imported) {
792
- document.body.setVariable(name,undefined);
793
- }
794
- return;
837
+ if (variable && variable.imported) document.body.setValue(name, undefined);
838
+
795
839
  }
796
840
  });
797
841
  const url = new URL(window.location.href);
798
842
  document.lightviewId = url.searchParams.get("id");
799
- postMessage({type:"DOMContentLoaded"})
843
+ postMessage({type: "DOMContentLoaded"})
800
844
  }
801
845
  } else if (url.searchParams.has("as")) {
802
- bodyAsComponent({as:url.searchParams.get("as"),unhide,importAnchors,bindForms});
846
+ bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors});
803
847
  }
804
- if(enableFrames) {
848
+ if (enableFrames) {
805
849
  postMessage.enabled = true;
806
- window.addEventListener("message",(message) => {
807
- const {type,iframeId,argsList,href} = JSON.parse(message.data),
850
+ addListener(window,"message", (message) => {
851
+ const {type, iframeId, argsList, href} = JSON.parse(message.data),
808
852
  iframe = document.getElementById(iframeId);
809
- if(iframe) {
810
- if(type==="DOMContentLoaded") {
811
- postMessage({type:"framed",href:window.location.href},iframe);
812
- Object.defineProperty(domContentLoadedEvent,"currentTarget",{enumerable:false,configurable:true,value:iframe});
853
+ if (iframe) {
854
+ if (type === "DOMContentLoaded") {
855
+ postMessage({type: "framed", href: window.location.href}, iframe);
856
+ Object.defineProperty(domContentLoadedEvent, "currentTarget", {
857
+ enumerable: false,
858
+ configurable: true,
859
+ value: iframe
860
+ });
813
861
  domContentLoadedEvent.href = href;
814
862
  domContentLoadedEvent.srcElement = iframe;
815
863
  domContentLoadedEvent.bubbles = false;
816
864
  domContentLoadedEvent.path = getNodePath(iframe);
817
- Object.defineProperty(domContentLoadedEvent,"timeStamp",{enumerable:false,configurable:true,value:performance.now()})
865
+ Object.defineProperty(domContentLoadedEvent, "timeStamp", {
866
+ enumerable: false,
867
+ configurable: true,
868
+ value: performance.now()
869
+ })
818
870
  iframe.dispatchEvent(domContentLoadedEvent);
819
871
  return;
820
872
  }
821
- if(type==="setAttribute") {
822
- const [name,value] = [...argsList];
823
- if(iframe.getAttribute(name)!==value+"") iframe.setAttribute(name,value);
873
+ if (type === "setAttribute") {
874
+ const [name, value] = [...argsList];
875
+ if (iframe.getAttribute(name) !== value + "") iframe.setAttribute(name, value);
824
876
  return;
825
877
  }
826
- if(type==="removeAttribute") {
878
+ if (type === "removeAttribute") {
827
879
  iframe.removeAttribute(...argsList);
828
880
  return;
829
881
  }
830
882
  }
831
- console.warn("iframe posted a message without providing an id",message);
883
+ console.warn("iframe posted a message without providing an id", message);
832
884
  });
833
885
  const mutationCallback = (mutationsList) => {
834
886
  const console = document.getElementById("console");
835
- for (const {target,attributeName,oldValue} of mutationsList) {
836
- if(!["height","width"].includes(attributeName)) {
887
+ for (const {target, attributeName, oldValue} of mutationsList) {
888
+ if (!["height", "width"].includes(attributeName)) {
837
889
  const value = target.getAttribute(attributeName);
838
890
  if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
839
- else if (value !== oldValue) postMessage({type: "setAttribute",argsList: [attributeName,value]}, iframe)
891
+ else if (value !== oldValue) postMessage({
892
+ type: "setAttribute",
893
+ argsList: [attributeName, value]
894
+ }, iframe)
840
895
  }
841
896
  }
842
897
  };
843
898
  const observer = new MutationObserver(mutationCallback),
844
899
  iframe = document.getElementById("myframe");
845
- observer.observe(iframe, { attributes:true, attributeOldValue: true });
900
+ observer.observe(iframe, {attributes: true, attributeOldValue: true});
846
901
  }
847
902
  }
848
- const whenFramed = (f,{isolated}={}) => {
849
- document.addEventListener("DOMContentLoaded",(event) => loader(f));
903
+ const whenFramed = (f, {isolated} = {}) => {
904
+ addListener(document,"DOMContentLoaded", (event) => loader(f));
850
905
  }
851
- Object.defineProperty(Lightview,"whenFramed",{configurable:true,writable:true,value:whenFramed});
852
- if(window.location===window.parent.location || !(window.parent instanceof Window) || window.parent!==window) { // CodePen mucks with window.parent
853
- document.addEventListener("DOMContentLoaded",() => loader())
906
+ Lightview.whenFramed = whenFramed;
907
+ //Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
908
+ if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) { // CodePen mucks with window.parent
909
+ addListener(document,"DOMContentLoaded", () => loader())
854
910
  }
855
911
 
856
912
  return {observe}
857
- })();
858
-
859
-
913
+ })();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lightview",
3
- "version": "1.2.1b",
4
- "description": "Small, simple, powerful web UI creation ...",
3
+ "version": "1.4.0b",
4
+ "description": "Small, simple, powerful web UI and micro front end creation ... imagine a blend of Svelte, React, Vue, Riot and more.",
5
5
  "main": "lightview.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -15,7 +15,13 @@
15
15
  "react",
16
16
  "angular",
17
17
  "riot",
18
- "vue"
18
+ "vue",
19
+ "moon",
20
+ "hyperapp",
21
+ "hyperhtml",
22
+ "micro front end",
23
+ "custom elements",
24
+ "web components"
19
25
  ],
20
26
  "author": "Simon Y. Blackwell",
21
27
  "license": "MIT",
package/remote.html CHANGED
@@ -9,7 +9,8 @@
9
9
 
10
10
  <body>
11
11
  <p>
12
- The component below is loaded from an alternate domain and running in an iframe.
12
+ The component below is loaded from an alternate domain and running in a child iframe.
13
+ The logging console is below the component in this frame.
13
14
  </p>
14
15
  <iframe id="myframe" src="https://lightview.dev/remoteform.html?id=myframe"></iframe>
15
16
  <div id="console" style="max-height:250px;scroll:auto"></div>
package/remoteform.html CHANGED
@@ -2,45 +2,73 @@
2
2
 
3
3
  <head>
4
4
  <title>Form</title>
5
- <meta name="l-bindForms">
6
5
  <script src="./lightview.js?as=x-body"></script>
7
- <script>Lightview.whenFramed(({as,unhide,importAnchors,bindForms,isolated,enableFrames}) => {
8
- Lightview.bodyAsComponent({as,unhide,importAnchors,bindForms,isolated,enableFrames});
6
+ <script>Lightview.whenFramed(({as,unhide,importAnchors,isolated,enableFrames}) => {
7
+ Lightview.bodyAsComponent({as,unhide,importAnchors,isolated,enableFrames});
9
8
  })</script>
10
9
  </head>
11
10
 
12
11
  <body style="height:fit-content;width:fit-content;display:flex;flex-direction:column;max-height:100%;overflow:auto;">
13
- <p>
14
- <button l-on:click="run">Run</button> <button l-on:click="reset">Reset</button> <button l-on:click="setNull">Set Null</button>
15
- </p>
16
- <form>
17
- <input name="name" type="text" value="Joe"><br>
18
- <input name="age" type="number" value="20"><br>
19
- </form>
20
- <div id="console"></div>
12
+ <div style="margin:20px">
13
+ <p>
14
+ <input type="text" value="${color}">
15
+ <input type="checkbox" value="${checked}">
16
+ <input type="radio" name="color" value="red">
17
+ <input type="radio" name="color" value="yellow">
18
+ <input type="radio" name="color" value="green">
19
+ <select value="${color}">
20
+ <option value="red">red</option>
21
+ <option>yellow</option>
22
+ <option> green </option>
23
+ </select>
24
+ <div>Hamburger options:</div>
25
+ <select value="${hamburger}" multiple>
26
+ <option value="lettuce">lettuce</option>
27
+ <option>tomato</option>
28
+ <option>cheese</option>
29
+ </select>
30
+ </p>
31
+ <p l-if="${checked}">
32
+ Now you've done it. You've exposed me.
33
+ </p>
34
+ <ul l-for="${hamburger}">
35
+ <li>${item}</li>
36
+ </ul>
37
+ <ul l-for:entries="${hamburger}">
38
+ <li>${item[0]}:${item[1]}</li>
39
+ </ul>
40
+ <ul l-for:values="${hamburger}">
41
+ <li>${item}</li>
42
+ </ul>
43
+ <p id="variables">
44
+
45
+ </p>
46
+ </div>
21
47
  <script type="lightview/module">
22
- self.variables({name:string,age:number},{exported,imported});
23
- self.run = () => {
24
- name = "Bill";
25
- age = 30;
26
- action("run");
27
- };
28
- self.reset = () => {
29
- name = "Joe";
30
- age = 20;
31
- const console = self.getElementById("console");
32
- while(console.lastElementChild) console.lastElementChild.remove();
33
- };
34
- self.setNull = () => {
35
- name = null;
36
- age = undefined;
37
- action("setNull");
48
+ self.variables({
49
+ color: string,
50
+ checked: boolean,
51
+ hamburger: Array
52
+ }, {
53
+ reactive
54
+ });
55
+ color = "green";
56
+ checked = true;
57
+ hamburger = ["lettuce"];
58
+ // demo instrumentation
59
+ const variableValues = () => {
60
+ const el = self.getElementById("variables");
61
+ while (el.lastElementChild) el.lastElementChild.remove();
62
+ self.getVariableNames().forEach((name) => {
63
+ const line = document.createElement("div");
64
+ line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
65
+ el.appendChild(line);
66
+ });
38
67
  };
39
- const action = (name) => {
40
- const div = document.createElement("div");
41
- div.innerText = name;
42
- self.getElementById("console").appendChild(div);
43
- }
68
+ variableValues();
69
+ addEventListener("change", () => {
70
+ variableValues()
71
+ });
44
72
  </script>
45
73
  </body>
46
74
 
package/scratch.html ADDED
@@ -0,0 +1,69 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Scratch</title>
6
+ <script src="./lightview.js?as=x-body"></script>
7
+ </head>
8
+ <body>
9
+ <div style="margin:20px;padding:5px;border:1px;border-style:solid;border-color:${color}">
10
+ <p>
11
+ <input type="text" value="${color}">
12
+ <input type="radio" name="color" value="red">
13
+ <input type="radio" name="color" value="yellow">
14
+ <input type="radio" name="color" value="green">
15
+ <select value="${color}">
16
+ <option value="red">red</option>
17
+ <option>yellow</option>
18
+ <option> green</option>
19
+ </select>
20
+ <div>Hamburger options:</div>
21
+ <select value="${hamburger}" multiple>
22
+ <option value="lettuce">lettuce</option>
23
+ <option>tomato</option>
24
+ <option>cheese</option>
25
+ </select>
26
+ </p>
27
+ Expose: <input type="checkbox" value="${checked}">
28
+ <p l-if="${checked}">
29
+ Now you've done it. You've exposed me.
30
+ </p>
31
+ <ul l-for="${hamburger}">
32
+ <li>${item}</li>
33
+ </ul>
34
+ <ul l-for:entries="${hamburger}">
35
+ <li>${item[0]}:${item[1]}</li>
36
+ </ul>
37
+ <ul l-for:values="${hamburger}">
38
+ <li>${item}</li>
39
+ </ul>
40
+ <p id="variables">
41
+
42
+ </p>
43
+ </div>
44
+ <script type="lightview/module">
45
+ self.variables({color:string,checked:boolean,hamburger:Array},{reactive});
46
+
47
+ color = "green";
48
+ checked = true;
49
+ hamburger = ["lettuce"];
50
+
51
+
52
+ // demo instrumentation
53
+ const variableValues = () => {
54
+ const el = self.getElementById("variables");
55
+ while (el.lastElementChild) el.lastElementChild.remove();
56
+ self.getVariableNames().forEach((name) => {
57
+ const line = document.createElement("div");
58
+ line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
59
+ el.appendChild(line);
60
+ });
61
+ };
62
+ variableValues();
63
+ addEventListener("change", () => {
64
+ variableValues()
65
+ });
66
+
67
+ </script>
68
+ </body>
69
+ </html>
package/xor.html ADDED
@@ -0,0 +1,60 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <template id="audiostream">
6
+ <p>${name}</p>
7
+ <p>
8
+ Play: <input type="checkbox" value="${run}">
9
+ </p>
10
+ <script type="lightview/module">
11
+ self.variables({
12
+ run: boolean
13
+ },{reactive});
14
+ self.variables({
15
+ name: string
16
+ }, {
17
+ imported
18
+ });
19
+ addEventListener("change", ({
20
+ variableName,
21
+ value
22
+ }) => {
23
+ if (variableName === "run" && value === true) {
24
+ self.siblings.forEach((sibling) => {
25
+ sibling.setValue(variableName, false);
26
+ })
27
+ }
28
+ })
29
+ </script>
30
+ </template>
31
+ <title>Form</title>
32
+ <script src="./lightview.js"></script>
33
+ <script>
34
+ Lightview.createComponent("x-audiostream", document.getElementById("audiostream"))
35
+ </script>
36
+ </head>
37
+
38
+ <body>
39
+ <div style="margin:20px">
40
+ <table>
41
+ <th>
42
+ <td colspan="3">Audio Streams</td>
43
+ </th>
44
+ <tr>
45
+ <td style="width:33%;text-align:center">
46
+ <x-audiostream name="Classical"></x-audiostream>
47
+ </td>
48
+ <td style="width:33%;text-align:center">
49
+ <x-audiostream name="Country"></x-audiostream>
50
+ </td>
51
+ <td style="width:33%;text-align:center">
52
+ <x-audiostream name="Classic Rock"></x-audiostream>
53
+ </td>
54
+ </tr>
55
+ </table>
56
+ </div>
57
+
58
+ </body>
59
+
60
+ </html>