@object-ui/plugin-detail 17.6.0 → 17.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6741 -0
- package/README.md +404 -71
- package/dist/ActivityTimeline.d.ts.map +1 -1
- package/dist/CommentAttachment.d.ts.map +1 -1
- package/dist/ConcurrentUpdateDialog.d.ts +17 -1
- package/dist/ConcurrentUpdateDialog.d.ts.map +1 -1
- package/dist/DetailSection.d.ts.map +1 -1
- package/dist/DetailSectionNode.d.ts +97 -0
- package/dist/DetailSectionNode.d.ts.map +1 -0
- package/dist/DetailView.d.ts.map +1 -1
- package/dist/HeaderHighlight.d.ts.map +1 -1
- package/dist/HistoryTimeline.d.ts +8 -1
- package/dist/HistoryTimeline.d.ts.map +1 -1
- package/dist/InlineEditSaveBar.d.ts +68 -0
- package/dist/InlineEditSaveBar.d.ts.map +1 -1
- package/dist/InlineFieldInput.d.ts +79 -6
- package/dist/InlineFieldInput.d.ts.map +1 -1
- package/dist/ReactionPicker.d.ts.map +1 -1
- package/dist/RecordActivityTimeline.d.ts +19 -4
- package/dist/RecordActivityTimeline.d.ts.map +1 -1
- package/dist/RecordChatterPanel.d.ts +6 -1
- package/dist/RecordChatterPanel.d.ts.map +1 -1
- package/dist/RecordComments.d.ts +1 -0
- package/dist/RecordComments.d.ts.map +1 -1
- package/dist/RecordDetailDrawer.d.ts +20 -6
- package/dist/RecordDetailDrawer.d.ts.map +1 -1
- package/dist/RecordDetailPanel.d.ts +101 -0
- package/dist/RecordDetailPanel.d.ts.map +1 -0
- package/dist/RecordMetaFooter.d.ts +1 -1
- package/dist/RecordMetaFooter.d.ts.map +1 -1
- package/dist/RelatedList.d.ts +83 -10
- package/dist/RelatedList.d.ts.map +1 -1
- package/dist/ThreadedReplies.d.ts +1 -1
- package/dist/ThreadedReplies.d.ts.map +1 -1
- package/dist/emptiness.d.ts +142 -0
- package/dist/emptiness.d.ts.map +1 -0
- package/dist/fieldEnrichment.d.ts +44 -4
- package/dist/fieldEnrichment.d.ts.map +1 -1
- package/dist/headerColor.d.ts +20 -0
- package/dist/headerColor.d.ts.map +1 -0
- package/dist/index.d.ts +94 -77
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4883 -5255
- package/dist/index.umd.cjs +2 -5
- package/dist/renderers/record-activity.d.ts.map +1 -1
- package/dist/renderers/record-alert.d.ts +23 -3
- package/dist/renderers/record-alert.d.ts.map +1 -1
- package/dist/renderers/record-alert.readProps.d.ts +40 -0
- package/dist/renderers/record-alert.readProps.d.ts.map +1 -0
- package/dist/renderers/record-chatter.d.ts.map +1 -1
- package/dist/renderers/record-details.d.ts +2 -0
- package/dist/renderers/record-details.d.ts.map +1 -1
- package/dist/renderers/record-highlights.d.ts.map +1 -1
- package/dist/renderers/record-history.d.ts +1 -1
- package/dist/renderers/record-history.d.ts.map +1 -1
- package/dist/renderers/record-path.d.ts.map +1 -1
- package/dist/renderers/record-quick-actions.d.ts +16 -6
- package/dist/renderers/record-quick-actions.d.ts.map +1 -1
- package/dist/renderers/record-reference-rail.d.ts +40 -0
- package/dist/renderers/record-reference-rail.d.ts.map +1 -1
- package/dist/renderers/record-related-list.d.ts +37 -3
- package/dist/renderers/record-related-list.d.ts.map +1 -1
- package/dist/renderers/recordActivityFeed.d.ts +272 -20
- package/dist/renderers/recordActivityFeed.d.ts.map +1 -1
- package/dist/renderers/recordComponentAria.d.ts +44 -0
- package/dist/renderers/recordComponentAria.d.ts.map +1 -0
- package/dist/renderers/relatedListActions.d.ts +102 -0
- package/dist/renderers/relatedListActions.d.ts.map +1 -0
- package/dist/summaryChipPercent.d.ts +83 -0
- package/dist/summaryChipPercent.d.ts.map +1 -0
- package/dist/summaryChipRenderers.d.ts +83 -0
- package/dist/summaryChipRenderers.d.ts.map +1 -0
- package/dist/synth/buildDefaultPageSchema.d.ts +98 -17
- package/dist/synth/buildDefaultPageSchema.d.ts.map +1 -1
- package/dist/useDetailTranslation.d.ts.map +1 -1
- package/dist/useRecordEditable.d.ts +1 -1
- package/dist/useRecordEditable.d.ts.map +1 -1
- package/package.json +19 -18
- package/dist/CommentInput.d.ts +0 -24
- package/dist/CommentInput.d.ts.map +0 -1
- package/dist/DiffView.d.ts +0 -24
- package/dist/DiffView.d.ts.map +0 -1
- package/dist/InlineCreateRelated.d.ts +0 -32
- package/dist/InlineCreateRelated.d.ts.map +0 -1
- package/dist/MentionAutocomplete.d.ts +0 -43
- package/dist/MentionAutocomplete.d.ts.map +0 -1
- package/dist/PointInTimeRestore.d.ts +0 -28
- package/dist/PointInTimeRestore.d.ts.map +0 -1
- package/dist/RecordNavigationEnhanced.d.ts +0 -18
- package/dist/RecordNavigationEnhanced.d.ts.map +0 -1
- package/dist/RelationshipGraph.d.ts +0 -23
- package/dist/RelationshipGraph.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@ DetailView plugin for ObjectUI - A comprehensive detail page component with fiel
|
|
|
7
7
|
- **Field Grouping/Sections**: Organize fields into logical sections with titles
|
|
8
8
|
- **Collapsible Sections**: Make sections collapsible to save space
|
|
9
9
|
- **Tab Navigation**: Organize content into tabs for better UX
|
|
10
|
-
- **Related Lists**: Display related records (e.g., contacts for an account)
|
|
10
|
+
- **Related Lists**: Display related records (e.g., contacts for an account) —
|
|
11
|
+
authored as a `record:related_list` block, ⛔ not as a `detail-view` `related`
|
|
12
|
+
array, which is retired (objectui#7997; see **RelatedList** below)
|
|
11
13
|
- **Action Buttons**: Edit, Delete, and custom action buttons
|
|
12
14
|
- **Readonly/Edit Mode**: Toggle between view and edit modes
|
|
13
15
|
- **Back Navigation**: Built-in back button with customizable behavior
|
|
@@ -30,16 +32,20 @@ present:
|
|
|
30
32
|
|
|
31
33
|
```tsx
|
|
32
34
|
import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react';
|
|
35
|
+
import { DetailView } from '@object-ui/plugin-detail';
|
|
36
|
+
import type { DataSource } from '@object-ui/types';
|
|
37
|
+
|
|
38
|
+
declare const dataSource: DataSource;
|
|
33
39
|
|
|
34
40
|
// 1. Injected by an ancestor — the pattern the rest of the family uses, and the
|
|
35
41
|
// one to reach for when a page renders several data-bound blocks.
|
|
36
|
-
<SchemaRendererProvider dataSource={dataSource}>
|
|
42
|
+
const viaProvider = <SchemaRendererProvider dataSource={dataSource}>
|
|
37
43
|
<SchemaRenderer schema={{ type: 'detail-view', objectName: 'account', resourceId: '42' }} />
|
|
38
|
-
</SchemaRendererProvider
|
|
44
|
+
</SchemaRendererProvider>;
|
|
39
45
|
|
|
40
46
|
// 2. Handed to one placement — what `<DetailView dataSource={…} />` has always
|
|
41
47
|
// taken, and what the examples below use.
|
|
42
|
-
<DetailView schema={{ type: 'detail-view', objectName: 'account', resourceId: '42' }} dataSource={dataSource}
|
|
48
|
+
const viaProp = <DetailView schema={{ type: 'detail-view', objectName: 'account', resourceId: '42' }} dataSource={dataSource} />;
|
|
43
49
|
```
|
|
44
50
|
|
|
45
51
|
Route 1 is new as of objectui#5378: this block used to read the adapter from its
|
|
@@ -87,7 +93,11 @@ function ContactDetail() {
|
|
|
87
93
|
### With Sections
|
|
88
94
|
|
|
89
95
|
```tsx
|
|
90
|
-
|
|
96
|
+
import { DetailView } from '@object-ui/plugin-detail';
|
|
97
|
+
|
|
98
|
+
declare const accountData: Record<string, unknown>;
|
|
99
|
+
|
|
100
|
+
const accountDetail = <DetailView
|
|
91
101
|
schema={{
|
|
92
102
|
type: 'detail-view',
|
|
93
103
|
title: 'Account Details',
|
|
@@ -117,13 +127,18 @@ function ContactDetail() {
|
|
|
117
127
|
],
|
|
118
128
|
data: accountData,
|
|
119
129
|
}}
|
|
120
|
-
|
|
130
|
+
/>;
|
|
121
131
|
```
|
|
122
132
|
|
|
123
|
-
### With Tabs
|
|
133
|
+
### With Tabs
|
|
124
134
|
|
|
125
135
|
```tsx
|
|
126
|
-
|
|
136
|
+
import { DetailView } from '@object-ui/plugin-detail';
|
|
137
|
+
|
|
138
|
+
declare const navigate: (url: string) => void;
|
|
139
|
+
declare const deleteAccount: (id: string) => void;
|
|
140
|
+
|
|
141
|
+
const accountDetail = <DetailView
|
|
127
142
|
schema={{
|
|
128
143
|
type: 'detail-view',
|
|
129
144
|
title: 'Account: Acme Corp',
|
|
@@ -150,66 +165,220 @@ function ContactDetail() {
|
|
|
150
165
|
key: 'activity',
|
|
151
166
|
label: 'Activity',
|
|
152
167
|
badge: '12',
|
|
168
|
+
// `record:activity` — the registered Activity Timeline block. Reachable
|
|
169
|
+
// under that exact key and no other; see the note below the block.
|
|
170
|
+
// Its inputs go in `properties`, as the spec declares them.
|
|
153
171
|
content: {
|
|
154
|
-
type: 'activity
|
|
155
|
-
|
|
172
|
+
type: 'record:activity',
|
|
173
|
+
properties: { limit: 20, showCompleted: false },
|
|
156
174
|
},
|
|
157
175
|
},
|
|
158
176
|
],
|
|
159
|
-
related: [
|
|
160
|
-
{
|
|
161
|
-
title: 'Contacts',
|
|
162
|
-
type: 'table',
|
|
163
|
-
api: '/api/accounts/12345/contacts',
|
|
164
|
-
columns: ['name', 'email', 'phone', 'title'],
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
title: 'Opportunities',
|
|
168
|
-
type: 'table',
|
|
169
|
-
api: '/api/accounts/12345/opportunities',
|
|
170
|
-
columns: ['name', 'amount', 'stage', 'close_date'],
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
177
|
showEdit: true,
|
|
174
178
|
showDelete: true,
|
|
175
179
|
}}
|
|
176
180
|
onEdit={() => navigate('/accounts/12345/edit')}
|
|
177
181
|
onDelete={() => deleteAccount('12345')}
|
|
178
182
|
onBack={() => navigate('/accounts')}
|
|
179
|
-
|
|
183
|
+
/>;
|
|
180
184
|
```
|
|
181
185
|
|
|
186
|
+
A tab's `content` is an **SDUI node**, not a private vocabulary: `DetailTabs`
|
|
187
|
+
renders it with `<SchemaRenderer schema={toRenderableSchema(tab.content)} />`,
|
|
188
|
+
so `content.type` is resolved by the component registry and an unregistered
|
|
189
|
+
name paints the `Unknown component type` panel (**OBJUI-001**) instead of a
|
|
190
|
+
component. Author only types this repository registers.
|
|
191
|
+
|
|
192
|
+
Two things about the Activity tab above are worth copying rather than guessing
|
|
193
|
+
(objectui#8114 — it previously taught `type: 'activity-timeline'`, which
|
|
194
|
+
nothing registers):
|
|
195
|
+
|
|
196
|
+
- **The key is `record:activity`, with the namespace spelled out.** The
|
|
197
|
+
registration in `src/index.tsx` passes the bare name under
|
|
198
|
+
`{ namespace: 'record', skipFallback: true }`, and `skipFallback` is what
|
|
199
|
+
stops the bare name from also being claimed globally — so `record:activity`
|
|
200
|
+
resolves and `activity` resolves to nothing. `activity` is additionally a tab
|
|
201
|
+
**key** in the example above; the two are unrelated.
|
|
202
|
+
- **The node declares the feed's filters, not the feed.** Its inputs (`limit`,
|
|
203
|
+
`showCompleted`, `types`, `filterMode`, …) go in its `properties` bag, and
|
|
204
|
+
the block finds its own feed: a mounted discussion context, or a self-fetch
|
|
205
|
+
from `sys_activity` scoped to the record `useRecordContext` binds. Both need
|
|
206
|
+
a record host; a bare `<DetailView>` like the one above mounts neither, so
|
|
207
|
+
there the tab draws the block's empty state ("No activity recorded"). Under
|
|
208
|
+
a record host, such as the console's record page, the same node draws that
|
|
209
|
+
record's activity. `items` (with its `loading` flag) is the host's feed
|
|
210
|
+
slot, not a key of this node: `@object-ui/types` refuses it by name, and
|
|
211
|
+
`data` is not a key this block reads.
|
|
212
|
+
|
|
213
|
+
See **The `record:activity` block** in the plugin-detail guide for its declared
|
|
214
|
+
inputs (`types`, `limit`, `filterMode`, `showCompleted`, …).
|
|
215
|
+
|
|
182
216
|
## Schema
|
|
183
217
|
|
|
184
|
-
The DetailView component accepts a `DetailViewSchema
|
|
218
|
+
The DetailView component accepts a `DetailViewSchema`, declared in
|
|
219
|
+
`@object-ui/types`. Import it rather than re-typing it — every key below is
|
|
220
|
+
checked against that declaration, so this example cannot drift from the package:
|
|
185
221
|
|
|
186
222
|
```typescript
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
223
|
+
import type { DetailViewSchema } from '@object-ui/types';
|
|
224
|
+
|
|
225
|
+
const schema: DetailViewSchema = {
|
|
226
|
+
type: 'detail-view',
|
|
227
|
+
title: 'Account Details',
|
|
228
|
+
objectName: 'accounts',
|
|
229
|
+
resourceId: '12345',
|
|
230
|
+
api: '/api/accounts/12345',
|
|
231
|
+
data: undefined,
|
|
232
|
+
// Field name whose value becomes the header title; `title` is the fallback.
|
|
233
|
+
primaryField: 'name',
|
|
234
|
+
// Field values rendered as badges beside the header title.
|
|
235
|
+
summaryFields: ['industry', 'website'],
|
|
236
|
+
layout: 'vertical',
|
|
237
|
+
columns: 2,
|
|
238
|
+
// See the examples above for the shapes these three carry.
|
|
239
|
+
sections: [],
|
|
240
|
+
fields: [],
|
|
241
|
+
tabs: [],
|
|
242
|
+
actions: [],
|
|
243
|
+
// The view's own heading (title, follow-star, copy-id chip); drawn unless
|
|
244
|
+
// `false`. `record:details` sets it from its own `showHeader`.
|
|
245
|
+
showHeader: true,
|
|
246
|
+
showBack: true,
|
|
247
|
+
backUrl: '/accounts',
|
|
248
|
+
showEdit: true,
|
|
249
|
+
editUrl: '/accounts/12345/edit',
|
|
250
|
+
showDelete: true,
|
|
251
|
+
deleteConfirmation: 'Delete this account?',
|
|
252
|
+
loading: false,
|
|
253
|
+
header: { type: 'text', label: 'Custom header' },
|
|
254
|
+
footer: { type: 'text', label: 'Custom footer' },
|
|
255
|
+
};
|
|
209
256
|
```
|
|
210
257
|
|
|
258
|
+
### `summaryFields` and non-scalar values
|
|
259
|
+
|
|
260
|
+
A summary chip is a single-line pill, and it formats `currency`, `date`,
|
|
261
|
+
`datetime`, `percent` and the option families itself. Any other value it can
|
|
262
|
+
render as text it renders as text.
|
|
263
|
+
|
|
264
|
+
A **`percent`** chip is formatted by `@object-ui/fields`' `formatPercent` — the
|
|
265
|
+
same call the list cell makes — so one stored value reads the same beside the H1
|
|
266
|
+
as it does in a list: scaled at the storage the field declares (the spec's
|
|
267
|
+
`percentScaleOf`: a fraction unless the field declares a `max` above 1, read
|
|
268
|
+
through `percentCellScale` in `@object-ui/fields`, never guessed from the
|
|
269
|
+
value), rounded to the width `resolveFieldScale` (`@objectstack/spec/data`)
|
|
270
|
+
resolves for the field — its declared `scale`, or the protocol's own width for
|
|
271
|
+
a percent that declares none, which every percent face reads (objectui#9843) —
|
|
272
|
+
and rendered through the display locale's own percent affix rather than an
|
|
273
|
+
appended sign. A stored `1234.5` on a field declaring `max: 100` therefore
|
|
274
|
+
reads `1,235%` in an `en` session, and a stored `1` on a field declaring no
|
|
275
|
+
`max` reads `100%`; both carry the locale's own affix and marks elsewhere. Which stored values that convention moves, and
|
|
276
|
+
what each reads in both places, is re-derived by
|
|
277
|
+
`src/__tests__/summaryChip.percentConvention-9167.test.tsx` rather than listed
|
|
278
|
+
here. The chip's bar keeps the unrounded magnitude, as the list cell's bar does.
|
|
279
|
+
|
|
280
|
+
An **object** value — an expanded lookup payload, an address, a location, a
|
|
281
|
+
file — is drawn by that field's own cell renderer, so the chip shows what the
|
|
282
|
+
same value shows everywhere else on the page: the referenced record's name, the
|
|
283
|
+
formatted postal line, the coordinates, the file name.
|
|
284
|
+
|
|
285
|
+
Fifteen field kinds are the exception, because their renderer does not fit a
|
|
286
|
+
pill: the option families draw a badge (which would nest inside the chip's own),
|
|
287
|
+
`user` draws an avatar, `image` / `avatar` / `signature` draw an image and no
|
|
288
|
+
text at all, and `boolean` / `toggle` / `date` / `datetime` / `repeater` draw a
|
|
289
|
+
"No value" face for a value the chip only exists because the page called
|
|
290
|
+
filled. Those keep a text chip, carrying `@object-ui/fields`' shared value
|
|
291
|
+
coercion — the record's name where the object has one, and `[Object]` where it
|
|
292
|
+
does not. The set and the measurement behind it are
|
|
293
|
+
`src/summaryChipRenderers.ts`.
|
|
294
|
+
|
|
211
295
|
## Components
|
|
212
296
|
|
|
297
|
+
### RecordDetailPanel
|
|
298
|
+
|
|
299
|
+
The record overlay's **payload**, with no shell of its own: one
|
|
300
|
+
`InlineEditProvider` session wrapping `DetailView` plus the record-level
|
|
301
|
+
`InlineEditSaveBar`, and the `objectSchema` → typed-field derivation that feeds
|
|
302
|
+
them.
|
|
303
|
+
|
|
304
|
+
All five list-type renderers (`ObjectGrid`, `ObjectTree`, `ObjectGantt`,
|
|
305
|
+
`ObjectKanban`, `ObjectCalendar`) mount this component through
|
|
306
|
+
`NavigationOverlay` from `@object-ui/components`, so an authored
|
|
307
|
+
`navigation.mode` of `drawer` / `modal` / `split` / `popover` means the same
|
|
308
|
+
thing on every view type (objectui#9299). ⛔ Do not re-wrap it in a shell of
|
|
309
|
+
your own — that is exactly the fork this component exists to remove.
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
import type { ReactNode } from 'react';
|
|
313
|
+
import { NavigationOverlay, useOverlayAnchor } from '@object-ui/components';
|
|
314
|
+
import { RecordDetailPanel } from '@object-ui/plugin-detail';
|
|
315
|
+
import { useNavigationOverlay } from '@object-ui/react';
|
|
316
|
+
import type { DataSource } from '@object-ui/types';
|
|
317
|
+
|
|
318
|
+
declare const dataSource: DataSource;
|
|
319
|
+
declare const objectSchema: { fields?: Record<string, unknown> };
|
|
320
|
+
declare const myView: ReactNode;
|
|
321
|
+
declare function saveField(field: string, value: unknown): Promise<void>;
|
|
322
|
+
declare function deleteRecord(): Promise<void>;
|
|
323
|
+
|
|
324
|
+
function ContactsOverlay() {
|
|
325
|
+
const navigation = useNavigationOverlay({
|
|
326
|
+
navigation: { mode: 'drawer' },
|
|
327
|
+
objectName: 'contacts',
|
|
328
|
+
});
|
|
329
|
+
// `popover` anchors to the element the click landed on; spread
|
|
330
|
+
// `anchorCaptureProps` on your own container, or call `captureAnchor(event)`
|
|
331
|
+
// at a click site that already has one.
|
|
332
|
+
const { anchorRef } = useOverlayAnchor();
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<NavigationOverlay
|
|
336
|
+
{...navigation}
|
|
337
|
+
title="Record Detail"
|
|
338
|
+
mainContent={myView}
|
|
339
|
+
popoverAnchorRef={anchorRef}
|
|
340
|
+
>
|
|
341
|
+
{(record) => (
|
|
342
|
+
<RecordDetailPanel
|
|
343
|
+
record={record}
|
|
344
|
+
objectName="contacts"
|
|
345
|
+
recordId={String(record.id)}
|
|
346
|
+
dataSource={dataSource}
|
|
347
|
+
objectSchema={objectSchema}
|
|
348
|
+
onFieldSave={saveField}
|
|
349
|
+
onDelete={deleteRecord}
|
|
350
|
+
onClose={navigation.close}
|
|
351
|
+
/>
|
|
352
|
+
)}
|
|
353
|
+
</NavigationOverlay>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Omit `onFieldSave` for a strictly read-only panel; omit `onDelete` for no
|
|
359
|
+
delete action.
|
|
360
|
+
|
|
361
|
+
**Capability is handler presence, not a boolean.** A caller that omits
|
|
362
|
+
`onFieldSave` gets a read-only panel and one that omits `onDelete` gets no
|
|
363
|
+
delete action — which is what lets a row locked through a renderer's `lockField`
|
|
364
|
+
open read-only without the panel knowing what a lock is.
|
|
365
|
+
|
|
366
|
+
`RECORD_OVERLAY_DEFAULT_WIDTH` is exported alongside it and is the single code
|
|
367
|
+
home of the default overlay width repo-wide. ⛔ Do not re-spell the literal at a
|
|
368
|
+
call site.
|
|
369
|
+
|
|
370
|
+
### RecordDetailDrawer
|
|
371
|
+
|
|
372
|
+
The same payload in the drawer shell — a published convenience wrapper that
|
|
373
|
+
delegates to `NavigationOverlay` in `mode="drawer"`. It no longer owns a
|
|
374
|
+
`Sheet`, a drag-resize implementation, or a chrome header of its own
|
|
375
|
+
(objectui#9299).
|
|
376
|
+
|
|
377
|
+
A width a user had already dragged is persisted by the shell under
|
|
378
|
+
`ov:drawer-width:<objectName>`; a width left by the retired implementation under
|
|
379
|
+
`objectui.drawerWidth.<objectName>` is read once, moved forward and removed, so
|
|
380
|
+
existing widths carry over rather than reset.
|
|
381
|
+
|
|
213
382
|
### DetailSection
|
|
214
383
|
|
|
215
384
|
Renders a group of fields with optional collapsing.
|
|
@@ -222,6 +391,54 @@ Tab navigation for organizing content into different views.
|
|
|
222
391
|
|
|
223
392
|
Displays related records in list, grid, or table format.
|
|
224
393
|
|
|
394
|
+
> **Retired: `DetailViewSchema.related`** (objectui#7997, ADR-0049
|
|
395
|
+
> enforce-or-remove). Author a `record:related_list` block instead.
|
|
396
|
+
>
|
|
397
|
+
> Until objectui#7997 a `detail-view` node could carry its own `related` array,
|
|
398
|
+
> and this README taught it. That array is retired under ADR-0049
|
|
399
|
+
> enforce-or-remove (maintainer ruling 2026-09-10). It was a second entry to a
|
|
400
|
+
> capability `@objectstack/spec` already governs — the protocol declares no
|
|
401
|
+
> `DetailView` schema at all — so it mirrored nothing and drifted from the
|
|
402
|
+
> renderer it fed: it typed `columns` as `TableColumn` objects while the
|
|
403
|
+
> renderer also accepted bare field names. Authoring it is now **refused by
|
|
404
|
+
> name** on both the TypeScript and the JSON face, ⛔ not silently ignored.
|
|
405
|
+
>
|
|
406
|
+
> Nothing about the rendered result changed: both entries always went through
|
|
407
|
+
> the `RelatedList` component documented here. Only the second door closed.
|
|
408
|
+
>
|
|
409
|
+
> ```tsx
|
|
410
|
+
> import { SchemaRenderer } from '@object-ui/react';
|
|
411
|
+
>
|
|
412
|
+
> const contacts = (
|
|
413
|
+
> <SchemaRenderer
|
|
414
|
+
> schema={{
|
|
415
|
+
> type: 'record:related_list',
|
|
416
|
+
> properties: {
|
|
417
|
+
> objectName: 'contact',
|
|
418
|
+
> relationshipField: 'account_id',
|
|
419
|
+
> title: 'Contacts',
|
|
420
|
+
> columns: ['name', 'email', 'phone'],
|
|
421
|
+
> },
|
|
422
|
+
> }}
|
|
423
|
+
> />
|
|
424
|
+
> );
|
|
425
|
+
> ```
|
|
426
|
+
>
|
|
427
|
+
> ⚠️ `columns` is an array of **field-name strings**, which is what the protocol
|
|
428
|
+
> declares (`RecordRelatedListProps.columns`). The header comes from the related
|
|
429
|
+
> object's field `label` and the cell from the field's type, so a label rename
|
|
430
|
+
> reaches the list for free — the hand-spelled `{ accessorKey, header }` form the
|
|
431
|
+
> retired array taught froze both. `relationshipField` names the field on the
|
|
432
|
+
> related object pointing back at this record, and replaces the retired form's
|
|
433
|
+
> `api` endpoint.
|
|
434
|
+
>
|
|
435
|
+
> ⚠️ The block reads the parent record from the record page's `RecordContext`,
|
|
436
|
+
> so author it on a record page. Placed anywhere it cannot resolve a parent id
|
|
437
|
+
> it scopes to nothing and renders an empty list — which is also what the
|
|
438
|
+
> retired `related` form did whenever it declared `api` without a
|
|
439
|
+
> `referenceField`.
|
|
440
|
+
|
|
441
|
+
|
|
225
442
|
Related lists are **paged by default**: the `record:related_list` renderer
|
|
226
443
|
applies the spec default `limit` of **5** when the node doesn't declare one
|
|
227
444
|
(`@objectstack/spec` `RecordRelatedListProps.limit`, "Number of records to
|
|
@@ -245,18 +462,92 @@ value the cell shows — the label this list already resolved, the formula resul
|
|
|
245
462
|
the server hydrated — so the affordance stays and orders by that.
|
|
246
463
|
Passing `data` directly keeps the historical client-side slicing, and typing
|
|
247
464
|
in the opt-in filter box temporarily falls back to the full-fetch client
|
|
248
|
-
pipeline (the contains-filter sweeps
|
|
249
|
-
filter can express).
|
|
465
|
+
pipeline (the contains-filter sweeps the columns the list shows, which no
|
|
466
|
+
generic server filter can express).
|
|
467
|
+
|
|
468
|
+
A `password` / `secret` column draws `••••••`, and the list flags it
|
|
469
|
+
`masked` on the table it renders (objectui#10657), so the table never copies it
|
|
470
|
+
on Ctrl+C / Cmd+C, shows it as a tooltip, sorts by it from its header, or sizes
|
|
471
|
+
it by its value. The rule is `isMaskedFieldType()` from `@object-ui/fields`,
|
|
472
|
+
read over the column's authored `type` and the object's field type, so an
|
|
473
|
+
authored `type: 'text'` over a `secret` field keeps the flag. While the object
|
|
474
|
+
definition is still loading, or after its read failed, the list cannot tell
|
|
475
|
+
which columns are masked, so it flags every one and draws every cell it would
|
|
476
|
+
draw from that definition as the mask, never as text; it keeps doing so when
|
|
477
|
+
the read failed (objectui#10657). A column with a `cell` of your own draws what
|
|
478
|
+
your `cell` returns.
|
|
479
|
+
|
|
480
|
+
The list's own filter box and sort read the same flag (objectui#10728). The
|
|
481
|
+
opt-in filter box matches a term against the columns the list shows that are
|
|
482
|
+
not masked, so a masked value, and a field no column shows, never keeps a row.
|
|
483
|
+
A masked column gets no sort button on a `list` card, and a sort by it is
|
|
484
|
+
refused (including one the embedded table asks for, which a server-paged list
|
|
485
|
+
would send as `$orderby`). In a list that sorts in the browser, a sort set on a
|
|
486
|
+
column before it was flagged also stops ordering the rows. While the definition
|
|
487
|
+
is loading, or after its read failed, every column is flagged: the filter box
|
|
488
|
+
keeps no row and no sort button is offered. Not covered: on a server-paged
|
|
489
|
+
list, an authored `sort` naming a masked field is still sent to the server as
|
|
490
|
+
`$orderby`, and so is a sort the user set on a column before that column was
|
|
491
|
+
flagged, with each page the list fetches, until the user sorts by another
|
|
492
|
+
column. The column is flagged after the sort only when the `columns` or the
|
|
493
|
+
object of a list already on screen change; `record:related_list` sets both
|
|
494
|
+
once per block.
|
|
495
|
+
|
|
496
|
+
A list that authors `columns` sends a **`$select` projection** on its
|
|
497
|
+
auto-fetch (`objectui#10186`), the one `ListView` and `ObjectGrid` already send
|
|
498
|
+
(`objectui#6898`). It asks for the authored columns that pass the same gates the
|
|
499
|
+
column layer draws through (redaction, the parent key, field-level security),
|
|
500
|
+
plus `id`, the `$expand` roots, and, once the child schema has loaded, the
|
|
501
|
+
fields the row predicates read (the child object's `userActions` Edit/Delete
|
|
502
|
+
overrides, its actions, and the host's row actions), plus the mobile card
|
|
503
|
+
gallery's cover field when declared and readable. A column the principal
|
|
504
|
+
cannot read is dropped once the permission answer has loaded; a request sent
|
|
505
|
+
before that answer can still carry it, the same deferral `ObjectGrid` has.
|
|
506
|
+
If every authored column is denied, the list asks for `id` and its predicate
|
|
507
|
+
operands, not for everything. On a backend that honours `$select`, rows then
|
|
508
|
+
carry only the projected fields, so the opt-in filter box above sweeps those.
|
|
509
|
+
A list **without** authored `columns` derives them from `highlightFields` or
|
|
510
|
+
the field walk, and both choose by the emptiness of the rows fetched. That list sends no projection, and its column layer alone keeps a
|
|
511
|
+
denied field off screen. Against ObjectStack's server the projection is defence
|
|
512
|
+
in depth, because the server already strips denied fields from every row.
|
|
250
513
|
|
|
251
514
|
The node's `filter` (spec `RecordRelatedListProps.filter`, "additional filter
|
|
252
515
|
criteria") narrows the list beyond the parent relationship: it is
|
|
253
|
-
**AND-combined** with
|
|
516
|
+
**AND-combined** with the parent-relationship condition, never substituted for
|
|
254
517
|
it, so a related list stays scoped to the record it appears on and an additional
|
|
255
|
-
criterion can only ever narrow that set.
|
|
518
|
+
criterion can only ever narrow that set. That condition is **not one fixed
|
|
519
|
+
spelling** — it is compiled to match the relationship field's ARITY on the
|
|
520
|
+
child object (`objectui#7299`). A single-valued relationship gets the equality
|
|
521
|
+
form `{ [relationshipField]: parentId }`; a multi-valued one gets the membership
|
|
522
|
+
form `{ [relationshipField]: { $contains: parentId } }`, because the stored
|
|
523
|
+
value is an array of ids and equality would ask whether the whole array *is* one
|
|
524
|
+
id. The arity verdict is `@objectstack/spec/data`'s own `isMultiValueField`,
|
|
525
|
+
never a rule this package keeps locally. Authors write `filter` in the spec's own
|
|
256
526
|
vocabulary (`[{ field, operator, value }]`); a `dataSource` binding's composed
|
|
257
527
|
filter (component AND saved view AND binding) lands on the same key. Both are
|
|
258
528
|
lowered to ObjectQL through the repo's single filter sink, so no second dialect
|
|
259
|
-
appears.
|
|
529
|
+
appears.
|
|
530
|
+
|
|
531
|
+
**A third producer writes that same key: the relationship itself.** A `lookup` /
|
|
532
|
+
`master_detail` field may declare `relatedListFilter` (added to `@objectstack/spec`
|
|
533
|
+
in objectstack#8704 / PR #8955 — see this package's own `@objectstack/spec`
|
|
534
|
+
dependency in `package.json` for the version in use), a canonical Query-DSL
|
|
535
|
+
`FilterCondition` such as
|
|
536
|
+
`{ status: { $ne: 'deleted' } }`. `RecordDetailView` derives the record page's
|
|
537
|
+
related lists from the relationship graph and carries that value onto this node's
|
|
538
|
+
`filter`, so an auto-derived page honours it without anyone authoring a page
|
|
539
|
+
(objectui#4664). It is an authored constraint, not a user-editable suggestion —
|
|
540
|
+
it never becomes a filter-bar row — and it lands on the existing key rather than
|
|
541
|
+
a new one, so all three producers share one read site and one lowering.
|
|
542
|
+
|
|
543
|
+
The **tab badge honours the same composed filter**. `page:tabs` auto-derives a
|
|
544
|
+
Related tab's count from the `record:related_list` nodes underneath it, and that
|
|
545
|
+
probe reads this node's `filter` and composes it with the parent scope through
|
|
546
|
+
the same `mergeFilterNodes` sink the row query uses — one value, sent twice. That
|
|
547
|
+
is what keeps the badge from saying 7 above a list showing 3; it is normative in
|
|
548
|
+
the spec key's own contract text, not a nicety. Counts are cached per
|
|
549
|
+
(object, relationship, parent, **scope**), so a filtered and an unfiltered probe
|
|
550
|
+
over the same relationship never badge each other. On the legacy raw-URL fallback path (no `dataSource` adapter, where the
|
|
260
551
|
query language is `filter[<field>]=<value>` and cannot carry an operator) a
|
|
261
552
|
declared filter is refused with a console explanation rather than dropped —
|
|
262
553
|
answering with more rows than the metadata asked for is the failure this key's
|
|
@@ -294,6 +585,16 @@ shared draft (`InlineEditProvider` from `@object-ui/react`), committed by
|
|
|
294
585
|
- **Approval lock**: hosts pass `locked` / `lockedHint` to the save bar and
|
|
295
586
|
gate `InlineEditProvider.canEdit` when the record is approval-locked, so a
|
|
296
587
|
locked record hides its edit affordances instead of rejecting at Save.
|
|
588
|
+
- **Approval band & recall** (#6464): the band reads `approvalPending` /
|
|
589
|
+
`approvalProgress` from the same provider, and offers its **Recall** button
|
|
590
|
+
only where the click can succeed. Recall is the submitter's lever — the
|
|
591
|
+
server authorizes it on submitter identity and refuses everyone else — so
|
|
592
|
+
hosts that resolve approvals pass `InlineEditProvider.approvalIsSubmitter`.
|
|
593
|
+
It is tri-state on purpose: `false` withdraws the button from a resolved
|
|
594
|
+
non-submitter, `true` keeps it, and **omitting it leaves the button exactly
|
|
595
|
+
as it was** — a host that resolves no approval identity is unchanged rather
|
|
596
|
+
than losing its submitter's only unlock lever. It gates the affordance only;
|
|
597
|
+
the recall endpoint remains the authority on who may actually recall.
|
|
297
598
|
|
|
298
599
|
## Links
|
|
299
600
|
|
|
@@ -309,32 +610,63 @@ shared draft (`InlineEditProvider` from `@object-ui/react`), committed by
|
|
|
309
610
|
The "Reference Rail" is the right-hand column on the record detail page
|
|
310
611
|
that surfaces summary cards for related collections (similar to
|
|
311
612
|
Salesforce's **Related** rail and HubSpot's **About this record**
|
|
312
|
-
sidebar). It is rendered by the `record:reference_rail` component
|
|
313
|
-
|
|
613
|
+
sidebar). It is rendered by the `record:reference_rail` component. The
|
|
614
|
+
rail is **off by default** — it fans out a collection query per related
|
|
615
|
+
list — so the synth emits it only when the `buildDefaultPageSchema` call
|
|
616
|
+
opts in with `showReferenceRail: true`, and then only when:
|
|
314
617
|
|
|
315
618
|
1. The page is generated by the synth (`buildDefaultPageSchema`) — i.e.
|
|
316
619
|
no explicit `Page` overrides the object's detail view.
|
|
317
|
-
2.
|
|
318
|
-
|
|
620
|
+
2. That same synth call passes **≥2 entries** in its `related` option —
|
|
621
|
+
the count is read off the caller's option, **not** off the objectDef.
|
|
319
622
|
3. The viewport is **≥ xl (1280 px)** — below that the rail collapses and
|
|
320
623
|
the **Related** tab keeps full coverage.
|
|
321
|
-
4. The
|
|
624
|
+
4. The synth call does **not** opt out via `hideReferenceRail`.
|
|
625
|
+
|
|
626
|
+
Conditions 1, 2 and 4 are one conjunction inside `buildDefaultPageSchema`,
|
|
627
|
+
quoted here verbatim so that a later change to it shows up as a diff
|
|
628
|
+
against this page: `options.showReferenceRail === true`,
|
|
629
|
+
`!options.hideReferenceRail`, `Array.isArray(options.related)`,
|
|
630
|
+
`options.related.length >= 2`. Condition 3 is not part of that
|
|
631
|
+
expression — it is the `hidden xl:flex` class the synth puts on the
|
|
632
|
+
`aside` region.
|
|
633
|
+
|
|
634
|
+
### Opting in
|
|
635
|
+
|
|
636
|
+
The opt-in flag and the ≥2 count are both `buildDefaultPageSchema`
|
|
637
|
+
**options**, never objectDef keys:
|
|
638
|
+
|
|
639
|
+
```ts
|
|
640
|
+
import { buildDefaultPageSchema } from '@object-ui/plugin-detail';
|
|
641
|
+
import type { ObjectDefLike } from '@object-ui/plugin-detail';
|
|
642
|
+
|
|
643
|
+
declare const opportunityDef: ObjectDefLike;
|
|
644
|
+
|
|
645
|
+
const page = buildDefaultPageSchema(opportunityDef, {
|
|
646
|
+
showReferenceRail: true, // required — omit it and no rail is emitted
|
|
647
|
+
related: [
|
|
648
|
+
{ objectName: 'quote', relationshipField: 'opportunity' },
|
|
649
|
+
{ objectName: 'task', relationshipField: 'related_to_opportunity' },
|
|
650
|
+
],
|
|
651
|
+
});
|
|
652
|
+
```
|
|
322
653
|
|
|
323
654
|
When the rail emits, the synth automatically suppresses the **Related**
|
|
324
655
|
tab so the same information isn't shown twice.
|
|
325
656
|
|
|
326
|
-
###
|
|
657
|
+
### Opting out
|
|
327
658
|
|
|
328
|
-
|
|
659
|
+
The opt-out is a `buildDefaultPageSchema` **option**, not an objectDef key:
|
|
329
660
|
|
|
330
661
|
```ts
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
662
|
+
import { buildDefaultPageSchema } from '@object-ui/plugin-detail';
|
|
663
|
+
import type { ObjectDefLike } from '@object-ui/plugin-detail';
|
|
664
|
+
|
|
665
|
+
declare const productDef: ObjectDefLike;
|
|
666
|
+
|
|
667
|
+
const page = buildDefaultPageSchema(productDef, {
|
|
668
|
+
hideReferenceRail: true, // hide the rail; restore the Related tab
|
|
669
|
+
hideRelatedTab: true, // (optional) force-hide the Related tab too
|
|
338
670
|
});
|
|
339
671
|
```
|
|
340
672
|
|
|
@@ -355,7 +687,7 @@ For explicit (non-synth) Pages, add an `aside` region after the `main`
|
|
|
355
687
|
region:
|
|
356
688
|
|
|
357
689
|
```ts
|
|
358
|
-
{
|
|
690
|
+
const asideRegion = {
|
|
359
691
|
name: 'aside',
|
|
360
692
|
width: 'small',
|
|
361
693
|
className: 'hidden xl:flex flex-col gap-4',
|
|
@@ -372,11 +704,12 @@ region:
|
|
|
372
704
|
},
|
|
373
705
|
},
|
|
374
706
|
],
|
|
375
|
-
}
|
|
707
|
+
};
|
|
376
708
|
```
|
|
377
709
|
|
|
378
|
-
The
|
|
379
|
-
`
|
|
710
|
+
The `entries` go in the node's `properties` bag, as the example shows: that
|
|
711
|
+
bag is the contract, and `objectui validate` refuses a flat `entries` written
|
|
712
|
+
on the node itself, naming `properties.entries`.
|
|
380
713
|
|
|
381
714
|
## License
|
|
382
715
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityTimeline.d.ts","sourceRoot":"","sources":["../src/ActivityTimeline.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"ActivityTimeline.d.ts","sourceRoot":"","sources":["../src/ActivityTimeline.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAItD,MAAM,MAAM,kBAAkB,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;AAE/D,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,8CAA8C;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAuHD,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAmG5D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentAttachment.d.ts","sourceRoot":"","sources":["../src/CommentAttachment.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,
|
|
1
|
+
{"version":3,"file":"CommentAttachment.d.ts","sourceRoot":"","sources":["../src/CommentAttachment.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAkJ9D,CAAC"}
|
|
@@ -33,9 +33,25 @@ export default ConcurrentUpdateDialog;
|
|
|
33
33
|
* raise the same kind of error) while still recognising the canonical
|
|
34
34
|
* shape: `code === 'CONCURRENT_UPDATE'` carrying optional
|
|
35
35
|
* `currentVersion` / `currentRecord` fields.
|
|
36
|
+
*
|
|
37
|
+
* The second limb — `name === 'ConcurrentUpdateError'` — is the deliberate
|
|
38
|
+
* cross-realm discriminator, and it stays: a host that bundles the adapter
|
|
39
|
+
* twice makes `instanceof` fail, leaving the class name as the only thing
|
|
40
|
+
* left to match on. The rationale is recorded above `isConcurrentUpdateError`
|
|
41
|
+
* in `packages/data-objectstack/src/index.ts` and, in full, above
|
|
42
|
+
* `isViewConfigPermissionDeniedError` in the same file.
|
|
43
|
+
*
|
|
44
|
+
* That limb is also why `code` is declared OPTIONAL below (objectui#6421). An
|
|
45
|
+
* error matched by `name` alone carries no `code` at all, so promising
|
|
46
|
+
* `code: 'CONCURRENT_UPDATE'` as a REQUIRED literal handed the caller a
|
|
47
|
+
* property the value does not have: `err.code === 'CONCURRENT_UPDATE'` would
|
|
48
|
+
* type-check and be `undefined` at runtime, with no compiler help. Declared =
|
|
49
|
+
* enforced — the narrowed type states only what BOTH limbs guarantee, which
|
|
50
|
+
* is the two optional payload fields its one consumer already reads. Pinned
|
|
51
|
+
* by `__tests__/ConcurrentUpdateDialog.narrowedCode-6421.test.tsx`.
|
|
36
52
|
*/
|
|
37
53
|
export declare function isConcurrentUpdateError(err: unknown): err is {
|
|
38
|
-
code
|
|
54
|
+
code?: 'CONCURRENT_UPDATE';
|
|
39
55
|
currentVersion?: string;
|
|
40
56
|
currentRecord?: Record<string, unknown> | null;
|
|
41
57
|
message?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConcurrentUpdateDialog.d.ts","sourceRoot":"","sources":["../src/ConcurrentUpdateDialog.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ConcurrentUpdateDialog.d.ts","sourceRoot":"","sources":["../src/ConcurrentUpdateDialog.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,MAAM,WAAW,wBAAwB;IACvC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,YAAY,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,YAAY,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC1C,qEAAqE;IACrE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA4BD,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CA8HxE,CAAC;AAEF,eAAe,sBAAsB,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI;IAC5D,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAIA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DetailSection.d.ts","sourceRoot":"","sources":["../src/DetailSection.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"DetailSection.d.ts","sourceRoot":"","sources":["../src/DetailSection.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAsB/B,OAAO,KAAK,EAAE,iBAAiB,IAAI,qBAAqB,EAAkC,MAAM,kBAAkB,CAAC;AA8BnH;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBxF;AAED,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,qBAAqB,CAAC;IAC/B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACpD;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6FAA6F;IAC7F,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,oEAAoE;IACpE,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAonBtD,CAAC"}
|