@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.
Files changed (143) hide show
  1. package/README.md +3 -4
  2. package/dist/build/assemble.d.ts +1 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +81 -7
  5. package/dist/build/assemble.js.map +1 -0
  6. package/dist/build/cli.d.ts +2 -2
  7. package/dist/build/cli.js +3 -2
  8. package/dist/build/cli.js.map +1 -0
  9. package/dist/build/elements.js +1 -0
  10. package/dist/build/elements.js.map +1 -0
  11. package/dist/build/expand.d.ts.map +1 -1
  12. package/dist/build/expand.js +20 -32
  13. package/dist/build/expand.js.map +1 -0
  14. package/dist/build/format.js +1 -0
  15. package/dist/build/format.js.map +1 -0
  16. package/dist/build/locations.d.ts +4 -4
  17. package/dist/build/locations.d.ts.map +1 -1
  18. package/dist/build/locations.js +16 -5
  19. package/dist/build/locations.js.map +1 -0
  20. package/dist/build/origins.d.ts +0 -13
  21. package/dist/build/origins.d.ts.map +1 -1
  22. package/dist/build/origins.js +33 -8
  23. package/dist/build/origins.js.map +1 -0
  24. package/dist/build/package-root.js +1 -0
  25. package/dist/build/package-root.js.map +1 -0
  26. package/dist/build/pages.d.ts +7 -3
  27. package/dist/build/pages.d.ts.map +1 -1
  28. package/dist/build/pages.js +14 -7
  29. package/dist/build/pages.js.map +1 -0
  30. package/dist/build/styles.js +1 -0
  31. package/dist/build/styles.js.map +1 -0
  32. package/dist/core/lb-constants.d.ts +15 -13
  33. package/dist/core/lb-constants.d.ts.map +1 -1
  34. package/dist/core/lb-constants.js +104 -53
  35. package/dist/core/lb-constants.js.map +1 -0
  36. package/dist/core/lb-types.d.ts +103 -62
  37. package/dist/core/lb-types.d.ts.map +1 -1
  38. package/dist/core/lb-types.js +12 -3
  39. package/dist/core/lb-types.js.map +1 -0
  40. package/dist/hub/lb-apply.d.ts +28 -4
  41. package/dist/hub/lb-apply.d.ts.map +1 -1
  42. package/dist/hub/lb-apply.js +227 -40
  43. package/dist/hub/lb-apply.js.map +1 -0
  44. package/dist/hub/lb-hub.browser.d.ts +1 -1
  45. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  46. package/dist/hub/lb-hub.browser.js +165 -110
  47. package/dist/hub/lb-hub.browser.js.map +1 -0
  48. package/dist/server/lb-express.d.ts +8 -5
  49. package/dist/server/lb-express.d.ts.map +1 -1
  50. package/dist/server/lb-express.js +46 -33
  51. package/dist/server/lb-express.js.map +1 -0
  52. package/dist/server/lb-server.d.ts +67 -45
  53. package/dist/server/lb-server.d.ts.map +1 -1
  54. package/dist/server/lb-server.js +56 -18
  55. package/dist/server/lb-server.js.map +1 -0
  56. package/docs/TECHREF-1.0.md +1107 -0
  57. package/docs/analysis-accidental-complexity.md +149 -0
  58. package/docs/reference/builder.md +4 -4
  59. package/docs/reference/chrome.md +10 -9
  60. package/docs/reference/custom-elements.md +54 -46
  61. package/docs/reference/data-binding.md +179 -97
  62. package/docs/reference/overview.md +1 -1
  63. package/docs/reference/page-files.md +64 -49
  64. package/docs/reference/server.md +25 -6
  65. package/docs/reference/widgets.md +22 -30
  66. package/docs/roadmap.md +68 -22
  67. package/docs/testing.md +47 -17
  68. package/docs/theory.md +116 -3
  69. package/docs/tutorials/010-pages-and-navigation.md +8 -8
  70. package/docs/tutorials/040-displaying-data.md +9 -9
  71. package/docs/tutorials/050-actions.md +5 -5
  72. package/docs/tutorials/060-custom-element-code.md +1 -1
  73. package/docs/tutorials/065-conditional-rendering.md +4 -4
  74. package/docs/tutorials/070-displaying-a-list.md +24 -47
  75. package/docs/tutorials/072-inserting-into-a-list.md +18 -15
  76. package/docs/tutorials/074-deleting-from-a-list.md +15 -17
  77. package/docs/tutorials/076-updating-a-list-item.md +20 -22
  78. package/docs/tutorials/080-widget-requests.md +22 -35
  79. package/docs/tutorials/090-using-widget-libraries.md +1 -1
  80. package/package.json +2 -3
  81. package/dist/hub/lb-rows.d.ts +0 -18
  82. package/dist/hub/lb-rows.d.ts.map +0 -1
  83. package/dist/hub/lb-rows.js +0 -106
  84. package/dist/tests/assemble.test.d.ts +0 -8
  85. package/dist/tests/assemble.test.d.ts.map +0 -1
  86. package/dist/tests/assemble.test.js +0 -58
  87. package/dist/tests/elements.test.d.ts +0 -8
  88. package/dist/tests/elements.test.d.ts.map +0 -1
  89. package/dist/tests/elements.test.js +0 -118
  90. package/dist/tests/expand.test.d.ts +0 -10
  91. package/dist/tests/expand.test.d.ts.map +0 -1
  92. package/dist/tests/expand.test.js +0 -250
  93. package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
  94. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
  95. package/dist/tests/fixtures/elements/collision/imports.js +0 -1
  96. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
  97. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
  98. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
  99. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
  100. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
  101. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
  102. package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
  103. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
  104. package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
  105. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
  106. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
  107. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
  108. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
  109. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
  110. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
  111. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
  112. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
  113. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
  114. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
  115. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
  116. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
  117. package/dist/tests/helpers/console.d.ts +0 -20
  118. package/dist/tests/helpers/console.d.ts.map +0 -1
  119. package/dist/tests/helpers/console.js +0 -28
  120. package/dist/tests/helpers/dom.d.ts +0 -18
  121. package/dist/tests/helpers/dom.d.ts.map +0 -1
  122. package/dist/tests/helpers/dom.js +0 -22
  123. package/dist/tests/lb-apply.test.d.ts +0 -8
  124. package/dist/tests/lb-apply.test.d.ts.map +0 -1
  125. package/dist/tests/lb-apply.test.js +0 -153
  126. package/dist/tests/lb-express.test.d.ts +0 -14
  127. package/dist/tests/lb-express.test.d.ts.map +0 -1
  128. package/dist/tests/lb-express.test.js +0 -238
  129. package/dist/tests/lb-rows.test.d.ts +0 -12
  130. package/dist/tests/lb-rows.test.d.ts.map +0 -1
  131. package/dist/tests/lb-rows.test.js +0 -336
  132. package/dist/tests/lb-server.test.d.ts +0 -9
  133. package/dist/tests/lb-server.test.d.ts.map +0 -1
  134. package/dist/tests/lb-server.test.js +0 -495
  135. package/dist/tests/origins.test.d.ts +0 -10
  136. package/dist/tests/origins.test.d.ts.map +0 -1
  137. package/dist/tests/origins.test.js +0 -369
  138. package/dist/tests/pages.test.d.ts +0 -6
  139. package/dist/tests/pages.test.d.ts.map +0 -1
  140. package/dist/tests/pages.test.js +0 -98
  141. package/dist/tests/styles.test.d.ts +0 -7
  142. package/dist/tests/styles.test.d.ts.map +0 -1
  143. package/dist/tests/styles.test.js +0 -76
@@ -1,88 +1,129 @@
1
- /** docs/reference/data-binding.md — the operation set is closed. */
2
- export type HubRequest = {
3
- op: "cell-change";
4
- query: string;
5
- key: string;
6
- cell: string;
7
- value: string;
8
- } | {
9
- op: "tuple-insert";
10
- query: string;
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
- op: "tuple-update";
14
- query: string;
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
- op: "tuple-delete";
19
- query: string;
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 a CRUD operation.
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 hooks: the browser cannot reach anything the
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
- * A button carries no value, so the server computes the whole of the new
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
- op: "action";
37
- name: string;
38
- query?: string;
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
- /** One query's result: the cells of a single tuple. Every value is a string. */
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
- * Every response on the data channel is the same shape, whether it is a
73
- * cold start or the narrowest refresh. The hub does not know which case
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 type HubData = Record<string, HubResult>;
106
+ export declare function isOperation(request: HubRequest): request is LbOperation;
77
107
  /**
78
- * What a list widget implements. The hub delivers a projection by calling
79
- * this and stops; where a row goes is the widget's, because only the widget
80
- * knows whether it sorts, groups, or appends.
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
- * One protocol name, not a table of tag names — the hub tests for the method,
83
- * never for the element.
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 HubRowHost {
86
- acceptRows(result: Projection): void;
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":"AAEA,oEAAoE;AACpE,MAAM,MAAM,UAAU,GAClB;IACE,EAAE,EAAE,aAAa,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IAAE,EAAE,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACrE;IACE,EAAE,EAAE,cAAc,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,GACD;IAAE,EAAE,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;AACpD;;;;;;;;;;;;GAYG;GACD;IACE,EAAE,EAAE,QAAQ,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,gFAAgF;AAChF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEjD;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,EAAE,CAAA;CAAE,GACnC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE3D,sEAAsE;AACtE,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAEjD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,IAAI,UAAU,CAGpE;AAED;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CACtC"}
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"}
@@ -1,5 +1,14 @@
1
+ /// <reference lib="dom" />
1
2
  // The wire vocabulary. Shared by browser and server.
2
- export function isProjection(result) {
3
- const op = result.op;
4
- return op === "rows" || op === "patch";
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"]}
@@ -1,13 +1,37 @@
1
- import { type QueryResult, type HubData } from "../core/lb-types";
1
+ import { type HubData, type ListResult, type Row } from "../core/lb-types.js";
2
2
  /**
3
- * Fill one scope from one tuple.
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 applyTuple(root: Element, cells: QueryResult): void;
11
- /** Land a whole response. Every query result arrives through here. */
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":"AAcA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,OAAO,EAEb,MAAM,kBAAkB,CAAC;AAkB1B;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAMlE;AAmBD,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAY/D"}
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"}
@@ -2,76 +2,263 @@
2
2
  /**
3
3
  * Landing data on a live host — see docs/reference/data-binding.md.
4
4
  *
5
- * This is one file rather than part of the hub because a list widget needs
6
- * the same operation. It clones a row and fills it with a tuple, and filling
7
- * a row is filling a scope: the only difference is how much of the page the
8
- * root covers. A widget imports `applyTuple` the way it imports `ATTR_VALUE`,
9
- * so there is one implementation of what a cell means and nobody reaches into
10
- * anybody's children.
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
- import { ATTR_CELL, ATTR_QUERY, ATTR_VALUE } from "../core/lb-constants";
13
- import { isProjection, } from "../core/lb-types";
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 two ways. A custom element owns whatever control it
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 native element has no behavior of its own, so its value is its text.
22
- * The test is lexical — a hyphen in the tag name — and the hub holds no
23
- * knowledge of any particular element.
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
- if (el.localName.includes("-"))
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
- else
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
- * Fill one scope from one tuple.
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 applyTuple(root, cells) {
40
- for (const [cell, value] of Object.entries(cells)) {
41
- const selector = `[${ATTR_CELL}="${cell}"]`;
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
- * A projection has to reach a widget. A native element has one destination
50
- * for a value and no way to acquire children, so a list is not something it
51
- * can be asked to show.
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 landRows(scope, query, result) {
54
- const host = scope;
55
- if (typeof host.acceptRows !== "function") {
56
- console.error(`lb-hub: query '${query}' returned rows, but <${scope.localName}> ` +
57
- `is not a list widget`);
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
- host.acceptRows(result);
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 query result arrives through here. */
230
+ /** Land a whole response. Every result arrives through here. */
63
231
  export function applyData(root, data) {
64
- for (const [query, result] of Object.entries(data)) {
65
- const scopes = root.querySelectorAll(`[${ATTR_QUERY}="${query}"]`);
66
- if (scopes.length === 0) {
67
- console.warn(`lb-hub: no scope for query '${query}', skipping`);
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
- for (const scope of scopes) {
71
- if (isProjection(result))
72
- landRows(scope, query, result);
73
- else
74
- applyTuple(scope, result);
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, applyTuple } from "./lb-apply";
1
+ export { applyData, applyRow } from "./lb-apply.js";
2
2
  //# sourceMappingURL=lb-hub.browser.d.ts.map