@object-ui/react 6.2.2 → 7.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 +171 -0
- package/dist/SchemaRenderer.d.ts +1 -1
- package/dist/SchemaRenderer.js +46 -16
- package/dist/context/ActionContext.d.ts +4 -1
- package/dist/context/ActionContext.js +18 -3
- package/dist/context/AppShellContext.d.ts +10 -0
- package/dist/context/AppShellContext.js +1 -0
- package/dist/context/DrillNavigationContext.d.ts +39 -0
- package/dist/context/DrillNavigationContext.js +31 -0
- package/dist/context/PreviewModeContext.d.ts +29 -0
- package/dist/context/PreviewModeContext.js +27 -0
- package/dist/context/SchemaRendererContext.d.ts +1 -1
- package/dist/context/ThemeContext.d.ts +1 -1
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +2 -0
- package/dist/hooks/useActionEngine.js +46 -3
- package/dist/hooks/useActionRunner.d.ts +5 -1
- package/dist/hooks/useActionRunner.js +5 -1
- package/dist/hooks/useClientNotifications.js +20 -2
- package/dist/spec-bridge/bridges/list-view.d.ts +0 -1
- package/dist/spec-bridge/bridges/list-view.js +0 -7
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,176 @@
|
|
|
1
1
|
# @object-ui/react
|
|
2
2
|
|
|
3
|
+
## 7.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c12986e: Add resultDialog + target interpolation for one-shot action reveals
|
|
8
|
+
|
|
9
|
+
Some platform actions return values the user MUST copy now because the
|
|
10
|
+
server will not surface them again — 2FA TOTP URI + backup codes, freshly
|
|
11
|
+
minted OAuth client_secret, regenerated recovery codes. Previously these
|
|
12
|
+
had to ship as bespoke pages in `apps/account` because actions only
|
|
13
|
+
emitted a fire-and-forget toast.
|
|
14
|
+
|
|
15
|
+
**`@object-ui/core` — ActionRunner**
|
|
16
|
+
|
|
17
|
+
- New `ActionDef.resultDialog: ResultDialogSpec` field. When set on a
|
|
18
|
+
successful action, the runner suppresses the `successMessage` toast and
|
|
19
|
+
awaits the registered `ResultDialogHandler` instead. Missing handler is
|
|
20
|
+
non-fatal (logs a warning); rejected handler is treated as acknowledged.
|
|
21
|
+
- New `setResultDialogHandler(handler)` setter.
|
|
22
|
+
- New types: `ResultDialogSpec`, `ResultDialogFieldSpec`,
|
|
23
|
+
`ResultDialogHandler`.
|
|
24
|
+
- `executeUrl` and `executeAPI` now run `${param.X}` and `${ctx.X}`
|
|
25
|
+
interpolation against `target` before fetching / navigating. Values are
|
|
26
|
+
`encodeURIComponent`'d, missing keys resolve to empty string. `ctx`
|
|
27
|
+
exposes `origin`, `user`, `org`, `recordId` by default; consumers can
|
|
28
|
+
inject more via `context.ctx`.
|
|
29
|
+
|
|
30
|
+
**`@object-ui/react`**
|
|
31
|
+
|
|
32
|
+
- `ActionProvider` and `useActionRunner` both gained an `onResultDialog`
|
|
33
|
+
option that wires straight through to the runner.
|
|
34
|
+
|
|
35
|
+
**`@object-ui/app-shell`**
|
|
36
|
+
|
|
37
|
+
- New `ActionResultDialog` component — promise-based, blocks click-outside
|
|
38
|
+
and Escape (the user MUST click acknowledge), renders five field
|
|
39
|
+
formats: `qrcode` (client-side via the `qrcode` package — never sent
|
|
40
|
+
off-device, so 2FA URIs stay secret), `code-list`, `secret`, `text`,
|
|
41
|
+
`json`. Falls back to `json` when a value's shape doesn't match its
|
|
42
|
+
declared format.
|
|
43
|
+
- `ObjectView` and `RecordDetailView` install the handler and mount the
|
|
44
|
+
dialog automatically, so any action with `resultDialog` declared in
|
|
45
|
+
metadata now works without code changes.
|
|
46
|
+
- New dependency: `qrcode@^1.5.x` for client-side QR rendering.
|
|
47
|
+
|
|
48
|
+
Pairs with the framework-side `Action.resultDialog` schema added in
|
|
49
|
+
`@objectstack/spec` and the `sys_two_factor` / `sys_oauth_application` /
|
|
50
|
+
`sys_account` updates in `@objectstack/platform-objects`.
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- 6cfa330: feat(dashboard): drill "Open in list" escape hatch + unify report drill
|
|
55
|
+
|
|
56
|
+
Adopts the mainstream BI peek-then-escalate drill model. Drill-through opens an
|
|
57
|
+
in-place drawer (keep context) and offers an "Open in list →" affordance to
|
|
58
|
+
escalate to the object's full list page (sort / bulk-select / export / shareable
|
|
59
|
+
URL) — the Looker / Power BI "see records → open in page" pattern.
|
|
60
|
+
|
|
61
|
+
- New `DrillNavigationContext` (`@object-ui/react`): the app shell provides
|
|
62
|
+
`openRecordList`; the renderer stays decoupled from console routing.
|
|
63
|
+
- The drill drawers (pivot / dataset / chart / KPI) render the escape hatch when
|
|
64
|
+
a host navigation handler is present, and hide it otherwise (self-contained
|
|
65
|
+
peek). `DashboardView` provides the handler via `useOpenRecordList`.
|
|
66
|
+
- `DrillDownConfig.target` gains `'navigate'` — skip the drawer and open the
|
|
67
|
+
list directly; degrades to `'drawer'` when no host handler is available.
|
|
68
|
+
- `ReportView` drill-through now opens the same in-place drawer (peek records →
|
|
69
|
+
click a row to open a record) instead of navigating away; the escape hatch
|
|
70
|
+
preserves the previous navigate-to-list behavior. Dashboard and report drill
|
|
71
|
+
are now unified.
|
|
72
|
+
- i18n: `dashboard.openInList` (en / zh).
|
|
73
|
+
|
|
74
|
+
- e95cc25: Fix the NavigationSyncEffect baseline race: lazily-loaded `page`/`dashboard` metadata (and the empty cache during `invalidate()` refetch) could seed a partial diff baseline, making platform `sys_` pages look "user added" — the effect then wrote them into every app's navigation, 403ing on ADR-0010 locked apps (red "Failed to update navigation" toasts) and polluting writable apps. The effect now diffs only while both types are `status === 'ready'` (new optional `MetadataContextValue.getTypeStatus`), never treats `sys_`-prefixed artifacts as user creations, and skips apps whose `_lock`/`protection.lock` is `full`/`no-overlay`.
|
|
75
|
+
- abe8ebc: Repoint `useClientNotifications` to the ADR-0030 `@objectstack/client` surface
|
|
76
|
+
|
|
77
|
+
The `useClientNotifications` bridge hook called `client.notifications.*` with
|
|
78
|
+
signatures that no longer exist on `@objectstack/client@7.x`:
|
|
79
|
+
|
|
80
|
+
- `registerDevice(token, platform)` → the SDK takes a single
|
|
81
|
+
`RegisterDeviceRequest` object (`{ token, platform }`).
|
|
82
|
+
- `markAsRead(id)` → there is no single-id method; the SDK exposes
|
|
83
|
+
`markRead(ids: string[])`. The hook keeps its friendly single-id API and
|
|
84
|
+
adapts to the batch call.
|
|
85
|
+
|
|
86
|
+
These helpers are the stable transport contract for ADR-0030 (Notification
|
|
87
|
+
Convergence): server-side they route to the L5 `sys_inbox_message`
|
|
88
|
+
materialization and the `sys_notification_receipt` read-state spine — the
|
|
89
|
+
re-modeled `sys_notification` L2 event no longer carries recipient/read
|
|
90
|
+
columns. (The Console bell itself reads the inbox + receipts directly via the
|
|
91
|
+
generic data API; see the `@object-ui/app-shell` bell cut-over.)
|
|
92
|
+
|
|
93
|
+
## Cut-over sequence (operational — run in this order)
|
|
94
|
+
|
|
95
|
+
The Console UI repoint must land together with the framework pipeline **and**
|
|
96
|
+
the data migration so the bell is never blank and read-state is never lost:
|
|
97
|
+
|
|
98
|
+
1. Deploy the framework ADR-0030 change (objects + `emit()` + producers). New
|
|
99
|
+
notifications now land in `sys_inbox_message` + `sys_notification_receipt`.
|
|
100
|
+
2. Run the data migration **once** to carry existing notifications across —
|
|
101
|
+
`migrateSysNotificationToEvent({ driver, data })` from
|
|
102
|
+
`@objectstack/metadata/migrations`. It splits each legacy `sys_notification`
|
|
103
|
+
inbox row into a `sys_inbox_message` + receipt, rewrites the row to the event
|
|
104
|
+
shape, and clears the legacy columns. It is **idempotent** and reports
|
|
105
|
+
`not_applicable` on fresh installs. This runs against the ObjectStack
|
|
106
|
+
**server/data engine** — it is not a Console (frontend) step.
|
|
107
|
+
3. Deploy the objectui repoint (this change + the `@object-ui/app-shell` bell
|
|
108
|
+
cut-over).
|
|
109
|
+
|
|
110
|
+
- 2270239: feat: scoped style-object rendering (ADR-0065)
|
|
111
|
+
|
|
112
|
+
A metadata node may carry `responsiveStyles` (per-breakpoint CSS-property maps);
|
|
113
|
+
`SchemaRenderer` compiles it to **id-scoped CSS** injected as a `<style>` tag and
|
|
114
|
+
appends a scope class to the node. Build-independent (arbitrary values + design
|
|
115
|
+
tokens pass through verbatim — no Tailwind JIT), collision-free (per-node scope,
|
|
116
|
+
unlayered so it beats base utilities), responsive-correct (model breakpoint maps
|
|
117
|
+
→ generated `@media`, never `md:` variant classes). Adds `compileScopedStyles`/
|
|
118
|
+
`scopeClassFor`/`hasResponsiveStyles` to `@object-ui/core` and an SDUI design-token
|
|
119
|
+
palette (`--space-*`, `--surface`, `--brand`, …) to the theme. Mirrors Builder.io.
|
|
120
|
+
|
|
121
|
+
- Updated dependencies [5976ba3]
|
|
122
|
+
- Updated dependencies [eaccefd]
|
|
123
|
+
- Updated dependencies [f7f325d]
|
|
124
|
+
- Updated dependencies [c12986e]
|
|
125
|
+
- Updated dependencies [71d7ce0]
|
|
126
|
+
- Updated dependencies [30ee761]
|
|
127
|
+
- Updated dependencies [053c948]
|
|
128
|
+
- Updated dependencies [b99d9bd]
|
|
129
|
+
- Updated dependencies [053c948]
|
|
130
|
+
- Updated dependencies [89e113c]
|
|
131
|
+
- Updated dependencies [ddbe4a2]
|
|
132
|
+
- Updated dependencies [2d47e94]
|
|
133
|
+
- Updated dependencies [9049bbe]
|
|
134
|
+
- Updated dependencies [77cc6bb]
|
|
135
|
+
- Updated dependencies [97c6831]
|
|
136
|
+
- Updated dependencies [cb2fdb1]
|
|
137
|
+
- Updated dependencies [a58c6b8]
|
|
138
|
+
- Updated dependencies [c3749eb]
|
|
139
|
+
- Updated dependencies [c09f44e]
|
|
140
|
+
- Updated dependencies [6cfa330]
|
|
141
|
+
- Updated dependencies [ad8ade6]
|
|
142
|
+
- Updated dependencies [d54346c]
|
|
143
|
+
- Updated dependencies [5332639]
|
|
144
|
+
- Updated dependencies [3870c20]
|
|
145
|
+
- Updated dependencies [b88c560]
|
|
146
|
+
- Updated dependencies [0ad72a6]
|
|
147
|
+
- Updated dependencies [3fa23a7]
|
|
148
|
+
- Updated dependencies [18d0339]
|
|
149
|
+
- Updated dependencies [59b6bbb]
|
|
150
|
+
- Updated dependencies [d16566f]
|
|
151
|
+
- Updated dependencies [1394e34]
|
|
152
|
+
- Updated dependencies [300d755]
|
|
153
|
+
- Updated dependencies [bd8b054]
|
|
154
|
+
- Updated dependencies [4eb9cb6]
|
|
155
|
+
- Updated dependencies [7c239fd]
|
|
156
|
+
- Updated dependencies [858ad94]
|
|
157
|
+
- Updated dependencies [2270239]
|
|
158
|
+
- Updated dependencies [2f31406]
|
|
159
|
+
- Updated dependencies [8d1195d]
|
|
160
|
+
- @object-ui/core@7.0.0
|
|
161
|
+
- @object-ui/data-objectstack@7.0.0
|
|
162
|
+
- @object-ui/i18n@7.0.0
|
|
163
|
+
- @object-ui/types@7.0.0
|
|
164
|
+
|
|
165
|
+
## 6.2.3
|
|
166
|
+
|
|
167
|
+
### Patch Changes
|
|
168
|
+
|
|
169
|
+
- @object-ui/types@6.2.3
|
|
170
|
+
- @object-ui/core@6.2.3
|
|
171
|
+
- @object-ui/i18n@6.2.3
|
|
172
|
+
- @object-ui/data-objectstack@6.2.3
|
|
173
|
+
|
|
3
174
|
## 6.2.2
|
|
4
175
|
|
|
5
176
|
### Patch Changes
|
package/dist/SchemaRenderer.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export declare class SchemaErrorBoundary extends Component<{
|
|
|
28
28
|
resetKey?: any;
|
|
29
29
|
}): void;
|
|
30
30
|
handleRetry: () => void;
|
|
31
|
-
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> |
|
|
31
|
+
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
|
|
32
32
|
}
|
|
33
33
|
export declare const SchemaRenderer: React.ForwardRefExoticComponent<Omit<{
|
|
34
34
|
schema: SchemaNode;
|
package/dist/SchemaRenderer.js
CHANGED
|
@@ -7,8 +7,9 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
9
|
import React, { forwardRef, useContext, useMemo, useEffect, useReducer, useState, Component } from 'react';
|
|
10
|
-
import { ComponentRegistry, ExpressionEvaluator, isObjectUIError, ERROR_CODES, debugLog, debugTime, debugTimeEnd, DebugCollector, validateSchema, } from '@object-ui/core';
|
|
10
|
+
import { ComponentRegistry, ExpressionEvaluator, isObjectUIError, ERROR_CODES, debugLog, debugTime, debugTimeEnd, DebugCollector, validateSchema, hasResponsiveStyles, scopeClassFor, compileScopedStyles, } from '@object-ui/core';
|
|
11
11
|
import { SchemaRendererContext } from './context/SchemaRendererContext';
|
|
12
|
+
import { usePredicateScope } from './hooks/useExpression';
|
|
12
13
|
import { resolveI18nLabel } from './utils/i18n';
|
|
13
14
|
/**
|
|
14
15
|
* Dev-mode schema validation.
|
|
@@ -137,6 +138,10 @@ export class SchemaErrorBoundary extends Component {
|
|
|
137
138
|
export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
138
139
|
const context = useContext(SchemaRendererContext);
|
|
139
140
|
const dataSource = context?.dataSource || {};
|
|
141
|
+
// Ambient host scope (user / app / features), fed by app-shell's
|
|
142
|
+
// ExpressionProvider. Threaded into `visible`/expression evaluation so
|
|
143
|
+
// component predicates can gate on the signed-in user & deployment flags.
|
|
144
|
+
const predicateScope = usePredicateScope();
|
|
140
145
|
// Re-render trigger when the global ComponentRegistry mutates (e.g. a
|
|
141
146
|
// lazy-loaded plugin finishes registering its components).
|
|
142
147
|
const [, forceUpdate] = useReducer((n) => n + 1, 0);
|
|
@@ -151,11 +156,20 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
151
156
|
return unsubscribe;
|
|
152
157
|
}, []);
|
|
153
158
|
const [lazyError, setLazyError] = useState(null);
|
|
159
|
+
// Stable fallback id for scoping a styled node that didn't declare an `id`.
|
|
160
|
+
const autoStyleId = React.useId();
|
|
154
161
|
// Evaluate schema expressions against the data source
|
|
155
162
|
const evaluatedSchema = useMemo(() => {
|
|
156
163
|
if (!schema || typeof schema === 'string')
|
|
157
164
|
return schema;
|
|
158
|
-
|
|
165
|
+
// `data` (record/datasource) plus the ambient host scope. `current_user`
|
|
166
|
+
// is aliased to `user` so both `user.email` and `current_user.email`
|
|
167
|
+
// resolve in component `visible`/`visibleOn` expressions.
|
|
168
|
+
const evaluator = new ExpressionEvaluator({
|
|
169
|
+
...predicateScope,
|
|
170
|
+
current_user: predicateScope?.user,
|
|
171
|
+
data: dataSource,
|
|
172
|
+
});
|
|
159
173
|
// Shallow copy
|
|
160
174
|
const newSchema = { ...schema };
|
|
161
175
|
// COMPAT: Hoist 'properties' up to schema level
|
|
@@ -226,7 +240,7 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
226
240
|
newSchema._disabled = true;
|
|
227
241
|
}
|
|
228
242
|
return newSchema;
|
|
229
|
-
}, [schema, dataSource]);
|
|
243
|
+
}, [schema, dataSource, predicateScope]);
|
|
230
244
|
if (!evaluatedSchema)
|
|
231
245
|
return null;
|
|
232
246
|
// Handle visibility: if evaluated schema is hidden, render nothing
|
|
@@ -263,7 +277,23 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
263
277
|
// Extract schema metadata properties that should NOT be passed as React props
|
|
264
278
|
const { type: _type, children: _children, body: _body, schema: _schema, visible: _visible, visibleOn: _visibleOn, hidden: _hidden, hiddenOn: _hiddenOn, disabled: _disabled, disabledOn: _disabledOn, _hidden: __hidden, // stripped: internal visibility flag
|
|
265
279
|
_disabled: __disabled, // stripped: internal disabled flag
|
|
280
|
+
responsiveStyles: _responsiveStyles, // stripped: compiled to scoped CSS, not a DOM prop
|
|
266
281
|
...componentProps } = evaluatedSchema;
|
|
282
|
+
// SDUI scoped styling (ADR-0065): a node's `responsiveStyles` compiles to
|
|
283
|
+
// id-scoped CSS injected as a <style> tag, and a scope class is appended to
|
|
284
|
+
// the node's className. Build-independent, collision-free, responsive-correct.
|
|
285
|
+
const hasScopedStyles = hasResponsiveStyles(_responsiveStyles);
|
|
286
|
+
const scopeClass = hasScopedStyles ? scopeClassFor(evaluatedSchema.id ?? autoStyleId) : '';
|
|
287
|
+
const scopedCss = hasScopedStyles ? compileScopedStyles(`.${scopeClass}`, _responsiveStyles) : '';
|
|
288
|
+
const mergedClassName = scopeClass
|
|
289
|
+
? [evaluatedSchema.className, scopeClass].filter(Boolean).join(' ')
|
|
290
|
+
: evaluatedSchema.className;
|
|
291
|
+
// Some renderers read `schema.className` directly (e.g. element:text) while
|
|
292
|
+
// others read the `className` prop (e.g. flex/container). Set both so the
|
|
293
|
+
// scope class lands regardless of which channel a renderer honours.
|
|
294
|
+
const schemaForComponent = scopeClass
|
|
295
|
+
? { ...evaluatedSchema, className: mergedClassName }
|
|
296
|
+
: evaluatedSchema;
|
|
267
297
|
// Extract AriaPropsSchema properties for accessibility
|
|
268
298
|
const ariaProps = resolveAriaProps(evaluatedSchema);
|
|
269
299
|
// Debug-mode enhancements: extra data attributes + perf tracking
|
|
@@ -277,19 +307,19 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
277
307
|
}
|
|
278
308
|
debugTime(`render:${evaluatedSchema.type}:${evaluatedSchema.id ?? 'anon'}`);
|
|
279
309
|
const renderStart = isDebug ? performance.now() : 0;
|
|
280
|
-
const rendered = (
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
310
|
+
const rendered = (_jsxs(SchemaErrorBoundary, { componentType: evaluatedSchema.type, resetKey: evaluatedSchema.id ?? null, children: [scopedCss ? (_jsx("style", { "data-os-scope": scopeClass, dangerouslySetInnerHTML: { __html: scopedCss } })) : null, React.createElement(Component, {
|
|
311
|
+
schema: schemaForComponent,
|
|
312
|
+
...componentProps, // Spread non-metadata schema properties as props
|
|
313
|
+
...(evaluatedSchema.props || {}), // Override with explicit props if provided
|
|
314
|
+
...ariaProps, // Inject ARIA attributes from AriaPropsSchema
|
|
315
|
+
...debugAttrs, // Debug-mode data attributes
|
|
316
|
+
disabled: __disabled || undefined,
|
|
317
|
+
className: mergedClassName,
|
|
318
|
+
'data-obj-id': evaluatedSchema.id,
|
|
319
|
+
'data-obj-type': evaluatedSchema.type,
|
|
320
|
+
...(__DEV__ && !_validation.valid ? { 'data-obj-schema-invalid': 'true' } : {}),
|
|
321
|
+
...props
|
|
322
|
+
})] }));
|
|
293
323
|
debugTimeEnd(`render:${evaluatedSchema.type}:${evaluatedSchema.id ?? 'anon'}`);
|
|
294
324
|
// Report render perf to DebugCollector when debug mode is active
|
|
295
325
|
if (isDebug && renderStart) {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Components can consume actions without creating their own runner instances.
|
|
13
13
|
*/
|
|
14
14
|
import React from 'react';
|
|
15
|
-
import { ActionRunner, type ActionContext as ActionCtx, type ActionDef, type ActionResult, type ConfirmationHandler, type ToastHandler, type ModalHandler, type NavigationHandler, type ParamCollectionHandler } from '@object-ui/core';
|
|
15
|
+
import { ActionRunner, type ActionContext as ActionCtx, type ActionDef, type ActionResult, type ConfirmationHandler, type ToastHandler, type ModalHandler, type NavigationHandler, type ParamCollectionHandler, type ResultDialogHandler } from '@object-ui/core';
|
|
16
16
|
export interface ActionProviderProps {
|
|
17
17
|
children: React.ReactNode;
|
|
18
18
|
/** Initial action context (record, user, data, etc.) */
|
|
@@ -27,6 +27,8 @@ export interface ActionProviderProps {
|
|
|
27
27
|
onNavigate?: NavigationHandler;
|
|
28
28
|
/** Custom param collection handler — show dialog to collect ActionParam values */
|
|
29
29
|
onParamCollection?: ParamCollectionHandler;
|
|
30
|
+
/** Custom result-dialog handler — show one-shot reveal of API response after success */
|
|
31
|
+
onResultDialog?: ResultDialogHandler;
|
|
30
32
|
/** Pre-registered custom action handlers */
|
|
31
33
|
handlers?: Record<string, (action: ActionDef, ctx: ActionCtx) => Promise<ActionResult>>;
|
|
32
34
|
}
|
|
@@ -46,6 +48,7 @@ interface ActionContextValue {
|
|
|
46
48
|
/** Update context data */
|
|
47
49
|
updateContext: (ctx: Partial<ActionCtx>) => void;
|
|
48
50
|
}
|
|
51
|
+
export declare const ActionCtxReact: React.Context<ActionContextValue | null>;
|
|
49
52
|
/**
|
|
50
53
|
* ActionProvider — Provides a shared ActionRunner to the tree.
|
|
51
54
|
*
|
|
@@ -14,7 +14,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
14
14
|
*/
|
|
15
15
|
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
|
16
16
|
import { ActionRunner, } from '@object-ui/core';
|
|
17
|
-
const ActionCtxReact = createContext(null);
|
|
17
|
+
export const ActionCtxReact = createContext(null);
|
|
18
18
|
/**
|
|
19
19
|
* ActionProvider — Provides a shared ActionRunner to the tree.
|
|
20
20
|
*
|
|
@@ -30,12 +30,25 @@ const ActionCtxReact = createContext(null);
|
|
|
30
30
|
* </ActionProvider>
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
|
-
export const ActionProvider = ({ children, context = {}, onConfirm, onToast, onModal, onNavigate, onParamCollection, handlers, }) => {
|
|
33
|
+
export const ActionProvider = ({ children, context = {}, onConfirm, onToast, onModal, onNavigate, onParamCollection, onResultDialog, handlers, }) => {
|
|
34
34
|
const [loading, setLoading] = useState(false);
|
|
35
35
|
const [error, setError] = useState(null);
|
|
36
36
|
const [result, setResult] = useState(null);
|
|
37
37
|
const runner = useMemo(() => {
|
|
38
|
-
|
|
38
|
+
// Normalize the evaluator scope so predicates can use *either* flat
|
|
39
|
+
// (`record`, `user`, `objectName`) *or* the canonical `ctx.*` namespace
|
|
40
|
+
// (`ctx.user`, `ctx.record`, `ctx.objectName`). The CEL/predicate
|
|
41
|
+
// convention used across the platform (sys-user, sys-invitation,
|
|
42
|
+
// skills/objectstack-formula, …) is `ctx.user.id` — exposing both
|
|
43
|
+
// shapes from one place keeps every consumer working without forcing
|
|
44
|
+
// every call site to repeat the same `{ ..., ctx: { ... } }` boilerplate.
|
|
45
|
+
const normalizedContext = {
|
|
46
|
+
...context,
|
|
47
|
+
ctx: (context && typeof context.ctx === 'object')
|
|
48
|
+
? { ...context, ...context.ctx }
|
|
49
|
+
: { ...context },
|
|
50
|
+
};
|
|
51
|
+
const r = new ActionRunner(normalizedContext);
|
|
39
52
|
if (onConfirm)
|
|
40
53
|
r.setConfirmHandler(onConfirm);
|
|
41
54
|
if (onToast)
|
|
@@ -46,6 +59,8 @@ export const ActionProvider = ({ children, context = {}, onConfirm, onToast, onM
|
|
|
46
59
|
r.setNavigationHandler(onNavigate);
|
|
47
60
|
if (onParamCollection)
|
|
48
61
|
r.setParamCollectionHandler(onParamCollection);
|
|
62
|
+
if (onResultDialog)
|
|
63
|
+
r.setResultDialogHandler(onResultDialog);
|
|
49
64
|
if (handlers) {
|
|
50
65
|
Object.entries(handlers).forEach(([name, handler]) => {
|
|
51
66
|
r.registerHandler(name, handler);
|
|
@@ -10,12 +10,22 @@ export interface MetadataState {
|
|
|
10
10
|
loading: boolean;
|
|
11
11
|
error: Error | null;
|
|
12
12
|
}
|
|
13
|
+
export type MetadataTypeStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
13
14
|
export interface MetadataContextValue extends MetadataState {
|
|
14
15
|
refresh: (type?: string) => Promise<void>;
|
|
15
16
|
invalidate: (type: string, name?: string) => void;
|
|
16
17
|
ensureType: (type: string) => Promise<any[]>;
|
|
17
18
|
getItem: (type: string, name: string) => Promise<any | null>;
|
|
18
19
|
getItemsByType: (type: string) => any[];
|
|
20
|
+
/**
|
|
21
|
+
* Per-type load status. Lazy types ('page', 'dashboard', …) return their
|
|
22
|
+
* items array immediately — empty or stale while a (re)fetch is in flight —
|
|
23
|
+
* so consumers that DIFF the list over time (e.g. NavigationSyncEffect)
|
|
24
|
+
* must distinguish "empty because unloaded" from "actually empty" and only
|
|
25
|
+
* trust snapshots taken while the type is 'ready'. Optional so hand-rolled
|
|
26
|
+
* context values in tests keep working; absent means "always ready".
|
|
27
|
+
*/
|
|
28
|
+
getTypeStatus?: (type: string) => MetadataTypeStatus;
|
|
19
29
|
}
|
|
20
30
|
export declare const MetadataCtx: import("react").Context<MetadataContextValue | null>;
|
|
21
31
|
export declare function useMetadata(): MetadataContextValue;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* DrillNavigationContext — host-provided navigation for drill "escape hatches".
|
|
10
|
+
*
|
|
11
|
+
* Dashboard/report drill-through opens an in-place drawer listing the records
|
|
12
|
+
* behind a clicked aggregate (the mainstream "peek without losing context"
|
|
13
|
+
* default). But sometimes the user wants the *full* object list page — to sort,
|
|
14
|
+
* bulk-select, switch views, export, or get a shareable URL. That requires
|
|
15
|
+
* console-specific URL building + SPA routing, which lives in the app shell,
|
|
16
|
+
* not the renderer.
|
|
17
|
+
*
|
|
18
|
+
* The app shell provides `openRecordList`; the drill drawers consume it to
|
|
19
|
+
* render an "Open in list →" action (and to honor `drillDown.target:
|
|
20
|
+
* 'navigate'`, which skips the drawer entirely). When no provider is present
|
|
21
|
+
* (e.g. an embedded/standalone renderer with no router) the affordance is
|
|
22
|
+
* simply hidden — drill stays in the drawer.
|
|
23
|
+
*/
|
|
24
|
+
import React from 'react';
|
|
25
|
+
export interface DrillNavigationValue {
|
|
26
|
+
/**
|
|
27
|
+
* Open the full list page for `objectName`, scoped by `filter` (an object
|
|
28
|
+
* FIELD name → raw stored value map). Implemented by the app shell as a
|
|
29
|
+
* SPA navigation to the object's list route. Absent when no host wired it.
|
|
30
|
+
*/
|
|
31
|
+
openRecordList?: (objectName: string, filter?: Record<string, unknown>) => void;
|
|
32
|
+
}
|
|
33
|
+
export declare const DrillNavigationContext: React.Context<DrillNavigationValue>;
|
|
34
|
+
export declare const DrillNavigationProvider: React.FC<{
|
|
35
|
+
value: DrillNavigationValue;
|
|
36
|
+
children: React.ReactNode;
|
|
37
|
+
}>;
|
|
38
|
+
/** Consume the host-provided drill navigation handlers (empty object if none). */
|
|
39
|
+
export declare function useDrillNavigation(): DrillNavigationValue;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ObjectUI
|
|
4
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* DrillNavigationContext — host-provided navigation for drill "escape hatches".
|
|
11
|
+
*
|
|
12
|
+
* Dashboard/report drill-through opens an in-place drawer listing the records
|
|
13
|
+
* behind a clicked aggregate (the mainstream "peek without losing context"
|
|
14
|
+
* default). But sometimes the user wants the *full* object list page — to sort,
|
|
15
|
+
* bulk-select, switch views, export, or get a shareable URL. That requires
|
|
16
|
+
* console-specific URL building + SPA routing, which lives in the app shell,
|
|
17
|
+
* not the renderer.
|
|
18
|
+
*
|
|
19
|
+
* The app shell provides `openRecordList`; the drill drawers consume it to
|
|
20
|
+
* render an "Open in list →" action (and to honor `drillDown.target:
|
|
21
|
+
* 'navigate'`, which skips the drawer entirely). When no provider is present
|
|
22
|
+
* (e.g. an embedded/standalone renderer with no router) the affordance is
|
|
23
|
+
* simply hidden — drill stays in the drawer.
|
|
24
|
+
*/
|
|
25
|
+
import { createContext, useContext } from 'react';
|
|
26
|
+
export const DrillNavigationContext = createContext({});
|
|
27
|
+
export const DrillNavigationProvider = ({ value, children }) => (_jsx(DrillNavigationContext.Provider, { value: value, children: children }));
|
|
28
|
+
/** Consume the host-provided drill navigation handlers (empty object if none). */
|
|
29
|
+
export function useDrillNavigation() {
|
|
30
|
+
return useContext(DrillNavigationContext);
|
|
31
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react';
|
|
9
|
+
/**
|
|
10
|
+
* PreviewModeContext — signals that the subtree is rendered inside a design /
|
|
11
|
+
* metadata preview surface (the Studio canvas, a view/page preview pane) rather
|
|
12
|
+
* than a live runtime route.
|
|
13
|
+
*
|
|
14
|
+
* Overlay form components (DrawerForm, ModalForm) consume this to render their
|
|
15
|
+
* body inline instead of mounting a portalled modal Sheet/Dialog. A modal in a
|
|
16
|
+
* preview escapes the canvas and locks the whole editor — Radix sets body
|
|
17
|
+
* `pointer-events:none` and traps focus while open — which reads as a frozen UI.
|
|
18
|
+
*
|
|
19
|
+
* Defaults to `false`, so live runtime usage (record pages, action modals,
|
|
20
|
+
* the field/object designers) is completely unaffected.
|
|
21
|
+
*/
|
|
22
|
+
declare const PreviewModeContext: React.Context<boolean>;
|
|
23
|
+
export declare const PreviewModeProvider: ({ value, children, }: {
|
|
24
|
+
value?: boolean;
|
|
25
|
+
children: React.ReactNode;
|
|
26
|
+
}) => React.JSX.Element;
|
|
27
|
+
/** True when rendering inside a design / preview surface. Safe outside a provider. */
|
|
28
|
+
export declare const usePreviewMode: () => boolean;
|
|
29
|
+
export { PreviewModeContext };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ObjectUI
|
|
4
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
import { createContext, useContext } from 'react';
|
|
10
|
+
/**
|
|
11
|
+
* PreviewModeContext — signals that the subtree is rendered inside a design /
|
|
12
|
+
* metadata preview surface (the Studio canvas, a view/page preview pane) rather
|
|
13
|
+
* than a live runtime route.
|
|
14
|
+
*
|
|
15
|
+
* Overlay form components (DrawerForm, ModalForm) consume this to render their
|
|
16
|
+
* body inline instead of mounting a portalled modal Sheet/Dialog. A modal in a
|
|
17
|
+
* preview escapes the canvas and locks the whole editor — Radix sets body
|
|
18
|
+
* `pointer-events:none` and traps focus while open — which reads as a frozen UI.
|
|
19
|
+
*
|
|
20
|
+
* Defaults to `false`, so live runtime usage (record pages, action modals,
|
|
21
|
+
* the field/object designers) is completely unaffected.
|
|
22
|
+
*/
|
|
23
|
+
const PreviewModeContext = createContext(false);
|
|
24
|
+
export const PreviewModeProvider = ({ value = true, children, }) => (_jsx(PreviewModeContext.Provider, { value: value, children: children }));
|
|
25
|
+
/** True when rendering inside a design / preview surface. Safe outside a provider. */
|
|
26
|
+
export const usePreviewMode = () => useContext(PreviewModeContext);
|
|
27
|
+
export { PreviewModeContext };
|
|
@@ -12,6 +12,6 @@ export declare const SchemaRendererProvider: ({ children, dataSource, debug, deb
|
|
|
12
12
|
dataSource: any;
|
|
13
13
|
debug?: boolean;
|
|
14
14
|
debugFlags?: DebugFlags;
|
|
15
|
-
}) =>
|
|
15
|
+
}) => React.JSX.Element;
|
|
16
16
|
export declare const useSchemaContext: () => SchemaRendererContextType;
|
|
17
17
|
export declare const useDataScope: (path?: string) => any;
|
|
@@ -75,7 +75,7 @@ export interface ThemeProviderProps {
|
|
|
75
75
|
* </ThemeProvider>
|
|
76
76
|
* ```
|
|
77
77
|
*/
|
|
78
|
-
export declare function ThemeProvider({ themes: themesInput, defaultTheme, defaultMode, persist, storageKey, target, children, }: ThemeProviderProps):
|
|
78
|
+
export declare function ThemeProvider({ themes: themesInput, defaultTheme, defaultMode, persist, storageKey, target, children, }: ThemeProviderProps): React.JSX.Element;
|
|
79
79
|
/**
|
|
80
80
|
* useTheme — access the current theme context.
|
|
81
81
|
*
|
package/dist/context/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './SchemaRendererContext';
|
|
2
|
+
export * from './PreviewModeContext';
|
|
2
3
|
export * from './ActionContext';
|
|
3
4
|
export * from './ThemeContext';
|
|
4
5
|
export * from './NotificationContext';
|
|
@@ -6,3 +7,4 @@ export * from './DndContext';
|
|
|
6
7
|
export * from './AppShellContext';
|
|
7
8
|
export * from './RecordContext';
|
|
8
9
|
export * from './DiscussionContext';
|
|
10
|
+
export * from './DrillNavigationContext';
|
package/dist/context/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './SchemaRendererContext';
|
|
2
|
+
export * from './PreviewModeContext';
|
|
2
3
|
export * from './ActionContext';
|
|
3
4
|
export * from './ThemeContext';
|
|
4
5
|
export * from './NotificationContext';
|
|
@@ -6,3 +7,4 @@ export * from './DndContext';
|
|
|
6
7
|
export * from './AppShellContext';
|
|
7
8
|
export * from './RecordContext';
|
|
8
9
|
export * from './DiscussionContext';
|
|
10
|
+
export * from './DrillNavigationContext';
|
|
@@ -21,16 +21,59 @@
|
|
|
21
21
|
* const toolbarActions = getActionsForLocation('list_toolbar');
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
import { useCallback, useMemo } from 'react';
|
|
24
|
+
import { useCallback, useContext, useMemo } from 'react';
|
|
25
25
|
import { ActionEngine, } from '@object-ui/core';
|
|
26
|
+
import { ActionCtxReact } from '../context/ActionContext';
|
|
26
27
|
export function useActionEngine(options = {}) {
|
|
27
28
|
const { actions = [], context = {} } = options;
|
|
29
|
+
// When wrapped in an <ActionProvider>, reuse its ActionRunner so that:
|
|
30
|
+
// • Visibility / disabled / condition CEL expressions evaluate against
|
|
31
|
+
// the same context (user, datasource, etc.) the provider was seeded
|
|
32
|
+
// with — not just the per-call `context` arg.
|
|
33
|
+
// • Action execution (executeAction / handleShortcut) inherits the
|
|
34
|
+
// provider's confirm / param-collection / modal / result-dialog /
|
|
35
|
+
// toast / navigate handlers. Without sharing, a nested engine would
|
|
36
|
+
// silently no-op on any action that declares `params: [...]` or
|
|
37
|
+
// `confirmText`, because its local runner has no handlers installed.
|
|
38
|
+
// When no provider is present (unit tests, standalone playgrounds), we
|
|
39
|
+
// fall back to a self-contained runner constructed from the `context` arg.
|
|
40
|
+
const providerCtx = useContext(ActionCtxReact);
|
|
41
|
+
const sharedRunner = providerCtx?.runner ?? null;
|
|
28
42
|
const engine = useMemo(() => {
|
|
29
|
-
|
|
43
|
+
// When standalone (no surrounding `<ActionProvider>`), normalize the
|
|
44
|
+
// context so predicates can use both `record`/`user` and `ctx.*`.
|
|
45
|
+
const normalizedStandalone = (context && Object.keys(context).length > 0)
|
|
46
|
+
? {
|
|
47
|
+
...context,
|
|
48
|
+
ctx: (context.ctx && typeof context.ctx === 'object')
|
|
49
|
+
? { ...context, ...context.ctx }
|
|
50
|
+
: { ...context },
|
|
51
|
+
}
|
|
52
|
+
: context;
|
|
53
|
+
const e = sharedRunner ? new ActionEngine(sharedRunner) : new ActionEngine(normalizedStandalone);
|
|
54
|
+
// When sharing a provider runner, MERGE per-render flat keys into the
|
|
55
|
+
// existing `ctx` instead of overwriting it. The provider seeds
|
|
56
|
+
// `ctx: { record, user, objectName, … }` once; if we replaced it with
|
|
57
|
+
// just `ctx: { record, recordId, objectName }` here we would erase
|
|
58
|
+
// `ctx.user` and every `record.id == ctx.user.id` predicate would
|
|
59
|
+
// evaluate against `undefined.id` (→ throws → hidden).
|
|
60
|
+
if (sharedRunner && context && Object.keys(context).length > 0) {
|
|
61
|
+
const runner = e.getRunner();
|
|
62
|
+
const existing = runner.getEvaluator().getContext().toObject();
|
|
63
|
+
const existingCtx = (existing && typeof existing.ctx === 'object') ? existing.ctx : {};
|
|
64
|
+
const callerCtx = (context.ctx && typeof context.ctx === 'object')
|
|
65
|
+
? context.ctx
|
|
66
|
+
: context;
|
|
67
|
+
const merged = {
|
|
68
|
+
...context,
|
|
69
|
+
ctx: { ...existingCtx, ...callerCtx },
|
|
70
|
+
};
|
|
71
|
+
runner.updateContext(merged);
|
|
72
|
+
}
|
|
30
73
|
e.registerActions(actions);
|
|
31
74
|
return e;
|
|
32
75
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
-
}, [JSON.stringify(actions), JSON.stringify(context)]);
|
|
76
|
+
}, [sharedRunner, JSON.stringify(actions), JSON.stringify(context)]);
|
|
34
77
|
const getActionsForLocation = useCallback((location) => engine.getActionsForLocation(location), [engine]);
|
|
35
78
|
const getBulkActions = useCallback(() => engine.getBulkActions(), [engine]);
|
|
36
79
|
const executeAction = useCallback((name, contextOverride) => engine.executeAction(name, contextOverride), [engine]);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
import { ActionRunner, type ActionContext, type ActionDef, type ActionResult, type ConfirmationHandler, type ToastHandler, type ModalHandler, type NavigationHandler } from '@object-ui/core';
|
|
8
|
+
import { ActionRunner, type ActionContext, type ActionDef, type ActionResult, type ConfirmationHandler, type ToastHandler, type ModalHandler, type NavigationHandler, type ParamCollectionHandler, type ResultDialogHandler } from '@object-ui/core';
|
|
9
9
|
export interface UseActionRunnerOptions {
|
|
10
10
|
context?: ActionContext;
|
|
11
11
|
/** Custom confirmation handler — wire to Shadcn AlertDialog or similar */
|
|
@@ -16,6 +16,10 @@ export interface UseActionRunnerOptions {
|
|
|
16
16
|
onModal?: ModalHandler;
|
|
17
17
|
/** Custom navigation handler — SPA-aware routing */
|
|
18
18
|
onNavigate?: NavigationHandler;
|
|
19
|
+
/** Custom param-collection handler — show dialog to gather params before execution */
|
|
20
|
+
onParamCollection?: ParamCollectionHandler;
|
|
21
|
+
/** Custom result-dialog handler — one-shot reveal of API response on success */
|
|
22
|
+
onResultDialog?: ResultDialogHandler;
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
25
|
* Hook for executing actions with loading state, typed with ActionDef.
|
|
@@ -31,7 +31,7 @@ export function useActionRunner(optionsOrContext = {}) {
|
|
|
31
31
|
const options = 'context' in optionsOrContext
|
|
32
32
|
? optionsOrContext
|
|
33
33
|
: { context: optionsOrContext };
|
|
34
|
-
const { context = {}, onConfirm, onToast, onModal, onNavigate } = options;
|
|
34
|
+
const { context = {}, onConfirm, onToast, onModal, onNavigate, onParamCollection, onResultDialog } = options;
|
|
35
35
|
const [loading, setLoading] = useState(false);
|
|
36
36
|
const [error, setError] = useState(null);
|
|
37
37
|
const [result, setResult] = useState(null);
|
|
@@ -45,6 +45,10 @@ export function useActionRunner(optionsOrContext = {}) {
|
|
|
45
45
|
r.setModalHandler(onModal);
|
|
46
46
|
if (onNavigate)
|
|
47
47
|
r.setNavigationHandler(onNavigate);
|
|
48
|
+
if (onParamCollection)
|
|
49
|
+
r.setParamCollectionHandler(onParamCollection);
|
|
50
|
+
if (onResultDialog)
|
|
51
|
+
r.setResultDialogHandler(onResultDialog);
|
|
48
52
|
return r;
|
|
49
53
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
50
54
|
}, [JSON.stringify(context)]);
|
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
* Bridge hook between the @objectstack/client notifications API
|
|
12
12
|
* and the local NotificationContext state. Fetches server-side
|
|
13
13
|
* notifications and surfaces them through the existing provider.
|
|
14
|
+
*
|
|
15
|
+
* ADR-0030 (Notification Convergence): the `client.notifications.*` helpers
|
|
16
|
+
* are the stable transport contract — the server routes them to the L5
|
|
17
|
+
* `sys_inbox_message` materialization and the `sys_notification_receipt`
|
|
18
|
+
* read-state spine (the re-modeled `sys_notification` L2 event carries no
|
|
19
|
+
* recipient/read columns). This hook only needs to call the SDK with its
|
|
20
|
+
* current signatures: `list(options)`, `markRead(ids[])`, `markAllRead()`,
|
|
21
|
+
* and `registerDevice(request)`.
|
|
14
22
|
*/
|
|
15
23
|
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
16
24
|
import { SchemaRendererContext } from '../context/SchemaRendererContext';
|
|
@@ -111,7 +119,13 @@ export function useClientNotifications(options = {}) {
|
|
|
111
119
|
return;
|
|
112
120
|
setError(null);
|
|
113
121
|
try {
|
|
114
|
-
|
|
122
|
+
// ADR-0030 / @objectstack/client v7+: registerDevice takes a single
|
|
123
|
+
// RegisterDeviceRequest object (`{ token, platform }`), not positional
|
|
124
|
+
// args. `platform` is the device platform enum.
|
|
125
|
+
await client.notifications.registerDevice({
|
|
126
|
+
token,
|
|
127
|
+
platform: platform,
|
|
128
|
+
});
|
|
115
129
|
}
|
|
116
130
|
catch (err) {
|
|
117
131
|
const wrapped = err instanceof Error ? err : new Error('Failed to register device');
|
|
@@ -155,7 +169,11 @@ export function useClientNotifications(options = {}) {
|
|
|
155
169
|
return;
|
|
156
170
|
setError(null);
|
|
157
171
|
try {
|
|
158
|
-
|
|
172
|
+
// ADR-0030 / @objectstack/client v7+: the SDK exposes `markRead(ids[])`
|
|
173
|
+
// (batch) — there is no single-id `markAsRead`. Server-side this writes
|
|
174
|
+
// the `read` state to the `sys_notification_receipt` spine. We keep the
|
|
175
|
+
// friendly single-id hook API and adapt to the batch call.
|
|
176
|
+
await client.notifications.markRead([id]);
|
|
159
177
|
}
|
|
160
178
|
catch (err) {
|
|
161
179
|
const wrapped = err instanceof Error ? err : new Error('Failed to mark as read');
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
import { normalizeQuickFilters } from '@object-ui/core';
|
|
9
8
|
function mapColumn(col) {
|
|
10
9
|
const mapped = {
|
|
11
10
|
accessorKey: col.field,
|
|
@@ -81,10 +80,6 @@ export const bridgeListView = (spec, _context) => {
|
|
|
81
80
|
node.searchableFields = spec.searchableFields;
|
|
82
81
|
if (spec.filterableFields)
|
|
83
82
|
node.filterableFields = spec.filterableFields;
|
|
84
|
-
// Normalize quickFilters: spec format { field, operator, value } → ObjectUI format
|
|
85
|
-
const normalizedQF = normalizeQuickFilters(spec.quickFilters);
|
|
86
|
-
if (normalizedQF)
|
|
87
|
-
node.quickFilters = normalizedQF;
|
|
88
83
|
if (spec.resizable != null)
|
|
89
84
|
node.resizable = spec.resizable;
|
|
90
85
|
if (spec.striped != null)
|
|
@@ -126,8 +121,6 @@ export const bridgeListView = (spec, _context) => {
|
|
|
126
121
|
node.appearance = spec.appearance;
|
|
127
122
|
if (spec.compactToolbar != null)
|
|
128
123
|
node.compactToolbar = spec.compactToolbar;
|
|
129
|
-
if (spec.tabs)
|
|
130
|
-
node.tabs = spec.tabs;
|
|
131
124
|
if (spec.addRecord)
|
|
132
125
|
node.addRecord = spec.addRecord;
|
|
133
126
|
if (spec.showRecordCount != null)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "React bindings and SchemaRenderer component for Object UI",
|
|
@@ -24,19 +24,19 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@objectstack/spec": "^
|
|
28
|
-
"react-hook-form": "^7.
|
|
29
|
-
"@object-ui/core": "
|
|
30
|
-
"@object-ui/data-objectstack": "
|
|
31
|
-
"@object-ui/i18n": "
|
|
32
|
-
"@object-ui/types": "
|
|
27
|
+
"@objectstack/spec": "^10.0.0",
|
|
28
|
+
"react-hook-form": "^7.80.0",
|
|
29
|
+
"@object-ui/core": "7.0.0",
|
|
30
|
+
"@object-ui/data-objectstack": "7.0.0",
|
|
31
|
+
"@object-ui/i18n": "7.0.0",
|
|
32
|
+
"@object-ui/types": "7.0.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^18.0.0 || ^19.0.0",
|
|
36
36
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@types/react": "19.2.
|
|
39
|
+
"@types/react": "19.2.17",
|
|
40
40
|
"@types/react-dom": "19.2.3",
|
|
41
41
|
"typescript": "^6.0.3"
|
|
42
42
|
},
|