@loadbare/app 0.4.0 → 0.5.1

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 (165) hide show
  1. package/README.md +53 -82
  2. package/dist/build/assemble.d.ts +7 -5
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +29 -9
  5. package/dist/build/cli.d.ts +20 -12
  6. package/dist/build/cli.d.ts.map +1 -1
  7. package/dist/build/cli.js +34 -16
  8. package/dist/build/elements.d.ts +15 -29
  9. package/dist/build/elements.d.ts.map +1 -1
  10. package/dist/build/elements.js +25 -111
  11. package/dist/build/expand.d.ts +1 -1
  12. package/dist/build/expand.js +1 -1
  13. package/dist/build/format.d.ts +6 -3
  14. package/dist/build/format.d.ts.map +1 -1
  15. package/dist/build/format.js +6 -3
  16. package/dist/build/locations.d.ts +14 -37
  17. package/dist/build/locations.d.ts.map +1 -1
  18. package/dist/build/locations.js +31 -69
  19. package/dist/build/origins.d.ts +109 -0
  20. package/dist/build/origins.d.ts.map +1 -0
  21. package/dist/build/origins.js +270 -0
  22. package/dist/core/lb-constants.d.ts +1 -0
  23. package/dist/core/lb-constants.d.ts.map +1 -1
  24. package/dist/core/lb-constants.js +15 -8
  25. package/dist/core/lb-types.d.ts +2 -2
  26. package/dist/core/lb-types.d.ts.map +1 -1
  27. package/dist/hub/lb-apply.js +1 -1
  28. package/dist/hub/lb-hub.d.ts.map +1 -1
  29. package/dist/hub/lb-hub.js +44 -17
  30. package/dist/hub/lb-rows.d.ts.map +1 -1
  31. package/dist/hub/lb-rows.js +3 -3
  32. package/dist/server/lb-express.d.ts.map +1 -1
  33. package/dist/server/lb-server.d.ts +5 -4
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/tests/assemble.test.js +11 -4
  36. package/dist/tests/elements.test.js +47 -51
  37. package/dist/tests/expand.test.d.ts +1 -1
  38. package/dist/tests/expand.test.js +2 -2
  39. package/dist/tests/fixtures/elements/collision/imports.d.ts +3 -0
  40. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +1 -0
  41. package/dist/tests/fixtures/elements/collision/imports.js +1 -0
  42. package/dist/tests/fixtures/elements/manifest/imports.d.ts +3 -0
  43. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +1 -0
  44. package/dist/tests/fixtures/elements/manifest/imports.js +1 -0
  45. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +3 -0
  46. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +1 -0
  47. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +1 -0
  48. package/dist/tests/fixtures/elements/{collision/elements.d.ts → manifest-not-array/imports.d.ts} +1 -1
  49. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +1 -0
  50. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +1 -0
  51. package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts +2 -0
  52. package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts.map +1 -0
  53. package/dist/tests/fixtures/elements/pkg/acme-widget.js +1 -0
  54. package/dist/tests/lb-express.test.js +1 -1
  55. package/dist/tests/origins.test.d.ts +10 -0
  56. package/dist/tests/origins.test.d.ts.map +1 -0
  57. package/dist/tests/origins.test.js +326 -0
  58. package/dist/tests/pages.test.js +3 -3
  59. package/dist/tests/styles.test.js +7 -4
  60. package/docs/reference/builder.md +128 -0
  61. package/docs/reference/chrome.md +75 -0
  62. package/docs/reference/css.md +44 -0
  63. package/docs/reference/custom-elements.md +327 -0
  64. package/docs/reference/data-binding.md +240 -0
  65. package/docs/reference/overview.md +38 -0
  66. package/docs/reference/page-files.md +175 -0
  67. package/docs/reference/server.md +123 -0
  68. package/docs/reference/widgets.md +163 -0
  69. package/docs/roadmap.md +130 -0
  70. package/docs/testing.md +228 -0
  71. package/docs/theory.md +344 -223
  72. package/docs/tutorials/000-getting-started.md +86 -0
  73. package/docs/tutorials/010-pages-and-navigation.md +129 -0
  74. package/docs/tutorials/020-css.md +103 -0
  75. package/docs/tutorials/030-html-decomposition.md +79 -0
  76. package/docs/tutorials/040-displaying-data.md +169 -0
  77. package/docs/tutorials/050-actions.md +77 -0
  78. package/docs/tutorials/060-custom-element-code.md +73 -0
  79. package/docs/tutorials/065-conditional-rendering.md +161 -0
  80. package/docs/tutorials/070-displaying-a-list.md +137 -0
  81. package/docs/tutorials/072-inserting-into-a-list.md +88 -0
  82. package/docs/tutorials/074-deleting-from-a-list.md +77 -0
  83. package/docs/tutorials/076-updating-a-list-item.md +86 -0
  84. package/docs/tutorials/080-widget-requests.md +124 -0
  85. package/docs/tutorials/090-using-widget-libraries.md +75 -0
  86. package/package.json +10 -18
  87. package/dist/client.js +0 -522
  88. package/dist/demo-static/src/widgets/app-box.d.ts +0 -15
  89. package/dist/demo-static/src/widgets/app-box.d.ts.map +0 -1
  90. package/dist/demo-static/src/widgets/app-box.js +0 -19
  91. package/dist/tests/fixtures/elements/collision/elements.d.ts.map +0 -1
  92. package/dist/tests/fixtures/elements/collision/elements.js +0 -3
  93. package/dist/tests/fixtures/elements/manifest/elements.d.ts +0 -5
  94. package/dist/tests/fixtures/elements/manifest/elements.d.ts.map +0 -1
  95. package/dist/tests/fixtures/elements/manifest/elements.js +0 -3
  96. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts +0 -5
  97. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts.map +0 -1
  98. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.js +0 -3
  99. package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts +0 -5
  100. package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts.map +0 -1
  101. package/dist/tests/fixtures/elements/manifest-bad-value/elements.js +0 -3
  102. package/dist/tests/golden.test.d.ts +0 -19
  103. package/dist/tests/golden.test.d.ts.map +0 -1
  104. package/dist/tests/golden.test.js +0 -60
  105. package/dist/tests/helpers/window.d.ts +0 -43
  106. package/dist/tests/helpers/window.d.ts.map +0 -1
  107. package/dist/tests/helpers/window.js +0 -78
  108. package/dist/tests/lb-input.test.d.ts +0 -9
  109. package/dist/tests/lb-input.test.d.ts.map +0 -1
  110. package/dist/tests/lb-input.test.js +0 -78
  111. package/dist/tests/lb-list.test.d.ts +0 -12
  112. package/dist/tests/lb-list.test.d.ts.map +0 -1
  113. package/dist/tests/lb-list.test.js +0 -44
  114. package/dist/tests/lb-options.test.d.ts +0 -10
  115. package/dist/tests/lb-options.test.d.ts.map +0 -1
  116. package/dist/tests/lb-options.test.js +0 -121
  117. package/dist/tests/lb-picker.test.d.ts +0 -14
  118. package/dist/tests/lb-picker.test.d.ts.map +0 -1
  119. package/dist/tests/lb-picker.test.js +0 -59
  120. package/dist/tests/lb-select.test.d.ts +0 -9
  121. package/dist/tests/lb-select.test.d.ts.map +0 -1
  122. package/dist/tests/lb-select.test.js +0 -71
  123. package/dist/tests/lb-table.test.d.ts +0 -15
  124. package/dist/tests/lb-table.test.d.ts.map +0 -1
  125. package/dist/tests/lb-table.test.js +0 -205
  126. package/dist/widgets/index.d.ts +0 -7
  127. package/dist/widgets/index.d.ts.map +0 -1
  128. package/dist/widgets/index.js +0 -6
  129. package/dist/widgets/lb-input.d.ts +0 -2
  130. package/dist/widgets/lb-input.d.ts.map +0 -1
  131. package/dist/widgets/lb-input.js +0 -48
  132. package/dist/widgets/lb-list.d.ts +0 -2
  133. package/dist/widgets/lb-list.d.ts.map +0 -1
  134. package/dist/widgets/lb-list.js +0 -17
  135. package/dist/widgets/lb-options.d.ts +0 -26
  136. package/dist/widgets/lb-options.d.ts.map +0 -1
  137. package/dist/widgets/lb-options.js +0 -72
  138. package/dist/widgets/lb-picker.d.ts +0 -2
  139. package/dist/widgets/lb-picker.d.ts.map +0 -1
  140. package/dist/widgets/lb-picker.js +0 -25
  141. package/dist/widgets/lb-select.d.ts +0 -2
  142. package/dist/widgets/lb-select.d.ts.map +0 -1
  143. package/dist/widgets/lb-select.js +0 -43
  144. package/dist/widgets/lb-table.d.ts +0 -2
  145. package/dist/widgets/lb-table.d.ts.map +0 -1
  146. package/dist/widgets/lb-table.js +0 -113
  147. package/docs/application-chrome.md +0 -36
  148. package/docs/building-html-pages.md +0 -130
  149. package/docs/getting-started.md +0 -120
  150. package/docs/guide.md +0 -1164
  151. package/docs/hosting.md +0 -218
  152. package/docs/latent-risks.md +0 -20
  153. package/widgets/index.ts +0 -6
  154. package/widgets/lb-input.html +0 -1
  155. package/widgets/lb-input.ts +0 -64
  156. package/widgets/lb-list.html +0 -1
  157. package/widgets/lb-list.ts +0 -21
  158. package/widgets/lb-options.html +0 -4
  159. package/widgets/lb-options.ts +0 -88
  160. package/widgets/lb-picker.html +0 -7
  161. package/widgets/lb-picker.ts +0 -27
  162. package/widgets/lb-select.html +0 -4
  163. package/widgets/lb-select.ts +0 -55
  164. package/widgets/lb-table.html +0 -8
  165. package/widgets/lb-table.ts +0 -126
package/dist/client.js DELETED
@@ -1,522 +0,0 @@
1
- "use strict";
2
- (() => {
3
- // core/lb-constants.ts
4
- var ATTR_QUERY = "lb-query";
5
- var ATTR_KEY = "lb-key";
6
- var ATTR_CELL = "lb-cell";
7
- var ATTR_VALUE = "lb-value";
8
- var ATTR_GROUP = "lb-group";
9
- var ATTR_SORT = "lb-sort";
10
- var ATTR_ROW_COUNT = "data-rows";
11
- var LB_EVENT_NAME = "lb-request";
12
- var ATTR_ACTION = "lb-action";
13
- var ATTR_DELETE = "lb-delete";
14
- var ATTR_INSERT = "lb-insert";
15
- var ATTR_UPDATE = "lb-update";
16
- var ATTR_NAV_LINK = "lb-nav-link";
17
- var PAGE_TEMPLATE_PREFIX = "page-";
18
- var HUB_TAG_NAME = "lb-hub";
19
- var ATTR_PENDING = "data-lb-pending";
20
- var ATTR_ERROR = "data-lb-error";
21
- var LB_DATA_ENDPOINT = "/lb/data";
22
- var LB_REQUEST_ENDPOINT = "/lb";
23
- var LB_REQUEST_TIMEOUT_MS = 1e4;
24
-
25
- // widgets/lb-input.ts
26
- var LbInput = class extends HTMLElement {
27
- static observedAttributes = [ATTR_VALUE];
28
- attributeChangedCallback(_name, _old, value) {
29
- const input = this.querySelector("input");
30
- if (!input) {
31
- console.error(`lb-input: no <input> to receive the value`);
32
- return;
33
- }
34
- input.value = value;
35
- }
36
- connectedCallback() {
37
- this.addEventListener("change", () => {
38
- const input = this.querySelector("input");
39
- if (!input) {
40
- console.error(`lb-input: change with no <input>, ignoring`);
41
- return;
42
- }
43
- const query = this.closest(`[${ATTR_QUERY}]`)?.getAttribute(ATTR_QUERY);
44
- const key = this.closest(`[${ATTR_KEY}]`)?.getAttribute(ATTR_KEY);
45
- const cell = this.getAttribute(ATTR_CELL);
46
- if (!query || !key || !cell) {
47
- console.error(
48
- `lb-input: change with no query/key/cell coordinates, ignoring`
49
- );
50
- return;
51
- }
52
- const detail = {
53
- op: "cell-change",
54
- query,
55
- key,
56
- cell,
57
- value: input.value
58
- };
59
- this.dispatchEvent(
60
- new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail })
61
- );
62
- });
63
- }
64
- };
65
- customElements.define("lb-input", LbInput);
66
-
67
- // widgets/lb-select.ts
68
- var LbSelect = class extends HTMLElement {
69
- static observedAttributes = [ATTR_VALUE];
70
- attributeChangedCallback(_name, _old, value) {
71
- const select = this.querySelector("select");
72
- if (!select) {
73
- console.error(`lb-select: no <select> to receive the value`);
74
- return;
75
- }
76
- select.value = value;
77
- }
78
- connectedCallback() {
79
- this.addEventListener("change", () => {
80
- const select = this.querySelector("select");
81
- if (!select) {
82
- console.error(`lb-select: change with no <select>, ignoring`);
83
- return;
84
- }
85
- const name = this.getAttribute(ATTR_ACTION);
86
- if (!name) {
87
- console.error(`lb-select: no ${ATTR_ACTION}, nothing to send`);
88
- return;
89
- }
90
- const detail = {
91
- op: "action",
92
- name,
93
- value: select.value
94
- };
95
- this.dispatchEvent(
96
- new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail })
97
- );
98
- });
99
- }
100
- };
101
- customElements.define("lb-select", LbSelect);
102
-
103
- // core/lb-types.ts
104
- function isProjection(result) {
105
- const op = result.op;
106
- return op === "rows" || op === "patch";
107
- }
108
-
109
- // hub/lb-apply.ts
110
- function land(el, value) {
111
- if (el.localName.includes("-")) el.setAttribute(ATTR_VALUE, value);
112
- else el.textContent = value;
113
- }
114
- function applyTuple(root, cells) {
115
- for (const [cell, value] of Object.entries(cells)) {
116
- const selector = `[${ATTR_CELL}="${cell}"]`;
117
- if (root.matches(selector)) land(root, value);
118
- for (const el of root.querySelectorAll(selector)) land(el, value);
119
- }
120
- }
121
- function landRows(scope, query, result) {
122
- const host = scope;
123
- if (typeof host.acceptRows !== "function") {
124
- console.error(
125
- `lb-hub: query '${query}' returned rows, but <${scope.localName}> is not a list widget`
126
- );
127
- return;
128
- }
129
- host.acceptRows(result);
130
- }
131
- function applyData(root, data) {
132
- for (const [query, result] of Object.entries(data)) {
133
- const scopes = root.querySelectorAll(`[${ATTR_QUERY}="${query}"]`);
134
- if (scopes.length === 0) {
135
- console.warn(`lb-hub: no scope for query '${query}', skipping`);
136
- continue;
137
- }
138
- for (const scope of scopes) {
139
- if (isProjection(result)) landRows(scope, query, result);
140
- else applyTuple(scope, result);
141
- }
142
- }
143
- }
144
-
145
- // hub/lb-rows.ts
146
- var before = (row, _tuple, template) => {
147
- template.parentElement.insertBefore(row, template);
148
- };
149
- function templateIn(scope) {
150
- const template = scope.querySelector("template");
151
- if (!template) {
152
- console.error(
153
- `lb-rows: <${scope.localName}> has no <template> to clone`
154
- );
155
- return null;
156
- }
157
- if (!template.getAttribute(ATTR_KEY)) {
158
- console.error(
159
- `lb-rows: the <template> in <${scope.localName}> has no ${ATTR_KEY} naming the column that identifies a row`
160
- );
161
- return null;
162
- }
163
- return template;
164
- }
165
- function showing(scope, template) {
166
- const rows = /* @__PURE__ */ new Map();
167
- for (const el of scope.querySelectorAll(`[${ATTR_KEY}]`)) {
168
- if (el !== template) rows.set(el.getAttribute(ATTR_KEY), el);
169
- }
170
- return rows;
171
- }
172
- function applyRows(scope, result, place = before) {
173
- const template = templateIn(scope);
174
- if (!template) return;
175
- const keyCell = template.getAttribute(ATTR_KEY);
176
- const rows = showing(scope, template);
177
- const upsert = (tuple) => {
178
- const key = tuple[keyCell];
179
- if (key === void 0) {
180
- console.error(
181
- `lb-rows: a tuple for <${scope.localName}> has no '${keyCell}' cell`
182
- );
183
- return null;
184
- }
185
- let row = rows.get(key);
186
- const fresh = row === void 0;
187
- if (!row) {
188
- row = template.content.firstElementChild.cloneNode(true);
189
- row.setAttribute(ATTR_KEY, key);
190
- rows.set(key, row);
191
- }
192
- applyTuple(row, tuple);
193
- if (fresh || result.op === "rows") place(row, tuple, template);
194
- return key;
195
- };
196
- if (result.op === "rows") {
197
- const arrived = /* @__PURE__ */ new Set();
198
- for (const tuple of result.rows) {
199
- const key = upsert(tuple);
200
- if (key !== null) arrived.add(key);
201
- }
202
- for (const [key, row] of rows) if (!arrived.has(key)) row.remove();
203
- } else {
204
- for (const tuple of result.rows ?? []) upsert(tuple);
205
- for (const key of result.drop ?? []) rows.get(key)?.remove();
206
- }
207
- scope.setAttribute(ATTR_ROW_COUNT, String(showing(scope, template).size));
208
- }
209
-
210
- // widgets/lb-list.ts
211
- var LbList = class extends HTMLElement {
212
- acceptRows(result) {
213
- applyRows(this, result);
214
- }
215
- };
216
- customElements.define("lb-list", LbList);
217
-
218
- // widgets/lb-options.ts
219
- var LbOptions = class extends HTMLElement {
220
- acceptRows(result) {
221
- applyRows(this, result, this.place);
222
- for (const option of this.querySelectorAll(`option[${ATTR_KEY}]`)) {
223
- option.setAttribute("value", option.getAttribute(ATTR_KEY));
224
- }
225
- for (const group of this.querySelectorAll("optgroup")) {
226
- if (!group.querySelector("option")) group.remove();
227
- }
228
- }
229
- place = (row, tuple, template) => {
230
- const select = this.querySelector("select");
231
- const groupCell = template.getAttribute(ATTR_GROUP);
232
- if (!groupCell) {
233
- select.insertBefore(row, template);
234
- return;
235
- }
236
- this.groupFor(select, template, tuple[groupCell] ?? "").append(row);
237
- };
238
- /** The group with this label, or a new one in front of the template. */
239
- groupFor(select, template, label) {
240
- for (const group2 of select.querySelectorAll("optgroup")) {
241
- if (group2.label === label) return group2;
242
- }
243
- const group = document.createElement("optgroup");
244
- group.label = label;
245
- select.insertBefore(group, template);
246
- return group;
247
- }
248
- connectedCallback() {
249
- this.addEventListener("change", () => {
250
- const select = this.querySelector("select");
251
- if (!select) {
252
- console.error(`lb-options: change with no <select>, ignoring`);
253
- return;
254
- }
255
- const name = this.getAttribute(ATTR_ACTION);
256
- if (!name) {
257
- console.error(`lb-options: no ${ATTR_ACTION}, nothing to send`);
258
- return;
259
- }
260
- const detail = { op: "action", name, value: select.value };
261
- this.dispatchEvent(
262
- new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail })
263
- );
264
- });
265
- }
266
- };
267
- customElements.define("lb-options", LbOptions);
268
-
269
- // widgets/lb-table.ts
270
- var GROUP_ROW = "data-group";
271
- var LbTable = class extends HTMLElement {
272
- acceptRows(result) {
273
- applyRows(this, result, this.place);
274
- for (const heading of this.querySelectorAll(`tr[${GROUP_ROW}]`)) {
275
- if (this.section(heading).rows.length === 0) heading.remove();
276
- }
277
- }
278
- /**
279
- * Group first, then sort within the group.
280
- *
281
- * Inserting a row before the first one that sorts after it keeps the
282
- * section ordered, whatever else is in it — which is what lets a whole set
283
- * and a single patched row take the same path.
284
- */
285
- place = (row, tuple, template) => {
286
- const groupCell = template.getAttribute(ATTR_GROUP);
287
- const sortCell = template.getAttribute(ATTR_SORT);
288
- const heading = groupCell ? this.headingFor(template, tuple[groupCell] ?? "") : null;
289
- const { rows, end } = this.section(heading);
290
- const value = sortCell ? tuple[sortCell] ?? "" : "";
291
- const after = sortCell ? rows.find((other) => other !== row && cellOf(other, sortCell) > value) : void 0;
292
- template.parentElement.insertBefore(row, after ?? end);
293
- };
294
- /**
295
- * The rows of one section, and the node it ends at.
296
- *
297
- * A section runs from its heading to the next heading, or to the template
298
- * if there is none after it. Without grouping there is one section and it
299
- * is the whole body — the heading row the page wrote is skipped along the
300
- * way, because it carries no key and is therefore not a row.
301
- */
302
- section(heading) {
303
- const template = this.querySelector("template");
304
- const rows = [];
305
- let node = heading ? heading.nextElementSibling : template.parentElement.firstElementChild;
306
- while (node && node !== template && !node.hasAttribute(GROUP_ROW)) {
307
- if (node.hasAttribute(ATTR_KEY)) rows.push(node);
308
- node = node.nextElementSibling;
309
- }
310
- return { rows, end: node ?? template };
311
- }
312
- /** The section with this label, or a new one in front of the template. */
313
- headingFor(template, label) {
314
- const body = template.parentElement;
315
- for (const heading2 of body.querySelectorAll(`tr[${GROUP_ROW}]`)) {
316
- if (heading2.getAttribute(GROUP_ROW) === label) return heading2;
317
- }
318
- const heading = document.createElement("tr");
319
- heading.setAttribute(GROUP_ROW, label);
320
- const cell = document.createElement("th");
321
- cell.setAttribute("scope", "rowgroup");
322
- cell.colSpan = template.content.firstElementChild?.childElementCount ?? 1;
323
- cell.textContent = label;
324
- heading.append(cell);
325
- body.insertBefore(heading, template);
326
- return heading;
327
- }
328
- };
329
- function cellOf(row, cell) {
330
- const selector = `[${ATTR_CELL}="${cell}"]`;
331
- const el = row.matches(selector) ? row : row.querySelector(selector);
332
- return el?.textContent ?? "";
333
- }
334
- customElements.define("lb-table", LbTable);
335
-
336
- // widgets/lb-picker.ts
337
- var LbPicker = class extends LbOptions {
338
- };
339
- customElements.define("lb-picker", LbPicker);
340
-
341
- // hub/lb-hub.ts
342
- function coordinates(el) {
343
- const query = el.closest(`[${ATTR_QUERY}]`)?.getAttribute(ATTR_QUERY);
344
- const key = el.closest(`[${ATTR_KEY}]`)?.getAttribute(ATTR_KEY);
345
- const cell = el.getAttribute(ATTR_CELL);
346
- return {
347
- ...query === null || query === void 0 ? {} : { query },
348
- ...key === null || key === void 0 ? {} : { key },
349
- ...cell === null ? {} : { cell }
350
- };
351
- }
352
- function controlValue(el) {
353
- const control = el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement ? el : el.querySelector("input, select, textarea");
354
- return control?.value;
355
- }
356
- function gatherValues(form) {
357
- const values = {};
358
- for (const cell of form.querySelectorAll(`[${ATTR_CELL}]`)) {
359
- const name = cell.getAttribute(ATTR_CELL);
360
- const value = controlValue(cell);
361
- if (value === void 0) {
362
- console.warn(
363
- `lb-hub: lb-cell '${name}' has no control to read, ignoring it`,
364
- cell
365
- );
366
- continue;
367
- }
368
- values[name] = value;
369
- }
370
- return values;
371
- }
372
- function pageNameFor(path) {
373
- const name = path.replace(/^\/+|\/+$/g, "");
374
- return name === "" ? "counter" : name;
375
- }
376
- async function fetchData(url, init) {
377
- const controller = new AbortController();
378
- const timeout = setTimeout(() => controller.abort(), LB_REQUEST_TIMEOUT_MS);
379
- try {
380
- const res = await fetch(url, { ...init, signal: controller.signal });
381
- if (!res.ok) {
382
- throw new Error(`lb-hub: server responded ${res.status} for ${url}`);
383
- }
384
- return await res.json();
385
- } finally {
386
- clearTimeout(timeout);
387
- }
388
- }
389
- var LbHub = class extends HTMLElement {
390
- main;
391
- /**
392
- * The page currently in <main>. Hooks are declared per page, so a request
393
- * has to say which page it came from. The page rides on the URL rather
394
- * than in the request, leaving the operation set closed.
395
- */
396
- page = "";
397
- connectedCallback() {
398
- this.main = this.querySelector("main");
399
- this.addEventListener(LB_EVENT_NAME, (e) => {
400
- const request = e.detail;
401
- if (!request?.op) {
402
- console.warn(`lb-hub: event without an op, ignoring`, request);
403
- return;
404
- }
405
- const origin = e.target;
406
- origin?.removeAttribute(ATTR_ERROR);
407
- origin?.setAttribute(ATTR_PENDING, "");
408
- const url = `${LB_REQUEST_ENDPOINT}?page=${encodeURIComponent(this.page)}`;
409
- void fetchData(url, {
410
- method: "POST",
411
- headers: { "content-type": "application/json" },
412
- body: JSON.stringify(request)
413
- }).then((data) => applyData(this.main, data)).catch((err) => {
414
- console.error(`lb-hub: request failed`, err);
415
- origin?.setAttribute(ATTR_ERROR, "");
416
- }).finally(() => origin?.removeAttribute(ATTR_PENDING));
417
- });
418
- this.addEventListener("click", (e) => {
419
- const el = e.target?.closest(`[${ATTR_ACTION}]`);
420
- if (!el || el.localName.includes("-")) return;
421
- e.preventDefault();
422
- const detail = {
423
- op: "action",
424
- name: el.getAttribute(ATTR_ACTION),
425
- ...coordinates(el)
426
- };
427
- el.dispatchEvent(
428
- new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail })
429
- );
430
- });
431
- this.addEventListener("click", (e) => {
432
- const el = e.target?.closest(`[${ATTR_DELETE}]`);
433
- if (!el || el.localName.includes("-")) return;
434
- e.preventDefault();
435
- const { query, key } = coordinates(el);
436
- if (!query || !key) {
437
- console.warn(`lb-hub: lb-delete with no query/key coordinates, ignoring`, el);
438
- return;
439
- }
440
- const detail = { op: "tuple-delete", query, key };
441
- el.dispatchEvent(
442
- new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail })
443
- );
444
- });
445
- this.addEventListener("submit", (e) => {
446
- const form = e.target?.closest(`[${ATTR_INSERT}]`);
447
- if (!form) return;
448
- e.preventDefault();
449
- const { query } = coordinates(form);
450
- if (!query) {
451
- console.warn(`lb-hub: lb-insert with no lb-query, ignoring`, form);
452
- return;
453
- }
454
- const detail = {
455
- op: "tuple-insert",
456
- query,
457
- values: gatherValues(form)
458
- };
459
- form.dispatchEvent(
460
- new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail })
461
- );
462
- });
463
- this.addEventListener("submit", (e) => {
464
- const form = e.target?.closest(`[${ATTR_UPDATE}]`);
465
- if (!form) return;
466
- e.preventDefault();
467
- const { query, key } = coordinates(form);
468
- if (!query || !key) {
469
- console.warn(
470
- `lb-hub: lb-update with no query/key coordinates, ignoring`,
471
- form
472
- );
473
- return;
474
- }
475
- const detail = {
476
- op: "tuple-update",
477
- query,
478
- key,
479
- values: gatherValues(form)
480
- };
481
- form.dispatchEvent(
482
- new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail })
483
- );
484
- });
485
- this.addEventListener("click", (e) => {
486
- const link = e.target?.closest(`a[${ATTR_NAV_LINK}]`);
487
- if (!link) return;
488
- e.preventDefault();
489
- const path = new URL(link.href).pathname;
490
- history.pushState(null, "", path);
491
- void this.navigate(path);
492
- });
493
- addEventListener("popstate", () => void this.navigate(location.pathname));
494
- void this.navigate(location.pathname);
495
- }
496
- /**
497
- * Navigation is the one operation that replaces host DOM. The host is
498
- * already in the document, so selecting a page is getElementById() and
499
- * there is no fetching mechanism on the host channel.
500
- */
501
- async navigate(path) {
502
- const page = pageNameFor(path);
503
- const template = document.getElementById(PAGE_TEMPLATE_PREFIX + page);
504
- if (!(template instanceof HTMLTemplateElement)) {
505
- console.error(`lb-hub: no page host for '${page}'`);
506
- return;
507
- }
508
- let data = {};
509
- try {
510
- data = await fetchData(
511
- `${LB_DATA_ENDPOINT}?page=${encodeURIComponent(page)}`
512
- );
513
- } catch (err) {
514
- console.error(`lb-hub: failed to load data for '${page}'`, err);
515
- }
516
- this.page = page;
517
- this.main.replaceChildren(template.content.cloneNode(true));
518
- applyData(this.main, data);
519
- }
520
- };
521
- customElements.define(HUB_TAG_NAME, LbHub);
522
- })();
@@ -1,15 +0,0 @@
1
- /**
2
- * A plain custom element with no tie to the framework: no lb- prefix, no
3
- * expansion definition, nothing but customElements.define. It exists to
4
- * prove the builder resolves and bundles a tag by directory convention
5
- * alone, exactly as it does for a widget the framework ships itself.
6
- *
7
- * Its look comes from app-box.css, a plain class rather than the style
8
- * attribute — a style attribute is inline script by another name, and the
9
- * CSP this framework targets refuses it exactly as it refuses inline
10
- * <script>.
11
- */
12
- declare class AppBox extends HTMLElement {
13
- connectedCallback(): void;
14
- }
15
- //# sourceMappingURL=app-box.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-box.d.ts","sourceRoot":"","sources":["../../../../demo-static/src/widgets/app-box.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,cAAM,MAAO,SAAQ,WAAW;IAC9B,iBAAiB;CAGlB"}
@@ -1,19 +0,0 @@
1
- "use strict";
2
- /// <reference lib="dom" />
3
- /**
4
- * A plain custom element with no tie to the framework: no lb- prefix, no
5
- * expansion definition, nothing but customElements.define. It exists to
6
- * prove the builder resolves and bundles a tag by directory convention
7
- * alone, exactly as it does for a widget the framework ships itself.
8
- *
9
- * Its look comes from app-box.css, a plain class rather than the style
10
- * attribute — a style attribute is inline script by another name, and the
11
- * CSP this framework targets refuses it exactly as it refuses inline
12
- * <script>.
13
- */
14
- class AppBox extends HTMLElement {
15
- connectedCallback() {
16
- this.classList.add("app-box");
17
- }
18
- }
19
- customElements.define("app-box", AppBox);
@@ -1 +0,0 @@
1
- {"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/collision/elements.ts"],"names":[],"mappings":";;;AAAA,wBAEE"}
@@ -1,3 +0,0 @@
1
- export default {
2
- "acme-widget": "@acme/widget",
3
- };
@@ -1,5 +0,0 @@
1
- declare const _default: {
2
- "acme-widget": string;
3
- };
4
- export default _default;
5
- //# sourceMappingURL=elements.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/manifest/elements.ts"],"names":[],"mappings":";;;AAAA,wBAEE"}
@@ -1,3 +0,0 @@
1
- export default {
2
- "acme-widget": "@acme/widget",
3
- };
@@ -1,5 +0,0 @@
1
- declare const _default: {
2
- acmewidget: string;
3
- };
4
- export default _default;
5
- //# sourceMappingURL=elements.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/manifest-bad-tag/elements.ts"],"names":[],"mappings":";;;AAAA,wBAEE"}
@@ -1,3 +0,0 @@
1
- export default {
2
- acmewidget: "@acme/widget",
3
- };
@@ -1,5 +0,0 @@
1
- declare const _default: {
2
- "acme-widget": number;
3
- };
4
- export default _default;
5
- //# sourceMappingURL=elements.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/manifest-bad-value/elements.ts"],"names":[],"mappings":";;;AAAA,wBAEE"}
@@ -1,3 +0,0 @@
1
- export default {
2
- "acme-widget": 42,
3
- };
@@ -1,19 +0,0 @@
1
- /**
2
- * Golden files — see docs/testing.md, tier 1.
3
- *
4
- * Every page in demo/pages is expanded against the real definition
5
- * directories and compared to a stored copy of its output. Ten cheap tests
6
- * that cover the whole chain at once, including the directory ordering that
7
- * lets an application override a built-in widget.
8
- *
9
- * The stored copy is formatted, because a golden file nobody can read is a
10
- * diff nobody reviews. Formatting is cosmetic and may decline (see
11
- * build/format.ts); if it does, the comparison is still exact — both sides
12
- * went through the same function.
13
- *
14
- * npm run test:golden rewrite the stored copies
15
- *
16
- * Rewriting is not blessing. The diff is the point.
17
- */
18
- export {};
19
- //# sourceMappingURL=golden.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"golden.test.d.ts","sourceRoot":"","sources":["../../tests/golden.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG"}