lightview 1.4.5-b → 1.4.10-b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # lightview v1.4.5b (BETA)
1
+ # lightview v1.4.10b (BETA)
2
2
 
3
3
  Small, simple, powerful web UI and micro front end creation ...
4
4
 
5
- Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 6K (minified/gzipped).
5
+ Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 7K (minified/gzipped).
6
6
 
7
7
  See the docs and examples at [https://lightview.dev](https://lightview.dev).
8
8
 
@@ -0,0 +1,2 @@
1
+ body.split-output #output > .active { width: 75%; }
2
+ body.split-output #output > #result-box.active { width: 25% }
@@ -9,6 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <script type="lightview/module">
12
+ debugger;
12
13
  self.variables({
13
14
  count: number
14
15
  }, {
@@ -2,7 +2,7 @@
2
2
 
3
3
  <head>
4
4
  <title>Directives</title>
5
- <script src="./lightview.js?as=x-body"></script>
5
+ <script src="../lightview.js?as=x-body"></script>
6
6
  </head>
7
7
 
8
8
  <body>
@@ -4,7 +4,7 @@
4
4
  <title>Remote</title>
5
5
  <link type="module" src="">
6
6
  <meta name="l-enableFrames">
7
- <script src="./lightview.js"></script>
7
+ <script src="../lightview.js"></script>
8
8
  </head>
9
9
 
10
10
  <body>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <head>
4
4
  <title>Form</title>
5
- <script src="./lightview.js?as=x-body"></script>
5
+ <script src="../lightview.js?as=x-body"></script>
6
6
  <script>Lightview.whenFramed(({as,unhide,importAnchors,isolated,enableFrames}) => {
7
7
  Lightview.bodyAsComponent({as,unhide,importAnchors,isolated,enableFrames});
8
8
  })</script>
@@ -0,0 +1,65 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Form</title>
6
+ <script src="../lightview.js?as=x-body"></script>
7
+ </head>
8
+
9
+ <body>
10
+ <div style="margin:20px;padding:5px;border:1px;border-style:solid;border-color:${color}">
11
+ <p>
12
+ <input type="text" value="${color}">
13
+ <input type="radio" name="color" value="red">
14
+ <input type="radio" name="color" value="yellow">
15
+ <input type="radio" name="color" value="green">
16
+ <select value="${color}">
17
+ <option value="red">red</option>
18
+ <option>yellow</option>
19
+ <option> green</option>
20
+ </select>
21
+ <div>Hamburger options:</div>
22
+ <select value="${hamburger}" multiple>
23
+ <option value="lettuce">lettuce</option>
24
+ <option value="tomato">tomato</option>
25
+ <option>cheese</option>
26
+ </select>
27
+ </p>
28
+ Expose: <input type="checkbox" value="${checked}">
29
+ <p l-if="${checked}">
30
+ Now you've done it. You've exposed me.
31
+ </p>
32
+ <p id="variables">
33
+
34
+ </p>
35
+ </div>
36
+ <script type="lightview/module">
37
+ self.variables({
38
+ color: string,
39
+ checked: boolean,
40
+ hamburger: Array
41
+ }, {
42
+ reactive
43
+ });
44
+ color = "red";
45
+ checked = true;
46
+ hamburger = ["cheese"];
47
+
48
+ // demo instrumentation
49
+ const variableValues = () => {
50
+ const el = self.getElementById("variables");
51
+ while (el.lastElementChild) el.lastElementChild.remove();
52
+ self.getVariableNames().forEach((name) => {
53
+ const line = document.createElement("div");
54
+ line.innerText = `${name} = ${JSON.stringify(self.getValue(name))}`;
55
+ el.appendChild(line);
56
+ });
57
+ };
58
+ variableValues();
59
+ addEventListener("change", () => {
60
+ variableValues()
61
+ });
62
+ </script>
63
+ </body>
64
+
65
+ </html>
@@ -0,0 +1,13 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="UTF-8">
4
+ <title>Message</title>
5
+ </head>
6
+ <body>
7
+ ${value}
8
+ <script type="lightview/module">
9
+ debugger;
10
+ self.variables({value:string},{imported});
11
+ </script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <head>
3
+ <title>Nested</title>
4
+ <link href="./message.html" rel="module">
5
+ <script src="../lightview.js?as=x-body"></script>
6
+ </head>
7
+ <body>
8
+ <l-message value="Hello One"></l-message>
9
+ <l-message value="Hello Two"></l-message>
10
+ </body>
11
+ </html>
@@ -0,0 +1,51 @@
1
+ const http = require("http"),
2
+ fs = require("fs"),
3
+ host = 'localhost',
4
+ port = 8000,
5
+ requestListener = async function (req, res) {
6
+ const path = `.${req.url}`;
7
+ res.setHeader("Access-Control-Allow-Origin","*");
8
+ res.setHeader("Access-Control-Allow-Methods", "*");
9
+ res.setHeader("Access-Control-Allow-Headers", "*");
10
+ res.setHeader("Content-Type", "application/json");
11
+ if(req.method==="OPTIONS") {
12
+ res.end();
13
+ return;
14
+ }
15
+ if(req.method==="GET") {
16
+ console.log("GET",req.url);
17
+ res.write(fs.readFileSync(path));
18
+ res.end();
19
+ return;
20
+ }
21
+ const buffers = [];
22
+ for await(const chunk of req) {
23
+ buffers.push(chunk);
24
+ }
25
+ const data = JSON.parse(Buffer.concat(buffers).toString());
26
+ console.log(req.method,req.url,data);
27
+ if(req.method==="PUT") {
28
+ const string = JSON.stringify(data);
29
+ fs.writeFileSync(path,string);
30
+ res.write(string);
31
+ res.end();
32
+ return;
33
+ }
34
+ if(req.method==="PATCH") {
35
+ const {property,value,oldValue} = data,
36
+ json = JSON.parse(fs.readFileSync(path));
37
+ if(property!==undefined && json[property]===oldValue) { // probably need a deepEqual for a production use
38
+ json[property] = value;
39
+ fs.writeFileSync(path,JSON.stringify(json))
40
+ }
41
+ res.write(JSON.stringify(json));
42
+ res.end();
43
+ return;
44
+ }
45
+ },
46
+ server = http.createServer(requestListener);
47
+ server.listen(port, host, () => {
48
+ console.log(`Server is running on http://${host}:${port}`);
49
+ });
50
+
51
+
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Remote</title>
6
+ <script src="../lightview.js?as=x-body"></script>
7
+ </head>
8
+ <body>
9
+
10
+ <input id="myRemote" type=text" value="${JSON.stringify(myRemote)}" size="${JSON.stringify(myRemote).length}"><br>
11
+ <button l-on:click="patch">Patch</button><br>
12
+ <button l-on:click="replace">Replace</button>
13
+
14
+
15
+ <script type="lightview/module">
16
+ self.variables({myRemote:object},{reactive,remote:"http://localhost:8000/remote.json"});
17
+
18
+ await myRemote; // must await remotes before the first time they are used, e.g. before HTML is rendered
19
+
20
+ self.patch = () => {
21
+ const json = JSON.parse(document.body.getElementById("myRemote").value);
22
+ Object.assign(myRemote,json);
23
+ };
24
+
25
+ self.replace = () => {
26
+ myRemote = JSON.parse(document.body.getElementById("myRemote").value);
27
+ };
28
+ </script>
29
+ </body>
30
+ </html>
@@ -0,0 +1 @@
1
+ {"name":"joe","age":20}
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <title>Scratch</title>
6
- <script src="./lightview.js?as=x-body"></script>
6
+ <script src="../lightview.js?as=x-body"></script>
7
7
  </head>
8
8
  <body>
9
9
  <div style="margin:20px;padding:5px;border:1px;border-style:solid;border-color:${color}">
@@ -0,0 +1,33 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Template</title>
6
+ <template id="local-component">
7
+ <p>
8
+ <button l-on:click="click">Click Me</button>
9
+ </p>
10
+ <p>
11
+ ${message ? message : ""}
12
+ </p>
13
+ <script type="lightview/module">
14
+ self.variables({message: string}, {reactive});
15
+
16
+ self.click = (event) => {
17
+ message = "Hi there!";
18
+ };
19
+ </script>
20
+ </template>
21
+ <script src="../lightview.js"></script>
22
+ <script>
23
+ Lightview.createComponent("x-hello", document.getElementById("local-component"));
24
+ </script>
25
+ </head>
26
+
27
+ <body>
28
+ <div style="margin:20px">
29
+ <x-hello></x-hello>
30
+ </div>
31
+ </body>
32
+
33
+ </html>
@@ -0,0 +1,10 @@
1
+ <!DOCTYPE html>
2
+ <head>
3
+ <title>Top</title>
4
+ <link href="./nested.html" rel="module">
5
+ <script src="../lightview.js?as=x-body"></script>
6
+ </head>
7
+ <body>
8
+ <l-nested></l-nested>
9
+ </body>
10
+ </html>
@@ -0,0 +1,73 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Types</title>
6
+ <script src="../lightview.js?as=x-body"></script>
7
+ </head>
8
+
9
+ <body>
10
+ <div style="margin:20px">
11
+ <p>
12
+ <button l-on:click="run">Run</button>
13
+ <button l-on:click="clear">Clear</button>
14
+ </p>
15
+ <p id="console"></p>
16
+ </div>
17
+ <script type="lightview/module">
18
+ self.variables({
19
+ astring: string,
20
+ aDate: Date,
21
+ err: Error
22
+ });
23
+ self.run = () => {
24
+ try {
25
+ astring = "my string";
26
+ } catch (e) {
27
+ err = e;
28
+ }
29
+ try {
30
+ astring = 1;
31
+ } catch (e) {
32
+ err = e;
33
+ }
34
+ try {
35
+ aDate = new Date();
36
+ } catch (e) {
37
+ err = e;
38
+ }
39
+ try {
40
+ aDate = 1;
41
+ } catch (e) {
42
+ err = e;
43
+ }
44
+ try {
45
+ err = 1;
46
+ } catch (e) {
47
+ err = e;
48
+ }
49
+ };
50
+ // demo instrumentation
51
+ self.clear = () => {
52
+ const cnsl = self.getElementById("console");
53
+ while (cnsl.lastChild) cnsl.lastChild.remove();
54
+ };
55
+ addEventListener("change", ({
56
+ variableName,
57
+ value
58
+ }) => {
59
+ const cnsl = self.getElementById("console");
60
+ if (cnsl) {
61
+ const message = document.createElement("div");
62
+ if (variableName === "err") {
63
+ message.innerHTML = `<b>&gt;</b> ${value}<br>`;
64
+ } else {
65
+ message.innerHTML = `<b>&gt;</b> ${variableName} = ${value}<br>`;
66
+ }
67
+ cnsl.appendChild(message);
68
+ }
69
+ });
70
+ </script>
71
+ </body>
72
+
73
+ </html>
@@ -29,7 +29,7 @@
29
29
  </script>
30
30
  </template>
31
31
  <title>Form</title>
32
- <script src="./lightview.js"></script>
32
+ <script src="../lightview.js"></script>
33
33
  <script>
34
34
  Lightview.createComponent("x-audiostream", document.getElementById("audiostream"))
35
35
  </script>
package/lightview.js CHANGED
@@ -30,6 +30,24 @@ const {observe} = (() => {
30
30
  let CURRENTOBSERVER;
31
31
  const parser = new DOMParser();
32
32
 
33
+ const templateSanitizer = (string) => {
34
+ return string.replace(/function\s+/g, "")
35
+ .replace(/function\(/g, "")
36
+ .replace(/=\s*>/g, "")
37
+ .replace(/(while|do|for|alert)\s*\(/g, "")
38
+ .replace(/console\.[a-zA-Z$]+\s*\(/g, "");
39
+ }
40
+ Lightview.sanitizeTemplate = templateSanitizer;
41
+
42
+ const escaper = document.createElement('textarea');
43
+
44
+ function escapeHTML(html) {
45
+ escaper.textContent = html;
46
+ return escaper.innerHTML;
47
+ }
48
+
49
+ Lightview.escapeHTML = escapeHTML;
50
+
33
51
  const addListener = (node, eventName, callback) => {
34
52
  node.addEventListener(eventName, callback); // just used to make code footprint smaller
35
53
  }
@@ -52,7 +70,8 @@ const {observe} = (() => {
52
70
  return "l-" + name;
53
71
  }
54
72
  const observe = (f, thisArg, argsList = []) => {
55
- function observer(...args) {
73
+ const observer = (...args) => {
74
+ if(observer.cancelled) return;
56
75
  CURRENTOBSERVER = observer;
57
76
  try {
58
77
  f.call(thisArg || this, ...argsList, ...args);
@@ -61,7 +80,6 @@ const {observe} = (() => {
61
80
  }
62
81
  CURRENTOBSERVER = null;
63
82
  }
64
-
65
83
  observer.cancel = () => observer.cancelled = true;
66
84
  observer();
67
85
  return observer;
@@ -73,7 +91,7 @@ const {observe} = (() => {
73
91
  if (toType === "number") return parseFloat(value + "");
74
92
  if (toType === "boolean") {
75
93
  if (["on", "checked", "selected"].includes(value)) return true;
76
- if(value==null || value==="") return false;
94
+ if (value == null || value === "") return false;
77
95
  try {
78
96
  const parsed = JSON.parse(value + "");
79
97
  if (typeof (parsed) === "boolean") return parsed;
@@ -96,10 +114,10 @@ const {observe} = (() => {
96
114
  if (instance instanceof Array) {
97
115
  let parsed = tryParse(value.startsWith("[") ? value : `[${value}]`);
98
116
  if (!Array.isArray(parsed)) {
99
- if(value.includes(",")) parsed = value.split(",");
117
+ if (value.includes(",")) parsed = value.split(",");
100
118
  else {
101
119
  parsed = tryParse(`["${value}"]`);
102
- if(!Array.isArray(parsed) || parsed[0]!==value && parsed.length!==1) parsed = null;
120
+ if (!Array.isArray(parsed) || parsed[0] !== value && parsed.length !== 1) parsed = null;
103
121
  }
104
122
  }
105
123
  if (!Array.isArray(parsed)) {
@@ -128,14 +146,16 @@ const {observe} = (() => {
128
146
  }
129
147
  throw new TypeError(`Unable to coerce ${value} to ${toType}`)
130
148
  }
131
- const Reactor = (value) => {
132
- if (value && typeof (value) === "object") {
133
- if (value.__isReactor__) return value;
149
+ const Reactor = (data) => {
150
+ if (data && typeof (data) === "object") {
151
+ if (data.__isReactor__) return data;
134
152
  const childReactors = [],
135
153
  dependents = {},
136
- proxy = new Proxy(value, {
154
+ proxy = new Proxy(data, {
137
155
  get(target, property) {
138
156
  if (property === "__isReactor__") return true;
157
+ if(property=== "__dependents__") return dependents;
158
+ if(property=== "__reactorProxyTarget__") return data;
139
159
  if (target instanceof Array) {
140
160
  if (property === "toJSON") return function toJSON() {
141
161
  return [...target];
@@ -150,9 +170,10 @@ const {observe} = (() => {
150
170
  const observers = dependents[property] ||= new Set();
151
171
  observers.add(CURRENTOBSERVER)
152
172
  }
173
+ if(value===undefined) return;
153
174
  if (childReactors.includes(value) || (value && type !== "object") || typeof (property) === "symbol") {
154
- // Dated must be bound to work with proxies
155
- if (type === "function" && [Date].includes(value)) value = value.bind(target)
175
+ // Dates and Promises must be bound to work with proxies
176
+ if (type === "function" && ([Date].includes(value) || property==="then")) value = value.bind(target)
156
177
  return value;
157
178
  }
158
179
  if (value && type === "object") {
@@ -162,8 +183,14 @@ const {observe} = (() => {
162
183
  target[property] = value;
163
184
  return value;
164
185
  },
165
- set(target, property, value) {
186
+ async set(target, property, value) {
187
+ if(target instanceof Promise) {
188
+ console.warn(`Setting ${property} = ${value} on a Promise in Reactor`);
189
+ }
166
190
  const type = typeof (value);
191
+ if(value && type==="object" && value instanceof Promise) {
192
+ value = await value;
193
+ }
167
194
  if (target[property] !== value) {
168
195
  if (value && type === "object") {
169
196
  value = Reactor(value);
@@ -181,7 +208,7 @@ const {observe} = (() => {
181
208
  });
182
209
  return proxy;
183
210
  }
184
- return value;
211
+ return data;
185
212
  }
186
213
 
187
214
  class VariableEvent {
@@ -198,6 +225,7 @@ const {observe} = (() => {
198
225
  return value;
199
226
  },
200
227
  set(target, property, newValue) {
228
+ //if(newValue && typeof(newValue)==="object" && newValue instanceof Promise) newValue = await newValue;
201
229
  const event = new VariableEvent({variableName: property, value: newValue});
202
230
  if (target[property] === undefined) {
203
231
  target[property] = {type: "any", value: newValue}; // should we allow this, do first to prevent loops
@@ -205,7 +233,7 @@ const {observe} = (() => {
205
233
  if (event.defaultPrevented) delete target[property].value;
206
234
  return true;
207
235
  }
208
- const {type, value, shared, exported, constant, reactive} = target[property];
236
+ const {type, value, shared, exported, constant, reactive, remote} = target[property];
209
237
  if (constant) throw new TypeError(`${property}:${type} is a constant`);
210
238
  const newtype = typeof (newValue),
211
239
  typetype = typeof (type);
@@ -214,7 +242,11 @@ const {observe} = (() => {
214
242
  event.oldValue = value;
215
243
  target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
216
244
  target.postEvent.value("change", event);
217
- if (event.defaultPrevented) target[property].value = value;
245
+ if (event.defaultPrevented) {
246
+ target[property].value = value;
247
+ } else if(remote) {
248
+ handleRemote({variable:target[property],remote,reactive},true);
249
+ }
218
250
  }
219
251
  return true;
220
252
  }
@@ -232,7 +264,7 @@ const {observe} = (() => {
232
264
  const observer = new MutationObserver((mutations) => {
233
265
  mutations.forEach((mutation) => {
234
266
  if (mutation.type === "attributes") {
235
- if (framed) debugger;
267
+ //if (framed) debugger;
236
268
  const name = mutation.attributeName,
237
269
  target = mutation.target,
238
270
  value = target.getAttribute(name);
@@ -275,39 +307,48 @@ const {observe} = (() => {
275
307
  nodes.push(root, ...getNodes(root.shadowRoot))
276
308
  } else {
277
309
  for (const node of root.childNodes) {
310
+ if (node.tagName === "SCRIPT") continue;
278
311
  if (node.nodeType === Node.TEXT_NODE && node.nodeValue?.includes("${")) {
279
312
  node.template ||= node.nodeValue;
280
313
  nodes.push(node);
281
314
  } else if (node.nodeType === Node.ELEMENT_NODE) {
282
- let skip;
283
- if (node.getAttribute("type") === "radio") nodes.push(node);
284
- [...node.attributes].forEach((attr) => {
285
- if (attr.value.includes("${")) {
286
- attr.template ||= attr.value;
287
- nodes.push(node);
288
- } else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
289
- skip = attr.name.includes("l-for:");
290
- nodes.push(node)
291
- }
292
- })
315
+ let skip, pushed;
316
+ [...node.attributes].forEach((attr) => {
317
+ if (attr.value.includes("${")) {
318
+ attr.template ||= attr.value;
319
+ pushed = true;
320
+ nodes.push(node);
321
+ } else if (attr.name.includes(":") || attr.name.startsWith("l-")) {
322
+ skip = attr.name.includes("l-for:");
323
+ pushed = true;
324
+ nodes.push(node)
325
+ }
326
+ })
327
+ if (!pushed && node.getAttribute("type") === "radio") nodes.push(node);
293
328
  if (!skip && !node.shadowRoot) nodes.push(...getNodes(node));
294
329
  }
295
330
  }
296
331
  }
297
332
  return nodes;
298
333
  }
299
- const resolveNode = (node, component) => {
300
- if (node?.template) {
334
+ const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
335
+ const resolveNodeOrText = (node, component, safe) => {
336
+ const type = typeof (node),
337
+ template = type === "string" ? node.trim() : node.template;
338
+ if (template) {
301
339
  try {
302
- const value = Function("context", "with(context) { return `" + node.template + "` }")(component.varsProxy);
303
- node.nodeValue = value === "null" || value === "undefined" ? "" : value;
340
+ let value = Function("context", "with(context) { return `" + Lightview.sanitizeTemplate(template) + "` }")(component.varsProxy);
341
+ value = node.nodeType === Node.TEXT_NODE || !safe ? value : Lightview.escapeHTML(value);
342
+ if (type === "string") return value;
343
+ node.nodeValue = value == "null" || value == "undefined" ? "" : value;
304
344
  } catch (e) {
345
+ console.warn(e);
305
346
  if (!e.message.includes("defined")) throw e; // actually looking for undefined or not defined
306
347
  }
307
348
  }
308
349
  return node?.nodeValue;
309
350
  }
310
- const render = (hasTemplate, render) => {
351
+ const render = (hasTemplate, render) => {
311
352
  let observer;
312
353
  if (hasTemplate) {
313
354
  if (observer) observer.cancel();
@@ -324,43 +365,47 @@ const {observe} = (() => {
324
365
  if (["checkbox"].includes(inputType)) return "boolean";
325
366
  return "any";
326
367
  }
327
- const _importAnchors = (node, component) => {
328
- [...node.querySelectorAll('a[href][target^="#"]')].forEach((node) => {
368
+ const importAnchors = (node, component) => {
369
+ [...node.querySelectorAll('a[href$=".html"][target^="#"]')].forEach((node) => {
329
370
  node.removeEventListener("click", anchorHandler);
330
371
  addListener(node, "click", anchorHandler);
331
372
  })
332
373
  }
333
- const bindInput = (input, name, component,value) => {
374
+ const bound = new WeakSet();
375
+ const bindInput = (input, variableName, component, value) => {
376
+ if (bound.has(input)) return;
377
+ bound.add(input);
334
378
  const inputtype = input.tagName === "SELECT" || input.tagName === "TEXTAREA" ? "text" : input.getAttribute("type"),
335
379
  type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
336
380
  deflt = input.getAttribute("default");
337
381
  value ||= input.getAttribute("value");
338
- let variable = component.vars[name] || {type};
382
+ let variable = component.vars[variableName] || {type};
339
383
  if (type !== variable.type) {
340
384
  if (variable.type === "any" || variable.type === "unknown") variable.type = type;
341
- else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
385
+ else throw new TypeError(`Attempt to bind <input name="${variableName}" type="${type}"> to variable ${variableName}:${variable.type}`)
342
386
  }
343
- component.variables({[name]: type});
344
- component.setValue(name,value);
387
+ component.variables({[variableName]: type});
388
+ if(inputtype!=="radio") component.setValue(variableName, value);
345
389
  let eventname = "change";
346
390
  if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
347
391
  eventname = "input";
348
392
  }
349
- addListener(input, eventname, (event) => {
350
- event.stopImmediatePropagation();
351
- const target = event.target;
352
- let value = target.value;
393
+ const listener = (event) => {
394
+ if (event) event.stopImmediatePropagation();
395
+ let value = input.value;
353
396
  if (inputtype === "checkbox") {
354
397
  value = input.checked
355
- } else if (target.tagName === "SELECT") {
356
- if (target.hasAttribute("multiple")) {
357
- value = [...target.querySelectorAll("option")]
358
- .filter((option) => option.selected || resolveNode(option.attributes.value, component) == value || option.innerText == value)
398
+ } else if (input.tagName === "SELECT") {
399
+ if (input.hasAttribute("multiple")) {
400
+ const varvalue = component.varsProxy[variableName];
401
+ value = [...input.querySelectorAll("option")]
402
+ .filter((option) => option.selected || resolveNodeOrText(option.attributes.value || option.innerText, component) === value) //todo make sync comopat
359
403
  .map((option) => option.getAttribute("value") || option.innerText);
360
404
  }
361
405
  }
362
- component.varsProxy[name] = coerce(value, type);
363
- })
406
+ component.varsProxy[variableName] = coerce(value, type);
407
+ };
408
+ addListener(input, eventname, listener);
364
409
  }
365
410
  const tryParse = (value) => {
366
411
  try {
@@ -370,16 +415,19 @@ const {observe} = (() => {
370
415
  }
371
416
  }
372
417
  let reserved = {
418
+ any: {value: "any", constant: true},
373
419
  boolean: {value: "boolean", constant: true},
374
420
  string: {value: "string", constant: true},
375
421
  number: {value: "number", constant: true},
422
+ object: {value: "object", constant: true},
376
423
  observed: {value: true, constant: true},
377
424
  reactive: {value: true, constant: true},
378
425
  shared: {value: true, constant: true},
379
426
  exported: {value: true, constant: true},
380
- imported: {value: true, constant: true}
427
+ imported: {value: true, constant: true},
428
+ remote: {}
381
429
  };
382
- const createClass = (domElementNode, {observer, importAnchors, framed}) => {
430
+ const createClass = (domElementNode, {observer, framed}) => {
383
431
  const instances = new Set(),
384
432
  dom = domElementNode.tagName === "TEMPLATE"
385
433
  ? domElementNode.content.cloneNode(true)
@@ -399,6 +447,17 @@ const {observe} = (() => {
399
447
  eventlisteners = {};
400
448
  this.vars = {
401
449
  ...reserved,
450
+ changeListener: {
451
+ value: ({variableName, value}) => {
452
+ if (currentComponent.vars.changeListener.value.targets.has(variableName)) {
453
+ value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
454
+ if (value == null) removeComponentAttribute(this, variableName);
455
+ else setComponentAttribute(this, variableName, value);
456
+ }
457
+ },
458
+ type: "function",
459
+ constant: true
460
+ },
402
461
  addEventListener: {
403
462
  value: (eventName, listener) => {
404
463
  const listeners = eventlisteners[eventName] ||= new Set();
@@ -423,6 +482,8 @@ const {observe} = (() => {
423
482
  };
424
483
  this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
425
484
  this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
485
+ this.vars.changeListener.value.targets = new Set();
486
+ this.varsProxy.addEventListener("change", this.varsProxy.changeListener);
426
487
  if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported: true});
427
488
  ["getElementById", "querySelector", "querySelectorAll"]
428
489
  .forEach((fname) => {
@@ -433,7 +494,7 @@ const {observe} = (() => {
433
494
  })
434
495
  });
435
496
  [...dom.childNodes].forEach((child) => shadow.appendChild(child.cloneNode(true)));
436
- if (importAnchors) _importAnchors(shadow, this);
497
+ importAnchors(shadow, this);
437
498
  }
438
499
 
439
500
  get siblings() {
@@ -464,7 +525,7 @@ const {observe} = (() => {
464
525
  }
465
526
  currentScript.classList.remove("lightview");
466
527
  const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
467
- currentScript.innerHTML = `Function('if(window["${scriptid}"]?.ctx) { with(window["${scriptid}"].ctx) { ${text}; } window["${scriptid}"](); }')(); `;
528
+ currentScript.innerHTML = `Object.getPrototypeOf(async function(){}).constructor('if(window["${scriptid}"]?.ctx) { with(window["${scriptid}"].ctx) { ${text}; } window["${scriptid}"](); }')(); `;
468
529
  let resolver;
469
530
  promises.push(new Promise((resolve) => resolver = resolve));
470
531
  window[scriptid] = () => {
@@ -479,19 +540,19 @@ const {observe} = (() => {
479
540
  const nodes = getNodes(ctx);
480
541
  nodes.forEach((node) => {
481
542
  if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
482
- render(!!node.template, () => resolveNode(node, this))
543
+ render(!!node.template, () => resolveNodeOrText(node, this))
483
544
  } else if (node.nodeType === Node.ELEMENT_NODE) {
484
545
  // resolve the value before all else;
485
546
  const attr = node.attributes.value;
486
547
  if (attr && attr.template) {
487
- render(!!attr.template, () => {
488
- const value = resolveNode(attr, this),
489
- eltype = resolveNode(node.attributes.type, ctx);
490
- if(node.attributes.value) {
548
+ render(!!attr.template, () => {
549
+ const value = resolveNodeOrText(attr, this),
550
+ eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
551
+ if (node.attributes.value) {
491
552
  const template = attr.template;
492
- if(/\$\{[a-zA-z_]+\}/g.test(template)) {
493
- const name = template.substring(2,template.length-1);
494
- if(!name.includes(" ")) bindInput(node,name,this,value);
553
+ if (/\$\{[a-zA-z_]+\}/g.test(template)) {
554
+ const name = template.substring(2, template.length - 1);
555
+ bindInput(node, name, this, value);
495
556
  }
496
557
  }
497
558
  if (eltype === "checkbox") {
@@ -502,19 +563,19 @@ const {observe} = (() => {
502
563
  node.removeAttribute("checked");
503
564
  node.checked = false;
504
565
  }
505
- const vname = resolveNode(node.attributes.name, ctx);
566
+ const vname = resolveNodeOrText(node.attributes.name, ctx);
506
567
  if (vname) ctx.setValue(vname, node.checked, {coerceTo: "boolean"});
507
568
  }
508
569
  if (node.tagName === "SELECT") {
509
570
  let values = [value];
510
571
  if (node.hasAttribute("multiple")) values = coerce(value, Array);
511
- [...node.querySelectorAll("option")].forEach((option) => {
572
+ [...node.querySelectorAll("option")].forEach(async (option) => {
512
573
  if (option.hasAttribute("value")) {
513
- if (values.includes(resolveNode(option.attributes.value, ctx))) {
574
+ if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
514
575
  option.setAttribute("selected", "");
515
576
  option.selected = true;
516
577
  }
517
- } else if (option.innerText.trim() === value) {
578
+ } else if (values.includes(resolveNodeOrText(option.innerText, ctx))) {
518
579
  option.setAttribute("selected", "");
519
580
  option.selected = true;
520
581
  }
@@ -522,18 +583,18 @@ const {observe} = (() => {
522
583
  }
523
584
  });
524
585
  }
525
- [...node.attributes].forEach((attr) => {
526
- if (attr.name === "value") return;
586
+ [...node.attributes].forEach(async (attr) => {
587
+ if (attr.name === "value" && attr.template) return;
527
588
  const {name, value} = attr;
528
589
  if (name === "type") {
529
- if (value === "radio") {
530
- const name = resolveNode(node.attributes.name, ctx);
590
+ if (value === "radio" && node.attributes.name) {
591
+ const name = resolveNodeOrText(node.attributes.name, ctx);
531
592
  for (const vname of this.getVariableNames()) {
532
593
  if (vname === name) {
533
- render(true, () => {
534
- const name = resolveNode(node.attributes.name, ctx),
535
- varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
536
- if (varvalue == resolveNode(node.attributes.value, ctx)) {
594
+ render(true, () => {
595
+ const name = resolveNodeOrText(node.attributes.name, ctx),
596
+ varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
597
+ if (node.attributes.value && varvalue == resolveNodeOrText(node.attributes.value, ctx)) {
537
598
  node.setAttribute("checked", "");
538
599
  node.checked = true;
539
600
  } else {
@@ -550,45 +611,48 @@ const {observe} = (() => {
550
611
 
551
612
  const [type, ...params] = name.split(":");
552
613
  if (type === "") { // name is :something
553
- render(!!attr.template, () => {
554
- const value = attr.value,
555
- elvalue = resolveNode(node.attributes.value, ctx),
556
- eltype = resolveNode(node.attributes.type, ctx),
557
- elname = resolveNode(node.attributes.name, ctx);
614
+ render(!!attr.template, () => {
615
+ const value = attr.value;
558
616
  if (params[0]) {
559
617
  if (value === "true") node.setAttribute(params[0], "")
560
618
  else node.removeAttribute(params[0]);
561
- } else if (eltype === "checkbox" || node.tagName === "OPTION") {
562
- if (value === "true") node.setAttribute("checked", "")
563
- else node.removeAttribute("checked");
619
+ } else {
620
+ const elvalue = node.attributes.value ? resolveNodeOrText(node.attributes.value, ctx) : null,
621
+ eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
622
+ if (eltype === "checkbox" || node.tagName === "OPTION") {
623
+ if (elvalue === "true") node.setAttribute("checked", "")
624
+ else node.removeAttribute("checked");
625
+ }
564
626
  }
565
627
  })
566
628
  } else if (type === "l-on") {
567
629
  let listener;
568
- render(!!attr.template, () => {
569
- const value = resolveNode(attr, this);
630
+ render(!!attr.template, () => {
631
+ const value = resolveNodeOrText(attr, this);
570
632
  if (listener) node.removeEventListener(params[0], listener);
571
633
  listener = this[value] || window[value] || Function(value);
572
634
  addListener(node, params[0], listener);
573
635
  })
574
636
  } else if (type === "l-if") {
575
- render(!!attr.template, () => {
576
- const value = resolveNode(attr, this);
637
+ render(!!attr.template, () => {
638
+ const value = resolveNodeOrText(attr, this);
577
639
  node.style.setProperty("display", value === "true" ? "revert" : "none");
578
640
  })
579
641
  } else if (type === "l-for") {
580
642
  node.template ||= node.innerHTML;
581
- render(!!attr.template, () => {
643
+ render(!!attr.template, () => {
582
644
  const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
583
- value = resolveNode(attr, this),
645
+ value = resolveNodeOrText(attr, this),
584
646
  coerced = coerce(value, what === "each" ? Array : "object"),
585
647
  target = what === "each" ? coerced : Object[what](coerced),
586
- html = target.reduce((html, item, i, target) => {
587
- return html += Function("context", "with(context) { return `" + node.template + "` }")({
588
- [vname]: item,
589
- [index]: i,
590
- [array]: target
591
- })
648
+ html = target.reduce( (html, item, i, target) => {
649
+ return html += Function("vars", "context", "with(vars) { with(context) { return `" + node.template + "` }}")(
650
+ ctx.varsProxy,
651
+ {
652
+ [vname]: item,
653
+ [index]: i,
654
+ [array]: target
655
+ })
592
656
  }, ""),
593
657
  parsed = parser.parseFromString(html, "text/html");
594
658
  if (!window.lightviewDebug) {
@@ -604,7 +668,7 @@ const {observe} = (() => {
604
668
  }
605
669
  })
606
670
  } else if (attr.template) {
607
- render(!!attr.template, () => resolveNode(attr, this));
671
+ render(!!attr.template, () => resolveNodeOrText(attr, this));
608
672
  }
609
673
  })
610
674
  }
@@ -620,23 +684,23 @@ const {observe} = (() => {
620
684
  //Object.defineProperty(this, "adoptedCallback", {configurable: true, writable: true, value});
621
685
  }
622
686
 
623
- connected(value) {
624
- this.connectedCallback = value;
687
+ connected(callback) {
688
+ this.connectedCallback = callback;
625
689
  //Object.defineProperty(this, "connectedCallback", {configurable: true, writable: true, value});
626
690
  }
627
691
 
628
- attributeChanged(value) {
629
- this.attributeChangedCallback = value;
692
+ attributeChanged(callback) {
693
+ this.attributeChangedCallback = callback;
630
694
  //Object.defineProperty(this, "attributeChangedCallback", {configurable: true, writable: true, value});
631
695
  }
632
696
 
633
- disconnected(value) {
697
+ disconnected(callback) {
634
698
  Object.defineProperty(this, "disconnectedCallback", {
635
699
  configurable: true,
636
700
  writable: true,
637
701
  value: () => {
638
702
  value();
639
- super.disconnectedCallback(value);
703
+ super.disconnectedCallback(callback);
640
704
  }
641
705
  });
642
706
  }
@@ -647,19 +711,19 @@ const {observe} = (() => {
647
711
  })
648
712
  }
649
713
 
650
- setValue(name, value, {shared, coerceTo = typeof (value)} = {}) {
714
+ setValue(variableName, value, {coerceTo = typeof (value)} = {}) {
651
715
  if (!this.isConnected) {
652
716
  instances.delete(this);
653
717
  return false;
654
718
  }
655
- let {type} = this.vars[name] || {};
719
+ let {type} = this.vars[variableName] || {};
656
720
  if (type) {
657
721
  value = coerce(value, type);
658
- if (this.varsProxy[name] !== value) {
659
- const variable = this.vars[name];
722
+ if (this.varsProxy[variableName] !== value) {
723
+ const variable = this.vars[variableName];
660
724
  if (variable.shared) {
661
725
  const event = new VariableEvent({
662
- variableName: name,
726
+ variableName: variableName,
663
727
  value: value,
664
728
  oldValue: variable.value
665
729
  });
@@ -667,12 +731,12 @@ const {observe} = (() => {
667
731
  this.vars.postEvent.value("change", event);
668
732
  if (event.defaultPrevented) variable.value = value;
669
733
  } else {
670
- this.varsProxy[name] = value;
734
+ this.varsProxy[variableName] = value;
671
735
  }
672
736
  }
673
737
  return true;
674
738
  }
675
- this.vars[name] = {name, type: coerceTo, value: coerce(value, coerceTo)};
739
+ this.vars[variableName] = {name, type: coerceTo, value: coerce(value, coerceTo)};
676
740
  return false;
677
741
  }
678
742
 
@@ -680,7 +744,7 @@ const {observe} = (() => {
680
744
  return this.vars[variableName]?.value;
681
745
  }
682
746
 
683
- variables(variables, {observed, reactive, shared, exported, imported} = {}) { // options = {observed,reactive,shared,exported,imported}
747
+ variables(variables, {observed, reactive, shared, exported, imported, remote} = {}) { // options = {observed,reactive,shared,exported,imported}
684
748
  const addEventListener = this.varsProxy.addEventListener;
685
749
  if (variables !== undefined) {
686
750
  Object.entries(variables)
@@ -707,11 +771,11 @@ const {observe} = (() => {
707
771
  variable.exported = true;
708
772
  // in case the export goes up to an iframe
709
773
  if (variable.value != null) setComponentAttribute(this, key, variable.value);
710
- addEventListener("change", ({variableName, value}) => {
711
- value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
712
- if (value == null) removeComponentAttribute(this, variableName);
713
- else setComponentAttribute(this, variableName, value);
714
- })
774
+ this.vars.changeListener.value.targets.add(key);
775
+ }
776
+ if (remote) {
777
+ variable.remote = remote;
778
+ handleRemote({variable, remote, reactive});
715
779
  }
716
780
  });
717
781
  }
@@ -741,7 +805,136 @@ const {observe} = (() => {
741
805
  }
742
806
  }
743
807
  }
744
- const createComponent = (name, node, {observer, importAnchors, framed} = {}) => {
808
+
809
+ const remoteProxy = ({json, variable,remote, reactive}) => {
810
+ const type = typeof (remote);
811
+ return new Proxy(json, {
812
+ get(target,property) {
813
+ if(property==="__remoteProxytarget__") return json;
814
+ return target[property];
815
+ },
816
+ async set(target, property, value) {
817
+ if(value && typeof(value)==="object" && value instanceof Promise) value = await value;
818
+ const oldValue = target[property];
819
+ if (oldValue !== value) {
820
+ let remotevalue;
821
+ if (type === "string") {
822
+ const href = new URL(remote,window.location.href).href;
823
+ remotevalue = patch({target,property,value,oldValue},href);
824
+ } else if(remote && type==="object") {
825
+ let href;
826
+ if(remote.path) href = new URL(remote.path,window.location.href).href;
827
+ if(!remote.patch) {
828
+ if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
829
+ remote.patch = patch;
830
+ }
831
+ remotevalue = remote.patch({target,property,value,oldValue},href);
832
+ }
833
+ if(remotevalue) {
834
+ await remotevalue.then((newjson) => {
835
+ if (newjson && typeof (newjson) === "object" && reactive) {
836
+ const target = variable.value?.__reactorProxyTarget__ ? json : variable.value;
837
+ Object.entries(newjson).forEach(([key,newValue]) => {
838
+ if(target[key]!==newValue) {
839
+ target[key] = newValue;
840
+ }
841
+ })
842
+ Object.keys(target).forEach((key) => {
843
+ if(!(key in newjson)) {
844
+ delete target[key];
845
+ }
846
+ });
847
+ if(variable.value?.__reactorProxyTarget__) {
848
+ const dependents = variable.value.__dependents__,
849
+ observers = dependents[property] || [];
850
+ [...observers].forEach((f) => {
851
+ if (f.cancelled) dependents[property].delete(f);
852
+ else f();
853
+ })
854
+ }
855
+ } else {
856
+ variable.value = json;
857
+ }
858
+ })
859
+ }
860
+ }
861
+ return true;
862
+ }
863
+ })
864
+ }
865
+
866
+ const patch = ({target,property,value,oldValue},href) => {
867
+ return fetch(href, {
868
+ method: "PATCH",
869
+ body: JSON.stringify({property,value,oldValue}),
870
+ headers: {
871
+ "Content-Type": "application/json"
872
+ }
873
+ }).then((response) => {
874
+ if (response.status < 400) return response.json();
875
+ })
876
+ }
877
+
878
+ const get = ({variable,remote,reactive},path) => {
879
+ return fetch(path)
880
+ .then((response) => {
881
+ if (response.status < 400) return response.json();
882
+ })
883
+ }
884
+
885
+ const put = ({variable,remote,reactive},href) => {
886
+ return fetch(href, {
887
+ method: "PUT",
888
+ body: JSON.stringify(variable.value),
889
+ headers: {
890
+ "Content-Type": "application/json"
891
+ }
892
+ }).then((response) => {
893
+ if (response.status === 200) {
894
+ return response.json();
895
+ }
896
+ })
897
+ }
898
+
899
+ const handleRemote = async ({variable, remote, reactive},doput) => {
900
+ const type = typeof (remote);
901
+ let value;
902
+ if (type === "string") {
903
+ const href = new URL(remote,window.location.href).href;
904
+ value = (doput
905
+ ? put({variable, remote, reactive},href)
906
+ : get({variable, remote, reactive},href));
907
+ if(variable.value===undefined) variable.value = value;
908
+ } else if (remote && type === "object") {
909
+ let href;
910
+ if(remote.path) href = new URL(remote.path,window.location.href).href;
911
+ if(!remote.get || !remote.put) {
912
+ if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
913
+ if(!remote.get) remote.get = get;
914
+ if(!remote.put) remote.put = put;
915
+ }
916
+ value = (doput
917
+ ? remote.put({variable, remote, reactive},href)
918
+ : remote.get({variable, remote, reactive},href));
919
+ if(remote.ttl && !doput && !remote.intervalId) {
920
+ remote.intervalId = setInterval(async () => {
921
+ await handleRemote({variable, remote, reactive});
922
+ })
923
+ }
924
+ if(variable.value===undefined) variable.value = value;
925
+ }
926
+ if(value) {
927
+ variable.value = await value.then((json) => {
928
+ if (json && typeof (json) === "object" && reactive) {
929
+ return remoteProxy({json, variable,remote, reactive})
930
+ } else {
931
+ return json;
932
+ }
933
+ })
934
+ }
935
+ }
936
+
937
+ const createComponent = (name, node, {framed, observer} = {}) => {
745
938
  let ctor = customElements.get(name);
746
939
  if (ctor) {
747
940
  if (framed && !ctor.lightviewFramed) {
@@ -751,38 +944,47 @@ const {observe} = (() => {
751
944
  }
752
945
  return ctor;
753
946
  }
754
- ctor = createClass(node, {observer, importAnchors, framed});
947
+ ctor = createClass(node, {observer, framed});
755
948
  customElements.define(name, ctor);
949
+ Lightview.customElements.set(name, ctor);
756
950
  return ctor;
757
951
  }
952
+ Lightview.customElements = new Map();
758
953
  Lightview.createComponent = createComponent;
759
954
  //Object.defineProperty(Lightview, "createComponent", {writable: true, configurable: true, value: createComponent})
760
955
  const importLink = async (link, observer) => {
761
956
  const url = (new URL(link.getAttribute("href"), window.location.href)),
762
957
  as = link.getAttribute("as") || getNameFromPath(url.pathname);
763
- if (url.hostname !== window.location.hostname) {
764
- throw new URIError(`importLink:HTML imports must be from same domain: ${url.hostname}!=${location.hostname}`)
958
+ if (url.hostname !== window.location.hostname && !link.getAttribute("crossorigin")) {
959
+ throw new URIError(`importLink:HTML imports must be from same domain: ${url.hostname}!=${location.hostname} unless 'crossorigin' attribute is set.`)
765
960
  }
766
961
  if (!customElements.get(as)) {
767
962
  const html = await (await fetch(url.href)).text(),
768
963
  dom = parser.parseFromString(html, "text/html"),
769
- importAnchors = !!dom.head.querySelector('meta[name="l-importAnchors"]'),
770
- unhide = !!dom.head.querySelector('meta[name="l-unhide"]');
964
+ unhide = !!dom.head.querySelector('meta[name="l-unhide"]'),
965
+ links = dom.head.querySelectorAll('link[href$=".html"][rel=module]');
966
+ for (const childlink of links) {
967
+ const href = childlink.getAttribute("href"),
968
+ childurl = new URL(href, url.href);
969
+ childlink.setAttribute("href", childurl.href);
970
+ if (link.hasAttribute("crossorigin")) childlink.setAttribute("crossorigin", link.getAttribute("crossorigin"))
971
+ await importLink(childlink, observer);
972
+ }
771
973
  if (unhide) dom.body.removeAttribute("hidden");
772
- createComponent(as, dom.body, {observer, importAnchors});
974
+ createComponent(as, dom.body, {observer});
773
975
  }
774
976
  return {as};
775
977
  }
776
978
  const importLinks = async () => {
777
979
  const observer = createObserver(document.body);
778
- for (const link of [...document.querySelectorAll("link[href][rel=module]")]) {
779
- await importLink(link);
980
+ for (const link of [...document.querySelectorAll(`link[href$=".html"][rel=module]`)]) {
981
+ await importLink(link, observer);
780
982
  }
781
983
  }
782
984
 
783
- const bodyAsComponent = ({as = "x-body", unhide, importAnchors, framed} = {}) => {
985
+ const bodyAsComponent = ({as = "x-body", unhide, framed} = {}) => {
784
986
  const parent = document.body.parentElement;
785
- createComponent(as, document.body, {importAnchors, framed});
987
+ createComponent(as, document.body, {framed});
786
988
  const component = document.createElement(as);
787
989
  parent.replaceChild(component, document.body);
788
990
  Object.defineProperty(document, "body", {
@@ -827,13 +1029,12 @@ const {observe} = (() => {
827
1029
  if (!domContentLoadedEvent) addListener(window, "DOMContentLoaded", (event) => domContentLoadedEvent = event);
828
1030
  let OBSERVER;
829
1031
  const loader = async (whenFramed) => {
830
- if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
831
- const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
832
- unhide = !!document.querySelector('meta[name="l-unhide"]'),
1032
+ await importLinks();
1033
+ const unhide = !!document.querySelector('meta[name="l-unhide"]'),
833
1034
  isolated = !!document.querySelector('meta[name="l-isolate"]'),
834
1035
  enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
835
1036
  if (whenFramed) {
836
- whenFramed({unhide, importAnchors, isolated, enableFrames, framed: true});
1037
+ whenFramed({unhide, isolated, enableFrames, framed: true});
837
1038
  if (!isolated) {
838
1039
  postMessage.enabled = true;
839
1040
  addListener(window, "message", ({data}) => {
@@ -868,7 +1069,7 @@ const {observe} = (() => {
868
1069
  postMessage({type: "DOMContentLoaded"})
869
1070
  }
870
1071
  } else if (url.searchParams.has("as")) {
871
- bodyAsComponent({as: url.searchParams.get("as"), unhide, importAnchors});
1072
+ bodyAsComponent({as: url.searchParams.get("as"), unhide});
872
1073
  }
873
1074
  if (enableFrames) {
874
1075
  postMessage.enabled = true;
@@ -942,12 +1143,12 @@ const {observe} = (() => {
942
1143
  }
943
1144
  }
944
1145
  }
945
- const whenFramed = (f, {isolated} = {}) => {
1146
+ const whenFramed = (callback, {isolated} = {}) => {
946
1147
  // loads for framed content
947
- addListener(document, "DOMContentLoaded", (event) => loader(f));
1148
+ addListener(document, "DOMContentLoaded", (event) => loader(callback));
948
1149
  }
949
1150
  Lightview.whenFramed = whenFramed;
950
- //Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
1151
+
951
1152
  if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
952
1153
  // loads for unframed content
953
1154
  // CodePen mucks with window.parent
@@ -955,6 +1156,4 @@ const {observe} = (() => {
955
1156
  }
956
1157
 
957
1158
  return {observe}
958
- })();
959
-
960
-
1159
+ })();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lightview",
3
- "version": "1.4.5b",
4
- "description": "Small, simple, powerful web UI and micro front end creation ... imagine a blend of Svelte, React, Vue, Riot and more.",
3
+ "version": "1.4.10b",
4
+ "description": "Small, simple, powerful web UI and micro front end creation ... Great ideas from Svelte, React, Vue and Riot combined.",
5
5
  "main": "lightview.js",
6
6
  "scripts": {
7
7
  "test": "set NODE_OPTIONS=--experimental-vm-modules && jest ./test"
package/test/basic.html CHANGED
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <title>Basic</title>
6
6
  <template id="x-test" name="joe" open="true" count=1 children='["mary"]' l-on:click="bump">
7
+
7
8
  <span id="name">${name}</span>
8
9
  <span id="open">${open}</span>
9
10
  <span id="count">${count}</span>
@@ -18,8 +19,9 @@
18
19
  <input id="itel" type="tel" value="${itel}">
19
20
  <input id="iemail" type="email" value="${iemail}">
20
21
  <input id="iurl" type="url" value="${iurl}">
21
- <input id="isearch" type="search" value="${isearch}">
22
22
  <input id="iradio" type="radio" value="${iradio}">
23
+ <input id="isearch" type="search" value="${isearch}">
24
+
23
25
  <input id="icolor" type="color" value="${icolor}">
24
26
  <input id="ipassword" type="password" value="${ipassword}">
25
27
 
@@ -31,7 +33,7 @@
31
33
  <input id="icheckbox" type="checkbox" value="${icheckbox}">
32
34
 
33
35
  <script type="lightview/module">
34
- debugger;
36
+ //debugger;
35
37
  self.variables({name:string,open:boolean,count:number,children:Array},{imported,reactive});
36
38
  self.variables({color:string,checked:boolean,age:number,hamburger:Array},{exported,reactive});
37
39
  self.variables({counter:number},{reactive});
@@ -194,7 +194,7 @@ describe('Lightview', () => {
194
194
  expect(value).toBe("test");
195
195
  expect(variable.name).toBe("i${type}");
196
196
  expect(variable.type).toBe("string");
197
- expect(variable.value).toBe(value);
197
+ if("${type}"!=="radio") expect(variable.value).toBe(value);
198
198
  }`)();
199
199
  test(`${type} input - i${type} should be "test"`,f);
200
200
  });