@object-ui/react 7.0.0 → 7.2.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 CHANGED
@@ -1,5 +1,46 @@
1
1
  # @object-ui/react
2
2
 
3
+ ## 7.2.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [8e7c1da]
8
+ - Updated dependencies [d23db5c]
9
+ - @object-ui/i18n@7.2.0
10
+ - @object-ui/types@7.2.0
11
+ - @object-ui/core@7.2.0
12
+ - @object-ui/data-objectstack@7.2.0
13
+
14
+ ## 7.1.0
15
+
16
+ ### Minor Changes
17
+
18
+ - a71be60: chore: drop the unrendered `blank` / `record_review` page types and their config
19
+
20
+ The `blank` and `record_review` page types have no renderer and were removed
21
+ from `@objectstack/spec`'s `PageTypeSchema` (framework#2265, enforce-or-remove).
22
+ This drops their now-dead references in objectui so the upstream spec can hard-
23
+ remove `BlankPageLayoutSchema` / `RecordReviewConfigSchema`:
24
+
25
+ - `PageType` union: removed `dashboard` / `form` / `record_detail` /
26
+ `record_review` / `overview` / `blank` (grid/gallery/kanban/calendar/timeline
27
+ remain — those are list _visualizations_, a separate cleanup).
28
+ - Removed `blankLayout` from `PageLayout` and the `blankLayout` / `recordReview`
29
+ handling in the spec→SDUI page bridge.
30
+ - Removed the redundant `BlankPageLayout{,Schema,Item,ItemSchema}` re-import from
31
+ `@objectstack/spec/ui` (it was never used).
32
+
33
+ ### Patch Changes
34
+
35
+ - Updated dependencies [677f7ed]
36
+ - Updated dependencies [08c47da]
37
+ - Updated dependencies [a71be60]
38
+ - Updated dependencies [cb03bc3]
39
+ - @object-ui/types@7.1.0
40
+ - @object-ui/core@7.1.0
41
+ - @object-ui/data-objectstack@7.1.0
42
+ - @object-ui/i18n@7.1.0
43
+
3
44
  ## 7.0.0
4
45
 
5
46
  ### Minor Changes
@@ -10,6 +10,7 @@ import React, { forwardRef, useContext, useMemo, useEffect, useReducer, useState
10
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
12
  import { usePredicateScope } from './hooks/useExpression';
13
+ import { usePageVariables } from './hooks/usePageVariables';
13
14
  import { resolveI18nLabel } from './utils/i18n';
14
15
  /**
15
16
  * Dev-mode schema validation.
@@ -142,6 +143,11 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
142
143
  // ExpressionProvider. Threaded into `visible`/expression evaluation so
143
144
  // component predicates can gate on the signed-in user & deployment flags.
144
145
  const predicateScope = usePredicateScope();
146
+ // Page-local state (PageSchema.variables), provided by PageVariablesProvider.
147
+ // Exposed to predicates/bindings under `page.<var>` so an interactive element
148
+ // (e.g. element:record_picker) writing a variable can drive another
149
+ // component's `visible`/`visibility`. Empty object outside a Page.
150
+ const { variables: pageVariables } = usePageVariables();
145
151
  // Re-render trigger when the global ComponentRegistry mutates (e.g. a
146
152
  // lazy-loaded plugin finishes registering its components).
147
153
  const [, forceUpdate] = useReducer((n) => n + 1, 0);
@@ -164,11 +170,14 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
164
170
  return schema;
165
171
  // `data` (record/datasource) plus the ambient host scope. `current_user`
166
172
  // is aliased to `user` so both `user.email` and `current_user.email`
167
- // resolve in component `visible`/`visibleOn` expressions.
173
+ // resolve in component `visible`/`visibleOn` expressions. `page` exposes
174
+ // page-local state so predicates can gate on `page.<var>` (e.g. a record
175
+ // picker's selection toggling another component's visibility).
168
176
  const evaluator = new ExpressionEvaluator({
169
177
  ...predicateScope,
170
178
  current_user: predicateScope?.user,
171
179
  data: dataSource,
180
+ page: pageVariables,
172
181
  });
173
182
  // Shallow copy
174
183
  const newSchema = { ...schema };
@@ -207,7 +216,7 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
207
216
  }
208
217
  newSchema.props = newProps;
209
218
  }
210
- // Evaluate visibility: visible / visibleOn / hidden / hiddenOn
219
+ // Evaluate visibility: visible / visibleOn / visibility / hidden / hiddenOn
211
220
  const shouldHide = (() => {
212
221
  if (newSchema.visible !== undefined) {
213
222
  return !evaluator.evaluateCondition(newSchema.visible);
@@ -215,6 +224,13 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
215
224
  if (newSchema.visibleOn !== undefined) {
216
225
  return !evaluator.evaluateCondition(newSchema.visibleOn);
217
226
  }
227
+ // `visibility` is the spec-canonical predicate (PageComponentSchema.visibility,
228
+ // an ExpressionInput) — show-when-truthy, same semantics as `visibleOn`. The
229
+ // spec→node bridge preserves it verbatim on the node, so this is where it
230
+ // finally gates rendering (previously it was inert and leaked as a DOM prop).
231
+ if (newSchema.visibility !== undefined) {
232
+ return !evaluator.evaluateCondition(newSchema.visibility);
233
+ }
218
234
  if (newSchema.hidden !== undefined) {
219
235
  return evaluator.evaluateCondition(newSchema.hidden);
220
236
  }
@@ -240,7 +256,7 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
240
256
  newSchema._disabled = true;
241
257
  }
242
258
  return newSchema;
243
- }, [schema, dataSource, predicateScope]);
259
+ }, [schema, dataSource, predicateScope, pageVariables]);
244
260
  if (!evaluatedSchema)
245
261
  return null;
246
262
  // Handle visibility: if evaluated schema is hidden, render nothing
@@ -275,7 +291,7 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
275
291
  // Note: We don't forward the ref to the Component because components in the registry
276
292
  // may not support refs. The SchemaRenderer itself can still receive refs for its own use.
277
293
  // Extract schema metadata properties that should NOT be passed as React props
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
294
+ const { type: _type, children: _children, body: _body, schema: _schema, visible: _visible, visibleOn: _visibleOn, visibility: _visibility, hidden: _hidden, hiddenOn: _hiddenOn, disabled: _disabled, disabledOn: _disabledOn, _hidden: __hidden, // stripped: internal visibility flag
279
295
  _disabled: __disabled, // stripped: internal disabled flag
280
296
  responsiveStyles: _responsiveStyles, // stripped: compiled to scoped CSS, not a DOM prop
281
297
  ...componentProps } = evaluatedSchema;
@@ -9,6 +9,7 @@ export * from './useExpression';
9
9
  export * from './useActionRunner';
10
10
  export * from './useNavigationOverlay';
11
11
  export * from './usePageVariables';
12
+ export * from './usePageVariableActionBridge';
12
13
  export * from './useViewData';
13
14
  export * from './useDynamicApp';
14
15
  export * from './useDiscovery';
@@ -9,6 +9,7 @@ export * from './useExpression';
9
9
  export * from './useActionRunner';
10
10
  export * from './useNavigationOverlay';
11
11
  export * from './usePageVariables';
12
+ export * from './usePageVariableActionBridge';
12
13
  export * from './useViewData';
13
14
  export * from './useDynamicApp';
14
15
  export * from './useDiscovery';
@@ -0,0 +1,28 @@
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
+ * PageVariableActionBridge — publishes the live page-variable snapshot into the
10
+ * shared ActionRunner context as `pageVariables`, so a submit action (`api` /
11
+ * `script` / `flow`, or an `onClick` expression) can pull page-local form state
12
+ * into its request body via `{{page.<var>}}` token interpolation (resolved in
13
+ * app-shell's `useConsoleActionRuntime`).
14
+ *
15
+ * This is the data-entry counterpart to ObjectGrid publishing `selectedRecords`
16
+ * into the same context. Page variables live BELOW the ActionProvider — they're
17
+ * mounted by `PageVariablesProvider` inside the page layout renderer, whereas
18
+ * the action runtime is mounted above it — so the runtime can't read them
19
+ * directly. The bridge carries them up via `updateContext`.
20
+ *
21
+ * Mount it once inside a `PageVariablesProvider`. Outside an `ActionProvider`,
22
+ * `useAction()` falls back to a throwaway runner, so the bridge is a safe no-op
23
+ * (never throws in standalone / test contexts). Renders nothing.
24
+ */
25
+ export declare function PageVariableActionBridge(): null;
26
+ export declare namespace PageVariableActionBridge {
27
+ var displayName: string;
28
+ }
@@ -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
+ import * as React from 'react';
9
+ import { usePageVariables } from './usePageVariables';
10
+ import { useAction } from '../context';
11
+ /**
12
+ * PageVariableActionBridge — publishes the live page-variable snapshot into the
13
+ * shared ActionRunner context as `pageVariables`, so a submit action (`api` /
14
+ * `script` / `flow`, or an `onClick` expression) can pull page-local form state
15
+ * into its request body via `{{page.<var>}}` token interpolation (resolved in
16
+ * app-shell's `useConsoleActionRuntime`).
17
+ *
18
+ * This is the data-entry counterpart to ObjectGrid publishing `selectedRecords`
19
+ * into the same context. Page variables live BELOW the ActionProvider — they're
20
+ * mounted by `PageVariablesProvider` inside the page layout renderer, whereas
21
+ * the action runtime is mounted above it — so the runtime can't read them
22
+ * directly. The bridge carries them up via `updateContext`.
23
+ *
24
+ * Mount it once inside a `PageVariablesProvider`. Outside an `ActionProvider`,
25
+ * `useAction()` falls back to a throwaway runner, so the bridge is a safe no-op
26
+ * (never throws in standalone / test contexts). Renders nothing.
27
+ */
28
+ export function PageVariableActionBridge() {
29
+ const { variables } = usePageVariables();
30
+ const { updateContext } = useAction();
31
+ React.useEffect(() => {
32
+ updateContext({ pageVariables: variables });
33
+ return () => {
34
+ updateContext({ pageVariables: {} });
35
+ };
36
+ }, [variables, updateContext]);
37
+ return null;
38
+ }
39
+ PageVariableActionBridge.displayName = 'PageVariableActionBridge';
@@ -14,6 +14,13 @@ import type { PageVariable } from '@object-ui/types';
14
14
  export interface PageVariablesContextValue {
15
15
  /** Current variable values */
16
16
  variables: Record<string, any>;
17
+ /**
18
+ * The variable definitions backing this provider. Exposed so writer
19
+ * elements can resolve which variable they feed via `PageVariable.source`
20
+ * (the component id that writes to the variable) — see
21
+ * {@link usePageVariableBinding}.
22
+ */
23
+ definitions: PageVariable[];
17
24
  /** Set a single variable value */
18
25
  setVariable: (name: string, value: any) => void;
19
26
  /** Set multiple variable values at once */
@@ -65,3 +72,36 @@ export declare function usePageVariables(): PageVariablesContextValue;
65
72
  * Hook to check if a PageVariablesProvider is available.
66
73
  */
67
74
  export declare function useHasPageVariables(): boolean;
75
+ /**
76
+ * A writer binding for a page variable, resolved from a component id.
77
+ */
78
+ export interface PageVariableBinding {
79
+ /** The bound variable's name */
80
+ name: string;
81
+ /** The bound variable's current value */
82
+ value: any;
83
+ /** Write a new value into the bound variable */
84
+ setValue: (value: any) => void;
85
+ }
86
+ /**
87
+ * Resolve the page variable that a given component writes to.
88
+ *
89
+ * Per `@objectstack/spec`'s `PageVariableSchema`, a variable's `source`
90
+ * names the **component id** that feeds it — e.g. a `variables` entry
91
+ * `{ name: 'selectedProjectId', source: 'project_picker' }` is written by the
92
+ * component whose `id` is `project_picker`. An interactive element (record
93
+ * picker, filter, …) calls this with its own `schema.id` and writes the
94
+ * user's selection through the returned `setValue`.
95
+ *
96
+ * Returns `null` when no variable targets the given component (or when called
97
+ * outside a {@link PageVariablesProvider}), so callers can treat themselves as
98
+ * uncontrolled in that case.
99
+ *
100
+ * @example
101
+ * ```tsx
102
+ * const binding = usePageVariableBinding(schema.id);
103
+ * // on selection:
104
+ * binding?.setValue(record.id);
105
+ * ```
106
+ */
107
+ export declare function usePageVariableBinding(componentId?: string): PageVariableBinding | null;
@@ -71,7 +71,8 @@ export const PageVariablesProvider = ({ definitions, children, }) => {
71
71
  const resetVariables = useCallback(() => {
72
72
  setVariablesState(initializeVariables(definitions));
73
73
  }, [definitions]);
74
- const value = useMemo(() => ({ variables, setVariable, setVariables, resetVariables }), [variables, setVariable, setVariables, resetVariables]);
74
+ const defs = useMemo(() => definitions ?? [], [definitions]);
75
+ const value = useMemo(() => ({ variables, definitions: defs, setVariable, setVariables, resetVariables }), [variables, defs, setVariable, setVariables, resetVariables]);
75
76
  return (_jsx(PageVariablesContext.Provider, { value: value, children: children }));
76
77
  };
77
78
  PageVariablesProvider.displayName = 'PageVariablesProvider';
@@ -94,6 +95,7 @@ export function usePageVariables() {
94
95
  // Graceful fallback — allows components to work outside a Page
95
96
  return {
96
97
  variables: {},
98
+ definitions: [],
97
99
  setVariable: () => { },
98
100
  setVariables: () => { },
99
101
  resetVariables: () => { },
@@ -107,3 +109,39 @@ export function usePageVariables() {
107
109
  export function useHasPageVariables() {
108
110
  return useContext(PageVariablesContext) !== null;
109
111
  }
112
+ /**
113
+ * Resolve the page variable that a given component writes to.
114
+ *
115
+ * Per `@objectstack/spec`'s `PageVariableSchema`, a variable's `source`
116
+ * names the **component id** that feeds it — e.g. a `variables` entry
117
+ * `{ name: 'selectedProjectId', source: 'project_picker' }` is written by the
118
+ * component whose `id` is `project_picker`. An interactive element (record
119
+ * picker, filter, …) calls this with its own `schema.id` and writes the
120
+ * user's selection through the returned `setValue`.
121
+ *
122
+ * Returns `null` when no variable targets the given component (or when called
123
+ * outside a {@link PageVariablesProvider}), so callers can treat themselves as
124
+ * uncontrolled in that case.
125
+ *
126
+ * @example
127
+ * ```tsx
128
+ * const binding = usePageVariableBinding(schema.id);
129
+ * // on selection:
130
+ * binding?.setValue(record.id);
131
+ * ```
132
+ */
133
+ export function usePageVariableBinding(componentId) {
134
+ const { variables, definitions, setVariable } = usePageVariables();
135
+ return useMemo(() => {
136
+ if (!componentId)
137
+ return null;
138
+ const def = definitions.find((d) => d.source === componentId);
139
+ if (!def)
140
+ return null;
141
+ return {
142
+ name: def.name,
143
+ value: variables[def.name],
144
+ setValue: (value) => setVariable(def.name, value),
145
+ };
146
+ }, [componentId, definitions, variables, setVariable]);
147
+ }
@@ -30,13 +30,6 @@ interface PageVariable {
30
30
  defaultValue?: any;
31
31
  source?: string;
32
32
  }
33
- interface BlankPageLayoutItem {
34
- componentId: string;
35
- x: number;
36
- y: number;
37
- width: number;
38
- height: number;
39
- }
40
33
  interface PageSpec {
41
34
  name?: string;
42
35
  label?: string;
@@ -47,13 +40,6 @@ interface PageSpec {
47
40
  regions?: PageRegion[];
48
41
  template?: string;
49
42
  object?: string;
50
- blankLayout?: {
51
- columns?: number;
52
- rowHeight?: number;
53
- gap?: number;
54
- items?: BlankPageLayoutItem[];
55
- };
56
- recordReview?: any;
57
43
  isDefault?: boolean;
58
44
  assignedProfiles?: string[];
59
45
  interfaceConfig?: any;
@@ -72,12 +72,8 @@ export const bridgePage = (spec, _context) => {
72
72
  node.template = spec.template;
73
73
  if (spec.object)
74
74
  node.object = spec.object;
75
- // P1.4 — Blank page layout
76
- if (spec.blankLayout)
77
- node.blankLayout = spec.blankLayout;
78
- // P1.4 — Additional page properties
79
- if (spec.recordReview)
80
- node.recordReview = spec.recordReview;
75
+ // Additional page properties (blankLayout/recordReview dropped — their page
76
+ // types have no renderer and were removed from @objectstack/spec, framework#2265)
81
77
  if (spec.isDefault != null)
82
78
  node.isDefault = spec.isDefault;
83
79
  if (spec.assignedProfiles)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/react",
3
- "version": "7.0.0",
3
+ "version": "7.2.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "React bindings and SchemaRenderer component for Object UI",
@@ -26,10 +26,10 @@
26
26
  "dependencies": {
27
27
  "@objectstack/spec": "^10.0.0",
28
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"
29
+ "@object-ui/core": "7.2.0",
30
+ "@object-ui/data-objectstack": "7.2.0",
31
+ "@object-ui/i18n": "7.2.0",
32
+ "@object-ui/types": "7.2.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^18.0.0 || ^19.0.0",