@object-ui/fields 17.5.0 → 17.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6150 -0
- package/README.md +108 -8
- package/dist/FieldEditWidget.d.ts +70 -8
- package/dist/coerceToSafeValue.d.ts +54 -0
- package/dist/currency.d.ts +1 -1
- package/dist/field-type-alias.d.ts +16 -0
- package/dist/index.cjs +5 -5
- package/dist/index.css +53 -11
- package/dist/index.d.ts +653 -128
- package/dist/index.js +4275 -3171
- package/dist/widgets/AddressField.d.ts +3 -3
- package/dist/widgets/AutoNumberField.d.ts +1 -1
- package/dist/widgets/AvatarField.d.ts +20 -4
- package/dist/widgets/BooleanField.d.ts +2 -2
- package/dist/widgets/CapabilityMultiSelectField.d.ts +1 -1
- package/dist/widgets/CheckboxesField.d.ts +1 -1
- package/dist/widgets/CodeField.d.ts +1 -1
- package/dist/widgets/ColorField.d.ts +1 -1
- package/dist/widgets/CurrencyField.d.ts +1 -1
- package/dist/widgets/DateField.d.ts +2 -2
- package/dist/widgets/DateTimeField.d.ts +2 -2
- package/dist/widgets/EmailField.d.ts +1 -1
- package/dist/widgets/FileField.d.ts +18 -2
- package/dist/widgets/FilterConditionField.d.ts +43 -2
- package/dist/widgets/FormulaField.d.ts +1 -1
- package/dist/widgets/FullscreenFieldEditor.d.ts +9 -2
- package/dist/widgets/GeolocationField.d.ts +10 -4
- package/dist/widgets/GridField.d.ts +102 -86
- package/dist/widgets/ImageField.d.ts +1 -1
- package/dist/widgets/LocationField.d.ts +31 -4
- package/dist/widgets/LookupField.d.ts +9 -1
- package/dist/widgets/MasterDetailField.d.ts +1 -1
- package/dist/widgets/MultiSelectField.d.ts +1 -1
- package/dist/widgets/NumberField.d.ts +2 -2
- package/dist/widgets/ObjectField.d.ts +10 -3
- package/dist/widgets/ObjectRefField.d.ts +1 -1
- package/dist/widgets/OptionsEmptyState.d.ts +38 -8
- package/dist/widgets/PasswordField.d.ts +15 -1
- package/dist/widgets/PeoplePicker.d.ts +17 -2
- package/dist/widgets/PercentField.d.ts +3 -2
- package/dist/widgets/PersonRow.d.ts +13 -2
- package/dist/widgets/PhoneField.d.ts +1 -1
- package/dist/widgets/QRCodeField.d.ts +1 -1
- package/dist/widgets/RadioField.d.ts +1 -1
- package/dist/widgets/RatingField.d.ts +1 -1
- package/dist/widgets/RecipientPickerField.d.ts +44 -1
- package/dist/widgets/RecordPickerDialog.d.ts +33 -16
- package/dist/widgets/RichTextField.d.ts +37 -1
- package/dist/widgets/SelectField.d.ts +1 -1
- package/dist/widgets/SelectionTray.d.ts +13 -2
- package/dist/widgets/SignatureField.d.ts +32 -2
- package/dist/widgets/SliderField.d.ts +37 -2
- package/dist/widgets/SummaryField.d.ts +1 -1
- package/dist/widgets/TagsField.d.ts +1 -1
- package/dist/widgets/TextAreaField.d.ts +6 -2
- package/dist/widgets/TextField.d.ts +2 -2
- package/dist/widgets/TimeField.d.ts +2 -2
- package/dist/widgets/UrlField.d.ts +1 -1
- package/dist/widgets/UserField.d.ts +1 -1
- package/dist/widgets/VectorField.d.ts +1 -1
- package/dist/widgets/_cell-renderer-bridge.d.ts +1 -1
- package/dist/widgets/address-format.d.ts +27 -7
- package/dist/widgets/file-affordance.d.ts +33 -0
- package/dist/widgets/file-size-guard.d.ts +5 -23
- package/dist/widgets/file-value.d.ts +38 -9
- package/dist/widgets/lookupColumnDisplay.d.ts +110 -0
- 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/personDisplay.d.ts +22 -4
- package/dist/widgets/richTextDisplay.d.ts +116 -0
- package/dist/widgets/toDomProps.d.ts +10 -1
- package/dist/widgets/toHostGroupProps.d.ts +172 -21
- package/dist/widgets/toHostProps.d.ts +105 -0
- package/dist/widgets/types.d.ts +19 -6
- 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 +4 -2
- package/dist/widgets/useUploadingSignal.d.ts +8 -0
- package/package.json +11 -13
- package/dist/widgets/CharacterCount.d.ts +0 -56
- package/src/FieldEditWidget.test.ts +0 -133
- package/src/FieldEditWidget.tsx +0 -207
- package/src/__tests__/AddressCellRenderer.test.tsx +0 -166
- package/src/__tests__/CurrencyField.minorUnits.test.tsx +0 -191
- package/src/__tests__/CurrencyField.symbol.test.tsx +0 -198
- package/src/__tests__/DateCellRenderer.test.tsx +0 -82
- package/src/__tests__/NumberCellRenderer.grouping.test.tsx +0 -146
- package/src/__tests__/NumberCellRenderer.test.tsx +0 -60
- package/src/__tests__/PercentCellRenderer.locale.test.tsx +0 -152
- package/src/__tests__/PercentCellRenderer.test.tsx +0 -115
- package/src/__tests__/boolean-label-association-e2e.test.tsx +0 -298
- package/src/__tests__/capability-multiselect-retired.test.ts +0 -109
- package/src/__tests__/cell-truncation.test.tsx +0 -96
- package/src/__tests__/composite-group-label-e2e.test.tsx +0 -436
- package/src/__tests__/composite-group-label-readonly-e2e.test.tsx +0 -403
- package/src/__tests__/date-formatter-residue-4272.test.ts +0 -120
- package/src/__tests__/date-locale-channel.test.tsx +0 -305
- package/src/__tests__/field-carrier-sdui.test.tsx +0 -169
- package/src/__tests__/formatCurrency.centsDigit.test.tsx +0 -158
- package/src/__tests__/formatCurrency.minorUnits.test.tsx +0 -221
- package/src/__tests__/group-labelling-declaration.test.ts +0 -148
- package/src/__tests__/lookupCellDisplayField.test.tsx +0 -107
- package/src/__tests__/lookupCellDisplayName.test.tsx +0 -119
- package/src/__tests__/lookupCellRecordLink.test.tsx +0 -171
- package/src/__tests__/lookupCellReferenceKey.test.tsx +0 -57
- package/src/__tests__/percent-cell-vs-measure-4576.test.ts +0 -115
- package/src/__tests__/percent-formatter-locale-4553.test.ts +0 -147
- package/src/__tests__/percent-tie-halfup-4590.test.ts +0 -224
- package/src/__tests__/registry-collision.test.ts +0 -91
- package/src/__tests__/resolveFieldCurrency.test.ts +0 -20
- package/src/__tests__/select-label-association-e2e.test.tsx +0 -199
- package/src/__tests__/spec-symbol-batch7.test.ts +0 -126
- package/src/__tests__/validation-feedback.test.tsx +0 -361
- package/src/__tests__/widget-aria-invalid-e2e.test.tsx +0 -265
- package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +0 -338
- package/src/__tests__/widget-dom-leak-e2e.test.tsx +0 -357
- package/src/__tests__/widget-props-contract.test.tsx +0 -142
- package/src/coerce-safe-value.test.ts +0 -34
- package/src/complex-widgets.test.tsx +0 -924
- package/src/currency.ts +0 -144
- package/src/datetime-widgets.test.tsx +0 -185
- package/src/field-type-alias.multiple.test.ts +0 -81
- package/src/field-type-alias.ts +0 -156
- package/src/field-type-coverage.test.ts +0 -67
- package/src/index.css +0 -44
- package/src/index.tsx +0 -2745
- package/src/standard-widgets.test.tsx +0 -364
- package/src/widgets/AddressField.postalCode.test.tsx +0 -257
- package/src/widgets/AddressField.tsx +0 -182
- package/src/widgets/AddressField.uniqueIds.test.tsx +0 -93
- package/src/widgets/AutoNumberField.tsx +0 -18
- package/src/widgets/AvatarField.tsx +0 -117
- package/src/widgets/BooleanField.tsx +0 -86
- package/src/widgets/CapabilityMultiSelectField.test.tsx +0 -168
- package/src/widgets/CapabilityMultiSelectField.tsx +0 -241
- package/src/widgets/CharacterCount.tsx +0 -263
- package/src/widgets/CheckboxesField.cascade.test.tsx +0 -143
- package/src/widgets/CheckboxesField.tsx +0 -169
- package/src/widgets/CodeField.tsx +0 -41
- package/src/widgets/ColorField.tsx +0 -53
- package/src/widgets/CurrencyField.tsx +0 -135
- package/src/widgets/DateField.tsx +0 -40
- package/src/widgets/DateTimeField.tsx +0 -46
- package/src/widgets/EmailField.tsx +0 -37
- package/src/widgets/FileField.tsx +0 -512
- package/src/widgets/FilterConditionField.tsx +0 -454
- package/src/widgets/FormulaField.tsx +0 -38
- package/src/widgets/FullscreenFieldEditor.tsx +0 -60
- package/src/widgets/GeolocationField.tsx +0 -193
- package/src/widgets/GeolocationField.uniqueIds.test.tsx +0 -88
- package/src/widgets/GridField.test.tsx +0 -632
- package/src/widgets/GridField.tsx +0 -1070
- package/src/widgets/ImageCropperDialog.tsx +0 -245
- package/src/widgets/ImageField.maxSize.test.tsx +0 -154
- package/src/widgets/ImageField.tsx +0 -305
- package/src/widgets/ImageLightbox.test.tsx +0 -52
- package/src/widgets/ImageLightbox.tsx +0 -108
- package/src/widgets/LocationField.tsx +0 -57
- package/src/widgets/LookupField.dependsOn.test.tsx +0 -233
- package/src/widgets/LookupField.gateHintLabel.test.tsx +0 -135
- package/src/widgets/LookupField.hydration.test.tsx +0 -136
- package/src/widgets/LookupField.idField.test.tsx +0 -61
- package/src/widgets/LookupField.tsx +0 -1295
- package/src/widgets/MarkdownContent.tsx +0 -19
- package/src/widgets/MasterDetailField.tsx +0 -131
- package/src/widgets/MultiSelectField.cascade.test.tsx +0 -143
- package/src/widgets/MultiSelectField.tsx +0 -196
- package/src/widgets/NumberField.tsx +0 -49
- package/src/widgets/ObjectField.tsx +0 -94
- package/src/widgets/ObjectRefField.tsx +0 -121
- package/src/widgets/OptionsEmptyState.no-provider.test.tsx +0 -88
- package/src/widgets/OptionsEmptyState.test.tsx +0 -169
- package/src/widgets/OptionsEmptyState.tsx +0 -99
- package/src/widgets/PasswordField.tsx +0 -49
- package/src/widgets/PeoplePicker.test.tsx +0 -360
- package/src/widgets/PeoplePicker.tsx +0 -579
- package/src/widgets/PeoplePickerComponents.test.tsx +0 -104
- package/src/widgets/PercentField.tsx +0 -95
- package/src/widgets/PersonRow.tsx +0 -117
- package/src/widgets/PhoneField.tsx +0 -37
- package/src/widgets/QRCodeField.tsx +0 -103
- package/src/widgets/RadioField.cascade.test.tsx +0 -141
- package/src/widgets/RadioField.tsx +0 -143
- package/src/widgets/RatingField.tsx +0 -94
- package/src/widgets/RecipientPickerField.test.tsx +0 -157
- package/src/widgets/RecipientPickerField.tsx +0 -188
- package/src/widgets/RecordPickerDialog.baseFilterShapes.test.tsx +0 -191
- package/src/widgets/RecordPickerDialog.dateLocale.test.tsx +0 -124
- package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +0 -611
- package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +0 -112
- package/src/widgets/RecordPickerDialog.tsx +0 -1365
- package/src/widgets/RichTextField.tsx +0 -176
- package/src/widgets/SelectField.cascade.test.tsx +0 -138
- package/src/widgets/SelectField.tsx +0 -193
- package/src/widgets/SelectionTray.tsx +0 -110
- package/src/widgets/SignatureField.tsx +0 -140
- package/src/widgets/SliderField.tsx +0 -39
- package/src/widgets/SummaryField.tsx +0 -31
- package/src/widgets/TagsField.placeholder.no-provider.test.tsx +0 -68
- package/src/widgets/TagsField.placeholder.test.tsx +0 -95
- package/src/widgets/TagsField.tsx +0 -88
- package/src/widgets/TextAreaField.tsx +0 -213
- package/src/widgets/TextField.tsx +0 -45
- package/src/widgets/TimeField.tsx +0 -31
- package/src/widgets/UrlField.tsx +0 -46
- package/src/widgets/UserField.tsx +0 -59
- package/src/widgets/UserFieldPicker.test.tsx +0 -78
- package/src/widgets/VectorField.tsx +0 -29
- package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +0 -66
- package/src/widgets/__tests__/FilterConditionField.operators.test.ts +0 -166
- package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +0 -248
- package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +0 -116
- package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +0 -244
- package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +0 -195
- package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +0 -417
- package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +0 -287
- package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +0 -160
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +0 -161
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +0 -346
- package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +0 -246
- package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +0 -175
- package/src/widgets/_cell-renderer-bridge.ts +0 -19
- package/src/widgets/address-format.ts +0 -120
- package/src/widgets/deriveLookupColumns.test.ts +0 -112
- package/src/widgets/deriveLookupColumns.ts +0 -108
- package/src/widgets/file-size-guard.ts +0 -55
- package/src/widgets/file-value.test.ts +0 -230
- package/src/widgets/file-value.ts +0 -246
- package/src/widgets/nativeDateValue.test.ts +0 -115
- package/src/widgets/nativeDateValue.ts +0 -112
- package/src/widgets/openNativePicker.ts +0 -18
- package/src/widgets/optionWidgets.gatedOptions.test.tsx +0 -267
- package/src/widgets/optionWidgets.unconfiguredOptions.test.tsx +0 -150
- package/src/widgets/personDisplay.test.ts +0 -113
- package/src/widgets/personDisplay.ts +0 -92
- package/src/widgets/recentLookups.test.ts +0 -45
- package/src/widgets/recentLookups.ts +0 -47
- package/src/widgets/toDomProps.ts +0 -204
- package/src/widgets/toHostGroupProps.ts +0 -102
- package/src/widgets/types.ts +0 -242
- package/src/widgets/useCascadingOptions.ts +0 -46
- package/src/widgets/useFieldTranslation.ts +0 -132
- package/src/widgets/useRecordQuery.test.ts +0 -174
- package/src/widgets/useRecordQuery.ts +0 -291
- package/src/widgets/useUploadingSignal.test.tsx +0 -62
- package/src/widgets/useUploadingSignal.ts +0 -20
- package/src/withFieldCarrier.tsx +0 -75
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,10 +40,16 @@ 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
|
-
|
|
43
|
+
import { getCellRenderer, resolveCellRendererType, type CellRendererProps } from '@object-ui/fields';
|
|
44
|
+
|
|
45
|
+
const MyGridCell = ({ field, value }: CellRendererProps) => {
|
|
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. Passing `field.type` raw skips that mapping and draws such a
|
|
50
|
+
// column as bare text, silently. Resolving first is never worse: with no
|
|
51
|
+
// format hint the resolver returns the declared `type` unchanged.
|
|
52
|
+
const Renderer = getCellRenderer(resolveCellRendererType(field));
|
|
42
53
|
return <Renderer field={field} value={value} />;
|
|
43
54
|
};
|
|
44
55
|
```
|
|
@@ -55,6 +66,33 @@ Supported types out of the box:
|
|
|
55
66
|
- **Media**: `file`, `image`
|
|
56
67
|
- **System**: `formula`, `summary`, `auto_number`
|
|
57
68
|
|
|
69
|
+
### `type="number"` widgets: what is announced and what is not
|
|
70
|
+
|
|
71
|
+
`NumberField`, `CurrencyField`, `PercentField` and `GeolocationField` all render
|
|
72
|
+
a native `type="number"` input, so the **browser** decides what the box accepts.
|
|
73
|
+
They share one reading of that, in `widgets/numberBadInput.tsx`:
|
|
74
|
+
|
|
75
|
+
- **Announced.** When the browser reports `validity.badInput` — the box is
|
|
76
|
+
holding text it cannot convert, e.g. a typed `1e`, which Chromium keeps
|
|
77
|
+
DISPLAYING while `.value` reads `''` — the control is marked `aria-invalid`
|
|
78
|
+
and draws a `Not saved: …` message, reusing objectui#6716's refusal shape.
|
|
79
|
+
Both a change arm and a blur arm are wired, because pasting into an empty box
|
|
80
|
+
never moves `.value` and so fires no React change event at all. The sentence
|
|
81
|
+
resolves through the `fields.number.badInput` locale key (objectui#8148), so
|
|
82
|
+
it follows the reader's language; the example numeral each box quotes is the
|
|
83
|
+
widget's own and is interpolated rather than translated.
|
|
84
|
+
- ⚠️ **Not announced, and not announceable.** Entries the browser silently
|
|
85
|
+
**truncates**: `1.2.3` stores `1.23`, `0x10` stores `10`. The characters are
|
|
86
|
+
discarded as they arrive, before any handler here runs, so no widget-side
|
|
87
|
+
guard can refuse them. Recovering them would mean abandoning `type="number"`
|
|
88
|
+
and with it the mobile numeric keyboard and the `min`/`max`/`step` spinner
|
|
89
|
+
(objectui#2572).
|
|
90
|
+
|
|
91
|
+
⛔ Silence therefore means "the browser read *something*", never "the value is
|
|
92
|
+
correct". User-facing wording lives in
|
|
93
|
+
[the fields guide](../../content/docs/guide/fields.md). The measured browser vs
|
|
94
|
+
happy-dom matrix is in `src/__tests__/numberInputBrowserReadings.ts`.
|
|
95
|
+
|
|
58
96
|
### Rendering form field widgets outside the form
|
|
59
97
|
|
|
60
98
|
The full widget surface is exported for consumers that render field widgets
|
|
@@ -72,6 +110,64 @@ The app-shell `ActionParamDialog` uses these to render declared action params
|
|
|
72
110
|
through the exact same widgets as the object form — with a drift test pinning
|
|
73
111
|
param support ⊇ form support.
|
|
74
112
|
|
|
113
|
+
### Enumerating the CELL renderer registry
|
|
114
|
+
|
|
115
|
+
`listCellRendererTypes()` is the read-side twin of `FORM_FIELD_TYPES`: every
|
|
116
|
+
field type `getCellRenderer` resolves to a renderer **of its own**, as opposed
|
|
117
|
+
to the `TextCellRenderer` fallback every other spelling lands on.
|
|
118
|
+
|
|
119
|
+
⚠️ It is a **function**, not a frozen constant, and the difference is
|
|
120
|
+
load-bearing. `registerFieldRenderer` is published, so the cell registry can
|
|
121
|
+
grow after this module is evaluated; a constant would be a snapshot taken at
|
|
122
|
+
import time. Consumers that reason about "all registered cell types" — the two
|
|
123
|
+
censuses in `@object-ui/fields` and `@object-ui/plugin-detail` that measure what
|
|
124
|
+
every type draws — reconcile their tables against this reading so a newly
|
|
125
|
+
registered type fails them by name instead of slipping past a frozen
|
|
126
|
+
population (objectui#8734).
|
|
127
|
+
|
|
128
|
+
### Asking whether a cell is masked
|
|
129
|
+
|
|
130
|
+
`isMaskedFieldType(type)` answers whether a cell of that field type is drawn
|
|
131
|
+
as a mask (`••••••`) instead of its value. It is the read-side twin of
|
|
132
|
+
`isInlineExcludedFieldType()`, and a consumer that acts on a cell's value asks
|
|
133
|
+
it rather than keeping its own list. The detail page's copy affordance in
|
|
134
|
+
`@object-ui/plugin-detail` is one such consumer: it refuses to copy a masked
|
|
135
|
+
row (objectui#8686). `MASKED_FIELD_TYPES` is the declared set behind it,
|
|
136
|
+
`password` and `secret`. The standard cell of each member draws the mask, and
|
|
137
|
+
the predicate answers `true` for it. Like `listCellRendererTypes()`, the
|
|
138
|
+
answer is a live reading of the cell registry taken when you call it, and,
|
|
139
|
+
with nothing overridden at runtime, it agrees with what `getCellRenderer`
|
|
140
|
+
resolves for every type that function lists. To add a masked type, register
|
|
141
|
+
the package's own mask under it with
|
|
142
|
+
`registerFieldRenderer('api_token', getCellRenderer('password'))`, while
|
|
143
|
+
`password` still resolves to the shipped mask. The predicate then answers
|
|
144
|
+
`true` with no change to the declared set, and the detail page refuses to copy
|
|
145
|
+
that row. Overriding a declared type with one of this package's own renderers
|
|
146
|
+
makes the predicate answer `false`, since none of them is the mask, and the
|
|
147
|
+
cell draws what that renderer draws: after
|
|
148
|
+
`registerFieldRenderer('password', TextCellRenderer)` the cell shows the value
|
|
149
|
+
and the detail row copies again. Overriding a declared type with a component
|
|
150
|
+
of your own keeps the declared answer, `true`, because the package cannot
|
|
151
|
+
tell whether an opaque component hides the value, so it errs toward
|
|
152
|
+
withholding it. Your own component under an undeclared type answers `false`.
|
|
153
|
+
Spellings are matched raw, the way `getCellRenderer` looks them up:
|
|
154
|
+
`field:password` renders in the clear and is not masked.
|
|
155
|
+
|
|
156
|
+
The mask itself is exported as `MaskedCellRenderer` (objectui#10657). A table
|
|
157
|
+
that cannot type a column yet, because its object's field types are still
|
|
158
|
+
loading or their read failed, draws that column WITHHELD with it rather than as
|
|
159
|
+
text: `object-grid`, the related list and `object-data-table` do. It is the
|
|
160
|
+
shipped mask, not whatever is registered for `password` at the time, and its
|
|
161
|
+
`field` prop is optional because it reads only whether a value is present.
|
|
162
|
+
`registerFieldRenderer('api_token', MaskedCellRenderer)` masks a type the same
|
|
163
|
+
way the `getCellRenderer('password')` form above does.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
import { MaskedCellRenderer } from '@object-ui/fields';
|
|
167
|
+
|
|
168
|
+
const withheldCell = (value: unknown) => <MaskedCellRenderer value={value} />;
|
|
169
|
+
```
|
|
170
|
+
|
|
75
171
|
### File uploads in line-item grids
|
|
76
172
|
|
|
77
173
|
`GridField` (the master-detail line-items grid) supports `type: 'file'` columns:
|
|
@@ -111,6 +207,10 @@ never drift). While a `dependsOn` parent is empty the control is gated; a parent
|
|
|
111
207
|
change re-filters the list and clears a now-invalid value (scalar `select` /
|
|
112
208
|
`radio` drop the value; multi-value `multiselect` / `checkboxes` prune just the
|
|
113
209
|
offered-out entries).
|
|
210
|
+
An option whose `label` is blank (the empty string is a legal label; an absent one is
|
|
211
|
+
not) displays its `value` instead of an empty row, on the editable and the read-only
|
|
212
|
+
path alike — all eight read sites across the four widgets share the one
|
|
213
|
+
`optionDisplayLabel` helper in `@object-ui/core` (objectui#9230).
|
|
114
214
|
Client-side hiding is UX only — gate authorization-sensitive values on the
|
|
115
215
|
server too. See
|
|
116
216
|
[`content/docs/fields/select.mdx`](../../content/docs/fields/select.mdx).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { FieldWidgetComponentProps } from './widgets/types';
|
|
2
|
+
import { FieldWidgetComponentProps } from './widgets/types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Form field types that are deliberately NOT given an inline editor — every
|
|
5
5
|
* other form widget type must be in {@link EDIT_WIDGETS}. This pairing is
|
|
@@ -10,7 +10,21 @@ import { FieldWidgetComponentProps } from './widgets/types';
|
|
|
10
10
|
export declare const INLINE_EXCLUDED_FIELD_TYPES: Set<string>;
|
|
11
11
|
/** Field types whose value is chosen in one discrete gesture (no free typing). */
|
|
12
12
|
export declare const DISCRETE_EDIT_TYPES: Set<string>;
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* True when a field type has a dedicated in-place edit widget.
|
|
15
|
+
*
|
|
16
|
+
* A RETIRED spelling answers `false` unconditionally, ahead of every table
|
|
17
|
+
* lookup (objectui#4931). This is the gate the retirement was missing: hosts
|
|
18
|
+
* ask this question to decide whether to hand a cell to {@link FieldEditWidget},
|
|
19
|
+
* and while it answered `true` for a retired spelling the whole tombstone was
|
|
20
|
+
* bypassable by delegation — the grid's inline cell editor rendered a working
|
|
21
|
+
* person picker for a field the record form refuses.
|
|
22
|
+
*
|
|
23
|
+
* Keyed on the retirement TABLE rather than on the raw `type`, and checked
|
|
24
|
+
* BEFORE `resolveInlineEditType`, so it holds even if a future retired spelling
|
|
25
|
+
* is still (or again) a key of {@link EDIT_WIDGETS} — closing the class, not
|
|
26
|
+
* one spelling.
|
|
27
|
+
*/
|
|
14
28
|
export declare function hasFieldEditWidget(type: string | undefined): boolean;
|
|
15
29
|
/**
|
|
16
30
|
* True when a field type is deliberately NOT inline-editable (alias-aware:
|
|
@@ -25,10 +39,58 @@ export declare function isInlineExcludedFieldType(type: string | undefined): boo
|
|
|
25
39
|
* `null` for types without a registered widget so the caller can fall back to
|
|
26
40
|
* a plain editor.
|
|
27
41
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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.
|
|
33
95
|
*/
|
|
34
|
-
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`;
|
|
@@ -19,6 +19,22 @@ export interface FieldTypeMappingConfig {
|
|
|
19
19
|
/** `FieldSchema.multiple` — the field holds zero-or-more values. */
|
|
20
20
|
multiple?: boolean | null;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* The retirement half of this module — the TOMBSTONE table
|
|
24
|
+
* {@link RETIRED_FIELD_TYPES}, the {@link isRetiredFieldType} gate, and the
|
|
25
|
+
* once-per-spelling reporter — now lives in `@object-ui/core`
|
|
26
|
+
* (`utils/retired-field-types.ts`), hoisted there by objectui#4914 so that
|
|
27
|
+
* `@object-ui/components`' filter builder can read the SAME table: `fields`
|
|
28
|
+
* depends on `components`, so the six ruled predicate faces could not all
|
|
29
|
+
* reach a table that lived here. The module's docblock carries the full
|
|
30
|
+
* reasoning, including why a second copy in `components` would have broken the
|
|
31
|
+
* ruling's "fires once" clause on day one.
|
|
32
|
+
*
|
|
33
|
+
* Re-exported from here, and from the `./index` barrel, so `@object-ui/fields`'
|
|
34
|
+
* published surface is exactly what it was plus the newly ruled gate. This file
|
|
35
|
+
* remains the retirement table's first consumer (the `field:text` tail below).
|
|
36
|
+
*/
|
|
37
|
+
export { RETIRED_FIELD_TYPES, isRetiredFieldType, reportRetiredFieldType, resetRetiredFieldTypeReports, } from '@object-ui/core';
|
|
22
38
|
/**
|
|
23
39
|
* Map field type to form component type
|
|
24
40
|
*
|