@kerfjs/ui 5.0.0-beta.77 → 5.0.0-beta.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/ai/application-ui-diagnostic-ids-v1.json +4 -0
- package/ai/compile-time-contracts-v1.json +3 -3
- package/ai/component-catalog.json +152 -31
- package/ai/component-composition-rules.json +6 -14
- package/ai/component-composition.json +123 -29
- package/ai/components/README.md +1 -1
- package/ai/components/document-baseline.md +1 -1
- package/ai/components/nav-stack.md +6 -6
- package/ai/components/pane.md +3 -1
- package/ai/components/surface-scaffold.md +11 -2
- package/ai/components/{tab-scaffold.md → tab-navigator.md} +14 -14
- package/ai/components/token-search-field.md +1 -0
- package/ai/components/toolbar-control-group.md +2 -0
- package/ai/components/toolbar-text.md +2 -0
- package/ai/components/toolbar.md +6 -8
- package/ai/public-api-signatures-v1.md +68 -140
- package/ai/skill.md +15 -4
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/browser/nav-stack.js +2 -0
- package/dist/browser/split-view.js +2 -0
- package/dist/browser/tab-navigator.js +4 -0
- package/dist/catalog-resources.d.ts +3 -3
- package/dist/catalog.d.ts +3 -3
- package/dist/catalog.js +3 -3
- package/dist/{chunk-WJN3V2H5.js → chunk-2GOCOLQL.js} +18 -4
- package/dist/chunk-2GOCOLQL.js.map +1 -0
- package/dist/{chunk-QYXKMLHO.js → chunk-3CXRQAYT.js} +2 -2
- package/dist/chunk-3CXRQAYT.js.map +1 -0
- package/dist/{chunk-G4VKQP7B.js → chunk-3SXWV2IR.js} +5 -1
- package/dist/chunk-3SXWV2IR.js.map +1 -0
- package/dist/{chunk-VLPE6OPN.js → chunk-62VMNBV2.js} +6 -2
- package/dist/chunk-62VMNBV2.js.map +1 -0
- package/dist/{chunk-XIZRXI7N.js → chunk-A7DYEERU.js} +13 -1
- package/dist/chunk-A7DYEERU.js.map +1 -0
- package/dist/{chunk-6VQPLV3O.js → chunk-DSLRNR43.js} +9 -8
- package/dist/chunk-DSLRNR43.js.map +1 -0
- package/dist/{chunk-AOAW55JN.js → chunk-DXQZ3BEO.js} +4 -1
- package/dist/chunk-DXQZ3BEO.js.map +1 -0
- package/dist/chunk-IKCUPTR2.js +170 -0
- package/dist/chunk-IKCUPTR2.js.map +1 -0
- package/dist/{chunk-Z6PE5T7N.js → chunk-JL4BBZP4.js} +4 -3
- package/dist/chunk-JL4BBZP4.js.map +1 -0
- package/dist/collapsible-panel.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -8
- package/dist/nav-stack-B1Qc2e5q.d.ts +101 -0
- package/dist/nav-stack.d.ts +11 -93
- package/dist/nav-stack.js +5 -1
- package/dist/pane.d.ts +5 -2
- package/dist/pane.js +1 -1
- package/dist/split-view.d.ts +9 -4
- package/dist/split-view.js +7 -3
- package/dist/split-view.js.map +1 -1
- package/dist/styles/components/actions/toolbar/toolbar.css +7 -14
- package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +63 -13
- package/dist/styles/components/actions/toolbar-text/toolbar-text.css +8 -1
- package/dist/styles/components/forms/token-search-field/token-search-field.css +12 -1
- package/dist/styles/components/layout/pane/pane.css +18 -1
- package/dist/styles/components/navigation/nav-stack/nav-stack.css +6 -12
- package/dist/styles/components/navigation/tab-scaffold/tab-scaffold.css +18 -1
- package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +55 -0
- package/dist/styles/nav-stack.css +6 -12
- package/dist/styles/pane.css +18 -1
- package/dist/styles/surface-scaffold.css +55 -0
- package/dist/styles/{tab-scaffold.css → tab-navigator.css} +18 -1
- package/dist/styles/token-search-field.css +12 -1
- package/dist/styles/toolbar-control-group.css +63 -13
- package/dist/styles/toolbar-text.css +8 -1
- package/dist/styles/toolbar.css +7 -14
- package/dist/surface-scaffold-register.d.ts +4 -0
- package/dist/surface-scaffold-register.js +59 -0
- package/dist/surface-scaffold-register.js.map +1 -0
- package/dist/surface-scaffold.d.ts +10 -2
- package/dist/surface-scaffold.js +1 -1
- package/dist/{tab-scaffold.d.ts → tab-navigator.d.ts} +12 -10
- package/dist/{tab-scaffold.js → tab-navigator.js} +4 -3
- package/dist/tab-navigator.js.map +1 -0
- package/dist/token-search-field.js +1 -1
- package/dist/wire-catalog.js +1 -1
- package/dist/wire-scroll-dividers.d.ts +3 -3
- package/dist/wire-scroll-dividers.js +1 -1
- package/dist/wire-tab-navigator.d.ts +11 -0
- package/dist/{wire-tab-scaffold.js → wire-tab-navigator.js} +3 -3
- package/dist/wire-tab-navigator.js.map +1 -0
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/wire-toolbar-visibility.js +1 -1
- package/dist/workbench.d.ts +5 -13
- package/dist/workbench.js +1 -1
- package/docs/app-layouts.md +44 -11
- package/docs/component-contract.md +9 -2
- package/docs/component-selection.md +17 -15
- package/docs/design/templates/token-search-field/empty-dark.svg +1 -1
- package/docs/design/templates/token-search-field/empty.svg +1 -1
- package/docs/design/templates/token-search-field-dark.svg +1 -1
- package/docs/design/templates/token-search-field.svg +1 -1
- package/docs/design/templates.md +1 -1
- package/docs/examples/application-ui-profile.json +4 -4
- package/docs/layout.md +31 -8
- package/docs/nav-stack.md +30 -18
- package/docs/recipes.md +6 -2
- package/docs/split-view.md +7 -4
- package/docs/surface-scaffold.md +46 -0
- package/docs/{tab-scaffold.md → tab-navigator.md} +83 -11
- package/docs/type-contracts.md +3 -3
- package/docs/ui-analyzer.md +5 -0
- package/docs/ui-doctor.md +33 -0
- package/docs/workbench.md +3 -2
- package/doctor/component-style-checks.mjs +457 -0
- package/doctor/index.mjs +43 -13
- package/llms.txt +18 -6
- package/package.json +16 -11
- package/ux-demo/recipes/list-detail-dialog.tsx +4 -0
- package/ux-demo/recipes/navigation-stack.tsx +2 -2
- package/dist/browser/tab-scaffold.js +0 -4
- package/dist/chunk-6VQPLV3O.js.map +0 -1
- package/dist/chunk-AOAW55JN.js.map +0 -1
- package/dist/chunk-G4VKQP7B.js.map +0 -1
- package/dist/chunk-QYXKMLHO.js.map +0 -1
- package/dist/chunk-VLPE6OPN.js.map +0 -1
- package/dist/chunk-WJN3V2H5.js.map +0 -1
- package/dist/chunk-XIZRXI7N.js.map +0 -1
- package/dist/chunk-Z4EPMX25.js +0 -123
- package/dist/chunk-Z4EPMX25.js.map +0 -1
- package/dist/chunk-Z6PE5T7N.js.map +0 -1
- package/dist/tab-scaffold.js.map +0 -1
- package/dist/wire-tab-scaffold.d.ts +0 -11
- package/dist/wire-tab-scaffold.js.map +0 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import WaDialog from '@awesome.me/webawesome/dist/components/dialog/dialog.js';
|
|
2
|
+
|
|
3
|
+
// src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts
|
|
4
|
+
|
|
5
|
+
// src/components/surfaces/surface-scaffold/internal/install-dialog-label.ts
|
|
6
|
+
var installed = /* @__PURE__ */ new WeakSet();
|
|
7
|
+
var observed = /* @__PURE__ */ new WeakSet();
|
|
8
|
+
var ownedNames = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
function installDialogLabel(prototype) {
|
|
10
|
+
if (installed.has(prototype)) return;
|
|
11
|
+
installed.add(prototype);
|
|
12
|
+
const target = prototype;
|
|
13
|
+
const update = target.update;
|
|
14
|
+
target.update = function(changed) {
|
|
15
|
+
update.call(this, changed);
|
|
16
|
+
if (!this.parentElement?.classList.contains("kui-dialog-surface")) return;
|
|
17
|
+
if (!observed.has(this)) {
|
|
18
|
+
observed.add(this);
|
|
19
|
+
new MutationObserver(() => syncDialogLabel(this)).observe(this, {
|
|
20
|
+
attributes: true,
|
|
21
|
+
attributeFilter: ["aria-label"]
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
syncDialogLabel(this);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function syncDialogLabel(host) {
|
|
28
|
+
if (!host.parentElement?.classList.contains("kui-dialog-surface")) return;
|
|
29
|
+
const panel = host.dialog;
|
|
30
|
+
if (!panel) return;
|
|
31
|
+
const previous = ownedNames.get(panel);
|
|
32
|
+
const externalName = ["aria-label", "aria-labelledby"].some((attribute2) => {
|
|
33
|
+
const value2 = panel.getAttribute(attribute2);
|
|
34
|
+
return value2 !== null && !(previous?.attribute === attribute2 && previous.value === value2);
|
|
35
|
+
});
|
|
36
|
+
if (externalName) {
|
|
37
|
+
if (previous && panel.getAttribute(previous.attribute) === previous.value)
|
|
38
|
+
panel.removeAttribute(previous.attribute);
|
|
39
|
+
ownedNames.delete(panel);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const explicit = host.getAttribute("aria-label")?.trim();
|
|
43
|
+
const title = host.shadowRoot?.getElementById("title");
|
|
44
|
+
const attribute = !explicit && !host.withoutHeader && title ? "aria-labelledby" : "aria-label";
|
|
45
|
+
const value = attribute === "aria-labelledby" ? "title" : explicit || host.label;
|
|
46
|
+
if (previous && previous.attribute !== attribute)
|
|
47
|
+
panel.removeAttribute(previous.attribute);
|
|
48
|
+
if (panel.getAttribute(attribute) !== value)
|
|
49
|
+
panel.setAttribute(attribute, value);
|
|
50
|
+
ownedNames.set(panel, { attribute, value });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts
|
|
54
|
+
installDialogLabel(WaDialog.prototype);
|
|
55
|
+
var surfaceScaffoldElementsRegistered = true;
|
|
56
|
+
|
|
57
|
+
export { surfaceScaffoldElementsRegistered };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=surface-scaffold-register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/surfaces/surface-scaffold/internal/install-dialog-label.ts","../src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts"],"names":["attribute","value"],"mappings":"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","file":"surface-scaffold-register.js","sourcesContent":["interface NamedDialog extends HTMLElement {\n dialog?: HTMLDialogElement;\n label: string;\n withoutHeader: boolean;\n update(changed: unknown): void;\n}\n\nconst installed = new WeakSet<object>();\nconst observed = new WeakSet<HTMLElement>();\nconst ownedNames = new WeakMap<\n HTMLElement,\n { attribute: string; value: string }\n>();\n\n/** Repair Web Awesome's missing native dialog name before its first modal open. */\nexport function installDialogLabel(prototype: object): void {\n if (installed.has(prototype)) return;\n installed.add(prototype);\n const target = prototype as NamedDialog;\n const update = target.update;\n target.update = function (this: NamedDialog, changed: unknown): void {\n update.call(this, changed);\n if (!this.parentElement?.classList.contains('kui-dialog-surface')) return;\n if (!observed.has(this)) {\n observed.add(this);\n new MutationObserver(() => syncDialogLabel(this)).observe(this, {\n attributes: true,\n attributeFilter: ['aria-label'],\n });\n }\n syncDialogLabel(this);\n };\n}\n\nfunction syncDialogLabel(host: NamedDialog): void {\n if (!host.parentElement?.classList.contains('kui-dialog-surface')) return;\n const panel = host.dialog;\n if (!panel) return;\n const previous = ownedNames.get(panel);\n const externalName = ['aria-label', 'aria-labelledby'].some((attribute) => {\n const value = panel.getAttribute(attribute);\n return (\n value !== null &&\n !(previous?.attribute === attribute && previous.value === value)\n );\n });\n if (externalName) {\n if (previous && panel.getAttribute(previous.attribute) === previous.value)\n panel.removeAttribute(previous.attribute);\n ownedNames.delete(panel);\n return;\n }\n const explicit = host.getAttribute('aria-label')?.trim();\n const title = host.shadowRoot?.getElementById('title');\n const attribute =\n !explicit && !host.withoutHeader && title\n ? 'aria-labelledby'\n : 'aria-label';\n const value =\n attribute === 'aria-labelledby' ? 'title' : explicit || host.label;\n if (previous && previous.attribute !== attribute)\n panel.removeAttribute(previous.attribute);\n if (panel.getAttribute(attribute) !== value)\n panel.setAttribute(attribute, value);\n ownedNames.set(panel, { attribute, value });\n}\n","import WaDialog from '@awesome.me/webawesome/dist/components/dialog/dialog.js';\n\nimport { installDialogLabel } from '../internal/install-dialog-label.js';\n\ninstallDialogLabel(WaDialog.prototype);\n\n/** Importing this module registers wa-dialog and repairs wrapped native names. */\nexport const surfaceScaffoldElementsRegistered = true;\n"]}
|
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
import * as kerfjs from 'kerfjs';
|
|
2
2
|
import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
|
|
3
|
+
import { CssLength } from './css-values.js';
|
|
3
4
|
|
|
4
5
|
type DialogSurfaceSize = 'small' | 'medium' | 'large';
|
|
5
6
|
type DialogSurfacePresentation = 'modal' | 'side-sheet' | 'fullscreen';
|
|
6
7
|
type SurfaceInset = 'none' | 'compact' | 'comfortable';
|
|
8
|
+
type DialogSurfaceMaxHeight = CssLength | 'viewport';
|
|
7
9
|
interface DialogSurfaceProps {
|
|
8
10
|
children: KerfUiContent;
|
|
9
11
|
size?: DialogSurfaceSize;
|
|
10
12
|
presentation?: DialogSurfacePresentation;
|
|
13
|
+
/** Modal preferred width. Takes precedence over size, within the viewport cap. */
|
|
14
|
+
preferredWidth?: CssLength;
|
|
11
15
|
bodyInset?: SurfaceInset;
|
|
12
16
|
footerInset?: SurfaceInset;
|
|
17
|
+
/** Modal edge clearance. Omit to retain Web Awesome's default viewport cap. */
|
|
18
|
+
viewportGutter?: CssLength;
|
|
19
|
+
/** Modal height cap, bounded by the dynamic viewport minus both gutters. */
|
|
20
|
+
maxHeight?: DialogSurfaceMaxHeight;
|
|
13
21
|
className?: string;
|
|
14
22
|
/** Native named-slot assignment when composed inside a web component. */
|
|
15
23
|
slot?: string;
|
|
16
24
|
}
|
|
17
25
|
/** Configure recurring Web Awesome dialog geometry without consumer ::part() CSS. */
|
|
18
|
-
declare function DialogSurface({ children, size, presentation, bodyInset, footerInset, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
|
|
26
|
+
declare function DialogSurface({ children, size, presentation, preferredWidth, bodyInset, footerInset, viewportGutter, maxHeight, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
|
|
19
27
|
type PopupSurfaceInset = 'standard' | 'compact' | 'list-zero';
|
|
20
28
|
interface PopupSurfaceProps {
|
|
21
29
|
children: KerfUiContent;
|
|
@@ -27,4 +35,4 @@ interface PopupSurfaceProps {
|
|
|
27
35
|
/** Configure recurring Web Awesome dropdown-menu geometry without consumer ::part() CSS. */
|
|
28
36
|
declare function PopupSurface({ children, inset, className, slot, }: PopupSurfaceProps): kerfjs.SafeHtml;
|
|
29
37
|
|
|
30
|
-
export { DialogSurface, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
|
|
38
|
+
export { DialogSurface, type DialogSurfaceMaxHeight, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
|
package/dist/surface-scaffold.js
CHANGED
|
@@ -2,7 +2,7 @@ import { SafeHtml } from 'kerfjs';
|
|
|
2
2
|
import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
|
|
3
3
|
import { PaneAppearance } from './pane.js';
|
|
4
4
|
|
|
5
|
-
interface
|
|
5
|
+
interface TabNavigatorTabBase<Id extends string> {
|
|
6
6
|
id: Id;
|
|
7
7
|
label: string;
|
|
8
8
|
/** Decorative icon shown above the label in the bottom bar. */
|
|
@@ -11,9 +11,11 @@ interface TabScaffoldTabBase<Id extends string> {
|
|
|
11
11
|
content: KerfUiContent;
|
|
12
12
|
/** Background of this scene's scrolling work surface. */
|
|
13
13
|
appearance?: PaneAppearance;
|
|
14
|
+
/** Add an extra 8px inline gutter to a plain scene's scrolling content. */
|
|
15
|
+
deepInset?: boolean;
|
|
14
16
|
}
|
|
15
17
|
/** A tab with an optional count or short-status badge. */
|
|
16
|
-
interface
|
|
18
|
+
interface TabNavigatorTabCountBadge {
|
|
17
19
|
/**
|
|
18
20
|
* Optional count or short status shown as a solid danger `Badge` at the
|
|
19
21
|
* top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,
|
|
@@ -31,7 +33,7 @@ interface TabScaffoldTabCountBadge {
|
|
|
31
33
|
badgeLabel?: string;
|
|
32
34
|
}
|
|
33
35
|
/** A tab with the text-free dot badge (new content without a count). */
|
|
34
|
-
interface
|
|
36
|
+
interface TabNavigatorTabDotBadge {
|
|
35
37
|
/**
|
|
36
38
|
* `true` shows a solid danger dot `Badge` at the top-trailing corner of the
|
|
37
39
|
* tab icon — the iOS tab-bar dot for new content without a count.
|
|
@@ -45,12 +47,12 @@ interface TabScaffoldTabDotBadge {
|
|
|
45
47
|
badgeLabel: string;
|
|
46
48
|
}
|
|
47
49
|
/** One bottom-bar destination: its label, optional icon and badge, and content. */
|
|
48
|
-
type
|
|
49
|
-
interface
|
|
50
|
+
type TabNavigatorTab<Id extends string = string> = TabNavigatorTabBase<Id> & (TabNavigatorTabCountBadge | TabNavigatorTabDotBadge);
|
|
51
|
+
interface TabNavigatorProps<Id extends string = string> {
|
|
50
52
|
id: string;
|
|
51
53
|
/** Accessible name for the tab bar. */
|
|
52
54
|
label: string;
|
|
53
|
-
tabs: readonly
|
|
55
|
+
tabs: readonly TabNavigatorTab<Id>[];
|
|
54
56
|
/** The controlled active tab id (the app owns selection). */
|
|
55
57
|
active: NoInfer<Id>;
|
|
56
58
|
/**
|
|
@@ -68,13 +70,13 @@ interface TabScaffoldProps<Id extends string = string> {
|
|
|
68
70
|
slot?: string;
|
|
69
71
|
}
|
|
70
72
|
/**
|
|
71
|
-
* A mobile-first, iOS-like bottom tab
|
|
73
|
+
* A mobile-first, iOS-like bottom tab navigator: a bottom tab bar that switches
|
|
72
74
|
* between major sections, each tab keeping its own content (usually a `NavStack`)
|
|
73
75
|
* mounted so its stack and scroll survive a switch. Controlled — the app owns
|
|
74
|
-
* `active`; wire selection with `@kerfjs/ui/wire-tab-
|
|
76
|
+
* `active`; wire selection with `@kerfjs/ui/wire-tab-navigator`'s `wireTabNavigator`.
|
|
75
77
|
* On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of
|
|
76
78
|
* a bottom bar. See `docs/23-app-layouts.md` §3.4.
|
|
77
79
|
*/
|
|
78
|
-
declare function
|
|
80
|
+
declare function TabNavigator<Id extends string>({ id, label, tabs, active, chromeDividers, className, slot, }: TabNavigatorProps<Id>): SafeHtml;
|
|
79
81
|
|
|
80
|
-
export {
|
|
82
|
+
export { TabNavigator, type TabNavigatorProps, type TabNavigatorTab };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Badge } from './chunk-J3AWE5I4.js';
|
|
2
2
|
import { jsxs, jsx } from 'kerfjs/jsx-runtime';
|
|
3
3
|
|
|
4
|
-
function
|
|
4
|
+
function TabNavigator({
|
|
5
5
|
id,
|
|
6
6
|
label,
|
|
7
7
|
tabs,
|
|
@@ -27,6 +27,7 @@ function TabScaffold({
|
|
|
27
27
|
"data-tab-scaffold-scene": tab.id,
|
|
28
28
|
"data-active": String(tab.id === active),
|
|
29
29
|
"data-appearance": tab.appearance === "sunken" ? "sunken" : void 0,
|
|
30
|
+
"data-deep-inset": tab.deepInset ? "true" : void 0,
|
|
30
31
|
"aria-hidden": String(tab.id !== active),
|
|
31
32
|
children: tab.content
|
|
32
33
|
}
|
|
@@ -85,6 +86,6 @@ function normalizeBadge(badge) {
|
|
|
85
86
|
return void 0;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
export {
|
|
89
|
+
export { TabNavigator };
|
|
89
90
|
|
|
90
|
-
//# sourceMappingURL=tab-
|
|
91
|
+
//# sourceMappingURL=tab-navigator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/navigation/tab-scaffold/tab-scaffold.tsx"],"names":[],"mappings":"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","file":"tab-navigator.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport { Badge } from '../../feedback/badge/badge.js';\nimport type { PaneAppearance } from '../../layout/pane/pane.js';\n\ninterface TabNavigatorTabBase<Id extends string> {\n id: Id;\n label: string;\n /** Decorative icon shown above the label in the bottom bar. */\n icon?: SafeHtml;\n /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */\n content: KerfUiContent;\n /** Background of this scene's scrolling work surface. */\n appearance?: PaneAppearance;\n /** Add an extra 8px inline gutter to a plain scene's scrolling content. */\n deepInset?: boolean;\n}\n\n/** A tab with an optional count or short-status badge. */\ninterface TabNavigatorTabCountBadge {\n /**\n * Optional count or short status shown as a solid danger `Badge` at the\n * top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,\n * or non-finite numbers render no badge. The visual badge is `aria-hidden`;\n * its meaning reaches assistive technology through `badgeLabel`.\n *\n * Pass `true` instead for the text-free dot (new content without a count).\n */\n badge?: string | number;\n /**\n * Localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"3 unread\"` → `\"Inbox, 3 unread\"`).\n * Defaults to the badge text itself. Ignored when no badge renders.\n */\n badgeLabel?: string;\n}\n\n/** A tab with the text-free dot badge (new content without a count). */\ninterface TabNavigatorTabDotBadge {\n /**\n * `true` shows a solid danger dot `Badge` at the top-trailing corner of the\n * tab icon — the iOS tab-bar dot for new content without a count.\n */\n badge: true;\n /**\n * Required localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"New activity\"` →\n * `\"Feed, New activity\"`). A dot has no text, so this is its only meaning.\n */\n badgeLabel: string;\n}\n\n/** One bottom-bar destination: its label, optional icon and badge, and content. */\nexport type TabNavigatorTab<Id extends string = string> =\n TabNavigatorTabBase<Id> &\n (TabNavigatorTabCountBadge | TabNavigatorTabDotBadge);\n\nexport interface TabNavigatorProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabNavigatorTab<Id>[];\n /** The controlled active tab id (the app owns selection). */\n active: NoInfer<Id>;\n /**\n * The line over the tab bar, where it meets the active scene. `scroll`\n * (default) shows it only while more of the scene's content lies below —\n * never at the scroll end or when the content fits — once\n * `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is wired above\n * the scaffold; unwired, it does not show. `always` shows it without the\n * wiring; `none` never, even when wired. The line is the bar's own 1px top\n * border, so no state moves the bar or a tab.\n */\n chromeDividers?: 'scroll' | 'always' | 'none';\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/**\n * A mobile-first, iOS-like bottom tab navigator: a bottom tab bar that switches\n * between major sections, each tab keeping its own content (usually a `NavStack`)\n * mounted so its stack and scroll survive a switch. Controlled — the app owns\n * `active`; wire selection with `@kerfjs/ui/wire-tab-navigator`'s `wireTabNavigator`.\n * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of\n * a bottom bar. See `docs/23-app-layouts.md` §3.4.\n */\nexport function TabNavigator<Id extends string>({\n id,\n label,\n tabs,\n active,\n chromeDividers = 'scroll',\n className = '',\n slot,\n}: TabNavigatorProps<Id>) {\n return (\n <section\n class={`kui-tab-scaffold ${className}`.trim()}\n id={id}\n data-component=\"tab-scaffold\"\n data-tab-scaffold-id={id}\n data-chrome-dividers={\n chromeDividers === 'always' || chromeDividers === 'none'\n ? chromeDividers\n : undefined\n }\n slot={slot}\n >\n <div class=\"kui-tab-scaffold__scenes\">\n {tabs.map((tab) => (\n <div\n class=\"kui-tab-scaffold__scene\"\n data-tab-scaffold-scene={tab.id}\n data-active={String(tab.id === active)}\n data-appearance={tab.appearance === 'sunken' ? 'sunken' : undefined}\n data-deep-inset={tab.deepInset ? 'true' : undefined}\n aria-hidden={String(tab.id !== active)}\n >\n {tab.content}\n </div>\n ))}\n </div>\n <nav class=\"kui-tab-scaffold__bar\" role=\"tablist\" aria-label={label}>\n {tabs.map((tab) => {\n const dot = tab.badge === true;\n const badgeText = dot ? undefined : normalizeBadge(tab.badge);\n const hasBadge = dot || badgeText !== undefined;\n const badge = hasBadge && (\n <span\n class=\"kui-tab-scaffold__tab-badge\"\n data-badge-kind={dot ? 'dot' : 'text'}\n >\n {badgeText === undefined ? (\n <Badge tone=\"danger\" size=\"dot\" ariaHidden />\n ) : (\n <Badge\n tone=\"danger\"\n appearance=\"solid\"\n size=\"compact\"\n ariaHidden\n >\n {badgeText}\n </Badge>\n )}\n </span>\n );\n // A dot has no text fallback: without a usable badgeLabel (reachable\n // only from untyped callers) the tab keeps its plain accessible name.\n const badgeLabel = hasBadge\n ? tab.badgeLabel?.trim() || badgeText\n : undefined;\n return (\n <button\n type=\"button\"\n class=\"kui-tab-scaffold__tab\"\n role=\"tab\"\n data-tab-scaffold-tab={tab.id}\n aria-selected={String(tab.id === active)}\n tabindex={tab.id === active ? '0' : '-1'}\n aria-label={\n badgeLabel === undefined\n ? undefined\n : `${tab.label}, ${badgeLabel}`\n }\n data-has-badge={hasBadge ? 'true' : undefined}\n >\n {tab.icon ? (\n <span class=\"kui-tab-scaffold__tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n {badge}\n </span>\n ) : (\n badge\n )}\n <span class=\"kui-tab-scaffold__tab-label\">{tab.label}</span>\n </button>\n );\n })}\n </nav>\n </section>\n );\n}\n\nfunction normalizeBadge(\n badge: string | number | undefined,\n): string | undefined {\n if (typeof badge === 'number')\n return Number.isFinite(badge) ? String(badge) : undefined;\n if (typeof badge === 'string' && badge.trim()) return badge.trim();\n return undefined;\n}\n"]}
|
package/dist/wire-catalog.js
CHANGED
|
@@ -28,7 +28,7 @@ interface WireScrollDividersOptions {
|
|
|
28
28
|
/**
|
|
29
29
|
* App-owned scroll arrangements to pair beyond the ones found by structure
|
|
30
30
|
* (every `Pane`'s header and footer around its content, every `NavStack`'s
|
|
31
|
-
* and `
|
|
31
|
+
* and `TabNavigator`'s chrome around its active region, and every `TabBar`
|
|
32
32
|
* strip).
|
|
33
33
|
*/
|
|
34
34
|
targets?: readonly ScrollDividerTarget[];
|
|
@@ -48,11 +48,11 @@ type Root = HTMLElement | Document;
|
|
|
48
48
|
* - every `Pane` with a header or footer: its header shows a bottom divider and
|
|
49
49
|
* its footer a top divider (drawn by the Pane, per its `chromeDividers`);
|
|
50
50
|
* - every `NavStack`'s top chrome and bottom toolbar around its active view,
|
|
51
|
-
* and every `
|
|
51
|
+
* and every `TabNavigator`'s bar under its active scene: the chrome shows a
|
|
52
52
|
* bottom (top chrome) or top (bottom toolbar, bar) divider. The scroller is
|
|
53
53
|
* whichever element actually scrolls there: the view or scene itself, or,
|
|
54
54
|
* through a sole child with no chrome of its own on that edge, a `Pane`'s
|
|
55
|
-
* content or a nested `NavStack` / `
|
|
55
|
+
* content or a nested `NavStack` / `TabNavigator` region (a Pane's own header
|
|
56
56
|
* or footer draws that boundary instead);
|
|
57
57
|
* - every `TabBar` strip: the bar draws a divider on each side of the strip
|
|
58
58
|
* whose tabs are scrolled out of view;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface WireTabNavigatorOptions {
|
|
2
|
+
/** Invoked with the selected tab id when a bottom-bar tab is activated. */
|
|
3
|
+
onSelect: (tabId: string) => void;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Wire a `TabNavigator`'s bottom tab bar: clicking a tab calls `onSelect` with its
|
|
7
|
+
* id (the app then updates its controlled `active`). Returns a disposer.
|
|
8
|
+
*/
|
|
9
|
+
declare function wireTabNavigator(root: Element, options: WireTabNavigatorOptions): () => void;
|
|
10
|
+
|
|
11
|
+
export { type WireTabNavigatorOptions, wireTabNavigator };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { delegate } from 'kerfjs';
|
|
2
2
|
|
|
3
3
|
// src/components/navigation/tab-scaffold/wiring/wire-tab-scaffold.ts
|
|
4
|
-
function
|
|
4
|
+
function wireTabNavigator(root, options) {
|
|
5
5
|
const section = root.matches('[data-component="tab-scaffold"]') ? root : root.querySelector('[data-component="tab-scaffold"]');
|
|
6
6
|
if (!(section instanceof HTMLElement)) return () => {
|
|
7
7
|
};
|
|
@@ -16,6 +16,6 @@ function wireTabScaffold(root, options) {
|
|
|
16
16
|
);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export {
|
|
19
|
+
export { wireTabNavigator };
|
|
20
20
|
|
|
21
|
-
//# sourceMappingURL=wire-tab-
|
|
21
|
+
//# sourceMappingURL=wire-tab-navigator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/navigation/tab-scaffold/wiring/wire-tab-scaffold.ts"],"names":[],"mappings":";;;AAWO,SAAS,gBAAA,CACd,MACA,OAAA,EACY;AACZ,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,CAAQ,iCAAiC,IAC1D,IAAA,GACA,IAAA,CAAK,cAAc,iCAAiC,CAAA;AACxD,EAAA,IAAI,EAAE,OAAA,YAAmB,WAAA,CAAA,EAAc,OAAO,MAAM;AAAA,EAAC,CAAA;AACrD,EAAA,OAAO,QAAA;AAAA,IACL,OAAA;AAAA,IACA,OAAA;AAAA,IACA,yBAAA;AAAA,IACA,CAAC,QAAQ,MAAA,KAAW;AAClB,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,YAAA,CAAa,uBAAuB,CAAA;AACzD,MAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA;AAAA,IACnC;AAAA,GACF;AACF","file":"wire-tab-navigator.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireTabNavigatorOptions {\n /** Invoked with the selected tab id when a bottom-bar tab is activated. */\n onSelect: (tabId: string) => void;\n}\n\n/**\n * Wire a `TabNavigator`'s bottom tab bar: clicking a tab calls `onSelect` with its\n * id (the app then updates its controlled `active`). Returns a disposer.\n */\nexport function wireTabNavigator(\n root: Element,\n options: WireTabNavigatorOptions,\n): () => void {\n const section = root.matches('[data-component=\"tab-scaffold\"]')\n ? root\n : root.querySelector('[data-component=\"tab-scaffold\"]');\n if (!(section instanceof HTMLElement)) return () => {};\n return delegate(\n section,\n 'click',\n '[data-tab-scaffold-tab]',\n (_event, target) => {\n const tabId = target.getAttribute('data-tab-scaffold-tab');\n if (tabId) options.onSelect(tabId);\n },\n );\n}\n"]}
|
package/dist/workbench.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { SafeHtml } from 'kerfjs';
|
|
2
2
|
import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
|
|
3
|
+
import { b as NavPane, N as NavStackProps } from './nav-stack-B1Qc2e5q.js';
|
|
3
4
|
import { ListConfig } from './list.js';
|
|
4
|
-
import { NavStackProps } from './nav-stack.js';
|
|
5
5
|
import { PaneConfig } from './pane.js';
|
|
6
6
|
import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
|
|
7
|
-
import { a as PanelToolbar,
|
|
7
|
+
import { a as PanelToolbar, c as PanelBottomToolbar, b as PanelChromePlacement, d as PanelToggle } from './panel-toolbar-CL9tYOkh.js';
|
|
8
8
|
import { ToolbarConfig } from './toolbar.js';
|
|
9
|
+
import './toolbar-text.js';
|
|
10
|
+
import './css-values.js';
|
|
9
11
|
import './flex-alignment-4ms8ZbV8.js';
|
|
10
12
|
import './sides-BPSWde0A.js';
|
|
11
|
-
import './css-values.js';
|
|
12
|
-
import './toolbar-text.js';
|
|
13
13
|
import './lucide-icon.js';
|
|
14
14
|
import 'lucide';
|
|
15
15
|
|
|
@@ -131,19 +131,11 @@ interface WorkbenchPanelBase {
|
|
|
131
131
|
restorePosition?: ResizableRegionRestorePosition;
|
|
132
132
|
}
|
|
133
133
|
/** The original static panel body: one toolbar, optional fixed chrome, and one Pane. */
|
|
134
|
-
interface WorkbenchStaticPanel extends WorkbenchPanelBase {
|
|
135
|
-
content: KerfUiContent;
|
|
134
|
+
interface WorkbenchStaticPanel extends WorkbenchPanelBase, NavPane<WorkbenchPanelToolbar, WorkbenchPanelBottomToolbar> {
|
|
136
135
|
/** The composed toolbar; marked groups and its toggle relocate on collapse. */
|
|
137
136
|
toolbar?: WorkbenchPanelToolbar;
|
|
138
|
-
header?: KerfUiContent;
|
|
139
|
-
headerList?: ListConfig;
|
|
140
137
|
headerPlacement?: WorkbenchChromePlacement;
|
|
141
|
-
footer?: KerfUiContent;
|
|
142
|
-
footerList?: ListConfig;
|
|
143
|
-
bottomToolbar?: WorkbenchPanelBottomToolbar;
|
|
144
138
|
footerPlacement?: WorkbenchChromePlacement;
|
|
145
|
-
/** Configuration for the static toolbar panel's Pane. */
|
|
146
|
-
pane?: PaneConfig;
|
|
147
139
|
navStack?: never;
|
|
148
140
|
}
|
|
149
141
|
/** A panel whose active NavStack view supplies the toolbar, fixed header, and scroll body. */
|
package/dist/workbench.js
CHANGED
package/docs/app-layouts.md
CHANGED
|
@@ -9,7 +9,7 @@ changes. The layouts:
|
|
|
9
9
|
- [`NavStack`](nav-stack.md) — push/pop navigation (a single pane is a one-entry stack).
|
|
10
10
|
- [`SplitView`](split-view.md) — list-detail (two panes, collapsing to a stack).
|
|
11
11
|
- [`Workbench`](workbench.md) — the Xcode-like collapsible rails + drawer.
|
|
12
|
-
- [`
|
|
12
|
+
- [`TabNavigator`](tab-navigator.md) — the iOS bottom tab bar (each tab a stack).
|
|
13
13
|
- [`CollapsiblePanel` + `wireSidebar`](collapsible-panel.md) — a standalone
|
|
14
14
|
collapsible rail or bottom drawer (with `CollapsiblePanelToggle`), outside a full
|
|
15
15
|
shell: the standard collapse animation, icon convention, and `wireSidebar`
|
|
@@ -45,7 +45,7 @@ the layouts handle the insets for you. There is nothing to configure:
|
|
|
45
45
|
- **No double inset.** Whichever region applies an inset clears it for its
|
|
46
46
|
descendants. A layout region whose only child is a `Pane` or another layout
|
|
47
47
|
lets that child own the insets. That child fills the region — a `Workbench`
|
|
48
|
-
area or panel, a `NavStack` view, a `
|
|
48
|
+
area or panel, a `NavStack` view, a `TabNavigator` scene — so a sole `Pane`'s
|
|
49
49
|
header stays pinned while its own content scrolls. A `Pane` header or footer whose only child is
|
|
50
50
|
a `Toolbar` hands the inline insets to the toolbar, so the toolbar's dividers
|
|
51
51
|
still reach the edge.
|
|
@@ -96,15 +96,48 @@ The device insets come from `--kui-safe-area-block-start`, `-block-end`,
|
|
|
96
96
|
Override them on `:root` to reserve room for app-owned chrome or to simulate a
|
|
97
97
|
device in tests.
|
|
98
98
|
|
|
99
|
+
### Edge-to-edge content inside a Pane
|
|
100
|
+
|
|
101
|
+
The Pane's scrolling slot publishes its resolved horizontal padding as
|
|
102
|
+
`--kui-pane-content-inset-inline-start` and
|
|
103
|
+
`--kui-pane-content-inset-inline-end`. Both inherited values include the safe
|
|
104
|
+
area on the corresponding side, plus the extra 8px when `deepInset` is true.
|
|
105
|
+
An app component can use them to let a row background or divider reach the
|
|
106
|
+
Pane edge while its text stays on the ordinary content axis:
|
|
107
|
+
|
|
108
|
+
```css
|
|
109
|
+
.orders-table {
|
|
110
|
+
margin-inline-start: calc(-1 * var(--kui-pane-content-inset-inline-start));
|
|
111
|
+
margin-inline-end: calc(-1 * var(--kui-pane-content-inset-inline-end));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.orders-table th:first-child,
|
|
115
|
+
.orders-table td:first-child {
|
|
116
|
+
padding-inline-start: calc(var(--kui-pane-content-inset-inline-start) + 17px);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.orders-table th:last-child,
|
|
120
|
+
.orders-table td:last-child {
|
|
121
|
+
padding-inline-end: calc(var(--kui-pane-content-inset-inline-end) + 17px);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Here 17px is the app's chosen text offset from the content slot: a default
|
|
126
|
+
`ContentItem` uses 8px outer margin, 1px border, and 8px inner padding. Adjust
|
|
127
|
+
that offset to match the neighboring content. Keep the table inside the Pane's
|
|
128
|
+
single scrolling slot; the negative margins change only its inline reach. The
|
|
129
|
+
[`Pane` UX catalog example](../ux-demo/demos/pane.tsx) shows both `deepInset`
|
|
130
|
+
states with the same table component.
|
|
131
|
+
|
|
99
132
|
## Decision matrix
|
|
100
133
|
|
|
101
|
-
| Situation | Layout
|
|
102
|
-
| ---------------------------------------------------------------------------- |
|
|
103
|
-
| Simple app, a few flat sections | `NavStack` with one entry (single pane); add `
|
|
104
|
-
| Drill-down browsing (list → item → sub-item) | `NavStack`; upgrade to `SplitView` once list + detail fit together
|
|
105
|
-
| Two related panes, selecting on the left updates the right | `SplitView`
|
|
106
|
-
| Complex tool / editor with peripheral panels (navigator, inspector, console) | `Workbench`
|
|
107
|
-
| Mobile app with 2–5 top-level destinations, each its own drill-down | `
|
|
134
|
+
| Situation | Layout | Device threshold |
|
|
135
|
+
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
136
|
+
| Simple app, a few flat sections | `NavStack` with one entry (single pane); add `TabNavigator` for 2–5 co-equal sections on handset | `TabNavigator` on `compact`; promote its tabs to a `Workbench` rail / sidebar `atLeast('desktop')` |
|
|
137
|
+
| Drill-down browsing (list → item → sub-item) | `NavStack`; upgrade to `SplitView` once list + detail fit together | `SplitView` two-pane `atLeast('tablet')` landscape / non-`compact`; `NavStack` form on `compact` |
|
|
138
|
+
| Two related panes, selecting on the left updates the right | `SplitView` | two panes when not `compact`; collapses to `NavStack` (list → detail) on `compact` |
|
|
139
|
+
| Complex tool / editor with peripheral panels (navigator, inspector, console) | `Workbench` | full three-panel `atLeast('desktop')`; on smaller classes present the rails via `NavStack` / overlay drawers, not a shrunken shell |
|
|
140
|
+
| Mobile app with 2–5 top-level destinations, each its own drill-down | `TabNavigator`, each tab a `NavStack` | bottom bar on `compact`; promote to a rail / sidebar `atLeast('desktop')` |
|
|
108
141
|
|
|
109
142
|
### Worked examples
|
|
110
143
|
|
|
@@ -129,13 +162,13 @@ device in tests.
|
|
|
129
162
|
default) and shrink in proportion when the window narrows; the bottom drawer
|
|
130
163
|
leaves it `mainMinHeight` (120px by default) and shrinks when the window gets
|
|
131
164
|
shorter.
|
|
132
|
-
- **Social app (tabbed):** `
|
|
165
|
+
- **Social app (tabbed):** `TabNavigator` with Home / Search / Profile tabs, each
|
|
133
166
|
`content` a `NavStack`. On a tablet/desktop, render the same sections as a
|
|
134
167
|
`Workbench` left rail instead of a bottom bar.
|
|
135
168
|
|
|
136
169
|
Whatever the layout, call `wireScrollDividers(appRoot)` from
|
|
137
170
|
`@kerfjs/ui/wire-scroll-dividers` once: every `Pane` a layout renders, every
|
|
138
|
-
`NavStack`'s top chrome and bottom toolbar, and every `
|
|
171
|
+
`NavStack`'s top chrome and bottom toolbar, and every `TabNavigator` bar then
|
|
139
172
|
draw the line between the pinned chrome and the scrolling content only while
|
|
140
173
|
content is scrolled beneath them, and toolbars need no divider of their own. See [Scroll dividers](layout.md#scroll-dividers).
|
|
141
174
|
|
|
@@ -713,7 +713,7 @@ helpers. Its collapse behavior was _rich and error-prone_ — reveal, focus
|
|
|
713
713
|
transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
|
|
714
714
|
apps kept reimplementing inconsistently, so the helper owns it. Everywhere else the
|
|
715
715
|
app's state is **domain or persisted, not transient chrome, and stays app-owned**: a
|
|
716
|
-
`NavStack`'s view stack is navigation history, a `TabBar`/`
|
|
716
|
+
`NavStack`'s view stack is navigation history, a `TabBar`/`TabNavigator`'s selection
|
|
717
717
|
and tab order are data, a `ResizableRegion`'s committed size and a
|
|
718
718
|
`Workbench`/`SplitView` rail's `collapsed` flag are persisted layout preferences.
|
|
719
719
|
Each helper already owns only the _ephemeral mechanics_ around that state —
|
|
@@ -890,7 +890,14 @@ leave the item visible under this width policy. Server-rendered content is visib
|
|
|
890
890
|
until wiring runs; without browser observers, initial wiring and window resize
|
|
891
891
|
still update it. Probe context and lengths are reconciled only when their CSSOM
|
|
892
892
|
values change, so unrelated DOM observers can redraw without feeding an ongoing
|
|
893
|
-
mutation loop.
|
|
893
|
+
mutation loop. Probes are helper-owned imperative nodes preserved across Kerf
|
|
894
|
+
rerenders, preventing repeated probe creation and resize/DOM observer churn.
|
|
895
|
+
Invisible preserved state children keep actual CSS visibility stable while a render
|
|
896
|
+
removes transient parent markers, so post-render geometry measurements cannot
|
|
897
|
+
feed a show/hide render loop. These children never enter layout, focus or the
|
|
898
|
+
accessible tree. Toolbar replacement or deliberate internal-node removal is
|
|
899
|
+
still repaired. Dispose to
|
|
900
|
+
remove measurement probes and restore prior markers.
|
|
894
901
|
|
|
895
902
|
Hidden controls leave layout and keyboard navigation. A hidden busy group also
|
|
896
903
|
hides only its own adjacent busy status. Keep primary actions always available;
|