@object-ui/react 16.0.0 → 17.1.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 +786 -0
- package/README.md +65 -0
- package/dist/SchemaRenderer.js +12 -2
- package/dist/context/InlineEditContext.d.ts +21 -0
- package/dist/context/InlineEditContext.js +7 -2
- package/dist/context/NotificationContext.d.ts +245 -17
- package/dist/context/NotificationContext.js +222 -20
- package/dist/context/SchemaRendererContext.d.ts +10 -1
- package/dist/context/SchemaRendererContext.js +7 -2
- package/dist/context/ThemeContext.js +4 -8
- package/dist/hooks/index.d.ts +2 -1
- package/dist/hooks/index.js +3 -1
- package/dist/hooks/useAnimation.d.ts +40 -4
- package/dist/hooks/useAnimation.js +41 -10
- package/dist/hooks/useCapabilityGate.d.ts +16 -0
- package/dist/hooks/useCapabilityGate.js +60 -0
- package/dist/hooks/useClientNotifications.d.ts +0 -6
- package/dist/hooks/useClientNotifications.js +7 -53
- package/dist/hooks/useETagCache.js +4 -0
- package/dist/hooks/useExpression.d.ts +8 -2
- package/dist/hooks/useExpression.js +9 -1
- package/dist/hooks/useFilterScope.d.ts +30 -0
- package/dist/hooks/useFilterScope.js +55 -0
- package/dist/hooks/useNavigationOverlay.d.ts +11 -0
- package/dist/hooks/useNavigationOverlay.js +35 -1
- package/dist/hooks/usePageVariables.js +23 -11
- package/dist/hooks/useRecordSearch.js +100 -5
- package/dist/hooks/useViewData.js +10 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/spec-bridge/SpecBridge.d.ts +0 -4
- package/dist/spec-bridge/SpecBridge.js +0 -12
- package/dist/spec-bridge/bridges/list-view.js +5 -11
- package/dist/spec-bridge/index.d.ts +0 -2
- package/dist/spec-bridge/index.js +0 -2
- package/dist/utils/error-message.d.ts +52 -0
- package/dist/utils/error-message.js +138 -0
- package/package.json +7 -7
- package/dist/hooks/useColumnSummary.d.ts +0 -38
- package/dist/hooks/useColumnSummary.js +0 -54
- package/dist/spec-bridge/bridges/dashboard.d.ts +0 -81
- package/dist/spec-bridge/bridges/dashboard.js +0 -121
- package/dist/spec-bridge/bridges/page.d.ts +0 -58
- package/dist/spec-bridge/bridges/page.js +0 -101
package/README.md
CHANGED
|
@@ -81,6 +81,30 @@ function App() {
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
### SchemaRendererProvider
|
|
85
|
+
|
|
86
|
+
Injects the host's data source (and optional capabilities) into every renderer
|
|
87
|
+
below it:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { SchemaRendererProvider } from '@object-ui/react'
|
|
91
|
+
|
|
92
|
+
<SchemaRendererProvider
|
|
93
|
+
dataSource={adapter}
|
|
94
|
+
// Optional: host-authenticated fetch used by `provider: 'api'` view data
|
|
95
|
+
// sources, so custom endpoints carry the same credentials (Authorization,
|
|
96
|
+
// tenant, locale headers) as the native data channel. When omitted,
|
|
97
|
+
// ApiDataSource falls back to the bare global fetch (cookies only).
|
|
98
|
+
apiFetch={authenticatedFetch}
|
|
99
|
+
>
|
|
100
|
+
<SchemaRenderer schema={schema} />
|
|
101
|
+
</SchemaRendererProvider>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Nested providers inherit `apiFetch` from their parent when they don't supply
|
|
105
|
+
their own, so re-wrapped subtrees (embedded pages, preview surfaces) keep the
|
|
106
|
+
host's authentication.
|
|
107
|
+
|
|
84
108
|
## Hooks
|
|
85
109
|
|
|
86
110
|
### useSchemaContext
|
|
@@ -158,6 +182,47 @@ The `previewMode` object contains:
|
|
|
158
182
|
| `expiresInSeconds` | `number` | `0` | Session duration (0 = no expiry) |
|
|
159
183
|
| `bannerMessage` | `string` | — | UI banner message |
|
|
160
184
|
|
|
185
|
+
### useNotifications / useNotificationsByPresentation
|
|
186
|
+
|
|
187
|
+
`NotificationProvider` implements the spec `NotificationSchema`. A notification's
|
|
188
|
+
`severity` picks its icon and tone; its `displayType` picks the **surface** that
|
|
189
|
+
renders it — and each of the five spec types has a distinct one:
|
|
190
|
+
|
|
191
|
+
| `displayType` | Presentation | Rendered by | Auto-dismiss |
|
|
192
|
+
| --- | --- | --- | --- |
|
|
193
|
+
| `toast` | transient overlay | the `onToast` delegate | yes |
|
|
194
|
+
| `snackbar` | bottom-anchored bar, one at a time, one action | `<NotificationSnackbar />` | yes |
|
|
195
|
+
| `banner` | page-width strip in the content flow | `<NotificationBanners />` | no |
|
|
196
|
+
| `alert` | blocking acknowledgement dialog (FIFO) | `<NotificationAlerts />` | no |
|
|
197
|
+
| `inline` | in place, at the raising surface | `<NotificationInline />` | no |
|
|
198
|
+
|
|
199
|
+
The surface components ship in `@object-ui/components`; mount them where they
|
|
200
|
+
belong (a banner is in flow, an inline notification sits next to its raiser).
|
|
201
|
+
`onToast` receives **only** `toast` items — it used to receive all five, which is
|
|
202
|
+
why every type looked like a toast.
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
const { notify } = useNotifications()
|
|
206
|
+
|
|
207
|
+
notify({ title: 'Saved', severity: 'success' }) // toast (spec default)
|
|
208
|
+
notify({ title: 'Viewing a draft', severity: 'warning', displayType: 'banner' })
|
|
209
|
+
notify({ title: 'Fix 2 fields', severity: 'error', displayType: 'inline', scope: 'contact-form' })
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
A surface component subscribes with `useNotificationsByPresentation(type, scope?)`,
|
|
213
|
+
which also registers the surface — raising a `banner` with no banner surface
|
|
214
|
+
mounted warns in dev instead of vanishing.
|
|
215
|
+
|
|
216
|
+
`config` is the spec `NotificationConfigSchema` (`defaultPosition`,
|
|
217
|
+
`defaultDuration`, `maxVisible`, `stackDirection`, `pauseOnHover`); the legacy
|
|
218
|
+
`position` / `stacking` spellings still resolve through
|
|
219
|
+
`resolveNotificationConfig`. Three helpers apply it so every surface agrees:
|
|
220
|
+
`resolveNotificationPosition` (a declared position always wins; nothing declared
|
|
221
|
+
leaves the surface on its own anchor), `visibleNotificationStack` (`maxVisible` +
|
|
222
|
+
`stackDirection`), and the context's `pauseAutoDismiss` / `resumeAutoDismiss`
|
|
223
|
+
(`pauseOnHover`). See the
|
|
224
|
+
[notifications guide](https://objectui.org/docs/guide/notifications).
|
|
225
|
+
|
|
161
226
|
## API Reference
|
|
162
227
|
|
|
163
228
|
See [full documentation](https://objectui.org/api/react) for detailed API reference.
|
package/dist/SchemaRenderer.js
CHANGED
|
@@ -136,9 +136,19 @@ export class SchemaErrorBoundary extends Component {
|
|
|
136
136
|
return this.props.children;
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Shared "no data source" fallback. MUST be a module constant: it feeds the
|
|
141
|
+
* `evaluatedSchema` memo below, and a fresh `{}` per render defeats that memo
|
|
142
|
+
* for every SchemaRenderer without a provider above it — re-cloning the schema
|
|
143
|
+
* and re-running the ExpressionEvaluator on each render, and handing a new
|
|
144
|
+
* schema identity to children that memoise on it. For a `kind:'react'` page
|
|
145
|
+
* that identity IS the compile key, so the page was silently remounted and its
|
|
146
|
+
* `useState` wiped (objectui#2954's latent hazard, made real by this line).
|
|
147
|
+
*/
|
|
148
|
+
const NO_DATA_SOURCE = {};
|
|
139
149
|
export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
140
150
|
const context = useContext(SchemaRendererContext);
|
|
141
|
-
const dataSource = context?.dataSource ||
|
|
151
|
+
const dataSource = context?.dataSource || NO_DATA_SOURCE;
|
|
142
152
|
// Ambient host scope (user / app / features), fed by app-shell's
|
|
143
153
|
// ExpressionProvider. Threaded into `visible`/expression evaluation so
|
|
144
154
|
// component predicates can gate on the signed-in user & deployment flags.
|
|
@@ -233,7 +243,7 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
233
243
|
if (newSchema.visibleOn !== undefined) {
|
|
234
244
|
return !evaluator.evaluateCondition(newSchema.visibleOn);
|
|
235
245
|
}
|
|
236
|
-
// @deprecated ADR-0089 → `visibleWhen` (was
|
|
246
|
+
// @deprecated ADR-0089 → `visibleWhen` (was PageNodeSchema.visibility,
|
|
237
247
|
// an ExpressionInput) — show-when-truthy, same semantics as `visibleOn`.
|
|
238
248
|
if (newSchema.visibility !== undefined) {
|
|
239
249
|
return !evaluator.evaluateCondition(newSchema.visibility);
|
|
@@ -43,6 +43,21 @@ export interface InlineEditContextValue {
|
|
|
43
43
|
* renderer DataSource-agnostic. Defaults to `false`.
|
|
44
44
|
*/
|
|
45
45
|
locked: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Whether an approval is in flight on this record, INDEPENDENT of whether it
|
|
48
|
+
* locks the record (objectui#2902). An approval node may declare
|
|
49
|
+
* `lockRecord: false`, in which case the backend accepts edits while the node
|
|
50
|
+
* waits — `approvalPending` is true and `locked` is false.
|
|
51
|
+
*
|
|
52
|
+
* Kept separate because the two drive different affordances: the recall
|
|
53
|
+
* button and the "an approval is running" status belong to `approvalPending`,
|
|
54
|
+
* while only `locked` may suppress editing. Consumers that conflate them
|
|
55
|
+
* either hide recall on an editable record or claim a lock that isn't there.
|
|
56
|
+
*
|
|
57
|
+
* `locked` implies an approval is pending, so hosts that thread only `locked`
|
|
58
|
+
* still get a coherent band. Defaults to `false`.
|
|
59
|
+
*/
|
|
60
|
+
approvalPending: boolean;
|
|
46
61
|
/**
|
|
47
62
|
* Human-readable reason for the approval lock, surfaced as the band's
|
|
48
63
|
* tooltip. Optional — consumers fall back to their own localized default
|
|
@@ -88,6 +103,12 @@ export interface InlineEditProviderProps {
|
|
|
88
103
|
* from a record field the backend may not materialize. Defaults to `false`.
|
|
89
104
|
*/
|
|
90
105
|
locked?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Whether an approval is pending on the record, whether or not it locks it
|
|
108
|
+
* (objectui#2902). Omitted ⇒ falls back to `locked`, which keeps hosts that
|
|
109
|
+
* only know about the lock rendering exactly as before.
|
|
110
|
+
*/
|
|
111
|
+
approvalPending?: boolean;
|
|
91
112
|
/** Optional human-readable lock reason, surfaced as the band tooltip. */
|
|
92
113
|
lockedReason?: string;
|
|
93
114
|
children: React.ReactNode;
|
|
@@ -24,7 +24,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
24
24
|
*/
|
|
25
25
|
import React from 'react';
|
|
26
26
|
const InlineEditContext = React.createContext(null);
|
|
27
|
-
export const InlineEditProvider = ({ canEdit = true, locked = false, lockedReason, children, }) => {
|
|
27
|
+
export const InlineEditProvider = ({ canEdit = true, locked = false, approvalPending, lockedReason, children, }) => {
|
|
28
|
+
// A locked record always has an approval running; the converse is what
|
|
29
|
+
// #2902 added. Defaulting this way means an un-migrated host keeps its
|
|
30
|
+
// previous behavior with no code change.
|
|
31
|
+
const pending = approvalPending ?? locked;
|
|
28
32
|
const [editing, setEditing] = React.useState(false);
|
|
29
33
|
const [draft, setDraft] = React.useState({});
|
|
30
34
|
const [autoFocusField, setAutoFocusField] = React.useState(null);
|
|
@@ -57,6 +61,7 @@ export const InlineEditProvider = ({ canEdit = true, locked = false, lockedReaso
|
|
|
57
61
|
editing,
|
|
58
62
|
canEdit,
|
|
59
63
|
locked,
|
|
64
|
+
approvalPending: pending,
|
|
60
65
|
lockedReason,
|
|
61
66
|
draft,
|
|
62
67
|
autoFocusField,
|
|
@@ -68,7 +73,7 @@ export const InlineEditProvider = ({ canEdit = true, locked = false, lockedReaso
|
|
|
68
73
|
reset: teardown,
|
|
69
74
|
setSaving,
|
|
70
75
|
setError,
|
|
71
|
-
}), [editing, canEdit, locked, lockedReason, draft, autoFocusField, saving, error, enter, setField, teardown]);
|
|
76
|
+
}), [editing, canEdit, locked, pending, lockedReason, draft, autoFocusField, saving, error, enter, setField, teardown]);
|
|
72
77
|
return _jsx(InlineEditContext.Provider, { value: value, children: children });
|
|
73
78
|
};
|
|
74
79
|
/**
|
|
@@ -15,15 +15,100 @@
|
|
|
15
15
|
import React from 'react';
|
|
16
16
|
/** Notification severity levels aligned with NotificationSeveritySchema */
|
|
17
17
|
export type NotificationSeverityLevel = 'info' | 'success' | 'warning' | 'error';
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Notification presentation — the spec `NotificationTypeSchema`
|
|
20
|
+
* (`ui/notification.zod.ts`: toast / snackbar / banner / alert / inline).
|
|
21
|
+
* Every member has a DISTINCT presentation; see {@link NOTIFICATION_PRESENTATIONS}.
|
|
22
|
+
*/
|
|
23
|
+
export type NotificationPresentation = 'toast' | 'snackbar' | 'banner' | 'alert' | 'inline';
|
|
24
|
+
/**
|
|
25
|
+
* Notification display type as AUTHORED. Identical to
|
|
26
|
+
* {@link NotificationPresentation} plus the deprecated renderer-local `modal`
|
|
27
|
+
* spelling, which stored items may still carry (#2942) and which resolves to
|
|
28
|
+
* `alert`. Use {@link resolveNotificationPresentation} to get the presentation.
|
|
29
|
+
*/
|
|
30
|
+
export type NotificationDisplayType = NotificationPresentation
|
|
31
|
+
/** @deprecated renderer dialect — never in the spec; presented as `alert` */
|
|
32
|
+
| 'modal';
|
|
33
|
+
/**
|
|
34
|
+
* Where a presentation is rendered.
|
|
35
|
+
*
|
|
36
|
+
* - `delegate` — handed to the provider's `onToast` callback; the host owns the
|
|
37
|
+
* overlay (sonner in the console). Historically EVERY type took this path,
|
|
38
|
+
* which is why a `banner` surfaced as a toast (#3014).
|
|
39
|
+
* - everything else names a surface component that subscribes to the context
|
|
40
|
+
* and renders the items in place. `@object-ui/components` ships one per
|
|
41
|
+
* surface (`NotificationSnackbar` / `NotificationBanners` /
|
|
42
|
+
* `NotificationAlerts` / `NotificationInline`).
|
|
43
|
+
*/
|
|
44
|
+
export type NotificationSurface = 'delegate' | 'snackbar' | 'banner' | 'alert' | 'inline';
|
|
45
|
+
export interface NotificationPresentationBehavior {
|
|
46
|
+
/** The surface responsible for rendering this presentation. */
|
|
47
|
+
surface: NotificationSurface;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the presentation auto-dismisses when the raiser declares no
|
|
50
|
+
* `duration`. A toast/snackbar is transient by definition; a banner is a
|
|
51
|
+
* persistent strip and an alert is a blocking acknowledgement, so neither may
|
|
52
|
+
* evaporate on the shared 5s timer. An explicit `duration` always wins.
|
|
53
|
+
*/
|
|
54
|
+
transient: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The presentation table — one entry per spec `NotificationTypeSchema` member.
|
|
58
|
+
*
|
|
59
|
+
* Typed as `Record<NotificationPresentation, …>`, so adding a member to the
|
|
60
|
+
* spec enum fails type-check here until its presentation is decided, rather
|
|
61
|
+
* than silently falling back to a toast.
|
|
62
|
+
*/
|
|
63
|
+
export declare const NOTIFICATION_PRESENTATIONS: Record<NotificationPresentation, NotificationPresentationBehavior>;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve an authored `displayType` to the presentation that renders it:
|
|
66
|
+
* the spec default (`toast`) when absent, and `alert` for the deprecated
|
|
67
|
+
* `modal` dialect.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveNotificationPresentation(displayType?: NotificationDisplayType): NotificationPresentation;
|
|
70
|
+
/**
|
|
71
|
+
* Notification position — the spec `NotificationPositionSchema`
|
|
72
|
+
* (underscore spellings). The hyphen forms are this context's historical
|
|
73
|
+
* dialect, kept accepted for stored items.
|
|
74
|
+
*/
|
|
75
|
+
export type NotificationPositionValue = 'top_left' | 'top_center' | 'top_right' | 'bottom_left' | 'bottom_center' | 'bottom_right'
|
|
76
|
+
/** @deprecated legacy spelling — use `top_left` */
|
|
77
|
+
| 'top-left'
|
|
78
|
+
/** @deprecated legacy spelling — use `top_center` */
|
|
79
|
+
| 'top-center'
|
|
80
|
+
/** @deprecated legacy spelling — use `top_right` */
|
|
81
|
+
| 'top-right'
|
|
82
|
+
/** @deprecated legacy spelling — use `bottom_left` */
|
|
83
|
+
| 'bottom-left'
|
|
84
|
+
/** @deprecated legacy spelling — use `bottom_center` */
|
|
85
|
+
| 'bottom-center'
|
|
86
|
+
/** @deprecated legacy spelling — use `bottom_right` */
|
|
87
|
+
| 'bottom-right';
|
|
88
|
+
/**
|
|
89
|
+
* The spec vocabularies this context implements — exported for the parity
|
|
90
|
+
* tests (#2942), which fail the moment `NotificationTypeSchema` /
|
|
91
|
+
* `NotificationPositionSchema` and these sets drift in either direction.
|
|
92
|
+
*/
|
|
93
|
+
export declare const SUPPORTED_NOTIFICATION_DISPLAY_TYPES: ReadonlySet<string>;
|
|
94
|
+
export declare const SUPPORTED_NOTIFICATION_POSITIONS: ReadonlySet<string>;
|
|
95
|
+
/**
|
|
96
|
+
* Action button variant — the spec `NotificationActionSchema.variant`
|
|
97
|
+
* (`primary | secondary | link`, default `primary`).
|
|
98
|
+
*
|
|
99
|
+
* This used to be the shadcn Button vocabulary (`default | destructive |
|
|
100
|
+
* outline`) — a renderer-local fork of a spec enum, i.e. a second de-facto
|
|
101
|
+
* contract. Surfaces map these three onto their own button styling instead.
|
|
102
|
+
*/
|
|
103
|
+
export type NotificationActionVariant = 'primary' | 'secondary' | 'link';
|
|
104
|
+
/** The spec vocabulary this context implements — exported for the parity test. */
|
|
105
|
+
export declare const SUPPORTED_NOTIFICATION_ACTION_VARIANTS: ReadonlySet<string>;
|
|
22
106
|
/** Action button on a notification */
|
|
23
107
|
export interface NotificationActionButton {
|
|
24
108
|
label: string;
|
|
25
109
|
onClick: () => void;
|
|
26
|
-
|
|
110
|
+
/** Spec `NotificationActionSchema.variant`. Default: `primary`. */
|
|
111
|
+
variant?: NotificationActionVariant;
|
|
27
112
|
}
|
|
28
113
|
/** A single notification item */
|
|
29
114
|
export interface NotificationItem {
|
|
@@ -31,10 +116,40 @@ export interface NotificationItem {
|
|
|
31
116
|
title: string;
|
|
32
117
|
message?: string;
|
|
33
118
|
severity: NotificationSeverityLevel;
|
|
119
|
+
/**
|
|
120
|
+
* The presentation the raiser asked for. Materialized by `notify()` — stored
|
|
121
|
+
* items always carry a resolved {@link NotificationPresentation}, never
|
|
122
|
+
* `modal` and never `undefined`.
|
|
123
|
+
*/
|
|
34
124
|
displayType?: NotificationDisplayType;
|
|
35
125
|
actions?: NotificationActionButton[];
|
|
36
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Duration in ms (0 = persistent). Defaults to the configured duration for
|
|
128
|
+
* TRANSIENT presentations (toast / snackbar) and to persistent for the rest —
|
|
129
|
+
* a banner that evaporates after 5s is not a banner.
|
|
130
|
+
*/
|
|
37
131
|
duration?: number;
|
|
132
|
+
/**
|
|
133
|
+
* Whether the surface offers a dismiss control (spec `dismissible`, default
|
|
134
|
+
* `true`). Only meaningful for the persistent presentations — a transient
|
|
135
|
+
* one dismisses itself.
|
|
136
|
+
*/
|
|
137
|
+
dismissible?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* `inline` only — names the surface that raised it, so the matching
|
|
140
|
+
* `<NotificationInline scope="…" />` renders it in place instead of every
|
|
141
|
+
* inline outlet on the page showing every inline notification. Renderer-local
|
|
142
|
+
* routing metadata, not a spec field: the spec describes what a notification
|
|
143
|
+
* IS, not which React subtree hosts it.
|
|
144
|
+
*/
|
|
145
|
+
scope?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Spec `NotificationSchema.position` — "Override default position", falling
|
|
148
|
+
* back to `config.defaultPosition`. Honored by the FLOATING presentations
|
|
149
|
+
* (toast, snackbar); `banner` / `inline` / `alert` are anchored by what they
|
|
150
|
+
* are (content top / in place / centered modal) and ignore it.
|
|
151
|
+
*/
|
|
152
|
+
position?: NotificationPositionValue;
|
|
38
153
|
/** Whether the notification has been read */
|
|
39
154
|
read?: boolean;
|
|
40
155
|
/** Timestamp */
|
|
@@ -42,23 +157,75 @@ export interface NotificationItem {
|
|
|
42
157
|
/** Optional icon name (lucide icon) */
|
|
43
158
|
icon?: string;
|
|
44
159
|
}
|
|
45
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* Configuration for the notification system — the spec
|
|
162
|
+
* `NotificationConfigSchema` (`defaultPosition` / `defaultDuration` /
|
|
163
|
+
* `maxVisible` / `stackDirection` / `pauseOnHover`).
|
|
164
|
+
*
|
|
165
|
+
* The field names used to fork from it (`position`, and a renderer-local
|
|
166
|
+
* `stacking` boolean with no spec counterpart), and only `defaultDuration` was
|
|
167
|
+
* ever read — the other three were stored and ignored. The legacy names are
|
|
168
|
+
* still accepted; see {@link resolveNotificationConfig}.
|
|
169
|
+
*/
|
|
46
170
|
export interface NotificationSystemConfig {
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Default screen position for the FLOATING presentations (toast, snackbar).
|
|
173
|
+
* A notification's own `position` overrides it.
|
|
174
|
+
*
|
|
175
|
+
* Deliberately undefined by default: "the host didn't say" has to be
|
|
176
|
+
* representable, otherwise a fabricated default silently competes with the
|
|
177
|
+
* host's own toast chrome (which also serves non-notification `toast.*`
|
|
178
|
+
* calls). Declared wins; undeclared defers to the surface's own anchor.
|
|
179
|
+
*/
|
|
180
|
+
defaultPosition?: NotificationPositionValue;
|
|
181
|
+
/** Default duration in ms for the transient presentations. */
|
|
50
182
|
defaultDuration?: number;
|
|
51
|
-
/** Maximum number of
|
|
183
|
+
/** Maximum number of notifications a stacking surface renders at once. */
|
|
52
184
|
maxVisible?: number;
|
|
53
|
-
/**
|
|
185
|
+
/** Which way a stack grows: `down` puts the newest below (spec default). */
|
|
186
|
+
stackDirection?: 'up' | 'down';
|
|
187
|
+
/** Pause a transient notification's auto-dismiss timer while hovered. */
|
|
188
|
+
pauseOnHover?: boolean;
|
|
189
|
+
/** @deprecated renderer dialect — use `defaultPosition` */
|
|
190
|
+
position?: NotificationPositionValue;
|
|
191
|
+
/**
|
|
192
|
+
* @deprecated renderer dialect with no spec counterpart. Read as "show only
|
|
193
|
+
* the newest" (`maxVisible: 1`) when false, so it is honored rather than
|
|
194
|
+
* fossilized; declare `maxVisible` instead.
|
|
195
|
+
*/
|
|
54
196
|
stacking?: boolean;
|
|
55
197
|
}
|
|
198
|
+
/** Config with the legacy spellings folded in and the spec defaults applied. */
|
|
199
|
+
export interface ResolvedNotificationConfig {
|
|
200
|
+
defaultPosition?: NotificationPositionValue;
|
|
201
|
+
defaultDuration: number;
|
|
202
|
+
maxVisible: number;
|
|
203
|
+
stackDirection: 'up' | 'down';
|
|
204
|
+
pauseOnHover: boolean;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Fold the deprecated spellings into the spec ones and apply the spec defaults
|
|
208
|
+
* (`NotificationConfigSchema`: 5000ms / 5 visible / stack down / pause on
|
|
209
|
+
* hover). `defaultPosition` gets no fabricated default — see the field docs.
|
|
210
|
+
*/
|
|
211
|
+
export declare function resolveNotificationConfig(config?: NotificationSystemConfig): ResolvedNotificationConfig;
|
|
56
212
|
export interface NotificationProviderProps {
|
|
57
213
|
children: React.ReactNode;
|
|
58
214
|
/** System configuration */
|
|
59
215
|
config?: NotificationSystemConfig;
|
|
60
|
-
/**
|
|
61
|
-
|
|
216
|
+
/**
|
|
217
|
+
* External toast handler (e.g. Sonner) — the `toast` presentation's surface.
|
|
218
|
+
*
|
|
219
|
+
* Called ONLY for `displayType: 'toast'`. It used to receive every
|
|
220
|
+
* notification, which is why the other four spec types all surfaced as
|
|
221
|
+
* toasts (#3014); they now render through their own surface components,
|
|
222
|
+
* which subscribe via {@link useNotificationsByPresentation}.
|
|
223
|
+
*
|
|
224
|
+
* The resolved config comes with it so the delegate can honor the parts of
|
|
225
|
+
* the contract only it can apply — `defaultPosition` above all, since the
|
|
226
|
+
* toast overlay is the host's.
|
|
227
|
+
*/
|
|
228
|
+
onToast?: (notification: NotificationItem, config: ResolvedNotificationConfig) => void;
|
|
62
229
|
}
|
|
63
230
|
interface NotificationContextValue {
|
|
64
231
|
/** All notifications (including history) */
|
|
@@ -83,19 +250,44 @@ interface NotificationContextValue {
|
|
|
83
250
|
dismiss: (id: string) => void;
|
|
84
251
|
/** Clear all notifications */
|
|
85
252
|
clearAll: () => void;
|
|
86
|
-
/** System configuration */
|
|
87
|
-
config:
|
|
253
|
+
/** System configuration, spec-normalized (see {@link resolveNotificationConfig}). */
|
|
254
|
+
config: ResolvedNotificationConfig;
|
|
255
|
+
/**
|
|
256
|
+
* Hold a transient notification's auto-dismiss timer (spec `pauseOnHover`).
|
|
257
|
+
* A surface calls this on pointer-enter; no-op when the notification is
|
|
258
|
+
* persistent or the config opted out.
|
|
259
|
+
*/
|
|
260
|
+
pauseAutoDismiss: (id: string) => void;
|
|
261
|
+
/** Resume a held timer with the time it had left. */
|
|
262
|
+
resumeAutoDismiss: (id: string) => void;
|
|
263
|
+
/**
|
|
264
|
+
* Announce that a surface is mounted and will render `surface` items.
|
|
265
|
+
* Returns the unregister callback. Surfaces call this from an effect; the
|
|
266
|
+
* provider uses it only to warn (dev builds) when a notification is raised
|
|
267
|
+
* with nothing on screen able to present it.
|
|
268
|
+
*/
|
|
269
|
+
registerSurface: (surface: NotificationSurface) => () => void;
|
|
88
270
|
}
|
|
89
271
|
/**
|
|
90
272
|
* NotificationProvider — Provides a spec-driven notification system.
|
|
91
273
|
*
|
|
274
|
+
* Each spec display type is presented by its own surface. `toast` is delegated
|
|
275
|
+
* to the host (`onToast`); the other four are rendered by surface components
|
|
276
|
+
* that subscribe to this context — mount them where they belong:
|
|
277
|
+
* `NotificationSnackbar` / `NotificationAlerts` anywhere inside the provider,
|
|
278
|
+
* `NotificationBanners` at the top of the content area, and
|
|
279
|
+
* `NotificationInline` in the surface that raises the notification.
|
|
280
|
+
*
|
|
92
281
|
* @example
|
|
93
282
|
* ```tsx
|
|
94
283
|
* <NotificationProvider
|
|
95
|
-
* config={{ position: '
|
|
284
|
+
* config={{ position: 'top_right', defaultDuration: 5000, maxVisible: 5 }}
|
|
96
285
|
* onToast={(n) => toast[n.severity](n.title, { description: n.message })}
|
|
97
286
|
* >
|
|
287
|
+
* <NotificationBanners />
|
|
98
288
|
* <App />
|
|
289
|
+
* <NotificationSnackbar />
|
|
290
|
+
* <NotificationAlerts />
|
|
99
291
|
* </NotificationProvider>
|
|
100
292
|
* ```
|
|
101
293
|
*/
|
|
@@ -110,4 +302,40 @@ export declare function useNotifications(): NotificationContextValue;
|
|
|
110
302
|
* Hook to check if a NotificationProvider is available.
|
|
111
303
|
*/
|
|
112
304
|
export declare function useHasNotificationProvider(): boolean;
|
|
305
|
+
/**
|
|
306
|
+
* Apply the config's stack rules to a surface's items: keep the newest
|
|
307
|
+
* `maxVisible`, then order them so the stack GROWS in `stackDirection`
|
|
308
|
+
* (`down`, the spec default, puts the newest below the older ones).
|
|
309
|
+
*
|
|
310
|
+
* Shared by the stacking surfaces (banner, inline) so `maxVisible` means one
|
|
311
|
+
* thing. It used to mean nothing at all — the config carried it and no surface
|
|
312
|
+
* read it, while `NotificationBanners` capped at a hard-coded 3 of its own.
|
|
313
|
+
*
|
|
314
|
+
* @param items the surface's notifications, newest-first
|
|
315
|
+
*/
|
|
316
|
+
export declare function visibleNotificationStack(items: NotificationItem[], config: Pick<ResolvedNotificationConfig, 'maxVisible' | 'stackDirection'>): NotificationItem[];
|
|
317
|
+
/**
|
|
318
|
+
* The screen position a FLOATING notification (toast, snackbar) should take:
|
|
319
|
+
* its own `position`, else the configured `defaultPosition`, else `undefined`.
|
|
320
|
+
*
|
|
321
|
+
* `undefined` is a meaningful answer, not a missing one — it means "nothing was
|
|
322
|
+
* declared", and the surface should keep its own anchor (a snackbar's bottom
|
|
323
|
+
* edge) or defer to the host's toast chrome, rather than being moved by a
|
|
324
|
+
* default nobody asked for. Always returns the spec (underscore) spelling.
|
|
325
|
+
*/
|
|
326
|
+
export declare function resolveNotificationPosition(notification: Pick<NotificationItem, 'position'>, config?: Pick<ResolvedNotificationConfig, 'defaultPosition'>): NotificationPositionValue | undefined;
|
|
327
|
+
/**
|
|
328
|
+
* Subscribe to the live notifications for ONE presentation — the contract a
|
|
329
|
+
* surface component implements (`@object-ui/components` ships one per spec
|
|
330
|
+
* type). Registering also tells the provider the surface exists, so raising a
|
|
331
|
+
* `banner` with no banner surface mounted warns in dev instead of vanishing.
|
|
332
|
+
*
|
|
333
|
+
* Items come back newest-first, matching `notifications`. A surface that wants
|
|
334
|
+
* FIFO order (an `alert` queue) reverses them itself.
|
|
335
|
+
*
|
|
336
|
+
* @param presentation the spec display type this surface renders
|
|
337
|
+
* @param scope optional `inline` routing key — when set, only items raised with
|
|
338
|
+
* the same `scope` are returned; when omitted, only unscoped items are.
|
|
339
|
+
*/
|
|
340
|
+
export declare function useNotificationsByPresentation(presentation: NotificationPresentation, scope?: string): NotificationItem[];
|
|
113
341
|
export {};
|