lightview 1.7.2-b → 1.8.2

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.
Files changed (122) hide show
  1. package/README.md +15 -16
  2. package/docs/CNAME +1 -0
  3. package/docs/api.html +674 -0
  4. package/docs/blank.html +10 -0
  5. package/docs/comparedto.html +89 -0
  6. package/docs/components/chart-repl.html +69 -0
  7. package/docs/components/chart.html +17 -0
  8. package/{components → docs/components}/components.js +31 -11
  9. package/docs/components/contents.html +17 -0
  10. package/docs/components/gantt-repl.html +61 -0
  11. package/docs/components/gantt.html +42 -0
  12. package/docs/components/gauge-repl.html +66 -0
  13. package/docs/components/gauge.html +20 -0
  14. package/docs/components/orgchart-repl.html +64 -0
  15. package/docs/components/orgchart.html +41 -0
  16. package/docs/components/repl-as-src.html +17 -0
  17. package/docs/components/repl-repl.html +95 -0
  18. package/docs/components/repl.html +527 -0
  19. package/docs/components/timeline-repl.html +72 -0
  20. package/docs/components/timeline.html +44 -0
  21. package/docs/components.html +14 -0
  22. package/docs/css/highlightjs.min.css +9 -0
  23. package/docs/css/tutorial.css +35 -0
  24. package/docs/examples/anchor.html +11 -0
  25. package/{components/chart/example.html → docs/examples/chart.html} +6 -4
  26. package/docs/examples/counter.html +26 -0
  27. package/{examples → docs/examples}/counter.test.mjs +0 -0
  28. package/docs/examples/counter2.html +26 -0
  29. package/{examples → docs/examples}/directives.html +20 -18
  30. package/{examples → docs/examples}/foreign.html +1 -1
  31. package/docs/examples/forgeinform.html +98 -0
  32. package/docs/examples/form.html +61 -0
  33. package/{examples → docs/examples}/gauge.html +4 -2
  34. package/{examples → docs/examples}/invalid-template-literals.html +6 -4
  35. package/{examples → docs/examples}/medium/remote.html +1 -1
  36. package/docs/examples/message.html +18 -0
  37. package/{examples → docs/examples}/nested.html +2 -2
  38. package/docs/examples/object-bound-form.html +34 -0
  39. package/{examples → docs/examples}/remote-server.js +0 -0
  40. package/docs/examples/remote.html +34 -0
  41. package/{examples → docs/examples}/remote.json +0 -0
  42. package/{examples → docs/examples}/scratch.html +1 -1
  43. package/docs/examples/sensors/index.html +44 -0
  44. package/{examples → docs/examples}/sensors/sensor-server.js +0 -0
  45. package/docs/examples/shared.html +41 -0
  46. package/{examples → docs/examples}/template.html +1 -1
  47. package/{examples → docs/examples}/timeline.html +2 -2
  48. package/docs/examples/todo.html +40 -0
  49. package/docs/examples/top.html +10 -0
  50. package/{examples → docs/examples}/types.html +1 -1
  51. package/{examples → docs/examples}/xor.html +22 -20
  52. package/docs/examples.html +25 -0
  53. package/docs/index.html +44 -0
  54. package/docs/javascript/codejar.min.js +8 -0
  55. package/docs/javascript/highlightjs.min.js +1173 -0
  56. package/docs/javascript/isomorphic-git.js +9 -0
  57. package/docs/javascript/json5.min.js +1 -0
  58. package/docs/javascript/lightning-fs.js +1 -0
  59. package/docs/javascript/lightview.js +1285 -0
  60. package/docs/javascript/marked.min.js +6 -0
  61. package/docs/javascript/peerjs.min.js +70 -0
  62. package/docs/javascript/turndown.js +973 -0
  63. package/docs/javascript/types.js +606 -0
  64. package/docs/javascript/utils.js +45 -0
  65. package/docs/lightview.html +63 -0
  66. package/docs/old_index.html +965 -0
  67. package/docs/old_index.md +1132 -0
  68. package/docs/slidein.html +51 -0
  69. package/docs/tutorial/0-getting-started.html +67 -0
  70. package/docs/tutorial/1-intro-to-variables.html +103 -0
  71. package/docs/tutorial/10-template-components.html +80 -0
  72. package/docs/tutorial/11-linked-components.html +76 -0
  73. package/docs/tutorial/12-imported-components.html +67 -0
  74. package/docs/tutorial/13-input-binding.html +94 -0
  75. package/docs/tutorial/14-automatic-variable-creation.html +74 -0
  76. package/docs/tutorial/15-form-binding.html +110 -0
  77. package/docs/tutorial/16-if-directive.html +60 -0
  78. package/docs/tutorial/17-loop-directives.html +83 -0
  79. package/docs/tutorial/18-sanitizing-and-escaping-input.html +79 -0
  80. package/docs/tutorial/2-imported-and-exported-variables.html +80 -0
  81. package/docs/tutorial/3-data-types.html +89 -0
  82. package/docs/tutorial/4-extended-data-types.html +83 -0
  83. package/docs/tutorial/5-extended-functional-types.html +96 -0
  84. package/docs/tutorial/5.1-extended-functional-types.html +79 -0
  85. package/docs/tutorial/5.2-extended-functional-types.html +70 -0
  86. package/docs/tutorial/6-conventional-javascript.html +75 -0
  87. package/docs/tutorial/7-monitoring-with-observers.html +107 -0
  88. package/docs/tutorial/8-event-listeners.html +65 -0
  89. package/docs/tutorial/9-intro-to-components.html +91 -0
  90. package/docs/tutorial/contents.html +32 -0
  91. package/docs/tutorial/my-component.html +29 -0
  92. package/docs/tutorial/remote-value.json +4 -0
  93. package/docs/websiterepl.html +46 -0
  94. package/lightview.js +499 -363
  95. package/lightview.min.js +1 -0
  96. package/lightview_good.js +1267 -0
  97. package/lightview_optimized.js +1274 -0
  98. package/package.json +7 -2
  99. package/repl_hold.html +320 -0
  100. package/test/basic.html +29 -17
  101. package/test/basic.test.mjs +1 -11
  102. package/test/extended.html +16 -19
  103. package/types.js +118 -36
  104. package/unsplash.key +1 -0
  105. package/components/chart/chart.html +0 -15
  106. package/components/chart.html +0 -81
  107. package/components/gantt/example.html +0 -27
  108. package/components/gantt/gantt.html +0 -34
  109. package/components/gauge/example.html +0 -28
  110. package/components/gauge/guage.html +0 -19
  111. package/components/gauge.html +0 -57
  112. package/components/timeline.html +0 -81
  113. package/examples/chart.html +0 -66
  114. package/examples/counter.html +0 -24
  115. package/examples/forgeinform.html +0 -96
  116. package/examples/form.html +0 -59
  117. package/examples/message.html +0 -12
  118. package/examples/object-bound-form.html +0 -32
  119. package/examples/remote.html +0 -32
  120. package/examples/sensors/index.html +0 -30
  121. package/examples/todo.html +0 -38
  122. package/examples/top.html +0 -10
package/lightview.js CHANGED
@@ -28,10 +28,12 @@ SOFTWARE.
28
28
  imported(x) => exported(x) => reactive(x) => remote(x,{path:".
29
29
  */
30
30
 
31
- const Lightview = {};
31
+ var currentComponent = window.currentComponent = null;
32
+ if(document.body) currentComponent = window.currentComponent = document.currentComponent = document.body;
33
+ var Lightview = window.Lightview = { };
32
34
 
33
- const {observe} = (() => {
34
- let CURRENTOBSERVER;
35
+ var {observe} = (() => {
36
+ let CURRENTOBSERVER;//, CURRENTNODE;
35
37
  const parser = new DOMParser();
36
38
 
37
39
  const templateSanitizer = (string) => {
@@ -44,19 +46,19 @@ const {observe} = (() => {
44
46
  Lightview.sanitizeTemplate = templateSanitizer;
45
47
 
46
48
  const escaper = document.createElement('textarea');
47
- function escapeHTML(html) {
49
+ const escapeHTML = html => {
48
50
  escaper.textContent = html;
49
51
  return escaper.innerHTML;
50
52
  }
51
53
  Lightview.escapeHTML = escapeHTML;
52
54
 
53
- const isArrowFunction = (f) => typeof(f)==="function" && (f+"").match(/\(*.*\)*\s*=>/g);
55
+ const isArrowFunction = f => typeof(f)==="function" && (f+"").match(/\(*.*\)*\s*=>/g);
54
56
 
55
- const getTemplateVariableName = (template) => {
56
- if(template && /^\$\{[a-zA-z_.]*\}$/g.test(template)) return template.substring(2, template.length - 1);
57
+ const getTemplateVariableName = template => {
58
+ if(template && !template.includes("[") && /^\$\{[a-zA-z_0-9.]*\}$/g.test(template)) return template.substring(2, template.length - 1);
57
59
  }
58
60
 
59
- const walk = (target,path,depth=path.length-1,create) => {
61
+ const walk = (target,path,{depth=path.length-1,create}={}) => {
60
62
  for(let i=0;i<=depth;i++) {
61
63
  target = (target[path[i]]==null && create ? target[path[i]] = (typeof(create)==="function" ? Object.create(create.prototype) : {}) : target[path[i]]);
62
64
  if(target===undefined) return;
@@ -66,12 +68,14 @@ const {observe} = (() => {
66
68
 
67
69
  const addListener = (node, eventName, callback, self) => {
68
70
  node.addEventListener(eventName, (event) => {
69
- if(self) event.self = self;
71
+ // todo shopuld self be currentComponent or component
72
+ if(self) Object.defineProperty(event,"self",{value:self});
70
73
  callback(event);
71
74
  });
72
75
  }
73
76
 
74
- const anchorHandler = async (event) => {
77
+ // imports a component based on an anchor and replace content of a target node with an instance of the component
78
+ const anchorHandler = async event => {
75
79
  event.preventDefault();
76
80
  const target = event.target;
77
81
  if (target === event.currentTarget) {
@@ -83,14 +87,15 @@ const {observe} = (() => {
83
87
  })
84
88
  }
85
89
  }
86
- const getNameFromPath = (path) => {
90
+ const getNameFromPath = path => {
87
91
  const file = path.split("/").pop(),
88
- name = file.split(".")[0];
89
- if (name.includes("-")) return name;
92
+ name = file.split(".")[0],
93
+ parts = name.split("-");
94
+ if (name.includes("-") && parts[0].length>=1) return name;
90
95
  return "l-" + name;
91
96
  }
92
97
  const observe = (f, thisArg, argsList = []) => {
93
- const observer = (...args) => {
98
+ let observer = (...args) => {
94
99
  if(observer.cancelled) return;
95
100
  CURRENTOBSERVER = observer;
96
101
  try {
@@ -100,25 +105,27 @@ const {observe} = (() => {
100
105
  }
101
106
  CURRENTOBSERVER = null;
102
107
  }
103
- observer.cancel = () => observer.cancelled = true;
108
+ observer.cancel = () => {
109
+ observer.cancelled = true;
110
+ return observer = null;
111
+ }
104
112
  observer();
105
113
  return observer;
106
114
  }
107
115
  const coerce = (value, toType) => {
108
- if (value + "" === "null" || value + "" === "undefined" || toType==="any") return value;
116
+ if (["null","undefined"].includes(value + "") || toType==="any") return value;
109
117
  const type = typeof (value);
110
118
  if (type === toType) return value;
111
- if (toType === "number") return parseFloat(value + "");
119
+ if (toType === "number") {
120
+ value = value + "";
121
+ if(value==="NaN") return NaN;
122
+ const result = parseFloat(value);
123
+ if(isNaN(result)) throw new TypeError(`Unable to coerce '${value}' to 'number'`)
124
+ return result;
125
+ }
112
126
  if (toType === "boolean") {
113
- if (["on", "checked", "selected"].includes(value)) return true;
114
- if (value == null || value === "") return false;
115
- try {
116
- const parsed = JSON.parse(value + "");
117
- if (typeof (parsed) === "boolean") return parsed;
118
- return [1, "on", "checked", "selected"].includes(parsed);
119
- } catch (e) {
120
- throw new TypeError(`Unable to convert ${value} into 'boolean'`);
121
- }
127
+ if ([1,"on", "checked", "selected","true"].includes(value)) return true;
128
+ if ([null,"",0,"false"].includes(value)) return false;
122
129
  }
123
130
  if (toType === "string") return value + "";
124
131
  const isfunction = typeof (toType) === "function";
@@ -139,7 +146,7 @@ const {observe} = (() => {
139
146
  }
140
147
  }
141
148
  if (!Array.isArray(parsed)) {
142
- throw new TypeError(`Expected an Array for parsed data`)
149
+ throw new TypeError(`Expected Array for parsed data ${parsed}:${typeof(parsed)}`)
143
150
  }
144
151
  instance.push(...parsed);
145
152
  } else if (instance instanceof Date) {
@@ -147,55 +154,47 @@ const {observe} = (() => {
147
154
  } else {
148
155
  Object.assign(instance, JSON.parse(value));
149
156
  }
150
- if (toType !== Date) {
151
- Object.defineProperty(instance, "constructor", {
152
- configurable: true,
153
- writable: true,
154
- value: toType.prototype.constructor || toType
155
- });
156
- }
157
157
  return instance;
158
158
  }
159
159
  return JSON.parse(value);
160
160
  } catch (e) {
161
- throw new TypeError(`Unable to convert ${value} into ${isfunction ? toType.name : type}`);
161
+ throw new TypeError(`Unable to coerce '${value}:${type}' to '${isfunction ? toType.name : type}'`);
162
162
  }
163
163
  }
164
164
  }
165
- throw new TypeError(`Unable to coerce ${value} to ${toType}`)
165
+ throw new TypeError(`Unable to coerce '${value}:${type}' to '${toType}'`)
166
166
  }
167
- const Reactor = (data) => {
167
+ const Reactor = data => {
168
168
  if (data && typeof (data) === "object") {
169
169
  if (data.__isReactor__) return data;
170
- const childReactors = [],
171
- dependents = {},
172
- proxy = new Proxy(data, {
170
+ let dependents = {};
171
+ const childReactors = new Set(),
172
+ //nodes = new Set(),
173
+ {proxy,revoke} = Proxy.revocable(data, {
173
174
  get(target, property) {
174
175
  if (property === "__isReactor__") return true;
175
- if(property=== "__dependents__") return dependents;
176
- if(property=== "__reactorProxyTarget__") return data;
176
+ if (property === "revoke") return () => {
177
+ dependents = {};
178
+ childReactors.forEach((reactor) => reactor.revoke());
179
+ revoke();
180
+ };
177
181
  if (target instanceof Array) {
178
- if (property === "toJSON") return function toJSON() { return [...target]; }
179
- if (property === "toString") return function toString() { return JSON.stringify([...target]); }
182
+ if (property === "toJSON") return function toJSON() { return [...target] }
183
+ if (property === "toString") return function toString() { return JSON.stringify([...target]) }
184
+ }
185
+ if(target instanceof Date) {
186
+ const value = data[property];
187
+ if(typeof(value)==="function") return value.bind(data);
180
188
  }
181
- if(target instanceof Date) return Reflect.get(target,property);
182
189
  let value = target[property];
190
+ if(typeof (property) === "symbol") return value;
183
191
  const type = typeof (value);
184
- if (CURRENTOBSERVER && typeof (property) !== "symbol" && type !== "function") {
185
- const observers = dependents[property] ||= new Set();
186
- observers.add(CURRENTOBSERVER)
187
- }
192
+ if (type === "function") return ([Date].includes(value) || property==="then") ? value.bind(target) : value;
193
+ if (CURRENTOBSERVER) (dependents[property] ||= new Set()).add(CURRENTOBSERVER)
188
194
  if(value===undefined) return;
189
- if (childReactors.includes(value) || (value && type !== "object") || typeof (property) === "symbol") {
190
- // Dates and Promises must be bound to work with proxies
191
- if (type === "function" && ([Date].includes(value) || property==="then")) value = value.bind(target)
192
- return value;
193
- }
194
- if (value && type === "object") {
195
- value = Reactor(value);
196
- childReactors.push(value);
197
- }
198
- target[property] = value;
195
+ if (value==null || type !== "object" || childReactors.has(value)) return value;
196
+ value = target[property] = Reactor(value);
197
+ childReactors.add(value);
199
198
  return value;
200
199
  },
201
200
  async set(target, property, value) {
@@ -207,11 +206,10 @@ const {observe} = (() => {
207
206
  if (target[property] !== value) {
208
207
  if (value && type === "object") {
209
208
  value = Reactor(value);
210
- childReactors.push(value);
209
+ childReactors.add(value);
211
210
  }
212
211
  target[property] = value;
213
- const observers = dependents[property] || [];
214
- [...observers].forEach((f) => {
212
+ [...dependents[property] || []].forEach((f) => { // handle dependent observers
215
213
  if (f.cancelled) dependents[property].delete(f);
216
214
  else f();
217
215
  })
@@ -219,28 +217,26 @@ const {observe} = (() => {
219
217
  return true;
220
218
  }
221
219
  });
220
+ //Object.entries(proxy).forEach(([key,value]) => proxy[key] = value);
222
221
  return proxy;
223
222
  }
224
223
  return data;
225
224
  }
226
225
 
227
- class VariableEvent {
228
- constructor(config) {
229
- Object.assign(this, config);
230
- }
231
- }
232
-
233
226
  const createVarsProxy = (vars, component, constructor) => {
234
- return new Proxy(vars, {
227
+ const {proxy,revoke} = Proxy.revocable(vars, {
235
228
  get(target, property) {
229
+ if(property==="self") return component;
230
+ if(property === "revoke") return revoke;
236
231
  if(target instanceof Date) return Reflect.get(target,property);
232
+ if(typeof(property)==="symbol") return target[property];
237
233
  let {value,get} = target[property] || {};
238
234
  if(get) return target[property].value = get.call(target[property]);
239
235
  if (typeof (value) === "function") return value.bind(target);
240
236
  return value;
241
237
  },
242
238
  set(target, property, newValue) {
243
- const event = new VariableEvent({variableName: property, value: newValue});
239
+ const event = {variableName: property, value: newValue};
244
240
  if (target[property] === undefined) {
245
241
  target[property] = {type: "any", value: newValue}; // should we allow this, do first to prevent loops
246
242
  target.postEvent.value("change", event);
@@ -250,7 +246,14 @@ const {observe} = (() => {
250
246
  const variable = target[property],
251
247
  {value, shared, exported, constant, reactive, remote} = variable;
252
248
  let type = variable.type;
253
- if (constant) throw new TypeError(`${property}:${type} is a constant`);
249
+ if (constant) {
250
+ const error = new TypeError(`${property}:${type} is a constant`);
251
+ if(Lightview.renderErrors) {
252
+ variable.value = error.toString();
253
+ return true;
254
+ }
255
+ throw error;
256
+ }
254
257
  if(newValue!=null || type.required) newValue = type.validate ? type.validate(newValue,target[property]) : coerce(newValue,type);
255
258
  const newtype = typeof (newValue),
256
259
  typetype = typeof (type);
@@ -260,24 +263,36 @@ const {observe} = (() => {
260
263
  (typetype === "function" && !type.validate && (newValue && newtype === "object" && newValue instanceof type) || variable.validityState?.valid)) {
261
264
  if (value !== newValue) {
262
265
  event.oldValue = value;
263
- target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
266
+ variable.value = reactive && !variable.__isReactor__ ? Reactor(newValue) : newValue; // do first to prevent loops
264
267
  target.postEvent.value("change", event);
265
268
  if (event.defaultPrevented) target[property].value = value;
266
- else if(remote && (variable.reactive || remote.put)) remote.handleRemote({variable,config:remote.config},true);
269
+ else if(remote && ((variable.reactive && variable?.remote?.config.put!==false) || remote.put)) remote.handleRemote({variable,config: variable?.remote?.config},true);
267
270
  else if(variable.set) variable.set(newValue);
268
271
  }
269
272
  return true;
270
273
  }
271
274
  if (typetype === "function" && newValue && newtype === "object") {
272
- throw new TypeError(`Can't assign instance of '${newValue.constructor.name}' to variable '${property}:${type.name.replace("bound ", "")}'`)
275
+ const error = new TypeError(`Can't assign instance of '${newValue.constructor.name}' to variable '${property}:${type.name.replace("bound ", "")}'`)
276
+ if(Lightview.renderErrors) {
277
+ variable.value = error.toString();
278
+ return true;
279
+ }
280
+ throw error;
281
+ }
282
+ const error = new TypeError(`Can't assign '${typeof (newValue)} ${newtype === "string" ? '"' + newValue + '"' : newValue}' to variable '${property}:${typetype === "function" ? type.name.replace("bound ", "") : type} ${type.required ? "required" : ""}'`)
283
+ if(Lightview.renderErrors) {
284
+ variable.value = error.toString();
285
+ return true;
273
286
  }
274
- throw new TypeError(`Can't assign '${typeof (newValue)} ${newtype === "string" ? '"' + newValue + '"' : newValue}' to variable '${property}:${typetype === "function" ? type.name.replace("bound ", "") : type} ${type.required ? "required" : ""}'`)
287
+ throw error;
275
288
  },
276
289
  keys() {
277
290
  return [...Object.keys(vars)];
278
291
  }
279
292
  });
293
+ return proxy;
280
294
  }
295
+ // this is a DOM observer, not an observer of the observer programming paradigm
281
296
  const createObserver = (domNode, framed) => {
282
297
  const mobserver = new MutationObserver((mutations) => {
283
298
  mutations.forEach((mutation) => {
@@ -296,6 +311,7 @@ const {observe} = (() => {
296
311
  }
297
312
  } else if (mutation.type === "childList") {
298
313
  for (const target of mutation.removedNodes) {
314
+ //target.lightviewProxies?.forEach((proxy) => proxy.forgetNode(target));
299
315
  if (target.disconnectedCallback) target.disconnectedCallback();
300
316
  }
301
317
  for (const target of mutation.addedNodes) {
@@ -318,77 +334,109 @@ const {observe} = (() => {
318
334
  }
319
335
  return nodes;
320
336
  }
321
- const getNodes = (root) => {
322
- const nodes = new Set();
337
+ const getNodes = (root,href,nodes = new Set()) => {
323
338
  if (root.shadowRoot) {
324
339
  nodes.add(root);
325
- getNodes(root.shadowRoot).forEach((node) => nodes.add(node))
340
+ getNodes(root.shadowRoot,href,nodes);
326
341
  } else {
327
342
  for (const node of root.childNodes) {
328
- if (node.tagName === "SCRIPT") continue;
329
- if (node.nodeType === Node.TEXT_NODE && node.nodeValue?.includes("${")) {
343
+ if (node.nodeType === Node.TEXT_NODE && (node.template || node.nodeValue?.includes("${"))) {
330
344
  node.template ||= node.nodeValue;
331
345
  nodes.add(node);
332
346
  } else if (node.nodeType === Node.ELEMENT_NODE) {
333
347
  let skip;
334
- [...node.attributes].forEach((attr) => {
335
- if (attr.value.includes("${")) {
348
+ for(const attr of node.attributes) {
349
+ if (attr.template || attr.value.includes("${")) {
336
350
  attr.template ||= attr.value;
337
351
  nodes.add(node);
338
- } else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
352
+ } else if (attr.name.startsWith("l-") || attr.name.includes(":")) {
339
353
  skip = attr.name.includes("l-for:");
340
- nodes.add(node)
354
+ nodes.add(node);
341
355
  }
342
- })
356
+ }
343
357
  if (node.getAttribute("type") === "radio") nodes.add(node);
344
- if (!skip && !node.shadowRoot) getNodes(node).forEach((node) => nodes.add(node));
358
+ if (!skip && !node.shadowRoot) getNodes(node,href,nodes);
345
359
  }
346
360
  }
347
361
  }
348
362
  return nodes;
349
363
  }
350
364
 
351
- const resolveNodeOrText = (node, component, safe,extras=node.extras||{}) => {
365
+ // populate a string literal template and turn objects into strings
366
+ const populateTemplate = (strings,...values) => {
367
+ values = values.map((value) => {
368
+ const type = typeof(value);
369
+ try {
370
+ if(value && type==="object") return JSON.stringify(value);
371
+ return value;
372
+ } catch(e) {
373
+ return e.toString();
374
+ }
375
+ });
376
+ return strings.reduce((result,string,index) => index < values.length ? result += string + values[index] : result + string,"");
377
+ }
378
+
379
+ const resolveNodeOrText = (node, component, safe,extras={},skipSetAttribute) => {
352
380
  const type = typeof (node),
353
381
  template = type === "string" ? node.trim() : node.template;
382
+ extras.populateTemplate = populateTemplate;
354
383
  if (template) {
355
384
  const name = getTemplateVariableName(template);
356
385
  try {
357
- let value = (name
358
- ? component[name] || walk(extras,name.split(".")) || walk(component.varsProxy,name.split("."))
359
- : Function("context", "extras", "with(context) { with(extras) { return `" + (safe ? template : Lightview.sanitizeTemplate(template)) + "` } }")(component.varsProxy,extras));
386
+ const parts = name ? name.split(".") : null;
387
+ let value;
388
+ value = (parts
389
+ ? (value = walk(extras,parts)) || (value = walk(component.varsProxy,parts)) || (value == null ? component[name] : value)
390
+ : Function("context", "extras", "with(context) { with(extras) { return populateTemplate`" + (safe ? template : Lightview.sanitizeTemplate(template)) + "` } }")(component.varsProxy,extras));
360
391
  //let value = Function("context", "with(context) { return `" + Lightview.sanitizeTemplate(template) + "` }")(component.varsProxy);
361
392
  if(typeof(value)==="function") return value;
362
393
  value = (name || node.nodeType === Node.TEXT_NODE || safe ? value : Lightview.escapeHTML(value));
363
394
  if (type === "string") return value==="undefined" ? undefined : value;
364
- if(name) {
365
- node.nodeValue = value==null ? "" : typeof(value)==="string" ? value : JSON.stringify(value);
366
- } else {
367
- node.nodeValue = value == "null" || value == "undefined" ? "" : value;
395
+
396
+ if(!(skipSetAttribute && node.nodeType===Node.ATTRIBUTE_NODE)) {
397
+ //requestAnimationFrame(() => {
398
+ if(name) node.nodeValue = value==null ? "" : typeof(value)==="string" ? value : JSON.stringify(value);
399
+ else node.nodeValue = value == "null" || value == "undefined" ? "" : value;
400
+ //})
368
401
  }
369
402
  return value;
370
403
  } catch (e) {
371
404
  //console.warn(e);
372
- if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
405
+ if (!e.message.includes("defined")) {
406
+ if(Lightview.renderErrors) return e.message;
407
+ throw e;
408
+ } // actually looking for undefined or not defined, but different browsers spell or quote differently
373
409
  return undefined;
374
410
  }
375
411
  }
376
412
  return node?.nodeValue;
377
413
  }
378
- const inputTypeToType = (inputType) => {
379
- if (!inputType) return "any"
380
- if (["text", "tel", "email", "url", "search", "radio", "color", "password"].includes(inputType)) return "string";
381
- if (["number", "range"].includes(inputType)) return "number";
382
- if (["datetime"].includes(inputType)) return Date;
383
- if (["checkbox"].includes(inputType)) return "boolean";
384
- return "any";
414
+ const inputTypeToType = inputType => {
415
+ const map = {
416
+ text:"string",
417
+ tel:"string",
418
+ email:"string",
419
+ url:"string",
420
+ search:"string",
421
+ radio:"string",
422
+ color:"string",
423
+ password:"string",
424
+ number:"number",
425
+ range:"number",
426
+ datetime:Date,
427
+ checkbox:"boolean"
428
+ }
429
+ return map[inputType] || "any";
385
430
  }
386
- const importAnchors = (node, component) => {
387
- [...node.querySelectorAll('a[href$=".html"][target^="#"]')].forEach((node) => {
388
- node.removeEventListener("click", anchorHandler);
389
- addListener(node, "click", anchorHandler);
390
- })
431
+ const enableAnchors = node => {
432
+ for(const anode of node.querySelectorAll('a[href$=".html"][target^="#"]')) {
433
+ anode.removeEventListener("click", anchorHandler);
434
+ addListener(anode, "click", anchorHandler);
435
+ }
391
436
  }
437
+ Object.defineProperty(Lightview,"enableAnchors",{value:enableAnchors});
438
+ //Lightview.enableAnchors = enableAnchors;
439
+
392
440
  const bound = new WeakSet();
393
441
  const bindInput = (input, variableName, component, value, object) => {
394
442
  if (bound.has(input)) return;
@@ -397,22 +445,30 @@ const {observe} = (() => {
397
445
  nameparts = variableName.split(".");
398
446
  let type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype);
399
447
  const variable = walk(component.vars,nameparts) || {type};
400
- if(type==="any") type = variable.type;
401
- if(value==null) value = input.getAttribute("value");
448
+ if(type==="any") type = variable?.type.type || variable?.type;
449
+ if(inputtype==="checkbox" && value==null) value = input.checked;
450
+ if(value==null) {
451
+ const avalue = input.getAttribute("value");
452
+ if(avalue) value = avalue;
453
+ }
402
454
  if(object && nameparts.length>1) {
403
- const [root,...path] = nameparts;
404
- object = walk(object,path,path.length-2,true);
405
- const key = path[path.length-1];
455
+ const [root,...path] = nameparts,
456
+ key = path[path.length-1];
457
+ object = walk(object,path,{depth:path.length-2,create:true});
406
458
  object[key] = coerce(value,type);
407
459
  } else {
408
- if (type !== variable.type) {
409
- if (variable.type === "any" || variable.type === "unknown") variable.type = type;
410
- else throw new TypeError(`Attempt to bind <input name="${variableName}" type="${type}"> to variable ${variableName}:${variable.type}`)
411
- }
412
460
  const existing = component.vars[variableName];
413
- if(!existing || existing.type!==type || !existing.reactive) component.variables({[variableName]: type},{reactive});
461
+ if(existing) {
462
+ existingtype = existing?.type.type || existing?.type;
463
+ if(existingtype!==type) throw new TypeError(`Attempt to bind <${input.tagName} name="${variableName}" type="${type}"> to variable ${variableName}:${existing.type}`)
464
+ existing.reactive = true;
465
+ } else if(Lightview.createInputVariables) {
466
+ component.variables({[variableName]: type},{reactive,set:typeof(value)==="string" && value.startsWith("${") ? "" : value});
467
+ } else {
468
+ throw new TypeError(`Attempt to bind undefined variable ${variableName} to <${input.tagName} type="${type}>`)
469
+ }
414
470
  if(inputtype!=="radio") {
415
- if(typeof(value)==="string" && value.includes("${")) input.attributes.value.value = "";
471
+ if(typeof(value)==="string" && value.includes("${")) input.setAttribute("value","");
416
472
  else component.setVariableValue(variableName, coerce(value,type));
417
473
  }
418
474
  }
@@ -434,32 +490,22 @@ const {observe} = (() => {
434
490
  }
435
491
  if(object) {
436
492
  const [root,...path] = nameparts;
437
- object = walk(object,nameparts,path.length-2,true);
493
+ object = walk(object,nameparts,{depth:path.length-2,create:true});
438
494
  } else {
439
- object = walk(component.varsProxy,nameparts,nameparts.length-2,true);
495
+ object = walk(component.varsProxy,nameparts,{depth:nameparts.length-2,create:true});
440
496
  }
441
497
  const key = nameparts[nameparts.length-1];
442
498
  object[key] = coerce(value,type);
443
499
  };
444
500
  addListener(input, eventname, listener,component);
445
501
  }
446
- const tryParse = (value) => {
502
+ const tryParse = value => {
447
503
  try {
448
504
  return JSON.parse(value);
449
505
  } catch (e) {
450
506
  return value;
451
507
  }
452
508
  }
453
- const observed = () => {
454
- return {
455
- init({variable, component}) {
456
- const name = variable.name;
457
- variable.value = component.hasAttribute(name) ? coerce(component.getAttribute(name), variable.type) : variable.value;
458
- variable.observed = true;
459
- component.observedAttributes.add(variable.name);
460
- }
461
- }
462
- }
463
509
  const reactive = () => {
464
510
  return {
465
511
  init({variable, component}) {
@@ -468,30 +514,21 @@ const {observe} = (() => {
468
514
  }
469
515
  }
470
516
  }
471
- const shared = () => {
472
- return {
473
- init({variable, component}) {
474
- variable.shared = true;
475
- /*addEventListener("change", ({variableName, value}) => {
476
- if (variableName===variable.name && component.vars[variableName]?.shared) component.siblings.forEach((instance) => instance.setVariableValue(variableName, value))
477
- })*/
478
- variable.set = function(newValue) {
479
- if(component.vars[this.name]?.shared) component.siblings.forEach((instance) => instance.setVariableValue(this.name, newValue));
480
- }
481
- }
482
- }
483
- }
484
517
  const exported = () => {
485
518
  return {
486
519
  init({variable, component}) {
487
- const name = variable.name;
520
+ const name = variable.name,
521
+ set = variable.set || (() => {});
488
522
  variable.exported = true;
489
523
  variable.set = (newValue) => {
490
- if(variable.exported) {
491
- if(newValue==null) {
524
+ set(newValue);
525
+ if(variable.exported) { // still exported
526
+ if(variable.value==null) {
492
527
  removeComponentAttribute(component, name);
493
528
  } else {
494
- newValue = typeof (newValue) === "string" ? newValue : JSON.stringify(newValue);
529
+ const type = typeof(newValue);
530
+ // note, using isArray below will not always work for proxied items, so using instanceOf
531
+ newValue = type === "string" ? newValue : (type==="object" && newValue instanceof Array) ? JSON.stringify([...newValue]) : JSON.stringify(newValue);
495
532
  setComponentAttribute(component, name, newValue);
496
533
  }
497
534
  }
@@ -503,26 +540,23 @@ const {observe} = (() => {
503
540
  const imported = () => {
504
541
  return {
505
542
  init({variable, component}) {
506
- const name = variable.name;
543
+ const name = variable.name.toLowerCase();
544
+ let value = component.hasAttribute(name) ? component.getAttribute(name) : null;
545
+ if((variable.type==="boolean" || variable.type.type==="boolean") && value==="") value = true;
507
546
  variable.imported = true;
508
- variable.value = component.hasAttribute(name) ? coerce(component.getAttribute(name), variable.type) : variable.value;
547
+ variable.value = value!=null ? coerce(value, variable.type) : variable.value;
548
+ if(variable.set) {
549
+ variable.set(variable.value);
550
+ }
509
551
  }
510
552
  }
511
553
  }
512
554
 
513
555
  let reserved = {
514
- observed: {
515
- constant: true,
516
- value: observed
517
- },
518
556
  reactive: {
519
557
  constant: true,
520
558
  value: reactive
521
559
  },
522
- shared: {
523
- constant: true,
524
- value: shared
525
- },
526
560
  exported: {
527
561
  constant: true,
528
562
  value: exported
@@ -531,24 +565,55 @@ const {observe} = (() => {
531
565
  constant: true,
532
566
  value: imported
533
567
  }
534
- };
535
- const createClass = (domElementNode, {observer, framed, href}) => {
568
+ }
569
+
570
+ // patches relative references to use the passed in href as base
571
+ const patchElementURIs = (node,tagName,attributeName,href) => { // attributeName will always be href or src
572
+ if(node) {
573
+ for(const child of node.querySelectorAll(tagName)) {
574
+ let value = child.getAttribute(attributeName);
575
+ if (value) child.setAttribute(attributeName, new URL(value, href).href);
576
+ }
577
+ }
578
+ }
579
+ // forces re-evaluation of scripts and links
580
+ const forceLoadElement = node => {
581
+ if((node.getAttribute("src")||"").includes("/lightview.js")) return;
582
+ const el = document.createElement(node.tagName.toLowerCase());
583
+ for(const attr of node.attributes) el.setAttribute(attr.name,attr.value);
584
+ el.innerHTML = node.innerHTML;
585
+ node.after(el);
586
+ node.remove();
587
+ }
588
+ const createClass = (domElementNode, {observer, framed, href= window.location.href.replace("blob:",""), mount}) => {
536
589
  const instances = new Set(),
537
- dom = domElementNode.tagName === "TEMPLATE"
538
- ? domElementNode.content.cloneNode(true)
539
- : domElementNode.cloneNode(true),
540
590
  observedAttributes = [];
541
- observedAttributes.add = function(name) { observedAttributes.includes(name) || observedAttributes.push(name); }
542
- if (domElementNode.tagName === "TEMPLATE") domElementNode = domElementNode.cloneNode(true);
543
- return class CustomElement extends HTMLElement {
591
+ let dom;
592
+ observedAttributes.add = function(name) { observedAttributes.includes(name) || observedAttributes.push(name) }
593
+ const cls = class CustomElement extends HTMLElement {
544
594
  static get instances() {
545
595
  return instances;
546
596
  }
597
+ static setTemplateNode(node) {
598
+ dom = node.tagName === "TEMPLATE"
599
+ ? document.createElement("div")
600
+ : node.cloneNode(true);
601
+ if(node.tagName === "TEMPLATE") {
602
+ dom.innerHTML = node.innerHTML;
603
+ for(const attr of node.attributes) dom.setAttribute(attr.name,attr.value);
604
+ }
605
+ patchElementURIs(dom.head,"link","href",href);
606
+ patchElementURIs(dom.head,"script","src",href);
607
+ patchElementURIs(dom.body||dom,"link","href",href);
608
+ patchElementURIs(dom.body||dom,"script","src",href);
609
+ dom.mount = node.mount || node.body?.mount;
610
+ }
547
611
  constructor() {
548
612
  super();
613
+ this.componentBaseURI = href;
614
+ currentComponent = window.currentComponent = document.currentComponent = this;
549
615
  instances.add(this);
550
- const currentComponent = this,
551
- shadow = this.attachShadow({mode: "open"}),
616
+ const shadow = this.attachShadow({mode: "open"}),
552
617
  eventlisteners = {};
553
618
  this.vars = {
554
619
  ...reserved,
@@ -560,7 +625,7 @@ const {observe} = (() => {
560
625
  addEventListener: {
561
626
  value: (eventName, listener) => {
562
627
  const listeners = eventlisteners[eventName] ||= new Set();
563
- [...listeners].forEach((f) => {
628
+ listeners.forEach((f) => {
564
629
  if (listener + "" === f + "") listeners.delete(f);
565
630
  })
566
631
  eventlisteners[eventName].add(listener);
@@ -580,19 +645,48 @@ const {observe} = (() => {
580
645
  },
581
646
  self: {value: currentComponent, type: CustomElement, constant: true}
582
647
  };
583
- this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
584
648
  this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
585
649
  if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported});
586
- ["getElementById", "querySelector", "querySelectorAll"]
650
+ ["getElementById", "querySelector"] //, "querySelectorAll"
651
+ .forEach((fname) => {
652
+ const f = this[fname];
653
+ Object.defineProperty(this, fname, {
654
+ configurable: true,
655
+ writable: true,
656
+ value: (...args) => { return (f ? f.call(this,...args) : null) || this.shadowRoot[fname](...args) }
657
+ })
658
+ });
659
+ ["querySelectorAll"]
587
660
  .forEach((fname) => {
661
+ const f = this[fname];
588
662
  Object.defineProperty(this, fname, {
589
663
  configurable: true,
590
664
  writable: true,
591
- value: (...args) => this.shadowRoot[fname](...args)
665
+ value: (...args) => { return [...f.call(this,...args),...this.shadowRoot[fname](...args)] }
592
666
  })
593
667
  });
594
- [...dom.childNodes].forEach((child) => shadow.appendChild(child.cloneNode(true)));
595
- importAnchors(shadow, this);
668
+ [...dom.head?.childNodes||[]].forEach((child) => {
669
+ const clone = child.cloneNode(true);
670
+ document.head.appendChild(clone);
671
+ if(["LINK","SCRIPT"].includes(clone.tagName)) forceLoadElement(clone);
672
+ });
673
+ const body = dom.body || dom;
674
+ for(const child of body.childNodes) {
675
+ if(child.tagName && customElements.get(child.tagName.toLowerCase())) {
676
+ const node = document.createElement(child.tagName);
677
+ for(const attr of child.attributes) node.setAttribute(attr.name,attr.value);
678
+ //currentComponent = window.currentComponent = document.currentComponent = node;
679
+ shadow.appendChild(node);
680
+ } else {
681
+ const clone = child.cloneNode(true);
682
+ shadow.appendChild(clone);
683
+ if(["LINK","SCRIPT"].includes(clone.tagName)) forceLoadElement(clone);
684
+ }
685
+ }
686
+ //forceLoadElements(shadow,"link");
687
+ //forceLoadElements(shadow,"script");
688
+ //importStyleSheets(shadow,this);
689
+ enableAnchors(shadow);
596
690
  }
597
691
 
598
692
  get siblings() {
@@ -607,117 +701,139 @@ const {observe} = (() => {
607
701
  instances.delete(this);
608
702
  }
609
703
 
610
- async connectedCallback() {
611
- const ctx = this,
612
- shadow = ctx.shadowRoot;
613
- for (const attr of this.defaultAttributes) this.hasAttribute(attr.name) || this.setAttribute(attr.name, attr.value);
614
- const scripts = shadow.querySelectorAll("script"),
615
- promises = [];
616
- for (const script of [...scripts]) {
617
- if (script.attributes.src?.value?.includes("/lightview.js")) continue;
618
- const text = script.innerHTML
619
- .replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1") // remove comments;
620
- .replaceAll(/\r?\n/g, "") // remove \n
621
- .replaceAll(/import\s*\((\s*["'][\.\/].*["'])\)/g,`import(new URL($1,"${href ? href : window.location.href}").href)`) // handle relative paths
622
- .replaceAll(/'(([^'\\]|\\.)*)'/g,"\\'$1\\'"); // handle quotes
623
- const currentScript = document.createElement("script");
624
- if (script.className !== "lightview" && !((script.attributes.type?.value || "").includes("lightview/"))) {
625
- for (const attr of script.attributes) currentScript.setAttribute(attr.name,attr.value);
626
- currentScript.innerHTML = text;
627
- shadow.appendChild(currentScript);
628
- await new Promise((resolve) => {
629
- const timeout = setTimeout(() => resolve(),500);
630
- currentScript.onload = () => {
631
- clearTimeout(timeout);
632
- currentScript.remove();
633
- resolve();
634
- }
635
- })
636
- continue;
637
- };
638
- const scriptid = Math.random() + "";
639
- for (const attr of script.attributes) {
640
- currentScript.setAttribute(attr.name, attr.name === "type" ? attr.value.replace("lightview/", "") : attr.value);
704
+ connectedCallback() {
705
+ const node = dom.body || dom;
706
+ for(const attr of node.attributes) {
707
+ if(!this.hasAttribute(attr.name)) this.setAttribute(attr.name,attr.value);
708
+ }
709
+ if(mount ||= dom.mount||this.mount) {
710
+ const script = document.createElement("script");
711
+ document.currentComponent = this;
712
+ script.innerHTML = `with(document.currentComponent.varsProxy) {
713
+ ${typeof(lightviewDebug)!=="undefined" && lightviewDebug===true ? "debugger;" : ""}
714
+ const component = document.currentComponent;
715
+ (async () => { await (${mount}).call(self,self);
716
+ component.compile(); })();
717
+ };`;
718
+ this.appendChild(script);
719
+ script.remove();
720
+ for(const child of this.querySelectorAll('link[rel="stylesheet"][export]')) { // ].forEach(async (child) =>
721
+ child.remove();
722
+ this.appendChild(child);
641
723
  }
642
- currentScript.classList.remove("lightview");
643
- currentScript.innerHTML = `Object.getPrototypeOf(async function(){}).constructor('if(window["${scriptid}"]?.ctx) { const ctx = window["${scriptid}"].ctx; { with(ctx) { ${text}; } } }')().then(() => window["${scriptid}"]()); `;
644
- await new Promise((resolve) => {
645
- window[scriptid] = () => {
646
- delete window[scriptid];
647
- currentScript.remove();
648
- script.remove();
649
- resolve();
650
- }
651
- window[scriptid].ctx = ctx.varsProxy;
652
- shadow.appendChild(currentScript);
653
- })
724
+ window.currentComponent = document.currentComponent = null;
654
725
  }
726
+ }
727
+ compile() {
655
728
  // Promise.all(promises).then(() => {
656
- const nodes = getNodes(ctx),
657
- processNodes = (nodes,object) => {
729
+ const shadow = this.shadowRoot,
730
+ nodes = getNodes(this,href),
731
+ processNodes = (nodes,{object,extras}={}) => { //rootName
658
732
  nodes.forEach((node) => {
659
733
  if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
660
- observe(() => resolveNodeOrText(node, this,true,node.extras));
734
+ const observer = observe(((node,ctx) => () => resolveNodeOrText(node, ctx,true,extras))(node,this));
735
+ node.observers ||= new Set();
736
+ node.observers.add(observer);
737
+ if(node.parentElement?.tagName==="TEXTAREA") {
738
+ const name = getTemplateVariableName(node.template);
739
+ if (name) {
740
+ const nameparts = name.split(".");
741
+ if(extras && extras[nameparts[0]]) object = extras[nameparts[0]];
742
+ if(!this.vars[nameparts[0]] || this.vars[nameparts[0]].reactive || object) {
743
+ bindInput(node.parentElement, name, this, resolveNodeOrText(node.template, this,true,extras), object);
744
+ }
745
+ }
746
+ }
661
747
  } else if (node.nodeType === Node.ELEMENT_NODE) {
748
+ if(node.tagName==="FORM") {
749
+ const value = node.getAttribute("value"),
750
+ name = getTemplateVariableName(value);
751
+ if(name) {
752
+ node.addEventListener("submit",(event) => {
753
+ if(!event.target.hasAttribute("action")) event.preventDefault();
754
+ const object = {};
755
+ [...node.querySelectorAll("input[name]"),...node.querySelectorAll("textarea[name]")]
756
+ .forEach((input) => {
757
+ const eltype = input.getAttribute("type"),
758
+ path = input.getAttribute("name").split("."),
759
+ property = path[path.length-1],
760
+ target = walk(object,path,{depth:path.length-2,create:true})
761
+ if(["radio","checkbox"].includes(eltype)) {
762
+ target[property] = input.checked;
763
+ } else {
764
+ target[property] = input.value;
765
+ }
766
+ });
767
+ this.varsProxy[name] = object;
768
+ })
769
+ }
770
+ return;
771
+ }
662
772
  // resolve the value before all else;
663
773
  const attr = node.attributes.value,
664
774
  template = attr?.template;
665
775
  if (attr && template) {
666
776
  //let value = resolveNodeOrText(attr, this),
667
777
  // ;
668
- const eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx, false,node.extras) : null,
669
- template = attr.template;
778
+ const eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, this, false,extras) : null,
779
+ aname = node.getAttribute("name"),
780
+ template = attr.template;// || (rootName && aname ? `\${${rootName}.${name}}` : null);
670
781
  if (template) {
671
782
  const name = getTemplateVariableName(template);
672
783
  if (name) {
673
784
  const nameparts = name.split(".");
674
- if(node.extras && node.extras[nameparts[0]]) {
675
- object = node.extras[nameparts[0]];
676
- }
785
+ if(extras && extras[nameparts[0]]) object = extras[nameparts[0]];
677
786
  if(!this.vars[nameparts[0]] || this.vars[nameparts[0]].reactive || object) {
678
- bindInput(node, name, this, resolveNodeOrText(attr, this,false,node.extras), object);
787
+ bindInput(node, name, this, resolveNodeOrText(attr, this,false,extras), object);
679
788
  }
680
789
  }
681
- observe(() => {
682
- const value = resolveNodeOrText(template, ctx,false,node.extras);
790
+ const observer = observe(((node,ctx,template) => () => {
791
+ const value = resolveNodeOrText(template, ctx,false,extras);
683
792
  if(value!==undefined) {
684
793
  if (eltype === "checkbox") {
685
794
  if (coerce(value, "boolean") === true) {
686
795
  node.setAttribute("checked", "");
687
- node.checked = true;
796
+ //node.checked = true; // do we need to do this and set attribute
688
797
  } else {
689
798
  node.removeAttribute("checked");
690
- node.checked = false;
799
+ //node.checked = false;
691
800
  }
692
801
  } else if (node.tagName === "SELECT") {
693
- let values = [value];
694
- if (node.hasAttribute("multiple")) values = coerce(value, Array);
802
+ const values = node.hasAttribute("multiple") ? coerce(value, Array) : [value];
695
803
  [...node.querySelectorAll("option")].forEach(async (option) => {
696
804
  if (option.hasAttribute("value")) {
697
- if (values.includes(resolveNodeOrText(option.attributes.value, ctx,false,node.extras))) {
805
+ if (values.includes(resolveNodeOrText(option.attributes.value, ctx,false,extras))) {
698
806
  option.setAttribute("selected", "");
699
- option.selected = true;
807
+ //option.selected = true;
700
808
  }
701
- } else if (values.includes(resolveNodeOrText(option.innerText, ctx,false,node.extras))) {
809
+ } else if (values.includes(resolveNodeOrText(option.innerText, ctx,false,extras))) {
702
810
  option.setAttribute("selected", "");
703
- option.selected = true;
811
+ //option.selected = true;
704
812
  }
705
813
  })
706
814
  } else if (eltype!=="radio") {
815
+ //attr.value = typeof(value)==="string" ? value : JSON.stringify(value);
816
+ let avalue = typeof(value)==="string" ? value : value.toString ? value.toString() : JSON.stringify(value);
817
+ if(avalue.startsWith('"')) avalue = avalue.substring(1);
818
+ if(avalue.endsWith('"')) avalue = avalue.substring(0,avalue.length-1);
707
819
  attr.value = value;
820
+ if(node.tagName==="INPUT") node.value = value;
708
821
  }
709
822
  }
710
- });
823
+ })(node,this,template));
824
+ node.observers ||= new Set();
825
+ node.observers.add(observer);
711
826
  }
712
827
  }
713
- [...node.attributes].forEach(async (attr) => {
714
- if (attr.name === "value" && attr.template) return;
828
+ for(const attr of node.attributes) {
829
+ if (attr.name === "value" && attr.template) continue;
715
830
  const {name, value} = attr,
716
831
  vname = node.attributes.name?.value;
717
- if (name === "type" && value=="radio" && vname) {
832
+ if (value.includes("${")) attr.template = value;
833
+ if (name === "type" && value == "radio" && vname) {
718
834
  bindInput(node, vname, this, undefined, object);
719
- observe(() => {
720
- const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
835
+ const observer = observe(((node,ctx) => () => {
836
+ const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
721
837
  if (node.attributes.value.value == varvalue) {
722
838
  node.setAttribute("checked", "");
723
839
  node.checked = true;
@@ -725,117 +841,122 @@ const {observe} = (() => {
725
841
  node.removeAttribute("checked");
726
842
  node.checked = false;
727
843
  }
728
- });
844
+ })(node,this));
845
+ node.observers ||= new Set();
846
+ node.observers.add(observer);
729
847
  }
730
-
731
848
  const [type, ...params] = name.split(":");
732
849
  if (type === "") { // name is :something
733
- observe(() => {
850
+ const observer = observe(((node,attr,ctx)=>() => {
734
851
  const value = attr.value;
735
852
  if (params[0]) {
736
853
  if (value === "true") node.setAttribute(params[0], "")
737
854
  else node.removeAttribute(params[0]);
738
855
  } else {
739
- const elvalue = node.attributes.value ? resolveNodeOrText(node.attributes.value, ctx,false,node.extras) : null,
740
- eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx,false,node.extras) : null;
856
+ const elvalue = node.attributes.value ? resolveNodeOrText(node.attributes.value, ctx, false, extras) : null,
857
+ eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx, false, extras) : null;
741
858
  if (eltype === "checkbox" || node.tagName === "OPTION") {
742
859
  if (elvalue === true) node.setAttribute("checked", "")
743
860
  else node.removeAttribute("checked");
744
861
  }
745
862
  }
746
- })
863
+ })(node,attr,this));
864
+ node.observers ||= new Set();
865
+ node.observers.add(observer);
747
866
  } else if (type === "l-on") {
748
867
  let listener;
749
- observe(() => {
750
- const value = resolveNodeOrText(attr, this,true,node.extras);
868
+ const observer = observe(((node,attr,ctx) => () => {
869
+ const value = resolveNodeOrText(attr, ctx, true, extras,true);
751
870
  if (listener) node.removeEventListener(params[0], listener);
752
871
  listener = null;
753
- if(typeof(value)==="function") {
872
+ if (typeof (value) === "function") {
754
873
  listener = value;
755
874
  } else {
756
875
  try {
757
876
  listener = Function("return " + value)();
758
- } catch(e) {
877
+ } catch (e) {
759
878
 
760
879
  }
761
880
  }
762
- if(listener) addListener(node, params[0], listener,ctx);
763
- })
881
+ if (listener) addListener(node, params[0], listener, ctx);
882
+ })(node,attr,this));
883
+ node.observers ||= new Set();
884
+ node.observers.add(observer);
764
885
  } else if (type === "l-if") {
765
- observe(() => {
766
- const value = resolveNodeOrText(attr, this,true,node.extras);
767
- node.style.setProperty("display", value == true ? "revert" : "none");
768
- })
886
+ const observer = observe(((node,attr,ctx) => () => {
887
+ const value = resolveNodeOrText(attr, ctx, true, extras);
888
+ node.style.setProperty("display", value == true || value === "true" ? "revert" : "none");
889
+ })(node,attr,this));
890
+ node.observers ||= new Set();
891
+ node.observers.add(observer);
769
892
  } else if (type === "l-for") {
770
- node.template ||= node.innerHTML;
771
- node.clone ||= node.cloneNode(true);
772
- observe(() => {
773
- const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
774
- value = resolveNodeOrText(attr, this,false,node.extras),
775
- coerced = coerce(value, what === "each" ? Array : "object"),
776
- target = what === "each" ? coerced : Object[what](coerced),
777
- children = target.reduce((children,item,i,target) => {
778
- const clone = node.clone.cloneNode(true),
779
- extras = node.extras = {
780
- [vname]: item,
781
- [index]: i,
782
- [array]: target
783
- },
784
- nodes = [...getNodes(clone)].map((node) => {
785
- node.extras = extras;
786
- return node;
787
- });
788
- processNodes(nodes);
789
- children.push(...clone.childNodes);
790
- return children;
791
- },[]);
792
- if (!window.lightviewDebug) {
793
- if (after) {
794
- node.style.setProperty("display", "none")
893
+ //node.template ||= node.innerHTML;
894
+ //node.clone ||= node.cloneNode(true);
895
+ let clone = node.cloneNode(true),
896
+ observer = observe(((node,attr,ctx) => () => {
897
+ const [what = "each", vname = "item", index = "index", array = "array", after = false] = params;
898
+ /*if (!window.lightviewDebug) {
899
+ requestAnimationFrame(() => {
900
+ if (after) node.style.setProperty("display", "none")
901
+ else node.innerHTML = "";
902
+ })
903
+ }*/
904
+ const value = resolveNodeOrText(attr, ctx, false, extras,true),
905
+ coerced = coerce(value, what === "each" ? Array : "object"),
906
+ target = what === "each" ? coerced : Object[what](value),
907
+ children = target.reduce((children, item, i, target) => {
908
+ const child = clone.cloneNode(true),
909
+ extras = {
910
+ [vname]: item,
911
+ [index]: i,
912
+ [array]: target
913
+ },
914
+ nodes = getNodes(child, href);
915
+ processNodes(nodes,{extras});
916
+ children.push(...child.childNodes);
917
+ return children;
918
+ }, []);
919
+ //requestAnimationFrame(() => {
920
+ if(children.length===0) {
921
+ node.innerHTML = ""
795
922
  } else {
796
- while (node.lastElementChild) node.lastElementChild.remove();
923
+ children.forEach((child,i) => {
924
+ if (after) node.parentElement.insertBefore(child, node);
925
+ else if(node?.childNodes[i]) {
926
+ const old = node.childNodes[i];
927
+ if(old.observers) {
928
+ old.observers.forEach((observer) => observer.cancel())
929
+ old.observers.clear();
930
+ old.observers = null;
931
+ }
932
+ node.replaceChild(child,old);
933
+ }
934
+ else node.appendChild(child);
935
+ })
936
+ while(node.childNodes.length>children.length) node.lastChild.remove();
797
937
  }
798
- }
799
- //const nodes = getNodes(parsed.body);
800
- children.forEach((child) => {
801
- //while (parsed.body.firstChild) {
802
- //const child = parsed.body.firstChild;
803
- if (after) node.parentElement.insertBefore(child, node);
804
- else node.appendChild(child);
805
- })
806
- //processNodes(nodes);
807
- })
808
- } else if(attr.template) {
809
- observe(() => {
810
- resolveNodeOrText(attr, this,false,node.extras);
811
- })
938
+ // });
939
+ })(node,attr,this,extras));
940
+ node.observers ||= new Set();
941
+ node.observers.add(observer);
942
+ } else if (attr.template) {
943
+ const observer = observe(((node,attr,ctx,extras) => () => {
944
+ resolveNodeOrText(attr, ctx, false, extras);
945
+ })(node,attr,this,extras));
946
+ node.observers ||= new Set();
947
+ node.observers.add(observer);
812
948
  }
813
- })
949
+ }
814
950
  }
815
951
  })
816
952
  };
817
- nodes.forEach((node) => {
818
- if(node.tagName==="FORM") {
819
- const value = node.getAttribute("value"),
820
- name = getTemplateVariableName(value);
821
- if(name) {
822
- const childnodes = [...nodes].filter((childnode) => node!==childnode && node.contains(childnode));
823
- childnodes.forEach((node) => nodes.delete(node));
824
- const variable = ctx.vars[name] ||= {type: "object", reactive:true, value: Reactor({})};
825
- if(variable.type !== "object" || !variable.reactive || !variable.value || typeof(variable.value)!=="object") {
826
- throw new TypeError(`Can't bind form ${node.getAttribute("id")} to non-object variable ${name}`);
827
- }
828
- processNodes(childnodes,variable.value);
829
- }
830
- }
831
- })
832
953
  processNodes(nodes);
833
954
  shadow.normalize();
834
- observer ||= createObserver(ctx, framed);
835
- observer.observe(ctx, {attributeOldValue: true, subtree:true, characterData:true, characterDataOldValue:true});
955
+ observer ||= createObserver(this, framed);
956
+ observer.observe(this, {attributeOldValue: true, subtree:true, characterData:true, characterDataOldValue:true});
836
957
  if(this.hasAttribute("l-unhide")) this.removeAttribute("hidden");
837
958
  //ctx.vars.postEvent.value("connected");
838
- this.dispatchEvent(new Event("connected"));
959
+ this.dispatchEvent(new Event("mounted"));
839
960
  // })
840
961
  }
841
962
  adoptedCallback(callback) {
@@ -856,6 +977,13 @@ const {observe} = (() => {
856
977
  .filter(name => !(name in reserved) && !["self", "addEventListener", "postEvent","observe"].includes(name))
857
978
  }
858
979
 
980
+ getVariableData() {
981
+ return this.getVariableNames().reduce((data,name) => {
982
+ data[name] = this.getVariableValue(name);
983
+ return data;
984
+ },{})
985
+ }
986
+
859
987
  getVariable(name) {
860
988
  return this.vars[name] ? {...this.vars[name]} : undefined;
861
989
  }
@@ -871,11 +999,11 @@ const {observe} = (() => {
871
999
  const variable = this.vars[variableName];
872
1000
  if (variable.shared) {
873
1001
  value = type.validate ? type.validate(value,variable) : coerce(value,coerceTo);
874
- const event = new VariableEvent({
1002
+ const event = {
875
1003
  variableName: variableName,
876
1004
  value: value,
877
1005
  oldValue: variable.value
878
- });
1006
+ };
879
1007
  variable.value = value;
880
1008
  this.vars.postEvent.value("change", event);
881
1009
  if (event.defaultPrevented) variable.value = value;
@@ -893,29 +1021,31 @@ const {observe} = (() => {
893
1021
  return this.vars[variableName]?.value;
894
1022
  }
895
1023
 
896
- variables(variables, {remote, constant,set,...rest} = {}) { // options = {observed,reactive,shared,exported,imported}
897
- const options = {remote, constant,...rest},
898
- addEventListener = this.varsProxy.addEventListener;
1024
+ variables(variables, {remote, constant, set,...rest} = {}) { // options = {observed,reactive,shared,exported,imported}
1025
+ const options = {remote, constant, ...rest};
899
1026
  if (variables !== undefined) {
900
1027
  Object.entries(variables)
901
1028
  .forEach(([key, type]) => {
902
1029
  if(isArrowFunction(type)) type = type();
903
1030
  const variable = this.vars[key] ||= {name: key, type};
904
1031
  if(set!==undefined && constant!==undefined) throw new TypeError(`${key} has the constant value ${constant} and can't be set to ${set}`);
905
- variable.value = set;
1032
+ if(set!==undefined) variable.value = set;
906
1033
  if(constant!==undefined) {
1034
+ if(remote || rest.imported || rest.observed) throw new TypeError(`${key} can't be a constant and also remote, imported or observed`)
907
1035
  variable.constant = true;
908
1036
  variable.value = constant;
909
1037
  }
910
1038
  if (remote) {
911
- if(typeof(remote)==="function") remote = remote(`./${key}`);
912
- variable.remote = remote;
913
- remote.handleRemote({variable, config:remote.config,component:this});
1039
+ const type = typeof(remote);
1040
+ if(type==="function") variable.remote = remote(`./${key}`);
1041
+ else if(this.vars.remote.value && type==="string") variable.remote = this.vars.remote.value(remote);
1042
+ else throw new TypeError("Attempt to use type 'remote' without importing 'remote' from types.js")
1043
+ variable.remote.handleRemote({variable, config:variable.remote.config,component:this});
914
1044
  }
915
1045
  // todo: handle custom functional types, remote should actually be handled this way
916
1046
  Object.entries(rest).forEach(([type,f]) => {
917
1047
  const functionalType = variable[type] = typeof(f)==="function" ? f() : f;
918
- if(functionalType.init) functionalType.init({variable,options,component:this});
1048
+ if(functionalType.init) functionalType.init({variable,options,component:this,coerce});
919
1049
  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`);
920
1050
  variable.set != functionalType.set;
921
1051
  variable.get != functionalType.get;
@@ -930,16 +1060,18 @@ const {observe} = (() => {
930
1060
  }, {});
931
1061
  }
932
1062
  }
1063
+ cls.setTemplateNode(domElementNode);
1064
+ return cls;
933
1065
  }
934
1066
 
935
- const createComponent = (name, node, {framed, observer, href} = {}) => {
1067
+ const createComponent = (name, node, {framed, observer, href, mount} = {}) => {
936
1068
  let ctor = customElements.get(name);
937
1069
  if (ctor) {
938
1070
  if (framed && !ctor.lightviewFramed) ctor.lightviewFramed = true;
939
1071
  else console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
940
1072
  return ctor;
941
1073
  }
942
- ctor = createClass(node, {observer, framed, href});
1074
+ ctor = createClass(node, {observer, framed, href, mount});
943
1075
  customElements.define(name, ctor);
944
1076
  Lightview.customElements.set(name, ctor);
945
1077
  return ctor;
@@ -947,40 +1079,46 @@ const {observe} = (() => {
947
1079
  Lightview.customElements = new Map();
948
1080
  Lightview.createComponent = createComponent;
949
1081
  const importLink = async (link, observer) => {
950
- const url = (new URL(link.getAttribute("href"), window.location.href)),
1082
+ const url = (new URL(link.getAttribute("href"),document.baseURI)),
951
1083
  as = link.getAttribute("as") || getNameFromPath(url.pathname);
952
- if (url.hostname !== window.location.hostname && !link.getAttribute("crossorigin")) {
1084
+ if (url.origin !== window.location.origin && !link.getAttribute("crossorigin")) {
953
1085
  throw new URIError(`importLink:HTML imports must be from same domain: ${url.hostname}!=${location.hostname} unless 'crossorigin' attribute is set.`)
954
1086
  }
955
1087
  if (!customElements.get(as)) {
956
1088
  const html = await (await fetch(url.href)).text(),
957
1089
  dom = parser.parseFromString(html, "text/html"),
958
- unhide = !!dom.head.querySelector('meta[name="l-unhide"]'),
959
- links = dom.head.querySelectorAll('link[href$=".html"][rel=module]');
960
- for (const childlink of links) {
961
- const href = childlink.getAttribute("href"),
962
- childurl = new URL(href, url.href);
963
- childlink.setAttribute("href", childurl.href);
1090
+ unhide = !!dom.head.querySelector('meta[name="l-unhide"]');
1091
+ for (const childlink of dom.head.querySelectorAll('link[href]')) {
1092
+ childlink.setAttribute("href", new URL(childlink.getAttribute("href"), url.href).href);
964
1093
  if (link.hasAttribute("crossorigin")) childlink.setAttribute("crossorigin", link.getAttribute("crossorigin"))
965
- await importLink(childlink, observer);
1094
+ if(childlink.getAttribute("href").endsWith(".html") && childlink.getAttribute("rel")==="module") await importLink(childlink, observer);
1095
+ }
1096
+ currentComponent = window.currentComponent = document.currentComponent = dom.body;
1097
+ currentComponent.componentBaseURI = url.href;
1098
+ const lvscript = dom.getElementById("lightview");
1099
+ if(lvscript) {
1100
+ const script = document.createElement("script");
1101
+ script.innerHTML = lvscript.innerHTML;
1102
+ document.body.appendChild(script);
1103
+ script.remove();
966
1104
  }
1105
+ currentComponent = window.currentComponent = document.currentComponent = null;
1106
+ createComponent(as, dom, {observer,href:url.href});
967
1107
  if (unhide) dom.body.removeAttribute("hidden");
968
- createComponent(as, dom.body, {observer,href:url.href});
969
1108
  }
970
1109
  return {as};
971
1110
  }
972
1111
  const importLinks = async () => {
973
1112
  const observer = createObserver(document.body);
974
- for (const link of [...document.querySelectorAll(`link[href$=".html"][rel=module]`)]) {
1113
+ for (const link of document.querySelectorAll(`link[href$=".html"][rel=module]`)) {
975
1114
  await importLink(link, observer);
976
1115
  }
977
1116
  }
978
1117
 
979
1118
  const bodyAsComponent = ({as = "x-body", unhide, framed} = {}) => {
980
- const parent = document.body.parentElement;
981
1119
  createComponent(as, document.body, {framed});
982
1120
  const component = document.createElement(as);
983
- parent.replaceChild(component, document.body);
1121
+ document.body.parentElement.replaceChild(component, document.body);
984
1122
  Object.defineProperty(document, "body", {
985
1123
  enumerable: true, configurable: true, get() {
986
1124
  return component;
@@ -992,10 +1130,10 @@ const {observe} = (() => {
992
1130
  const postMessage = (data, target = window.parent) => {
993
1131
  if (postMessage.enabled) {
994
1132
  if (target instanceof HTMLIFrameElement) {
995
- data = {...data, href: window.location.href};
1133
+ data = {...data, href: document.baseURI};
996
1134
  target.contentWindow.postMessage(JSON.stringify(data), "*");
997
1135
  } else {
998
- data = {...data, iframeId: document.lightviewId, href: window.location.href};
1136
+ data = {...data, iframeId: document.lightviewId, href: document.baseURI};
999
1137
  target.postMessage(JSON.stringify(data), "*");
1000
1138
  }
1001
1139
  }
@@ -1018,11 +1156,11 @@ const {observe} = (() => {
1018
1156
  resizeObserver.observe(node);
1019
1157
  };
1020
1158
 
1021
- const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
1159
+ const url = new URL(document.currentScript.getAttribute("src"), document.baseURI);
1022
1160
  let domContentLoadedEvent;
1023
1161
  if (!domContentLoadedEvent) addListener(window, "DOMContentLoaded", (event) => domContentLoadedEvent = event);
1024
1162
  let OBSERVER;
1025
- const loader = async (whenFramed) => {
1163
+ const loader = async whenFramed => {
1026
1164
  await importLinks();
1027
1165
  const unhide = !!document.querySelector('meta[name="l-unhide"]'),
1028
1166
  isolated = !!document.querySelector('meta[name="l-isolate"]'),
@@ -1043,20 +1181,14 @@ const {observe} = (() => {
1043
1181
  onresize(document.body, () => resize());
1044
1182
  return
1045
1183
  }
1046
- if (type === "setAttribute") {
1047
- const [name, value] = [...argsList],
1048
- variable = document.body.vars[name];
1184
+ if (["setAttribute","removeAttribute"].includes(type)) {
1185
+ let [name, value] = [...argsList];
1186
+ const variable = document.body.vars[name];
1187
+ if(type==="removeAttribute") value = undefined;
1049
1188
  if (variable && variable.imported) document.body.setVariableValue(name, value);
1050
- return;
1051
- }
1052
- if (type === "removeAttribute") {
1053
- const [name] = argsList[0],
1054
- variable = document.body.vars[name];
1055
- if (variable && variable.imported) document.body.setVariableValue(name, undefined);
1056
-
1057
1189
  }
1058
1190
  });
1059
- const url = new URL(window.location.href);
1191
+ const url = new URL(document.baseURI);
1060
1192
  document.lightviewId = url.searchParams.get("id");
1061
1193
  postMessage({type: "DOMContentLoaded"})
1062
1194
  }
@@ -1070,7 +1202,7 @@ const {observe} = (() => {
1070
1202
  iframe = document.getElementById(iframeId);
1071
1203
  if (iframe) {
1072
1204
  if (type === "DOMContentLoaded") {
1073
- postMessage({type: "framed", href: window.location.href}, iframe);
1205
+ postMessage({type: "framed", href: document.baseURI}, iframe);
1074
1206
  Object.defineProperty(domContentLoadedEvent, "currentTarget", {
1075
1207
  enumerable: false,
1076
1208
  configurable: true,
@@ -1127,9 +1259,11 @@ const {observe} = (() => {
1127
1259
  }
1128
1260
  }
1129
1261
  };
1130
- const observer = OBSERVER = new MutationObserver(mutationCallback),
1131
- iframe = document.getElementById("myframe");
1132
- observer.observe(iframe, {attributes: true, attributeOldValue: true});
1262
+ const observer = OBSERVER = new MutationObserver(mutationCallback);
1263
+ // iframe = document.getElementById("myframe");
1264
+ for(const iframe of document.body.querySelectorAll("iframe")) {
1265
+ observer.observe(iframe, {attributes: true, attributeOldValue: true});
1266
+ }
1133
1267
  }
1134
1268
  }
1135
1269
  }
@@ -1137,8 +1271,10 @@ const {observe} = (() => {
1137
1271
  // loads for framed content
1138
1272
  addListener(document, "DOMContentLoaded", (event) => loader(callback));
1139
1273
  }
1140
- Lightview.whenFramed = whenFramed;
1141
-
1274
+ Object.defineProperty(Lightview,"whenFramed",{value:whenFramed});
1275
+ //Lightview.whenFramed = whenFramed;
1276
+ Lightview.loader = loader;
1277
+ //debugger;
1142
1278
  if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
1143
1279
  // loads for unframed content
1144
1280
  // CodePen mucks with window.parent