@object-ui/fields 16.1.0 → 17.1.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 +432 -0
- package/dist/FieldEditWidget.d.ts +7 -0
- package/dist/field-type-alias.d.ts +16 -0
- package/dist/index.cjs +5 -5
- package/dist/index.d.ts +14 -11
- package/dist/index.js +2428 -2100
- package/dist/widgets/FilterConditionField.d.ts +32 -0
- package/dist/widgets/ImageLightbox.d.ts +30 -0
- package/dist/widgets/ImageLightbox.test.d.ts +1 -0
- package/dist/widgets/LookupField.hydration.test.d.ts +1 -0
- package/dist/widgets/LookupField.idField.test.d.ts +1 -0
- package/dist/widgets/RecipientPickerField.test.d.ts +0 -0
- package/dist/widgets/__tests__/FilterConditionField.matchAll.test.d.ts +8 -0
- package/dist/widgets/__tests__/FilterConditionField.operators.test.d.ts +8 -0
- package/dist/widgets/file-value.d.ts +112 -0
- package/dist/widgets/file-value.test.d.ts +1 -0
- package/dist/widgets/useFieldTranslation.d.ts +2 -0
- package/package.json +9 -8
- package/src/FieldEditWidget.test.ts +74 -2
- package/src/FieldEditWidget.tsx +44 -4
- package/src/field-type-alias.ts +100 -0
- package/src/index.tsx +94 -133
- package/src/widgets/CheckboxesField.tsx +8 -5
- package/src/widgets/FileField.tsx +88 -57
- package/src/widgets/FilterConditionField.tsx +109 -9
- package/src/widgets/ImageField.tsx +71 -33
- package/src/widgets/ImageLightbox.test.tsx +52 -0
- package/src/widgets/ImageLightbox.tsx +108 -0
- package/src/widgets/LookupField.hydration.test.tsx +136 -0
- package/src/widgets/LookupField.idField.test.tsx +61 -0
- package/src/widgets/LookupField.tsx +106 -9
- package/src/widgets/MultiSelectField.tsx +6 -3
- package/src/widgets/ObjectRefField.tsx +9 -4
- package/src/widgets/PeoplePicker.test.tsx +20 -0
- package/src/widgets/PeoplePicker.tsx +15 -3
- package/src/widgets/RadioField.tsx +6 -3
- package/src/widgets/RecipientPickerField.test.tsx +157 -0
- package/src/widgets/RecipientPickerField.tsx +36 -16
- package/src/widgets/TextAreaField.tsx +4 -1
- package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +66 -0
- package/src/widgets/__tests__/FilterConditionField.operators.test.ts +142 -0
- package/src/widgets/file-value.test.ts +230 -0
- package/src/widgets/file-value.ts +230 -0
- package/src/widgets/useFieldTranslation.ts +30 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,437 @@
|
|
|
1
1
|
# @object-ui/fields
|
|
2
2
|
|
|
3
|
+
## 17.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f8a95e5: fix(fields): the sharing-criteria builder stops calling an empty criteria "All records" (objectstack#3896)
|
|
8
|
+
|
|
9
|
+
`FilterConditionField` renders `sys_sharing_rule.criteria_json`. With no
|
|
10
|
+
criteria it displayed **"All records"**, and `filterGroupToMongo` carried a
|
|
11
|
+
matching `// empty = match all` comment. That was describing a bug as a
|
|
12
|
+
feature: a sharing rule with no predicate was stored as `criteria_json: null`
|
|
13
|
+
and evaluated as `find(object, { filter: {} })` under the system context —
|
|
14
|
+
every record of the object, granted to the recipient. `SharingRuleSchema` had
|
|
15
|
+
always forbidden the shape ("never seeded as a permissive match-all",
|
|
16
|
+
ADR-0049); the REST and data-API entries just never checked.
|
|
17
|
+
|
|
18
|
+
objectstack#3896 closes those entries: the server now refuses to save a rule
|
|
19
|
+
whose criteria would match everything, and one already stored shares nothing.
|
|
20
|
+
This is the renderer catching up.
|
|
21
|
+
|
|
22
|
+
- **The empty read-only state now says the rule shares nothing**, in
|
|
23
|
+
`destructive` styling — key renamed `fields.filterCondition.allRecords` →
|
|
24
|
+
`fields.filterCondition.noCriteria`, retranslated across all ten locales.
|
|
25
|
+
Nothing else read the old key.
|
|
26
|
+
- **A new `fields.filterCondition.criteriaRequired` hint** renders under the
|
|
27
|
+
builder (and the JSON editor) while the criteria is empty. The server's
|
|
28
|
+
rejection is precise but only arrives as a toast _after_ Save; this says it
|
|
29
|
+
while the admin is still looking at the empty builder.
|
|
30
|
+
- **`isMatchAllCriteria` is exported** — a client-side mirror of the server
|
|
31
|
+
predicate covering `{}`, `[]`, and the vacuous combinators (`{ $and: [] }`,
|
|
32
|
+
`{ $or: [{}] }`), conservative in the same direction. The server stays
|
|
33
|
+
authoritative; this only decides whether to show the hint.
|
|
34
|
+
|
|
35
|
+
Unparsable JSON keeps its own `invalidJson` message and does **not** also
|
|
36
|
+
collect the empty-criteria hint.
|
|
37
|
+
|
|
38
|
+
Note for anyone wiring this end-to-end: the Criteria field is not marked
|
|
39
|
+
`required` in the object metadata, deliberately — `sys_sharing_rule.criteria_json`
|
|
40
|
+
is nullable in deployed tenants, so `required: true` would only produce a
|
|
41
|
+
destructive `NOT NULL` migration that those nulls block. The invariant lives in
|
|
42
|
+
the server's write guards; this change makes the UI stop contradicting it.
|
|
43
|
+
|
|
44
|
+
### Patch Changes
|
|
45
|
+
|
|
46
|
+
- aecc934: fix(fields): PeoplePicker's keyboard cursor can no longer be eaten by a late reset
|
|
47
|
+
|
|
48
|
+
The cursor reset on new results lived in a `useEffect`. Effects flush
|
|
49
|
+
asynchronously after the render that delivered the records — so a reset queued
|
|
50
|
+
by their arrival could land AFTER a subsequent ArrowDown and wipe the just-set
|
|
51
|
+
cursor. That was the residual ArrowDown→Enter flake in
|
|
52
|
+
`PeoplePicker.test.tsx` (the earlier signature-keyed fix closed the
|
|
53
|
+
too-often resets, not the too-late one), and a real fast-fingers UX bug: rows
|
|
54
|
+
appear, the user presses ArrowDown, the highlight vanishes.
|
|
55
|
+
|
|
56
|
+
The reset now runs in the render phase (the "adjusting state during render"
|
|
57
|
+
pattern), in the same render that shows the new rows — by the time a row is
|
|
58
|
+
visible, the reset has already happened, so it can never race a keypress.
|
|
59
|
+
Semantics unchanged and now pinned by a test: a replaced result set does not
|
|
60
|
+
inherit the previous set's cursor.
|
|
61
|
+
|
|
62
|
+
- 7f23cd0: fix(form): a numeric/boolean select option survives selection with its type intact — #3090
|
|
63
|
+
|
|
64
|
+
`SelectOptionSchema.value` has accepted `string | number | boolean` for as
|
|
65
|
+
long as it has existed, but the Radix controls underneath speak strings:
|
|
66
|
+
picking `{ value: 2 }` silently submitted `"2"` — a wrong-typed write into a
|
|
67
|
+
number field that nothing on the client ever reported. (Display half-worked:
|
|
68
|
+
a numeric default matched its numeric item; only SELECTION morphed the type.)
|
|
69
|
+
|
|
70
|
+
The renderers now stringify on the way into the control and map the selection
|
|
71
|
+
back to the AUTHORED option value on the way out (`matchOptionValue`), across
|
|
72
|
+
the in-form select, the standalone `type: 'select'` component, and the
|
|
73
|
+
standalone `type: 'radio-group'` component. The TS types stop lying to match:
|
|
74
|
+
`SelectOption.value` / `RadioOption.value` and the corresponding
|
|
75
|
+
`value`/`defaultValue`/`onChange` channels widen to what the zod schemas
|
|
76
|
+
always accepted — a call site treating `option.value` as `string` is now a
|
|
77
|
+
compile error pointing at a real latent crash, not a false comfort.
|
|
78
|
+
|
|
79
|
+
The ripple the widening named, handled at each boundary: `@object-ui/core`'s
|
|
80
|
+
`OptionLike.value` widens (the option engines compare by identity, so values
|
|
81
|
+
flow opaquely; the option-lint's CEL-literal domain stringifies at its
|
|
82
|
+
boundary), and the multi-value field widgets (checkboxes / multiselect /
|
|
83
|
+
radio) stringify at theirs — multi-value fields store string arrays.
|
|
84
|
+
|
|
85
|
+
Round-trip pinned by real Radix interactions in jsdom: the in-form select
|
|
86
|
+
submits `2` (number), the standalone select hands its handler `false`
|
|
87
|
+
(boolean).
|
|
88
|
+
|
|
89
|
+
- Updated dependencies [62311b6]
|
|
90
|
+
- Updated dependencies [fc0272a]
|
|
91
|
+
- Updated dependencies [9e7349e]
|
|
92
|
+
- Updated dependencies [8864971]
|
|
93
|
+
- Updated dependencies [1cf0de7]
|
|
94
|
+
- Updated dependencies [752e18f]
|
|
95
|
+
- Updated dependencies [c785740]
|
|
96
|
+
- Updated dependencies [b41f401]
|
|
97
|
+
- Updated dependencies [19e9fa0]
|
|
98
|
+
- Updated dependencies [d61efd1]
|
|
99
|
+
- Updated dependencies [95b7214]
|
|
100
|
+
- Updated dependencies [7d9734d]
|
|
101
|
+
- Updated dependencies [6ae818e]
|
|
102
|
+
- Updated dependencies [9eb932b]
|
|
103
|
+
- Updated dependencies [746dd00]
|
|
104
|
+
- Updated dependencies [aebfa4f]
|
|
105
|
+
- Updated dependencies [38ca8be]
|
|
106
|
+
- Updated dependencies [3cb9646]
|
|
107
|
+
- Updated dependencies [68ef584]
|
|
108
|
+
- Updated dependencies [4952edf]
|
|
109
|
+
- Updated dependencies [7f0252e]
|
|
110
|
+
- Updated dependencies [c4d7b20]
|
|
111
|
+
- Updated dependencies [c769d3d]
|
|
112
|
+
- Updated dependencies [7639a61]
|
|
113
|
+
- Updated dependencies [94e63ef]
|
|
114
|
+
- Updated dependencies [c735bf7]
|
|
115
|
+
- Updated dependencies [02aef0c]
|
|
116
|
+
- Updated dependencies [6f29aa5]
|
|
117
|
+
- Updated dependencies [d21794c]
|
|
118
|
+
- Updated dependencies [c4db402]
|
|
119
|
+
- Updated dependencies [5319bf1]
|
|
120
|
+
- Updated dependencies [49e5671]
|
|
121
|
+
- Updated dependencies [9a04d25]
|
|
122
|
+
- Updated dependencies [b5b97e2]
|
|
123
|
+
- Updated dependencies [f59f2c1]
|
|
124
|
+
- Updated dependencies [07de839]
|
|
125
|
+
- Updated dependencies [2a40b5e]
|
|
126
|
+
- Updated dependencies [df613fa]
|
|
127
|
+
- Updated dependencies [4874117]
|
|
128
|
+
- Updated dependencies [ad0183a]
|
|
129
|
+
- Updated dependencies [ce08d55]
|
|
130
|
+
- Updated dependencies [eb4b740]
|
|
131
|
+
- Updated dependencies [5b084eb]
|
|
132
|
+
- Updated dependencies [aa1240a]
|
|
133
|
+
- Updated dependencies [2374a49]
|
|
134
|
+
- Updated dependencies [390c071]
|
|
135
|
+
- Updated dependencies [d10f526]
|
|
136
|
+
- Updated dependencies [2d5d594]
|
|
137
|
+
- Updated dependencies [ea7f477]
|
|
138
|
+
- Updated dependencies [379728f]
|
|
139
|
+
- Updated dependencies [7f23cd0]
|
|
140
|
+
- Updated dependencies [0ded602]
|
|
141
|
+
- Updated dependencies [24e0e0a]
|
|
142
|
+
- Updated dependencies [f8a95e5]
|
|
143
|
+
- Updated dependencies [3a6cf24]
|
|
144
|
+
- Updated dependencies [aa35561]
|
|
145
|
+
- Updated dependencies [03bd53b]
|
|
146
|
+
- Updated dependencies [3c1f321]
|
|
147
|
+
- Updated dependencies [a045a32]
|
|
148
|
+
- Updated dependencies [912496d]
|
|
149
|
+
- Updated dependencies [80edbd4]
|
|
150
|
+
- Updated dependencies [9867281]
|
|
151
|
+
- @object-ui/core@17.1.0
|
|
152
|
+
- @object-ui/components@17.1.0
|
|
153
|
+
- @object-ui/react@17.1.0
|
|
154
|
+
- @object-ui/types@17.1.0
|
|
155
|
+
- @object-ui/i18n@17.1.0
|
|
156
|
+
- @object-ui/providers@17.1.0
|
|
157
|
+
|
|
158
|
+
## 17.0.0
|
|
159
|
+
|
|
160
|
+
### Minor Changes
|
|
161
|
+
|
|
162
|
+
- 697cda4: feat(fields): adopt the file-as-reference value shape (ObjectStack ADR-0104 D3 wave 2)
|
|
163
|
+
|
|
164
|
+
A `file`/`image` field value now reaches the UI in one of three forms, and the
|
|
165
|
+
rules for reading them live in one place — `@object-ui/fields`' new
|
|
166
|
+
`file-value` module — instead of being re-derived in each widget:
|
|
167
|
+
|
|
168
|
+
1. **Reference** — a bare `sys_file` id string, what the backend stores once
|
|
169
|
+
file-as-reference is adopted.
|
|
170
|
+
2. **Expanded** — `{ id, name, size, mimeType, url }`, what the read path
|
|
171
|
+
returns after resolving a reference.
|
|
172
|
+
3. **Legacy inline blob** — `{ file_id?, name, original_name, size, mime_type,
|
|
173
|
+
url }`, the pre-reference shape this package used to build itself.
|
|
174
|
+
|
|
175
|
+
**The casing split is the bug this fixes.** The expanded form carries
|
|
176
|
+
`mimeType`; the legacy blob carries `mime_type`. `FileField`, `FileCell` and
|
|
177
|
+
`ImageField` all read only `mime_type`, so the moment a backend starts returning
|
|
178
|
+
the expanded form they stop recognising images — thumbnails silently degrade to
|
|
179
|
+
a generic file icon, with nothing pointing at a value shape as the cause.
|
|
180
|
+
`readFileValue()` accepts both.
|
|
181
|
+
|
|
182
|
+
**Uploads now submit the reference form** — the bare `sys_file` id — when the
|
|
183
|
+
upload adapter surfaced one, falling back to the legacy blob when it did not
|
|
184
|
+
(the object-URL fallback adapter, or a backend predating file-as-reference). The
|
|
185
|
+
same build therefore works against both. Action params already POSTed a bare
|
|
186
|
+
fileId; record field values now use the same contract, and
|
|
187
|
+
`serializeParamValues` shares the `fileIdOf()` extractor so the two surfaces
|
|
188
|
+
cannot drift on what counts as an id.
|
|
189
|
+
|
|
190
|
+
Because a bare id carries no name or URL, each widget remembers the display
|
|
191
|
+
details of files it just uploaded, keyed by id, so an upload renders immediately
|
|
192
|
+
rather than showing a bare token until the next read enriches it.
|
|
193
|
+
|
|
194
|
+
### Patch Changes
|
|
195
|
+
|
|
196
|
+
- 4b1ed7d: feat(app-shell): approval approver values become record lookups (framework #3508)
|
|
197
|
+
|
|
198
|
+
- The flow designer's approver `Value` cell now sources directory kinds from DATA
|
|
199
|
+
records instead of the metadata registry: `user` / `team` / `department` / `position`
|
|
200
|
+
render a single-select record lookup (`LookupField` over `sys_user` / `sys_team` /
|
|
201
|
+
`sys_business_unit` / `sys_position` via the DataSource adapter), with a manual-entry
|
|
202
|
+
escape hatch and a plain free-text fallback when no adapter is available (offline
|
|
203
|
+
preview). `position` commits the machine name; the others commit the record id —
|
|
204
|
+
matching the approval engine's resolution semantics.
|
|
205
|
+
- `org-membership-level` is now a strict select (owner/admin/member); a stored
|
|
206
|
+
out-of-enum value renders flagged instead of being blanked.
|
|
207
|
+
- `manager` renders as an auto-resolved (disabled) cell; `queue` is no longer offered
|
|
208
|
+
for new approver rows and stored queue rows carry a "not supported by the runtime"
|
|
209
|
+
warning.
|
|
210
|
+
- `@object-ui/fields`: `LookupField` hydrates the selected label through `id_field`
|
|
211
|
+
when it is not the primary id (e.g. `id_field: 'name'`), instead of always calling
|
|
212
|
+
`findOne` with the primary id.
|
|
213
|
+
|
|
214
|
+
- 3e886eb: fix(i18n): localize FileField upload widget + approvals snapshot field labels
|
|
215
|
+
|
|
216
|
+
- `FileField` (the shared upload widget) hard-coded every visible string
|
|
217
|
+
("Drag & drop files here", "or click to browse", "Take photo", "Uploading…",
|
|
218
|
+
size/upload validation messages, …). They now route through
|
|
219
|
+
`useObjectTranslation` with new `fields.file.*` keys, translated across all
|
|
220
|
+
10 locale bundles. This is why the approvals Approve/Reject dialog's
|
|
221
|
+
attachment dropzone was English in a Chinese console.
|
|
222
|
+
- The approvals inbox record-snapshot summary title-cased raw machine keys
|
|
223
|
+
instead of the target object's field labels. It now consumes the
|
|
224
|
+
server-sent `payload_labels` in `payloadSummary`/`decisionAmountEntry`,
|
|
225
|
+
falling back to the prettified key when absent; `approvalsApi`'s row type
|
|
226
|
+
gains `payload_labels`.
|
|
227
|
+
|
|
228
|
+
- faad45e: fix(fields): render `image` fields consistently and add click-to-zoom (#2836)
|
|
229
|
+
|
|
230
|
+
An `image` field rendered differently — and wrongly — on three surfaces:
|
|
231
|
+
|
|
232
|
+
- **Edit form showed broken thumbnails.** A record read back its `image` value
|
|
233
|
+
as a bare `sys_file` id (the reference form), but `readFileValue` returned an
|
|
234
|
+
id with no URL — the comment assumed the read path expands it, which the
|
|
235
|
+
edit-form data path does not. The result was `<img src="">`. `file-value` now
|
|
236
|
+
derives the stable download URL (`/api/v1/storage/files/:id`, which
|
|
237
|
+
302-redirects to a signed URL and works directly as `<img src>`) for a bare
|
|
238
|
+
id or an id-only object, so every widget and cell renderer resolves one.
|
|
239
|
+
- **Inline edit leaked the raw storage URL.** `InlineFieldInput` had no branch
|
|
240
|
+
for file-backed types and fell through to a plain text input showing
|
|
241
|
+
`/api/v1/storage/files/…`. It now renders the same upload widgets the form
|
|
242
|
+
uses (`image`/`avatar`/`signature`/`file`/`video`/`audio`).
|
|
243
|
+
- **Hard-coded English.** `ImageField`'s upload/crop/remove/alt strings now go
|
|
244
|
+
through `t('fields.image.*')` (en + zh added).
|
|
245
|
+
|
|
246
|
+
Also adds an `ImageLightbox` — click a read-only thumbnail (detail or list cell)
|
|
247
|
+
to open a full-screen preview; multiple images get prev/next navigation, a
|
|
248
|
+
position counter and arrow-key support, a single image just the image. In a
|
|
249
|
+
grid cell the click is `stopPropagation`-guarded so enlarging doesn't also open
|
|
250
|
+
the row.
|
|
251
|
+
|
|
252
|
+
- 341bfb5: fix: read spec-canonical keys for dashboard header title and field length rules
|
|
253
|
+
|
|
254
|
+
Two naming-drift closeouts (framework#1878 / framework#1891):
|
|
255
|
+
|
|
256
|
+
- `DashboardRenderer` header now falls back to the spec-canonical `label` when
|
|
257
|
+
the legacy `title` is absent (mirrors the `DashboardGridLayout` fallback from
|
|
258
|
+
#2666) — a spec-compliant dashboard gets its header title.
|
|
259
|
+
- Field validation rules now read the spec-canonical camelCase
|
|
260
|
+
`minLength`/`maxLength` (what the server record-validator enforces) with the
|
|
261
|
+
legacy snake_case `min_length`/`max_length` kept as fallback — authored
|
|
262
|
+
length constraints reach the client form.
|
|
263
|
+
|
|
264
|
+
- 2f947e4: fix(page,field): consume the spec's `type`/`label`/`maxLength` keys (framework#1878 §3 naming-drift recheck)
|
|
265
|
+
|
|
266
|
+
Three forward-drifts where objectui read a different key than the spec
|
|
267
|
+
declares, so authoring the documented key silently no-oped:
|
|
268
|
+
|
|
269
|
+
- **page `type` → `pageType`** (app-shell + components): `PageSchema` declares
|
|
270
|
+
the page KIND as `type`, but `PageRenderer` reads `schema.pageType` and fell
|
|
271
|
+
back to `'record'` — and nothing mapped between them. Every non-record page
|
|
272
|
+
(`home`/`app`/`list`/`utility`) rendered with the record max-width, a wrong
|
|
273
|
+
`data-page-type` attribute, and a suppressed header. `PageView` now passes
|
|
274
|
+
`pageType` alongside the SchemaNode discriminator `type`.
|
|
275
|
+
- **page `label` → `title`** (components): `PageSchema.label` is required but the
|
|
276
|
+
region renderer read only `title`. Now dual-reads `title ?? label`, mirroring
|
|
277
|
+
the fallback `DashboardRenderer` already uses. Coupled with the above — the
|
|
278
|
+
header is gated on `pageType !== 'record'`, so both were needed for a title to
|
|
279
|
+
appear.
|
|
280
|
+
- **field `maxLength`/`minLength`** (plugin-form + fields): validation already
|
|
281
|
+
dual-read these, but `ObjectForm`'s HTML-attribute pass and `TextAreaField`
|
|
282
|
+
read `max_length` only, so a spec-authored `maxLength` gave no browser cap and
|
|
283
|
+
no character counter. Both now dual-read, matching `buildValidationRules`.
|
|
284
|
+
|
|
285
|
+
Verified in the browser against the showcase: `capability_map` (`type: 'home'`)
|
|
286
|
+
now renders `data-page-type="home"`, the `home` max-width and its page title;
|
|
287
|
+
record pages are unchanged.
|
|
288
|
+
|
|
289
|
+
- 53642d4: fix(core,fields): a string `$orderby` is a clause, not a character array — and localize the sharing-rule widgets (objectstack#3821)
|
|
290
|
+
|
|
291
|
+
**The recipient picker listed nothing, ever.** `QueryParams['$orderby']` was
|
|
292
|
+
typed as `Record | string[] | SortObject[]`, so `queryParamsToRecord` sent any
|
|
293
|
+
non-array value through `Object.entries`. Handed the clause string `'name asc'`
|
|
294
|
+
— which callers do build by hand — it walked the string index by index and
|
|
295
|
+
emitted `$orderby=0 n,1 a,2 m,3 e,4 ,5 a,6 s,7 c`. The server sorted by columns
|
|
296
|
+
that don't exist and every row was filtered out, so
|
|
297
|
+
`sys_sharing_rule.recipient_id` rendered "No matches" for every recipient type
|
|
298
|
+
and no sharing rule could be created from the Console. `ObjectGrid` builds the
|
|
299
|
+
same shape from a schema-level `sort` in three places, so grids with a string
|
|
300
|
+
sort silently showed an empty table.
|
|
301
|
+
|
|
302
|
+
A string `$orderby` is now passed through verbatim (the server's OData
|
|
303
|
+
normalizer has always parsed `'name asc'`), and the type admits `string`.
|
|
304
|
+
`RecipientPickerField` additionally switched to the structured
|
|
305
|
+
`{ name: 'asc' }` form so it can't regress this way against any data source.
|
|
306
|
+
|
|
307
|
+
**The three sharing-rule authoring widgets never had translations.**
|
|
308
|
+
`ObjectRefField`, `RecipientPickerField` and `FilterConditionField` hardcoded
|
|
309
|
+
their English copy — a Chinese Console showed "Select an object", "Select a
|
|
310
|
+
user", "Search…", "No matches", "Edit as JSON". They now go through
|
|
311
|
+
`useFieldTranslation` like every other widget, with keys added under `fields.*`
|
|
312
|
+
in all ten locales.
|
|
313
|
+
|
|
314
|
+
The recipient placeholder was the interesting one: it read
|
|
315
|
+
`` `Select a ${recipientType.replace(/_/g,' ')}` ``, interpolating the enum
|
|
316
|
+
value into an English sentence — a shape no locale can translate. It is now a
|
|
317
|
+
per-type key (`fields.recipient.selectUser`, `…selectBusinessUnit`, …), so
|
|
318
|
+
"选择业务单元" and "Select a business unit" no longer have to share a structure.
|
|
319
|
+
|
|
320
|
+
**Editing a rule silently dropped its recipient.** The picker resets the stored
|
|
321
|
+
id when `recipient_type` changes, because an id valid for a user is meaningless
|
|
322
|
+
for a team. It treated the edit form's `'' → 'user'` hydration as such a change:
|
|
323
|
+
opening any saved rule blanked the recipient, and saving persisted the blank.
|
|
324
|
+
Only a non-empty predecessor now counts as a type switch.
|
|
325
|
+
|
|
326
|
+
**Building a filter submitted the surrounding form.** None of `FilterBuilder`'s
|
|
327
|
+
controls declared `type="button"`, and a bare `<button>` inside a `<form>`
|
|
328
|
+
defaults to `type="submit"`. Adding, removing or clearing a condition therefore
|
|
329
|
+
submitted the sharing-rule dialog — firing validation mid-edit, and on an
|
|
330
|
+
already-valid form saving the record before the admin was done.
|
|
331
|
+
|
|
332
|
+
**A rejected write showed the user raw server diagnostics.** The form rendered
|
|
333
|
+
`error.message` verbatim, so a sharing / RLS denial reached the dialog and the
|
|
334
|
+
toast as `FORBIDDEN: insufficient privileges to update showcase_private_note
|
|
335
|
+
pi-TgoJ4_DM55Fqz` — untranslated, and leaking the object's machine name and the
|
|
336
|
+
record id to whoever hit it. Permission failures now render localized copy
|
|
337
|
+
(`form.noPermissionToSave`, added in all ten locales), with the server text kept
|
|
338
|
+
on the console for debugging; other failures still show the server's message,
|
|
339
|
+
which is the useful part, and fall back to `form.submitFailed` when there is
|
|
340
|
+
none — replacing the previously hardcoded English "An error occurred during
|
|
341
|
+
submission".
|
|
342
|
+
|
|
343
|
+
**The detail header offered "Edit" on records the user may only read.** Object
|
|
344
|
+
permissions can't express "this one record is read-only" — a read-only sharing
|
|
345
|
+
grant sits inside an object the user may otherwise edit — so the header showed
|
|
346
|
+
the primary Edit CTA, opened the form, and let the user retype a field before
|
|
347
|
+
the server rejected the save. `DetailView` now gates Edit / Delete on the
|
|
348
|
+
object-level check AND on the explain engine's record-grained verdict
|
|
349
|
+
(`POST /api/v1/security/explain` with a `recordId`, ADR-0090 D6 / ADR-0095 C2 —
|
|
350
|
+
the same pipeline the enforcement middleware runs, so button and server cannot
|
|
351
|
+
disagree). Explaining oneself needs no special permission. The probe is one
|
|
352
|
+
cached request per record, skipped entirely when the object-level check already
|
|
353
|
+
says no, and **fails open** on every uncertainty — an unanswered hint must never
|
|
354
|
+
be the reason a permitted user cannot act; the server stays the authority
|
|
355
|
+
(ADR-0057 D10).
|
|
356
|
+
|
|
357
|
+
**A long option rendered straight past the combobox border.** `Combobox`'s
|
|
358
|
+
trigger pinned itself to the component's `w-[200px]` default while the fields
|
|
359
|
+
around it ran the full form column, and the selected label was a bare text child
|
|
360
|
+
of a flex button — flex items need `truncate` AND `min-w-0` to clip, and it had
|
|
361
|
+
neither. So "成员 (showcase_project_membership)" in the object picker overflowed
|
|
362
|
+
the control and collided with the field beside it. The label now truncates, the
|
|
363
|
+
trigger can shrink, the dropdown matches the trigger's width instead of a
|
|
364
|
+
hardcoded 200px (a widened combobox used to clip its own options), and the two
|
|
365
|
+
sharing-rule pickers ask for `w-full` so they line up with every other input.
|
|
366
|
+
|
|
367
|
+
Hardens `evaluatePermission` while there: a role config carrying only
|
|
368
|
+
`fieldPermissions` (no `actions`) made `check()` throw a TypeError that
|
|
369
|
+
propagated out of the render. A permission check must not be able to crash a
|
|
370
|
+
view.
|
|
371
|
+
|
|
372
|
+
Browser-verified against the framework showcase Console in Chinese: object /
|
|
373
|
+
criteria / recipient copy is fully localized, the recipient dropdown lists real
|
|
374
|
+
users, business units and positions, a saved rule reopens with its recipient and
|
|
375
|
+
criteria intact, editing the filter no longer submits, and a rule created
|
|
376
|
+
end-to-end stores a real record id rather than free text. The criteria authored
|
|
377
|
+
in the builder is honored by the evaluator: `{"pinned":true}` on an owner-private
|
|
378
|
+
object granted the recipient exactly the matching records and nothing else.
|
|
379
|
+
|
|
380
|
+
- Updated dependencies [7b21891]
|
|
381
|
+
- Updated dependencies [0b3be01]
|
|
382
|
+
- Updated dependencies [3c4d935]
|
|
383
|
+
- Updated dependencies [4b60d2d]
|
|
384
|
+
- Updated dependencies [952b978]
|
|
385
|
+
- Updated dependencies [de5e40c]
|
|
386
|
+
- Updated dependencies [1a03af6]
|
|
387
|
+
- Updated dependencies [3e886eb]
|
|
388
|
+
- Updated dependencies [cfc675e]
|
|
389
|
+
- Updated dependencies [20df08c]
|
|
390
|
+
- Updated dependencies [1767124]
|
|
391
|
+
- Updated dependencies [8ecf5a6]
|
|
392
|
+
- Updated dependencies [af705b9]
|
|
393
|
+
- Updated dependencies [0502a7c]
|
|
394
|
+
- Updated dependencies [7b35e4b]
|
|
395
|
+
- Updated dependencies [8fb1295]
|
|
396
|
+
- Updated dependencies [e16ed2d]
|
|
397
|
+
- Updated dependencies [c6fd752]
|
|
398
|
+
- Updated dependencies [f9bbddb]
|
|
399
|
+
- Updated dependencies [dfd3705]
|
|
400
|
+
- Updated dependencies [c77108c]
|
|
401
|
+
- Updated dependencies [2735de6]
|
|
402
|
+
- Updated dependencies [c19ac11]
|
|
403
|
+
- Updated dependencies [6dee2cb]
|
|
404
|
+
- Updated dependencies [e05f052]
|
|
405
|
+
- Updated dependencies [0502a7c]
|
|
406
|
+
- Updated dependencies [faad45e]
|
|
407
|
+
- Updated dependencies [09c6a17]
|
|
408
|
+
- Updated dependencies [c7cff19]
|
|
409
|
+
- Updated dependencies [ba73a02]
|
|
410
|
+
- Updated dependencies [cd09a7b]
|
|
411
|
+
- Updated dependencies [f1abf0e]
|
|
412
|
+
- Updated dependencies [f05b84e]
|
|
413
|
+
- Updated dependencies [9b4b952]
|
|
414
|
+
- Updated dependencies [2f947e4]
|
|
415
|
+
- Updated dependencies [7d46648]
|
|
416
|
+
- Updated dependencies [9b53d72]
|
|
417
|
+
- Updated dependencies [bb4aa25]
|
|
418
|
+
- Updated dependencies [75f1cdf]
|
|
419
|
+
- Updated dependencies [662bdf9]
|
|
420
|
+
- Updated dependencies [059a052]
|
|
421
|
+
- Updated dependencies [53642d4]
|
|
422
|
+
- Updated dependencies [8aae006]
|
|
423
|
+
- Updated dependencies [c6cfdf1]
|
|
424
|
+
- Updated dependencies [d147a13]
|
|
425
|
+
- Updated dependencies [c6aaed8]
|
|
426
|
+
- Updated dependencies [263f885]
|
|
427
|
+
- Updated dependencies [dc334da]
|
|
428
|
+
- @object-ui/components@17.0.0
|
|
429
|
+
- @object-ui/i18n@17.0.0
|
|
430
|
+
- @object-ui/react@17.0.0
|
|
431
|
+
- @object-ui/types@17.0.0
|
|
432
|
+
- @object-ui/core@17.0.0
|
|
433
|
+
- @object-ui/providers@17.0.0
|
|
434
|
+
|
|
3
435
|
## 16.1.0
|
|
4
436
|
|
|
5
437
|
### Minor Changes
|
|
@@ -12,6 +12,13 @@ export declare const INLINE_EXCLUDED_FIELD_TYPES: Set<string>;
|
|
|
12
12
|
export declare const DISCRETE_EDIT_TYPES: Set<string>;
|
|
13
13
|
/** True when a field type has a dedicated in-place edit widget. */
|
|
14
14
|
export declare function hasFieldEditWidget(type: string | undefined): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* True when a field type is deliberately NOT inline-editable (alias-aware:
|
|
17
|
+
* `composite` resolves to the excluded `object`, `video` to `file`, …).
|
|
18
|
+
* Grid hosts must treat these as read-only cells — falling back to a plain
|
|
19
|
+
* text editor is exactly the corruption path the exclusion exists to close.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isInlineExcludedFieldType(type: string | undefined): boolean;
|
|
15
22
|
/**
|
|
16
23
|
* Render the dedicated edit widget for a field's type — the SAME control the
|
|
17
24
|
* form uses — as a controlled `{ value, onChange, field }` input. Returns
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Map field type to form component type
|
|
10
|
+
*
|
|
11
|
+
* @param fieldType - The ObjectQL field type identifier to convert
|
|
12
|
+
* (for example: `"text"`, `"number"`, `"date"`, `"lookup"`).
|
|
13
|
+
* @returns The normalized form field type string used in the form schema
|
|
14
|
+
* (for example: `"input"`, `"textarea"`, `"date-picker"`, `"select"`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function mapFieldTypeToFormType(fieldType: string): string;
|