@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
@@ -0,0 +1,280 @@
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. Same pick `normalizeChartSchema` makes, so a label reads the same
12
+ * on both paths.
13
+ *
14
+ * **First-string-wins, deliberately, and deliberately NOT locale-aware** — this
15
+ * package is React-free and holds no i18n provider, so it cannot know which
16
+ * limb a console wants. A caller that CAN resolve the language (the report
17
+ * renderer, via `pickLocalized`) resolves the label itself and overrides the
18
+ * merged one; see objectui#4020, whose whole point is that picking the first
19
+ * limb paints English on a zh console.
20
+ */
21
+ function labelText(v) {
22
+ if (typeof v === 'string' && v)
23
+ return v;
24
+ if (isRecord(v)) {
25
+ const first = Object.values(v).find((x) => typeof x === 'string' && x);
26
+ return first;
27
+ }
28
+ return undefined;
29
+ }
30
+ /**
31
+ * One authored `ChartSeries`, minus its `name` — i.e. everything about it that
32
+ * is presentation rather than membership.
33
+ *
34
+ * `type` is narrowed to the three families that COMPOSE on one cartesian plot,
35
+ * because this array reaches the renderer already speaking the internal shape
36
+ * (`ChartRenderer` forwards a `dataKey`-shaped array untouched, so
37
+ * `normalizeChartSchema`'s own identical narrowing never sees it). Without the
38
+ * narrowing a `type: 'pie'` would not merely be inert — it would count as a
39
+ * family disagreement in `effectiveChartFamily`, flip the whole chart into a
40
+ * combo, and then draw that series as a bar anyway.
41
+ */
42
+ export function seriesPresentation(raw) {
43
+ const out = {};
44
+ const family = raw.type;
45
+ if (family === 'bar' || family === 'line' || family === 'area')
46
+ out.chartType = family;
47
+ if (raw.yAxis === 'left' || raw.yAxis === 'right')
48
+ out.yAxis = raw.yAxis;
49
+ const label = labelText(raw.label);
50
+ if (label)
51
+ out.label = label;
52
+ if (typeof raw.color === 'string' && raw.color)
53
+ out.color = raw.color;
54
+ if (typeof raw.stack === 'string' && raw.stack)
55
+ out.stack = raw.stack;
56
+ if (raw.variant === 'primary' || raw.variant === 'comparison')
57
+ out.variant = raw.variant;
58
+ if (typeof raw.dashArray === 'string' && raw.dashArray)
59
+ out.dashArray = raw.dashArray;
60
+ if (typeof raw.opacity === 'number' && Number.isFinite(raw.opacity))
61
+ out.opacity = raw.opacity;
62
+ return out;
63
+ }
64
+ /**
65
+ * One authored `ChartAxis`, minus its `field` — the axis's presentation.
66
+ *
67
+ * `field` is the one DATA key on an axis (it names the plotted column), and
68
+ * dropping it here is what keeps membership with the dataset **structurally**
69
+ * rather than by a guard: `normalizeChartSchema` synthesises series out of
70
+ * `yAxis[].field` when a chart declares no series, so a forwarded `field`
71
+ * would be a live membership channel on an empty selection. With it gone the
72
+ * axis carries scale and chrome only, and the count of entries — which is what
73
+ * turns on the secondary axis (`yAxes.length > 1`) — survives, including for
74
+ * an entry that declares nothing but its own existence.
75
+ *
76
+ * Keys the renderer does not read on a given axis are dropped by
77
+ * `normalizeChartSchema`, the ONE normalization layer (#2880 S1): today it
78
+ * keeps `format`/`title`/`showGridLines` on the x-axis and the full set on the
79
+ * y-axes. That narrowing is deliberately NOT mirrored here — a second copy
80
+ * would drift from the renderer's real capability the moment it grew.
81
+ */
82
+ export function axisPresentation(raw) {
83
+ const out = {};
84
+ if (!isRecord(raw))
85
+ return out;
86
+ const title = labelText(raw.title);
87
+ if (title)
88
+ out.title = title;
89
+ if (typeof raw.format === 'string' && raw.format)
90
+ out.format = raw.format;
91
+ if (typeof raw.min === 'number' && Number.isFinite(raw.min))
92
+ out.min = raw.min;
93
+ if (typeof raw.max === 'number' && Number.isFinite(raw.max))
94
+ out.max = raw.max;
95
+ if (typeof raw.stepSize === 'number' && Number.isFinite(raw.stepSize) && raw.stepSize > 0) {
96
+ out.stepSize = raw.stepSize;
97
+ }
98
+ if (typeof raw.showGridLines === 'boolean')
99
+ out.showGridLines = raw.showGridLines;
100
+ if (raw.position === 'left' || raw.position === 'right' || raw.position === 'top' || raw.position === 'bottom') {
101
+ out.position = raw.position;
102
+ }
103
+ if (typeof raw.logarithmic === 'boolean')
104
+ out.logarithmic = raw.logarithmic;
105
+ return out;
106
+ }
107
+ /**
108
+ * Merge an authored `series[]` array's PRESENTATION onto the bindings the
109
+ * dataset selection derived — the series half of #4229's split, and the whole
110
+ * of it for a surface whose axes are names rather than objects (see the file
111
+ * header).
112
+ *
113
+ * The match rule is **by name/key**: an authored `series[].name` is paired with
114
+ * the derived binding whose `dataKey` it equals, and the pairing decides
115
+ * nothing but presentation:
116
+ *
117
+ * - an authored entry naming a measure that is NOT in the dataset selection is
118
+ * **ignored** — membership belongs to the dataset, so an author cannot add,
119
+ * remove or re-point a series from the chart config;
120
+ * - a derived series with no authored entry keeps the family default, so every
121
+ * surface that never wrote `series` renders byte-for-byte as before;
122
+ * - where both exist the **explicit binding wins** (#2880 S2), which is the
123
+ * whole point: `type: 'line'` + `yAxis: 'right'` is how the spec says "this
124
+ * measure is a line on the secondary axis".
125
+ *
126
+ * Matching on `name` only is deliberate: `name` is the spec's authorable key
127
+ * for a series (`dataKey` is a declared ALIAS of it, resolved where the
128
+ * metadata is parsed), so reading a second spelling here would fossilize a
129
+ * dialect this renderer has no business accepting (AGENTS.md #0.1).
130
+ *
131
+ * The FIRST entry naming a measure wins; a later duplicate cannot silently
132
+ * reconfigure a series the author already described.
133
+ *
134
+ * @param derived the bindings {@link buildChartSeries} produced from the selection
135
+ * @param authoredSeries the authored `series` array (anything, incl. absent)
136
+ */
137
+ export function mergeAuthoredSeries(derived, authoredSeries) {
138
+ const authored = new Map();
139
+ for (const entry of Array.isArray(authoredSeries) ? authoredSeries : []) {
140
+ if (!isRecord(entry))
141
+ continue;
142
+ const name = typeof entry.name === 'string' ? entry.name : undefined;
143
+ if (name && !authored.has(name))
144
+ authored.set(name, entry);
145
+ }
146
+ return derived.map((s) => {
147
+ const entry = authored.get(s.dataKey);
148
+ return entry ? { ...s, ...seriesPresentation(entry) } : s;
149
+ });
150
+ }
151
+ /**
152
+ * Merge an authored chart config's PRESENTATION onto the series and axes the
153
+ * dataset selection derived — the one place that happens for a surface whose
154
+ * axes are spec `ChartAxis` OBJECTS (#4229).
155
+ *
156
+ * The series half is {@link mergeAuthoredSeries}; see it for the match rule and
157
+ * for why membership is safe. The axes half reads `xAxis` / `yAxis` through
158
+ * {@link axisPresentation}, which is why a surface spelling those as bare NAME
159
+ * strings must call `mergeAuthoredSeries` directly instead (file header).
160
+ *
161
+ * @param derived the bindings {@link buildChartSeries} produced from the selection
162
+ * @param raw the authored chart config (anything, incl. absent)
163
+ * @returns the merged series, plus the presentation-only axes to spread onto
164
+ * the chart schema (absent when the author declared none)
165
+ */
166
+ export function mergeAuthoredPresentation(derived, raw) {
167
+ const config = isRecord(raw) ? raw : {};
168
+ const series = mergeAuthoredSeries(derived, config.series);
169
+ const axes = {};
170
+ const xAxis = axisPresentation(config.xAxis);
171
+ if (Object.keys(xAxis).length > 0)
172
+ axes.xAxis = xAxis;
173
+ // The COUNT of y-axis entries is itself presentation — it is what declares a
174
+ // secondary axis — so every declared entry keeps its slot even when it
175
+ // carries nothing but `field` (which is data and does not travel).
176
+ const yAxisRaw = Array.isArray(config.yAxis)
177
+ ? config.yAxis
178
+ : config.yAxis !== undefined
179
+ ? [config.yAxis]
180
+ : [];
181
+ if (yAxisRaw.length > 0)
182
+ axes.yAxis = yAxisRaw.map(axisPresentation);
183
+ return { series, axes };
184
+ }
185
+ /**
186
+ * Lower an authored chart config's CHROME (spec `ChartConfigSchema` /
187
+ * `ReportChartSchema` — the same keys on both) onto the chart schema a
188
+ * dataset-bound surface hands the renderer.
189
+ *
190
+ * ## Why this is a whitelist and not a spread
191
+ *
192
+ * Until #3135 NONE of a dashboard widget's `chartConfig` reached the renderer:
193
+ * the widget read `options` and nothing else, so an author who wrote
194
+ * `showLegend: false` still got a legend and one who wrote `true` only got one
195
+ * because "on" is the renderer's default. #3135 lowered that single flag and
196
+ * left the rest declared and inert. objectstack#7016 lowered the rest of the
197
+ * keys that are actually DELIVERED, admitting a key only when both of these
198
+ * hold:
199
+ *
200
+ * 1. **The chart block draws it end to end.** `{ type: 'chart' }` resolves to
201
+ * `ChartRenderer` → `AdvancedChartImpl`, which draws `title`/`subtitle` in
202
+ * its ChartFrame, turns `description` into the chart container's
203
+ * `role="img"` + `aria-label`, applies `height` as that container's inline
204
+ * height, reads `colors` as the positional palette, prints
205
+ * `showDataLabels` as a Recharts `LabelList`, draws `annotations` as
206
+ * ReferenceLine/ReferenceArea and honours `interaction` as the tooltip
207
+ * toggle plus `Brush`. Forwarding a key the renderer ignores would only
208
+ * move declared-but-not-delivered one layer down, which is the failure this
209
+ * exists to remove.
210
+ * 2. **It does not fight the dataset derivation.** `type` stays out: the
211
+ * calling surface's own type already picks the chart family, which is the
212
+ * dataset path's chart-family channel.
213
+ *
214
+ * `aria` is the one declared key with **no reader at all**: `AdvancedChartImpl`
215
+ * has no `aria` prop, and `SchemaRenderer`'s ARIA injection reads the FLAT
216
+ * `ariaLabel`/`ariaDescribedBy`/`role`, never a nested `aria` object. It is
217
+ * therefore left unforwarded on purpose (criterion 1) rather than papered over
218
+ * with a caller-side flattening that would also collide with the accessible
219
+ * name `description` already sets — reported back to objectstack#5175's
220
+ * narrowing half instead.
221
+ *
222
+ * `title` IS emitted here. A caller that paints the title itself (the report
223
+ * renderer's own `h3` above the chart) must drop it from the result, or the
224
+ * chart draws a second one.
225
+ *
226
+ * @param raw the authored chart config (anything, incl. absent)
227
+ * @param fieldCategoryColors per-category colours resolved from the category
228
+ * dimension's own select/lookup option colours, merged UNDER an explicit
229
+ * author map (see the `colors` note below)
230
+ * @returns only the keys that resolved, so the caller can spread it over the
231
+ * derived chart schema and every undeclared key keeps the renderer's default
232
+ */
233
+ export function chartConfigPresentation(raw, fieldCategoryColors) {
234
+ const config = isRecord(raw) ? raw : {};
235
+ const out = {};
236
+ const text = (v) => (typeof v === 'string' && v ? v : undefined);
237
+ if (typeof config.showLegend === 'boolean')
238
+ out.showLegend = config.showLegend;
239
+ if (typeof config.showDataLabels === 'boolean')
240
+ out.showDataLabels = config.showDataLabels;
241
+ const title = text(config.title);
242
+ if (title)
243
+ out.title = title;
244
+ const subtitle = text(config.subtitle);
245
+ if (subtitle)
246
+ out.subtitle = subtitle;
247
+ const description = text(config.description);
248
+ if (description)
249
+ out.description = description;
250
+ // A non-positive height would collapse the plot; the container default is the
251
+ // more honest answer than an invisible chart.
252
+ if (typeof config.height === 'number' && Number.isFinite(config.height) && config.height > 0) {
253
+ out.height = config.height;
254
+ }
255
+ if (Array.isArray(config.annotations) && config.annotations.length > 0)
256
+ out.annotations = config.annotations;
257
+ if (config.interaction && typeof config.interaction === 'object' && !Array.isArray(config.interaction)) {
258
+ out.interaction = config.interaction;
259
+ }
260
+ // `colors` is overloaded kanban-style — and the two arms reach the renderer
261
+ // through two DIFFERENT props, so the split has to happen here (the react
262
+ // tier's ObjectChart splits it the same way): a `string[]` is the positional
263
+ // palette (`colors`), a `{ value: color }` record is an explicit per-category
264
+ // map (`categoryColors`). The author's map is merged OVER the dimension
265
+ // field's own option colours, which is the precedence the spec field comment
266
+ // states ("a value→color map — and a select/lookup dimension's option colors
267
+ // — take precedence over the positional palette per category").
268
+ const palette = Array.isArray(config.colors)
269
+ ? config.colors.filter((c) => typeof c === 'string' && !!c)
270
+ : undefined;
271
+ if (palette?.length)
272
+ out.colors = palette;
273
+ const authorCategoryColors = config.colors && typeof config.colors === 'object' && !Array.isArray(config.colors)
274
+ ? config.colors
275
+ : undefined;
276
+ if (fieldCategoryColors || authorCategoryColors) {
277
+ out.categoryColors = { ...(fieldCategoryColors ?? {}), ...(authorCategoryColors ?? {}) };
278
+ }
279
+ return out;
280
+ }