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