@object-ui/core 17.3.0 → 17.5.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 +1224 -0
- package/README.md +2 -12
- package/dist/actions/ActionEngine.d.ts +42 -25
- package/dist/actions/ActionEngine.js +66 -52
- package/dist/actions/ActionRunner.d.ts +199 -21
- package/dist/actions/ActionRunner.js +332 -16
- package/dist/actions/actionKeys.d.ts +96 -20
- package/dist/actions/actionKeys.js +157 -18
- package/dist/data-scope/ViewDataProvider.d.ts +37 -12
- package/dist/data-scope/ViewDataProvider.js +83 -5
- package/dist/data-scope/element-data-source.d.ts +113 -0
- package/dist/data-scope/element-data-source.js +216 -0
- package/dist/data-scope/index.d.ts +2 -1
- package/dist/data-scope/index.js +1 -0
- package/dist/evaluator/declaredPredicate.d.ts +119 -0
- package/dist/evaluator/declaredPredicate.js +152 -0
- package/dist/evaluator/fieldRules.d.ts +16 -0
- package/dist/evaluator/fieldRules.js +51 -16
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/evaluator/listConditional.d.ts +5 -2
- package/dist/evaluator/listConditional.js +58 -16
- package/dist/evaluator/predicateInput.d.ts +51 -3
- package/dist/evaluator/predicateInput.js +62 -5
- package/dist/index.d.ts +4 -1
- package/dist/index.js +14 -1
- package/dist/protocols/DndProtocol.d.ts +42 -1
- package/dist/protocols/KeyboardProtocol.d.ts +31 -1
- package/dist/protocols/SharingProtocol.d.ts +11 -1
- package/dist/registry/Registry.d.ts +24 -0
- package/dist/theme/ThemeEngine.d.ts +0 -8
- package/dist/theme/ThemeEngine.js +22 -103
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +7 -1
- package/dist/types/index.d.ts +42 -8
- package/dist/utils/chart-series.d.ts +258 -5
- package/dist/utils/chart-series.js +423 -20
- package/dist/utils/compare-to.d.ts +40 -13
- package/dist/utils/compare-to.js +4 -32
- package/dist/utils/dashboard-filters.d.ts +54 -8
- package/dist/utils/dashboard-filters.js +261 -12
- package/dist/utils/dataset-format.d.ts +73 -14
- package/dist/utils/dataset-format.js +130 -9
- package/dist/utils/dataset-pivot.d.ts +28 -7
- package/dist/utils/dataset-pivot.js +27 -6
- package/dist/utils/dom-props.d.ts +137 -0
- package/dist/utils/dom-props.js +153 -0
- package/dist/utils/normalize-list-view.d.ts +26 -6
- package/dist/utils/normalize-list-view.js +30 -8
- package/dist/utils/number-display.d.ts +151 -0
- package/dist/utils/number-display.js +71 -0
- package/dist/utils/resolve-view-id.d.ts +31 -0
- package/dist/utils/resolve-view-id.js +45 -0
- package/dist/utils/sort-query.d.ts +55 -0
- package/dist/utils/sort-query.js +35 -0
- package/package.json +5 -5
- package/dist/utils/merge-views-into-objects.d.ts +0 -19
- package/dist/utils/merge-views-into-objects.js +0 -39
|
@@ -116,41 +116,33 @@ export function generateColorVars(colors) {
|
|
|
116
116
|
/**
|
|
117
117
|
* Generate CSS custom properties from a Theme's typography config.
|
|
118
118
|
*/
|
|
119
|
+
// ----------------------------------------------------------------------------
|
|
120
|
+
// RETIRED THEME BLOCKS — @objectstack/spec 17.0.0-rc.3 (objectstack#5021 option
|
|
121
|
+
// 2, PR objectstack#5289), objectui#3361.
|
|
122
|
+
//
|
|
123
|
+
// `theme.animation`, `theme.zIndex` and five typography groups
|
|
124
|
+
// (`fontSize` / `fontWeight` / `lineHeight` / `letterSpacing`, plus
|
|
125
|
+
// `fontFamily.heading` / `fontFamily.mono`) became TOMBSTONES: the schema now
|
|
126
|
+
// rejects them by name and their prescription points at `theme.customVars`, the
|
|
127
|
+
// declared — and since #5021 the only — door for a custom property. A
|
|
128
|
+
// `--z-modal` or a `--duration-fast` is authored there now, emitted verbatim as
|
|
129
|
+
// `--<key>: <value>`.
|
|
130
|
+
//
|
|
131
|
+
// The emission code below them was therefore structurally dead: no author can
|
|
132
|
+
// produce the input that would reach it. It is removed rather than left behind
|
|
133
|
+
// a cast, because the tombstoned keys type as `never` and any cast that made
|
|
134
|
+
// them compile would fossilize a shape the contract has withdrawn (AGENTS.md
|
|
135
|
+
// #0.1). LIVE emission — `colors`, `borderRadius`, `shadows`,
|
|
136
|
+
// `typography.fontFamily.base` (→ `--font-sans`) and `customVars` — is
|
|
137
|
+
// untouched, byte for byte.
|
|
138
|
+
// ----------------------------------------------------------------------------
|
|
119
139
|
export function generateTypographyVars(typography) {
|
|
120
140
|
const vars = {};
|
|
141
|
+
// `fontFamily.base` is the only surviving typography input — see the RETIRED
|
|
142
|
+
// THEME BLOCKS note above for where the other five groups went.
|
|
121
143
|
if (typography.fontFamily?.base) {
|
|
122
144
|
vars['--font-sans'] = typography.fontFamily.base;
|
|
123
145
|
}
|
|
124
|
-
if (typography.fontFamily?.heading) {
|
|
125
|
-
vars['--font-heading'] = typography.fontFamily.heading;
|
|
126
|
-
}
|
|
127
|
-
if (typography.fontFamily?.mono) {
|
|
128
|
-
vars['--font-mono'] = typography.fontFamily.mono;
|
|
129
|
-
}
|
|
130
|
-
if (typography.fontSize) {
|
|
131
|
-
for (const [key, value] of Object.entries(typography.fontSize)) {
|
|
132
|
-
if (value)
|
|
133
|
-
vars[`--font-size-${key}`] = value;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (typography.fontWeight) {
|
|
137
|
-
for (const [key, value] of Object.entries(typography.fontWeight)) {
|
|
138
|
-
if (value != null)
|
|
139
|
-
vars[`--font-weight-${key}`] = String(value);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (typography.lineHeight) {
|
|
143
|
-
for (const [key, value] of Object.entries(typography.lineHeight)) {
|
|
144
|
-
if (value)
|
|
145
|
-
vars[`--line-height-${key}`] = value;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (typography.letterSpacing) {
|
|
149
|
-
for (const [key, value] of Object.entries(typography.letterSpacing)) {
|
|
150
|
-
if (value)
|
|
151
|
-
vars[`--letter-spacing-${key}`] = value;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
146
|
return vars;
|
|
155
147
|
}
|
|
156
148
|
/**
|
|
@@ -197,36 +189,6 @@ export function generateShadowVars(shadows) {
|
|
|
197
189
|
}
|
|
198
190
|
return vars;
|
|
199
191
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Generate CSS custom properties from a Theme's animation config.
|
|
202
|
-
*/
|
|
203
|
-
export function generateAnimationVars(animation) {
|
|
204
|
-
const vars = {};
|
|
205
|
-
if (animation.duration) {
|
|
206
|
-
for (const [key, value] of Object.entries(animation.duration)) {
|
|
207
|
-
if (value)
|
|
208
|
-
vars[`--duration-${key}`] = value;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (animation.timing) {
|
|
212
|
-
for (const [key, value] of Object.entries(animation.timing)) {
|
|
213
|
-
if (value)
|
|
214
|
-
vars[`--timing-${key}`] = value;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return vars;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Generate CSS custom properties from a Theme's z-index config.
|
|
221
|
-
*/
|
|
222
|
-
export function generateZIndexVars(zIndex) {
|
|
223
|
-
const vars = {};
|
|
224
|
-
for (const [key, value] of Object.entries(zIndex)) {
|
|
225
|
-
if (value != null)
|
|
226
|
-
vars[`--z-${key}`] = String(value);
|
|
227
|
-
}
|
|
228
|
-
return vars;
|
|
229
|
-
}
|
|
230
192
|
/**
|
|
231
193
|
* Generate ALL CSS custom properties from a complete Theme.
|
|
232
194
|
* This is the main entry point for theme → CSS conversion.
|
|
@@ -247,14 +209,6 @@ export function generateThemeVars(theme) {
|
|
|
247
209
|
if (theme.shadows) {
|
|
248
210
|
Object.assign(vars, generateShadowVars(theme.shadows));
|
|
249
211
|
}
|
|
250
|
-
// Animation
|
|
251
|
-
if (theme.animation) {
|
|
252
|
-
Object.assign(vars, generateAnimationVars(theme.animation));
|
|
253
|
-
}
|
|
254
|
-
// Z-Index
|
|
255
|
-
if (theme.zIndex) {
|
|
256
|
-
Object.assign(vars, generateZIndexVars(theme.zIndex));
|
|
257
|
-
}
|
|
258
212
|
// Custom CSS variables (passthrough)
|
|
259
213
|
if (theme.customVars) {
|
|
260
214
|
for (const [key, value] of Object.entries(theme.customVars)) {
|
|
@@ -290,22 +244,6 @@ export function mergeThemes(parent, child) {
|
|
|
290
244
|
...parent.typography?.fontFamily,
|
|
291
245
|
...child.typography?.fontFamily,
|
|
292
246
|
},
|
|
293
|
-
fontSize: {
|
|
294
|
-
...parent.typography?.fontSize,
|
|
295
|
-
...child.typography?.fontSize,
|
|
296
|
-
},
|
|
297
|
-
fontWeight: {
|
|
298
|
-
...parent.typography?.fontWeight,
|
|
299
|
-
...child.typography?.fontWeight,
|
|
300
|
-
},
|
|
301
|
-
lineHeight: {
|
|
302
|
-
...parent.typography?.lineHeight,
|
|
303
|
-
...child.typography?.lineHeight,
|
|
304
|
-
},
|
|
305
|
-
letterSpacing: {
|
|
306
|
-
...parent.typography?.letterSpacing,
|
|
307
|
-
...child.typography?.letterSpacing,
|
|
308
|
-
},
|
|
309
247
|
}
|
|
310
248
|
: undefined,
|
|
311
249
|
// Deep-merge border radius
|
|
@@ -316,25 +254,6 @@ export function mergeThemes(parent, child) {
|
|
|
316
254
|
shadows: child.shadows || parent.shadows
|
|
317
255
|
? { ...parent.shadows, ...child.shadows }
|
|
318
256
|
: undefined,
|
|
319
|
-
// Deep-merge animation
|
|
320
|
-
animation: child.animation || parent.animation
|
|
321
|
-
? {
|
|
322
|
-
...parent.animation,
|
|
323
|
-
...child.animation,
|
|
324
|
-
duration: {
|
|
325
|
-
...parent.animation?.duration,
|
|
326
|
-
...child.animation?.duration,
|
|
327
|
-
},
|
|
328
|
-
timing: {
|
|
329
|
-
...parent.animation?.timing,
|
|
330
|
-
...child.animation?.timing,
|
|
331
|
-
},
|
|
332
|
-
}
|
|
333
|
-
: undefined,
|
|
334
|
-
// Deep-merge zIndex
|
|
335
|
-
zIndex: child.zIndex || parent.zIndex
|
|
336
|
-
? { ...parent.zIndex, ...child.zIndex }
|
|
337
|
-
: undefined,
|
|
338
257
|
// Deep-merge customVars
|
|
339
258
|
customVars: child.customVars || parent.customVars
|
|
340
259
|
? { ...parent.customVars, ...child.customVars }
|
package/dist/theme/index.d.ts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
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
|
-
export { hexToHSL, toCSSColor, generateColorVars, generateTypographyVars, generateBorderRadiusVars, generateShadowVars,
|
|
8
|
+
export { hexToHSL, toCSSColor, generateColorVars, generateTypographyVars, generateBorderRadiusVars, generateShadowVars, generateThemeVars, mergeThemes, resolveThemeInheritance, resolveMode, contrastRatio, meetsContrastLevel, } from './ThemeEngine';
|
package/dist/theme/index.js
CHANGED
|
@@ -5,4 +5,10 @@
|
|
|
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
|
-
export { hexToHSL, toCSSColor, generateColorVars, generateTypographyVars, generateBorderRadiusVars, generateShadowVars,
|
|
8
|
+
export { hexToHSL, toCSSColor, generateColorVars, generateTypographyVars, generateBorderRadiusVars, generateShadowVars,
|
|
9
|
+
// `generateAnimationVars` / `generateZIndexVars` removed with the
|
|
10
|
+
// `theme.animation` / `theme.zIndex` tombstones in @objectstack/spec
|
|
11
|
+
// 17.0.0-rc.3 (objectstack#5021) — see ThemeEngine's RETIRED THEME BLOCKS
|
|
12
|
+
// note. `theme.customVars` is the declared door for a `--duration-*` or
|
|
13
|
+
// `--z-*` now.
|
|
14
|
+
generateThemeVars, mergeThemes, resolveThemeInheritance, resolveMode, contrastRatio, meetsContrastLevel, } from './ThemeEngine';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,14 +5,48 @@
|
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
/**
|
|
9
|
+
* One `SchemaNode` (objectui#4580).
|
|
10
|
+
*
|
|
11
|
+
* This package used to hand-declare `interface SchemaNode { type: string; …
|
|
12
|
+
* [key: string]: any }` here, while `@object-ui/types` exported
|
|
13
|
+
* `type SchemaNode = BaseSchema | string | number | boolean | null | undefined`
|
|
14
|
+
* — two different types under one name, from two packages the same consumers
|
|
15
|
+
* import together. Which declaration a call site got depended on which package
|
|
16
|
+
* it happened to import from, and #4548's option-A canary measured **19 of 35**
|
|
17
|
+
* errors as exactly that collision:
|
|
18
|
+
*
|
|
19
|
+
* ```
|
|
20
|
+
* Type 'import(".../packages/types/dist/base").SchemaNode' is not assignable to
|
|
21
|
+
* type 'import(".../packages/core/dist/types/index").SchemaNode'
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* PR #4578 sidestepped it — `SchemaRenderer` states its own component-level
|
|
25
|
+
* union rather than picking a side — and left the reconciliation to this card.
|
|
26
|
+
*
|
|
27
|
+
* **`@object-ui/types`' union wins**, per #4580's ruling 1: it is the spec of
|
|
28
|
+
* record (its own doc comment names `'Plain string'` a valid node), and it is
|
|
29
|
+
* the side the measured collisions resolve toward. The declaration is RE-EXPORTED
|
|
30
|
+
* rather than restated, so there is exactly one declaration left to disagree
|
|
31
|
+
* with — a structural copy would reproduce the defect the moment either side
|
|
32
|
+
* moved. This package already depends on `@object-ui/types`, so the edge exists
|
|
33
|
+
* and adds no cycle, and core's own entry surface is unchanged (`dist/index.d.ts`
|
|
34
|
+
* is byte-identical across the change — measured, both rounds).
|
|
35
|
+
*
|
|
36
|
+
* The collision is only observable from a package that resolves BOTH through
|
|
37
|
+
* `node_modules`; the pin therefore lives in `@object-ui/react`
|
|
38
|
+
* (`src/__tests__/SchemaNode.reconciliation.test.ts`), not here.
|
|
39
|
+
*/
|
|
40
|
+
export type { SchemaNode } from '@object-ui/types';
|
|
41
|
+
import type { SchemaNode } from '@object-ui/types';
|
|
42
|
+
/**
|
|
43
|
+
* ⛔ Deliberately NOT reconciled with `@object-ui/types`' `ComponentRendererProps`
|
|
44
|
+
* (objectui#4594). The two declarations differ — types' is generic
|
|
45
|
+
* (`< TSchema extends BaseSchema = BaseSchema >`), this one is not — but that
|
|
46
|
+
* card measured **zero consumers** of this declaration, so reconciling it here
|
|
47
|
+
* would be an unmeasured change riding a card that was scoped to `SchemaNode`.
|
|
48
|
+
* It stays dual-declared until #4594 is dispatched on its own evidence.
|
|
49
|
+
*/
|
|
16
50
|
export interface ComponentRendererProps {
|
|
17
51
|
schema: SchemaNode;
|
|
18
52
|
[key: string]: any;
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
* visible instead of just repeating the x-axis label.
|
|
21
21
|
* - **otherwise** (single dimension, or multiple measures) → first dimension is
|
|
22
22
|
* the x-axis and each measure is its own series (long format passes through).
|
|
23
|
+
*
|
|
24
|
+
* BOTH branches map a NULL first-dimension VALUE to an explicit bucket label
|
|
25
|
+
* ({@link NULL_CATEGORY_LABEL}) so the group renders instead of vanishing —
|
|
26
|
+
* objectui#4466 for the single-dimension branch, objectui#4497 for the pivot.
|
|
27
|
+
* One doctrine, one predicate (`isNullCategory`), two call sites.
|
|
23
28
|
*/
|
|
24
29
|
export interface ChartResultField {
|
|
25
30
|
name: string;
|
|
@@ -47,7 +52,36 @@ export interface ChartSeriesResult {
|
|
|
47
52
|
xAxisKey: string | undefined;
|
|
48
53
|
series: ChartSeriesBinding[];
|
|
49
54
|
}
|
|
50
|
-
|
|
55
|
+
/**
|
|
56
|
+
* The bucket a NULL/undefined category VALUE renders under, when the caller
|
|
57
|
+
* supplies no localized label of its own (objectui#4466).
|
|
58
|
+
*
|
|
59
|
+
* English by construction and deliberately so: this package is React-free and
|
|
60
|
+
* i18n-free (see {@link OptionLabelTranslator} for the same reason stated one
|
|
61
|
+
* layer down), so the locale bundle cannot be read here. Every renderer that
|
|
62
|
+
* has an i18n provider passes its own resolved label through
|
|
63
|
+
* {@link ChartSeriesOptions.nullCategoryLabel} — this constant is the floor
|
|
64
|
+
* that keeps a provider-less host (or a caller that has not been wired yet)
|
|
65
|
+
* drawing the group rather than dropping it.
|
|
66
|
+
*
|
|
67
|
+
* Exported so a consumer can recognise the bucket it will be handed —
|
|
68
|
+
* {@link findChartSeriesRow} uses the same default, which is what makes a
|
|
69
|
+
* clicked bucket bar resolve back to its (raw null) dataset row.
|
|
70
|
+
*/
|
|
71
|
+
export declare const NULL_CATEGORY_LABEL = "(None)";
|
|
72
|
+
/** Per-call knobs shared by {@link buildChartSeries} / {@link findChartSeriesRow}. */
|
|
73
|
+
export interface ChartSeriesOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Display label for a category whose value is `null`/`undefined`. Defaults to
|
|
76
|
+
* {@link NULL_CATEGORY_LABEL}.
|
|
77
|
+
*
|
|
78
|
+
* Pass the SAME value to both helpers: `buildChartSeries` writes it into the
|
|
79
|
+
* rendered rows and `findChartSeriesRow` matches a clicked category against
|
|
80
|
+
* it, so a mismatch costs the null bucket its drill-through.
|
|
81
|
+
*/
|
|
82
|
+
nullCategoryLabel?: string;
|
|
83
|
+
}
|
|
84
|
+
export declare function buildChartSeries(rows: Array<Record<string, unknown>> | null | undefined, dimensions: string[] | null | undefined, values: string[] | null | undefined, fields?: ChartResultField[] | null, options?: ChartSeriesOptions): ChartSeriesResult;
|
|
51
85
|
/**
|
|
52
86
|
* Resolve select/enum dimension VALUES to display LABELS in chart rows.
|
|
53
87
|
*
|
|
@@ -85,8 +119,28 @@ export declare function relabelDimensions(rows: Array<Record<string, unknown>> |
|
|
|
85
119
|
*
|
|
86
120
|
* Comparison is string-wise on the rows' display values (which is what the chart
|
|
87
121
|
* surfaces as `category` / series key). Returns `-1` when nothing matches.
|
|
122
|
+
*
|
|
123
|
+
* The NULL-category bucket {@link buildChartSeries} renders (objectui#4466) is
|
|
124
|
+
* matched back to its row here, and it has to be: the caller passes the rows it
|
|
125
|
+
* charted FROM (still carrying the raw null) while the click event carries the
|
|
126
|
+
* bucket LABEL, so without this the one bar the fix made visible would resolve
|
|
127
|
+
* to `-1` and its drill-through would silently no-op. Pass the same
|
|
128
|
+
* `nullCategoryLabel` both helpers were given. The pre-existing `''` spelling
|
|
129
|
+
* of "no group value" still matches too — `computeDrillFilter` writes that one.
|
|
130
|
+
*
|
|
131
|
+
* **`xOf` covers BOTH arms, which is what let objectui#4497 bucket the pivot
|
|
132
|
+
* branch without touching this function.** Worth stating because the reverse
|
|
133
|
+
* would be silent: the pivot's emitted rows are AGGREGATED (`byX` collapses N
|
|
134
|
+
* raw rows into one bucket per first-dimension value), so they are NOT
|
|
135
|
+
* index-aligned with anything, and a caller cannot drill by the emitted row's
|
|
136
|
+
* position. It drills by SEARCHING the raw rows here and indexing
|
|
137
|
+
* `drillRawRows` with what this returns — see `DatasetWidget.handleChartDrill`,
|
|
138
|
+
* the one production caller. So the pivot's map key and its emitted display
|
|
139
|
+
* value being two different things never reached the drill: the raw rows this
|
|
140
|
+
* searches still carry their null either way, and `xOf` reads the label back to
|
|
141
|
+
* them in the multi-dimension arm exactly as in the single-dimension one.
|
|
88
142
|
*/
|
|
89
|
-
export declare function findChartSeriesRow(rows: Array<Record<string, unknown>> | null | undefined, dimensions: string[] | null | undefined, values: string[] | null | undefined, category: string | undefined, seriesKey?: string): number;
|
|
143
|
+
export declare function findChartSeriesRow(rows: Array<Record<string, unknown>> | null | undefined, dimensions: string[] | null | undefined, values: string[] | null | undefined, category: string | undefined, seriesKey?: string, options?: ChartSeriesOptions): number;
|
|
90
144
|
/**
|
|
91
145
|
* Build a per-category colour map from a select/lookup field's `options`.
|
|
92
146
|
*
|
|
@@ -129,14 +183,213 @@ export declare function buildCategoryOrder(options: unknown): string[] | null;
|
|
|
129
183
|
* after the declared ones rather than dropping them).
|
|
130
184
|
*/
|
|
131
185
|
export declare function buildCategoryRank(order: string[] | null | undefined): Map<string, number> | null;
|
|
186
|
+
/**
|
|
187
|
+
* The i18n seam of the analytics label net (objectui#4030).
|
|
188
|
+
*
|
|
189
|
+
* `(storedValue, authoredLabel) => displayLabel` — the SAME signature
|
|
190
|
+
* `resolveGroupByLabels` (plugin-charts) already takes for the legacy aggregate
|
|
191
|
+
* path, which callers bind to `useSafeFieldLabel().fieldOptionLabel(object,
|
|
192
|
+
* field, …)`. That resolver reads `{ns}.fieldOptions.<object>.<field>.<value>`,
|
|
193
|
+
* the one convention list and form surfaces already translate select options
|
|
194
|
+
* through (`useObjectLabel.translateOptions`, and `@objectstack/spec` names
|
|
195
|
+
* objectui as its reader). Analytics reuses that channel rather than growing a
|
|
196
|
+
* chart-side per-locale dialect.
|
|
197
|
+
*
|
|
198
|
+
* Omitted everywhere it isn't available (no i18n provider, an unresolvable
|
|
199
|
+
* owning object): every helper below then behaves exactly as it did before this
|
|
200
|
+
* seam existed.
|
|
201
|
+
*/
|
|
202
|
+
export type OptionLabelTranslator = (value: string, authoredLabel: string) => string;
|
|
203
|
+
/**
|
|
204
|
+
* Run a select field's `options` through the locale bundle, returning options
|
|
205
|
+
* whose `label` is the translated one (objectui#4030).
|
|
206
|
+
*
|
|
207
|
+
* The mirror of `useObjectLabel().translateOptions` — the channel list and form
|
|
208
|
+
* surfaces localize select options through — kept pure and shape-tolerant so
|
|
209
|
+
* the analytics net can apply it at the point the resolved options are read.
|
|
210
|
+
* Everything downstream ({@link buildOptionColorMap},
|
|
211
|
+
* {@link buildCategoryOrder}) keeps reading `option.label` and needs no
|
|
212
|
+
* knowledge of i18n, exactly like `SelectCellRenderer` on the list side.
|
|
213
|
+
*
|
|
214
|
+
* Colour, order and every other option key survive untouched — only `label`
|
|
215
|
+
* changes. Returns the input ARRAY ITSELF when there is no translator or
|
|
216
|
+
* nothing translated, so an untranslated app keeps the identities (and the
|
|
217
|
+
* memo/render behaviour) it had before.
|
|
218
|
+
*/
|
|
219
|
+
export declare function localizeFieldOptions(options: unknown, translate?: OptionLabelTranslator): unknown;
|
|
132
220
|
/**
|
|
133
221
|
* Build a `{ value → label }` map from a select/enum field's `options`, for
|
|
134
222
|
* resolving a grouped dimension's stored value to its display label (fed to
|
|
135
223
|
* {@link relabelDimensions}). Mirrors {@link buildOptionColorMap}.
|
|
136
224
|
*
|
|
137
225
|
* Options may be `{ value, label }` objects or bare strings (value == label —
|
|
138
|
-
* nothing to relabel). Only entries whose
|
|
139
|
-
*
|
|
226
|
+
* nothing to relabel). Only entries whose display label actually differs from
|
|
227
|
+
* the key are kept, so the map is empty (→ `null`) when relabeling would be a
|
|
140
228
|
* no-op and the caller can skip it entirely.
|
|
229
|
+
*
|
|
230
|
+
* **With a `translate` seam (objectui#4030) the map gains a SECOND key per
|
|
231
|
+
* option: the AUTHORED label.** A dimension's rows reach this net keyed either
|
|
232
|
+
* way — value-keyed when the server did not resolve the dimension (the whole
|
|
233
|
+
* reason this net exists), already label-keyed when it did (ADR-0021) — and
|
|
234
|
+
* the reported symptom is the second case: a chart legend showing the object's
|
|
235
|
+
* English `label` verbatim beside a related list showing the translation. One
|
|
236
|
+
* key resolves `orion`, the other re-translates `Orion Engineered Carbons`;
|
|
237
|
+
* `relabelDimensions` is value-wise and idempotent, so whichever the row
|
|
238
|
+
* carries lands on the same translated display.
|
|
239
|
+
*
|
|
240
|
+
* Value keys win over authored-label keys: a stored value that happens to
|
|
241
|
+
* equal some other option's label is still that option's value.
|
|
242
|
+
*
|
|
243
|
+
* Without a translator this is byte-for-byte the pre-#4030 map — the authored
|
|
244
|
+
* label then IS the display, so no second key is ever emitted.
|
|
245
|
+
*/
|
|
246
|
+
export declare function buildDimensionLabelMap(options: unknown, translate?: OptionLabelTranslator): Record<string, string> | null;
|
|
247
|
+
/**
|
|
248
|
+
* The object a relationship field points at, or `undefined` when the field is
|
|
249
|
+
* not a relationship (or names no target).
|
|
250
|
+
*
|
|
251
|
+
* The target lives under `reference` on framework-served field defs; older /
|
|
252
|
+
* spec shapes spell it `reference_to` / `referenceTo` / `reference_to_object`,
|
|
253
|
+
* and any of them may carry a bare name, a one-element array, or `{ object }`.
|
|
254
|
+
* Same canonicalization as the dataset designer's `resolveReferenceTo`.
|
|
255
|
+
*
|
|
256
|
+
* The **type gate is deliberate**: only a declared relationship is walked, so a
|
|
257
|
+
* path segment naming a plain field can never be turned into an object name and
|
|
258
|
+
* fetched speculatively.
|
|
259
|
+
*/
|
|
260
|
+
export declare function resolveRelationshipTarget(fieldDef: unknown): string | undefined;
|
|
261
|
+
/**
|
|
262
|
+
* Resolve each dataset dimension's underlying `field` to that field's select
|
|
263
|
+
* `options`, following DOTTED relationship paths to the object that actually
|
|
264
|
+
* owns the terminal field (objectui#4053).
|
|
265
|
+
*
|
|
266
|
+
* A dimension's `field` is either a local field name (`industry`) or a
|
|
267
|
+
* relationship path (`crm_account.industry`, and — per ADR-0071, which the
|
|
268
|
+
* dataset designer emits — `crm_account.owner.department`). Reading the options
|
|
269
|
+
* as `baseSchema.fields[path]` only ever works for the local spelling: for a
|
|
270
|
+
* dotted path the options live on the RELATED object, the lookup missed
|
|
271
|
+
* silently, and the chart fell through to the raw stored enum while the same
|
|
272
|
+
* field as a local dimension rendered its labels beside it.
|
|
273
|
+
*
|
|
274
|
+
* This is the object-resolution step of that ONE lookup, not a dotted-path
|
|
275
|
+
* variant beside it: a single-segment path never enters the walk and resolves
|
|
276
|
+
* exactly as before, so the local path cannot drift away from the joined one.
|
|
277
|
+
*
|
|
278
|
+
* `loadObjectSchema` supplies each hop's object metadata doc — the caller's
|
|
279
|
+
* existing channel (the same `GET /meta/object/:name` read that produced
|
|
280
|
+
* `baseSchema`), so no new fetch layer is introduced. It is memoized per call,
|
|
281
|
+
* because sibling dimensions routinely share a prefix (`crm_account.industry`
|
|
282
|
+
* alongside `crm_account.type` fetches `crm_account` once), and seeded with
|
|
283
|
+
* `baseSchema` under its own `name` so the base is never re-fetched.
|
|
284
|
+
*
|
|
285
|
+
* Best-effort by construction: a hop that is not a relationship, a target that
|
|
286
|
+
* cannot be loaded, or a terminal field that carries no `options` simply yields
|
|
287
|
+
* no entry, and the caller keeps the raw value. Returns `{ fieldPath → options }`
|
|
288
|
+
* for the paths that did resolve.
|
|
289
|
+
*
|
|
290
|
+
* Thin wrapper over {@link resolveDimensionFieldMeta}, which is the same ONE
|
|
291
|
+
* walk keeping the identity of what it found. Callers that translate option
|
|
292
|
+
* labels need that identity (the i18n key is
|
|
293
|
+
* `fieldOptions.<owningObject>.<terminalField>.<value>`, and for a dotted path
|
|
294
|
+
* the owner is the RELATIONSHIP TARGET, not the dataset's base object) — see
|
|
295
|
+
* objectui#4030.
|
|
296
|
+
*/
|
|
297
|
+
export declare function resolveDimensionFieldOptions(baseSchema: unknown, fieldPaths: Array<string | undefined | null>, loadObjectSchema: (objectName: string) => Promise<unknown>): Promise<Record<string, unknown>>;
|
|
298
|
+
/**
|
|
299
|
+
* What {@link resolveDimensionFieldOptions} found for ONE dimension field path
|
|
300
|
+
* — the options AND the identity of the field they belong to.
|
|
301
|
+
*/
|
|
302
|
+
export interface DimensionFieldMeta {
|
|
303
|
+
/**
|
|
304
|
+
* The object that OWNS the terminal field: the dataset's base object for a
|
|
305
|
+
* local path, the last relationship's TARGET for a dotted one. `undefined`
|
|
306
|
+
* only when the base schema carries no `name` and the path is local.
|
|
307
|
+
*/
|
|
308
|
+
object: string | undefined;
|
|
309
|
+
/** The terminal field's own name — the LAST path segment, never the path. */
|
|
310
|
+
field: string;
|
|
311
|
+
/** The terminal field's `options`, exactly as the metadata doc carries them. */
|
|
312
|
+
options: unknown;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* {@link resolveDimensionFieldOptions} keeping what it walked THROUGH.
|
|
316
|
+
*
|
|
317
|
+
* Same single walk, same best-effort tolerance, same memoized loader — it just
|
|
318
|
+
* returns `{ object, field, options }` per resolved path instead of the options
|
|
319
|
+
* alone. Split out for objectui#4030: applying the locale bundle to a resolved
|
|
320
|
+
* option label needs the key the bundle is written under
|
|
321
|
+
* (`fieldOptions.<object>.<field>.<value>`), and for `crm_account.industry`
|
|
322
|
+
* that object is `crm_account` — the walk already knows it and used to drop it
|
|
323
|
+
* on the floor. Deriving it at the call site would mean re-walking the
|
|
324
|
+
* relationship chain a second time, i.e. two derivations of one fact.
|
|
325
|
+
*/
|
|
326
|
+
export declare function resolveDimensionFieldMeta(baseSchema: unknown, fieldPaths: Array<string | undefined | null>, loadObjectSchema: (objectName: string) => Promise<unknown>): Promise<Record<string, DimensionFieldMeta>>;
|
|
327
|
+
/**
|
|
328
|
+
* The locale-bundle resolver a caller supplies to translate one option label —
|
|
329
|
+
* `useSafeFieldLabel().fieldOptionLabel` in every current caller, i.e. the
|
|
330
|
+
* `{ns}.fieldOptions.<object>.<field>.<value>` convention list and form
|
|
331
|
+
* surfaces already translate select options through (objectui#4030).
|
|
332
|
+
*
|
|
333
|
+
* Taken as a plain function so this package needs no knowledge of i18n, React,
|
|
334
|
+
* or which provider is mounted — the same reason {@link OptionLabelTranslator}
|
|
335
|
+
* is a bare function type.
|
|
336
|
+
*/
|
|
337
|
+
export type FieldOptionLabelResolver = (object: string, field: string, value: string, authoredLabel: string) => string;
|
|
338
|
+
/**
|
|
339
|
+
* Bind a {@link FieldOptionLabelResolver} to ONE resolved dimension field,
|
|
340
|
+
* yielding the {@link OptionLabelTranslator} the option helpers take.
|
|
341
|
+
*
|
|
342
|
+
* The binding is the part that is easy to get wrong, which is why it is stated
|
|
343
|
+
* once here rather than at each call site: the translator must be bound to the
|
|
344
|
+
* object that OWNS the terminal field — `crm_account` for `crm_account.industry`,
|
|
345
|
+
* NOT the dataset's base object — because that owner is the key the locale
|
|
346
|
+
* bundle is written under. {@link resolveDimensionFieldMeta} already walked the
|
|
347
|
+
* relationship chain and kept that owner; this just reads it.
|
|
348
|
+
*
|
|
349
|
+
* Returns `undefined` — i.e. "no translation, keep the authored labels" — when
|
|
350
|
+
* the owner could not be resolved or no resolver was supplied. Every helper
|
|
351
|
+
* downstream then behaves exactly as it did before the #4030 seam existed.
|
|
352
|
+
*/
|
|
353
|
+
export declare function dimensionOptionTranslator(meta: DimensionFieldMeta | undefined, fieldOptionLabel?: FieldOptionLabelResolver): OptionLabelTranslator | undefined;
|
|
354
|
+
/** One dimension paired with the field path its values resolve through. */
|
|
355
|
+
export interface DimensionRelabelTarget {
|
|
356
|
+
/** The dimension name as the surface renders it (the row key). */
|
|
357
|
+
dim: string;
|
|
358
|
+
/** That dimension's underlying field path, as the dataset query reported it. */
|
|
359
|
+
path: string;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Derive the `{ dimension → { value|authoredLabel → displayLabel } }` maps that
|
|
363
|
+
* {@link relabelDimensions} consumes, from metadata {@link loadDimensionFieldMeta}
|
|
364
|
+
* (or {@link resolveDimensionFieldMeta}) already resolved.
|
|
365
|
+
*
|
|
366
|
+
* This is the LOCALE-APPLYING step, and keeping it separate from the load is
|
|
367
|
+
* the whole point of the split (objectui#4030 / PR #4324): the fetched metadata
|
|
368
|
+
* stays locale-free, so switching language re-derives these maps in a render
|
|
369
|
+
* memo instead of re-issuing the metadata read. A caller that folds the two
|
|
370
|
+
* together reintroduces a refetch on every language switch — the defect #4324
|
|
371
|
+
* fixed, and the property objectui#4389 lifted here so it is stated once.
|
|
372
|
+
*
|
|
373
|
+
* Best-effort per dimension, exactly as the longhand copies were: a path that
|
|
374
|
+
* resolved no metadata, or whose field carries no `options`, contributes no
|
|
375
|
+
* entry, and `relabelDimensions` then returns the caller's rows by identity.
|
|
376
|
+
* Returns `null` — never an empty object — when nothing resolved, so callers
|
|
377
|
+
* can skip the relabel entirely.
|
|
378
|
+
*/
|
|
379
|
+
export declare function deriveDimensionLabelMaps(metaByPath: Record<string, DimensionFieldMeta> | null | undefined, relabel: readonly DimensionRelabelTarget[] | null | undefined, fieldOptionLabel?: FieldOptionLabelResolver): Record<string, Record<string, string>> | null;
|
|
380
|
+
/**
|
|
381
|
+
* Load one dataset query's dimension field metadata: fetch the base object's
|
|
382
|
+
* schema, then resolve every dimension's field path against it.
|
|
383
|
+
*
|
|
384
|
+
* The two-step composition every caller wrote out by hand. `loadObjectSchema`
|
|
385
|
+
* stays the CALLER's channel — this package issues no reads of its own and has
|
|
386
|
+
* no opinion about transport, which is what keeps it free of React, of
|
|
387
|
+
* `SchemaRendererContext`, and of the authenticated-`apiFetch` concern
|
|
388
|
+
* (objectui#4121) that belongs to the layer holding the host context.
|
|
389
|
+
*
|
|
390
|
+
* The same loader serves both steps by design: {@link resolveDimensionFieldMeta}
|
|
391
|
+
* memoizes it per call and seeds the cache with the base schema under its own
|
|
392
|
+
* `name`, so the base object is read ONCE even when several dotted dimensions
|
|
393
|
+
* walk back through it. That read count is pinned on both consuming surfaces.
|
|
141
394
|
*/
|
|
142
|
-
export declare function
|
|
395
|
+
export declare function loadDimensionFieldMeta(loadObjectSchema: (objectName: string) => Promise<unknown>, object: string, fieldPaths: Array<string | undefined | null>): Promise<Record<string, DimensionFieldMeta>>;
|