@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
@@ -0,0 +1,259 @@
1
+ import { ActionParamDef, ActionParamOption } from '@object-ui/core';
2
+ import { I18nLabel } from '@objectstack/spec/ui';
3
+ /**
4
+ * Resolve a param `type` to the form widget key that renders it — the fold from
5
+ * a PARAM type spelling to its widget, the one alias table, read rather than
6
+ * restated (see `isLookupResolvedType` below, and app-shell's `paramToField`,
7
+ * which asks the same question one step later). Any type in `FORM_FIELD_TYPES`
8
+ * resolves to itself (identity — asserted by app-shell's drift test); aliases
9
+ * and unknown types resolve through the same fallback chain the form applies
10
+ * (unknown → `text`).
11
+ */
12
+ export declare function resolveParamWidgetType(paramType: string): string;
13
+ /**
14
+ * Resolved params keep raw `FieldType` values (`text` / `email` / `select` /
15
+ * `file` / …), matching the `FieldType` enum in `@objectstack/spec`. **Do
16
+ * not** translate into the FormField widget vocabulary (`field:select`, …)
17
+ * here — `ActionParamDialog` owns that translation via its `paramToField()`
18
+ * adapter (ADR-0059).
19
+ */
20
+ /**
21
+ * One option as AUTHORED on an action param — {@link ActionParamOption} with
22
+ * the label on the authoring side of rc.6's `I18nLabel` widening.
23
+ *
24
+ * The distinction is the whole point of the authored/resolved split: an author
25
+ * may write `{ value: 'high', label: { en: 'High', 'zh-CN': '高' } }`, and
26
+ * `ActionParamDialog` reads `ActionParamOption.label` as a plain `string`. Left
27
+ * un-narrowed, the map would ride an INLINE option list straight through to the
28
+ * select widget (inline options pass through verbatim — see `resolvedOptions`),
29
+ * and render as `[object Object]` with no diagnostic anywhere. So the two types
30
+ * are kept apart and {@link resolveOptionLabels} is the one crossing point.
31
+ *
32
+ * Spelled out rather than `Omit<ActionParamOption, 'label'> & { … }`: `Omit`
33
+ * over a type carrying `[key: string]: unknown` resolves `Exclude<keyof T,
34
+ * 'label'>` back to `string | number`, so it drops `value`'s type AND keeps
35
+ * `label` as `unknown` — the derivation silently erases exactly the two keys it
36
+ * is supposed to be about.
37
+ */
38
+ export type RawActionParamOption = {
39
+ /** Authored label — a plain string, or rc.6's inline per-locale map. */
40
+ label?: I18nLabel;
41
+ value: string;
42
+ /**
43
+ * Everything else an option declares (`visibleWhen` / `color` / `icon` /
44
+ * `disabled`), preserved verbatim — `fbc23e094`.
45
+ */
46
+ [key: string]: unknown;
47
+ };
48
+ /** Raw param as authored on a schema action (post-zod). */
49
+ export interface RawActionParam {
50
+ name?: string;
51
+ field?: string;
52
+ objectOverride?: string;
53
+ /**
54
+ * Display label, as authored. `@objectstack/spec` 17.0.0-rc.6 widened
55
+ * `I18nLabel` from `string` to `string | Record<string, string>`, so an
56
+ * author may inline a per-locale map here
57
+ * (`label: { en: 'Reason', 'zh-CN': '原因' }`) — the same widening the spec's
58
+ * `ActionParamSchema.label` carries.
59
+ *
60
+ * This restatement is the load-bearing half (objectui#3174): when it drifts
61
+ * from what `@object-ui/types`' `ActionParam` says an author may write, this
62
+ * resolver stops accepting the public authoring type. That drift is exactly
63
+ * what a widened `I18nLabel` produced, and it is why the fix belongs on this
64
+ * DECLARATION rather than on the test that surfaced it.
65
+ *
66
+ * `resolveActionParams` emits `ActionParamDef.label`, which is a plain
67
+ * `string` — so the map is resolved on the way out (see {@link
68
+ * ResolveActionParamsContext.locale}), never forwarded.
69
+ */
70
+ label?: I18nLabel;
71
+ type?: string;
72
+ required?: boolean;
73
+ /**
74
+ * Option list authored inline on the param. Speaks the same vocabulary as the
75
+ * resolved side ({@link ActionParamOption}): the two keys this resolver reads
76
+ * plus a catch-all for whatever else an option declares (`visibleWhen`,
77
+ * `color`, `icon`, `disabled`) — `fbc23e094`.
78
+ *
79
+ * One key differs, and it is the same rc.6 widening as {@link
80
+ * RawActionParam.label} one level down: an option's `label` is authored as
81
+ * `I18nLabel` and RESOLVED to a `string` before it reaches
82
+ * `ActionParamOption`. See {@link RawActionParamOption}.
83
+ */
84
+ options?: RawActionParamOption[];
85
+ placeholder?: string;
86
+ helpText?: string;
87
+ defaultValue?: unknown;
88
+ /** When true, seed defaultValue from the row record using the field name. */
89
+ defaultFromRow?: boolean;
90
+ /**
91
+ * Carry-over declaration — the spec's `ActionParamSchema.carryOver`
92
+ * (objectui#6246): seeded from the row, rendered read-only, submitted
93
+ * verbatim. Copied onto `ActionParamDef.carryOver` unchanged on every branch
94
+ * below; `ActionParamDialog` is what honours it.
95
+ */
96
+ carryOver?: boolean;
97
+ /** Allow multiple values (file/image/lookup/user params → array value). */
98
+ multiple?: boolean;
99
+ /** Accepted upload types (MIME types / extensions) for `file`/`image` params. */
100
+ accept?: string[];
101
+ /** Max upload size in bytes for `file`/`image` params. */
102
+ maxSize?: number;
103
+ /**
104
+ * Reference target for an INLINE `lookup`/`master_detail` param (objectstack-ai/objectstack#3405) —
105
+ * the object whose records the picker searches. Field-backed params inherit
106
+ * it from the referenced field instead (see `lookupExtras` below). Spelled
107
+ * `reference` to match `FieldSchema.reference` / `ActionParamSchema.reference`.
108
+ */
109
+ reference?: string;
110
+ /**
111
+ * Visibility predicate (CEL) — mirrors the spec `ActionParamSchema.visible`.
112
+ * The server serialises it through `ExpressionInputSchema` as an
113
+ * `{ dialect, source }` envelope, so accept both the raw string and the
114
+ * envelope. Propagated to `ActionParamDef.visible` so `ActionParamDialog`
115
+ * can gate the param (e.g. hide `phoneNumber` unless `features.phoneNumber`).
116
+ * Absent = always visible.
117
+ */
118
+ visible?: string | {
119
+ dialect?: string;
120
+ source?: string;
121
+ };
122
+ }
123
+ /**
124
+ * Keys of the RESOLVED `ActionParamDef` that are not authorable on a raw param,
125
+ * mapped to the prescription an author needs (objectui#3174).
126
+ *
127
+ * They are deliberately ABSENT from `RawActionParam` above rather than declared
128
+ * and ignored: the whole defect this table exists for is that
129
+ * `@object-ui/types`' public `ActionParam` declared them "for parity with the
130
+ * resolved shape", so `{ name: 'account_id', type: 'lookup', referenceTo:
131
+ * 'account' }` type-checked, lost its picker target here, and rendered as a
132
+ * plain record-id text box. `ActionParamSchema` in `@objectstack/spec` is
133
+ * `.strict()` and rejects every one of them at parse time — `referenceTo` by
134
+ * name, with `reference` as the suggestion — so resolving them here would make
135
+ * objectui accept metadata the platform itself refuses to store. That is a
136
+ * second de-facto contract, not a kindness (AGENTS.md "contract-first"): such a
137
+ * param would work in a locally-authored TS action and fail at publish.
138
+ *
139
+ * So the disposition is: recognise, name, and refuse — never silently drop, and
140
+ * never quietly honour.
141
+ */
142
+ export declare const RESOLVED_ONLY_PARAM_KEYS: Readonly<Record<string, string>>;
143
+ /** Field metadata as exposed by `useMetadata().objects[].fields`. */
144
+ interface RuntimeField {
145
+ type?: string;
146
+ label?: string;
147
+ required?: boolean;
148
+ placeholder?: string;
149
+ /**
150
+ * `@objectstack/spec`'s `FieldSchema.inlineHelpText`, the help line drawn
151
+ * under the field's control (objectui#12213). Read first for a field-backed
152
+ * param's `helpText`, through `fieldHelpText`, the read the record form
153
+ * shares.
154
+ */
155
+ inlineHelpText?: string;
156
+ /**
157
+ * The help line's fallback, read second through `fieldHelpText`. An
158
+ * objectstack server on `@objectstack/spec` 17.7.0 or later serves a field's
159
+ * translated help text here.
160
+ */
161
+ description?: string;
162
+ /**
163
+ * The field's own option list — `select` / `radio` / `checkboxes` metadata as
164
+ * `@objectstack/spec`'s `SelectOptionSchema` declares it, so entries may carry
165
+ * `visibleWhen` / `color` / `icon` / `disabled` alongside label and value. A
166
+ * bare string is shorthand for `{ label: s, value: s }`.
167
+ */
168
+ options?: Array<ActionParamOption | string>;
169
+ multiple?: boolean;
170
+ defaultValue?: unknown;
171
+ accept?: string[];
172
+ maxSize?: number;
173
+ reference?: string;
174
+ displayField?: string;
175
+ reference_field?: string;
176
+ id_field?: string;
177
+ descriptionField?: string;
178
+ description_field?: string;
179
+ title_format?: string;
180
+ lookupColumns?: unknown[];
181
+ lookupFilters?: unknown[];
182
+ lookup_filters?: unknown[];
183
+ lookupPageSize?: number;
184
+ dependsOn?: unknown[];
185
+ }
186
+ interface RuntimeObject {
187
+ name?: string;
188
+ fields?: Record<string, RuntimeField>;
189
+ }
190
+ export interface ResolveActionParamsContext {
191
+ /** Default object name when a param's `objectOverride` is absent. */
192
+ objectName: string;
193
+ /** All known runtime objects (`useMetadata().objects`). */
194
+ objects: RuntimeObject[];
195
+ /** i18n resolver — `useObjectLabel().fieldLabel`. */
196
+ fieldLabel: (objectName: string, fieldName: string, fallback: string) => string;
197
+ /** Optional option-label translator — `useObjectLabel().fieldOptionLabel`. */
198
+ fieldOptionLabel?: (objectName: string, fieldName: string, optionValue: string, fallback: string) => string;
199
+ /**
200
+ * Row record providing default values for params with `defaultFromRow` set.
201
+ * Used by list_item actions (edit/delete dialogs) so the dialog opens with
202
+ * the row's current values pre-filled.
203
+ */
204
+ row?: Record<string, unknown>;
205
+ /**
206
+ * Active UI language (BCP-47) used to resolve an inline per-locale
207
+ * {@link RawActionParam.label} down to the one string `ActionParamDef.label`
208
+ * can carry. Both callers thread `useObjectTranslation().language`.
209
+ *
210
+ * Optional and nullish-tolerant: omitted means "no locale known", which the
211
+ * spec's resolver documents as resolving to `en`. It is a context field
212
+ * rather than a hook read because this is a pure function — the caller is
213
+ * the component that already knows the language.
214
+ */
215
+ locale?: string;
216
+ }
217
+ /**
218
+ * Resolve a single raw param against object metadata. Inline params pass
219
+ * through (with safe defaults); field-backed params inherit from the
220
+ * referenced field and accept inline overrides on top.
221
+ */
222
+ export declare function resolveActionParam(param: RawActionParam, ctx: ResolveActionParamsContext): ActionParamDef;
223
+ /**
224
+ * Union the objects a CALLER holds with the ones the console's metadata store
225
+ * holds, caller first — the object list `resolveActionParams()` should be given.
226
+ *
227
+ * ## Why a union and not simply the store (`6cc910b6d`)
228
+ *
229
+ * The two lists answer different questions and neither contains the other.
230
+ * The caller's list is the world that caller is rendering: `ObjectView` passes
231
+ * the full published set, `DeclaredActionsBar` passes the ONE object it is
232
+ * drawing a toolbar for, `ConsoleShell`'s root runtime passes none, and a
233
+ * preview/draft host passes an overlaid world whose defs deliberately differ
234
+ * from the published ones. The store's list is every object the session has
235
+ * loaded. A caller that carries a def must keep it — replacing it would make a
236
+ * draft overlay render its published twin — so the caller is ranked FIRST and
237
+ * `resolveActionParam`'s `find()` takes the first match by name.
238
+ *
239
+ * What the store adds is the objects the caller never had: the owner of a
240
+ * `{ field }` param dispatched from a surface that is not scoped to that object
241
+ * at all, and the `objectOverride` target of a cross-object param. Before this,
242
+ * such a param resolved against a list that could not contain its owner and
243
+ * degraded — for a field-backed param, which carries no inline `type`, into a
244
+ * bare `text` param: an empty box, no dropdown, no request for the referenced
245
+ * object on the wire, while the identical field rendered a real picker on a
246
+ * record form off this very store.
247
+ *
248
+ * ⛔ Not a lenient fallback in the AGENTS.md #0.1 sense: no dialect is being
249
+ * tolerated and no shape coerced. The metadata is spec-compliant and already in
250
+ * the client; this is the seam that was not handing it over.
251
+ *
252
+ * Pure, and deduplicated by object `name` so a def cannot be resolved twice.
253
+ * Entries with no usable `name` are carried through from the caller's list
254
+ * (they are that caller's business) and skipped from the store's.
255
+ */
256
+ export declare function withKnownObjects<T>(supplied: readonly T[] | undefined, stored: readonly T[] | undefined): T[];
257
+ /** Resolve an array of raw action params. */
258
+ export declare function resolveActionParams(params: RawActionParam[] | undefined, ctx: ResolveActionParamsContext): ActionParamDef[];
259
+ export {};
@@ -1,7 +1,23 @@
1
1
  import { default as React } from 'react';
2
2
  import { FieldWidgetComponentProps } from './types.js';
3
3
  /**
4
- * Avatar field widget - provides an avatar/profile picture uploader
5
- * Supports image URLs or file uploads
4
+ * Avatar field widget - provides an avatar/profile picture uploader.
5
+ *
6
+ * `avatar` is a member of the file-reference family, so its stored value is
7
+ * the bare `sys_file` id, exactly as for `file` and `image` (see
8
+ * `file-value`'s "Submitting"). A pick uploads through the ambient
9
+ * `UploadProvider` (`useUpload()`), and the id the adapter surfaced in
10
+ * `meta.fileId` is what `onChange` receives, through `fileValueForSubmit` —
11
+ * the one submit rule the file and image widgets use. A pick whose adapter
12
+ * surfaced no id (the object-URL default `useUpload()` falls back to when no
13
+ * provider is mounted, an S3/Azure-style adapter that mints no `sys_file`
14
+ * row) is refused with the same translated "did not complete" row, and the
15
+ * field is not changed. ⛔ The widget used to read the pick into a `data:` URL
16
+ * and store that string, inlining the whole image into the record row as a
17
+ * value the stored contract refuses (objectui#10785).
18
+ *
19
+ * Reading is `readFileValue`'s: a bare id renders from the storage endpoint,
20
+ * and a legacy `data:` or http(s) URL already on a record still renders as
21
+ * itself — ADR-0104's dual-read window is a read rule.
6
22
  */
7
- export declare function AvatarField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
23
+ export declare function AvatarField({ value, onChange, field, readonly, onUploadingChange, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types.js';
4
4
  * BooleanField - Toggle input supporting switch and checkbox variants
5
5
  * Renders as Switch or Checkbox based on field widget configuration
6
6
  */
7
- export declare function BooleanField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<boolean>): React.JSX.Element;
7
+ export declare function BooleanField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<boolean>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types.js';
4
4
  * DateField - Date picker input widget
5
5
  * Uses native date input and displays locale-formatted date in readonly mode
6
6
  */
7
- export declare function DateField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
7
+ export declare function DateField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -4,4 +4,4 @@ import { FieldWidgetComponentProps } from './types.js';
4
4
  * DateTimeField - Combined date and time picker widget
5
5
  * Displays both date and time in locale format when readonly
6
6
  */
7
- export declare function DateTimeField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
7
+ export declare function DateTimeField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -7,8 +7,7 @@ interface BuilderCondition {
7
7
  value: any;
8
8
  }
9
9
  /**
10
- * Opt-in FilterBuilder operators this widget offers (objectui#4023,
11
- * objectui#4736).
10
+ * Opt-in FilterBuilder operators this widget offers (objectui#4736).
12
11
  *
13
12
  * The shared dropdown withholds these because two of its three consumers
14
13
  * persist into dialects that cannot carry them (see `OPT_IN_OPERATORS` in
@@ -18,14 +17,19 @@ interface BuilderCondition {
18
17
  * folded into a `ViewFilterRule` — so the spec's `FILTER_OPERATORS` is the only
19
18
  * vocabulary it has to satisfy.
20
19
  *
21
- * - `containsCaseInsensitive` authors `$icontains`, executable on every
22
- * driver and evaluation face the platform ships (objectstack#5702 +
23
- * objectstack#6520).
24
20
  * - `exists` / `notExists` author `$exists`, which `condToMongo` has emitted
25
21
  * and `kvToCondition` has read back since objectui#2942. Naming them here
26
22
  * is what KEEPS them reachable now that the shared dropdown no longer
27
23
  * offers them to the list and view surfaces, whose dialects have no
28
- * existence operator at all (objectui#4736).
24
+ * existence operator at all (objectui#4736). They are NOT folded onto
25
+ * `is_not_null` / `is_null` (objectui#9559 ruling B, objectui#9306): on the
26
+ * key-presence drivers that would change which records a stored sharing
27
+ * rule matches.
28
+ *
29
+ * The case-insensitive contains used to be named here too, as
30
+ * `containsCaseInsensitive`. Since objectui#9306 it is the protocol's own
31
+ * `icontains` and an ordinary operator every consumer is offered, so there is
32
+ * nothing to opt into; it still authors `$icontains`, exactly as before.
29
33
  *
30
34
  * Module scope, not an inline literal: a fresh array each render would reset
31
35
  * `FilterBuilder`'s memo inputs on every keystroke.
@@ -38,12 +42,22 @@ export declare const FILTER_CONDITION_EXTRA_OPERATORS: readonly string[];
38
42
  * chokepoint where a builder token becomes a spec `FieldOperatorsSchema` key,
39
43
  * and a wrong spelling here is rejected downstream by `convertFiltersToAST`
40
44
  * rather than at authoring time. @internal
45
+ *
46
+ * ⚠️ The arms are keyed on the builder's PROTOCOL ids (objectui#9306), and the
47
+ * `default` arm stores an EQUALITY. So an operator id with no arm here is not
48
+ * refused — it silently becomes `{ [field]: value }`, which for a
49
+ * `greater_than_or_equal` row would share a different set of records than the
50
+ * one on screen. Every id the dropdown can draw has its own arm, and
51
+ * `filter-builder-protocol-ids-census-9306.test.ts` (app-shell) pins each one's
52
+ * stored predicate, so a missing arm is a red test rather than a quiet `$eq`.
41
53
  */
42
54
  export declare function condToMongo(c: BuilderCondition, typeOf: (f: string) => string | undefined): Record<string, any> | null;
43
55
  /**
44
56
  * Criteria → builder condition (the reverse of {@link condToMongo}). Returning
45
57
  * `null` makes the builder refuse to load the rule ("criteria can't be
46
58
  * represented"), so this must keep accepting spellings previously written.
59
+ * The rows it produces carry the builder's protocol ids (objectui#9306); the
60
+ * stored criteria are `$`-tokens, which that change did not touch.
47
61
  * @internal
48
62
  */
49
63
  export declare function kvToCondition(field: string, v: any, idx: number): BuilderCondition | null;
@@ -51,7 +65,7 @@ export declare function kvToCondition(field: string, v: any, idx: number): Build
51
65
  * Would this criteria select EVERY record of the object?
52
66
  *
53
67
  * Mirrors the server's `isMatchAllCriteria` (objectstack `plugin-sharing`,
54
- * #3896) closely enough to warn before the round-trip: blank, `{}`, `[]`, and
68
+ * objectstack-ai/objectstack#3896) closely enough to warn before the round-trip: blank, `{}`, `[]`, and
55
69
  * the vacuous combinators. Deliberately conservative in the same direction —
56
70
  * the cost of a false positive is one extra hint, the cost of a false negative
57
71
  * is a save that fails with a toast. The server stays authoritative.
@@ -3,5 +3,46 @@ import { FieldWidgetComponentProps } from './types.js';
3
3
  /**
4
4
  * FormulaField - Read-only computed field
5
5
  * Values are computed on the backend and cannot be edited
6
+ *
7
+ * ## One value, one reading (objectui#11748)
8
+ *
9
+ * This is the read-only FORM face of a formula; `FormulaCellRenderer` in this
10
+ * package's barrel is its TABLE face. The two used to format the value each by
11
+ * itself, so one stored value read two ways: a number here was `200000` (no
12
+ * `returnType`) or `200000.00` (a `toFixed(2)`), in monospace, beside the
13
+ * cell's `200,000`; a declared boolean or date read `Yes` / `Jul 4` here and
14
+ * `true` / `2026-07-04` in the cell.
15
+ *
16
+ * Both faces now read the SAME rule and draw each type through the calls the
17
+ * matching field type's faces make, so they agree on every value:
18
+ *
19
+ * - which type: the declared `returnType`; with none, a JS number is a
20
+ * number and anything else is text. ⛔ Nothing is inferred from the
21
+ * expression: the spec's own field form says consumers read `returnType`
22
+ * "instead of re-parsing the expression".
23
+ * - number: `formatNumberFieldValue`, the one call every number face makes
24
+ * (the locale's grouping, the width `resolveFieldScale` answers for a
25
+ * declared `scale`). The value is read the way the formula cell reads it
26
+ * (objectui#11683): blank is empty, and a declared number held as a
27
+ * numeric string is a number.
28
+ * - currency: money, through the currency field's own faces (`CurrencyField`
29
+ * read-only here, `CurrencyCellRenderer` in the cell), reading the
30
+ * formula's own `currencyConfig` as they read a currency field's
31
+ * (objectui#12111).
32
+ * - boolean: the locale's word, as `BooleanField`'s read-only branch draws
33
+ * it; only a JS boolean is a boolean, as there (objectui#8593).
34
+ * - date: `formatDate`'s DEFAULT face, as `DateField`'s read-only branch
35
+ * draws it (objectui#8194).
36
+ * - text: the value as the cell prints it, in the font `TextField`'s
37
+ * read-only branch draws (objectui#12357), declared `text` or not.
38
+ *
39
+ * Both faces also share the emptiness floor (`isEmptyValue` on the coerced
40
+ * value), so an empty value reads the shared affordance in both.
41
+ *
42
+ * The rule is spelled here and in the cell, because the barrel and this lazily
43
+ * loaded widget may not import each other (the reason `number-format.ts`
44
+ * gives). `__tests__/formulaFaces.returnType-11748.test.tsx` draws every
45
+ * declared type in both faces and compares the two texts, so neither can move
46
+ * alone.
6
47
  */
7
48
  export declare function FormulaField({ value, field, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
@@ -4,9 +4,15 @@ import { FieldWidgetComponentProps } from './types.js';
4
4
  * Geolocation data structure
5
5
  */
6
6
  export interface GeolocationValue {
7
- latitude?: number;
8
- longitude?: number;
9
- accuracy?: number;
7
+ /**
8
+ * `number | null` since objectui#6848. `null` is what an emptied box emits —
9
+ * the spelling `CurrencyField` / `PercentField` / `NumberField` already used
10
+ * for the same user action — so the type has to admit it. `undefined` stays
11
+ * admissible because an untouched coordinate is genuinely absent.
12
+ */
13
+ latitude?: number | null;
14
+ longitude?: number | null;
15
+ accuracy?: number | null;
10
16
  }
11
17
  /**
12
18
  * Geolocation field widget - provides a location picker with coordinates
@@ -1,5 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  import { FieldWidgetComponentProps } from './types.js';
3
+ import { InlineGridColumn } from '@objectstack/spec/data';
3
4
  /**
4
5
  * GridField / LineItemsField — editable child-grid ("line items") widget.
5
6
  *
@@ -46,106 +47,66 @@ import { FieldWidgetComponentProps } from './types.js';
46
47
  * column. This is the renderer for the `field:grid` widget and the cell
47
48
  * engine behind the master-detail subform (see ADR-0001).
48
49
  *
49
- * Column config (a subset of `GridColumnDefinition`):
50
- * { name, label?, type?, options?, width?, required?, prefix?, step? }
51
- * type ∈ 'text' | 'number' | 'currency' | 'date' | 'datetime' | 'time'
52
- * | 'select' | 'lookup' | 'file'
50
+ * Column config: `@objectstack/spec`'s inline grid column, by reference —
51
+ * see {@link GridColumn}.
53
52
  *
54
- * Field-level config (from `GridFieldMetadata`):
55
- * columns, min_rows, max_rows, allow_add, allow_delete, total_field
53
+ * Field-level config: the keys `GridFieldMetadata` (`@object-ui/types`)
54
+ * declares, each read under that one spelling (objectui#11070 round 8), and
55
+ * no other key: `sortField`, the last key read before any face declared it,
56
+ * is declared there too (objectui#11070 round 9). objectui#11610 renamed all
57
+ * eight from snake_case to camelCase; a field still carrying a snake_case
58
+ * spelling is refused by name instead of drawn (`RetiredGridFieldKeys`).
56
59
  */
57
- export interface GridColumn {
58
- /**
59
- * The column's field name — the key it reads and writes on each row object.
60
- *
61
- * Spelled `name`, exactly as the declared `GridColumnDefinition`
62
- * (`@object-ui/types`) and the grid docs page say (objectui#3951). This
63
- * widget used to read a divergent `field` key, so metadata authored against
64
- * the published type rendered every cell empty plus a React key warning.
65
- * There is deliberately no tolerant alias bridging the retired spelling to
66
- * this one: a single spelling, enforced at the producer — AGENTS.md #0.1.
67
- *
68
- * (Wording note: do not restate that rule as an alternation expression over
69
- * the two key names. `column-identity.ratchet.test.ts` (objectui#3104) scans
70
- * these files line by line and cannot tell prose from code, so spelling the
71
- * shape out here registers as a new dual read and fails the gate.)
72
- */
73
- name: string;
74
- label?: string;
75
- /**
76
- * Cell control + read/write adapter for the column.
77
- *
78
- * `date` / `datetime` / `time` are three DISTINCT controls, not one
79
- * (objectui#3569). Collapsing `datetime` onto the `date` control did not
80
- * merely under-render it — `<input type="date">` hands back a bare
81
- * `YYYY-MM-DD` on change, so touching the day of a `datetime` cell silently
82
- * DELETED its time component from the record.
83
- */
84
- type?: 'text' | 'number' | 'currency' | 'date' | 'datetime' | 'time' | 'select' | 'lookup' | 'file';
85
- options?: Array<{
86
- label: string;
87
- value: string;
88
- }>;
89
- width?: number;
90
- required?: boolean;
91
- prefix?: string;
92
- step?: number;
93
- /** For `type: 'lookup'` — the referenced object and label/id fields. */
94
- reference?: string;
95
- displayField?: string;
96
- idField?: string;
97
- /** Multi-value column: multi-record lookup, or multi-file upload cell. */
98
- multiple?: boolean;
99
- /** For `type: 'file'` — accepted MIME types / extensions for the picker
100
- * (e.g. `['image/*', '.pdf']`). Omit to accept anything. */
101
- accept?: string[];
102
- /**
103
- * Hidden from the grid by default but revealable via the column chooser.
104
- * Set by `deriveColumns` for fields beyond the default-visible budget — the
105
- * data is NOT dropped (it's just collapsed, like Odoo's `optional` columns /
106
- * Salesforce column personalization), so business-critical fields stay
107
- * reachable. Required columns are never default-hidden.
108
- */
109
- defaultHidden?: boolean;
110
- /**
111
- * A computed (read-only) column whose value is derived live from sibling
112
- * cells via {@link expr} — e.g. an invoice line's `amount = quantity *
113
- * unit_price`. The grid renders it read-only, recomputes it as the row's
114
- * inputs change, and writes the result back into the row so it persists
115
- * (and any running total reflects it). The classic spreadsheet pattern used
116
- * by QuickBooks / Stripe / NetSuite line grids — nobody types the amount.
117
- */
118
- computed?: boolean;
119
- /** Arithmetic expression for a {@link computed} column. Supports `+ - * / %`,
120
- * parentheses, numeric literals and field refs (`record.qty` or bare `qty`). */
121
- expr?: string;
122
- /** Decimal places to round a computed numeric/currency result to. */
123
- scale?: number;
124
- /** For `type: 'lookup'` — when a record is picked, copy its fields into any
125
- * sibling columns of the same name (e.g. a product's unit_price/description).
126
- * On by default for lookup columns; set `false` to disable the auto-fill. */
127
- autofill?: boolean;
128
- /**
129
- * CEL predicate: when TRUE for this row, the cell is **read-only** (B2 field
130
- * rules, generalized to grid cells). Evaluated per row against the row as
131
- * `record` plus the header as `parent` (so a line locks when
132
- * `parent.status == 'paid'` *or* on an intra-row condition like
133
- * `record.kind == 'auto'`). Client-side UX; fails open (stays editable).
134
- */
135
- readonlyWhen?: string | {
136
- dialect?: string;
137
- source: string;
138
- };
139
- /**
140
- * CEL predicate: when TRUE for this row, the cell is **required** (flagged
141
- * inline-invalid while empty). Same `record` + `parent` scope as
142
- * {@link readonlyWhen}.
143
- */
144
- requiredWhen?: string | {
145
- dialect?: string;
146
- source: string;
147
- };
148
- }
60
+ /**
61
+ * One grid column — `@objectstack/spec`'s inline grid column
62
+ * (`InlineGridColumn`, the element of `FieldSchema.inlineColumns`), BY
63
+ * REFERENCE (objectui#11070). The spec declares that shape as the strict
64
+ * mirror of this widget's column, so the widget's type IS the spec's: one
65
+ * declaration, the same one `GridFieldMetadata.columns` and
66
+ * `FormField.columns` (`@object-ui/types`) carry. This widget reads each
67
+ * column by exactly the spec's keys — every key below is one the spec
68
+ * declares, and there is no second spelling of any of them.
69
+ *
70
+ * How the widget reads the keys (the spec's own descriptions say what each
71
+ * MEANS; these are the renderer's notes):
72
+ *
73
+ * - `name` — the key a column reads and writes on each row object. There is
74
+ * deliberately no tolerant alias bridging the spelling objectui#3951
75
+ * retired to this one: a single spelling, enforced at the producer —
76
+ * AGENTS.md #0.1 (the spec refuses the retired spelling by name).
77
+ * (Wording note: do not restate that rule as an alternation expression over
78
+ * the two key names. `column-identity.ratchet.test.ts` (objectui#3104) scans
79
+ * these files line by line and cannot tell prose from code, so spelling the
80
+ * shape out here registers as a new dual read and fails the gate.)
81
+ * - `type` — the cell control and its read/write adapter. `date` /
82
+ * `datetime` / `time` are three DISTINCT controls, not one
83
+ * (objectui#3569): `<input type="date">` hands back a bare `YYYY-MM-DD` on
84
+ * change, so collapsing `datetime` onto it silently DELETED the time
85
+ * component from the record.
86
+ * - `prefix` — a symbol shown in a `currency` cell IN PLACE OF the resolved
87
+ * currency's own symbol. When absent, the cell shows the symbol of the
88
+ * currency it resolves (objectui#10355); there is no default symbol.
89
+ * - `defaultHidden` — collapsed into the column chooser, not dropped
90
+ * (`deriveColumns` sets it beyond the default-visible budget). Required
91
+ * columns are never default-hidden.
92
+ * - `computed` + `expr` — a read-only column recomputed live from sibling
93
+ * cells by this file's own safe arithmetic evaluator (`+ - * / %`,
94
+ * parentheses, numeric literals, `record.qty` or bare `qty`), and written
95
+ * back into the row so it persists and any running total reflects it.
96
+ * - `scale` — decimal places for a computed `number` result. ⛔ Not read on
97
+ * a `currency` column (objectui#10783): the resolved currency's ISO 4217
98
+ * minor unit decides both the stored and the shown width
99
+ * (`currencyWidth`), and the spec refuses `scale` on a column declaring
100
+ * `type: 'currency'`. A column that takes `currency` from its child field
101
+ * at render time is reported by `hydrateColumns` in
102
+ * `@object-ui/plugin-form` instead of being read in silence.
103
+ * - `autofill` — for a `lookup` column: picking a record copies its fields
104
+ * into sibling columns of the same name. On unless set `false`.
105
+ * - `readonlyWhen` / `requiredWhen` — CEL predicates evaluated per row
106
+ * against the row as `record` plus the header as `parent`. Client-side UX;
107
+ * a predicate that faults fails open.
108
+ */
109
+ export type GridColumn = InlineGridColumn;
149
110
  type Row = Record<string, any>;
150
111
  /** Evaluate an arithmetic `expr` against `row`. null when blank/unparseable. */
151
112
  export declare function evalArith(expr: string, row: Row): number | null;
@@ -163,10 +124,15 @@ export declare function evalArith(expr: string, row: Row): number | null;
163
124
  * it is unit-testable independent of the picker UI.
164
125
  */
165
126
  export declare function lookupAutofillPatch(columns: GridColumn[], col: GridColumn, record: any): Row;
166
- export declare function computeRow(columns: GridColumn[], row: Row): Row;
127
+ /**
128
+ * @param tenantCurrency the tenant's default currency (`useLocalization()`),
129
+ * the resolver's last step for a `currency` column — see
130
+ * {@link storedFractionScale}.
131
+ */
132
+ export declare function computeRow(columns: GridColumn[], row: Row, tenantCurrency?: string): Row;
167
133
  /** Sum a numeric column across rows (ignoring blanks/NaN). */
168
134
  export declare function sumColumn(rows: Row[], field: string): number;
169
- export declare function GridField({ value, onChange, field, readonly, disabled, className, error, onRowExpand, displayMode, onAdd, ...props }: FieldWidgetComponentProps<Row[]> & {
135
+ type GridFieldProps = FieldWidgetComponentProps<Row[]> & {
170
136
  /** When provided, each row shows an "expand" button that opens the row in a
171
137
  * full form (the host — e.g. MasterDetailForm — renders the drawer/modal and
172
138
  * writes the edited values back). Lets a "fat" child be edited in a real form
@@ -183,7 +149,13 @@ export declare function GridField({ value, onChange, field, readonly, disabled,
183
149
  * `readonlyWhen` / `requiredWhen` CEL predicate — so a line cell can react to
184
150
  * the header (`parent.status == 'paid'`). Supplied by MasterDetailForm. */
185
151
  contextRecord?: Record<string, unknown>;
186
- }): React.JSX.Element;
152
+ };
153
+ /**
154
+ * The `grid` field widget. A field whose metadata carries one of the retired
155
+ * snake_case keys is refused by name ({@link RetiredGridFieldKeys}); any other
156
+ * field draws the grid.
157
+ */
158
+ export declare function GridField(props: GridFieldProps): React.JSX.Element;
187
159
  /** Semantic alias — the master-detail subform's child grid. */
188
160
  export declare const LineItemsField: typeof GridField;
189
161
  export {};