@kerfjs/ui 5.0.0-beta.32 → 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.
- package/ai/component-catalog-v2.json +2 -1
- package/ai/component-catalog.json +2 -1
- package/ai/public-api-signatures-v1.md +1 -1
- package/ai/skill.md +8 -0
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/select-register.js +126 -1
- package/dist/select-register.js.map +1 -1
- package/docs/accessibility.md +13 -0
- package/docs/component-selection.md +1 -1
- package/llms.txt +3 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
@@ -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.
|
|
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';
|
package/dist/select-register.js
CHANGED
|
@@ -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"]}
|
package/docs/accessibility.md
CHANGED
|
@@ -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
|
|
@@ -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.
|
|
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
|