@object-ui/fields 17.6.0 → 18.0.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 (64) hide show
  1. package/CHANGELOG.md +6228 -0
  2. package/README.md +251 -9
  3. package/dist/FieldEditWidget.d.ts +54 -6
  4. package/dist/coerceToSafeValue.d.ts +54 -0
  5. package/dist/currency.d.ts +1 -1
  6. package/dist/fieldHelpText.d.ts +41 -0
  7. package/dist/index.cjs +5 -5
  8. package/dist/index.css +68 -5
  9. package/dist/index.d.ts +446 -69
  10. package/dist/index.js +5326 -3443
  11. package/dist/resolveActionParams.d.ts +259 -0
  12. package/dist/widgets/AvatarField.d.ts +19 -3
  13. package/dist/widgets/BooleanField.d.ts +1 -1
  14. package/dist/widgets/DateField.d.ts +1 -1
  15. package/dist/widgets/DateTimeField.d.ts +1 -1
  16. package/dist/widgets/FilterConditionField.d.ts +21 -7
  17. package/dist/widgets/FormulaField.d.ts +41 -0
  18. package/dist/widgets/GeolocationField.d.ts +9 -3
  19. package/dist/widgets/GridField.d.ts +73 -101
  20. package/dist/widgets/LocationField.d.ts +30 -3
  21. package/dist/widgets/LookupField.d.ts +8 -0
  22. package/dist/widgets/NumberField.d.ts +1 -1
  23. package/dist/widgets/ObjectField.d.ts +9 -2
  24. package/dist/widgets/OptionsEmptyState.d.ts +29 -5
  25. package/dist/widgets/PasswordField.d.ts +14 -0
  26. package/dist/widgets/PeoplePicker.d.ts +17 -2
  27. package/dist/widgets/PercentField.d.ts +2 -1
  28. package/dist/widgets/PersonRow.d.ts +13 -2
  29. package/dist/widgets/RecipientPickerField.d.ts +75 -0
  30. package/dist/widgets/RecordPickerDialog.d.ts +31 -12
  31. package/dist/widgets/RegistryRefField.d.ts +77 -0
  32. package/dist/widgets/RichTextField.d.ts +1 -1
  33. package/dist/widgets/SelectionTray.d.ts +13 -2
  34. package/dist/widgets/SummaryField.d.ts +30 -0
  35. package/dist/widgets/TextField.d.ts +1 -1
  36. package/dist/widgets/TimeField.d.ts +1 -1
  37. package/dist/widgets/booleanValueLabel.d.ts +18 -0
  38. package/dist/widgets/date-picker/DatePickerField.d.ts +40 -0
  39. package/dist/widgets/date-picker/localeDateText.d.ts +39 -0
  40. package/dist/widgets/deriveLookupColumns.d.ts +1 -1
  41. package/dist/widgets/file-affordance.d.ts +63 -0
  42. package/dist/widgets/file-refused.d.ts +17 -0
  43. package/dist/widgets/file-size-guard.d.ts +5 -23
  44. package/dist/widgets/file-value.d.ts +49 -9
  45. package/dist/widgets/longTextCarrierHandoff.d.ts +35 -0
  46. package/dist/widgets/lookupColumnDisplay.d.ts +26 -9
  47. package/dist/widgets/nativeDateValue.d.ts +9 -27
  48. package/dist/widgets/number-format.d.ts +137 -0
  49. package/dist/widgets/numberBadInput.d.ts +135 -0
  50. package/dist/widgets/objectRefLabel.d.ts +16 -0
  51. package/dist/widgets/personDisplay.d.ts +22 -4
  52. package/dist/widgets/recipientKinds.d.ts +85 -0
  53. package/dist/widgets/richTextDisplay.d.ts +32 -13
  54. package/dist/widgets/searchOffer.d.ts +8 -0
  55. package/dist/widgets/toDomProps.d.ts +26 -4
  56. package/dist/widgets/toHostGroupProps.d.ts +53 -0
  57. package/dist/widgets/toHostProps.d.ts +105 -0
  58. package/dist/widgets/types.d.ts +11 -3
  59. package/dist/widgets/upload-error-message.d.ts +6 -0
  60. package/dist/widgets/uploadingScope.d.ts +142 -0
  61. package/dist/widgets/useCascadingOptions.d.ts +3 -2
  62. package/dist/widgets/useRecordQuery.d.ts +17 -2
  63. package/dist/widgets/useUploadingSignal.d.ts +8 -0
  64. package/package.json +11 -12
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { default as React } from 'react';
2
2
  import { FieldMetadata } from '@object-ui/types';
3
- import { humanizeLabel, ComponentMeta } from '@object-ui/core';
3
+ import { PercentScale, humanizeLabel, formatDate, formatDateTime, formatDateTimeCompactParts, formatRelativeDate, RegistryComponentMeta, DateDisplayOptions } from '@object-ui/core';
4
+ import { formatCurrency, percentCellScale } from './widgets/number-format.js';
5
+ import { coerceToSafeValue } from './coerceToSafeValue.js';
4
6
  import { resolveFieldCurrency } from './currency.js';
5
7
  /**
6
8
  * Pick the most reasonable display name from an arbitrary record object.
@@ -9,8 +11,18 @@ import { resolveFieldCurrency } from './currency.js';
9
11
  export declare function pickRecordDisplayName(record: Record<string, unknown> | null | undefined, preferredField?: string): string | undefined;
10
12
  /**
11
13
  * Heuristic: detect strings that look like opaque foreign-key IDs (e.g. nanoid
12
- * or BSON ObjectId). Used so we don't display random gibberish to users when
13
- * a lookup wasn't expanded.
14
+ * or BSON ObjectId).
15
+ *
16
+ * ⛔ Nothing in this repo calls it any more, and re-introducing a caller that
17
+ * decides PRESENTATION from it would re-open objectui#8695. It used to gate
18
+ * `LookupCellRenderer`'s muted `—`, i.e. it decided how an unresolved
19
+ * reference was drawn from the SHAPE of the string rather than from whether
20
+ * anything resolved — so `'Ada Lovelace'` printed as a confident name and an
21
+ * opaque id lost its value, two opposite answers to one epistemic state. The
22
+ * export is kept because it is part of this package's published surface and
23
+ * retiring it is a breaking change no display card is entitled to make; the
24
+ * shape question itself is legitimate (a picker filter, an id-vs-name guess),
25
+ * it is only an answer to "what did this screen resolve?" that it can never be.
14
26
  */
15
27
  export declare function isLikelyOpaqueId(v: unknown): boolean;
16
28
  /**
@@ -21,16 +33,42 @@ export interface CellRendererProps {
21
33
  field: FieldMetadata;
22
34
  isEditing?: boolean;
23
35
  onChange?: (value: any) => void;
36
+ /**
37
+ * The row's values, for a renderer whose value only means something next to
38
+ * a sibling column — the cell-side counterpart of the edit widget's
39
+ * `FieldWidgetComponentProps.dependentValues` (objectui#12613). The console's
40
+ * `recipient-picker` face reads the row's `recipient_type` from it, as the
41
+ * picker reads the form record's.
42
+ *
43
+ * A call site that holds the row passes it; a renderer that needs a sibling
44
+ * and gets none draws the stored value as the text cell does, so a call site
45
+ * that does not pass it changes nothing.
46
+ */
47
+ dependentValues?: Record<string, unknown>;
48
+ /**
49
+ * The host's answer to "is this declared deadline settled for this row?"
50
+ * (objectui#11815) — `isDeadlineSettled` from `@object-ui/core`, which
51
+ * evaluates the field's `settledWhen` against the row the host holds. A cell
52
+ * cannot evaluate a predicate, so the host hands it the verdict.
53
+ *
54
+ * While it is `true` the `date` and `datetime` cells paint no overdue wording
55
+ * and no overdue colour: the relative wording falls back to the neutral
56
+ * phrase a non-deadline reads. Absent or `false` changes nothing.
57
+ */
58
+ deadlineSettled?: boolean;
24
59
  }
60
+ export { coerceToSafeValue };
61
+ export { resolveFieldCurrency };
25
62
  /**
26
- * Coerce a value to a safe primitive for rendering.
27
- * Handles MongoDB wrapper types ($numberDecimal, $oid, $date), expanded
28
- * reference objects, and arrays so that no raw object is ever passed as
29
- * a React child — preventing React error #310.
63
+ * `formatCurrency` lives beside `formatNumberFieldValue` in
64
+ * `./widgets/number-format.ts`, re-exported here under its long-standing
65
+ * `@object-ui/fields` name: `CurrencyField`'s read-only branch calls the same
66
+ * function `CurrencyCellRenderer` below calls, and the lazily loaded widget
67
+ * cannot import this barrel (objectui#11444). The width (the currency's ISO
68
+ * 4217 minor-unit count, every amount, no whole-amount trimming) and its
69
+ * history are documented there.
30
70
  */
31
- export declare function coerceToSafeValue(value: unknown): string | number | boolean | null | undefined;
32
- export { resolveFieldCurrency };
33
- export declare function formatCurrency(value: number, currency?: string, locale?: string): string;
71
+ export { formatCurrency };
34
72
  /**
35
73
  * Format currency value in compact form for mobile display.
36
74
  * E.g., $150,000 → $150K, $1,200,000 → $1.2M
@@ -38,18 +76,36 @@ export declare function formatCurrency(value: number, currency?: string, locale?
38
76
  */
39
77
  export declare function formatCompactCurrency(value: number, currency?: string, locale?: string): string;
40
78
  /**
41
- * Format a plain number with thousands separators, no currency symbol.
42
- * Used as a safe fallback when a currency-typed field has no `currency`
43
- * configured — we'd rather render `1,234.50` than silently assume USD.
79
+ * Format a plain number with thousands separators at a fixed display width, no
80
+ * currency symbol. `formatCurrency` renders an amount with no currency resolved
81
+ * through the same call shape (`./widgets/number-format.ts`): we'd rather
82
+ * render `1,234.50` than silently assume USD.
44
83
  */
45
84
  export declare function formatNumber(value: number, decimals?: number, locale?: string): string;
46
85
  /**
47
- * Format percent value.
48
- * Handles both decimal (0.8 = 80%) and whole number (80 = 80%) inputs.
86
+ * `percentCellScale` lives beside `formatPercentPoints` in
87
+ * `./widgets/number-format.ts` and is re-exported here: the storage scale the
88
+ * percent cell reads a field at, so every face rendering the cell's number for
89
+ * the same field reads the same answer by reference (objectui#11475).
90
+ */
91
+ export { percentCellScale };
92
+ /**
93
+ * Format a stored percentage at the storage the caller STATES.
49
94
  *
50
- * `locale` is the third positional parameter, matching {@link formatNumber} and
51
- * {@link formatCurrency} — the shape the sibling formatters already use.
52
- * Callers should pass the tag from `useDisplayLocale()`.
95
+ * `percentScale` is REQUIRED (objectui#11475): `'fraction'` (0.8 means 80%) or
96
+ * `'whole'` (80 means 80%), the spec's `PercentScale`. A face holding a field
97
+ * reads it with {@link percentCellScale} (the spec's `percentScaleOf`, a
98
+ * fraction unless the field declares a `max` above 1); a face without one
99
+ * states what it knows. A value outside the union throws. This used to guess
100
+ * from the value's magnitude (`percentDisplayValue`'s old body), so a
101
+ * fraction-stored `1` read `1%` here and `100%` in the read-only form.
102
+ *
103
+ * FROM `formatPercent(value, precision, locale)` TO
104
+ * `formatPercent(value, percentScale, precision, locale)`.
105
+ *
106
+ * `locale` is the last positional parameter, matching {@link formatNumber} and
107
+ * {@link formatCurrency}. Callers should pass the tag from
108
+ * `useDisplayLocale()`.
53
109
  *
54
110
  * Before objectui#4553 this function took no locale and never touched `Intl`:
55
111
  * its whole body was `${percentDisplayValue(value).toFixed(precision)}%`, so it
@@ -59,7 +115,7 @@ export declare function formatNumber(value: number, decimals?: number, locale?:
59
115
  * so the grouping and the locale are fixed together: en output MOVES from
60
116
  * `1235%` to `1,235%` at four digits and up, and that move is the fix.
61
117
  */
62
- export declare function formatPercent(value: number, precision?: number, locale?: string): string;
118
+ export declare function formatPercent(value: number, percentScale: PercentScale, precision?: number, locale?: string): string;
63
119
  /**
64
120
  * Humanize a snake_case or kebab-case string into Title Case — the fallback
65
121
  * label when no explicit `option.label` exists.
@@ -76,41 +132,20 @@ export declare function formatPercent(value: number, precision?: number, locale?
76
132
  * `humanizeFieldKey`'s camelCase-splitting KEY convention.
77
133
  */
78
134
  export { humanizeLabel };
79
- /** Options shared by {@link formatDate} / {@link formatRelativeDate}. */
80
- export interface DateDisplayOptions {
81
- dueLike?: boolean;
82
- /** BCP-47 display locale (ADR-0053 tenant default); falls back to the runtime locale. */
83
- locale?: string;
84
- /** i18n translate fn for phrases `Intl` can't produce (the "Overdue Nd" wording). */
85
- t?: (key: string, params?: Record<string, unknown>) => string;
86
- }
87
135
  /**
88
- * Format date as relative time (e.g., "3 days ago", "Today", "Overdue 3d"),
89
- * localized via `Intl.RelativeTimeFormat` (objectstack-ai/objectstack#3040).
136
+ * The date/datetime display path - `@object-ui/core`'s `utils/date-display.ts`
137
+ * (objectui#7178), re-exported here under its original names so every existing
138
+ * consumer of `@object-ui/fields` is unchanged.
90
139
  *
91
- * `dueLike` gates the "Overdue" wording — a past `start_date`/`created_at`
92
- * isn't overdue, only a past due/deadline-semantic field is. Non-due-like
93
- * past dates render as plain "N days ago" instead. The overdue phrase has no
94
- * `Intl` equivalent, so it resolves through `options.t` (key
95
- * `fields.relativeDate.overdue`) with an English fallback.
140
+ * It moved for the reason `formatDisplayNumber` moved in objectui#4576: this
141
+ * barrel is a React package, and the React-free engine could not import from
142
+ * it. `core`'s `formatMeasure` needed this exact path - a dataset measure over
143
+ * a date field was rendering its raw ISO string - and the alternative to
144
+ * moving was a second date convention in `dataset-format.ts`, which is the
145
+ * drift #4576 already paid for once with percent.
96
146
  */
97
- export declare function formatRelativeDate(value: string | Date | number, options?: DateDisplayOptions): string;
98
- /**
99
- * Format date value
100
- */
101
- export declare function formatDate(value: string | Date | number, style?: string, options?: DateDisplayOptions): string;
102
- /**
103
- * Format datetime value.
104
- *
105
- * `options` mirrors {@link formatDate}'s and is optional, so an existing
106
- * caller that passes nothing keeps the exact runtime-default behavior it had.
107
- * Before objectui#4272 the parameter did not exist at all, which meant no
108
- * caller could localize this function however hard it tried — it always handed
109
- * `Intl` an `undefined` tag, i.e. the MACHINE's locale, which is neither of
110
- * the repo's two locale channels. Callers should pass the tag from
111
- * `useDisplayLocale()`.
112
- */
113
- export declare function formatDateTime(value: string | Date | number, options?: DateDisplayOptions): string;
147
+ export { formatDate, formatDateTime, formatDateTimeCompactParts, formatRelativeDate };
148
+ export type { DateDisplayOptions };
114
149
  /**
115
150
  * Text field cell renderer
116
151
  */
@@ -136,11 +171,11 @@ export declare function BooleanCellRenderer({ value, field }: CellRendererProps)
136
171
  /**
137
172
  * Date field cell renderer
138
173
  */
139
- export declare function DateCellRenderer({ value, field }: CellRendererProps): React.ReactElement;
174
+ export declare function DateCellRenderer({ value, field, deadlineSettled }: CellRendererProps): React.ReactElement;
140
175
  /**
141
176
  * DateTime field cell renderer (Airtable-style with date and time visually separated)
142
177
  */
143
- export declare function DateTimeCellRenderer({ value }: CellRendererProps): React.ReactElement;
178
+ export declare function DateTimeCellRenderer({ value, field, deadlineSettled }: CellRendererProps): React.ReactElement;
144
179
  /** The six pill colors plus the two dot colors derived from one declared hex. */
145
180
  export interface HexBadgePalette {
146
181
  bg: string;
@@ -245,44 +280,201 @@ export declare function ImageCellRenderer({ value }: CellRendererProps): React.R
245
280
  * Display order:
246
281
  * 1. Embedded record object (`{ id, name, ... }` from `$expand`) → use its name
247
282
  * 2. Static `field.options[]` (e.g. when the lookup is a closed enum) → look up label
248
- * 3. Fetch-on-demand: when the value is a primitive ID and `field.reference_to`
283
+ * 3. Fetch-on-demand: when the value is a primitive ID and `field.reference`
249
284
  * is known, resolve via dataSource and show the related record's display name.
250
- * Falls back to a muted placeholder while pending and on failure.
285
+ * 4. Nothing named it → the unresolved-reference affordance (objectui#8695):
286
+ * the raw value, kept visible, beside a stated epistemic marker. This arm
287
+ * used to be two — a muted `—` for opaque-LOOKING strings and confident
288
+ * bare text for everything else — which answered one state two ways.
251
289
  *
252
290
  * Record → name resolution (1 and 3) goes through the referenced object's
253
291
  * schema when the data source exposes it (`displayField` → nameField/titleFormat
254
292
  * → derivation, see {@link resolveLookupRecordName}), so the chip and the
255
- * picker agree (issue #2357).
293
+ * picker agree (issue #2357) — and, like the picker's option label, it reads
294
+ * only the fields the viewer may read on the referenced object
295
+ * (objectui#10501).
256
296
  */
257
297
  export declare function LookupCellRenderer({ value, field }: CellRendererProps): React.ReactElement;
258
298
  /**
259
- * Formula field cell renderer (read-only)
299
+ * Formula field cell renderer (read-only). `summary` is registered to it too.
300
+ *
301
+ * The TABLE face of a formula; `FormulaField`'s read-only branch is its FORM
302
+ * face, and the two read one rule (objectui#11748): the declared `returnType`,
303
+ * or, with none, a JS number as a number and anything else as text. Each type
304
+ * is then drawn through the calls the matching field type's faces make, so
305
+ * one stored value reads the same in the form and in the table. The rule is
306
+ * spelled in both modules because this barrel and the lazily loaded widget may
307
+ * not import each other; `__tests__/formulaFaces.returnType-11748.test.tsx`
308
+ * compares the two faces' text for every declared type.
309
+ */
310
+ export declare function FormulaCellRenderer({ value, field }: CellRendererProps): React.ReactElement;
311
+ /**
312
+ * The shipped PRESENTATION of an unresolved reference, shared by the two
313
+ * renderers that state one (objectui#8434 for `user`, objectui#8695 for
314
+ * `lookup` / `master_detail` / `tree`), and published for the host faces of the
315
+ * widget-hinted reference keys (objectui#12613), which draw the same state.
316
+ *
317
+ * Only the drawing is shared. Each caller keeps its own literal i18n key and
318
+ * its own English fallback, because a key reaching this component as a prop
319
+ * would be a key `check:i18n-keys` can no longer judge — and because the two
320
+ * sentences are genuinely different claims: one is about a person, the other
321
+ * about a record of whatever object the lookup points at.
322
+ *
323
+ * ⚠️ No `pointer-events-none` here, unlike `EmptyValue`: that utility stops the
324
+ * span being a hit target, so a `title` on it never renders a tooltip
325
+ * (objectui#8506). The stated sentence has to be reachable by hovering.
326
+ *
327
+ * ⚠️ `truncate` on the inner span rather than the outer one, and the outer is
328
+ * `inline-flex`: `overflow: hidden` gives a flex item an automatic minimum
329
+ * size of zero, so the text shrinks and ellipsises instead of forcing the row
330
+ * wider. The full value stays reachable through the `title` sentence, which
331
+ * names it — that is how this shape meets objectui#3466's truncation contract
332
+ * (a single-line value must never expand its column and must expose its full
333
+ * text) with an icon in front of the text.
260
334
  */
261
- export declare function FormulaCellRenderer({ value }: CellRendererProps): React.ReactElement;
335
+ export declare function UnresolvedReferenceMark({ raw, hint, className, }: {
336
+ raw: string;
337
+ hint: string;
338
+ className?: string;
339
+ }): React.ReactElement;
262
340
  /**
263
341
  * User/Owner field cell renderer (with avatars)
342
+ *
343
+ * The name and the avatar are DISPLAY values, so each person row is drawn as
344
+ * the viewer may read it (objectui#10535): with a loaded policy, the fields it
345
+ * denies on the person's object are removed first ({@link withoutDeniedFields},
346
+ * `id` kept), and everything below reads that row — exactly as it reads the
347
+ * row a stripping backend serves. The person's object is the field's
348
+ * `reference`, and `sys_user` when it names none: the object `UserField`
349
+ * points the picker at.
264
350
  */
265
- export declare function UserCellRenderer({ value }: CellRendererProps): React.ReactElement;
351
+ export declare function UserCellRenderer({ value, field }: CellRendererProps): React.ReactElement;
266
352
  /**
267
353
  * Register a custom field renderer
268
354
  * @param type Field type (e.g. 'text', 'location', 'my-custom-type')
269
355
  * @param renderer React component to render the field
270
356
  */
271
357
  export declare function registerFieldRenderer(type: string, renderer: React.FC<CellRendererProps>): void;
358
+ /**
359
+ * ## Polymorphic pointer pairs: `referenceVia` (objectui#12045)
360
+ *
361
+ * `@objectstack/spec`'s `FieldSchema.referenceVia` declares a `text` field as
362
+ * the ID HALF of a two-column pointer pair (ADR-0052 §5): its value is a
363
+ * record id of the object that the SIBLING column names, on the same row.
364
+ * `sys_approval_request.record_id` with `referenceVia: 'object_name'` is one;
365
+ * `sys_activity.source_id` with `referenceVia: 'source_object'` is another.
366
+ *
367
+ * The target object varies per ROW, so the pair is resolved where the row is
368
+ * in hand, at the call site. {@link CellRendererProps} carries one value and no
369
+ * row, which is why this is a function a call site asks and not a branch inside
370
+ * {@link getCellRenderer}, whose dispatch stays on the field type alone.
371
+ *
372
+ * ⭐ This is the one place the rule lives. A call site that holds the row asks
373
+ * `resolveRecordPointer(field, row, optionsOf)`, and draws a pair it gets back
374
+ * with the renderer registered under {@link RECORD_POINTER_CARD_TYPE}, passing
375
+ * the pair as that renderer's `value`. The same call answers whether the
376
+ * platform asserted the target deleted on this row (`referenceDeletedWhen`,
377
+ * objectui#12489), so no face and no call site evaluates that predicate. This package registers a default under that key
378
+ * which draws the record id as text, exactly as the field drew before its pair
379
+ * was read, so a host with nothing better changes nothing. A host that can read
380
+ * records registers a richer face under the same key with
381
+ * {@link registerFieldRenderer}: `@object-ui/app-shell` registers its record
382
+ * preview card there.
383
+ *
384
+ * Two call sites ask it. The record details grid (`DetailSection` in
385
+ * `@object-ui/plugin-detail`) draws the pair with the renderer registered under
386
+ * {@link RECORD_POINTER_CARD_TYPE}. The list grid (`ObjectGrid` in
387
+ * `@object-ui/plugin-grid`, objectui#12608) draws it with its own cell face,
388
+ * not with that renderer: a face that reads its target one record at a time
389
+ * costs one request per row in a list, so the list face batches the page's
390
+ * targets into one read per object.
391
+ */
392
+ export declare const RECORD_POINTER_CARD_TYPE = "record_pointer_card";
393
+ /** One resolved pointer pair: the record a `referenceVia` field points at. */
394
+ export interface RecordPointer {
395
+ /** The target's object machine name: the sibling column's value. */
396
+ objectName: string;
397
+ /** The target's record id: the pointer field's own value. */
398
+ recordId: string;
399
+ /**
400
+ * Present only when the platform asserted, on this row, that the target was
401
+ * deleted (the field's `referenceDeletedWhen` predicate holds, objectui#12489):
402
+ * the label of the declared option that says so, as the call site's option
403
+ * labels spell it. A face that receives it draws the tombstone in this text,
404
+ * reads nothing and links nothing. Absent on every other row, and a face
405
+ * then draws the pair as before.
406
+ */
407
+ deletedLabel?: string;
408
+ }
409
+ /**
410
+ * `@objectstack/spec`'s `FieldSchema.referenceDeletedWhen`: the sibling
411
+ * `select` field, and the one option of it, that the platform writes on a row
412
+ * whose pointed-at record it deleted. The spec refuses it at parse unless the
413
+ * field declares `referenceVia`, `field` is a single-value `select` of the same
414
+ * object declaring inline `options`, and `value` is one of those options.
415
+ */
416
+ type ReferenceDeletedWhen = {
417
+ readonly field: string;
418
+ readonly value: string;
419
+ };
420
+ /** The call site's option list for one field: what `select` cells draw with. */
421
+ type OptionsOf = (fieldName: string) => ReadonlyArray<{
422
+ readonly value?: unknown;
423
+ readonly label?: unknown;
424
+ }> | null | undefined;
425
+ /**
426
+ * Resolve the record a `referenceVia` pointer field points at on one row, or
427
+ * `null` when the field is not a pointer or the row does not address a record.
428
+ *
429
+ * - `field` must be a `text` field declaring a non-empty `referenceVia`; the
430
+ * spec refuses the key on every other type, so nothing else qualifies here.
431
+ * - The pair is read from `row`: the id under the field's own name, the object
432
+ * name under the sibling column the field names.
433
+ * - Both halves must be non-blank strings. A half-addressed row is not a
434
+ * pointer this can draw, and the caller falls back to the field's ordinary
435
+ * face (the stored text).
436
+ *
437
+ * ## A deletion the platform asserts: `referenceDeletedWhen` (objectui#12489)
438
+ *
439
+ * When the field also declares `referenceDeletedWhen: { field, value }` and
440
+ * this row's sibling `field` holds exactly `value`, the pair comes back with
441
+ * `deletedLabel`: the label of that declared option, looked up in
442
+ * `optionsOf(field)`. The call site supplies `optionsOf` because it holds the
443
+ * object definition and the option labels the rest of its grid draws with
444
+ * (localized ones included), so the tombstone and the sibling `select`'s own
445
+ * cell say the same thing.
446
+ *
447
+ * - Only the declared assertion marks a target deleted. A target read that
448
+ * fails or answers no record says nothing about why: the platform answers a
449
+ * deleted record and one outside the reader's row set alike, on purpose.
450
+ * - No `optionsOf`, or no option with that value among the sibling's options,
451
+ * means no label to draw, and the pair comes back without `deletedLabel`.
452
+ * The spec refuses a predicate whose value is not a declared option, so the
453
+ * second case is not reachable from spec-valid metadata. ⛔ Never the raw
454
+ * option value as the text: the tombstone is the option's own label.
455
+ */
456
+ export declare function resolveRecordPointer(field: {
457
+ readonly name: string;
458
+ readonly type?: string | null;
459
+ readonly referenceVia?: string | null;
460
+ readonly referenceDeletedWhen?: ReferenceDeletedWhen | null;
461
+ } | null | undefined, row: Readonly<Record<string, unknown>> | null | undefined, optionsOf?: OptionsOf): RecordPointer | null;
272
462
  /**
273
463
  * Resolve the canonical cell-renderer key for a field. Accepts either a
274
464
  * raw type string (back-compat) or a field-metadata object so that
275
465
  * format hints (`format: 'phone'` etc.) can promote a plain `text`
276
- * field to its richer renderer counterpart.
466
+ * field to its richer renderer counterpart, and a declared `widget` with
467
+ * a read cell of its own (`WIDGET_CELL_RENDERER_TYPES`) to that cell.
277
468
  */
278
469
  export declare function resolveCellRendererType(fieldOrType: string | {
279
470
  type?: string;
280
471
  format?: string;
472
+ widget?: string;
281
473
  } | null | undefined): string;
282
474
  /**
283
- * Renders structured/embedded values (json, object, composite, record,
284
- * address, geolocation) as compact, readable JSON. Objects and arrays are
285
- * stringified; primitives fall through to their string form.
475
+ * Renders a free-form JSON value (`json`, `object`) as compact, readable JSON,
476
+ * and is the fallback face for a shape a structured renderer cannot recognize
477
+ * (`location` / `geolocation`, `address`, `composite` / `record`).
286
478
  */
287
479
  export declare function JsonCellRenderer({ value }: CellRendererProps): React.ReactElement;
288
480
  /**
@@ -293,12 +485,13 @@ export declare function ColorSwatchCellRenderer({ value }: CellRendererProps): R
293
485
  * The rich-content display pipelines — `markdown` through the GFM renderer,
294
486
  * `html`/`richtext` through the sanitizing HTML renderer — live in
295
487
  * `./widgets/richTextDisplay.js` rather than here, so `RichTextField` can
296
- * import them without importing this barrel back (objectui#5498). Re-exported
488
+ * import them without importing this barrel back (`4bb940b6e`). Re-exported
297
489
  * unchanged: they are part of this package's published surface, and
298
490
  * `RICH_TEXT_CELL_RENDERERS` below is the one table both the cell resolver and
299
491
  * the widget's readonly branch read.
300
492
  */
301
493
  export { MarkdownCellRenderer, HtmlCellRenderer } from './widgets/richTextDisplay.js';
494
+ export { RICH_TEXT_FIELD_TYPES, type RichTextFieldType } from './widgets/richTextDisplay.js';
302
495
  /**
303
496
  * Renders a `location`/`geolocation` value as readable coordinates with a pin.
304
497
  * Accepts `{ lat, lng }` / `{ latitude, longitude }`, a `"lat,lng"` string,
@@ -325,6 +518,136 @@ export declare function LocationCellRenderer({ value }: CellRendererProps): Reac
325
518
  * is never silently swallowed by the fix.
326
519
  */
327
520
  export declare function AddressCellRenderer({ value }: CellRendererProps): React.ReactElement;
521
+ /**
522
+ * `password` / `secret` cell renderer: the mask for a credential that is SET,
523
+ * and the shared affordance for one that is NOT (objectui#8678).
524
+ *
525
+ * The entry this replaces was an argument-less arrow that drew the mask for
526
+ * every input, `null` included. On screen, a credential that was never set was
527
+ * then drawn exactly like one that is set. The platform's read contract is
528
+ * presence-preserving on purpose: `@objectstack/spec`'s `SECRET_MASK` docblock
529
+ * says an unset credential "reads back `null` instead, never this mask", which
530
+ * is what lets a console render "configured" vs "not configured" at all. This
531
+ * cell threw that distinction away one step before the reader saw it.
532
+ *
533
+ * ⛔ The value is NEVER printed and never reaches the DOM. Only its presence is
534
+ * read. A populated value keeps the exact mask it drew before this card, and
535
+ * nothing here is a weaker mask.
536
+ *
537
+ * "Empty" is the string class's answer, since both types are
538
+ * `STRING_VALUE_TYPES` members in the spec: `TextCellRenderer`'s predicate
539
+ * exactly, the floor on the coerced text. `{}` therefore stays a VALUE and
540
+ * keeps the mask, per objectui#8596's string-class ruling ("the record IS
541
+ * storing something"). A whitespace-only string also keeps it, because `text`
542
+ * keeps its spaces and a blank credential is still a set one.
543
+ * `cellRenderers.valueIndependent-8678` pins this byte-equal to `text` for
544
+ * every empty input.
545
+ *
546
+ * ⭐ A NAMED module-level component, for the reason {@link RepeaterCellRenderer}
547
+ * states: `EmptyValue` holds a hook, and an inline arrow in the table below
548
+ * is a new component type on every resolution, so it would tear that hook down
549
+ * per render.
550
+ *
551
+ * Exported (objectui#10657) for the cell a producer cannot type yet. While an
552
+ * object-bound table is still waiting for its object's field types, or after
553
+ * that read failed, a column with no type of its own could be a credential,
554
+ * so `ObjectGrid`, `RelatedList` and `ObjectDataTable` draw it WITHHELD: as
555
+ * this mask, never as text. It is this component and not the live registry
556
+ * entry for `password`, so a host override of a masked type cannot change what
557
+ * a withheld cell draws. Registering it for a type
558
+ * (`registerFieldRenderer('api_token', MaskedCellRenderer)`) masks that type,
559
+ * as {@link isMaskedFieldType} describes. `field` is optional: the mask reads
560
+ * only whether the value is present, so a withheld cell has no field metadata
561
+ * to invent.
562
+ */
563
+ export declare function MaskedCellRenderer({ value, }: Omit<CellRendererProps, 'field'> & {
564
+ field?: CellRendererProps['field'];
565
+ }): React.ReactElement;
566
+ /**
567
+ * The field types this package DECLARES masked: their standard cell is
568
+ * {@link MaskedCellRenderer}, which draws `••••••` in place of the value
569
+ * (objectui#8686). The read-side twin of the credential entry in
570
+ * `INLINE_EXCLUDED_FIELD_TYPES`, which lives in `FieldEditWidget`.
571
+ *
572
+ * ⭐ This set BUILDS the table's masked entries — `buildStandardCellRendererMap`
573
+ * spreads one {@link MaskedCellRenderer} entry per member — so adding a type
574
+ * here masks its cell AND makes {@link isMaskedFieldType} answer `true` for it,
575
+ * in one edit. There is no second list to keep in step.
576
+ *
577
+ * ⚠️ Membership answers "what ships masked", ⛔ not "what is masked right now":
578
+ * `registerFieldRenderer` can add or replace a masked type at runtime, and a
579
+ * set cannot see that. Consumers deciding what to do with a cell's value ask
580
+ * {@link isMaskedFieldType}.
581
+ *
582
+ * RAW spellings, deliberately: the cell path does not resolve form aliases
583
+ * (`getCellRenderer` is an exact-key lookup), so `field:password` renders in
584
+ * the clear and is not a member.
585
+ *
586
+ * Owned here for now. The objectui#8686 ruling made the protocol the first
587
+ * place to look: if `@objectstack/spec` comes to declare which field types are
588
+ * credentials, this set derives from that declaration instead of listing types.
589
+ */
590
+ export declare const MASKED_FIELD_TYPES: ReadonlySet<string>;
591
+ /**
592
+ * Is a cell of this field type drawn as a mask instead of as its value? The
593
+ * one authority for that question (objectui#8686): ask it rather than keep a
594
+ * list of types. The read-side twin of `isInlineExcludedFieldType()`.
595
+ *
596
+ * A LIVE reading of the cell registry, taken at call time, in the order
597
+ * {@link getCellRenderer} resolves: the runtime registry first, then the
598
+ * standard table.
599
+ *
600
+ * 1. The type resolves to THE mask ({@link MaskedCellRenderer}) → `true`,
601
+ * declared or not. That is how a host adds a masked type:
602
+ * `registerFieldRenderer('api_token', getCellRenderer('password'))`.
603
+ * 2. Otherwise, a type outside {@link MASKED_FIELD_TYPES} → `false`.
604
+ * 3. A declared type whose mask a host REPLACED at runtime
605
+ * (`registerFieldRenderer('password', X)`) → the override is read:
606
+ * - X is one of this package's own cell renderers → `false`. None of them
607
+ * is the mask, so the predicate answers `false` and the cell draws what
608
+ * X draws (for `TextCellRenderer`, the value).
609
+ * - X is the host's own component → `true`, the declared answer. Nothing
610
+ * here can tell whether an opaque component masks. Answering `false`
611
+ * would offer a credential to anything that trusts this predicate
612
+ * (the detail page's copy affordance, objectui#8440) while a custom mask
613
+ * hides it on screen. So an unreadable override falls back to the
614
+ * declared set, on the side that withholds.
615
+ *
616
+ * RAW spelling, no alias resolution, for the reason {@link MASKED_FIELD_TYPES}
617
+ * states. Side-effect free, like `isInlineExcludedFieldType()`: unlike
618
+ * {@link getCellRenderer}, it never reports a retired spelling.
619
+ */
620
+ export declare function isMaskedFieldType(fieldType: string | undefined): boolean;
621
+ /**
622
+ * Every field type that resolves to a cell renderer OF ITS OWN — a LIVE
623
+ * reading of the registry, taken when you call it (objectui#8734).
624
+ *
625
+ * ## Why this exists
626
+ *
627
+ * Two censuses — `cellRenderers.objectLiteral-8596` in this package and
628
+ * `summaryChip.badgeFitCensus-8464` in `@object-ui/plugin-detail` — declare in
629
+ * prose that they measure "every type `getCellRenderer` resolves to a renderer
630
+ * of its own", and each enforced that declaration with a hard-coded population
631
+ * size. A newly registered type is absent from such a table, so every row in it
632
+ * still passes: the census goes on measuring a snapshot while claiming to
633
+ * measure the registry. This function is what those censuses reconcile against,
634
+ * so a new type turns a silent pass into a red row that NAMES the type.
635
+ *
636
+ * ## ⭐ A function, ⛔ not a frozen constant
637
+ *
638
+ * `FORM_FIELD_TYPES` next door may be `Object.freeze(Object.keys(...))` because
639
+ * `fieldWidgetMap` is a module literal that never changes. This registry DOES
640
+ * change: {@link registerFieldRenderer} is published, and a host may add or
641
+ * override a type after this module is evaluated. A frozen constant here would
642
+ * be a snapshot taken at import time — the very defect the censuses are being
643
+ * repaired for, one level down. Every call re-reads.
644
+ *
645
+ * The answer is the UNION of the runtime registry and the standard table,
646
+ * because {@link getCellRenderer} dispatches on both; it is sorted so callers
647
+ * get a stable order, and `TextCellRenderer` is the TOTAL fallback for
648
+ * everything else, which is exactly why "everything else" is not listed here.
649
+ */
650
+ export declare function listCellRendererTypes(): readonly string[];
328
651
  /**
329
652
  * Get the appropriate cell renderer for a field type
330
653
  */
@@ -341,6 +664,14 @@ export declare function formatFileSize(bytes: number): string;
341
664
  * Build validation rules from field metadata
342
665
  * @param field - Field metadata from ObjectStack
343
666
  * @returns Validation rule object compatible with react-hook-form
667
+ *
668
+ * ⛔ No field-level `pattern` / `pattern_message` / `validate` (objectui#12242,
669
+ * maintainer ruling A). `@objectstack/spec`'s `FieldSchema` refuses all three
670
+ * by name, so no published object carries them, and the field metadata types
671
+ * retire them as `never`. A regex check is the OBJECT's `format` validation
672
+ * rule (`validations[]`), which the server runs on every write that touches
673
+ * the field and refuses with the rule's message; this function adds no client
674
+ * pre-check of it.
344
675
  */
345
676
  export declare function buildValidationRules(field: any): any;
346
677
  /**
@@ -379,12 +710,12 @@ declare const fieldWidgetMap: {
379
710
  date: () => Promise<{
380
711
  default: React.ComponentType<any>;
381
712
  } | {
382
- default: typeof import('./widgets/DateField.js').DateField;
713
+ default: typeof import('./widgets/date-picker/DatePickerField.js').DatePickerField;
383
714
  }>;
384
715
  datetime: () => Promise<{
385
716
  default: React.ComponentType<any>;
386
717
  } | {
387
- default: typeof import('./widgets/DateTimeField.js').DateTimeField;
718
+ default: typeof import('./widgets/date-picker/DatePickerField.js').DateTimePickerField;
388
719
  }>;
389
720
  time: () => Promise<{
390
721
  default: React.ComponentType<any>;
@@ -576,6 +907,16 @@ declare const fieldWidgetMap: {
576
907
  } | {
577
908
  default: typeof import('./widgets/RecipientPickerField.js').RecipientPickerField;
578
909
  }>;
910
+ 'position-ref': () => Promise<{
911
+ default: React.ComponentType<any>;
912
+ } | {
913
+ default: typeof import('./widgets/RegistryRefField.js').PositionRefField;
914
+ }>;
915
+ 'permission-ref': () => Promise<{
916
+ default: React.ComponentType<any>;
917
+ } | {
918
+ default: typeof import('./widgets/RegistryRefField.js').PermissionRefField;
919
+ }>;
579
920
  };
580
921
  /** The registered field widget keys, as a literal union (objectui#4857). */
581
922
  export type RegisteredFieldWidgetType = keyof typeof fieldWidgetMap;
@@ -610,6 +951,23 @@ export declare function resolveFormWidgetType(fieldType: string): string;
610
951
  * which converged the three private copies that preceded it).
611
952
  */
612
953
  export { CASCADE_OPTION_WIDGET_TYPES } from '@object-ui/core';
954
+ /**
955
+ * The ONE action-param resolver (objectui#12105): a declared action param —
956
+ * inline, or field-backed via `{ field, objectOverride }` — resolved against
957
+ * the object's field definitions into the `ActionParamDef` a param dialog
958
+ * renders. Exported from here because this is the package both of its callers
959
+ * depend on: app-shell's single-record `ActionParamDialog` and plugin-grid's
960
+ * selection-bar bulk dialog (a promoted action's params). See the module.
961
+ */
962
+ export { resolveActionParam, resolveActionParams, resolveParamWidgetType, withKnownObjects, RESOLVED_ONLY_PARAM_KEYS, } from './resolveActionParams.js';
963
+ export type { RawActionParam, RawActionParamOption, ResolveActionParamsContext, } from './resolveActionParams.js';
964
+ /**
965
+ * The ONE read of a field definition's help line (objectui#12213):
966
+ * `inlineHelpText`, else `description`. Exported from here because both of its
967
+ * readers can reach this package: `@object-ui/plugin-form`'s record form and
968
+ * this package's own `resolveActionParam`. See the module.
969
+ */
970
+ export { fieldHelpText } from './fieldHelpText.js';
613
971
  /**
614
972
  * Lazily-loaded form field widget for a field type. Shares the exact loaders
615
973
  * of {@link fieldWidgetMap} (the same components `registerField` registers for
@@ -621,7 +979,7 @@ export { CASCADE_OPTION_WIDGET_TYPES } from '@object-ui/core';
621
979
  export declare function getLazyFieldWidget(fieldType: string): React.ComponentType<any>;
622
980
  /**
623
981
  * The labelling declaration of EVERY registered field widget
624
- * (`ComponentMeta.labelling` — the closed `'control' | 'group' | 'display'`
982
+ * (`RegistryComponentMeta.labelling` — the closed `'control' | 'group' | 'display'`
625
983
  * vocabulary, objectui#3961 extended by objectui#4857). This `Record` is keyed
626
984
  * by the widget map's own literal key union, so it is exhaustive BY
627
985
  * CONSTRUCTION: registering a widget without deciding how a host's label
@@ -671,7 +1029,7 @@ export declare function getLazyFieldWidget(fieldType: string): React.ComponentTy
671
1029
  * out-of-registry widgets alike; the entry here is still mandatory, because
672
1030
  * "defaulted by omission" and "decided to be the default" are different facts.
673
1031
  */
674
- export declare const FIELD_WIDGET_LABELLING: Record<RegisteredFieldWidgetType, NonNullable<ComponentMeta['labelling']>>;
1032
+ export declare const FIELD_WIDGET_LABELLING: Record<RegisteredFieldWidgetType, NonNullable<RegistryComponentMeta['labelling']>>;
675
1033
  export declare function registerField(fieldType: string): void;
676
1034
  /**
677
1035
  * Register all field types (for backward compatibility)
@@ -694,6 +1052,17 @@ export declare function registerField(fieldType: string): void;
694
1052
  * nothing is silently substituted either, which is the whole point: the author
695
1053
  * sees a refusal where they expected an input, not a text box that looks like
696
1054
  * it worked.
1055
+ *
1056
+ * The spelling it names is `retiredFieldType` — the key the RESOLVER answered
1057
+ * with this tombstone — and is re-derived from the field only when a host
1058
+ * renders the tombstone directly without one (objectui#10471). The field is
1059
+ * the wrong witness whenever the retired spelling arrived through a different
1060
+ * key than `field.type`: an authored `widget` wins over `type` in both form
1061
+ * hosts, so `{ type: 'user', widget: 'owner' }` reaches the `owner` tombstone
1062
+ * carrying a `user` field, and deriving from it named the LIVE type as
1063
+ * retired, lost the prescription, and logged nothing. The two resolvers fill
1064
+ * the prop by binding it ({@link retiredFieldTombstoneFor}); a host whose key
1065
+ * IS `field.type` (plugin-detail's inline editor) may keep omitting it.
697
1066
  */
698
1067
  export declare const RetiredFieldTombstone: React.FC<Record<string, any>>;
699
1068
  export declare function registerAllFields(): void;
@@ -703,6 +1072,7 @@ export * from './FieldEditWidget.js';
703
1072
  export * from './widgets/TextField.js';
704
1073
  export * from './widgets/NumberField.js';
705
1074
  export * from './widgets/BooleanField.js';
1075
+ export { useBooleanValueLabel, type BooleanValueLabel } from './widgets/booleanValueLabel.js';
706
1076
  export * from './widgets/SelectField.js';
707
1077
  export * from './widgets/DateField.js';
708
1078
  export * from './widgets/DateTimeField.js';
@@ -720,6 +1090,8 @@ export * from './widgets/CapabilityMultiSelectField.js';
720
1090
  export * from './widgets/ObjectRefField.js';
721
1091
  export * from './widgets/FilterConditionField.js';
722
1092
  export * from './widgets/RecipientPickerField.js';
1093
+ export { objectRefOptionLabel } from './widgets/objectRefLabel.js';
1094
+ export { RECIPIENT_RECORD_KINDS, RECIPIENT_REGISTRY_KINDS, FIELD_RECIPIENT_TYPE, recipientCandidateLabel, } from './widgets/recipientKinds.js';
723
1095
  export * from './widgets/RecordPickerDialog.js';
724
1096
  export * from './widgets/deriveLookupColumns.js';
725
1097
  export * from './widgets/FileField.js';
@@ -750,5 +1122,10 @@ export * from './widgets/CheckboxesField.js';
750
1122
  export * from './widgets/TagsField.js';
751
1123
  export { withFieldCarrier } from './withFieldCarrier.js';
752
1124
  export { toDomProps } from './widgets/toDomProps.js';
753
- export type { DomProps } from './widgets/toDomProps.js';
1125
+ export type { FieldDomProps } from './widgets/toDomProps.js';
1126
+ export { toHostProps } from './widgets/toHostProps.js';
1127
+ export type { HostProps } from './widgets/toHostProps.js';
754
1128
  export { toDateInputValue, toDateTimeInputValue, fromDateTimeInputValue, } from './widgets/nativeDateValue.js';
1129
+ export { useUploadingScope, UploadingScopeProvider } from './widgets/uploadingScope.js';
1130
+ export { useUploadingSignal } from './widgets/useUploadingSignal.js';
1131
+ export type { UploadingScope } from './widgets/uploadingScope.js';