@kerfjs/ui 5.0.0-beta.76 → 5.0.0-beta.78
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 +15 -0
- package/ai/application-ui-diagnostic-ids-v1.json +3 -0
- package/ai/component-catalog.json +333 -28
- package/ai/component-composition.json +224 -17
- package/ai/components/nav-stack.md +4 -3
- package/ai/components/select.md +1 -0
- package/ai/components/split-view.md +1 -0
- package/ai/components/surface-scaffold.md +11 -2
- package/ai/components/text.md +4 -2
- package/ai/components/toolbar-control-group.md +22 -3
- package/ai/components/toolbar-text.md +21 -4
- package/ai/components/toolbar.md +12 -4
- package/ai/public-api-signatures-v1.md +99 -127
- package/ai/skill.md +37 -3
- 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/catalog-resources.d.ts +3 -3
- package/dist/catalog.d.ts +3 -3
- package/dist/catalog.js +6 -6
- package/dist/chunk-2GOCOLQL.js +270 -0
- package/dist/chunk-2GOCOLQL.js.map +1 -0
- package/dist/{chunk-ZVYA6TLR.js → chunk-4Y4L5YEI.js} +3 -1
- package/dist/chunk-4Y4L5YEI.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-7QWIJEK5.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-HGSTMZZ6.js → chunk-JL4BBZP4.js} +5 -4
- package/dist/chunk-JL4BBZP4.js.map +1 -0
- package/dist/{chunk-IVLTEBBT.js → chunk-M6BWQMQY.js} +2 -2
- package/dist/{chunk-IVLTEBBT.js.map → chunk-M6BWQMQY.js.map} +1 -1
- package/dist/{chunk-72LLWYP2.js → chunk-NVKRZ5DI.js} +1 -1
- package/dist/chunk-NVKRZ5DI.js.map +1 -0
- package/dist/{chunk-GO2CZX4M.js → chunk-RRCD3A6G.js} +13 -5
- package/dist/chunk-RRCD3A6G.js.map +1 -0
- package/dist/{chunk-5PAVAR4B.js → chunk-VHXHLZ6R.js} +10 -1
- package/dist/chunk-VHXHLZ6R.js.map +1 -0
- package/dist/collapsible-panel.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +9 -8
- package/dist/list-header.d.ts +1 -0
- package/dist/list-header.js +1 -1
- package/dist/nav-stack-B1Qc2e5q.d.ts +101 -0
- package/dist/nav-stack.d.ts +12 -93
- package/dist/nav-stack.js +5 -1
- package/dist/pane.d.ts +5 -2
- package/dist/pane.js +1 -1
- package/dist/select-register.js +3 -0
- package/dist/select-register.js.map +1 -1
- package/dist/select.d.ts +1 -0
- package/dist/select.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 +32 -0
- package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +105 -7
- package/dist/styles/components/actions/toolbar-text/toolbar-text.css +17 -0
- package/dist/styles/components/layout/pane/pane.css +7 -0
- package/dist/styles/components/navigation/nav-stack/nav-stack.css +17 -12
- package/dist/styles/components/navigation/tab-scaffold/tab-scaffold.css +17 -0
- package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +55 -0
- package/dist/styles/components/typography/text/text.css +19 -0
- package/dist/styles/nav-stack.css +17 -12
- package/dist/styles/pane.css +7 -0
- package/dist/styles/surface-scaffold.css +55 -0
- package/dist/styles/tab-scaffold.css +17 -0
- package/dist/styles/text.css +19 -0
- package/dist/styles/toolbar-control-group.css +105 -7
- package/dist/styles/toolbar-text.css +17 -0
- package/dist/styles/toolbar.css +32 -0
- 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 +2 -0
- package/dist/tab-scaffold.js +1 -0
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/text.d.ts +5 -2
- package/dist/text.js +1 -1
- package/dist/toolbar-control-group.d.ts +14 -2
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +10 -2
- package/dist/toolbar-text.js +1 -1
- package/dist/wire-nav-stack.js +58 -22
- package/dist/wire-nav-stack.js.map +1 -1
- package/dist/wire-toolbar-visibility.d.ts +13 -0
- package/dist/wire-toolbar-visibility.js +3 -0
- package/dist/wire-toolbar-visibility.js.map +1 -0
- package/dist/workbench.d.ts +5 -13
- package/dist/workbench.js +1 -1
- package/docs/accessibility.md +15 -0
- package/docs/collapsible-panel.md +4 -0
- package/docs/component-contract.md +38 -0
- package/docs/component-selection.md +56 -2
- package/docs/design/templates/toolbar/width-visibility-320-dark.svg +1 -0
- package/docs/design/templates/toolbar/width-visibility-320.svg +1 -0
- package/docs/design/templates/toolbar/width-visibility-600-dark.svg +1 -0
- package/docs/design/templates/toolbar/width-visibility-600.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-brand-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-brand.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-danger-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-danger.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-neutral-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-neutral.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-success-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-success.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-warning-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-warning.svg +1 -0
- package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
- package/docs/design/templates/toolbar-control-group.svg +1 -1
- package/docs/design/templates/toolbar-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/dark-tone-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/dark-tone.svg +1 -0
- package/docs/design/templates/toolbar-text-dark.svg +1 -1
- package/docs/design/templates/toolbar-text.svg +1 -1
- package/docs/design/templates/toolbar.svg +1 -1
- package/docs/design/templates.md +2 -0
- package/docs/layout.md +32 -1
- package/docs/nav-stack.md +35 -19
- package/docs/recipes.md +5 -1
- package/docs/split-view.md +7 -4
- package/docs/surface-scaffold.md +46 -0
- package/docs/tab-scaffold.md +57 -0
- package/docs/ui-doctor.md +23 -0
- package/docs/workbench.md +3 -2
- package/doctor/component-style-checks.mjs +224 -0
- package/doctor/index.mjs +42 -13
- package/llms.txt +34 -0
- package/package.json +11 -2
- package/ux-demo/recipes/list-detail-dialog.tsx +4 -0
- package/ux-demo/recipes/navigation-stack.tsx +2 -2
- package/dist/chunk-5PAVAR4B.js.map +0 -1
- package/dist/chunk-72LLWYP2.js.map +0 -1
- package/dist/chunk-7QWIJEK5.js.map +0 -1
- package/dist/chunk-AOAW55JN.js.map +0 -1
- package/dist/chunk-GO2CZX4M.js.map +0 -1
- package/dist/chunk-HGSTMZZ6.js.map +0 -1
- package/dist/chunk-SJY7KC45.js +0 -123
- package/dist/chunk-SJY7KC45.js.map +0 -1
- package/dist/chunk-XIZRXI7N.js.map +0 -1
- package/dist/chunk-ZVYA6TLR.js.map +0 -1
package/docs/recipes.md
CHANGED
|
@@ -88,7 +88,11 @@ its label still names the modal — and the close button uses
|
|
|
88
88
|
`data-dialog="close"`. Web Awesome keeps modal focus and Escape, and the thin
|
|
89
89
|
recipe adapter restores the invoking control after the hide event.
|
|
90
90
|
`DialogSurface` chooses the large modal with no body inset, because the panes
|
|
91
|
-
own their geometry.
|
|
91
|
+
own their geometry. Its explicit 16px `viewportGutter` and `maxHeight="viewport"`
|
|
92
|
+
bound the roomy modal to the dynamic viewport; the compact fullscreen
|
|
93
|
+
presentation retains its existing geometry. Application render state chooses
|
|
94
|
+
the presentation and can likewise choose gutter/cap values for a compact modal.
|
|
95
|
+
On compact devices the dialog becomes a full-screen sheet
|
|
92
96
|
with its own labeled header and footer actions, and `SplitView` becomes a
|
|
93
97
|
`NavStack` drill-down whose back control the application pops. The
|
|
94
98
|
application owns open state, selection, the pushed detail, dismissal policy,
|
package/docs/split-view.md
CHANGED
|
@@ -86,15 +86,18 @@ resizable={{
|
|
|
86
86
|
|
|
87
87
|
`compactStack` configures the compact `NavStack`: `toolbarConfig`, `backIcon`,
|
|
88
88
|
`backText`, `hideToolbar`, the persistent `bottomToolbar`, and `chromeDividers`
|
|
89
|
-
forward to the stack, and `list` / `detail` give each view its own
|
|
90
|
-
(`leading`, `center`, trailing
|
|
89
|
+
forward to the stack, and `list` / `detail` give each view its own structured
|
|
90
|
+
top `toolbar` (`leading`, `center`, `trailing`) and `bottomToolbar`:
|
|
91
91
|
|
|
92
92
|
```tsx
|
|
93
93
|
compactStack={{
|
|
94
94
|
backText: "Threads",
|
|
95
95
|
toolbarConfig: { headingLevel: 1 },
|
|
96
|
-
list: { toolbar: <ComposeGroup /> },
|
|
97
|
-
detail: {
|
|
96
|
+
list: { toolbar: { trailing: <ComposeGroup /> } },
|
|
97
|
+
detail: {
|
|
98
|
+
toolbar: { trailing: <ReplyGroup /> },
|
|
99
|
+
bottomToolbar: { label: "Message actions", leading: <MessageActions /> },
|
|
100
|
+
},
|
|
98
101
|
}}
|
|
99
102
|
```
|
|
100
103
|
|
package/docs/surface-scaffold.md
CHANGED
|
@@ -12,6 +12,50 @@ Wrap one `wa-dialog` with `DialogSurface`. Choose `size` (`small`, `medium`, or
|
|
|
12
12
|
`bodyInset` / `footerInset` (`none`, `compact`, or `comfortable`). The defaults
|
|
13
13
|
match Kerf's medium modal, 8px body, and 16px footer rhythm.
|
|
14
14
|
|
|
15
|
+
The modal size presets have preferred widths of 400px, 560px, and 800px,
|
|
16
|
+
respectively, before the viewport width cap. Set `preferredWidth` to a branded
|
|
17
|
+
`CssLength` when the desktop shell needs another width. It takes precedence over
|
|
18
|
+
`size`, while the viewport cap still applies. For example, 832px and 480px
|
|
19
|
+
desktop preferences can share the same responsive dialog without changing its
|
|
20
|
+
resource tracks or preview aspect sizing.
|
|
21
|
+
|
|
22
|
+
### Modal viewport bounds
|
|
23
|
+
|
|
24
|
+
`viewportGutter` accepts a branded `CssLength` from `@kerfjs/ui/css-values`.
|
|
25
|
+
It sets the minimum horizontal clearance; `preferredWidth` overrides the `size`
|
|
26
|
+
preset when supplied. `maxHeight` accepts a branded length or `"viewport"`; explicit caps are
|
|
27
|
+
clamped to the dynamic viewport height minus twice the gutter (16px per edge
|
|
28
|
+
when the gutter is omitted). Negative gutters/caps clamp to zero. Omitting
|
|
29
|
+
both options preserves Web Awesome's existing width and phone height caps.
|
|
30
|
+
These options apply only to `presentation="modal"`; side sheets and fullscreen
|
|
31
|
+
surfaces keep their own geometry.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import '@kerfjs/ui/surface-scaffold/register';
|
|
35
|
+
import { remify } from '@kerfjs/ui/css-values';
|
|
36
|
+
import { DialogSurface } from '@kerfjs/ui/surface-scaffold';
|
|
37
|
+
|
|
38
|
+
<DialogSurface preferredWidth={remify(832)} viewportGutter={remify(8)} maxHeight="viewport" bodyInset="none">
|
|
39
|
+
<wa-dialog label="Close workspace">{/* Application-owned content and footer */}</wa-dialog>
|
|
40
|
+
</DialogSurface>;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This gives a desktop modal an 832px preferred width and a phone modal an 8px edge gutter with a `calc(100dvh - 16px)` height
|
|
44
|
+
cap without application selectors on Kerf roots or Web Awesome parts. A cap
|
|
45
|
+
is not a fixed height: short content stays naturally sized, and taller content
|
|
46
|
+
scrolls in Web Awesome's body while its header/footer remain outside that
|
|
47
|
+
scroll owner. Choosing a larger cap cannot guarantee that arbitrary content
|
|
48
|
+
fits without scrolling; preview tracks, canvas aspect ratios and content sizing
|
|
49
|
+
remain application-owned.
|
|
50
|
+
|
|
51
|
+
Responsive policy is explicit application state: read `deviceClass()` during
|
|
52
|
+
the render to choose the gutter, cap or presentation. For example use
|
|
53
|
+
`viewportGutter={remify(device.value.handset ? 8 : 16)}` with `maxHeight="viewport"`.
|
|
54
|
+
The scaffold introduces no hidden breakpoint or automatic fullscreen switch.
|
|
55
|
+
An application can configure device-class breakpoints when its phone policy
|
|
56
|
+
differs from the default. Prefer a typed cap such as `maxHeight={remify(640)}`
|
|
57
|
+
when the modal should stop growing before the viewport bound.
|
|
58
|
+
|
|
15
59
|
Treat a dialog body as a `List` by default. When its rows or sections own their
|
|
16
60
|
standard list geometry, set `bodyInset="none"` so the dialog does not add a
|
|
17
61
|
second inset. Bare prose is still a list child: wrap it in `ListInsetText` so its
|
|
@@ -26,3 +70,5 @@ padding for a child that already owns row insets, `compact` uses 4px, and
|
|
|
26
70
|
preserve `data-morph-skip-children` when its upgraded light-DOM items must retain
|
|
27
71
|
identity across Kerf rerenders. Import `@kerfjs/ui/popup-menu/register` once for
|
|
28
72
|
the focus handoff fix when wrapping a raw `wa-dropdown`.
|
|
73
|
+
|
|
74
|
+
Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog` and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.
|
package/docs/tab-scaffold.md
CHANGED
|
@@ -59,6 +59,63 @@ truncate horizontally with an ellipsis when a destination name exceeds its
|
|
|
59
59
|
share of the bar. On larger device classes, promote the tab set to a
|
|
60
60
|
`Workbench` rail or a persistent sidebar instead of a bottom bar.
|
|
61
61
|
|
|
62
|
+
Set `deepInset: true` on a plain tab scene to add an 8px outer inline gutter
|
|
63
|
+
to its scrolling content, giving ordinary content items a 16px edge inset.
|
|
64
|
+
The default is `false`. A scene whose only child is a `Pane` or layout lets
|
|
65
|
+
that child own its content gutter: for a nested `NavStack`, set
|
|
66
|
+
`pane: { deepInset: true }` on its views instead. The scaffold bar and a
|
|
67
|
+
stack's top toolbar remain full width.
|
|
68
|
+
|
|
69
|
+
## A NavStack with a top toolbar and Pane
|
|
70
|
+
|
|
71
|
+
Put the `NavStack` directly in a tab's `content`. Give each view a structured
|
|
72
|
+
`toolbar` and `pane` configuration. Leave `bottomToolbar` off both the views
|
|
73
|
+
and the stack: the scaffold's tab bar is the only bottom chrome. The view's
|
|
74
|
+
Pane owns scrolling, while the stack keeps its toolbar above it and the
|
|
75
|
+
scaffold keeps the tab bar below it.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { List } from "@kerfjs/ui/list";
|
|
79
|
+
import { ListItem } from "@kerfjs/ui/list-item";
|
|
80
|
+
import { NavStack, type NavStackView } from "@kerfjs/ui/nav-stack";
|
|
81
|
+
|
|
82
|
+
const projectViews: NavStackView[] = [
|
|
83
|
+
{
|
|
84
|
+
key: "projects",
|
|
85
|
+
toolbar: { title: "Projects" },
|
|
86
|
+
pane: { appearance: "sunken" },
|
|
87
|
+
content: <List><ListItem label="Project Atlas" /></List>,
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
<TabScaffold
|
|
92
|
+
id="app"
|
|
93
|
+
label="Sections"
|
|
94
|
+
active={active.value}
|
|
95
|
+
tabs={[
|
|
96
|
+
{
|
|
97
|
+
id: "projects",
|
|
98
|
+
label: "Projects",
|
|
99
|
+
content: (
|
|
100
|
+
<NavStack
|
|
101
|
+
id="projects-stack"
|
|
102
|
+
label="Projects navigation"
|
|
103
|
+
views={projectViews}
|
|
104
|
+
/>
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
{ id: "search", label: "Search", content: <SearchScene /> },
|
|
108
|
+
]}
|
|
109
|
+
/>;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The app owns each stack's view array and the scaffold's active tab. Call
|
|
113
|
+
`wireTabScaffold` for tab selection and `wireNavStack` for back navigation
|
|
114
|
+
and transitions, each on its own component root. Push a detail by appending
|
|
115
|
+
a view with its own `toolbar` and `pane`; switching tabs keeps that view
|
|
116
|
+
mounted. The catalog's **NavStack inside a tab** example demonstrates the
|
|
117
|
+
push, tab switch, and return flow.
|
|
118
|
+
|
|
62
119
|
Set a tab's `appearance: "sunken"` when its scene owns scrolling and should
|
|
63
120
|
paint the lowered surface behind short or long content. For a scene whose
|
|
64
121
|
only child is a `Pane` or `NavStack`, set appearance on that child's scroll
|
package/docs/ui-doctor.md
CHANGED
|
@@ -58,6 +58,29 @@ without that marker remain app-owned.
|
|
|
58
58
|
The analyzer resolves a cataloged application component imported through an
|
|
59
59
|
exact or wildcard TypeScript `paths` alias from the nearest consumer tsconfig,
|
|
60
60
|
so the same root hook-class check applies in those files.
|
|
61
|
+
|
|
62
|
+
## Component style naming
|
|
63
|
+
|
|
64
|
+
The analyzer stage also checks colocated JSX/TSX components and stylesheets.
|
|
65
|
+
When a stylesheet has a same-basename component module, or exactly one
|
|
66
|
+
component module imports it, Doctor checks that the stylesheet filename is
|
|
67
|
+
the component's kebab-case name (`KUI-D031`). A component that renders its own
|
|
68
|
+
HTML element must put that kebab-case class on rendered markup (`KUI-D030`).
|
|
69
|
+
For example, `WorkspaceHeader` pairs with `workspace-header.css` and renders
|
|
70
|
+
`.workspace-header`. Thin components that only compose other components need
|
|
71
|
+
no root class. Shared stylesheets declared in `ownershipGroups` are exempt
|
|
72
|
+
from the naming check.
|
|
73
|
+
|
|
74
|
+
Doctor reports `KUI-D032` when a paired stylesheet selects another local
|
|
75
|
+
component's kebab-case class or its BEM descendants or modifiers. Move that
|
|
76
|
+
rule to the owning component's stylesheet or use the component's public
|
|
77
|
+
configuration. This check uses source declarations and CSS selectors; it does
|
|
78
|
+
not execute component code. In changed mode, it reads other component sources
|
|
79
|
+
to resolve ownership but reports only pairs containing a selected path. For
|
|
80
|
+
broader cross-component ownership, including
|
|
81
|
+
cataloged package components and hook classes, enable `ownership: "component"`
|
|
82
|
+
with `implicitComponentOwnership: true` as described above.
|
|
83
|
+
|
|
61
84
|
Set `"ownership": "component"` in `.kerf-ui-doctor.json` to enforce the same
|
|
62
85
|
boundary between a consumer package's own cataloged components. The default is
|
|
63
86
|
`"package"`. Selection-only entries participate too. A component owns its
|
package/docs/workbench.md
CHANGED
|
@@ -167,8 +167,9 @@ into `.kui-resizable-region__content`.
|
|
|
167
167
|
|
|
168
168
|
A panel with `navStack` hosts a controlled `NavStack` in place of the static
|
|
169
169
|
`content` and `Pane` slots. Give it a `toolbar` with a `toggle`; each
|
|
170
|
-
`navStack.views` entry then supplies its own title
|
|
171
|
-
groups,
|
|
170
|
+
`navStack.views` entry then supplies its own structured `toolbar` with a title
|
|
171
|
+
and leading/center/trailing groups, optional `header` / `footer` and `pane`
|
|
172
|
+
configuration, and scrolling `content`. The active view's toolbar is
|
|
172
173
|
the panel's only toolbar row, with the standard toggle always last. On
|
|
173
174
|
collapse, marked groups from the active view and the toggle relocate to the
|
|
174
175
|
work-area toolbar; the view stack stays mounted. The app owns the view array
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, extname, relative, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import postcss from 'postcss';
|
|
5
|
+
import ts from 'typescript';
|
|
6
|
+
|
|
7
|
+
import { isUiTraversalExcluded } from '../traversal-exclusions.mjs';
|
|
8
|
+
|
|
9
|
+
const scriptExtensions = new Set(['.tsx', '.jsx']);
|
|
10
|
+
const kebab = (name) =>
|
|
11
|
+
name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
12
|
+
const componentName = (name) => /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
13
|
+
const classNames = (value) =>
|
|
14
|
+
[...value.matchAll(/(?:^|\s)([a-z][a-z0-9_-]*)(?=\s|$)/g)].map(
|
|
15
|
+
(match) => match[1],
|
|
16
|
+
);
|
|
17
|
+
const block = (name) => name.split(/__|--/)[0];
|
|
18
|
+
|
|
19
|
+
function componentFacts(file, source) {
|
|
20
|
+
const syntax = file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.JSX;
|
|
21
|
+
const ast = ts.createSourceFile(
|
|
22
|
+
file,
|
|
23
|
+
source,
|
|
24
|
+
ts.ScriptTarget.Latest,
|
|
25
|
+
true,
|
|
26
|
+
syntax,
|
|
27
|
+
);
|
|
28
|
+
const components = new Map();
|
|
29
|
+
const visit = (node, owner) => {
|
|
30
|
+
if (
|
|
31
|
+
ts.isFunctionDeclaration(node) &&
|
|
32
|
+
node.name &&
|
|
33
|
+
componentName(node.name.text)
|
|
34
|
+
)
|
|
35
|
+
owner = node.name.text;
|
|
36
|
+
if (
|
|
37
|
+
ts.isVariableDeclaration(node) &&
|
|
38
|
+
ts.isIdentifier(node.name) &&
|
|
39
|
+
componentName(node.name.text)
|
|
40
|
+
)
|
|
41
|
+
owner = node.name.text;
|
|
42
|
+
if (
|
|
43
|
+
owner &&
|
|
44
|
+
(ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node))
|
|
45
|
+
) {
|
|
46
|
+
if (ts.isIdentifier(node.tagName) && /^[a-z]/.test(node.tagName.text)) {
|
|
47
|
+
if (components.has(owner)) components.get(owner).intrinsic = true;
|
|
48
|
+
for (const attribute of node.attributes.properties) {
|
|
49
|
+
if (
|
|
50
|
+
!ts.isJsxAttribute(attribute) ||
|
|
51
|
+
!['class', 'className'].includes(attribute.name.text)
|
|
52
|
+
)
|
|
53
|
+
continue;
|
|
54
|
+
const value = attribute.initializer;
|
|
55
|
+
const literal =
|
|
56
|
+
value && ts.isStringLiteral(value)
|
|
57
|
+
? value.text
|
|
58
|
+
: value &&
|
|
59
|
+
ts.isJsxExpression(value) &&
|
|
60
|
+
value.expression &&
|
|
61
|
+
(ts.isStringLiteral(value.expression) ||
|
|
62
|
+
ts.isNoSubstitutionTemplateLiteral(value.expression))
|
|
63
|
+
? value.expression.text
|
|
64
|
+
: undefined;
|
|
65
|
+
if (literal)
|
|
66
|
+
for (const name of classNames(literal))
|
|
67
|
+
components.get(owner)?.add(name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
ts.forEachChild(node, (child) => visit(child, owner));
|
|
72
|
+
};
|
|
73
|
+
const declarations = (node) => {
|
|
74
|
+
if (
|
|
75
|
+
ts.isFunctionDeclaration(node) &&
|
|
76
|
+
node.name &&
|
|
77
|
+
componentName(node.name.text)
|
|
78
|
+
)
|
|
79
|
+
components.set(node.name.text, new Set());
|
|
80
|
+
if (
|
|
81
|
+
ts.isVariableDeclaration(node) &&
|
|
82
|
+
ts.isIdentifier(node.name) &&
|
|
83
|
+
componentName(node.name.text)
|
|
84
|
+
)
|
|
85
|
+
components.set(node.name.text, new Set());
|
|
86
|
+
ts.forEachChild(node, declarations);
|
|
87
|
+
};
|
|
88
|
+
declarations(ast);
|
|
89
|
+
visit(ast);
|
|
90
|
+
return components;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function diagnostic(id, file, line, message, action) {
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
severity: 'error',
|
|
97
|
+
stage: 'analyzer',
|
|
98
|
+
message,
|
|
99
|
+
location: { file, line, column: 1 },
|
|
100
|
+
action,
|
|
101
|
+
documentation: '@kerfjs/ui/docs/ui-doctor.md#component-style-naming',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function checkComponentStyles(
|
|
106
|
+
packageRoot,
|
|
107
|
+
inputs,
|
|
108
|
+
ownershipGroups = [],
|
|
109
|
+
selectedPaths,
|
|
110
|
+
) {
|
|
111
|
+
const scripts = new Map();
|
|
112
|
+
const styles = new Map();
|
|
113
|
+
const importedBy = new Map();
|
|
114
|
+
for (const file of inputs) {
|
|
115
|
+
if (isUiTraversalExcluded(packageRoot, file)) continue;
|
|
116
|
+
const extension = extname(file);
|
|
117
|
+
if (scriptExtensions.has(extension)) {
|
|
118
|
+
const source = await readFile(file, 'utf8');
|
|
119
|
+
scripts.set(file, componentFacts(file, source));
|
|
120
|
+
for (const match of source.matchAll(
|
|
121
|
+
/\bimport\s*(?:['"]|[^;]*?\bfrom\s*['"])(\.[^'"]+\.css)['"]/g,
|
|
122
|
+
)) {
|
|
123
|
+
const style = resolve(dirname(file), match[1]);
|
|
124
|
+
const importers = importedBy.get(style) ?? [];
|
|
125
|
+
importers.push(file);
|
|
126
|
+
importedBy.set(style, importers);
|
|
127
|
+
}
|
|
128
|
+
} else if (extension === '.css')
|
|
129
|
+
styles.set(file, await readFile(file, 'utf8'));
|
|
130
|
+
}
|
|
131
|
+
const owners = new Map();
|
|
132
|
+
for (const [file, components] of scripts)
|
|
133
|
+
for (const [name, classes] of components)
|
|
134
|
+
if (classes.has(kebab(name)))
|
|
135
|
+
owners.set(kebab(name), { file, name, classes });
|
|
136
|
+
const diagnostics = [];
|
|
137
|
+
for (const [file, source] of styles) {
|
|
138
|
+
if (
|
|
139
|
+
ownershipGroups.some((group) =>
|
|
140
|
+
group.styleSources?.some(
|
|
141
|
+
(style) => resolve(packageRoot, style) === file,
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
continue;
|
|
146
|
+
const stem = basename(file, '.css');
|
|
147
|
+
const pair =
|
|
148
|
+
[...scriptExtensions]
|
|
149
|
+
.map((extension) => resolve(dirname(file), `${stem}${extension}`))
|
|
150
|
+
.find((candidate) => scripts.has(candidate)) ??
|
|
151
|
+
(importedBy.get(file)?.length === 1
|
|
152
|
+
? importedBy.get(file)[0]
|
|
153
|
+
: undefined);
|
|
154
|
+
if (!pair) continue;
|
|
155
|
+
const path = relative(packageRoot, file).replaceAll('\\', '/');
|
|
156
|
+
if (
|
|
157
|
+
selectedPaths?.length &&
|
|
158
|
+
!selectedPaths.includes(path) &&
|
|
159
|
+
!selectedPaths.includes(relative(packageRoot, pair).replaceAll('\\', '/'))
|
|
160
|
+
)
|
|
161
|
+
continue;
|
|
162
|
+
const components = scripts.get(pair);
|
|
163
|
+
const matching = [...components].find(([name]) => kebab(name) === stem);
|
|
164
|
+
if (!matching) {
|
|
165
|
+
const names = [...components.keys()];
|
|
166
|
+
if (names.length)
|
|
167
|
+
diagnostics.push(
|
|
168
|
+
diagnostic(
|
|
169
|
+
'KUI-D031',
|
|
170
|
+
path,
|
|
171
|
+
1,
|
|
172
|
+
`${basename(file)} is paired with ${basename(pair)}, but its component ${names[0]} calls for ${kebab(names[0])}.css.`,
|
|
173
|
+
`Rename the stylesheet and its import to ${kebab(names[0])}.css, or rename the component and source pair.`,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const [name, renderedClasses] = matching;
|
|
179
|
+
if (renderedClasses.intrinsic && !renderedClasses.has(stem))
|
|
180
|
+
diagnostics.push(
|
|
181
|
+
diagnostic(
|
|
182
|
+
'KUI-D030',
|
|
183
|
+
relative(packageRoot, pair).replaceAll('\\', '/'),
|
|
184
|
+
1,
|
|
185
|
+
`${name} must render its own .${stem} root class.`,
|
|
186
|
+
`Add class="${stem}" to an element rendered by ${name}, then select that class in ${basename(file)}.`,
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
let sheet;
|
|
190
|
+
try {
|
|
191
|
+
sheet = postcss.parse(source, { from: file });
|
|
192
|
+
} catch {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
sheet.walkRules((rule) => {
|
|
196
|
+
if (
|
|
197
|
+
rule.parent?.type === 'atrule' &&
|
|
198
|
+
/keyframes$/i.test(rule.parent.name)
|
|
199
|
+
)
|
|
200
|
+
return;
|
|
201
|
+
const selected = [
|
|
202
|
+
...new Set(
|
|
203
|
+
[...rule.selector.matchAll(/\.([a-z][a-z0-9_-]*)/gi)].map(
|
|
204
|
+
(match) => match[1],
|
|
205
|
+
),
|
|
206
|
+
),
|
|
207
|
+
];
|
|
208
|
+
for (const className of selected) {
|
|
209
|
+
const foreign = owners.get(block(className));
|
|
210
|
+
if (!foreign || foreign.name === name) continue;
|
|
211
|
+
diagnostics.push(
|
|
212
|
+
diagnostic(
|
|
213
|
+
'KUI-D032',
|
|
214
|
+
path,
|
|
215
|
+
rule.source?.start?.line ?? 1,
|
|
216
|
+
`${basename(file)} selects .${className}, which belongs to ${foreign.name} in ${relative(packageRoot, foreign.file).replaceAll('\\', '/')}.`,
|
|
217
|
+
`Move this rule to ${kebab(foreign.name)}.css or configure ${foreign.name} through its public API.`,
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return diagnostics;
|
|
224
|
+
}
|
package/doctor/index.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
UI_ANALYSIS_RULES,
|
|
28
28
|
} from '../analyzer/index.mjs';
|
|
29
29
|
import { evaluateUi, UI_EVALUATION_RULES } from '../evaluator/index.mjs';
|
|
30
|
+
import { checkComponentStyles } from './component-style-checks.mjs';
|
|
30
31
|
import {
|
|
31
32
|
isForeignRuleDefinitionDiagnostic,
|
|
32
33
|
projectConsumerCoreConfig,
|
|
@@ -53,6 +54,18 @@ export const UI_DOCTOR_RULES = Object.freeze({
|
|
|
53
54
|
severity: 'error',
|
|
54
55
|
title: 'Local component catalog is stale or invalid',
|
|
55
56
|
},
|
|
57
|
+
'KUI-D030': {
|
|
58
|
+
severity: 'error',
|
|
59
|
+
title: 'Component root class does not match its name',
|
|
60
|
+
},
|
|
61
|
+
'KUI-D031': {
|
|
62
|
+
severity: 'error',
|
|
63
|
+
title: 'Component stylesheet does not match its name',
|
|
64
|
+
},
|
|
65
|
+
'KUI-D032': {
|
|
66
|
+
severity: 'error',
|
|
67
|
+
title: 'Stylesheet selects another component class',
|
|
68
|
+
},
|
|
56
69
|
});
|
|
57
70
|
|
|
58
71
|
const sourceExtensions = new Set([
|
|
@@ -480,7 +493,7 @@ async function installedPackageVersion(root, name) {
|
|
|
480
493
|
}
|
|
481
494
|
|
|
482
495
|
async function cacheKey(root, packageRoot, mode, paths, config) {
|
|
483
|
-
const files = await collectInputs(packageRoot
|
|
496
|
+
const files = await collectInputs(packageRoot);
|
|
484
497
|
for (const directory of new Set([root, packageRoot])) {
|
|
485
498
|
for (const name of [
|
|
486
499
|
'package.json',
|
|
@@ -521,6 +534,14 @@ async function cacheKey(root, packageRoot, mode, paths, config) {
|
|
|
521
534
|
}),
|
|
522
535
|
)
|
|
523
536
|
.update(await readFile(fileURLToPath(import.meta.url)));
|
|
537
|
+
hash.update(
|
|
538
|
+
await readFile(
|
|
539
|
+
resolve(
|
|
540
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
541
|
+
'component-style-checks.mjs',
|
|
542
|
+
),
|
|
543
|
+
),
|
|
544
|
+
);
|
|
524
545
|
for (const file of files)
|
|
525
546
|
hash.update(portablePath(root, file)).update(await readFile(file));
|
|
526
547
|
return hash.digest('hex');
|
|
@@ -1215,18 +1236,26 @@ export async function runUiDoctor({
|
|
|
1215
1236
|
signal,
|
|
1216
1237
|
})
|
|
1217
1238
|
: (async () => ({
|
|
1218
|
-
diagnostics:
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1239
|
+
diagnostics: [
|
|
1240
|
+
...normalizeAnalyzer(
|
|
1241
|
+
await analyzeUiProject({
|
|
1242
|
+
root: packageRoot,
|
|
1243
|
+
paths: selectedPaths,
|
|
1244
|
+
ownership: config.ownership ?? 'package',
|
|
1245
|
+
ownershipContext: config.ownershipContext ?? 'subject',
|
|
1246
|
+
implicitComponentOwnership:
|
|
1247
|
+
config.implicitComponentOwnership ?? false,
|
|
1248
|
+
ownershipGroups: config.ownershipGroups ?? [],
|
|
1249
|
+
knownRules: [...registeredRuleIds, ...eslintRuleIds],
|
|
1250
|
+
}),
|
|
1251
|
+
),
|
|
1252
|
+
...(await checkComponentStyles(
|
|
1253
|
+
packageRoot,
|
|
1254
|
+
await collectInputs(packageRoot),
|
|
1255
|
+
config.ownershipGroups ?? [],
|
|
1256
|
+
selectedPaths,
|
|
1257
|
+
)),
|
|
1258
|
+
],
|
|
1230
1259
|
}))(),
|
|
1231
1260
|
signal,
|
|
1232
1261
|
),
|
package/llms.txt
CHANGED
|
@@ -19,6 +19,10 @@ the folder modules, and CSS only imports their owner stylesheets.
|
|
|
19
19
|
|
|
20
20
|
### Shared semantic tones
|
|
21
21
|
|
|
22
|
+
Use `ToolbarText tone="dark"` on a loud or photo-backed toolbar surface to
|
|
23
|
+
match dark control groups. It applies to read-only headings and actionable
|
|
24
|
+
titles at every size; default tone retains existing identity and heading colors.
|
|
25
|
+
|
|
22
26
|
`SemanticTone` is the shared color-role type for `Badge`, `Chip`, `StateBanner`,
|
|
23
27
|
and semantic `ContentItem` appearances: `neutral`, `info`, `pop`, `success`,
|
|
24
28
|
`warning`, and `danger`. Import it as a type from `@kerfjs/ui`,
|
|
@@ -342,6 +346,11 @@ headingLevel`, `TokenSearchField clearIcon`, `ContentItem frame="framed"` for a
|
|
|
342
346
|
compact, prominent, display, and code roles instead of global utility classes. It passes native attributes
|
|
343
347
|
through; block variants own the standard transparent border plus item padding, while `span` owns none. Keep
|
|
344
348
|
toolbar titles on `ToolbarText` and list-aligned bare prose on `ListInsetText`.
|
|
349
|
+
Text supports `controlMargins` with canonical physical Sides for an exterior 8px control gutter; it preserves border/padding and avoids an isolated Row wrapper. Avoid stacking it with parent insets. Inline spans retain native inline margin behavior.
|
|
350
|
+
|
|
351
|
+
`ToolbarControlGroup.tileTone` selects `neutral`, `brand`, `success`, `warning`, or `danger` quiet fill, matching quiet border, and on-quiet foreground for a non-interactive icon tile. Compose a contained `single` group whose only child is a direct decorative LucideIcon (`aria-hidden="true"`). Omission preserves the normal group palette. The prop does not recolor interactive, mixed, multi-control, or borderless groups; keep real actions in controls with their existing focus/hover/pressed treatments.
|
|
352
|
+
Expanded search groups accept `expandedOverflow="visible"` to anchor app-owned suggestion/date/help surfaces below their relative root; collapsed/default groups retain existing clipping. Apps own surface placement/dismissal and use `data-token-search-keep-open` or `collapsible.keepOpenOn` for focus.
|
|
353
|
+
|
|
345
354
|
- **Space by the five-step scale, by relationship not by eye.** `0`
|
|
346
355
|
`--kui-space-none`, `4px` `--kui-space-2xs`, `8px` `--kui-space-xs` (inside a
|
|
347
356
|
group), `16px` `--kui-space-m`, `24px` `--kui-space-l` (between major differing
|
|
@@ -533,3 +542,28 @@ shell, `@kerfjs/ui/collapsible-panel` (`CollapsiblePanel`, `CollapsiblePanelTogg
|
|
|
533
542
|
- [README.md](./README.md): package overview and the component subpath table.
|
|
534
543
|
|
|
535
544
|
Neutral lowered surfaces and decorative neutral fills/borders are translucent theme colors; compose nested public components directly. Default/raised canvases, foregrounds, focus indicators, and loud contrast-bearing fills remain opaque. Do not assign depth-specific component tokens in demo wrappers.
|
|
545
|
+
|
|
546
|
+
Navigation transition continuity: `wireNavStack` slides the incoming view over the preserved previous view on push and the outgoing view off on pop, cross-fades both live chrome surfaces with inert snapshots, and settles/cancels an interrupted transition before starting the next. Reduced motion and duration zero settle immediately.
|
|
547
|
+
|
|
548
|
+
For a tab scene with drill-down, put a `NavStack` directly in `TabScaffoldTab.content`, configure each view's top `toolbar` and `pane`, and omit `bottomToolbar` at both levels so the scaffold bar remains the only bottom chrome. Wire both components on their own roots. The catalog's **NavStack inside a tab** example exercises push and tab switching.
|
|
549
|
+
|
|
550
|
+
`Pane.deepInset` (and `PaneConfig.deepInset`) or `TabScaffoldTab.deepInset` on a plain scene adds an 8px outer gutter to the scroll content, so ordinary 8px item margins land 16px from the edge. Nested Row/List/Grid spacing stays at its default. Set a nested scene's Pane option instead of insetting its entire NavStack or toolbar.
|
|
551
|
+
|
|
552
|
+
`ToolbarText` and `ToolbarControlGroup` accept typed `hideBelow`/`showBelow`
|
|
553
|
+
lengths (for example `px(416)`). Wire `wireToolbarVisibility(root)` from
|
|
554
|
+
`@kerfjs/ui/wire-toolbar-visibility` and dispose on unmount. Thresholds use the
|
|
555
|
+
nearest Toolbar content-box width: hideBelow hides strictly below, showBelow
|
|
556
|
+
shows strictly below, with complementary behavior at equality. Percentages use
|
|
557
|
+
that content width; em and custom properties use the item's own context. Invalid,
|
|
558
|
+
negative, unresolved, or contradictory values leave the item visible under this
|
|
559
|
+
width policy. Keep primary actions visible and offer a complementary overflow
|
|
560
|
+
group. See `docs/component-contract.md` "Toolbar width visibility" for lifecycle
|
|
561
|
+
details.
|
|
562
|
+
|
|
563
|
+
Select menus use native viewport shifting with 10px gutters: default menus grow to content (up to 20rem), while `fitMenu` matches trigger width and long labels wrap. Keep popup sizing and position repairs out of consumer CSS.
|
|
564
|
+
|
|
565
|
+
Modal `DialogSurface` bounds use typed `viewportGutter` and `maxHeight` (`CssLength` or `"viewport"`); explicit caps stay within the dynamic viewport minus both gutters. Omitted props preserve Web Awesome defaults. Application render state chooses responsive values, and preview layout remains application-owned.
|
|
566
|
+
|
|
567
|
+
Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog` and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.
|
|
568
|
+
|
|
569
|
+
Toolbar visibility measurement probes and invisible state children are helper-owned and survive Kerf rerenders through `data-morph-preserve`. Actual CSS visibility stays stable while rendering removes transient parent markers, preventing geometry-driven render feedback and repeated probe recreation. Toolbar replacement, explicit internal-node removal and disposal retain their existing behavior.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kerfjs/ui",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.78",
|
|
4
4
|
"description": "Accessible, composable UI primitives for kerf applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"./dist/browser/*.js",
|
|
41
41
|
"./dist/select-register.js",
|
|
42
42
|
"./dist/popup-menu-register.js",
|
|
43
|
-
"./dist/help-tags-register.js"
|
|
43
|
+
"./dist/help-tags-register.js",
|
|
44
|
+
"./dist/surface-scaffold-register.js"
|
|
44
45
|
],
|
|
45
46
|
"exports": {
|
|
46
47
|
".": {
|
|
@@ -90,6 +91,10 @@
|
|
|
90
91
|
"browser": "./dist/browser/surface-scaffold.js",
|
|
91
92
|
"import": "./dist/surface-scaffold.js"
|
|
92
93
|
},
|
|
94
|
+
"./surface-scaffold/register": {
|
|
95
|
+
"types": "./dist/surface-scaffold-register.d.ts",
|
|
96
|
+
"import": "./dist/surface-scaffold-register.js"
|
|
97
|
+
},
|
|
93
98
|
"./floating-toolbar": {
|
|
94
99
|
"types": "./dist/floating-toolbar.d.ts",
|
|
95
100
|
"browser": "./dist/browser/floating-toolbar.js",
|
|
@@ -215,6 +220,10 @@
|
|
|
215
220
|
"types": "./dist/wire-scroll-dividers.d.ts",
|
|
216
221
|
"import": "./dist/wire-scroll-dividers.js"
|
|
217
222
|
},
|
|
223
|
+
"./wire-toolbar-visibility": {
|
|
224
|
+
"types": "./dist/wire-toolbar-visibility.d.ts",
|
|
225
|
+
"import": "./dist/wire-toolbar-visibility.js"
|
|
226
|
+
},
|
|
218
227
|
"./nav-stack": {
|
|
219
228
|
"types": "./dist/nav-stack.d.ts",
|
|
220
229
|
"browser": "./dist/browser/nav-stack.js",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import '@kerfjs/ui/surface-scaffold/register';
|
|
1
2
|
import '@kerfjs/ui/layout.css';
|
|
2
3
|
import '@kerfjs/ui/nav-stack.css';
|
|
3
4
|
import '@kerfjs/ui/split-view.css';
|
|
@@ -5,6 +6,7 @@ import '@kerfjs/ui/webawesome.css';
|
|
|
5
6
|
import '@awesome.me/webawesome/dist/components/button/button.js';
|
|
6
7
|
import '@awesome.me/webawesome/dist/components/dialog/dialog.js';
|
|
7
8
|
|
|
9
|
+
import { remify } from '@kerfjs/ui/css-values';
|
|
8
10
|
import { deviceClass } from '@kerfjs/ui/device-class';
|
|
9
11
|
import { List } from '@kerfjs/ui/list';
|
|
10
12
|
import { ListItem } from '@kerfjs/ui/list-item';
|
|
@@ -184,6 +186,8 @@ export const createRecipe: RecipeFactory = (announce) => {
|
|
|
184
186
|
<DialogSurface
|
|
185
187
|
size="large"
|
|
186
188
|
presentation={compact ? 'fullscreen' : 'modal'}
|
|
189
|
+
viewportGutter={remify(16)}
|
|
190
|
+
maxHeight="viewport"
|
|
187
191
|
bodyInset="none"
|
|
188
192
|
footerInset="comfortable"
|
|
189
193
|
>
|
|
@@ -58,7 +58,7 @@ const ITEMS: LibraryItem[] = [
|
|
|
58
58
|
export const createRecipe: RecipeFactory = (announce) => {
|
|
59
59
|
const listView = (): NavStackView => ({
|
|
60
60
|
key: 'library',
|
|
61
|
-
title: 'Library',
|
|
61
|
+
toolbar: { title: 'Library' },
|
|
62
62
|
content: (
|
|
63
63
|
<List>
|
|
64
64
|
<section>
|
|
@@ -79,7 +79,7 @@ export const createRecipe: RecipeFactory = (announce) => {
|
|
|
79
79
|
|
|
80
80
|
const detailView = (item: LibraryItem): NavStackView => ({
|
|
81
81
|
key: item.id,
|
|
82
|
-
title: item.label,
|
|
82
|
+
toolbar: { title: item.label },
|
|
83
83
|
content: (
|
|
84
84
|
<List controlInsets="t">
|
|
85
85
|
<ContentItem>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/actions/toolbar-text/toolbar-text.tsx"],"names":[],"mappings":";;;;AA8DO,SAAS,WAAA,CAAY;AAAA,EAC1B,IAAA;AAAA,EACA,IAAA,GAAO,SAAA;AAAA,EACP,SAAA,GAAY,EAAA;AAAA,EACZ,EAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA,GAAc,KAAA;AAAA,EACd,IAAA,GAAO,KAAA;AAAA,EACP,QAAA,GAAW,IAAA;AAAA,EACX,QAAA,GAAW,IAAA;AAAA,EACX,IAAA,GAAO,KAAA;AAAA,EACP,IAAA;AAAA,EACA;AACF,CAAA,EAAqB;AACnB,EAAA,MAAM,MAAA,GAAS,IAAA,IAAQ,QAAA,IAAY,IAAA,IAAQ,QAAA,GAAW,CAAA;AACtD,EAAA,IAAI,MAAA,KAAW,MAAA;AACb,IAAA,uBACE,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,QAAA;AAAA,QACL,KAAA,EAAO,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,QAC5C,gBAAA,EAAe,cAAA;AAAA,QACf,WAAA,EAAW,IAAA;AAAA,QACX,WAAA,EAAW,OAAO,MAAA,GAAS,MAAA;AAAA,QAC3B,eAAA,EAAe,WAAW,MAAA,GAAY,OAAA;AAAA,QACtC,aAAA,EAAa,MAAA;AAAA,QACb,EAAA;AAAA,QACA,IAAA;AAAA,QAEA,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAM,wBAAA,EAA0B,QAAA,EAAA,IAAA,EAAK;AAAA;AAAA,KAC7C;AAEJ,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MAC5C,gBAAA,EAAe,cAAA;AAAA,MACf,WAAA,EAAW,IAAA;AAAA,MACX,WAAA,EAAW,OAAO,MAAA,GAAS,MAAA;AAAA,MAC3B,WAAA,EAAW,OAAO,MAAA,GAAS,MAAA;AAAA,MAC3B,eAAA,EAAe,WAAW,MAAA,GAAY,OAAA;AAAA,MACtC,gBAAA,EAAgB,MAAA,GAAS,MAAA,CAAO,QAAQ,CAAA,GAAI,MAAA;AAAA,MAC5C,KAAA,EAAO,MAAA,GAAS,CAAA,6BAAA,EAAgC,QAAQ,CAAA,CAAA,GAAK,MAAA;AAAA,MAC7D,kBAAA,EAAkB,cAAc,MAAA,GAAS,MAAA;AAAA,MACzC,EAAA;AAAA,MACA,IAAA,EAAM,eAAe,SAAA,GAAY,MAAA;AAAA,MACjC,YAAA,EAAY,YAAA,GAAe,MAAA,CAAO,YAAY,CAAA,GAAI,MAAA;AAAA,MAClD,WAAA,EAAW,cAAc,MAAA,GAAS,MAAA;AAAA,MAClC,IAAA;AAAA,MAEC,QAAA,EAAA,WAAA,mBACC,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAO,EAAA,CAAG,CAAC,CAAA,EAAG,CAAA,mBAExB,GAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAM,wBAAA,EAA0B,QAAA,EAAA,IAAA,EAAK;AAAA;AAAA,GAE/C;AAEJ","file":"chunk-5PAVAR4B.js","sourcesContent":["import { em } from '../../../shared/styles/css-values.js';\nimport { Skeleton } from '../../feedback/skeleton/skeleton.js';\n\nexport type ToolbarTextSize =\n 'xlarge' | 'xlarge-fixed' | 'large' | 'default' | 'small' | 'xsmall';\n\n/** ARIA heading level for a title exposed as a heading landmark. */\nexport type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\ninterface ToolbarTextBaseProps {\n text: string;\n size?: ToolbarTextSize;\n className?: string;\n /** Optional id, e.g. so a dialog can reference the title via aria-labelledby. */\n id?: string;\n /**\n * Show a trailing ellipsis (…) where the text is truncated — on the single line\n * (default), or at the `maxLines` boundary when wrapping. Set false to hard-clip\n * instead. Default true.\n */\n ellipsis?: boolean;\n /**\n * Grow to fill the free space of the flex row it sits in (a Toolbar zone),\n * truncating at the space left by its siblings instead of taking only its\n * text's width. Default false.\n */\n fill?: boolean;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n /**\n * Cap wrapped text to this many lines, truncating past it. Only takes effect with\n * `wrap`; ignored on a single line. `null`/omitted wraps without a line cap. Default null.\n */\n}\n\ntype ToolbarTextWrappingProps =\n | {\n /** Wrap onto multiple lines; combine with `maxLines` to cap them. */\n wrap: true;\n maxLines?: number | null;\n }\n | { wrap?: false; maxLines?: never };\n\nexport type ToolbarTextProps = ToolbarTextBaseProps &\n (\n | ({\n action?: never;\n /** Expose `role=\"heading\"` and `aria-level` on read-only text. */\n headingLevel?: HeadingLevel;\n /** Render an unanimated loading skeleton instead of the text. */\n placeholder?: boolean;\n } & ToolbarTextWrappingProps)\n | {\n /** Render a native button with this delegated action; the app owns editing. */\n action: string;\n headingLevel?: never;\n placeholder?: never;\n wrap?: never;\n maxLines?: never;\n }\n );\n\nexport function ToolbarText({\n text,\n size = 'default',\n className = '',\n id,\n headingLevel,\n placeholder = false,\n wrap = false,\n ellipsis = true,\n maxLines = null,\n fill = false,\n slot,\n action,\n}: ToolbarTextProps) {\n const capped = wrap && maxLines != null && maxLines > 0;\n if (action !== undefined)\n return (\n <button\n type=\"button\"\n class={`kui-toolbar-text ${className}`.trim()}\n data-component=\"toolbar-text\"\n data-size={size}\n data-fill={fill ? 'true' : undefined}\n data-ellipsis={ellipsis ? undefined : 'false'}\n data-action={action}\n id={id}\n slot={slot}\n >\n <span class=\"kui-toolbar-text__text\">{text}</span>\n </button>\n );\n return (\n <span\n class={`kui-toolbar-text ${className}`.trim()}\n data-component=\"toolbar-text\"\n data-size={size}\n data-fill={fill ? 'true' : undefined}\n data-wrap={wrap ? 'true' : undefined}\n data-ellipsis={ellipsis ? undefined : 'false'}\n data-max-lines={capped ? String(maxLines) : undefined}\n style={capped ? `--kui-toolbar-text-max-lines:${maxLines}` : undefined}\n data-placeholder={placeholder ? 'true' : undefined}\n id={id}\n role={headingLevel ? 'heading' : undefined}\n aria-level={headingLevel ? String(headingLevel) : undefined}\n aria-busy={placeholder ? 'true' : undefined}\n slot={slot}\n >\n {placeholder ? (\n <Skeleton width={em(8)} />\n ) : (\n <span class=\"kui-toolbar-text__text\">{text}</span>\n )}\n </span>\n );\n}\n"]}
|