@kerfjs/ui 5.0.0-beta.31 → 5.0.0-beta.33

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.
@@ -2316,7 +2316,8 @@
2316
2316
  "obligations": [
2317
2317
  "Verify the live value, standard events, focus, and rendered output.",
2318
2318
  "A nonempty label names the internal combobox; otherwise ariaLabel supplies a visually hidden label without adding control height, including custom selected content.",
2319
- "Hint text is visible below the control through Web Awesome's native hint relationship; placeholderText remains the empty value inside the closed control."
2319
+ "Hint text is visible below the control through Web Awesome's native hint relationship; placeholderText remains the empty value inside the closed control.",
2320
+ "Import select/register once; its scoped lifecycle adapter keeps the latest open/close request authoritative across animation reversals, canceled transitions, and removal. Do not add consumer popup timing or position repairs."
2320
2321
  ]
2321
2322
  },
2322
2323
  "cssValueProps": [
@@ -2852,7 +2852,8 @@
2852
2852
  "accessibility": [
2853
2853
  "Verify the live value, standard events, focus, and rendered output.",
2854
2854
  "A nonempty label names the internal combobox; otherwise ariaLabel supplies a visually hidden label without adding control height, including custom selected content.",
2855
- "Hint text is visible below the control through Web Awesome's native hint relationship; placeholderText remains the empty value inside the closed control."
2855
+ "Hint text is visible below the control through Web Awesome's native hint relationship; placeholderText remains the empty value inside the closed control.",
2856
+ "Import select/register once; its scoped lifecycle adapter keeps the latest open/close request authoritative across animation reversals, canceled transitions, and removal. Do not add consumer popup timing or position repairs."
2856
2857
  ],
2857
2858
  "geometry": {
2858
2859
  "margin": "none",
@@ -1,6 +1,6 @@
1
1
  # Public API signatures for the UI authoring corpus
2
2
 
3
- Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.31` and `kerfjs@5.0.0-beta.31`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
3
+ Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.33` and `kerfjs@5.0.0-beta.33`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
4
4
 
5
5
  ## `@kerfjs/ui/css-values`
6
6
 
package/ai/skill.md CHANGED
@@ -129,7 +129,7 @@ Hard rules:
129
129
  14. Treat the complete Web Awesome catalog as support coverage, not a recommendation list. Its UX sidebar marks superseded and exceptional entries `Discouraged`. Consider Popup when it replaces custom anchored positioning. Prefer Kerf `Select` over direct Dropdown/Dropdown Item/Select/Option composition, `SegmentedControl` over Button Group, `TabBar` or `SegmentedControl` over Web Awesome Tabs, `LucideIcon` over Web Awesome Icon, and `ResizableRegion` over Split Panel. Use Tree/Tree Item, Animated Image, and Comparison only for a specific required behavior; avoid Zoomable Frame.
130
130
  15. Build sidebars, main areas, inspectors, and dialogs from `@kerfjs/ui/layout.css`: an unpadded `.kui-pane`, optional `.kui-pane__toolbar`, one scrolling `.kui-pane__content`, and optional `.kui-pane__footer`. Use `List` instead of a hand-written flex-column wrapper when rows or sections need stretch alignment, standard/custom gap, flex growth, one vertical scroll owner, or physical-edge dividers; `dividerSides` uses canonical `t`/`r`/`b`/`l` order. A dialog body is generally a `List`: set `DialogSurface bodyInset="none"` when its list children own geometry, and wrap bare dialog prose in `ListInsetText` so it aligns with bordered siblings without a second inset. Add `.kui-content` for 24px major vertical separation and `.kui-content-item` for a child-owned 8px inline margin, 1px transparent-or-visible border, 8px padding, and 12px radius. Use the pill modifier for 22px. Do not pad pane shells, duplicate item geometry in wrappers, or create nested scroll owners.
131
131
  16. Keep a visible collapsible pane's collapse control in its own toolbar. When hidden, move its restore control into the adjacent main toolbar on the corresponding logical edge: leading for an inline-start sidebar and trailing for an inline-end inspector. Collapse the pane completely; do not preserve an empty icon-only rail.
132
- 17. The only direct children of a `Toolbar` zone (`leading`/`center`/`trailing`) are `ToolbarText` (identity/title text) and `ToolbarControlGroup`; never drop bare buttons, inputs, links, or arbitrary markup straight into a zone. Put identity/title text directly in the zone as `ToolbarText`; wrap interactive controls and icon tiles in `ToolbarControlGroup`. `SegmentedControl`, `Select`, a collapsible `TokenSearchField`, and Web Awesome controls all live inside a group. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature, chiefly a `wa-dropdown` popup trigger. A popup menu in a toolbar is a `single` group wrapping a `wa-dropdown` whose `slot="trigger"` `wa-button` is the toolbar button and whose `wa-dropdown-item`s are the menu, with the dropdown kept under `data-morph-skip-children`. For an avatar control, set `content="avatar"` and `avatarImage` on the group instead of inserting an `img`: a lone control paints the contained image on the outer group and a multi-button group paints it only on the pressed selection highlight. Compose panel/dialog/page headings with a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Split dormant and interactive regions: `ListHeader` fills the available inline width and keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end 44px action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-list-header-action-icon-size`; never shrink the target to match it. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `ListItem.trailing` is dormant; use `ListActionRow` when primary and trailing actions need sibling 44px native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets.
132
+ 17. The only direct children of a `Toolbar` zone (`leading`/`center`/`trailing`) are `ToolbarText` (identity/title text) and `ToolbarControlGroup`; never drop bare buttons, inputs, links, or arbitrary markup straight into a zone. Put identity/title text directly in the zone as `ToolbarText`; wrap interactive controls and icon tiles in `ToolbarControlGroup`. `SegmentedControl`, `Select`, a collapsible `TokenSearchField`, and Web Awesome controls all live inside a group. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature, chiefly a `wa-dropdown` popup trigger. A popup menu in a toolbar is a `single` group wrapping a `wa-dropdown` whose `slot="trigger"` `wa-button` is the toolbar button and whose `wa-dropdown-item`s are the menu, with the dropdown kept under `data-morph-skip-children`. For a compact mixed group, keep the standard item padding and raised selected chrome; the selected item paints over the outer border without an internal separator. For an avatar control, set `content="avatar"` and `avatarImage` on the group instead of inserting an `img`: a lone control paints the contained image on the outer group and a multi-button group paints it only on the pressed selection highlight. Compose panel/dialog/page headings with a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Split dormant and interactive regions: `ListHeader` fills the available inline width and keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end 44px action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-list-header-action-icon-size`; never shrink the target to match it. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `ListItem.trailing` is dormant; use `ListActionRow` when primary and trailing actions need sibling 44px native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets.
133
133
  18. When a recurring concept has no matching export or production recipe, keep its semantics in a thin application adapter while reusing the public layout vocabulary. The composer recipe uses one visible form surface, toolbar title/supporting-copy ids, shared 8px field/action gutters, 24px major rhythm, and a conditional StateBanner as its only nested semantic surface; do not turn every section into a card or double-inset intrinsically bordered controls. The application-local `../docs/examples/command-palette-adapter.tsx` is reference source for one such missing concept, not an `@kerfjs/ui` runtime export or catalog recipe. The application owns its registration, ranking, history, permissions, availability, shortcut policy, focus policy, dispatch, and copy. If a missing concept recurs across products, open an upstream component or recipe request.
134
134
  19. Compose `ValueTable` from typed `ValueTableRow` entries instead of handwritten `dt`/`dd` wrappers. Pass `icon` for the optional 24px leading visual; the row owns 8px of root-scaled top and bottom padding, the 8px iconless or 40px icon-bearing separator start, and the common 8px right inset.
135
135
  20. Use the standard direct `Toolbar` composition for a panel, dialog, or page heading, not a private wrapper or custom heading row. Put an optional icon in a `ToolbarControlGroup`, the title directly in the leading zone as extra-large `ToolbarText`, and actions in a trailing group. Set `headingLevel` for page/section landmarks. Supporting copy stays below the toolbar as app-owned content. The app owns modal behavior, focus, dismissal, command policy, ids, and action handling.
@@ -166,3 +166,11 @@ Common mistakes:
166
166
  | Put raw prose directly in a padded dialog body | Compose the body as `List`, set `bodyInset="none"` for list-owned geometry, and wrap bare prose in `ListInsetText` |
167
167
  | Keep chrome, a label, or a readout that aids no decision | Delete it; every element must help a person decide or act |
168
168
  | Override a component's default size or color because it "looks off" | Trust the default (a LucideIcon is 24px) and fix the surrounding layout instead |
169
+
170
+ ## Select lifecycle ownership
171
+
172
+ Import `@kerfjs/ui/select/register` once. It owns the canonical Select animation
173
+ lifecycle: the latest request wins through open/close reversals, canceled
174
+ transitions, and removal. Native value, keyboard, dismissal, and anchor placement
175
+ remain with Web Awesome. Do not add popup delays, position repairs, or animation
176
+ completion handlers in consumers. Raw `wa-select` elements remain native.
@@ -1,6 +1,6 @@
1
1
  # Web Awesome JSX signatures for the UI authoring corpus
2
2
 
3
- Generated from the emitted `@kerfjs/ui@5.0.0-beta.31` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
3
+ Generated from the emitted `@kerfjs/ui@5.0.0-beta.33` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
4
4
 
5
5
  ```ts
6
6
  import { KerfCustomElement } from 'kerfjs/jsx-runtime';
@@ -3,9 +3,134 @@ import '@awesome.me/webawesome/dist/components/divider/divider.js';
3
3
  import '@awesome.me/webawesome/dist/components/dropdown/dropdown.js';
4
4
  import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
5
5
  import '@awesome.me/webawesome/dist/components/option/option.js';
6
- import '@awesome.me/webawesome/dist/components/select/select.js';
6
+ import WaSelect from '@awesome.me/webawesome/dist/components/select/select.js';
7
+ import { WaAfterHideEvent } from '@awesome.me/webawesome/dist/events/after-hide.js';
8
+ import { WaAfterShowEvent } from '@awesome.me/webawesome/dist/events/after-show.js';
9
+ import { WaHideEvent } from '@awesome.me/webawesome/dist/events/hide.js';
10
+ import { WaShowEvent } from '@awesome.me/webawesome/dist/events/show.js';
7
11
 
8
12
  // src/select-register.ts
13
+
14
+ // src/animate-select-popup.ts
15
+ function animateSelectPopup(popup, phase, signal) {
16
+ return new Promise((resolve) => {
17
+ let settled = false;
18
+ let frame = 0;
19
+ const finish = () => {
20
+ if (settled) return;
21
+ settled = true;
22
+ window.cancelAnimationFrame(frame);
23
+ signal.removeEventListener("abort", finish);
24
+ popup.classList.remove(phase);
25
+ resolve();
26
+ };
27
+ signal.addEventListener("abort", finish, { once: true });
28
+ popup.classList.add(phase);
29
+ frame = window.requestAnimationFrame(() => {
30
+ void Promise.all(
31
+ popup.getAnimations().map((animation) => animation.finished.catch(() => void 0))
32
+ ).then(finish);
33
+ });
34
+ if (signal.aborted) finish();
35
+ });
36
+ }
37
+
38
+ // src/install-select-lifecycle.ts
39
+ function installSelectLifecycle(prototype) {
40
+ const installed = /* @__PURE__ */ Symbol.for("@kerfjs/ui/select-lifecycle");
41
+ if (Object.prototype.hasOwnProperty.call(prototype, installed)) return;
42
+ Object.defineProperty(prototype, installed, { value: true });
43
+ const original = prototype.handleOpenChange;
44
+ const originalDisabled = prototype.handleDisabledChange;
45
+ prototype.handleDisabledChange = function() {
46
+ const host = this;
47
+ const wasOpen = host.open;
48
+ originalDisabled.call(this);
49
+ if (host.dataset.component === "select" && wasOpen && !host.open) {
50
+ void host.handleOpenChange();
51
+ }
52
+ };
53
+ const transitions = /* @__PURE__ */ new WeakMap();
54
+ const restorations = /* @__PURE__ */ new WeakMap();
55
+ prototype.handleOpenChange = async function() {
56
+ const host = this;
57
+ if (host.dataset.component !== "select") return original.call(this);
58
+ const restoring = restorations.get(host)?.open === host.open;
59
+ restorations.delete(host);
60
+ if (restoring) return;
61
+ const transition = new AbortController();
62
+ const opening = host.open && !host.disabled;
63
+ const current = () => !transition.signal.aborted && host.isConnected && (host.open && !host.disabled) === opening;
64
+ try {
65
+ if (!host.isConnected) return;
66
+ if (opening) {
67
+ host.setCurrentOption(host.selectedOptions[0] || host.getFirstOption());
68
+ }
69
+ const event = opening ? new WaShowEvent() : new WaHideEvent();
70
+ host.dispatchEvent(event);
71
+ if (event.defaultPrevented) {
72
+ host.open = !opening;
73
+ const restoration = { open: host.open };
74
+ restorations.set(host, restoration);
75
+ void host.updateComplete.then(() => {
76
+ if (restorations.get(host) === restoration) restorations.delete(host);
77
+ });
78
+ return;
79
+ }
80
+ if (!current()) return;
81
+ transitions.get(host)?.abort();
82
+ transitions.set(host, transition);
83
+ if (opening) {
84
+ host.addOpenListeners();
85
+ host.listbox.hidden = false;
86
+ host.popup.active = true;
87
+ window.requestAnimationFrame(() => {
88
+ if (current()) host.setCurrentOption(host.currentOption);
89
+ });
90
+ } else {
91
+ host.removeOpenListeners();
92
+ }
93
+ await animateSelectPopup(
94
+ host.popup.popup,
95
+ opening ? "show" : "hide",
96
+ transition.signal
97
+ );
98
+ if (!transition.signal.aborted && !host.isConnected) {
99
+ host.removeOpenListeners();
100
+ host.listbox.hidden = true;
101
+ host.popup.active = false;
102
+ }
103
+ if (!current()) return;
104
+ if (opening) {
105
+ const option = host.currentOption;
106
+ if (option) {
107
+ const list = host.listbox;
108
+ const offset = Math.round(
109
+ option.getBoundingClientRect().top - list.getBoundingClientRect().top
110
+ );
111
+ if (offset < 0)
112
+ list.scrollTo({ top: list.scrollTop + offset, behavior: "auto" });
113
+ else if (offset + option.clientHeight > list.offsetHeight) {
114
+ list.scrollTo({
115
+ top: list.scrollTop + offset - list.offsetHeight + option.clientHeight,
116
+ behavior: "auto"
117
+ });
118
+ }
119
+ }
120
+ host.dispatchEvent(new WaAfterShowEvent());
121
+ } else {
122
+ host.listbox.hidden = true;
123
+ host.popup.active = false;
124
+ host.dispatchEvent(new WaAfterHideEvent());
125
+ }
126
+ } finally {
127
+ if (transitions.get(host) === transition) transitions.delete(host);
128
+ }
129
+ };
130
+ }
131
+
132
+ // src/select-register.ts
133
+ installSelectLifecycle(WaSelect.prototype);
9
134
  var selectElementsRegistered = true;
10
135
 
11
136
  export { selectElementsRegistered };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/select-register.ts"],"names":[],"mappings":";;;;;;;;AAYO,IAAM,wBAAA,GAA2B","file":"select-register.js","sourcesContent":["import '@awesome.me/webawesome/dist/components/button/button.js';\nimport '@awesome.me/webawesome/dist/components/divider/divider.js';\nimport '@awesome.me/webawesome/dist/components/dropdown/dropdown.js';\nimport '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';\nimport '@awesome.me/webawesome/dist/components/option/option.js';\nimport '@awesome.me/webawesome/dist/components/select/select.js';\n\n/**\n * Marker export for tests and tooling; importing this module performs registration.\n * Registers both the `Select` elements and the `Catalog` related-entries popup-menu\n * elements (`wa-dropdown` / `wa-dropdown-item` / `wa-button`).\n */\nexport const selectElementsRegistered = true;\n"]}
1
+ {"version":3,"sources":["../src/animate-select-popup.ts","../src/install-select-lifecycle.ts","../src/select-register.ts"],"names":[],"mappings":"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","file":"select-register.js","sourcesContent":["/** Run one native Select CSS animation; a reversal owns cancellation and cleanup. */\nexport function animateSelectPopup(\n popup: HTMLElement,\n phase: 'show' | 'hide',\n signal: AbortSignal,\n): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n let frame = 0;\n const finish = () => {\n if (settled) return;\n settled = true;\n window.cancelAnimationFrame(frame);\n signal.removeEventListener('abort', finish);\n popup.classList.remove(phase);\n resolve();\n };\n signal.addEventListener('abort', finish, { once: true });\n popup.classList.add(phase);\n // Activation renders asynchronously. Check for reduced/absent motion only\n // after the native popup has had its normal render opportunity.\n frame = window.requestAnimationFrame(() => {\n // Observe these exact animations. An old CSS animationcancel event can\n // arrive after the next phase starts and must not finish that new phase.\n void Promise.all(\n popup\n .getAnimations()\n .map((animation) => animation.finished.catch(() => undefined)),\n ).then(finish);\n });\n if (signal.aborted) finish();\n });\n}\n","import type WaSelect from '@awesome.me/webawesome/dist/components/select/select.js';\nimport { WaAfterHideEvent } from '@awesome.me/webawesome/dist/events/after-hide.js';\nimport { WaAfterShowEvent } from '@awesome.me/webawesome/dist/events/after-show.js';\nimport { WaHideEvent } from '@awesome.me/webawesome/dist/events/hide.js';\nimport { WaShowEvent } from '@awesome.me/webawesome/dist/events/show.js';\n\nimport { animateSelectPopup } from './animate-select-popup.js';\n\n// Web Awesome 3.12/3.13 expose the lifecycle method but not these helper types.\n// Keep this integration boundary local: native option selection, dismissal\n// listeners, popup positioning and rendering remain owned by Web Awesome.\ninterface SelectHost extends Pick<\n WaSelect,\n | 'open'\n | 'disabled'\n | 'isConnected'\n | 'dataset'\n | 'popup'\n | 'listbox'\n | 'currentOption'\n | 'selectedOptions'\n | 'dispatchEvent'\n | 'handleOpenChange'\n | 'updateComplete'\n> {\n getFirstOption(): WaSelect['currentOption'];\n setCurrentOption(option: WaSelect['currentOption']): void;\n addOpenListeners(): void;\n removeOpenListeners(): void;\n}\n\n/** Install once at the explicit registration boundary, only for Kerf Selects. */\nexport function installSelectLifecycle(\n prototype: Pick<WaSelect, 'handleOpenChange' | 'handleDisabledChange'>,\n): void {\n const installed = Symbol.for('@kerfjs/ui/select-lifecycle');\n if (Object.prototype.hasOwnProperty.call(prototype, installed)) return;\n Object.defineProperty(prototype, installed, { value: true });\n const original = prototype.handleOpenChange;\n const originalDisabled = prototype.handleDisabledChange;\n prototype.handleDisabledChange = function () {\n const host = this as unknown as SelectHost;\n const wasOpen = host.open;\n originalDisabled.call(this);\n // WA's disabled watcher runs after its open watcher in the same Lit update.\n // Its open=false mutation otherwise misses the open lifecycle altogether.\n if (host.dataset.component === 'select' && wasOpen && !host.open) {\n void host.handleOpenChange();\n }\n };\n const transitions = new WeakMap<SelectHost, AbortController>();\n const restorations = new WeakMap<SelectHost, { open: boolean }>();\n prototype.handleOpenChange = async function () {\n const host = this as unknown as SelectHost;\n if (host.dataset.component !== 'select') return original.call(this);\n const restoring = restorations.get(host)?.open === host.open;\n restorations.delete(host);\n if (restoring) return;\n const transition = new AbortController();\n const opening = host.open && !host.disabled;\n const current = () =>\n !transition.signal.aborted &&\n host.isConnected &&\n (host.open && !host.disabled) === opening;\n try {\n if (!host.isConnected) return;\n if (opening) {\n host.setCurrentOption(host.selectedOptions[0] || host.getFirstOption());\n }\n const event = opening ? new WaShowEvent() : new WaHideEvent();\n host.dispatchEvent(event);\n if (event.defaultPrevented) {\n // Restore the accepted state without emitting the opposite transition.\n // Clear at the update checkpoint too: Lit can coalesce a restoration.\n host.open = !opening;\n const restoration = { open: host.open };\n restorations.set(host, restoration);\n void host.updateComplete.then(() => {\n if (restorations.get(host) === restoration) restorations.delete(host);\n });\n return;\n }\n // A consumer can reverse the request synchronously in a lifecycle event.\n if (!current()) return;\n // A rejected reversal must not cancel the previously accepted animation.\n transitions.get(host)?.abort();\n transitions.set(host, transition);\n if (opening) {\n host.addOpenListeners();\n host.listbox.hidden = false;\n host.popup.active = true;\n window.requestAnimationFrame(() => {\n if (current()) host.setCurrentOption(host.currentOption);\n });\n } else {\n host.removeOpenListeners();\n }\n await animateSelectPopup(\n host.popup.popup,\n opening ? 'show' : 'hide',\n transition.signal,\n );\n if (!transition.signal.aborted && !host.isConnected) {\n host.removeOpenListeners();\n host.listbox.hidden = true;\n host.popup.active = false;\n }\n if (!current()) return;\n if (opening) {\n // Match native vertical, immediate option reveal without scrolling an\n // ancestor page or moving focus after animation completion.\n const option = host.currentOption;\n if (option) {\n const list = host.listbox;\n const offset = Math.round(\n option.getBoundingClientRect().top -\n list.getBoundingClientRect().top,\n );\n if (offset < 0)\n list.scrollTo({ top: list.scrollTop + offset, behavior: 'auto' });\n else if (offset + option.clientHeight > list.offsetHeight) {\n list.scrollTo({\n top:\n list.scrollTop +\n offset -\n list.offsetHeight +\n option.clientHeight,\n behavior: 'auto',\n });\n }\n }\n host.dispatchEvent(new WaAfterShowEvent());\n } else {\n host.listbox.hidden = true;\n host.popup.active = false;\n host.dispatchEvent(new WaAfterHideEvent());\n }\n } finally {\n if (transitions.get(host) === transition) transitions.delete(host);\n }\n };\n}\n","import '@awesome.me/webawesome/dist/components/button/button.js';\nimport '@awesome.me/webawesome/dist/components/divider/divider.js';\nimport '@awesome.me/webawesome/dist/components/dropdown/dropdown.js';\nimport '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';\nimport '@awesome.me/webawesome/dist/components/option/option.js';\n\nimport WaSelect from '@awesome.me/webawesome/dist/components/select/select.js';\n\nimport { installSelectLifecycle } from './install-select-lifecycle.js';\n\ninstallSelectLifecycle(WaSelect.prototype);\n\n/**\n * Marker export for tests and tooling; importing this module performs registration.\n * Registers both the `Select` elements and the `Catalog` related-entries popup-menu\n * elements (`wa-dropdown` / `wa-dropdown-item` / `wa-button`).\n */\nexport const selectElementsRegistered = true;\n"]}
@@ -73,13 +73,6 @@
73
73
  display: flex;
74
74
  gap: var(--kui-space-xs);
75
75
  }
76
- .kui-toolbar-control-group[data-content="mixed"] {
77
- gap: 0.5rem;
78
- }
79
- .kui-toolbar-control-group[data-content="mixed"] > wa-dropdown {
80
- padding-inline-start: 0.375rem;
81
- border-inline-start: 1px solid var(--kui-toolbar-control-border);
82
- }
83
76
  .kui-toolbar-control-group[data-content="avatar"][data-scrim="true"] {
84
77
  --kui-toolbar-control-background: color-mix(
85
78
  in srgb,
@@ -193,6 +186,22 @@
193
186
  min-height: 2rem;
194
187
  }
195
188
 
189
+ /* Compact mixed controls keep the default horizontal breathing room. Their
190
+ raised selection grows over the group's own border instead of becoming a
191
+ smaller inset lozenge, while negative margins keep the outer geometry and
192
+ neighboring trigger position stable. */
193
+ .kui-toolbar-control-group[data-size="compact"][data-content="mixed"] {
194
+ --kui-layout-item-padding: 0.5rem;
195
+ }
196
+ .kui-toolbar-control-group[data-size="compact"][data-content="mixed"]
197
+ > button[aria-pressed="true"] {
198
+ position: relative;
199
+ z-index: 1;
200
+ height: var(--kui-toolbar-group-size);
201
+ margin: -2px;
202
+ border-radius: var(--kui-toolbar-group-radius);
203
+ }
204
+
196
205
  /* A compact icon-only trigger may stay square, but a mixed-content dropdown
197
206
  trigger must grow for its slotted label plus Web Awesome's caret. Otherwise
198
207
  the fixed 32px base centers both outside its host, painting the label across
@@ -247,13 +256,13 @@
247
256
 
248
257
  .kui-toolbar-control-group wa-button:hover::part(base) {
249
258
  border-color: var(--kui-toolbar-control-border);
250
- background: var(--kui-toolbar-control-hover-background);
259
+ background-color: var(--kui-toolbar-control-hover-background);
251
260
  }
252
261
 
253
262
  .kui-toolbar-control-group[data-single="true"]:has(
254
263
  > :is(button, wa-button, wa-dropdown)
255
264
  ):hover {
256
- background: var(--kui-toolbar-control-hover-background);
265
+ background-color: var(--kui-toolbar-control-hover-background);
257
266
  }
258
267
 
259
268
  .kui-toolbar-control-group[data-single="true"] > :is(button, wa-button):hover,
@@ -269,7 +278,7 @@
269
278
  .kui-toolbar-control-group:not([data-single="true"]):has(
270
279
  > :is(button, wa-button):only-child
271
280
  ):hover {
272
- background: var(--kui-toolbar-control-hover-background);
281
+ background-color: var(--kui-toolbar-control-hover-background);
273
282
  }
274
283
 
275
284
  .kui-toolbar-control-group:not([data-single="true"]):has(
@@ -298,6 +298,9 @@
298
298
  wa-card::part(footer) {
299
299
  border-top: none;
300
300
  padding: var(--kui-wa-container-inset);
301
+ display: flex;
302
+ justify-content: flex-end;
303
+ gap: var(--kui-layout-item-gap, 0.5rem);
301
304
  }
302
305
 
303
306
  /* Dialog content follows the compact surface inset, while its action region
@@ -314,6 +317,9 @@
314
317
 
315
318
  wa-dialog::part(footer) {
316
319
  padding: var(--kui-wa-container-inset);
320
+ display: flex;
321
+ justify-content: flex-end;
322
+ gap: var(--kui-layout-item-gap, 0.5rem);
317
323
  }
318
324
 
319
325
  /* Dialogs that provide their own dismissal affordance can opt out of Web
@@ -54,6 +54,19 @@ variants. `tests/browser/select-accessibility.spec.ts` verifies actual accessibl
54
54
  names, unchanged unlabeled geometry, keyboard selection, controlled rerenders,
55
55
  native hint wiring, and wide/narrow presentation in Chromium, Firefox, and WebKit.
56
56
 
57
+ The explicit `@kerfjs/ui/select/register` import also installs the canonical
58
+ Select lifecycle adapter. The latest open/close request owns animation completion
59
+ and deferred option focus. A stale close cannot hide a reopened popup after
60
+ resize; stale completion events are not emitted. Preventing `wa-show` or
61
+ `wa-hide` retains the accepted closed/open state, and removal prevents deferred
62
+ activation. Native option selection, keyboard behavior, dismissal listeners,
63
+ and popup anchor placement remain with Web Awesome. Raw `wa-select` elements
64
+ without the canonical `data-component="select"` marker are unchanged.
65
+ `tests/unit/select-lifecycle.test.ts` and `animate-select-popup.test.ts` cover
66
+ repeated and out-of-order completions, cancellation, instance isolation, and
67
+ removal. `tests/browser/select-lifecycle.spec.ts` repeats animation/resize/reopen
68
+ sequences at wide and narrow widths in Chromium, Firefox, and WebKit.
69
+
57
70
  ## ListActionRow
58
71
 
59
72
  `ListActionRow` renders a noninteractive visual root containing primary and
@@ -299,8 +299,10 @@ free of a Web Awesome dependency and shadow DOM. Reach for `wa-button` only when
299
299
  you need a Web Awesome feature, chiefly the `slot="trigger"` button of a
300
300
  `wa-dropdown` popup menu. For a compact mixed-content group, set
301
301
  `nestedDropdown`; a text-and-caret trigger grows to its intrinsic width while an
302
- icon-only trigger can remain square, keeping the separator and caret inside the
303
- group. The short text trigger has an engine-stable 66px floor because WebKit
302
+ icon-only trigger can remain square. Compact mixed controls retain the standard
303
+ item padding, omit an internal separator, and let the raised selected item paint
304
+ over the outer border instead of shrinking to an inset highlight. The short text
305
+ trigger has an engine-stable 66px floor because WebKit
304
306
  does not include the shadow caret in the custom-element host's intrinsic width;
305
307
  raise `--kui-toolbar-dropdown-trigger-width` for longer localized copy.
306
308
  `ListHeader` similarly separates its dormant title and
@@ -141,7 +141,7 @@ an upstream component or recipe request.
141
141
  | Key/value facts — `ValueTable`, `ValueTableRow` | Read-only labels and values form a semantic definition list, optionally with a leading icon. | Do not use it for editable form fields or a row/column data grid; use native form or table semantics. | Compose typed `ValueTableRow` entries; pass `icon` when a 24px leading icon adds useful context. | Values, formatting, icon meaning, and empty/loading policy. | `@kerfjs/ui/value-table` | [Component ownership](./component-contract.md#ownership-boundaries) |
142
142
  | Indeterminate activity — `LoadingSpinner` | A Kerf surface needs compact, labeled or decorative indeterminate progress. | Do not use it for known progress; use Web Awesome `wa-progress-bar` or `wa-progress-ring`. Direct Web Awesome UI may use `wa-spinner`; do not mix spinner systems within one surface. | None; pass a label when the spinner conveys status. | Loading lifecycle and adjacent status copy. | `@kerfjs/ui/loading-spinner` | [Accessibility](./accessibility.md#shared-rules) |
143
143
  | Loading placeholder — `Skeleton` + a component's `placeholder` prop | A value or a whole component is still loading and should hold its space as a subtle, unanimated block, keeping the layout stable — an inspector or detail view rendering its real chrome with per-record values absent. | Do not use it for known progress (use `LoadingSpinner`), do not animate it, and do not hand-rebuild a component's empty state — set `placeholder` on the component instead. Prefer it over `wa-skeleton`, which the pure-Kerf primitives avoid to stay Web-Awesome-free. | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `SegmentedControl`, `StateBanner`, `AppTab`, `ToolbarText`, `ListActionRow`) to render skeletons in its value slots with interactivity disabled; use the standalone `Skeleton` for a custom slot. | Loading lifecycle, which slots are unknown, and announcing the loading region. | `@kerfjs/ui/skeleton` | [Accessibility](./accessibility.md#shared-rules) |
144
- | Value selection — `Select` | A controlled form, toolbar, or compact navigation value comes from a moderate or long choice list, possibly grouped, icon-bearing, icon-only when closed, or truncating. | Do not use it for commands; use a real action menu. Do not use it for a small visible choice set; use `SegmentedControl`. Prefer it over direct `wa-select`, `wa-option`, or value-like `wa-dropdown`/`wa-dropdown-item` composition. | Import `@kerfjs/ui/select/register` once; listen for standard input/change events. Use typed presentation props rather than consumer `::part()` overrides; let the parent own placement. | Controlled value, validation, choices, domain mapping, and outer placement. | `@kerfjs/ui/select` | [Web Awesome integration](../README.md#web-awesome-theme) |
144
+ | Value selection — `Select` | A controlled form, toolbar, or compact navigation value comes from a moderate or long choice list, possibly grouped, icon-bearing, icon-only when closed, or truncating. | Do not use it for commands; use a real action menu. Do not use it for a small visible choice set; use `SegmentedControl`. Prefer it over direct `wa-select`, `wa-option`, or value-like `wa-dropdown`/`wa-dropdown-item` composition. | Import `@kerfjs/ui/select/register` once for registration and current-request animation ownership; listen for standard input/change events. Do not add consumer popup timing or positioning repairs. Use typed presentation props rather than consumer `::part()` overrides; let the parent own placement. | Controlled value, validation, choices, domain mapping, and outer placement. | `@kerfjs/ui/select` | [Web Awesome integration](../README.md#web-awesome-theme) |
145
145
  | Small exclusive choice — `SegmentedControl` | A few visible choices switch a compact view or setting, with toolbar, rounded, or pill presentation. | Do not use it for tabpanel semantics; use `TabBar`. Do not use it for many choices; use `Select`. Prefer it over `wa-button-group` when the controls select one value. | Delegate its action, read `data-segment-value`, update `value`, and rerender. | Controlled value, labels, action, and persistence. | `@kerfjs/ui/segmented-control` | [SegmentedControl contract](./accessibility.md#segmentedcontrol) |
146
146
  | Structured search editor — `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret`, `wireTokenSearchFields` | Free text and ordered, editable, removable filter tokens share one searchbox; enable `collapsible` when an empty, unfocused field should reduce to one iconic action, standalone or in a toolbar group. | Do not use it for ordinary text entry; use a native input or Web Awesome `wa-input`. Do not use it when filters belong in separate form controls. | Read DOM-owned text on input, empty `textContent` on clear, and use `placeTokenSearchCaret` after explicit controlled focus changes. Call `wireTokenSearchFields` from `@kerfjs/ui/wire-token-search-fields` once so Enter submits without adding a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. In `collapsible` mode it also manages the transient expand/collapse/focus by default (activate to reveal + focus, Escape or empty blur to collapse); bind the field's `expanded` to the signal on the returned handle (`handle.expanded(id)`) or adopt your own via `collapsible.signals`, and opt out per behavior only when the app must own it. | Parsing, suggestions, tokens, query execution, results, announcements, and — only if overriding the default — the collapsible `expanded` signal. | `@kerfjs/ui/token-search-field` | [TokenSearchField contract](./accessibility.md#tokensearchfield) |
147
147
  | Persistent inline status — `StateBanner` | A neutral, info, pop, success, warning, or danger message belongs next to the affected work; pop highlights featured or novel content without implying status. | Do not use pop as a success, warning, or danger substitute. Do not use StateBanner for a no-content screen; use `EmptyState`. Do not use it for transient confirmation; use a toast. Web Awesome `wa-callout` is the ecosystem alternative for Web Awesome-owned content. | Delegate an optional action; choose alert urgency only for attention-requiring failure. Use the optional tone-tinted `badge` for a terse count or status beside the title, not as the only expression of meaning. | State mapping, message lifetime, retry/action behavior, badge content, and copy. | `@kerfjs/ui/state-banner` | [Feedback accessibility](./accessibility.md#shared-rules) |
package/llms.txt CHANGED
@@ -123,6 +123,9 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
123
123
  - `Select` requires a visible `label` or an `ariaLabel` for a visually hidden
124
124
  accessible name. The package names the internal combobox, including custom
125
125
  selected content; consumers do not need shadow-DOM naming workarounds.
126
+ Import `select/register` once for the canonical animation lifecycle: stale
127
+ close/show completions cannot override a later request. Consumers must not
128
+ repair popup timing or anchor placement; raw Web Awesome selects stay native.
126
129
  Use `hint` for supporting text below the control and `placeholderText` only
127
130
  for the empty value inside it; loading placeholders retain the visible hint.
128
131
  - **Trust the defaults.** Render a control at its natural size and color (a
@@ -138,7 +141,9 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
138
141
  need a Web Awesome feature, chiefly the `slot="trigger"` button of a `wa-dropdown`
139
142
  popup menu. A toolbar popup menu is a `single` `ToolbarControlGroup` around a
140
143
  `wa-dropdown` (its `slot="trigger"` `wa-button` is the toolbar button, kept under
141
- `data-morph-skip-children`). For an avatar control, set `content="avatar"` and
144
+ `data-morph-skip-children`). Compact mixed groups keep standard item padding
145
+ and raised selected chrome without an internal separator; the selected item
146
+ paints over the outer border. For an avatar control, set `content="avatar"` and
142
147
  `avatarImage` on the group instead of inserting an `img`; a lone control paints
143
148
  the group and a multi-button group paints only its pressed highlight.
144
149
  - **Use one direct toolbar composition for panel/dialog/page headings.** Put an
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.31",
3
+ "version": "5.0.0-beta.33",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",