@loadbare/app 0.6.0 → 0.7.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.
- package/dist/build/assemble.js +1 -0
- package/dist/build/assemble.js.map +1 -0
- package/dist/build/cli.js +1 -0
- package/dist/build/cli.js.map +1 -0
- package/dist/build/elements.js +1 -0
- package/dist/build/elements.js.map +1 -0
- package/dist/build/expand.js +1 -0
- package/dist/build/expand.js.map +1 -0
- package/dist/build/format.js +1 -0
- package/dist/build/format.js.map +1 -0
- package/dist/build/locations.d.ts +2 -2
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +3 -4
- package/dist/build/locations.js.map +1 -0
- package/dist/build/origins.js +1 -0
- package/dist/build/origins.js.map +1 -0
- package/dist/build/package-root.js +1 -0
- package/dist/build/package-root.js.map +1 -0
- package/dist/build/pages.d.ts +4 -0
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +5 -0
- package/dist/build/pages.js.map +1 -0
- package/dist/build/styles.js +1 -0
- package/dist/build/styles.js.map +1 -0
- package/dist/core/lb-constants.js +1 -0
- package/dist/core/lb-constants.js.map +1 -0
- package/dist/core/lb-types.d.ts +1 -1
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +2 -1
- package/dist/core/lb-types.js.map +1 -0
- package/dist/hub/lb-apply.d.ts +11 -1
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +50 -13
- package/dist/hub/lb-apply.js.map +1 -0
- package/dist/hub/lb-hub.browser.d.ts +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +143 -112
- package/dist/hub/lb-hub.browser.js.map +1 -0
- package/dist/server/lb-express.d.ts +1 -1
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +1 -0
- package/dist/server/lb-express.js.map +1 -0
- package/dist/server/lb-server.d.ts +1 -1
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +2 -1
- package/dist/server/lb-server.js.map +1 -0
- package/docs/TECHREF-1.0.md +158 -43
- package/docs/analysis-accidental-complexity.md +149 -0
- package/docs/reference/custom-elements.md +27 -17
- package/docs/reference/data-binding.md +88 -29
- package/docs/reference/server.md +18 -0
- package/docs/reference/widgets.md +3 -3
- package/docs/theory.md +114 -1
- package/docs/tutorials/072-inserting-into-a-list.md +14 -11
- package/docs/tutorials/080-widget-requests.md +9 -26
- package/package.json +2 -2
- package/dist/tests/assemble.test.d.ts +0 -8
- package/dist/tests/assemble.test.d.ts.map +0 -1
- package/dist/tests/assemble.test.js +0 -210
- package/dist/tests/elements.test.d.ts +0 -8
- package/dist/tests/elements.test.d.ts.map +0 -1
- package/dist/tests/elements.test.js +0 -118
- package/dist/tests/expand.test.d.ts +0 -10
- package/dist/tests/expand.test.d.ts.map +0 -1
- package/dist/tests/expand.test.js +0 -253
- package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/imports.js +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
- package/dist/tests/helpers/console.d.ts +0 -20
- package/dist/tests/helpers/console.d.ts.map +0 -1
- package/dist/tests/helpers/console.js +0 -28
- package/dist/tests/helpers/dom.d.ts +0 -18
- package/dist/tests/helpers/dom.d.ts.map +0 -1
- package/dist/tests/helpers/dom.js +0 -22
- package/dist/tests/helpers/hub.d.ts +0 -73
- package/dist/tests/helpers/hub.d.ts.map +0 -1
- package/dist/tests/helpers/hub.js +0 -151
- package/dist/tests/lb-apply.test.d.ts +0 -8
- package/dist/tests/lb-apply.test.d.ts.map +0 -1
- package/dist/tests/lb-apply.test.js +0 -177
- package/dist/tests/lb-express.test.d.ts +0 -14
- package/dist/tests/lb-express.test.d.ts.map +0 -1
- package/dist/tests/lb-express.test.js +0 -243
- package/dist/tests/lb-hub.test.d.ts +0 -14
- package/dist/tests/lb-hub.test.d.ts.map +0 -1
- package/dist/tests/lb-hub.test.js +0 -319
- package/dist/tests/lb-list.test.d.ts +0 -12
- package/dist/tests/lb-list.test.d.ts.map +0 -1
- package/dist/tests/lb-list.test.js +0 -339
- package/dist/tests/lb-server.test.d.ts +0 -9
- package/dist/tests/lb-server.test.d.ts.map +0 -1
- package/dist/tests/lb-server.test.js +0 -546
- package/dist/tests/origins.test.d.ts +0 -10
- package/dist/tests/origins.test.d.ts.map +0 -1
- package/dist/tests/origins.test.js +0 -387
- package/dist/tests/pages.test.d.ts +0 -6
- package/dist/tests/pages.test.d.ts.map +0 -1
- package/dist/tests/pages.test.js +0 -148
- package/dist/tests/styles.test.d.ts +0 -7
- package/dist/tests/styles.test.d.ts.map +0 -1
- package/dist/tests/styles.test.js +0 -76
package/dist/hub/lb-apply.js
CHANGED
|
@@ -15,26 +15,56 @@
|
|
|
15
15
|
* element already showing it, and filling that element are the same in every
|
|
16
16
|
* list, so no widget can get them wrong.
|
|
17
17
|
*/
|
|
18
|
-
import { ATTR_CELL, ATTR_KEY, ATTR_KEY_VALUE, ATTR_LIST, ATTR_ROW, ATTR_ROW_COUNT, ATTR_VALUE, } from "../core/lb-constants";
|
|
19
|
-
import { isPatch, } from "../core/lb-types";
|
|
18
|
+
import { ATTR_CELL, ATTR_KEY, ATTR_KEY_VALUE, ATTR_LIST, ATTR_ROW, ATTR_ROW_COUNT, ATTR_VALUE, } from "../core/lb-constants.js";
|
|
19
|
+
import { isPatch, } from "../core/lb-types.js";
|
|
20
|
+
/** The input types whose state is not their `value`, or cannot be set. */
|
|
21
|
+
const UNLANDED_INPUTS = ["checkbox", "radio", "file"];
|
|
20
22
|
/**
|
|
21
|
-
* A
|
|
23
|
+
* A form control whose state is its `value`: a `<select>`, a `<textarea>`,
|
|
24
|
+
* or an `<input>` of any type but checkbox, radio and file. Landing and
|
|
25
|
+
* gathering draw the same line, so a value read back from a form is the one
|
|
26
|
+
* that landed there.
|
|
27
|
+
*
|
|
28
|
+
* Told apart by tag name and type rather than by class, so it holds for an
|
|
29
|
+
* element from any document.
|
|
30
|
+
*/
|
|
31
|
+
export function isValueControl(el) {
|
|
32
|
+
if (el.localName === "select" || el.localName === "textarea")
|
|
33
|
+
return true;
|
|
34
|
+
return (el.localName === "input" &&
|
|
35
|
+
!UNLANDED_INPUTS.includes(el.type));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A cell lands one of three ways. A custom element owns whatever control it
|
|
22
39
|
* wraps, so it receives the value as an attribute and renders it itself.
|
|
23
40
|
* Because the browser runs `attributeChangedCallback` for attributes already
|
|
24
41
|
* present when a widget upgrades, that is the same operation whether the host
|
|
25
42
|
* was inserted a microsecond ago or an hour ago.
|
|
26
43
|
*
|
|
27
|
-
* A
|
|
28
|
-
*
|
|
29
|
-
*
|
|
44
|
+
* A form control shows its state as its `value`, so that is where the value
|
|
45
|
+
* goes; its text is its options, for a select. Any other native element has
|
|
46
|
+
* no behavior of its own, so its value is its text. Either one also carries
|
|
47
|
+
* the value as `lb-value`, so a stylesheet can select on what landed.
|
|
48
|
+
* Checkboxes and radio buttons are not implemented, and receive nothing — see
|
|
49
|
+
* docs/TECHREF-1.0.md, "Blockers".
|
|
30
50
|
*/
|
|
31
51
|
function land(el, value) {
|
|
32
52
|
// Untouched, whatever the server sent: the browser decides what a
|
|
33
53
|
// non-string looks like. See Row in core/lb-types.ts.
|
|
34
|
-
if (el.localName.includes("-"))
|
|
54
|
+
if (el.localName.includes("-")) {
|
|
35
55
|
el.setAttribute(ATTR_VALUE, value);
|
|
36
|
-
|
|
56
|
+
}
|
|
57
|
+
else if (isValueControl(el)) {
|
|
58
|
+
el.value = value;
|
|
59
|
+
el.setAttribute(ATTR_VALUE, value);
|
|
60
|
+
}
|
|
61
|
+
else if (el.localName === "input") {
|
|
62
|
+
console.warn(`lb-hub: a value does not land on <input type="${el.type}">, ignoring`, el);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
37
65
|
el.textContent = value;
|
|
66
|
+
el.setAttribute(ATTR_VALUE, value);
|
|
67
|
+
}
|
|
38
68
|
}
|
|
39
69
|
/**
|
|
40
70
|
* Whether an element under `root` belongs to root's scope, rather than to a
|
|
@@ -81,15 +111,15 @@ export function applyRow(root, row) {
|
|
|
81
111
|
}
|
|
82
112
|
/**
|
|
83
113
|
* The row template: the one the developer wrote inside this list scope, or
|
|
84
|
-
* null when this scope shows nothing.
|
|
114
|
+
* null when this scope shows nothing. A template inside a nested scope is
|
|
115
|
+
* that scope's.
|
|
85
116
|
*
|
|
86
|
-
* A scope with no template is bound to the list without displaying it
|
|
87
|
-
* insert form names the list it adds a row to, and has no rows of its own.
|
|
117
|
+
* A scope with no template is bound to the list without displaying it.
|
|
88
118
|
* That is not a mistake, so it is silent. A template that does not name its
|
|
89
119
|
* key column is a mistake, and says so.
|
|
90
120
|
*/
|
|
91
121
|
function templateIn(scope) {
|
|
92
|
-
const template = scope
|
|
122
|
+
const template = within(scope, "template")[0];
|
|
93
123
|
if (!template)
|
|
94
124
|
return null;
|
|
95
125
|
if (!template.getAttribute(ATTR_KEY)) {
|
|
@@ -102,11 +132,17 @@ function templateIn(scope) {
|
|
|
102
132
|
/**
|
|
103
133
|
* The rows already showing, by the key value each carries. Only a live row
|
|
104
134
|
* carries one: the template names the key column under a different name.
|
|
135
|
+
*
|
|
136
|
+
* A row of a nested scope is that scope's. A row's own `lb-list` or `lb-row`
|
|
137
|
+
* names what it displays, not which list it is a row of, so the walk starts
|
|
138
|
+
* at its parent.
|
|
105
139
|
*/
|
|
106
140
|
function showing(scope) {
|
|
107
141
|
const rows = new Map();
|
|
108
142
|
for (const el of scope.querySelectorAll(`[${ATTR_KEY_VALUE}]`)) {
|
|
109
|
-
|
|
143
|
+
if (inScope(scope, el.parentElement)) {
|
|
144
|
+
rows.set(el.getAttribute(ATTR_KEY_VALUE), el);
|
|
145
|
+
}
|
|
110
146
|
}
|
|
111
147
|
return rows;
|
|
112
148
|
}
|
|
@@ -225,3 +261,4 @@ export function applyData(root, data) {
|
|
|
225
261
|
applyRow(scope, result);
|
|
226
262
|
}
|
|
227
263
|
}
|
|
264
|
+
//# sourceMappingURL=lb-apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lb-apply.js","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\n/**\n * Landing data on a live host — see docs/reference/data-binding.md.\n *\n * One containment ladder, and one function per rung. `applyData` lands a\n * whole response, `applyList` lands one list result, `applyRow` fills one\n * scope from one row. Filling a live row and filling a single-row scope are\n * the same operation, and the only difference is how much of the page the\n * root covers.\n *\n * The row machinery used to live in its own file because every list widget\n * imported and called it. The hub reconciles a list scope itself now, so a\n * widget supplies placement and scaffolding through two optional hooks and\n * never sees a whole result. Cloning the template, matching a row to the\n * element already showing it, and filling that element are the same in every\n * list, so no widget can get them wrong.\n */\n\nimport {\n ATTR_CELL,\n ATTR_KEY,\n ATTR_KEY_VALUE,\n ATTR_LIST,\n ATTR_ROW,\n ATTR_ROW_COUNT,\n ATTR_VALUE,\n} from \"../core/lb-constants.js\";\nimport {\n isPatch,\n type HubData,\n type ListHost,\n type ListResult,\n type Row,\n} from \"../core/lb-types.js\";\n\n/** The input types whose state is not their `value`, or cannot be set. */\nconst UNLANDED_INPUTS = [\"checkbox\", \"radio\", \"file\"];\n\n/**\n * A form control whose state is its `value`: a `<select>`, a `<textarea>`,\n * or an `<input>` of any type but checkbox, radio and file. Landing and\n * gathering draw the same line, so a value read back from a form is the one\n * that landed there.\n *\n * Told apart by tag name and type rather than by class, so it holds for an\n * element from any document.\n */\nexport function isValueControl(\n el: Element,\n): el is HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement {\n if (el.localName === \"select\" || el.localName === \"textarea\") return true;\n return (\n el.localName === \"input\" &&\n !UNLANDED_INPUTS.includes((el as HTMLInputElement).type)\n );\n}\n\n/**\n * A cell lands one of three ways. A custom element owns whatever control it\n * wraps, so it receives the value as an attribute and renders it itself.\n * Because the browser runs `attributeChangedCallback` for attributes already\n * present when a widget upgrades, that is the same operation whether the host\n * was inserted a microsecond ago or an hour ago.\n *\n * A form control shows its state as its `value`, so that is where the value\n * goes; its text is its options, for a select. Any other native element has\n * no behavior of its own, so its value is its text. Either one also carries\n * the value as `lb-value`, so a stylesheet can select on what landed.\n * Checkboxes and radio buttons are not implemented, and receive nothing — see\n * docs/TECHREF-1.0.md, \"Blockers\".\n */\nfunction land(el: Element, value: unknown): void {\n // Untouched, whatever the server sent: the browser decides what a\n // non-string looks like. See Row in core/lb-types.ts.\n if (el.localName.includes(\"-\")) {\n el.setAttribute(ATTR_VALUE, value as string);\n } else if (isValueControl(el)) {\n el.value = value as string;\n el.setAttribute(ATTR_VALUE, value as string);\n } else if (el.localName === \"input\") {\n console.warn(\n `lb-hub: a value does not land on <input type=\"${(el as HTMLInputElement).type}\">, ignoring`,\n el,\n );\n } else {\n el.textContent = value as string;\n el.setAttribute(ATTR_VALUE, value as string);\n }\n}\n\n/**\n * Whether an element under `root` belongs to root's scope, rather than to a\n * scope nested inside it.\n *\n * Both scope attributes scope their DOM children, and a nested one of either\n * kind begins a new scope (docs/reference/data-binding.md). So a descendant\n * is in root's scope unless something between it and root — the element\n * itself included — carries one. Root's own attribute does not count: root is\n * the scope being filled, whatever it carries.\n */\nfunction inScope(root: Element, el: Element): boolean {\n for (let node: Element | null = el; node && node !== root;) {\n if (node.hasAttribute(ATTR_LIST) || node.hasAttribute(ATTR_ROW)) {\n return false;\n }\n node = node.parentElement;\n }\n return true;\n}\n\n/** Root if it matches, then every descendant in root's scope that does. */\nfunction within(root: Element, selector: string): Element[] {\n const found: Element[] = root.matches(selector) ? [root] : [];\n for (const el of root.querySelectorAll(selector)) {\n if (inScope(root, el)) found.push(el);\n }\n return found;\n}\n\n/**\n * Fill one scope from one row.\n *\n * The root counts as a cell if it carries one. A `<tr>` holds its cells in\n * `<td>` children, but `<option>`'s content model is text, so an option row\n * has to be the cell it displays. Requiring a wrapper there would require an\n * element HTML does not allow.\n */\nexport function applyRow(root: Element, row: Row): void {\n for (const [column, value] of Object.entries(row)) {\n for (const el of within(root, `[${ATTR_CELL}=\"${column}\"]`)) {\n land(el, value);\n }\n }\n}\n\n/**\n * The row template: the one the developer wrote inside this list scope, or\n * null when this scope shows nothing. A template inside a nested scope is\n * that scope's.\n *\n * A scope with no template is bound to the list without displaying it.\n * That is not a mistake, so it is silent. A template that does not name its\n * key column is a mistake, and says so.\n */\nfunction templateIn(scope: Element): HTMLTemplateElement | null {\n const template = within(scope, \"template\")[0] as\n HTMLTemplateElement | undefined;\n if (!template) return null;\n if (!template.getAttribute(ATTR_KEY)) {\n console.error(\n `lb-hub: the <template> in <${scope.localName}> has no ${ATTR_KEY} ` +\n `naming the column that identifies a row`,\n );\n return null;\n }\n return template;\n}\n\n/**\n * The rows already showing, by the key value each carries. Only a live row\n * carries one: the template names the key column under a different name.\n *\n * A row of a nested scope is that scope's. A row's own `lb-list` or `lb-row`\n * names what it displays, not which list it is a row of, so the walk starts\n * at its parent.\n */\nfunction showing(scope: Element): Map<string, Element> {\n const rows = new Map<string, Element>();\n for (const el of scope.querySelectorAll(`[${ATTR_KEY_VALUE}]`)) {\n if (inScope(scope, el.parentElement!)) {\n rows.set(el.getAttribute(ATTR_KEY_VALUE)!, el);\n }\n }\n return rows;\n}\n\n/**\n * Land a list result in a list scope.\n *\n * An array is the whole set, so it decides membership and order: every row is\n * placed in the order given, and a row whose key did not arrive is gone. A\n * patch disturbs only what it names — a row it did not mention keeps its\n * contents and its position.\n *\n * A widget that carries `lbPlaceRow` decides where a row goes, because only\n * it knows whether it sorts or groups. Without it a row lands immediately\n * before the template, so rows accumulate in the order they arrive and the\n * template stays put as the insertion marker.\n */\nexport function applyList(scope: Element, result: ListResult): void {\n const template = templateIn(scope);\n if (!template) return;\n const keyColumn = template.getAttribute(ATTR_KEY)!;\n const shown = showing(scope);\n const host = scope as Element & ListHost;\n const whole = !isPatch(result);\n\n const place = (el: Element, row: Row): void => {\n if (host.lbPlaceRow) host.lbPlaceRow(el, row, template);\n else template.parentElement!.insertBefore(el, template);\n };\n\n const upsert = (row: Row): string | null => {\n if (row[keyColumn] === undefined) {\n console.error(\n `lb-hub: a row for <${scope.localName}> has no '${keyColumn}' column`,\n );\n return null;\n }\n // The key is stored on the row as an attribute and read back from\n // there, so it is compared as the string the attribute holds.\n const key = String(row[keyColumn]);\n let el = shown.get(key);\n const fresh = el === undefined;\n if (!el) {\n el = template.content.firstElementChild!.cloneNode(true) as Element;\n el.setAttribute(ATTR_KEY_VALUE, key);\n shown.set(key, el);\n }\n // Fill before insertion. The attributes are already there when the row\n // upgrades, which is the same thing that makes hydration and refresh one\n // operation everywhere else.\n applyRow(el, row);\n if (fresh || whole) place(el, row);\n return key;\n };\n\n if (Array.isArray(result)) {\n const arrived = new Set<string>();\n for (const row of result) {\n const key = upsert(row);\n if (key !== null) arrived.add(key);\n }\n for (const [key, el] of shown) if (!arrived.has(key)) el.remove();\n } else {\n for (const row of result.rows ?? []) upsert(row);\n for (const key of result.drop ?? []) shown.get(String(key))?.remove();\n }\n\n // How many rows are showing, counted from the DOM rather than from either\n // branch above, so a whole set and a patch report the same fact the same\n // way.\n //\n // It is stamped here because only this function knows the count: it is the\n // one conditional a page cannot be sent, since the server answers with rows\n // and says nothing about how many survived reconciliation. A page says what\n // an empty list looks like in a stylesheet, and no list widget carries code\n // for it. See docs/reference/data-binding.md.\n scope.setAttribute(ATTR_ROW_COUNT, String(showing(scope).size));\n\n // Derived scaffolding — a section heading, an <optgroup> — goes when its\n // last row does, and only the widget knows it exists.\n host.lbRowsLanded?.();\n}\n\n/** Land a whole response. Every result arrives through here. */\nexport function applyData(root: ParentNode, data: HubData): void {\n for (const [name, result] of Object.entries(data)) {\n const lists = root.querySelectorAll(`[${ATTR_LIST}=\"${name}\"]`);\n const rows = root.querySelectorAll(`[${ATTR_ROW}=\"${name}\"]`);\n if (lists.length === 0 && rows.length === 0) {\n console.warn(`lb-hub: no scope for '${name}', skipping`);\n continue;\n }\n // Cardinality is a property of the name, so one name is a list or a row\n // and never both. Two spellings of it on one page is a mistake in the\n // markup rather than a case to reconcile.\n if (lists.length > 0 && rows.length > 0) {\n console.error(\n `lb-hub: '${name}' is bound as a list in one place and a row in ` +\n `another; a name answers with one shape`,\n );\n continue;\n }\n if (lists.length > 0) {\n for (const scope of lists) applyList(scope, result as ListResult);\n continue;\n }\n // The only disagreement visible from here: a patch and a row are both\n // objects, so an array arriving at a row scope is the one case the\n // browser can name. createHub holds the stronger check, because it knows\n // what the query declared.\n if (Array.isArray(result)) {\n console.error(\n `lb-hub: '${name}' is bound with ${ATTR_ROW} but answered with rows`,\n );\n continue;\n }\n for (const scope of rows) applyRow(scope, result as Row);\n }\n}\n"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { applyData, applyRow } from "./lb-apply";
|
|
1
|
+
export { applyData, applyRow } from "./lb-apply.js";
|
|
2
2
|
//# sourceMappingURL=lb-hub.browser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-hub.browser.d.ts","sourceRoot":"","sources":["../../hub/lb-hub.browser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lb-hub.browser.d.ts","sourceRoot":"","sources":["../../hub/lb-hub.browser.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -1,28 +1,109 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
|
-
import { ACTION_ROW_DELETE, ACTION_ROW_INSERT, ACTION_ROW_UPDATE, ATTR_ACTION, ATTR_CELL, ATTR_ERROR, ATTR_KEY_VALUE, ATTR_LIST, ATTR_NAV_LINK, ATTR_PAGE, ATTR_PENDING, ATTR_ROW, ATTR_UNKNOWN_PAGE, LB_ACTIONS, LB_RESERVED_PREFIX, HUB_TAG_NAME, NAV_CELL_LABEL, NAV_CELL_URI, NAV_ROW, LB_EVENT_NAME, LB_ENDPOINT, LB_REQUEST_TIMEOUT_MS, } from "../core/lb-constants";
|
|
3
|
-
import { applyData } from "./lb-apply";
|
|
4
|
-
export { applyData, applyRow } from "./lb-apply";
|
|
2
|
+
import { ACTION_CELL_CHANGE, ACTION_ROW_DELETE, ACTION_ROW_INSERT, ACTION_ROW_UPDATE, ATTR_ACTION, ATTR_CELL, ATTR_ERROR, ATTR_KEY_VALUE, ATTR_LIST, ATTR_NAV_LINK, ATTR_PAGE, ATTR_PENDING, ATTR_ROW, ATTR_UNKNOWN_PAGE, LB_ACTIONS, LB_RESERVED_PREFIX, HUB_TAG_NAME, NAV_CELL_LABEL, NAV_CELL_URI, NAV_ROW, LB_EVENT_NAME, LB_ENDPOINT, LB_REQUEST_TIMEOUT_MS, } from "../core/lb-constants.js";
|
|
3
|
+
import { applyData, isValueControl } from "./lb-apply.js";
|
|
4
|
+
export { applyData, applyRow } from "./lb-apply.js";
|
|
5
|
+
/** What each operation takes from scope, and what it cannot be sent without. */
|
|
6
|
+
const OPERATIONS = {
|
|
7
|
+
[ACTION_CELL_CHANGE]: {
|
|
8
|
+
fills: ["list", "key", "cell"],
|
|
9
|
+
requires: ["list", "key", "cell", "value"],
|
|
10
|
+
},
|
|
11
|
+
[ACTION_ROW_INSERT]: { fills: ["list"], requires: ["list", "values"] },
|
|
12
|
+
[ACTION_ROW_DELETE]: { fills: ["list", "key"], requires: ["list", "key"] },
|
|
13
|
+
[ACTION_ROW_UPDATE]: {
|
|
14
|
+
fills: ["list", "key"],
|
|
15
|
+
requires: ["list", "key", "values"],
|
|
16
|
+
},
|
|
17
|
+
};
|
|
5
18
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
19
|
+
* Complete a request from where its element sits — see docs/TECHREF-1.0.md,
|
|
20
|
+
* "Requests". Every request passes through here, whoever built it, so a
|
|
21
|
+
* widget states what it did and never where it is.
|
|
9
22
|
*
|
|
10
23
|
* The scope field is `list` or `row`, whichever attribute scoped the nearest
|
|
11
|
-
* ancestor, so
|
|
12
|
-
* wire never needs a noun for the addressable thing.
|
|
24
|
+
* ancestor, so a declared action fired inside a single-row scope says so and
|
|
25
|
+
* the wire never needs a noun for the addressable thing. A field the request
|
|
26
|
+
* already carries is the dispatcher's to decide and is left as it is.
|
|
27
|
+
*
|
|
28
|
+
* False when the request cannot be sent, having said why.
|
|
13
29
|
*/
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
function scope(request, el) {
|
|
31
|
+
if (!request?.action) {
|
|
32
|
+
console.warn(`lb-hub: event without an action, ignoring`, request);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const { action } = request;
|
|
36
|
+
const reserved = action.startsWith(LB_RESERVED_PREFIX);
|
|
37
|
+
// The reserved prefix is the whole of the discriminant, so a reserved
|
|
38
|
+
// name that is not one of the four operations is a typo rather than
|
|
39
|
+
// something to forward. Refused here, where the widget can see why: a
|
|
40
|
+
// server would only report that it declares no such action.
|
|
41
|
+
if (reserved && !LB_ACTIONS.includes(action)) {
|
|
42
|
+
console.error(`lb-hub: '${action}' begins with '${LB_RESERVED_PREFIX}' ` +
|
|
43
|
+
`but names no operation, ignoring`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const fields = request;
|
|
47
|
+
// The element's own lb-list or lb-row is what it displays. Where it sits
|
|
48
|
+
// is its ancestors', the way a control belongs to the form around it.
|
|
49
|
+
const parent = el.parentElement;
|
|
50
|
+
const enclosing = parent?.closest(`[${ATTR_LIST}], [${ATTR_ROW}]`);
|
|
51
|
+
const row = parent?.closest(`[${ATTR_KEY_VALUE}]`);
|
|
52
|
+
const inScope = {
|
|
53
|
+
list: enclosing?.getAttribute(ATTR_LIST),
|
|
54
|
+
row: enclosing?.hasAttribute(ATTR_LIST)
|
|
55
|
+
? null
|
|
56
|
+
: enclosing?.getAttribute(ATTR_ROW),
|
|
57
|
+
// Only a row of that scope: a key from outside it names a row of some
|
|
58
|
+
// other list.
|
|
59
|
+
key: row && enclosing?.contains(row) ? row.getAttribute(ATTR_KEY_VALUE) : null,
|
|
60
|
+
cell: el.getAttribute(ATTR_CELL),
|
|
25
61
|
};
|
|
62
|
+
const fill = (name) => {
|
|
63
|
+
const value = inScope[name];
|
|
64
|
+
if (fields[name] === undefined && value !== null && value !== undefined) {
|
|
65
|
+
fields[name] = value;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const operation = OPERATIONS[action];
|
|
69
|
+
if (!operation) {
|
|
70
|
+
// A declared action names one scope or the other, never both.
|
|
71
|
+
if (fields.list === undefined && fields.row === undefined) {
|
|
72
|
+
fill("list");
|
|
73
|
+
fill("row");
|
|
74
|
+
}
|
|
75
|
+
fill("key");
|
|
76
|
+
fill("cell");
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
for (const name of operation.fills)
|
|
80
|
+
fill(name);
|
|
81
|
+
// Gathered from the nearest element holding a cell to read, starting at
|
|
82
|
+
// whatever dispatched: a form holds its own, and a button in a new row's
|
|
83
|
+
// <tr> reads the row around it. Never past the live row the element sits
|
|
84
|
+
// in, nor out into the scope, whose cells are other rows'. Nothing to read
|
|
85
|
+
// is nothing to send, so values stays missing.
|
|
86
|
+
if (fields.values === undefined && operation.requires.includes("values")) {
|
|
87
|
+
const bound = row && enclosing?.contains(row) ? row : null;
|
|
88
|
+
let holder = el;
|
|
89
|
+
while (holder && holder !== enclosing && !hasReadableCell(holder)) {
|
|
90
|
+
holder = holder === bound ? null : holder.parentElement;
|
|
91
|
+
}
|
|
92
|
+
if (holder && holder !== enclosing)
|
|
93
|
+
fields.values = gatherValues(holder);
|
|
94
|
+
}
|
|
95
|
+
const missing = operation.requires.filter((f) => fields[f] === undefined);
|
|
96
|
+
if (missing.length > 0) {
|
|
97
|
+
console.error(`lb-hub: '${action}' from <${el.localName}> has no ` +
|
|
98
|
+
`${missing.join(", ")} in scope, not sent`, el);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
/** Dispatch the bare action an element carries, for the hub to scope. */
|
|
104
|
+
function dispatchAction(el) {
|
|
105
|
+
const detail = { action: el.getAttribute(ATTR_ACTION) };
|
|
106
|
+
el.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
26
107
|
}
|
|
27
108
|
/**
|
|
28
109
|
* The value a cell is currently showing, for gathering rather than landing.
|
|
@@ -30,28 +111,32 @@ function binding(el) {
|
|
|
30
111
|
* A cell is always either a native form control itself (a bare
|
|
31
112
|
* `<input lb-cell>`) or a widget that wraps one (`<lb-input>`, `<lb-select>`)
|
|
32
113
|
* — that wrapping is structural, not incidental, which is what lets this be
|
|
33
|
-
* one rule instead of one per widget type.
|
|
114
|
+
* one rule instead of one per widget type. The controls are the ones a value
|
|
115
|
+
* lands on, so what is read back is what landed.
|
|
34
116
|
*/
|
|
35
117
|
function controlValue(el) {
|
|
36
|
-
const control = el
|
|
37
|
-
el instanceof HTMLSelectElement ||
|
|
38
|
-
el instanceof HTMLTextAreaElement
|
|
118
|
+
const control = isValueControl(el)
|
|
39
119
|
? el
|
|
40
|
-
: el.
|
|
120
|
+
: [...el.querySelectorAll("input, select, textarea")].find(isValueControl);
|
|
41
121
|
return control?.value;
|
|
42
122
|
}
|
|
123
|
+
/** Whether gathering from an element would read anything, reporting nothing. */
|
|
124
|
+
function hasReadableCell(el) {
|
|
125
|
+
return [...el.querySelectorAll(`[${ATTR_CELL}]`)].some((cell) => controlValue(cell) !== undefined);
|
|
126
|
+
}
|
|
43
127
|
/**
|
|
44
|
-
* Every lb-cell inside
|
|
45
|
-
* lb-insert and lb-update — both submit a batch of cells, and differ
|
|
46
|
-
* whether a key comes with them.
|
|
128
|
+
* Every lb-cell inside an element, gathered into one values map. Shared by
|
|
129
|
+
* lb-row-insert and lb-row-update — both submit a batch of cells, and differ
|
|
130
|
+
* only in whether a key comes with them.
|
|
47
131
|
*/
|
|
48
|
-
function gatherValues(
|
|
132
|
+
function gatherValues(el) {
|
|
49
133
|
const values = {};
|
|
50
|
-
for (const cell of
|
|
134
|
+
for (const cell of el.querySelectorAll(`[${ATTR_CELL}]`)) {
|
|
51
135
|
const name = cell.getAttribute(ATTR_CELL);
|
|
52
136
|
const value = controlValue(cell);
|
|
53
137
|
if (value === undefined) {
|
|
54
|
-
console.warn(`lb-hub: lb-cell '${name}' has no
|
|
138
|
+
console.warn(`lb-hub: lb-cell '${name}' has no input, select or textarea to ` +
|
|
139
|
+
`read, ignoring it`, cell);
|
|
55
140
|
continue;
|
|
56
141
|
}
|
|
57
142
|
values[name] = value;
|
|
@@ -133,22 +218,17 @@ class LbHub extends HTMLElement {
|
|
|
133
218
|
page = "";
|
|
134
219
|
connectedCallback() {
|
|
135
220
|
this.main = this.querySelector("main");
|
|
221
|
+
// Scoped in the capture phase, on the way down, so every ancestor that
|
|
222
|
+
// sees the request on its way back up sees it whole. A request that
|
|
223
|
+
// cannot be sent is stopped here, before anything sees it at all.
|
|
224
|
+
this.addEventListener(LB_EVENT_NAME, (e) => {
|
|
225
|
+
const origin = e.target;
|
|
226
|
+
const request = e.detail;
|
|
227
|
+
if (!scope(request, origin))
|
|
228
|
+
e.stopPropagation();
|
|
229
|
+
}, { capture: true });
|
|
136
230
|
this.addEventListener(LB_EVENT_NAME, (e) => {
|
|
137
231
|
const request = e.detail;
|
|
138
|
-
if (!request?.action) {
|
|
139
|
-
console.warn(`lb-hub: event without an action, ignoring`, request);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
// The reserved prefix is the whole of the discriminant, so a reserved
|
|
143
|
-
// name that is not one of the four operations is a typo rather than
|
|
144
|
-
// something to forward. Refused here, where the widget can see why: a
|
|
145
|
-
// server would only report that it declares no such action.
|
|
146
|
-
if (request.action.startsWith(LB_RESERVED_PREFIX) &&
|
|
147
|
-
!LB_ACTIONS.includes(request.action)) {
|
|
148
|
-
console.error(`lb-hub: '${request.action}' begins with '${LB_RESERVED_PREFIX}' ` +
|
|
149
|
-
`but names no operation, ignoring`);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
232
|
// The element that dispatched the request, not e.currentTarget (the
|
|
153
233
|
// hub itself, since the event bubbles) — pending/error state belongs
|
|
154
234
|
// at the origin so a widget can watch its own attributes.
|
|
@@ -164,11 +244,11 @@ class LbHub extends HTMLElement {
|
|
|
164
244
|
.finally(() => origin?.removeAttribute(ATTR_PENDING));
|
|
165
245
|
});
|
|
166
246
|
/**
|
|
167
|
-
* A native element carrying an action. The hub
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* confirm. A native action button and a
|
|
171
|
-
* produce identical events.
|
|
247
|
+
* A native element carrying an action. The hub dispatches the bare action
|
|
248
|
+
* from that element as the ordinary bubbling event, rather than sending
|
|
249
|
+
* it directly, so it is scoped exactly as a widget's is and an ancestor
|
|
250
|
+
* widget can still stop it and confirm. A native action button and a
|
|
251
|
+
* hand-written widget therefore produce identical events.
|
|
172
252
|
*
|
|
173
253
|
* A widget carrying an action is left alone — it owns its own interaction
|
|
174
254
|
* and decides what counts as performing it, which for a <select> is a
|
|
@@ -182,26 +262,26 @@ class LbHub extends HTMLElement {
|
|
|
182
262
|
// A form performs its action on submit, whatever was clicked in it.
|
|
183
263
|
if (el instanceof HTMLFormElement)
|
|
184
264
|
return;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
265
|
+
// A button beside the cells is pressed once they are done. An element
|
|
266
|
+
// holding them has no such moment: a click into one of its own inputs
|
|
267
|
+
// would send the row.
|
|
268
|
+
const action = el.getAttribute(ATTR_ACTION);
|
|
269
|
+
if ((action === ACTION_ROW_INSERT || action === ACTION_ROW_UPDATE) &&
|
|
270
|
+
el.querySelector(`[${ATTR_CELL}]`)) {
|
|
271
|
+
console.error(`lb-hub: '${action}' on <${el.localName}> holds its own cells, ` +
|
|
272
|
+
`so a click inside it is not sent; put it on a <form> or a button`, el);
|
|
273
|
+
return;
|
|
190
274
|
}
|
|
275
|
+
e.preventDefault();
|
|
276
|
+
dispatchAction(el);
|
|
191
277
|
});
|
|
192
|
-
// The same delegation for the event a form owns.
|
|
193
|
-
// gathers the form's cells; a declared one carries the binding, as a
|
|
194
|
-
// click does.
|
|
278
|
+
// The same delegation for the event a form owns.
|
|
195
279
|
this.addEventListener("submit", (e) => {
|
|
196
280
|
const form = e.target;
|
|
197
281
|
if (!form || !form.hasAttribute(ATTR_ACTION))
|
|
198
282
|
return;
|
|
199
283
|
e.preventDefault();
|
|
200
|
-
|
|
201
|
-
const detail = this.requestFor(name, form);
|
|
202
|
-
if (detail) {
|
|
203
|
-
form.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
204
|
-
}
|
|
284
|
+
dispatchAction(form);
|
|
205
285
|
});
|
|
206
286
|
// Only anchors that opted in. Everything else is a real link.
|
|
207
287
|
this.addEventListener("click", (e) => {
|
|
@@ -221,56 +301,6 @@ class LbHub extends HTMLElement {
|
|
|
221
301
|
* already in the document, so selecting a page is getElementById() and
|
|
222
302
|
* there is no fetching mechanism on the host channel.
|
|
223
303
|
*/
|
|
224
|
-
/**
|
|
225
|
-
* What a native element carrying `lb-action` asks for. A reserved name is
|
|
226
|
-
* one of the CRUD operations and takes the binding that operation needs;
|
|
227
|
-
* any other name is an action the page declared, carrying whatever
|
|
228
|
-
* binding is in scope. A reserved name the hub does not turn into an
|
|
229
|
-
* operation here — a cell change, which needs a widget to say what a
|
|
230
|
-
* change is, or a name nobody reserved — is refused rather than sent.
|
|
231
|
-
*/
|
|
232
|
-
requestFor(name, el) {
|
|
233
|
-
const { list, row, key, cell } = binding(el);
|
|
234
|
-
const form = el instanceof HTMLFormElement;
|
|
235
|
-
switch (name) {
|
|
236
|
-
case ACTION_ROW_DELETE:
|
|
237
|
-
if (list && key)
|
|
238
|
-
return { action: ACTION_ROW_DELETE, list, key };
|
|
239
|
-
break;
|
|
240
|
-
case ACTION_ROW_INSERT:
|
|
241
|
-
if (form && list) {
|
|
242
|
-
return {
|
|
243
|
-
action: ACTION_ROW_INSERT,
|
|
244
|
-
list,
|
|
245
|
-
values: gatherValues(el),
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
break;
|
|
249
|
-
case ACTION_ROW_UPDATE:
|
|
250
|
-
if (form && list && key) {
|
|
251
|
-
return {
|
|
252
|
-
action: ACTION_ROW_UPDATE,
|
|
253
|
-
list,
|
|
254
|
-
key,
|
|
255
|
-
values: gatherValues(el),
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
break;
|
|
259
|
-
default:
|
|
260
|
-
if (!name.startsWith(LB_RESERVED_PREFIX)) {
|
|
261
|
-
return {
|
|
262
|
-
action: name,
|
|
263
|
-
...(list === undefined ? {} : { list }),
|
|
264
|
-
...(row === undefined ? {} : { row }),
|
|
265
|
-
...(key === undefined ? {} : { key }),
|
|
266
|
-
...(cell === undefined ? {} : { cell }),
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
console.warn(`lb-hub: ${ATTR_ACTION}="${name}" on <${el.localName}> cannot be ` +
|
|
271
|
-
`sent from here, ignoring`, el);
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
304
|
async navigate(path) {
|
|
275
305
|
const page = pageNameFor(path);
|
|
276
306
|
this.landNavigation(path, page);
|
|
@@ -361,3 +391,4 @@ class LbHub extends HTMLElement {
|
|
|
361
391
|
}
|
|
362
392
|
}
|
|
363
393
|
customElements.define(HUB_TAG_NAME, LbHub);
|
|
394
|
+
//# sourceMappingURL=lb-hub.browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lb-hub.browser.js","sourceRoot":"","sources":["../../hub/lb-hub.browser.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ACTION_CELL_CHANGE,\n ACTION_ROW_DELETE,\n ACTION_ROW_INSERT,\n ACTION_ROW_UPDATE,\n ATTR_ACTION,\n ATTR_CELL,\n ATTR_ERROR,\n ATTR_KEY_VALUE,\n ATTR_LIST,\n ATTR_NAV_LINK,\n ATTR_PAGE,\n ATTR_PENDING,\n ATTR_ROW,\n ATTR_UNKNOWN_PAGE,\n LB_ACTIONS,\n LB_RESERVED_PREFIX,\n HUB_TAG_NAME,\n NAV_CELL_LABEL,\n NAV_CELL_URI,\n NAV_ROW,\n LB_EVENT_NAME,\n LB_ENDPOINT,\n LB_REQUEST_TIMEOUT_MS,\n} from \"../core/lb-constants.js\";\nimport type { HubData, HubRequest } from \"../core/lb-types.js\";\nimport { applyData, isValueControl } from \"./lb-apply.js\";\n\nexport { applyData, applyRow } from \"./lb-apply.js\";\n\n/** What each operation takes from scope, and what it cannot be sent without. */\nconst OPERATIONS: Record<\n string,\n { fills: readonly string[]; requires: readonly string[] }\n> = {\n [ACTION_CELL_CHANGE]: {\n fills: [\"list\", \"key\", \"cell\"],\n requires: [\"list\", \"key\", \"cell\", \"value\"],\n },\n [ACTION_ROW_INSERT]: { fills: [\"list\"], requires: [\"list\", \"values\"] },\n [ACTION_ROW_DELETE]: { fills: [\"list\", \"key\"], requires: [\"list\", \"key\"] },\n [ACTION_ROW_UPDATE]: {\n fills: [\"list\", \"key\"],\n requires: [\"list\", \"key\", \"values\"],\n },\n};\n\n/**\n * Complete a request from where its element sits — see docs/TECHREF-1.0.md,\n * \"Requests\". Every request passes through here, whoever built it, so a\n * widget states what it did and never where it is.\n *\n * The scope field is `list` or `row`, whichever attribute scoped the nearest\n * ancestor, so a declared action fired inside a single-row scope says so and\n * the wire never needs a noun for the addressable thing. A field the request\n * already carries is the dispatcher's to decide and is left as it is.\n *\n * False when the request cannot be sent, having said why.\n */\nfunction scope(request: HubRequest | undefined, el: Element): boolean {\n if (!request?.action) {\n console.warn(`lb-hub: event without an action, ignoring`, request);\n return false;\n }\n const { action } = request;\n const reserved = action.startsWith(LB_RESERVED_PREFIX);\n // The reserved prefix is the whole of the discriminant, so a reserved\n // name that is not one of the four operations is a typo rather than\n // something to forward. Refused here, where the widget can see why: a\n // server would only report that it declares no such action.\n if (reserved && !(LB_ACTIONS as readonly string[]).includes(action)) {\n console.error(\n `lb-hub: '${action}' begins with '${LB_RESERVED_PREFIX}' ` +\n `but names no operation, ignoring`,\n );\n return false;\n }\n\n const fields = request as unknown as Record<string, unknown>;\n // The element's own lb-list or lb-row is what it displays. Where it sits\n // is its ancestors', the way a control belongs to the form around it.\n const parent = el.parentElement;\n const enclosing = parent?.closest(`[${ATTR_LIST}], [${ATTR_ROW}]`);\n const row = parent?.closest(`[${ATTR_KEY_VALUE}]`);\n const inScope: Record<string, string | null | undefined> = {\n list: enclosing?.getAttribute(ATTR_LIST),\n row: enclosing?.hasAttribute(ATTR_LIST)\n ? null\n : enclosing?.getAttribute(ATTR_ROW),\n // Only a row of that scope: a key from outside it names a row of some\n // other list.\n key:\n row && enclosing?.contains(row) ? row.getAttribute(ATTR_KEY_VALUE) : null,\n cell: el.getAttribute(ATTR_CELL),\n };\n const fill = (name: string) => {\n const value = inScope[name];\n if (fields[name] === undefined && value !== null && value !== undefined) {\n fields[name] = value;\n }\n };\n\n const operation = OPERATIONS[action];\n if (!operation) {\n // A declared action names one scope or the other, never both.\n if (fields.list === undefined && fields.row === undefined) {\n fill(\"list\");\n fill(\"row\");\n }\n fill(\"key\");\n fill(\"cell\");\n return true;\n }\n\n for (const name of operation.fills) fill(name);\n // Gathered from the nearest element holding a cell to read, starting at\n // whatever dispatched: a form holds its own, and a button in a new row's\n // <tr> reads the row around it. Never past the live row the element sits\n // in, nor out into the scope, whose cells are other rows'. Nothing to read\n // is nothing to send, so values stays missing.\n if (fields.values === undefined && operation.requires.includes(\"values\")) {\n const bound = row && enclosing?.contains(row) ? row : null;\n let holder: Element | null = el;\n while (holder && holder !== enclosing && !hasReadableCell(holder)) {\n holder = holder === bound ? null : holder.parentElement;\n }\n if (holder && holder !== enclosing) fields.values = gatherValues(holder);\n }\n const missing = operation.requires.filter((f) => fields[f] === undefined);\n if (missing.length > 0) {\n console.error(\n `lb-hub: '${action}' from <${el.localName}> has no ` +\n `${missing.join(\", \")} in scope, not sent`,\n el,\n );\n return false;\n }\n return true;\n}\n\n/** Dispatch the bare action an element carries, for the hub to scope. */\nfunction dispatchAction(el: Element): void {\n const detail = { action: el.getAttribute(ATTR_ACTION)! } as HubRequest;\n el.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));\n}\n\n/**\n * The value a cell is currently showing, for gathering rather than landing.\n *\n * A cell is always either a native form control itself (a bare\n * `<input lb-cell>`) or a widget that wraps one (`<lb-input>`, `<lb-select>`)\n * — that wrapping is structural, not incidental, which is what lets this be\n * one rule instead of one per widget type. The controls are the ones a value\n * lands on, so what is read back is what landed.\n */\nfunction controlValue(el: Element): string | undefined {\n const control = isValueControl(el)\n ? el\n : [...el.querySelectorAll(\"input, select, textarea\")].find(isValueControl);\n return control?.value;\n}\n\n/** Whether gathering from an element would read anything, reporting nothing. */\nfunction hasReadableCell(el: Element): boolean {\n return [...el.querySelectorAll(`[${ATTR_CELL}]`)].some(\n (cell) => controlValue(cell) !== undefined,\n );\n}\n\n/**\n * Every lb-cell inside an element, gathered into one values map. Shared by\n * lb-row-insert and lb-row-update — both submit a batch of cells, and differ\n * only in whether a key comes with them.\n */\nfunction gatherValues(el: Element): Record<string, string> {\n const values: Record<string, string> = {};\n for (const cell of el.querySelectorAll(`[${ATTR_CELL}]`)) {\n const name = cell.getAttribute(ATTR_CELL)!;\n const value = controlValue(cell);\n if (value === undefined) {\n console.warn(\n `lb-hub: lb-cell '${name}' has no input, select or textarea to ` +\n `read, ignoring it`,\n cell,\n );\n continue;\n }\n values[name] = value;\n }\n return values;\n}\n\n/**\n * A path names a page host, and nothing finer. The bare path resolves to\n * `index`, the same convention a static file server follows — an\n * application names its landing page that way rather than the hub singling\n * one out.\n */\nfunction pageNameFor(path: string): string {\n const name = path.replace(/^\\/+|\\/+$/g, \"\");\n return name === \"\" ? \"index\" : name;\n}\n\n/**\n * The `<template>` a page's markup ships in. The builder guarantees one per\n * page name (build/locations.ts refuses two page files sharing a stub), so\n * a second match here means the document was assembled by something else,\n * and is worth saying out loud rather than resolving by document order.\n */\nfunction pageHostFor(page: string): HTMLTemplateElement | null {\n const hosts: HTMLTemplateElement[] = [];\n for (const el of document.querySelectorAll(`template[${ATTR_PAGE}]`)) {\n if (el.getAttribute(ATTR_PAGE) === page) {\n hosts.push(el as HTMLTemplateElement);\n }\n }\n if (hosts.length === 0) {\n console.error(`lb-hub: no page host for '${page}'`);\n return null;\n }\n if (hosts.length > 1) {\n console.error(\n `lb-hub: ${hosts.length} page hosts carry ${ATTR_PAGE}=\"${page}\", ` +\n `using the first`,\n );\n }\n return hosts[0]!;\n}\n\n/**\n * Fetch one round trip against the client-side deadline. Throws — rather\n * than swallowing — on a non-ok response, a network failure, or a timeout,\n * so a caller that needs to know the request failed (as opposed to one that\n * only ever wants the freshest data, like navigate()) can tell the two\n * apart from a genuinely empty result.\n */\nasync function fetchData(page: string, request?: HubRequest): Promise<HubData> {\n const url = `${LB_ENDPOINT}?page=${encodeURIComponent(page)}`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), LB_REQUEST_TIMEOUT_MS);\n try {\n // An empty body is how the hub asks for the page's whole query set.\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(request ?? {}),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new Error(`lb-hub: server responded ${res.status} for ${url}`);\n }\n return (await res.json()) as HubData;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * The hub is a singleton outside <main>. It survives every page change,\n * so it needs no id to disambiguate it.\n */\nclass LbHub extends HTMLElement {\n private main!: HTMLElement;\n\n /**\n * The page currently in <main>. A page declares its own requests, so a request\n * has to say which page it came from. The page rides on the URL rather\n * than in the request, leaving the operation set closed.\n */\n private page = \"\";\n\n connectedCallback() {\n this.main = this.querySelector(\"main\")!;\n\n // Scoped in the capture phase, on the way down, so every ancestor that\n // sees the request on its way back up sees it whole. A request that\n // cannot be sent is stopped here, before anything sees it at all.\n this.addEventListener(\n LB_EVENT_NAME,\n (e: Event) => {\n const origin = e.target as Element;\n const request = (e as CustomEvent<HubRequest>).detail;\n if (!scope(request, origin)) e.stopPropagation();\n },\n { capture: true },\n );\n\n this.addEventListener(LB_EVENT_NAME, (e: Event) => {\n const request = (e as CustomEvent<HubRequest>).detail;\n // The element that dispatched the request, not e.currentTarget (the\n // hub itself, since the event bubbles) — pending/error state belongs\n // at the origin so a widget can watch its own attributes.\n const origin = e.target as Element | null;\n origin?.removeAttribute(ATTR_ERROR);\n origin?.setAttribute(ATTR_PENDING, \"\");\n void fetchData(this.page, request)\n .then((data) => applyData(this, data))\n .catch((err) => {\n console.error(`lb-hub: request failed`, err);\n origin?.setAttribute(ATTR_ERROR, \"\");\n })\n .finally(() => origin?.removeAttribute(ATTR_PENDING));\n });\n\n /**\n * A native element carrying an action. The hub dispatches the bare action\n * from that element as the ordinary bubbling event, rather than sending\n * it directly, so it is scoped exactly as a widget's is and an ancestor\n * widget can still stop it and confirm. A native action button and a\n * hand-written widget therefore produce identical events.\n *\n * A widget carrying an action is left alone — it owns its own interaction\n * and decides what counts as performing it, which for a <select> is a\n * change rather than a click. The test is the same lexical one applyData\n * uses: a hyphen in the tag name.\n */\n this.addEventListener(\"click\", (e: MouseEvent) => {\n const el = (e.target as Element | null)?.closest(`[${ATTR_ACTION}]`);\n if (!el || el.localName.includes(\"-\")) return;\n // A form performs its action on submit, whatever was clicked in it.\n if (el instanceof HTMLFormElement) return;\n // A button beside the cells is pressed once they are done. An element\n // holding them has no such moment: a click into one of its own inputs\n // would send the row.\n const action = el.getAttribute(ATTR_ACTION);\n if (\n (action === ACTION_ROW_INSERT || action === ACTION_ROW_UPDATE) &&\n el.querySelector(`[${ATTR_CELL}]`)\n ) {\n console.error(\n `lb-hub: '${action}' on <${el.localName}> holds its own cells, ` +\n `so a click inside it is not sent; put it on a <form> or a button`,\n el,\n );\n return;\n }\n e.preventDefault();\n dispatchAction(el);\n });\n\n // The same delegation for the event a form owns.\n this.addEventListener(\"submit\", (e: SubmitEvent) => {\n const form = e.target as Element | null;\n if (!form || !form.hasAttribute(ATTR_ACTION)) return;\n e.preventDefault();\n dispatchAction(form);\n });\n\n // Only anchors that opted in. Everything else is a real link.\n this.addEventListener(\"click\", (e: MouseEvent) => {\n const link = (e.target as Element | null)?.closest(`a[${ATTR_NAV_LINK}]`);\n if (!link) return;\n e.preventDefault();\n const path = new URL((link as HTMLAnchorElement).href).pathname;\n history.pushState(null, \"\", path);\n void this.navigate(path);\n });\n\n addEventListener(\"popstate\", () => void this.navigate(location.pathname));\n\n void this.navigate(location.pathname);\n }\n\n /**\n * Navigation is the one operation that replaces host DOM. The host is\n * already in the document, so selecting a page is getElementById() and\n * there is no fetching mechanism on the host channel.\n */\n private async navigate(path: string): Promise<void> {\n const page = pageNameFor(path);\n this.landNavigation(path, page);\n const template = pageHostFor(page);\n if (!template) {\n this.reportUnknownPage();\n this.reveal();\n return;\n }\n this.page = page;\n // Insert the page shell before fetching its data, not after: the\n // template is already in the document, so holding it back behind a\n // network round trip only leaves <main> empty for no reason.\n this.main.replaceChildren(template.content.cloneNode(true));\n this.reveal();\n let data: HubData = {};\n try {\n data = await fetchData(page);\n } catch (err) {\n console.error(`lb-hub: failed to load data for '${page}'`, err);\n }\n applyData(this, data);\n }\n\n /**\n * An app that ships `<body hidden>` (see docs/reference/chrome.md) is\n * asking to stay invisible until there is something coherent to show,\n * rather than flash the chrome before `<main>` has real content. This is\n * a no-op for an app that doesn't use that convention.\n */\n private reveal(): void {\n document.body.hidden = false;\n }\n\n /**\n * The hub's own row: where the page is, in the vocabulary a server\n * answer arrives in, so a chrome displays it the way it displays anything\n * — `lb-row=\"lb-navigation\"` on a subtree, `lb-cell` on what shows a\n * value. Landed before the page host is looked up, so a miss has it too;\n * the unknown-page dialog is one consumer among whatever others the\n * chrome writes.\n *\n * The label is the nav's: the text of the first lb-nav-link anchor whose\n * href names this page. Taken from the nav rather than from a click, so a\n * click, a reload and the back button all land the same label — and a\n * path no anchor names lands an empty one, explicitly, so a consumer is\n * never left showing the previous page's.\n *\n * Landed only where a subtree names it. applyData() warns about a query\n * with no scope, and a chrome that displays no navigation has nothing to\n * be warned about.\n */\n private landNavigation(path: string, page: string): void {\n if (!this.querySelector(`[${ATTR_ROW}=\"${NAV_ROW}\"]`)) return;\n let label = \"\";\n for (const a of this.querySelectorAll<HTMLAnchorElement>(\n `a[${ATTR_NAV_LINK}]`,\n )) {\n if (pageNameFor(new URL(a.href).pathname) !== page) continue;\n label = a.textContent?.trim() ?? \"\";\n break;\n }\n applyData(this, {\n [NAV_ROW]: { [NAV_CELL_LABEL]: label, [NAV_CELL_URI]: path },\n });\n }\n\n /**\n * A path that resolves to no page host is discovered client-side, after a\n * successful 200 — every route gets the same document, so there is no\n * HTTP 404 to report (see docs/tutorials/010-pages-and-navigation.md).\n * lb-unknown-page is optional: an application that declares none gets\n * nothing beyond navigate()'s console.error. The builder guarantees the\n * element it's on is a <dialog> (build/assemble.ts), so this can call\n * showModal() without checking the tag here.\n *\n * Only opens it. What the dialog says it gets from landNavigation(),\n * already run, by naming the hub's row in lb-row like any subtree.\n *\n * Scoped to the hub, like every other lookup here, so that one rule holds\n * without exception: what the hub acts on is inside the hub.\n */\n private reportUnknownPage(): void {\n const dialog = this.querySelector(`[${ATTR_UNKNOWN_PAGE}]`);\n if (!dialog) return;\n const modal = dialog as HTMLDialogElement;\n if (!modal.open) modal.showModal();\n }\n}\n\ncustomElements.define(HUB_TAG_NAME, LbHub);\n"]}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* is reserved.
|
|
19
19
|
*/
|
|
20
20
|
import { type Request, type Router } from "express";
|
|
21
|
-
import type { Hub, HubContext } from "./lb-server";
|
|
21
|
+
import type { Hub, HubContext } from "./lb-server.js";
|
|
22
22
|
/**
|
|
23
23
|
* Building the context is the application's job — it is where an
|
|
24
24
|
* authenticated database handle comes from — so it is injected rather than
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-express.d.ts","sourceRoot":"","sources":["../../server/lb-express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAgB,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAS7D,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"lb-express.d.ts","sourceRoot":"","sources":["../../server/lb-express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAgB,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAS7D,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,UAAU,CAAC;AAEtD,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAkFlE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lb-express.js","sourceRoot":"","sources":["../../server/lb-express.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Express adapter — the standard hand-off from an Express router to a\n * Hub built by createHub(). Every Hub application wires this same router,\n * which is why it ships from here rather than being copied into each one.\n *\n * Express itself is a peer dependency: nothing outside this file imports it,\n * so an application that hosts Hub some other way never pays for it.\n *\n * One endpoint, and no second:\n *\n * POST /lb?page=<name> an empty body asks for the page's whole query\n * set; a body carrying an action runs that action\n * and answers with its refresh set\n *\n * The page rides on the query string rather than in the body, which is what\n * lets the operation set stay closed. Every hub call is a POST, so an\n * application's own GET routes never collide with this one and no page name\n * is reserved.\n */\n\nimport express, { type Request, type Router } from \"express\";\nimport {\n ACTION_CELL_CHANGE,\n ACTION_ROW_DELETE,\n ACTION_ROW_INSERT,\n ACTION_ROW_UPDATE,\n LB_ENDPOINT,\n} from \"../core/lb-constants.js\";\nimport { isOperation, type HubRequest } from \"../core/lb-types.js\";\nimport type { Hub, HubContext } from \"./lb-server.js\";\n\n/**\n * Building the context is the application's job — it is where an\n * authenticated database handle comes from — so it is injected rather than\n * assumed. Whatever established identity has already run by the time this\n * router is reached; where that happens is the application's ordering\n * decision, not Hub's.\n */\nexport type ContextFor = (req: Request) => HubContext;\n\nexport function hubRoutes(hub: Hub, contextFor: ContextFor): Router {\n const router = express.Router();\n\n // Mounted here rather than on the app so that Hub's need for a parsed\n // body is stated where the routes that need it are.\n router.use(express.json());\n\n router.post(LB_ENDPOINT, async (req, res) => {\n const page = String(req.query.page ?? \"\");\n const request = req.body as HubRequest | undefined;\n const ctx = contextFor(req);\n\n // No body at all is the page load: the browser is asking for this\n // page's whole query set, which is every request it makes that names no\n // action. A body that carries something but not an action is malformed.\n if (request === undefined || Object.keys(request).length === 0) {\n res.json(await hub.dataForPage(page, ctx));\n return;\n }\n\n if (!request.action) {\n console.warn(`loadbare: request without an action`);\n res.status(400).json({});\n return;\n }\n\n // A thrown error is how application code (a query, an action, a CRUD\n // op) says \"this request is invalid\" or hits an unexpected failure —\n // it resolves to a real response rather than an unhandled rejection, so\n // the browser side has something to catch.\n try {\n // The reserved prefix is the whole of the discriminant: a name so\n // prefixed is one of the four operations, and anything else is a name\n // the page declared. Nothing else tells them apart, here or anywhere.\n if (!isOperation(request)) {\n const { action, list, row, key, cell, value } = request;\n res.json(\n await hub.runAction(\n page,\n action,\n { list, row, key, cell, value },\n ctx,\n ),\n );\n return;\n }\n switch (request.action) {\n case ACTION_CELL_CHANGE: {\n const { list, key, cell, value } = request;\n res.json(\n await hub.runCellChange(page, { list, key, cell, value }, ctx),\n );\n return;\n }\n case ACTION_ROW_DELETE: {\n const { list, key } = request;\n res.json(await hub.runRowDelete(page, { list, key }, ctx));\n return;\n }\n case ACTION_ROW_INSERT: {\n const { list, values } = request;\n res.json(await hub.runRowInsert(page, { list, values }, ctx));\n return;\n }\n case ACTION_ROW_UPDATE: {\n const { list, key, values } = request;\n res.json(await hub.runRowUpdate(page, { list, key, values }, ctx));\n return;\n }\n default:\n console.warn(\n `loadbare: operation '${(request as HubRequest).action}' not implemented`,\n );\n res.status(400).json({});\n }\n } catch (err) {\n console.error(`loadbare: '${request.action}' threw`, err);\n res.status(500).json({});\n }\n });\n\n return router;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"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"}
|