@loadbare/app 0.5.6 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/dist/build/assemble.d.ts +1 -1
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +81 -7
- package/dist/build/assemble.js.map +1 -0
- package/dist/build/cli.d.ts +2 -2
- package/dist/build/cli.js +3 -2
- 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.d.ts.map +1 -1
- package/dist/build/expand.js +20 -32
- 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 +4 -4
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +16 -5
- package/dist/build/locations.js.map +1 -0
- package/dist/build/origins.d.ts +0 -13
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +33 -8
- 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 +7 -3
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +14 -7
- 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.d.ts +15 -13
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +104 -53
- package/dist/core/lb-constants.js.map +1 -0
- package/dist/core/lb-types.d.ts +103 -62
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +12 -3
- package/dist/core/lb-types.js.map +1 -0
- package/dist/hub/lb-apply.d.ts +28 -4
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +227 -40
- 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 +165 -110
- package/dist/hub/lb-hub.browser.js.map +1 -0
- package/dist/server/lb-express.d.ts +8 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +46 -33
- package/dist/server/lb-express.js.map +1 -0
- package/dist/server/lb-server.d.ts +67 -45
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +56 -18
- package/dist/server/lb-server.js.map +1 -0
- package/docs/TECHREF-1.0.md +1107 -0
- package/docs/analysis-accidental-complexity.md +149 -0
- package/docs/reference/builder.md +4 -4
- package/docs/reference/chrome.md +10 -9
- package/docs/reference/custom-elements.md +54 -46
- package/docs/reference/data-binding.md +179 -97
- package/docs/reference/overview.md +1 -1
- package/docs/reference/page-files.md +64 -49
- package/docs/reference/server.md +25 -6
- package/docs/reference/widgets.md +22 -30
- package/docs/roadmap.md +68 -22
- package/docs/testing.md +47 -17
- package/docs/theory.md +116 -3
- package/docs/tutorials/010-pages-and-navigation.md +8 -8
- package/docs/tutorials/040-displaying-data.md +9 -9
- package/docs/tutorials/050-actions.md +5 -5
- package/docs/tutorials/060-custom-element-code.md +1 -1
- package/docs/tutorials/065-conditional-rendering.md +4 -4
- package/docs/tutorials/070-displaying-a-list.md +24 -47
- package/docs/tutorials/072-inserting-into-a-list.md +18 -15
- package/docs/tutorials/074-deleting-from-a-list.md +15 -17
- package/docs/tutorials/076-updating-a-list-item.md +20 -22
- package/docs/tutorials/080-widget-requests.md +22 -35
- package/docs/tutorials/090-using-widget-libraries.md +1 -1
- package/package.json +2 -3
- package/dist/hub/lb-rows.d.ts +0 -18
- package/dist/hub/lb-rows.d.ts.map +0 -1
- package/dist/hub/lb-rows.js +0 -106
- 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 -58
- 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 -250
- 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/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 -153
- 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 -238
- package/dist/tests/lb-rows.test.d.ts +0 -12
- package/dist/tests/lb-rows.test.d.ts.map +0 -1
- package/dist/tests/lb-rows.test.js +0 -336
- 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 -495
- 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 -369
- 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 -98
- 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/core/lb-types.d.ts
CHANGED
|
@@ -1,88 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { ACTION_CELL_CHANGE, ACTION_ROW_DELETE, ACTION_ROW_INSERT, ACTION_ROW_UPDATE } from "./lb-constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* One row: its columns, by name.
|
|
4
|
+
*
|
|
5
|
+
* A value is whatever the server serialized. The hub knows nothing about its
|
|
6
|
+
* type and enforces nothing: it hands each value to the browser untouched,
|
|
7
|
+
* and the browser renders it as it sees fit. What a number, a date or a null
|
|
8
|
+
* should look like is the application's decision, made in the query.
|
|
9
|
+
*/
|
|
10
|
+
export type Row = Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Part of a list — see docs/reference/server.md, "Queries".
|
|
13
|
+
*
|
|
14
|
+
* Rows named here arrive or are updated, keys in `drop` are gone, and
|
|
15
|
+
* anything unnamed is left alone: its contents, and its place in whatever
|
|
16
|
+
* order the widget is keeping. Add and remove are one result rather than two
|
|
17
|
+
* because the interesting cases are both at once — a row whose sort key
|
|
18
|
+
* changed has to move, a swap is one out and one in — and two messages would
|
|
19
|
+
* paint the intermediate state.
|
|
20
|
+
*/
|
|
21
|
+
export interface Patch {
|
|
22
|
+
rows?: Row[];
|
|
23
|
+
drop?: unknown[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What a list answers with.
|
|
27
|
+
*
|
|
28
|
+
* An array is the entire set and therefore also the order: a row whose key
|
|
29
|
+
* is not in it is gone. A Patch disturbs only what it names.
|
|
30
|
+
*
|
|
31
|
+
* `Array.isArray` tells the two apart, which is why no column name has to be
|
|
32
|
+
* reserved to carry a discriminant. A list answering with an object is a
|
|
33
|
+
* patch, because the declaration already said this name answers with rows.
|
|
34
|
+
*/
|
|
35
|
+
export type ListResult = Row[] | Patch;
|
|
36
|
+
export declare function isPatch(result: ListResult): result is Patch;
|
|
37
|
+
/**
|
|
38
|
+
* What any one name answers with. Which of the two it is comes from the
|
|
39
|
+
* query's own declaration, never from inspecting the value.
|
|
40
|
+
*/
|
|
41
|
+
export type HubResult = Row | ListResult;
|
|
42
|
+
/**
|
|
43
|
+
* Every response on the data channel is the same shape, whether it is a cold
|
|
44
|
+
* start or the narrowest refresh. The hub does not know which case it is in.
|
|
45
|
+
*
|
|
46
|
+
* A name rides as an object key and is labelled by nothing, which is why the
|
|
47
|
+
* response direction never needed a noun for the addressable thing.
|
|
48
|
+
*/
|
|
49
|
+
export type HubData = Record<string, HubResult>;
|
|
50
|
+
/**
|
|
51
|
+
* The four operations — docs/reference/data-binding.md. The set is closed.
|
|
52
|
+
*
|
|
53
|
+
* `action` carries the value of `lb-action` verbatim, so the attribute, the
|
|
54
|
+
* wire field and the CRUD key are one vocabulary with no translation step.
|
|
55
|
+
* The scope field is `list` here because all four are list operations: each
|
|
56
|
+
* needs a key, and a key exists only on a live row inside a list.
|
|
57
|
+
*/
|
|
58
|
+
export type LbOperation = {
|
|
59
|
+
action: typeof ACTION_ROW_INSERT;
|
|
60
|
+
list: string;
|
|
11
61
|
values: Record<string, string>;
|
|
12
62
|
} | {
|
|
13
|
-
|
|
14
|
-
|
|
63
|
+
action: typeof ACTION_ROW_DELETE;
|
|
64
|
+
list: string;
|
|
65
|
+
key: string;
|
|
66
|
+
} | {
|
|
67
|
+
action: typeof ACTION_ROW_UPDATE;
|
|
68
|
+
list: string;
|
|
15
69
|
key: string;
|
|
16
70
|
values: Record<string, string>;
|
|
17
71
|
} | {
|
|
18
|
-
|
|
19
|
-
|
|
72
|
+
action: typeof ACTION_CELL_CHANGE;
|
|
73
|
+
list: string;
|
|
20
74
|
key: string;
|
|
21
|
-
|
|
75
|
+
cell: string;
|
|
76
|
+
value: string;
|
|
77
|
+
};
|
|
22
78
|
/**
|
|
23
|
-
* Everything that is not
|
|
79
|
+
* Everything that is not one of the four.
|
|
24
80
|
*
|
|
25
81
|
* The name is the application's and is looked up in the page's declared
|
|
26
82
|
* actions. What keeps this from being an RPC endpoint is that the name must
|
|
27
|
-
* already appear in the page's
|
|
83
|
+
* already appear in the page's requests: the browser cannot reach anything the
|
|
28
84
|
* page has not published, and there is no argument list — only where the
|
|
29
85
|
* interaction happened and, where a control has one, its value.
|
|
30
86
|
*
|
|
31
|
-
*
|
|
87
|
+
* The scope field is `list` or `row`, whichever attribute scoped the element
|
|
88
|
+
* the interaction came from, so an action fired from a single-row scope says
|
|
89
|
+
* so. A button carries no value, so the server computes the whole of the new
|
|
32
90
|
* state and the browser never displays a number it has not confirmed. A
|
|
33
91
|
* `<select>` carries one, because the choice is the interaction.
|
|
34
92
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
93
|
+
export interface DeclaredAction {
|
|
94
|
+
action: string;
|
|
95
|
+
list?: string;
|
|
96
|
+
row?: string;
|
|
39
97
|
key?: string;
|
|
40
98
|
cell?: string;
|
|
41
99
|
value?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export type QueryResult = Record<string, string>;
|
|
45
|
-
/**
|
|
46
|
-
* A query that returns many tuples — see docs/reference/server.md, "Queries".
|
|
47
|
-
*
|
|
48
|
-
* Two results, because a list widget must be told the difference. `rows` is
|
|
49
|
-
* the entire set and therefore also the order; `patch` names only what
|
|
50
|
-
* changed and leaves everything it does not name alone. Add and remove are
|
|
51
|
-
* one result rather than two because the interesting cases are both at once
|
|
52
|
-
* — a row whose sort key changed has to move, a swap is one out and one in —
|
|
53
|
-
* and two messages would paint the intermediate state.
|
|
54
|
-
*
|
|
55
|
-
* `op` is the same discriminant word the request union uses, so one
|
|
56
|
-
* vocabulary runs in both directions. It is therefore a reserved cell name:
|
|
57
|
-
* a tuple with a cell called `op` whose value is `rows` or `patch` would be
|
|
58
|
-
* read as a projection.
|
|
59
|
-
*/
|
|
60
|
-
export type Projection = {
|
|
61
|
-
op: "rows";
|
|
62
|
-
rows: QueryResult[];
|
|
63
|
-
} | {
|
|
64
|
-
op: "patch";
|
|
65
|
-
rows?: QueryResult[];
|
|
66
|
-
drop?: string[];
|
|
67
|
-
};
|
|
68
|
-
/** A tuple is the sugar and the common case; a projection says so. */
|
|
69
|
-
export type HubResult = QueryResult | Projection;
|
|
70
|
-
export declare function isProjection(result: HubResult): result is Projection;
|
|
100
|
+
}
|
|
101
|
+
export type HubRequest = LbOperation | DeclaredAction;
|
|
71
102
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* it is in.
|
|
103
|
+
* The reserved prefix is the whole of the discriminant. Nothing else tells
|
|
104
|
+
* an operation from a declared action, on the wire or anywhere else.
|
|
75
105
|
*/
|
|
76
|
-
export
|
|
106
|
+
export declare function isOperation(request: HubRequest): request is LbOperation;
|
|
77
107
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
108
|
+
* The two hooks a list widget may implement, both optional.
|
|
109
|
+
*
|
|
110
|
+
* The hub reconciles every list scope itself, so a widget supplies placement
|
|
111
|
+
* and scaffolding and nothing else. A scope that implements neither is a
|
|
112
|
+
* plain element with a row template inside it, which is why there is no
|
|
113
|
+
* repeater widget.
|
|
81
114
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
115
|
+
* They are methods on an element the hub does not own, so they carry the
|
|
116
|
+
* `lb` prefix that Loadbare reserves for exactly that — see
|
|
117
|
+
* docs/reference/custom-elements.md.
|
|
84
118
|
*/
|
|
85
|
-
export interface
|
|
86
|
-
|
|
119
|
+
export interface ListHost {
|
|
120
|
+
/**
|
|
121
|
+
* Where a row belongs, called with the row detached on its first
|
|
122
|
+
* appearance. Without it a row lands immediately before the template, so
|
|
123
|
+
* rows accumulate in the order they arrive.
|
|
124
|
+
*/
|
|
125
|
+
lbPlaceRow?(el: Element, row: Row, template: HTMLTemplateElement): void;
|
|
126
|
+
/** Called once after a whole result has landed, for derived scaffolding. */
|
|
127
|
+
lbRowsLanded?(): void;
|
|
87
128
|
}
|
|
88
129
|
//# sourceMappingURL=lb-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAElB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;GAOG;AACH,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,WAAW,KAAK;IACpB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,GAAG,EAAE,GAAG,KAAK,CAAC;AAEvC,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,IAAI,KAAK,CAE3D;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,UAAU,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GACnB;IACE,MAAM,EAAE,OAAO,iBAAiB,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,GACD;IAAE,MAAM,EAAE,OAAO,iBAAiB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/D;IACE,MAAM,EAAE,OAAO,iBAAiB,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,GACD;IACE,MAAM,EAAE,OAAO,kBAAkB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,cAAc,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,IAAI,WAAW,CAEvE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;OAIG;IACH,UAAU,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAExE,4EAA4E;IAC5E,YAAY,CAAC,IAAI,IAAI,CAAC;CACvB"}
|
package/dist/core/lb-types.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
// The wire vocabulary. Shared by browser and server.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return
|
|
3
|
+
import { LB_RESERVED_PREFIX, } from "./lb-constants.js";
|
|
4
|
+
export function isPatch(result) {
|
|
5
|
+
return !Array.isArray(result);
|
|
5
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* The reserved prefix is the whole of the discriminant. Nothing else tells
|
|
9
|
+
* an operation from a declared action, on the wire or anywhere else.
|
|
10
|
+
*/
|
|
11
|
+
export function isOperation(request) {
|
|
12
|
+
return request.action.startsWith(LB_RESERVED_PREFIX);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=lb-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lb-types.js","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,qDAAqD;AAErD,OAAO,EAKL,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAuC3B,MAAM,UAAU,OAAO,CAAC,MAAkB;IACxC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC;AAwED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAmB;IAC7C,OAAO,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;AACvD,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\n// The wire vocabulary. Shared by browser and server.\n\nimport {\n ACTION_CELL_CHANGE,\n ACTION_ROW_DELETE,\n ACTION_ROW_INSERT,\n ACTION_ROW_UPDATE,\n LB_RESERVED_PREFIX,\n} from \"./lb-constants.js\";\n\n/**\n * One row: its columns, by name.\n *\n * A value is whatever the server serialized. The hub knows nothing about its\n * type and enforces nothing: it hands each value to the browser untouched,\n * and the browser renders it as it sees fit. What a number, a date or a null\n * should look like is the application's decision, made in the query.\n */\nexport type Row = Record<string, unknown>;\n\n/**\n * Part of a list — see docs/reference/server.md, \"Queries\".\n *\n * Rows named here arrive or are updated, keys in `drop` are gone, and\n * anything unnamed is left alone: its contents, and its place in whatever\n * order the widget is keeping. Add and remove are one result rather than two\n * because the interesting cases are both at once — a row whose sort key\n * changed has to move, a swap is one out and one in — and two messages would\n * paint the intermediate state.\n */\nexport interface Patch {\n rows?: Row[];\n drop?: unknown[];\n}\n\n/**\n * What a list answers with.\n *\n * An array is the entire set and therefore also the order: a row whose key\n * is not in it is gone. A Patch disturbs only what it names.\n *\n * `Array.isArray` tells the two apart, which is why no column name has to be\n * reserved to carry a discriminant. A list answering with an object is a\n * patch, because the declaration already said this name answers with rows.\n */\nexport type ListResult = Row[] | Patch;\n\nexport function isPatch(result: ListResult): result is Patch {\n return !Array.isArray(result);\n}\n\n/**\n * What any one name answers with. Which of the two it is comes from the\n * query's own declaration, never from inspecting the value.\n */\nexport type HubResult = Row | ListResult;\n\n/**\n * Every response on the data channel is the same shape, whether it is a cold\n * start or the narrowest refresh. The hub does not know which case it is in.\n *\n * A name rides as an object key and is labelled by nothing, which is why the\n * response direction never needed a noun for the addressable thing.\n */\nexport type HubData = Record<string, HubResult>;\n\n/**\n * The four operations — docs/reference/data-binding.md. The set is closed.\n *\n * `action` carries the value of `lb-action` verbatim, so the attribute, the\n * wire field and the CRUD key are one vocabulary with no translation step.\n * The scope field is `list` here because all four are list operations: each\n * needs a key, and a key exists only on a live row inside a list.\n */\nexport type LbOperation =\n | {\n action: typeof ACTION_ROW_INSERT;\n list: string;\n values: Record<string, string>;\n }\n | { action: typeof ACTION_ROW_DELETE; list: string; key: string }\n | {\n action: typeof ACTION_ROW_UPDATE;\n list: string;\n key: string;\n values: Record<string, string>;\n }\n | {\n action: typeof ACTION_CELL_CHANGE;\n list: string;\n key: string;\n cell: string;\n value: string;\n };\n\n/**\n * Everything that is not one of the four.\n *\n * The name is the application's and is looked up in the page's declared\n * actions. What keeps this from being an RPC endpoint is that the name must\n * already appear in the page's requests: the browser cannot reach anything the\n * page has not published, and there is no argument list — only where the\n * interaction happened and, where a control has one, its value.\n *\n * The scope field is `list` or `row`, whichever attribute scoped the element\n * the interaction came from, so an action fired from a single-row scope says\n * so. A button carries no value, so the server computes the whole of the new\n * state and the browser never displays a number it has not confirmed. A\n * `<select>` carries one, because the choice is the interaction.\n */\nexport interface DeclaredAction {\n action: string;\n list?: string;\n row?: string;\n key?: string;\n cell?: string;\n value?: string;\n}\n\nexport type HubRequest = LbOperation | DeclaredAction;\n\n/**\n * The reserved prefix is the whole of the discriminant. Nothing else tells\n * an operation from a declared action, on the wire or anywhere else.\n */\nexport function isOperation(request: HubRequest): request is LbOperation {\n return request.action.startsWith(LB_RESERVED_PREFIX);\n}\n\n/**\n * The two hooks a list widget may implement, both optional.\n *\n * The hub reconciles every list scope itself, so a widget supplies placement\n * and scaffolding and nothing else. A scope that implements neither is a\n * plain element with a row template inside it, which is why there is no\n * repeater widget.\n *\n * They are methods on an element the hub does not own, so they carry the\n * `lb` prefix that Loadbare reserves for exactly that — see\n * docs/reference/custom-elements.md.\n */\nexport interface ListHost {\n /**\n * Where a row belongs, called with the row detached on its first\n * appearance. Without it a row lands immediately before the template, so\n * rows accumulate in the order they arrive.\n */\n lbPlaceRow?(el: Element, row: Row, template: HTMLTemplateElement): void;\n\n /** Called once after a whole result has landed, for derived scaffolding. */\n lbRowsLanded?(): void;\n}\n"]}
|
package/dist/hub/lb-apply.d.ts
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type HubData, type ListResult, type Row } from "../core/lb-types.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* A form control whose state is its `value`: a `<select>`, a `<textarea>`,
|
|
4
|
+
* or an `<input>` of any type but checkbox, radio and file. Landing and
|
|
5
|
+
* gathering draw the same line, so a value read back from a form is the one
|
|
6
|
+
* that landed there.
|
|
7
|
+
*
|
|
8
|
+
* Told apart by tag name and type rather than by class, so it holds for an
|
|
9
|
+
* element from any document.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isValueControl(el: Element): el is HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
12
|
+
/**
|
|
13
|
+
* Fill one scope from one row.
|
|
4
14
|
*
|
|
5
15
|
* The root counts as a cell if it carries one. A `<tr>` holds its cells in
|
|
6
16
|
* `<td>` children, but `<option>`'s content model is text, so an option row
|
|
7
17
|
* has to be the cell it displays. Requiring a wrapper there would require an
|
|
8
18
|
* element HTML does not allow.
|
|
9
19
|
*/
|
|
10
|
-
export declare function
|
|
11
|
-
/**
|
|
20
|
+
export declare function applyRow(root: Element, row: Row): void;
|
|
21
|
+
/**
|
|
22
|
+
* Land a list result in a list scope.
|
|
23
|
+
*
|
|
24
|
+
* An array is the whole set, so it decides membership and order: every row is
|
|
25
|
+
* placed in the order given, and a row whose key did not arrive is gone. A
|
|
26
|
+
* patch disturbs only what it names — a row it did not mention keeps its
|
|
27
|
+
* contents and its position.
|
|
28
|
+
*
|
|
29
|
+
* A widget that carries `lbPlaceRow` decides where a row goes, because only
|
|
30
|
+
* it knows whether it sorts or groups. Without it a row lands immediately
|
|
31
|
+
* before the template, so rows accumulate in the order they arrive and the
|
|
32
|
+
* template stays put as the insertion marker.
|
|
33
|
+
*/
|
|
34
|
+
export declare function applyList(scope: Element, result: ListResult): void;
|
|
35
|
+
/** Land a whole response. Every result arrives through here. */
|
|
12
36
|
export declare function applyData(root: ParentNode, data: HubData): void;
|
|
13
37
|
//# sourceMappingURL=lb-apply.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"AA4BA,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,UAAU,EACf,KAAK,GAAG,EACT,MAAM,qBAAqB,CAAC;AAK7B;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,OAAO,GACV,EAAE,IAAI,gBAAgB,GAAG,iBAAiB,GAAG,mBAAmB,CAMlE;AAgED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAMtD;AA2CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAgElE;AAED,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAkC/D"}
|
package/dist/hub/lb-apply.js
CHANGED
|
@@ -2,76 +2,263 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Landing data on a live host — see docs/reference/data-binding.md.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* One containment ladder, and one function per rung. `applyData` lands a
|
|
6
|
+
* whole response, `applyList` lands one list result, `applyRow` fills one
|
|
7
|
+
* scope from one row. Filling a live row and filling a single-row scope are
|
|
8
|
+
* the same operation, and the only difference is how much of the page the
|
|
9
|
+
* root covers.
|
|
10
|
+
*
|
|
11
|
+
* The row machinery used to live in its own file because every list widget
|
|
12
|
+
* imported and called it. The hub reconciles a list scope itself now, so a
|
|
13
|
+
* widget supplies placement and scaffolding through two optional hooks and
|
|
14
|
+
* never sees a whole result. Cloning the template, matching a row to the
|
|
15
|
+
* element already showing it, and filling that element are the same in every
|
|
16
|
+
* list, so no widget can get them wrong.
|
|
17
|
+
*/
|
|
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"];
|
|
22
|
+
/**
|
|
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.
|
|
11
30
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
}
|
|
14
37
|
/**
|
|
15
|
-
* A cell lands one of
|
|
38
|
+
* A cell lands one of three ways. A custom element owns whatever control it
|
|
16
39
|
* wraps, so it receives the value as an attribute and renders it itself.
|
|
17
40
|
* Because the browser runs `attributeChangedCallback` for attributes already
|
|
18
41
|
* present when a widget upgrades, that is the same operation whether the host
|
|
19
42
|
* was inserted a microsecond ago or an hour ago.
|
|
20
43
|
*
|
|
21
|
-
* A
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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".
|
|
24
50
|
*/
|
|
25
51
|
function land(el, value) {
|
|
26
|
-
|
|
52
|
+
// Untouched, whatever the server sent: the browser decides what a
|
|
53
|
+
// non-string looks like. See Row in core/lb-types.ts.
|
|
54
|
+
if (el.localName.includes("-")) {
|
|
27
55
|
el.setAttribute(ATTR_VALUE, value);
|
|
28
|
-
|
|
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 {
|
|
29
65
|
el.textContent = value;
|
|
66
|
+
el.setAttribute(ATTR_VALUE, value);
|
|
67
|
+
}
|
|
30
68
|
}
|
|
31
69
|
/**
|
|
32
|
-
*
|
|
70
|
+
* Whether an element under `root` belongs to root's scope, rather than to a
|
|
71
|
+
* scope nested inside it.
|
|
72
|
+
*
|
|
73
|
+
* Both scope attributes scope their DOM children, and a nested one of either
|
|
74
|
+
* kind begins a new scope (docs/reference/data-binding.md). So a descendant
|
|
75
|
+
* is in root's scope unless something between it and root — the element
|
|
76
|
+
* itself included — carries one. Root's own attribute does not count: root is
|
|
77
|
+
* the scope being filled, whatever it carries.
|
|
78
|
+
*/
|
|
79
|
+
function inScope(root, el) {
|
|
80
|
+
for (let node = el; node && node !== root;) {
|
|
81
|
+
if (node.hasAttribute(ATTR_LIST) || node.hasAttribute(ATTR_ROW)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
node = node.parentElement;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
/** Root if it matches, then every descendant in root's scope that does. */
|
|
89
|
+
function within(root, selector) {
|
|
90
|
+
const found = root.matches(selector) ? [root] : [];
|
|
91
|
+
for (const el of root.querySelectorAll(selector)) {
|
|
92
|
+
if (inScope(root, el))
|
|
93
|
+
found.push(el);
|
|
94
|
+
}
|
|
95
|
+
return found;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Fill one scope from one row.
|
|
33
99
|
*
|
|
34
100
|
* The root counts as a cell if it carries one. A `<tr>` holds its cells in
|
|
35
101
|
* `<td>` children, but `<option>`'s content model is text, so an option row
|
|
36
102
|
* has to be the cell it displays. Requiring a wrapper there would require an
|
|
37
103
|
* element HTML does not allow.
|
|
38
104
|
*/
|
|
39
|
-
export function
|
|
40
|
-
for (const [
|
|
41
|
-
const
|
|
42
|
-
if (root.matches(selector))
|
|
43
|
-
land(root, value);
|
|
44
|
-
for (const el of root.querySelectorAll(selector))
|
|
105
|
+
export function applyRow(root, row) {
|
|
106
|
+
for (const [column, value] of Object.entries(row)) {
|
|
107
|
+
for (const el of within(root, `[${ATTR_CELL}="${column}"]`)) {
|
|
45
108
|
land(el, value);
|
|
109
|
+
}
|
|
46
110
|
}
|
|
47
111
|
}
|
|
48
112
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
113
|
+
* The row template: the one the developer wrote inside this list scope, or
|
|
114
|
+
* null when this scope shows nothing. A template inside a nested scope is
|
|
115
|
+
* that scope's.
|
|
116
|
+
*
|
|
117
|
+
* A scope with no template is bound to the list without displaying it.
|
|
118
|
+
* That is not a mistake, so it is silent. A template that does not name its
|
|
119
|
+
* key column is a mistake, and says so.
|
|
52
120
|
*/
|
|
53
|
-
function
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
121
|
+
function templateIn(scope) {
|
|
122
|
+
const template = within(scope, "template")[0];
|
|
123
|
+
if (!template)
|
|
124
|
+
return null;
|
|
125
|
+
if (!template.getAttribute(ATTR_KEY)) {
|
|
126
|
+
console.error(`lb-hub: the <template> in <${scope.localName}> has no ${ATTR_KEY} ` +
|
|
127
|
+
`naming the column that identifies a row`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return template;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The rows already showing, by the key value each carries. Only a live row
|
|
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.
|
|
139
|
+
*/
|
|
140
|
+
function showing(scope) {
|
|
141
|
+
const rows = new Map();
|
|
142
|
+
for (const el of scope.querySelectorAll(`[${ATTR_KEY_VALUE}]`)) {
|
|
143
|
+
if (inScope(scope, el.parentElement)) {
|
|
144
|
+
rows.set(el.getAttribute(ATTR_KEY_VALUE), el);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return rows;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Land a list result in a list scope.
|
|
151
|
+
*
|
|
152
|
+
* An array is the whole set, so it decides membership and order: every row is
|
|
153
|
+
* placed in the order given, and a row whose key did not arrive is gone. A
|
|
154
|
+
* patch disturbs only what it names — a row it did not mention keeps its
|
|
155
|
+
* contents and its position.
|
|
156
|
+
*
|
|
157
|
+
* A widget that carries `lbPlaceRow` decides where a row goes, because only
|
|
158
|
+
* it knows whether it sorts or groups. Without it a row lands immediately
|
|
159
|
+
* before the template, so rows accumulate in the order they arrive and the
|
|
160
|
+
* template stays put as the insertion marker.
|
|
161
|
+
*/
|
|
162
|
+
export function applyList(scope, result) {
|
|
163
|
+
const template = templateIn(scope);
|
|
164
|
+
if (!template)
|
|
58
165
|
return;
|
|
166
|
+
const keyColumn = template.getAttribute(ATTR_KEY);
|
|
167
|
+
const shown = showing(scope);
|
|
168
|
+
const host = scope;
|
|
169
|
+
const whole = !isPatch(result);
|
|
170
|
+
const place = (el, row) => {
|
|
171
|
+
if (host.lbPlaceRow)
|
|
172
|
+
host.lbPlaceRow(el, row, template);
|
|
173
|
+
else
|
|
174
|
+
template.parentElement.insertBefore(el, template);
|
|
175
|
+
};
|
|
176
|
+
const upsert = (row) => {
|
|
177
|
+
if (row[keyColumn] === undefined) {
|
|
178
|
+
console.error(`lb-hub: a row for <${scope.localName}> has no '${keyColumn}' column`);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
// The key is stored on the row as an attribute and read back from
|
|
182
|
+
// there, so it is compared as the string the attribute holds.
|
|
183
|
+
const key = String(row[keyColumn]);
|
|
184
|
+
let el = shown.get(key);
|
|
185
|
+
const fresh = el === undefined;
|
|
186
|
+
if (!el) {
|
|
187
|
+
el = template.content.firstElementChild.cloneNode(true);
|
|
188
|
+
el.setAttribute(ATTR_KEY_VALUE, key);
|
|
189
|
+
shown.set(key, el);
|
|
190
|
+
}
|
|
191
|
+
// Fill before insertion. The attributes are already there when the row
|
|
192
|
+
// upgrades, which is the same thing that makes hydration and refresh one
|
|
193
|
+
// operation everywhere else.
|
|
194
|
+
applyRow(el, row);
|
|
195
|
+
if (fresh || whole)
|
|
196
|
+
place(el, row);
|
|
197
|
+
return key;
|
|
198
|
+
};
|
|
199
|
+
if (Array.isArray(result)) {
|
|
200
|
+
const arrived = new Set();
|
|
201
|
+
for (const row of result) {
|
|
202
|
+
const key = upsert(row);
|
|
203
|
+
if (key !== null)
|
|
204
|
+
arrived.add(key);
|
|
205
|
+
}
|
|
206
|
+
for (const [key, el] of shown)
|
|
207
|
+
if (!arrived.has(key))
|
|
208
|
+
el.remove();
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
for (const row of result.rows ?? [])
|
|
212
|
+
upsert(row);
|
|
213
|
+
for (const key of result.drop ?? [])
|
|
214
|
+
shown.get(String(key))?.remove();
|
|
59
215
|
}
|
|
60
|
-
|
|
216
|
+
// How many rows are showing, counted from the DOM rather than from either
|
|
217
|
+
// branch above, so a whole set and a patch report the same fact the same
|
|
218
|
+
// way.
|
|
219
|
+
//
|
|
220
|
+
// It is stamped here because only this function knows the count: it is the
|
|
221
|
+
// one conditional a page cannot be sent, since the server answers with rows
|
|
222
|
+
// and says nothing about how many survived reconciliation. A page says what
|
|
223
|
+
// an empty list looks like in a stylesheet, and no list widget carries code
|
|
224
|
+
// for it. See docs/reference/data-binding.md.
|
|
225
|
+
scope.setAttribute(ATTR_ROW_COUNT, String(showing(scope).size));
|
|
226
|
+
// Derived scaffolding — a section heading, an <optgroup> — goes when its
|
|
227
|
+
// last row does, and only the widget knows it exists.
|
|
228
|
+
host.lbRowsLanded?.();
|
|
61
229
|
}
|
|
62
|
-
/** Land a whole response. Every
|
|
230
|
+
/** Land a whole response. Every result arrives through here. */
|
|
63
231
|
export function applyData(root, data) {
|
|
64
|
-
for (const [
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
232
|
+
for (const [name, result] of Object.entries(data)) {
|
|
233
|
+
const lists = root.querySelectorAll(`[${ATTR_LIST}="${name}"]`);
|
|
234
|
+
const rows = root.querySelectorAll(`[${ATTR_ROW}="${name}"]`);
|
|
235
|
+
if (lists.length === 0 && rows.length === 0) {
|
|
236
|
+
console.warn(`lb-hub: no scope for '${name}', skipping`);
|
|
68
237
|
continue;
|
|
69
238
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
239
|
+
// Cardinality is a property of the name, so one name is a list or a row
|
|
240
|
+
// and never both. Two spellings of it on one page is a mistake in the
|
|
241
|
+
// markup rather than a case to reconcile.
|
|
242
|
+
if (lists.length > 0 && rows.length > 0) {
|
|
243
|
+
console.error(`lb-hub: '${name}' is bound as a list in one place and a row in ` +
|
|
244
|
+
`another; a name answers with one shape`);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (lists.length > 0) {
|
|
248
|
+
for (const scope of lists)
|
|
249
|
+
applyList(scope, result);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// The only disagreement visible from here: a patch and a row are both
|
|
253
|
+
// objects, so an array arriving at a row scope is the one case the
|
|
254
|
+
// browser can name. createHub holds the stronger check, because it knows
|
|
255
|
+
// what the query declared.
|
|
256
|
+
if (Array.isArray(result)) {
|
|
257
|
+
console.error(`lb-hub: '${name}' is bound with ${ATTR_ROW} but answered with rows`);
|
|
258
|
+
continue;
|
|
75
259
|
}
|
|
260
|
+
for (const scope of rows)
|
|
261
|
+
applyRow(scope, result);
|
|
76
262
|
}
|
|
77
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,
|
|
1
|
+
export { applyData, applyRow } from "./lb-apply.js";
|
|
2
2
|
//# sourceMappingURL=lb-hub.browser.d.ts.map
|