@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +786 -0
  2. package/README.md +65 -0
  3. package/dist/SchemaRenderer.js +12 -2
  4. package/dist/context/InlineEditContext.d.ts +21 -0
  5. package/dist/context/InlineEditContext.js +7 -2
  6. package/dist/context/NotificationContext.d.ts +245 -17
  7. package/dist/context/NotificationContext.js +222 -20
  8. package/dist/context/SchemaRendererContext.d.ts +10 -1
  9. package/dist/context/SchemaRendererContext.js +7 -2
  10. package/dist/context/ThemeContext.js +4 -8
  11. package/dist/hooks/index.d.ts +2 -1
  12. package/dist/hooks/index.js +3 -1
  13. package/dist/hooks/useAnimation.d.ts +40 -4
  14. package/dist/hooks/useAnimation.js +41 -10
  15. package/dist/hooks/useCapabilityGate.d.ts +16 -0
  16. package/dist/hooks/useCapabilityGate.js +60 -0
  17. package/dist/hooks/useClientNotifications.d.ts +0 -6
  18. package/dist/hooks/useClientNotifications.js +7 -53
  19. package/dist/hooks/useETagCache.js +4 -0
  20. package/dist/hooks/useExpression.d.ts +8 -2
  21. package/dist/hooks/useExpression.js +9 -1
  22. package/dist/hooks/useFilterScope.d.ts +30 -0
  23. package/dist/hooks/useFilterScope.js +55 -0
  24. package/dist/hooks/useNavigationOverlay.d.ts +11 -0
  25. package/dist/hooks/useNavigationOverlay.js +35 -1
  26. package/dist/hooks/usePageVariables.js +23 -11
  27. package/dist/hooks/useRecordSearch.js +100 -5
  28. package/dist/hooks/useViewData.js +10 -2
  29. package/dist/index.d.ts +3 -1
  30. package/dist/index.js +4 -1
  31. package/dist/spec-bridge/SpecBridge.d.ts +0 -4
  32. package/dist/spec-bridge/SpecBridge.js +0 -12
  33. package/dist/spec-bridge/bridges/list-view.js +5 -11
  34. package/dist/spec-bridge/index.d.ts +0 -2
  35. package/dist/spec-bridge/index.js +0 -2
  36. package/dist/utils/error-message.d.ts +52 -0
  37. package/dist/utils/error-message.js +138 -0
  38. package/package.json +7 -7
  39. package/dist/hooks/useColumnSummary.d.ts +0 -38
  40. package/dist/hooks/useColumnSummary.js +0 -54
  41. package/dist/spec-bridge/bridges/dashboard.d.ts +0 -81
  42. package/dist/spec-bridge/bridges/dashboard.js +0 -121
  43. package/dist/spec-bridge/bridges/page.d.ts +0 -58
  44. package/dist/spec-bridge/bridges/page.js +0 -101
@@ -0,0 +1,52 @@
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
+ * Pull a clean, human-readable message out of a failed write. Strips noisy
10
+ * backend prefixes (`[Security] `, `PERMISSION_DENIED: `) so it reads well in a
11
+ * toast — mirrors `plugin-detail`'s `cleanError`. Returns `null` when nothing
12
+ * usable can be extracted, so callers fall back to a generic localized string.
13
+ */
14
+ export declare function extractWriteErrorMessage(err: unknown): string | null;
15
+ /** One field the server rejected, normalised for `form.setError`. */
16
+ export interface WriteFieldError {
17
+ /** Field name, matching the form control's `name`. */
18
+ field: string;
19
+ /** Human-readable reason, already localized by the server when it can be. */
20
+ message: string;
21
+ }
22
+ /**
23
+ * Per-field errors from a rejected write, or `null` when the failure was not
24
+ * field-scoped.
25
+ *
26
+ * The server has always sent these. `@objectstack/objectql`'s validators throw
27
+ * `{ code: 'VALIDATION_FAILED', fields: [{ field, code, message }] }`, and both
28
+ * the REST layer and the runtime dispatcher serve that as a 400 with `fields[]`
29
+ * intact. What was missing was the last hop: every form caught the rejection,
30
+ * showed one generic toast, and dropped `fields[]` on the floor — so a user told
31
+ * "Validation failed" had to guess WHICH input was wrong, on a surface that
32
+ * already knows how to mark it.
33
+ *
34
+ * Reads three shapes, because the error can arrive from either side of the
35
+ * adapter boundary:
36
+ * - `validationErrors` — a `ValidationError` from `@object-ui/data-objectstack`;
37
+ * - `details.fields` — the raw `@objectstack/client` error, whose `details`
38
+ * falls back to the whole response body;
39
+ * - `fields` — a hand-rolled error of the same shape (the server duck-types
40
+ * these identically, so we do too).
41
+ *
42
+ * Entries without a usable `field` are dropped rather than guessed at: a wrong
43
+ * mark on an innocent input is worse than the generic toast we already show.
44
+ */
45
+ export declare function extractFieldErrors(err: unknown): WriteFieldError[] | null;
46
+ /**
47
+ * True when a failed write was rejected for authorization reasons (HTTP 403 /
48
+ * `PERMISSION_DENIED` / `FORBIDDEN` / a row-level-security denial). Lets a
49
+ * caller show a friendly "you don't have permission" toast rather than dumping
50
+ * the raw server text.
51
+ */
52
+ export declare function isPermissionError(err: unknown): boolean;
@@ -0,0 +1,138 @@
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
+ * Helpers for turning a failed data-source write into user-facing feedback.
10
+ *
11
+ * Different DataSource adapters throw differently shaped errors:
12
+ * - the ObjectStack adapter (`@object-ui/data-objectstack`) decorates the
13
+ * thrown Error with `httpStatus` + a machine-readable `code` and puts the
14
+ * server text on `.message` (sometimes also a parsed body on `.details`);
15
+ * - `ApiDataSource` appends the raw JSON response body as a tail on the
16
+ * message string (`… — {"error":…,"code":…}`).
17
+ *
18
+ * These helpers normalise across both so a caller's `catch` can surface the
19
+ * real reason (e.g. a row-level-security denial) instead of swallowing it.
20
+ */
21
+ function firstString(...vals) {
22
+ for (const v of vals) {
23
+ if (typeof v === 'string' && v.trim())
24
+ return v;
25
+ }
26
+ return null;
27
+ }
28
+ /**
29
+ * Pull a clean, human-readable message out of a failed write. Strips noisy
30
+ * backend prefixes (`[Security] `, `PERMISSION_DENIED: `) so it reads well in a
31
+ * toast — mirrors `plugin-detail`'s `cleanError`. Returns `null` when nothing
32
+ * usable can be extracted, so callers fall back to a generic localized string.
33
+ */
34
+ export function extractWriteErrorMessage(err) {
35
+ const e = err;
36
+ // 1) Structured server body, when the adapter attaches one.
37
+ let msg = e && typeof e === 'object'
38
+ ? firstString(e.details?.error, e.details?.message, e.error)
39
+ : null;
40
+ // 2) Otherwise the Error message itself. ApiDataSource embeds the raw JSON
41
+ // response body as a tail — parse it out when present.
42
+ if (!msg) {
43
+ const raw = err instanceof Error
44
+ ? err.message
45
+ : typeof err === 'string'
46
+ ? err
47
+ : typeof e?.message === 'string'
48
+ ? e.message
49
+ : '';
50
+ const brace = raw.indexOf('{');
51
+ if (brace >= 0) {
52
+ try {
53
+ const body = JSON.parse(raw.slice(brace));
54
+ msg = firstString(body?.error, body?.message);
55
+ }
56
+ catch {
57
+ /* tail wasn't JSON — fall through to the raw message */
58
+ }
59
+ }
60
+ if (!msg && raw.trim())
61
+ msg = raw;
62
+ }
63
+ if (!msg)
64
+ return null;
65
+ const clean = msg
66
+ .replace(/^\[[^\]]+\]\s*/, '')
67
+ .replace(/^[A-Z][A-Z0-9_]+:\s*/, '')
68
+ .trim();
69
+ return clean || null;
70
+ }
71
+ /**
72
+ * Per-field errors from a rejected write, or `null` when the failure was not
73
+ * field-scoped.
74
+ *
75
+ * The server has always sent these. `@objectstack/objectql`'s validators throw
76
+ * `{ code: 'VALIDATION_FAILED', fields: [{ field, code, message }] }`, and both
77
+ * the REST layer and the runtime dispatcher serve that as a 400 with `fields[]`
78
+ * intact. What was missing was the last hop: every form caught the rejection,
79
+ * showed one generic toast, and dropped `fields[]` on the floor — so a user told
80
+ * "Validation failed" had to guess WHICH input was wrong, on a surface that
81
+ * already knows how to mark it.
82
+ *
83
+ * Reads three shapes, because the error can arrive from either side of the
84
+ * adapter boundary:
85
+ * - `validationErrors` — a `ValidationError` from `@object-ui/data-objectstack`;
86
+ * - `details.fields` — the raw `@objectstack/client` error, whose `details`
87
+ * falls back to the whole response body;
88
+ * - `fields` — a hand-rolled error of the same shape (the server duck-types
89
+ * these identically, so we do too).
90
+ *
91
+ * Entries without a usable `field` are dropped rather than guessed at: a wrong
92
+ * mark on an innocent input is worse than the generic toast we already show.
93
+ */
94
+ export function extractFieldErrors(err) {
95
+ const e = err;
96
+ if (!e || typeof e !== 'object')
97
+ return null;
98
+ const raw = (Array.isArray(e.validationErrors) && e.validationErrors) ||
99
+ (Array.isArray(e.details?.fields) && e.details.fields) ||
100
+ (Array.isArray(e.fields) && e.fields) ||
101
+ null;
102
+ if (!raw || !Array.isArray(raw))
103
+ return null;
104
+ const out = [];
105
+ for (const entry of raw) {
106
+ if (!entry || typeof entry !== 'object')
107
+ continue;
108
+ const rec = entry;
109
+ const field = firstString(rec.field, rec.name, rec.path);
110
+ if (!field)
111
+ continue;
112
+ // `code` is the machine enum (`required`, `invalid_option`, …). It is a
113
+ // last resort only: the server builds `message` to be shown verbatim, and
114
+ // an untranslated enum in the UI reads as a bug.
115
+ const message = firstString(rec.message, rec.error, rec.code) ?? '';
116
+ out.push({ field, message });
117
+ }
118
+ return out.length > 0 ? out : null;
119
+ }
120
+ /**
121
+ * True when a failed write was rejected for authorization reasons (HTTP 403 /
122
+ * `PERMISSION_DENIED` / `FORBIDDEN` / a row-level-security denial). Lets a
123
+ * caller show a friendly "you don't have permission" toast rather than dumping
124
+ * the raw server text.
125
+ */
126
+ export function isPermissionError(err) {
127
+ const e = err;
128
+ if (!e || typeof e !== 'object')
129
+ return false;
130
+ const status = e.httpStatus ?? e.status ?? e.statusCode;
131
+ if (status === 403)
132
+ return true;
133
+ const code = firstString(e.code, e.details?.code) ?? '';
134
+ if (code === 'PERMISSION_DENIED' || code === 'FORBIDDEN')
135
+ return true;
136
+ const text = `${typeof e.message === 'string' ? e.message : ''} ${e.details?.error ?? e.error ?? ''}`;
137
+ return /row-level security|access denied|permission denied|not permitted/i.test(text);
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/react",
3
- "version": "16.0.0",
3
+ "version": "17.1.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "React bindings and SchemaRenderer component for Object UI",
@@ -24,12 +24,12 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@objectstack/spec": "^15.1.1",
28
- "react-hook-form": "^7.81.0",
29
- "@object-ui/core": "16.0.0",
30
- "@object-ui/data-objectstack": "16.0.0",
31
- "@object-ui/i18n": "16.0.0",
32
- "@object-ui/types": "16.0.0"
27
+ "@objectstack/spec": "^17.0.0-rc.0",
28
+ "react-hook-form": "^7.83.0",
29
+ "@object-ui/core": "17.1.0",
30
+ "@object-ui/data-objectstack": "17.1.0",
31
+ "@object-ui/i18n": "17.1.0",
32
+ "@object-ui/types": "17.1.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^18.0.0 || ^19.0.0",
@@ -1,38 +0,0 @@
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
- /** Summary function types aligned with ColumnSummarySchema */
9
- export type SummaryFunction = 'sum' | 'avg' | 'count' | 'min' | 'max' | 'distinct';
10
- export interface ColumnSummaryConfig {
11
- /** The field/column to aggregate */
12
- field: string;
13
- /** The aggregation function */
14
- function: SummaryFunction;
15
- /** Optional label for the summary */
16
- label?: string;
17
- /** Number format options */
18
- format?: Intl.NumberFormatOptions;
19
- }
20
- export interface ColumnSummaryResult {
21
- /** The computed value */
22
- value: number;
23
- /** Formatted display string */
24
- formatted: string;
25
- /** The label */
26
- label: string;
27
- }
28
- /**
29
- * Hook for computing column-level summaries (SUM, AVG, COUNT, MIN, MAX, DISTINCT).
30
- * Implements ColumnSummarySchema from @objectstack/spec v2.0.7.
31
- *
32
- * @example
33
- * ```tsx
34
- * const summary = useColumnSummary(data, { field: 'amount', function: 'sum', label: 'Total' });
35
- * // summary.formatted => "$12,345.67"
36
- * ```
37
- */
38
- export declare function useColumnSummary(data: Record<string, unknown>[], config: ColumnSummaryConfig, locale?: string): ColumnSummaryResult;
@@ -1,54 +0,0 @@
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 { useMemo } from 'react';
9
- /**
10
- * Hook for computing column-level summaries (SUM, AVG, COUNT, MIN, MAX, DISTINCT).
11
- * Implements ColumnSummarySchema from @objectstack/spec v2.0.7.
12
- *
13
- * @example
14
- * ```tsx
15
- * const summary = useColumnSummary(data, { field: 'amount', function: 'sum', label: 'Total' });
16
- * // summary.formatted => "$12,345.67"
17
- * ```
18
- */
19
- export function useColumnSummary(data, config, locale) {
20
- return useMemo(() => {
21
- const values = data
22
- .map((row) => {
23
- const val = row[config.field];
24
- return typeof val === 'number' ? val : Number(val);
25
- })
26
- .filter((v) => !isNaN(v));
27
- let value;
28
- switch (config.function) {
29
- case 'sum':
30
- value = values.reduce((acc, v) => acc + v, 0);
31
- break;
32
- case 'avg':
33
- value = values.length > 0 ? values.reduce((acc, v) => acc + v, 0) / values.length : 0;
34
- break;
35
- case 'count':
36
- value = data.length;
37
- break;
38
- case 'min':
39
- value = values.length > 0 ? Math.min(...values) : 0;
40
- break;
41
- case 'max':
42
- value = values.length > 0 ? Math.max(...values) : 0;
43
- break;
44
- case 'distinct':
45
- value = new Set(data.map((row) => row[config.field])).size;
46
- break;
47
- default:
48
- value = 0;
49
- }
50
- const formatted = new Intl.NumberFormat(locale, config.format).format(value);
51
- const label = config.label ?? config.function.toUpperCase();
52
- return { value, formatted, label };
53
- }, [data, config.field, config.function, config.label, config.format, locale]);
54
- }
@@ -1,81 +0,0 @@
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 type { BridgeFn } from '../types';
9
- interface DashboardWidget {
10
- title?: string;
11
- description?: string;
12
- type?: string;
13
- chartConfig?: any;
14
- colorVariant?: string;
15
- actionUrl?: string;
16
- actionType?: string;
17
- actionIcon?: string;
18
- object?: string;
19
- filter?: any;
20
- categoryField?: string;
21
- valueField?: string;
22
- aggregate?: string;
23
- measures?: any[];
24
- layout?: any;
25
- options?: any;
26
- responsive?: any;
27
- aria?: {
28
- ariaLabel?: string;
29
- ariaDescribedBy?: string;
30
- role?: string;
31
- };
32
- }
33
- interface DashboardHeader {
34
- showTitle?: boolean;
35
- showDescription?: boolean;
36
- actions?: Array<{
37
- label: string;
38
- actionUrl?: string;
39
- actionType?: string;
40
- icon?: string;
41
- }>;
42
- [key: string]: any;
43
- }
44
- interface GlobalFilter {
45
- field: string;
46
- label?: string;
47
- type?: string;
48
- options?: string[];
49
- optionsFrom?: {
50
- object: string;
51
- valueField: string;
52
- labelField?: string;
53
- filter?: any;
54
- };
55
- defaultValue?: any;
56
- scope?: string;
57
- targetWidgets?: string[];
58
- }
59
- interface DashboardSpec {
60
- name?: string;
61
- label?: string;
62
- description?: string;
63
- widgets?: DashboardWidget[];
64
- globalFilters?: GlobalFilter[];
65
- header?: DashboardHeader;
66
- refreshInterval?: number;
67
- dateRange?: {
68
- field?: string;
69
- defaultRange?: string;
70
- allowCustomRange?: boolean;
71
- };
72
- aria?: {
73
- ariaLabel?: string;
74
- ariaDescribedBy?: string;
75
- role?: string;
76
- };
77
- performance?: any;
78
- }
79
- /** Transforms a Dashboard spec into a dashboard layout SchemaNode */
80
- export declare const bridgeDashboard: BridgeFn<DashboardSpec>;
81
- export {};
@@ -1,121 +0,0 @@
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
- function mapWidget(widget, index) {
9
- const node = {
10
- type: widget.type ?? 'chart',
11
- id: `widget-${index}`,
12
- };
13
- if (widget.title)
14
- node.title = widget.title;
15
- if (widget.description)
16
- node.description = widget.description;
17
- if (widget.chartConfig)
18
- node.chartConfig = widget.chartConfig;
19
- if (widget.colorVariant)
20
- node.colorVariant = widget.colorVariant;
21
- if (widget.actionUrl)
22
- node.actionUrl = widget.actionUrl;
23
- if (widget.actionType)
24
- node.actionType = widget.actionType;
25
- if (widget.actionIcon)
26
- node.actionIcon = widget.actionIcon;
27
- if (widget.object)
28
- node.object = widget.object;
29
- if (widget.filter)
30
- node.filter = widget.filter;
31
- if (widget.categoryField)
32
- node.categoryField = widget.categoryField;
33
- if (widget.valueField)
34
- node.valueField = widget.valueField;
35
- if (widget.aggregate)
36
- node.aggregate = widget.aggregate;
37
- if (widget.measures)
38
- node.measures = widget.measures;
39
- if (widget.layout)
40
- node.layout = widget.layout;
41
- if (widget.options)
42
- node.options = widget.options;
43
- if (widget.responsive)
44
- node.responsive = widget.responsive;
45
- if (widget.aria)
46
- node.aria = widget.aria;
47
- return node;
48
- }
49
- function mapGlobalFilters(filters) {
50
- return {
51
- type: 'filter-bar',
52
- id: 'dashboard-filters',
53
- filters: filters.map((f) => {
54
- const mapped = { field: f.field };
55
- if (f.label)
56
- mapped.label = f.label;
57
- if (f.type)
58
- mapped.type = f.type;
59
- if (f.options)
60
- mapped.options = f.options;
61
- if (f.optionsFrom)
62
- mapped.optionsFrom = f.optionsFrom;
63
- if (f.defaultValue != null)
64
- mapped.defaultValue = f.defaultValue;
65
- if (f.scope)
66
- mapped.scope = f.scope;
67
- if (f.targetWidgets)
68
- mapped.targetWidgets = f.targetWidgets;
69
- return mapped;
70
- }),
71
- };
72
- }
73
- function mapHeader(header) {
74
- const node = {
75
- type: 'dashboard-header',
76
- id: 'dashboard-header',
77
- ...header,
78
- };
79
- return node;
80
- }
81
- /** Transforms a Dashboard spec into a dashboard layout SchemaNode */
82
- export const bridgeDashboard = (spec, _context) => {
83
- const widgetNodes = (spec.widgets ?? []).map(mapWidget);
84
- const bodyChildren = [];
85
- if (spec.header) {
86
- bodyChildren.push(mapHeader(spec.header));
87
- }
88
- if (spec.globalFilters && spec.globalFilters.length > 0) {
89
- bodyChildren.push(mapGlobalFilters(spec.globalFilters));
90
- }
91
- // P1.3 — Date range filter
92
- if (spec.dateRange) {
93
- bodyChildren.push({
94
- type: 'date-range-filter',
95
- id: 'dashboard-date-range',
96
- ...spec.dateRange,
97
- });
98
- }
99
- bodyChildren.push({
100
- type: 'widget-grid',
101
- id: 'dashboard-widgets',
102
- body: widgetNodes,
103
- });
104
- const node = {
105
- type: 'dashboard',
106
- id: spec.name,
107
- body: bodyChildren,
108
- };
109
- if (spec.label)
110
- node.label = spec.label;
111
- if (spec.description)
112
- node.description = spec.description;
113
- if (spec.refreshInterval != null)
114
- node.refreshInterval = spec.refreshInterval;
115
- // P1.6 — i18n & ARIA
116
- if (spec.aria)
117
- node.aria = spec.aria;
118
- if (spec.performance)
119
- node.performance = spec.performance;
120
- return node;
121
- };
@@ -1,58 +0,0 @@
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 type { BridgeFn } from '../types';
9
- interface PageComponent {
10
- type: string;
11
- id?: string;
12
- label?: string;
13
- properties?: Record<string, any>;
14
- events?: Record<string, any>;
15
- style?: Record<string, any>;
16
- responsiveStyles?: Record<string, any>;
17
- className?: string;
18
- /** Canonical conditional-visibility predicate (ADR-0089). */
19
- visibleWhen?: string;
20
- /** @deprecated ADR-0089 → `visibleWhen`. */
21
- visibility?: string;
22
- dataSource?: any;
23
- responsive?: any;
24
- aria?: Record<string, string>;
25
- }
26
- interface PageRegion {
27
- name?: string;
28
- width?: string;
29
- components?: PageComponent[];
30
- }
31
- interface PageVariable {
32
- name: string;
33
- type?: string;
34
- defaultValue?: any;
35
- source?: string;
36
- }
37
- interface PageSpec {
38
- name?: string;
39
- label?: string;
40
- description?: string;
41
- icon?: string;
42
- type?: string;
43
- variables?: PageVariable[];
44
- regions?: PageRegion[];
45
- template?: string;
46
- object?: string;
47
- isDefault?: boolean;
48
- assignedProfiles?: string[];
49
- interfaceConfig?: any;
50
- aria?: {
51
- ariaLabel?: string;
52
- ariaDescribedBy?: string;
53
- role?: string;
54
- };
55
- }
56
- /** Transforms a Page spec into a page layout SchemaNode */
57
- export declare const bridgePage: BridgeFn<PageSpec>;
58
- export {};
@@ -1,101 +0,0 @@
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
- function mapComponent(component) {
9
- const node = {
10
- type: component.type,
11
- id: component.id,
12
- };
13
- if (component.label)
14
- node.label = component.label;
15
- if (component.className)
16
- node.className = component.className;
17
- if (component.style)
18
- node.style = component.style;
19
- // ADR-0065 scoped per-breakpoint styles — declared on the page-spec
20
- // component (sibling of className/style) but previously dropped here
21
- // before reaching SchemaRenderer, so a layout override like
22
- // `{ large: { display: 'grid', gridTemplateColumns: '...' } }` never
23
- // compiled to CSS and the node fell back to its default layout.
24
- if (component.responsiveStyles)
25
- node.responsiveStyles = component.responsiveStyles;
26
- if (component.properties) {
27
- // Avoid overwriting the component dispatch keys (`type`/`id`) with inner
28
- // renderer-specific props. E.g. PageTabsProps.properties.type is the tab
29
- // visual style ('line'|'card'|'pill'), NOT the component type.
30
- for (const [k, v] of Object.entries(component.properties)) {
31
- if (k === 'type' || k === 'id')
32
- continue;
33
- node[k] = v;
34
- }
35
- // Preserve the original `properties` bag so renderers can still read
36
- // collision-prone keys via `schema.properties.<key>`.
37
- node.properties = component.properties;
38
- }
39
- if (component.events)
40
- node.events = component.events;
41
- // ADR-0089: `visibleWhen` is the canonical conditional-visibility predicate;
42
- // the spec folds the deprecated `visibility` alias into it at parse, so prefer
43
- // it and fall back to `visibility` for raw / un-normalized metadata. SchemaRenderer
44
- // reads `visibleWhen` first (show-when-truthy).
45
- const visiblePredicate = component.visibleWhen ?? component.visibility;
46
- if (visiblePredicate)
47
- node.visibleWhen = visiblePredicate;
48
- if (component.dataSource)
49
- node.dataSource = component.dataSource;
50
- if (component.responsive)
51
- node.responsive = component.responsive;
52
- if (component.aria)
53
- node.aria = component.aria;
54
- return node;
55
- }
56
- function mapRegion(region) {
57
- const children = (region.components ?? []).map(mapComponent);
58
- const node = {
59
- type: 'page-region',
60
- id: region.name,
61
- body: children,
62
- };
63
- if (region.width)
64
- node.width = region.width;
65
- return node;
66
- }
67
- /** Transforms a Page spec into a page layout SchemaNode */
68
- export const bridgePage = (spec, _context) => {
69
- const regions = (spec.regions ?? []).map(mapRegion);
70
- const node = {
71
- type: 'page',
72
- id: spec.name,
73
- body: regions,
74
- };
75
- if (spec.label)
76
- node.label = spec.label;
77
- if (spec.description)
78
- node.description = spec.description;
79
- if (spec.icon)
80
- node.icon = spec.icon;
81
- if (spec.type)
82
- node.pageType = spec.type;
83
- if (spec.variables)
84
- node.variables = spec.variables;
85
- if (spec.template)
86
- node.template = spec.template;
87
- if (spec.object)
88
- node.object = spec.object;
89
- // Additional page properties (blankLayout/recordReview dropped — their page
90
- // types have no renderer and were removed from @objectstack/spec, framework#2265)
91
- if (spec.isDefault != null)
92
- node.isDefault = spec.isDefault;
93
- if (spec.assignedProfiles)
94
- node.assignedProfiles = spec.assignedProfiles;
95
- if (spec.interfaceConfig)
96
- node.interfaceConfig = spec.interfaceConfig;
97
- // P1.6 — i18n & ARIA
98
- if (spec.aria)
99
- node.aria = spec.aria;
100
- return node;
101
- };