@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
@@ -44,7 +44,8 @@ export interface RowPredicateOptions {
44
44
  * row-action `visible`/`disabled`; `false` (no style) for formatting. */
45
45
  fallback?: boolean;
46
46
  /** Extra top-level scope merged alongside the row — e.g. the global predicate
47
- * scope (`features` / `user` / `app`) a host shell provides. */
47
+ * scope (`features` / `user` / `app`) a host shell provides. The row wins on
48
+ * collision: a `record` or `data` key here never shadows the row. */
48
49
  scope?: Record<string, unknown>;
49
50
  /** When true, log a one-time warning if a *present* predicate faults. */
50
51
  warnOnError?: boolean;
@@ -65,7 +66,9 @@ export interface RowPredicateOptions {
65
66
  * fields are bound three ways so every authoring convention resolves:
66
67
  * `record.status` (spec/canonical), bare `status` (row-action shorthand), and
67
68
  * `data.status` (legacy). The optional `scope` (host predicate scope) is bound
68
- * alongside so `features.*` / `user.*` predicates keep working.
69
+ * alongside so `features.*` / `user.*` predicates keep working — but the row is
70
+ * the subject: `record` and `data` always name THIS row, on both dialect paths,
71
+ * even when the host scope carries keys of those names (objectui#3796).
69
72
  */
70
73
  export declare function evalRowPredicate(pred: FieldRulePredicate | undefined | null, row: Record<string, unknown> | null | undefined, opts?: RowPredicateOptions): boolean;
71
74
  /** A conditional-formatting rule in any of the three historical shapes. */
@@ -54,27 +54,47 @@ function warnLegacyDialect(source) {
54
54
  if (warnedLegacy.has(source))
55
55
  return;
56
56
  warnedLegacy.add(source);
57
- console.warn('[object-ui] A list conditional predicate uses the legacy expression dialect ' +
57
+ console.warn('[object-ui] A conditional predicate uses the legacy expression dialect ' +
58
58
  'and is not portable to the server CEL engine: ' +
59
59
  JSON.stringify(source) +
60
60
  '. Rewrite it as CEL (use "==" not "===", "x in [..]" for membership, ' +
61
61
  '".contains()" not ".includes()", and drop template interpolation).');
62
62
  }
63
63
  const warnedError = new Set();
64
- function warnEvalError(source, label) {
64
+ /**
65
+ * One-time fail-closed report for a predicate that could not be evaluated.
66
+ *
67
+ * `reason` is the engine's own description of the failure (`"[runtime] No such
68
+ * key: owner_id"`) — the single most useful line for the author, and precisely
69
+ * what this path used to discard: the fault-probing CEL route silences the
70
+ * canonical helper's warning to avoid double-reporting, so before objectui#3792
71
+ * the more safety-critical the surface, the LESS it said. It is optional
72
+ * because a caller may genuinely have no reason to pass (nothing fabricates
73
+ * one).
74
+ *
75
+ * The wording is deliberately surface-neutral: this function is not
76
+ * list-specific and has not been for a long time — row kebabs, the bulk
77
+ * selection bar, kanban conditional formatting and (since objectui#3521)
78
+ * `page:header` all report through it, and telling a page-header author that
79
+ * "a list conditional predicate" failed sends them to the wrong screen.
80
+ */
81
+ function warnEvalError(source, label, reason) {
65
82
  // One-time-warning identity for the (label, source) pair. JSON-encoded rather
66
83
  // than joined on a character the two halves "cannot contain" — that separator
67
84
  // was a raw U+0000, which made this whole file binary to grep (objectstack#5450).
68
85
  // JSON needs no impossible character, so the boundary is unambiguous for any
69
86
  // label and any predicate source, and the literal is plain ASCII.
87
+ // `reason` is deliberately NOT part of the key: one predicate text has one
88
+ // reason, so keying on it could only ever weaken the warn-once contract.
70
89
  const key = JSON.stringify([label ?? '', source]);
71
90
  if (warnedError.has(key))
72
91
  return;
73
92
  warnedError.add(key);
74
- console.warn('[object-ui] A list conditional predicate failed to evaluate' +
93
+ console.warn('[object-ui] A conditional predicate failed to evaluate' +
75
94
  (label ? ` (${label})` : '') +
76
95
  ' and was treated as its safe default: ' +
77
96
  JSON.stringify(source) +
97
+ (reason ? `. Reason: ${reason}` : '') +
78
98
  '. Check the field names and CEL syntax.');
79
99
  }
80
100
  /**
@@ -91,11 +111,18 @@ function evalCel(pred, record, scope) {
91
111
  // verbatim — no second engine — so CEL semantics never drift from B2.
92
112
  // `warn: false`: this caller reports the fault itself (the labelled
93
113
  // `warnEvalError` in `evalRowPredicate`) — one warning per broken predicate,
94
- // not two (#5149).
95
- const asTrue = evalFieldPredicate(pred, record, true, undefined, scope, { warn: false });
96
- const asFalse = evalFieldPredicate(pred, record, false, undefined, scope, { warn: false });
114
+ // not two (#5149). `onFault` carries the engine's reason across that silence
115
+ // so the labelled report can still name it (objectui#3792); both probes fault
116
+ // identically, so the first reason is the reason.
117
+ let reason;
118
+ const onFault = (r) => {
119
+ reason ?? (reason = r);
120
+ };
121
+ const diagnostic = { warn: false, onFault };
122
+ const asTrue = evalFieldPredicate(pred, record, true, undefined, scope, diagnostic);
123
+ const asFalse = evalFieldPredicate(pred, record, false, undefined, scope, diagnostic);
97
124
  if (asTrue !== asFalse)
98
- return { ok: false, value: false };
125
+ return { ok: false, value: false, reason };
99
126
  return { ok: true, value: asTrue };
100
127
  }
101
128
  /**
@@ -104,7 +131,9 @@ function evalCel(pred, record, scope) {
104
131
  * fields are bound three ways so every authoring convention resolves:
105
132
  * `record.status` (spec/canonical), bare `status` (row-action shorthand), and
106
133
  * `data.status` (legacy). The optional `scope` (host predicate scope) is bound
107
- * alongside so `features.*` / `user.*` predicates keep working.
134
+ * alongside so `features.*` / `user.*` predicates keep working — but the row is
135
+ * the subject: `record` and `data` always name THIS row, on both dialect paths,
136
+ * even when the host scope carries keys of those names (objectui#3796).
108
137
  */
109
138
  export function evalRowPredicate(pred, row, opts = {}) {
110
139
  const fallback = opts.fallback ?? false;
@@ -117,20 +146,33 @@ export function evalRowPredicate(pred, row, opts = {}) {
117
146
  // means one thing whether or not this surface expanded the column
118
147
  // (objectui#3501). Returned by reference when there is nothing to collapse.
119
148
  const rowObj = toPredicateRecord(row && typeof row === 'object' ? row : {}, opts.fields);
120
- // Bare fields + `data.*` + the host scope, all top-level; `record` is bound
121
- // by the engine call itself (evalCel / the legacy evaluator below).
122
- const scope = { ...(opts.scope ?? {}), ...rowObj, data: rowObj };
149
+ // Bare fields + `data.*` + `record.*` + the host scope, all top-level.
150
+ //
151
+ // `data` AND `record` are pinned AFTER the spread, so a host scope that
152
+ // happens to carry either key is background and the ROW stays the subject of
153
+ // this function — on BOTH dialect paths. `data` always had that protection;
154
+ // `record` did not, and relied instead on each engine's own binding, which
155
+ // disagreed: the legacy evaluator re-pinned `record` (row won) while the CEL
156
+ // engine takes `extra` over its `record` binding (host scope won). Same
157
+ // function, same predicate text, opposite subjects — decided by whether the
158
+ // string happens to contain `===`/`${…}`, which no author is choosing
159
+ // deliberately (objectui#3796). Pinning here fixes both paths at the merge,
160
+ // independently of either engine's precedence.
161
+ const scope = { ...(opts.scope ?? {}), ...rowObj, data: rowObj, record: rowObj };
123
162
  // Legacy-dialect *strings* route to the legacy engine (back-compat). The
124
163
  // `{ dialect: 'cel', source }` envelope is never routed here — it is CEL.
125
164
  if (source !== undefined && isLegacyDialectSource(source)) {
126
165
  warnLegacyDialect(source);
127
166
  try {
128
- const evaluator = new ExpressionEvaluator({ ...scope, record: rowObj });
167
+ const evaluator = new ExpressionEvaluator(scope);
129
168
  return evaluator.evaluateCondition(source, { throwOnError: true });
130
169
  }
131
- catch {
132
- if (opts.warnOnError)
133
- warnEvalError(source, opts.label);
170
+ catch (err) {
171
+ // The legacy engine reports by throwing, so its message IS the reason —
172
+ // the fail-closed report carries it for the same reason the CEL path does.
173
+ if (opts.warnOnError) {
174
+ warnEvalError(source, opts.label, `[legacy] ${err instanceof Error ? err.message : String(err)}`);
175
+ }
134
176
  return fallback;
135
177
  }
136
178
  }
@@ -145,7 +187,7 @@ export function evalRowPredicate(pred, row, opts = {}) {
145
187
  }
146
188
  const verdict = evalCel(pred, rowObj, scope);
147
189
  if (!verdict.ok) {
148
- warnEvalError(source ?? '(expression)', opts.label);
190
+ warnEvalError(source ?? '(expression)', opts.label, verdict.reason);
149
191
  return fallback;
150
192
  }
151
193
  return verdict.value;
@@ -44,7 +44,9 @@ export type EvaluatorPredicateInput = string | boolean | {
44
44
  *
45
45
  * Accepts:
46
46
  * - `boolean` → returned as-is (predicate evaluation short-circuits).
47
- * - `string` → wrapped as `${string}` (legacy DX shorthand).
47
+ * - `string` → a BARE expression is wrapped as `${string}` (legacy DX
48
+ * shorthand); a string that is already a `${…}` template is ALREADY in the
49
+ * normalized shape and is returned untouched (objectui#3871, below).
48
50
  * - `Expression` envelope `{ dialect, source }` (the normalized form
49
51
  * `@objectstack/spec`'s `ExpressionInputSchema` produces for every
50
52
  * authored predicate, including bare strings) → a `cel` dialect keeps
@@ -73,8 +75,54 @@ export type EvaluatorPredicateInput = string | boolean | {
73
75
  * site. `@object-ui/react`'s `toPredicateInput` is a re-export of this function
74
76
  * (since #3367; it used to be an independent twin held in step by a 14-shape
75
77
  * normalization parity table, which is exactly the arrangement the paragraph
76
- * above describes the failure mode of). What pins that now is the identity
77
- * assertion in
78
+ * above describes the failure mode of).
79
+ *
80
+ * ## Why an already-`${…}` string is NOT wrapped again (objectui#3871)
81
+ *
82
+ * `${…}` is one of the spellings this repo documents for a predicate
83
+ * (AGENTS.md §4: `hidden?: string; // expression: "${data.role != 'admin'}"`)
84
+ * AND one of the shapes {@link EvaluatorPredicateInput} lists as a normalized
85
+ * OUTPUT. Wrapping unconditionally therefore corrupted every value that was
86
+ * already normalized: `'${x}'` became `'${${x}}'`, which no longer matches
87
+ * `ExpressionEvaluator`'s single-template fast path (`/^\$\{([^}]+)\}$/` —
88
+ * `[^}]+` cannot cross the inner `}`), so it fell to the global replace, whose
89
+ * inner expression `'${x'` does not parse. The verdict then stopped depending
90
+ * on the predicate at all, in a direction set by the CALLER's error policy:
91
+ *
92
+ * - fail-soft callers (every `disabled` / `enabled` leg, and the `visible`
93
+ * legs of `action:group` / `action:icon` / `RelatedList`) got the original
94
+ * non-empty string back from the evaluator's `catch { return match }`, so
95
+ * `Boolean(…)` was a constant `true` — `disabled` greyed out forever,
96
+ * `visible` shown forever, `enabled` never disabling.
97
+ * - fail-closed callers (`throwOnError: true` — `action:button` / `action:bar`
98
+ * / `action:menu` / `DeclaredActionsBar` `visible`, and
99
+ * `ActionEngine.getActionsForLocation`) got a THROW, so the constant was
100
+ * the other one: the action was hidden forever, including when its
101
+ * predicate held.
102
+ *
103
+ * Either way the author's expression was never consulted. The guard is
104
+ * `includes('${')` — "does this string already carry template syntax", the
105
+ * same question `evaluateCondition` itself asks before choosing the template
106
+ * path — not an anchored full match: a multi-part string like `'a ${x} b'` is
107
+ * not a predicate in any spelling, and wrapping it produced the same constant
108
+ * verdict, so the narrower guard would buy nothing and would still corrupt a
109
+ * value the evaluator can read.
110
+ *
111
+ * The same guard covers the envelope branch below, where the unwrapped
112
+ * `source` reaches the identical wrap one line later. It is one defect with two
113
+ * spellings, and `{ dialect: 'template', source: '${x}' }` is the shape a
114
+ * producer that compiles an authored template emits. A bare `source` (what
115
+ * `@objectstack/spec`'s `ExpressionInputSchema` produces today, pinned in
116
+ * `packages/react/src/hooks/__tests__/useExpression.test.ts`) is unaffected.
117
+ *
118
+ * What this does NOT do is decide whether `${…}` SHOULD be authorable on an
119
+ * action predicate: `@objectstack/spec`'s `PredicateInput` models a bare string
120
+ * and a dialect envelope, not the template spelling. Rejecting it loudly at
121
+ * publish time is a spec-side ruling (deferred to the objectstack seat by the
122
+ * objectui#3871 dispatch); until then this normalizer must not silently invent
123
+ * a verdict for a spelling both the docs and this file's own output type bless.
124
+ *
125
+ * What pins the single implementation is the identity assertion in
78
126
  * `packages/react/src/hooks/__tests__/actionPredicate.parity.test.tsx` — the
79
127
  * react export must BE this function object — alongside the engine-path vs
80
128
  * renderer-path verdict parity suite in the same file, which is a separate
@@ -5,6 +5,15 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
+ /**
9
+ * Wrap a BARE expression as a `${…}` template, and leave a string that already
10
+ * carries template syntax alone (objectui#3871 — rationale in
11
+ * {@link toPredicateInput}'s doc below). One rule, so the two string spellings
12
+ * inside the normalizer cannot drift apart.
13
+ */
14
+ function wrapIfBare(expression) {
15
+ return expression.includes('${') ? expression : `\${${expression}}`;
16
+ }
8
17
  /**
9
18
  * Normalize a schema-supplied predicate (`visible` / `enabled` / `disabled` /
10
19
  * `hidden`) into the form {@link ExpressionEvaluator.evaluateCondition}
@@ -12,7 +21,9 @@
12
21
  *
13
22
  * Accepts:
14
23
  * - `boolean` → returned as-is (predicate evaluation short-circuits).
15
- * - `string` → wrapped as `${string}` (legacy DX shorthand).
24
+ * - `string` → a BARE expression is wrapped as `${string}` (legacy DX
25
+ * shorthand); a string that is already a `${…}` template is ALREADY in the
26
+ * normalized shape and is returned untouched (objectui#3871, below).
16
27
  * - `Expression` envelope `{ dialect, source }` (the normalized form
17
28
  * `@objectstack/spec`'s `ExpressionInputSchema` produces for every
18
29
  * authored predicate, including bare strings) → a `cel` dialect keeps
@@ -41,8 +52,54 @@
41
52
  * site. `@object-ui/react`'s `toPredicateInput` is a re-export of this function
42
53
  * (since #3367; it used to be an independent twin held in step by a 14-shape
43
54
  * normalization parity table, which is exactly the arrangement the paragraph
44
- * above describes the failure mode of). What pins that now is the identity
45
- * assertion in
55
+ * above describes the failure mode of).
56
+ *
57
+ * ## Why an already-`${…}` string is NOT wrapped again (objectui#3871)
58
+ *
59
+ * `${…}` is one of the spellings this repo documents for a predicate
60
+ * (AGENTS.md §4: `hidden?: string; // expression: "${data.role != 'admin'}"`)
61
+ * AND one of the shapes {@link EvaluatorPredicateInput} lists as a normalized
62
+ * OUTPUT. Wrapping unconditionally therefore corrupted every value that was
63
+ * already normalized: `'${x}'` became `'${${x}}'`, which no longer matches
64
+ * `ExpressionEvaluator`'s single-template fast path (`/^\$\{([^}]+)\}$/` —
65
+ * `[^}]+` cannot cross the inner `}`), so it fell to the global replace, whose
66
+ * inner expression `'${x'` does not parse. The verdict then stopped depending
67
+ * on the predicate at all, in a direction set by the CALLER's error policy:
68
+ *
69
+ * - fail-soft callers (every `disabled` / `enabled` leg, and the `visible`
70
+ * legs of `action:group` / `action:icon` / `RelatedList`) got the original
71
+ * non-empty string back from the evaluator's `catch { return match }`, so
72
+ * `Boolean(…)` was a constant `true` — `disabled` greyed out forever,
73
+ * `visible` shown forever, `enabled` never disabling.
74
+ * - fail-closed callers (`throwOnError: true` — `action:button` / `action:bar`
75
+ * / `action:menu` / `DeclaredActionsBar` `visible`, and
76
+ * `ActionEngine.getActionsForLocation`) got a THROW, so the constant was
77
+ * the other one: the action was hidden forever, including when its
78
+ * predicate held.
79
+ *
80
+ * Either way the author's expression was never consulted. The guard is
81
+ * `includes('${')` — "does this string already carry template syntax", the
82
+ * same question `evaluateCondition` itself asks before choosing the template
83
+ * path — not an anchored full match: a multi-part string like `'a ${x} b'` is
84
+ * not a predicate in any spelling, and wrapping it produced the same constant
85
+ * verdict, so the narrower guard would buy nothing and would still corrupt a
86
+ * value the evaluator can read.
87
+ *
88
+ * The same guard covers the envelope branch below, where the unwrapped
89
+ * `source` reaches the identical wrap one line later. It is one defect with two
90
+ * spellings, and `{ dialect: 'template', source: '${x}' }` is the shape a
91
+ * producer that compiles an authored template emits. A bare `source` (what
92
+ * `@objectstack/spec`'s `ExpressionInputSchema` produces today, pinned in
93
+ * `packages/react/src/hooks/__tests__/useExpression.test.ts`) is unaffected.
94
+ *
95
+ * What this does NOT do is decide whether `${…}` SHOULD be authorable on an
96
+ * action predicate: `@objectstack/spec`'s `PredicateInput` models a bare string
97
+ * and a dialect envelope, not the template spelling. Rejecting it loudly at
98
+ * publish time is a spec-side ruling (deferred to the objectstack seat by the
99
+ * objectui#3871 dispatch); until then this normalizer must not silently invent
100
+ * a verdict for a spelling both the docs and this file's own output type bless.
101
+ *
102
+ * What pins the single implementation is the identity assertion in
46
103
  * `packages/react/src/hooks/__tests__/actionPredicate.parity.test.tsx` — the
47
104
  * react export must BE this function object — alongside the engine-path vs
48
105
  * renderer-path verdict parity suite in the same file, which is a separate
@@ -55,7 +112,7 @@ export function toPredicateInput(value) {
55
112
  if (typeof value === 'boolean')
56
113
  return value;
57
114
  if (typeof value === 'string')
58
- return `\${${value}}`;
115
+ return wrapIfBare(value);
59
116
  if (typeof value === 'object' && typeof value.source === 'string') {
60
117
  const src = value.source;
61
118
  if (!src)
@@ -66,7 +123,7 @@ export function toPredicateInput(value) {
66
123
  if (value.dialect === 'cel')
67
124
  return { dialect: 'cel', source: src };
68
125
  // Every other dialect (template / unset) keeps the legacy `${…}` behavior.
69
- return `\${${src}}`;
126
+ return wrapIfBare(src);
70
127
  }
71
128
  return undefined;
72
129
  }
package/dist/index.d.ts CHANGED
@@ -13,12 +13,15 @@ export * from './registry/PluginScopeImpl.js';
13
13
  export * from './registry/WidgetRegistry.js';
14
14
  export * from './validation/index.js';
15
15
  export * from './builder/schema-builder.js';
16
+ export * from './utils/dom-props.js';
16
17
  export * from './utils/filter-converter.js';
17
18
  export * from './utils/managedBy.js';
18
19
  export * from './utils/extract-records.js';
19
20
  export * from './utils/expand-fields.js';
20
21
  export * from './utils/column-identity.js';
21
22
  export * from './utils/sort-values.js';
23
+ export * from './utils/sort-query.js';
24
+ export * from './utils/resolve-view-id.js';
22
25
  export * from './evaluator/index.js';
23
26
  export * from './actions/index.js';
24
27
  export * from './query/index.js';
@@ -28,7 +31,6 @@ export * from './data-scope/index.js';
28
31
  export * from './errors/index.js';
29
32
  export * from './utils/debug.js';
30
33
  export * from './utils/debug-collector.js';
31
- export * from './utils/merge-views-into-objects.js';
32
34
  export * from './utils/freeze-schema.js';
33
35
  export * from './protocols/index.js';
34
36
  export * from './styling/scoped-styles.js';
@@ -47,6 +49,7 @@ export * from './utils/dashboard-filters.js';
47
49
  export * from './utils/merge-filters.js';
48
50
  export * from './utils/compare-to.js';
49
51
  export * from './utils/chart-series.js';
52
+ export * from './utils/number-display.js';
50
53
  export * from './utils/dataset-format.js';
51
54
  export * from './utils/dataset-pivot.js';
52
55
  export * from './utils/record-title.js';
package/dist/index.js CHANGED
@@ -12,12 +12,20 @@ export * from './registry/PluginScopeImpl.js';
12
12
  export * from './registry/WidgetRegistry.js';
13
13
  export * from './validation/index.js';
14
14
  export * from './builder/schema-builder.js';
15
+ // The DOM pass-through whitelist of the SDUI widget prop contract
16
+ // (objectui#4425 phase 2): a registered widget's host element receives only
17
+ // what `toDomProps` passes — everything else is consumed or dropped.
18
+ // `@object-ui/fields` executes the SAME mechanism against its own declared key
19
+ // list (`pickDomProps`), so there is one judge, not two.
20
+ export * from './utils/dom-props.js';
15
21
  export * from './utils/filter-converter.js';
16
22
  export * from './utils/managedBy.js';
17
23
  export * from './utils/extract-records.js';
18
24
  export * from './utils/expand-fields.js';
19
25
  export * from './utils/column-identity.js';
20
26
  export * from './utils/sort-values.js';
27
+ export * from './utils/sort-query.js';
28
+ export * from './utils/resolve-view-id.js';
21
29
  export * from './evaluator/index.js';
22
30
  export * from './actions/index.js';
23
31
  export * from './query/index.js';
@@ -27,7 +35,6 @@ export * from './data-scope/index.js';
27
35
  export * from './errors/index.js';
28
36
  export * from './utils/debug.js';
29
37
  export * from './utils/debug-collector.js';
30
- export * from './utils/merge-views-into-objects.js';
31
38
  export * from './utils/freeze-schema.js';
32
39
  export * from './protocols/index.js';
33
40
  export * from './styling/scoped-styles.js';
@@ -49,6 +56,12 @@ export * from './utils/dashboard-filters.js';
49
56
  export * from './utils/merge-filters.js';
50
57
  export * from './utils/compare-to.js';
51
58
  export * from './utils/chart-series.js';
59
+ // The ONE number-display formatter (objectui#4033) — grouping policy, display
60
+ // locale and the percent convention. It lived in `@object-ui/i18n` until
61
+ // objectui#4576; it is pure, and living above `core` was what kept
62
+ // `dataset-format` below from reaching it (so the two drifted). `@object-ui/i18n`
63
+ // re-exports these names unchanged, so both import paths name the same symbol.
64
+ export * from './utils/number-display.js';
52
65
  export * from './utils/dataset-format.js';
53
66
  // Pivot lookup-key encoders, shared by every cross-tab renderer so the
54
67
  // dashboard widget and the report renderer key their buckets identically
@@ -14,7 +14,48 @@
14
14
  * @module protocols/DndProtocol
15
15
  * @packageDocumentation
16
16
  */
17
- import type { DndConfig, DragItem, DropZone, DragConstraint } from '@object-ui/types';
17
+ /** How a drag is initiated. */
18
+ export type DragHandle = 'element' | 'handle' | 'grip_icon';
19
+ /** The `dropEffect` advertised to assistive technology and DnD libraries. */
20
+ export type DropEffect = 'none' | 'link' | 'move' | 'copy';
21
+ /** Movement constraints applied to a drag. */
22
+ export interface DragConstraint {
23
+ axis: 'x' | 'y' | 'both';
24
+ bounds: 'none' | 'parent' | 'viewport';
25
+ grid?: [number, number];
26
+ }
27
+ /** A draggable item's declaration. */
28
+ export interface DragItem {
29
+ type: string;
30
+ label?: string;
31
+ handle: DragHandle;
32
+ constraint?: DragConstraint;
33
+ preview: 'custom' | 'none' | 'element';
34
+ disabled: boolean;
35
+ ariaLabel?: string;
36
+ ariaDescribedBy?: string;
37
+ role?: string;
38
+ }
39
+ /** A drop target's declaration. */
40
+ export interface DropZone {
41
+ label?: string;
42
+ accept: string[];
43
+ maxItems?: number;
44
+ highlightOnDragOver: boolean;
45
+ dropEffect: DropEffect;
46
+ ariaLabel?: string;
47
+ ariaDescribedBy?: string;
48
+ role?: string;
49
+ }
50
+ /** Top-level drag-and-drop configuration. */
51
+ export interface DndConfig {
52
+ enabled: boolean;
53
+ dragItem?: DragItem;
54
+ dropZone?: DropZone;
55
+ sortable: boolean;
56
+ autoScroll: boolean;
57
+ touchDelay: number;
58
+ }
18
59
  /** Fully resolved DnD configuration with all defaults applied. */
19
60
  export interface ResolvedDndConfig {
20
61
  enabled: boolean;
@@ -15,7 +15,37 @@
15
15
  * @module protocols/KeyboardProtocol
16
16
  * @packageDocumentation
17
17
  */
18
- import type { KeyboardNavigationConfig, KeyboardShortcut, FocusTrapConfig } from '@object-ui/types';
18
+ /** A single keyboard shortcut binding. */
19
+ export interface KeyboardShortcut {
20
+ key: string;
21
+ action: string;
22
+ description?: string;
23
+ scope: 'global' | 'form' | 'modal' | 'view' | 'list';
24
+ }
25
+ /** Focus trap declaration for a modal-like region. */
26
+ export interface FocusTrapConfig {
27
+ enabled: boolean;
28
+ initialFocus?: string;
29
+ returnFocus: boolean;
30
+ escapeDeactivates: boolean;
31
+ }
32
+ /** Focus management declaration. */
33
+ export interface FocusManagement {
34
+ tabOrder: 'manual' | 'auto';
35
+ skipLinks: boolean;
36
+ focusVisible: boolean;
37
+ focusTrap?: FocusTrapConfig;
38
+ arrowNavigation: boolean;
39
+ }
40
+ /** Top-level keyboard navigation configuration. */
41
+ export interface KeyboardNavigationConfig {
42
+ shortcuts?: KeyboardShortcut[];
43
+ focusManagement?: FocusManagement;
44
+ rovingTabindex: boolean;
45
+ ariaLabel?: string;
46
+ ariaDescribedBy?: string;
47
+ role?: string;
48
+ }
19
49
  /** Fully resolved keyboard navigation configuration. */
20
50
  export interface ResolvedKeyboardConfig {
21
51
  shortcuts: KeyboardShortcut[];
@@ -15,7 +15,17 @@
15
15
  * @module protocols/SharingProtocol
16
16
  * @packageDocumentation
17
17
  */
18
- import type { SharingConfig, EmbedConfig } from '@object-ui/types';
18
+ import type { SharingConfig } from '@object-ui/types';
19
+ /** An iframe embed declaration. */
20
+ export interface EmbedConfig {
21
+ enabled: boolean;
22
+ allowedOrigins?: string[];
23
+ width?: string;
24
+ height?: string;
25
+ showHeader?: boolean;
26
+ showNavigation?: boolean;
27
+ responsive?: boolean;
28
+ }
19
29
  /** Fully resolved sharing configuration. */
20
30
  export interface ResolvedSharingConfig {
21
31
  enabled: boolean;
@@ -43,6 +43,30 @@ export type ComponentMeta = {
43
43
  * registry.register('form', FormView, { namespace: 'view', skipFallback: true });
44
44
  */
45
45
  skipFallback?: boolean;
46
+ /**
47
+ * How a HOST must associate its own visible label with what this component
48
+ * renders (objectui#3961). Read by the form renderer; absent ⇒ `'control'`.
49
+ *
50
+ * - `'control'` — the component's outermost rendered element is a LABELABLE
51
+ * HTML element (`input` / `textarea` / `select` / `button` / …), so the host
52
+ * associates its label the plain way: `<label for>` → the id the host handed
53
+ * down. This is the default and covers every single-control widget.
54
+ * - `'group'` — it is NOT labelable: either a real composite (several inputs
55
+ * under one container, e.g. `address`) or a single control that is not a
56
+ * labelable element (a `div[role="button"]` dropzone, e.g. `file`). A
57
+ * `<label for>` pointing at such an element is inert in HTML — it activates
58
+ * nothing and contributes no accessible name (`HTMLLabelElement.control` is
59
+ * `null`) — so the host must instead give its label an `id` and hand the
60
+ * component `aria-labelledby`, which associates by IDREF and works on any
61
+ * element.
62
+ *
63
+ * This is a DECLARATION, not a guess: the host cannot infer it from the DOM a
64
+ * widget happens to render, and a widget that fails to declare it falls back
65
+ * to the `'control'` path where the dangling/inert `for` is caught by the
66
+ * label-association tests (objectui#3952) instead of silently producing an
67
+ * unlabelled group.
68
+ */
69
+ labelling?: 'control' | 'group';
46
70
  inputs?: ComponentInput[];
47
71
  defaultProps?: Record<string, any>;
48
72
  defaultChildren?: SchemaNode[];
@@ -43,14 +43,6 @@ export declare function generateBorderRadiusVars(borderRadius: NonNullable<Theme
43
43
  * Generate CSS custom properties from a Theme's shadow config.
44
44
  */
45
45
  export declare function generateShadowVars(shadows: NonNullable<Theme['shadows']>): Record<string, string>;
46
- /**
47
- * Generate CSS custom properties from a Theme's animation config.
48
- */
49
- export declare function generateAnimationVars(animation: NonNullable<Theme['animation']>): Record<string, string>;
50
- /**
51
- * Generate CSS custom properties from a Theme's z-index config.
52
- */
53
- export declare function generateZIndexVars(zIndex: NonNullable<Theme['zIndex']>): Record<string, string>;
54
46
  /**
55
47
  * Generate ALL CSS custom properties from a complete Theme.
56
48
  * This is the main entry point for theme → CSS conversion.