@loadbare/app 0.8.1 → 0.9.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 (41) hide show
  1. package/dist/core/lb-constants.d.ts +3 -0
  2. package/dist/core/lb-constants.d.ts.map +1 -1
  3. package/dist/core/lb-constants.js +28 -4
  4. package/dist/core/lb-constants.js.map +1 -1
  5. package/dist/core/lb-types.d.ts +11 -0
  6. package/dist/core/lb-types.d.ts.map +1 -1
  7. package/dist/core/lb-types.js +20 -0
  8. package/dist/core/lb-types.js.map +1 -1
  9. package/dist/hub/lb-apply.d.ts +10 -0
  10. package/dist/hub/lb-apply.d.ts.map +1 -1
  11. package/dist/hub/lb-apply.js +15 -1
  12. package/dist/hub/lb-apply.js.map +1 -1
  13. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  14. package/dist/hub/lb-hub.browser.js +161 -38
  15. package/dist/hub/lb-hub.browser.js.map +1 -1
  16. package/dist/server/lb-express.d.ts +22 -7
  17. package/dist/server/lb-express.d.ts.map +1 -1
  18. package/dist/server/lb-express.js +79 -31
  19. package/dist/server/lb-express.js.map +1 -1
  20. package/dist/server/lb-server.d.ts +26 -0
  21. package/dist/server/lb-server.d.ts.map +1 -1
  22. package/dist/server/lb-server.js +53 -3
  23. package/dist/server/lb-server.js.map +1 -1
  24. package/docs/TECHREF-1.0.md +154 -22
  25. package/docs/comparison.md +8 -7
  26. package/docs/reference/chrome.md +5 -3
  27. package/docs/reference/data-binding.md +33 -0
  28. package/docs/reference/page-files.md +39 -0
  29. package/docs/reference/server.md +17 -0
  30. package/docs/reference/widgets.md +6 -2
  31. package/docs/roadmap.md +28 -0
  32. package/docs/theory.md +8 -1
  33. package/docs/tutorials/010-pages-and-navigation.md +2 -1
  34. package/package.json +1 -1
  35. package/skills/loadbare-app/SKILL.md +36 -8
  36. package/skills/loadbare-app/references/TECHREF-1.0.md +154 -22
  37. package/skills/loadbare-app/references/chrome.md +5 -3
  38. package/skills/loadbare-app/references/data-binding.md +33 -0
  39. package/skills/loadbare-app/references/page-files.md +39 -0
  40. package/skills/loadbare-app/references/server.md +17 -0
  41. package/skills/loadbare-app/references/widgets.md +6 -2
@@ -16,10 +16,13 @@ export declare const LB_ACTIONS: readonly ["lb-row-insert", "lb-row-delete", "lb
16
16
  export declare const ATTR_SLOT = "lb-slot";
17
17
  export declare const ATTR_TEMPLATE = "lb-template";
18
18
  export declare const ATTR_NAV_LINK = "lb-nav-link";
19
+ export declare const ATTR_QUERY_PARM = "lb-query-parm";
20
+ export declare const ATTR_QUERY_PARM_PUSH = "lb-query-parm-push";
19
21
  export declare const ATTR_PAGE = "lb-page";
20
22
  export declare const NAV_ROW = "lb-navigation";
21
23
  export declare const NAV_CELL_LABEL = "page-label";
22
24
  export declare const NAV_CELL_URI = "page-uri";
25
+ export declare const QUERY_PARMS_ROW = "lb-query-parms";
23
26
  export declare const ATTR_UNKNOWN_PAGE = "lb-unknown-page";
24
27
  export declare const HUB_TAG_NAME = "lb-hub";
25
28
  export declare const ATTR_PENDING = "lb-pending";
@@ -1 +1 @@
1
- {"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,SAAS,YAAY,CAAC;AAQnC,eAAO,MAAM,QAAQ,WAAW,CAAC;AAMjC,eAAO,MAAM,QAAQ,WAAW,CAAC;AAMjC,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAQ7C,eAAO,MAAM,SAAS,YAAY,CAAC;AAanC,eAAO,MAAM,SAAS,YAAY,CAAC;AAEnC,eAAO,MAAM,UAAU,aAAa,CAAC;AAOrC,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAK7C,eAAO,MAAM,aAAa,eAAe,CAAC;AAS1C,eAAO,MAAM,WAAW,cAAc,CAAC;AAYvC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAMxC,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAKjD,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAcjD,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAIjD,eAAO,MAAM,UAAU,8DAIb,CAAC;AAMX,eAAO,MAAM,SAAS,YAAY,CAAC;AAYnC,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAK3C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAM3C,eAAO,MAAM,SAAS,YAAY,CAAC;AAQnC,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAKvC,eAAO,MAAM,cAAc,eAAe,CAAC;AAC3C,eAAO,MAAM,YAAY,aAAa,CAAC;AAUvC,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAQnD,eAAO,MAAM,YAAY,WAAW,CAAC;AASrC,eAAO,MAAM,YAAY,eAAe,CAAC;AAOzC,eAAO,MAAM,UAAU,aAAa,CAAC;AAMrC,eAAO,MAAM,WAAW,QAAQ,CAAC;AAIjC,eAAO,MAAM,qBAAqB,QAAS,CAAC"}
1
+ {"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,SAAS,YAAY,CAAC;AAQnC,eAAO,MAAM,QAAQ,WAAW,CAAC;AAMjC,eAAO,MAAM,QAAQ,WAAW,CAAC;AAMjC,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAQ7C,eAAO,MAAM,SAAS,YAAY,CAAC;AAanC,eAAO,MAAM,SAAS,YAAY,CAAC;AAEnC,eAAO,MAAM,UAAU,aAAa,CAAC;AAOrC,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAK7C,eAAO,MAAM,aAAa,eAAe,CAAC;AAS1C,eAAO,MAAM,WAAW,cAAc,CAAC;AAYvC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAMxC,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAKjD,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAcjD,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAIjD,eAAO,MAAM,UAAU,8DAIb,CAAC;AAMX,eAAO,MAAM,SAAS,YAAY,CAAC;AAYnC,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAK3C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAQ3C,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAK/C,eAAO,MAAM,oBAAoB,uBAAuB,CAAC;AAMzD,eAAO,MAAM,SAAS,YAAY,CAAC;AAQnC,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAKvC,eAAO,MAAM,cAAc,eAAe,CAAC;AAC3C,eAAO,MAAM,YAAY,aAAa,CAAC;AAWvC,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAUhD,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAQnD,eAAO,MAAM,YAAY,WAAW,CAAC;AASrC,eAAO,MAAM,YAAY,eAAe,CAAC;AAOzC,eAAO,MAAM,UAAU,aAAa,CAAC;AASrC,eAAO,MAAM,WAAW,QAAQ,CAAC;AAIjC,eAAO,MAAM,qBAAqB,QAAS,CAAC"}
@@ -122,6 +122,17 @@ export const ATTR_TEMPLATE = "lb-template";
122
122
  // Only anchors carrying this are intercepted, so external links are never
123
123
  // hijacked.
124
124
  export const ATTR_NAV_LINK = "lb-nav-link";
125
+ // Names the query parm a control writes. The query string describes what is
126
+ // on screen and never commands, so a control that narrows the page writes its
127
+ // value into the address bar rather than sending it: the hub sets the one
128
+ // parm, or deletes it when the value is empty, and loads the page at the new
129
+ // URL. Every other parm is left as it is. Landed back onto the control from
130
+ // the URL on every load, so a reload, a mailed link and Back all show it.
131
+ export const ATTR_QUERY_PARM = "lb-query-parm";
132
+ // Changing what the current page shows is not going anywhere, so a query parm
133
+ // write replaces the history entry. A control carrying this pushes one
134
+ // instead, for the rare parm a user would expect Back to undo.
135
+ export const ATTR_QUERY_PARM_PUSH = "lb-query-parm-push";
125
136
  // A page host ships as <template lb-page="<name>">. An attribute rather than
126
137
  // an id: an id lives in the application's own namespace, where a page named
127
138
  // for something the chrome also ids would collide, and getElementById()
@@ -136,9 +147,19 @@ export const ATTR_PAGE = "lb-page";
136
147
  export const NAV_ROW = "lb-navigation";
137
148
  // Its two columns. The label is the nav's: the text of the lb-nav-link
138
149
  // anchor whose href names the current page, and empty when no anchor does.
139
- // The URI is the path as the browser has it.
150
+ // The URI is the path and query string as the browser has them.
140
151
  export const NAV_CELL_LABEL = "page-label";
141
152
  export const NAV_CELL_URI = "page-uri";
153
+ // A server response that changes the query parms, in one row whose columns
154
+ // are the parms. Returned from a request's run (write it with queryParms() in
155
+ // server/lb-server.ts), because some values exist only once the write has
156
+ // run: the key of a row just inserted, or the absence of one just deleted.
157
+ // The server then loads the page at the query string with these parms set,
158
+ // in the same round trip, and the hub writes them into the URL before
159
+ // landing that load. An empty value takes the parm out. Only parms: the
160
+ // path is never the server's to change, so this cannot redirect. A reserved
161
+ // name (LB_RESERVED_PREFIX), so it never collides with a declared query.
162
+ export const QUERY_PARMS_ROW = "lb-query-parms";
142
163
  // Marks the chrome's own <dialog> for a page name that resolves to no
143
164
  // template — a routing miss discovered client-side, not an HTTP 404 (every
144
165
  // route gets the same 200 response; see docs/tutorials/010-pages-and-navigation.md).
@@ -170,9 +191,12 @@ export const ATTR_PENDING = "lb-pending";
170
191
  // happened.
171
192
  export const ATTR_ERROR = "lb-error";
172
193
  // --- Wire --------------------------------------------------------------
173
- // One path, one method: everything the hub asks of the server is a POST to
174
- // this path. Navigation is therefore the only GET an application serves,
175
- // which is what leaves the page name space unrestricted.
194
+ // One path, one method: everything the hub asks of the server is a POST
195
+ // below this path. The page name follows it, and the query string the
196
+ // browser is showing rides after that verbatim, so `/accounts?acct=23` asks
197
+ // as `POST /lb/accounts?acct=23` and the server always knows what URL the
198
+ // browser is showing. Navigation is therefore the only GET an application
199
+ // serves, which is what leaves the page name space unrestricted.
176
200
  export const LB_ENDPOINT = "/lb";
177
201
  // A client-side deadline for one round trip. fetch() has no deadline of its
178
202
  // own; past this the request is aborted and treated as a failure.
@@ -1 +1 @@
1
- {"version":3,"file":"lb-constants.js","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"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","sourcesContent":["// Every hub attribute name lives here. Nothing writes one as a string\n// literal — not a widget, not a page host, not a test. See\n// docs/reference/data-binding.md.\n\n// --- Binding (docs/reference/data-binding.md) ---------------------------\n// One containment ladder: a list holds rows, a row holds cells. Both scope\n// attributes take a name the server declares, and a nested one of either\n// kind begins a new scope.\n\n// Scopes DOM children to a named set of rows.\nexport const ATTR_LIST = \"lb-list\";\n\n// Scopes DOM children to one named row.\n//\n// A single-row scope is read-only. Every write operation needs a key, and a\n// key exists only on a live row the hub stamped inside a list, so a declared\n// action is the only thing such a scope can send. An application that wants\n// a writable single row declares a list that answers with one row.\nexport const ATTR_ROW = \"lb-row\";\n\n// Named on a row template by the developer: the column that identifies a row\n// of the list. A property of the list rather than of the template — a list\n// without a fixed key column is meaningless — but written where the rows\n// land, because that is the only place the page says anything about them.\nexport const ATTR_KEY = \"lb-key\";\n\n// Stamped on a live row by the row machinery: that row's value of the column\n// ATTR_KEY names. Written by the hub, never by a developer, and read back by\n// whatever needs to know which row it is inside — a delete button, an update\n// form, a widget building its own request, or a stylesheet.\nexport const ATTR_KEY_VALUE = \"lb-key-value\";\n\n// The column an element displays.\n//\n// Its value is a column name and never a cell name. A cell has no name of\n// its own: it is identified by its row and its column, and the row arrives\n// from scope, so the column is all that is left to say. ATTR_KEY holds a\n// column name for the same reason.\nexport const ATTR_CELL = \"lb-cell\";\n\n// The column that decides whether an element is present: `null` and `false`\n// are off, and every other value is on. It binds by the same ancestor rule as\n// ATTR_CELL, and names a column for the same reason.\n//\n// An element that is off is moved into a `<template>` carrying this attribute\n// and the same column, standing where the element stood, and moved back out\n// when the column turns on. The builder ships every such element already\n// inside its template. A developer writes this attribute on an element and\n// never on a template: the template form is Loadbare's, and holds the absent\n// branch in the document so the hub holds nothing. Landing reaches inside it,\n// so a branch that returns is already current; nothing else does.\nexport const ATTR_SHOW = \"lb-show\";\n\nexport const ATTR_VALUE = \"lb-value\";\n\n// Stamped on a list scope by the shared row machinery: how many rows it is\n// showing. Written by the hub and never bound to — it is derived from rows\n// the hub already knows, exactly as the section heading in lb-table is. A\n// list widget watches it, and it exists so that \"no members yet\" is a\n// stylesheet rule rather than a conditional in every list widget.\nexport const ATTR_ROW_COUNT = \"lb-row-count\";\n\n// --- Requests (docs/reference/data-binding.md) --------------------------\n// One event name, used by a widget reaching the server and by a widget\n// reaching an ancestor widget.\nexport const LB_EVENT_NAME = \"lb-request\";\n\n// Names what an interaction asks for.\n//\n// One attribute name, one wire field, one set of values: the request field\n// `action` carries this attribute's value verbatim, and nothing is\n// translated in between. On a native element the hub turns the element's own\n// event into the request — a form submits, anything else clicks; on a\n// widget, the widget builds it from whatever interaction it owns.\nexport const ATTR_ACTION = \"lb-action\";\n\n// --- Reserved names ----------------------------------------------------\n// Loadbare owns every value beginning with this prefix, in every lb-*\n// attribute: a name so prefixed is the hub's own (NAV_ROW), and an\n// lb-action so prefixed is an operation (below). The server refuses a page\n// that declares a query or an action beginning with it, so a new reserved\n// name can never collide with one an application already uses.\n//\n// It is also the whole of the wire discriminant. A request whose `action`\n// begins with it is one of the three operations; anything else is a name the\n// page declared under its requests. Nothing else tells them apart.\nexport const LB_RESERVED_PREFIX = \"lb-\";\n\n// On a <form> in a list scope: builds a new row on submit. The hub gathers\n// every lb-cell inside the form into a values map — no key, because there is\n// no row yet — and the form's own lb-list says which list it is inserting\n// into.\nexport const ACTION_ROW_INSERT = \"lb-row-insert\";\n\n// Deletes the row the element is inside. Needs no declared name: it takes\n// only the row's own lb-list/lb-key-value binding, which is all the server\n// needs to know which row and whether the operation is permitted on it.\nexport const ACTION_ROW_DELETE = \"lb-row-delete\";\n\n// Commits cells of the row the element is inside. From a <form> or a button,\n// on submit or click, the hub gathers the same values map as an insert, plus\n// the row's own lb-key-value: a form that is a list's row template root, or\n// sits inside one, has both from its ancestors exactly as a delete button\n// does.\n//\n// From an element carrying lb-cell — a widget wrapping one control — the\n// values map holds that one cell and nothing else, the way a control has a\n// value and a form has values. The widget decides when a change is made and\n// sends the action with its value; without one, the hub reads the control\n// the widget wraps. SQL has one UPDATE whether it sets one column or many,\n// and so there is one operation here.\nexport const ACTION_ROW_UPDATE = \"lb-row-update\";\n\n// Every reserved lb-action, in one place, so the builder can refuse an\n// unknown one and the hub can refuse to send one it does not implement.\nexport const LB_ACTIONS = [\n ACTION_ROW_INSERT,\n ACTION_ROW_DELETE,\n ACTION_ROW_UPDATE,\n] as const;\n\n// --- Expansion (docs/reference/custom-elements.md) ---------------------\n// Marks the element in a definition whose children receive the authored\n// inner content. An attribute rather than an element because HTML content\n// models discard foreign elements inside <select> and <table>.\nexport const ATTR_SLOT = \"lb-slot\";\n\n// One name in two positions. In a definition it marks a destination; on an\n// authored <template> it names the destination that template is for, and the\n// template is unwrapped into it.\n//\n// It exists because the parser, not the content model, is the obstacle. A\n// <thead> written inside <lb-table> on a page is not in a table, so the\n// tokenizer drops the tag and keeps the text. A <template> survives anywhere\n// and its contents parse as if they were in a table, so wrapping the markup\n// is what gets it to expansion intact — and unwrapping it is what puts real\n// elements in the shipped document.\nexport const ATTR_TEMPLATE = \"lb-template\";\n\n// --- Host channel (docs/reference/chrome.md) ---------------------------\n// Only anchors carrying this are intercepted, so external links are never\n// hijacked.\nexport const ATTR_NAV_LINK = \"lb-nav-link\";\n\n// A page host ships as <template lb-page=\"<name>\">. An attribute rather than\n// an id: an id lives in the application's own namespace, where a page named\n// for something the chrome also ids would collide, and getElementById()\n// answers a collision by silently picking one.\nexport const ATTR_PAGE = \"lb-page\";\n\n// The hub's own row. Landed on every navigation, on any subtree inside the\n// hub that names it, through the same applyData() a server answer goes\n// through — so a chrome displays where the page is the way it displays\n// anything else. One row rather than a list, so a subtree binds it with\n// lb-row. A reserved name (LB_RESERVED_PREFIX): the server refuses to\n// declare a query so named.\nexport const NAV_ROW = \"lb-navigation\";\n\n// Its two columns. The label is the nav's: the text of the lb-nav-link\n// anchor whose href names the current page, and empty when no anchor does.\n// The URI is the path as the browser has it.\nexport const NAV_CELL_LABEL = \"page-label\";\nexport const NAV_CELL_URI = \"page-uri\";\n\n// Marks the chrome's own <dialog> for a page name that resolves to no\n// template — a routing miss discovered client-side, not an HTTP 404 (every\n// route gets the same 200 response; see docs/tutorials/010-pages-and-navigation.md).\n// The hub only opens it; what it says comes from NAV_ROW, which the dialog\n// consumes like any other subtree, by naming it in lb-row. Optional: an\n// application that declares none gets today's console.error and nothing\n// more. The builder requires the element it's on to be a <dialog>\n// (build/assemble.ts), since the hub calls showModal() on it.\nexport const ATTR_UNKNOWN_PAGE = \"lb-unknown-page\";\n\n// The hub's own tag. It is exempt from the rule every other LB-* tag\n// follows: expansion does not require it to have a definition (build/expand.ts),\n// and the client build does not require it to have a script of its own\n// (build/elements.ts) — it is the framework's live element, always present,\n// registered once by the package's main entry point, not a widget an\n// application discovers or declares.\nexport const HUB_TAG_NAME = \"lb-hub\";\n\n// --- Request lifecycle --------------------------------------------------\n// Stamped on the element that dispatched a request — the button, form, or\n// widget itself — for the duration of its round trip. It carries no\n// meaning beyond \"in flight\": a stylesheet dims the element, or a custom\n// button widget watches it to disable itself. The hub sets and clears it,\n// alongside aria-busy, and reads it in one place: a native action (a button\n// or form, not a widget) still pending ignores being performed again.\nexport const ATTR_PENDING = \"lb-pending\";\n\n// Stamped on the same element if that round trip failed — a non-2xx\n// response, a thrown application error, or a timeout, all alike. Cleared at\n// the start of that element's next request. What (if anything) an\n// application shows for it is undecided; this is only the fact that it\n// happened.\nexport const ATTR_ERROR = \"lb-error\";\n\n// --- Wire --------------------------------------------------------------\n// One path, one method: everything the hub asks of the server is a POST to\n// this path. Navigation is therefore the only GET an application serves,\n// which is what leaves the page name space unrestricted.\nexport const LB_ENDPOINT = \"/lb\";\n\n// A client-side deadline for one round trip. fetch() has no deadline of its\n// own; past this the request is aborted and treated as a failure.\nexport const LB_REQUEST_TIMEOUT_MS = 10_000;\n"]}
1
+ {"version":3,"file":"lb-constants.js","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"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","sourcesContent":["// Every hub attribute name lives here. Nothing writes one as a string\n// literal — not a widget, not a page host, not a test. See\n// docs/reference/data-binding.md.\n\n// --- Binding (docs/reference/data-binding.md) ---------------------------\n// One containment ladder: a list holds rows, a row holds cells. Both scope\n// attributes take a name the server declares, and a nested one of either\n// kind begins a new scope.\n\n// Scopes DOM children to a named set of rows.\nexport const ATTR_LIST = \"lb-list\";\n\n// Scopes DOM children to one named row.\n//\n// A single-row scope is read-only. Every write operation needs a key, and a\n// key exists only on a live row the hub stamped inside a list, so a declared\n// action is the only thing such a scope can send. An application that wants\n// a writable single row declares a list that answers with one row.\nexport const ATTR_ROW = \"lb-row\";\n\n// Named on a row template by the developer: the column that identifies a row\n// of the list. A property of the list rather than of the template — a list\n// without a fixed key column is meaningless — but written where the rows\n// land, because that is the only place the page says anything about them.\nexport const ATTR_KEY = \"lb-key\";\n\n// Stamped on a live row by the row machinery: that row's value of the column\n// ATTR_KEY names. Written by the hub, never by a developer, and read back by\n// whatever needs to know which row it is inside — a delete button, an update\n// form, a widget building its own request, or a stylesheet.\nexport const ATTR_KEY_VALUE = \"lb-key-value\";\n\n// The column an element displays.\n//\n// Its value is a column name and never a cell name. A cell has no name of\n// its own: it is identified by its row and its column, and the row arrives\n// from scope, so the column is all that is left to say. ATTR_KEY holds a\n// column name for the same reason.\nexport const ATTR_CELL = \"lb-cell\";\n\n// The column that decides whether an element is present: `null` and `false`\n// are off, and every other value is on. It binds by the same ancestor rule as\n// ATTR_CELL, and names a column for the same reason.\n//\n// An element that is off is moved into a `<template>` carrying this attribute\n// and the same column, standing where the element stood, and moved back out\n// when the column turns on. The builder ships every such element already\n// inside its template. A developer writes this attribute on an element and\n// never on a template: the template form is Loadbare's, and holds the absent\n// branch in the document so the hub holds nothing. Landing reaches inside it,\n// so a branch that returns is already current; nothing else does.\nexport const ATTR_SHOW = \"lb-show\";\n\nexport const ATTR_VALUE = \"lb-value\";\n\n// Stamped on a list scope by the shared row machinery: how many rows it is\n// showing. Written by the hub and never bound to — it is derived from rows\n// the hub already knows, exactly as the section heading in lb-table is. A\n// list widget watches it, and it exists so that \"no members yet\" is a\n// stylesheet rule rather than a conditional in every list widget.\nexport const ATTR_ROW_COUNT = \"lb-row-count\";\n\n// --- Requests (docs/reference/data-binding.md) --------------------------\n// One event name, used by a widget reaching the server and by a widget\n// reaching an ancestor widget.\nexport const LB_EVENT_NAME = \"lb-request\";\n\n// Names what an interaction asks for.\n//\n// One attribute name, one wire field, one set of values: the request field\n// `action` carries this attribute's value verbatim, and nothing is\n// translated in between. On a native element the hub turns the element's own\n// event into the request — a form submits, anything else clicks; on a\n// widget, the widget builds it from whatever interaction it owns.\nexport const ATTR_ACTION = \"lb-action\";\n\n// --- Reserved names ----------------------------------------------------\n// Loadbare owns every value beginning with this prefix, in every lb-*\n// attribute: a name so prefixed is the hub's own (NAV_ROW), and an\n// lb-action so prefixed is an operation (below). The server refuses a page\n// that declares a query or an action beginning with it, so a new reserved\n// name can never collide with one an application already uses.\n//\n// It is also the whole of the wire discriminant. A request whose `action`\n// begins with it is one of the three operations; anything else is a name the\n// page declared under its requests. Nothing else tells them apart.\nexport const LB_RESERVED_PREFIX = \"lb-\";\n\n// On a <form> in a list scope: builds a new row on submit. The hub gathers\n// every lb-cell inside the form into a values map — no key, because there is\n// no row yet — and the form's own lb-list says which list it is inserting\n// into.\nexport const ACTION_ROW_INSERT = \"lb-row-insert\";\n\n// Deletes the row the element is inside. Needs no declared name: it takes\n// only the row's own lb-list/lb-key-value binding, which is all the server\n// needs to know which row and whether the operation is permitted on it.\nexport const ACTION_ROW_DELETE = \"lb-row-delete\";\n\n// Commits cells of the row the element is inside. From a <form> or a button,\n// on submit or click, the hub gathers the same values map as an insert, plus\n// the row's own lb-key-value: a form that is a list's row template root, or\n// sits inside one, has both from its ancestors exactly as a delete button\n// does.\n//\n// From an element carrying lb-cell — a widget wrapping one control — the\n// values map holds that one cell and nothing else, the way a control has a\n// value and a form has values. The widget decides when a change is made and\n// sends the action with its value; without one, the hub reads the control\n// the widget wraps. SQL has one UPDATE whether it sets one column or many,\n// and so there is one operation here.\nexport const ACTION_ROW_UPDATE = \"lb-row-update\";\n\n// Every reserved lb-action, in one place, so the builder can refuse an\n// unknown one and the hub can refuse to send one it does not implement.\nexport const LB_ACTIONS = [\n ACTION_ROW_INSERT,\n ACTION_ROW_DELETE,\n ACTION_ROW_UPDATE,\n] as const;\n\n// --- Expansion (docs/reference/custom-elements.md) ---------------------\n// Marks the element in a definition whose children receive the authored\n// inner content. An attribute rather than an element because HTML content\n// models discard foreign elements inside <select> and <table>.\nexport const ATTR_SLOT = \"lb-slot\";\n\n// One name in two positions. In a definition it marks a destination; on an\n// authored <template> it names the destination that template is for, and the\n// template is unwrapped into it.\n//\n// It exists because the parser, not the content model, is the obstacle. A\n// <thead> written inside <lb-table> on a page is not in a table, so the\n// tokenizer drops the tag and keeps the text. A <template> survives anywhere\n// and its contents parse as if they were in a table, so wrapping the markup\n// is what gets it to expansion intact — and unwrapping it is what puts real\n// elements in the shipped document.\nexport const ATTR_TEMPLATE = \"lb-template\";\n\n// --- Host channel (docs/reference/chrome.md) ---------------------------\n// Only anchors carrying this are intercepted, so external links are never\n// hijacked.\nexport const ATTR_NAV_LINK = \"lb-nav-link\";\n\n// Names the query parm a control writes. The query string describes what is\n// on screen and never commands, so a control that narrows the page writes its\n// value into the address bar rather than sending it: the hub sets the one\n// parm, or deletes it when the value is empty, and loads the page at the new\n// URL. Every other parm is left as it is. Landed back onto the control from\n// the URL on every load, so a reload, a mailed link and Back all show it.\nexport const ATTR_QUERY_PARM = \"lb-query-parm\";\n\n// Changing what the current page shows is not going anywhere, so a query parm\n// write replaces the history entry. A control carrying this pushes one\n// instead, for the rare parm a user would expect Back to undo.\nexport const ATTR_QUERY_PARM_PUSH = \"lb-query-parm-push\";\n\n// A page host ships as <template lb-page=\"<name>\">. An attribute rather than\n// an id: an id lives in the application's own namespace, where a page named\n// for something the chrome also ids would collide, and getElementById()\n// answers a collision by silently picking one.\nexport const ATTR_PAGE = \"lb-page\";\n\n// The hub's own row. Landed on every navigation, on any subtree inside the\n// hub that names it, through the same applyData() a server answer goes\n// through — so a chrome displays where the page is the way it displays\n// anything else. One row rather than a list, so a subtree binds it with\n// lb-row. A reserved name (LB_RESERVED_PREFIX): the server refuses to\n// declare a query so named.\nexport const NAV_ROW = \"lb-navigation\";\n\n// Its two columns. The label is the nav's: the text of the lb-nav-link\n// anchor whose href names the current page, and empty when no anchor does.\n// The URI is the path and query string as the browser has them.\nexport const NAV_CELL_LABEL = \"page-label\";\nexport const NAV_CELL_URI = \"page-uri\";\n\n// A server response that changes the query parms, in one row whose columns\n// are the parms. Returned from a request's run (write it with queryParms() in\n// server/lb-server.ts), because some values exist only once the write has\n// run: the key of a row just inserted, or the absence of one just deleted.\n// The server then loads the page at the query string with these parms set,\n// in the same round trip, and the hub writes them into the URL before\n// landing that load. An empty value takes the parm out. Only parms: the\n// path is never the server's to change, so this cannot redirect. A reserved\n// name (LB_RESERVED_PREFIX), so it never collides with a declared query.\nexport const QUERY_PARMS_ROW = \"lb-query-parms\";\n\n// Marks the chrome's own <dialog> for a page name that resolves to no\n// template — a routing miss discovered client-side, not an HTTP 404 (every\n// route gets the same 200 response; see docs/tutorials/010-pages-and-navigation.md).\n// The hub only opens it; what it says comes from NAV_ROW, which the dialog\n// consumes like any other subtree, by naming it in lb-row. Optional: an\n// application that declares none gets today's console.error and nothing\n// more. The builder requires the element it's on to be a <dialog>\n// (build/assemble.ts), since the hub calls showModal() on it.\nexport const ATTR_UNKNOWN_PAGE = \"lb-unknown-page\";\n\n// The hub's own tag. It is exempt from the rule every other LB-* tag\n// follows: expansion does not require it to have a definition (build/expand.ts),\n// and the client build does not require it to have a script of its own\n// (build/elements.ts) — it is the framework's live element, always present,\n// registered once by the package's main entry point, not a widget an\n// application discovers or declares.\nexport const HUB_TAG_NAME = \"lb-hub\";\n\n// --- Request lifecycle --------------------------------------------------\n// Stamped on the element that dispatched a request — the button, form, or\n// widget itself — for the duration of its round trip. It carries no\n// meaning beyond \"in flight\": a stylesheet dims the element, or a custom\n// button widget watches it to disable itself. The hub sets and clears it,\n// alongside aria-busy, and reads it in one place: a native action (a button\n// or form, not a widget) still pending ignores being performed again.\nexport const ATTR_PENDING = \"lb-pending\";\n\n// Stamped on the same element if that round trip failed — a non-2xx\n// response, a thrown application error, or a timeout, all alike. Cleared at\n// the start of that element's next request. What (if anything) an\n// application shows for it is undecided; this is only the fact that it\n// happened.\nexport const ATTR_ERROR = \"lb-error\";\n\n// --- Wire --------------------------------------------------------------\n// One path, one method: everything the hub asks of the server is a POST\n// below this path. The page name follows it, and the query string the\n// browser is showing rides after that verbatim, so `/accounts?acct=23` asks\n// as `POST /lb/accounts?acct=23` and the server always knows what URL the\n// browser is showing. Navigation is therefore the only GET an application\n// serves, which is what leaves the page name space unrestricted.\nexport const LB_ENDPOINT = \"/lb\";\n\n// A client-side deadline for one round trip. fetch() has no deadline of its\n// own; past this the request is aborted and treated as a failure.\nexport const LB_REQUEST_TIMEOUT_MS = 10_000;\n"]}
@@ -93,6 +93,17 @@ export interface DeclaredAction {
93
93
  value?: string;
94
94
  }
95
95
  export type HubRequest = LbOperation | DeclaredAction;
96
+ /**
97
+ * A query string with some parms set, and the rest as they were. An empty
98
+ * value takes its parm out, since empty is every parm's default and a
99
+ * default is left out of the URL.
100
+ *
101
+ * One rule on both sides of the wire: the hub applies it to the address bar,
102
+ * and the server to the query string it loads the page at after a request
103
+ * returned `QUERY_PARMS_ROW`. The two must agree, or what the page shows
104
+ * would differ from what the address bar says.
105
+ */
106
+ export declare function withQueryParms(search: URLSearchParams, parms: Record<string, string>): URLSearchParams;
96
107
  /**
97
108
  * The reserved prefix is the whole of the discriminant. Nothing else tells
98
109
  * an operation from a declared action, on the wire or anywhere else.
@@ -1 +1 @@
1
- {"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,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,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
+ {"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,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,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;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,eAAe,CAOjB;AAED;;;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"}
@@ -4,6 +4,26 @@ import { LB_RESERVED_PREFIX, } from "./lb-constants.js";
4
4
  export function isPatch(result) {
5
5
  return !Array.isArray(result);
6
6
  }
7
+ /**
8
+ * A query string with some parms set, and the rest as they were. An empty
9
+ * value takes its parm out, since empty is every parm's default and a
10
+ * default is left out of the URL.
11
+ *
12
+ * One rule on both sides of the wire: the hub applies it to the address bar,
13
+ * and the server to the query string it loads the page at after a request
14
+ * returned `QUERY_PARMS_ROW`. The two must agree, or what the page shows
15
+ * would differ from what the address bar says.
16
+ */
17
+ export function withQueryParms(search, parms) {
18
+ const next = new URLSearchParams(search);
19
+ for (const [name, value] of Object.entries(parms)) {
20
+ if (value === "")
21
+ next.delete(name);
22
+ else
23
+ next.set(name, value);
24
+ }
25
+ return next;
26
+ }
7
27
  /**
8
28
  * The reserved prefix is the whole of the discriminant. Nothing else tells
9
29
  * an operation from a declared action, on the wire or anywhere else.
@@ -1 +1 @@
1
- {"version":3,"file":"lb-types.js","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,qDAAqD;AAErD,OAAO,EAIL,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAuC3B,MAAM,UAAU,OAAO,CAAC,MAAkB;IACxC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC;AAiED;;;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_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 three 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 three 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/**\n * Everything that is not one of the three.\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
+ {"version":3,"file":"lb-types.js","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,qDAAqD;AAErD,OAAO,EAIL,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAuC3B,MAAM,UAAU,OAAO,CAAC,MAAkB;IACxC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC;AAiED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAuB,EACvB,KAA6B;IAE7B,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,EAAE;YAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;;YAC/B,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;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_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 three 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 three 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/**\n * Everything that is not one of the three.\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 * A query string with some parms set, and the rest as they were. An empty\n * value takes its parm out, since empty is every parm's default and a\n * default is left out of the URL.\n *\n * One rule on both sides of the wire: the hub applies it to the address bar,\n * and the server to the query string it loads the page at after a request\n * returned `QUERY_PARMS_ROW`. The two must agree, or what the page shows\n * would differ from what the address bar says.\n */\nexport function withQueryParms(\n search: URLSearchParams,\n parms: Record<string, string>,\n): URLSearchParams {\n const next = new URLSearchParams(search);\n for (const [name, value] of Object.entries(parms)) {\n if (value === \"\") next.delete(name);\n else next.set(name, value);\n }\n return next;\n}\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"]}
@@ -40,4 +40,14 @@ export declare function applyRow(root: Element, row: Row): void;
40
40
  export declare function applyList(scope: Element, result: ListResult): void;
41
41
  /** Land a whole response. Every result arrives through here. */
42
42
  export declare function applyData(root: ParentNode, data: HubData): void;
43
+ /**
44
+ * Land the query string on every control that writes one of its parms, the
45
+ * way a cell lands a column. A parm the URL does not carry lands empty, so a
46
+ * control shows what the address bar says after Back as well as after a
47
+ * reload: the URL is what is on screen, and nothing else is.
48
+ *
49
+ * Landed after the page's data, so a picker already has the options its
50
+ * value selects.
51
+ */
52
+ export declare function applyQueryParms(root: ParentNode, parms: URLSearchParams): void;
43
53
  //# sourceMappingURL=lb-apply.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"AAkCA,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,UAAU,EACf,KAAK,GAAG,EACT,MAAM,qBAAqB,CAAC;AAW7B;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,OAAO,GACV,EAAE,IAAI,gBAAgB,GAAG,iBAAiB,GAAG,mBAAmB,CAMlE;AA8DD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAMjE;AAqED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAetD;AA6CD;;;;;;;;;;;;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"}
1
+ {"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"AAmCA,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,UAAU,EACf,KAAK,GAAG,EACT,MAAM,qBAAqB,CAAC;AAW7B;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,OAAO,GACV,EAAE,IAAI,gBAAgB,GAAG,iBAAiB,GAAG,mBAAmB,CAMlE;AA8DD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAMjE;AAqED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAetD;AA6CD;;;;;;;;;;;;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;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,eAAe,GACrB,IAAI,CAIN"}
@@ -20,7 +20,7 @@
20
20
  * landing reaches in there as it reaches everywhere else, so a branch that
21
21
  * returns is already current. Nothing but landing reaches in.
22
22
  */
23
- import { ATTR_CELL, ATTR_KEY, ATTR_KEY_VALUE, ATTR_LIST, ATTR_ROW, ATTR_ROW_COUNT, ATTR_SHOW, ATTR_VALUE, } from "../core/lb-constants.js";
23
+ import { ATTR_CELL, ATTR_KEY, ATTR_KEY_VALUE, ATTR_LIST, ATTR_QUERY_PARM, ATTR_ROW, ATTR_ROW_COUNT, ATTR_SHOW, ATTR_VALUE, } from "../core/lb-constants.js";
24
24
  import { isPatch, } from "../core/lb-types.js";
25
25
  /** The template holding an absent branch — see ATTR_SHOW. */
26
26
  const PARKED = `template[${ATTR_SHOW}]`;
@@ -358,4 +358,18 @@ export function applyData(root, data) {
358
358
  applyRow(scope, result);
359
359
  }
360
360
  }
361
+ /**
362
+ * Land the query string on every control that writes one of its parms, the
363
+ * way a cell lands a column. A parm the URL does not carry lands empty, so a
364
+ * control shows what the address bar says after Back as well as after a
365
+ * reload: the URL is what is on screen, and nothing else is.
366
+ *
367
+ * Landed after the page's data, so a picker already has the options its
368
+ * value selects.
369
+ */
370
+ export function applyQueryParms(root, parms) {
371
+ for (const el of everywhere(root, `[${ATTR_QUERY_PARM}]`)) {
372
+ land(el, parms.get(el.getAttribute(ATTR_QUERY_PARM)) ?? "");
373
+ }
374
+ }
361
375
  //# sourceMappingURL=lb-apply.js.map
@@ -1 +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 * A condition is the one other thing landing moves. An element whose\n * `lb-show` column is off stands in the document inside a template, and\n * landing reaches in there as it reaches everywhere else, so a branch that\n * returns is already current. Nothing but landing reaches in.\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_SHOW,\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 template holding an absent branch — see ATTR_SHOW. */\nconst PARKED = `template[${ATTR_SHOW}]`;\n\n/** A DocumentFragment's nodeType. Tier 3 has no `Node` global to read it from. */\nconst FRAGMENT_NODE = 11;\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 carries one. The\n * element's own attribute does not count: it names what the element\n * displays, and where it belongs is its ancestors', so a picker carrying\n * `lb-list` and `lb-cell` is a cell of the row around it. Root's own\n * attribute does not count either: root is the scope being filled, whatever\n * it carries.\n *\n * Root may be the content of a template holding an absent branch. The walk\n * up from an element inside it stops at the top of that content, which has\n * no parent, and the template it stands for was in scope already.\n */\nfunction inScope(root: Element | DocumentFragment, el: Element): boolean {\n for (let node = el.parentElement; 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/**\n * Root if it matches, then every descendant in root's scope that does.\n * Gathering finds cells through here. It does not reach into an absent\n * branch, so a form reads back from the places a row lands that are showing.\n */\nexport function 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 * Every descendant in root's scope that matches, absent branches included.\n *\n * `querySelectorAll` does not enter template content, so an element that is\n * off would otherwise miss everything landed while it was away, and return\n * stale or with its lists empty. A template holding one is in root's scope by\n * the ordinary rule, and its content is searched as if it stood there.\n */\nfunction below(root: Element | DocumentFragment, selector: string): Element[] {\n const found: Element[] = [];\n for (const el of root.querySelectorAll(selector)) {\n if (inScope(root, el)) found.push(el);\n }\n for (const parked of root.querySelectorAll(PARKED)) {\n if (inScope(root, parked)) {\n found.push(...below((parked as HTMLTemplateElement).content, selector));\n }\n }\n return found;\n}\n\n/** What `within` is for gathering, `reach` is for landing. */\nfunction reach(root: Element, selector: string): Element[] {\n const found = below(root, selector);\n return root.matches(selector) ? [root, ...found] : found;\n}\n\n/**\n * Every element in a whole response's root that matches, absent branches\n * included. A name is looked up this way before any scope is known, so\n * nothing is filtered out by scope.\n */\nfunction everywhere(root: ParentNode, selector: string): Element[] {\n const found = [...root.querySelectorAll(selector)];\n for (const parked of root.querySelectorAll(PARKED)) {\n const content = (parked as HTMLTemplateElement).content;\n found.push(...everywhere(content, selector));\n }\n return found;\n}\n\n/**\n * Put an element in the document or take it out, as its `lb-show` column\n * says.\n *\n * Off is inside a template that stands where the element stood, carrying the\n * same `lb-show`. The element is moved rather than rebuilt, so a widget keeps\n * its instance and a control keeps what was typed into it. On moves it back\n * and the template goes.\n *\n * Landing reaches into that template, so both it and the element it holds\n * arrive here. The template decides; the element it holds, whose parent is\n * the template's content, is left to it.\n */\nfunction present(el: Element, on: boolean): void {\n if (el.localName === \"template\") {\n const branch = (el as HTMLTemplateElement).content.firstElementChild;\n if (on && branch) el.replaceWith(branch);\n return;\n }\n if (on || el.parentNode?.nodeType === FRAGMENT_NODE) return;\n const parked = el.ownerDocument.createElement(\"template\");\n parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW)!);\n el.replaceWith(parked);\n parked.content.append(el);\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 reach(root, `[${ATTR_CELL}=\"${column}\"]`)) {\n land(el, value);\n }\n // Only null and false are off. A string is never read, so \"false\" is on,\n // and the query spells a condition as a boolean or a null.\n //\n // Never the root's own: a scope's condition is a cell of the row around\n // it, the way its lb-cell is, and a row template's root carries none.\n const on = value !== null && value !== false;\n for (const el of below(root, `[${ATTR_SHOW}=\"${column}\"]`)) {\n present(el, on);\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, and a template holding an absent branch is not a row\n * template: rows land before the row template, so one inside the first live\n * row comes first in document order.\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:not([${ATTR_SHOW}])`)[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, which is the same\n * test a cell gets.\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)) {\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 = everywhere(root, `[${ATTR_LIST}=\"${name}\"]`);\n const rows = everywhere(root, `[${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
+ {"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 * A condition is the one other thing landing moves. An element whose\n * `lb-show` column is off stands in the document inside a template, and\n * landing reaches in there as it reaches everywhere else, so a branch that\n * returns is already current. Nothing but landing reaches in.\n */\n\nimport {\n ATTR_CELL,\n ATTR_KEY,\n ATTR_KEY_VALUE,\n ATTR_LIST,\n ATTR_QUERY_PARM,\n ATTR_ROW,\n ATTR_ROW_COUNT,\n ATTR_SHOW,\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 template holding an absent branch — see ATTR_SHOW. */\nconst PARKED = `template[${ATTR_SHOW}]`;\n\n/** A DocumentFragment's nodeType. Tier 3 has no `Node` global to read it from. */\nconst FRAGMENT_NODE = 11;\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 carries one. The\n * element's own attribute does not count: it names what the element\n * displays, and where it belongs is its ancestors', so a picker carrying\n * `lb-list` and `lb-cell` is a cell of the row around it. Root's own\n * attribute does not count either: root is the scope being filled, whatever\n * it carries.\n *\n * Root may be the content of a template holding an absent branch. The walk\n * up from an element inside it stops at the top of that content, which has\n * no parent, and the template it stands for was in scope already.\n */\nfunction inScope(root: Element | DocumentFragment, el: Element): boolean {\n for (let node = el.parentElement; 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/**\n * Root if it matches, then every descendant in root's scope that does.\n * Gathering finds cells through here. It does not reach into an absent\n * branch, so a form reads back from the places a row lands that are showing.\n */\nexport function 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 * Every descendant in root's scope that matches, absent branches included.\n *\n * `querySelectorAll` does not enter template content, so an element that is\n * off would otherwise miss everything landed while it was away, and return\n * stale or with its lists empty. A template holding one is in root's scope by\n * the ordinary rule, and its content is searched as if it stood there.\n */\nfunction below(root: Element | DocumentFragment, selector: string): Element[] {\n const found: Element[] = [];\n for (const el of root.querySelectorAll(selector)) {\n if (inScope(root, el)) found.push(el);\n }\n for (const parked of root.querySelectorAll(PARKED)) {\n if (inScope(root, parked)) {\n found.push(...below((parked as HTMLTemplateElement).content, selector));\n }\n }\n return found;\n}\n\n/** What `within` is for gathering, `reach` is for landing. */\nfunction reach(root: Element, selector: string): Element[] {\n const found = below(root, selector);\n return root.matches(selector) ? [root, ...found] : found;\n}\n\n/**\n * Every element in a whole response's root that matches, absent branches\n * included. A name is looked up this way before any scope is known, so\n * nothing is filtered out by scope.\n */\nfunction everywhere(root: ParentNode, selector: string): Element[] {\n const found = [...root.querySelectorAll(selector)];\n for (const parked of root.querySelectorAll(PARKED)) {\n const content = (parked as HTMLTemplateElement).content;\n found.push(...everywhere(content, selector));\n }\n return found;\n}\n\n/**\n * Put an element in the document or take it out, as its `lb-show` column\n * says.\n *\n * Off is inside a template that stands where the element stood, carrying the\n * same `lb-show`. The element is moved rather than rebuilt, so a widget keeps\n * its instance and a control keeps what was typed into it. On moves it back\n * and the template goes.\n *\n * Landing reaches into that template, so both it and the element it holds\n * arrive here. The template decides; the element it holds, whose parent is\n * the template's content, is left to it.\n */\nfunction present(el: Element, on: boolean): void {\n if (el.localName === \"template\") {\n const branch = (el as HTMLTemplateElement).content.firstElementChild;\n if (on && branch) el.replaceWith(branch);\n return;\n }\n if (on || el.parentNode?.nodeType === FRAGMENT_NODE) return;\n const parked = el.ownerDocument.createElement(\"template\");\n parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW)!);\n el.replaceWith(parked);\n parked.content.append(el);\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 reach(root, `[${ATTR_CELL}=\"${column}\"]`)) {\n land(el, value);\n }\n // Only null and false are off. A string is never read, so \"false\" is on,\n // and the query spells a condition as a boolean or a null.\n //\n // Never the root's own: a scope's condition is a cell of the row around\n // it, the way its lb-cell is, and a row template's root carries none.\n const on = value !== null && value !== false;\n for (const el of below(root, `[${ATTR_SHOW}=\"${column}\"]`)) {\n present(el, on);\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, and a template holding an absent branch is not a row\n * template: rows land before the row template, so one inside the first live\n * row comes first in document order.\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:not([${ATTR_SHOW}])`)[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, which is the same\n * test a cell gets.\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)) {\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 = everywhere(root, `[${ATTR_LIST}=\"${name}\"]`);\n const rows = everywhere(root, `[${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\n/**\n * Land the query string on every control that writes one of its parms, the\n * way a cell lands a column. A parm the URL does not carry lands empty, so a\n * control shows what the address bar says after Back as well as after a\n * reload: the URL is what is on screen, and nothing else is.\n *\n * Landed after the page's data, so a picker already has the options its\n * value selects.\n */\nexport function applyQueryParms(\n root: ParentNode,\n parms: URLSearchParams,\n): void {\n for (const el of everywhere(root, `[${ATTR_QUERY_PARM}]`)) {\n land(el, parms.get(el.getAttribute(ATTR_QUERY_PARM)!) ?? \"\");\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"lb-hub.browser.d.ts","sourceRoot":"","sources":["../../hub/lb-hub.browser.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"lb-hub.browser.d.ts","sourceRoot":"","sources":["../../hub/lb-hub.browser.ts"],"names":[],"mappings":"AA0CA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}