@object-ui/plugin-form 17.2.0 → 17.5.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 +681 -0
- package/README.md +65 -11
- package/dist/DrawerForm.d.ts.map +1 -1
- package/dist/LineItemsPanel.d.ts +33 -0
- package/dist/LineItemsPanel.d.ts.map +1 -1
- package/dist/ModalForm.d.ts.map +1 -1
- package/dist/ObjectForm.d.ts.map +1 -1
- package/dist/SplitForm.d.ts.map +1 -1
- package/dist/TabbedForm.d.ts.map +1 -1
- package/dist/WizardForm.d.ts.map +1 -1
- package/dist/autoLayout.d.ts.map +1 -1
- package/dist/deriveMasterDetail.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +873 -757
- package/dist/index.umd.cjs +1 -1
- package/dist/schemaDefaults.d.ts +142 -0
- package/dist/schemaDefaults.d.ts.map +1 -0
- package/dist/sectionFields.d.ts +9 -0
- package/dist/sectionFields.d.ts.map +1 -1
- package/package.json +14 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,680 @@
|
|
|
1
1
|
# @object-ui/plugin-form
|
|
2
2
|
|
|
3
|
+
## 17.5.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ae10a01: Console chrome reaches the bundle — the list switcher, the aggregate footer, the dialog a11y fallbacks and the whole Settings namespace screen stop being English on non-English consoles
|
|
8
|
+
|
|
9
|
+
Six strings on the two screens a user looks at most were hardcoded English literals rather than bundle lookups, so they stayed English on every non-English console with nothing an app could author to change them. They are not object, field, view or action labels — no key in `TranslationData` reaches them — while the console's own bundle already ships zh-CN, ja-JP, es-ES, de, fr, pt, ru, ko and ar and translates hundreds of neighbouring strings. Omissions from an otherwise complete bundle, not a missing capability.
|
|
10
|
+
|
|
11
|
+
**Two of the six needed no new keys at all, which is the more interesting half.** The list-view mode switcher named its nine visualizations from a private `VIEW_LABELS` table while `console.objectView.viewType*` — the same nine words — had been resolved through the bundle by the create-view picker for months; the switcher now reads those keys, so the picker's 「画廊」 and the switcher's 「画廊」 cannot drift apart in nine languages. The create/edit dialog's close button is the remainder of a fix that already landed: objectstack#5505 routed the `sr-only` close label through `common.close` for the two Shadcn-synced primitives, but `MobileDialogContent` is a hand-written wrapper outside that regeneration zone with its own close button, and it is exactly what `ModalForm` renders — so the dialog the report measured was the one place still announcing "Close" in English.
|
|
12
|
+
|
|
13
|
+
The aggregate footer is the one the original report singled out: the **number** was already locale-formatted and the **prefix** was a hardcoded `Avg: ` / `Sum: `. All eleven aggregation kinds now take their prefix from `grid.summary.*`, and the label/value join is its own key rather than a `': '` baked into the renderer — the separator is translatable content, so zh sets a fullwidth colon and fr the French space-before-colon. The numbers are untouched. The form dialog's `sr-only` description fallback joins the packs too; it is clipped, not visible, so the only way an app could displace it was to author a `description` and thereby put a visible subtitle on every dialog.
|
|
14
|
+
|
|
15
|
+
**The Settings namespace screen converts as one unit.** `SettingsView` routed zero framing copy through i18n — save/failure toasts, the env-lock and crypto refusals, the load-error card, the empty-route state, the navigation buttons, the unsaved-changes save bar — while its immediate sibling `SettingsHub`, in the same directory, resolved everything through `t('console.settingsHub.*')`. A zh-CN admin read correctly translated field labels sitting inside an English save bar, because `useSettingsLabel` translates a namespace's authored content but reaches none of the chrome around it. All of it now resolves through a `console.settingsView.*` namespace placed beside the hub's, including the crypto-refusal strings that objectui#4579 deliberately left in English rather than leave one translated string among a dozen literals.
|
|
16
|
+
|
|
17
|
+
The save-bar counter was an English plural rule executing in every locale (`change` plus an `s` when the count exceeds one). It is now a real i18next plural family — base key plus `_one` and `_other` in all ten packs — not the `(s)` spelling translated nine ways. The base key is the load-bearing part: i18next asks `Intl.PluralRules` for the one suffix a language needs and, finding no such slot, falls back to English, so without it Russian would read English at counts 2-20 and Arabic at 2-99. Russian and Arabic take the "noun: {count}" form their packs already use for this exact reason, and the counter is verified rendering in-language at 1, 2 and 5.
|
|
18
|
+
|
|
19
|
+
The Beta badge reuses the hub's existing key rather than minting a twin, and the refusal messages interpolate their subject through the bundle instead of concatenating a translated word onto an English prefix.
|
|
20
|
+
|
|
21
|
+
- c32a8a1: `richtext` fields are placed like the long-form fields they are — four layout sets stopped spelling the type three ways the spec rejects
|
|
22
|
+
|
|
23
|
+
`@objectstack/spec` spells the WYSIWYG type `richtext`, one word, and **rejects** `rich_text` and `rich-text`: both exist only as typo keys in the spec's own `suggestFieldType` table, so `FieldSchema` refuses a field declared with either. Four sets that place fields by matching the RAW type string carried nothing else — `SKIP_TYPES` in the related list spelled it `rich_text`, both `WIDE_FIELD_TYPES` and `SECONDARY_FIELD_TYPES` spelled it `rich-text` — so each set was inert for the only spelling a producer can emit, and every one of them named the type it was failing to handle.
|
|
24
|
+
|
|
25
|
+
For a real `richtext` field that meant: it was auto-derived into a related-list column, it never spanned the full row in a multi-column detail section or form (unlike `markdown` and `html` sitting right beside it in the same sets), and it stayed in the dense primary section of the record page instead of dropping into "More details". All four move together — half of them would have left the detail page and the form disagreeing about the same field, which is worse than the uniform gap.
|
|
26
|
+
|
|
27
|
+
The dead spellings are dropped rather than kept alongside the live one: the alias table is the single place aliases belong, and a set that carries both invites the next drift. The pins are derived from the spec's own `FieldType` vocabulary instead of enumerated, so a member that stops being a real type name fails by name — replacing an assertion that was green only because the set contained the string it asked about.
|
|
28
|
+
|
|
29
|
+
`markdown` joins `richtext` and `html` in the related list's `SKIP_TYPES`, on a measurement rather than on the assumption that it renders raw. It does not: markdown and richtext both render through `MarkdownCellRenderer`, formatted and sanitized. The reason none of the three works in a table is that the formatted output is block-level — a heading, paragraphs, a list — inside a single-line truncating cell, so a document shows as one clipped heading with the rest invisible. `textarea` stays derived for the same reason read the other way: it renders as plain truncated text, which is a useful column. Author-declared columns are untouched — this set only filters the zero-config auto-derive walk.
|
|
30
|
+
|
|
31
|
+
- Updated dependencies [0e67b53]
|
|
32
|
+
- Updated dependencies [ceccdcf]
|
|
33
|
+
- Updated dependencies [d6e5124]
|
|
34
|
+
- Updated dependencies [debad27]
|
|
35
|
+
- Updated dependencies [dc2aa3e]
|
|
36
|
+
- Updated dependencies [ee66e2e]
|
|
37
|
+
- Updated dependencies [e2e6360]
|
|
38
|
+
- Updated dependencies [ee26e65]
|
|
39
|
+
- Updated dependencies [5900ac5]
|
|
40
|
+
- Updated dependencies [932cbcd]
|
|
41
|
+
- Updated dependencies [734d186]
|
|
42
|
+
- Updated dependencies [f650253]
|
|
43
|
+
- Updated dependencies [3d9769a]
|
|
44
|
+
- Updated dependencies [8f85f8b]
|
|
45
|
+
- Updated dependencies [d0c3b26]
|
|
46
|
+
- Updated dependencies [3fc2971]
|
|
47
|
+
- Updated dependencies [aca27fa]
|
|
48
|
+
- Updated dependencies [dde7283]
|
|
49
|
+
- Updated dependencies [f7c6430]
|
|
50
|
+
- Updated dependencies [4dadf0d]
|
|
51
|
+
- Updated dependencies [ae10a01]
|
|
52
|
+
- Updated dependencies [0f21348]
|
|
53
|
+
- Updated dependencies [d2e2caf]
|
|
54
|
+
- Updated dependencies [92876f0]
|
|
55
|
+
- Updated dependencies [f279deb]
|
|
56
|
+
- Updated dependencies [4b70d28]
|
|
57
|
+
- Updated dependencies [eb7f586]
|
|
58
|
+
- Updated dependencies [e901131]
|
|
59
|
+
- Updated dependencies [ebb4e0e]
|
|
60
|
+
- Updated dependencies [3a9021e]
|
|
61
|
+
- Updated dependencies [d9d3463]
|
|
62
|
+
- Updated dependencies [2a40f69]
|
|
63
|
+
- Updated dependencies [bec3e14]
|
|
64
|
+
- Updated dependencies [613b167]
|
|
65
|
+
- Updated dependencies [b4d3c22]
|
|
66
|
+
- Updated dependencies [1f9b905]
|
|
67
|
+
- Updated dependencies [8f60d73]
|
|
68
|
+
- Updated dependencies [cb13400]
|
|
69
|
+
- Updated dependencies [828549a]
|
|
70
|
+
- Updated dependencies [e1ade8f]
|
|
71
|
+
- Updated dependencies [bc64bfe]
|
|
72
|
+
- Updated dependencies [abb0f81]
|
|
73
|
+
- Updated dependencies [38ab505]
|
|
74
|
+
- Updated dependencies [3e19fe7]
|
|
75
|
+
- Updated dependencies [bb58d1d]
|
|
76
|
+
- Updated dependencies [433ff9f]
|
|
77
|
+
- Updated dependencies [5cc847c]
|
|
78
|
+
- Updated dependencies [e7663f2]
|
|
79
|
+
- Updated dependencies [fa21254]
|
|
80
|
+
- Updated dependencies [33c32bf]
|
|
81
|
+
- Updated dependencies [66fb4fa]
|
|
82
|
+
- Updated dependencies [b953a97]
|
|
83
|
+
- Updated dependencies [d7f3e30]
|
|
84
|
+
- Updated dependencies [6d641c9]
|
|
85
|
+
- Updated dependencies [7e4f0e5]
|
|
86
|
+
- Updated dependencies [a84385b]
|
|
87
|
+
- Updated dependencies [45e1949]
|
|
88
|
+
- Updated dependencies [92250d6]
|
|
89
|
+
- Updated dependencies [c1d939f]
|
|
90
|
+
- Updated dependencies [58bebf6]
|
|
91
|
+
- Updated dependencies [36310dc]
|
|
92
|
+
- Updated dependencies [52d878a]
|
|
93
|
+
- Updated dependencies [405e808]
|
|
94
|
+
- Updated dependencies [49ae9f4]
|
|
95
|
+
- Updated dependencies [a3ae404]
|
|
96
|
+
- Updated dependencies [bfdf3d4]
|
|
97
|
+
- Updated dependencies [bb68488]
|
|
98
|
+
- Updated dependencies [c0f9a4b]
|
|
99
|
+
- Updated dependencies [b1e42d0]
|
|
100
|
+
- Updated dependencies [2459a3e]
|
|
101
|
+
- Updated dependencies [ac853ce]
|
|
102
|
+
- Updated dependencies [fa51109]
|
|
103
|
+
- Updated dependencies [d6aa172]
|
|
104
|
+
- Updated dependencies [fe52a04]
|
|
105
|
+
- Updated dependencies [d46f9b8]
|
|
106
|
+
- Updated dependencies [3f5f87c]
|
|
107
|
+
- Updated dependencies [2fea4d2]
|
|
108
|
+
- Updated dependencies [f5e1143]
|
|
109
|
+
- Updated dependencies [7f1cb33]
|
|
110
|
+
- Updated dependencies [f148a64]
|
|
111
|
+
- Updated dependencies [bb68488]
|
|
112
|
+
- Updated dependencies [2e3b0c0]
|
|
113
|
+
- Updated dependencies [9461dd3]
|
|
114
|
+
- Updated dependencies [78fa331]
|
|
115
|
+
- Updated dependencies [47f551b]
|
|
116
|
+
- Updated dependencies [31ab1ac]
|
|
117
|
+
- Updated dependencies [0082db8]
|
|
118
|
+
- Updated dependencies [ab04728]
|
|
119
|
+
- Updated dependencies [5bf09fd]
|
|
120
|
+
- Updated dependencies [06915b0]
|
|
121
|
+
- Updated dependencies [ff84b05]
|
|
122
|
+
- @object-ui/i18n@17.5.0
|
|
123
|
+
- @object-ui/react@17.5.0
|
|
124
|
+
- @object-ui/components@17.5.0
|
|
125
|
+
- @object-ui/core@17.5.0
|
|
126
|
+
- @object-ui/fields@17.5.0
|
|
127
|
+
- @object-ui/types@17.5.0
|
|
128
|
+
- @object-ui/permissions@17.5.0
|
|
129
|
+
|
|
130
|
+
## 17.4.0
|
|
131
|
+
|
|
132
|
+
### Minor Changes
|
|
133
|
+
|
|
134
|
+
- ecae400: Retire the `capability-multiselect` field widget name, which existed only on the docs-site registration path and which nothing ever stamped (objectui#3308, ADR-0049 enforce-or-remove)
|
|
135
|
+
|
|
136
|
+
`field:capability-multiselect` was registered by `registerFields()` and only there. That function's sole caller is the docs site, so the key never existed on the live path (`registerAllFields()`, run at module import, iterates `fieldWidgetMap` — which never listed it). A field authored with `widget: 'capability-multiselect'` therefore resolved to nothing in every real application, while the comment above the registration described it as usable from a record form: a code comment promising a capability that does not exist, which is the worst direction for a metadata renderer AI-authored apps read as authority.
|
|
137
|
+
|
|
138
|
+
Nothing stamped the hint either. ADR-0056 P1 stamps `permission-facet-link` on all six `sys_permission_set` facets — `system_permissions` included — through the single `ObjectStackAdapter.getObjectSchema` choke point, and P2 put the capability editor in Studio. The widget name was a leftover from an intermediate iteration of that rollout.
|
|
139
|
+
|
|
140
|
+
Removed, with a tombstone at each site:
|
|
141
|
+
|
|
142
|
+
- `@object-ui/fields` — the `field:capability-multiselect` registration and the comment that advertised it. **Breaking in name only**: the key was unreachable outside the docs site, so no application could have resolved it. A field still carrying the hint now degrades to its declared `type` renderer, the defined behavior for an unregistered widget.
|
|
143
|
+
- `@object-ui/plugin-detail` — `InlineFieldInput`'s `widget === 'capability-multiselect'` branch, the hint's last honoring surface. Leaving one consumer for a name no producer emits and no form resolves is the same declared-vs-enforced split, inverted. The sibling `permission-facet-link` branch is untouched and pinned.
|
|
144
|
+
- `@object-ui/components` — the dead `capability-multiselect` entry in the form renderer's `DATA_SOURCE_FIELD_TYPES` set, which could never match a resolvable widget.
|
|
145
|
+
- `@object-ui/plugin-form` — a comment naming `capability-multiselect` as the widget stamped onto `sys_permission_set.system_permissions`; it names `permission-facet-link` now, which is what is actually stamped.
|
|
146
|
+
|
|
147
|
+
`CapabilityMultiSelectField` itself is **unchanged and still exported**: Studio's `PermissionMatrixEditor` imports and renders it directly, which is ADR-0056 P2's design. Only the widget name is retired — the component is not a registry field widget and its doc comment now says so.
|
|
148
|
+
|
|
149
|
+
`registerFields()` is also **kept**, with its `@deprecated Use registerAllFields() instead` note corrected. The two are not interchangeable: it registers `createFieldRenderer(widget)`, which synthesizes the label, description and the local `value`/`onChange` state that lets a bare field node (`{ type: 'currency', label: 'Amount' }`) render standalone in the docs demos. Retiring it needs a decision about where that demo chrome goes; the note now records that instead of implying a drop-in replacement.
|
|
150
|
+
|
|
151
|
+
- 1bd6faa: fix(fields,plugin-form): stop the inline child grid from collapsing `datetime`/`time` columns onto the `date` control
|
|
152
|
+
|
|
153
|
+
`deriveMasterDetail`'s `fieldTypeToColumnType` mapped `date`, `datetime` and `time` onto the single `date` grid column type, and `GridField` renders that as `<input type="date">`. The consequence was not cosmetic: that control emits a bare `YYYY-MM-DD` on change, so a user who merely re-picked the **day** of a `datetime` cell silently wrote the record's time component out of existence — a `14:30` became midnight with no warning and no undo.
|
|
154
|
+
|
|
155
|
+
`GridColumn['type']` now carries `'datetime'` and `'time'` alongside `'date'`, and each renders its own control with its own read/write adapter:
|
|
156
|
+
|
|
157
|
+
- `datetime` → `<input type="datetime-local">`, read through `toDateTimeInputValue` and written back through `fromDateTimeInputValue`, so the stored shape stays ISO-8601 and read and write share one basis (the contract `DateTimeField` already follows, objectui#3127).
|
|
158
|
+
- `time` → `<input type="time">`, round-tripping the stored zone-less `HH:mm[:ss]` verbatim.
|
|
159
|
+
- `date` → unchanged.
|
|
160
|
+
|
|
161
|
+
The read-only surfaces are fixed with it. `displayText()` and the read-only table both fell through to `String(value)` and printed the raw stored ISO on screen (`2026-06-17T00:00:00.000Z`); each temporal type now formats as itself — a day for `date`, day + local time for `datetime`, the wall clock for `time`. That could not be fixed before the type collapse was undone, because with one column type the renderer had no way to know which of the two to show.
|
|
162
|
+
|
|
163
|
+
Authors writing explicit grid `columns` can now declare `type: 'datetime'` / `type: 'time'`; previously those spellings were not part of the exported union.
|
|
164
|
+
|
|
165
|
+
### Patch Changes
|
|
166
|
+
|
|
167
|
+
- 8497579: A required field whose `defaultValue` is a runtime token is submittable from a create form
|
|
168
|
+
|
|
169
|
+
`@objectstack/spec` lets a field's `defaultValue` be a runtime _instruction_
|
|
170
|
+
rather than a value — the `DEFAULT_VALUE_TOKENS` family (`'NOW()'`,
|
|
171
|
+
`'current_user'`) or a CEL Expression envelope. The server resolves those per
|
|
172
|
+
insert, in `ObjectQL.applyFieldDefaults`, for any field that arrives absent or
|
|
173
|
+
null, which is why a create form must leave them empty: seeding the literal text
|
|
174
|
+
`NOW()` into a datetime input and submitting it suppresses the very resolution
|
|
175
|
+
the declaration asked for.
|
|
176
|
+
|
|
177
|
+
Correct for an optional field. Combined with `required: true` it deadlocked:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
the control opened empty, the client-side required rule refused the submit, and
|
|
184
|
+
there was nothing sensible for the user to type — the declaration had already
|
|
185
|
+
said what the value is, and omitting the field is exactly what makes the server
|
|
186
|
+
supply it. Same shape as the `required` + static-default case, one layer down.
|
|
187
|
+
|
|
188
|
+
In **create** mode a runtime `defaultValue` now suppresses the client-side
|
|
189
|
+
`required` rule, and the field is omitted from the payload. The producer
|
|
190
|
+
guarantees the value at insert, so the field is not "missing" — it is
|
|
191
|
+
server-owned. `required: true` alongside a runtime default is coherent authoring
|
|
192
|
+
(storage-level required, producer-guaranteed), not an authoring error.
|
|
193
|
+
|
|
194
|
+
Both halves matter. Suppressing the rule alone would have been half an answer: a
|
|
195
|
+
rendered control registers with the form whether or not anything seeded it, so
|
|
196
|
+
an untouched runtime-default field still reached the payload as `undefined` — or
|
|
197
|
+
as `''` once anything focused it. `undefined` is invisible to a
|
|
198
|
+
`JSON.stringify` inspection while remaining a KEY a data source may translate
|
|
199
|
+
into an explicit column write, and `''` is neither absent nor null, so it stores
|
|
200
|
+
a blank and defeats the declaration outright.
|
|
201
|
+
|
|
202
|
+
Three boundaries came with it, each pinned in both directions:
|
|
203
|
+
|
|
204
|
+
- **Create only.** An edit form shows a persisted row, where the token was
|
|
205
|
+
resolved at insert; blanking a required column there is a real removal and is
|
|
206
|
+
still refused.
|
|
207
|
+
- **Runtime defaults only.** A static literal default _is_ seeded into the
|
|
208
|
+
control, so if the user clears it they have removed a value that was really
|
|
209
|
+
there — `required` still fires.
|
|
210
|
+
- **The rule, not the field.** A value the user does type is submitted normally
|
|
211
|
+
and outranks the declared default. Only the "must not be empty" check is
|
|
212
|
+
suppressed.
|
|
213
|
+
|
|
214
|
+
Seeding and this rule read ONE predicate (`isRuntimeDefault`), so a form can
|
|
215
|
+
never seed a field it also refuses to submit. The suppression also drops the
|
|
216
|
+
required marker and `aria-required` for that field in create mode, since both
|
|
217
|
+
are driven by the same boolean — the honest reading, as the user really is not
|
|
218
|
+
required to provide the value. Surfacing what the server _will_ supply, as a
|
|
219
|
+
non-authoritative preview, is a separate follow-up.
|
|
220
|
+
|
|
221
|
+
Not extended to `requiredWhen` (the conditional-required CEL rule), which is
|
|
222
|
+
resolved downstream in the form renderer against the live record.
|
|
223
|
+
|
|
224
|
+
- f0c9a90: Create forms now open with the object schema's declared `defaultValue`s
|
|
225
|
+
|
|
226
|
+
A field declared `required: true, defaultValue: 'draft'` opened the console's
|
|
227
|
+
create dialog with an empty select and a required marker: the user had to pick a
|
|
228
|
+
value the system already knew, with every neighbouring option — some with side
|
|
229
|
+
effects — one click away. `defaultValue` + `required` produced the worst create
|
|
230
|
+
experience of any modelling choice, strictly worse than declaring no default.
|
|
231
|
+
|
|
232
|
+
The server was never the problem. Omitting the field from a create request
|
|
233
|
+
stores the declared default, because `ObjectQL.applyFieldDefaults` resolves it on
|
|
234
|
+
insert. The gap was container-side: `ObjectForm` seeded its opening values from
|
|
235
|
+
the object schema, and the five other object-form containers did not — their
|
|
236
|
+
create branch set the form data to `initialData || initialValues || {}` and never
|
|
237
|
+
looked at the schema. The console's create dialog is the global `<ModalForm>`,
|
|
238
|
+
one of those five. Modal, Drawer, Tabbed, Split and Wizard now seed through one
|
|
239
|
+
shared module (`schemaDefaults`), so a create form opens preselected and
|
|
240
|
+
submittable.
|
|
241
|
+
|
|
242
|
+
Three boundaries came with it, each pinned in both directions:
|
|
243
|
+
|
|
244
|
+
- **Create only.** An edit form shows a persisted row as the server holds it.
|
|
245
|
+
`ObjectForm`'s pass had been running in every mode, so a column the record
|
|
246
|
+
leaves unset showed the default — arming a silent write of a value the user
|
|
247
|
+
never chose on the next save of any other field. It is now gated on the same
|
|
248
|
+
"no persisted record" test the data-fetch effect uses.
|
|
249
|
+
- **Static defaults only.** A `defaultValue` may be an instruction the server
|
|
250
|
+
resolves per insert — the `NOW()` / `current_user` runtime tokens
|
|
251
|
+
(`DEFAULT_VALUE_TOKENS`) or a CEL Expression envelope. `ObjectForm` had been
|
|
252
|
+
seeding those verbatim, which put the literal text `NOW()` into a datetime
|
|
253
|
+
input and then submitted it as the field's value, suppressing the very
|
|
254
|
+
resolution the declaration asked for (`applyFieldDefaults` only fills fields
|
|
255
|
+
that arrive empty). Those are now left empty for the server.
|
|
256
|
+
- **Callers still win.** `initialData` / `initialValues` outrank a schema
|
|
257
|
+
default — a lookup prefill or a duplicate-record seed is the more specific
|
|
258
|
+
instruction.
|
|
259
|
+
|
|
260
|
+
Only the field-level `defaultValue` is honoured, not a select option's
|
|
261
|
+
`default: true`, even though `@objectstack/spec`'s `SelectOptionSchema` declares
|
|
262
|
+
that key: the insert path resolves `defaultValue` and nothing else, so seeding
|
|
263
|
+
from option-level `default` would preselect values the server would never have
|
|
264
|
+
applied — a UI-only second default contract.
|
|
265
|
+
|
|
266
|
+
- 5bfaabd: `PageComponentSchema.dataSource` now reaches every object-bound block, not just
|
|
267
|
+
`list-view` — and `element:record_picker` stops discarding `view`
|
|
268
|
+
(objectstack#6953).
|
|
269
|
+
|
|
270
|
+
objectstack#5576 wired the spec's per-element data binding
|
|
271
|
+
(`dataSource: { object, view?, filter?, sort?, limit? }`) to `list-view` and left
|
|
272
|
+
the same declaration inert on every other page component. Two gaps remained, and
|
|
273
|
+
both were silent:
|
|
274
|
+
|
|
275
|
+
- **`element:record_picker` read four of the five keys and dropped `view`.** So
|
|
276
|
+
`dataSource: { object: 'account', view: 'hot' }` — the spec's own example —
|
|
277
|
+
built a picker over EVERY account instead of the rows the saved view selects.
|
|
278
|
+
Nothing threw and nothing rendered an error; the option list was simply wider
|
|
279
|
+
than what was authored, which also means a user could select a record the page
|
|
280
|
+
said was out of scope.
|
|
281
|
+
- **`object-grid` / `object-form` / `object-kanban` / `object-calendar` /
|
|
282
|
+
`object-chart` / `object-metric` / `record:related_list` read none of it.**
|
|
283
|
+
Each gates its fetch on its own `objectName`, and nothing mapped
|
|
284
|
+
`dataSource.object` onto it, so a page written the way the spec documents
|
|
285
|
+
rendered an empty grid / a field-less form / a board with no cards / an empty
|
|
286
|
+
month / an empty chart / a static metric number — with no request and no
|
|
287
|
+
diagnostic anywhere. Spec-valid metadata rendering nothing is the
|
|
288
|
+
objectstack#4413 shape.
|
|
289
|
+
|
|
290
|
+
Composition follows objectstack#5576's landed semantics unchanged on every block:
|
|
291
|
+
a named saved view supplies the baseline, a key written on the component itself
|
|
292
|
+
overrides it, an explicit binding key overrides both, `filter` AND-combines
|
|
293
|
+
("additional filter criteria" — a binding can narrow a view, never widen it), and
|
|
294
|
+
a `view` name that does not resolve renders a configuration error instead of
|
|
295
|
+
degrading to the object's full scope.
|
|
296
|
+
|
|
297
|
+
- `@object-ui/react` — new `useElementDataSourceSchema(schema, mapping, dataSource?)`
|
|
298
|
+
and `ElementDataSourceGate` apply a resolved binding to the schema keys a given
|
|
299
|
+
block reads, plus `ElementDataSourceErrorPanel` / `ElementDataSourceLoadingPanel`
|
|
300
|
+
for the two non-final states. One precedence table for all blocks rather than
|
|
301
|
+
one copy per block — that copy is how "additional filter criteria" would have
|
|
302
|
+
become two dialects.
|
|
303
|
+
- A mapping names **only** keys its block genuinely reads. A composed value
|
|
304
|
+
written onto a key the block ignores would be accepted and dropped, which is
|
|
305
|
+
the defect being removed, one layer deeper — so a kanban's swimlane `columns`
|
|
306
|
+
never receive a view's field list, and a block with no row cap leaves `limit`
|
|
307
|
+
unmapped. The per-block coverage table, including two residual gaps that are
|
|
308
|
+
named rather than papered over, is in `content/docs/guide/data-source.md`.
|
|
309
|
+
|
|
310
|
+
No behaviour changes for a block that carries no `dataSource`: the binding-free
|
|
311
|
+
path returns the schema by reference, so nothing remounts and nothing refetches.
|
|
312
|
+
|
|
313
|
+
- 022002a: `PageComponentSchema.dataSource` now reaches the remaining object-bound public
|
|
314
|
+
blocks: `object-gantt` / `object-timeline` / `object-map` / `object-pivot` /
|
|
315
|
+
`object-master-detail-form` / `embeddable-form` / `record:line_items`
|
|
316
|
+
(objectstack#7121).
|
|
317
|
+
|
|
318
|
+
objectstack#6953 wired the spec's per-element data binding
|
|
319
|
+
(`dataSource: { object, view?, filter?, sort?, limit? }`) to the eight blocks it
|
|
320
|
+
named and left the same declaration inert on these seven. Each gates its fetch on
|
|
321
|
+
its own object key and nothing mapped `dataSource.object` onto it, so a page
|
|
322
|
+
written the way the spec documents rendered an empty gantt / an empty timeline
|
|
323
|
+
rail / a map with no markers / an empty cross-tab / a field-less form — with no
|
|
324
|
+
request and no diagnostic anywhere. Spec-valid metadata rendering nothing is the
|
|
325
|
+
objectstack#4413 shape.
|
|
326
|
+
|
|
327
|
+
Composition follows objectstack#5576's landed semantics unchanged, through the
|
|
328
|
+
shared `ElementDataSourceGate` (no change to it or to the resolution layer): a
|
|
329
|
+
named saved view supplies the baseline, a key written on the component itself
|
|
330
|
+
overrides it, an explicit binding key overrides both, `filter` AND-combines
|
|
331
|
+
("additional filter criteria" — a binding can narrow a view, never widen it), and
|
|
332
|
+
a `view` name that does not resolve renders a configuration error on every one of
|
|
333
|
+
these blocks instead of degrading to the object's full scope.
|
|
334
|
+
|
|
335
|
+
Each block maps **only** the keys it genuinely reads, which for this batch means
|
|
336
|
+
several keys stay deliberately unmapped rather than being parked somewhere
|
|
337
|
+
plausible:
|
|
338
|
+
|
|
339
|
+
- `object-gantt` and `object-map` take `object` / `filter` / `sort`; neither has a
|
|
340
|
+
row cap or a field-list read site.
|
|
341
|
+
- `object-pivot` takes `object` / `filter`; a cross-tab orders itself by its own
|
|
342
|
+
row/column grouping and cannot be computed over a truncated page.
|
|
343
|
+
- `object-timeline` takes `object` only — its fetch is
|
|
344
|
+
`find(objectName, { options: { $top: 100 } })`, with no filter/sort read site
|
|
345
|
+
at all, so a named view is error-checked and then contributes nothing.
|
|
346
|
+
- `embeddable-form` and `object-master-detail-form` take `object` only (the
|
|
347
|
+
parent object, in the master-detail case); a form that writes one record has no
|
|
348
|
+
collection query for `filter` / `sort` / `limit` to narrow.
|
|
349
|
+
- `record:line_items` takes `object` onto **`childObject`** — the collection it
|
|
350
|
+
actually lists — and nothing else: its query is the parent FK plus a fixed
|
|
351
|
+
`$top: 500`, and its `columns` are editable `GridColumn` objects rather than a
|
|
352
|
+
field-name projection a view could supply.
|
|
353
|
+
|
|
354
|
+
The per-block coverage table, including every residual gap named above, is in
|
|
355
|
+
`content/docs/guide/data-source.md`.
|
|
356
|
+
|
|
357
|
+
No behaviour change for a block that carries no `dataSource`: the binding-free
|
|
358
|
+
path returns the schema by reference, so nothing remounts and nothing refetches.
|
|
359
|
+
|
|
360
|
+
- 6d762da: The five locale keys behind #3546's eight no-fallback `t()` call sites are now defined in all ten packs, so the built-in-view toasts, the activity-timeline source link, the wizard's required-field toast and the Gantt refresh button's accessible name are translated instead of falling back to English — or, on two surfaces, to the key itself (part of #3546).
|
|
361
|
+
|
|
362
|
+
`scripts/check-i18n-call-site-keys.mjs` measured 258 keys that a `t()` call site asks for and no pack defines. These five were the subset with no working inline default: `console.objectView.cannotEditMetaView`, `console.objectView.cannotDeleteMetaView`, `detail.viewSource`, `gantt.toolbar.refresh` and `wizard.missingRequired`. Adding a `defaultValue` is deliberately not the fix — that mechanism is what kept all 258 invisible for months.
|
|
363
|
+
|
|
364
|
+
**Two of the eight sites really did render the raw key**, and both go through a binding with nothing in front of i18next. `ObjectView.tsx` calls `useObjectTranslation()` directly, so five toasts read `console.objectView.cannotEditMetaView` / `cannotDeleteMetaView` on screen; the `|| 'Built-in views cannot be renamed.'` guards next to them were dead on every path, because i18next answers a miss with the key itself and a non-empty string never falls through `||`. Those four unreachable English strings are removed rather than repaired: one key served four call sites (rename / pin / set-as-default / configure), so the pack copy covers any change to a built-in view instead of naming one operation. `RecordActivityTimeline.tsx` fails the same way for a subtler reason — `useDetailTranslation` is `createSafeTranslation(..., 'detail.back')`, and because `detail.back` does resolve, the probe hands back i18next's `t` for every key and bypasses the defaults map wholesale, so `detail.viewSource` reached the user verbatim.
|
|
365
|
+
|
|
366
|
+
**The other two sites were not rendering a raw key**, contrary to the issue's description, and are fixed here as the milder "English in all ten languages" class. `wizard.missingRequired` is its own hook's probe key, so the probe failed and `createSafeTranslation` correctly served its English default. `gantt.toolbar.refresh` goes through `useGanttTranslation`, which deliberately does not use `createSafeTranslation` and falls back per key — so the refresh button's `aria-label` was "Refresh", in English, never the key. Screen-reader users heard an English word rather than an identifier; a `zh` session now hears 刷新.
|
|
367
|
+
|
|
368
|
+
Regression cover is provider-mounted on purpose: with no `I18nProvider` the defaults maps answer every one of these keys and the assertions pass while the console is broken, which is precisely the false-green the issue documents. For the two sites whose English output was already correct, `en` cannot discriminate before from after — the `zh` assertions are the ones that pin the fix.
|
|
369
|
+
|
|
370
|
+
- 11c1e71: Resolve a `select` field declared `multiple: true` to the `field:multiselect` widget, so the object form's visible label actually names the chip picker it renders (objectui#3986).
|
|
371
|
+
|
|
372
|
+
`mapFieldTypeToFormType` keyed the widget id on the field's `type` string alone, so an object-schema `{ type: 'select', multiple: true }` picklist — a spec-legal, entirely ordinary shape — became `field:select`. `SelectField` then delegated to `MultiSelectField` on `config.multiple`, so the component that RENDERED was the chip picker while everything keyed on the widget id still answered for the single-value combobox. Above all the label-association declaration (`ComponentMeta.labelling`, objectui#3961), which the form renderer resolves per widget id: the host emitted `<label for>` at the chip row's wrapper `div`, where a `for` is inert — `HTMLLabelElement.control` returns `null`. Visually the field had a label; in the accessibility tree that label named nothing. Measured on the object-form path, `role=group` + accessible name went from 1 for a `multiselect`-typed field (fixed in objectui#3975) to 0 for this one.
|
|
373
|
+
|
|
374
|
+
Declaring `select` itself `labelling: 'group'` was not available: a single-value select's trigger is a labelable `button[role=combobox]` whose `for` association works, and a bare `select` is a builtin the renderer resolves without consulting the registry at all. The fix is therefore at the producer — the widget id now carries the arity, so one place decides which widget renders and the declaration can no longer be addressed to a widget that is not rendering.
|
|
375
|
+
|
|
376
|
+
- `mapFieldTypeToFormType(fieldType, config?)` takes an optional second argument — the rest of the field definition, of which only `multiple` is read. Existing single-argument calls are unchanged, and so is every type outside the new table: `select` is the only one whose `multiple` form is a different WIDGET. The spec's multi-capable set is larger (select / lookup / file / image, with `radio` on the select branch and `user` storing like `lookup`), but `LookupField`, `FileField` and `ImageField` each render both arities themselves, so their id — and their labelling declaration — is already right for either.
|
|
377
|
+
- The four object-form producers pass the pair: `ObjectForm`, `DrawerForm`, `ModalForm`, and `sectionFields` (Tabbed / Wizard / Split / Drawer / Modal). In `sectionFields` the id is now computed once from the EFFECTIVE pair, after view-level overrides have merged, because `multiple` is itself a spec `FormField` key: a view restating only `multiple: true` over a single-value object field moves the widget too, and `multiple: false` moves it back.
|
|
378
|
+
- `SelectField`'s `multiple` delegation is KEPT, not retired. Measured, it stays reachable from three entrances that never consult the alias map: the inline grid editor (`FieldEditWidget` finds `select` in its own table first), `ActionParamDialog` (`resolveFormWidgetType` returns `select` from `fieldWidgetMap` first), and hand-written SDUI addressing `field:select` by name with `multiple` on its metadata.
|
|
379
|
+
|
|
380
|
+
Read-only rendering of these widgets is untouched (objectui#4005), as is the built-in `Select` branch (objectui#3976).
|
|
381
|
+
|
|
382
|
+
- 523be48: `object-timeline` and `record:line_items` now apply the filter / sort / row cap they are given, so a named `dataSource.view` narrows them instead of contributing nothing
|
|
383
|
+
|
|
384
|
+
These were the two residual gaps in objectstack#7121's per-block coverage table
|
|
385
|
+
(objectstack#7137). Both blocks are object-bound lists, both accepted the spec's
|
|
386
|
+
per-element `dataSource` binding, and neither had a read site for `filter` or
|
|
387
|
+
`sort` anywhere in its fetch:
|
|
388
|
+
|
|
389
|
+
- `object-timeline`'s entire query was
|
|
390
|
+
`find(objectName, { options: { $top: 100 } })`.
|
|
391
|
+
- `record:line_items`' was the parent FK plus a fixed `$top: 500`.
|
|
392
|
+
|
|
393
|
+
So `dataSource: { object, view: 'hot' }` resolved the view — a typo still reported
|
|
394
|
+
a configuration error, it never degraded into an unfiltered query — and then
|
|
395
|
+
dropped everything the view said. The rendered rows could be **wider than the view
|
|
396
|
+
they named**, silently, which is exactly the class of mistake AI-authored metadata
|
|
397
|
+
hides best: the page looks like it works. objectstack#7121 deliberately left the
|
|
398
|
+
keys unmapped and recorded the gap rather than writing composed values onto schema
|
|
399
|
+
keys nobody read; this closes it at the fetch instead.
|
|
400
|
+
|
|
401
|
+
What each block now reads:
|
|
402
|
+
|
|
403
|
+
- **`object-timeline`** — `$filter: schema.filter`,
|
|
404
|
+
`$orderby: convertSortToQueryParams(schema.sort)`, and
|
|
405
|
+
`$top: schema.limit ?? 100`, matching the form `object-gantt` / `object-map` /
|
|
406
|
+
`object-calendar` already use. Its registry mapping gains
|
|
407
|
+
`filter` / `sort` / `limit`; `columns` stays unmapped, because a timeline
|
|
408
|
+
projects the fields its `timeline` config names.
|
|
409
|
+
- **`record:line_items`** — the composed filter is **AND-combined** with the parent
|
|
410
|
+
relationship condition through `mergeFilterNodes`, never substituted for it, the
|
|
411
|
+
same way `record:related_list` composes its own since objectstack#7118: a
|
|
412
|
+
line-items panel is always scoped to the record it sits on, so an _additional_
|
|
413
|
+
criterion can only narrow this parent's children and can never surface another
|
|
414
|
+
parent's rows. `sort` becomes the load order and `limit` the row cap (default
|
|
415
|
+
500). `columns` stays unmapped — here they are `GridColumn[]` driving an editable
|
|
416
|
+
grid, not a field-name projection, so a view's column list would be the wrong
|
|
417
|
+
_shape_ rather than merely a wider answer.
|
|
418
|
+
|
|
419
|
+
**Behaviour change worth knowing about:** the timeline's default window is now a
|
|
420
|
+
real cap. `{ options: { $top: 100 } }` nested the limit under a key that is not a
|
|
421
|
+
`QueryParams` field and that no adapter in this repo reads (`convertQueryParams`
|
|
422
|
+
maps `params.$top`), so the intended window never reached the wire and a timeline
|
|
423
|
+
over a large object fetched whatever the server chose to return. It is now sent as
|
|
424
|
+
`$top`, and authorable via `limit` or a view's `pagination.pageSize`.
|
|
425
|
+
|
|
426
|
+
`@object-ui/core` gains `convertSortToQueryParams`, the sort→`$orderby` lowering
|
|
427
|
+
the three sibling blocks each inline privately. It is shared rather than copied
|
|
428
|
+
twice more, and is slightly more faithful to the declared contract than those
|
|
429
|
+
copies: a sort entry that omits `order` means ascending instead of being dropped
|
|
430
|
+
(the string spelling `"amount"` already meant ascending in the same copies), and
|
|
431
|
+
nothing orderable yields `undefined` rather than a truthy empty `{}`. Migrating
|
|
432
|
+
the three existing copies onto it is objectstack#7148 and is not done here.
|
|
433
|
+
|
|
434
|
+
- Updated dependencies [794c497]
|
|
435
|
+
- Updated dependencies [993336f]
|
|
436
|
+
- Updated dependencies [f0a625a]
|
|
437
|
+
- Updated dependencies [b5980f4]
|
|
438
|
+
- Updated dependencies [8aad9fd]
|
|
439
|
+
- Updated dependencies [6719877]
|
|
440
|
+
- Updated dependencies [56ff091]
|
|
441
|
+
- Updated dependencies [0186cdc]
|
|
442
|
+
- Updated dependencies [7864f03]
|
|
443
|
+
- Updated dependencies [ea41a59]
|
|
444
|
+
- Updated dependencies [0cbdca8]
|
|
445
|
+
- Updated dependencies [d229dfa]
|
|
446
|
+
- Updated dependencies [ecae400]
|
|
447
|
+
- Updated dependencies [4bc6c23]
|
|
448
|
+
- Updated dependencies [d3e738a]
|
|
449
|
+
- Updated dependencies [c3b01a7]
|
|
450
|
+
- Updated dependencies [f5f8744]
|
|
451
|
+
- Updated dependencies [7ed3360]
|
|
452
|
+
- Updated dependencies [69becd2]
|
|
453
|
+
- Updated dependencies [5e52495]
|
|
454
|
+
- Updated dependencies [0fa5e4d]
|
|
455
|
+
- Updated dependencies [b750823]
|
|
456
|
+
- Updated dependencies [5bfaabd]
|
|
457
|
+
- Updated dependencies [e06810e]
|
|
458
|
+
- Updated dependencies [ab3ad4f]
|
|
459
|
+
- Updated dependencies [65bb513]
|
|
460
|
+
- Updated dependencies [c97a45e]
|
|
461
|
+
- Updated dependencies [b19162d]
|
|
462
|
+
- Updated dependencies [c2fd122]
|
|
463
|
+
- Updated dependencies [1bd6faa]
|
|
464
|
+
- Updated dependencies [ac2139c]
|
|
465
|
+
- Updated dependencies [b14ab3a]
|
|
466
|
+
- Updated dependencies [e24d767]
|
|
467
|
+
- Updated dependencies [8c60819]
|
|
468
|
+
- Updated dependencies [aca561a]
|
|
469
|
+
- Updated dependencies [e64a52e]
|
|
470
|
+
- Updated dependencies [844d17f]
|
|
471
|
+
- Updated dependencies [d8a0be4]
|
|
472
|
+
- Updated dependencies [48132f7]
|
|
473
|
+
- Updated dependencies [4dcd52a]
|
|
474
|
+
- Updated dependencies [42ae5c6]
|
|
475
|
+
- Updated dependencies [0ef9dfd]
|
|
476
|
+
- Updated dependencies [f4b97c8]
|
|
477
|
+
- Updated dependencies [1d723e3]
|
|
478
|
+
- Updated dependencies [0109f54]
|
|
479
|
+
- Updated dependencies [7e5bb5d]
|
|
480
|
+
- Updated dependencies [fbc23e0]
|
|
481
|
+
- Updated dependencies [6d762da]
|
|
482
|
+
- Updated dependencies [e6fdbdc]
|
|
483
|
+
- Updated dependencies [54233b1]
|
|
484
|
+
- Updated dependencies [c2ecbae]
|
|
485
|
+
- Updated dependencies [f9faa7d]
|
|
486
|
+
- Updated dependencies [97b63d7]
|
|
487
|
+
- Updated dependencies [6bb454a]
|
|
488
|
+
- Updated dependencies [11c1e71]
|
|
489
|
+
- Updated dependencies [523be48]
|
|
490
|
+
- Updated dependencies [7e2b7e9]
|
|
491
|
+
- Updated dependencies [33526fd]
|
|
492
|
+
- Updated dependencies [32413ec]
|
|
493
|
+
- Updated dependencies [c1e1e6b]
|
|
494
|
+
- @object-ui/components@17.4.0
|
|
495
|
+
- @object-ui/react@17.4.0
|
|
496
|
+
- @object-ui/core@17.4.0
|
|
497
|
+
- @object-ui/fields@17.4.0
|
|
498
|
+
- @object-ui/i18n@17.4.0
|
|
499
|
+
- @object-ui/types@17.4.0
|
|
500
|
+
- @object-ui/permissions@17.4.0
|
|
501
|
+
|
|
502
|
+
## 17.3.0
|
|
503
|
+
|
|
504
|
+
### Minor Changes
|
|
505
|
+
|
|
506
|
+
- f44d872: `mobile.fullscreenLongText` finally reaches auto-generated long-text fields, and
|
|
507
|
+
`mobile_fullscreen` gets one declared carrier (objectui#3245).
|
|
508
|
+
|
|
509
|
+
FROM: `ObjectForm` stamped the flag onto the FormField itself
|
|
510
|
+
(`{ ...f, mobile_fullscreen: true }`). TO: it stamps the flag onto the object the
|
|
511
|
+
form renderer will actually forward to the widget as `field` — `f.field || f`,
|
|
512
|
+
resolved exactly the way `renderFieldComponent` resolves it.
|
|
513
|
+
|
|
514
|
+
**The flag's only legal carrier is the field metadata, and its only producer is
|
|
515
|
+
`ObjectForm`.** That convention was already what the widget side assumed after
|
|
516
|
+
objectui#3232/#3233 (`TextAreaField` reads `field.mobile_fullscreen` and nothing
|
|
517
|
+
else, and `field` is the single metadata carrier); the producer was writing to a
|
|
518
|
+
different object, so for auto-generated fields the two never met.
|
|
519
|
+
|
|
520
|
+
What was broken, end to end: `ObjectForm` builds an auto-generated field as
|
|
521
|
+
`type: 'field:textarea'` **and** stashes the object-field metadata on `.field`.
|
|
522
|
+
The renderer forwards `field: field.field || field`, so the widget received the
|
|
523
|
+
raw metadata — which never carried the flag — while the FormField-level copy was
|
|
524
|
+
dropped by `stripRegisteredFieldProps`. Every entry point into `TextAreaField`
|
|
525
|
+
therefore read `undefined` and the expand affordance never rendered. Only the
|
|
526
|
+
hand-authored `customFields` path (no `.field` to shadow the FormField) ever
|
|
527
|
+
worked, i.e. the feature was dead on the path virtually every form takes. Unit
|
|
528
|
+
tests on both ends passed the whole time, because the break lived in the seam
|
|
529
|
+
between them; this release adds the feature's first integration coverage — real
|
|
530
|
+
`ObjectForm` → real form renderer → real `TextAreaField`, no mocks — which fails
|
|
531
|
+
against the old producer and passes against the new one.
|
|
532
|
+
|
|
533
|
+
`mobile_fullscreen` is now declared on `@object-ui/types`' `BaseFieldMetadata`,
|
|
534
|
+
hence on every member of the `FieldMetadata` union that
|
|
535
|
+
`FieldWidgetComponentProps.field` resolves to. It is deliberately **not** an
|
|
536
|
+
`@objectstack/spec` property: nobody authors it on a field definition, it is a
|
|
537
|
+
projection of the form-level `ObjectFormSchema.mobile.fullscreenLongText` setting
|
|
538
|
+
onto the field metadata at render time. Declaring it removes the last untyped
|
|
539
|
+
end of the chain — the producer's `as FormField` cast is gone — so the two sides
|
|
540
|
+
can now disagree out loud instead of silently.
|
|
541
|
+
|
|
542
|
+
The hand-authored `customFields` path keeps working unchanged, and keeps its own
|
|
543
|
+
metadata: the flag is stamped on the FormField only when there is no `.field` to
|
|
544
|
+
carry it. Synthesizing a `field` object in that case would light the affordance
|
|
545
|
+
up while quietly replacing the field's `rows` / `placeholder` with defaults — the
|
|
546
|
+
regression test pins that too.
|
|
547
|
+
|
|
548
|
+
- 30ae33a: `RichTextField` honours `mobile_fullscreen`, so `mobile.fullscreenLongText` is
|
|
549
|
+
finally true of rich text too (objectui#3301).
|
|
550
|
+
|
|
551
|
+
`ObjectFormSchema.mobile.fullscreenLongText` has always been documented as
|
|
552
|
+
"textarea/rich-text get an expand button", and `ObjectForm` has always stamped
|
|
553
|
+
`mobile_fullscreen` onto `field:markdown` / `field:html` fields to deliver it.
|
|
554
|
+
Both of those types resolve to `RichTextField`, and that widget never read the
|
|
555
|
+
flag: a producer with no consumer. Turning the setting on gave a phone user an
|
|
556
|
+
expand affordance on their textareas and nothing at all on their markdown or
|
|
557
|
+
HTML fields, with nothing anywhere reporting that half the feature was inert.
|
|
558
|
+
|
|
559
|
+
FROM: `RichTextField` ignored the flag entirely (`grep fullscreen` over that
|
|
560
|
+
file returned nothing). TO: it reads `field.mobile_fullscreen` — the same single
|
|
561
|
+
metadata carrier `TextAreaField` reads, and nowhere else — and renders the same
|
|
562
|
+
expand affordance and full-height editing dialog.
|
|
563
|
+
|
|
564
|
+
**The affordance now has one implementation, not two.** One form-level setting
|
|
565
|
+
should produce one behaviour, so the expand button, the dialog and the
|
|
566
|
+
draft/commit semantics moved into a shared `FullscreenFieldEditor` that both
|
|
567
|
+
widgets render; only the EDITOR is per-widget. A second hand-written copy of
|
|
568
|
+
that state machine would be the same defect this release fixes, with an extra
|
|
569
|
+
step — it drifts, and nothing reports the drift. The rich-text dialog hosts the
|
|
570
|
+
widget's real editing surface (same format indicator, same editor), not a bare
|
|
571
|
+
textarea, so whatever that editor grows into, both positions get it at once.
|
|
572
|
+
|
|
573
|
+
Behaviour is identical across the two widgets and unchanged for
|
|
574
|
+
`TextAreaField`: the dialog seeds its draft from the committed value at open
|
|
575
|
+
time, keeps typing local (a react-hook-form field is not marked dirty by an
|
|
576
|
+
edit the user may still cancel), commits once on "Done", and discards on
|
|
577
|
+
"Cancel". Test ids follow the existing convention per widget —
|
|
578
|
+
`richtext-fullscreen-toggle` / `-dialog` / `-input` / `-save` alongside the
|
|
579
|
+
`textarea-*` ones, since a single form can contain both.
|
|
580
|
+
|
|
581
|
+
There is deliberately no prop spelling of the flag and no `??` fallback chain in
|
|
582
|
+
either widget. The field metadata is the one carrier (objectui#3233), so a
|
|
583
|
+
misspelled or misplaced flag stays inert and visible rather than being quietly
|
|
584
|
+
caught by a tolerant consumer.
|
|
585
|
+
|
|
586
|
+
Also removes a dead type from the producer: `ObjectForm` stamped the flag on
|
|
587
|
+
`'string-multiline'`, a string that `grep -rn` finds exactly once across both
|
|
588
|
+
this repo and `objectstack` — that line itself. No producer emitted it, no
|
|
589
|
+
registry key matched it, no widget read it. The remaining four stamped types
|
|
590
|
+
(`textarea`, `field:textarea`, `field:markdown`, `field:html`) each have a real
|
|
591
|
+
reader.
|
|
592
|
+
|
|
593
|
+
### Patch Changes
|
|
594
|
+
|
|
595
|
+
- a4cff5b: Conditional-rule predicates that fail to evaluate are no longer silent
|
|
596
|
+
(objectstack#5149, appeal 2). `evalFieldPredicate` — the canonical funnel for
|
|
597
|
+
`visibleWhen` / `readonlyWhen` / `requiredWhen`, view-level `visibleOn`, legacy
|
|
598
|
+
`condition`, per-option `visibleWhen`, screen-field predicates and list
|
|
599
|
+
conditional formatting — now logs **one `console.warn` per predicate text**
|
|
600
|
+
when evaluation fails (parse error, unbound identifier, engine fault), carrying
|
|
601
|
+
the predicate source, the engine's failure reason, and the field/rule locator
|
|
602
|
+
the call site provides. Renderer call sites thread that locator
|
|
603
|
+
(`visibleWhen of field 'amount'`), so a broken predicate identifies itself in
|
|
604
|
+
the browser console instead of being indistinguishable from an absent one.
|
|
605
|
+
|
|
606
|
+
Verdicts are unchanged: evaluation still fails open to the caller's safe
|
|
607
|
+
default (flipping that default is objectstack#5149 appeal 1, tracked
|
|
608
|
+
separately). Fault-probing callers (`evalRowPredicate`'s fail-closed path,
|
|
609
|
+
`ExpressionEvaluator`'s `throwOnError`) opt out via the new
|
|
610
|
+
`diagnostic.warn: false` and keep their own single diagnostic, so no broken
|
|
611
|
+
predicate ever warns twice.
|
|
612
|
+
|
|
613
|
+
- Updated dependencies [18cd432]
|
|
614
|
+
- Updated dependencies [b7165ce]
|
|
615
|
+
- Updated dependencies [532cf8b]
|
|
616
|
+
- Updated dependencies [680080a]
|
|
617
|
+
- Updated dependencies [a7651e6]
|
|
618
|
+
- Updated dependencies [d915c47]
|
|
619
|
+
- Updated dependencies [b71fc92]
|
|
620
|
+
- Updated dependencies [65516ba]
|
|
621
|
+
- Updated dependencies [94c5b7c]
|
|
622
|
+
- Updated dependencies [ca0fa8f]
|
|
623
|
+
- Updated dependencies [34595eb]
|
|
624
|
+
- Updated dependencies [3889ffb]
|
|
625
|
+
- Updated dependencies [5781fb1]
|
|
626
|
+
- Updated dependencies [7e2406a]
|
|
627
|
+
- Updated dependencies [9e9e9a9]
|
|
628
|
+
- Updated dependencies [19b8c9b]
|
|
629
|
+
- Updated dependencies [56409c2]
|
|
630
|
+
- Updated dependencies [042e09d]
|
|
631
|
+
- Updated dependencies [7d08c3f]
|
|
632
|
+
- Updated dependencies [9cbcbf4]
|
|
633
|
+
- Updated dependencies [85c4c9c]
|
|
634
|
+
- Updated dependencies [fd54c3e]
|
|
635
|
+
- Updated dependencies [4eeb932]
|
|
636
|
+
- Updated dependencies [6fe485b]
|
|
637
|
+
- Updated dependencies [5c856ec]
|
|
638
|
+
- Updated dependencies [23018cc]
|
|
639
|
+
- Updated dependencies [53811d1]
|
|
640
|
+
- Updated dependencies [68b6a28]
|
|
641
|
+
- Updated dependencies [0554e88]
|
|
642
|
+
- Updated dependencies [d915c47]
|
|
643
|
+
- Updated dependencies [f44d872]
|
|
644
|
+
- Updated dependencies [28b2e65]
|
|
645
|
+
- Updated dependencies [509104a]
|
|
646
|
+
- Updated dependencies [825bbe3]
|
|
647
|
+
- Updated dependencies [6195841]
|
|
648
|
+
- Updated dependencies [5dd0127]
|
|
649
|
+
- Updated dependencies [06632e9]
|
|
650
|
+
- Updated dependencies [a415684]
|
|
651
|
+
- Updated dependencies [a4cff5b]
|
|
652
|
+
- Updated dependencies [175bd79]
|
|
653
|
+
- Updated dependencies [5af2852]
|
|
654
|
+
- Updated dependencies [34d9169]
|
|
655
|
+
- Updated dependencies [5881a2c]
|
|
656
|
+
- Updated dependencies [9bc3709]
|
|
657
|
+
- Updated dependencies [f833d3a]
|
|
658
|
+
- Updated dependencies [30ae33a]
|
|
659
|
+
- Updated dependencies [a6ec93d]
|
|
660
|
+
- Updated dependencies [2a9513d]
|
|
661
|
+
- Updated dependencies [49f7449]
|
|
662
|
+
- Updated dependencies [71be406]
|
|
663
|
+
- Updated dependencies [d22ae31]
|
|
664
|
+
- Updated dependencies [c7ed4c3]
|
|
665
|
+
- Updated dependencies [2409e1d]
|
|
666
|
+
- Updated dependencies [789fe3e]
|
|
667
|
+
- Updated dependencies [f789c3b]
|
|
668
|
+
- Updated dependencies [a321fa4]
|
|
669
|
+
- Updated dependencies [8d8094a]
|
|
670
|
+
- @object-ui/core@17.3.0
|
|
671
|
+
- @object-ui/fields@17.3.0
|
|
672
|
+
- @object-ui/components@17.3.0
|
|
673
|
+
- @object-ui/types@17.3.0
|
|
674
|
+
- @object-ui/i18n@17.3.0
|
|
675
|
+
- @object-ui/react@17.3.0
|
|
676
|
+
- @object-ui/permissions@17.3.0
|
|
677
|
+
|
|
3
678
|
## 17.2.0
|
|
4
679
|
|
|
5
680
|
### Minor Changes
|
|
@@ -1092,6 +1767,7 @@ undefined` — swallowing the very errors the command exists to print. Now reads
|
|
|
1092
1767
|
objects+fields were editable in Studio; this reworks both surfaces.
|
|
1093
1768
|
|
|
1094
1769
|
**Setup (assign + read-only):**
|
|
1770
|
+
|
|
1095
1771
|
- The six facets (`object_permissions`, `field_permissions`, `system_permissions`,
|
|
1096
1772
|
`row_level_security`, `tab_permissions`, `admin_scope`) now render read-only on
|
|
1097
1773
|
the `sys_permission_set` record page as a compact summary (counts, or capability
|
|
@@ -1106,6 +1782,7 @@ undefined` — swallowing the very errors the command exists to print. Now reads
|
|
|
1106
1782
|
|
|
1107
1783
|
**Studio (design every facet):** the permission matrix editor gains structured
|
|
1108
1784
|
editors for the facets that were JSON-only —
|
|
1785
|
+
|
|
1109
1786
|
- **System Capabilities**: a multi-select over the live `sys_capability` registry
|
|
1110
1787
|
(scope-grouped, labelled chips).
|
|
1111
1788
|
- **Row-Level Security**: per-policy rows (object · operation · enabled) with CEL
|
|
@@ -2283,6 +2960,7 @@ parentData, details)` — emits a parent `update` op (index 0) then diffs each
|
|
|
2283
2960
|
### Patch Changes
|
|
2284
2961
|
|
|
2285
2962
|
- 89ae109: Fix click navigation and required-FK form rendering
|
|
2963
|
+
|
|
2286
2964
|
- **plugin-grid**: ObjectGrid's `getSelectFields()` now always includes `id` in
|
|
2287
2965
|
the SELECT projection. Previously, when a view configured `columns` without
|
|
2288
2966
|
`id`, the SQL driver stripped it from results, and row-click handlers silently
|
|
@@ -2345,6 +3023,7 @@ parentData, details)` — emits a parent `update` op (index 0) then diffs each
|
|
|
2345
3023
|
Library builds (vite lib mode) now externalize every non-relative import instead of bundling third-party CJS dependencies into the published dist. This avoids inlined `require("react")` / `require("react-dom")` calls that cause `Calling \`require\` for "react" in an environment that doesn't expose the \`require\` function` runtime errors when consumer apps re-bundle the published dist.
|
|
2346
3024
|
|
|
2347
3025
|
Specifically fixes:
|
|
3026
|
+
|
|
2348
3027
|
- `@object-ui/plugin-dashboard` no longer inlines `react-grid-layout` (and its transitive `react-draggable` / `react-resizable` CJS bundles). `react-grid-layout` is now declared as a peer dependency so consumers install a single ESM-friendly copy.
|
|
2349
3028
|
- `@object-ui/components`, `@object-ui/plugin-calendar`, `@object-ui/plugin-charts`, `@object-ui/plugin-designer` no longer inline `react-i18next` / `i18next` / `use-sync-external-store` CJS shims.
|
|
2350
3029
|
- All plugin packages now use a unified `external: (id) => !/^[./]/.test(id) && !id.startsWith(__dirname)` rule, ensuring future additions of CJS deps are automatically externalized.
|
|
@@ -2422,11 +3101,13 @@ parentData, details)` — emits a parent `update` op (index 0) then diffs each
|
|
|
2422
3101
|
`FormSchema.mobileStickyActions` (new) is the lower-level escape hatch — applied automatically when `mobile.stickyActions` is set on `ObjectFormSchema`.
|
|
2423
3102
|
|
|
2424
3103
|
**`@object-ui/plugin-form`** — `ObjectForm` now:
|
|
3104
|
+
|
|
2425
3105
|
- propagates `mobile.fullscreenLongText` to every textarea/markdown/html field as `mobile_fullscreen: true`,
|
|
2426
3106
|
- sets `mobileStickyActions` on the inner form schema and adds `pb-20` padding so content isn't covered by the fixed bar,
|
|
2427
3107
|
- when `mobile.stepper === true` (or `'auto'` + `useIsMobile()` + > `stepperMinFields` fields), routes the flat field list through the existing `WizardForm` with synthetic single-field "steps" — keeping per-step validation and the existing `Next`/`Back`/`Submit` flow.
|
|
2428
3108
|
|
|
2429
3109
|
**`@object-ui/components`** — the registered `form` renderer adds:
|
|
3110
|
+
|
|
2430
3111
|
- a `mobileStickyActions` opt-in that turns the action row into a `position: sticky; bottom: 0` bar on small viewports, and
|
|
2431
3112
|
- an inline `FullscreenTextarea` wrapper used when no field-package widget is registered, providing the same expand-button + edit-dialog UX so the feature works even in lighter setups.
|
|
2432
3113
|
|