@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
|
@@ -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": "
|
|
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": "^
|
|
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": "^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
|
-
};
|