@loadbare/widgets 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,6 @@ Inventory:
8
8
  | --- | --- |
9
9
  | [`lb-input`](docs/lb-input.md) | an editable cell wrapping an `<input>` |
10
10
  | [`lb-select`](docs/lb-select.md) | a `<select>` whose options the page writes |
11
- | [`lb-list`](docs/lb-list.md) | the plain repeater |
12
11
  | [`lb-options`](docs/lb-options.md) | a `<select>` whose options come from a query |
13
12
  | [`lb-picker`](docs/lb-picker.md) | `lb-options` with the row template built in |
14
13
  | [`lb-table`](docs/lb-table.md) | a `<table>` supplying its own scaffolding |
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import "./lb-input.browser.js";
2
2
  import "./lb-select.browser.js";
3
- import "./lb-list.browser.js";
4
3
  import "./lb-options.browser.js";
5
4
  import "./lb-table.browser.js";
6
5
  import "./lb-picker.browser.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../widgets/index.ts"],"names":[],"mappings":"AAAA,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,sBAAsB,CAAC;AAC9B,OAAO,yBAAyB,CAAC;AACjC,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../widgets/index.ts"],"names":[],"mappings":"AAAA,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,yBAAyB,CAAC;AACjC,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import "./lb-input.browser.js";
2
2
  import "./lb-select.browser.js";
3
- import "./lb-list.browser.js";
4
3
  import "./lb-options.browser.js";
5
4
  import "./lb-table.browser.js";
6
5
  import "./lb-picker.browser.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../widgets/index.ts"],"names":[],"mappings":"AAAA,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,sBAAsB,CAAC;AAC9B,OAAO,yBAAyB,CAAC;AACjC,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC","sourcesContent":["import \"./lb-input.browser.js\";\nimport \"./lb-select.browser.js\";\nimport \"./lb-list.browser.js\";\nimport \"./lb-options.browser.js\";\nimport \"./lb-table.browser.js\";\nimport \"./lb-picker.browser.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../widgets/index.ts"],"names":[],"mappings":"AAAA,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,yBAAyB,CAAC;AACjC,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC","sourcesContent":["import \"./lb-input.browser.js\";\nimport \"./lb-select.browser.js\";\nimport \"./lb-options.browser.js\";\nimport \"./lb-table.browser.js\";\nimport \"./lb-picker.browser.js\";\n"]}
@@ -1,19 +1,18 @@
1
1
  /// <reference lib="dom" />
2
- import { ATTR_CELL, ATTR_KEY, ATTR_QUERY, ATTR_VALUE, LB_EVENT_NAME, } from "@loadbare/app/constants";
3
- /** The page asks for a request. A widget that was not asked stays quiet. */
4
- const FIRE_ON_CHANGE = "data-fire-on-change";
2
+ import { ATTR_ACTION, ATTR_VALUE, LB_EVENT_NAME, } from "@loadbare/app/constants";
5
3
  /**
6
4
  * A leaf cell wrapping an <input>. The hub writes one attribute name for
7
5
  * every widget type, so the value lands in `lb-value` and the widget
8
6
  * forwards it to the control it owns.
9
7
  *
10
- * The other direction of the same cell: `data-fire-on-change` asks for a
11
- * `cell-change` on the input's own `change` event, bound by the same
12
- * lb-query/lb-key/lb-cell the value arrived on.
8
+ * The other direction of the same cell: `lb-action` names what a change
9
+ * sends, carrying the input's value. The hub scopes it like any request, so
10
+ * the reserved `lb-cell-change` reaches the server with the list, key and
11
+ * cell this input sits in. Without `lb-action` a change sends nothing.
13
12
  *
14
- * The page has to ask, because committing per field is one editing model
15
- * and not the only one. An input inside an `lb-insert` or `lb-update` form
16
- * is read again by the form on submit, so a widget that sent on its own
13
+ * The developer has to ask, because committing per field is one editing model
14
+ * and not the only one. An input inside an `lb-row-insert` or `lb-row-update`
15
+ * form is read again by the form on submit, so a widget that sent on its own
17
16
  * would write the same edit twice and defeat the batch the form is for.
18
17
  */
19
18
  class LbInput extends HTMLElement {
@@ -28,27 +27,15 @@ class LbInput extends HTMLElement {
28
27
  }
29
28
  connectedCallback() {
30
29
  this.addEventListener("change", () => {
31
- if (!this.hasAttribute(FIRE_ON_CHANGE))
30
+ const action = this.getAttribute(ATTR_ACTION);
31
+ if (!action)
32
32
  return;
33
33
  const input = this.querySelector("input");
34
34
  if (!input) {
35
35
  console.error(`lb-input: change with no <input>, ignoring`);
36
36
  return;
37
37
  }
38
- const query = this.closest(`[${ATTR_QUERY}]`)?.getAttribute(ATTR_QUERY);
39
- const key = this.closest(`[${ATTR_KEY}]`)?.getAttribute(ATTR_KEY);
40
- const cell = this.getAttribute(ATTR_CELL);
41
- if (!query || !key || !cell) {
42
- console.error(`lb-input: change with no query/key/cell binding, ignoring`);
43
- return;
44
- }
45
- const detail = {
46
- op: "cell-change",
47
- query,
48
- key,
49
- cell,
50
- value: input.value,
51
- };
38
+ const detail = { action, value: input.value };
52
39
  this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
53
40
  });
54
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lb-input.browser.js","sourceRoot":"","sources":["../widgets/lb-input.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EACL,SAAS,EACT,QAAQ,EACR,UAAU,EACV,UAAU,EACV,aAAa,GACd,MAAM,yBAAyB,CAAC;AAGjC,4EAA4E;AAC5E,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AAEH,MAAM,OAAQ,SAAQ,WAAW;IAC/B,MAAM,CAAC,kBAAkB,GAAG,CAAC,UAAU,CAAC,CAAC;IAEzC,wBAAwB,CAAC,KAAa,EAAE,IAAY,EAAE,KAAa;QACjE,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QACD,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;YACnC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC;gBAAE,OAAO;YAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;gBAC5D,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,UAAU,GAAG,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC;YACxE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,QAAQ,GAAG,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;YAClE,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;YAC1C,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC5B,OAAO,CAAC,KAAK,CACX,2DAA2D,CAC5D,CAAC;gBACF,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAe;gBACzB,EAAE,EAAE,aAAa;gBACjB,KAAK;gBACL,GAAG;gBACH,IAAI;gBACJ,KAAK,EAAE,KAAK,CAAC,KAAK;aACnB,CAAC;YACF,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAC1D,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;;AAGH,cAAc,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_CELL,\n ATTR_KEY,\n ATTR_QUERY,\n ATTR_VALUE,\n LB_EVENT_NAME,\n} from \"@loadbare/app/constants\";\nimport type { HubRequest } from \"@loadbare/app/types\";\n\n/** The page asks for a request. A widget that was not asked stays quiet. */\nconst FIRE_ON_CHANGE = \"data-fire-on-change\";\n\n/**\n * A leaf cell wrapping an <input>. The hub writes one attribute name for\n * every widget type, so the value lands in `lb-value` and the widget\n * forwards it to the control it owns.\n *\n * The other direction of the same cell: `data-fire-on-change` asks for a\n * `cell-change` on the input's own `change` event, bound by the same\n * lb-query/lb-key/lb-cell the value arrived on.\n *\n * The page has to ask, because committing per field is one editing model\n * and not the only one. An input inside an `lb-insert` or `lb-update` form\n * is read again by the form on submit, so a widget that sent on its own\n * would write the same edit twice and defeat the batch the form is for.\n */\n\nclass LbInput extends HTMLElement {\n static observedAttributes = [ATTR_VALUE];\n\n attributeChangedCallback(_name: string, _old: string, value: string) {\n const input = this.querySelector(\"input\");\n if (!input) {\n console.error(`lb-input: no <input> to receive the value`);\n return;\n }\n input.value = value;\n }\n\n connectedCallback() {\n this.addEventListener(\"change\", () => {\n if (!this.hasAttribute(FIRE_ON_CHANGE)) return;\n const input = this.querySelector(\"input\");\n if (!input) {\n console.error(`lb-input: change with no <input>, ignoring`);\n return;\n }\n const query = this.closest(`[${ATTR_QUERY}]`)?.getAttribute(ATTR_QUERY);\n const key = this.closest(`[${ATTR_KEY}]`)?.getAttribute(ATTR_KEY);\n const cell = this.getAttribute(ATTR_CELL);\n if (!query || !key || !cell) {\n console.error(\n `lb-input: change with no query/key/cell binding, ignoring`,\n );\n return;\n }\n const detail: HubRequest = {\n op: \"cell-change\",\n query,\n key,\n cell,\n value: input.value,\n };\n this.dispatchEvent(\n new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }),\n );\n });\n }\n}\n\ncustomElements.define(\"lb-input\", LbInput);\n"]}
1
+ {"version":3,"file":"lb-input.browser.js","sourceRoot":"","sources":["../widgets/lb-input.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EACL,WAAW,EACX,UAAU,EACV,aAAa,GACd,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;;GAcG;AAEH,MAAM,OAAQ,SAAQ,WAAW;IAC/B,MAAM,CAAC,kBAAkB,GAAG,CAAC,UAAU,CAAC,CAAC;IAEzC,wBAAwB,CAAC,KAAa,EAAE,IAAY,EAAE,KAAa;QACjE,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QACD,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;YACnC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;gBAC5D,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAe,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;YAC1D,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAC1D,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;;AAGH,cAAc,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_ACTION,\n ATTR_VALUE,\n LB_EVENT_NAME,\n} from \"@loadbare/app/constants\";\nimport type { HubRequest } from \"@loadbare/app/types\";\n\n/**\n * A leaf cell wrapping an <input>. The hub writes one attribute name for\n * every widget type, so the value lands in `lb-value` and the widget\n * forwards it to the control it owns.\n *\n * The other direction of the same cell: `lb-action` names what a change\n * sends, carrying the input's value. The hub scopes it like any request, so\n * the reserved `lb-cell-change` reaches the server with the list, key and\n * cell this input sits in. Without `lb-action` a change sends nothing.\n *\n * The developer has to ask, because committing per field is one editing model\n * and not the only one. An input inside an `lb-row-insert` or `lb-row-update`\n * form is read again by the form on submit, so a widget that sent on its own\n * would write the same edit twice and defeat the batch the form is for.\n */\n\nclass LbInput extends HTMLElement {\n static observedAttributes = [ATTR_VALUE];\n\n attributeChangedCallback(_name: string, _old: string, value: string) {\n const input = this.querySelector(\"input\");\n if (!input) {\n console.error(`lb-input: no <input> to receive the value`);\n return;\n }\n input.value = value;\n }\n\n connectedCallback() {\n this.addEventListener(\"change\", () => {\n const action = this.getAttribute(ATTR_ACTION);\n if (!action) return;\n const input = this.querySelector(\"input\");\n if (!input) {\n console.error(`lb-input: change with no <input>, ignoring`);\n return;\n }\n const detail: HubRequest = { action, value: input.value };\n this.dispatchEvent(\n new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }),\n );\n });\n }\n}\n\ncustomElements.define(\"lb-input\", LbInput);\n"]}
@@ -1,24 +1,12 @@
1
- import type { Projection, HubRowHost } from "@loadbare/app/types";
2
- /**
3
- * A `<select>` whose choices come from a query, and the case that shows why
4
- * placement belongs to the widget rather than to the framework.
5
- *
6
- * The rows arrive flat and keyed. Nothing on the wire knows about
7
- * `<optgroup>`, and nothing needs to: a group is a column like any other, and
8
- * turning a repeated column value into an element is exactly the local
9
- * knowledge a widget has and the hub does not. A table wanting section
10
- * headings would read the same `lb-group` and build something else.
11
- *
12
- * The identity of a row is the value of the option, so `lb-key` supplies
13
- * both and the page declares it once.
14
- *
15
- * Exported because `lb-picker` is this widget with its row template
16
- * supplied by the definition instead of by the page. Nothing about the
17
- * behavior changes, so nothing about the behavior is written twice.
18
- */
19
- export declare class LbOptions extends HTMLElement implements HubRowHost {
20
- acceptRows(result: Projection): void;
21
- private place;
1
+ import type { ListHost, Row } from "@loadbare/app/types";
2
+ export declare class LbOptions extends HTMLElement implements ListHost {
3
+ /**
4
+ * The hub reconciles the list and calls this for placement only, so this
5
+ * widget carries no copy of what a row is or when one arrives.
6
+ */
7
+ lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement): void;
8
+ /** Everything derived from the rows, once the whole result has landed. */
9
+ lbRowsLanded(): void;
22
10
  /** The group with this label, or a new one in front of the template. */
23
11
  private groupFor;
24
12
  connectedCallback(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"lb-options.browser.d.ts","sourceRoot":"","sources":["../widgets/lb-options.browser.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAc,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAG9E;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,SAAU,SAAQ,WAAY,YAAW,UAAU;IAC9D,UAAU,CAAC,MAAM,EAAE,UAAU;IAa7B,OAAO,CAAC,KAAK,CAQX;IAEF,wEAAwE;IACxE,OAAO,CAAC,QAAQ;IAUhB,iBAAiB;CAkBlB"}
1
+ {"version":3,"file":"lb-options.browser.d.ts","sourceRoot":"","sources":["../widgets/lb-options.browser.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAc,QAAQ,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAyBrE,qBAAa,SAAU,SAAQ,WAAY,YAAW,QAAQ;IAC5D;;;OAGG;IACH,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAUtE,0EAA0E;IAC1E,YAAY,IAAI,IAAI;IAYpB,wEAAwE;IACxE,OAAO,CAAC,QAAQ;IAUhB,iBAAiB;CAmBlB"}
@@ -1,30 +1,47 @@
1
1
  /// <reference lib="dom" />
2
- import { ATTR_ACTION, ATTR_GROUP, ATTR_KEY, LB_EVENT_NAME, } from "@loadbare/app/constants";
3
- import { applyRows } from "@loadbare/app/rows";
2
+ import { ATTR_ACTION, ATTR_KEY_VALUE, LB_EVENT_NAME, } from "@loadbare/app/constants";
4
3
  /**
5
- * A `<select>` whose choices come from a query, and the case that shows why
4
+ * A `<select>` whose choices come from a list, and the case that shows why
6
5
  * placement belongs to the widget rather than to the framework.
7
6
  *
8
7
  * The rows arrive flat and keyed. Nothing on the wire knows about
9
8
  * `<optgroup>`, and nothing needs to: a group is a column like any other, and
10
9
  * turning a repeated column value into an element is exactly the local
11
- * knowledge a widget has and the hub does not. A table wanting section
12
- * headings would read the same `lb-group` and build something else.
10
+ * knowledge a widget has and the hub does not. So the attribute that names
11
+ * the column is this widget's own, in HTML's `data-` namespace rather than
12
+ * the hub's: the hub never reads it, and `lb-` is reserved for what the hub
13
+ * does read.
13
14
  *
14
- * The identity of a row is the value of the option, so `lb-key` supplies
15
- * both and the page declares it once.
15
+ * The identity of a row is the value of the option, so the `lb-key-value`
16
+ * the hub stamps on each option is copied to its `value`, and the developer
17
+ * names the key column once, with `lb-key` on the template.
16
18
  *
17
19
  * Exported because `lb-picker` is this widget with its row template
18
20
  * supplied by the definition instead of by the page. Nothing about the
19
21
  * behavior changes, so nothing about the behavior is written twice.
20
22
  */
23
+ /** Named on the row template: the column whose distinct values become groups. */
24
+ const GROUP_COLUMN = "data-group";
21
25
  export class LbOptions extends HTMLElement {
22
- acceptRows(result) {
23
- applyRows(this, result, this.place);
26
+ /**
27
+ * The hub reconciles the list and calls this for placement only, so this
28
+ * widget carries no copy of what a row is or when one arrives.
29
+ */
30
+ lbPlaceRow(el, row, template) {
31
+ const select = this.querySelector("select");
32
+ const groupColumn = template.getAttribute(GROUP_COLUMN);
33
+ if (!groupColumn) {
34
+ select.insertBefore(el, template);
35
+ return;
36
+ }
37
+ this.groupFor(select, template, String(row[groupColumn] ?? "")).append(el);
38
+ }
39
+ /** Everything derived from the rows, once the whole result has landed. */
40
+ lbRowsLanded() {
24
41
  // The identity of the row is the value of the option, so nothing on the
25
42
  // page has to say so twice.
26
- for (const option of this.querySelectorAll(`option[${ATTR_KEY}]`)) {
27
- option.setAttribute("value", option.getAttribute(ATTR_KEY));
43
+ for (const option of this.querySelectorAll(`option[${ATTR_KEY_VALUE}]`)) {
44
+ option.setAttribute("value", option.getAttribute(ATTR_KEY_VALUE));
28
45
  }
29
46
  // A group is derived, so it goes when its last row does.
30
47
  for (const group of this.querySelectorAll("optgroup")) {
@@ -32,15 +49,6 @@ export class LbOptions extends HTMLElement {
32
49
  group.remove();
33
50
  }
34
51
  }
35
- place = (row, tuple, template) => {
36
- const select = this.querySelector("select");
37
- const groupCell = template.getAttribute(ATTR_GROUP);
38
- if (!groupCell) {
39
- select.insertBefore(row, template);
40
- return;
41
- }
42
- this.groupFor(select, template, tuple[groupCell] ?? "").append(row);
43
- };
44
52
  /** The group with this label, or a new one in front of the template. */
45
53
  groupFor(select, template, label) {
46
54
  for (const group of select.querySelectorAll("optgroup")) {
@@ -64,7 +72,8 @@ export class LbOptions extends HTMLElement {
64
72
  console.error(`lb-options: no ${ATTR_ACTION}, nothing to send`);
65
73
  return;
66
74
  }
67
- const detail = { op: "action", name, value: select.value };
75
+ // The hub scopes it, so a choice inside a row says which row.
76
+ const detail = { action: name, value: select.value };
68
77
  this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
69
78
  });
70
79
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lb-options.browser.js","sourceRoot":"","sources":["../widgets/lb-options.browser.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_ACTION,\n ATTR_GROUP,\n ATTR_KEY,\n LB_EVENT_NAME,\n} from \"@loadbare/app/constants\";\nimport type { Projection, HubRequest, HubRowHost } from \"@loadbare/app/types\";\nimport { applyRows, type Place } from \"@loadbare/app/rows\";\n\n/**\n * A `<select>` whose choices come from a query, and the case that shows why\n * placement belongs to the widget rather than to the framework.\n *\n * The rows arrive flat and keyed. Nothing on the wire knows about\n * `<optgroup>`, and nothing needs to: a group is a column like any other, and\n * turning a repeated column value into an element is exactly the local\n * knowledge a widget has and the hub does not. A table wanting section\n * headings would read the same `lb-group` and build something else.\n *\n * The identity of a row is the value of the option, so `lb-key` supplies\n * both and the page declares it once.\n *\n * Exported because `lb-picker` is this widget with its row template\n * supplied by the definition instead of by the page. Nothing about the\n * behavior changes, so nothing about the behavior is written twice.\n */\nexport class LbOptions extends HTMLElement implements HubRowHost {\n acceptRows(result: Projection) {\n applyRows(this, result, this.place);\n // The identity of the row is the value of the option, so nothing on the\n // page has to say so twice.\n for (const option of this.querySelectorAll(`option[${ATTR_KEY}]`)) {\n option.setAttribute(\"value\", option.getAttribute(ATTR_KEY)!);\n }\n // A group is derived, so it goes when its last row does.\n for (const group of this.querySelectorAll(\"optgroup\")) {\n if (!group.querySelector(\"option\")) group.remove();\n }\n }\n\n private place: Place = (row, tuple, template) => {\n const select = this.querySelector(\"select\")!;\n const groupCell = template.getAttribute(ATTR_GROUP);\n if (!groupCell) {\n select.insertBefore(row, template);\n return;\n }\n this.groupFor(select, template, tuple[groupCell] ?? \"\").append(row);\n };\n\n /** The group with this label, or a new one in front of the template. */\n private groupFor(select: Element, template: Element, label: string): Element {\n for (const group of select.querySelectorAll(\"optgroup\")) {\n if (group.label === label) return group;\n }\n const group = document.createElement(\"optgroup\");\n group.label = label;\n select.insertBefore(group, template);\n return group;\n }\n\n connectedCallback() {\n this.addEventListener(\"change\", () => {\n const select = this.querySelector(\"select\");\n if (!select) {\n console.error(`lb-options: change with no <select>, ignoring`);\n return;\n }\n const name = this.getAttribute(ATTR_ACTION);\n if (!name) {\n console.error(`lb-options: no ${ATTR_ACTION}, nothing to send`);\n return;\n }\n const detail: HubRequest = { op: \"action\", name, value: select.value };\n this.dispatchEvent(\n new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }),\n );\n });\n }\n}\n\ncustomElements.define(\"lb-options\", LbOptions);\n"]}
1
+ {"version":3,"file":"lb-options.browser.js","sourceRoot":"","sources":["../widgets/lb-options.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EACL,WAAW,EACX,cAAc,EACd,aAAa,GACd,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,iFAAiF;AACjF,MAAM,YAAY,GAAG,YAAY,CAAC;AAElC,MAAM,OAAO,SAAU,SAAQ,WAAW;IACxC;;;OAGG;IACH,UAAU,CAAC,EAAW,EAAE,GAAQ,EAAE,QAA6B;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAE,CAAC;QAC7C,MAAM,WAAW,GAAG,QAAQ,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QACxD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,CAAC,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;YAClC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,0EAA0E;IAC1E,YAAY;QACV,wEAAwE;QACxE,4BAA4B;QAC5B,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC,UAAU,cAAc,GAAG,CAAC,EAAE,CAAC;YACxE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,YAAY,CAAC,cAAc,CAAE,CAAC,CAAC;QACrE,CAAC;QACD,yDAAyD;QACzD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC;gBAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QACrD,CAAC;IACH,CAAC;IAED,wEAAwE;IAChE,QAAQ,CAAC,MAAe,EAAE,QAAiB,EAAE,KAAa;QAChE,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;YACxD,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1C,CAAC;QACD,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACjD,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;QACpB,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;YACnC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;gBAC/D,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,CAAC,KAAK,CAAC,kBAAkB,WAAW,mBAAmB,CAAC,CAAC;gBAChE,OAAO;YACT,CAAC;YACD,8DAA8D;YAC9D,MAAM,MAAM,GAAe,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YACjE,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAC1D,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,cAAc,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_ACTION,\n ATTR_KEY_VALUE,\n LB_EVENT_NAME,\n} from \"@loadbare/app/constants\";\nimport type { HubRequest, ListHost, Row } from \"@loadbare/app/types\";\n\n/**\n * A `<select>` whose choices come from a list, and the case that shows why\n * placement belongs to the widget rather than to the framework.\n *\n * The rows arrive flat and keyed. Nothing on the wire knows about\n * `<optgroup>`, and nothing needs to: a group is a column like any other, and\n * turning a repeated column value into an element is exactly the local\n * knowledge a widget has and the hub does not. So the attribute that names\n * the column is this widget's own, in HTML's `data-` namespace rather than\n * the hub's: the hub never reads it, and `lb-` is reserved for what the hub\n * does read.\n *\n * The identity of a row is the value of the option, so the `lb-key-value`\n * the hub stamps on each option is copied to its `value`, and the developer\n * names the key column once, with `lb-key` on the template.\n *\n * Exported because `lb-picker` is this widget with its row template\n * supplied by the definition instead of by the page. Nothing about the\n * behavior changes, so nothing about the behavior is written twice.\n */\n/** Named on the row template: the column whose distinct values become groups. */\nconst GROUP_COLUMN = \"data-group\";\n\nexport class LbOptions extends HTMLElement implements ListHost {\n /**\n * The hub reconciles the list and calls this for placement only, so this\n * widget carries no copy of what a row is or when one arrives.\n */\n lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement): void {\n const select = this.querySelector(\"select\")!;\n const groupColumn = template.getAttribute(GROUP_COLUMN);\n if (!groupColumn) {\n select.insertBefore(el, template);\n return;\n }\n this.groupFor(select, template, String(row[groupColumn] ?? \"\")).append(el);\n }\n\n /** Everything derived from the rows, once the whole result has landed. */\n lbRowsLanded(): void {\n // The identity of the row is the value of the option, so nothing on the\n // page has to say so twice.\n for (const option of this.querySelectorAll(`option[${ATTR_KEY_VALUE}]`)) {\n option.setAttribute(\"value\", option.getAttribute(ATTR_KEY_VALUE)!);\n }\n // A group is derived, so it goes when its last row does.\n for (const group of this.querySelectorAll(\"optgroup\")) {\n if (!group.querySelector(\"option\")) group.remove();\n }\n }\n\n /** The group with this label, or a new one in front of the template. */\n private groupFor(select: Element, template: Element, label: string): Element {\n for (const group of select.querySelectorAll(\"optgroup\")) {\n if (group.label === label) return group;\n }\n const group = document.createElement(\"optgroup\");\n group.label = label;\n select.insertBefore(group, template);\n return group;\n }\n\n connectedCallback() {\n this.addEventListener(\"change\", () => {\n const select = this.querySelector(\"select\");\n if (!select) {\n console.error(`lb-options: change with no <select>, ignoring`);\n return;\n }\n const name = this.getAttribute(ATTR_ACTION);\n if (!name) {\n console.error(`lb-options: no ${ATTR_ACTION}, nothing to send`);\n return;\n }\n // The hub scopes it, so a choice inside a row says which row.\n const detail: HubRequest = { action: name, value: select.value };\n this.dispatchEvent(\n new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }),\n );\n });\n }\n}\n\ncustomElements.define(\"lb-options\", LbOptions);\n"]}
@@ -1,7 +1,7 @@
1
1
  <label
2
2
  >{{label}}
3
3
  <select>
4
- <template lb-key="{{key}}" lb-group="{{group}}">
4
+ <template lb-key="{{key}}" data-group="{{group}}">
5
5
  <option lb-cell="{{cell}}"></option>
6
6
  </template></select
7
7
  ></label>
@@ -2,9 +2,9 @@
2
2
  import { ATTR_ACTION, ATTR_VALUE, LB_EVENT_NAME, } from "@loadbare/app/constants";
3
3
  /**
4
4
  * A leaf cell wrapping a <select>. It receives its value like any other cell,
5
- * and on change it sends the action the page declared for it — the request
6
- * vocabulary and the binding vocabulary running in both directions on one
7
- * widget.
5
+ * and on change it sends the action the developer declared for it — the
6
+ * request vocabulary and the binding vocabulary running in both directions on
7
+ * one widget.
8
8
  *
9
9
  * The choice is the interaction, so this is the case where an action carries
10
10
  * a value. The server still decides what that choice means.
@@ -31,11 +31,8 @@ class LbSelect extends HTMLElement {
31
31
  console.error(`lb-select: no ${ATTR_ACTION}, nothing to send`);
32
32
  return;
33
33
  }
34
- const detail = {
35
- op: "action",
36
- name,
37
- value: select.value,
38
- };
34
+ // The hub scopes it, so a choice inside a row says which row.
35
+ const detail = { action: name, value: select.value };
39
36
  this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
40
37
  });
41
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lb-select.browser.js","sourceRoot":"","sources":["../widgets/lb-select.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EACL,WAAW,EACX,UAAU,EACV,aAAa,GACd,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;GAQG;AACH,MAAM,QAAS,SAAQ,WAAW;IAChC,MAAM,CAAC,kBAAkB,GAAG,CAAC,UAAU,CAAC,CAAC;IAEzC,wBAAwB,CAAC,KAAa,EAAE,IAAY,EAAE,KAAa;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;QACD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;YACnC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;gBAC9D,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,CAAC,KAAK,CAAC,iBAAiB,WAAW,mBAAmB,CAAC,CAAC;gBAC/D,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAe;gBACzB,EAAE,EAAE,QAAQ;gBACZ,IAAI;gBACJ,KAAK,EAAE,MAAM,CAAC,KAAK;aACpB,CAAC;YACF,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAC1D,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;;AAGH,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_ACTION,\n ATTR_VALUE,\n LB_EVENT_NAME,\n} from \"@loadbare/app/constants\";\nimport type { HubRequest } from \"@loadbare/app/types\";\n\n/**\n * A leaf cell wrapping a <select>. It receives its value like any other cell,\n * and on change it sends the action the page declared for it — the request\n * vocabulary and the binding vocabulary running in both directions on one\n * widget.\n *\n * The choice is the interaction, so this is the case where an action carries\n * a value. The server still decides what that choice means.\n */\nclass LbSelect extends HTMLElement {\n static observedAttributes = [ATTR_VALUE];\n\n attributeChangedCallback(_name: string, _old: string, value: string) {\n const select = this.querySelector(\"select\");\n if (!select) {\n console.error(`lb-select: no <select> to receive the value`);\n return;\n }\n select.value = value;\n }\n\n connectedCallback() {\n this.addEventListener(\"change\", () => {\n const select = this.querySelector(\"select\");\n if (!select) {\n console.error(`lb-select: change with no <select>, ignoring`);\n return;\n }\n const name = this.getAttribute(ATTR_ACTION);\n if (!name) {\n console.error(`lb-select: no ${ATTR_ACTION}, nothing to send`);\n return;\n }\n const detail: HubRequest = {\n op: \"action\",\n name,\n value: select.value,\n };\n this.dispatchEvent(\n new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }),\n );\n });\n }\n}\n\ncustomElements.define(\"lb-select\", LbSelect);\n"]}
1
+ {"version":3,"file":"lb-select.browser.js","sourceRoot":"","sources":["../widgets/lb-select.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EACL,WAAW,EACX,UAAU,EACV,aAAa,GACd,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;GAQG;AACH,MAAM,QAAS,SAAQ,WAAW;IAChC,MAAM,CAAC,kBAAkB,GAAG,CAAC,UAAU,CAAC,CAAC;IAEzC,wBAAwB,CAAC,KAAa,EAAE,IAAY,EAAE,KAAa;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;QACD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;YACnC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;gBAC9D,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,CAAC,KAAK,CAAC,iBAAiB,WAAW,mBAAmB,CAAC,CAAC;gBAC/D,OAAO;YACT,CAAC;YACD,8DAA8D;YAC9D,MAAM,MAAM,GAAe,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YACjE,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAC1D,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;;AAGH,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_ACTION,\n ATTR_VALUE,\n LB_EVENT_NAME,\n} from \"@loadbare/app/constants\";\nimport type { HubRequest } from \"@loadbare/app/types\";\n\n/**\n * A leaf cell wrapping a <select>. It receives its value like any other cell,\n * and on change it sends the action the developer declared for it — the\n * request vocabulary and the binding vocabulary running in both directions on\n * one widget.\n *\n * The choice is the interaction, so this is the case where an action carries\n * a value. The server still decides what that choice means.\n */\nclass LbSelect extends HTMLElement {\n static observedAttributes = [ATTR_VALUE];\n\n attributeChangedCallback(_name: string, _old: string, value: string) {\n const select = this.querySelector(\"select\");\n if (!select) {\n console.error(`lb-select: no <select> to receive the value`);\n return;\n }\n select.value = value;\n }\n\n connectedCallback() {\n this.addEventListener(\"change\", () => {\n const select = this.querySelector(\"select\");\n if (!select) {\n console.error(`lb-select: change with no <select>, ignoring`);\n return;\n }\n const name = this.getAttribute(ATTR_ACTION);\n if (!name) {\n console.error(`lb-select: no ${ATTR_ACTION}, nothing to send`);\n return;\n }\n // The hub scopes it, so a choice inside a row says which row.\n const detail: HubRequest = { action: name, value: select.value };\n this.dispatchEvent(\n new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }),\n );\n });\n }\n}\n\ncustomElements.define(\"lb-select\", LbSelect);\n"]}
@@ -1,48 +1,43 @@
1
1
  /// <reference lib="dom" />
2
- import { ATTR_CELL, ATTR_GROUP, ATTR_KEY, ATTR_SORT, } from "@loadbare/app/constants";
3
- import { applyRows } from "@loadbare/app/rows";
2
+ import { ATTR_CELL, ATTR_KEY_VALUE } from "@loadbare/app/constants";
4
3
  /**
5
4
  * A table that owns its own scaffolding, and the case that shows what a list
6
- * widget is for once the repeater exists.
5
+ * widget is for once the hub reconciles lists on its own.
7
6
  *
8
- * `lb-list` goes around a table the page author wrote. This one supplies
9
- * the table, and the page writes what only the page knows: the heading row
10
- * and the row template. That division is forced rather than chosen — a
11
- * `<thead>` written inside a custom element is not inside a table, so the
12
- * parser drops the tag and keeps the text. A `<template>` survives anywhere,
13
- * so the heading row arrives wrapped and expansion unwraps it into the
14
- * `lb-template` destination beside the slot.
7
+ * A plain `<table lb-list>` with a row template inside it needs no widget at
8
+ * all. This one supplies the table, and the developer writes what only the
9
+ * page knows: the heading row and the row template. That division is forced
10
+ * rather than chosen — a `<thead>` written inside a custom element is not
11
+ * inside a table, so the parser drops the tag and keeps the text. A
12
+ * `<template>` survives anywhere, so the heading row arrives wrapped and
13
+ * expansion unwraps it into the `lb-template` destination beside the slot.
15
14
  *
16
- * What it adds at run time is placement, and only placement. `lb-sort` names
17
- * the column rows are ordered by; `lb-group` names the column they are
18
- * sectioned by, and is the same attribute a grouped `<select>` reads, because
19
- * a table with section headings and an `<optgroup>` are asking the same thing
20
- * of the same data.
15
+ * What it adds at run time is placement, and only placement. `data-sort`
16
+ * names the column rows are ordered by; `data-group` names the column they
17
+ * are sectioned by. Both are this widget's own attributes, in HTML's `data-`
18
+ * namespace rather than the hub's: the hub never reads them, and `lb-` is
19
+ * reserved for what the hub does read. `lb-options` happens to spell its
20
+ * grouping attribute the same way, which is a convenience and not a contract.
21
21
  *
22
22
  * The `foot` destination is the head's argument a second time and needs no
23
23
  * code here. A `<tfoot>` is a place on the page, not a row the hub delivers,
24
- * so what lands in it is an ordinary scope carrying its own `lb-query` — a
25
- * grand total is a second projection of the same table, and the hub resolves
26
- * it by name like any other. See demo/pages/ledger.html.
24
+ * so what lands in it is an ordinary scope carrying its own `lb-row` — a
25
+ * grand total is a second query over the same data, and the hub resolves it
26
+ * by name like any other. See demo/pages/ledger.html.
27
27
  */
28
+ /** Named on the row template: the column whose distinct values become sections. */
29
+ const GROUP_COLUMN = "data-group";
30
+ /** Named on the row template: the column rows are ordered by within a section. */
31
+ const SORT_COLUMN = "data-sort";
28
32
  /**
29
33
  * The heading row of a section, carrying the value it stands for.
30
34
  *
31
- * The widget's own scaffolding, so it is HTML's namespace rather than
32
- * Loadbare App's: it is derived from rows the hub knows and is invisible to the hub
33
- * itself, exactly as the `<optgroup>` in `lb-options` is. Nothing
34
- * binds to it, so it carries no `lb-key` and no name from the vocabulary.
35
+ * The widget's own scaffolding, derived from rows the hub knows and invisible
36
+ * to the hub itself, exactly as the `<optgroup>` in `lb-options` is. Nothing
37
+ * binds to it, so it carries no `lb-key-value` and no name from the vocabulary.
35
38
  */
36
- const GROUP_ROW = "data-group";
39
+ const SECTION_ROW = "data-section";
37
40
  class LbTable extends HTMLElement {
38
- acceptRows(result) {
39
- applyRows(this, result, this.place);
40
- // A section is derived, so it goes when its last row does.
41
- for (const heading of this.querySelectorAll(`tr[${GROUP_ROW}]`)) {
42
- if (this.section(heading).rows.length === 0)
43
- heading.remove();
44
- }
45
- }
46
41
  /**
47
42
  * Group first, then sort within the group.
48
43
  *
@@ -50,26 +45,33 @@ class LbTable extends HTMLElement {
50
45
  * section ordered, whatever else is in it — which is what lets a whole set
51
46
  * and a single patched row take the same path.
52
47
  */
53
- place = (row, tuple, template) => {
54
- const groupCell = template.getAttribute(ATTR_GROUP);
55
- const sortCell = template.getAttribute(ATTR_SORT);
56
- const heading = groupCell
57
- ? this.headingFor(template, tuple[groupCell] ?? "")
48
+ lbPlaceRow(el, row, template) {
49
+ const groupColumn = template.getAttribute(GROUP_COLUMN);
50
+ const sortColumn = template.getAttribute(SORT_COLUMN);
51
+ const heading = groupColumn
52
+ ? this.headingFor(template, String(row[groupColumn] ?? ""))
58
53
  : null;
59
54
  const { rows, end } = this.section(heading);
60
- const value = sortCell ? (tuple[sortCell] ?? "") : "";
61
- const after = sortCell
62
- ? rows.find((other) => other !== row && cellOf(other, sortCell) > value)
55
+ const value = sortColumn ? String(row[sortColumn] ?? "") : "";
56
+ const after = sortColumn
57
+ ? rows.find((other) => other !== el && cellOf(other, sortColumn) > value)
63
58
  : undefined;
64
- template.parentElement.insertBefore(row, after ?? end);
65
- };
59
+ template.parentElement.insertBefore(el, after ?? end);
60
+ }
61
+ /** A section is derived, so it goes when its last row does. */
62
+ lbRowsLanded() {
63
+ for (const heading of this.querySelectorAll(`tr[${SECTION_ROW}]`)) {
64
+ if (this.section(heading).rows.length === 0)
65
+ heading.remove();
66
+ }
67
+ }
66
68
  /**
67
69
  * The rows of one section, and the node it ends at.
68
70
  *
69
71
  * A section runs from its heading to the next heading, or to the template
70
72
  * if there is none after it. Without grouping there is one section and it
71
- * is the whole body — the heading row the page wrote is skipped along the
72
- * way, because it carries no key and is therefore not a row.
73
+ * is the whole body — the heading row the developer wrote is skipped along
74
+ * the way, because it carries no key and is therefore not a row.
73
75
  */
74
76
  section(heading) {
75
77
  const template = this.querySelector("template");
@@ -77,8 +79,8 @@ class LbTable extends HTMLElement {
77
79
  let node = heading
78
80
  ? heading.nextElementSibling
79
81
  : template.parentElement.firstElementChild;
80
- while (node && node !== template && !node.hasAttribute(GROUP_ROW)) {
81
- if (node.hasAttribute(ATTR_KEY))
82
+ while (node && node !== template && !node.hasAttribute(SECTION_ROW)) {
83
+ if (node.hasAttribute(ATTR_KEY_VALUE))
82
84
  rows.push(node);
83
85
  node = node.nextElementSibling;
84
86
  }
@@ -87,15 +89,15 @@ class LbTable extends HTMLElement {
87
89
  /** The section with this label, or a new one in front of the template. */
88
90
  headingFor(template, label) {
89
91
  const body = template.parentElement;
90
- for (const heading of body.querySelectorAll(`tr[${GROUP_ROW}]`)) {
91
- if (heading.getAttribute(GROUP_ROW) === label)
92
+ for (const heading of body.querySelectorAll(`tr[${SECTION_ROW}]`)) {
93
+ if (heading.getAttribute(SECTION_ROW) === label)
92
94
  return heading;
93
95
  }
94
96
  const heading = document.createElement("tr");
95
- heading.setAttribute(GROUP_ROW, label);
97
+ heading.setAttribute(SECTION_ROW, label);
96
98
  const cell = document.createElement("th");
97
99
  cell.setAttribute("scope", "rowgroup");
98
- // How wide the table is, from the row the page author wrote. Nothing
100
+ // How wide the table is, from the row the developer wrote. Nothing
99
101
  // else knows, and nothing had to be told twice.
100
102
  cell.colSpan = template.content.firstElementChild?.childElementCount ?? 1;
101
103
  cell.textContent = label;
@@ -105,8 +107,8 @@ class LbTable extends HTMLElement {
105
107
  }
106
108
  }
107
109
  /** What a live row is showing in one column. */
108
- function cellOf(row, cell) {
109
- const selector = `[${ATTR_CELL}="${cell}"]`;
110
+ function cellOf(row, column) {
111
+ const selector = `[${ATTR_CELL}="${column}"]`;
110
112
  const el = row.matches(selector) ? row : row.querySelector(selector);
111
113
  return el?.textContent ?? "";
112
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lb-table.browser.js","sourceRoot":"","sources":["../widgets/lb-table.browser.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_CELL,\n ATTR_GROUP,\n ATTR_KEY,\n ATTR_SORT,\n} from \"@loadbare/app/constants\";\nimport type { Projection, HubRowHost } from \"@loadbare/app/types\";\nimport { applyRows, type Place } from \"@loadbare/app/rows\";\n\n/**\n * A table that owns its own scaffolding, and the case that shows what a list\n * widget is for once the repeater exists.\n *\n * `lb-list` goes around a table the page author wrote. This one supplies\n * the table, and the page writes what only the page knows: the heading row\n * and the row template. That division is forced rather than chosen — a\n * `<thead>` written inside a custom element is not inside a table, so the\n * parser drops the tag and keeps the text. A `<template>` survives anywhere,\n * so the heading row arrives wrapped and expansion unwraps it into the\n * `lb-template` destination beside the slot.\n *\n * What it adds at run time is placement, and only placement. `lb-sort` names\n * the column rows are ordered by; `lb-group` names the column they are\n * sectioned by, and is the same attribute a grouped `<select>` reads, because\n * a table with section headings and an `<optgroup>` are asking the same thing\n * of the same data.\n *\n * The `foot` destination is the head's argument a second time and needs no\n * code here. A `<tfoot>` is a place on the page, not a row the hub delivers,\n * so what lands in it is an ordinary scope carrying its own `lb-query` — a\n * grand total is a second projection of the same table, and the hub resolves\n * it by name like any other. See demo/pages/ledger.html.\n */\n\n/**\n * The heading row of a section, carrying the value it stands for.\n *\n * The widget's own scaffolding, so it is HTML's namespace rather than\n * Loadbare App's: it is derived from rows the hub knows and is invisible to the hub\n * itself, exactly as the `<optgroup>` in `lb-options` is. Nothing\n * binds to it, so it carries no `lb-key` and no name from the vocabulary.\n */\nconst GROUP_ROW = \"data-group\";\n\nclass LbTable extends HTMLElement implements HubRowHost {\n acceptRows(result: Projection) {\n applyRows(this, result, this.place);\n // A section is derived, so it goes when its last row does.\n for (const heading of this.querySelectorAll(`tr[${GROUP_ROW}]`)) {\n if (this.section(heading).rows.length === 0) heading.remove();\n }\n }\n\n /**\n * Group first, then sort within the group.\n *\n * Inserting a row before the first one that sorts after it keeps the\n * section ordered, whatever else is in it — which is what lets a whole set\n * and a single patched row take the same path.\n */\n private place: Place = (row, tuple, template) => {\n const groupCell = template.getAttribute(ATTR_GROUP);\n const sortCell = template.getAttribute(ATTR_SORT);\n const heading = groupCell\n ? this.headingFor(template, tuple[groupCell] ?? \"\")\n : null;\n const { rows, end } = this.section(heading);\n\n const value = sortCell ? (tuple[sortCell] ?? \"\") : \"\";\n const after = sortCell\n ? rows.find((other) => other !== row && cellOf(other, sortCell) > value)\n : undefined;\n template.parentElement!.insertBefore(row, after ?? end);\n };\n\n /**\n * The rows of one section, and the node it ends at.\n *\n * A section runs from its heading to the next heading, or to the template\n * if there is none after it. Without grouping there is one section and it\n * is the whole body — the heading row the page wrote is skipped along the\n * way, because it carries no key and is therefore not a row.\n */\n private section(heading: Element | null): { rows: Element[]; end: Element } {\n const template = this.querySelector(\"template\")!;\n const rows: Element[] = [];\n let node = heading\n ? heading.nextElementSibling\n : template.parentElement!.firstElementChild;\n while (node && node !== template && !node.hasAttribute(GROUP_ROW)) {\n if (node.hasAttribute(ATTR_KEY)) rows.push(node);\n node = node.nextElementSibling;\n }\n return { rows, end: node ?? template };\n }\n\n /** The section with this label, or a new one in front of the template. */\n private headingFor(template: HTMLTemplateElement, label: string): Element {\n const body = template.parentElement!;\n for (const heading of body.querySelectorAll(`tr[${GROUP_ROW}]`)) {\n if (heading.getAttribute(GROUP_ROW) === label) return heading;\n }\n const heading = document.createElement(\"tr\");\n heading.setAttribute(GROUP_ROW, label);\n const cell = document.createElement(\"th\");\n cell.setAttribute(\"scope\", \"rowgroup\");\n // How wide the table is, from the row the page author wrote. Nothing\n // else knows, and nothing had to be told twice.\n cell.colSpan = template.content.firstElementChild?.childElementCount ?? 1;\n cell.textContent = label;\n heading.append(cell);\n body.insertBefore(heading, template);\n return heading;\n }\n}\n\n/** What a live row is showing in one column. */\nfunction cellOf(row: Element, cell: string): string {\n const selector = `[${ATTR_CELL}=\"${cell}\"]`;\n const el = row.matches(selector) ? row : row.querySelector(selector);\n return el?.textContent ?? \"\";\n}\n\ncustomElements.define(\"lb-table\", LbTable);\n"]}
1
+ {"version":3,"file":"lb-table.browser.js","sourceRoot":"","sources":["../widgets/lb-table.browser.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport { ATTR_CELL, ATTR_KEY_VALUE } from \"@loadbare/app/constants\";\nimport type { ListHost, Row } from \"@loadbare/app/types\";\n\n/**\n * A table that owns its own scaffolding, and the case that shows what a list\n * widget is for once the hub reconciles lists on its own.\n *\n * A plain `<table lb-list>` with a row template inside it needs no widget at\n * all. This one supplies the table, and the developer writes what only the\n * page knows: the heading row and the row template. That division is forced\n * rather than chosen — a `<thead>` written inside a custom element is not\n * inside a table, so the parser drops the tag and keeps the text. A\n * `<template>` survives anywhere, so the heading row arrives wrapped and\n * expansion unwraps it into the `lb-template` destination beside the slot.\n *\n * What it adds at run time is placement, and only placement. `data-sort`\n * names the column rows are ordered by; `data-group` names the column they\n * are sectioned by. Both are this widget's own attributes, in HTML's `data-`\n * namespace rather than the hub's: the hub never reads them, and `lb-` is\n * reserved for what the hub does read. `lb-options` happens to spell its\n * grouping attribute the same way, which is a convenience and not a contract.\n *\n * The `foot` destination is the head's argument a second time and needs no\n * code here. A `<tfoot>` is a place on the page, not a row the hub delivers,\n * so what lands in it is an ordinary scope carrying its own `lb-row` — a\n * grand total is a second query over the same data, and the hub resolves it\n * by name like any other. See demo/pages/ledger.html.\n */\n\n/** Named on the row template: the column whose distinct values become sections. */\nconst GROUP_COLUMN = \"data-group\";\n\n/** Named on the row template: the column rows are ordered by within a section. */\nconst SORT_COLUMN = \"data-sort\";\n\n/**\n * The heading row of a section, carrying the value it stands for.\n *\n * The widget's own scaffolding, derived from rows the hub knows and invisible\n * to the hub itself, exactly as the `<optgroup>` in `lb-options` is. Nothing\n * binds to it, so it carries no `lb-key-value` and no name from the vocabulary.\n */\nconst SECTION_ROW = \"data-section\";\n\nclass LbTable extends HTMLElement implements ListHost {\n /**\n * Group first, then sort within the group.\n *\n * Inserting a row before the first one that sorts after it keeps the\n * section ordered, whatever else is in it — which is what lets a whole set\n * and a single patched row take the same path.\n */\n lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement): void {\n const groupColumn = template.getAttribute(GROUP_COLUMN);\n const sortColumn = template.getAttribute(SORT_COLUMN);\n const heading = groupColumn\n ? this.headingFor(template, String(row[groupColumn] ?? \"\"))\n : null;\n const { rows, end } = this.section(heading);\n\n const value = sortColumn ? String(row[sortColumn] ?? \"\") : \"\";\n const after = sortColumn\n ? rows.find((other) => other !== el && cellOf(other, sortColumn) > value)\n : undefined;\n template.parentElement!.insertBefore(el, after ?? end);\n }\n\n /** A section is derived, so it goes when its last row does. */\n lbRowsLanded(): void {\n for (const heading of this.querySelectorAll(`tr[${SECTION_ROW}]`)) {\n if (this.section(heading).rows.length === 0) heading.remove();\n }\n }\n\n /**\n * The rows of one section, and the node it ends at.\n *\n * A section runs from its heading to the next heading, or to the template\n * if there is none after it. Without grouping there is one section and it\n * is the whole body — the heading row the developer wrote is skipped along\n * the way, because it carries no key and is therefore not a row.\n */\n private section(heading: Element | null): { rows: Element[]; end: Element } {\n const template = this.querySelector(\"template\")!;\n const rows: Element[] = [];\n let node = heading\n ? heading.nextElementSibling\n : template.parentElement!.firstElementChild;\n while (node && node !== template && !node.hasAttribute(SECTION_ROW)) {\n if (node.hasAttribute(ATTR_KEY_VALUE)) rows.push(node);\n node = node.nextElementSibling;\n }\n return { rows, end: node ?? template };\n }\n\n /** The section with this label, or a new one in front of the template. */\n private headingFor(template: HTMLTemplateElement, label: string): Element {\n const body = template.parentElement!;\n for (const heading of body.querySelectorAll(`tr[${SECTION_ROW}]`)) {\n if (heading.getAttribute(SECTION_ROW) === label) return heading;\n }\n const heading = document.createElement(\"tr\");\n heading.setAttribute(SECTION_ROW, label);\n const cell = document.createElement(\"th\");\n cell.setAttribute(\"scope\", \"rowgroup\");\n // How wide the table is, from the row the developer wrote. Nothing\n // else knows, and nothing had to be told twice.\n cell.colSpan = template.content.firstElementChild?.childElementCount ?? 1;\n cell.textContent = label;\n heading.append(cell);\n body.insertBefore(heading, template);\n return heading;\n }\n}\n\n/** What a live row is showing in one column. */\nfunction cellOf(row: Element, column: string): string {\n const selector = `[${ATTR_CELL}=\"${column}\"]`;\n const el = row.matches(selector) ? row : row.querySelector(selector);\n return el?.textContent ?? \"\";\n}\n\ncustomElements.define(\"lb-table\", LbTable);\n"]}
@@ -1,4 +1,4 @@
1
- <dialog lb-unknown-page lb-query="lb-navigation">
1
+ <dialog lb-unknown-page lb-row="lb-navigation">
2
2
  <p>This page is not in the app.</p>
3
3
  <dl>
4
4
  <dt>Page</dt>
package/docs/lb-input.md CHANGED
@@ -7,14 +7,14 @@ A wrapper for HTML Input.
7
7
  Requires attributes `lb-cell` and `exp-label`.
8
8
 
9
9
  ```html
10
- <lb-list lb-query="people">
10
+ <div lb-list="people">
11
11
  <template lb-key="id">
12
12
  <div>
13
- <lb-input lb-cell="name" exp-label="Name" data-fire-on-change></lb-input>
13
+ <lb-input lb-cell="name" exp-label="Name" lb-action="lb-cell-change"></lb-input>
14
14
  <lb-input lb-cell="email" exp-label="Email" exp-readonly></lb-input>
15
15
  </div>
16
16
  </template>
17
- </lb-list>
17
+ </div>
18
18
  ```
19
19
 
20
20
  ## Expansion Attributes
@@ -32,27 +32,31 @@ off drops the attribute.
32
32
 
33
33
  ## Behavioral Attributes
34
34
 
35
- | Parameter | Fills |
36
- |-----------------------|--------------------------------------------------|
37
- | `data-fire-on-change` | if true, fires a request to the server on change |
35
+ | Attribute | Names |
36
+ |-------------|----------------------------------------------------|
37
+ | `lb-action` | what a change sends; nothing is sent without it |
38
+
39
+ `lb-action="lb-cell-change"` sends the lb-cell-change operation for the input's
40
+ own cell. Any other name sends that action. Either carries the input's
41
+ value, and the hub adds the binding below.
38
42
 
39
43
  ## Binding
40
44
 
41
45
  | Attribute | Read from | Names |
42
46
  |------------|----------------------|------------------------------|
43
47
  | `lb-cell` | the element itself | the column the input shows |
44
- | `lb-key` | the nearest ancestor | the row |
45
- | `lb-query` | the nearest ancestor | the query |
48
+ | `lb-key-value` | the nearest ancestor | the row |
49
+ | `lb-list` | the nearest ancestor | the query |
46
50
 
47
51
  ## Inside a form
48
52
 
49
- An `lb-input` inside an `lb-insert` or `lb-update` form leaves
50
- `data-fire-on-change` off. The form reads every `lb-cell` in it on submit and
51
- sends one request for all of them, so an input that also sent its own would
52
- write the same edit twice.
53
+ An `lb-input` inside an `lb-row-insert` or `lb-row-update` form carries no
54
+ `lb-action`. The form reads every `lb-cell` in it on submit and sends one
55
+ request for all of them, so an input that also sent its own would write the
56
+ same edit twice.
53
57
 
54
58
  ```html
55
- <form lb-update>
59
+ <form lb-action="lb-row-update">
56
60
  <lb-input lb-cell="name" exp-label="Name"></lb-input>
57
61
  <lb-input lb-cell="email" exp-label="Email"></lb-input>
58
62
  <button type="submit">Save</button>
@@ -64,9 +68,9 @@ write the same edit twice.
64
68
  By default an `lb-input` does not notify the hub when its value changes, as shown
65
69
  in the form example above.
66
70
 
67
- To fire a server event through the hub, add `data-fire-on-change`. The hub will
68
- catch the event and fire a request to the server.
71
+ To send on change, add `lb-action`. The hub catches the event and sends the
72
+ request to the server.
69
73
 
70
74
  While the request is in flight the hub marks the `lb-input` with
71
- `data-lb-pending`, and on failure with `data-lb-error`. Both are there for a
75
+ `lb-pending`, and on failure with `lb-error`. Both are there for a
72
76
  stylesheet to read.
@@ -2,16 +2,16 @@
2
2
 
3
3
  A wrapper for HTML Select, with the `<option>`s coming from a query.
4
4
 
5
- Apply the `lb-query` attribute to name the query, and `lb-action` to name the
5
+ Apply the `lb-list` attribute to name the query, and `lb-action` to name the
6
6
  action a change sends. Write a `<template lb-key="...">` inside the tag, and
7
7
  apply `lb-cell` to the `<option>` itself — an `<option>` holds text, not
8
- elements. Apply `lb-group` to the template to section the options.
8
+ elements. Apply `data-group` to the template to section the options.
9
9
 
10
10
  Provide `exp-label`.
11
11
 
12
12
  ```html
13
- <lb-options lb-query="statuses" exp-label="Status" lb-action="setStatus">
14
- <template lb-key="id" lb-group="category">
13
+ <lb-options lb-list="statuses" exp-label="Status" lb-action="setStatus">
14
+ <template lb-key="id" data-group="category">
15
15
  <option lb-cell="label"></option>
16
16
  </template>
17
17
  </lb-options>
@@ -32,18 +32,17 @@ improvements through iterative use.
32
32
 
33
33
  | Attribute | Read from | Names |
34
34
  |-------------|--------------------|--------------------------------------|
35
- | `lb-query` | the element itself | the query whose rows become options |
35
+ | `lb-list` | the element itself | the query whose rows become options |
36
36
  | `lb-action` | the element itself | the action a change sends |
37
- | `lb-key` | the row template | the column that identifies a row |
38
- | `lb-group` | the row template | the column that sections the options |
37
+ | `lb-key` | the row template | the column that identifies a row |
38
+ | `data-group` | the row template | the column that sections the options |
39
39
  | `lb-cell` | the `<option>` | the column the option shows |
40
40
 
41
- `lb-key` gives the option its `value`, so the page names that column once.
42
- `lb-sort` is not read here.
41
+ The key column gives the option its `value`, so the page names it once.
43
42
 
44
43
  ## Grouping
45
44
 
46
- `lb-group` names a column whose distinct values become `<optgroup>`s, each
45
+ `data-group` names a column whose distinct values become `<optgroup>`s, each
47
46
  labelled with the value.
48
47
 
49
48
  ## Choosing
@@ -55,12 +54,12 @@ action on the server.
55
54
  `lb-action` is required. Without it, a change does nothing.
56
55
 
57
56
  While the request is in flight the hub marks the `lb-options` with
58
- `data-lb-pending`, and on failure with `data-lb-error`. Both are there for a
57
+ `lb-pending`, and on failure with `lb-error`. Both are there for a
59
58
  stylesheet to read.
60
59
 
61
60
  ## Styling an empty list
62
61
 
63
- `lb-options` carries `data-rows` with the number of options showing, for a
62
+ `lb-options` carries `lb-row-count` with the number of options showing, for a
64
63
  stylesheet to read.
65
64
 
66
65
  For the case where every row is one option and only the column names vary,
package/docs/lb-picker.md CHANGED
@@ -3,14 +3,14 @@
3
3
  [`lb-options`](./lb-options.md) with the row template built in, for when every
4
4
  row is one option and only the column names vary. It takes no children.
5
5
 
6
- Apply the `lb-query` attribute to name the query, and `lb-action` to name the
6
+ Apply the `lb-list` attribute to name the query, and `lb-action` to name the
7
7
  action a change sends.
8
8
 
9
9
  Provide `exp-label`, `exp-key`, `exp-cell`, and optionally `exp-group`.
10
10
 
11
11
  ```html
12
12
  <lb-picker
13
- lb-query="statuses"
13
+ lb-list="statuses"
14
14
  exp-label="Status"
15
15
  exp-key="id"
16
16
  exp-cell="label"
@@ -40,13 +40,13 @@ options ungrouped.
40
40
 
41
41
  | Attribute | Read from | Names |
42
42
  |-------------|--------------------|-------------------------------------|
43
- | `lb-query` | the element itself | the query whose rows become options |
43
+ | `lb-list` | the element itself | the query whose rows become options |
44
44
  | `lb-action` | the element itself | the action a change sends |
45
45
 
46
46
  ## Choosing
47
47
 
48
48
  The same as [`lb-options`](./lb-options.md): grouping, the key as the option's
49
- value, `data-rows`, and the action on change.
49
+ value, `lb-row-count`, and the action on change.
50
50
 
51
51
  Write `lb-options` and the template instead for a row that needs a second
52
52
  element, or an option built from two columns.
package/docs/lb-select.md CHANGED
@@ -41,7 +41,7 @@ action on the server.
41
41
  `lb-action` is required. Without it, a change does nothing.
42
42
 
43
43
  While the request is in flight the hub marks the `lb-select` with
44
- `data-lb-pending`, and on failure with `data-lb-error`. Both are there for a
44
+ `lb-pending`, and on failure with `lb-error`. Both are there for a
45
45
  stylesheet to read.
46
46
 
47
47
  For a `<select>` whose options come from a query, see
package/docs/lb-table.md CHANGED
@@ -2,24 +2,24 @@
2
2
 
3
3
  A wrapper for HTML Table, supplying the `<table>` itself.
4
4
 
5
- Apply the `lb-query` attribute to name the query. Write three `<template>`s
5
+ Apply the `lb-list` attribute to name the query. Write three `<template>`s
6
6
  inside the tag: one marked `lb-template="head"` for the heading row, one
7
7
  carrying `lb-key` for the row, and optionally one marked `lb-template="foot"`
8
- for the footer. Apply `lb-sort` and `lb-group` to the row template to order
8
+ for the footer. Apply `data-sort` and `data-group` to the row template to order
9
9
  and section the rows.
10
10
 
11
11
  Provide `exp-caption`.
12
12
 
13
13
  ```html
14
- <lb-table lb-query="ledger" exp-caption="Ledger">
14
+ <lb-table lb-list="ledger" exp-caption="Ledger">
15
15
  <template lb-template="head">
16
16
  <tr><th>Date</th><th>Amount</th></tr>
17
17
  </template>
18
- <template lb-key="id" lb-sort="date" lb-group="month">
18
+ <template lb-key="id" data-sort="date" data-group="month">
19
19
  <tr><td lb-cell="date"></td><td lb-cell="amount"></td></tr>
20
20
  </template>
21
21
  <template lb-template="foot">
22
- <tr lb-query="ledger-total"><td>Total</td><td lb-cell="total"></td></tr>
22
+ <tr lb-row="ledger-total"><td>Total</td><td lb-cell="total"></td></tr>
23
23
  </template>
24
24
  </lb-table>
25
25
  ```
@@ -42,28 +42,28 @@ improvements through iterative use.
42
42
 
43
43
  | Attribute | Read from | Names |
44
44
  |------------|----------------------------------|------------------------------------|
45
- | `lb-query` | the element itself | the query whose rows fill the body |
46
- | `lb-key` | the row template | the column that identifies a row |
47
- | `lb-sort` | the row template | the column rows are ordered by |
48
- | `lb-group` | the row template | the column rows are sectioned by |
45
+ | `lb-list` | the element itself | the query whose rows fill the body |
46
+ | `lb-key` | the row template | the column that identifies a row |
47
+ | `data-sort` | the row template | the column rows are ordered by |
48
+ | `data-group` | the row template | the column rows are sectioned by |
49
49
  | `lb-cell` | elements inside the row template | the column each element shows |
50
50
 
51
51
  ## Sorting and grouping
52
52
 
53
- `lb-sort` names the column rows are ordered by, comparing the text each row
54
- shows. `lb-group` names the column they are sectioned by: each distinct value
53
+ `data-sort` names the column rows are ordered by, comparing the text each row
54
+ shows. `data-group` names the column they are sectioned by: each distinct value
55
55
  gets a heading row of its own, spanning the width of the row template, and
56
56
  rows sort within their section.
57
57
 
58
58
  ## The footer
59
59
 
60
60
  The footer is an ordinary part of the page, not a row from the table's query.
61
- Give it its own `lb-query` — a grand total is a second query, declared on the
61
+ Give it its own `lb-row` — a grand total is a second query, declared on the
62
62
  server like any other.
63
63
 
64
64
  ## Styling an empty table
65
65
 
66
- `lb-table` carries `data-rows` with the number of rows showing, for a
66
+ `lb-table` carries `lb-row-count` with the number of rows showing, for a
67
67
  stylesheet to read.
68
68
 
69
69
  For a repeater with no table of its own, see [`lb-list`](./lb-list.md).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loadbare/widgets",
3
- "description": "The basic widget set for @loadbare/app: input, select, list, options, picker, table, unknown-page",
4
- "version": "0.2.3",
3
+ "description": "The basic widget set for @loadbare/app: input, select, options, picker, table, unknown-page",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "scripts": {
24
24
  "build": "node scripts/build.mjs",
25
+ "postbuild": "node ../../scripts/check-dist-imports.mjs",
25
26
  "test": "tsx --test \"tests/**/*.test.ts\"",
26
27
  "typecheck": "tsc --noEmit",
27
28
  "format": "prettier --write .",
@@ -48,10 +49,10 @@
48
49
  "node": ">=22"
49
50
  },
50
51
  "peerDependencies": {
51
- "@loadbare/app": "^0.5.0"
52
+ "@loadbare/app": "^0.7.0"
52
53
  },
53
54
  "devDependencies": {
54
- "@loadbare/app": "^0.5.0",
55
+ "@loadbare/app": "^0.7.0",
55
56
  "@types/jsdom": "^21.1.7",
56
57
  "@types/node": "^22.0.0",
57
58
  "jsdom": "^25.0.0",
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=lb-list.browser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lb-list.browser.d.ts","sourceRoot":"","sources":["../widgets/lb-list.browser.ts"],"names":[],"mappings":""}
@@ -1,18 +0,0 @@
1
- /// <reference lib="dom" />
2
- import { applyRows } from "@loadbare/app/rows";
3
- /**
4
- * The plain repeater: whatever the page author wrote inside is repeated once
5
- * per tuple, in the order the server sent.
6
- *
7
- * It exists because a projection has to land on a widget, and because a
8
- * `<table>` cannot be one — a custom element written inside `<tbody>` is
9
- * discarded by the parser, so the widget goes around the table and the
10
- * template goes inside it, where the content model already allows it.
11
- */
12
- class LbList extends HTMLElement {
13
- acceptRows(result) {
14
- applyRows(this, result);
15
- }
16
- }
17
- customElements.define("lb-list", LbList);
18
- //# sourceMappingURL=lb-list.browser.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lb-list.browser.js","sourceRoot":"","sources":["../widgets/lb-list.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAG3B,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,MAAO,SAAQ,WAAW;IAC9B,UAAU,CAAC,MAAkB;QAC3B,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC;CACF;AAED,cAAc,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport type { Projection, HubRowHost } from \"@loadbare/app/types\";\nimport { applyRows } from \"@loadbare/app/rows\";\n\n/**\n * The plain repeater: whatever the page author wrote inside is repeated once\n * per tuple, in the order the server sent.\n *\n * It exists because a projection has to land on a widget, and because a\n * `<table>` cannot be one — a custom element written inside `<tbody>` is\n * discarded by the parser, so the widget goes around the table and the\n * template goes inside it, where the content model already allows it.\n */\nclass LbList extends HTMLElement implements HubRowHost {\n acceptRows(result: Projection) {\n applyRows(this, result);\n }\n}\n\ncustomElements.define(\"lb-list\", LbList);\n"]}
package/dist/lb-list.html DELETED
@@ -1 +0,0 @@
1
- <div lb-slot></div>
package/docs/lb-list.md DELETED
@@ -1,61 +0,0 @@
1
- # `lb-list`
2
-
3
- The plain repeater: a row per tuple, in the order the rows arrive.
4
-
5
- Apply the `lb-query` attribute to name the query. Write a
6
- `<template lb-key="...">` inside the tag, naming the column that identifies a
7
- row, and apply `lb-cell` to each element in it.
8
-
9
- ```html
10
- <lb-list lb-query="people">
11
- <template lb-key="id">
12
- <div>
13
- <span lb-cell="name"></span>
14
- <span lb-cell="email"></span>
15
- </div>
16
- </template>
17
- </lb-list>
18
- ```
19
-
20
- ## Status
21
-
22
- Barebones starter to prove out the publish/consume chain and identify necessary
23
- improvements through iterative use.
24
-
25
- ## Parameters
26
-
27
- None.
28
-
29
- ## Binding
30
-
31
- | Attribute | Read from | Names |
32
- |------------|----------------------------------|----------------------------------|
33
- | `lb-query` | the element itself | the query whose rows land here |
34
- | `lb-key` | the row template | the column that identifies a row |
35
- | `lb-cell` | elements inside the row template | the column each element shows |
36
-
37
- For ordering and section headings, see [`lb-table`](./lb-table.md).
38
-
39
- ## Repeating table rows
40
-
41
- Put `lb-list` around the `<table>` and the `<template>` inside `<tbody>`. A
42
- custom element written inside `<tbody>` is thrown away by the HTML parser.
43
-
44
- ```html
45
- <lb-list lb-query="ledger">
46
- <table>
47
- <tbody>
48
- <template lb-key="id">
49
- <tr><td lb-cell="date"></td><td lb-cell="amount"></td></tr>
50
- </template>
51
- </tbody>
52
- </table>
53
- </lb-list>
54
- ```
55
-
56
- [`lb-table`](./lb-table.md) supplies the table itself.
57
-
58
- ## Styling an empty list
59
-
60
- `lb-list` carries `data-rows` with the number of rows showing, for a
61
- stylesheet to read.