@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
@@ -108,3 +108,46 @@ export declare function normalizeColumnIdentity<T>(entry: T): T;
108
108
  * every consumer already special-cases it.
109
109
  */
110
110
  export declare function normalizeColumnIdentities<T>(columns: T): T;
111
+ /**
112
+ * The spec's canonical key for "what this column is CALLED on screen".
113
+ *
114
+ * `ListColumnSchema` (`@objectstack/spec/view`) spells a column's display text
115
+ * `label`, next to its `field`. It is metadata vocabulary, exactly like
116
+ * {@link CANONICAL_COLUMN_IDENTITY_KEY} — not an objectui legacy spelling, and
117
+ * not something to amputate.
118
+ */
119
+ export declare const CANONICAL_COLUMN_LABEL_KEY = "label";
120
+ /**
121
+ * NOT metadata — the display half of the same boundary
122
+ * {@link TABLE_ADAPTER_COLUMN_KEY} draws for identity.
123
+ *
124
+ * `header` is `TableColumn.header` (`packages/types/src/data-display.ts`), the
125
+ * data-table ADAPTER's own key for a column's rendered title. The adapter
126
+ * declares `header` and does not declare `label`; the spec declares `label` and
127
+ * does not declare `header`. Those are two vocabularies, and the translation
128
+ * between them belongs at the producer — one translation, one place
129
+ * (objectui#5068) — never as a tolerated `col.header || col.label` alias inside
130
+ * the adapter (objectui#5351).
131
+ */
132
+ export declare const TABLE_ADAPTER_HEADER_KEY = "header";
133
+ /**
134
+ * Read a column entry's display text on its way INTO the data-table adapter —
135
+ * the header counterpart of {@link columnIdentity} (objectui#5351).
136
+ *
137
+ * Adapter-first, which is the opposite order from {@link columnIdentity} and is
138
+ * deliberate. `columnIdentity` folds several METADATA spellings of one metadata
139
+ * concept, so the canonical metadata key wins. This reader crosses a boundary
140
+ * between two vocabularies instead: an author who wrote the adapter's own
141
+ * `header` addressed the table directly, and a producer must not overwrite that
142
+ * — the same rule `RelatedList` and `ObjectDataTable` already state for an
143
+ * author-supplied `accessorKey` (objectui#5022).
144
+ *
145
+ * Returns `undefined` — not `''` — when neither key carries text, so a caller
146
+ * can tell "no header authored" from "the header is deliberately blank" and
147
+ * stamp nothing rather than stamping an empty string.
148
+ *
149
+ * A bare-string column entry has no display text of its own; producers derive
150
+ * one from the field key (`humanizeFieldKey`) or the object schema's label, and
151
+ * that derivation is theirs, not this reader's.
152
+ */
153
+ export declare function columnHeader(entry: unknown): string | undefined;
@@ -125,7 +125,6 @@ function warnOnConflictingIdentity(entry, identity, losing) {
125
125
  if (warnedConflicts.has(memo))
126
126
  continue;
127
127
  warnedConflicts.add(memo);
128
- // eslint-disable-next-line no-console
129
128
  console.warn(`[ObjectUI] Column carries two identities: \`${CANONICAL_COLUMN_IDENTITY_KEY}: ` +
130
129
  `'${identity}'\` and \`${key}: '${String(entry[key])}'\`. ` +
131
130
  `\`${CANONICAL_COLUMN_IDENTITY_KEY}\` wins — it is the only key ` +
@@ -208,3 +207,50 @@ export function normalizeColumnIdentities(columns) {
208
207
  });
209
208
  return (changed ? next : columns);
210
209
  }
210
+ /**
211
+ * The spec's canonical key for "what this column is CALLED on screen".
212
+ *
213
+ * `ListColumnSchema` (`@objectstack/spec/view`) spells a column's display text
214
+ * `label`, next to its `field`. It is metadata vocabulary, exactly like
215
+ * {@link CANONICAL_COLUMN_IDENTITY_KEY} — not an objectui legacy spelling, and
216
+ * not something to amputate.
217
+ */
218
+ export const CANONICAL_COLUMN_LABEL_KEY = 'label';
219
+ /**
220
+ * NOT metadata — the display half of the same boundary
221
+ * {@link TABLE_ADAPTER_COLUMN_KEY} draws for identity.
222
+ *
223
+ * `header` is `TableColumn.header` (`packages/types/src/data-display.ts`), the
224
+ * data-table ADAPTER's own key for a column's rendered title. The adapter
225
+ * declares `header` and does not declare `label`; the spec declares `label` and
226
+ * does not declare `header`. Those are two vocabularies, and the translation
227
+ * between them belongs at the producer — one translation, one place
228
+ * (objectui#5068) — never as a tolerated `col.header || col.label` alias inside
229
+ * the adapter (objectui#5351).
230
+ */
231
+ export const TABLE_ADAPTER_HEADER_KEY = 'header';
232
+ /**
233
+ * Read a column entry's display text on its way INTO the data-table adapter —
234
+ * the header counterpart of {@link columnIdentity} (objectui#5351).
235
+ *
236
+ * Adapter-first, which is the opposite order from {@link columnIdentity} and is
237
+ * deliberate. `columnIdentity` folds several METADATA spellings of one metadata
238
+ * concept, so the canonical metadata key wins. This reader crosses a boundary
239
+ * between two vocabularies instead: an author who wrote the adapter's own
240
+ * `header` addressed the table directly, and a producer must not overwrite that
241
+ * — the same rule `RelatedList` and `ObjectDataTable` already state for an
242
+ * author-supplied `accessorKey` (objectui#5022).
243
+ *
244
+ * Returns `undefined` — not `''` — when neither key carries text, so a caller
245
+ * can tell "no header authored" from "the header is deliberately blank" and
246
+ * stamp nothing rather than stamping an empty string.
247
+ *
248
+ * A bare-string column entry has no display text of its own; producers derive
249
+ * one from the field key (`humanizeFieldKey`) or the object schema's label, and
250
+ * that derivation is theirs, not this reader's.
251
+ */
252
+ export function columnHeader(entry) {
253
+ if (!isRecord(entry))
254
+ return undefined;
255
+ return (asIdentity(entry[TABLE_ADAPTER_HEADER_KEY]) ?? asIdentity(entry[CANONICAL_COLUMN_LABEL_KEY]));
256
+ }
@@ -8,23 +8,50 @@
8
8
  /**
9
9
  * Period-over-period comparison config for dashboard widgets.
10
10
  *
11
- * - `'previousPeriod'` — substitute `current_*` / `today` date macro tokens
12
- * with their `last_*` / `yesterday` counterparts (e.g. `current_quarter_start`
13
- * → `last_quarter_start`). Best when the filter uses date macros.
14
- * - `'previousYear'` — re-resolve date macros against a `now` shifted back
15
- * one calendar year (same week / month / quarter, one year earlier).
16
- * - `{ offset: '7d' | '4w' | '1M' | '1y' }` — re-resolve macros against a
17
- * `now` shifted by the given duration. Units: `d` (day), `w` (week),
18
- * `M` (month), `y` (year).
11
+ * Converged (objectstack#5011) onto the contract the analytics executor
12
+ * already implements — `DatasetSelection.compareTo`, i.e. `DatasetCompareTo`
13
+ * in `@objectstack/spec` (`contracts/analytics-service.ts`). This type is that
14
+ * contract's thin projection, so the same widget key means the same thing on
15
+ * the ADR-0021 dataset path and on this legacy inline object-provider path.
16
+ *
17
+ * - `kind: 'previousPeriod'` — the equal-length window immediately before the
18
+ * current one. On the inline path that is done by substituting `current_*` /
19
+ * `today` date macro tokens with their `last_*` / `yesterday` counterparts
20
+ * (`current_quarter_start` → `last_quarter_start`), so it works best when the
21
+ * filter is written with date macros.
22
+ * - `kind: 'previousYear'` — re-resolve the filter's date macros against a
23
+ * `now` shifted back one calendar year (same week / month / quarter, one year
24
+ * earlier).
25
+ * - `dimension` — OPTIONAL, and deliberately never read on this path. It names
26
+ * the dataset time dimension whose `dateRange` the EXECUTOR shifts. That
27
+ * resolution lives at the producer of the comparison so that every caller
28
+ * gets the same answer or the same loud error; a renderer that guessed a
29
+ * dimension would trade "loud error" for "quietly wrong window" — precisely
30
+ * the failure class this convergence exists to end.
31
+ *
32
+ * **Design note — a plain strict object, NOT a union** (objectstack#5011,
33
+ * framework#5014). The retired shape was a three-branch union
34
+ * (`'previousPeriod' | 'previousYear' | { offset }`); zod collapses a failed
35
+ * union into a bare `Invalid input`, so the prescriptions written inside each
36
+ * arm never reached the author. One strict object keeps the rejection specific.
37
+ * It also leaves this renderer with exactly one contract to read — no second
38
+ * de-facto dialect to be tolerant about (AGENTS.md #0.1).
39
+ *
40
+ * The `{ offset: '7d' | '1M' | '1y' }` arm went with the union: `{ offset: '1y' }`
41
+ * IS `kind: 'previousYear'` (rewritten deterministically upstream), while
42
+ * `'7d'` / `'1M'` have no faithful target — an author restates that window on
43
+ * the widget's own `filter` and asks for `kind: 'previousPeriod'` (registered
44
+ * semantic migration `dashboard-widget-compareto-offset`).
19
45
  */
20
- export type CompareToConfig = 'previousPeriod' | 'previousYear' | {
21
- offset: string;
22
- };
46
+ export interface CompareToConfig {
47
+ kind: 'previousPeriod' | 'previousYear';
48
+ dimension?: string;
49
+ }
23
50
  /**
24
- * Resolve a filter for a period-over-period comparison query.
51
+ * Resolve a filter for a period-over-period comparison query. Dispatches on
52
+ * `compareTo.kind` — the only discriminator the converged shape has:
25
53
  *
26
54
  * - `previousYear` — re-resolve date macros against a `now` shifted back by one year.
27
- * - `{ offset }` — re-resolve date macros against a `now` shifted by the given duration.
28
55
  * - `previousPeriod` — substitute `current_*` / `today` tokens with `last_*` / `yesterday`
29
56
  * before resolving.
30
57
  */
@@ -44,44 +44,19 @@ function substituteTokens(value, map) {
44
44
  }
45
45
  return value;
46
46
  }
47
- function shiftNow(now, offset) {
48
- const m = offset.match(/^(\d+)([dwMy])$/);
49
- if (!m)
50
- return null;
51
- const n = parseInt(m[1], 10);
52
- const unit = m[2];
53
- const x = new Date(now);
54
- if (unit === 'd')
55
- x.setDate(x.getDate() - n);
56
- else if (unit === 'w')
57
- x.setDate(x.getDate() - n * 7);
58
- else if (unit === 'M')
59
- x.setMonth(x.getMonth() - n);
60
- else if (unit === 'y')
61
- x.setFullYear(x.getFullYear() - n);
62
- else
63
- return null;
64
- return x;
65
- }
66
47
  /**
67
- * Resolve a filter for a period-over-period comparison query.
48
+ * Resolve a filter for a period-over-period comparison query. Dispatches on
49
+ * `compareTo.kind` — the only discriminator the converged shape has:
68
50
  *
69
51
  * - `previousYear` — re-resolve date macros against a `now` shifted back by one year.
70
- * - `{ offset }` — re-resolve date macros against a `now` shifted by the given duration.
71
52
  * - `previousPeriod` — substitute `current_*` / `today` tokens with `last_*` / `yesterday`
72
53
  * before resolving.
73
54
  */
74
55
  export function shiftFilterByCompareTo(filter, compareTo, now = new Date()) {
75
- if (compareTo === 'previousYear') {
56
+ if (compareTo.kind === 'previousYear') {
76
57
  const shifted = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
77
58
  return resolveDateMacros(filter, shifted);
78
59
  }
79
- if (typeof compareTo === 'object' && compareTo && 'offset' in compareTo) {
80
- const shifted = shiftNow(now, compareTo.offset);
81
- if (!shifted)
82
- return resolveDateMacros(filter, now);
83
- return resolveDateMacros(filter, shifted);
84
- }
85
60
  // previousPeriod
86
61
  const substituted = substituteTokens(filter, CURRENT_TO_LAST_TOKENS);
87
62
  return resolveDateMacros(substituted, now);
@@ -91,11 +66,8 @@ export function shiftFilterByCompareTo(filter, compareTo, now = new Date()) {
91
66
  * comparison window. Used by metric widgets to label the trend delta.
92
67
  */
93
68
  export function compareToTrendLabelKey(compareTo, filter) {
94
- if (compareTo === 'previousYear')
69
+ if (compareTo.kind === 'previousYear')
95
70
  return 'vsLastYear';
96
- if (typeof compareTo === 'object' && compareTo && 'offset' in compareTo) {
97
- return 'vsPreviousPeriod';
98
- }
99
71
  // previousPeriod — sniff the dominant token in the filter
100
72
  const json = filter ? JSON.stringify(filter) : '';
101
73
  if (/current_year_|year_start|year_end/.test(json))
@@ -18,7 +18,8 @@
18
18
  * Everything in this module is pure and synchronous so the binding rules
19
19
  * are unit-testable in isolation from React and the data layer.
20
20
  */
21
- import type { DashboardComponentSchema, DashboardWidgetSchema, PageVariable } from '@object-ui/types';
21
+ import type { DashboardComponentSchema, DashboardWidgetSchema, I18nLabel, PageVariable } from '@object-ui/types';
22
+ import { DATE_RANGE_PRESETS } from '@objectstack/spec/ui';
22
23
  /** Reserved filter name for the dashboard's built-in date range. */
23
24
  export declare const DATE_RANGE_FILTER_NAME = "dateRange";
24
25
  export interface DashboardFilterDef {
@@ -26,17 +27,41 @@ export interface DashboardFilterDef {
26
27
  name: string;
27
28
  /** Default target field when a widget declares no explicit binding. */
28
29
  field: string;
29
- label?: string;
30
+ /**
31
+ * Display label, in @objectstack/spec's `I18nLabel` vocabulary — a plain
32
+ * string, or an inline per-locale map (`{ en: 'Owner', 'zh-CN': '负责人' }`).
33
+ *
34
+ * Widened from `string` under the objectstack#5428 ruling of 2026-08-06
35
+ * (option A): `GlobalFilterSchema.label` has been `I18nLabel` on the spec
36
+ * side since 17.0.0-rc.6, so declaring it `string` here made this module
37
+ * NARROWER than the contract it implements — which is exactly why the filter
38
+ * bar's map reads were invisible to `tsc` (objectui#4163).
39
+ *
40
+ * This module is locale-free BY DESIGN (`@object-ui/core` is logic-only), so
41
+ * it carries the authored vocabulary through unresolved and the RENDER side
42
+ * collapses it to the active language. Resolving here would need a locale
43
+ * this layer has no business knowing.
44
+ */
45
+ label?: string | I18nLabel;
30
46
  type: 'text' | 'select' | 'date' | 'number' | 'lookup' | 'dateRange';
31
47
  /**
32
48
  * Static options, NORMALIZED to `{ value, label }` pairs by
33
- * `resolveDashboardFilterDefs` — authors may write either the
34
- * @objectstack/spec object form (`{ value, label }`) or the bare-string
35
- * shorthand; consumers always see the object form.
49
+ * `resolveDashboardFilterDefs` — consumers always see the object form.
50
+ *
51
+ * The canonical authoring form is @objectstack/spec's `{ value, label }`
52
+ * pair, and it is the ONLY one the platform accepts at publish. A bare-string
53
+ * shorthand in a STORED document is still lifted here, with a deprecation
54
+ * warning, on the objectstack#7917 retirement schedule — see
55
+ * `normalizeFilterOptions`. Do not author a new one.
56
+ *
57
+ * The PAIR SHAPE is normalized; the label's own vocabulary is not. `label`
58
+ * is `I18nLabel` in `GlobalFilterSchema.options[]` too, and it reaches the
59
+ * renderer as authored — see `normalizeFilterOptions` for why collapsing it
60
+ * here was data loss rather than normalization.
36
61
  */
37
62
  options?: Array<{
38
63
  value: string;
39
- label: string;
64
+ label: string | I18nLabel;
40
65
  }>;
41
66
  optionsFrom?: {
42
67
  object: string;
@@ -58,8 +83,29 @@ export interface DateRangeValue {
58
83
  from?: string;
59
84
  to?: string;
60
85
  }
61
- /** Preset keys the filter bar offers, in display order. */
62
- export declare const DATE_RANGE_PRESETS: string[];
86
+ /**
87
+ * Preset keys the filter bar offers, in display order — the spec's list,
88
+ * RE-EXPORTED since objectui#4167 rather than derived from the local bounds
89
+ * table (objectstack#4115).
90
+ *
91
+ * `@objectstack/spec` 17.0.0-rc.6 publishes `DATE_RANGE_PRESETS`, and its own
92
+ * doc comment names this module as one of the three copies the extraction
93
+ * (objectstack#4614) existed to collapse: "it used to exist three times —
94
+ * inline in `dateRange.defaultRange`, as `PRESET_RANGES` in objectui's
95
+ * `dashboard-filters`, and as a hand-written table in
96
+ * `content/docs/ui/dashboards.mdx`". So the burn-down direction here is not a
97
+ * judgement call, it is upstream's stated intent, and the two lists were
98
+ * already identical in content AND display order — the copy had nothing to
99
+ * protect.
100
+ *
101
+ * What stays local is the BOUNDS table above, which is the other half of
102
+ * #4614's design ("the two vocabularies live one import apart and neither
103
+ * restates the other's grammar"): the spec owns which presets exist, this
104
+ * module owns what each one resolves to in date-macro tokens.
105
+ */
106
+ export { DATE_RANGE_PRESETS };
107
+ /** Reset the shorthand-options warn-once memo. Exported for tests. */
108
+ export declare function resetDashboardFilterWarnings(): void;
63
109
  /**
64
110
  * Normalize a dashboard schema's filter declarations into a flat list of
65
111
  * filter definitions. The built-in `dateRange` (when declared) comes first