@raclettejs/core 0.1.31 → 0.1.32-nightly

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +31 -4
  2. package/dist/cli.js +94 -94
  3. package/dist/cli.js.map +3 -3
  4. package/dist/index.js +3 -3
  5. package/dist/index.js.map +1 -1
  6. package/package.json +4 -4
  7. package/services/backend/.yarn/install-state.gz +0 -0
  8. package/services/backend/dist/core/eventBus/index.js +7 -0
  9. package/services/backend/dist/core/pluginSystem/configGenerator/index.js +346 -0
  10. package/services/backend/dist/core/sockets/index.js +95 -0
  11. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/events/index.js +31 -0
  12. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/index.js +48 -0
  13. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/routes/index.js +15 -0
  14. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/events/index.js +22 -0
  15. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/index.js +51 -0
  16. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/routes/index.js +19 -0
  17. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/events/index.js +22 -0
  18. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/index.js +48 -0
  19. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.js +19 -0
  20. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/events/index.js +31 -0
  21. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/index.js +49 -0
  22. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/routes/index.js +16 -0
  23. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/events/index.js +39 -0
  24. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/index.js +50 -0
  25. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/routes/index.js +19 -0
  26. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/events/index.js +31 -0
  27. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/index.js +51 -0
  28. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/routes/index.js +21 -0
  29. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/events/index.js +31 -0
  30. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/index.js +48 -0
  31. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.js +15 -0
  32. package/services/backend/dist/corePlugins/raclette__core/backend/index.js +34 -0
  33. package/services/backend/dist/domains/index.js +11 -0
  34. package/services/backend/dist/domains/system/index.js +11 -0
  35. package/services/backend/dist/domains/system/routes/index.js +17 -0
  36. package/services/backend/dist/helpers/index.js +14 -0
  37. package/services/backend/dist/index.js +3 -0
  38. package/services/backend/dist/modules/authentication/index.js +253 -0
  39. package/services/backend/dist/shared/types/core/index.js +8 -0
  40. package/services/backend/dist/shared/types/dataTypes/index.js +6 -0
  41. package/services/backend/dist/shared/types/index.js +8 -0
  42. package/services/backend/dist/shared/types/plugins/index.js +8 -0
  43. package/services/backend/dist/types/index.js +12 -0
  44. package/services/backend/dist/utils/index.js +2 -0
  45. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patchOwn.ts +1 -1
  46. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/user.service.ts +22 -1
  47. package/services/backend/yarn.lock +1 -1
  48. package/services/frontend/.yarn/install-state.gz +0 -0
  49. package/services/frontend/package.json +2 -2
  50. package/services/frontend/src/core/lib/data/dataApi.ts +1 -2
  51. package/services/frontend/src/orchestrator/ProductOrchestrator.vue +8 -1
  52. package/services/frontend/src/orchestrator/assets/styles/layers.css +1 -4
  53. package/services/frontend/src/orchestrator/assets/styles/tailwindStyles.css +3 -3
  54. package/services/frontend/src/orchestrator/assets/styles/vuetifyStyles.scss +4 -1
  55. package/services/frontend/src/orchestrator/components/dataExport/DataExporter.vue +303 -0
  56. package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +473 -0
  57. package/services/frontend/src/orchestrator/components/dataTable/FileUpload.vue +83 -0
  58. package/services/frontend/src/orchestrator/components/dataTable/SelectionDialog.vue +169 -0
  59. package/services/frontend/src/orchestrator/components/dataTable/SummaryDialog.vue +211 -0
  60. package/services/frontend/src/orchestrator/components/index.ts +2 -0
  61. package/services/frontend/src/orchestrator/composables/index.ts +1 -0
  62. package/services/frontend/src/orchestrator/composables/useExport/formats/builtins.ts +124 -0
  63. package/services/frontend/src/orchestrator/composables/useExport/index.ts +176 -0
  64. package/services/frontend/src/orchestrator/composables/useExport/types.ts +141 -0
  65. package/services/frontend/src/orchestrator/i18n/de-DE.json +26 -1
  66. package/services/frontend/src/orchestrator/i18n/en-EU.json +26 -1
  67. package/services/frontend/src/orchestrator/i18n/sk.json +26 -1
  68. package/services/frontend/src/orchestrator/i18n/tl-TL.json +14 -0
  69. package/services/frontend/src/orchestrator/setup/vuetify.ts +0 -1
  70. package/services/frontend/yarn.lock +4854 -0
  71. package/types/index.ts +1 -1
  72. package/package/LICENSE.md +0 -699
  73. package/package/README.md +0 -49
  74. package/package/dev/README.md +0 -4
  75. package/package/index.js +0 -11
  76. package/package/raclette.default.config.yaml +0 -60
  77. package/package/services/backend/README.md +0 -172
  78. package/package/services/backend/yarn.lock +0 -5320
  79. package/package/services/frontend/README.md +0 -511
  80. package/package/services/frontend/src/core/lib/eggs/readme.md +0 -75
  81. package/package/services/frontend/yarn.lock +0 -0
  82. package/package/src/README.md +0 -404
  83. package/package/templates/README.md +0 -0
@@ -0,0 +1,473 @@
1
+ <template>
2
+ <div>
3
+ <v-sheet border rounded>
4
+ <v-data-table
5
+ v-model="selected"
6
+ v-model:search="search"
7
+ density="compact"
8
+ :items-per-page="itemsPerPage"
9
+ sticky
10
+ fixed-header
11
+ :headers="computedHeaders"
12
+ :items="items"
13
+ :loading="loading"
14
+ @click:row="handleRowClick"
15
+ :class="{ itemsDeleted }"
16
+ :show-select="showExport"
17
+ :item-value
18
+ >
19
+ <template #top>
20
+ <v-toolbar flat>
21
+ <v-toolbar-title v-if="dataName?.length">
22
+ <v-icon
23
+ color="medium-emphasis"
24
+ icon="mdi-book-multiple"
25
+ size="x-small"
26
+ start
27
+ />
28
+
29
+ {{ dataName }}
30
+ </v-toolbar-title>
31
+
32
+ <template #append>
33
+ <div
34
+ class="tw:flex tw:flex-wrap tw:gap-2 tw:items-center tw:justify-end"
35
+ >
36
+ <slot name="actions" />
37
+ <slot name="toolbar-leading" />
38
+ <FileUpload
39
+ v-if="onFileLoaded"
40
+ ref="importFileUploadRef"
41
+ :on-file-loaded="onFileLoaded"
42
+ :accept="importAccept"
43
+ :read-as="importReadAs"
44
+ />
45
+ <v-btn-group density="compact" divided>
46
+ <DataExporter
47
+ v-if="showExport"
48
+ :data="exportPayloadData"
49
+ :disabled="!selected.length"
50
+ :formats="exportFormats"
51
+ :default-format="exportDefaultFormat"
52
+ :button-label="$t('core.export')"
53
+ color="info"
54
+ variant="elevated"
55
+ />
56
+ <v-btn
57
+ v-if="onFileLoaded"
58
+ color="teal"
59
+ variant="elevated"
60
+ density="compact"
61
+ prepend-icon="mdi-database-import"
62
+ :text="$t('core.import')"
63
+ @click="importFileUploadRef?.openPicker()"
64
+ />
65
+ <v-btn
66
+ v-if="showCreateButton"
67
+ @click="handleCreate"
68
+ prepend-icon="mdi-plus"
69
+ density="compact"
70
+ :text="
71
+ $t('core.baseDataTable.createDataButton', {
72
+ dataName,
73
+ })
74
+ "
75
+ variant="elevated"
76
+ color="success"
77
+ />
78
+ </v-btn-group>
79
+ </div>
80
+ </template>
81
+ </v-toolbar>
82
+
83
+ <v-text-field
84
+ v-model="search"
85
+ :label="
86
+ $t('core.baseDataTable.searchPlaceholder', {
87
+ dataName,
88
+ })
89
+ "
90
+ prepend-inner-icon="mdi-magnify"
91
+ variant="outlined"
92
+ hide-details
93
+ single-line
94
+ density="compact"
95
+ />
96
+ </template>
97
+
98
+ <template
99
+ #item.color="{ item }"
100
+ v-if="!customSlots.includes('item.color')"
101
+ >
102
+ <div class="tw:flex tw:gap-2" v-if="item.color?.length">
103
+ <v-chip :color="item.color" label variant="outlined" size="small">
104
+ {{ item.color }}
105
+ </v-chip>
106
+ </div>
107
+ </template>
108
+ <!-- Built-in tags column -->
109
+ <template
110
+ #item.tags="{ item }"
111
+ v-if="!customSlots.includes('item.tags')"
112
+ >
113
+ <div class="tw:flex tw:gap-2" v-if="item.tags?.length">
114
+ <v-chip
115
+ v-for="tag in item.tags"
116
+ :key="tag._id || tag.id || tag"
117
+ :color="tag.color"
118
+ label
119
+ variant="outlined"
120
+ size="small"
121
+ >
122
+ {{ tag.title || tag.name || tag }}
123
+ </v-chip>
124
+ </div>
125
+ </template>
126
+
127
+ <!-- Dynamic slots for custom column rendering -->
128
+ <template v-for="slot in customSlots" :key="slot" #[slot]="slotProps">
129
+ <slot :name="slot" v-bind="slotProps" />
130
+ </template>
131
+
132
+ <!-- Actions column template -->
133
+ <template #item.actions="{ item }" v-if="showActionsColumn">
134
+ <slot name="prepend-row-actions" :item="item"> </slot>
135
+ <slot name="row-actions" :item="item">
136
+ <!-- Default delete action -->
137
+ <v-icon
138
+ v-if="showDeleteAction && !isDeleteDisabled(item)"
139
+ color="red"
140
+ @click.stop.prevent="handleDelete(item)"
141
+ class="mr-2"
142
+ :title="
143
+ $t('core.baseDataTable.deleteDataTitle', {
144
+ dataName,
145
+ })
146
+ "
147
+ icon="mdi-delete"
148
+ />
149
+ </slot>
150
+ <slot name="append-row-actions" :item="item"> </slot>
151
+ </template>
152
+
153
+ <!-- Loading template -->
154
+ <template #loader>
155
+ <v-skeleton-loader
156
+ class="mx-auto border"
157
+ :height="500"
158
+ width="100%"
159
+ type="list-item"
160
+ :loading="true"
161
+ />
162
+ </template>
163
+
164
+ <!-- Additional table slots (this caused the table to be empty) -->
165
+ <!-- <slot name="table-slots" /> -->
166
+ </v-data-table>
167
+ </v-sheet>
168
+ <!-- Delete Confirmation Dialog -->
169
+ <v-dialog
170
+ v-model="deleteDialog"
171
+ max-width="400"
172
+ v-if="showDeleteAction && itemToDelete"
173
+ >
174
+ <v-card>
175
+ <v-card-title class="text-h6">
176
+ {{
177
+ $t("core.baseDataTable.deleteDataTitle", {
178
+ dataName,
179
+ })
180
+ }}
181
+ </v-card-title>
182
+
183
+ <v-card-text
184
+ v-html="
185
+ $t('core.baseDataTable.deleteDataConfirmMessage', {
186
+ article: dataArticle,
187
+ dataName,
188
+ value:
189
+ itemToDelete.email ||
190
+ itemToDelete.title ||
191
+ itemToDelete.name ||
192
+ itemToDelete._id,
193
+ })
194
+ "
195
+ />
196
+ <v-card-actions>
197
+ <v-spacer />
198
+ <v-btn variant="text" @click="deleteDialog = false">
199
+ {{ $t("core.baseDataTable.deleteDataConfirmCancel") }}
200
+ </v-btn>
201
+ <v-btn color="red" variant="flat" @click="confirmDelete">
202
+ {{ $t("core.baseDataTable.deleteDataConfirmButton") }}
203
+ </v-btn>
204
+ </v-card-actions>
205
+ </v-card>
206
+ </v-dialog>
207
+
208
+ <!-- Additional dialogs slot -->
209
+ <slot name="dialogs" />
210
+ </div>
211
+ </template>
212
+
213
+ <script setup lang="ts" generic="T extends Record<string, any>">
214
+ import { computed, ref, useSlots, useTemplateRef } from "vue"
215
+ import { useRouteState } from "@raclettejs/core/orchestrator/composables"
216
+ import DataExporter from "@raclettejs/core/orchestrator/components/dataExport/DataExporter.vue"
217
+ import FileUpload from "@raclettejs/core/orchestrator/components/dataTable/FileUpload.vue"
218
+ import type {
219
+ ExportPayload,
220
+ FormatEntry,
221
+ } from "@racletteOrchestrator/composables/useExport/types"
222
+
223
+ export interface BaseDataTableProps<T> {
224
+ uuid?: string
225
+ // Data props
226
+ items?: T[]
227
+ loading?: boolean
228
+ itemsDeleted?: boolean
229
+ // Table configuration
230
+ headers: Array<{
231
+ title: string
232
+ key: keyof T | string
233
+ align?: "start" | "center" | "end"
234
+ sortable?: boolean
235
+ width?: string | number
236
+ }>
237
+ itemsPerPage?: number
238
+
239
+ // Create button
240
+ showCreateButton?: boolean
241
+ createInteractionLinkId?: string
242
+
243
+ // Row actions
244
+ showActionsColumn?: boolean
245
+ showDeleteAction?: boolean
246
+
247
+ // Navigation
248
+ editInteractionLinkId?: string
249
+
250
+ // Item processing
251
+ deleteValidator?: (item: T) => boolean
252
+ rowClickHandler?: (item: T) => void
253
+
254
+ dataName: string
255
+ dataArticle: string
256
+
257
+ showExport?: boolean
258
+
259
+ itemValue?: string
260
+
261
+ /**
262
+ * When set, exported rows come from here (matched to table selection via `itemValue`, default `_id`).
263
+ * Use this to omit display-only fields (e.g. relative dates) from JSON export while keeping table `items`.
264
+ */
265
+ exportableItems?: Record<string, unknown>[]
266
+ exportMeta?: Record<string, unknown>
267
+
268
+ /** Formats offered in the export menu (workbench list tables default to JSON only). */
269
+ exportFormats?: FormatEntry[]
270
+ exportDefaultFormat?: string
271
+
272
+ /** When set, an import control is shown and the handler receives file contents (same contract as FileUpload). */
273
+ onFileLoaded?: (content: string | ArrayBuffer, file: File) => void
274
+ importAccept?: string
275
+ importReadAs?: "text" | "arrayBuffer"
276
+ }
277
+
278
+ const props = withDefaults(defineProps<BaseDataTableProps<T>>(), {
279
+ uuid: undefined,
280
+ items: () => [],
281
+ itemsDeleted: false,
282
+ itemsPerPage: 50,
283
+ showCreateButton: true,
284
+ showActionsColumn: true,
285
+ showDeleteAction: true,
286
+ showExport: true,
287
+ itemValue: "_id",
288
+ exportableItems: undefined,
289
+ exportMeta: undefined,
290
+ exportFormats: () => ["json"] as FormatEntry[],
291
+ exportDefaultFormat: "json",
292
+ onFileLoaded: undefined,
293
+ importAccept: ".json",
294
+ importReadAs: "text",
295
+ })
296
+
297
+ const emit = defineEmits<{
298
+ create: []
299
+ edit: [item: T]
300
+ delete: [item: T]
301
+ rowClick: [item: T]
302
+ }>()
303
+
304
+ const slots = useSlots()
305
+ const { triggerInteractionLinkById } = useRouteState()
306
+ const importFileUploadRef = useTemplateRef<{
307
+ openPicker: () => void
308
+ }>("importFileUploadRef")
309
+
310
+ // Reactive data
311
+ const search = ref("")
312
+ const selected = ref<Array<string | number>>([])
313
+ const deleteDialog = ref(false)
314
+ const itemToDelete = ref<T | null>(null)
315
+
316
+ const computedHeaders = computed(() => {
317
+ if (!props.headers || !props.headers?.length) {
318
+ return null
319
+ }
320
+
321
+ const headers = [...props.headers]
322
+
323
+ // Add actions column if needed
324
+ if (props.showActionsColumn) {
325
+ headers.push({
326
+ title: "Actions",
327
+ key: "actions",
328
+ align: "end" as const,
329
+ sortable: false,
330
+ })
331
+ }
332
+
333
+ return headers
334
+ })
335
+
336
+ const customSlots = computed(() => {
337
+ return Object.keys(slots).filter(
338
+ (slot) => slot.startsWith("item.") && slot !== "item.actions",
339
+ )
340
+ })
341
+
342
+ const exportColumnKeys = computed(() => {
343
+ return (props.headers ?? [])
344
+ .map((header) => String(header.key).trim())
345
+ .filter((key) => key.length > 0 && key !== "actions")
346
+ })
347
+
348
+ const projectRowsToVisibleColumns = <TRow extends Record<string, unknown>>(
349
+ rows: TRow[],
350
+ ) => {
351
+ if (!exportColumnKeys.value.length) {
352
+ return rows
353
+ }
354
+
355
+ return rows.map((row) => {
356
+ const projectedRow: Record<string, unknown> = {}
357
+ for (const key of exportColumnKeys.value) {
358
+ if (key in row) {
359
+ projectedRow[key] = row[key]
360
+ }
361
+ }
362
+ return projectedRow
363
+ })
364
+ }
365
+
366
+ /** Rows passed to DataExporter when something is selected. */
367
+ const exportPayloadData = computed<ExportPayload>(() => {
368
+ const idKey = props.itemValue as keyof T
369
+ const selectedIds = selected.value
370
+ const toKey = (value: unknown) => String(value)
371
+
372
+ if (props.exportableItems != null) {
373
+ const idToRow = new Map<string, Record<string, unknown>>()
374
+
375
+ for (const row of props.exportableItems) {
376
+ const rawKey = row[idKey as string]
377
+ if (rawKey === undefined || rawKey === null) {
378
+ console.warn(
379
+ `[BaseDataTable] Export row is missing "${String(idKey)}" key and will be ignored.`,
380
+ row,
381
+ )
382
+ continue
383
+ }
384
+
385
+ const rowKey = toKey(rawKey)
386
+ if (idToRow.has(rowKey)) {
387
+ console.warn(
388
+ `[BaseDataTable] Duplicate export key "${rowKey}" for itemValue "${String(idKey)}".`,
389
+ )
390
+ }
391
+ idToRow.set(rowKey, row)
392
+ }
393
+
394
+ const selectedExportRows = selectedIds
395
+ .map((id) => idToRow.get(toKey(id)))
396
+ .filter((row): row is Record<string, unknown> => row !== undefined)
397
+
398
+ return {
399
+ items: projectRowsToVisibleColumns(selectedExportRows),
400
+ ...(props.exportMeta ? { meta: props.exportMeta } : {}),
401
+ }
402
+ }
403
+
404
+ const selectedKeySet = new Set(selectedIds.map((id) => toKey(id)))
405
+ const selectedRows = (props.items ?? []).filter((item) => {
406
+ const rowId = item[idKey]
407
+ if (rowId === undefined || rowId === null) {
408
+ console.warn(
409
+ `[BaseDataTable] Table item is missing "${String(idKey)}" key and will not be exportable.`,
410
+ item,
411
+ )
412
+ return false
413
+ }
414
+ return selectedKeySet.has(toKey(rowId))
415
+ }) as Record<string, unknown>[]
416
+
417
+ return {
418
+ items: projectRowsToVisibleColumns(selectedRows),
419
+ ...(props.exportMeta ? { meta: props.exportMeta } : {}),
420
+ }
421
+ })
422
+
423
+ // Methods
424
+ const handleCreate = () => {
425
+ if (props.createInteractionLinkId) {
426
+ triggerInteractionLinkById(props.createInteractionLinkId)
427
+ }
428
+ emit("create")
429
+ }
430
+
431
+ const handleRowClick = (_: PointerEvent, row: { item: T }) => {
432
+ if (props.rowClickHandler) {
433
+ props.rowClickHandler(row.item)
434
+ return
435
+ }
436
+
437
+ if (props.editInteractionLinkId) {
438
+ const item = row.item as any
439
+
440
+ triggerInteractionLinkById(props.editInteractionLinkId, {
441
+ id: item._id,
442
+ })
443
+ }
444
+
445
+ emit("edit", row.item)
446
+ emit("rowClick", row.item)
447
+ }
448
+
449
+ const handleDelete = (item: T) => {
450
+ itemToDelete.value = item
451
+ deleteDialog.value = true
452
+ }
453
+
454
+ const confirmDelete = () => {
455
+ if (itemToDelete.value) {
456
+ emit("delete", itemToDelete.value)
457
+ }
458
+
459
+ itemToDelete.value = null
460
+ deleteDialog.value = false
461
+ }
462
+
463
+ const isDeleteDisabled = (item: T): boolean => {
464
+ return props.deleteValidator ? !props.deleteValidator(item) : false
465
+ }
466
+ </script>
467
+
468
+ <style lang="css">
469
+ .itemsDeleted .v-data-table__tr {
470
+ background-color: rgb(233, 190, 190);
471
+ color: #000;
472
+ }
473
+ </style>
@@ -0,0 +1,83 @@
1
+ <template>
2
+ <!-- Visually hidden; opened via openPicker() from a separate toolbar button -->
3
+ <input
4
+ ref="nativeInput"
5
+ type="file"
6
+ class="raclette-file-upload-input"
7
+ :accept="accept"
8
+ @change="handleNativeChange"
9
+ />
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { useTemplateRef } from "vue"
14
+
15
+ const props = defineProps({
16
+ onFileLoaded: {
17
+ type: Function,
18
+ required: true,
19
+ },
20
+ accept: {
21
+ type: String,
22
+ default: "*/*",
23
+ },
24
+ readAs: {
25
+ type: String,
26
+ default: "text",
27
+ validator: (value: string) => ["text", "arrayBuffer"].includes(value),
28
+ },
29
+ })
30
+
31
+ const nativeInput = useTemplateRef<HTMLInputElement>("nativeInput")
32
+
33
+ const handleNativeChange = (event: Event) => {
34
+ const input = event.target as HTMLInputElement
35
+ const file = input.files?.[0]
36
+ if (!file) {
37
+ input.value = ""
38
+ return
39
+ }
40
+
41
+ const reader = new FileReader()
42
+
43
+ reader.onload = () => {
44
+ ;(props.onFileLoaded as (c: string | ArrayBuffer, f: File) => void)(
45
+ reader.result as string | ArrayBuffer,
46
+ file,
47
+ )
48
+ input.value = ""
49
+ }
50
+
51
+ reader.onerror = () => {
52
+ input.value = ""
53
+ }
54
+
55
+ if (props.readAs === "arrayBuffer") {
56
+ reader.readAsArrayBuffer(file)
57
+ } else {
58
+ reader.readAsText(file)
59
+ }
60
+ }
61
+
62
+ /** Programmatically open the file picker (toolbar button should call this). */
63
+ const openPicker = () => {
64
+ nativeInput.value?.click()
65
+ }
66
+
67
+ defineExpose({ openPicker, trigger: openPicker })
68
+ </script>
69
+
70
+ <style scoped>
71
+ .raclette-file-upload-input {
72
+ position: absolute;
73
+ width: 1px;
74
+ height: 1px;
75
+ padding: 0;
76
+ margin: -1px;
77
+ overflow: hidden;
78
+ clip: rect(0, 0, 0, 0);
79
+ white-space: nowrap;
80
+ border: 0;
81
+ }
82
+ </style>
83
+