@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.
- package/CHANGELOG.md +6228 -0
- package/README.md +251 -9
- package/dist/FieldEditWidget.d.ts +54 -6
- package/dist/coerceToSafeValue.d.ts +54 -0
- package/dist/currency.d.ts +1 -1
- package/dist/fieldHelpText.d.ts +41 -0
- package/dist/index.cjs +5 -5
- package/dist/index.css +68 -5
- package/dist/index.d.ts +446 -69
- package/dist/index.js +5326 -3443
- package/dist/resolveActionParams.d.ts +259 -0
- package/dist/widgets/AvatarField.d.ts +19 -3
- package/dist/widgets/BooleanField.d.ts +1 -1
- package/dist/widgets/DateField.d.ts +1 -1
- package/dist/widgets/DateTimeField.d.ts +1 -1
- package/dist/widgets/FilterConditionField.d.ts +21 -7
- package/dist/widgets/FormulaField.d.ts +41 -0
- package/dist/widgets/GeolocationField.d.ts +9 -3
- package/dist/widgets/GridField.d.ts +73 -101
- package/dist/widgets/LocationField.d.ts +30 -3
- package/dist/widgets/LookupField.d.ts +8 -0
- package/dist/widgets/NumberField.d.ts +1 -1
- package/dist/widgets/ObjectField.d.ts +9 -2
- package/dist/widgets/OptionsEmptyState.d.ts +29 -5
- package/dist/widgets/PasswordField.d.ts +14 -0
- package/dist/widgets/PeoplePicker.d.ts +17 -2
- package/dist/widgets/PercentField.d.ts +2 -1
- package/dist/widgets/PersonRow.d.ts +13 -2
- package/dist/widgets/RecipientPickerField.d.ts +75 -0
- package/dist/widgets/RecordPickerDialog.d.ts +31 -12
- package/dist/widgets/RegistryRefField.d.ts +77 -0
- package/dist/widgets/RichTextField.d.ts +1 -1
- package/dist/widgets/SelectionTray.d.ts +13 -2
- package/dist/widgets/SummaryField.d.ts +30 -0
- package/dist/widgets/TextField.d.ts +1 -1
- package/dist/widgets/TimeField.d.ts +1 -1
- package/dist/widgets/booleanValueLabel.d.ts +18 -0
- package/dist/widgets/date-picker/DatePickerField.d.ts +40 -0
- package/dist/widgets/date-picker/localeDateText.d.ts +39 -0
- package/dist/widgets/deriveLookupColumns.d.ts +1 -1
- package/dist/widgets/file-affordance.d.ts +63 -0
- package/dist/widgets/file-refused.d.ts +17 -0
- package/dist/widgets/file-size-guard.d.ts +5 -23
- package/dist/widgets/file-value.d.ts +49 -9
- package/dist/widgets/longTextCarrierHandoff.d.ts +35 -0
- package/dist/widgets/lookupColumnDisplay.d.ts +26 -9
- package/dist/widgets/nativeDateValue.d.ts +9 -27
- package/dist/widgets/number-format.d.ts +137 -0
- package/dist/widgets/numberBadInput.d.ts +135 -0
- package/dist/widgets/objectRefLabel.d.ts +16 -0
- package/dist/widgets/personDisplay.d.ts +22 -4
- package/dist/widgets/recipientKinds.d.ts +85 -0
- package/dist/widgets/richTextDisplay.d.ts +32 -13
- package/dist/widgets/searchOffer.d.ts +8 -0
- package/dist/widgets/toDomProps.d.ts +26 -4
- package/dist/widgets/toHostGroupProps.d.ts +53 -0
- package/dist/widgets/toHostProps.d.ts +105 -0
- package/dist/widgets/types.d.ts +11 -3
- package/dist/widgets/upload-error-message.d.ts +6 -0
- package/dist/widgets/uploadingScope.d.ts +142 -0
- package/dist/widgets/useCascadingOptions.d.ts +3 -2
- package/dist/widgets/useRecordQuery.d.ts +17 -2
- package/dist/widgets/useUploadingSignal.d.ts +8 -0
- package/package.json +11 -12
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ The standard field library and registry for Object UI.
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- 📚 **Standard Fields** - Implementation of all ObjectStack protocol fields (Text, Number, Date, Lookup, etc.)
|
|
8
|
-
- 🔌 **Plugin System** - `
|
|
8
|
+
- 🔌 **Plugin System** - `registerFieldRenderer` registers custom renderers, or overrides standard ones.
|
|
9
9
|
- 🛠 **Helpers** - Utilities for schema mapping, validation, and expression evaluation.
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
@@ -23,10 +23,15 @@ The Field Registry is the core mechanism that allows decoupling view components
|
|
|
23
23
|
You can override standard fields or add new ones:
|
|
24
24
|
|
|
25
25
|
```tsx
|
|
26
|
-
import { registerFieldRenderer } from '@object-ui/fields';
|
|
27
|
-
import {
|
|
26
|
+
import { registerFieldRenderer, type CellRendererProps } from '@object-ui/fields';
|
|
27
|
+
import type { FC } from 'react';
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
// Your own renderer — anywhere in your app; it takes the standard props bag.
|
|
30
|
+
declare const MyCustomColorPicker: FC<CellRendererProps>;
|
|
31
|
+
|
|
32
|
+
// ⚠️ `color` is a SHIPPED type (`ColorSwatchCellRenderer`), and `getCellRenderer`
|
|
33
|
+
// reads the registry BEFORE the standard map — so this call OVERRIDES the
|
|
34
|
+
// built-in renderer. To ADD a type instead, register a name nothing ships.
|
|
30
35
|
registerFieldRenderer('color', MyCustomColorPicker);
|
|
31
36
|
```
|
|
32
37
|
|
|
@@ -35,14 +40,33 @@ registerFieldRenderer('color', MyCustomColorPicker);
|
|
|
35
40
|
View components use `getCellRenderer` to resolve the correct component for a field type.
|
|
36
41
|
|
|
37
42
|
```tsx
|
|
38
|
-
import { getCellRenderer } from '@object-ui/fields';
|
|
39
|
-
|
|
40
|
-
const MyGridCell = ({ field, value }) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
import { getCellRenderer, resolveCellRendererType, type CellRendererProps } from '@object-ui/fields';
|
|
44
|
+
|
|
45
|
+
const MyGridCell = ({ field, value, row }: CellRendererProps & { row: Record<string, unknown> }) => {
|
|
46
|
+
// Resolve the renderer KEY first, then the renderer. A field's declared
|
|
47
|
+
// `type` is not always the renderer's key: a textual field carrying a
|
|
48
|
+
// format hint (`Field.text({ format: 'phone' })`) resolves to the richer
|
|
49
|
+
// renderer, and one declaring a widget with a read cell of its own
|
|
50
|
+
// (`widget: 'object-ref'`, `widget: 'recipient-picker'`) resolves to that
|
|
51
|
+
// cell. Passing `field.type` raw skips that mapping and draws such a
|
|
52
|
+
// column as bare text, silently. Resolving first is never worse: with no
|
|
53
|
+
// hint the resolver returns the declared `type` unchanged.
|
|
54
|
+
const Renderer = getCellRenderer(resolveCellRendererType(field));
|
|
55
|
+
// `dependentValues` is the row, for a cell whose value only means something
|
|
56
|
+
// beside a sibling column: a `recipient-picker` face reads the row's
|
|
57
|
+
// `recipient_type` from it. Without it, that face draws the stored text.
|
|
58
|
+
return <Renderer field={field} value={value} dependentValues={row} />;
|
|
43
59
|
};
|
|
44
60
|
```
|
|
45
61
|
|
|
62
|
+
This package registers the `object-ref` and `recipient-picker` keys with the stored
|
|
63
|
+
text as their default face. A host that can read the referenced lists registers faces
|
|
64
|
+
that name the value under the same keys (`registerFieldRenderer`); the console's live in
|
|
65
|
+
`@object-ui/app-shell`. They name a value as the edit widgets do, through the kind table
|
|
66
|
+
and the spellings this package publishes for it: `RECIPIENT_RECORD_KINDS`,
|
|
67
|
+
`RECIPIENT_REGISTRY_KINDS`, `FIELD_RECIPIENT_TYPE`, `recipientCandidateLabel`,
|
|
68
|
+
`objectRefOptionLabel` and the shared `UnresolvedReferenceMark` drawing.
|
|
69
|
+
|
|
46
70
|
## Standard Field Types
|
|
47
71
|
|
|
48
72
|
Supported types out of the box:
|
|
@@ -55,6 +79,33 @@ Supported types out of the box:
|
|
|
55
79
|
- **Media**: `file`, `image`
|
|
56
80
|
- **System**: `formula`, `summary`, `auto_number`
|
|
57
81
|
|
|
82
|
+
### `type="number"` widgets: what is announced and what is not
|
|
83
|
+
|
|
84
|
+
`NumberField`, `CurrencyField`, `PercentField` and `GeolocationField` all render
|
|
85
|
+
a native `type="number"` input, so the **browser** decides what the box accepts.
|
|
86
|
+
They share one reading of that, in `widgets/numberBadInput.tsx`:
|
|
87
|
+
|
|
88
|
+
- **Announced.** When the browser reports `validity.badInput` — the box is
|
|
89
|
+
holding text it cannot convert, e.g. a typed `1e`, which Chromium keeps
|
|
90
|
+
DISPLAYING while `.value` reads `''` — the control is marked `aria-invalid`
|
|
91
|
+
and draws a `Not saved: …` message, reusing objectui#6716's refusal shape.
|
|
92
|
+
Both a change arm and a blur arm are wired, because pasting into an empty box
|
|
93
|
+
never moves `.value` and so fires no React change event at all. The sentence
|
|
94
|
+
resolves through the `fields.number.badInput` locale key (objectui#8148), so
|
|
95
|
+
it follows the reader's language; the example numeral each box quotes is the
|
|
96
|
+
widget's own and is interpolated rather than translated.
|
|
97
|
+
- ⚠️ **Not announced, and not announceable.** Entries the browser silently
|
|
98
|
+
**truncates**: `1.2.3` stores `1.23`, `0x10` stores `10`. The characters are
|
|
99
|
+
discarded as they arrive, before any handler here runs, so no widget-side
|
|
100
|
+
guard can refuse them. Recovering them would mean abandoning `type="number"`
|
|
101
|
+
and with it the mobile numeric keyboard and the `min`/`max`/`step` spinner
|
|
102
|
+
(objectui#2572).
|
|
103
|
+
|
|
104
|
+
⛔ Silence therefore means "the browser read *something*", never "the value is
|
|
105
|
+
correct". User-facing wording lives in
|
|
106
|
+
[the fields guide](../../content/docs/guide/fields.md). The measured browser vs
|
|
107
|
+
happy-dom matrix is in `src/__tests__/numberInputBrowserReadings.ts`.
|
|
108
|
+
|
|
58
109
|
### Rendering form field widgets outside the form
|
|
59
110
|
|
|
60
111
|
The full widget surface is exported for consumers that render field widgets
|
|
@@ -72,6 +123,178 @@ The app-shell `ActionParamDialog` uses these to render declared action params
|
|
|
72
123
|
through the exact same widgets as the object form — with a drift test pinning
|
|
73
124
|
param support ⊇ form support.
|
|
74
125
|
|
|
126
|
+
### Resolving declared action params
|
|
127
|
+
|
|
128
|
+
An action param is declared either inline (`name` + `type`) or field-backed
|
|
129
|
+
(`field`, plus `objectOverride` when the field lives on another object). Before
|
|
130
|
+
a dialog can render one, it has to be resolved against the object's field
|
|
131
|
+
definitions. This package holds the one resolver for that, shared by the
|
|
132
|
+
record page's action dialog (`@object-ui/app-shell`) and the selection bar's
|
|
133
|
+
bulk dialog (`@object-ui/plugin-grid`):
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import {
|
|
137
|
+
resolveActionParams,
|
|
138
|
+
withKnownObjects,
|
|
139
|
+
type RawActionParam,
|
|
140
|
+
type ResolveActionParamsContext,
|
|
141
|
+
} from '@object-ui/fields';
|
|
142
|
+
|
|
143
|
+
type KnownObject = ResolveActionParamsContext['objects'][number];
|
|
144
|
+
|
|
145
|
+
// The object definitions the caller renders, and the ones the session's
|
|
146
|
+
// metadata store holds (`useMetadata().objects` from `@object-ui/react`).
|
|
147
|
+
declare const callerObjects: KnownObject[];
|
|
148
|
+
declare const storeObjects: KnownObject[];
|
|
149
|
+
|
|
150
|
+
const params: RawActionParam[] = [
|
|
151
|
+
{ field: 'crm_campaign', objectOverride: 'crm_campaign_member' }, // field-backed
|
|
152
|
+
{ name: 'note', type: 'textarea', label: 'Note' }, // inline
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
const resolved = resolveActionParams(params, {
|
|
156
|
+
objectName: 'crm_lead', // the owner when a param names no objectOverride
|
|
157
|
+
objects: withKnownObjects(callerObjects, storeObjects), // the caller's defs win by name
|
|
158
|
+
fieldLabel: (_objectName, _fieldName, fallback) => fallback,
|
|
159
|
+
locale: 'en',
|
|
160
|
+
});
|
|
161
|
+
// resolved[0] takes its type, label and `referenceTo` from the
|
|
162
|
+
// `crm_campaign_member.crm_campaign` field definition; resolved[1] is as declared.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
- `resolveActionParam(param, ctx)` / `resolveActionParams(params, ctx)` — a
|
|
166
|
+
field-backed param inherits the field's type, label, options, placeholder,
|
|
167
|
+
help text, default and picker config. Inline keys on the param override the
|
|
168
|
+
field. A param whose field is not in `ctx.objects` comes back with
|
|
169
|
+
`unresolvedField: '<object>.<field>'`, and both dialogs refuse it rather than
|
|
170
|
+
drawing an input.
|
|
171
|
+
- `withKnownObjects(supplied, stored)` — unions two object lists by `name`,
|
|
172
|
+
the caller's first.
|
|
173
|
+
- `resolveParamWidgetType(type)` — the param-type fold (`checkbox`,
|
|
174
|
+
`reference`, `datetime-local` aliases) onto `resolveFormWidgetType`.
|
|
175
|
+
- `RESOLVED_ONLY_PARAM_KEYS` — keys of the resolved shape an author may not
|
|
176
|
+
write on a param (`referenceTo`, `displayField`, …), each with the fix the
|
|
177
|
+
resolver's dev warning prints.
|
|
178
|
+
- Types: `RawActionParam`, `RawActionParamOption`, `ResolveActionParamsContext`.
|
|
179
|
+
|
|
180
|
+
### A field's help line
|
|
181
|
+
|
|
182
|
+
`fieldHelpText(field)` is the one read of the help line a field draws under its
|
|
183
|
+
control. The record form (`@object-ui/plugin-form`) and a field-backed action
|
|
184
|
+
param both use it, so the two cannot disagree:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { fieldHelpText } from '@object-ui/fields';
|
|
188
|
+
|
|
189
|
+
fieldHelpText({ inlineHelpText: 'Shown under the control', description: 'About the field' });
|
|
190
|
+
// → 'Shown under the control'
|
|
191
|
+
fieldHelpText({ inlineHelpText: '', description: 'About the field' });
|
|
192
|
+
// → 'About the field' (an empty string reads as absent)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
It reads `@objectstack/spec`'s `inlineHelpText`, then `description`. It does
|
|
196
|
+
not read `help`: the spec refuses that key on a field, and an objectstack
|
|
197
|
+
server on spec 17.7.0 or later serves a field's translated help text on
|
|
198
|
+
`description`.
|
|
199
|
+
|
|
200
|
+
### Enumerating the CELL renderer registry
|
|
201
|
+
|
|
202
|
+
`listCellRendererTypes()` is the read-side twin of `FORM_FIELD_TYPES`: every
|
|
203
|
+
field type `getCellRenderer` resolves to a renderer **of its own**, as opposed
|
|
204
|
+
to the `TextCellRenderer` fallback every other spelling lands on.
|
|
205
|
+
|
|
206
|
+
⚠️ It is a **function**, not a frozen constant, and the difference is
|
|
207
|
+
load-bearing. `registerFieldRenderer` is published, so the cell registry can
|
|
208
|
+
grow after this module is evaluated; a constant would be a snapshot taken at
|
|
209
|
+
import time. Consumers that reason about "all registered cell types" — the two
|
|
210
|
+
censuses in `@object-ui/fields` and `@object-ui/plugin-detail` that measure what
|
|
211
|
+
every type draws — reconcile their tables against this reading so a newly
|
|
212
|
+
registered type fails them by name instead of slipping past a frozen
|
|
213
|
+
population (objectui#8734).
|
|
214
|
+
|
|
215
|
+
### Asking whether a cell is masked
|
|
216
|
+
|
|
217
|
+
`isMaskedFieldType(type)` answers whether a cell of that field type is drawn
|
|
218
|
+
as a mask (`••••••`) instead of its value. It is the read-side twin of
|
|
219
|
+
`isInlineExcludedFieldType()`, and a consumer that acts on a cell's value asks
|
|
220
|
+
it rather than keeping its own list. The detail page's copy affordance in
|
|
221
|
+
`@object-ui/plugin-detail` is one such consumer: it refuses to copy a masked
|
|
222
|
+
row (objectui#8686). `MASKED_FIELD_TYPES` is the declared set behind it,
|
|
223
|
+
`password` and `secret`. The standard cell of each member draws the mask, and
|
|
224
|
+
the predicate answers `true` for it. Like `listCellRendererTypes()`, the
|
|
225
|
+
answer is a live reading of the cell registry taken when you call it, and,
|
|
226
|
+
with nothing overridden at runtime, it agrees with what `getCellRenderer`
|
|
227
|
+
resolves for every type that function lists. To add a masked type, register
|
|
228
|
+
the package's own mask under it with
|
|
229
|
+
`registerFieldRenderer('api_token', getCellRenderer('password'))`, while
|
|
230
|
+
`password` still resolves to the shipped mask. The predicate then answers
|
|
231
|
+
`true` with no change to the declared set, and the detail page refuses to copy
|
|
232
|
+
that row. Overriding a declared type with one of this package's own renderers
|
|
233
|
+
makes the predicate answer `false`, since none of them is the mask, and the
|
|
234
|
+
cell draws what that renderer draws: after
|
|
235
|
+
`registerFieldRenderer('password', TextCellRenderer)` the cell shows the value
|
|
236
|
+
and the detail row copies again. Overriding a declared type with a component
|
|
237
|
+
of your own keeps the declared answer, `true`, because the package cannot
|
|
238
|
+
tell whether an opaque component hides the value, so it errs toward
|
|
239
|
+
withholding it. Your own component under an undeclared type answers `false`.
|
|
240
|
+
Spellings are matched raw, the way `getCellRenderer` looks them up:
|
|
241
|
+
`field:password` renders in the clear and is not masked.
|
|
242
|
+
|
|
243
|
+
The mask itself is exported as `MaskedCellRenderer` (objectui#10657). A table
|
|
244
|
+
that cannot type a column yet, because its object's field types are still
|
|
245
|
+
loading or their read failed, draws that column WITHHELD with it rather than as
|
|
246
|
+
text: `object-grid`, the related list and `object-data-table` do. It is the
|
|
247
|
+
shipped mask, not whatever is registered for `password` at the time, and its
|
|
248
|
+
`field` prop is optional because it reads only whether a value is present.
|
|
249
|
+
`registerFieldRenderer('api_token', MaskedCellRenderer)` masks a type the same
|
|
250
|
+
way the `getCellRenderer('password')` form above does.
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
import { MaskedCellRenderer } from '@object-ui/fields';
|
|
254
|
+
|
|
255
|
+
const withheldCell = (value: unknown) => <MaskedCellRenderer value={value} />;
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Polymorphic pointer pairs (`referenceVia`)
|
|
259
|
+
|
|
260
|
+
A `text` field that declares `@objectstack/spec`'s `referenceVia` is the id half
|
|
261
|
+
of a two-column pointer pair: its value is a record id of the object that the
|
|
262
|
+
sibling column it names holds, on the same row (`sys_approval_request.record_id`
|
|
263
|
+
with `referenceVia: 'object_name'`, or `sys_activity.source_id` with
|
|
264
|
+
`referenceVia: 'source_object'`). The target changes per row, and a cell
|
|
265
|
+
renderer gets one value and no row, so the pair is resolved by the caller that
|
|
266
|
+
holds the row (objectui#12045):
|
|
267
|
+
|
|
268
|
+
- `resolveRecordPointer(field, row, optionsOf?)` is the one place the rule
|
|
269
|
+
lives. It returns `{ objectName, recordId }` (a `RecordPointer`) for a `text`
|
|
270
|
+
field with a non-empty `referenceVia` whose row fills both halves, and `null`
|
|
271
|
+
otherwise.
|
|
272
|
+
- When the field also declares `referenceDeletedWhen: { field, value }` (the
|
|
273
|
+
platform's assertion that it deleted the target, objectui#12489) and the row's
|
|
274
|
+
sibling `field` holds `value`, the pair carries `deletedLabel`: that option's
|
|
275
|
+
label from `optionsOf(field)`, the options the caller draws `select` cells
|
|
276
|
+
with. A face draws it as a tombstone and reads and links nothing; the default
|
|
277
|
+
face draws it in place of the record id.
|
|
278
|
+
- `RECORD_POINTER_CARD_TYPE` is the registry key a resolved pair is drawn with,
|
|
279
|
+
the pair as the renderer's `value`. This package registers a default under it
|
|
280
|
+
that draws the record id as text, so nothing changes for a host that does not
|
|
281
|
+
register a face. `@object-ui/app-shell` registers its record preview card
|
|
282
|
+
there.
|
|
283
|
+
|
|
284
|
+
The record details grid in `@object-ui/plugin-detail` is the call site today;
|
|
285
|
+
list cells are not one yet.
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { getCellRenderer, resolveRecordPointer, RECORD_POINTER_CARD_TYPE } from '@object-ui/fields';
|
|
289
|
+
|
|
290
|
+
function PointerValue({ field, row }: { field: { name: string; type: string; referenceVia?: string }; row: Record<string, unknown> }) {
|
|
291
|
+
const pointer = resolveRecordPointer(field, row);
|
|
292
|
+
if (!pointer) return <>{String(row[field.name] ?? '')}</>;
|
|
293
|
+
const Face = getCellRenderer(RECORD_POINTER_CARD_TYPE);
|
|
294
|
+
return <Face value={pointer} field={{ name: field.name, type: 'text' }} />;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
75
298
|
### File uploads in line-item grids
|
|
76
299
|
|
|
77
300
|
`GridField` (the master-detail line-items grid) supports `type: 'file'` columns:
|
|
@@ -83,6 +306,21 @@ or photo per row without opening the row form (objectui#2360). Columns accept
|
|
|
83
306
|
exported as `FileCell`). Auto-derived subform columns map `file`/`image`/
|
|
84
307
|
`avatar` fields to file columns instead of dropping them.
|
|
85
308
|
|
|
309
|
+
### Registry-name pickers (`position-ref`, `permission-ref`)
|
|
310
|
+
|
|
311
|
+
A `text` field that stores the machine name of one registry item declares
|
|
312
|
+
`widget: 'position-ref'` or `widget: 'permission-ref'` (resolved as
|
|
313
|
+
`field:<widget>`). The widget lists that registry through the data source's
|
|
314
|
+
`listMetadataItems(type)`, shows each item's label beside its name, and writes
|
|
315
|
+
the name only. A stored name the loaded registry lacks stays, flagged *not
|
|
316
|
+
found*. `position-ref` leaves out the audience anchors (the spec's
|
|
317
|
+
`AUDIENCE_ANCHOR_POSITIONS`: `everyone`, `guest`), because the server refuses
|
|
318
|
+
an anchor as a grant target; a stored anchor still shows as its own option.
|
|
319
|
+
When the adapter cannot list a registry, the list is empty, or the
|
|
320
|
+
load fails, it is a text box that takes the typed name (a failure is shown with
|
|
321
|
+
its cause). See
|
|
322
|
+
[`content/docs/guide/fields.md`](../../content/docs/guide/fields.md).
|
|
323
|
+
|
|
86
324
|
### Multi-value selects
|
|
87
325
|
|
|
88
326
|
A `select` field declared `multiple: true` selects zero-or-more values (spec
|
|
@@ -111,6 +349,10 @@ never drift). While a `dependsOn` parent is empty the control is gated; a parent
|
|
|
111
349
|
change re-filters the list and clears a now-invalid value (scalar `select` /
|
|
112
350
|
`radio` drop the value; multi-value `multiselect` / `checkboxes` prune just the
|
|
113
351
|
offered-out entries).
|
|
352
|
+
An option whose `label` is blank (the empty string is a legal label; an absent one is
|
|
353
|
+
not) displays its `value` instead of an empty row, on the editable and the read-only
|
|
354
|
+
path alike — all eight read sites across the four widgets share the one
|
|
355
|
+
`optionDisplayLabel` helper in `@object-ui/core` (objectui#9230).
|
|
114
356
|
Client-side hiding is UX only — gate authorization-sensitive values on the
|
|
115
357
|
server too. See
|
|
116
358
|
[`content/docs/fields/select.mdx`](../../content/docs/fields/select.mdx).
|
|
@@ -39,10 +39,58 @@ export declare function isInlineExcludedFieldType(type: string | undefined): boo
|
|
|
39
39
|
* `null` for types without a registered widget so the caller can fall back to
|
|
40
40
|
* a plain editor.
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
42
|
+
* The host's DOM pass-through set is forwarded WHOLE (objectui#6909). This
|
|
43
|
+
* component's props are `FieldWidgetComponentProps`, so a caller may already
|
|
44
|
+
* pass `id`, `name`, `autoFocus`, `tabIndex`, `onBlur`, `onFocus`, `onClick`,
|
|
45
|
+
* any `aria-*` and any `data-*` with no type error — but the body used to
|
|
46
|
+
* destructure five keys and render the widget with those, so `autoFocus` was
|
|
47
|
+
* the ONLY survivor of the whole block and everything else was silently
|
|
48
|
+
* dropped. That is this package's own first-class defect class, named in
|
|
49
|
+
* `widgets/toDomProps.ts`: a key that type-checks, reads as supported, and
|
|
50
|
+
* silently never reaches the element (objectui#3290's `aria-required`,
|
|
51
|
+
* objectui#3222's validation slot). Forwarding is not a widening: the keys were
|
|
52
|
+
* already declared, and each widget still re-filters through its own
|
|
53
|
+
* `toDomProps` before anything reaches a DOM element.
|
|
54
|
+
*
|
|
55
|
+
* `autoFocus`' original reason survives inside that set — an inline-edit host
|
|
56
|
+
* enters edit mode ON a field and expects the caret to land there
|
|
57
|
+
* (objectui#4220, the detail page's delegation) — and so does its property:
|
|
58
|
+
* each widget's own whitelist carries these onto the real focusable control, so
|
|
59
|
+
* nothing here needs to know which element that is. A host that passes nothing
|
|
60
|
+
* is unaffected.
|
|
61
|
+
*
|
|
62
|
+
* The host's NON-DOM set is forwarded WHOLE too (`f08bcd9af`), through the
|
|
63
|
+
* sibling executor `toHostProps`. The DOM fix left the other half of the
|
|
64
|
+
* contract undelivered: `error`, `onUploadingChange` and the "Host plumbing"
|
|
65
|
+
* block (`dataSource`, `dependentValues`, `dependsOn`, `dependsOnLabels`,
|
|
66
|
+
* `emptyHint`, `onSelectRecord`, `onCreateNew`) still type-checked, read as
|
|
67
|
+
* supported, and never reached the widget. `error` was the live one:
|
|
68
|
+
* `InlineFieldInput` has passed `error={error}` since PR #7109 and this factory
|
|
69
|
+
* dropped it, so a control that had failed validation never reported
|
|
70
|
+
* `aria-invalid`. Forwarding is not a widening — every one of those keys is
|
|
71
|
+
* already declared on `FieldWidgetComponentProps`, the same argument #7009
|
|
72
|
+
* landed on in this file.
|
|
73
|
+
*
|
|
74
|
+
* ⛔ They do NOT go through `toDomProps`. None of them is DOM-legal, and that
|
|
75
|
+
* whitelist is closed for exactly this reason — a `dataSource` adapter routed
|
|
76
|
+
* there becomes `dataSource="[object Object]"` on an `<input>`, the leak the
|
|
77
|
+
* helper exists to prevent. `toHostProps`' direction-3 assertion makes the two
|
|
78
|
+
* sets provably disjoint, so the order of the two spreads below is not a
|
|
79
|
+
* question anyone has to answer again.
|
|
80
|
+
*
|
|
81
|
+
* ## `dataSource` precedence: the explicit prop WINS
|
|
82
|
+
*
|
|
83
|
+
* Delivering `dataSource` can change behaviour where before it could not
|
|
84
|
+
* arrive, because the relational widgets fall back to `SchemaRendererContext`
|
|
85
|
+
* (which the grid already provides). The precedence is therefore STATED rather
|
|
86
|
+
* than left to emerge: **a host's explicit `dataSource` prop wins over the
|
|
87
|
+
* context**. That is not a new decision — `LookupField` already resolves
|
|
88
|
+
* `props.dataSource ?? lookupField?.dataSource ?? fieldMeta?.dataSource ??
|
|
89
|
+
* contextDataSource` and documents that order on the line that does it. This
|
|
90
|
+
* factory is a CONDUIT and resolves nothing: adding a resolution here would
|
|
91
|
+
* give `dataSource` a second author, the `field || schema` shape objectui#3233
|
|
92
|
+
* removed. A host that passes no `dataSource` keeps reading the context exactly
|
|
93
|
+
* as before, so no in-repo host changes behaviour. The full per-key precedence
|
|
94
|
+
* table lives on `toHostProps`, next to the list it governs.
|
|
47
95
|
*/
|
|
48
|
-
export declare function FieldEditWidget(
|
|
96
|
+
export declare function FieldEditWidget(props: FieldWidgetComponentProps<any>): React.ReactElement | null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* `coerceToSafeValue` — the one answer this package has to "what text does a
|
|
10
|
+
* string-typed cell draw for a value that is not a string?".
|
|
11
|
+
*
|
|
12
|
+
* ## Why this is its own module (objectui#8580)
|
|
13
|
+
*
|
|
14
|
+
* It used to be defined in `./index.tsx`, the barrel, and every text-like cell
|
|
15
|
+
* renderer there reaches it. The rich-content renderers in
|
|
16
|
+
* `./widgets/richTextDisplay.tsx` need the SAME answer — `@objectstack/spec`
|
|
17
|
+
* places `markdown` / `html` / `richtext` in the same value class as `text` /
|
|
18
|
+
* `textarea` / `code` (`STRING_VALUE_TYPES`, "Value is a plain string"), and
|
|
19
|
+
* before objectui#8580 they had a private one: `String(value)`, which drew a
|
|
20
|
+
* childless container for `[]` and the literal `[object Object]` for `{}`
|
|
21
|
+
* where `text` draws the No-value affordance and `[Object]`. That module is
|
|
22
|
+
* imported BY the barrel (`4bb940b6e` extracted it so `RichTextField` could
|
|
23
|
+
* reach the display pipelines without importing the barrel back), so it
|
|
24
|
+
* cannot import the barrel to borrow this helper without re-creating exactly
|
|
25
|
+
* the cycle that extraction removed. The helper therefore lives below both
|
|
26
|
+
* consumers. The barrel re-exports it unchanged; it is part of this package's
|
|
27
|
+
* published surface and nothing about its contract moved.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Coerce a value to a safe primitive for rendering.
|
|
31
|
+
* Handles MongoDB wrapper types ($numberDecimal, $oid, $date), expanded
|
|
32
|
+
* reference objects, and arrays so that no raw object is ever passed as
|
|
33
|
+
* a React child — preventing React error #310.
|
|
34
|
+
*
|
|
35
|
+
* A STRING is returned verbatim, whatever its shape. This helper is reached by
|
|
36
|
+
* every text-like cell (`text`, `textarea`, `code`, `time`, `auto_number`,
|
|
37
|
+
* `qrcode` all register to `TextCellRenderer`), so it may not classify a value
|
|
38
|
+
* by looking at its characters. It used to: any string starting `{`/`[` and
|
|
39
|
+
* ending `}`/`]` was `JSON.parse`d and the result run through the
|
|
40
|
+
* reference-label extraction below, which answers `[Object]` for an object
|
|
41
|
+
* carrying no name/label/externalId/id. That turned the showcase Field Zoo's
|
|
42
|
+
* `code` value `{"ok": true}` into the literal `[Object]`, and `[1, 2, 3]` in a
|
|
43
|
+
* text field into `1, 2, 3` (objectui#7246).
|
|
44
|
+
*
|
|
45
|
+
* Shape is not a type. The reference case the parse was written for
|
|
46
|
+
* (objectui#1426 — an unresolved external-id ref arriving as
|
|
47
|
+
* '{"externalId":"Website Relaunch"}') belongs to reference-TYPED columns and
|
|
48
|
+
* is handled there: `LookupCellRenderer` carries its own JSON-string branch,
|
|
49
|
+
* which resolves the label through the referenced object's schema and links to
|
|
50
|
+
* the record — neither of which this type-blind helper could ever do. Scoped,
|
|
51
|
+
* not dropped; both halves are pinned in
|
|
52
|
+
* `__tests__/textCellJsonText-7246.test.tsx`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function coerceToSafeValue(value: unknown): string | number | boolean | null | undefined;
|
package/dist/currency.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare function currencyFractionDigits(currency: string): number;
|
|
|
44
44
|
* Falls back to `currency` itself — the code as given — in the three cases
|
|
45
45
|
* where ICU cannot answer, because a bare code is a legitimate adornment (it is
|
|
46
46
|
* what ICU itself returns for KWD/BHD/CHF/ISK, and what
|
|
47
|
-
* `
|
|
47
|
+
* `formatCurrency`'s own bad-code branch renders beside it):
|
|
48
48
|
*
|
|
49
49
|
* 1. an invalid currency code — `Intl` throws `RangeError: Invalid currency
|
|
50
50
|
* code`;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BaseFieldMetadata } from '@object-ui/types';
|
|
2
|
+
/**
|
|
3
|
+
* The help line a field draws under its control: the ONE read of a field
|
|
4
|
+
* definition's help text (objectui#12213). The order lives here and nowhere
|
|
5
|
+
* else, and both of its readers call this function:
|
|
6
|
+
*
|
|
7
|
+
* - `@object-ui/plugin-form`, for the record form's help line. `ObjectForm`'s
|
|
8
|
+
* field builder and `fromObjectSchema` both call it, so the flat, sectioned
|
|
9
|
+
* and tabbed forms cannot drift apart.
|
|
10
|
+
* - `resolveActionParam` in this package, for a field-backed action param
|
|
11
|
+
* that declares no `helpText` of its own.
|
|
12
|
+
*
|
|
13
|
+
* First non-empty value wins:
|
|
14
|
+
*
|
|
15
|
+
* 1. `inlineHelpText`, `@objectstack/spec`'s `FieldSchema.inlineHelpText`
|
|
16
|
+
* ("Help text displayed below the field in forms"). Studio's object field
|
|
17
|
+
* inspector writes it, and flow screens draw it in the same place.
|
|
18
|
+
* 2. `description`, the fallback the form always had. An objectstack server
|
|
19
|
+
* on `@objectstack/spec` 17.7.0 or later serves a field's translated help
|
|
20
|
+
* text here.
|
|
21
|
+
*
|
|
22
|
+
* ## `||`, not `??`
|
|
23
|
+
*
|
|
24
|
+
* An empty string reads as absent, so `inlineHelpText: ''` falls back to the
|
|
25
|
+
* description instead of hiding it. The spec declares no setting that hides
|
|
26
|
+
* the help line, and Studio's inspector stores a cleared box as no key at all
|
|
27
|
+
* (`inlineHelpText: v || undefined`). With `??`, an empty string would become
|
|
28
|
+
* a second, undeclared way to suppress the description.
|
|
29
|
+
*
|
|
30
|
+
* ## `help` is not read
|
|
31
|
+
*
|
|
32
|
+
* `FieldSchema` refuses `help` by name and suggests `inlineHelpText`, so no
|
|
33
|
+
* document that passes the write door carries it. Its one producer was an
|
|
34
|
+
* objectstack server older than `@objectstack/spec` 17.7.0, whose translator
|
|
35
|
+
* put a field's translated description on the served field's `help` key;
|
|
36
|
+
* 17.7.0 serves that text on `description`. The read of that served leg is
|
|
37
|
+
* retired (the ruling on objectui#12213): translated field help needs an
|
|
38
|
+
* objectstack server on spec 17.7.0 or later. A definition that still carries
|
|
39
|
+
* `help` draws its `inlineHelpText`, else its `description`, else nothing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function fieldHelpText(field: Pick<BaseFieldMetadata, 'inlineHelpText' | 'description'>): string | undefined;
|