@object-ui/core 17.3.0 → 17.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +1224 -0
  2. package/README.md +2 -12
  3. package/dist/actions/ActionEngine.d.ts +42 -25
  4. package/dist/actions/ActionEngine.js +66 -52
  5. package/dist/actions/ActionRunner.d.ts +199 -21
  6. package/dist/actions/ActionRunner.js +332 -16
  7. package/dist/actions/actionKeys.d.ts +96 -20
  8. package/dist/actions/actionKeys.js +157 -18
  9. package/dist/data-scope/ViewDataProvider.d.ts +37 -12
  10. package/dist/data-scope/ViewDataProvider.js +83 -5
  11. package/dist/data-scope/element-data-source.d.ts +113 -0
  12. package/dist/data-scope/element-data-source.js +216 -0
  13. package/dist/data-scope/index.d.ts +2 -1
  14. package/dist/data-scope/index.js +1 -0
  15. package/dist/evaluator/declaredPredicate.d.ts +119 -0
  16. package/dist/evaluator/declaredPredicate.js +152 -0
  17. package/dist/evaluator/fieldRules.d.ts +16 -0
  18. package/dist/evaluator/fieldRules.js +51 -16
  19. package/dist/evaluator/index.d.ts +1 -0
  20. package/dist/evaluator/index.js +1 -0
  21. package/dist/evaluator/listConditional.d.ts +5 -2
  22. package/dist/evaluator/listConditional.js +58 -16
  23. package/dist/evaluator/predicateInput.d.ts +51 -3
  24. package/dist/evaluator/predicateInput.js +62 -5
  25. package/dist/index.d.ts +4 -1
  26. package/dist/index.js +14 -1
  27. package/dist/protocols/DndProtocol.d.ts +42 -1
  28. package/dist/protocols/KeyboardProtocol.d.ts +31 -1
  29. package/dist/protocols/SharingProtocol.d.ts +11 -1
  30. package/dist/registry/Registry.d.ts +24 -0
  31. package/dist/theme/ThemeEngine.d.ts +0 -8
  32. package/dist/theme/ThemeEngine.js +22 -103
  33. package/dist/theme/index.d.ts +1 -1
  34. package/dist/theme/index.js +7 -1
  35. package/dist/types/index.d.ts +42 -8
  36. package/dist/utils/chart-series.d.ts +258 -5
  37. package/dist/utils/chart-series.js +423 -20
  38. package/dist/utils/compare-to.d.ts +40 -13
  39. package/dist/utils/compare-to.js +4 -32
  40. package/dist/utils/dashboard-filters.d.ts +54 -8
  41. package/dist/utils/dashboard-filters.js +261 -12
  42. package/dist/utils/dataset-format.d.ts +73 -14
  43. package/dist/utils/dataset-format.js +130 -9
  44. package/dist/utils/dataset-pivot.d.ts +28 -7
  45. package/dist/utils/dataset-pivot.js +27 -6
  46. package/dist/utils/dom-props.d.ts +137 -0
  47. package/dist/utils/dom-props.js +153 -0
  48. package/dist/utils/normalize-list-view.d.ts +26 -6
  49. package/dist/utils/normalize-list-view.js +30 -8
  50. package/dist/utils/number-display.d.ts +151 -0
  51. package/dist/utils/number-display.js +71 -0
  52. package/dist/utils/resolve-view-id.d.ts +31 -0
  53. package/dist/utils/resolve-view-id.js +45 -0
  54. package/dist/utils/sort-query.d.ts +55 -0
  55. package/dist/utils/sort-query.js +35 -0
  56. package/package.json +5 -5
  57. package/dist/utils/merge-views-into-objects.d.ts +0 -19
  58. package/dist/utils/merge-views-into-objects.js +0 -39
@@ -32,17 +32,39 @@ export const ROW_HEIGHT_TO_DENSITY_MODE = {
32
32
  extra_tall: 'spacious',
33
33
  };
34
34
  /**
35
- * Tolerant reader for {@link ROW_HEIGHT_TO_DENSITY_MODE}. View metadata is
36
- * user-authored, so `rowHeight` is not guaranteed to be one of the five spec
37
- * values — an unknown one lands on `comfortable` rather than rendering an
38
- * undefined density. Keeping the fallback here means every surface collapses
39
- * the same way.
35
+ * Runtime reader for {@link ROW_HEIGHT_TO_DENSITY_MODE}: the spec's five row
36
+ * heights narrowed onto the renderer's three densities, and **nothing else**.
37
+ *
38
+ * The parameter stays `unknown` because this is the boundary user-authored view
39
+ * metadata actually crosses — `ListViewSchema.rowHeight` is statically a
40
+ * `RowHeight`, but the value arrives from stored view definitions TypeScript
41
+ * never saw (`ObjectView`: `viewDef.rowHeight ?? listSchema.rowHeight`). The
42
+ * type-level half of the guarantee is the `Record<RowHeight, …>` on the table
43
+ * above, which fails the build when the spec grows a row height.
44
+ *
45
+ * An off-spec value gets NO density (objectui#4440). It used to be coerced to
46
+ * `comfortable`, which is the opposite of what `@object-ui/react`'s spec bridge
47
+ * answers for the same string after objectui#4352 — one metadata-driven system
48
+ * holding two answers for one input. AGENTS.md #0.1 decides which one survives:
49
+ * a renderer-side rehabilitation of off-spec metadata is a second de-facto
50
+ * contract, and one strict contract beats N. The producer is where a bad
51
+ * `rowHeight` gets fixed.
52
+ *
53
+ * Callers apply their own "nothing was said" default to `undefined`, so an
54
+ * off-spec row height now renders exactly like an absent one — `'compact'` in
55
+ * both `ListView` and `ObjectGrid`.
56
+ *
57
+ * `hasOwnProperty`, not `in`: `in` walks the prototype chain, so `'toString'`
58
+ * used to come back as `Object.prototype.toString` — a FUNCTION returned from
59
+ * something typed `DensityMode`.
40
60
  */
41
61
  export function rowHeightToDensityMode(rowHeight) {
42
- if (typeof rowHeight === 'string' && rowHeight in ROW_HEIGHT_TO_DENSITY_MODE) {
43
- return ROW_HEIGHT_TO_DENSITY_MODE[rowHeight];
62
+ if (typeof rowHeight !== 'string')
63
+ return undefined;
64
+ if (!Object.prototype.hasOwnProperty.call(ROW_HEIGHT_TO_DENSITY_MODE, rowHeight)) {
65
+ return undefined;
44
66
  }
45
- return 'comfortable';
67
+ return ROW_HEIGHT_TO_DENSITY_MODE[rowHeight];
46
68
  }
47
69
  const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
48
70
  /**
@@ -0,0 +1,151 @@
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
+ /**
9
+ * `formatDisplayNumber` — the ONE number-display formatter behind every field
10
+ * cell, field widget, metric renderer and dataset measure in the console.
11
+ *
12
+ * It exists because the same `new Intl.NumberFormat('en-US', …)` construction
13
+ * had been copied into the number cell renderer, the currency cell renderer,
14
+ * the currency widget, the compact `formatNumber` helper and the dashboard
15
+ * metric widget. Two defects therefore had five homes each, and fixing "the"
16
+ * renderer never changed the answer (objectui#4033, source thread
17
+ * objectstack#5067):
18
+ *
19
+ * 1. the locale was hardcoded to `en-US`, so a `zh-CN` / `de-DE` console still
20
+ * grouped and pointed decimals the US way; and
21
+ * 2. `useGrouping` was never set, so a four-digit YEAR stored as
22
+ * `Field.number({ scale: 0 })` rendered as `2,026` — in every locale, with
23
+ * no field property able to turn it off.
24
+ *
25
+ * Both policies now live here and nowhere else. Call sites bring the value and
26
+ * the display width; they do not bring a locale default and they do not decide
27
+ * grouping.
28
+ *
29
+ * ── Why this module lives in `@object-ui/core` (objectui#4576) ──
30
+ * It used to live in `@object-ui/i18n`, and that home cost the repo a real
31
+ * defect. `@object-ui/core`'s `utils/dataset-format.ts` could not import it —
32
+ * `core` is the React-free engine (AGENTS.md §3: "No UI-lib deps. Logic only.")
33
+ * and is a runtime dependency of React-FREE consumers (the `object-ui` VS Code
34
+ * extension, `@object-ui/data-objectstack`), while `i18n` depends on
35
+ * `i18next`/`react-i18next` and peer-depends on React. So `formatMeasure` kept
36
+ * a parallel `Intl` implementation, the two drifted, and a German session read
37
+ * `1.234,5 %` from a list cell beside `1.234,5%` from a dashboard measure
38
+ * (objectui#4576). #4577 measured the option mapping LOSSLESS across 32,760
39
+ * combinations and recorded the duplication at both ends rather than routing
40
+ * through a boundary it could not cross.
41
+ *
42
+ * The function is pure — no React, no i18next, no DOM — so the boundary was
43
+ * never a property of the CODE, only of where the code sat. Moving it DOWN to
44
+ * the lower layer removes the obstacle instead of working around it:
45
+ * `@object-ui/i18n` now RE-EXPORTS these three names, so every existing import
46
+ * path keeps compiling and `import { formatDisplayNumber } from '@object-ui/i18n'`
47
+ * and `from '@object-ui/core'` are the SAME symbol (pinned in
48
+ * `packages/i18n/src/__tests__/number-display.reexport-identity.test.ts`).
49
+ * `core` imports `i18n` nowhere, so the new edge introduces no cycle.
50
+ */
51
+ export interface DisplayNumberFormatOptions {
52
+ /**
53
+ * BCP-47 tag for the ACTIVE display locale — in React, whatever
54
+ * `useDisplayLocale()` returns.
55
+ *
56
+ * `undefined` means "follow the runtime default", which is the honest answer
57
+ * for a non-React caller that has no locale in hand. It never means `en-US`:
58
+ * assuming US conventions for the whole world is the defect this module was
59
+ * created to remove.
60
+ */
61
+ locale?: string;
62
+ /**
63
+ * ISO 4217 code. When present the value is formatted as money — which also
64
+ * means grouping is kept, because a grouped amount is what every currency
65
+ * convention expects and an ordinal amount of money is not a thing.
66
+ *
67
+ * Money wins over {@link DisplayNumberFormatOptions.style}: a call that sets
68
+ * both gets a currency rendering. Nothing in the repo does that, and the
69
+ * combination has no meaning to express.
70
+ */
71
+ currency?: string;
72
+ /**
73
+ * The FIELD's declared `scale` — the `s` of a `decimal(p, s)` column — and
74
+ * nothing else. This is a POLICY input, not a display width: pass it only
75
+ * when a field declaration actually said so.
76
+ *
77
+ * `scale: 0` with no currency declares a discrete integer (a year, a fiscal
78
+ * period, an ordinal), and those are not grouped. Leave `scale` undefined and
79
+ * grouping is kept — which is correct for the two cases that look similar but
80
+ * are not:
81
+ *
82
+ * - an UNDECLARED scale (`scale` is optional in the spec, so absent means
83
+ * "decimals unknown", not "integer"); and
84
+ * - a caller whose zero-decimal display comes from something other than a
85
+ * field declaration — e.g. the dashboard `MetricWidget`, whose decimals
86
+ * come from a numeral.js format pattern and whose large KPI aggregates
87
+ * are *documented* to want separators ("`1,930,000` not `1930000`").
88
+ *
89
+ * ⚠️ INTERIM DEFAULT (objectui#4033, PM ruling 2026-08-11). Suppressing
90
+ * grouping for every scale-0 number is a transitional policy with a known,
91
+ * accepted cost: a large scale-0 COUNT loses its separators too. It is the
92
+ * better trade only until the spec gains an authorable presentation hint
93
+ * (`useGrouping` / `displayFormat` — being specified separately, contract-first,
94
+ * in the objectstack repo). When that hint lands it OVERRIDES this default,
95
+ * and this heuristic should be reduced to the fallback for fields that
96
+ * declare nothing.
97
+ */
98
+ scale?: number;
99
+ /**
100
+ * How the number relates to a percentage — the ONE place that distinction is
101
+ * expressed, because getting it wrong is the whole of objectui#4576.
102
+ *
103
+ * - `'decimal'` (default) — a plain number, no percent sign.
104
+ * - `'percent'` — the value is a FRACTION. `Intl` multiplies it by 100 and
105
+ * appends the locale's percent convention, so `0.8` renders as `80%`.
106
+ * - `'percentPoints'` — the value is ALREADY in percentage points, so `80`
107
+ * renders as `80%`. Same locale convention, NO re-scaling.
108
+ *
109
+ * The third one exists because "divide by 100 so `Intl` can multiply it back"
110
+ * is not free, and the round trip is lossy at rounding TIES: measured on this
111
+ * repo's runner, `80.175` percentage points formatted to 2 decimals renders
112
+ * `80.18%` when formatted directly and `80.17%` after the divide-and-remultiply
113
+ * — 27,581 of 1,200,013 ordinary-magnitude en-US forms move, plus every
114
+ * value at the top of the double range (`MAX_SAFE_INTEGER`, `1e23`). A caller
115
+ * holding percentage points must therefore NOT reach for `'percent'`; that is
116
+ * exactly the trap this option removes.
117
+ *
118
+ * `'percentPoints'` is implemented with `Intl`'s `style: 'unit'` /
119
+ * `unit: 'percent'`, which was measured to produce a BYTE-IDENTICAL percent
120
+ * affix to `style: 'percent'` across all 171 locale tags tested — including
121
+ * the German/French/Russian no-break space before the sign, Turkish's PREFIX
122
+ * position (`%1.234,5`), Arabic's own percent sign plus U+061C, and the
123
+ * Bengali/Marathi percent patterns that group Western-style where their
124
+ * decimal patterns group by lakh. It is the same CONVENTION by a route that
125
+ * does not touch the value.
126
+ */
127
+ style?: 'decimal' | 'percent' | 'percentPoints';
128
+ minimumFractionDigits?: number;
129
+ maximumFractionDigits?: number;
130
+ notation?: 'standard' | 'compact';
131
+ }
132
+ /**
133
+ * The grouping policy, alone and testable: does this number get thousands
134
+ * separators?
135
+ *
136
+ * @param scale the field's declared `scale`, or `undefined` when the caller
137
+ * has no field declaration behind it
138
+ * @param currency ISO 4217 code when the number is money
139
+ */
140
+ export declare function shouldGroupDisplayNumber(scale?: number, currency?: string): boolean;
141
+ /**
142
+ * Format a number for DISPLAY, in the active locale, under the grouping policy
143
+ * above.
144
+ *
145
+ * Throws for a bad `currency` code exactly as `Intl.NumberFormat` does, so the
146
+ * fallbacks call sites already had (`${currency} ${value.toFixed(n)}`) keep
147
+ * working unchanged. A bad LOCALE is handled here instead of throwing: `locale`
148
+ * arrives from a server response (ADR-0053 `localization.locale`), and a
149
+ * malformed tag from a tenant config must never take a grid cell down.
150
+ */
151
+ export declare function formatDisplayNumber(value: number, options?: DisplayNumberFormatOptions): string;
@@ -0,0 +1,71 @@
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
+ /**
9
+ * The grouping policy, alone and testable: does this number get thousands
10
+ * separators?
11
+ *
12
+ * @param scale the field's declared `scale`, or `undefined` when the caller
13
+ * has no field declaration behind it
14
+ * @param currency ISO 4217 code when the number is money
15
+ */
16
+ export function shouldGroupDisplayNumber(scale, currency) {
17
+ // Money always groups — including money whose currency code could not be
18
+ // resolved, which still renders as an amount (just without a symbol).
19
+ if (currency)
20
+ return true;
21
+ // Only an explicitly declared scale of 0 is an ordinal. `undefined !== 0`.
22
+ return scale !== 0;
23
+ }
24
+ /**
25
+ * Format a number for DISPLAY, in the active locale, under the grouping policy
26
+ * above.
27
+ *
28
+ * Throws for a bad `currency` code exactly as `Intl.NumberFormat` does, so the
29
+ * fallbacks call sites already had (`${currency} ${value.toFixed(n)}`) keep
30
+ * working unchanged. A bad LOCALE is handled here instead of throwing: `locale`
31
+ * arrives from a server response (ADR-0053 `localization.locale`), and a
32
+ * malformed tag from a tenant config must never take a grid cell down.
33
+ */
34
+ export function formatDisplayNumber(value, options = {}) {
35
+ const { locale, currency, scale, style, ...passthrough } = options;
36
+ const intlOptions = { ...passthrough };
37
+ if (style === 'percentPoints') {
38
+ // The locale's percent convention WITHOUT `style: 'percent'`'s ×100 — see
39
+ // the option's doc above for the measurement that separates the two.
40
+ intlOptions.style = 'unit';
41
+ intlOptions.unit = 'percent';
42
+ intlOptions.unitDisplay = 'narrow';
43
+ }
44
+ else if (style) {
45
+ intlOptions.style = style;
46
+ }
47
+ if (currency) {
48
+ intlOptions.style = 'currency';
49
+ intlOptions.currency = currency;
50
+ delete intlOptions.unit;
51
+ delete intlOptions.unitDisplay;
52
+ }
53
+ // ⚠️ Set `useGrouping` ONLY to suppress. `useGrouping: true` is NOT the same
54
+ // as omitting the key: `true` means "always", while omitting it means "auto"
55
+ // (and "min2" under compact notation), which is the locale's own preference.
56
+ // Measured — for 1234: es-ES "auto" → `1234` but "always" → `1.234`; pl-PL
57
+ // "auto" → `1234` but "always" → `1 234`. Writing `true` here would silently
58
+ // override those locales' conventions in the name of preserving en-US output.
59
+ if (!shouldGroupDisplayNumber(scale, currency)) {
60
+ intlOptions.useGrouping = false;
61
+ }
62
+ try {
63
+ return new Intl.NumberFormat(locale, intlOptions).format(value);
64
+ }
65
+ catch {
66
+ // Retry WITHOUT the locale, keeping every other option: this rescues a
67
+ // malformed tag while still surfacing a genuinely bad `currency` to the
68
+ // caller's own catch.
69
+ return new Intl.NumberFormat(undefined, intlOptions).format(value);
70
+ }
71
+ }
@@ -0,0 +1,31 @@
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
+ /**
9
+ * Resolve a REQUESTED view name against an object's actual view ids.
10
+ *
11
+ * Canonical view ids are fully qualified (`<object>.<viewKind>`, see app-shell's
12
+ * MetadataProvider), but the surfaces that NAME a view emit the short form —
13
+ * nav items write their `viewName` verbatim, `ElementDataSource.view` on a page
14
+ * component is authored by hand, and legacy embedded `listViews` carry bare keys
15
+ * (including the `'all'` fallback view). Accept both directions (#2217):
16
+ *
17
+ * 1. exact id match;
18
+ * 2. short name (no `.`) → retry as `<objectName>.<name>`;
19
+ * 3. qualified name → retry with the `<objectName>.` prefix stripped.
20
+ *
21
+ * Returns the matching view id, or `undefined` when nothing matches — the
22
+ * caller decides how to fall back (and should report it rather than swallow it).
23
+ *
24
+ * Lives in `@object-ui/core` because it has two consumers in different layers:
25
+ * app-shell's `ObjectView` (URL-requested view) and the page-component data
26
+ * binding that resolves `dataSource.view` (objectstack#5576). A second copy at
27
+ * the second call site is the drift AGENTS.md #0.1 forbids — one matcher, one
28
+ * place, so a view name that resolves in the object page resolves identically
29
+ * when a page component names it.
30
+ */
31
+ export declare function resolveViewId(requested: string | undefined | null, viewIds: readonly string[], objectName: string): string | undefined;
@@ -0,0 +1,45 @@
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
+ /**
9
+ * Resolve a REQUESTED view name against an object's actual view ids.
10
+ *
11
+ * Canonical view ids are fully qualified (`<object>.<viewKind>`, see app-shell's
12
+ * MetadataProvider), but the surfaces that NAME a view emit the short form —
13
+ * nav items write their `viewName` verbatim, `ElementDataSource.view` on a page
14
+ * component is authored by hand, and legacy embedded `listViews` carry bare keys
15
+ * (including the `'all'` fallback view). Accept both directions (#2217):
16
+ *
17
+ * 1. exact id match;
18
+ * 2. short name (no `.`) → retry as `<objectName>.<name>`;
19
+ * 3. qualified name → retry with the `<objectName>.` prefix stripped.
20
+ *
21
+ * Returns the matching view id, or `undefined` when nothing matches — the
22
+ * caller decides how to fall back (and should report it rather than swallow it).
23
+ *
24
+ * Lives in `@object-ui/core` because it has two consumers in different layers:
25
+ * app-shell's `ObjectView` (URL-requested view) and the page-component data
26
+ * binding that resolves `dataSource.view` (objectstack#5576). A second copy at
27
+ * the second call site is the drift AGENTS.md #0.1 forbids — one matcher, one
28
+ * place, so a view name that resolves in the object page resolves identically
29
+ * when a page component names it.
30
+ */
31
+ export function resolveViewId(requested, viewIds, objectName) {
32
+ if (!requested)
33
+ return undefined;
34
+ const has = (id) => viewIds.includes(id);
35
+ if (has(requested))
36
+ return requested;
37
+ const prefix = `${objectName}.`;
38
+ if (!requested.includes('.') && has(prefix + requested)) {
39
+ return prefix + requested;
40
+ }
41
+ if (requested.startsWith(prefix) && has(requested.slice(prefix.length))) {
42
+ return requested.slice(prefix.length);
43
+ }
44
+ return undefined;
45
+ }
@@ -0,0 +1,55 @@
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
+ /**
9
+ * sort-query — lower a block's authored `sort` to the `$orderby` value a
10
+ * `DataSource.find` query carries.
11
+ *
12
+ * Two authored spellings reach every object-bound block, because both are
13
+ * declared: the legacy OData-ish string (`ObjectGridSchema.sort: "name desc"`)
14
+ * and the spec's `SortConfig[]` (`[{ field, order }]`). A read site that hands
15
+ * either straight to `$orderby` works only because `QueryParams.$orderby`
16
+ * accepts four shapes; normalizing here means one shape reaches the wire and a
17
+ * block does not have to know which spelling its author used.
18
+ *
19
+ * This is the same conversion three sibling blocks already inline privately
20
+ * (`ObjectGantt` / `ObjectMap` / `ObjectCalendar`, byte-identical copies). It is
21
+ * hoisted here because objectstack#7137 added two more read sites
22
+ * (`object-timeline`, `record:line_items`), and a fifth and sixth private copy is
23
+ * how the conversions start disagreeing. Collapsing the three existing copies
24
+ * onto this one is deliberately NOT part of #7137 — it is tracked as
25
+ * objectstack#7148.
26
+ *
27
+ * Two deliberate differences from those private copies, both of which make this
28
+ * function more faithful to the declared contract rather than adding tolerance:
29
+ *
30
+ * - **`order` is optional in `SortConfig`**, so an entry that omits it means
31
+ * ascending (that is what `$orderby`'s own
32
+ * `Array<{ field: string; order?: 'asc' | 'desc' }>` shape says). The private
33
+ * copies require BOTH keys and silently drop such an entry, which loses an
34
+ * authored sort key instead of ordering by it.
35
+ * - **Nothing usable yields `undefined`, never `{}`.** An empty object is a
36
+ * truthy value that means "no ordering" only by accident of the adapter's
37
+ * serializer; `undefined` says it, so the query simply carries no `$orderby`.
38
+ *
39
+ * Not a lenient alias: an unusable input (a number, an object, an array of
40
+ * strings) yields `undefined` rather than a guess. Only the two spellings the
41
+ * schema types declare are honoured.
42
+ */
43
+ /** A field name paired with a direction — the spec's `SortConfig`. */
44
+ export interface QuerySortEntry {
45
+ field?: string;
46
+ order?: 'asc' | 'desc';
47
+ }
48
+ /**
49
+ * Normalize an authored `sort` into the field→direction map used for
50
+ * `QueryParams.$orderby`.
51
+ *
52
+ * @param sort `"name desc"` (legacy string) or `SortConfig[]`.
53
+ * @returns The ordering map, or `undefined` when nothing orderable was authored.
54
+ */
55
+ export declare function convertSortToQueryParams(sort: string | QuerySortEntry[] | undefined | null): Record<string, 'asc' | 'desc'> | undefined;
@@ -0,0 +1,35 @@
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
+ /**
9
+ * Normalize an authored `sort` into the field→direction map used for
10
+ * `QueryParams.$orderby`.
11
+ *
12
+ * @param sort `"name desc"` (legacy string) or `SortConfig[]`.
13
+ * @returns The ordering map, or `undefined` when nothing orderable was authored.
14
+ */
15
+ export function convertSortToQueryParams(sort) {
16
+ if (!sort)
17
+ return undefined;
18
+ // Legacy string clause: "name desc" / "name" (a bare field means ascending).
19
+ if (typeof sort === 'string') {
20
+ const [field, direction] = sort.trim().split(/\s+/);
21
+ if (!field)
22
+ return undefined;
23
+ return { [field]: direction?.toLowerCase() === 'desc' ? 'desc' : 'asc' };
24
+ }
25
+ if (Array.isArray(sort)) {
26
+ const out = {};
27
+ for (const entry of sort) {
28
+ if (!entry || typeof entry.field !== 'string' || entry.field === '')
29
+ continue;
30
+ out[entry.field] = entry.order === 'desc' ? 'desc' : 'asc';
31
+ }
32
+ return Object.keys(out).length > 0 ? out : undefined;
33
+ }
34
+ return undefined;
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/core",
3
- "version": "17.3.0",
3
+ "version": "17.5.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",
@@ -25,11 +25,11 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@objectstack/formula": "^17.0.0-rc.2",
29
- "@objectstack/spec": "^17.0.0-rc.2",
28
+ "@objectstack/formula": "^17.0.0-rc.6",
29
+ "@objectstack/spec": "^17.0.0-rc.6",
30
30
  "lodash": "^4.18.1",
31
31
  "zod": "^4.4.3",
32
- "@object-ui/types": "17.3.0"
32
+ "@object-ui/types": "17.5.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "typescript": "^6.0.3",
@@ -62,7 +62,7 @@
62
62
  "scripts": {
63
63
  "build": "tsc",
64
64
  "test": "vitest run",
65
- "type-check": "tsc --noEmit",
65
+ "type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
66
66
  "lint": "eslint ."
67
67
  }
68
68
  }
@@ -1,19 +0,0 @@
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
- /**
9
- * Adapter: merge stack-level views into object definitions.
10
- *
11
- * Views are defined at the stack level (views[].listViews) but the runtime
12
- * expects listViews on each object definition. This bridges the gap until
13
- * the runtime/provider layer handles it natively.
14
- *
15
- * @param objects - Object definitions from composed stack
16
- * @param views - View definitions containing listViews keyed by object name
17
- * @returns Objects with listViews merged in from matching views
18
- */
19
- export declare function mergeViewsIntoObjects(objects: any[], views: any[]): any[];
@@ -1,39 +0,0 @@
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
- /**
9
- * Adapter: merge stack-level views into object definitions.
10
- *
11
- * Views are defined at the stack level (views[].listViews) but the runtime
12
- * expects listViews on each object definition. This bridges the gap until
13
- * the runtime/provider layer handles it natively.
14
- *
15
- * @param objects - Object definitions from composed stack
16
- * @param views - View definitions containing listViews keyed by object name
17
- * @returns Objects with listViews merged in from matching views
18
- */
19
- export function mergeViewsIntoObjects(objects, views) {
20
- const viewsByObject = {};
21
- for (const view of views) {
22
- if (!view.listViews)
23
- continue;
24
- for (const [viewName, listView] of Object.entries(view.listViews)) {
25
- const objectName = listView?.data?.object;
26
- if (!objectName)
27
- continue;
28
- if (!viewsByObject[objectName])
29
- viewsByObject[objectName] = {};
30
- viewsByObject[objectName][viewName] = listView;
31
- }
32
- }
33
- return objects.map((obj) => {
34
- const v = viewsByObject[obj.name];
35
- if (!v)
36
- return obj;
37
- return { ...obj, listViews: { ...(obj.listViews || {}), ...v } };
38
- });
39
- }