@object-ui/core 17.6.0 → 17.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6610 -0
- package/README.md +101 -14
- package/dist/actions/ActionEngine.d.ts +4 -2
- package/dist/actions/ActionEngine.js +13 -8
- package/dist/actions/ActionRunner.d.ts +288 -27
- package/dist/actions/ActionRunner.js +443 -63
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/actionKeys.d.ts +9 -7
- package/dist/actions/actionKeys.js +57 -14
- package/dist/actions/actionResponse.js +2 -2
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/recordDelete.d.ts +118 -0
- package/dist/actions/recordDelete.js +112 -0
- package/dist/adapters/ValueDataSource.d.ts +6 -1
- package/dist/adapters/ValueDataSource.js +1090 -98
- package/dist/adapters/batchTransaction.d.ts +1 -1
- package/dist/adapters/batchTransaction.js +1 -1
- package/dist/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +1 -1
- package/dist/data-scope/ViewDataProvider.js +21 -1
- package/dist/data-scope/element-data-source.d.ts +261 -7
- package/dist/data-scope/element-data-source.js +342 -52
- package/dist/data-scope/index.d.ts +9 -4
- package/dist/data-scope/index.js +9 -4
- package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
- package/dist/evaluator/ExpressionEvaluator.js +184 -12
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +117 -16
- package/dist/evaluator/fieldRules.d.ts +88 -4
- package/dist/evaluator/fieldRules.js +203 -31
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/evaluator/listConditional.d.ts +103 -34
- package/dist/evaluator/listConditional.js +127 -50
- package/dist/evaluator/optionRules.d.ts +77 -6
- package/dist/evaluator/optionRules.js +87 -10
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
- package/dist/evaluator/rowPredicateCanon.js +65 -115
- package/dist/evaluator/subjectPermissions.d.ts +76 -0
- package/dist/evaluator/subjectPermissions.js +44 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +80 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +358 -31
- package/dist/registry/Registry.js +263 -11
- package/dist/registry/WidgetRegistry.js +7 -3
- package/dist/registry/html-tier-intrinsics.d.ts +90 -0
- package/dist/registry/html-tier-intrinsics.js +110 -0
- package/dist/registry/public-blocks.d.ts +8 -0
- package/dist/registry/public-blocks.js +57 -0
- package/dist/theme/ThemeEngine.js +1 -1
- package/dist/utils/chart-category-key.d.ts +82 -0
- package/dist/utils/chart-category-key.js +84 -0
- package/dist/utils/chart-measure-key.d.ts +64 -0
- package/dist/utils/chart-measure-key.js +65 -0
- package/dist/utils/chart-presentation.d.ts +95 -112
- package/dist/utils/chart-presentation.js +169 -104
- package/dist/utils/chart-series.d.ts +131 -5
- package/dist/utils/chart-series.js +98 -16
- package/dist/utils/column-sortability.d.ts +157 -0
- package/dist/utils/column-sortability.js +217 -0
- package/dist/utils/dashboard-filters.d.ts +38 -6
- package/dist/utils/dashboard-filters.js +107 -61
- package/dist/utils/dataset-format.d.ts +211 -30
- package/dist/utils/dataset-format.js +389 -46
- package/dist/utils/date-display.d.ts +354 -0
- package/dist/utils/date-display.js +463 -0
- package/dist/utils/date-macros.js +7 -0
- package/dist/utils/drill-down.d.ts +78 -0
- package/dist/utils/drill-down.js +93 -0
- package/dist/utils/emptiness.d.ts +96 -0
- package/dist/utils/emptiness.js +101 -0
- package/dist/utils/expand-fields.d.ts +76 -6
- package/dist/utils/expand-fields.js +76 -6
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +152 -4
- package/dist/utils/filter-converter.js +1092 -22
- package/dist/utils/filter-tokens.d.ts +49 -5
- package/dist/utils/filter-tokens.js +105 -33
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/managedBy.d.ts +3 -3
- package/dist/utils/managedBy.js +5 -5
- package/dist/utils/native-date-value.d.ts +48 -0
- package/dist/utils/native-date-value.js +167 -0
- package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
- package/dist/utils/non-grid-row-ceiling.js +141 -0
- package/dist/utils/normalize-list-view.d.ts +121 -1
- package/dist/utils/normalize-list-view.js +186 -6
- package/dist/utils/number-display.d.ts +63 -14
- package/dist/utils/number-display.js +34 -12
- package/dist/utils/object-aggregate-query.d.ts +113 -0
- package/dist/utils/object-aggregate-query.js +50 -0
- package/dist/utils/parent-scope.d.ts +123 -0
- package/dist/utils/parent-scope.js +140 -0
- package/dist/utils/predicate-fields.d.ts +45 -12
- package/dist/utils/predicate-fields.js +137 -12
- package/dist/utils/record-color.d.ts +75 -0
- package/dist/utils/record-color.js +65 -0
- package/dist/utils/record-source.d.ts +298 -0
- package/dist/utils/record-source.js +331 -0
- package/dist/utils/record-title.d.ts +148 -8
- package/dist/utils/record-title.js +220 -26
- package/dist/utils/reference-keys.d.ts +21 -22
- package/dist/utils/reference-keys.js +532 -29
- package/dist/utils/sort-query.d.ts +115 -15
- package/dist/utils/sort-query.js +240 -13
- package/dist/utils/text-comparand.d.ts +92 -0
- package/dist/utils/text-comparand.js +99 -0
- package/dist/utils/without-denied-fields.d.ts +49 -0
- package/dist/utils/without-denied-fields.js +52 -0
- package/dist/validation/index.d.ts +8 -4
- package/dist/validation/index.js +8 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +200 -82
- package/dist/validation/validators/object-validation-engine.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -3
- package/package.json +8 -9
- package/dist/data-scope/DataScopeManager.d.ts +0 -127
- package/dist/data-scope/DataScopeManager.js +0 -229
- package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
- package/dist/protocols/ResponsiveProtocol.js +0 -158
- package/dist/validation/validation-engine.d.ts +0 -88
- package/dist/validation/validation-engine.js +0 -434
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
* The parent-relationship condition a detail-page related list is scoped by —
|
|
9
|
+
* compiled ONCE, here, for every surface that asks the question.
|
|
10
|
+
*
|
|
11
|
+
* ## Why this is a seam and not a helper
|
|
12
|
+
*
|
|
13
|
+
* A related list asks its backend "which children belong to this parent?" from
|
|
14
|
+
* two places: the ROW query (`RelatedList`) and the tab-badge count probe
|
|
15
|
+
* (`RelatedCountStore`). Those two used to compile the condition separately,
|
|
16
|
+
* and objectui#8882 is what that costs: objectui#7299 taught the ROW side to
|
|
17
|
+
* compile by the relationship field's ARITY, the BADGE side kept sending bare
|
|
18
|
+
* equality, and on a multi-value relationship the page then rendered rows above
|
|
19
|
+
* a tab with no count at all — two implementations of one question, drifted.
|
|
20
|
+
*
|
|
21
|
+
* Patching the second copy to match the first would leave two copies. So the
|
|
22
|
+
* condition has one implementation, and both callers import it.
|
|
23
|
+
*
|
|
24
|
+
* ## The rule itself is not invented here
|
|
25
|
+
*
|
|
26
|
+
* The arity verdict is `@objectstack/spec/data`'s own `isMultiValueField`. That
|
|
27
|
+
* matters more here than anywhere: this function chooses `$contains` vs `=` and
|
|
28
|
+
* every surface that asks the question now reads that one choice, so two
|
|
29
|
+
* readers of one question disagreeing — the entire defect class — cannot
|
|
30
|
+
* recur INSIDE this repository. The spec's rule is BROADER than an eyeballed
|
|
31
|
+
* `multiple === true` in both directions — `multiselect` / `checkboxes` /
|
|
32
|
+
* `tags` persist an array with no flag at all, and `multiple: true` is INERT on
|
|
33
|
+
* a type outside the spec's multi-capable set (`master_detail`, say) — so a
|
|
34
|
+
* local approximation is wrong in both directions, not merely incomplete.
|
|
35
|
+
*
|
|
36
|
+
* ⛔ Do not add a local arity rule at any call site, however small, and ⛔ do
|
|
37
|
+
* not widen this function to accept an arity the caller computed: the parameter
|
|
38
|
+
* it takes is METADATA, and the verdict is drawn from it here.
|
|
39
|
+
*
|
|
40
|
+
* ## ⚠️ The STORAGE side does not read this predicate — the two rules DIVERGE
|
|
41
|
+
*
|
|
42
|
+
* This header used to say `isMultiValueField` is "the same predicate the driver
|
|
43
|
+
* that executes the query decides on". It is not, and the difference is
|
|
44
|
+
* observable (objectui#8937). Measured on objectstack `origin/main`,
|
|
45
|
+
* `driver-sql` gates the equality family on its own STORAGE question,
|
|
46
|
+
* `isJsonField`: a column is JSON when the field's type is in that driver's
|
|
47
|
+
* `JSON_COLUMN_TYPES` — the spec's `STRUCTURED_JSON_TYPES` and
|
|
48
|
+
* `MULTI_OPTION_TYPES`, plus the driver-internal `object` / `array` aliases —
|
|
49
|
+
* OR when `multiple` is merely TRUTHY, on ANY type; a single-value media type
|
|
50
|
+
* answers from the ADR-0104 dual-encoding window instead. The spec's predicate
|
|
51
|
+
* is `MULTI_OPTION_TYPES.has(type) || (MULTI_CAPABLE_TYPES.has(type) &&
|
|
52
|
+
* multiple === true)`.
|
|
53
|
+
*
|
|
54
|
+
* ⇒ They diverge for a type OUTSIDE `MULTI_CAPABLE_TYPES` carrying
|
|
55
|
+
* `multiple: true` (`master_detail` / `tree` / `text`): the spec says
|
|
56
|
+
* single-valued, so this seam compiles `=`, while the driver stores a JSON
|
|
57
|
+
* column and refuses `=` with the same `400 INVALID_FILTER` objectui#7299 was
|
|
58
|
+
* filed for. That is a KNOWN divergence and ⛔ not a regression — both
|
|
59
|
+
* surfaces sent `=` for that shape before objectui#7299 and objectui#8882 too.
|
|
60
|
+
*
|
|
61
|
+
* ⛔ Do NOT close it by widening the predicate here: which of the two rules is
|
|
62
|
+
* right is an upstream question, filed as objectstack#17469 (enforce-or-align),
|
|
63
|
+
* and this seam is deliberately not blocked on it. Widening on this side would
|
|
64
|
+
* move the disagreement across the wire rather than end it, and would make the
|
|
65
|
+
* ONE compiler this module exists to be disagree with the spec it delegates to.
|
|
66
|
+
* The divergence is re-derived from the installed spec on every run by
|
|
67
|
+
* `relatedListParentScopeResidue-8937.test.ts`, so this section reddens when the
|
|
68
|
+
* spec side moves.
|
|
69
|
+
*/
|
|
70
|
+
import { type ValueShapeFieldDef } from '@objectstack/spec/data';
|
|
71
|
+
import type { FieldContainerLike } from './predicate-record.js';
|
|
72
|
+
/**
|
|
73
|
+
* Look one field def up in either container shape the metadata API serves.
|
|
74
|
+
*
|
|
75
|
+
* The pair is the one {@link FieldContainerLike} names: the Record keyed by
|
|
76
|
+
* field name, and the array of defs carrying their own `name`. A reader that
|
|
77
|
+
* knows only one of them silently answers "no such field" for the other — and
|
|
78
|
+
* "no such field" here means "single-valued", which is this card's own bug
|
|
79
|
+
* spelled as a default.
|
|
80
|
+
*/
|
|
81
|
+
export declare function parentRelationshipFieldDef(fields: FieldContainerLike, fieldName: string | undefined): ValueShapeFieldDef | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Does this relationship field store MANY parent ids rather than one?
|
|
84
|
+
*
|
|
85
|
+
* The seam's verdict, exposed on its own because a caller sometimes needs the
|
|
86
|
+
* ANSWER without the condition — the raw-URL related-list path, whose
|
|
87
|
+
* `filter[<field>]=<value>` grammar has no membership operator, has to know
|
|
88
|
+
* the arity in order to REFUSE. Deriving it a second time at that call site is
|
|
89
|
+
* how the two compilers this module exists to merge came about, so it is
|
|
90
|
+
* derived once here and read from both shapes below.
|
|
91
|
+
*/
|
|
92
|
+
export declare function isMultiValueRelationship(fields: FieldContainerLike, fieldName: string | undefined): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* The parent-relationship condition, compiled to match the field's ARITY.
|
|
95
|
+
*
|
|
96
|
+
* - single-valued → `{ [relationshipField]: parentId }` — equality, byte for
|
|
97
|
+
* byte what both surfaces have always sent;
|
|
98
|
+
* - `multiple: true` (per the spec predicate) →
|
|
99
|
+
* `{ [relationshipField]: { $contains: parentId } }` — MEMBERSHIP, because
|
|
100
|
+
* the stored value is an ARRAY of ids and equality asks whether that whole
|
|
101
|
+
* array IS one id. `$contains` is the spelling the drivers execute for it,
|
|
102
|
+
* the one `driver-sql` names in the `400 INVALID_FILTER` it answers the
|
|
103
|
+
* equality form with.
|
|
104
|
+
*
|
|
105
|
+
* The author never writes either: they named a relationship, and its storage
|
|
106
|
+
* form is the renderer's business.
|
|
107
|
+
*
|
|
108
|
+
* The return value is the plain MongoDB-style object both call sites have
|
|
109
|
+
* always put on the wire, NOT a lowered ObjectQL AST. That is deliberate: with
|
|
110
|
+
* nothing else declared the query must stay byte-identical to what it was, and
|
|
111
|
+
* a freshly lowered AST would mean the same thing, be invisible on screen, and
|
|
112
|
+
* break every caller pinning the wire. Composition with a list's own declared
|
|
113
|
+
* scope stays the caller's step, through `mergeFilterNodes`.
|
|
114
|
+
*
|
|
115
|
+
* @param relationshipField The child field pointing back at the parent.
|
|
116
|
+
* @param parentId The parent record's primary key value.
|
|
117
|
+
* @param fields The CHILD object's field defs (`objectSchema.fields`),
|
|
118
|
+
* in either served shape. Without them the condition
|
|
119
|
+
* compiles to equality — the historical wire, and the
|
|
120
|
+
* only answer available to a caller that cannot see
|
|
121
|
+
* the metadata. A caller that CAN see it must pass it.
|
|
122
|
+
*/
|
|
123
|
+
export declare function composeParentScopeFilter(relationshipField: string, parentId: string | number, fields?: FieldContainerLike): Record<string, unknown>;
|
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
* The parent-relationship condition a detail-page related list is scoped by —
|
|
9
|
+
* compiled ONCE, here, for every surface that asks the question.
|
|
10
|
+
*
|
|
11
|
+
* ## Why this is a seam and not a helper
|
|
12
|
+
*
|
|
13
|
+
* A related list asks its backend "which children belong to this parent?" from
|
|
14
|
+
* two places: the ROW query (`RelatedList`) and the tab-badge count probe
|
|
15
|
+
* (`RelatedCountStore`). Those two used to compile the condition separately,
|
|
16
|
+
* and objectui#8882 is what that costs: objectui#7299 taught the ROW side to
|
|
17
|
+
* compile by the relationship field's ARITY, the BADGE side kept sending bare
|
|
18
|
+
* equality, and on a multi-value relationship the page then rendered rows above
|
|
19
|
+
* a tab with no count at all — two implementations of one question, drifted.
|
|
20
|
+
*
|
|
21
|
+
* Patching the second copy to match the first would leave two copies. So the
|
|
22
|
+
* condition has one implementation, and both callers import it.
|
|
23
|
+
*
|
|
24
|
+
* ## The rule itself is not invented here
|
|
25
|
+
*
|
|
26
|
+
* The arity verdict is `@objectstack/spec/data`'s own `isMultiValueField`. That
|
|
27
|
+
* matters more here than anywhere: this function chooses `$contains` vs `=` and
|
|
28
|
+
* every surface that asks the question now reads that one choice, so two
|
|
29
|
+
* readers of one question disagreeing — the entire defect class — cannot
|
|
30
|
+
* recur INSIDE this repository. The spec's rule is BROADER than an eyeballed
|
|
31
|
+
* `multiple === true` in both directions — `multiselect` / `checkboxes` /
|
|
32
|
+
* `tags` persist an array with no flag at all, and `multiple: true` is INERT on
|
|
33
|
+
* a type outside the spec's multi-capable set (`master_detail`, say) — so a
|
|
34
|
+
* local approximation is wrong in both directions, not merely incomplete.
|
|
35
|
+
*
|
|
36
|
+
* ⛔ Do not add a local arity rule at any call site, however small, and ⛔ do
|
|
37
|
+
* not widen this function to accept an arity the caller computed: the parameter
|
|
38
|
+
* it takes is METADATA, and the verdict is drawn from it here.
|
|
39
|
+
*
|
|
40
|
+
* ## ⚠️ The STORAGE side does not read this predicate — the two rules DIVERGE
|
|
41
|
+
*
|
|
42
|
+
* This header used to say `isMultiValueField` is "the same predicate the driver
|
|
43
|
+
* that executes the query decides on". It is not, and the difference is
|
|
44
|
+
* observable (objectui#8937). Measured on objectstack `origin/main`,
|
|
45
|
+
* `driver-sql` gates the equality family on its own STORAGE question,
|
|
46
|
+
* `isJsonField`: a column is JSON when the field's type is in that driver's
|
|
47
|
+
* `JSON_COLUMN_TYPES` — the spec's `STRUCTURED_JSON_TYPES` and
|
|
48
|
+
* `MULTI_OPTION_TYPES`, plus the driver-internal `object` / `array` aliases —
|
|
49
|
+
* OR when `multiple` is merely TRUTHY, on ANY type; a single-value media type
|
|
50
|
+
* answers from the ADR-0104 dual-encoding window instead. The spec's predicate
|
|
51
|
+
* is `MULTI_OPTION_TYPES.has(type) || (MULTI_CAPABLE_TYPES.has(type) &&
|
|
52
|
+
* multiple === true)`.
|
|
53
|
+
*
|
|
54
|
+
* ⇒ They diverge for a type OUTSIDE `MULTI_CAPABLE_TYPES` carrying
|
|
55
|
+
* `multiple: true` (`master_detail` / `tree` / `text`): the spec says
|
|
56
|
+
* single-valued, so this seam compiles `=`, while the driver stores a JSON
|
|
57
|
+
* column and refuses `=` with the same `400 INVALID_FILTER` objectui#7299 was
|
|
58
|
+
* filed for. That is a KNOWN divergence and ⛔ not a regression — both
|
|
59
|
+
* surfaces sent `=` for that shape before objectui#7299 and objectui#8882 too.
|
|
60
|
+
*
|
|
61
|
+
* ⛔ Do NOT close it by widening the predicate here: which of the two rules is
|
|
62
|
+
* right is an upstream question, filed as objectstack#17469 (enforce-or-align),
|
|
63
|
+
* and this seam is deliberately not blocked on it. Widening on this side would
|
|
64
|
+
* move the disagreement across the wire rather than end it, and would make the
|
|
65
|
+
* ONE compiler this module exists to be disagree with the spec it delegates to.
|
|
66
|
+
* The divergence is re-derived from the installed spec on every run by
|
|
67
|
+
* `relatedListParentScopeResidue-8937.test.ts`, so this section reddens when the
|
|
68
|
+
* spec side moves.
|
|
69
|
+
*/
|
|
70
|
+
import { isMultiValueField } from '@objectstack/spec/data';
|
|
71
|
+
/**
|
|
72
|
+
* Look one field def up in either container shape the metadata API serves.
|
|
73
|
+
*
|
|
74
|
+
* The pair is the one {@link FieldContainerLike} names: the Record keyed by
|
|
75
|
+
* field name, and the array of defs carrying their own `name`. A reader that
|
|
76
|
+
* knows only one of them silently answers "no such field" for the other — and
|
|
77
|
+
* "no such field" here means "single-valued", which is this card's own bug
|
|
78
|
+
* spelled as a default.
|
|
79
|
+
*/
|
|
80
|
+
export function parentRelationshipFieldDef(fields, fieldName) {
|
|
81
|
+
if (!fieldName || !fields || typeof fields !== 'object')
|
|
82
|
+
return undefined;
|
|
83
|
+
const def = Array.isArray(fields)
|
|
84
|
+
? fields.find((f) => f?.name === fieldName)
|
|
85
|
+
: fields[fieldName];
|
|
86
|
+
if (!def || typeof def !== 'object')
|
|
87
|
+
return undefined;
|
|
88
|
+
// `type` is the one member the spec's predicate reads besides `multiple`; a
|
|
89
|
+
// def without it answers `false` through both of the predicate's set lookups,
|
|
90
|
+
// which is the right answer for a field whose type nobody declared.
|
|
91
|
+
return def;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Does this relationship field store MANY parent ids rather than one?
|
|
95
|
+
*
|
|
96
|
+
* The seam's verdict, exposed on its own because a caller sometimes needs the
|
|
97
|
+
* ANSWER without the condition — the raw-URL related-list path, whose
|
|
98
|
+
* `filter[<field>]=<value>` grammar has no membership operator, has to know
|
|
99
|
+
* the arity in order to REFUSE. Deriving it a second time at that call site is
|
|
100
|
+
* how the two compilers this module exists to merge came about, so it is
|
|
101
|
+
* derived once here and read from both shapes below.
|
|
102
|
+
*/
|
|
103
|
+
export function isMultiValueRelationship(fields, fieldName) {
|
|
104
|
+
const def = parentRelationshipFieldDef(fields, fieldName);
|
|
105
|
+
return def !== undefined && isMultiValueField(def);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The parent-relationship condition, compiled to match the field's ARITY.
|
|
109
|
+
*
|
|
110
|
+
* - single-valued → `{ [relationshipField]: parentId }` — equality, byte for
|
|
111
|
+
* byte what both surfaces have always sent;
|
|
112
|
+
* - `multiple: true` (per the spec predicate) →
|
|
113
|
+
* `{ [relationshipField]: { $contains: parentId } }` — MEMBERSHIP, because
|
|
114
|
+
* the stored value is an ARRAY of ids and equality asks whether that whole
|
|
115
|
+
* array IS one id. `$contains` is the spelling the drivers execute for it,
|
|
116
|
+
* the one `driver-sql` names in the `400 INVALID_FILTER` it answers the
|
|
117
|
+
* equality form with.
|
|
118
|
+
*
|
|
119
|
+
* The author never writes either: they named a relationship, and its storage
|
|
120
|
+
* form is the renderer's business.
|
|
121
|
+
*
|
|
122
|
+
* The return value is the plain MongoDB-style object both call sites have
|
|
123
|
+
* always put on the wire, NOT a lowered ObjectQL AST. That is deliberate: with
|
|
124
|
+
* nothing else declared the query must stay byte-identical to what it was, and
|
|
125
|
+
* a freshly lowered AST would mean the same thing, be invisible on screen, and
|
|
126
|
+
* break every caller pinning the wire. Composition with a list's own declared
|
|
127
|
+
* scope stays the caller's step, through `mergeFilterNodes`.
|
|
128
|
+
*
|
|
129
|
+
* @param relationshipField The child field pointing back at the parent.
|
|
130
|
+
* @param parentId The parent record's primary key value.
|
|
131
|
+
* @param fields The CHILD object's field defs (`objectSchema.fields`),
|
|
132
|
+
* in either served shape. Without them the condition
|
|
133
|
+
* compiles to equality — the historical wire, and the
|
|
134
|
+
* only answer available to a caller that cannot see
|
|
135
|
+
* the metadata. A caller that CAN see it must pass it.
|
|
136
|
+
*/
|
|
137
|
+
export function composeParentScopeFilter(relationshipField, parentId, fields) {
|
|
138
|
+
const multi = isMultiValueRelationship(fields, relationshipField);
|
|
139
|
+
return { [relationshipField]: multi ? { $contains: parentId } : parentId };
|
|
140
|
+
}
|
|
@@ -32,7 +32,10 @@
|
|
|
32
32
|
* predicate.)
|
|
33
33
|
*
|
|
34
34
|
* Since objectstack#8018 the same reader also carries the non-predicate row keys
|
|
35
|
-
* a view's actions read
|
|
35
|
+
* a view's actions read: `recordIdField`, since objectui#10277 the field a
|
|
36
|
+
* `defaultFromRow` param seeds from and the `{field}` tokens of an action's
|
|
37
|
+
* `target`, and since objectui#10404 the fields an `undoable` action writes,
|
|
38
|
+
* whose prior values the Undo capture reads off the row. The name kept the
|
|
36
39
|
* "predicate" spelling because the mechanism is identical (harvest a name, gate
|
|
37
40
|
* it against the declared fields, add it to `$select`); see
|
|
38
41
|
* {@link listViewPredicates} for what is in the set and why.
|
|
@@ -85,24 +88,54 @@ export declare function collectPredicateFieldRefs(predicates: readonly unknown[]
|
|
|
85
88
|
* kebab (custom defs AND the built-in Edit/Delete overrides), the selection bar,
|
|
86
89
|
* and the object's declared actions.
|
|
87
90
|
*
|
|
88
|
-
* Most entries are predicates.
|
|
91
|
+
* Most entries are predicates. Five are not, and all are spelled as a synthetic
|
|
89
92
|
* `record.<name>` so the one harvester handles them: conditional formatting's
|
|
90
|
-
* native `{ field, operator, value }` shape,
|
|
91
|
-
* (objectstack#8018)
|
|
92
|
-
*
|
|
93
|
+
* native `{ field, operator, value }` shape, an action's `recordIdField`
|
|
94
|
+
* (objectstack#8018), the field each `defaultFromRow` param seeds from, each
|
|
95
|
+
* `{field}` token of the action's `target` (both objectui#10277), and the fields
|
|
96
|
+
* an `undoable` action writes (objectui#10404). The
|
|
97
|
+
* `recordIdField` case is the same *class* of bug the predicate harvest exists
|
|
98
|
+
* to close, one surface over — the action runtime reads
|
|
93
99
|
* `rowRecord[action.recordIdField]` to seed `recordIdParam`, so a key outside the
|
|
94
100
|
* listView columns arrived absent and the injection was silently skipped, which
|
|
95
101
|
* turns a record-scoped mutation into one that names no record while still
|
|
96
102
|
* reporting success. The default (`id`) is already projected unconditionally, so
|
|
97
103
|
* only an explicit declaration adds anything here.
|
|
98
104
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* and
|
|
105
|
+
* The last two are the same class again, and they were silent in the same way.
|
|
106
|
+
* `resolveActionParam` seeds a `defaultFromRow` param only when the row HAS the
|
|
107
|
+
* key (an own-property check), so an absent key opened the param dialog with the
|
|
108
|
+
* field blank; and the console's `api` handler fills a `{field}` token from the
|
|
109
|
+
* row, so an absent key put an empty path segment into the URL. The key a param
|
|
110
|
+
* seeds from is `field ?? name` — the runtime's precedence, not the param's
|
|
111
|
+
* payload name — and the token grammar is the handler's own. Neither is
|
|
112
|
+
* narrowed by the action's `type`, for the asymmetry stated below: an extra
|
|
113
|
+
* column costs bytes.
|
|
114
|
+
*
|
|
115
|
+
* The written fields are the same class once more, and the worst of them,
|
|
116
|
+
* because what went wrong was a write. The Undo capture of an `undoable` update
|
|
117
|
+
* reads each written field's prior value off the row; on a projected row a
|
|
118
|
+
* written field no column shows was absent, the capture recorded `null`, and
|
|
119
|
+
* Undo then wrote `null` over the stored value it existed to restore. The
|
|
120
|
+
* written keys are `patch`, each param's collected key and `bodyExtra` (see
|
|
121
|
+
* `undoableWrittenKeys`). This one IS narrowed, to `undoable`, and not for
|
|
122
|
+
* cost: the capture is the only reader of a written field's prior value, and it
|
|
123
|
+
* runs only under `undoable`, so without it the row reads none of these keys.
|
|
124
|
+
*
|
|
125
|
+
* A `recordIdField`, param key or written key that is not a bare identifier is
|
|
126
|
+
* dropped here
|
|
127
|
+
* (a `target` token is one by construction of its grammar), and a name the
|
|
128
|
+
* object does not declare is dropped by the caller's `isProjectableField`
|
|
129
|
+
* guard. Both drops are the safe direction: such a name is not a column
|
|
130
|
+
* anywhere, and an unknown key in `$select` is not ignored by every backend.
|
|
131
|
+
* The identifier gate is not decoration — without it the harvester's regex would
|
|
132
|
+
* read a PREFIX of a malformed name (`record.not a field` → `not`) and
|
|
133
|
+
* contribute a plausible wrong field instead of nothing.
|
|
134
|
+
*
|
|
135
|
+
* ⛔ Harvesting is not a read grant. Every name here is a CANDIDATE: each
|
|
136
|
+
* consumer still passes it through its own field-level read filter before it
|
|
137
|
+
* reaches `$select`, so a field the principal may not read is not requested
|
|
138
|
+
* because an action names it.
|
|
106
139
|
*
|
|
107
140
|
* `objectActions` is deliberately the object's WHOLE action set rather than
|
|
108
141
|
* only the ones this view promotes. Narrowing it would mean re-running the
|
|
@@ -32,7 +32,10 @@
|
|
|
32
32
|
* predicate.)
|
|
33
33
|
*
|
|
34
34
|
* Since objectstack#8018 the same reader also carries the non-predicate row keys
|
|
35
|
-
* a view's actions read
|
|
35
|
+
* a view's actions read: `recordIdField`, since objectui#10277 the field a
|
|
36
|
+
* `defaultFromRow` param seeds from and the `{field}` tokens of an action's
|
|
37
|
+
* `target`, and since objectui#10404 the fields an `undoable` action writes,
|
|
38
|
+
* whose prior values the Undo capture reads off the row. The name kept the
|
|
36
39
|
* "predicate" spelling because the mechanism is identical (harvest a name, gate
|
|
37
40
|
* it against the declared fields, add it to `$select`); see
|
|
38
41
|
* {@link listViewPredicates} for what is in the set and why.
|
|
@@ -88,6 +91,73 @@ const RECORD_REF = /\b(?:record|data)\.([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
|
88
91
|
* declaration contributes nothing instead of contributing a plausible wrong name.
|
|
89
92
|
*/
|
|
90
93
|
const BARE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
94
|
+
/**
|
|
95
|
+
* A `{field}` token in an action's `target` — the grammar the console's `api`
|
|
96
|
+
* handler (`useConsoleActionRuntime` in `@object-ui/app-shell`) substitutes from
|
|
97
|
+
* the row record, spelled exactly as that handler spells it, so the harvest
|
|
98
|
+
* reads the same tokens it fills. The capture is a bare identifier by
|
|
99
|
+
* construction: a dotted path (`{owner.name}`), an expression (`{a + b}`) and
|
|
100
|
+
* the runner's own `${param.X}` / `${ctx.X}` scopes all fall outside it and
|
|
101
|
+
* match nothing here — this grammar does not read them off the row, so they owe
|
|
102
|
+
* the projection nothing.
|
|
103
|
+
*/
|
|
104
|
+
const TARGET_ROW_TOKEN = /\{([a-z_][a-z0-9_]*)\}/gi;
|
|
105
|
+
/**
|
|
106
|
+
* The row key a `defaultFromRow` param seeds its value from — `field` wins,
|
|
107
|
+
* because row data is keyed by object field; `name` is the fallback. The same
|
|
108
|
+
* precedence `resolveActionParam` (`@object-ui/app-shell`) reads, which is the
|
|
109
|
+
* point: the projection must carry the key the runtime looks up, not the
|
|
110
|
+
* param's payload name. `remove_team_member` seeds `teamId` from `team_id`.
|
|
111
|
+
*/
|
|
112
|
+
function defaultFromRowKey(param) {
|
|
113
|
+
if (!param || typeof param !== 'object')
|
|
114
|
+
return undefined;
|
|
115
|
+
const p = param;
|
|
116
|
+
if (!p.defaultFromRow)
|
|
117
|
+
return undefined;
|
|
118
|
+
return p.field ?? p.name;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The keys an `undoable` action WRITES, which are the keys its Undo capture
|
|
122
|
+
* reads off the row (objectui#10404). Returned unfiltered; the caller applies
|
|
123
|
+
* the identifier gate.
|
|
124
|
+
*
|
|
125
|
+
* Each source is the one a writer actually merges into the written set:
|
|
126
|
+
*
|
|
127
|
+
* - `patch`: the static field values of `operation: 'update'`, merged under the
|
|
128
|
+
* collected params (`ActionRunner`'s `executeUpdateOperation`);
|
|
129
|
+
* - each param's COLLECTED key, `name ?? field`: the key the param dialog
|
|
130
|
+
* stores its answer under (`paramName` in `app-shell`'s
|
|
131
|
+
* `resolveActionParams`). This is the opposite precedence to
|
|
132
|
+
* {@link defaultFromRowKey} on purpose: that one asks which row key a value
|
|
133
|
+
* is READ from, this one asks which key a value is WRITTEN under, and the
|
|
134
|
+
* capture looks the written key up on the row;
|
|
135
|
+
* - `bodyExtra`: the static body the console's generic `api` handler merges
|
|
136
|
+
* over the params before it writes (`useConsoleActionRuntime`).
|
|
137
|
+
*
|
|
138
|
+
* `recordId` is left out: both writers read it as the record's address and
|
|
139
|
+
* strip it from the written set, so no capture ever looks it up.
|
|
140
|
+
*
|
|
141
|
+
* A key the harvest cannot make the row carry (undeclared, or a field the
|
|
142
|
+
* principal may not read) is not rescued here. The capture refuses to invent a
|
|
143
|
+
* prior value for it instead, and offers no Undo.
|
|
144
|
+
*/
|
|
145
|
+
function undoableWrittenKeys(def) {
|
|
146
|
+
const keys = [];
|
|
147
|
+
for (const bag of [def.patch, def.bodyExtra]) {
|
|
148
|
+
if (bag && typeof bag === 'object' && !Array.isArray(bag))
|
|
149
|
+
keys.push(...Object.keys(bag));
|
|
150
|
+
}
|
|
151
|
+
if (Array.isArray(def.params)) {
|
|
152
|
+
for (const param of def.params) {
|
|
153
|
+
if (!param || typeof param !== 'object')
|
|
154
|
+
continue;
|
|
155
|
+
const p = param;
|
|
156
|
+
keys.push(p.name ?? p.field);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return keys.filter((key) => key !== 'recordId');
|
|
160
|
+
}
|
|
91
161
|
/** Pull the CEL source out of any of the shapes a predicate is authored in. */
|
|
92
162
|
function predicateSource(pred) {
|
|
93
163
|
if (typeof pred === 'string')
|
|
@@ -139,24 +209,54 @@ export function collectPredicateFieldRefs(predicates) {
|
|
|
139
209
|
* kebab (custom defs AND the built-in Edit/Delete overrides), the selection bar,
|
|
140
210
|
* and the object's declared actions.
|
|
141
211
|
*
|
|
142
|
-
* Most entries are predicates.
|
|
212
|
+
* Most entries are predicates. Five are not, and all are spelled as a synthetic
|
|
143
213
|
* `record.<name>` so the one harvester handles them: conditional formatting's
|
|
144
|
-
* native `{ field, operator, value }` shape,
|
|
145
|
-
* (objectstack#8018)
|
|
146
|
-
*
|
|
214
|
+
* native `{ field, operator, value }` shape, an action's `recordIdField`
|
|
215
|
+
* (objectstack#8018), the field each `defaultFromRow` param seeds from, each
|
|
216
|
+
* `{field}` token of the action's `target` (both objectui#10277), and the fields
|
|
217
|
+
* an `undoable` action writes (objectui#10404). The
|
|
218
|
+
* `recordIdField` case is the same *class* of bug the predicate harvest exists
|
|
219
|
+
* to close, one surface over — the action runtime reads
|
|
147
220
|
* `rowRecord[action.recordIdField]` to seed `recordIdParam`, so a key outside the
|
|
148
221
|
* listView columns arrived absent and the injection was silently skipped, which
|
|
149
222
|
* turns a record-scoped mutation into one that names no record while still
|
|
150
223
|
* reporting success. The default (`id`) is already projected unconditionally, so
|
|
151
224
|
* only an explicit declaration adds anything here.
|
|
152
225
|
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* and
|
|
226
|
+
* The last two are the same class again, and they were silent in the same way.
|
|
227
|
+
* `resolveActionParam` seeds a `defaultFromRow` param only when the row HAS the
|
|
228
|
+
* key (an own-property check), so an absent key opened the param dialog with the
|
|
229
|
+
* field blank; and the console's `api` handler fills a `{field}` token from the
|
|
230
|
+
* row, so an absent key put an empty path segment into the URL. The key a param
|
|
231
|
+
* seeds from is `field ?? name` — the runtime's precedence, not the param's
|
|
232
|
+
* payload name — and the token grammar is the handler's own. Neither is
|
|
233
|
+
* narrowed by the action's `type`, for the asymmetry stated below: an extra
|
|
234
|
+
* column costs bytes.
|
|
235
|
+
*
|
|
236
|
+
* The written fields are the same class once more, and the worst of them,
|
|
237
|
+
* because what went wrong was a write. The Undo capture of an `undoable` update
|
|
238
|
+
* reads each written field's prior value off the row; on a projected row a
|
|
239
|
+
* written field no column shows was absent, the capture recorded `null`, and
|
|
240
|
+
* Undo then wrote `null` over the stored value it existed to restore. The
|
|
241
|
+
* written keys are `patch`, each param's collected key and `bodyExtra` (see
|
|
242
|
+
* `undoableWrittenKeys`). This one IS narrowed, to `undoable`, and not for
|
|
243
|
+
* cost: the capture is the only reader of a written field's prior value, and it
|
|
244
|
+
* runs only under `undoable`, so without it the row reads none of these keys.
|
|
245
|
+
*
|
|
246
|
+
* A `recordIdField`, param key or written key that is not a bare identifier is
|
|
247
|
+
* dropped here
|
|
248
|
+
* (a `target` token is one by construction of its grammar), and a name the
|
|
249
|
+
* object does not declare is dropped by the caller's `isProjectableField`
|
|
250
|
+
* guard. Both drops are the safe direction: such a name is not a column
|
|
251
|
+
* anywhere, and an unknown key in `$select` is not ignored by every backend.
|
|
252
|
+
* The identifier gate is not decoration — without it the harvester's regex would
|
|
253
|
+
* read a PREFIX of a malformed name (`record.not a field` → `not`) and
|
|
254
|
+
* contribute a plausible wrong field instead of nothing.
|
|
255
|
+
*
|
|
256
|
+
* ⛔ Harvesting is not a read grant. Every name here is a CANDIDATE: each
|
|
257
|
+
* consumer still passes it through its own field-level read filter before it
|
|
258
|
+
* reaches `$select`, so a field the principal may not read is not requested
|
|
259
|
+
* because an action names it.
|
|
160
260
|
*
|
|
161
261
|
* `objectActions` is deliberately the object's WHOLE action set rather than
|
|
162
262
|
* only the ones this view promotes. Narrowing it would mean re-running the
|
|
@@ -191,6 +291,31 @@ export function listViewPredicates(view) {
|
|
|
191
291
|
if (typeof d.recordIdField === 'string' && BARE_IDENTIFIER.test(d.recordIdField)) {
|
|
192
292
|
preds.push(`record.${d.recordIdField}`);
|
|
193
293
|
}
|
|
294
|
+
// A `defaultFromRow` param's seed field and the `{field}` tokens of the
|
|
295
|
+
// action's `target` (objectui#10277) — two more row keys the runtime
|
|
296
|
+
// READS, by the same rule and the same identifier gate.
|
|
297
|
+
if (Array.isArray(d.params)) {
|
|
298
|
+
for (const param of d.params) {
|
|
299
|
+
const key = defaultFromRowKey(param);
|
|
300
|
+
if (typeof key === 'string' && BARE_IDENTIFIER.test(key))
|
|
301
|
+
preds.push(`record.${key}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (typeof d.target === 'string') {
|
|
305
|
+
TARGET_ROW_TOKEN.lastIndex = 0;
|
|
306
|
+
for (let m = TARGET_ROW_TOKEN.exec(d.target); m; m = TARGET_ROW_TOKEN.exec(d.target)) {
|
|
307
|
+
preds.push(`record.${m[1]}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// The fields an `undoable` action writes (objectui#10404): its Undo
|
|
311
|
+
// capture reads each one's prior value off the row. Same rule, same
|
|
312
|
+
// identifier gate.
|
|
313
|
+
if (d.undoable) {
|
|
314
|
+
for (const key of undoableWrittenKeys(d)) {
|
|
315
|
+
if (typeof key === 'string' && BARE_IDENTIFIER.test(key))
|
|
316
|
+
preds.push(`record.${key}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
194
319
|
}
|
|
195
320
|
}
|
|
196
321
|
for (const override of Object.values(view.userActions ?? {})) {
|
|
@@ -0,0 +1,75 @@
|
|
|
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 the colour a record's value stands for, off the field's own metadata.
|
|
10
|
+
*
|
|
11
|
+
* ## Why this is shared (objectui#7243)
|
|
12
|
+
*
|
|
13
|
+
* `colorField` is one authored key — `gantt.colorField`, `calendar.colorField`,
|
|
14
|
+
* `timeline.colorField` — and it used to mean three different things:
|
|
15
|
+
*
|
|
16
|
+
* - `plugin-timeline` resolved the field's option `color` (correct);
|
|
17
|
+
* - `plugin-calendar` passed the raw value on to a deterministic hash, so the
|
|
18
|
+
* colour the author declared on the option was never consulted;
|
|
19
|
+
* - `plugin-gantt` passed the raw value straight into `backgroundColor`, so
|
|
20
|
+
* `colorField: 'status'` emitted `backgroundColor: "open"` — not a colour.
|
|
21
|
+
* The browser dropped the declaration and every bar rendered identically,
|
|
22
|
+
* which made DECLARING the documented key strictly worse than omitting it.
|
|
23
|
+
*
|
|
24
|
+
* The timeline's resolver is lifted here verbatim so all three answer the same
|
|
25
|
+
* question the same way. What lives here is the part that is genuinely common —
|
|
26
|
+
* the two rungs that depend only on the field metadata and the stored value:
|
|
27
|
+
*
|
|
28
|
+
* 1. the field's own option `color` for that value;
|
|
29
|
+
* 2. the value itself when it already IS a CSS colour literal.
|
|
30
|
+
*
|
|
31
|
+
* The LAST rung deliberately stays with each caller, because each one has a
|
|
32
|
+
* different right answer for "a value that is neither": the gantt derives a
|
|
33
|
+
* semantic token hex (a bar must be painted), the calendar hashes onto its
|
|
34
|
+
* theme-aware 8-stop class palette (a soft tint, not a solid fill), and the
|
|
35
|
+
* timeline draws its default marker variant. Pulling those together would be a
|
|
36
|
+
* repaint of existing views, not a bug fix.
|
|
37
|
+
*
|
|
38
|
+
* ## Keyed by option `value` only
|
|
39
|
+
*
|
|
40
|
+
* Deliberately NOT `buildOptionColorMap` (`./chart-series.ts`), which keys by
|
|
41
|
+
* value AND display label: that one reads dataset ROWS, where the server may
|
|
42
|
+
* have resolved a select dimension to its label. These callers read RECORDS,
|
|
43
|
+
* where `record[colorField]` is the stored value. Keying by label as well would
|
|
44
|
+
* colour a record whose stored value happens to collide with another option's
|
|
45
|
+
* label — a tolerance nothing here needs, and one the timeline never had.
|
|
46
|
+
*/
|
|
47
|
+
/** One entry of a select-like field's `options`, as far as colour is concerned. */
|
|
48
|
+
export interface FieldColorOption {
|
|
49
|
+
value?: unknown;
|
|
50
|
+
color?: unknown;
|
|
51
|
+
}
|
|
52
|
+
/** The slice of a field definition this resolver reads. */
|
|
53
|
+
export interface FieldColorDefinition {
|
|
54
|
+
options?: unknown;
|
|
55
|
+
}
|
|
56
|
+
/** Does this string already name a colour the DOM can paint? */
|
|
57
|
+
export declare function isColorLiteral(value: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Build a `value -> option colour` lookup from a field definition's `options`.
|
|
60
|
+
* Returns an empty map for a field with no options, so callers need no guard.
|
|
61
|
+
*/
|
|
62
|
+
export declare function buildFieldColorMap(fieldDef: FieldColorDefinition | undefined | null): Record<string, string>;
|
|
63
|
+
/**
|
|
64
|
+
* Build the per-field colour resolver: rung 1 (option colour) then rung 2 (the
|
|
65
|
+
* value is already a colour literal), else `undefined` so the caller runs its
|
|
66
|
+
* own last rung.
|
|
67
|
+
*
|
|
68
|
+
* A factory rather than a plain function because callers resolve one field
|
|
69
|
+
* across every row of a view — the option map is built once, not per record.
|
|
70
|
+
*
|
|
71
|
+
* @param fieldDef `objectSchema.fields[colorField]`, or `undefined` when the
|
|
72
|
+
* view has no schema in hand (an inline-data or `api`-provider view). With no
|
|
73
|
+
* options, rung 1 is a no-op and rung 2 still answers for literal colours.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createFieldColorResolver(fieldDef: FieldColorDefinition | undefined | null): (value: unknown) => string | undefined;
|