@object-ui/core 17.5.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 +7404 -0
- package/README.md +127 -26
- package/dist/actions/ActionEngine.d.ts +5 -3
- package/dist/actions/ActionEngine.js +14 -10
- package/dist/actions/ActionRunner.d.ts +363 -30
- package/dist/actions/ActionRunner.js +544 -58
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/actionKeys.d.ts +75 -13
- package/dist/actions/actionKeys.js +131 -16
- package/dist/actions/actionResponse.js +3 -3
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/recordDelete.d.ts +118 -0
- package/dist/actions/recordDelete.js +112 -0
- package/dist/actions/recordIdParam.d.ts +77 -0
- package/dist/actions/recordIdParam.js +37 -0
- package/dist/actions/serverActionHandler.d.ts +1 -1
- package/dist/actions/serverActionHandler.js +1 -1
- 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 +128 -1
- package/dist/evaluator/ExpressionEvaluator.js +226 -13
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/SafeExpressionParser.js +0 -4
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +118 -17
- package/dist/evaluator/fieldRules.d.ts +107 -4
- package/dist/evaluator/fieldRules.js +231 -35
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -0
- package/dist/evaluator/listConditional.d.ts +125 -9
- package/dist/evaluator/listConditional.js +167 -26
- package/dist/evaluator/optionRules.d.ts +126 -0
- package/dist/evaluator/optionRules.js +141 -4
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +56 -0
- package/dist/evaluator/rowPredicateCanon.js +158 -0
- 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 +18 -0
- package/dist/index.js +97 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +397 -48
- 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/theme/index.d.ts +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/types/index.d.ts +92 -13
- 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 +188 -0
- package/dist/utils/chart-presentation.js +345 -0
- package/dist/utils/chart-series.d.ts +271 -31
- package/dist/utils/chart-series.js +478 -55
- package/dist/utils/column-identity.d.ts +43 -0
- package/dist/utils/column-identity.js +47 -1
- 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 +407 -52
- 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/debug.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 +171 -11
- package/dist/utils/expand-fields.js +171 -11
- 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 -34
- package/dist/utils/freeze-schema.d.ts +38 -1
- package/dist/utils/freeze-schema.js +10 -0
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/humanize-label.d.ts +60 -0
- package/dist/utils/humanize-label.js +62 -0
- package/dist/utils/managedBy.d.ts +30 -5
- 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 +131 -1
- package/dist/utils/normalize-list-view.js +248 -6
- package/dist/utils/number-display.d.ts +70 -17
- 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 +62 -4
- package/dist/utils/predicate-fields.js +169 -4
- 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/retired-field-types.d.ts +90 -0
- package/dist/utils/retired-field-types.js +124 -0
- package/dist/utils/sort-query.d.ts +128 -26
- 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/unmaterialized-fields.d.ts +52 -0
- package/dist/utils/unmaterialized-fields.js +57 -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 +9 -4
- package/dist/validation/index.js +9 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +305 -82
- package/dist/validation/server-owned-value.d.ts +62 -0
- package/dist/validation/server-owned-value.js +99 -0
- package/dist/validation/validators/object-validation-engine.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -5
- package/package.json +8 -9
- package/dist/__benchmarks__/core.bench.d.ts +0 -8
- package/dist/__benchmarks__/core.bench.js +0 -53
- 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
|
@@ -22,8 +22,12 @@
|
|
|
22
22
|
* (`nameField`) to canonical and de-prioritizes the legacy render-only
|
|
23
23
|
* `titleFormat` template:
|
|
24
24
|
*
|
|
25
|
+
* 0. `options.titleField` — the caller's own DECLARED view-level pointer
|
|
26
|
+
* (gallery / calendar / gantt / map / kanban / timeline config).
|
|
25
27
|
* 1. `objectDef.nameField` — the canonical record-title pointer (ADR-0079);
|
|
26
|
-
* a field the server can return and query, if present & non-empty.
|
|
28
|
+
* a field the server can return and query, if present & non-empty. With
|
|
29
|
+
* the undeclared `objectDef.titleField` read gone (objectui#6531) this is
|
|
30
|
+
* the top of the OBJECT-level ladder, exactly as ADR-0079 states.
|
|
27
31
|
* 2. `objectDef.displayNameField` (alias `NAME_FIELD_KEY`) — deprecated alias
|
|
28
32
|
* of `nameField`, kept for back-compat.
|
|
29
33
|
* 3. `objectDef.titleFormat` — LEGACY render-only template, rendered via
|
|
@@ -38,6 +42,7 @@
|
|
|
38
42
|
* app-shell, the field widgets, and every view plugin — exactly like
|
|
39
43
|
* `percentDisplayValue` / `formatMeasure`.
|
|
40
44
|
*/
|
|
45
|
+
import { isSystemManagedField } from '@object-ui/types';
|
|
41
46
|
// Sentinel marking an empty-placeholder position inside a titleFormat render,
|
|
42
47
|
// so adjacent separators can be stripped in a second pass.
|
|
43
48
|
//
|
|
@@ -138,10 +143,20 @@ function displayNameOfEmbeddedObject(o) {
|
|
|
138
143
|
* supported. Dotted paths (`{account.name}`) walk `$expanded` lookup objects; a
|
|
139
144
|
* placeholder that resolves to a whole object is reduced to its display name.
|
|
140
145
|
*
|
|
141
|
-
* Empty placeholders (missing / null / empty fields) are
|
|
142
|
-
* any orphan separator they leave behind, so
|
|
143
|
-
* evaluated against `{ company: "Acme" }` yields
|
|
144
|
-
* Returns an empty string when no placeholder
|
|
146
|
+
* Empty placeholders (missing / null / empty / whitespace-only fields) are
|
|
147
|
+
* stripped along with any orphan separator they leave behind, so
|
|
148
|
+
* `"{full_name} - {company}"` evaluated against `{ company: "Acme" }` yields
|
|
149
|
+
* `"Acme"`, not `" - Acme"`. Returns an empty string when no placeholder
|
|
150
|
+
* resolved.
|
|
151
|
+
*
|
|
152
|
+
* What counts as "empty" is not decided here (objectui#10446). Each
|
|
153
|
+
* placeholder's value goes through {@link displayValueOf}, the body of
|
|
154
|
+
* {@link recordDisplayValueAt}: trim, then empty. This function used to test
|
|
155
|
+
* the raw value against `null` / `undefined` / `''` on its own, so a
|
|
156
|
+
* whitespace-only value counted as RESOLVED. The later whitespace collapse
|
|
157
|
+
* then left its separator dangling: `{contract_no} - {name}` with a blank
|
|
158
|
+
* `name` rendered `HT-2026-003 -`, while every value-keyed rung of
|
|
159
|
+
* {@link getRecordDisplayName} judged the same value empty.
|
|
145
160
|
*/
|
|
146
161
|
export function formatTitleTemplate(titleFormat, record) {
|
|
147
162
|
let template = typeof titleFormat === 'string'
|
|
@@ -163,14 +178,11 @@ export function formatTitleTemplate(titleFormat, record) {
|
|
|
163
178
|
break;
|
|
164
179
|
value = value[p];
|
|
165
180
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
if (value === null || value === undefined || value === '') {
|
|
181
|
+
const shown = displayValueOf(value);
|
|
182
|
+
if (shown === undefined)
|
|
170
183
|
return EMPTY_TOKEN;
|
|
171
|
-
}
|
|
172
184
|
anyResolved = true;
|
|
173
|
-
return
|
|
185
|
+
return shown;
|
|
174
186
|
});
|
|
175
187
|
if (!anyResolved)
|
|
176
188
|
return '';
|
|
@@ -283,13 +295,132 @@ export function deriveTitleField(objectDef) {
|
|
|
283
295
|
deriveTitleFieldCache.set(objectDef, derived);
|
|
284
296
|
return derived;
|
|
285
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* The declared name-field pointer, read exactly as {@link getRecordDisplayName}
|
|
300
|
+
* reads it: the canonical `nameField` (ADR-0079 Phase 2), then its deprecated
|
|
301
|
+
* `displayNameField` / `NAME_FIELD_KEY` aliases.
|
|
302
|
+
*
|
|
303
|
+
* One spelling, every reader. Inside this module that is the value-space
|
|
304
|
+
* resolver and the name-space {@link resolveNameField}. Re-typing the `??`
|
|
305
|
+
* chain at another reader is how two readers drift into disagreeing about
|
|
306
|
+
* which field titles an object. No new alias is read here; this is the
|
|
307
|
+
* existing ladder, extracted.
|
|
308
|
+
*
|
|
309
|
+
* Exported (objectui#9436) for callers that must rank the DECLARED pointer on
|
|
310
|
+
* its own, apart from the type-aware derivation that {@link resolveNameField}
|
|
311
|
+
* folds in after it. The record-page H1 needs exactly that: ADR-0079 puts the
|
|
312
|
+
* declared pointer (steps 1+2) above the legacy `titleFormat` (step 3), and the
|
|
313
|
+
* derivation (step 4) below it, so a caller that interleaves its own
|
|
314
|
+
* `titleFormat` rendering has to read the two halves separately. Read the value
|
|
315
|
+
* as `recordDisplayValueAt(record, declaredNameField(objectDef))`, which is how
|
|
316
|
+
* {@link getRecordDisplayName} reads steps 1+2.
|
|
317
|
+
*
|
|
318
|
+
* Returns the pointer exactly as declared, or `undefined` when none is. It
|
|
319
|
+
* never derives: an object that declares nothing answers `undefined` even when
|
|
320
|
+
* its `fields` would derive a title field.
|
|
321
|
+
*/
|
|
322
|
+
export function declaredNameField(objectDef) {
|
|
323
|
+
return objectDef?.nameField ?? objectDef?.displayNameField ?? objectDef?.NAME_FIELD_KEY;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* WHICH FIELD titles this object — the name-space twin of
|
|
327
|
+
* {@link getRecordDisplayName}, which answers what that field SAYS on one
|
|
328
|
+
* record. Same ladder, minus the rungs that only exist per record:
|
|
329
|
+
*
|
|
330
|
+
* 1+2. the declared pointer ({@link declaredNameField});
|
|
331
|
+
* 4. else the type-aware derivation ({@link deriveTitleField}).
|
|
332
|
+
*
|
|
333
|
+
* The two omissions are deliberate, not oversights. `options.titleField`
|
|
334
|
+
* (step 0) belongs to one view, not to the object. `titleFormat` (step 3) is a
|
|
335
|
+
* render-only template, not a field name — there is no column, no sort key and
|
|
336
|
+
* no `$select` entry to be had from it, which is the same reason ADR-0079
|
|
337
|
+
* demoted it. A caller that wants a title for a `titleFormat`-only object wants
|
|
338
|
+
* {@link getRecordDisplayName}, per record.
|
|
339
|
+
*
|
|
340
|
+
* Deliberately does NOT require the answer to exist in `objectDef.fields`: in
|
|
341
|
+
* value-space the declared pointer is read off the RECORD, so demanding a field
|
|
342
|
+
* def here would make the two resolvers disagree. A caller that needs a
|
|
343
|
+
* RENDERABLE column checks presence itself — see {@link leadWithNameField}.
|
|
344
|
+
*
|
|
345
|
+
* Returns `undefined` when nothing declares or derives a name field.
|
|
346
|
+
*/
|
|
347
|
+
export function resolveNameField(objectDef) {
|
|
348
|
+
const declared = declaredNameField(objectDef);
|
|
349
|
+
if (typeof declared === 'string' && declared.length > 0)
|
|
350
|
+
return declared;
|
|
351
|
+
return deriveTitleField(objectDef);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Put the object's name field at the FRONT of a **synthesized** default column
|
|
355
|
+
* list, so a list no author ever configured still lets you tell its rows apart.
|
|
356
|
+
*
|
|
357
|
+
* Why this exists (objectui#7245): the three faces that synthesize default list
|
|
358
|
+
* columns each took a declared `highlightFields` verbatim. `highlightFields` is
|
|
359
|
+
* the ADR-0085 "most important fields" role, and its canonical consumer — the
|
|
360
|
+
* detail-page highlight strip — deliberately *excludes* the title field,
|
|
361
|
+
* because the strip sits directly under the page H1 that already shows it. So
|
|
362
|
+
* well-authored metadata routinely omits the name from `highlightFields`: the
|
|
363
|
+
* showcase `showcase_account` declares `["status", "industry", "annual_revenue"]`
|
|
364
|
+
* against `nameField: "name"`, and its default grid rendered 14 rows with no
|
|
365
|
+
* name column and nothing to distinguish them.
|
|
366
|
+
*
|
|
367
|
+
* A list has no H1 to lean on, so the same role needs the opposite treatment
|
|
368
|
+
* here. This is not a new convention: `deriveLookupColumns` in `@object-ui/fields`
|
|
369
|
+
* already leads its picker columns with the display field and filters it out of
|
|
370
|
+
* the declared list — the record-picker equivalent of this problem, solved the
|
|
371
|
+
* same way. This makes the list faces agree with it.
|
|
372
|
+
*
|
|
373
|
+
* MOVES the name field rather than only prepending a missing one: an author who
|
|
374
|
+
* lists it third still gets it first, which is what "the column that identifies
|
|
375
|
+
* the row" means. Order among the remaining columns is untouched.
|
|
376
|
+
*
|
|
377
|
+
* NOT for author-declared column lists. A view that declares `columns` said what
|
|
378
|
+
* it wants; overriding that would be exactly the renderer-side second-guessing
|
|
379
|
+
* AGENTS.md Commandment #0.1 rules out. Every caller passes a list it
|
|
380
|
+
* synthesized itself.
|
|
381
|
+
*
|
|
382
|
+
* Three cases return `columns` untouched, each guarding a rule that already
|
|
383
|
+
* governs these column lists:
|
|
384
|
+
*
|
|
385
|
+
* - **No field def** (nothing resolved, or the pointer names a field the object
|
|
386
|
+
* does not carry) — a synthesized list must never fabricate a column the
|
|
387
|
+
* object cannot render.
|
|
388
|
+
* - **`hidden: true`** — the author said don't show this field. That outranks
|
|
389
|
+
* the fact that it also titles the record; every one of these faces already
|
|
390
|
+
* drops hidden fields from its own walk.
|
|
391
|
+
* - **DERIVED onto a system-managed field.** {@link deriveTitleField} filters
|
|
392
|
+
* by TYPE only, so on an object with no name-ish business field it can land
|
|
393
|
+
* on a framework-injected column — and leading a default list with a raw id
|
|
394
|
+
* is precisely the regression objectui#2702 / #2777 fixed. A *declared*
|
|
395
|
+
* `nameField` is exempt: `sys_migration` really does point at `id`, and an
|
|
396
|
+
* author's explicit designation is not a heuristic misfire.
|
|
397
|
+
*/
|
|
398
|
+
export function leadWithNameField(objectDef, columns) {
|
|
399
|
+
const declared = declaredNameField(objectDef);
|
|
400
|
+
const isDeclared = typeof declared === 'string' && declared.length > 0;
|
|
401
|
+
const lead = isDeclared ? declared : deriveTitleField(objectDef);
|
|
402
|
+
const def = lead ? objectDef?.fields?.[lead] : undefined;
|
|
403
|
+
if (!lead || !def)
|
|
404
|
+
return columns;
|
|
405
|
+
if (def.hidden)
|
|
406
|
+
return columns;
|
|
407
|
+
if (!isDeclared && isSystemManagedField(lead, def))
|
|
408
|
+
return columns;
|
|
409
|
+
return [lead, ...columns.filter((c) => c !== lead)];
|
|
410
|
+
}
|
|
286
411
|
/**
|
|
287
412
|
* Standard name-ish keys probed directly on a *record* as a last resort before
|
|
288
413
|
* the `Record #<id>` floor — used when `objectDef.fields` is absent so
|
|
289
|
-
* {@link deriveTitleField} can't run (loosely-typed metadata, lightweight
|
|
290
|
-
*
|
|
414
|
+
* {@link deriveTitleField} can't run (loosely-typed metadata, the lightweight
|
|
415
|
+
* object stub `useRecordSearch` synthesizes for a hit whose object is not in
|
|
416
|
+
* the app's metadata — `{ name: objectName }` — etc.). Mirrors the historical
|
|
291
417
|
* hard-coded fallback chain the divergent resolvers used, so removing them is
|
|
292
418
|
* non-regressive. Ordered by priority.
|
|
419
|
+
*
|
|
420
|
+
* objectui#6531 rewrote the parenthetical above: it used to say those search
|
|
421
|
+
* candidates "carry only `name`/`titleField`", which read as evidence that some
|
|
422
|
+
* producer puts `titleField` on an object-shaped payload. The census found
|
|
423
|
+
* none, and the spec's object schema rejects the key outright.
|
|
293
424
|
*/
|
|
294
425
|
const NAME_ISH_RECORD_KEYS = [
|
|
295
426
|
'name',
|
|
@@ -301,11 +432,57 @@ const NAME_ISH_RECORD_KEYS = [
|
|
|
301
432
|
'subject',
|
|
302
433
|
'label',
|
|
303
434
|
];
|
|
304
|
-
/**
|
|
305
|
-
|
|
435
|
+
/**
|
|
436
|
+
* Read a non-empty string-ish display value off a record at `field`, else
|
|
437
|
+
* `undefined`.
|
|
438
|
+
*
|
|
439
|
+
* THE definition of "this record has a value here" — the question every
|
|
440
|
+
* value-keyed rung of {@link getRecordDisplayName} asks, and the reason a
|
|
441
|
+
* whitespace-only field is not a title.
|
|
442
|
+
*
|
|
443
|
+
* ## Why this is exported (objectui#8350)
|
|
444
|
+
*
|
|
445
|
+
* A second reader existed. `record:details`' dedupe ladder in
|
|
446
|
+
* `@object-ui/plugin-detail` asks the same question about the same record for
|
|
447
|
+
* the same field, to decide which body-grid row duplicates the page H1 — and it
|
|
448
|
+
* asked it with a hand-written raw `undefined` / `null` / `''` test, which a
|
|
449
|
+
* whitespace-only value passes and this one does not. So for a record whose
|
|
450
|
+
* title field held only spaces the two halves disagreed about whether that
|
|
451
|
+
* field had a value at all: the H1 walked on to the next rung, while the grid
|
|
452
|
+
* hid the row anyway — a field vanished to deduplicate against a heading that
|
|
453
|
+
* never showed it. The ladder now calls THIS. One authority, not two
|
|
454
|
+
* implementations that agree today.
|
|
455
|
+
*
|
|
456
|
+
* ⛔ Do not re-spell this test at a call site, and do not "just add a trim"
|
|
457
|
+
* there: three things beyond the trim are part of the definition, and a second
|
|
458
|
+
* implementation drifts on each of them independently —
|
|
459
|
+
* - an expanded/embedded reference object resolves through its Salesforce-
|
|
460
|
+
* style display chain and is EMPTY when that yields nothing (a bare
|
|
461
|
+
* `{ id }` lookup payload is not a title);
|
|
462
|
+
* - non-strings are stringified, so `0` and `false` ARE values, not blanks;
|
|
463
|
+
* - `null` and `undefined` are empty, and only those two.
|
|
464
|
+
*
|
|
465
|
+
* The rule itself is {@link displayValueOf}, which {@link formatTitleTemplate}
|
|
466
|
+
* applies to each placeholder value (objectui#10446). So a `titleFormat`
|
|
467
|
+
* placeholder is empty exactly when this function would call its field empty.
|
|
468
|
+
*/
|
|
469
|
+
export function recordDisplayValueAt(record, field) {
|
|
306
470
|
if (!field || !record || typeof record !== 'object')
|
|
307
471
|
return undefined;
|
|
308
|
-
|
|
472
|
+
return displayValueOf(record[field]);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* The value half of {@link recordDisplayValueAt}: one already-read value in,
|
|
476
|
+
* its display string or `undefined` (empty) out. Trim, then empty.
|
|
477
|
+
*
|
|
478
|
+
* Module-private on purpose. It exists so the two readers in this file that
|
|
479
|
+
* judge a single value, `recordDisplayValueAt` and each placeholder of
|
|
480
|
+
* {@link formatTitleTemplate}, run ONE implementation of that judgement rather
|
|
481
|
+
* than two that agree today (objectui#10446). Read its full definition on
|
|
482
|
+
* {@link recordDisplayValueAt}.
|
|
483
|
+
*/
|
|
484
|
+
function displayValueOf(value) {
|
|
485
|
+
let v = value;
|
|
309
486
|
if (v && typeof v === 'object')
|
|
310
487
|
v = displayNameOfEmbeddedObject(v);
|
|
311
488
|
if (v === null || v === undefined)
|
|
@@ -318,9 +495,10 @@ function valueAt(record, field) {
|
|
|
318
495
|
*
|
|
319
496
|
* Precedence (ADR-0079 Phase 2 makes the object's declared field canonical and
|
|
320
497
|
* de-prioritizes the legacy `titleFormat` template):
|
|
321
|
-
* 0. `options.titleField
|
|
322
|
-
*
|
|
323
|
-
*
|
|
498
|
+
* 0. `options.titleField` (when provided & non-empty on the record) — lets
|
|
499
|
+
* a view keep its author-chosen title field. The object-level
|
|
500
|
+
* `objectDef.titleField` leg was removed in objectui#6531: the spec's
|
|
501
|
+
* object schema rejects that key, so nothing could produce it.
|
|
324
502
|
* 1. `objectDef.nameField` — the NEW canonical record-title pointer
|
|
325
503
|
* (ADR-0079). A field name the server can return and query.
|
|
326
504
|
* 2. `objectDef.displayNameField` / `objectDef.NAME_FIELD_KEY` — the
|
|
@@ -340,9 +518,23 @@ function valueAt(record, field) {
|
|
|
340
518
|
* @param record The record data.
|
|
341
519
|
*/
|
|
342
520
|
export function getRecordDisplayName(objectDef, record, options) {
|
|
343
|
-
// 0. Explicit title field — caller option
|
|
344
|
-
//
|
|
345
|
-
|
|
521
|
+
// 0. Explicit title field — the caller's option, if it actually resolves on
|
|
522
|
+
// the record. This is a DECLARED key: every view config that offers a
|
|
523
|
+
// title pointer (`ui/CalendarConfig`, `ui/GalleryConfig`, `ui/GanttConfig`,
|
|
524
|
+
// `ui/ListMapConfig`, `ui/ObjectKanbanProps`, `ui/TimelineConfig`) declares
|
|
525
|
+
// `titleField`, and its author chose it for THIS view, so it outranks the
|
|
526
|
+
// object's own pointer by design.
|
|
527
|
+
//
|
|
528
|
+
// An object-level `objectDef.titleField` used to be consulted here too, as
|
|
529
|
+
// a second `??` leg. Removed in objectui#6531: `@objectstack/spec`'s object
|
|
530
|
+
// schema is a `strictObject` that REJECTS `titleField` with
|
|
531
|
+
// `unrecognized_keys` — the same code a nonsense key gets — so no
|
|
532
|
+
// spec-compliant producer can ship it, and a census across both repos
|
|
533
|
+
// (metadata, every `getObjectSchema` implementation, the lookup-chip and
|
|
534
|
+
// search-candidate paths) found none that does. Reading it was a
|
|
535
|
+
// consumer-side alias for an undeclared key (AGENTS.md Commandment #0.1),
|
|
536
|
+
// ranked ABOVE the `nameField` ADR-0079 Phase 2 made canonical.
|
|
537
|
+
const explicit = recordDisplayValueAt(record, options?.titleField);
|
|
346
538
|
if (explicit)
|
|
347
539
|
return explicit;
|
|
348
540
|
// 1+2. Declared name field — the canonical `nameField` (ADR-0079 Phase 2),
|
|
@@ -350,7 +542,9 @@ export function getRecordDisplayName(objectDef, record, options) {
|
|
|
350
542
|
// explicitly-declared field now wins over the legacy `titleFormat`
|
|
351
543
|
// template (step 3): an object that sets `nameField` resolves via it even
|
|
352
544
|
// if it also carries a stale `titleFormat`.
|
|
353
|
-
|
|
545
|
+
// The `??` chain itself lives in {@link declaredNameField} so the
|
|
546
|
+
// name-space {@link resolveNameField} reads the identical ladder.
|
|
547
|
+
const declared = recordDisplayValueAt(record, declaredNameField(objectDef));
|
|
354
548
|
if (declared)
|
|
355
549
|
return declared;
|
|
356
550
|
// 3. titleFormat (LEGACY, render-only template). DEPRECATED by ADR-0079: the
|
|
@@ -366,7 +560,7 @@ export function getRecordDisplayName(objectDef, record, options) {
|
|
|
366
560
|
}
|
|
367
561
|
// 4. Type-aware derivation from the object's fields.
|
|
368
562
|
const derivedField = deriveTitleField(objectDef);
|
|
369
|
-
const derived =
|
|
563
|
+
const derived = recordDisplayValueAt(record, derivedField);
|
|
370
564
|
if (derived)
|
|
371
565
|
return derived;
|
|
372
566
|
// 4b. Name-ish keys on the record itself — the safety net for when the
|
|
@@ -377,7 +571,7 @@ export function getRecordDisplayName(objectDef, record, options) {
|
|
|
377
571
|
if (options?.deriveFromRecordKeys !== false && record && typeof record === 'object') {
|
|
378
572
|
// (i) exact standard keys (name, full_name, title, …).
|
|
379
573
|
for (const key of NAME_ISH_RECORD_KEYS) {
|
|
380
|
-
const v =
|
|
574
|
+
const v = recordDisplayValueAt(record, key);
|
|
381
575
|
if (v)
|
|
382
576
|
return v;
|
|
383
577
|
}
|
|
@@ -392,7 +586,7 @@ export function getRecordDisplayName(objectDef, record, options) {
|
|
|
392
586
|
if (k === 'id' || k === '_id' || k.endsWith('_id') || k.startsWith('_'))
|
|
393
587
|
continue;
|
|
394
588
|
if (k.endsWith('_name') || k.endsWith('_title') || k.startsWith('name_')) {
|
|
395
|
-
const v =
|
|
589
|
+
const v = recordDisplayValueAt(record, key);
|
|
396
590
|
if (v)
|
|
397
591
|
return v;
|
|
398
592
|
}
|
|
@@ -1,36 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ObjectUI —
|
|
2
|
+
* ObjectUI — object-schema key canonicalization at ingestion
|
|
3
3
|
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
4
|
*
|
|
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
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Backend object schemas follow the ObjectStack convention and name a
|
|
10
|
-
* relational field's target object `reference`
|
|
11
|
-
* (e.g. `{ type: 'lookup', reference: 'showcase_account' }`), while ObjectUI's
|
|
12
|
-
* types — and most in-repo consumers — historically read `reference_to`
|
|
13
|
-
* (some legacy configs also carry camelCase `referenceTo`). A consumer that
|
|
14
|
-
* reads only one key silently loses the relation under the other convention:
|
|
15
|
-
* the exact bug HeaderHighlight had (#2407 / PR #2587), where a served
|
|
16
|
-
* `reference`-keyed lookup rendered a raw id.
|
|
17
7
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
8
|
+
* ⚠️ The FILE and its exports still say `reference`; the pass no longer does
|
|
9
|
+
* only that. The `reference` / `reference_to` pair was the first tenant, and
|
|
10
|
+
* objectui#7650 added the retired-dialect arm below, which folds ANY undeclared
|
|
11
|
+
* snake twin of a declared `FieldSchema` key. Renaming the exports would be a
|
|
12
|
+
* published-export change across ~20 in-repo citations and belongs to its own
|
|
13
|
+
* card; the two arms are marked so the name cannot mislead a reader who got
|
|
14
|
+
* here from a grep.
|
|
24
15
|
*/
|
|
25
|
-
|
|
16
|
+
/** Reset the warn-once memos. Exported for tests. */
|
|
17
|
+
export declare function resetReferenceKeyWarnings(): void;
|
|
18
|
+
export declare function normalizeFieldReferenceKeys<T>(fieldDef: T, fieldName?: string, objectName?: string): T;
|
|
26
19
|
/**
|
|
27
20
|
* Apply {@link normalizeFieldReferenceKeys} to every field of an object
|
|
28
|
-
* schema
|
|
29
|
-
* a `name → def` map or
|
|
30
|
-
*
|
|
21
|
+
* schema — BOTH arms: the `reference` fold and the retired dialect. Accepts
|
|
22
|
+
* both field-container shapes the metadata API serves — a `name → def` map or
|
|
23
|
+
* an array of defs — and tolerates anything else by returning the input
|
|
24
|
+
* untouched. Mutates in place; idempotent.
|
|
31
25
|
*
|
|
32
26
|
* This is meant to run at the choke point where object schemas enter the
|
|
33
27
|
* client (`ObjectStackAdapter.getObjectSchema`, the app-shell metadata
|
|
34
|
-
* provider) so
|
|
28
|
+
* provider), so no consumer needs a dual-key fallback of its own.
|
|
29
|
+
*
|
|
30
|
+
* Field NAMES and the OBJECT name are forwarded so the dev-mode warning above
|
|
31
|
+
* can name the whole producer site rather than half of it: the map form keys
|
|
32
|
+
* the def by name, the array form carries it as `def.name`, and the object name
|
|
33
|
+
* comes off `schema.name`.
|
|
35
34
|
*/
|
|
36
35
|
export declare function normalizeSchemaReferenceKeys<T>(schema: T): T;
|