@stonecrop/desktop 0.16.5 → 0.17.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/README.md +62 -47
- package/dist/desktop.d.ts +2 -12
- package/dist/desktop.js +411 -388
- package/dist/desktop.js.map +1 -1
- package/dist/src/types/index.d.ts +8 -9
- package/dist/src/types/index.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/components/Desktop.vue +143 -110
- package/src/types/index.ts +8 -9
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
<p>Loading {{ currentView }} data...</p>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
|
+
<!-- No row count and no "of N": Desktop asks for neither, so stating one would be a guess. -->
|
|
18
|
+
<p v-if="listIsTruncated" class="truncation-note">This is a partial list — more records exist on the server.</p>
|
|
19
|
+
|
|
17
20
|
<!-- Sheet Navigation -->
|
|
18
21
|
<SheetNav :breadcrumbs="navigationBreadcrumbs" />
|
|
19
22
|
|
|
@@ -35,7 +38,10 @@
|
|
|
35
38
|
</template>
|
|
36
39
|
|
|
37
40
|
<script setup lang="ts">
|
|
38
|
-
|
|
41
|
+
// The draft segment comes from @stonecrop/stonecrop rather than being spelled out here: that
|
|
42
|
+
// package guards fetching, field initialization and workflow readiness on the same question, and
|
|
43
|
+
// when the two were written separately they disagreed and every guard over there went dead.
|
|
44
|
+
import { DRAFT_RECORD_ID, isDraftRecordId, useStonecrop, useValidationStore } from '@stonecrop/stonecrop'
|
|
39
45
|
import { AForm, type AFormLinkNavigator, type ResolvedField, type ResolvedTable } from '@stonecrop/aform'
|
|
40
46
|
import type { ColumnSchema } from '@stonecrop/schema'
|
|
41
47
|
import { computed, onMounted, onUnmounted, provide, ref, unref, watch } from 'vue'
|
|
@@ -53,12 +59,7 @@ import type {
|
|
|
53
59
|
LoadRecordEventPayload,
|
|
54
60
|
} from '../types'
|
|
55
61
|
|
|
56
|
-
const {
|
|
57
|
-
availableDoctypes = [],
|
|
58
|
-
routeAdapter,
|
|
59
|
-
confirmFn,
|
|
60
|
-
recordIdField,
|
|
61
|
-
} = defineProps<{
|
|
62
|
+
const { availableDoctypes = [], routeAdapter } = defineProps<{
|
|
62
63
|
availableDoctypes?: string[]
|
|
63
64
|
/**
|
|
64
65
|
* Pluggable router adapter. When provided, Desktop uses these functions for all
|
|
@@ -66,18 +67,6 @@ const {
|
|
|
66
67
|
* Nuxt hosts (or any host with custom route conventions) should supply this.
|
|
67
68
|
*/
|
|
68
69
|
routeAdapter?: RouteAdapter
|
|
69
|
-
/**
|
|
70
|
-
* Replacement for the native `confirm()` dialog. Desktop calls this before
|
|
71
|
-
* performing a destructive action. Return `true` to proceed.
|
|
72
|
-
* Defaults to the native `window.confirm` if omitted.
|
|
73
|
-
*/
|
|
74
|
-
confirmFn?: (message: string) => boolean | Promise<boolean>
|
|
75
|
-
/**
|
|
76
|
-
* The field name that holds the canonical record ID (e.g., 'rowId' for UUID).
|
|
77
|
-
* Used for navigation and table row identification.
|
|
78
|
-
* Defaults to 'id' if not specified.
|
|
79
|
-
*/
|
|
80
|
-
recordIdField?: string
|
|
81
70
|
}>()
|
|
82
71
|
|
|
83
72
|
const emit = defineEmits<{
|
|
@@ -96,13 +85,14 @@ const emit = defineEmits<{
|
|
|
96
85
|
*/
|
|
97
86
|
'record:open': [payload: RecordOpenEventPayload]
|
|
98
87
|
/**
|
|
99
|
-
* Fired when Desktop
|
|
100
|
-
*
|
|
88
|
+
* Fired when Desktop is about to read records for a list view. A notification, not a request:
|
|
89
|
+
* Desktop performs the read itself through `Stonecrop.getRecords`. A host that fetches here
|
|
90
|
+
* races that read into the same HST key.
|
|
101
91
|
*/
|
|
102
92
|
'load-records': [payload: LoadRecordsEventPayload]
|
|
103
93
|
/**
|
|
104
|
-
* Fired when Desktop
|
|
105
|
-
*
|
|
94
|
+
* Fired when Desktop is about to read a single record for a form view. A notification, not a
|
|
95
|
+
* request — see `load-records`. Not emitted for a draft, which has nothing to fetch.
|
|
106
96
|
*/
|
|
107
97
|
'load-record': [payload: LoadRecordEventPayload]
|
|
108
98
|
}>()
|
|
@@ -131,9 +121,14 @@ const fieldErrors = computed<Record<string, string[]>>(() =>
|
|
|
131
121
|
const loading = ref(false)
|
|
132
122
|
const commandPaletteOpen = ref(false)
|
|
133
123
|
|
|
124
|
+
// The record being composed on a `/{doctype}/new` route. Deliberately not in HST: a draft has no
|
|
125
|
+
// identity to be keyed by, and both ways of faking one fail — see `DRAFT_RECORD_ID`.
|
|
126
|
+
const draftRecord = ref<Record<string, any>>({})
|
|
127
|
+
|
|
134
128
|
// Form/list data management — each view produces a different data shape.
|
|
135
129
|
// List views (doctypes, records) return table row data keyed by fieldname.
|
|
136
|
-
// Record view returns
|
|
130
|
+
// Record view returns the record's fields for two-way binding — from HST, or from `draftRecord`
|
|
131
|
+
// when the route is a draft.
|
|
137
132
|
const currentViewData = computed<Record<string, any>>({
|
|
138
133
|
get() {
|
|
139
134
|
// Doctypes list — rows come from availableDoctypes prop (reactive via availableDoctypes)
|
|
@@ -144,7 +139,6 @@ const currentViewData = computed<Record<string, any>>({
|
|
|
144
139
|
id: doctype,
|
|
145
140
|
doctype,
|
|
146
141
|
display_name: formatDoctypeName(doctype),
|
|
147
|
-
record_count: getRecordCount(doctype),
|
|
148
142
|
actions: 'View Records',
|
|
149
143
|
})) ?? [],
|
|
150
144
|
}
|
|
@@ -156,7 +150,13 @@ const currentViewData = computed<Record<string, any>>({
|
|
|
156
150
|
records_table: getRecords().map(record =>
|
|
157
151
|
Object.assign({}, record, {
|
|
158
152
|
id: resolveRecordId(record) ?? '',
|
|
159
|
-
|
|
153
|
+
// A list row is navigation. Actions live on the record view, where the Actions
|
|
154
|
+
// dropdown is built from what the doctype declares and what the record's
|
|
155
|
+
// current state allows. This cell used to also offer Delete, which dispatched
|
|
156
|
+
// an action named `DELETE` that Desktop invented — no doctype in a
|
|
157
|
+
// WorkflowMeta app declares it, so it failed on every click. Removal is a
|
|
158
|
+
// workflow outcome (`archive`, `cancel`) and belongs in the doctype.
|
|
159
|
+
actions: 'Edit',
|
|
160
160
|
})
|
|
161
161
|
),
|
|
162
162
|
}
|
|
@@ -168,11 +168,14 @@ const currentViewData = computed<Record<string, any>>({
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
try {
|
|
171
|
-
const
|
|
171
|
+
const source = isNewRecord.value
|
|
172
|
+
? draftRecord.value
|
|
173
|
+
: (stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as
|
|
174
|
+
Record<string, any> | undefined)
|
|
172
175
|
// Return a plain shallow copy so AForm mutations don't propagate directly into
|
|
173
176
|
// the HST reactive object, which would bypass field-trigger diffing and cause
|
|
174
177
|
// setupDeepReactivity to fire triggers for all fields on every keystroke.
|
|
175
|
-
const flat: Record<string, any> = { ...
|
|
178
|
+
const flat: Record<string, any> = { ...source }
|
|
176
179
|
|
|
177
180
|
// AFieldset receives data[fieldsetFieldname] as its data prop, so the fieldset's
|
|
178
181
|
// children must be grouped under the fieldset key. The server returns flat SQL rows,
|
|
@@ -233,16 +236,30 @@ const currentViewData = computed<Record<string, any>>({
|
|
|
233
236
|
|
|
234
237
|
// Only update fields that actually changed. Never write undefined — AForm may emit
|
|
235
238
|
// schema fields absent from the record as undefined; writing them would silently
|
|
236
|
-
// clear values that exist
|
|
237
|
-
const hstStore = stonecrop.value.getStore()
|
|
239
|
+
// clear values that exist. Explicit null is allowed (intentional clear).
|
|
238
240
|
const changedFields: string[] = []
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
241
|
+
if (isNewRecord.value) {
|
|
242
|
+
// Reassigned, not mutated, so the getter re-runs. Relying on in-place mutation of the
|
|
243
|
+
// cached object is what made a draft's edits vanish on any invalidation.
|
|
244
|
+
const next = { ...draftRecord.value }
|
|
245
|
+
for (const [fieldname, value] of Object.entries(flatData)) {
|
|
246
|
+
if (value === undefined) continue
|
|
247
|
+
if (next[fieldname] !== value) {
|
|
248
|
+
next[fieldname] = value
|
|
249
|
+
changedFields.push(fieldname)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
draftRecord.value = next
|
|
253
|
+
} else {
|
|
254
|
+
const hstStore = stonecrop.value.getStore()
|
|
255
|
+
for (const [fieldname, value] of Object.entries(flatData)) {
|
|
256
|
+
if (value === undefined) continue
|
|
257
|
+
const fieldPath = `${currentDoctype.value}.${currentRecordId.value}.${fieldname}`
|
|
258
|
+
const currentValue = hstStore.has(fieldPath) ? hstStore.get(fieldPath) : undefined
|
|
259
|
+
if (currentValue !== value) {
|
|
260
|
+
hstStore.set(fieldPath, value)
|
|
261
|
+
changedFields.push(fieldname)
|
|
262
|
+
}
|
|
246
263
|
}
|
|
247
264
|
}
|
|
248
265
|
|
|
@@ -267,8 +284,15 @@ function driveFieldValidation(changedFields: string[]) {
|
|
|
267
284
|
const triggers = doctype?.getTriggers()
|
|
268
285
|
if (!triggers || Object.keys(triggers).length === 0) return
|
|
269
286
|
|
|
270
|
-
|
|
271
|
-
const record =
|
|
287
|
+
// A draft's siblings come from the buffer; reading HST would hand every validator an empty record.
|
|
288
|
+
const record = isNewRecord.value
|
|
289
|
+
? { ...draftRecord.value }
|
|
290
|
+
: {
|
|
291
|
+
...(stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as Record<
|
|
292
|
+
string,
|
|
293
|
+
unknown
|
|
294
|
+
>),
|
|
295
|
+
}
|
|
272
296
|
|
|
273
297
|
for (const field of changedFields) {
|
|
274
298
|
void validationStore.validateField(triggers, field, record)
|
|
@@ -343,7 +367,7 @@ const currentRecordId = computed(() => {
|
|
|
343
367
|
|
|
344
368
|
return ''
|
|
345
369
|
})
|
|
346
|
-
const isNewRecord = computed(() => currentRecordId.value
|
|
370
|
+
const isNewRecord = computed(() => isDraftRecordId(currentRecordId.value))
|
|
347
371
|
|
|
348
372
|
// Determine current view based on route
|
|
349
373
|
const currentView = computed(() => {
|
|
@@ -571,11 +595,13 @@ const formatDoctypeName = (doctype: string): string => {
|
|
|
571
595
|
.join(' ')
|
|
572
596
|
}
|
|
573
597
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
598
|
+
// Whether the list on screen is a page rather than the whole set. Read from the backend's own
|
|
599
|
+
// answer, not inferred from how many rows arrived: a page that happens to be exactly the limit
|
|
600
|
+
// is indistinguishable from a complete one by counting.
|
|
601
|
+
const listIsTruncated = computed(() => {
|
|
602
|
+
if (currentView.value !== 'records' || !currentDoctype.value) return false
|
|
603
|
+
return stonecrop.value?.getPageInfo(currentDoctype.value)?.hasMore === true
|
|
604
|
+
})
|
|
579
605
|
|
|
580
606
|
// Internal navigation helper: emits 'navigate', then calls the adapter (if any)
|
|
581
607
|
// or falls back to the registry's Vue Router instance.
|
|
@@ -605,8 +631,7 @@ const openRecord = async (recordId: string) => {
|
|
|
605
631
|
}
|
|
606
632
|
|
|
607
633
|
const createNewRecord = async () => {
|
|
608
|
-
|
|
609
|
-
await doNavigate({ view: 'record', doctype: routeDoctype.value, recordId: newId })
|
|
634
|
+
await doNavigate({ view: 'record', doctype: routeDoctype.value, recordId: DRAFT_RECORD_ID })
|
|
610
635
|
}
|
|
611
636
|
|
|
612
637
|
// Flatten Fieldset containers into individual columns for list/table views.
|
|
@@ -651,14 +676,10 @@ const getDoctypesSchema = (): ResolvedField[] => {
|
|
|
651
676
|
edit: false,
|
|
652
677
|
width: '30ch',
|
|
653
678
|
},
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
align: 'center' as const,
|
|
659
|
-
edit: false,
|
|
660
|
-
width: '15ch',
|
|
661
|
-
},
|
|
679
|
+
// No record count column. It read `getRecordIds(doctype).length`, which is how many
|
|
680
|
+
// records HST happens to hold — zero for a doctype never opened, and the page size
|
|
681
|
+
// for one that was. The real total belongs to the backend and Desktop never asks
|
|
682
|
+
// for it, so this shell cannot answer it. Same reason the `recordIdField` prop went.
|
|
662
683
|
{
|
|
663
684
|
fieldname: 'actions',
|
|
664
685
|
label: 'Actions',
|
|
@@ -756,44 +777,19 @@ const handleActionClick = (_label: string, action: (() => void | Promise<void>)
|
|
|
756
777
|
}
|
|
757
778
|
}
|
|
758
779
|
|
|
759
|
-
// Desktop does NOT own the delete lifecycle — it asks for confirmation, then emits
|
|
760
|
-
// an 'action' event. The host app is responsible for removing the record from HST
|
|
761
|
-
// and calling the server.
|
|
762
|
-
const handleDelete = async (recordId?: string) => {
|
|
763
|
-
const targetRecordId = recordId || currentRecordId.value
|
|
764
|
-
if (!targetRecordId) return
|
|
765
|
-
|
|
766
|
-
const confirmed = confirmFn
|
|
767
|
-
? await confirmFn('Are you sure you want to delete this record?')
|
|
768
|
-
: confirm('Are you sure you want to delete this record?')
|
|
769
|
-
|
|
770
|
-
if (confirmed) {
|
|
771
|
-
emit('action', {
|
|
772
|
-
name: 'DELETE',
|
|
773
|
-
doctype: currentDoctype.value,
|
|
774
|
-
recordId: targetRecordId,
|
|
775
|
-
data: currentViewData.value || {},
|
|
776
|
-
})
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
780
|
/**
|
|
781
781
|
* Resolve a record's identity for links and navigation.
|
|
782
782
|
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
783
|
+
* Identity is declared once, on the doctype: `primaryKey`, or `id` when nothing is declared.
|
|
784
|
+
* Delegating to `Doctype.getRecordId` is what guarantees this matches the key
|
|
785
|
+
* `Stonecrop.getRecords` stored the record under — resolving it independently here would let a
|
|
786
|
+
* row render a link to an HST path that does not exist.
|
|
786
787
|
*
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
*
|
|
788
|
+
* There is deliberately no per-shell override. Identity is a per-doctype fact and one shell
|
|
789
|
+
* renders many doctypes, so a single prop cannot answer it; a shell that named a field would
|
|
790
|
+
* also be overriding the very declaration the store keyed on, which is the bug above.
|
|
790
791
|
*/
|
|
791
792
|
const resolveRecordId = (record: Record<string, unknown>): string | undefined => {
|
|
792
|
-
if (recordIdField) {
|
|
793
|
-
const explicit = record[recordIdField]
|
|
794
|
-
if (typeof explicit === 'number') return String(explicit)
|
|
795
|
-
if (typeof explicit === 'string' && explicit !== '') return explicit
|
|
796
|
-
}
|
|
797
793
|
if (!stonecrop.value || !currentDoctype.value) return undefined
|
|
798
794
|
return stonecrop.value.registry.registry[currentDoctype.value]?.getRecordId(record)
|
|
799
795
|
}
|
|
@@ -839,37 +835,52 @@ const handleClick = async (event: Event) => {
|
|
|
839
835
|
await navigateToDoctype(doctype)
|
|
840
836
|
}
|
|
841
837
|
}
|
|
842
|
-
} else if (cellText
|
|
838
|
+
} else if (cellText === 'Edit' && row) {
|
|
839
|
+
// Matched exactly, not by substring. This handler is bound to the whole desktop, so a
|
|
840
|
+
// substring match fired on any cell whose *data* happened to contain the word — a task
|
|
841
|
+
// titled "Delete old backups" popped a delete confirmation when you clicked it.
|
|
843
842
|
const recordId = getRecordIdFromRow(row)
|
|
844
843
|
if (recordId) {
|
|
845
844
|
await openRecord(recordId)
|
|
846
845
|
}
|
|
847
|
-
} else if (cellText?.includes('Delete') && row) {
|
|
848
|
-
const recordId = getRecordIdFromRow(row)
|
|
849
|
-
if (recordId) {
|
|
850
|
-
await handleDelete(recordId)
|
|
851
|
-
}
|
|
852
846
|
}
|
|
853
847
|
}
|
|
854
848
|
}
|
|
855
849
|
|
|
850
|
+
// Reads go through Stonecrop, which owns whether to fetch, how to key the result, and where to
|
|
851
|
+
// put it. Desktop asks for data and renders what arrives; it decides none of that itself.
|
|
852
|
+
//
|
|
853
|
+
// Both loaders are no-ops without a client, so a host that populates HST some other way keeps
|
|
854
|
+
// working unchanged rather than taking a thrown error on every navigation.
|
|
856
855
|
const loadRecordData = async () => {
|
|
857
|
-
if (!stonecrop.value || !currentDoctype.value ||
|
|
856
|
+
if (!stonecrop.value || !currentDoctype.value || !stonecrop.value.getClient()) return
|
|
858
857
|
|
|
859
|
-
|
|
860
|
-
|
|
858
|
+
loading.value = true
|
|
859
|
+
try {
|
|
860
|
+
await stonecrop.value.getRecord(currentDoctype.value, currentRecordId.value)
|
|
861
|
+
} catch (error) {
|
|
862
|
+
console.warn('Error fetching record:', error)
|
|
863
|
+
} finally {
|
|
864
|
+
loading.value = false
|
|
865
|
+
}
|
|
866
|
+
}
|
|
861
867
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
868
|
+
const loadRecordsData = async () => {
|
|
869
|
+
if (!stonecrop.value || !currentDoctype.value || !stonecrop.value.getClient()) return
|
|
870
|
+
|
|
871
|
+
const doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)
|
|
872
|
+
if (!doctype) return
|
|
873
|
+
|
|
874
|
+
// No row limit is passed. Desktop cannot know what is safe for the host's backend, and a
|
|
875
|
+
// single per-shell number could not serve doctypes of wildly different size anyway — the same
|
|
876
|
+
// reason the `recordIdField` prop was removed. The server decides the page.
|
|
877
|
+
loading.value = true
|
|
878
|
+
try {
|
|
879
|
+
await stonecrop.value.getRecords(doctype)
|
|
880
|
+
} catch (error) {
|
|
881
|
+
console.warn('Error fetching records:', error)
|
|
882
|
+
} finally {
|
|
883
|
+
loading.value = false
|
|
873
884
|
}
|
|
874
885
|
}
|
|
875
886
|
|
|
@@ -877,11 +888,18 @@ const loadRecordData = async () => {
|
|
|
877
888
|
watch(
|
|
878
889
|
[currentView, currentDoctype, currentRecordId],
|
|
879
890
|
() => {
|
|
891
|
+
// The events are notifications, not fetch requests: they announce what Desktop is about to
|
|
892
|
+
// read so a host can hang analytics or a prefetch off them. The read itself is Stonecrop's.
|
|
880
893
|
if (currentView.value === 'records' && currentDoctype.value) {
|
|
881
|
-
// Emit load-records event so host app can populate HST
|
|
882
894
|
emit('load-records', { doctype: currentDoctype.value })
|
|
895
|
+
void loadRecordsData()
|
|
883
896
|
} else if (currentView.value === 'record' && currentDoctype.value && currentRecordId.value) {
|
|
884
|
-
//
|
|
897
|
+
// A draft has nothing to fetch — the record does not exist on the server yet. Desktop
|
|
898
|
+
// used to emit anyway and leave the host to work it out, which meant every host had to
|
|
899
|
+
// recognise the private draft-id scheme above just to suppress a doomed request; all of
|
|
900
|
+
// them did, identically. `getRecord` declines the same case for the same reason.
|
|
901
|
+
if (isNewRecord.value) return
|
|
902
|
+
|
|
885
903
|
emit('load-record', { doctype: currentDoctype.value, recordId: currentRecordId.value })
|
|
886
904
|
void loadRecordData()
|
|
887
905
|
}
|
|
@@ -895,6 +913,22 @@ watch([currentDoctype, currentRecordId], () => {
|
|
|
895
913
|
validationStore?.clearAll()
|
|
896
914
|
})
|
|
897
915
|
|
|
916
|
+
// Seeding on entry gives a new record the doctype's declared defaults, which it never used to get.
|
|
917
|
+
// Discarding on exit matters as much: the draft segment is one shared literal, so a stale buffer
|
|
918
|
+
// would open the next New Record pre-filled with the abandoned one's values.
|
|
919
|
+
watch(
|
|
920
|
+
[currentDoctype, currentRecordId],
|
|
921
|
+
() => {
|
|
922
|
+
if (!isNewRecord.value) {
|
|
923
|
+
draftRecord.value = {}
|
|
924
|
+
return
|
|
925
|
+
}
|
|
926
|
+
const registry = stonecrop.value?.registry
|
|
927
|
+
draftRecord.value = registry ? registry.initializeRecord(getRecordFormSchema()) : {}
|
|
928
|
+
},
|
|
929
|
+
{ immediate: true }
|
|
930
|
+
)
|
|
931
|
+
|
|
898
932
|
// Stonecrop reactive computed properties update automatically when the instance
|
|
899
933
|
// becomes available — no manual watcher needed.
|
|
900
934
|
|
|
@@ -903,7 +937,6 @@ const desktopMethods = {
|
|
|
903
937
|
navigateToDoctype,
|
|
904
938
|
openRecord,
|
|
905
939
|
createNewRecord,
|
|
906
|
-
handleDelete,
|
|
907
940
|
/**
|
|
908
941
|
* Convenience wrapper so child components (e.g. slot content) can emit
|
|
909
942
|
* an action event without needing a direct reference to the emit function.
|
package/src/types/index.ts
CHANGED
|
@@ -69,17 +69,16 @@ export type RouteAdapter = {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* Payload emitted with the 'action' event when the user triggers
|
|
72
|
+
* Payload emitted with the 'action' event when the user triggers a declared action.
|
|
73
|
+
*
|
|
74
|
+
* Re-exported, not defined here: the shell emits it and `useClientAction` consumes it, and those
|
|
75
|
+
* now live in different packages. It is declared in `@stonecrop/stonecrop` — which this package
|
|
76
|
+
* already depends on — so the two cannot drift. Importing it from `@stonecrop/desktop` still
|
|
77
|
+
* works and is still the natural place for a host to reach for it.
|
|
78
|
+
*
|
|
73
79
|
* @public
|
|
74
80
|
*/
|
|
75
|
-
export type ActionEventPayload
|
|
76
|
-
/** The FSM transition name (e.g. 'SAVE', 'SUBMIT', 'APPROVE') */
|
|
77
|
-
name: string
|
|
78
|
-
doctype: string
|
|
79
|
-
recordId: string
|
|
80
|
-
/** Snapshot of the form data at the time the action was triggered */
|
|
81
|
-
data: Record<string, any>
|
|
82
|
-
}
|
|
81
|
+
export type { ActionEventPayload } from '@stonecrop/stonecrop'
|
|
83
82
|
|
|
84
83
|
/**
|
|
85
84
|
* Payload emitted with the 'record:open' event
|