@loadbare/widgets 0.7.0 → 0.8.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/dist/control.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { HubLifecycle } from "@loadbare/app/types";
1
2
  /**
2
3
  * A form-associated custom element wrapping one native control, which makes
3
4
  * the widget itself the control: the hub sets its `value` when a column
@@ -12,7 +13,7 @@
12
13
  * a consumer's builder passes it over, and each widget that extends it
13
14
  * brings it along.
14
15
  */
15
- export declare abstract class WrappedControl extends HTMLElement {
16
+ export declare abstract class WrappedControl extends HTMLElement implements HubLifecycle {
16
17
  static formAssociated: boolean;
17
18
  /** The tag of the native control this widget wraps. */
18
19
  protected abstract inner: string;
@@ -22,20 +23,14 @@ export declare abstract class WrappedControl extends HTMLElement {
22
23
  * leaves it out.
23
24
  */
24
25
  private internals;
25
- /** Whether the widget has connected once. */
26
- private connected;
27
26
  constructor();
28
27
  /**
29
- * A value that landed before this widget's definition did is only a
30
- * stamp, since the hub sets `value` on a control and the element was not
31
- * one yet. It is taken up the first time the widget connects, and never
32
- * again: after that the hub sets `value` itself, and a later move must not
33
- * undo an edit.
34
- *
35
28
  * The native control follows the widget's `form` attribute, so a form
36
- * this widget joins by id resets the control inside it too.
29
+ * this widget joins by id resets the control inside it too. An element
30
+ * around this one that joins it to a form does so in its own
31
+ * `lbAttached`, which the hub calls first.
37
32
  */
38
- connectedCallback(): void;
33
+ lbAttached(): void;
39
34
  /** The native control this widget wraps, once it is there. */
40
35
  protected get control(): HTMLInputElement | HTMLSelectElement | null;
41
36
  get value(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../widgets/control.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH,8BAAsB,cAAe,SAAQ,WAAW;IACtD,MAAM,CAAC,cAAc,UAAQ;IAE7B,uDAAuD;IACvD,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEjC;;;;OAIG;IACH,OAAO,CAAC,SAAS,CAAqB;IAEtC,6CAA6C;IAC7C,OAAO,CAAC,SAAS,CAAS;;IAY1B;;;;;;;;;OASG;IACH,iBAAiB,IAAI,IAAI;IASzB,8DAA8D;IAC9D,SAAS,KAAK,OAAO,IAAI,gBAAgB,GAAG,iBAAiB,GAAG,IAAI,CAEnE;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAUtB;IAED;;;;OAIG;IACH,iBAAiB,IAAI,IAAI;IAIzB;;;;;OAKG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIxC,uDAAuD;IACvD,cAAc,IAAI,OAAO;IAIzB,4DAA4D;IAC5D,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,2EAA2E;IAC3E,SAAS,CAAC,OAAO,IAAI,IAAI;IAIzB,yEAAyE;IACzE,SAAS,CAAC,SAAS,IAAI,IAAI;CAG5B"}
1
+ {"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../widgets/control.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,8BAAsB,cACpB,SAAQ,WACR,YAAW,YAAY;IAEvB,MAAM,CAAC,cAAc,UAAQ;IAE7B,uDAAuD;IACvD,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEjC;;;;OAIG;IACH,OAAO,CAAC,SAAS,CAAqB;;IAYtC;;;;;OAKG;IACH,UAAU,IAAI,IAAI;IAKlB,8DAA8D;IAC9D,SAAS,KAAK,OAAO,IAAI,gBAAgB,GAAG,iBAAiB,GAAG,IAAI,CAEnE;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAUtB;IAED;;;;OAIG;IACH,iBAAiB,IAAI,IAAI;IAIzB;;;;;OAKG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIxC,uDAAuD;IACvD,cAAc,IAAI,OAAO;IAIzB,4DAA4D;IAC5D,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,2EAA2E;IAC3E,SAAS,CAAC,OAAO,IAAI,IAAI;IAIzB,yEAAyE;IACzE,SAAS,CAAC,SAAS,IAAI,IAAI;CAG5B"}
package/dist/control.js CHANGED
@@ -1,5 +1,4 @@
1
1
  /// <reference lib="dom" />
2
- import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
3
2
  /**
4
3
  * A form-associated custom element wrapping one native control, which makes
5
4
  * the widget itself the control: the hub sets its `value` when a column
@@ -22,8 +21,6 @@ export class WrappedControl extends HTMLElement {
22
21
  * leaves it out.
23
22
  */
24
23
  internals = internalsOf(this);
25
- /** Whether the widget has connected once. */
26
- connected = false;
27
24
  constructor() {
28
25
  super();
29
26
  this.addEventListener("change", (e) => {
@@ -35,25 +32,15 @@ export class WrappedControl extends HTMLElement {
35
32
  });
36
33
  }
37
34
  /**
38
- * A value that landed before this widget's definition did is only a
39
- * stamp, since the hub sets `value` on a control and the element was not
40
- * one yet. It is taken up the first time the widget connects, and never
41
- * again: after that the hub sets `value` itself, and a later move must not
42
- * undo an edit.
43
- *
44
35
  * The native control follows the widget's `form` attribute, so a form
45
- * this widget joins by id resets the control inside it too.
36
+ * this widget joins by id resets the control inside it too. An element
37
+ * around this one that joins it to a form does so in its own
38
+ * `lbAttached`, which the hub calls first.
46
39
  */
47
- connectedCallback() {
40
+ lbAttached() {
48
41
  const form = this.getAttribute("form");
49
42
  if (form !== null)
50
43
  this.control?.setAttribute("form", form);
51
- if (this.connected)
52
- return;
53
- this.connected = true;
54
- const stamped = this.getAttribute(ATTR_COLUMN_VALUE);
55
- if (stamped !== null)
56
- this.value = stamped;
57
44
  }
58
45
  /** The native control this widget wraps, once it is there. */
59
46
  get control() {
@@ -1 +1 @@
1
- {"version":3,"file":"control.js","sourceRoot":"","sources":["../widgets/control.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAgB,cAAe,SAAQ,WAAW;IACtD,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;IAK7B;;;;OAIG;IACK,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAEtC,6CAA6C;IACrC,SAAS,GAAG,KAAK,CAAC;IAE1B;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;YACpC,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,OAAO;gBAAE,OAAO;YAC3D,CAAC,CAAC,eAAe,EAAE,CAAC;YACpB,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,iBAAiB;QACf,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,IAAI,KAAK,IAAI;YAAE,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC5D,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;QACrD,IAAI,OAAO,KAAK,IAAI;YAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;IAC7C,CAAC;IAED,8DAA8D;IAC9D,IAAc,OAAO;QACnB,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC;IACnC,CAAC;IAED,IAAI,KAAK,CAAC,KAAa;QACrB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CACX,GAAG,IAAI,CAAC,SAAS,SAAS,IAAI,CAAC,KAAK,wBAAwB,CAC7D,CAAC;YACF,OAAO;QACT,CAAC;QACD,OAAO,CAAC,KAAK,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,iBAAiB;QACf,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;;;OAKG;IACH,iBAAiB,CAAC,OAAe;QAC/B,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,uDAAuD;IACvD,cAAc;QACZ,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,IAAI,CAAC;IAChD,CAAC;IAED,4DAA4D;IAC5D,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,OAAO,EAAE,iBAAiB,IAAI,EAAE,CAAC;IAC/C,CAAC;IAED,2EAA2E;IACjE,OAAO;QACf,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAED,yEAAyE;IAC/D,SAAS;QACjB,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC;;AAGH,SAAS,WAAW,CAAC,EAAe;IAClC,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,EAAE,CAAC,eAAe,EAAE,CAAC;QACvC,OAAO,OAAO,SAAS,CAAC,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport { ATTR_COLUMN_VALUE } from \"@loadbare/app/constants\";\n\n/**\n * A form-associated custom element wrapping one native control, which makes\n * the widget itself the control: the hub sets its `value` when a column\n * lands, reads it back when a request gathers, and commits it on `change`\n * when it carries `lb-request`. Nothing about a request is the widget's.\n *\n * The native control inside is an implementation detail. Its `change` is\n * stopped and fired again on the widget, so a listener sees one control and\n * one event per edit.\n *\n * Not a widget itself, so the file carries no `.browser` and no `lb-` name:\n * a consumer's builder passes it over, and each widget that extends it\n * brings it along.\n */\nexport abstract class WrappedControl extends HTMLElement {\n static formAssociated = true;\n\n /** The tag of the native control this widget wraps. */\n protected abstract inner: string;\n\n /**\n * Null where the DOM implementation offers no form value, which includes\n * jsdom: the widget still works as a control, and a native form submission\n * leaves it out.\n */\n private internals = internalsOf(this);\n\n /** Whether the widget has connected once. */\n private connected = false;\n\n constructor() {\n super();\n this.addEventListener(\"change\", (e) => {\n if (e.target === this || e.target !== this.control) return;\n e.stopPropagation();\n this.changed();\n this.dispatchEvent(new Event(\"change\", { bubbles: true }));\n });\n }\n\n /**\n * A value that landed before this widget's definition did is only a\n * stamp, since the hub sets `value` on a control and the element was not\n * one yet. It is taken up the first time the widget connects, and never\n * again: after that the hub sets `value` itself, and a later move must not\n * undo an edit.\n *\n * The native control follows the widget's `form` attribute, so a form\n * this widget joins by id resets the control inside it too.\n */\n connectedCallback(): void {\n const form = this.getAttribute(\"form\");\n if (form !== null) this.control?.setAttribute(\"form\", form);\n if (this.connected) return;\n this.connected = true;\n const stamped = this.getAttribute(ATTR_COLUMN_VALUE);\n if (stamped !== null) this.value = stamped;\n }\n\n /** The native control this widget wraps, once it is there. */\n protected get control(): HTMLInputElement | HTMLSelectElement | null {\n return this.querySelector(this.inner);\n }\n\n get value(): string {\n return this.control?.value ?? \"\";\n }\n\n set value(value: string) {\n const control = this.control;\n if (!control) {\n console.error(\n `${this.localName}: no <${this.inner}> to receive the value`,\n );\n return;\n }\n control.value = value ?? \"\";\n this.formValue();\n }\n\n /**\n * The native control is the form's too, and the form resets it by HTML's\n * own algorithm. This widget comes first in tree order, so it tells the\n * form its value once the control has been reset.\n */\n formResetCallback(): void {\n queueMicrotask(() => this.formValue());\n }\n\n /**\n * A message the hub sets when a handler refuses this control's column. It\n * goes on the native control inside, which is what the form checks before\n * it submits, what the browser shows the message beside, and what takes\n * focus when it is reported.\n */\n setCustomValidity(message: string): void {\n this.control?.setCustomValidity(message);\n }\n\n /** Show the native control's message, and focus it. */\n reportValidity(): boolean {\n return this.control?.reportValidity() ?? true;\n }\n\n /** The native control's message, empty when it has none. */\n get validationMessage(): string {\n return this.control?.validationMessage ?? \"\";\n }\n\n /** The inner control changed, before the widget fires its own `change`. */\n protected changed(): void {\n this.formValue();\n }\n\n /** Tell the form owner the value, where the implementation allows it. */\n protected formValue(): void {\n this.internals?.setFormValue(this.value);\n }\n}\n\nfunction internalsOf(el: HTMLElement): ElementInternals | null {\n try {\n const internals = el.attachInternals();\n return typeof internals.setFormValue === \"function\" ? internals : null;\n } catch {\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"control.js","sourceRoot":"","sources":["../widgets/control.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAI3B;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAgB,cACpB,SAAQ,WAAW;IAGnB,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;IAK7B;;;;OAIG;IACK,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAEtC;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;YACpC,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,OAAO;gBAAE,OAAO;YAC3D,CAAC,CAAC,eAAe,EAAE,CAAC;YACpB,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,UAAU;QACR,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,IAAI,KAAK,IAAI;YAAE,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC9D,CAAC;IAED,8DAA8D;IAC9D,IAAc,OAAO;QACnB,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC;IACnC,CAAC;IAED,IAAI,KAAK,CAAC,KAAa;QACrB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CACX,GAAG,IAAI,CAAC,SAAS,SAAS,IAAI,CAAC,KAAK,wBAAwB,CAC7D,CAAC;YACF,OAAO;QACT,CAAC;QACD,OAAO,CAAC,KAAK,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,iBAAiB;QACf,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;;;OAKG;IACH,iBAAiB,CAAC,OAAe;QAC/B,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,uDAAuD;IACvD,cAAc;QACZ,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,IAAI,CAAC;IAChD,CAAC;IAED,4DAA4D;IAC5D,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,OAAO,EAAE,iBAAiB,IAAI,EAAE,CAAC;IAC/C,CAAC;IAED,2EAA2E;IACjE,OAAO;QACf,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAED,yEAAyE;IAC/D,SAAS;QACjB,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC;;AAGH,SAAS,WAAW,CAAC,EAAe;IAClC,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,EAAE,CAAC,eAAe,EAAE,CAAC;QACvC,OAAO,OAAO,SAAS,CAAC,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport type { HubLifecycle } from \"@loadbare/app/types\";\n\n/**\n * A form-associated custom element wrapping one native control, which makes\n * the widget itself the control: the hub sets its `value` when a column\n * lands, reads it back when a request gathers, and commits it on `change`\n * when it carries `lb-request`. Nothing about a request is the widget's.\n *\n * The native control inside is an implementation detail. Its `change` is\n * stopped and fired again on the widget, so a listener sees one control and\n * one event per edit.\n *\n * Not a widget itself, so the file carries no `.browser` and no `lb-` name:\n * a consumer's builder passes it over, and each widget that extends it\n * brings it along.\n */\nexport abstract class WrappedControl\n extends HTMLElement\n implements HubLifecycle\n{\n static formAssociated = true;\n\n /** The tag of the native control this widget wraps. */\n protected abstract inner: string;\n\n /**\n * Null where the DOM implementation offers no form value, which includes\n * jsdom: the widget still works as a control, and a native form submission\n * leaves it out.\n */\n private internals = internalsOf(this);\n\n constructor() {\n super();\n this.addEventListener(\"change\", (e) => {\n if (e.target === this || e.target !== this.control) return;\n e.stopPropagation();\n this.changed();\n this.dispatchEvent(new Event(\"change\", { bubbles: true }));\n });\n }\n\n /**\n * The native control follows the widget's `form` attribute, so a form\n * this widget joins by id resets the control inside it too. An element\n * around this one that joins it to a form does so in its own\n * `lbAttached`, which the hub calls first.\n */\n lbAttached(): void {\n const form = this.getAttribute(\"form\");\n if (form !== null) this.control?.setAttribute(\"form\", form);\n }\n\n /** The native control this widget wraps, once it is there. */\n protected get control(): HTMLInputElement | HTMLSelectElement | null {\n return this.querySelector(this.inner);\n }\n\n get value(): string {\n return this.control?.value ?? \"\";\n }\n\n set value(value: string) {\n const control = this.control;\n if (!control) {\n console.error(\n `${this.localName}: no <${this.inner}> to receive the value`,\n );\n return;\n }\n control.value = value ?? \"\";\n this.formValue();\n }\n\n /**\n * The native control is the form's too, and the form resets it by HTML's\n * own algorithm. This widget comes first in tree order, so it tells the\n * form its value once the control has been reset.\n */\n formResetCallback(): void {\n queueMicrotask(() => this.formValue());\n }\n\n /**\n * A message the hub sets when a handler refuses this control's column. It\n * goes on the native control inside, which is what the form checks before\n * it submits, what the browser shows the message beside, and what takes\n * focus when it is reported.\n */\n setCustomValidity(message: string): void {\n this.control?.setCustomValidity(message);\n }\n\n /** Show the native control's message, and focus it. */\n reportValidity(): boolean {\n return this.control?.reportValidity() ?? true;\n }\n\n /** The native control's message, empty when it has none. */\n get validationMessage(): string {\n return this.control?.validationMessage ?? \"\";\n }\n\n /** The inner control changed, before the widget fires its own `change`. */\n protected changed(): void {\n this.formValue();\n }\n\n /** Tell the form owner the value, where the implementation allows it. */\n protected formValue(): void {\n this.internals?.setFormValue(this.value);\n }\n}\n\nfunction internalsOf(el: HTMLElement): ElementInternals | null {\n try {\n const internals = el.attachInternals();\n return typeof internals.setFormValue === \"function\" ? internals : null;\n } catch {\n return null;\n }\n}\n"]}
@@ -1,5 +1,4 @@
1
1
  /// <reference lib="dom" />
2
- import { LB_DONE_EVENT_NAME } from "@loadbare/app/constants";
3
2
  /**
4
3
  * Opens and closes its own dialog, and nothing else.
5
4
  *
@@ -8,8 +7,8 @@ import { LB_DONE_EVENT_NAME } from "@loadbare/app/constants";
8
7
  * own `commandfor` would open it with no script, but only by id.
9
8
  *
10
9
  * The confirm button carries `lb-request`, and the hub commits it. This code
11
- * never touches the request; it closes the dialog when `lb-request-done` says
12
- * the request succeeded. A refused request leaves the dialog open, with the
10
+ * never touches the request; it closes the dialog when the hub says the
11
+ * request succeeded. A refused request leaves the dialog open, with the
13
12
  * hub's `lb-request-error` on the confirm button, so the user sees the
14
13
  * refusal where they asked and can cancel or try again.
15
14
  */
@@ -23,14 +22,15 @@ class LbConfirm extends HTMLElement {
23
22
  else if (target.closest("[data-close]"))
24
23
  this.dialog.close();
25
24
  });
26
- this.addEventListener(LB_DONE_EVENT_NAME, (e) => {
27
- if (!e.detail.error)
28
- this.dialog.close();
29
- });
25
+ }
26
+ lbRequestDone({ error }) {
27
+ if (!error)
28
+ this.dialog.close();
30
29
  }
31
30
  get dialog() {
32
31
  return this.querySelector("dialog");
33
32
  }
34
33
  }
35
34
  customElements.define("lb-confirm", LbConfirm);
35
+ export {};
36
36
  //# sourceMappingURL=lb-confirm.browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lb-confirm.browser.js","sourceRoot":"","sources":["../widgets/lb-confirm.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG7D;;;;;;;;;;;;GAYG;AACH,MAAM,SAAU,SAAQ,WAAW;IACjC;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;YACnC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAiB,CAAC;YACnC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;iBACtD,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,CAAC,CAAC,EAAE,EAAE;YAC9C,IAAI,CAAE,CAAiC,CAAC,MAAM,CAAC,KAAK;gBAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAY,MAAM;QAChB,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAE,CAAC;IACvC,CAAC;CACF;AAED,cAAc,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport { LB_DONE_EVENT_NAME } from \"@loadbare/app/constants\";\nimport type { HubRequestDone } from \"@loadbare/app/types\";\n\n/**\n * Opens and closes its own dialog, and nothing else.\n *\n * The dialog is found by position rather than by id, because ids repeat when\n * the tag sits in a row template and the hub clones it once per row. HTML's\n * own `commandfor` would open it with no script, but only by id.\n *\n * The confirm button carries `lb-request`, and the hub commits it. This code\n * never touches the request; it closes the dialog when `lb-request-done` says\n * the request succeeded. A refused request leaves the dialog open, with the\n * hub's `lb-request-error` on the confirm button, so the user sees the\n * refusal where they asked and can cancel or try again.\n */\nclass LbConfirm extends HTMLElement {\n constructor() {\n super();\n this.addEventListener(\"click\", (e) => {\n const target = e.target as Element;\n if (target.closest(\"[data-open]\")) this.dialog.showModal();\n else if (target.closest(\"[data-close]\")) this.dialog.close();\n });\n this.addEventListener(LB_DONE_EVENT_NAME, (e) => {\n if (!(e as CustomEvent<HubRequestDone>).detail.error) this.dialog.close();\n });\n }\n\n private get dialog(): HTMLDialogElement {\n return this.querySelector(\"dialog\")!;\n }\n}\n\ncustomElements.define(\"lb-confirm\", LbConfirm);\n"]}
1
+ {"version":3,"file":"lb-confirm.browser.js","sourceRoot":"","sources":["../widgets/lb-confirm.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAI3B;;;;;;;;;;;;GAYG;AACH,MAAM,SAAU,SAAQ,WAAW;IACjC;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;YACnC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAiB,CAAC;YACnC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;iBACtD,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC/D,CAAC,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,EAAE,KAAK,EAAkB;QACrC,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IAClC,CAAC;IAED,IAAY,MAAM;QAChB,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAE,CAAC;IACvC,CAAC;CACF;AAED,cAAc,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport type { HubLifecycle, HubRequestDone } from \"@loadbare/app/types\";\n\n/**\n * Opens and closes its own dialog, and nothing else.\n *\n * The dialog is found by position rather than by id, because ids repeat when\n * the tag sits in a row template and the hub clones it once per row. HTML's\n * own `commandfor` would open it with no script, but only by id.\n *\n * The confirm button carries `lb-request`, and the hub commits it. This code\n * never touches the request; it closes the dialog when the hub says the\n * request succeeded. A refused request leaves the dialog open, with the\n * hub's `lb-request-error` on the confirm button, so the user sees the\n * refusal where they asked and can cancel or try again.\n */\nclass LbConfirm extends HTMLElement implements HubLifecycle {\n constructor() {\n super();\n this.addEventListener(\"click\", (e) => {\n const target = e.target as Element;\n if (target.closest(\"[data-open]\")) this.dialog.showModal();\n else if (target.closest(\"[data-close]\")) this.dialog.close();\n });\n }\n\n lbRequestDone({ error }: HubRequestDone): void {\n if (!error) this.dialog.close();\n }\n\n private get dialog(): HTMLDialogElement {\n return this.querySelector(\"dialog\")!;\n }\n}\n\ncustomElements.define(\"lb-confirm\", LbConfirm);\n"]}
@@ -1,17 +1,15 @@
1
1
  /// <reference lib="dom" />
2
- import { LB_DONE_EVENT_NAME } from "@loadbare/app/constants";
3
2
  /**
4
3
  * Two rules. Closing the dialog resets the form, and the row the form creates
5
4
  * is chosen in the picker the dialog was opened from.
6
5
  *
7
- * Save closes the dialog when `lb-request-done` says the insert succeeded,
8
- * so there is a row to show for it, and a refusal leaves the entry open to
9
- * correct. Cancel and Escape close it, and throw away what was typed.
6
+ * Save closes the dialog when the hub says the insert succeeded, so there
7
+ * is a row to show for it, and a refusal leaves the entry open to correct. Cancel and Escape close it, and throw away what was typed.
10
8
  *
11
9
  * The picker is the nearest `lb-options` around the button that opened the
12
10
  * dialog, which a `command` event names as its `source`. The new row's key is
13
- * in the patch the insert answers with, and by the time `lb-request-done`
14
- * reaches here that row is among the picker's options. Choosing it fires
11
+ * in the patch the insert answers with, and by the time the hub calls
12
+ * `lbRequestDone` that row is among the picker's options. Choosing it fires
15
13
  * `change`, so a picker that saves its own column saves it, and one in a ghost
16
14
  * row or a new record holds it until Save.
17
15
  *
@@ -26,17 +24,15 @@ class LbInsertDialog extends HTMLElement {
26
24
  this.addEventListener("command", (e) => {
27
25
  this.opener = e.source ?? null;
28
26
  }, true);
29
- this.addEventListener(LB_DONE_EVENT_NAME, (e) => {
30
- const { request, items, error } = e
31
- .detail;
32
- if (error)
33
- return;
34
- const item = items.find((i) => i.query === request.query);
35
- const created = item?.patch?.rows?.[0];
36
- if (created)
37
- this.choose(String(created[item.key]));
38
- this.querySelector("dialog")?.close();
39
- });
27
+ }
28
+ lbRequestDone({ request, items, error }) {
29
+ if (error)
30
+ return;
31
+ const item = items.find((i) => i.query === request.query);
32
+ const created = item?.patch?.rows?.[0];
33
+ if (created)
34
+ this.choose(String(created[item.key]));
35
+ this.querySelector("dialog")?.close();
40
36
  }
41
37
  choose(key) {
42
38
  const picker = pickerNear(this.opener);
@@ -56,4 +52,5 @@ function pickerNear(el) {
56
52
  return null;
57
53
  }
58
54
  customElements.define("lb-insert-dialog", LbInsertDialog);
55
+ export {};
59
56
  //# sourceMappingURL=lb-insert-dialog.browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lb-insert-dialog.browser.js","sourceRoot":"","sources":["../widgets/lb-insert-dialog.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG7D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,cAAe,SAAQ,WAAW;IACtC,8CAA8C;IACtC,MAAM,GAAmB,IAAI,CAAC;IAEtC;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,gBAAgB,CACnB,OAAO,EACP,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EACzC,IAAI,CACL,CAAC;QACF,IAAI,CAAC,gBAAgB,CACnB,SAAS,EACT,CAAC,CAAC,EAAE,EAAE;YACJ,IAAI,CAAC,MAAM,GAAI,CAAyC,CAAC,MAAM,IAAI,IAAI,CAAC;QAC1E,CAAC,EACD,IAAI,CACL,CAAC;QACF,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,CAAC,CAAC,EAAE,EAAE;YAC9C,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAI,CAAiC;iBACjE,MAAM,CAAC;YACV,IAAI,KAAK;gBAAE,OAAO;YAClB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1D,MAAM,OAAO,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACvC,IAAI,OAAO;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACrD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,MAAM,CAAC,GAAW;QACxB,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC;QACnB,MAAM,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;CACF;AAED,qEAAqE;AACrE,SAAS,UAAU,CACjB,EAAkB;IAElB,KAAK,IAAI,IAAI,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,KAAK;YAAE,OAAO,KAAwC,CAAC;IAC7D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,cAAc,CAAC,MAAM,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport { LB_DONE_EVENT_NAME } from \"@loadbare/app/constants\";\nimport type { HubRequestDone } from \"@loadbare/app/types\";\n\n/**\n * Two rules. Closing the dialog resets the form, and the row the form creates\n * is chosen in the picker the dialog was opened from.\n *\n * Save closes the dialog when `lb-request-done` says the insert succeeded,\n * so there is a row to show for it, and a refusal leaves the entry open to\n * correct. Cancel and Escape close it, and throw away what was typed.\n *\n * The picker is the nearest `lb-options` around the button that opened the\n * dialog, which a `command` event names as its `source`. The new row's key is\n * in the patch the insert answers with, and by the time `lb-request-done`\n * reaches here that row is among the picker's options. Choosing it fires\n * `change`, so a picker that saves its own column saves it, and one in a ghost\n * row or a new record holds it until Save.\n *\n * `close` and `command` do not bubble, so they are heard in capture.\n */\nclass LbInsertDialog extends HTMLElement {\n /** The button that last opened the dialog. */\n private opener: Element | null = null;\n\n constructor() {\n super();\n this.addEventListener(\n \"close\",\n () => this.querySelector(\"form\")?.reset(),\n true,\n );\n this.addEventListener(\n \"command\",\n (e) => {\n this.opener = (e as Event & { source?: Element | null }).source ?? null;\n },\n true,\n );\n this.addEventListener(LB_DONE_EVENT_NAME, (e) => {\n const { request, items, error } = (e as CustomEvent<HubRequestDone>)\n .detail;\n if (error) return;\n const item = items.find((i) => i.query === request.query);\n const created = item?.patch?.rows?.[0];\n if (created) this.choose(String(created[item!.key]));\n this.querySelector(\"dialog\")?.close();\n });\n }\n\n private choose(key: string): void {\n const picker = pickerNear(this.opener);\n if (!picker) return;\n picker.value = key;\n picker.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n}\n\n/** The nearest `lb-options` around an element, searching outward. */\nfunction pickerNear(\n el: Element | null,\n): (HTMLElement & { value: string }) | null {\n for (let node = el?.parentElement; node; node = node.parentElement) {\n const found = node.querySelector(\"lb-options\");\n if (found) return found as HTMLElement & { value: string };\n }\n return null;\n}\n\ncustomElements.define(\"lb-insert-dialog\", LbInsertDialog);\n"]}
1
+ {"version":3,"file":"lb-insert-dialog.browser.js","sourceRoot":"","sources":["../widgets/lb-insert-dialog.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAI3B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,cAAe,SAAQ,WAAW;IACtC,8CAA8C;IACtC,MAAM,GAAmB,IAAI,CAAC;IAEtC;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,gBAAgB,CACnB,OAAO,EACP,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EACzC,IAAI,CACL,CAAC;QACF,IAAI,CAAC,gBAAgB,CACnB,SAAS,EACT,CAAC,CAAC,EAAE,EAAE;YACJ,IAAI,CAAC,MAAM,GAAI,CAAyC,CAAC,MAAM,IAAI,IAAI,CAAC;QAC1E,CAAC,EACD,IAAI,CACL,CAAC;IACJ,CAAC;IAED,aAAa,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAkB;QACrD,IAAI,KAAK;YAAE,OAAO;QAClB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,OAAO,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACvC,IAAI,OAAO;YAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACrD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;IACxC,CAAC;IAEO,MAAM,CAAC,GAAW;QACxB,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC;QACnB,MAAM,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;CACF;AAED,qEAAqE;AACrE,SAAS,UAAU,CACjB,EAAkB;IAElB,KAAK,IAAI,IAAI,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,KAAK;YAAE,OAAO,KAAwC,CAAC;IAC7D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,cAAc,CAAC,MAAM,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport type { HubLifecycle, HubRequestDone } from \"@loadbare/app/types\";\n\n/**\n * Two rules. Closing the dialog resets the form, and the row the form creates\n * is chosen in the picker the dialog was opened from.\n *\n * Save closes the dialog when the hub says the insert succeeded, so there\n * is a row to show for it, and a refusal leaves the entry open to correct. Cancel and Escape close it, and throw away what was typed.\n *\n * The picker is the nearest `lb-options` around the button that opened the\n * dialog, which a `command` event names as its `source`. The new row's key is\n * in the patch the insert answers with, and by the time the hub calls\n * `lbRequestDone` that row is among the picker's options. Choosing it fires\n * `change`, so a picker that saves its own column saves it, and one in a ghost\n * row or a new record holds it until Save.\n *\n * `close` and `command` do not bubble, so they are heard in capture.\n */\nclass LbInsertDialog extends HTMLElement implements HubLifecycle {\n /** The button that last opened the dialog. */\n private opener: Element | null = null;\n\n constructor() {\n super();\n this.addEventListener(\n \"close\",\n () => this.querySelector(\"form\")?.reset(),\n true,\n );\n this.addEventListener(\n \"command\",\n (e) => {\n this.opener = (e as Event & { source?: Element | null }).source ?? null;\n },\n true,\n );\n }\n\n lbRequestDone({ request, items, error }: HubRequestDone): void {\n if (error) return;\n const item = items.find((i) => i.query === request.query);\n const created = item?.patch?.rows?.[0];\n if (created) this.choose(String(created[item!.key]));\n this.querySelector(\"dialog\")?.close();\n }\n\n private choose(key: string): void {\n const picker = pickerNear(this.opener);\n if (!picker) return;\n picker.value = key;\n picker.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n}\n\n/** The nearest `lb-options` around an element, searching outward. */\nfunction pickerNear(\n el: Element | null,\n): (HTMLElement & { value: string }) | null {\n for (let node = el?.parentElement; node; node = node.parentElement) {\n const found = node.querySelector(\"lb-options\");\n if (found) return found as HTMLElement & { value: string };\n }\n return null;\n}\n\ncustomElements.define(\"lb-insert-dialog\", LbInsertDialog);\n"]}
@@ -1,4 +1,4 @@
1
- import type { RowsHost } from "@loadbare/app/types";
1
+ import type { HubLifecycle } from "@loadbare/app/types";
2
2
  import { WrappedControl } from "./control.js";
3
3
  /**
4
4
  * A `<select>` whose choices are the rows of the query it names.
@@ -26,7 +26,7 @@ import { WrappedControl } from "./control.js";
26
26
  * Exported because `lb-picker` is this widget with its row template supplied
27
27
  * by the definition instead of by the page.
28
28
  */
29
- export declare class LbOptions extends WrappedControl implements RowsHost {
29
+ export declare class LbOptions extends WrappedControl implements HubLifecycle {
30
30
  protected inner: string;
31
31
  /**
32
32
  * The value last set or chosen, which the options may not include yet.
@@ -1 +1 @@
1
- {"version":3,"file":"lb-options.browser.d.ts","sourceRoot":"","sources":["../widgets/lb-options.browser.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,qBAAa,SAAU,SAAQ,cAAe,YAAW,QAAQ;IAC/D,SAAS,CAAC,KAAK,SAAY;IAE3B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAuB;IAErC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAGtB;IAED,iBAAiB,IAAI,IAAI;IAKzB,SAAS,CAAC,OAAO,IAAI,IAAI;IAKzB,0EAA0E;IAC1E,YAAY,IAAI,IAAI;CAcrB"}
1
+ {"version":3,"file":"lb-options.browser.d.ts","sourceRoot":"","sources":["../widgets/lb-options.browser.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,qBAAa,SAAU,SAAQ,cAAe,YAAW,YAAY;IACnE,SAAS,CAAC,KAAK,SAAY;IAE3B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAuB;IAErC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAGtB;IAED,iBAAiB,IAAI,IAAI;IAKzB,SAAS,CAAC,OAAO,IAAI,IAAI;IAKzB,0EAA0E;IAC1E,YAAY,IAAI,IAAI;CAcrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"lb-options.browser.js","sourceRoot":"","sources":["../widgets/lb-options.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,QAAQ,GACT,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,OAAO,SAAU,SAAQ,cAAc;IACjC,KAAK,GAAG,QAAQ,CAAC;IAE3B;;;OAGG;IACK,MAAM,GAAkB,IAAI,CAAC;IAErC,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC;IACpC,CAAC;IAED,IAAI,KAAK,CAAC,KAAa;QACrB,IAAI,CAAC,MAAM,GAAG,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3E,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,KAAK,CAAC,iBAAiB,EAAE,CAAC;IAC5B,CAAC;IAES,OAAO;QACf,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,IAAI,CAAC;QAC1C,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;IAED,0EAA0E;IAC1E,YAAY;QACV,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC,SAAS,QAAQ,EAAE,CAAC,EAAE,CAAC;YAChE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,YAAY,CAAC,cAAc,CAAE,CAAC,CAAC;QACrE,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,gBAAgB,CACvC,WAAW,UAAU,EAAE,CACxB,EAAE,CAAC;YACF,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;QAC3D,CAAC;QACD,iEAAiE;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;YAAE,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC/D,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;CACF;AAED,cAAc,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_GROUP_VALUE,\n ATTR_KEY_VALUE,\n LIVE_GROUP,\n LIVE_ROW,\n} from \"@loadbare/app/constants\";\nimport type { RowsHost } from \"@loadbare/app/types\";\nimport { WrappedControl } from \"./control.js\";\n\n/**\n * A `<select>` whose choices are the rows of the query it names.\n *\n * The hub places the options by the query's order, and a group template\n * holding an `<optgroup>` puts them in groups. What landing does not give an\n * `<optgroup>` is its label, so the widget sets it from the `lb-group-value`\n * the hub stamps on it.\n *\n * The identity of a row is the value of its option, so the `lb-key-value` the\n * hub stamps on each option is copied to its `value`. The key column is the\n * one the server declared for the query; the page never names it.\n *\n * It is two things at once, and the hub keeps them apart: its own `lb-query`\n * names the rows it offers, and its `lb-column` is a column of the row around\n * it, which is what it holds and what it sends. As a control it is\n * form-associated, with a `value` and a `change` of its own, so the hub lands\n * that column on `value` and commits `lb-request` on `change`.\n *\n * The value is held here as well as on the select. A `<select>` does not hold\n * a value it has no option for: it selects nothing and forgets it. The value\n * and the choices are two answers that land in either order, so the held\n * value is put on the select again whenever rows land.\n *\n * Exported because `lb-picker` is this widget with its row template supplied\n * by the definition instead of by the page.\n */\n\nexport class LbOptions extends WrappedControl implements RowsHost {\n protected inner = \"select\";\n\n /**\n * The value last set or chosen, which the options may not include yet.\n * Null when nothing chose one, so the select's own default stands.\n */\n private chosen: string | null = null;\n\n get value(): string {\n return this.chosen ?? super.value;\n }\n\n set value(value: string) {\n this.chosen = value === null || value === undefined ? null : String(value);\n super.value = value;\n }\n\n formResetCallback(): void {\n this.chosen = null;\n super.formResetCallback();\n }\n\n protected changed(): void {\n this.chosen = this.control?.value ?? null;\n super.changed();\n }\n\n /** Everything derived from the rows, once the whole answer has landed. */\n lbRowsLanded(): void {\n for (const option of this.querySelectorAll(`option${LIVE_ROW}`)) {\n option.setAttribute(\"value\", option.getAttribute(ATTR_KEY_VALUE)!);\n }\n for (const group of this.querySelectorAll<HTMLOptGroupElement>(\n `optgroup${LIVE_GROUP}`,\n )) {\n group.label = group.getAttribute(ATTR_GROUP_VALUE) ?? \"\";\n }\n // After the values above, since the select matches against them.\n const select = this.querySelector(\"select\");\n if (select && this.chosen !== null) select.value = this.chosen;\n this.formValue();\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,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,QAAQ,GACT,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,OAAO,SAAU,SAAQ,cAAc;IACjC,KAAK,GAAG,QAAQ,CAAC;IAE3B;;;OAGG;IACK,MAAM,GAAkB,IAAI,CAAC;IAErC,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC;IACpC,CAAC;IAED,IAAI,KAAK,CAAC,KAAa;QACrB,IAAI,CAAC,MAAM,GAAG,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3E,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,KAAK,CAAC,iBAAiB,EAAE,CAAC;IAC5B,CAAC;IAES,OAAO;QACf,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,IAAI,CAAC;QAC1C,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;IAED,0EAA0E;IAC1E,YAAY;QACV,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC,SAAS,QAAQ,EAAE,CAAC,EAAE,CAAC;YAChE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,YAAY,CAAC,cAAc,CAAE,CAAC,CAAC;QACrE,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,gBAAgB,CACvC,WAAW,UAAU,EAAE,CACxB,EAAE,CAAC;YACF,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;QAC3D,CAAC;QACD,iEAAiE;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;YAAE,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC/D,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;CACF;AAED,cAAc,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_GROUP_VALUE,\n ATTR_KEY_VALUE,\n LIVE_GROUP,\n LIVE_ROW,\n} from \"@loadbare/app/constants\";\nimport type { HubLifecycle } from \"@loadbare/app/types\";\nimport { WrappedControl } from \"./control.js\";\n\n/**\n * A `<select>` whose choices are the rows of the query it names.\n *\n * The hub places the options by the query's order, and a group template\n * holding an `<optgroup>` puts them in groups. What landing does not give an\n * `<optgroup>` is its label, so the widget sets it from the `lb-group-value`\n * the hub stamps on it.\n *\n * The identity of a row is the value of its option, so the `lb-key-value` the\n * hub stamps on each option is copied to its `value`. The key column is the\n * one the server declared for the query; the page never names it.\n *\n * It is two things at once, and the hub keeps them apart: its own `lb-query`\n * names the rows it offers, and its `lb-column` is a column of the row around\n * it, which is what it holds and what it sends. As a control it is\n * form-associated, with a `value` and a `change` of its own, so the hub lands\n * that column on `value` and commits `lb-request` on `change`.\n *\n * The value is held here as well as on the select. A `<select>` does not hold\n * a value it has no option for: it selects nothing and forgets it. The value\n * and the choices are two answers that land in either order, so the held\n * value is put on the select again whenever rows land.\n *\n * Exported because `lb-picker` is this widget with its row template supplied\n * by the definition instead of by the page.\n */\n\nexport class LbOptions extends WrappedControl implements HubLifecycle {\n protected inner = \"select\";\n\n /**\n * The value last set or chosen, which the options may not include yet.\n * Null when nothing chose one, so the select's own default stands.\n */\n private chosen: string | null = null;\n\n get value(): string {\n return this.chosen ?? super.value;\n }\n\n set value(value: string) {\n this.chosen = value === null || value === undefined ? null : String(value);\n super.value = value;\n }\n\n formResetCallback(): void {\n this.chosen = null;\n super.formResetCallback();\n }\n\n protected changed(): void {\n this.chosen = this.control?.value ?? null;\n super.changed();\n }\n\n /** Everything derived from the rows, once the whole answer has landed. */\n lbRowsLanded(): void {\n for (const option of this.querySelectorAll(`option${LIVE_ROW}`)) {\n option.setAttribute(\"value\", option.getAttribute(ATTR_KEY_VALUE)!);\n }\n for (const group of this.querySelectorAll<HTMLOptGroupElement>(\n `optgroup${LIVE_GROUP}`,\n )) {\n group.label = group.getAttribute(ATTR_GROUP_VALUE) ?? \"\";\n }\n // After the values above, since the select matches against them.\n const select = this.querySelector(\"select\");\n if (select && this.chosen !== null) select.value = this.chosen;\n this.formValue();\n }\n}\n\ncustomElements.define(\"lb-options\", LbOptions);\n"]}
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="dom" />
2
- import { ATTR_COLUMN, ATTR_ROW_REQUESTED, LB_DONE_EVENT_NAME, REQUEST_ROW_INSERT, REQUESTED_CREATED, REQUESTED_MOVED, } from "@loadbare/app/constants";
2
+ import { ATTR_COLUMN, ATTR_ROW_REQUESTED, REQUEST_ROW_INSERT, REQUESTED_CREATED, REQUESTED_MOVED, } from "@loadbare/app/constants";
3
3
  /**
4
4
  * A table that owns its own scaffolding.
5
5
  *
@@ -36,27 +36,9 @@ const GHOST = "data-ghost";
36
36
  /** Ghost row forms need ids for the `form` attribute; any unique one will do. */
37
37
  let ghostForms = 0;
38
38
  class LbTable extends HTMLElement {
39
- /**
40
- * A ghost row is for entering the next row, so once one is saved the
41
- * cursor goes back to the start of it. `lb-request-done` comes after the
42
- * response items have landed, so nothing the hub places afterwards takes
43
- * the focus away again. A refused insert leaves the cursor where the user
44
- * left it.
45
- */
46
- constructor() {
47
- super();
48
- this.addEventListener(LB_DONE_EVENT_NAME, (e) => {
49
- const { request, error } = e.detail;
50
- if (error || request.name !== REQUEST_ROW_INSERT)
51
- return;
52
- const row = e.target.closest(`[${GHOST}]`);
53
- if (row)
54
- firstControl(row)?.focus();
55
- });
56
- }
57
- connectedCallback() {
39
+ lbAttached() {
58
40
  const ghost = this.querySelector(`tbody[${GHOST}]`);
59
- if (!ghost?.firstElementChild || this.ghostForm())
41
+ if (!ghost?.firstElementChild)
60
42
  return;
61
43
  const form = document.createElement("form");
62
44
  form.id = `ghost-row-${++ghostForms}`;
@@ -71,9 +53,19 @@ class LbTable extends HTMLElement {
71
53
  this.querySelector(`[${ATTR_ROW_REQUESTED}="${REQUESTED_CREATED}"], ` +
72
54
  `[${ATTR_ROW_REQUESTED}="${REQUESTED_MOVED}"]`)?.scrollIntoView({ block: "nearest" });
73
55
  }
74
- /** The ghost row's form, once this table has made it. */
75
- ghostForm() {
76
- return this.querySelector(":scope > form");
56
+ /**
57
+ * A ghost row is for entering the next row, so once one is saved the
58
+ * cursor goes back to the start of it. The hub calls this after the
59
+ * response items have landed, so nothing it places afterwards takes the
60
+ * focus away again. A refused insert leaves the cursor where the user left
61
+ * it.
62
+ */
63
+ lbRequestDone({ request, error }, origin) {
64
+ if (error || request.name !== REQUEST_ROW_INSERT)
65
+ return;
66
+ const row = origin.closest(`[${GHOST}]`);
67
+ if (row)
68
+ firstControl(row)?.focus();
77
69
  }
78
70
  }
79
71
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"lb-table.browser.js","sourceRoot":"","sources":["../widgets/lb-table.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,GAChB,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,KAAK,GAAG,YAAY,CAAC;AAE3B,iFAAiF;AACjF,IAAI,UAAU,GAAG,CAAC,CAAC;AAEnB,MAAM,OAAQ,SAAQ,WAAW;IAC/B;;;;;;OAMG;IACH;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,CAAC,CAAC,EAAE,EAAE;YAC9C,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAI,CAAiC,CAAC,MAAM,CAAC;YACrE,IAAI,KAAK,IAAI,OAAO,CAAC,IAAI,KAAK,kBAAkB;gBAAE,OAAO;YACzD,MAAM,GAAG,GAAI,CAAC,CAAC,MAAkB,CAAC,OAAO,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;YACxD,IAAI,GAAG;gBAAE,YAAY,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,iBAAiB;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,KAAK,GAAG,CAAC,CAAC;QACpD,IAAI,CAAC,KAAK,EAAE,iBAAiB,IAAI,IAAI,CAAC,SAAS,EAAE;YAAE,OAAO;QAE1D,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,EAAE,GAAG,aAAa,EAAE,UAAU,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClB,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,gBAAgB,CAAC,IAAI,WAAW,WAAW,CAAC,EAAE,CAAC;YACpE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,YAAY;QACV,IAAI,CAAC,aAAa,CAChB,IAAI,kBAAkB,KAAK,iBAAiB,MAAM;YAChD,IAAI,kBAAkB,KAAK,eAAe,IAAI,CACjD,EAAE,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,yDAAyD;IACjD,SAAS;QACf,OAAO,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;IAC7C,CAAC;CACF;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,GAAY;IAChC,OAAO;QACL,GAAG,GAAG,CAAC,gBAAgB,CACrB,8CAA8C,CAC/C;KACF,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,eAAe,EAAE,EAAE,KAAK,KAAK,CAAC,CAAC;AAC/E,CAAC;AAED,cAAc,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_COLUMN,\n ATTR_ROW_REQUESTED,\n LB_DONE_EVENT_NAME,\n REQUEST_ROW_INSERT,\n REQUESTED_CREATED,\n REQUESTED_MOVED,\n} from \"@loadbare/app/constants\";\nimport type { HubRequestDone, RowsHost } from \"@loadbare/app/types\";\n\n/**\n * A table that owns its own scaffolding.\n *\n * A plain `<table lb-query>` 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-exp-template` destination beside the slot.\n *\n * Placement and groups are the hub's, by the query's order and the page's\n * group templates. What this widget adds at run time is the ghost row, and\n * scrolling to the row the page's own request created or moved.\n *\n * The `foot` destination needs no code here. A `<tfoot>` is a place on the\n * page, not a row the hub delivers, so what lands in it is an aggregate or a\n * `row` query of its own, named on its `<tr>`.\n */\n\n/**\n * The ghost row's `<tbody>`, a blank row for entering a new one, between the\n * rows and the footer. The page writes it as the `ghost` template, so it\n * ships in the document like the heading and the footer, and the hub lands\n * on whatever in it names a query. It is not a live row: the hub never\n * reconciles it and never counts it.\n *\n * A `<form>` cannot wrap a `<tr>`, so the widget makes one outside the table\n * and joins the row's controls and buttons to it with the HTML `form`\n * attribute. Sending is the page's: a button in the row carrying\n * `lb-row-insert` submits that form, and the hub gathers what the form owns.\n * The hub resets the form once the insert succeeds.\n */\nconst GHOST = \"data-ghost\";\n\n/** Ghost row forms need ids for the `form` attribute; any unique one will do. */\nlet ghostForms = 0;\n\nclass LbTable extends HTMLElement implements RowsHost {\n /**\n * A ghost row is for entering the next row, so once one is saved the\n * cursor goes back to the start of it. `lb-request-done` comes after the\n * response items have landed, so nothing the hub places afterwards takes\n * the focus away again. A refused insert leaves the cursor where the user\n * left it.\n */\n constructor() {\n super();\n this.addEventListener(LB_DONE_EVENT_NAME, (e) => {\n const { request, error } = (e as CustomEvent<HubRequestDone>).detail;\n if (error || request.name !== REQUEST_ROW_INSERT) return;\n const row = (e.target as Element).closest(`[${GHOST}]`);\n if (row) firstControl(row)?.focus();\n });\n }\n\n connectedCallback(): void {\n const ghost = this.querySelector(`tbody[${GHOST}]`);\n if (!ghost?.firstElementChild || this.ghostForm()) return;\n\n const form = document.createElement(\"form\");\n form.id = `ghost-row-${++ghostForms}`;\n form.hidden = true;\n this.append(form);\n for (const el of ghost.querySelectorAll(`[${ATTR_COLUMN}], button`)) {\n el.setAttribute(\"form\", form.id);\n }\n }\n\n /** The row this page's request created or moved, brought into view. */\n lbRowsLanded(): void {\n this.querySelector(\n `[${ATTR_ROW_REQUESTED}=\"${REQUESTED_CREATED}\"], ` +\n `[${ATTR_ROW_REQUESTED}=\"${REQUESTED_MOVED}\"]`,\n )?.scrollIntoView({ block: \"nearest\" });\n }\n\n /** The ghost row's form, once this table has made it. */\n private ghostForm(): HTMLFormElement | null {\n return this.querySelector(\":scope > form\");\n }\n}\n\n/**\n * The first control in a row the user can type into or choose from. A\n * control a widget wraps is found inside it, since the wrapper takes no focus.\n */\nfunction firstControl(row: Element): HTMLElement | undefined {\n return [\n ...row.querySelectorAll<HTMLElement>(\n 'input:not([type=\"hidden\"]), select, textarea',\n ),\n ].find((el) => !el.matches(\":disabled\") && el.checkVisibility?.() !== false);\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":"AAAA,2BAA2B;AAE3B,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,GAChB,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,KAAK,GAAG,YAAY,CAAC;AAE3B,iFAAiF;AACjF,IAAI,UAAU,GAAG,CAAC,CAAC;AAEnB,MAAM,OAAQ,SAAQ,WAAW;IAC/B,UAAU;QACR,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,KAAK,GAAG,CAAC,CAAC;QACpD,IAAI,CAAC,KAAK,EAAE,iBAAiB;YAAE,OAAO;QAEtC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,EAAE,GAAG,aAAa,EAAE,UAAU,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClB,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,gBAAgB,CAAC,IAAI,WAAW,WAAW,CAAC,EAAE,CAAC;YACpE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,YAAY;QACV,IAAI,CAAC,aAAa,CAChB,IAAI,kBAAkB,KAAK,iBAAiB,MAAM;YAChD,IAAI,kBAAkB,KAAK,eAAe,IAAI,CACjD,EAAE,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,EAAE,KAAK,EAAkB,EAAE,MAAe;QAC/D,IAAI,KAAK,IAAI,OAAO,CAAC,IAAI,KAAK,kBAAkB;YAAE,OAAO;QACzD,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;QACzC,IAAI,GAAG;YAAE,YAAY,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;IACtC,CAAC;CACF;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,GAAY;IAChC,OAAO;QACL,GAAG,GAAG,CAAC,gBAAgB,CACrB,8CAA8C,CAC/C;KACF,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,eAAe,EAAE,EAAE,KAAK,KAAK,CAAC,CAAC;AAC/E,CAAC;AAED,cAAc,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_COLUMN,\n ATTR_ROW_REQUESTED,\n REQUEST_ROW_INSERT,\n REQUESTED_CREATED,\n REQUESTED_MOVED,\n} from \"@loadbare/app/constants\";\nimport type { HubLifecycle, HubRequestDone } from \"@loadbare/app/types\";\n\n/**\n * A table that owns its own scaffolding.\n *\n * A plain `<table lb-query>` 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-exp-template` destination beside the slot.\n *\n * Placement and groups are the hub's, by the query's order and the page's\n * group templates. What this widget adds at run time is the ghost row, and\n * scrolling to the row the page's own request created or moved.\n *\n * The `foot` destination needs no code here. A `<tfoot>` is a place on the\n * page, not a row the hub delivers, so what lands in it is an aggregate or a\n * `row` query of its own, named on its `<tr>`.\n */\n\n/**\n * The ghost row's `<tbody>`, a blank row for entering a new one, between the\n * rows and the footer. The page writes it as the `ghost` template, so it\n * ships in the document like the heading and the footer, and the hub lands\n * on whatever in it names a query. It is not a live row: the hub never\n * reconciles it and never counts it.\n *\n * A `<form>` cannot wrap a `<tr>`, so the widget makes one outside the table\n * and joins the row's controls and buttons to it with the HTML `form`\n * attribute. Sending is the page's: a button in the row carrying\n * `lb-row-insert` submits that form, and the hub gathers what the form owns.\n * The hub resets the form once the insert succeeds.\n */\nconst GHOST = \"data-ghost\";\n\n/** Ghost row forms need ids for the `form` attribute; any unique one will do. */\nlet ghostForms = 0;\n\nclass LbTable extends HTMLElement implements HubLifecycle {\n lbAttached(): void {\n const ghost = this.querySelector(`tbody[${GHOST}]`);\n if (!ghost?.firstElementChild) return;\n\n const form = document.createElement(\"form\");\n form.id = `ghost-row-${++ghostForms}`;\n form.hidden = true;\n this.append(form);\n for (const el of ghost.querySelectorAll(`[${ATTR_COLUMN}], button`)) {\n el.setAttribute(\"form\", form.id);\n }\n }\n\n /** The row this page's request created or moved, brought into view. */\n lbRowsLanded(): void {\n this.querySelector(\n `[${ATTR_ROW_REQUESTED}=\"${REQUESTED_CREATED}\"], ` +\n `[${ATTR_ROW_REQUESTED}=\"${REQUESTED_MOVED}\"]`,\n )?.scrollIntoView({ block: \"nearest\" });\n }\n\n /**\n * A ghost row is for entering the next row, so once one is saved the\n * cursor goes back to the start of it. The hub calls this after the\n * response items have landed, so nothing it places afterwards takes the\n * focus away again. A refused insert leaves the cursor where the user left\n * it.\n */\n lbRequestDone({ request, error }: HubRequestDone, origin: Element): void {\n if (error || request.name !== REQUEST_ROW_INSERT) return;\n const row = origin.closest(`[${GHOST}]`);\n if (row) firstControl(row)?.focus();\n }\n}\n\n/**\n * The first control in a row the user can type into or choose from. A\n * control a widget wraps is found inside it, since the wrapper takes no focus.\n */\nfunction firstControl(row: Element): HTMLElement | undefined {\n return [\n ...row.querySelectorAll<HTMLElement>(\n 'input:not([type=\"hidden\"]), select, textarea',\n ),\n ].find((el) => !el.matches(\":disabled\") && el.checkVisibility?.() !== false);\n}\n\ncustomElements.define(\"lb-table\", LbTable);\n"]}
@@ -37,8 +37,7 @@ sits in, so the request carries that row's query and key, and the message
37
37
  can show the row's columns with `lb-column`.
38
38
 
39
39
  Cancel comes first and takes focus, so Enter on a dialog opened by accident
40
- sends nothing. The widget closes the dialog when `lb-request-done` says the
41
- request succeeded. A refused request leaves it open, with the hub's
40
+ sends nothing. The widget closes the dialog when the request succeeds. A refused request leaves it open, with the hub's
42
41
  `lb-request-error` on the confirm button.
43
42
 
44
43
  The dialog is found by its place in the tag, not by id, so the tag works in a
package/docs/lb-input.md CHANGED
@@ -45,10 +45,6 @@ sets the value of the `<input>` inside it, and it fires `change` on itself when
45
45
  that `<input>` fires `change`. A form reset returns the `<input>` to its
46
46
  default.
47
47
 
48
- A value that lands on a live row before the row's elements upgrade reaches the
49
- widget as `lb-column-value`, and the widget takes it up when it first
50
- connects.
51
-
52
48
  ## Inside a form
53
49
 
54
50
  An `lb-input` inside an `lb-row-insert` or `lb-row-update` form carries no
@@ -39,8 +39,7 @@ the control it serves.
39
39
 
40
40
  ## What it does
41
41
 
42
- Save is the form's `lb-row-insert`. When `lb-request-done` says it
43
- succeeded, the widget chooses the new row in the nearest `lb-options` around
42
+ Save is the form's `lb-row-insert`. When it succeeds, the widget chooses the new row in the nearest `lb-options` around
44
43
  the button that opened the dialog, fires `change` on it, and closes the
45
44
  dialog. The new key comes from the patch the insert answers with, so the
46
45
  handler answers with the row it created. A picker that saves its own column
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loadbare/widgets",
3
3
  "description": "The basic widget set for @loadbare/app: input, select, options, picker, table, unknown-page, confirm, form, insert-dialog, master, checkbox",
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -49,10 +49,10 @@
49
49
  "node": ">=22"
50
50
  },
51
51
  "peerDependencies": {
52
- "@loadbare/app": ">=0.13.0 <1.0.0"
52
+ "@loadbare/app": ">=0.15.0 <1.0.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@loadbare/app": ">=0.13.0 <1.0.0",
55
+ "@loadbare/app": ">=0.15.0 <1.0.0",
56
56
  "@types/jsdom": "^21.1.7",
57
57
  "@types/node": "^22.0.0",
58
58
  "jsdom": "^25.0.0",