@object-ui/core 17.5.0 → 17.7.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 (157) hide show
  1. package/CHANGELOG.md +7404 -0
  2. package/README.md +127 -26
  3. package/dist/actions/ActionEngine.d.ts +5 -3
  4. package/dist/actions/ActionEngine.js +14 -10
  5. package/dist/actions/ActionRunner.d.ts +363 -30
  6. package/dist/actions/ActionRunner.js +544 -58
  7. package/dist/actions/TransactionManager.d.ts +7 -2
  8. package/dist/actions/actionKeys.d.ts +75 -13
  9. package/dist/actions/actionKeys.js +131 -16
  10. package/dist/actions/actionResponse.js +3 -3
  11. package/dist/actions/index.d.ts +2 -0
  12. package/dist/actions/index.js +2 -0
  13. package/dist/actions/recordDelete.d.ts +118 -0
  14. package/dist/actions/recordDelete.js +112 -0
  15. package/dist/actions/recordIdParam.d.ts +77 -0
  16. package/dist/actions/recordIdParam.js +37 -0
  17. package/dist/actions/serverActionHandler.d.ts +1 -1
  18. package/dist/actions/serverActionHandler.js +1 -1
  19. package/dist/adapters/ValueDataSource.d.ts +6 -1
  20. package/dist/adapters/ValueDataSource.js +1090 -98
  21. package/dist/adapters/batchTransaction.d.ts +1 -1
  22. package/dist/adapters/batchTransaction.js +1 -1
  23. package/dist/builder/schema-builder.d.ts +38 -81
  24. package/dist/builder/schema-builder.js +33 -152
  25. package/dist/data-scope/ViewDataProvider.d.ts +1 -1
  26. package/dist/data-scope/ViewDataProvider.js +21 -1
  27. package/dist/data-scope/element-data-source.d.ts +261 -7
  28. package/dist/data-scope/element-data-source.js +342 -52
  29. package/dist/data-scope/index.d.ts +9 -4
  30. package/dist/data-scope/index.js +9 -4
  31. package/dist/evaluator/ExpressionEvaluator.d.ts +128 -1
  32. package/dist/evaluator/ExpressionEvaluator.js +226 -13
  33. package/dist/evaluator/FormulaFunctions.d.ts +0 -10
  34. package/dist/evaluator/FormulaFunctions.js +166 -27
  35. package/dist/evaluator/SafeExpressionParser.js +0 -4
  36. package/dist/evaluator/declaredPredicate.d.ts +93 -9
  37. package/dist/evaluator/declaredPredicate.js +118 -17
  38. package/dist/evaluator/fieldRules.d.ts +107 -4
  39. package/dist/evaluator/fieldRules.js +231 -35
  40. package/dist/evaluator/index.d.ts +2 -0
  41. package/dist/evaluator/index.js +2 -0
  42. package/dist/evaluator/listConditional.d.ts +125 -9
  43. package/dist/evaluator/listConditional.js +167 -26
  44. package/dist/evaluator/optionRules.d.ts +126 -0
  45. package/dist/evaluator/optionRules.js +141 -4
  46. package/dist/evaluator/predicateInput.d.ts +15 -4
  47. package/dist/evaluator/predicateInput.js +35 -2
  48. package/dist/evaluator/rowPredicateCanon.d.ts +56 -0
  49. package/dist/evaluator/rowPredicateCanon.js +158 -0
  50. package/dist/evaluator/subjectPermissions.d.ts +76 -0
  51. package/dist/evaluator/subjectPermissions.js +44 -0
  52. package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
  53. package/dist/evaluator/unevaluablePredicate.js +70 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.js +97 -2
  56. package/dist/protocols/index.d.ts +0 -1
  57. package/dist/protocols/index.js +9 -1
  58. package/dist/registry/Registry.d.ts +397 -48
  59. package/dist/registry/Registry.js +263 -11
  60. package/dist/registry/WidgetRegistry.js +7 -3
  61. package/dist/registry/html-tier-intrinsics.d.ts +90 -0
  62. package/dist/registry/html-tier-intrinsics.js +110 -0
  63. package/dist/registry/public-blocks.d.ts +8 -0
  64. package/dist/registry/public-blocks.js +57 -0
  65. package/dist/theme/ThemeEngine.js +1 -1
  66. package/dist/theme/index.d.ts +1 -1
  67. package/dist/theme/index.js +1 -1
  68. package/dist/types/index.d.ts +92 -13
  69. package/dist/utils/chart-category-key.d.ts +82 -0
  70. package/dist/utils/chart-category-key.js +84 -0
  71. package/dist/utils/chart-measure-key.d.ts +64 -0
  72. package/dist/utils/chart-measure-key.js +65 -0
  73. package/dist/utils/chart-presentation.d.ts +188 -0
  74. package/dist/utils/chart-presentation.js +345 -0
  75. package/dist/utils/chart-series.d.ts +271 -31
  76. package/dist/utils/chart-series.js +478 -55
  77. package/dist/utils/column-identity.d.ts +43 -0
  78. package/dist/utils/column-identity.js +47 -1
  79. package/dist/utils/column-sortability.d.ts +157 -0
  80. package/dist/utils/column-sortability.js +217 -0
  81. package/dist/utils/dashboard-filters.d.ts +38 -6
  82. package/dist/utils/dashboard-filters.js +107 -61
  83. package/dist/utils/dataset-format.d.ts +211 -30
  84. package/dist/utils/dataset-format.js +407 -52
  85. package/dist/utils/date-display.d.ts +354 -0
  86. package/dist/utils/date-display.js +463 -0
  87. package/dist/utils/date-macros.js +7 -0
  88. package/dist/utils/debug.js +7 -0
  89. package/dist/utils/drill-down.d.ts +78 -0
  90. package/dist/utils/drill-down.js +93 -0
  91. package/dist/utils/emptiness.d.ts +96 -0
  92. package/dist/utils/emptiness.js +101 -0
  93. package/dist/utils/expand-fields.d.ts +171 -11
  94. package/dist/utils/expand-fields.js +171 -11
  95. package/dist/utils/extract-records.d.ts +65 -5
  96. package/dist/utils/extract-records.js +65 -8
  97. package/dist/utils/filter-converter.d.ts +152 -4
  98. package/dist/utils/filter-converter.js +1092 -22
  99. package/dist/utils/filter-tokens.d.ts +49 -5
  100. package/dist/utils/filter-tokens.js +105 -34
  101. package/dist/utils/freeze-schema.d.ts +38 -1
  102. package/dist/utils/freeze-schema.js +10 -0
  103. package/dist/utils/grouping-fields.d.ts +61 -0
  104. package/dist/utils/grouping-fields.js +84 -0
  105. package/dist/utils/humanize-label.d.ts +60 -0
  106. package/dist/utils/humanize-label.js +62 -0
  107. package/dist/utils/managedBy.d.ts +30 -5
  108. package/dist/utils/managedBy.js +5 -5
  109. package/dist/utils/native-date-value.d.ts +48 -0
  110. package/dist/utils/native-date-value.js +167 -0
  111. package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
  112. package/dist/utils/non-grid-row-ceiling.js +141 -0
  113. package/dist/utils/normalize-list-view.d.ts +131 -1
  114. package/dist/utils/normalize-list-view.js +248 -6
  115. package/dist/utils/number-display.d.ts +70 -17
  116. package/dist/utils/number-display.js +34 -12
  117. package/dist/utils/object-aggregate-query.d.ts +113 -0
  118. package/dist/utils/object-aggregate-query.js +50 -0
  119. package/dist/utils/parent-scope.d.ts +123 -0
  120. package/dist/utils/parent-scope.js +140 -0
  121. package/dist/utils/predicate-fields.d.ts +62 -4
  122. package/dist/utils/predicate-fields.js +169 -4
  123. package/dist/utils/record-color.d.ts +75 -0
  124. package/dist/utils/record-color.js +65 -0
  125. package/dist/utils/record-source.d.ts +298 -0
  126. package/dist/utils/record-source.js +331 -0
  127. package/dist/utils/record-title.d.ts +148 -8
  128. package/dist/utils/record-title.js +220 -26
  129. package/dist/utils/reference-keys.d.ts +21 -22
  130. package/dist/utils/reference-keys.js +532 -29
  131. package/dist/utils/retired-field-types.d.ts +90 -0
  132. package/dist/utils/retired-field-types.js +124 -0
  133. package/dist/utils/sort-query.d.ts +128 -26
  134. package/dist/utils/sort-query.js +240 -13
  135. package/dist/utils/text-comparand.d.ts +92 -0
  136. package/dist/utils/text-comparand.js +99 -0
  137. package/dist/utils/unmaterialized-fields.d.ts +52 -0
  138. package/dist/utils/unmaterialized-fields.js +57 -0
  139. package/dist/utils/without-denied-fields.d.ts +49 -0
  140. package/dist/utils/without-denied-fields.js +52 -0
  141. package/dist/validation/index.d.ts +9 -4
  142. package/dist/validation/index.js +9 -4
  143. package/dist/validation/schema-validator.d.ts +1 -1
  144. package/dist/validation/schema-validator.js +305 -82
  145. package/dist/validation/server-owned-value.d.ts +62 -0
  146. package/dist/validation/server-owned-value.js +99 -0
  147. package/dist/validation/validators/object-validation-engine.d.ts +1 -1
  148. package/dist/validation/validators/object-validation-engine.js +14 -5
  149. package/package.json +8 -9
  150. package/dist/__benchmarks__/core.bench.d.ts +0 -8
  151. package/dist/__benchmarks__/core.bench.js +0 -53
  152. package/dist/data-scope/DataScopeManager.d.ts +0 -127
  153. package/dist/data-scope/DataScopeManager.js +0 -229
  154. package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
  155. package/dist/protocols/ResponsiveProtocol.js +0 -158
  156. package/dist/validation/validation-engine.d.ts +0 -88
  157. package/dist/validation/validation-engine.js +0 -434
@@ -0,0 +1,188 @@
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 { ChartSeriesBinding } from './chart-series.js';
9
+ /** Authored spec `ChartSeries` presentation, in the renderer's internal spelling. */
10
+ export interface AuthoredSeriesPresentation {
11
+ label?: string;
12
+ /** Spec `ChartSeries.type`, narrowed — see {@link seriesPresentation}. */
13
+ chartType?: 'bar' | 'line' | 'area';
14
+ yAxis?: 'left' | 'right';
15
+ color?: string;
16
+ stack?: string;
17
+ variant?: 'primary' | 'comparison';
18
+ dashArray?: string;
19
+ opacity?: number;
20
+ }
21
+ /** A derived series binding with the author's presentation merged onto it. */
22
+ export type MergedChartSeries = ChartSeriesBinding & AuthoredSeriesPresentation;
23
+ /**
24
+ * One authored `ChartSeries`, minus its `name` — i.e. everything about it that
25
+ * is presentation rather than membership.
26
+ *
27
+ * `type` is narrowed to the three families that COMPOSE on one cartesian plot.
28
+ * Without the narrowing a `type: 'pie'` would not merely be inert — it would
29
+ * count as a family disagreement in `effectiveChartFamily`, flip the whole
30
+ * chart into a combo, and then draw that series as a bar anyway.
31
+ *
32
+ * `ChartRenderer` now narrows the same way for every series it receives,
33
+ * `dataKey`-shaped or not (`01c27c431` fixed the fast path that used to
34
+ * forward a `dataKey`-shaped array straight through, so `normalizeChartSchema`'s
35
+ * own identical narrowing never saw it) — so this module is no longer that
36
+ * narrowing's only line of defense. It stays anyway:
37
+ * `AuthoredSeriesPresentation.chartType` is a typed, narrower field
38
+ * (`'bar' | 'line' | 'area'`) this module's own callers read directly, and the
39
+ * double narrowing downstream is idempotent.
40
+ */
41
+ export declare function seriesPresentation(raw: Record<string, unknown>): AuthoredSeriesPresentation;
42
+ /**
43
+ * Merge an authored `series[]` array's PRESENTATION onto the bindings the
44
+ * dataset selection derived — the series half of #4229's split, and the only
45
+ * half this module lowers (see the file header).
46
+ *
47
+ * The match rule is **by name/key**: an authored `series[].name` is paired with
48
+ * the derived binding whose `dataKey` it equals, and the pairing decides
49
+ * nothing but presentation:
50
+ *
51
+ * - an authored entry naming a measure that is NOT in the dataset selection is
52
+ * **ignored** — membership belongs to the dataset, so an author cannot add,
53
+ * remove or re-point a series from the chart config;
54
+ * - a derived series with no authored entry keeps the family default, so every
55
+ * surface that never wrote `series` renders byte-for-byte as before;
56
+ * - where both exist the **explicit binding wins** (#2880 S2), which is the
57
+ * whole point: `type: 'line'` + `yAxis: 'right'` is how the spec says "this
58
+ * measure is a line on the secondary axis".
59
+ *
60
+ * Matching on `name` only is deliberate: `name` is the spec's authorable key
61
+ * for a series (`dataKey` is a declared ALIAS of it, resolved where the
62
+ * metadata is parsed), so reading a second spelling here would fossilize a
63
+ * dialect this renderer has no business accepting (AGENTS.md #0.1).
64
+ *
65
+ * The FIRST entry naming a measure wins; a later duplicate cannot silently
66
+ * reconfigure a series the author already described.
67
+ *
68
+ * @param derived the bindings {@link buildChartSeries} produced from the selection
69
+ * @param authoredSeries the authored `series` array (anything, incl. absent)
70
+ */
71
+ export declare function mergeAuthoredSeries(derived: ChartSeriesBinding[], authoredSeries: unknown): MergedChartSeries[];
72
+ /**
73
+ * Lower an authored chart config's CHROME (spec
74
+ * `DashboardWidgetChartConfigSchema` / `ReportChartSchema`, both extensions of
75
+ * `ChartConfigSchema` that keep its chrome keys unchanged) onto the chart
76
+ * schema a dataset-bound surface hands the renderer.
77
+ *
78
+ * ## Why this is a whitelist and not a spread
79
+ *
80
+ * Until #3135 NONE of a dashboard widget's `chartConfig` reached the renderer:
81
+ * the widget read `options` and nothing else, so an author who wrote
82
+ * `showLegend: false` still got a legend and one who wrote `true` only got one
83
+ * because "on" is the renderer's default. #3135 lowered that single flag and
84
+ * left the rest declared and inert. objectstack#7016 lowered the rest of the
85
+ * keys that are actually DELIVERED, admitting a key only when both of these
86
+ * hold:
87
+ *
88
+ * 1. **The chart block draws it end to end.** `{ type: 'chart' }` resolves to
89
+ * `ChartRenderer` → `AdvancedChartImpl`, which draws `title`/`subtitle` in
90
+ * its ChartFrame, turns `description` into the chart container's
91
+ * `role="img"` + `aria-label`, applies `height` as that container's inline
92
+ * height, reads `colors` as the positional palette, prints
93
+ * `showDataLabels` as a Recharts `LabelList`, draws `annotations` as
94
+ * ReferenceLine/ReferenceArea and honours `interaction` as the tooltip
95
+ * toggle plus `Brush`. Forwarding a key the renderer ignores would only
96
+ * move declared-but-not-delivered one layer down, which is the failure this
97
+ * exists to remove.
98
+ * 2. **It does not fight the dataset derivation.** `type` stays out: the
99
+ * calling surface's own type already picks the chart family, which is the
100
+ * dataset path's chart-family channel. The spec agrees on the dashboard
101
+ * carrier: since 17.5.0 `DashboardWidgetChartConfigSchema` refuses
102
+ * `chartConfig.type` at parse, with `xAxis` / `yAxis` / `series`, none of
103
+ * which this whitelist ever lowered (objectui#11315).
104
+ *
105
+ * `aria` is not lowered either. No renderer reads it: `AdvancedChartImpl` has
106
+ * no `aria` prop, and `SchemaRenderer`'s ARIA injection reads the FLAT
107
+ * `ariaLabel`/`ariaDescribedBy`/`role`, never a nested `aria` object (criterion
108
+ * 1). And it is no longer declared: spec 17.5.0 retired `ChartConfig.aria` as
109
+ * a `retiredKey` tombstone (objectstack#17751, objectui#4044), so an authored
110
+ * one is refused at parse. The accessible name a chart applies is
111
+ * `description`, above.
112
+ *
113
+ * `title` IS emitted here. A caller that paints the title itself (the report
114
+ * renderer's own `h3` above the chart) must drop it from the result, or the
115
+ * chart draws a second one.
116
+ *
117
+ * ## `title` / `subtitle` / `description` are `I18nLabel`, and travel as such
118
+ *
119
+ * `ChartConfigSchema` types all three as the spec's `I18nLabel` union — a plain
120
+ * string OR an inline locale map — so all three are lowered by
121
+ * {@link forwardedI18nLabel}, which hands the authored value on untouched for
122
+ * the renderer to resolve against the viewer's language. Read that helper for
123
+ * why resolving cannot happen in this package.
124
+ *
125
+ * ⚠️ LEDGERED, by name, as still unresolved after objectui#9038 — not in
126
+ * that card's scope and not reached by the change above:
127
+ *
128
+ * - **A series `label`** still goes through {@link labelText}'s
129
+ * first-string-wins pick, the design objectui#4020 ledgered and a caller
130
+ * can override. Not reopened here.
131
+ *
132
+ * ✓ CLEARED, objectui#9150 — **the report renderer's own heading.**
133
+ * `DatasetReportChart` (`@object-ui/plugin-report`) still drops `title` from
134
+ * this result and still paints its own `h3`, but that read site no longer
135
+ * narrows `chart.title` to a plain string: it resolves the union through
136
+ * `pickLocalized` against the viewer's language, like every other `I18nLabel`
137
+ * on that surface. Kept here rather than deleted because the ledger entry is
138
+ * what the fixing card was dispatched from, and because the FIRST half of it —
139
+ * that a caller painting the title itself must drop `title` from this result —
140
+ * is a live constraint on this whitelist, stated above.
141
+ *
142
+ * @param raw the authored chart config (anything, incl. absent)
143
+ * @param fieldCategoryColors per-category colours resolved from the category
144
+ * dimension's own select/lookup option colours, merged UNDER an explicit
145
+ * author map (see the `colors` note below)
146
+ * @returns only the keys that resolved, so the caller can spread it over the
147
+ * derived chart schema and every undeclared key keeps the renderer's default
148
+ */
149
+ export declare function chartConfigPresentation(raw: unknown, fieldCategoryColors?: Record<string, string> | null): Record<string, unknown>;
150
+ /**
151
+ * Whether a chart of this family IGNORES `compareTo` — the one declaration of
152
+ * that rule (objectui#7495).
153
+ *
154
+ * Every surface that turns a `compareTo` into a comparison reads this; none
155
+ * keeps a list of its own. The inline chart path (`@object-ui/plugin-charts`'
156
+ * ObjectChart) skips the comparison fetch and synthesises no overlay series for
157
+ * these families, and the dataset widget path (`@object-ui/plugin-dashboard`'s
158
+ * DatasetWidget) forwards no `compareTo` to the executor for a chart of one of
159
+ * them, so the comparison pass never runs and no overlay series is appended.
160
+ *
161
+ * - `pie` / `donut` / `funnel` — single-distribution charts: a comparison
162
+ * overlay has no meaning on them, and the renderer's pie and funnel arms
163
+ * draw only `series[0]`.
164
+ * - `scatter` (objectui#7402) — a scatter binds ONE measure: the renderer reads
165
+ * y through the single `YAxis dataKey={series[0].dataKey}`, so an overlay was
166
+ * painted on the PRIMARY's y and "previous period" landed exactly on top of
167
+ * "current". Drawing it honestly needs the multi-measure projection declined
168
+ * as option A of objectui#7194; `compareTo` on a scatter returns WITH that
169
+ * projection.
170
+ *
171
+ * It reads the RENDERER's chart family (`bar`, `pie`, `scatter`, …), not a
172
+ * dashboard widget type: a caller whose widget types alias a family (the
173
+ * dashboard maps `bubble` to `scatter` and `pyramid` to `funnel`) maps first and
174
+ * asks about the family. An absent or unrecognised family does NOT ignore
175
+ * `compareTo` — the comparison runs.
176
+ *
177
+ * Why it lives here: the dashboard reaches `@object-ui/plugin-charts` only as a
178
+ * devDependency, so this package — a runtime dependency of both — is the one
179
+ * place both paths can read. Two copies were how the lists drifted apart: the
180
+ * dashboard's said "scatter only" while the charts package said all four, so a
181
+ * compare-to pie widget ran a comparison query whose overlay the renderer then
182
+ * dropped (objectui#7495; objectui#4389 is the precedent that a second copy of
183
+ * chart-presentation logic is a defect).
184
+ *
185
+ * @param chartType the chart family, or `undefined` when none is declared
186
+ * @returns `true` for `pie`, `donut`, `funnel` and `scatter`; `false` otherwise
187
+ */
188
+ export declare function chartTypeIgnoresCompareTo(chartType: string | undefined): boolean;
@@ -0,0 +1,345 @@
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
+ const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
9
+ /**
10
+ * An i18n label is a plain string or a `{ en, zh-CN, … }` record; charts render
11
+ * a string.
12
+ *
13
+ * ⚠️ Its one caller is {@link seriesPresentation}'s `label`. An axis `title`
14
+ * read this too until `061f5e829` moved it off the pick, and the axis lowering
15
+ * itself is gone since objectui#11372 (file header).
16
+ *
17
+ * **First-string-wins, deliberately, and deliberately NOT locale-aware** — this
18
+ * package is React-free and holds no i18n provider, so it cannot know which
19
+ * limb a console wants. A caller that CAN resolve the language (the report
20
+ * renderer, via `pickLocalized`) resolves the label itself and overrides the
21
+ * merged one; see objectui#4020, whose whole point is that picking the first
22
+ * limb paints English on a zh console.
23
+ */
24
+ function labelText(v) {
25
+ if (typeof v === 'string' && v)
26
+ return v;
27
+ if (isRecord(v)) {
28
+ const first = Object.values(v).find((x) => typeof x === 'string' && x);
29
+ return first;
30
+ }
31
+ return undefined;
32
+ }
33
+ /**
34
+ * An authored `I18nLabel` that travels to the renderer **unresolved** — the
35
+ * chart's own `title` / `subtitle` / `description` (objectui#9038).
36
+ *
37
+ * ## Why this does not resolve, and why that is not {@link labelText}'s answer
38
+ *
39
+ * These three keys are lowered onto a chart SCHEMA slot that already declares
40
+ * the union: `normalizeChartSchema` (`@object-ui/plugin-charts`) resolves
41
+ * `title`/`subtitle`/`description` through `pickLocalized` with the viewer's
42
+ * language, which `ChartRenderer` reads from `useObjectTranslation` and hands
43
+ * down (objectui#8943), and `ObjectChart` reads `schema.title` through the same
44
+ * resolver for its drill heading. So the value's one resolution point is
45
+ * already downstream of here, holding the one thing this package does not have
46
+ * — the viewer. Forwarding is what lets that resolver see the value at all.
47
+ *
48
+ * Resolving here is not merely unnecessary, it is unavailable: `pickLocalized`
49
+ * lives in `@object-ui/i18n`, which DEPENDS on this package. Declaring the
50
+ * reverse edge makes the build graph cyclic (measured: `turbo run build
51
+ * --filter=@object-ui/core` refuses with `Cyclic dependency detected`), and
52
+ * that package's entry point is a React provider plus hooks — the layering
53
+ * this file's header states, arrived at from the other side.
54
+ *
55
+ * ⛔ Do NOT "fix" this by reusing {@link labelText}. The two answer different
56
+ * questions and only one of them is a choice:
57
+ *
58
+ * - `labelText` is a locale-unaware PICK for a slot whose consumer takes a
59
+ * plain string. It is wrong for a zh console and is ledgered as such
60
+ * (objectui#4020) because a caller that CAN resolve the language overrides
61
+ * the merged label — the mitigation depends on the value still existing.
62
+ * - The guard this replaced (`typeof v === 'string' && v ? v : undefined`)
63
+ * offered no such route: it ERASED the map arm, the `if (title)` guard then
64
+ * skipped the assignment, and the chart drew **no heading at all**, in every
65
+ * language, with no diagnostic. Nothing downstream could override a key that
66
+ * never arrived.
67
+ *
68
+ * ## The admission test, and what it deliberately does not decide
69
+ *
70
+ * A plain string, or a record carrying at least one usable string entry. That
71
+ * is the same "is there anything here" question the `if (title)` truthiness
72
+ * guard always asked, extended to the map arm — it does NOT decide which limb
73
+ * wins, which stays `pickLocalized`'s alone. Everything else (a number, a
74
+ * boolean, an array, `''`, `{}`, a record with no string value) is refused
75
+ * exactly as before, so no key reaches the caller that cannot render, and the
76
+ * `@returns` contract below — only keys that resolved — still holds.
77
+ *
78
+ * The map is forwarded VERBATIM: `InlineLocaleMapSchema` is
79
+ * `z.record(<tag>, z.string())` and enforcing that belongs at the parse, not in
80
+ * a renderer-side coercion (AGENTS.md #0.1). A non-string entry falls through
81
+ * `pickLocalized`'s limbs exactly as an absent one does, which is why admitting
82
+ * the record on the strength of one usable entry cannot paint `[object
83
+ * Object]`.
84
+ */
85
+ function forwardedI18nLabel(v) {
86
+ if (typeof v === 'string')
87
+ return v || undefined;
88
+ if (isRecord(v) && Object.values(v).some((x) => typeof x === 'string' && x)) {
89
+ return v;
90
+ }
91
+ return undefined;
92
+ }
93
+ /**
94
+ * One authored `ChartSeries`, minus its `name` — i.e. everything about it that
95
+ * is presentation rather than membership.
96
+ *
97
+ * `type` is narrowed to the three families that COMPOSE on one cartesian plot.
98
+ * Without the narrowing a `type: 'pie'` would not merely be inert — it would
99
+ * count as a family disagreement in `effectiveChartFamily`, flip the whole
100
+ * chart into a combo, and then draw that series as a bar anyway.
101
+ *
102
+ * `ChartRenderer` now narrows the same way for every series it receives,
103
+ * `dataKey`-shaped or not (`01c27c431` fixed the fast path that used to
104
+ * forward a `dataKey`-shaped array straight through, so `normalizeChartSchema`'s
105
+ * own identical narrowing never saw it) — so this module is no longer that
106
+ * narrowing's only line of defense. It stays anyway:
107
+ * `AuthoredSeriesPresentation.chartType` is a typed, narrower field
108
+ * (`'bar' | 'line' | 'area'`) this module's own callers read directly, and the
109
+ * double narrowing downstream is idempotent.
110
+ */
111
+ export function seriesPresentation(raw) {
112
+ const out = {};
113
+ const family = raw.type;
114
+ if (family === 'bar' || family === 'line' || family === 'area')
115
+ out.chartType = family;
116
+ if (raw.yAxis === 'left' || raw.yAxis === 'right')
117
+ out.yAxis = raw.yAxis;
118
+ const label = labelText(raw.label);
119
+ if (label)
120
+ out.label = label;
121
+ if (typeof raw.color === 'string' && raw.color)
122
+ out.color = raw.color;
123
+ if (typeof raw.stack === 'string' && raw.stack)
124
+ out.stack = raw.stack;
125
+ if (raw.variant === 'primary' || raw.variant === 'comparison')
126
+ out.variant = raw.variant;
127
+ if (typeof raw.dashArray === 'string' && raw.dashArray)
128
+ out.dashArray = raw.dashArray;
129
+ if (typeof raw.opacity === 'number' && Number.isFinite(raw.opacity))
130
+ out.opacity = raw.opacity;
131
+ return out;
132
+ }
133
+ /**
134
+ * Merge an authored `series[]` array's PRESENTATION onto the bindings the
135
+ * dataset selection derived — the series half of #4229's split, and the only
136
+ * half this module lowers (see the file header).
137
+ *
138
+ * The match rule is **by name/key**: an authored `series[].name` is paired with
139
+ * the derived binding whose `dataKey` it equals, and the pairing decides
140
+ * nothing but presentation:
141
+ *
142
+ * - an authored entry naming a measure that is NOT in the dataset selection is
143
+ * **ignored** — membership belongs to the dataset, so an author cannot add,
144
+ * remove or re-point a series from the chart config;
145
+ * - a derived series with no authored entry keeps the family default, so every
146
+ * surface that never wrote `series` renders byte-for-byte as before;
147
+ * - where both exist the **explicit binding wins** (#2880 S2), which is the
148
+ * whole point: `type: 'line'` + `yAxis: 'right'` is how the spec says "this
149
+ * measure is a line on the secondary axis".
150
+ *
151
+ * Matching on `name` only is deliberate: `name` is the spec's authorable key
152
+ * for a series (`dataKey` is a declared ALIAS of it, resolved where the
153
+ * metadata is parsed), so reading a second spelling here would fossilize a
154
+ * dialect this renderer has no business accepting (AGENTS.md #0.1).
155
+ *
156
+ * The FIRST entry naming a measure wins; a later duplicate cannot silently
157
+ * reconfigure a series the author already described.
158
+ *
159
+ * @param derived the bindings {@link buildChartSeries} produced from the selection
160
+ * @param authoredSeries the authored `series` array (anything, incl. absent)
161
+ */
162
+ export function mergeAuthoredSeries(derived, authoredSeries) {
163
+ const authored = new Map();
164
+ for (const entry of Array.isArray(authoredSeries) ? authoredSeries : []) {
165
+ if (!isRecord(entry))
166
+ continue;
167
+ const name = typeof entry.name === 'string' ? entry.name : undefined;
168
+ if (name && !authored.has(name))
169
+ authored.set(name, entry);
170
+ }
171
+ return derived.map((s) => {
172
+ const entry = authored.get(s.dataKey);
173
+ return entry ? { ...s, ...seriesPresentation(entry) } : s;
174
+ });
175
+ }
176
+ /**
177
+ * Lower an authored chart config's CHROME (spec
178
+ * `DashboardWidgetChartConfigSchema` / `ReportChartSchema`, both extensions of
179
+ * `ChartConfigSchema` that keep its chrome keys unchanged) onto the chart
180
+ * schema a dataset-bound surface hands the renderer.
181
+ *
182
+ * ## Why this is a whitelist and not a spread
183
+ *
184
+ * Until #3135 NONE of a dashboard widget's `chartConfig` reached the renderer:
185
+ * the widget read `options` and nothing else, so an author who wrote
186
+ * `showLegend: false` still got a legend and one who wrote `true` only got one
187
+ * because "on" is the renderer's default. #3135 lowered that single flag and
188
+ * left the rest declared and inert. objectstack#7016 lowered the rest of the
189
+ * keys that are actually DELIVERED, admitting a key only when both of these
190
+ * hold:
191
+ *
192
+ * 1. **The chart block draws it end to end.** `{ type: 'chart' }` resolves to
193
+ * `ChartRenderer` → `AdvancedChartImpl`, which draws `title`/`subtitle` in
194
+ * its ChartFrame, turns `description` into the chart container's
195
+ * `role="img"` + `aria-label`, applies `height` as that container's inline
196
+ * height, reads `colors` as the positional palette, prints
197
+ * `showDataLabels` as a Recharts `LabelList`, draws `annotations` as
198
+ * ReferenceLine/ReferenceArea and honours `interaction` as the tooltip
199
+ * toggle plus `Brush`. Forwarding a key the renderer ignores would only
200
+ * move declared-but-not-delivered one layer down, which is the failure this
201
+ * exists to remove.
202
+ * 2. **It does not fight the dataset derivation.** `type` stays out: the
203
+ * calling surface's own type already picks the chart family, which is the
204
+ * dataset path's chart-family channel. The spec agrees on the dashboard
205
+ * carrier: since 17.5.0 `DashboardWidgetChartConfigSchema` refuses
206
+ * `chartConfig.type` at parse, with `xAxis` / `yAxis` / `series`, none of
207
+ * which this whitelist ever lowered (objectui#11315).
208
+ *
209
+ * `aria` is not lowered either. No renderer reads it: `AdvancedChartImpl` has
210
+ * no `aria` prop, and `SchemaRenderer`'s ARIA injection reads the FLAT
211
+ * `ariaLabel`/`ariaDescribedBy`/`role`, never a nested `aria` object (criterion
212
+ * 1). And it is no longer declared: spec 17.5.0 retired `ChartConfig.aria` as
213
+ * a `retiredKey` tombstone (objectstack#17751, objectui#4044), so an authored
214
+ * one is refused at parse. The accessible name a chart applies is
215
+ * `description`, above.
216
+ *
217
+ * `title` IS emitted here. A caller that paints the title itself (the report
218
+ * renderer's own `h3` above the chart) must drop it from the result, or the
219
+ * chart draws a second one.
220
+ *
221
+ * ## `title` / `subtitle` / `description` are `I18nLabel`, and travel as such
222
+ *
223
+ * `ChartConfigSchema` types all three as the spec's `I18nLabel` union — a plain
224
+ * string OR an inline locale map — so all three are lowered by
225
+ * {@link forwardedI18nLabel}, which hands the authored value on untouched for
226
+ * the renderer to resolve against the viewer's language. Read that helper for
227
+ * why resolving cannot happen in this package.
228
+ *
229
+ * ⚠️ LEDGERED, by name, as still unresolved after objectui#9038 — not in
230
+ * that card's scope and not reached by the change above:
231
+ *
232
+ * - **A series `label`** still goes through {@link labelText}'s
233
+ * first-string-wins pick, the design objectui#4020 ledgered and a caller
234
+ * can override. Not reopened here.
235
+ *
236
+ * ✓ CLEARED, objectui#9150 — **the report renderer's own heading.**
237
+ * `DatasetReportChart` (`@object-ui/plugin-report`) still drops `title` from
238
+ * this result and still paints its own `h3`, but that read site no longer
239
+ * narrows `chart.title` to a plain string: it resolves the union through
240
+ * `pickLocalized` against the viewer's language, like every other `I18nLabel`
241
+ * on that surface. Kept here rather than deleted because the ledger entry is
242
+ * what the fixing card was dispatched from, and because the FIRST half of it —
243
+ * that a caller painting the title itself must drop `title` from this result —
244
+ * is a live constraint on this whitelist, stated above.
245
+ *
246
+ * @param raw the authored chart config (anything, incl. absent)
247
+ * @param fieldCategoryColors per-category colours resolved from the category
248
+ * dimension's own select/lookup option colours, merged UNDER an explicit
249
+ * author map (see the `colors` note below)
250
+ * @returns only the keys that resolved, so the caller can spread it over the
251
+ * derived chart schema and every undeclared key keeps the renderer's default
252
+ */
253
+ export function chartConfigPresentation(raw, fieldCategoryColors) {
254
+ const config = isRecord(raw) ? raw : {};
255
+ const out = {};
256
+ if (typeof config.showLegend === 'boolean')
257
+ out.showLegend = config.showLegend;
258
+ if (typeof config.showDataLabels === 'boolean')
259
+ out.showDataLabels = config.showDataLabels;
260
+ // The three `I18nLabel` slots, carried through unresolved — see
261
+ // {@link forwardedI18nLabel} for why this package forwards rather than picks,
262
+ // and for what is still ledgered as unresolved on purpose.
263
+ const title = forwardedI18nLabel(config.title);
264
+ if (title)
265
+ out.title = title;
266
+ const subtitle = forwardedI18nLabel(config.subtitle);
267
+ if (subtitle)
268
+ out.subtitle = subtitle;
269
+ const description = forwardedI18nLabel(config.description);
270
+ if (description)
271
+ out.description = description;
272
+ // A non-positive height would collapse the plot; the container default is the
273
+ // more honest answer than an invisible chart.
274
+ if (typeof config.height === 'number' && Number.isFinite(config.height) && config.height > 0) {
275
+ out.height = config.height;
276
+ }
277
+ if (Array.isArray(config.annotations) && config.annotations.length > 0)
278
+ out.annotations = config.annotations;
279
+ if (config.interaction && typeof config.interaction === 'object' && !Array.isArray(config.interaction)) {
280
+ out.interaction = config.interaction;
281
+ }
282
+ // `colors` is overloaded kanban-style — and the two arms reach the renderer
283
+ // through two DIFFERENT props, so the split has to happen here (the react
284
+ // tier's ObjectChart splits it the same way): a `string[]` is the positional
285
+ // palette (`colors`), a `{ value: color }` record is an explicit per-category
286
+ // map (`categoryColors`). The author's map is merged OVER the dimension
287
+ // field's own option colours, which is the precedence the spec field comment
288
+ // states ("a value→color map — and a select/lookup dimension's option colors
289
+ // — take precedence over the positional palette per category").
290
+ const palette = Array.isArray(config.colors)
291
+ ? config.colors.filter((c) => typeof c === 'string' && !!c)
292
+ : undefined;
293
+ if (palette?.length)
294
+ out.colors = palette;
295
+ const authorCategoryColors = config.colors && typeof config.colors === 'object' && !Array.isArray(config.colors)
296
+ ? config.colors
297
+ : undefined;
298
+ if (fieldCategoryColors || authorCategoryColors) {
299
+ out.categoryColors = { ...(fieldCategoryColors ?? {}), ...(authorCategoryColors ?? {}) };
300
+ }
301
+ return out;
302
+ }
303
+ /** The families {@link chartTypeIgnoresCompareTo} answers `true` for. */
304
+ const CHART_TYPES_IGNORING_COMPARE_TO = new Set(['pie', 'donut', 'funnel', 'scatter']);
305
+ /**
306
+ * Whether a chart of this family IGNORES `compareTo` — the one declaration of
307
+ * that rule (objectui#7495).
308
+ *
309
+ * Every surface that turns a `compareTo` into a comparison reads this; none
310
+ * keeps a list of its own. The inline chart path (`@object-ui/plugin-charts`'
311
+ * ObjectChart) skips the comparison fetch and synthesises no overlay series for
312
+ * these families, and the dataset widget path (`@object-ui/plugin-dashboard`'s
313
+ * DatasetWidget) forwards no `compareTo` to the executor for a chart of one of
314
+ * them, so the comparison pass never runs and no overlay series is appended.
315
+ *
316
+ * - `pie` / `donut` / `funnel` — single-distribution charts: a comparison
317
+ * overlay has no meaning on them, and the renderer's pie and funnel arms
318
+ * draw only `series[0]`.
319
+ * - `scatter` (objectui#7402) — a scatter binds ONE measure: the renderer reads
320
+ * y through the single `YAxis dataKey={series[0].dataKey}`, so an overlay was
321
+ * painted on the PRIMARY's y and "previous period" landed exactly on top of
322
+ * "current". Drawing it honestly needs the multi-measure projection declined
323
+ * as option A of objectui#7194; `compareTo` on a scatter returns WITH that
324
+ * projection.
325
+ *
326
+ * It reads the RENDERER's chart family (`bar`, `pie`, `scatter`, …), not a
327
+ * dashboard widget type: a caller whose widget types alias a family (the
328
+ * dashboard maps `bubble` to `scatter` and `pyramid` to `funnel`) maps first and
329
+ * asks about the family. An absent or unrecognised family does NOT ignore
330
+ * `compareTo` — the comparison runs.
331
+ *
332
+ * Why it lives here: the dashboard reaches `@object-ui/plugin-charts` only as a
333
+ * devDependency, so this package — a runtime dependency of both — is the one
334
+ * place both paths can read. Two copies were how the lists drifted apart: the
335
+ * dashboard's said "scatter only" while the charts package said all four, so a
336
+ * compare-to pie widget ran a comparison query whose overlay the renderer then
337
+ * dropped (objectui#7495; objectui#4389 is the precedent that a second copy of
338
+ * chart-presentation logic is a defect).
339
+ *
340
+ * @param chartType the chart family, or `undefined` when none is declared
341
+ * @returns `true` for `pie`, `donut`, `funnel` and `scatter`; `false` otherwise
342
+ */
343
+ export function chartTypeIgnoresCompareTo(chartType) {
344
+ return typeof chartType === 'string' && CHART_TYPES_IGNORING_COMPARE_TO.has(chartType);
345
+ }