@object-ui/layout 17.6.0 → 18.0.0
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/CHANGELOG.md +2006 -0
- package/README.md +35 -6
- package/dist/AppSchemaRenderer.d.ts +5 -7
- package/dist/AppShell.d.ts +28 -1
- package/dist/NavigationRenderer.d.ts +205 -37
- package/dist/PageHeader.d.ts +7 -6
- package/dist/ResponsiveGrid.d.ts +53 -18
- package/dist/index.js +821 -723
- package/dist/index.umd.cjs +1 -1
- package/dist/navOrder.d.ts +18 -0
- package/package.json +10 -12
package/README.md
CHANGED
|
@@ -23,10 +23,9 @@ pnpm add @object-ui/layout
|
|
|
23
23
|
|
|
24
24
|
## Registration
|
|
25
25
|
|
|
26
|
-
Importing this package registers its component keys (`page
|
|
27
|
-
`
|
|
28
|
-
|
|
29
|
-
is enough:
|
|
26
|
+
Importing this package registers its component keys (`page:card`,
|
|
27
|
+
`app-schema-renderer`) on the `ComponentRegistry` as a module load side effect,
|
|
28
|
+
so the side-effect-only import is enough:
|
|
30
29
|
|
|
31
30
|
```typescript
|
|
32
31
|
import '@object-ui/layout';
|
|
@@ -39,6 +38,16 @@ used to say `"sideEffects": false`, and a bundler honouring it dropped the
|
|
|
39
38
|
registration silently). `registerLayout()` is also exported for hosts that
|
|
40
39
|
prefer to register explicitly.
|
|
41
40
|
|
|
41
|
+
The `page-header` key is retired (objectui#10859): author `page:header`, which
|
|
42
|
+
`@object-ui/components` registers, with the secondary line as `subtitle`. `PageHeader`
|
|
43
|
+
stays an export for JSX composition.
|
|
44
|
+
|
|
45
|
+
The `responsive-grid` and `navigation-renderer` keys are retired too (objectui#11441).
|
|
46
|
+
A breakpoint grid is the `grid` node with a breakpoint `columns` object
|
|
47
|
+
(`{ "type": "grid", "columns": { "xs": 1, "md": 2 }, "gap": 4 }`), and navigation is
|
|
48
|
+
application metadata: the app's `navigation` items, drawn by the shell.
|
|
49
|
+
`ResponsiveGrid` and `NavigationRenderer` stay exports for JSX composition.
|
|
50
|
+
|
|
42
51
|
## Components
|
|
43
52
|
|
|
44
53
|
### AppShell
|
|
@@ -199,10 +208,22 @@ composed in JSX rather than authored as a JSON node. Full guide:
|
|
|
199
208
|
The layout components are designed to work seamlessly with React Router:
|
|
200
209
|
|
|
201
210
|
```typescript
|
|
202
|
-
import {
|
|
211
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
203
212
|
import { AppShell, SidebarNav } from '@object-ui/layout';
|
|
204
213
|
import { Home, Users } from 'lucide-react';
|
|
205
214
|
|
|
215
|
+
// `react-router-dom` is a PEER dependency (see Installation above): your app
|
|
216
|
+
// installs it, this package does not. These three stand in for what you would
|
|
217
|
+
// import from it, so the composition below is still checked against the shipped
|
|
218
|
+
// `@object-ui/layout` types.
|
|
219
|
+
declare const BrowserRouter: ComponentType<{ children?: ReactNode }>;
|
|
220
|
+
declare const Routes: ComponentType<{ children?: ReactNode }>;
|
|
221
|
+
declare const Route: ComponentType<{ path: string; element: ReactNode }>;
|
|
222
|
+
|
|
223
|
+
// Your own page components, one per route.
|
|
224
|
+
declare const Dashboard: ComponentType;
|
|
225
|
+
declare const UsersPage: ComponentType;
|
|
226
|
+
|
|
206
227
|
function App() {
|
|
207
228
|
return (
|
|
208
229
|
<BrowserRouter>
|
|
@@ -219,7 +240,7 @@ function App() {
|
|
|
219
240
|
>
|
|
220
241
|
<Routes>
|
|
221
242
|
<Route path="/" element={<Dashboard />} />
|
|
222
|
-
<Route path="/users" element={<
|
|
243
|
+
<Route path="/users" element={<UsersPage />} />
|
|
223
244
|
</Routes>
|
|
224
245
|
</AppShell>
|
|
225
246
|
</BrowserRouter>
|
|
@@ -227,6 +248,12 @@ function App() {
|
|
|
227
248
|
}
|
|
228
249
|
```
|
|
229
250
|
|
|
251
|
+
A route's `element` takes **your page component**, never the `lucide-react` icon
|
|
252
|
+
of the same name. This example used to route `/users` to `Users` — the icon it
|
|
253
|
+
imports for the sidebar — so the page rendered a 24-pixel glyph where its content
|
|
254
|
+
belonged. Nothing catches that for you: an icon is a valid component, so the
|
|
255
|
+
route type-checks either way, which is why the two are named apart here.
|
|
256
|
+
|
|
230
257
|
## Customization
|
|
231
258
|
|
|
232
259
|
`AppShell` takes a single `className`, and it lands on the `<main>` content
|
|
@@ -235,6 +262,8 @@ per-slot `headerClassName` / `sidebarClassName` — the navbar and the sidebar a
|
|
|
235
262
|
nodes **you** build, so style them where you build them:
|
|
236
263
|
|
|
237
264
|
```typescript
|
|
265
|
+
import { AppShell } from '@object-ui/layout';
|
|
266
|
+
|
|
238
267
|
<AppShell
|
|
239
268
|
className="bg-gray-50"
|
|
240
269
|
navbar={<div className="border-b px-4">My App</div>}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { AppComponentSchema, NavigationItem } from '@object-ui/types';
|
|
3
|
-
import { VisibilityEvaluator, PermissionChecker, CapabilityChecker } from './NavigationRenderer.js';
|
|
3
|
+
import { VisibilityEvaluator, PermissionChecker, CapabilityChecker, DocTargetChecker } from './NavigationRenderer.js';
|
|
4
4
|
/**
|
|
5
5
|
* Mobile navigation display mode — the two modes this renderer implements.
|
|
6
6
|
*
|
|
@@ -34,6 +34,8 @@ export interface AppSchemaRendererProps {
|
|
|
34
34
|
checkPermission?: PermissionChecker;
|
|
35
35
|
/** Optional capability checker passed to NavigationRenderer (gates `requiresObject` / `requiresService`) */
|
|
36
36
|
checkCapability?: CapabilityChecker;
|
|
37
|
+
/** Optional member-readability checker for `doc` entries, passed to NavigationRenderer (objectui#10188) */
|
|
38
|
+
checkDocTarget?: DocTargetChecker;
|
|
37
39
|
/** Called when an action-type navigation item is clicked */
|
|
38
40
|
onAction?: (item: NavigationItem) => void;
|
|
39
41
|
/** Slot: top navbar content (rendered beside the sidebar trigger) */
|
|
@@ -56,16 +58,12 @@ export interface AppSchemaRendererProps {
|
|
|
56
58
|
enablePinning?: boolean;
|
|
57
59
|
/** Called when a navigation item is pinned or unpinned */
|
|
58
60
|
onPinToggle?: (itemId: string, pinned: boolean, item?: NavigationItem, basePath?: string) => void;
|
|
59
|
-
/** Enable drag-to-reorder for navigation items */
|
|
60
|
-
enableReorder?: boolean;
|
|
61
|
-
/** Called when navigation items are reordered via drag */
|
|
62
|
-
onReorder?: (reorderedItems: NavigationItem[]) => void;
|
|
63
61
|
}
|
|
64
62
|
/**
|
|
65
63
|
* Renders a complete application shell from an `AppSchema` JSON document.
|
|
66
64
|
*
|
|
67
65
|
* Responsibilities:
|
|
68
|
-
* - Reads `name`, `title`, `description`, `logo`, `favicon` for branding
|
|
66
|
+
* - Reads `name`, `title`, `description`, `icon`, `branding.logo`, `branding.favicon` for branding
|
|
69
67
|
* - Renders sidebar navigation from `navigation` or `areas[].navigation`
|
|
70
68
|
* - Area switcher when multiple areas are VISIBLE — area visibility is
|
|
71
69
|
* derived from the items inside, not authored (objectui#3311): an area
|
|
@@ -87,4 +85,4 @@ export interface AppSchemaRendererProps {
|
|
|
87
85
|
* </AppSchemaRenderer>
|
|
88
86
|
* ```
|
|
89
87
|
*/
|
|
90
|
-
export declare function AppSchemaRenderer({ schema, basePath, mobileNavMode, evaluateVisibility: evalVisProp, checkPermission: checkPermProp, checkCapability: checkCapProp, onAction, navbar, sidebarHeader, sidebarFooter, sidebarExtra, children, className, defaultOpen, enableSearch, enablePinning, onPinToggle,
|
|
88
|
+
export declare function AppSchemaRenderer({ schema, basePath, mobileNavMode, evaluateVisibility: evalVisProp, checkPermission: checkPermProp, checkCapability: checkCapProp, checkDocTarget, onAction, navbar, sidebarHeader, sidebarFooter, sidebarExtra, children, className, defaultOpen, enableSearch, enablePinning, onPinToggle, }: AppSchemaRendererProps): React.JSX.Element;
|
package/dist/AppShell.d.ts
CHANGED
|
@@ -10,7 +10,11 @@ export interface AppShellBranding {
|
|
|
10
10
|
accentColor?: string;
|
|
11
11
|
/** Favicon URL — replaces the <link rel="icon"> href */
|
|
12
12
|
favicon?: string;
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Assigned to `document.title` as given. It is the whole title, not a suffix:
|
|
15
|
+
* the caller composes the string it wants (the console passes
|
|
16
|
+
* `"App label — Product name"`).
|
|
17
|
+
*/
|
|
14
18
|
title?: string;
|
|
15
19
|
}
|
|
16
20
|
export interface AppShellProps {
|
|
@@ -32,6 +36,29 @@ export interface AppShellProps {
|
|
|
32
36
|
/**
|
|
33
37
|
* Apply branding CSS custom properties to the document root.
|
|
34
38
|
* This is extracted as a standalone hook so it can be re-used independently.
|
|
39
|
+
*
|
|
40
|
+
* It is also the ONE writer of `document.title` for as long as a shell is
|
|
41
|
+
* mounted (objectui#8637). Ownership is scoped, not permanent: the hook
|
|
42
|
+
* captures whatever the tab already said, writes `title` over it, and puts the
|
|
43
|
+
* captured string back when the shell unmounts or `title` changes. So a host
|
|
44
|
+
* that mounts a shell for part of its route tree gets the specific title while
|
|
45
|
+
* it is there and its previous title back when it leaves, without a second
|
|
46
|
+
* writer keyed on navigation — which is what the console used to do, and what
|
|
47
|
+
* reverted the composed title to the bare product name on every in-app
|
|
48
|
+
* navigation.
|
|
49
|
+
*
|
|
50
|
+
* ⚠️ The capture is a read of the live `document.title`, so a host that lets
|
|
51
|
+
* something else write the tab title WHILE a shell is mounted hands this hook a
|
|
52
|
+
* value it did not put there, and that value is what comes back on unmount.
|
|
53
|
+
* Nesting a second title-writing surface inside a mounted shell is the shape to
|
|
54
|
+
* avoid; the console's own auth surfaces sit outside the shell for this reason.
|
|
55
|
+
*
|
|
56
|
+
* The favicon is scoped the same way (objectui#10040): when `branding.favicon`
|
|
57
|
+
* is set and the page has an icon link, the hook captures that link's `href`
|
|
58
|
+
* attribute, writes the branded URL, and puts the captured attribute back (or
|
|
59
|
+
* removes it, if there was none) when the shell unmounts or the effect re-runs
|
|
60
|
+
* on changed inputs. The same replay caveat applies — an icon written by something else
|
|
61
|
+
* while the shell is mounted is overwritten by the restore.
|
|
35
62
|
*/
|
|
36
63
|
export declare function useAppShellBranding(branding?: AppShellBranding, title?: string): void;
|
|
37
64
|
export declare function AppShell({ sidebar, navbar, children, className, defaultOpen, branding, rightRail, }: AppShellProps): React.JSX.Element;
|
|
@@ -4,8 +4,20 @@ import { NavigationItem, KeyedI18nLabel } from '@object-ui/types';
|
|
|
4
4
|
* Callback to evaluate a visibility expression.
|
|
5
5
|
* Return `true` if the item should be visible.
|
|
6
6
|
* When not provided, all items default to visible.
|
|
7
|
+
*
|
|
8
|
+
* The parameter is the item's own `visible` type, so the spec's
|
|
9
|
+
* `{ dialect, source }` envelope reaches the evaluator as authored
|
|
10
|
+
* (objectui#13075).
|
|
11
|
+
*
|
|
12
|
+
* `entry` is the navigation entry whose `visible` this is (objectui#13029).
|
|
13
|
+
* The item guard always passes it. An entry may declare the spec's API
|
|
14
|
+
* `dataSource` (`{ provider: 'api', read: { url }, path? }`), whose selected
|
|
15
|
+
* value its `visible` reads as `data`; reading that binding is the host's job,
|
|
16
|
+
* so an evaluator that answers such an entry reads it through `entry`. The
|
|
17
|
+
* default evaluator reads no binding, so it hides an entry that declares one:
|
|
18
|
+
* a value nobody read is unknown, and an unknown navigation gate is closed.
|
|
7
19
|
*/
|
|
8
|
-
export type VisibilityEvaluator = (expression:
|
|
20
|
+
export type VisibilityEvaluator = (expression: NavigationItem['visible'], entry?: NavigationItem) => boolean;
|
|
9
21
|
/**
|
|
10
22
|
* Callback to check whether the current user satisfies **all** of the
|
|
11
23
|
* given permission strings. Each string is opaque — the consumer decides
|
|
@@ -27,6 +39,77 @@ export type PermissionChecker = (permissions: string[]) => boolean;
|
|
|
27
39
|
* runtime capability probe yet.
|
|
28
40
|
*/
|
|
29
41
|
export type CapabilityChecker = (kind: 'object' | 'service', name: string) => boolean;
|
|
42
|
+
/** What a `type: 'doc'` navigation entry opens: a book, a page, or that page in that book. */
|
|
43
|
+
export interface DocNavTarget {
|
|
44
|
+
book?: string;
|
|
45
|
+
doc?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Answers whether the signed-in member may read what a `type: 'doc'` entry
|
|
49
|
+
* opens (objectui#10188). `false` hides the entry.
|
|
50
|
+
*
|
|
51
|
+
* The server is the enforcer (ADR-0046 §6.7): its app read already drops a
|
|
52
|
+
* `doc` entry the caller may not read (objectstack#19790), and this checker is
|
|
53
|
+
* the renderer's defence in depth behind it (that ruling's point 2). This layer
|
|
54
|
+
* holds no audience rules, so the host answers from the member's own doc / book
|
|
55
|
+
* reads and never re-derives an audience. Kept apart from
|
|
56
|
+
* {@link CapabilityChecker} on purpose: that one asks whether the RUNTIME has a
|
|
57
|
+
* target, this one whether the MEMBER may read it.
|
|
58
|
+
*
|
|
59
|
+
* When not provided, `doc` entries pass — the server's answer stands.
|
|
60
|
+
*/
|
|
61
|
+
export type DocTargetChecker = (target: DocNavTarget) => boolean;
|
|
62
|
+
/**
|
|
63
|
+
* What an UNLABELLED navigation entry inherits its text from (objectui#9868).
|
|
64
|
+
*
|
|
65
|
+
* `@objectstack/spec` 17.5.0 made a nav entry's `label` optional with a
|
|
66
|
+
* declared semantic: absent ⇒ the entry shows, at RENDER time, the CURRENT label
|
|
67
|
+
* of what it opens — the view's label when it names a view and that view is
|
|
68
|
+
* labelled, else the object's / dashboard's label. These are the three targets
|
|
69
|
+
* that sentence names; `resolveNavItemLabel` walks them in that order and asks a
|
|
70
|
+
* {@link NavTargetLabelResolver} about each one.
|
|
71
|
+
*/
|
|
72
|
+
export type NavLabelTarget = {
|
|
73
|
+
kind: 'view';
|
|
74
|
+
objectName: string;
|
|
75
|
+
viewName: string;
|
|
76
|
+
} | {
|
|
77
|
+
kind: 'object';
|
|
78
|
+
objectName: string;
|
|
79
|
+
} | {
|
|
80
|
+
kind: 'dashboard';
|
|
81
|
+
dashboardName: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Answers the CURRENT display label of a nav target from the host's metadata —
|
|
85
|
+
* the object schema's `label`, the named view's `label`, the dashboard's
|
|
86
|
+
* `label` — or `undefined` when the target carries none (or is not loaded).
|
|
87
|
+
*
|
|
88
|
+
* This layer holds no metadata, so the host supplies it: the console shell
|
|
89
|
+
* reads the metadata cache it already loads (`useNavTargetLabel` in
|
|
90
|
+
* `@object-ui/app-shell`). Because it is asked on every render and nothing is
|
|
91
|
+
* stored, a renamed target shows its new name on the next render. Without it,
|
|
92
|
+
* an unlabelled entry falls through to its target's machine name.
|
|
93
|
+
*/
|
|
94
|
+
export type NavTargetLabelResolver = (target: NavLabelTarget) => string | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* What a host's per-entry control is told about the row it sits in
|
|
97
|
+
* (objectui#12427, {@link NavigationRendererProps.renderItemAction}).
|
|
98
|
+
*/
|
|
99
|
+
export interface NavItemActionContext {
|
|
100
|
+
/**
|
|
101
|
+
* The row's href, as its link renders it ({@link resolveHref}). Absent for an
|
|
102
|
+
* `action` entry, which runs something and navigates nowhere, and for an
|
|
103
|
+
* entry whose target resolves to no address (`#`).
|
|
104
|
+
*/
|
|
105
|
+
href?: string;
|
|
106
|
+
/** `href` leaves the console: the row opens it in a new tab. */
|
|
107
|
+
external: boolean;
|
|
108
|
+
/** The text the row shows ({@link resolveNavItemLabel}). */
|
|
109
|
+
label: string;
|
|
110
|
+
/** The renderer's `basePath`. */
|
|
111
|
+
basePath: string;
|
|
112
|
+
}
|
|
30
113
|
export interface NavigationRendererProps {
|
|
31
114
|
/** Navigation items to render */
|
|
32
115
|
items: NavigationItem[];
|
|
@@ -41,6 +124,8 @@ export interface NavigationRendererProps {
|
|
|
41
124
|
checkPermission?: PermissionChecker;
|
|
42
125
|
/** Optional runtime-capability checker for `requiresObject` / `requiresService` */
|
|
43
126
|
checkCapability?: CapabilityChecker;
|
|
127
|
+
/** Optional member-readability checker for `doc` entries — see {@link DocTargetChecker} */
|
|
128
|
+
checkDocTarget?: DocTargetChecker;
|
|
44
129
|
/**
|
|
45
130
|
* Called when an `action`-type item is clicked.
|
|
46
131
|
*
|
|
@@ -67,43 +152,76 @@ export interface NavigationRendererProps {
|
|
|
67
152
|
* working unchanged.
|
|
68
153
|
*/
|
|
69
154
|
onPinToggle?: (itemId: string, pinned: boolean, item?: NavigationItem, basePath?: string) => void;
|
|
70
|
-
/** Enable drag-to-reorder for navigation items */
|
|
71
|
-
enableReorder?: boolean;
|
|
72
|
-
/** Called when navigation items are reordered via drag */
|
|
73
|
-
onReorder?: (reorderedItems: NavigationItem[]) => void;
|
|
74
155
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
156
|
+
* The host's control for one entry (objectui#12427), drawn on every leaf and
|
|
157
|
+
* `action` row where the pin action sits, IN PLACE of it: a row this fills
|
|
158
|
+
* draws no pin action, so the host's control owns pinning (the console's ⋯
|
|
159
|
+
* menu offers Add to / Remove from favorites, through the same pin store).
|
|
160
|
+
* The control is a `SidebarMenuAction` (or renders one), so it sits where the
|
|
161
|
+
* pin action sat and follows the row's hover.
|
|
162
|
+
*
|
|
163
|
+
* Drawn whatever `enablePinning` says, which keeps deciding the Favorites
|
|
164
|
+
* section alone: a host that wants no control on a screen (a phone) does not
|
|
165
|
+
* pass this there. Omitted ⇒ the pin action under `enablePinning` +
|
|
166
|
+
* `onPinToggle`, as before. Groups and separators get none.
|
|
167
|
+
*/
|
|
168
|
+
renderItemAction?: (item: NavigationItem, context: NavItemActionContext) => React.ReactNode;
|
|
169
|
+
/**
|
|
170
|
+
* Rows the host appends to the Favorites section, after the pinned entries
|
|
171
|
+
* (objectui#12427): favorites that are not navigation entries, such as a
|
|
172
|
+
* record. Each row is a `SidebarMenuItem`; the renderer lists them in a
|
|
173
|
+
* `SidebarMenu` of their own, outside the pinned entries' drag order. The
|
|
174
|
+
* section renders when it has a pinned entry (under `enablePinning`) or one
|
|
175
|
+
* of these rows; `null`, `false` and an empty array are no rows. Omitted ⇒
|
|
176
|
+
* the section holds pinned entries only, as before.
|
|
177
|
+
*/
|
|
178
|
+
extraFavorites?: React.ReactNode;
|
|
179
|
+
/**
|
|
180
|
+
* The ids of the pinned entries in the user's order (objectui#12059). The
|
|
181
|
+
* pinned section draws its entries in this order; a pinned entry it does not
|
|
182
|
+
* name follows, in menu order. Omitted ⇒ the pinned section follows the menu.
|
|
80
183
|
*/
|
|
81
|
-
|
|
184
|
+
pinnedOrder?: string[];
|
|
82
185
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
186
|
+
* Makes the pinned section reorderable (objectui#12059): a pinned row is
|
|
187
|
+
* dragged by the row itself, with no grip at rest (`cursor: grab`, and an
|
|
188
|
+
* insertion line while dragging), or from the keyboard with dnd-kit's
|
|
189
|
+
* keyboard sensor (Space picks the focused row up, the arrow keys move it,
|
|
190
|
+
* Space drops it, Escape cancels). Called on a drop with the ids of the rows
|
|
191
|
+
* the section draws, in their new order. Only the pinned section reorders;
|
|
192
|
+
* the menu itself never does.
|
|
88
193
|
*/
|
|
89
|
-
|
|
194
|
+
onPinnedReorder?: (pinnedIds: string[]) => void;
|
|
90
195
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* `
|
|
94
|
-
* `
|
|
95
|
-
*
|
|
196
|
+
* The viewer's locale (a BCP-47 tag such as `zh-CN`), for a PRESENT label
|
|
197
|
+
* written as an inline locale map (`{ en: 'Accounts', 'zh-CN': '客户' }`,
|
|
198
|
+
* the spec's `I18nLabel`): the map renders this locale's entry, through the
|
|
199
|
+
* spec's own `resolveI18nLabel`. When the map has no entry for this locale,
|
|
200
|
+
* the fallback order is that resolver's and is not restated here
|
|
201
|
+
* (objectui#11299).
|
|
96
202
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
203
|
+
* Injected, like `t`: this layer carries no i18n dependency, so the host that
|
|
204
|
+
* knows the language passes it (the console passes its active UI language,
|
|
205
|
+
* the same value its sidebar resolves area labels in). Omitted ⇒ the
|
|
206
|
+
* resolver's documented no-locale default, `en`. A plain-string label and an
|
|
207
|
+
* absent one are unaffected.
|
|
101
208
|
*/
|
|
102
|
-
|
|
209
|
+
locale?: string;
|
|
103
210
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
211
|
+
* Resolver for the text an entry with NO `label` inherits (objectui#9868):
|
|
212
|
+
* the current label of its view / object / dashboard, read from the host's
|
|
213
|
+
* metadata at render time. Consulted ONLY for an absent `label` — an authored
|
|
214
|
+
* label is never replaced by the target's metadata label, not even one
|
|
215
|
+
* spelled like the target's machine name (the ruling refused that sentinel).
|
|
216
|
+
* See {@link NavTargetLabelResolver} and {@link resolveNavItemLabel}.
|
|
217
|
+
*/
|
|
218
|
+
resolveTargetLabel?: NavTargetLabelResolver;
|
|
219
|
+
/**
|
|
220
|
+
* Optional i18n translation function for resolving KEYED label objects
|
|
221
|
+
* (`{ key, defaultValue }`, see {@link resolveLabel}). When provided, labels
|
|
222
|
+
* are translated through i18next; otherwise falls back to `defaultValue`.
|
|
223
|
+
* An inline locale map (`{ en, 'zh-CN' }`) is not keyed and never reaches
|
|
224
|
+
* it — {@link resolveNavItemLabel} reads a map itself, in {@link locale}.
|
|
107
225
|
*/
|
|
108
226
|
t?: (key: string, options?: any) => string;
|
|
109
227
|
/**
|
|
@@ -137,6 +255,45 @@ export declare function resolveIcon(name?: string): React.ComponentType<any>;
|
|
|
137
255
|
* wrongly for each other's input, silently (objectui#4167).
|
|
138
256
|
*/
|
|
139
257
|
export declare function resolveLabel(label: string | KeyedI18nLabel, t?: (key: string, options?: any) => string): string;
|
|
258
|
+
/**
|
|
259
|
+
* Resolve a navigation item's display text, in the spec's one order
|
|
260
|
+
* (`@objectstack/spec` `BaseNavItemSchema.label`, objectstack#20849):
|
|
261
|
+
*
|
|
262
|
+
* 1. The id-keyed bundle entry `apps.APP.navigation.ID.label`. That rung runs
|
|
263
|
+
* UPSTREAM of this function, at the server-side `/meta` boundary:
|
|
264
|
+
* `translateApp` in `@objectstack/spec` (`src/system/i18n-resolver.ts`)
|
|
265
|
+
* rewrites a node's `label` by its `id` before the metadata reaches this
|
|
266
|
+
* renderer. App-navigation localization has that one owner — localize nav
|
|
267
|
+
* labels there, never here.
|
|
268
|
+
* 2. A PRESENT label, as authored ({@link presentNavItemLabel}): an inline
|
|
269
|
+
* locale map reads the entry for `locale` (the viewer's), and a string
|
|
270
|
+
* renders verbatim.
|
|
271
|
+
* 3. An ABSENT label inherits its target's CURRENT label (objectui#9868 —
|
|
272
|
+
* `@objectstack/spec` 17.5.0 made it optional, cloud#2021 letter-A ruling),
|
|
273
|
+
* resolved here at render time and never written back — see
|
|
274
|
+
* {@link inheritedNavItemLabel} for the ladder. That arm is keyed on
|
|
275
|
+
* ABSENCE alone, so an authored label is never swapped for the target's
|
|
276
|
+
* metadata label, however it is spelled.
|
|
277
|
+
*
|
|
278
|
+
* ⛔ Nothing here matches a label's text against a name (objectui#11201,
|
|
279
|
+
* ruling B). A present label equal to its target's machine name (`account`)
|
|
280
|
+
* renders `account` in every locale: text cannot tell a deliberate `account`
|
|
281
|
+
* from a machine-written one, so translation is keyed on identity (rung 1) or
|
|
282
|
+
* comes through inheritance (rung 3). The three convention resolvers that
|
|
283
|
+
* the retired rule consulted, and the three arguments that carried them, are
|
|
284
|
+
* gone (objectui#11299).
|
|
285
|
+
*
|
|
286
|
+
* EXPORTED since `969ba84f4`, for the same reason {@link resolveHref} is: a
|
|
287
|
+
* second surface now renders the same `NavigationItem[]`. `nav:menu` is the
|
|
288
|
+
* app's navigation tree as PAGE CONTENT (`app-shell/src/views/nav-menu-renderer.tsx`),
|
|
289
|
+
* and it cannot mount `NavigationRenderer` itself — that renders through
|
|
290
|
+
* `SidebarMenuButton`, whose `useSidebar()` throws outside the shell's
|
|
291
|
+
* `SidebarProvider`. Re-deriving the label rules there would put one nav entry
|
|
292
|
+
* under two names on two surfaces of one app, which is exactly the drift the
|
|
293
|
+
* "single source of truth" note on `resolveHref` exists to prevent. Nothing
|
|
294
|
+
* about the behaviour changed with the keyword.
|
|
295
|
+
*/
|
|
296
|
+
export declare function resolveNavItemLabel(item: NavigationItem, t?: (key: string, options?: any) => string, targetLabel?: NavTargetLabelResolver, locale?: string): string;
|
|
140
297
|
/** Guard callbacks for {@link hasVisibleNavigationItems}. */
|
|
141
298
|
export interface NavigationVisibilityOptions {
|
|
142
299
|
/** Evaluator for item `visible` expressions. Defaults to always-visible. */
|
|
@@ -145,6 +302,8 @@ export interface NavigationVisibilityOptions {
|
|
|
145
302
|
checkPermission?: PermissionChecker;
|
|
146
303
|
/** Checker for `requiresObject` / `requiresService`. Defaults to pass. */
|
|
147
304
|
checkCapability?: CapabilityChecker;
|
|
305
|
+
/** Checker for what a `doc` entry opens ({@link DocTargetChecker}). Defaults to pass. */
|
|
306
|
+
checkDocTarget?: DocTargetChecker;
|
|
148
307
|
/**
|
|
149
308
|
* Whether the host wires an `onAction` dispatcher. Without one, `action`
|
|
150
309
|
* items are not rendered at all (framework#4509 — a nav entry that looks
|
|
@@ -240,21 +399,30 @@ export declare function resolveActiveNavItem(items: NavigationItem[], pathname:
|
|
|
240
399
|
/**
|
|
241
400
|
* Recursively filter navigation items by search query (case-insensitive label match).
|
|
242
401
|
* Groups are kept if any child matches, with non-matching children pruned.
|
|
402
|
+
*
|
|
403
|
+
* Matches the text the row SHOWS: `labelOf` defaults to
|
|
404
|
+
* {@link resolveNavItemLabel} with no resolvers, and `NavigationRenderer` passes
|
|
405
|
+
* its own resolvers and locale so an unlabelled entry is found by the label it
|
|
406
|
+
* inherits (objectui#9868), not by a `label` it does not have, and a map-valued
|
|
407
|
+
* one by its entry in the viewer's locale (objectui#11299).
|
|
243
408
|
*/
|
|
244
|
-
export declare function filterNavigationItems(items: NavigationItem[], query: string): NavigationItem[];
|
|
409
|
+
export declare function filterNavigationItems(items: NavigationItem[], query: string, labelOf?: (item: NavigationItem) => string): NavigationItem[];
|
|
245
410
|
/**
|
|
246
411
|
* Renders a `NavigationItem[]` tree into Shadcn Sidebar components.
|
|
247
412
|
*
|
|
248
413
|
* Features:
|
|
249
|
-
* -
|
|
414
|
+
* - Every navigation item type + separators
|
|
250
415
|
* - Nested collapsible groups
|
|
251
416
|
* - Badge indicators
|
|
252
417
|
* - Visibility expression evaluation
|
|
253
418
|
* - RBAC permission guards
|
|
254
419
|
* - Active-route highlighting
|
|
255
420
|
* - Search filtering across navigation tree
|
|
256
|
-
* - Pin/favorite items with dedicated "Favorites" section
|
|
257
|
-
*
|
|
421
|
+
* - Pin/favorite items with dedicated "Favorites" section, in the user's
|
|
422
|
+
* order and reorderable by dragging a pinned row (objectui#12059)
|
|
423
|
+
* - A host control on every entry in place of the pin action
|
|
424
|
+
* (`renderItemAction`), and host rows after the pinned ones in the Favorites
|
|
425
|
+
* section (`extraFavorites`) (objectui#12427)
|
|
258
426
|
*
|
|
259
427
|
* @example
|
|
260
428
|
* ```tsx
|
|
@@ -266,9 +434,9 @@ export declare function filterNavigationItems(items: NavigationItem[], query: st
|
|
|
266
434
|
* searchQuery={searchTerm}
|
|
267
435
|
* enablePinning
|
|
268
436
|
* onPinToggle={(id, pinned) => updatePin(id, pinned)}
|
|
269
|
-
*
|
|
270
|
-
*
|
|
437
|
+
* pinnedOrder={pinnedIds}
|
|
438
|
+
* onPinnedReorder={(ids) => savePinOrder(ids)}
|
|
271
439
|
* />
|
|
272
440
|
* ```
|
|
273
441
|
*/
|
|
274
|
-
export declare function NavigationRenderer({ items, basePath, evaluateVisibility: evalVis, checkPermission: checkPerm, checkCapability: checkCap, onAction, searchQuery, enablePinning, onPinToggle,
|
|
442
|
+
export declare function NavigationRenderer({ items, basePath, evaluateVisibility: evalVis, checkPermission: checkPerm, checkCapability: checkCap, checkDocTarget, onAction, searchQuery, enablePinning, onPinToggle, renderItemAction, extraFavorites, pinnedOrder, onPinnedReorder, resolveTargetLabel, locale, t: tProp, templateContext, }: NavigationRendererProps): React.JSX.Element;
|
package/dist/PageHeader.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ import { default as React } from 'react';
|
|
|
3
3
|
* Props of this package's `<PageHeader>` React component.
|
|
4
4
|
*
|
|
5
5
|
* Named `PageHeaderComponentProps`, not `PageHeaderProps` (objectui#3161,
|
|
6
|
-
* objectstack#4115 ledger batch 7)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* objectstack#4115 ledger batch 7). `@object-ui/app-shell` once used this
|
|
7
|
+
* name for its own console header (objectui#3169). That header is a different
|
|
8
|
+
* component, with rendered-node `title`, `subtitle` and `actions`, so since
|
|
9
|
+
* objectui#6349 (batch 8) it is `ConsolePageHeaderProps`, and this package is
|
|
10
|
+
* the one authority for this name.
|
|
10
11
|
*
|
|
11
12
|
* `@objectstack/spec/ui` owns `PageHeaderProps` for the AUTHORED `page:header`
|
|
12
13
|
* node: a zod object of `title: string`, `subtitle`, an icon NAME, `breadcrumb`
|
|
@@ -17,7 +18,7 @@ import { default as React } from 'react';
|
|
|
17
18
|
* prop rides along. Authored layer vs rendered layer, which is exactly the
|
|
18
19
|
* one-name-two-layers failure objectstack#4115 exists to end.
|
|
19
20
|
*
|
|
20
|
-
* Pinned by `__tests__/spec-symbol-batch7.test.
|
|
21
|
+
* Pinned by `__tests__/spec-symbol-batch7.test.ts`, which fails if the spec
|
|
21
22
|
* stops owning `PageHeaderProps` (then this can take the plain name back) or
|
|
22
23
|
* starts owning `PageHeaderComponentProps`.
|
|
23
24
|
*/
|
|
@@ -32,7 +33,7 @@ export interface PageHeaderComponentProps extends React.HTMLAttributes<HTMLDivEl
|
|
|
32
33
|
* was retired in objectui#3789, once the ADR-0087 D2 conversion
|
|
33
34
|
* `page-header-subtitle-alias` was measured to reach EVERY spec-valid header
|
|
34
35
|
* position on the load path (regions, slots, and containers nested to any
|
|
35
|
-
* depth — objectstack#6775 / #6776 / PR #7034). Stored metadata authored with
|
|
36
|
+
* depth — objectstack#6775 / objectstack-ai/objectstack#6776 / PR objectstack-ai/objectstack#7034). Stored metadata authored with
|
|
36
37
|
* `description` is rewritten to `subtitle` before it ever reaches a renderer,
|
|
37
38
|
* so the second line survives without this side holding a second dialect open.
|
|
38
39
|
*/
|
package/dist/ResponsiveGrid.d.ts
CHANGED
|
@@ -1,25 +1,59 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { BreakpointColumnMap, BreakpointOrderMap } from '@objectstack/spec/ui';
|
|
3
2
|
/**
|
|
4
|
-
* Breakpoint column map
|
|
5
|
-
* (
|
|
3
|
+
* Breakpoint column map — grid column counts per Tailwind-style breakpoint,
|
|
4
|
+
* owned by this package since objectui#7580 (maintainer ruling 2026-09-04,
|
|
5
|
+
* option A).
|
|
6
6
|
*
|
|
7
7
|
* These were hand copies until objectui#4167, each carrying the word "mirrors"
|
|
8
8
|
* — the doc-comment shape objectstack#4115 catalogues, where the claim is what
|
|
9
|
-
* the next agent reads as canonical and the code is what actually decides.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* than deleted
|
|
20
|
-
*
|
|
9
|
+
* the next agent reads as canonical and the code is what actually decides. They
|
|
10
|
+
* were then bound by reference to `@objectstack/spec/ui`, which was right for
|
|
11
|
+
* as long as the spec published them.
|
|
12
|
+
*
|
|
13
|
+
* objectstack#11027 retired the whole `ui/responsive` vocabulary upstream, so
|
|
14
|
+
* there is no longer anything to bind to — and, unlike the four types that left
|
|
15
|
+
* with the key, this one has a renderer that implements it:
|
|
16
|
+
* {@link resolveColumnClasses} applies it on `ResponsiveGrid`'s render path.
|
|
17
|
+
* The tombstone's own return condition — the vocabulary "returns if and when a
|
|
18
|
+
* renderer implements it" — is met here, which is why the type is re-homed
|
|
19
|
+
* rather than deleted.
|
|
20
|
+
*
|
|
21
|
+
* The node that once made it authorable from JSON is gone. The
|
|
22
|
+
* `responsive-grid` registration retired under objectui#11441 (the
|
|
23
|
+
* maintainer's ruling `5950208338`, letter B), which kept this declaration and
|
|
24
|
+
* named the `grid` node with a breakpoint `columns` object as the one
|
|
25
|
+
* authorable spelling. `ResponsiveGrid` is now a React-only export, and this
|
|
26
|
+
* map is the type of its `columns` prop.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ Members are the retired `BreakpointColumnMapSchema`'s, verbatim: six
|
|
29
|
+
* optional column counts keyed `xs`…`2xl`. The schema was `$strict`, so no
|
|
30
|
+
* index signature is restored here either — an unknown breakpoint key was a
|
|
31
|
+
* parse error upstream and stays a type error here.
|
|
32
|
+
*
|
|
33
|
+
* `BreakpointOrderMap` RETIRED with the key (same ruling, item 3) and is NOT
|
|
34
|
+
* re-homed. Its own comment in this file already recorded the reason: it had no
|
|
35
|
+
* read point in this package — `ResponsiveGrid` resolves columns only — and it
|
|
36
|
+
* was published solely because the spec's `ResponsiveConfigSchema` paired the
|
|
37
|
+
* two, so an author configuring `order` needed the type. That pairing is what
|
|
38
|
+
* objectstack#11027 deleted; with the schema gone there is no order vocabulary
|
|
39
|
+
* left for it to be the type OF, and re-homing it would have re-declared a key
|
|
40
|
+
* nothing reads on either side of the boundary — the declare-without-enforce
|
|
41
|
+
* shape ADR-0049 removes.
|
|
42
|
+
*
|
|
43
|
+
* ⏳ Interim, and it self-expires: the pin is still `@objectstack/spec` 17.2.0,
|
|
44
|
+
* which PRE-dates the retirement and still exports this name, so the collision
|
|
45
|
+
* is real today and carries a reasoned entry in
|
|
46
|
+
* `scripts/check-spec-symbol-derivation.mjs`. The guard's ratchet 3 fails an
|
|
47
|
+
* ALLOW entry that excuses nothing, so the pin bump is forced to delete it.
|
|
21
48
|
*/
|
|
22
|
-
export
|
|
49
|
+
export interface BreakpointColumnMap {
|
|
50
|
+
xs?: number;
|
|
51
|
+
sm?: number;
|
|
52
|
+
md?: number;
|
|
53
|
+
lg?: number;
|
|
54
|
+
xl?: number;
|
|
55
|
+
'2xl'?: number;
|
|
56
|
+
}
|
|
23
57
|
export interface ResponsiveGridProps {
|
|
24
58
|
/** Grid column map per breakpoint */
|
|
25
59
|
columns?: BreakpointColumnMap;
|
|
@@ -31,8 +65,9 @@ export interface ResponsiveGridProps {
|
|
|
31
65
|
children: React.ReactNode;
|
|
32
66
|
}
|
|
33
67
|
/**
|
|
34
|
-
* ResponsiveGrid — A layout component that consumes
|
|
35
|
-
*
|
|
68
|
+
* ResponsiveGrid — A layout component that consumes this package's own
|
|
69
|
+
* BreakpointColumnMap (the six keys xs, sm, md, lg, xl, '2xl') for
|
|
70
|
+
* responsive grid layouts.
|
|
36
71
|
*
|
|
37
72
|
* Uses pure Tailwind CSS classes for responsive behavior (no JS resize listeners).
|
|
38
73
|
*
|