@object-ui/fields 17.5.0 → 17.6.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 +714 -0
- package/dist/FieldEditWidget.d.ts +16 -2
- package/dist/field-type-alias.d.ts +16 -0
- package/dist/index.cjs +4 -4
- package/dist/index.css +27 -9
- package/dist/index.d.ts +425 -72
- package/dist/index.js +2530 -2228
- package/dist/widgets/AddressField.d.ts +3 -3
- package/dist/widgets/AutoNumberField.d.ts +1 -1
- package/dist/widgets/AvatarField.d.ts +1 -1
- package/dist/widgets/BooleanField.d.ts +1 -1
- 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 +1 -1
- package/dist/widgets/DateTimeField.d.ts +1 -1
- package/dist/widgets/EmailField.d.ts +1 -1
- package/dist/widgets/FileField.d.ts +18 -2
- package/dist/widgets/FilterConditionField.d.ts +28 -1
- package/dist/widgets/FormulaField.d.ts +1 -1
- package/dist/widgets/FullscreenFieldEditor.d.ts +9 -2
- package/dist/widgets/GeolocationField.d.ts +1 -1
- package/dist/widgets/GridField.d.ts +56 -4
- package/dist/widgets/ImageField.d.ts +1 -1
- package/dist/widgets/LocationField.d.ts +1 -1
- package/dist/widgets/LookupField.d.ts +1 -1
- package/dist/widgets/MasterDetailField.d.ts +1 -1
- package/dist/widgets/MultiSelectField.d.ts +1 -1
- package/dist/widgets/NumberField.d.ts +1 -1
- package/dist/widgets/ObjectField.d.ts +1 -1
- package/dist/widgets/ObjectRefField.d.ts +1 -1
- package/dist/widgets/OptionsEmptyState.d.ts +13 -7
- package/dist/widgets/PasswordField.d.ts +1 -1
- package/dist/widgets/PercentField.d.ts +1 -1
- 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 +1 -1
- package/dist/widgets/RecordPickerDialog.d.ts +2 -4
- package/dist/widgets/RichTextField.d.ts +37 -1
- package/dist/widgets/SelectField.d.ts +1 -1
- 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 +1 -1
- package/dist/widgets/TimeField.d.ts +1 -1
- 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/lookupColumnDisplay.d.ts +93 -0
- package/dist/widgets/richTextDisplay.d.ts +80 -0
- package/dist/widgets/toHostGroupProps.d.ts +119 -21
- package/dist/widgets/types.d.ts +8 -3
- package/package.json +9 -10
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,719 @@
|
|
|
1
1
|
# @object-ui/fields
|
|
2
2
|
|
|
3
|
+
## 17.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a09bc33: fix(fields): render the option colour an author declared as an explicit hex, instead of quantizing it to nine palette families (objectui#5141)
|
|
8
|
+
|
|
9
|
+
`options[].color` accepts any hex, but the badge renderer answered a lossy
|
|
10
|
+
question with it: `hexToPaletteName` bucketed the value by hue into nine
|
|
11
|
+
families (`red`/`orange`/`yellow`/`green`/`blue`/`indigo`/`purple`/`pink`, plus
|
|
12
|
+
`gray` below 22% saturation), and `BADGE_COLOR_MAP` held exactly one class set
|
|
13
|
+
per family. Two tiers an author declared as distinct therefore rendered
|
|
14
|
+
byte-identical: `#2ecc71` ("in progress") and `#1e8449` ("completed") differ by
|
|
15
|
+
0.1 degree of hue, both landed in `green`, and both emitted
|
|
16
|
+
`bg-green-50 text-green-700 border-green-200`. Pressing the second colour darker
|
|
17
|
+
still changed nothing. End users could not tell the two states apart in a list.
|
|
18
|
+
|
|
19
|
+
`plugin-gantt` had already settled this class of conflict the other way —
|
|
20
|
+
*explicit colorField value (hex or semantic name) — metadata wins* — and
|
|
21
|
+
Studio's own option editor paints the author's swatch straight from the raw hex.
|
|
22
|
+
Badges were the odd surface out.
|
|
23
|
+
|
|
24
|
+
Now an explicitly declared hex is rendered as declared: the soft-pill surface,
|
|
25
|
+
label and border are derived from that hex rather than snapped to a family, for
|
|
26
|
+
both `appearance: 'badge'` and `appearance: 'dot'`.
|
|
27
|
+
|
|
28
|
+
Two properties the family maps gave us for free are kept deliberately:
|
|
29
|
+
|
|
30
|
+
- **The design system keeps control of theming.** The derived colours are
|
|
31
|
+
published as CSS custom properties and consumed by *static* Tailwind
|
|
32
|
+
utilities, so light and dark remain ordinary `dark:` variants
|
|
33
|
+
(`.dark\:bg-...:where(.dark, .dark *)` in the built sheet) rather than a
|
|
34
|
+
hard-coded inline background that would render identically in dark mode.
|
|
35
|
+
Tailwind cannot generate a class for a runtime value, so the custom property —
|
|
36
|
+
not the colour — has to be the dynamic part.
|
|
37
|
+
- **Contrast is pinned, not just colour identity.** The label is the lightness
|
|
38
|
+
along the declared hue nearest the declared one that still clears WCAG AA
|
|
39
|
+
(4.5:1) against the surface actually rendered. Authors can and do declare
|
|
40
|
+
colours that are unreadable under a label; honoring the declaration must not
|
|
41
|
+
turn legibility loose across every list view. Dots are held to 1.9:1 against
|
|
42
|
+
the row, the measured floor of the `-500` shades shipped today.
|
|
43
|
+
|
|
44
|
+
**What changes for an author relying on the current look:** every select/status
|
|
45
|
+
badge whose option colour is declared as a hex — which the renderer's own notes
|
|
46
|
+
describe as almost all of them — will render in that declared colour rather than
|
|
47
|
+
its palette family's fixed pill. Colours near a family's canonical shade look
|
|
48
|
+
much as before; a colour the author picked deliberately *away* from it (a deep
|
|
49
|
+
green, a muted red) now looks like what was written, and the pill's depth tracks
|
|
50
|
+
the declared lightness. Declarations that are not an explicit hex are untouched:
|
|
51
|
+
family names, the semantic value map and the deterministic hash fallback all
|
|
52
|
+
resolve exactly as they did, and `getSemanticColorName` still returns family
|
|
53
|
+
names, so the Gantt path is unaffected.
|
|
54
|
+
|
|
55
|
+
The badge classes exported by `getBadgeColorClasses` are unchanged, so callers
|
|
56
|
+
that consume only a class string (the grid's compact card view and group-header
|
|
57
|
+
pills, Kanban) keep today's quantized rendering until they adopt the new
|
|
58
|
+
`getBadgeHexAppearance` / `getDotHexAppearance` helpers.
|
|
59
|
+
- 167ec42: `ComponentMeta.labelling` grows a third value: `'control' | 'group' | 'display'`
|
|
60
|
+
(objectui#4857, ruled jointly with objectui#4871 as the single repo-wide vocabulary for
|
|
61
|
+
"how does a host learn what a widget will render"). `'display'` declares a widget whose
|
|
62
|
+
whole surface is a pure display in EVERY state — no focusable control, nothing a
|
|
63
|
+
`<label for>` could ever reach.
|
|
64
|
+
|
|
65
|
+
The form renderer answers the declaration with the objectui#4788 host container (field
|
|
66
|
+
id + `aria-labelledby` + `aria-describedby` + `role="group"`) in the editable state too;
|
|
67
|
+
the `readonly === true` arm keeps its exact #4788 semantics for undeclared widgets. The
|
|
68
|
+
display-only four (`formula` / `summary` / `auto_number` / `vector`) declare `'display'`
|
|
69
|
+
— on the real object-form path they arrive `disabled`, never `readonly` (a deliberate
|
|
70
|
+
distinction this change does not touch), so their visible labels pointed `for` at an id
|
|
71
|
+
no element carried and their help text had zero consumers in every editable form.
|
|
72
|
+
|
|
73
|
+
`grid` was re-measured before being classified: its only bare-config focusable is the
|
|
74
|
+
auxiliary "Add line" button (routing `for` there would have label clicks insert rows),
|
|
75
|
+
and every realistic config is a table of per-cell inputs — a composite. It declares
|
|
76
|
+
`labelling: 'group'` and its root container now consumes the host id, name and
|
|
77
|
+
description, exactly like `address` / `checkboxes`.
|
|
78
|
+
|
|
79
|
+
Companion registry gate: `FIELD_WIDGET_LABELLING` (exported) is a `Record` keyed by the
|
|
80
|
+
field-widget map's own literal key union, so registering a widget without deciding its
|
|
81
|
+
labelling is a compile error rather than a silent fall-through to the dangling-`for`
|
|
82
|
+
path, and the declaration test asserts the registered meta agrees with it key by key.
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- 69251bf: `AddressField` is translatable, shows no US example placeholders, and formats its readonly line in the reader's address order.
|
|
87
|
+
|
|
88
|
+
The five sub-labels ("Street Address", "City", "State / Province", "ZIP /
|
|
89
|
+
Postal Code", "Country") were English string literals with no i18n key. On a
|
|
90
|
+
non-English console every address field showed five English words in the middle
|
|
91
|
+
of an otherwise fully translated form, and an app had no way to reach them: the
|
|
92
|
+
parts are not fields on the object (`billing_address` is a single `address`
|
|
93
|
+
column), so a translation bundle had nothing to key on, there is no `subLabels`
|
|
94
|
+
property to declare, and the widget cannot be replaced from metadata. They now
|
|
95
|
+
resolve through `fields.address.street` / `.city` / `.state` / `.postalCode` /
|
|
96
|
+
`.country`, added to all ten locale packs. The `en` values are byte-identical to
|
|
97
|
+
the literals they replace, and `FIELD_DEFAULTS` carries the same five, so
|
|
98
|
+
English and provider-less rendering are unchanged.
|
|
99
|
+
|
|
100
|
+
The five input placeholders (`123 Main St`, `San Francisco`, `CA`, `94102`,
|
|
101
|
+
`United States`) are **removed** rather than keyed. They were untranslated and
|
|
102
|
+
US-specific — a zh/ja/ar user was shown an American address as the example of
|
|
103
|
+
what to type — and the right example is a function of the address's country,
|
|
104
|
+
not the reader's language, which no channel in the stored value can supply
|
|
105
|
+
today. Each box keeps the visible label that names it.
|
|
106
|
+
|
|
107
|
+
The readonly line's part order now follows the reader's display locale
|
|
108
|
+
(`useDisplayLocale()`): `zh`, `ja` and `ko` read largest-first (`Country, ZIP
|
|
109
|
+
State, City, Street`), every other locale keeps the unchanged small-to-large
|
|
110
|
+
order (`Street, City, State ZIP, Country`). The display cell renderer takes the
|
|
111
|
+
same locale through the same shared `formatAddress`, so a stored address reads
|
|
112
|
+
identically in a readonly form and in a grid cell.
|
|
113
|
+
- 0ae27f7: The form renderer's built-in `textarea` branch now honours a declared character cap the same way the registered `field:textarea` widget does.
|
|
114
|
+
|
|
115
|
+
One `maxLength` declaration produced two experiences. The registered path has
|
|
116
|
+
shipped four things since objectui#3406/#3408/#3417 — the native cap, visible
|
|
117
|
+
`{n}/{max}` digits, a description reached through `aria-describedby` so the
|
|
118
|
+
limit is announced on focus, and a threshold-gated debounced near-limit notice.
|
|
119
|
+
The built-in branch — the path standalone and embedded hosts take, the ones that
|
|
120
|
+
call no `registerAllFields()` — shipped a subset of one of them.
|
|
121
|
+
|
|
122
|
+
The accessibility half is the half that mattered: a screen-reader user on this
|
|
123
|
+
path learned the field's limit only as a validation error AFTER submitting. All
|
|
124
|
+
four affordances now render on both of the branch's surfaces (the inline control
|
|
125
|
+
and the fullscreen dialog), from the SAME `CharacterCount` component the widget
|
|
126
|
+
renders rather than a second copy of it.
|
|
127
|
+
|
|
128
|
+
Also fixed, and wider than the visible gap: the branch never READ the cap, it
|
|
129
|
+
only spread its leftover field props onto the element. A camelCase `maxLength`
|
|
130
|
+
therefore worked by coincidence — it names a real DOM attribute — while the
|
|
131
|
+
legacy `max_length` spelling, which the registered widget and all three
|
|
132
|
+
producers of a form field have dual-read since framework#1878 §3, landed as a
|
|
133
|
+
stray inert `max_length="…"` attribute and capped nothing at all. The branch now
|
|
134
|
+
resolves both spellings and keeps the non-attribute spelling off the DOM.
|
|
135
|
+
|
|
136
|
+
`CharacterCount` moved from `@object-ui/fields` to `@object-ui/components`, the
|
|
137
|
+
package both render paths may import, in the direction and for the reason
|
|
138
|
+
objectui#3398 measured for `FullscreenEditor`. It was internal to `fields` (never
|
|
139
|
+
exported from that package's barrel), so no published export changed; it is a
|
|
140
|
+
new export of `@object-ui/components`. Its copy moved with it onto the same
|
|
141
|
+
`fields.textarea.*` keys with byte-identical English defaults, so the ten locale
|
|
142
|
+
packs need no edit and provider-less rendering is unchanged.
|
|
143
|
+
- bbe8b86: The allow-list of option widgets that are fed the live record is now one exported constant, `CASCADE_OPTION_WIDGET_TYPES`, instead of three private copies.
|
|
144
|
+
|
|
145
|
+
`select` / `multiselect` / `radio` / `checkboxes` are the widgets whose OFFERED
|
|
146
|
+
option set is re-resolved against a record (per-option `visibleWhen`, plus the
|
|
147
|
+
`dependsOn` gate), so they are the widgets a surface must thread its live record
|
|
148
|
+
to. Three surfaces feed that one evaluator — the object form, the single-record
|
|
149
|
+
action dialog and the bulk action dialog — and until now each carried its own
|
|
150
|
+
private `new Set([...])` of the same four keys, with a comment in each asking the
|
|
151
|
+
next person to change all three together. Nothing could have reported them
|
|
152
|
+
drifting: every copy passed its own behavioural tests, and a divergence would
|
|
153
|
+
have shown up only as one surface silently disagreeing with another about what
|
|
154
|
+
"the record" is.
|
|
155
|
+
|
|
156
|
+
The set now lives in `@object-ui/core`, next to `resolveCascadingOptions` — the
|
|
157
|
+
evaluator that reads that record — because core is the one package all three
|
|
158
|
+
surfaces already depend on, and it is re-exported from `@object-ui/fields` next
|
|
159
|
+
to `resolveFormWidgetType`, whose output is the vocabulary the keys are written
|
|
160
|
+
in. Both are the same object, pinned by test; each consumer keeps its own
|
|
161
|
+
normalization (`normalizeFieldType` in the form, `resolveFormWidgetType` in the
|
|
162
|
+
dialogs), which agree on these four members.
|
|
163
|
+
|
|
164
|
+
No behaviour changes: the members are identical on all three surfaces, and the
|
|
165
|
+
existing pins for each surface still assert the same records reaching the same
|
|
166
|
+
widgets. The rationale that was repeated in the three copies — including the
|
|
167
|
+
note that the widget-hint picker family (`filter-condition`, `recipient-picker`,
|
|
168
|
+
the lookup family) reads a different sibling key off the same channel and is
|
|
169
|
+
deliberately NOT in this set — is now stated once, in the constant's own
|
|
170
|
+
documentation. Whether the action and bulk dialogs should ever feed those
|
|
171
|
+
pickers stays an open question (objectui#4771), unchanged by this convergence.
|
|
172
|
+
- 65e88e6: `@object-ui/fields` stops publishing its `src/` tree
|
|
173
|
+
|
|
174
|
+
The manifest's `files` array listed `src` alongside `dist`, so every published tarball carried all 173 source files — 97 of them `*.test.tsx` / `*.test.ts`. It has been that way since the file's first commit (`780a1b993`), where `files` was already `["dist", "src", "README.md"]` while `exports` named only `dist`, so the entry was never added for a consumer. objectui#4006 recorded this exact shape and did not act on it: its scope was the `*.test.d.ts` half the build program emitted into `dist`, and it noted in passing that the test sources were already in the tarball by this other route.
|
|
175
|
+
|
|
176
|
+
Nothing in the published surface reached those files, which is why no consumer changes in either direction. Measured on a cleanly rebuilt `dist`, all four ways in are closed: the `exports` map has two entries and both target `dist` (`.` resolves `types` / `import` / `require` to `./dist/index.d.ts` / `./dist/index.js` / `./dist/index.cjs`, and `./style.css` to `./dist/index.css`); `main` / `module` / `types` are `./dist/index.cjs`, `./dist/index.js`, `./dist/index.d.ts`; no deep import into this package exists anywhere in the repo or the docs — the one that used to, `@object-ui/fields/widgets/MarkdownContent`, was ruled out by objectui#4325 precisely because a package's surface is its index, and the `../fields/src` paths in sibling `vite.config.ts` files are workspace aliases resolved through `resolve()` against the source tree, which no `files` array shapes; and the tarball holds no sourcemap that could point back at `src`. That last one is the check that had to be measured rather than assumed, because this package emits its declarations through `vite-plugin-dts` rather than the `tsup` of objectui#4847 or the bare `tsc` of `@object-ui/types`: a clean rebuild writes 78 files into `dist`, of which zero are `.d.ts.map`, zero are `.js.map`, zero contain a `sourceMappingURL` comment and zero mention `../src`. `src/index.css` is an input to `scripts/build-css.mjs`, not an output anyone resolves; the sheet the `./style.css` export names is the built `dist/index.css`, which still ships.
|
|
177
|
+
|
|
178
|
+
`npm pack --dry-run` across the change, on the same `dist`:
|
|
179
|
+
|
|
180
|
+
| | before | after |
|
|
181
|
+
| --- | --- | --- |
|
|
182
|
+
| entries | 255 | 82 |
|
|
183
|
+
| unpacked | 2265557 B | 841772 B |
|
|
184
|
+
| tarball | 629843 B | 252364 B |
|
|
185
|
+
|
|
186
|
+
173 files leave, none arrives, nothing outside `src/` moves, and every surviving entry is byte-identical apart from the edited `package.json` itself. The 173 are the 97 tests plus 76 implementation modules, whose published form remains the bundled `dist/index.js` / `dist/index.cjs` and the 75 declaration files beside them.
|
|
187
|
+
|
|
188
|
+
`@object-ui/types` keeps its `src` entry for now, and that is a different judgement rather than an omission: it builds with a bare `tsc` under a `declarationMap` / `sourceMap` config with no `inlineSources`, so its shipped `dist/*.d.ts.map` name `../src/*.ts` with no embedded content and dropping `src` there would leave published maps pointing at files the tarball no longer carries. That trade-off is filed as objectui#4851.
|
|
189
|
+
- 37f6844: FilterConditionField can author the spec's `$icontains` — case-insensitive contains is reachable from the filter UI.
|
|
190
|
+
|
|
191
|
+
`@objectstack/spec`'s `FieldOperatorsSchema` gained `$icontains` between
|
|
192
|
+
`17.0.0-rc.2` and `rc.5`, and every driver and evaluation face the platform
|
|
193
|
+
ships now executes it. `FilterConditionField` had no builder operator that could
|
|
194
|
+
author it, so the capability was unreachable from the sharing-rule criteria
|
|
195
|
+
builder and sat in that widget's parity test as an explicit `KNOWN_UNREACHABLE`
|
|
196
|
+
entry.
|
|
197
|
+
|
|
198
|
+
The FilterBuilder gains a `containsCaseInsensitive` operator ("Contains (ignore
|
|
199
|
+
case)", translated in all ten locale packs). `condToMongo` emits
|
|
200
|
+
`{ field: { $icontains: value } }` and `kvToCondition` reads it back, so a saved
|
|
201
|
+
criteria reopens in the visual builder instead of falling into the raw-JSON
|
|
202
|
+
editor. Today's `contains` is unchanged and still emits the case-SENSITIVE
|
|
203
|
+
`$contains`; whether it should have been case-insensitive all along is a product
|
|
204
|
+
question that stays open, and stored filter views keep meaning what they meant.
|
|
205
|
+
|
|
206
|
+
The fold is ASCII-only by contract — `café` does not match `CAFÉ`.
|
|
207
|
+
|
|
208
|
+
The new operator is **opt-in per consumer**: `FilterBuilder` takes an
|
|
209
|
+
`extraOperators` prop, and only `FilterConditionField` passes it. The one
|
|
210
|
+
dropdown feeds three at-rest dialects and only the MongoDB-style criteria this
|
|
211
|
+
widget writes can carry the operator — the spec's `VIEW_FILTER_OPERATORS` (saved
|
|
212
|
+
views) and `VALID_AST_OPERATORS` (the live grid's filter AST) have no
|
|
213
|
+
case-insensitive contains, so offering it there would author a filter those
|
|
214
|
+
paths cannot execute. Every other FilterBuilder is unchanged.
|
|
215
|
+
- 911ceaa: The fullscreen long-text dialog announces the field's validation state and carries the field's name
|
|
216
|
+
|
|
217
|
+
objectui#4824, objectui#4832.
|
|
218
|
+
|
|
219
|
+
`mobile.fullscreenLongText` is a shipped opt-in, and with it on the phone user
|
|
220
|
+
edits long text in this dialog and nowhere else. Measured on all three surfaces
|
|
221
|
+
that render the dialog — `TextAreaField`, `RichTextField`, and the form
|
|
222
|
+
renderer's built-in `textarea` branch — with the field genuinely invalid at that
|
|
223
|
+
moment:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
INLINE richtext aria-invalid= true
|
|
227
|
+
DIALOG richtext aria-invalid= false aria-describedby= null
|
|
228
|
+
INLINE textarea aria-invalid= true
|
|
229
|
+
DIALOG textarea aria-invalid= null aria-describedby= null
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
and the accessible name of every dialog control empty, against `F` on every
|
|
233
|
+
inline one. The rich-text row is the sharp half: the dialog was not silent about
|
|
234
|
+
the failure, it was announcing the OPPOSITE of the inline control for the same
|
|
235
|
+
field at the same moment, because `RichTextEditorSurface` computed
|
|
236
|
+
`aria-invalid={!!error}` from an `error` prop the dialog rendering never
|
|
237
|
+
received. 3 surfaces, 3 broken, one cause: the dialog's control is built from
|
|
238
|
+
scratch by the host, so none of the wiring the inline control gets from the form
|
|
239
|
+
renderer reaches it.
|
|
240
|
+
|
|
241
|
+
**Answered once, in the primitive.** `FullscreenEditor` now takes the field's
|
|
242
|
+
`error` and owns what the dialog does with it: it renders the message in a
|
|
243
|
+
dialog-local node, and hands `children` a required fourth argument — a
|
|
244
|
+
spreadable set of DOM attributes — carrying `aria-labelledby` (the dialog
|
|
245
|
+
title's text, i.e. the field label #3393 already put there), `aria-invalid`, and
|
|
246
|
+
`aria-errormessage` naming that node. The host spreads it; the host never learns
|
|
247
|
+
an id, so it cannot name the wrong node, cannot compose the attributes subtly
|
|
248
|
+
wrong, and cannot compute its own `aria-invalid` from a prop it forgot to plumb.
|
|
249
|
+
Three hosts hand-answering this is the shape that produced three identical
|
|
250
|
+
holes.
|
|
251
|
+
|
|
252
|
+
**On objectui#3222's "the text belongs to `FormMessage`".** The maintainer's
|
|
253
|
+
ruling of 2026-08-16 restates that rule as what it was always protecting —
|
|
254
|
+
only one copy of the error text is in the accessibility tree at any moment —
|
|
255
|
+
which the dialog-local node satisfies: it exists only while the dialog is open,
|
|
256
|
+
and for exactly that window Radix `aria-hidden`s everything outside the modal,
|
|
257
|
+
`FormMessage` included. The shortcut of pointing the dialog control's
|
|
258
|
+
`aria-describedby` / `aria-errormessage` at the host's `FormMessage` id is
|
|
259
|
+
forbidden rather than merely unused: it resolves to a node that is `aria-hidden`
|
|
260
|
+
for the whole time the reference is live (an ARIA MUST violation), and neither
|
|
261
|
+
happy-dom nor jsdom can see the difference — which is why every new pin asserts
|
|
262
|
+
that the named node is inside THIS dialog, not merely that it exists.
|
|
263
|
+
|
|
264
|
+
`aria-errormessage` carries a single IDREF and is emitted only alongside
|
|
265
|
+
`aria-invalid="true"`. It is deliberately not folded into the host's
|
|
266
|
+
`aria-describedby` chain, which on the textarea surface already carries the
|
|
267
|
+
fullscreen character counter's sentence.
|
|
268
|
+
|
|
269
|
+
**The name reuses the visible title rather than minting a second author for it**
|
|
270
|
+
(#3978): `aria-labelledby` points at a span inside `DialogTitle`, not at
|
|
271
|
+
`DialogTitle` itself — Radix renders the title as `h2` with the id its own
|
|
272
|
+
`DialogContent` `aria-labelledby` names, so putting an id on it would buy the
|
|
273
|
+
control a name at the cost of the dialog's.
|
|
274
|
+
|
|
275
|
+
**Breaking (shipped as `minor`, see below), `@object-ui/components` only.**
|
|
276
|
+
`FullscreenEditorProps.error` is REQUIRED, not optional, and
|
|
277
|
+
`FullscreenEditorProps['children']` takes a fourth argument.
|
|
278
|
+
|
|
279
|
+
FROM → TO for an out-of-repo host:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
<FullscreenEditor value={v} onCommit={c} label={l} testIdPrefix="x">
|
|
283
|
+
{(draft, setDraft, disabled) => <textarea … />}
|
|
284
|
+
</FullscreenEditor>
|
|
285
|
+
|
|
286
|
+
<FullscreenEditor value={v} onCommit={c} label={l} testIdPrefix="x" error={err}>
|
|
287
|
+
{(draft, setDraft, disabled, aria) => <textarea {...aria} … />}
|
|
288
|
+
</FullscreenEditor>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
A render prop may still declare fewer parameters, so only `error` fails to
|
|
292
|
+
compile — which is the point of making it required. Every consumer already has
|
|
293
|
+
the value at hand (registered widgets take `error` off the widget props
|
|
294
|
+
contract, objectui#3222; the built-in branch reads `fieldState.error?.message`),
|
|
295
|
+
and an omitted `error` reproduces this defect exactly: a dialog announcing
|
|
296
|
+
`aria-invalid="false"` for a field its own form has already failed. An optional
|
|
297
|
+
key was forgotten by three surfaces in a row; a required one cannot be.
|
|
298
|
+
|
|
299
|
+
`@object-ui/fields` is `patch`: `FullscreenFieldEditor` is internal to that
|
|
300
|
+
package (not re-exported from its entry), so nothing in its public surface
|
|
301
|
+
changes — only the behaviour of the two long-text widgets' dialogs.
|
|
302
|
+
|
|
303
|
+
`minor` rather than `major` follows the repo's standing retirement precedent
|
|
304
|
+
(AGENTS.md §版本号策略, enforced by `scripts/check-changeset-no-major.mjs`): all
|
|
305
|
+
publishable packages sit in one `fixed` group, so a `major` here would carry the
|
|
306
|
+
whole family up against an `@objectstack` that has not moved.
|
|
307
|
+
- 0bffb18: A readonly group-labelled field is now DESCRIBED by its own help text, not just named by its label.
|
|
308
|
+
|
|
309
|
+
The seven `labelling: 'group'` widgets (`address`, `geolocation`, `checkboxes`, `radio`, `rating`, `file`, `multiselect`) render a `<FormDescription>` in their readonly and zero-option states, and the form renderer publishes its `id` — but nothing in the document referenced that id, so the visible help text had no programmatic association with the field it describes. Measured before the change, one field per row: `consumers=0` on every readonly surface of all seven, against `consumers=1` on the same widget's editable one.
|
|
310
|
+
|
|
311
|
+
`toHostGroupProps` now carries `aria-describedby` alongside the host `id` and `aria-labelledby`, onto the `role="group"` surface those states already render (a group is a description carrier under ARIA 1.2 — no focusable control required, which is why this became possible only once objectui#3990 gave those surfaces a role).
|
|
312
|
+
|
|
313
|
+
Control-channel state deliberately does NOT come along: `aria-invalid` and `aria-required` report what a user's editing may do wrong, and a readonly display cannot be edited. The boundary is pinned from both sides, including with a live failed validation on a readonly required field. Editable surfaces are unchanged — a composite's editable container still leaves the description on the sub-input the user focuses, so the help text is never announced twice.
|
|
314
|
+
- 800f455: fix(fields): grid columns are keyed by the declared `name`, so spec-compliant grid metadata renders populated cells
|
|
315
|
+
|
|
316
|
+
`GridField` declared its own column interface keyed by `field` and read
|
|
317
|
+
`c.field` at every site (`key=`, `row[…]`, the blank row, cell writes, the
|
|
318
|
+
column chooser, the running-total lookup), while the published
|
|
319
|
+
`GridColumnDefinition` in `@object-ui/types` — and the grid documentation, and
|
|
320
|
+
the `fields-grid` catalog examples — declare the key as `name`. Metadata
|
|
321
|
+
authored against the published type therefore rendered a grid with the correct
|
|
322
|
+
row count and every cell empty, plus a React "unique key" warning per column.
|
|
323
|
+
|
|
324
|
+
The renderer now reads the declared `name`, and the master-detail derivation
|
|
325
|
+
(`deriveColumns` / `hydrateColumns` / `pickAmountField`) produces and consumes
|
|
326
|
+
the same key. There is deliberately **no** `col.field ?? col.name` alias: one
|
|
327
|
+
spelling at the producer (AGENTS.md #0.1).
|
|
328
|
+
|
|
329
|
+
**Breaking for `field`-keyed columns.** Grid / line-item / master-detail
|
|
330
|
+
subform columns spelled `{ field: 'amount' }` must be re-spelled
|
|
331
|
+
`{ name: 'amount' }`. This affects author-supplied `columns` on the `grid`
|
|
332
|
+
field, `record:line_items`, `object-master-detail-form` details and a
|
|
333
|
+
relationship field's `inlineColumns`. Auto-derived columns (no explicit
|
|
334
|
+
`columns` block) need no change. List-view and `object-grid` columns are a
|
|
335
|
+
different contract (`ListColumn`) and keep their own `field` key.
|
|
336
|
+
- 5458414: Publish relative import specifiers with explicit `.js` extensions so these six packages load under plain Node ESM.
|
|
337
|
+
|
|
338
|
+
Node's ESM resolver does not extension-search relative specifiers and `tsc` never rewrites them, so an extensionless `./Foo` in the source shipped as an extensionless `./Foo` in `dist` and importing the package entry outside a bundler failed with `ERR_MODULE_NOT_FOUND`. Bundled consumers were unaffected. Unbundled consumers — plain Node ESM, an SSR host importing the package directly, anyone running the published tarball without a build step — can now import these entries, and so can the downstream `@object-ui/plugin-*` packages that evaluate through `mobile`, `permissions` and `providers`.
|
|
339
|
+
- 3241559: `object-kanban` now caps its fetch with a real top-level `$top`, and honours `limit`.
|
|
340
|
+
|
|
341
|
+
The board's row cap was written `dataSource.find(objectName, { options: { $top: 100 }, $filter: … })`
|
|
342
|
+
— `$filter` at the top level, where the adapters read it, and the cap one level
|
|
343
|
+
down under `options`, which is not a `QueryParams` key. Nothing in this repo
|
|
344
|
+
reads `params.options` (`convertQueryParams` in `@object-ui/data-objectstack` and
|
|
345
|
+
`ApiDataSource` both read `params.$top`), so the intended cap never reached the
|
|
346
|
+
wire: a board over a large object fetched every row the server would return and
|
|
347
|
+
grouped all of it into lanes client-side. The author's 100 was never live.
|
|
348
|
+
|
|
349
|
+
The cap is now `$top: schema.limit ?? 100`, the shape `object-timeline` took for
|
|
350
|
+
the identical defect. Two consequences worth reading before upgrading:
|
|
351
|
+
|
|
352
|
+
- **A board that used to fetch unbounded now fetches 100 rows by default.** That
|
|
353
|
+
is the previously-declared intent taking effect, not a new limit — but a board
|
|
354
|
+
over an object with more than 100 matching records will show fewer cards than
|
|
355
|
+
it did. Authors who want the old behaviour should declare the window they
|
|
356
|
+
actually want (`limit: 500`), not rely on the absence of one.
|
|
357
|
+
- **`limit` is now mapped from the `dataSource` binding** (`object-kanban`'s
|
|
358
|
+
`ElementDataSourceMapping` gains `limit: 'limit'`). A board bound to a saved
|
|
359
|
+
view is capped by that view's `pagination.pageSize`, and the binding's own
|
|
360
|
+
`limit` overrides it. The flag comes with the read site: it stayed unmapped
|
|
361
|
+
until now on the rationale that the board had a "fixed window", which is the
|
|
362
|
+
claim this change falsifies. `sort` remains unmapped — the board still has no
|
|
363
|
+
`$orderby` read site — and `columns` remains unmapped because a board's columns
|
|
364
|
+
are its swimlanes, not a field projection.
|
|
365
|
+
|
|
366
|
+
`KanbanSchema` gains `limit?: number` (the board's row cap; distinct from a
|
|
367
|
+
column's `limit`, which is that lane's WIP limit and never reaches the query),
|
|
368
|
+
and the guide's per-block table in `content/docs/guide/data-source.md` no longer
|
|
369
|
+
claims a fixed window for `object-kanban`.
|
|
370
|
+
|
|
371
|
+
`@object-ui/fields` carried the same nesting in the lookup-name fallback
|
|
372
|
+
(`find(referenceTo, { $filter: { id }, options: { $top: 1 } })`) where it was
|
|
373
|
+
harmless — a primary-key equality returns at most one row with or without a cap.
|
|
374
|
+
It is spelled `$top: 1` now, so no live instance of the dead nesting is left in
|
|
375
|
+
the repo to read as precedent. No behaviour change there.
|
|
376
|
+
- 616a2a5: The list filter builder no longer offers `Is set` / `Is not set`, which its query dialects cannot express.
|
|
377
|
+
|
|
378
|
+
**User-visible before/after.** The operator dropdown in the list toolbar's
|
|
379
|
+
filter popover — and in the Studio view/tab/page filter inspectors, the dataset
|
|
380
|
+
inspector and the generic `filter` config field — loses two rows: **"Is set"**
|
|
381
|
+
and **"Is not set"**. The sharing-rule criteria builder (`FilterConditionField`)
|
|
382
|
+
keeps both, unchanged. `Is null` / `Is not null` and `Is empty` / `Is not empty`
|
|
383
|
+
are untouched everywhere and remain the way to filter on a missing value from
|
|
384
|
+
the list.
|
|
385
|
+
|
|
386
|
+
Nothing that worked stops working. Every save path behind those two rows was
|
|
387
|
+
already broken, in three different ways depending on the surface:
|
|
388
|
+
|
|
389
|
+
- **Live grid** — `ListView.mapOperator` had no row for either id, so its
|
|
390
|
+
`default:` arm returned the id verbatim and the query went out as
|
|
391
|
+
`['name', 'exists', 'x']`. `exists` is not a member of the spec's
|
|
392
|
+
`VALID_AST_OPERATORS`, so `isFilterAST()` rejects the shape: an unfiltered
|
|
393
|
+
read or a 400, never the filter the user asked for.
|
|
394
|
+
- **Save as view** — `foldFilterGroupToSpecRules` normalizes through the spec's
|
|
395
|
+
`normalizeFilterOperator`, which does not know the pair, and
|
|
396
|
+
`ViewFilterRuleSchema`'s enum then refuses the rule.
|
|
397
|
+
- **Dataset inspector** — `groupToCondition` has no row either and drops the
|
|
398
|
+
condition silently, so the filter simply never applied.
|
|
399
|
+
|
|
400
|
+
**Why withheld rather than mapped.** Measured on `@objectstack/spec`
|
|
401
|
+
17.0.0-rc.6: neither `VIEW_FILTER_OPERATORS` nor `VALID_AST_OPERATORS` contains
|
|
402
|
+
an existence operator, under any spelling — both sets have zero members matching
|
|
403
|
+
`/exist/`. Only the MongoDB-style `FieldOperatorsSchema` criteria carries
|
|
404
|
+
`$exists`, and that is precisely the dialect `FilterConditionField` writes, so
|
|
405
|
+
the pair moves behind the existing `OPT_IN_OPERATORS` gate and that widget opts
|
|
406
|
+
in. Collapsing them onto `isNotNull` / `isNull` was rejected: the builder
|
|
407
|
+
already draws those as their own rows, the round trip is lossy (a saved
|
|
408
|
+
`exists` reads back as `isNotNull`), and the spec's own note records `$exists` =
|
|
409
|
+
has-value as still unsettled across drivers — `driver-memory`'s live mingo path
|
|
410
|
+
and `driver-mongodb` read key-presence.
|
|
411
|
+
|
|
412
|
+
**The class is now closed by an assertion, not by discipline.** objectui's three
|
|
413
|
+
existing operator-parity guards all sweep spec vocabulary → objectui; none asked
|
|
414
|
+
whether an id the dropdown draws is an id the consumer can persist, which is the
|
|
415
|
+
direction that broke. `plugin-list`'s new
|
|
416
|
+
`list-offered-operator-expressible-parity.test.ts` forces the set the list
|
|
417
|
+
toolbar offers to **equal** the set its two dialects can express, in both
|
|
418
|
+
directions — so an unexpressible operator cannot be offered, and an operator
|
|
419
|
+
that becomes expressible upstream cannot stay needlessly withheld.
|
|
420
|
+
- 6c68b13: Lookup "recently used" now obeys the field's declared filters. The recents rail
|
|
421
|
+
re-fetched its rows without merging either `lookupFilters` or the `dependsOn`
|
|
422
|
+
cascade, so a record the author's metadata excludes stayed visible and
|
|
423
|
+
selectable — pick a product under project A, switch the form to project B, and
|
|
424
|
+
project A's product was still offered. Both re-fetch channels are fixed: the
|
|
425
|
+
inline dropdown (which re-read each remembered id with an unfiltered `findOne`)
|
|
426
|
+
and the search-first picker (which batched recents into an unfiltered `$in`
|
|
427
|
+
query). The currently-selected value still resolves unfiltered, so an existing
|
|
428
|
+
value keeps its label and a selection tray is never silently emptied.
|
|
429
|
+
- 5607092: objectui#4029 — the repo root now lints `no-console` (`error`, allowing
|
|
430
|
+
`warn`/`error`) so a stray module- or function-scope `console.log`/`info`/
|
|
431
|
+
`debug` fails CI instead of shipping silently (as `console.log('Registering
|
|
432
|
+
object-map...')` did in #7139, caught only by hand). Landing the rule meant
|
|
433
|
+
individually judging every real hit outside the tooling exemptions
|
|
434
|
+
(`scripts/**`, `**/examples/**`, test files, `packages/cli/src/**`,
|
|
435
|
+
`packages/create-plugin/src/**`) — this changeset covers the published
|
|
436
|
+
packages whose call sites changed:
|
|
437
|
+
|
|
438
|
+
- `@object-ui/app-shell`: `ObjectDataPage`'s dropped-URL-filter message is a
|
|
439
|
+
real diagnostic (data silently discarded), so it moves from `console.debug`
|
|
440
|
+
to `console.warn` to match the house convention.
|
|
441
|
+
- `@object-ui/plugin-detail`: `DetailView`'s Web Share API failure now reports
|
|
442
|
+
via `console.error` (it is a real failure, not debug noise); a redundant
|
|
443
|
+
"Link copied to clipboard" success log is removed.
|
|
444
|
+
- `@object-ui/fields`: `MasterDetailField`'s `handleView` stub no longer logs
|
|
445
|
+
the item it does nothing with.
|
|
446
|
+
- `@object-ui/runner`: `App`'s loader-selection debug prints, `LayoutRenderer`'s
|
|
447
|
+
unused click-handler stub log, and `MockDataSource`'s per-call narration
|
|
448
|
+
(`find`/`create`/`getObjectSchema`) are removed — none diagnosed a problem,
|
|
449
|
+
they only echoed the happy path.
|
|
450
|
+
- `object-ui` (VS Code extension): the "extension is now active!" activation
|
|
451
|
+
log is removed.
|
|
452
|
+
|
|
453
|
+
No behavior changes beyond console output. `@object-ui/core` and
|
|
454
|
+
`@object-ui/data-objectstack` also touch `no-console`-adjacent lines
|
|
455
|
+
(`debugLog`/`debugTime`/`debugTimeEnd`, `createQuietHttpLogger`) but only to
|
|
456
|
+
add `eslint-disable-next-line` documentation — those ARE the repo's
|
|
457
|
+
deliberate debug/logger infrastructure, not leaked residue, so their own
|
|
458
|
+
changeset carries empty frontmatter.
|
|
459
|
+
- e7747f1: fix(fields): retire the `owner` field-type alias with a loud tombstone
|
|
460
|
+
|
|
461
|
+
`owner` was a synonym for `user` with zero behavioral delta — both resolved to
|
|
462
|
+
the same `UserField` widget — and it is not a member of `@objectstack/spec`'s
|
|
463
|
+
closed `FieldType`, so no object schema could ever declare it. It was reachable
|
|
464
|
+
only through hand-written SDUI, and the three code faces that read it had
|
|
465
|
+
already drifted apart on the word: the form's data-source rule excluded it,
|
|
466
|
+
while plugin-grid's bulk-action dialog and app-shell's `paramToField` included
|
|
467
|
+
it.
|
|
468
|
+
|
|
469
|
+
The retired spelling now fails **loudly**. Deleting the alias on its own would
|
|
470
|
+
have been absorbed by two silent tails (`mapFieldTypeToFormType`'s
|
|
471
|
+
`|| 'field:text'` and `resolveFormWidgetType`'s `: 'text'`), each handing back a
|
|
472
|
+
working plain text input with no check turning red — so anyone who had written
|
|
473
|
+
`type: 'owner'`, including an AI author copying it out of a doc, would have
|
|
474
|
+
shipped a text box believing they shipped a person picker. Instead:
|
|
475
|
+
|
|
476
|
+
- `type: 'owner'` and `widget: 'field:owner'` both resolve to a registered
|
|
477
|
+
tombstone widget that renders a visible refusal naming the migration;
|
|
478
|
+
- the same prescription is written to the console once per spelling;
|
|
479
|
+
- the read/cell path degrades to the text cell deliberately and says so.
|
|
480
|
+
|
|
481
|
+
Migration: write the record-owner field as `{ type: 'user', name: 'owner' }` —
|
|
482
|
+
the field NAME carries the ownership meaning, the type carries the widget.
|
|
483
|
+
`UserField` and `UserCellRenderer` are unchanged; only the synonym is gone.
|
|
484
|
+
|
|
485
|
+
Also corrects the `dataSource` TSDoc in `@object-ui/fields`, which listed `grid`
|
|
486
|
+
among the widgets the form renderer wires a DataSource to. `GridField` never
|
|
487
|
+
read `dataSource` and no data-source table ever contained the key.
|
|
488
|
+
- ac2f332: Retired field-type spellings can no longer reach an inline editor by delegation — the grid's inline cell editor stops offering a working person picker for `owner`.
|
|
489
|
+
|
|
490
|
+
objectui#4814 retired the `owner` field type with a loud tombstone, and the
|
|
491
|
+
record form has answered `type: 'owner'` with a visible refusal ever since.
|
|
492
|
+
`FieldEditWidget` did not participate, and the reason it was missed is that no
|
|
493
|
+
routing table had to list the spelling for it to stay alive:
|
|
494
|
+
`hasFieldEditWidget` answers `resolveInlineEditType(type) in EDIT_WIDGETS`, and
|
|
495
|
+
`resolveInlineEditType` returns a type **unchanged** when it is already a key of
|
|
496
|
+
`EDIT_WIDGETS`. So while `owner: UserField` sat in that map, the alias table —
|
|
497
|
+
where the retirement lives — was never consulted, and the retirement never
|
|
498
|
+
applied. Measured: `hasFieldEditWidget('owner') === true`, and a direct
|
|
499
|
+
`FieldEditWidget` call rendered a working person picker. Every host built on
|
|
500
|
+
this seam, the data grid's inline cell editor being the user-facing one, still
|
|
501
|
+
let a person be picked and saved for a field the record form refuses.
|
|
502
|
+
|
|
503
|
+
The gate now consults the retirement table itself, so this closes the **class**
|
|
504
|
+
rather than the spelling — the next retirement covers this seam the day it lands
|
|
505
|
+
in `RETIRED_FIELD_TYPES`, with no edit here:
|
|
506
|
+
|
|
507
|
+
- `hasFieldEditWidget()` answers `false` for any retired spelling, ahead of
|
|
508
|
+
every table lookup (so it holds even if a retired spelling is still a key of
|
|
509
|
+
the widget map);
|
|
510
|
+
- `isInlineExcludedFieldType()` answers `true` for any retired spelling, which
|
|
511
|
+
is what routes a host to its read-only path;
|
|
512
|
+
- `FieldEditWidget` itself renders no control for a retired spelling and writes
|
|
513
|
+
the once-per-spelling console prescription naming the migration;
|
|
514
|
+
- `owner` is dropped from `EDIT_WIDGETS` and from the cosmetic
|
|
515
|
+
`COMPACT_EDIT_TYPES` sizing set.
|
|
516
|
+
|
|
517
|
+
The second bullet is load-bearing and not cosmetic. `ObjectGrid` marks a column
|
|
518
|
+
`editable: false` exactly when `isFieldInlineEditable` is false, and that
|
|
519
|
+
predicate consults `isInlineExcludedFieldType`. Without it, closing the first
|
|
520
|
+
gate alone would have left the column editable, handed the cell editor a `null`
|
|
521
|
+
widget, and let DataTable fall back to its built-in **plain text input** — which
|
|
522
|
+
for a stored person reference displays a coerced value and writes a bare string
|
|
523
|
+
straight back over it. Measured before/after on the grid consumer: a stored
|
|
524
|
+
`owner` field went from an editable cell rendering the person picker to a
|
|
525
|
+
read-only cell (`isFieldInlineEditable('owner')` `true` → `false`), which is the
|
|
526
|
+
same disposition `password`, `formula` and `composite` already get, for the same
|
|
527
|
+
reason. The read path is unchanged from #4814: the text cell plus the console
|
|
528
|
+
prescription.
|
|
529
|
+
|
|
530
|
+
`user` is untouched — it keeps the person picker, its compact sizing and its
|
|
531
|
+
expanded-object value resolution. The record-owner idiom survives verbatim as
|
|
532
|
+
`{ type: 'user', name: 'owner' }`: the field NAME carries the ownership meaning,
|
|
533
|
+
the type carries the widget.
|
|
534
|
+
- a777058: Editable `markdown` / `html` / `richtext` fields now carry the host's `id` and `aria-describedby` on the editor.
|
|
535
|
+
|
|
536
|
+
`RichTextField` — the one widget all three registry keys resolve to — read only
|
|
537
|
+
`className` and `disabled` off its props and had no `toDomProps` anywhere in the
|
|
538
|
+
file, so everything `<FormControl>`'s Radix `Slot` hands a field widget landed on
|
|
539
|
+
no element at all. Measured on a real form with a field carrying a description:
|
|
540
|
+
|
|
541
|
+
```
|
|
542
|
+
markdown editable descEl=SET consumers=0 hostIdEl=NONE for=DANGLING
|
|
543
|
+
text editable descEl=SET consumers=1 hostIdEl=input for=RESOLVES
|
|
544
|
+
textarea editable descEl=SET consumers=1 hostIdEl=textarea for=RESOLVES
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Two user-visible consequences: the form's visible label pointed at an id nothing
|
|
548
|
+
carried, so clicking it did not reach the editor and the control had no
|
|
549
|
+
accessible name; and the `<FormDescription>` the form rendered below the field
|
|
550
|
+
had zero consumers, so a screen reader never announced the help text or — on a
|
|
551
|
+
failed submit — the error message id that rides in the same `aria-describedby`.
|
|
552
|
+
|
|
553
|
+
The fix is objectui#3318's standing recipe: `toDomProps(props)` spread onto the
|
|
554
|
+
REAL focusable control, which here is the `<Textarea>` inside the shared
|
|
555
|
+
`RichTextEditorSurface`. The pass-through is given to the inline surface only —
|
|
556
|
+
the fullscreen dialog's copy of the same surface must not carry a duplicate of
|
|
557
|
+
the host id, and the description ids it names sit outside the modal Radix
|
|
558
|
+
`aria-hidden`s. `aria-invalid` still comes from the widget's own `error` read,
|
|
559
|
+
kept after the spread so objectui#3318's PASS entry for this widget is unchanged.
|
|
560
|
+
|
|
561
|
+
The readonly branch is untouched: the same reading there belongs to
|
|
562
|
+
objectui#4788, whose mechanism is still open.
|
|
563
|
+
- 75444e3: The dependency-gate hint now enumerates its controlling fields with the locale's
|
|
564
|
+
own list separator, and reads identically whichever caller produced it.
|
|
565
|
+
|
|
566
|
+
`lookup.selectFirst` and `fields.options.selectFirst` are deliberately one
|
|
567
|
+
wording, so a field gated on two or more parents says the same thing whether the
|
|
568
|
+
lookup widget or the form renderer rendered it. The sentence was shared but its
|
|
569
|
+
`{{fields}}` slot was not: each call site joined the controlling-field names
|
|
570
|
+
with its own hardcoded separator, and not even the same one — `', '` in
|
|
571
|
+
`LookupField`, `' / '` in the form renderer's `gatedHint` and in
|
|
572
|
+
`OptionsEmptyState`. A field gated on Account and Lead Source read
|
|
573
|
+
`Select Account, Lead Source first` from one side and
|
|
574
|
+
`Select Account / Lead Source first` from the other.
|
|
575
|
+
|
|
576
|
+
A list separator is a property of the locale rather than of the code, so both
|
|
577
|
+
spellings were also wrong for the script under zh/ja (which enumerate with
|
|
578
|
+
U+3001) and under ar (U+060C). All three call sites now read
|
|
579
|
+
`validation.formInvalidJoiner` — the key already shipped in all ten packs for
|
|
580
|
+
the invalid-submit toast's field list, which is the same class of truncated-name
|
|
581
|
+
list. One key, every caller: a second, gate-specific key would have recreated
|
|
582
|
+
the divergence the shared sentence exists to prevent.
|
|
583
|
+
|
|
584
|
+
No locale pack changes, and no change to what a provider-less render produces in
|
|
585
|
+
English: the `@object-ui/fields` defaults table declares the joiner as `', '`,
|
|
586
|
+
the `en` pack's value and the literal `LookupField` previously hardcoded.
|
|
587
|
+
- dad51e5: fix(fields): deliver the host's a11y channels to `slider` and name `signature`
|
|
588
|
+
|
|
589
|
+
`SliderField` and `SignatureField` forwarded nothing a form host handed them —
|
|
590
|
+
neither spread `toDomProps(props)` at all — so `<FormControl>`'s whole payload
|
|
591
|
+
landed on nothing. Measured on a real form, one required field per row, freshly
|
|
592
|
+
failed validation:
|
|
593
|
+
|
|
594
|
+
```
|
|
595
|
+
slider ariaInvalidTrue=[] labelFor=…-form-item -> DANGLING descConsumers=0 ids=[]
|
|
596
|
+
signature ariaInvalidTrue=[] labelFor=…-form-item -> DANGLING descConsumers=0 ids=[]
|
|
597
|
+
text ariaInvalidTrue=[input] labelFor -> input descConsumers=1
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
`ids=[]` is the tell: no element in either row carried an id at all, so the
|
|
601
|
+
visible label pointed `for` at nothing, the rendered help text had zero
|
|
602
|
+
consumers, and a failed slider announced no error state.
|
|
603
|
+
|
|
604
|
+
**`slider`** now delivers all three. Its focusable control is Radix's
|
|
605
|
+
`span[role="slider"]` thumb, which the synced `ui/slider.tsx` renders internally
|
|
606
|
+
and does not export, so the primitive grew a declared `thumbProps` — routed
|
|
607
|
+
through a new `lib/slider-thumb` and applied to the no-touch file as a declared
|
|
608
|
+
sync patch, so it survives regeneration. The split of which keys stay on Root
|
|
609
|
+
(`name`, `disabled`) is the one the `select` fix already settled.
|
|
610
|
+
|
|
611
|
+
**`signature`** gets the name and the description on a `role="group"` container.
|
|
612
|
+
Its control state deliberately does not follow: the drawing surface is a
|
|
613
|
+
`<canvas>` with no keyboard path, and its only other element is disabled while
|
|
614
|
+
the pad is empty, so there is no element a control state could be read from.
|
|
615
|
+
|
|
616
|
+
Both are now declared `labelling: 'group'` — a `<span>` and a `<canvas>` are not
|
|
617
|
+
labelable elements, so a host `for` could only dangle at them.
|
|
618
|
+
- Updated dependencies [88085e3]
|
|
619
|
+
- Updated dependencies [69251bf]
|
|
620
|
+
- Updated dependencies [57e668f]
|
|
621
|
+
- Updated dependencies [516663d]
|
|
622
|
+
- Updated dependencies [41ac1b7]
|
|
623
|
+
- Updated dependencies [1eaf0a1]
|
|
624
|
+
- Updated dependencies [460c4d0]
|
|
625
|
+
- Updated dependencies [0ae27f7]
|
|
626
|
+
- Updated dependencies [2533ec5]
|
|
627
|
+
- Updated dependencies [78c0f9a]
|
|
628
|
+
- Updated dependencies [bbe8b86]
|
|
629
|
+
- Updated dependencies [8477be5]
|
|
630
|
+
- Updated dependencies [279fb13]
|
|
631
|
+
- Updated dependencies [2e82ab2]
|
|
632
|
+
- Updated dependencies [ad07b65]
|
|
633
|
+
- Updated dependencies [41f498b]
|
|
634
|
+
- Updated dependencies [ef0d150]
|
|
635
|
+
- Updated dependencies [f34226e]
|
|
636
|
+
- Updated dependencies [564b605]
|
|
637
|
+
- Updated dependencies [e1d4251]
|
|
638
|
+
- Updated dependencies [40d3a33]
|
|
639
|
+
- Updated dependencies [8b9dc62]
|
|
640
|
+
- Updated dependencies [1184192]
|
|
641
|
+
- Updated dependencies [a2a9747]
|
|
642
|
+
- Updated dependencies [a1609a6]
|
|
643
|
+
- Updated dependencies [53f23bc]
|
|
644
|
+
- Updated dependencies [c4533dc]
|
|
645
|
+
- Updated dependencies [be60815]
|
|
646
|
+
- Updated dependencies [37f6844]
|
|
647
|
+
- Updated dependencies [93de4f6]
|
|
648
|
+
- Updated dependencies [2b50261]
|
|
649
|
+
- Updated dependencies [384f30d]
|
|
650
|
+
- Updated dependencies [ac600e5]
|
|
651
|
+
- Updated dependencies [97fba31]
|
|
652
|
+
- Updated dependencies [232f61a]
|
|
653
|
+
- Updated dependencies [d374caf]
|
|
654
|
+
- Updated dependencies [5673576]
|
|
655
|
+
- Updated dependencies [c1ef923]
|
|
656
|
+
- Updated dependencies [911ceaa]
|
|
657
|
+
- Updated dependencies [98eab36]
|
|
658
|
+
- Updated dependencies [af5e292]
|
|
659
|
+
- Updated dependencies [3fbbea1]
|
|
660
|
+
- Updated dependencies [5458414]
|
|
661
|
+
- Updated dependencies [7f96b10]
|
|
662
|
+
- Updated dependencies [167ec42]
|
|
663
|
+
- Updated dependencies [616a2a5]
|
|
664
|
+
- Updated dependencies [0046d8f]
|
|
665
|
+
- Updated dependencies [f1d4748]
|
|
666
|
+
- Updated dependencies [bea374e]
|
|
667
|
+
- Updated dependencies [b1119ec]
|
|
668
|
+
- Updated dependencies [9f23d2b]
|
|
669
|
+
- Updated dependencies [578e025]
|
|
670
|
+
- Updated dependencies [af025ee]
|
|
671
|
+
- Updated dependencies [d109a4d]
|
|
672
|
+
- Updated dependencies [598c89a]
|
|
673
|
+
- Updated dependencies [4a0bd17]
|
|
674
|
+
- Updated dependencies [b8b9af4]
|
|
675
|
+
- Updated dependencies [31676be]
|
|
676
|
+
- Updated dependencies [8c0d52e]
|
|
677
|
+
- Updated dependencies [aff10e2]
|
|
678
|
+
- Updated dependencies [70a774b]
|
|
679
|
+
- Updated dependencies [9ce096f]
|
|
680
|
+
- Updated dependencies [e05db88]
|
|
681
|
+
- Updated dependencies [7458a41]
|
|
682
|
+
- Updated dependencies [ad13d63]
|
|
683
|
+
- Updated dependencies [5ffcc14]
|
|
684
|
+
- Updated dependencies [d971e51]
|
|
685
|
+
- Updated dependencies [97abb24]
|
|
686
|
+
- Updated dependencies [deb157a]
|
|
687
|
+
- Updated dependencies [9c60144]
|
|
688
|
+
- Updated dependencies [d2ce342]
|
|
689
|
+
- Updated dependencies [9695da7]
|
|
690
|
+
- Updated dependencies [75444e3]
|
|
691
|
+
- Updated dependencies [58b8346]
|
|
692
|
+
- Updated dependencies [2d0bd16]
|
|
693
|
+
- Updated dependencies [a9e17b4]
|
|
694
|
+
- Updated dependencies [b8ce7dc]
|
|
695
|
+
- Updated dependencies [dad51e5]
|
|
696
|
+
- Updated dependencies [1c9c342]
|
|
697
|
+
- Updated dependencies [787c738]
|
|
698
|
+
- Updated dependencies [8396656]
|
|
699
|
+
- Updated dependencies [dbbd38a]
|
|
700
|
+
- Updated dependencies [8871c14]
|
|
701
|
+
- Updated dependencies [93fe362]
|
|
702
|
+
- Updated dependencies [dfc6975]
|
|
703
|
+
- Updated dependencies [3cf4de0]
|
|
704
|
+
- Updated dependencies [c9dc811]
|
|
705
|
+
- Updated dependencies [144ef9b]
|
|
706
|
+
- Updated dependencies [138ab04]
|
|
707
|
+
- Updated dependencies [a0b9e91]
|
|
708
|
+
- Updated dependencies [99bd015]
|
|
709
|
+
- Updated dependencies [21e4585]
|
|
710
|
+
- @object-ui/types@17.6.0
|
|
711
|
+
- @object-ui/i18n@17.6.0
|
|
712
|
+
- @object-ui/react@17.6.0
|
|
713
|
+
- @object-ui/components@17.6.0
|
|
714
|
+
- @object-ui/core@17.6.0
|
|
715
|
+
- @object-ui/providers@17.6.0
|
|
716
|
+
|
|
3
717
|
## 17.5.0
|
|
4
718
|
|
|
5
719
|
### Minor Changes
|