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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightview",
3
- "version": "1.8.1b",
3
+ "version": "1.8.2",
4
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": {
package/repl_hold.html ADDED
@@ -0,0 +1,320 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Lightview:REPL</title>
6
+ </head>
7
+ <body>
8
+ <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/default.min.css">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
10
+ <div id="content" style="flex:auto;min-height:100%;width:750px;max-width:750px;height:100%;border:1px solid;padding:10px;margin:0 auto">
11
+ <div style="display:flex;flex-direction:column;">
12
+ <div id="tabs" style="flex-grow:0;width:100%;border:1px;padding-bottom:5px;text-align:center">
13
+ <span style="padding-right:10px"><label for="headhtml" l-on:click="${onTabClick}">HTML Head</label><input for="headhtml" value="${headhtmlPinned}" type="checkbox" l-on:click="${onPinClick}"></span>
14
+ <span style="padding-right:10px"><label for="bodyhtml" l-on:click="${onTabClick}">HTML Body</label><input for="bodyhtml" value="${bodyhtmlPinned}" type="checkbox" l-on:click="${onPinClick}" checked></span>
15
+ <!--span style="padding-right:10px"><label for="markdown" l-on:click="${onTabClick}">Markdown (Body)</label><input for="markdown" value="${markdownPinned}" type="checkbox" l-on:click="${onPinClick}"></span-->
16
+ <span style="padding-right:10px"><label for="css" l-on:click="${onTabClick}">Style</label><input for="css" value="${cssPinned}" type="checkbox" l-on:click="${onPinClick}"></span>
17
+ <span style="padding-right:10px"><label for="script" l-on:click="${onTabClick}">Script</label><input for="script" value="${scriptPinned}" type="checkbox" l-on:click="${onPinClick}"></span>
18
+ <span style="padding-right:10px"><label for="preview" l-on:click="${onTabClick}">Preview</label><input for="preview" value="${previewPinned}" type="checkbox" l-on:click="${onPinClick}"></span>
19
+ </div>
20
+ </div>
21
+ <div id="headhtml" style="display:none;margin:10px" class="language-html">&lt;head&gt;<slot name="head"></slot>&lt;/head&gt;</div>
22
+ <div id="bodyhtml" style="margin:10px" class="language-html">&lt;body&gt;<slot name="body"></slot>&lt;/body&gt;</div>
23
+ <!--textarea id="markdown" style="margin-right:2px;display:none">${markdown}</textarea-->
24
+ <div id="css" style="display:none;margin:10px" class="language-css">&lt;style&gt;<slot name="css"></slot>&lt;/style&gt;</div>
25
+ <div id="script" style="display:none;margin:10px" class="language-javascript">&lt;script&gt;<slot name="script"></slot>&lt;/script&gt;</div>
26
+ <iframe id="preview" style="max-width:99%;width:99%;margin:10px;display:none" src="./blank.html"></iframe>
27
+ </div>
28
+ <div style="width:100%;text-align:center">
29
+ <div style="padding:5px">${source}</div>
30
+ <button l-on:click="${doSave}">Save</button>&nbsp;&nbsp;
31
+ <button l-if="${!source.trim().startsWith('http')}" l-on:click="${doReset}">Reset</button>
32
+ </div>
33
+ <style id="style">
34
+ label:hover {
35
+ text-decoration: underline
36
+ }
37
+ </style>
38
+ <script id="lightview">
39
+ (document.currentComponent||(document.currentComponent=document.body)).mount = async function() {
40
+ const {CodeJar} = await import("https://cdn.jsdelivr.net/npm/codejar@3.6.0/codejar.min.js");
41
+ const turndownService = new TurndownService({
42
+ headingStyle: "atx",
43
+ codeBlockStyle: "fenced",
44
+ emDelimiter: "*"
45
+ });
46
+ turndownService.keep(() => true);
47
+
48
+ const {html, css, script} = await import("../types.js");
49
+ self.variables({
50
+ onTabClick: "function",
51
+ onPinClick: "function",
52
+ doSave: "function",
53
+ doReset: "function"
54
+ });
55
+ self.variables({
56
+ wysiwygPinned: "boolean",
57
+ bodyhtmlPinned: "boolean",
58
+ bodyhtml: html,
59
+ markdownPinned: "boolean",
60
+ markdown: "string",
61
+ cssPinned: "boolean",
62
+ cssText: css,
63
+ scriptPinned: "boolean",
64
+ scriptText: script,
65
+ headhtmlPinned: "boolean",
66
+ headhtml: html,
67
+ previewPinned: "boolean",
68
+ source: "string",
69
+ }, {reactive});
70
+ self.variables({
71
+ src: "string",
72
+ }, {imported});
73
+
74
+ bodyhtmlPinned = true;
75
+
76
+ const loadFromFile = async () => {
77
+ const html = await fs.readFile(url.pathname, {encoding: "utf8"});
78
+ source = `IndexedDB://${url.hostname + "_repl"}${url.pathname}`;
79
+ return html;
80
+ };
81
+
82
+ const loadFromServer = async () => {
83
+ const response = await fetch(url.href);
84
+ const html = await response.text();
85
+ source = url.href;
86
+ return html;
87
+ };
88
+
89
+ const parseFullHTML = (fullHTML) => {
90
+ const parser = new DOMParser(),
91
+ fragment = parser.parseFromString(fullHTML, "text/html"),
92
+ body_el = fragment?.querySelector("body"),
93
+ style_el = fragment?.querySelector("style"),
94
+ script_el = body_el?.querySelector("script");
95
+ if (style_el) {
96
+ cssText = style_el?.innerHTML.trim();
97
+ style_el.remove();
98
+ } else {
99
+ cssText = "";
100
+ }
101
+ if (script_el) {
102
+ scriptText = script_el?.innerHTML.trim(),
103
+ script_el.remove();
104
+ } else {
105
+ scriptText = "";
106
+ }
107
+ headhtml = (fragment?.head.innerHTML || "").trim();
108
+ bodyhtml = body_el?.innerHTML.trim();
109
+ };
110
+
111
+ let fs,
112
+ url;
113
+ if (src) {
114
+ url = new URL(src, window.location.href);
115
+ fs = new LightningFS(url.hostname + "_repl").promises;
116
+ try {
117
+ parseFullHTML(await loadFromFile());
118
+ } catch (e) {
119
+ try {
120
+ parseFullHTML(await loadFromServer());
121
+ } catch (e) {
122
+ fullHTML = e.message;
123
+ }
124
+ }
125
+ } else {
126
+ url = new URL(window.location.href);
127
+ fs = new LightningFS(url.hostname + "_repl").promises;
128
+ bodyhtml = "";
129
+ headhtml = "";
130
+ scriptText = "";
131
+ cssText = "";
132
+ }
133
+ ;
134
+
135
+ // initialize variables
136
+ markdown = "";
137
+
138
+ const tabs = [...self.querySelectorAll("label[for]")]
139
+ .map((label) => {
140
+ const id = label.getAttribute("for");
141
+ return [id, self.getElementById(id), label];
142
+ });
143
+
144
+ const showTab = (targetid) => {
145
+ tabs.forEach(([id, el, label]) => {
146
+ if (id === targetid || self.varsProxy[`${id}Pinned`]) {
147
+ el.style.display = "block";
148
+ } else if (!self.varsProxy[`${id}Pinned`]) {
149
+ el.style.display = "none";
150
+ }
151
+ });
152
+ };
153
+
154
+ const hideTab = (targetid) => {
155
+ tabs.forEach(([id, el, label]) => {
156
+ if (id === targetid) el.style.display = "none";
157
+ });
158
+ };
159
+
160
+ onTabClick = (event) => {
161
+ showTab(event.target.getAttribute("for"));
162
+ };
163
+
164
+ onPinClick = (event) => {
165
+ const id = event.target.getAttribute("for"),
166
+ checked = self.varsProxy[`${id}Pinned`] = event.target.checked;
167
+ if (checked) onTabClick(event);
168
+ else hideTab(id);
169
+ };
170
+
171
+ doSave = async () => {
172
+ const parts = url.pathname.split("/");
173
+ let dir = "";
174
+ parts.shift();
175
+ parts.pop();
176
+ for (const part of parts) {
177
+ dir = dir + "/" + part;
178
+ try {
179
+ await fs.mkdir(dir);
180
+ } catch (e) {
181
+ if (e.message === "EEXIST") break;
182
+ throw e;
183
+ }
184
+ }
185
+ fs.writeFile(url.pathname, doPreview(), {encoding: "utf8"}, () => {
186
+ });
187
+ source = `IndexedDB://${url.hostname + "_repl"}${url.pathname}`;
188
+ };
189
+
190
+ doReset = async () => {
191
+ try {
192
+ await fs.unlink(url.pathname);
193
+ } catch (e) {
194
+
195
+ }
196
+ if (src) {
197
+ try {
198
+ parseFullHTML(await loadFromServer());
199
+ doPreview();
200
+ } catch (e) {
201
+ previewEl.innerHTML = fullHTML = e.message;
202
+ }
203
+ } else {
204
+ bodyhtml = "";
205
+ headhtml = "";
206
+ scriptText = "";
207
+ cssText = "";
208
+ previewEl.innerHTML = "";
209
+ }
210
+ };
211
+
212
+ const doPreview = () => {
213
+ // not quite write, createComponent needs to be reworked to handle a head and import its links
214
+ /*const template = document.createElement("template");
215
+ template.innerHTML = bodyhtml +
216
+ "<style>" + cssText + "</style>" +
217
+ '<script id="lightview">' + scriptText + "<" + "/script>";
218
+ const component = window.customElements.get("x-preview");
219
+ if (component) {
220
+ component.setTemplateNode(template);
221
+ } else {
222
+ Lightview.createComponent("x-preview", template);
223
+ }
224
+ previewEl.innerHTML = "<x-preview></x-preview>";
225
+ return "<html><head>" + headhtml + "</head><body>" + template.innerHTML + "<body></html>"*/
226
+ const str =
227
+ "<html>" +
228
+ "<head>" + headhtml.replace("./lightview.js",new URL("./lightview.js",window.location.href).href) +" </head>" +
229
+ "<body>" + bodyhtml +
230
+ "<style>" + cssText + "</style>" +
231
+ '<script id="lightview">debugger;' + scriptText + "<" + "/script>" +
232
+ "</body>" +
233
+ "</html>",
234
+ blob = new Blob([str], {type : 'text/html'}),
235
+ newurl = window.URL.createObjectURL(blob);
236
+ previewEl.src = newurl;
237
+ };
238
+
239
+ const tabsEl = self.getElementById("tabs"),
240
+ headhtmlEl = document.body.querySelector('[slot="head"]'),
241
+ bodyhtmlEl = document.body.querySelector('[slot="body"]'),
242
+ markdownEl = self.getElementById("markdown"),
243
+ cssEl = document.body.querySelector('[slot="css"]'),
244
+ scriptEl = document.body.querySelector('[slot="script"]'),
245
+ previewEl = self.getElementById("preview");
246
+
247
+ const highlight = (el,...args) => {
248
+ hljs.highlightElement(el,...args);
249
+ }
250
+
251
+ if(headhtmlEl) {
252
+ headhtmlEl.className = "language-html";
253
+ const bodyJar = CodeJar(headhtmlEl, highlight);
254
+ bodyJar.updateCode(headhtml);
255
+ bodyJar.onUpdate(code => {
256
+ headhtml = code;
257
+ doPreview();
258
+ })
259
+ }
260
+
261
+ if(bodyhtmlEl) {
262
+ bodyhtmlEl.className = "language-html";
263
+ const bodyJar = CodeJar(bodyhtmlEl, highlight);
264
+ bodyJar.updateCode(bodyhtml);
265
+ bodyJar.onUpdate(code => {
266
+ bodyhtml = code;
267
+ doPreview();
268
+ })
269
+ }
270
+
271
+ if(cssEl) {
272
+ cssEl.className = "language-css";
273
+ const bodyJar = CodeJar(cssEl, highlight);
274
+ bodyJar.updateCode(cssText);
275
+ bodyJar.onUpdate(code => {
276
+ cssText = code;
277
+ doPreview();
278
+ })
279
+ }
280
+
281
+ if(scriptEl) {
282
+ scriptEl.className = "language-javascript";
283
+ const bodyJar = CodeJar(scriptEl, highlight);
284
+ bodyJar.updateCode(scriptText);
285
+ bodyJar.onUpdate(code => {
286
+ scriptText = code;
287
+ doPreview();
288
+ })
289
+ }
290
+
291
+ doPreview();
292
+
293
+ /*let prevmarkdown; // prevents indirect recursion
294
+ observe(() => {
295
+ const text = turndownService.turndown(bodyhtml).trim();
296
+ if (text && text !== prevtext) {
297
+ markdown = markdownEl.innerHTML = prevmarkdown = text;
298
+ }
299
+ });
300
+
301
+ let prevbodyhtml; // prevents indirect recursion
302
+ observe(() => {
303
+ const html = marked.parse(markdown).trim();
304
+ if (html && html !== prevbodyhtml) {
305
+ bodyhtml = bodyhtmlEl.innerText = prevbodyhtml = html;
306
+ }
307
+ });*/
308
+
309
+ const initEditor = () => {
310
+
311
+ };
312
+
313
+ self.addEventListener("mounted", () => {
314
+ initEditor();
315
+ doPreview();
316
+ });
317
+ }
318
+ </script>
319
+ </body>
320
+ </html>
package/test/basic.html CHANGED
@@ -5,6 +5,7 @@
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
+ <input id="iuntyped" value="${iuntyped}">
8
9
  <input id="idatetime" type="datetime" value="${idatetime}">
9
10
 
10
11
  <span id="children">${children}</span>
@@ -18,7 +19,7 @@
18
19
  <span id="age">${age}</span>
19
20
  <span id="hamburger">${hamburger}</span>
20
21
 
21
- <input id="iuntyped" value="${iuntyped}">
22
+
22
23
  <input id="itext" type="text" value="${itext}">
23
24
  <input id="itel" type="tel" value="${itel}">
24
25
  <input id="iemail" type="email" value="${iemail}">
@@ -35,8 +36,16 @@
35
36
  <input id="icheckbox" type="checkbox" value="${icheckbox}">
36
37
  </template>
37
38
  <script>
38
- document.getElementById("x-test").mount = (self) => {
39
+ document.getElementById("x-test").mount = async (self) => {
39
40
  // debugger;
41
+ self.variables(
42
+ {
43
+ shared:"function"
44
+ },
45
+ {
46
+ constant: (await import("../types.js")).shared
47
+ }
48
+ );
40
49
  self.variables({name: "string", open: "boolean", count: "number", children: Array}, {imported, reactive});
41
50
  self.variables({color: "string", checked: "boolean", age: "number", hamburger: Array}, {
42
51
  exported,
@@ -52,7 +61,8 @@
52
61
  counter = 0;
53
62
  myshare = 1;
54
63
 
55
- self.addEventListener("connected", ({target}) => {
64
+ self.addEventListener("mounted", ({target}) => {
65
+ debugger;
56
66
  iuntyped = "test";
57
67
  itext = "test";
58
68
  itel = "test";
@@ -80,8 +90,9 @@
80
90
  });
81
91
  }
82
92
  </script>
83
- <script src="../lightview2.js"></script>
93
+ <script src="../lightview.js"></script>
84
94
  <script>
95
+ Lightview.createInputVariables = true;
85
96
  Lightview.createComponent("x-test",document.getElementById("x-test"));
86
97
  </script>
87
98
  </head>
@@ -164,7 +164,7 @@ describe('Lightview - Variables', () => {
164
164
  });
165
165
 
166
166
  test('untyped input - iuntyped should be "test"', async () => {
167
- const result = await page.evaluate(async () => {
167
+ const result = await page.evaluate(() => {
168
168
  const el = document.getElementById("test"),
169
169
  result = el.getElementById("iuntyped")
170
170
  return result.getAttribute("value");
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <title>Extended</title>
6
- <script src="../lightview2.js?as=x-body"></script>
6
+ <script src="../lightview.js?as=x-body"></script>
7
7
  </head>
8
8
  <body>
9
9
  <script>
package/types.js CHANGED
@@ -17,6 +17,41 @@ function reviver(property,value) {
17
17
  if(value==="@NaN") return NaN;
18
18
  return value;
19
19
  }
20
+ const deepEqual = (a,b,matchType=deepEqual.LEFT, seen=new Set()) => {
21
+ if(matchType===deepEqual.RIGHT) return deepEqual(b,a,deepEqual.LEFT,seen);
22
+ if(matchType===deepEqual.COMMUTATIVE) return deepEqual(a,b,deepEqual.LEFT) && deepEqual(b,a,deepEqual.LEFT);
23
+ if(a===b) return true;
24
+ const type = typeof(a);
25
+ if(type==="function" || type!==typeof(b) || (a && !b) || (b && !a)) return false;
26
+ if(type==="number" && isNaN(a) && isNaN(b)) return true;
27
+ if(a && type==="object") {
28
+ if(seen.has(a)) return true;
29
+ seen.add(a);
30
+ if(a.constructor!==b.constructor || a.length!==b.length || a.size!==b.size) return false;
31
+ if(a instanceof Date) a.getTime() === b.getTime();
32
+ if(a instanceof RegExp) return a.toString() === b.toString();
33
+ if(a instanceof Set) {
34
+ for(const avalue of [...a]) {
35
+ if(![...b].some((bvalue) => deepEqual(avalue,bvalue,matchType,seen))) return false;
36
+ }
37
+ return true;
38
+ }
39
+ if(a instanceof Map) {
40
+ for(const [key,value] of [...a]) {
41
+ if(!deepEqual(b.get(key),value,matchType,seen)) return false;
42
+ }
43
+ return true;
44
+ }
45
+ for(const key in a) {
46
+ if(!deepEqual(a[key],b[key],matchType,seen)) return false;
47
+ }
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ deepEqual.LEFT = 1;
53
+ deepEqual.COMMUTATIVE = 2;
54
+ deepEqual.RIGHT = 3;
20
55
 
21
56
  function ValidityState(options) {
22
57
  if(!this || !(this instanceof ValidityState)) return new ValidityState(options);
@@ -245,11 +280,11 @@ const validateNumber = function(value,variable) {
245
280
  variable.validityState = ValidityState({typeMismatch:true,value});
246
281
  } else if(isNaN(result) && !this.allowNaN) {
247
282
  variable.validityState = ValidityState({badInput:true,value});
248
- } else if(this.min!=null && result<this.min) {
283
+ } else if(this.min!=null && result<this.min && !(this.min===-Infinity && isNaN(result))) {
249
284
  variable.validityState = ValidityState({rangeUnderflow:true,value});
250
- } else if(this.max!=null && result>this.max) {
285
+ } else if(this.max!=null && result>this.max && !(this.max===Infinity && isNaN(result))) {
251
286
  variable.validityState = ValidityState({rangeOverflow:true,value});
252
- } else if(this.step!==null && (result % this.step)!==0) {
287
+ } else if(this.step!=null && (result % this.step)!==0) {
253
288
  variable.validityState = ValidityState({rangeUnderflow:true,value});
254
289
  } else {
255
290
  variable.validityState = ValidityState({valid:true});
@@ -258,7 +293,7 @@ const validateNumber = function(value,variable) {
258
293
  }
259
294
  return this.whenInvalid(variable,value);
260
295
  }
261
- const number = ({coerce=false,required = false,whenInvalid = ifInvalid,min=-Infinity,max=Infinity,step = 1,allowNaN = true,...rest}={}) => {
296
+ const number = ({coerce=false,required = false,whenInvalid = ifInvalid,min=-Infinity,max=Infinity,step,allowNaN = true,...rest}={}) => {
262
297
  if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
263
298
  if(typeof(required)!=="boolean") throw new TypeError(`required, ${JSON.stringify(required)}, must be a boolean`);
264
299
  if(typeof(whenInvalid)!=="function") throw new TypeError(`whenInvalid, ${whenInvalid}, must be a function`);
@@ -484,51 +519,89 @@ const put = (href,variable) => {
484
519
  }
485
520
 
486
521
  const handleRemote = async ({variable, functionalType, config=functionalType, component},doput) => {
487
- const type = typeof (config);
488
522
  let value;
489
- if (type === "string") {
490
- const href = new URL(config,window.location.href).href;
491
- value = (doput
492
- ? put(href,variable)
493
- : get(href,variable));
494
- if(variable.value===undefined) variable.value = value; // do not await here
495
- } else if (remote && type === "object") {
496
- if(!config.path) config.path = `./${variable.name}`;
497
- if(config.path.endsWith("/")) config.path = `${config.path}${variable.name}`;
498
- const href = new URL(config.path,window.location.href).href;
499
- if(!config.get || !config.put) {
500
- if(!href) throw new Error(`A remote path is required if no put function is provided for remote data`)
501
- if(!config.get) config.get = get;
502
- if(!config.put && variable.reactive) config.put = put;
503
- }
504
- value = (doput
505
- ? config.put(href,variable)
506
- : config.get(href,variable));
507
- if(config.ttl && !doput && !config.intervalId) {
508
- config.intervalId = setInterval(async () => {
509
- await handleRemote({variable, config, component});
510
- //schedule();
511
- },config.ttl);
512
- }
513
- if(variable.value===undefined) variable.value = value;
523
+ if(!config.path) config.path = `./${variable.name}`;
524
+ if(config.path.endsWith("/")) config.path = `${config.path}${variable.name}`;
525
+ const href = new URL(config.path,new URL(config.base.replace("blob:","")).href).href;
526
+ if(!config.get || !config.put) {
527
+ if(!href) throw new Error(`A remote path is required if no put function is provided for remote data`)
528
+ if(!config.get) config.get = get;
529
+ if(!config.put && variable.reactive) config.put = put;
530
+ }
531
+ let areequal;
532
+ value = (doput
533
+ ? (areequal = deepEqual(variable.value,config.previousValue,deepEqual.COMMUTATIVE)) ? variable.value : config.put(href,variable)
534
+ : config.get(href,variable));
535
+ if(config.ttl && !doput && !config.intervalId) {
536
+ config.intervalId = setInterval(async () => {
537
+ await handleRemote({variable, config, component});
538
+ //schedule();
539
+ },config.ttl);
514
540
  }
515
- if(value) {
516
- const json = await value;
541
+ if(variable.value===undefined) variable.value = value;
542
+ if(value && !areequal) {
543
+ const json = config.previousValue = await value;
517
544
  if (json && typeof (json) === "object" && variable.reactive) {
518
- variable.value = remoteProxy({json, variable,config, component});
545
+ component.setVariableValue(variable.name,remoteProxy({json, variable,config, component}));
546
+ // variable.value = remoteProxy({json, variable,config, component});
519
547
  } else {
520
548
  component.setVariableValue(variable.name,json);
521
549
  }
522
550
  }
523
551
  }
524
552
 
525
- const remote = (config) => {
553
+ const remote = (config,base=document.baseURI||window.location.href) => {
554
+ if(typeof(config)==="string") config = {path:config};
555
+ config.base = base;
526
556
  return {
527
- config,
557
+ config:{...config},
528
558
  handleRemote
529
559
  }
530
560
  }
531
561
 
562
+ const shared = () => {
563
+ return {
564
+ init({variable, component}) {
565
+ const name = variable.name,
566
+ set = variable.set || (() => {});
567
+ variable.shared = true;
568
+ variable.set = function(newValue) {
569
+ set(newValue);
570
+ if(this.shared) { // still shared
571
+ component.siblings.forEach((instance) => {
572
+ const svariable = instance.vars[name];
573
+ if(svariable?.shared) {
574
+ instance.setVariableValue(name, newValue);
575
+ }
576
+ });
577
+ }
578
+ }
579
+ // initialize
580
+ component.siblings.forEach((instance) => {
581
+ if(instance===component) return;
582
+ const svariable = instance.vars[name];
583
+ if(svariable?.shared) {
584
+ if(variable.value==null) {
585
+ if(svariable.value!=null) variable.value = instance.vars[name].value
586
+ } else instance.setVariableValue(name, variable.value);
587
+ }
588
+ });
589
+ }
590
+ }
591
+ }
592
+
593
+ const observed = () => {
594
+ return {
595
+ init({variable, component}) {
596
+ if(variable.observed) return;
597
+ const name = variable.name;
598
+ variable.value = component.hasAttribute(name) ? coerce(component.getAttribute(name), variable.type) : variable.value;
599
+ variable.observed = true;
600
+ component.observedAttributes.add(name);
601
+ }
602
+ }
603
+ }
604
+
532
605
  const remoteGenerator = handleRemote;
533
606
 
534
- export {ValidityState,any,array,boolean,duration,number,object,remote,remoteGenerator,string,symbol,reviver,html,css,script}
607
+ export {ValidityState,any,array,boolean,duration,number,object,remote,shared,observed,remoteGenerator,string,symbol,reviver,html,css,script}
@@ -1,32 +0,0 @@
1
- <!DOCTYPE html>
2
- <head>
3
- <title>Lightview:Chart:Example</title>
4
- <link href="./chart.html" rel="module">
5
- <script src="../../lightview.js"></script>
6
- </head>
7
- <body>
8
- <l-chart id="myPieChart" type="PieChart" style="height:500px;" title="How Much Pizza I Ate Last Night">
9
- {
10
- options: { },
11
- columns: [
12
- {label: "Topping", type: "string"},
13
- {label: "Slices", type: "number"}
14
- ],
15
- rows: [
16
- ["Mushrooms", 3],
17
- ["Onions", 1],
18
- ["Olives", 1],
19
- ["Zucchini", 1],
20
- ["Pepperoni", 2]
21
- ]
22
- }
23
- </l-chart>
24
- <script>
25
- const el = document.getElementById("myPieChart");
26
- el.addEventListener("mounted",() => {
27
- chart = el.chart;
28
- chart.addRow(["Anchovies",1]);
29
- });
30
- </script>
31
- </body>
32
- </html>