@object-ui/core 17.3.0 → 17.6.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 (99) hide show
  1. package/CHANGELOG.md +2018 -0
  2. package/README.md +38 -34
  3. package/dist/actions/ActionEngine.d.ts +43 -26
  4. package/dist/actions/ActionEngine.js +67 -54
  5. package/dist/actions/ActionRunner.d.ts +281 -31
  6. package/dist/actions/ActionRunner.js +458 -36
  7. package/dist/actions/actionKeys.d.ts +163 -27
  8. package/dist/actions/actionKeys.js +238 -27
  9. package/dist/actions/actionResponse.js +1 -1
  10. package/dist/actions/index.d.ts +1 -0
  11. package/dist/actions/index.js +1 -0
  12. package/dist/actions/recordIdParam.d.ts +77 -0
  13. package/dist/actions/recordIdParam.js +37 -0
  14. package/dist/actions/serverActionHandler.d.ts +1 -1
  15. package/dist/actions/serverActionHandler.js +1 -1
  16. package/dist/data-scope/ViewDataProvider.d.ts +37 -12
  17. package/dist/data-scope/ViewDataProvider.js +83 -5
  18. package/dist/data-scope/element-data-source.d.ts +113 -0
  19. package/dist/data-scope/element-data-source.js +216 -0
  20. package/dist/data-scope/index.d.ts +2 -1
  21. package/dist/data-scope/index.js +1 -0
  22. package/dist/evaluator/ExpressionEvaluator.d.ts +42 -1
  23. package/dist/evaluator/ExpressionEvaluator.js +42 -1
  24. package/dist/evaluator/SafeExpressionParser.js +0 -4
  25. package/dist/evaluator/declaredPredicate.d.ts +119 -0
  26. package/dist/evaluator/declaredPredicate.js +152 -0
  27. package/dist/evaluator/fieldRules.d.ts +35 -0
  28. package/dist/evaluator/fieldRules.js +79 -20
  29. package/dist/evaluator/index.d.ts +2 -0
  30. package/dist/evaluator/index.js +2 -0
  31. package/dist/evaluator/listConditional.d.ts +54 -4
  32. package/dist/evaluator/listConditional.js +125 -19
  33. package/dist/evaluator/optionRules.d.ts +55 -0
  34. package/dist/evaluator/optionRules.js +60 -0
  35. package/dist/evaluator/predicateInput.d.ts +51 -3
  36. package/dist/evaluator/predicateInput.js +62 -5
  37. package/dist/evaluator/rowPredicateCanon.d.ts +67 -0
  38. package/dist/evaluator/rowPredicateCanon.js +208 -0
  39. package/dist/index.d.ts +8 -1
  40. package/dist/index.js +31 -1
  41. package/dist/protocols/DndProtocol.d.ts +42 -1
  42. package/dist/protocols/KeyboardProtocol.d.ts +31 -1
  43. package/dist/protocols/SharingProtocol.d.ts +11 -1
  44. package/dist/registry/Registry.d.ts +62 -16
  45. package/dist/theme/ThemeEngine.d.ts +0 -8
  46. package/dist/theme/ThemeEngine.js +22 -103
  47. package/dist/theme/index.d.ts +1 -1
  48. package/dist/theme/index.js +7 -1
  49. package/dist/types/index.d.ts +125 -12
  50. package/dist/utils/chart-presentation.d.ts +205 -0
  51. package/dist/utils/chart-presentation.js +280 -0
  52. package/dist/utils/chart-series.d.ts +390 -23
  53. package/dist/utils/chart-series.js +776 -32
  54. package/dist/utils/column-identity.d.ts +43 -0
  55. package/dist/utils/column-identity.js +47 -1
  56. package/dist/utils/compare-to.d.ts +40 -13
  57. package/dist/utils/compare-to.js +4 -32
  58. package/dist/utils/dashboard-filters.d.ts +54 -8
  59. package/dist/utils/dashboard-filters.js +261 -12
  60. package/dist/utils/dataset-format.d.ts +73 -14
  61. package/dist/utils/dataset-format.js +142 -9
  62. package/dist/utils/dataset-pivot.d.ts +28 -7
  63. package/dist/utils/dataset-pivot.js +27 -6
  64. package/dist/utils/debug.js +7 -0
  65. package/dist/utils/dom-props.d.ts +137 -0
  66. package/dist/utils/dom-props.js +153 -0
  67. package/dist/utils/expand-fields.d.ts +97 -7
  68. package/dist/utils/expand-fields.js +97 -7
  69. package/dist/utils/filter-tokens.js +0 -1
  70. package/dist/utils/freeze-schema.d.ts +38 -1
  71. package/dist/utils/freeze-schema.js +10 -0
  72. package/dist/utils/humanize-label.d.ts +60 -0
  73. package/dist/utils/humanize-label.js +62 -0
  74. package/dist/utils/managedBy.d.ts +27 -2
  75. package/dist/utils/normalize-list-view.d.ts +36 -6
  76. package/dist/utils/normalize-list-view.js +93 -9
  77. package/dist/utils/number-display.d.ts +155 -0
  78. package/dist/utils/number-display.js +71 -0
  79. package/dist/utils/predicate-fields.d.ts +29 -4
  80. package/dist/utils/predicate-fields.js +44 -4
  81. package/dist/utils/resolve-view-id.d.ts +31 -0
  82. package/dist/utils/resolve-view-id.js +45 -0
  83. package/dist/utils/retired-field-types.d.ts +90 -0
  84. package/dist/utils/retired-field-types.js +124 -0
  85. package/dist/utils/sort-query.d.ts +57 -0
  86. package/dist/utils/sort-query.js +35 -0
  87. package/dist/utils/unmaterialized-fields.d.ts +52 -0
  88. package/dist/utils/unmaterialized-fields.js +57 -0
  89. package/dist/validation/index.d.ts +1 -0
  90. package/dist/validation/index.js +1 -0
  91. package/dist/validation/schema-validator.js +105 -0
  92. package/dist/validation/server-owned-value.d.ts +62 -0
  93. package/dist/validation/server-owned-value.js +99 -0
  94. package/dist/validation/validators/object-validation-engine.js +0 -2
  95. package/package.json +5 -5
  96. package/dist/__benchmarks__/core.bench.d.ts +0 -8
  97. package/dist/__benchmarks__/core.bench.js +0 -53
  98. package/dist/utils/merge-views-into-objects.d.ts +0 -19
  99. package/dist/utils/merge-views-into-objects.js +0 -39
@@ -5,22 +5,6 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
- /**
9
- * chart-series — shape a semantic-layer `queryDataset` result into the
10
- * `{ data, xAxisKey, series }` triple a chart renderer consumes (ADR-0021, #1759).
11
- *
12
- * Shared by `ObjectChart` (plugin-charts) and `DatasetWidget` (plugin-dashboard)
13
- * so multi-dimension charts visualise identically across surfaces.
14
- *
15
- * Rules:
16
- * - **2+ dimensions, single measure** → pivot the SECOND dimension into one
17
- * series per distinct value (grouped/coloured bars). `xAxisKey` = first
18
- * dimension; each output row is one first-dimension bucket with a column per
19
- * second-dimension value holding the measure. This makes the second dimension
20
- * visible instead of just repeating the x-axis label.
21
- * - **otherwise** (single dimension, or multiple measures) → first dimension is
22
- * the x-axis and each measure is its own series (long format passes through).
23
- */
24
8
  export interface ChartResultField {
25
9
  name: string;
26
10
  label?: string;
@@ -47,7 +31,150 @@ export interface ChartSeriesResult {
47
31
  xAxisKey: string | undefined;
48
32
  series: ChartSeriesBinding[];
49
33
  }
50
- export declare function buildChartSeries(rows: Array<Record<string, unknown>> | null | undefined, dimensions: string[] | null | undefined, values: string[] | null | undefined, fields?: ChartResultField[] | null): ChartSeriesResult;
34
+ /**
35
+ * The bucket a NULL/undefined category VALUE renders under, when the caller
36
+ * supplies no localized label of its own (objectui#4466).
37
+ *
38
+ * English by construction and deliberately so: this package is React-free and
39
+ * i18n-free (see {@link OptionLabelTranslator} for the same reason stated one
40
+ * layer down), so the locale bundle cannot be read here. Every renderer that
41
+ * has an i18n provider passes its own resolved label through
42
+ * {@link ChartSeriesOptions.nullCategoryLabel} — this constant is the floor
43
+ * that keeps a provider-less host (or a caller that has not been wired yet)
44
+ * drawing the group rather than dropping it.
45
+ *
46
+ * Exported so a consumer can recognise the bucket it will be handed —
47
+ * {@link findChartSeriesRow} uses the same default, which is what makes a
48
+ * clicked bucket bar resolve back to its (raw null) dataset row.
49
+ */
50
+ export declare const NULL_CATEGORY_LABEL = "(None)";
51
+ /**
52
+ * The IDENTITY of the chart bucket a first-dimension raw value falls in —
53
+ * distinct from the string that bucket DISPLAYS (objectui#4508).
54
+ *
55
+ * Deliberately the SAME encoder the pivot TABLE already keys its buckets with
56
+ * (`pivotBucketId` over a `pivotDimensionValue`-normalized value), because the
57
+ * two surfaces answer the same question about the same dataset rows and were
58
+ * answering it differently: `buildPivot` gives a null dimension value a bucket
59
+ * id no string can spell, while the chart used the display label as its own
60
+ * key. That gap is the whole of objectui#4508 — see `dataset-pivot.ts` for why
61
+ * the encoding is JSON rather than a delimiter or a placeholder character.
62
+ *
63
+ * Two collisions follow from a label-as-key, and one encoder closes both:
64
+ * - `null` and `''` are different groups. `String(x ?? '')` made them one
65
+ * bucket, so two groups drew ONE bar and one of them lost its drill.
66
+ * `[null]` and `[""]` are different ids.
67
+ * - a stored value that literally spells the bucket label (`'(None)'`, or any
68
+ * localized `nullCategoryLabel` — `'(未指定)'` and the other nine packs) is
69
+ * not the null group. `["(None)"]` and `[null]` are different ids, so the
70
+ * click resolves to the rows its own bar was drawn from.
71
+ */
72
+ export declare function chartBucketId(rawValue: unknown): string;
73
+ /**
74
+ * The key an emitted chart row carries its {@link chartBucketId} under, when it
75
+ * carries one — the "alongside the display label" half of objectui#4508.
76
+ *
77
+ * **Written exactly when the display string does not name one bucket**, i.e.
78
+ * when two or more DISTINCT buckets render the same axis text. That is not a
79
+ * cost-saving heuristic, it is the complete condition: with the reader's
80
+ * matching made exact (see {@link findChartSeriesRow}), a display string that
81
+ * one bucket alone produces already identifies it, and the rows a chart is
82
+ * drawn from are also an authoring-visible surface (`data` on a `chart`
83
+ * schema) that renderer-internal metadata should not appear in for every
84
+ * ordinary chart. Rows that do not carry the category key at all never get one
85
+ * — they have no bucket, and that shape belongs to `hasNoCategoryKey`
86
+ * (framework#4033), not here.
87
+ *
88
+ * Read it with {@link chartRowBucketId} rather than the literal key.
89
+ */
90
+ export declare const CHART_BUCKET_ID_KEY = "__bucketId";
91
+ /**
92
+ * Read the bucket identity off a row a chart was drawn from, or `undefined`
93
+ * when the row carries none (an unambiguous bucket, or rows a caller built
94
+ * itself rather than through {@link buildChartSeries}).
95
+ *
96
+ * Takes `unknown` because its callers hold a click payload, not a typed row:
97
+ * recharts hands a pie/funnel click a SPREAD COPY of the data item, which is
98
+ * why the identity is an ordinary enumerable property and not a symbol or a
99
+ * non-enumerable one — either of those would silently not survive the copy.
100
+ */
101
+ export declare function chartRowBucketId(row: unknown): string | undefined;
102
+ /** Per-call knobs shared by {@link buildChartSeries} / {@link findChartSeriesRow}. */
103
+ export interface ChartSeriesOptions {
104
+ /**
105
+ * Display label for a category whose value is `null`/`undefined`. Defaults to
106
+ * {@link NULL_CATEGORY_LABEL}.
107
+ *
108
+ * Pass the SAME value to both helpers: `buildChartSeries` writes it into the
109
+ * rendered rows and `findChartSeriesRow` matches a clicked category against
110
+ * it, so a mismatch costs the null bucket its drill-through.
111
+ */
112
+ nullCategoryLabel?: string;
113
+ }
114
+ /** Per-CLICK knobs for {@link findChartSeriesRow}, on top of the shared ones. */
115
+ export interface ChartDrillLookupOptions extends ChartSeriesOptions {
116
+ /**
117
+ * The clicked bucket's {@link chartBucketId}, when the renderer could read one
118
+ * off the row the segment was drawn from ({@link chartRowBucketId} over
119
+ * {@link CHART_BUCKET_ID_KEY}) — objectui#4508.
120
+ *
121
+ * Supplied ⇒ AUTHORITATIVE: the lookup matches on identity and ignores
122
+ * `category` entirely, which is what lets two bars painting the same axis
123
+ * text drill to their own rows instead of both resolving to the first.
124
+ * Absent ⇒ the bucket's display string identified it on its own (that is
125
+ * exactly when `buildChartSeries` writes no id), or the rows were not built
126
+ * by `buildChartSeries` at all, and the display match answers.
127
+ */
128
+ bucketId?: string;
129
+ }
130
+ /**
131
+ * What a chart renderer hands a drill handler when a segment is clicked.
132
+ *
133
+ * Declared HERE, beside the two helpers that write and read a bucket, rather
134
+ * than re-spelled inline in each of the three packages that pass it along
135
+ * (`AdvancedChartImpl` builds it, `ChartRenderer` and `ObjectChart` forward it,
136
+ * `DatasetWidget` consumes it). It was three inline literals when
137
+ * {@link ChartDrillLookupOptions.bucketId} needed a carrier, and a field added
138
+ * to one of three copies reaches the consumer as `undefined` with nothing red.
139
+ */
140
+ export interface ChartSegmentClickEvent {
141
+ /** The clicked bucket's axis TEXT — what the user sees on the tick. */
142
+ category?: string;
143
+ /**
144
+ * The clicked bucket's IDENTITY, when its row carried one (objectui#4508).
145
+ * Feed it straight to {@link ChartDrillLookupOptions.bucketId}; it is the
146
+ * only field that survives two buckets painting the same `category`.
147
+ */
148
+ categoryId?: string;
149
+ /** The clicked series' key — a measure, or a pivoted second-dimension value. */
150
+ series?: string;
151
+ /**
152
+ * The clicked series' DISPLAY LABEL, when the renderer knew one
153
+ * (objectui#4682) — `series[].label` as {@link buildChartSeries} assigned it.
154
+ *
155
+ * {@link ChartSegmentClickEvent.series} stays the LOOKUP key and this never
156
+ * substitutes for it: {@link findChartSeriesRow} resolves a key through the
157
+ * same assignment `buildChartSeries` made, and a label cannot be resolved
158
+ * that way — it is not unique (that is precisely why the colliding groups key
159
+ * by identity instead). This field exists for what a key cannot do: be SHOWN.
160
+ *
161
+ * The two strings are equal for every ordinary group, which is why reading
162
+ * the key as a title went unnoticed. They part company when a group's label
163
+ * cannot name it — objectui#4508's collision on the series axis — and the key
164
+ * becomes an opaque `chartBucketId`. A drawer titled `Backlog / [null]` over
165
+ * a segment the user saw labelled `(None)` reads as broken DATA rather than
166
+ * as a broken title, which is why the label travels with the click rather
167
+ * than being re-derived by the consumer.
168
+ *
169
+ * Absent when the renderer resolved no series, or when the series carries no
170
+ * label distinct from its key; a consumer titles itself from
171
+ * `seriesLabel ?? series` and so is unchanged wherever it is absent.
172
+ */
173
+ seriesLabel?: string;
174
+ /** The measure value at the click point. */
175
+ value?: number;
176
+ }
177
+ 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
178
  /**
52
179
  * Resolve select/enum dimension VALUES to display LABELS in chart rows.
53
180
  *
@@ -83,10 +210,51 @@ export declare function relabelDimensions(rows: Array<Record<string, unknown>> |
83
210
  * BOTH the x-axis dimension (`category`) and the series dimension (`seriesKey`).
84
211
  * - **otherwise** → match the x-axis (first) dimension only.
85
212
  *
86
- * Comparison is string-wise on the rows' display values (which is what the chart
87
- * surfaces as `category` / series key). Returns `-1` when nothing matches.
213
+ * The x-axis comparison is string-wise on the rows' display values (which is
214
+ * what the chart surfaces as `category`), unless the click carried a bucket
215
+ * IDENTITY — {@link ChartDrillLookupOptions.bucketId} — in which case that is
216
+ * authoritative and the display string is not consulted at all. Returns `-1`
217
+ * when nothing matches.
218
+ *
219
+ * The SERIES comparison is not string-wise at all as of objectui#4673: a series
220
+ * key is a `dataKey` {@link buildChartSeries} assigned, so it is resolved back
221
+ * through the same {@link pivotSeriesBuckets} assignment (same rows, same
222
+ * `nullCategoryLabel`) to the group IDENTITY it names, and rows are matched on
223
+ * that. A null-valued second dimension had no series at all before, so its
224
+ * segment could not be clicked; now it can, and `String(r[gDim] ?? '')` would
225
+ * have answered that click with an empty-string group's rows — the same
226
+ * conflation objectui#4508 removed on the x-axis.
227
+ *
228
+ * The NULL-category bucket {@link buildChartSeries} renders (objectui#4466) is
229
+ * matched back to its row here, and it has to be: the caller passes the rows it
230
+ * charted FROM (still carrying the raw null) while the click event carries the
231
+ * bucket LABEL, so without this the one bar the fix made visible would resolve
232
+ * to `-1` and its drill-through would silently no-op. Pass the same
233
+ * `nullCategoryLabel` both helpers were given.
234
+ *
235
+ * **A null category reads as its bucket LABEL and nothing else** (objectui#4508).
236
+ * It used to read as the label OR the empty string, on the stated grounds that
237
+ * `''` is the drill layer's own spelling of "no group value" — but no producer
238
+ * of this function's `category` argument writes that spelling (the one
239
+ * production caller feeds it a chart click's category, and `computeDrillFilter`
240
+ * is downstream of the drill, not upstream of this lookup), while `''` IS what
241
+ * a genuine empty-string group paints. So the tolerance cost that group its own
242
+ * drill and gave it a null group's records instead — a WRONG click, not a dead
243
+ * one. `''` now means the empty-string group, which is what it says.
244
+ *
245
+ * **`xOf` covers BOTH arms, which is what let objectui#4497 bucket the pivot
246
+ * branch without touching this function.** Worth stating because the reverse
247
+ * would be silent: the pivot's emitted rows are AGGREGATED (`byX` collapses N
248
+ * raw rows into one bucket per first-dimension value), so they are NOT
249
+ * index-aligned with anything, and a caller cannot drill by the emitted row's
250
+ * position. It drills by SEARCHING the raw rows here and indexing
251
+ * `drillRawRows` with what this returns — see `DatasetWidget.handleChartDrill`,
252
+ * the one production caller. So the pivot's map key and its emitted display
253
+ * value being two different things never reached the drill: the raw rows this
254
+ * searches still carry their null either way, and `xOf` reads the label back to
255
+ * them in the multi-dimension arm exactly as in the single-dimension one.
88
256
  */
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;
257
+ 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?: ChartDrillLookupOptions): number;
90
258
  /**
91
259
  * Build a per-category colour map from a select/lookup field's `options`.
92
260
  *
@@ -129,14 +297,213 @@ export declare function buildCategoryOrder(options: unknown): string[] | null;
129
297
  * after the declared ones rather than dropping them).
130
298
  */
131
299
  export declare function buildCategoryRank(order: string[] | null | undefined): Map<string, number> | null;
300
+ /**
301
+ * The i18n seam of the analytics label net (objectui#4030).
302
+ *
303
+ * `(storedValue, authoredLabel) => displayLabel` — the SAME signature
304
+ * `resolveGroupByLabels` (plugin-charts) already takes for the legacy aggregate
305
+ * path, which callers bind to `useSafeFieldLabel().fieldOptionLabel(object,
306
+ * field, …)`. That resolver reads `{ns}.fieldOptions.<object>.<field>.<value>`,
307
+ * the one convention list and form surfaces already translate select options
308
+ * through (`useObjectLabel.translateOptions`, and `@objectstack/spec` names
309
+ * objectui as its reader). Analytics reuses that channel rather than growing a
310
+ * chart-side per-locale dialect.
311
+ *
312
+ * Omitted everywhere it isn't available (no i18n provider, an unresolvable
313
+ * owning object): every helper below then behaves exactly as it did before this
314
+ * seam existed.
315
+ */
316
+ export type OptionLabelTranslator = (value: string, authoredLabel: string) => string;
317
+ /**
318
+ * Run a select field's `options` through the locale bundle, returning options
319
+ * whose `label` is the translated one (objectui#4030).
320
+ *
321
+ * The mirror of `useObjectLabel().translateOptions` — the channel list and form
322
+ * surfaces localize select options through — kept pure and shape-tolerant so
323
+ * the analytics net can apply it at the point the resolved options are read.
324
+ * Everything downstream ({@link buildOptionColorMap},
325
+ * {@link buildCategoryOrder}) keeps reading `option.label` and needs no
326
+ * knowledge of i18n, exactly like `SelectCellRenderer` on the list side.
327
+ *
328
+ * Colour, order and every other option key survive untouched — only `label`
329
+ * changes. Returns the input ARRAY ITSELF when there is no translator or
330
+ * nothing translated, so an untranslated app keeps the identities (and the
331
+ * memo/render behaviour) it had before.
332
+ */
333
+ export declare function localizeFieldOptions(options: unknown, translate?: OptionLabelTranslator): unknown;
132
334
  /**
133
335
  * Build a `{ value → label }` map from a select/enum field's `options`, for
134
336
  * resolving a grouped dimension's stored value to its display label (fed to
135
337
  * {@link relabelDimensions}). Mirrors {@link buildOptionColorMap}.
136
338
  *
137
339
  * Options may be `{ value, label }` objects or bare strings (value == label —
138
- * nothing to relabel). Only entries whose `label` actually differs from the
139
- * `value` are kept, so the map is empty (→ `null`) when relabeling would be a
340
+ * nothing to relabel). Only entries whose display label actually differs from
341
+ * the key are kept, so the map is empty (→ `null`) when relabeling would be a
140
342
  * no-op and the caller can skip it entirely.
343
+ *
344
+ * **With a `translate` seam (objectui#4030) the map gains a SECOND key per
345
+ * option: the AUTHORED label.** A dimension's rows reach this net keyed either
346
+ * way — value-keyed when the server did not resolve the dimension (the whole
347
+ * reason this net exists), already label-keyed when it did (ADR-0021) — and
348
+ * the reported symptom is the second case: a chart legend showing the object's
349
+ * English `label` verbatim beside a related list showing the translation. One
350
+ * key resolves `orion`, the other re-translates `Orion Engineered Carbons`;
351
+ * `relabelDimensions` is value-wise and idempotent, so whichever the row
352
+ * carries lands on the same translated display.
353
+ *
354
+ * Value keys win over authored-label keys: a stored value that happens to
355
+ * equal some other option's label is still that option's value.
356
+ *
357
+ * Without a translator this is byte-for-byte the pre-#4030 map — the authored
358
+ * label then IS the display, so no second key is ever emitted.
359
+ */
360
+ export declare function buildDimensionLabelMap(options: unknown, translate?: OptionLabelTranslator): Record<string, string> | null;
361
+ /**
362
+ * The object a relationship field points at, or `undefined` when the field is
363
+ * not a relationship (or names no target).
364
+ *
365
+ * The target lives under `reference` on framework-served field defs; older /
366
+ * spec shapes spell it `reference_to` / `referenceTo` / `reference_to_object`,
367
+ * and any of them may carry a bare name, a one-element array, or `{ object }`.
368
+ * Same canonicalization as the dataset designer's `resolveReferenceTo`.
369
+ *
370
+ * The **type gate is deliberate**: only a declared relationship is walked, so a
371
+ * path segment naming a plain field can never be turned into an object name and
372
+ * fetched speculatively.
373
+ */
374
+ export declare function resolveRelationshipTarget(fieldDef: unknown): string | undefined;
375
+ /**
376
+ * Resolve each dataset dimension's underlying `field` to that field's select
377
+ * `options`, following DOTTED relationship paths to the object that actually
378
+ * owns the terminal field (objectui#4053).
379
+ *
380
+ * A dimension's `field` is either a local field name (`industry`) or a
381
+ * relationship path (`crm_account.industry`, and — per ADR-0071, which the
382
+ * dataset designer emits — `crm_account.owner.department`). Reading the options
383
+ * as `baseSchema.fields[path]` only ever works for the local spelling: for a
384
+ * dotted path the options live on the RELATED object, the lookup missed
385
+ * silently, and the chart fell through to the raw stored enum while the same
386
+ * field as a local dimension rendered its labels beside it.
387
+ *
388
+ * This is the object-resolution step of that ONE lookup, not a dotted-path
389
+ * variant beside it: a single-segment path never enters the walk and resolves
390
+ * exactly as before, so the local path cannot drift away from the joined one.
391
+ *
392
+ * `loadObjectSchema` supplies each hop's object metadata doc — the caller's
393
+ * existing channel (the same `GET /meta/object/:name` read that produced
394
+ * `baseSchema`), so no new fetch layer is introduced. It is memoized per call,
395
+ * because sibling dimensions routinely share a prefix (`crm_account.industry`
396
+ * alongside `crm_account.type` fetches `crm_account` once), and seeded with
397
+ * `baseSchema` under its own `name` so the base is never re-fetched.
398
+ *
399
+ * Best-effort by construction: a hop that is not a relationship, a target that
400
+ * cannot be loaded, or a terminal field that carries no `options` simply yields
401
+ * no entry, and the caller keeps the raw value. Returns `{ fieldPath → options }`
402
+ * for the paths that did resolve.
403
+ *
404
+ * Thin wrapper over {@link resolveDimensionFieldMeta}, which is the same ONE
405
+ * walk keeping the identity of what it found. Callers that translate option
406
+ * labels need that identity (the i18n key is
407
+ * `fieldOptions.<owningObject>.<terminalField>.<value>`, and for a dotted path
408
+ * the owner is the RELATIONSHIP TARGET, not the dataset's base object) — see
409
+ * objectui#4030.
410
+ */
411
+ export declare function resolveDimensionFieldOptions(baseSchema: unknown, fieldPaths: Array<string | undefined | null>, loadObjectSchema: (objectName: string) => Promise<unknown>): Promise<Record<string, unknown>>;
412
+ /**
413
+ * What {@link resolveDimensionFieldOptions} found for ONE dimension field path
414
+ * — the options AND the identity of the field they belong to.
415
+ */
416
+ export interface DimensionFieldMeta {
417
+ /**
418
+ * The object that OWNS the terminal field: the dataset's base object for a
419
+ * local path, the last relationship's TARGET for a dotted one. `undefined`
420
+ * only when the base schema carries no `name` and the path is local.
421
+ */
422
+ object: string | undefined;
423
+ /** The terminal field's own name — the LAST path segment, never the path. */
424
+ field: string;
425
+ /** The terminal field's `options`, exactly as the metadata doc carries them. */
426
+ options: unknown;
427
+ }
428
+ /**
429
+ * {@link resolveDimensionFieldOptions} keeping what it walked THROUGH.
430
+ *
431
+ * Same single walk, same best-effort tolerance, same memoized loader — it just
432
+ * returns `{ object, field, options }` per resolved path instead of the options
433
+ * alone. Split out for objectui#4030: applying the locale bundle to a resolved
434
+ * option label needs the key the bundle is written under
435
+ * (`fieldOptions.<object>.<field>.<value>`), and for `crm_account.industry`
436
+ * that object is `crm_account` — the walk already knows it and used to drop it
437
+ * on the floor. Deriving it at the call site would mean re-walking the
438
+ * relationship chain a second time, i.e. two derivations of one fact.
439
+ */
440
+ export declare function resolveDimensionFieldMeta(baseSchema: unknown, fieldPaths: Array<string | undefined | null>, loadObjectSchema: (objectName: string) => Promise<unknown>): Promise<Record<string, DimensionFieldMeta>>;
441
+ /**
442
+ * The locale-bundle resolver a caller supplies to translate one option label —
443
+ * `useSafeFieldLabel().fieldOptionLabel` in every current caller, i.e. the
444
+ * `{ns}.fieldOptions.<object>.<field>.<value>` convention list and form
445
+ * surfaces already translate select options through (objectui#4030).
446
+ *
447
+ * Taken as a plain function so this package needs no knowledge of i18n, React,
448
+ * or which provider is mounted — the same reason {@link OptionLabelTranslator}
449
+ * is a bare function type.
450
+ */
451
+ export type FieldOptionLabelResolver = (object: string, field: string, value: string, authoredLabel: string) => string;
452
+ /**
453
+ * Bind a {@link FieldOptionLabelResolver} to ONE resolved dimension field,
454
+ * yielding the {@link OptionLabelTranslator} the option helpers take.
455
+ *
456
+ * The binding is the part that is easy to get wrong, which is why it is stated
457
+ * once here rather than at each call site: the translator must be bound to the
458
+ * object that OWNS the terminal field — `crm_account` for `crm_account.industry`,
459
+ * NOT the dataset's base object — because that owner is the key the locale
460
+ * bundle is written under. {@link resolveDimensionFieldMeta} already walked the
461
+ * relationship chain and kept that owner; this just reads it.
462
+ *
463
+ * Returns `undefined` — i.e. "no translation, keep the authored labels" — when
464
+ * the owner could not be resolved or no resolver was supplied. Every helper
465
+ * downstream then behaves exactly as it did before the #4030 seam existed.
466
+ */
467
+ export declare function dimensionOptionTranslator(meta: DimensionFieldMeta | undefined, fieldOptionLabel?: FieldOptionLabelResolver): OptionLabelTranslator | undefined;
468
+ /** One dimension paired with the field path its values resolve through. */
469
+ export interface DimensionRelabelTarget {
470
+ /** The dimension name as the surface renders it (the row key). */
471
+ dim: string;
472
+ /** That dimension's underlying field path, as the dataset query reported it. */
473
+ path: string;
474
+ }
475
+ /**
476
+ * Derive the `{ dimension → { value|authoredLabel → displayLabel } }` maps that
477
+ * {@link relabelDimensions} consumes, from metadata {@link loadDimensionFieldMeta}
478
+ * (or {@link resolveDimensionFieldMeta}) already resolved.
479
+ *
480
+ * This is the LOCALE-APPLYING step, and keeping it separate from the load is
481
+ * the whole point of the split (objectui#4030 / PR #4324): the fetched metadata
482
+ * stays locale-free, so switching language re-derives these maps in a render
483
+ * memo instead of re-issuing the metadata read. A caller that folds the two
484
+ * together reintroduces a refetch on every language switch — the defect #4324
485
+ * fixed, and the property objectui#4389 lifted here so it is stated once.
486
+ *
487
+ * Best-effort per dimension, exactly as the longhand copies were: a path that
488
+ * resolved no metadata, or whose field carries no `options`, contributes no
489
+ * entry, and `relabelDimensions` then returns the caller's rows by identity.
490
+ * Returns `null` — never an empty object — when nothing resolved, so callers
491
+ * can skip the relabel entirely.
492
+ */
493
+ export declare function deriveDimensionLabelMaps(metaByPath: Record<string, DimensionFieldMeta> | null | undefined, relabel: readonly DimensionRelabelTarget[] | null | undefined, fieldOptionLabel?: FieldOptionLabelResolver): Record<string, Record<string, string>> | null;
494
+ /**
495
+ * Load one dataset query's dimension field metadata: fetch the base object's
496
+ * schema, then resolve every dimension's field path against it.
497
+ *
498
+ * The two-step composition every caller wrote out by hand. `loadObjectSchema`
499
+ * stays the CALLER's channel — this package issues no reads of its own and has
500
+ * no opinion about transport, which is what keeps it free of React, of
501
+ * `SchemaRendererContext`, and of the authenticated-`apiFetch` concern
502
+ * (objectui#4121) that belongs to the layer holding the host context.
503
+ *
504
+ * The same loader serves both steps by design: {@link resolveDimensionFieldMeta}
505
+ * memoizes it per call and seeds the cache with the base schema under its own
506
+ * `name`, so the base object is read ONCE even when several dotted dimensions
507
+ * walk back through it. That read count is pinned on both consuming surfaces.
141
508
  */
142
- export declare function buildDimensionLabelMap(options: unknown): Record<string, string> | null;
509
+ export declare function loadDimensionFieldMeta(loadObjectSchema: (objectName: string) => Promise<unknown>, object: string, fieldPaths: Array<string | undefined | null>): Promise<Record<string, DimensionFieldMeta>>;