@adia-ai/web-modules 0.8.20 → 0.8.21

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.
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/FormPopover.json",
4
4
  "title": "FormPopover",
5
- "description": "Module-tier \"form fragment in a popover\" (operator mock, 2026-07-27): a\nselect-style summary trigger that opens an anchored panel holding ANY\nform primitives — check-ui groups, radio-ui groups, input-ui,\ndivider-ui, field-ui — slotted as ordinary light-DOM children. The\nmodule owns the trigger + popover packaging and a live selection\nsummary; the slotted controls keep their own name/value/event\ncontracts untouched.\n\nSummary contract: the trigger reads `{label} · N selected` while any\nslotted check-ui is checked, `{label} · N total` when none are (N =\ncheck-ui count), and just `{label}` when the fragment contains no\ncheck-ui at all. Radio groups, inputs, and other controls deliberately\ndo not count — checkboxes are the only \"N selected\" semantic that\nreads unambiguously in a summary.\n\nDecision rule vs adjacent surfaces (inherits popover-ui's): a pure\naction list is menu-ui; a single-value choice is select-ui (which owns\nits option list — do NOT rebuild select inside this module); an\nedge-anchored multi-field form is drawer-ui. form-popover-ui is for the\nin-between: a small anchored fragment mixing selection controls and\ninputs, e.g. a filter panel or a quick-save form.\n",
5
+ "description": "Module-tier \"form fragment in a popover\" (operator mock, 2026-07-27): a\nselect-style summary trigger that opens an anchored panel holding ANY\nform primitives — check-ui groups, radio-ui groups, input-ui,\ndivider-ui, field-ui — slotted as ordinary light-DOM children. The\nmodule owns the trigger + popover packaging and a live selection\nsummary; the slotted controls keep their own name/value/event\ncontracts untouched.\n\nSummary contract (gh#474 identical to select-ui[summary-label], so the\ntwo multi-select triggers speak one language): none checked reads just\n`{label}` (which doubles as the placeholder, since [label] is required\nhere), exactly one reads THAT check-ui's own label, and more than one\nreads `{label} (N)`. Radio groups, inputs, and other controls\ndeliberately do not count — checkboxes are the only selection semantic\nthat reads unambiguously in a summary.\n\nDecision rule vs adjacent surfaces (inherits popover-ui's): a pure\naction list is menu-ui; a single-value choice is select-ui (which owns\nits option list — do NOT rebuild select inside this module); an\nedge-anchored multi-field form is drawer-ui. form-popover-ui is for the\nin-between: a small anchored fragment mixing selection controls and\ninputs, e.g. a filter panel or a quick-save form.\n",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -66,12 +66,16 @@ class FormPopover extends UIElement {
66
66
  }
67
67
  }
68
68
 
69
- /** { selected, total } over the slotted check-ui population. */
69
+ /**
70
+ * { selected, total, checked } over the slotted check-ui population.
71
+ * `checked` is the checked elements themselves — the summary needs the
72
+ * single-selection element to read its own label (gh#474).
73
+ */
70
74
  get summary() {
71
75
  const boxes = this.#bodyEl ? this.#bodyEl.querySelectorAll('check-ui') : [];
72
- let selected = 0;
73
- for (const box of boxes) if (box.hasAttribute('checked') || box.checked) selected += 1;
74
- return { selected, total: boxes.length };
76
+ const checked = [];
77
+ for (const box of boxes) if (box.hasAttribute('checked') || box.checked) checked.push(box);
78
+ return { selected: checked.length, total: boxes.length, checked };
75
79
  }
76
80
 
77
81
  #stamp() {
@@ -113,14 +117,23 @@ class FormPopover extends UIElement {
113
117
  el.textContent = this.heading;
114
118
  }
115
119
 
120
+ /** The own label of a slotted check-ui — [label] prop, else its text. */
121
+ #labelOf(box) {
122
+ return (box.getAttribute('label') || box.label || box.textContent || '').trim();
123
+ }
124
+
116
125
  #updateSummary() {
117
126
  if (!this.#triggerEl) return;
118
- const { selected, total } = this.summary;
127
+ const { selected, total, checked } = this.summary;
128
+ // gh#474 — ONE summary contract across the two multi-select triggers:
129
+ // none → the label alone (it doubles as select-ui's placeholder, since
130
+ // [label] is required here), one → that control's own label, more →
131
+ // "Label (N)". Was "Label · N selected", which contradicted
132
+ // select-ui[summary-label]'s ruled format (gh#442) and blocked
133
+ // consumers from swapping a hand-rolled composition for this module.
119
134
  let text = this.label;
120
- if (total > 0) {
121
- const count = selected > 0 ? `${selected} selected` : `${total} total`;
122
- text = text ? `${text} · ${count}` : count;
123
- }
135
+ if (selected === 1 && checked[0]) text = this.#labelOf(checked[0]) || text;
136
+ else if (selected > 1) text = text ? `${text} (${selected})` : `${selected}`;
124
137
  // [label] is required by contract; this fallback only guards the
125
138
  // no-label + no-checkbox misuse so the trigger never renders as an
126
139
  // empty, nameless button (a11y — CodeRabbit finding on #441).
@@ -15,12 +15,13 @@ description: |
15
15
  summary; the slotted controls keep their own name/value/event
16
16
  contracts untouched.
17
17
 
18
- Summary contract: the trigger reads `{label} · N selected` while any
19
- slotted check-ui is checked, `{label} · N total` when none are (N =
20
- check-ui count), and just `{label}` when the fragment contains no
21
- check-ui at all. Radio groups, inputs, and other controls deliberately
22
- do not count checkboxes are the only "N selected" semantic that
23
- reads unambiguously in a summary.
18
+ Summary contract (gh#474 identical to select-ui[summary-label], so the
19
+ two multi-select triggers speak one language): none checked reads just
20
+ `{label}` (which doubles as the placeholder, since [label] is required
21
+ here), exactly one reads THAT check-ui's own label, and more than one
22
+ reads `{label} (N)`. Radio groups, inputs, and other controls
23
+ deliberately do not count — checkboxes are the only selection semantic
24
+ that reads unambiguously in a summary.
24
25
 
25
26
  Decision rule vs adjacent surfaces (inherits popover-ui's): a pure
26
27
  action list is menu-ui; a single-value choice is select-ui (which owns
@@ -97,12 +98,13 @@ a2ui:
97
98
  small MIXED fragment (checks + radios + an input) behind a summary
98
99
  trigger.
99
100
  - >-
100
- The trigger summary counts CheckBox children only ("N selected" /
101
- "N total"); Radio and Input children never count. Give every Radio
102
- child one shared name per group or the browser treats them as
103
- independent.
101
+ The trigger summary counts CheckBox children only one checked
102
+ shows its own label, more show "Label (N)", none shows the bare
103
+ label (same contract as Select[summary-label]); Radio and Input
104
+ children never count. Give every Radio child one shared name per
105
+ group or the browser treats them as independent.
104
106
  props:
105
- label: { type: string, description: "Summary-trigger prefix text" }
107
+ label: { type: string, description: "Summary-trigger text — shown alone when nothing is checked (its placeholder role) and as the \"Label (N)\" prefix when more than one is" }
106
108
  heading: { type: string, description: "Optional panel heading" }
107
109
  placement: { type: string, description: "Popover placement (default bottom-start)" }
108
110
  children: form primitives (CheckBox, Radio, Input, Divider, Field)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-modules",
3
- "version": "0.8.20",
3
+ "version": "0.8.21",
4
4
  "description": "AdiaUI composite custom elements \u2014 shell, chat, editor, runtime clusters built from @adia-ai/web-components primitives. Subpath exports per cluster.",
5
5
  "type": "module",
6
6
  "exports": {