viur-ui 0.0.1

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 (114) hide show
  1. package/README.md +79 -0
  2. package/index.d.ts +351 -0
  3. package/index.js +46 -0
  4. package/package.json +84 -0
  5. package/src/components/Browser/Browser.vue +496 -0
  6. package/src/components/Browser/TreeNav.vue +408 -0
  7. package/src/components/Browser/ViurBrowser.d.vue.ts +64 -0
  8. package/src/components/Browser/ViurBrowser.vue +271 -0
  9. package/src/components/Browser/dragSession.ts +134 -0
  10. package/src/components/Browser/index.ts +14 -0
  11. package/src/components/Browser/types.ts +30 -0
  12. package/src/components/DataTable/ColumnFilterPanel.vue +225 -0
  13. package/src/components/DataTable/DataTable.vue +3155 -0
  14. package/src/components/DataTable/ViurDataTable.d.vue.ts +72 -0
  15. package/src/components/DataTable/ViurDataTable.vue +331 -0
  16. package/src/components/DataTable/columns.ts +400 -0
  17. package/src/components/DataTable/index.ts +60 -0
  18. package/src/components/DataTable/rows.ts +446 -0
  19. package/src/components/DataTable/types.ts +267 -0
  20. package/src/components/DataTable/useColumnLayout.ts +294 -0
  21. package/src/components/DataTable/useGridNavigation.ts +193 -0
  22. package/src/components/DataTable/useVirtualRows.ts +126 -0
  23. package/src/components/DataTable/viurColumns.ts +330 -0
  24. package/src/forms/ViForm.d.vue.ts +106 -0
  25. package/src/forms/ViForm.vue +592 -0
  26. package/src/forms/ViFormCategory.vue +57 -0
  27. package/src/forms/bones/Bone.vue +1023 -0
  28. package/src/forms/bones/BoneActions.vue +127 -0
  29. package/src/forms/bones/BoneLabel.vue +122 -0
  30. package/src/forms/bones/BoneMultipleList.vue +91 -0
  31. package/src/forms/bones/BoneMultipleWrapper.vue +105 -0
  32. package/src/forms/bones/BoneNestedWrapper.vue +155 -0
  33. package/src/forms/bones/BoneSingleEntry.vue +51 -0
  34. package/src/forms/bones/FilterBone.vue +694 -0
  35. package/src/forms/bones/actionbar/defaultBar.vue +98 -0
  36. package/src/forms/bones/actionbar/fileBar.vue +165 -0
  37. package/src/forms/bones/actionbar/relationalBar.vue +125 -0
  38. package/src/forms/bones/actions/ActionDialog.vue +70 -0
  39. package/src/forms/bones/actions/Debug.vue +106 -0
  40. package/src/forms/bones/actions/Delete.vue +13 -0
  41. package/src/forms/bones/actions/DescribeImage.vue +201 -0
  42. package/src/forms/bones/actions/Download.vue +27 -0
  43. package/src/forms/bones/actions/Placeholder.vue +14 -0
  44. package/src/forms/bones/actions/Translation.vue +238 -0
  45. package/src/forms/bones/actions/index.js +280 -0
  46. package/src/forms/bones/bone.d.ts +45 -0
  47. package/src/forms/bones/country_information.json +779 -0
  48. package/src/forms/bones/index.js +225 -0
  49. package/src/forms/bones/uploadAdapter.js +46 -0
  50. package/src/forms/bones/utils.js +284 -0
  51. package/src/forms/bones/view/base_item.vue +35 -0
  52. package/src/forms/bones/view/boneLogic.js +202 -0
  53. package/src/forms/bones/view/index.js +5 -0
  54. package/src/forms/bones/widgets/booleanBone.vue +83 -0
  55. package/src/forms/bones/widgets/booleanBoneChoose.vue +57 -0
  56. package/src/forms/bones/widgets/booleanBoneSelect.vue +58 -0
  57. package/src/forms/bones/widgets/codeBone.vue +214 -0
  58. package/src/forms/bones/widgets/colorBone.vue +75 -0
  59. package/src/forms/bones/widgets/dateBone.vue +114 -0
  60. package/src/forms/bones/widgets/emailBone.vue +100 -0
  61. package/src/forms/bones/widgets/fileBone.vue +273 -0
  62. package/src/forms/bones/widgets/jsonBone.vue +63 -0
  63. package/src/forms/bones/widgets/keyBone.vue +71 -0
  64. package/src/forms/bones/widgets/numericBone.vue +125 -0
  65. package/src/forms/bones/widgets/passwordBone.vue +215 -0
  66. package/src/forms/bones/widgets/phoneBone.vue +173 -0
  67. package/src/forms/bones/widgets/rawBone.vue +94 -0
  68. package/src/forms/bones/widgets/recordBone.vue +64 -0
  69. package/src/forms/bones/widgets/relationalBone.vue +164 -0
  70. package/src/forms/bones/widgets/relationalBoneSelect.vue +120 -0
  71. package/src/forms/bones/widgets/selectBone.vue +122 -0
  72. package/src/forms/bones/widgets/selectBoneChoose.vue +104 -0
  73. package/src/forms/bones/widgets/spatialBone.vue +111 -0
  74. package/src/forms/bones/widgets/stringBone.vue +139 -0
  75. package/src/forms/bones/widgets/textBone.vue +641 -0
  76. package/src/forms/install.d.ts +4 -0
  77. package/src/forms/install.js +43 -0
  78. package/src/forms/layouts/LayloutList2Columns.vue +57 -0
  79. package/src/forms/layouts/LayoutCategory.vue +38 -0
  80. package/src/forms/layouts/LayoutList.vue +31 -0
  81. package/src/forms/utils.js +601 -0
  82. package/src/public/assets/icons/fontawesome-manifest.json +8 -0
  83. package/src/public/assets/icons/regular/file.svg +1 -0
  84. package/src/public/assets/icons/solid/arrows-left-right.svg +1 -0
  85. package/src/public/assets/icons/solid/bug.svg +1 -0
  86. package/src/public/assets/icons/solid/check.svg +1 -0
  87. package/src/public/assets/icons/solid/circle-info.svg +1 -0
  88. package/src/public/assets/icons/solid/compress.svg +1 -0
  89. package/src/public/assets/icons/solid/download.svg +1 -0
  90. package/src/public/assets/icons/solid/expand.svg +1 -0
  91. package/src/public/assets/icons/solid/grip-vertical.svg +1 -0
  92. package/src/public/assets/icons/solid/plus.svg +1 -0
  93. package/src/public/assets/icons/solid/question.svg +1 -0
  94. package/src/public/assets/icons/solid/triangle-exclamation.svg +1 -0
  95. package/src/public/assets/icons/solid/upload.svg +1 -0
  96. package/src/public/assets/icons/solid/xmark.svg +1 -0
  97. package/src/request/cache.js +183 -0
  98. package/src/request/configStore.js +516 -0
  99. package/src/request/envelope.js +211 -0
  100. package/src/request/index.js +35 -0
  101. package/src/request/messageStore.js +201 -0
  102. package/src/request/request.js +752 -0
  103. package/src/request/skel.js +90 -0
  104. package/src/theme/colorStore.js +168 -0
  105. package/src/translations/de.js +178 -0
  106. package/src/translations/en.js +177 -0
  107. package/src/user/LoginScreen.d.vue.ts +54 -0
  108. package/src/user/LoginScreen.vue +627 -0
  109. package/src/user/google.js +75 -0
  110. package/src/user/index.js +3 -0
  111. package/src/user/store.d.ts +59 -0
  112. package/src/user/store.js +183 -0
  113. package/src/webawesomeCompat.css +72 -0
  114. package/src/webawesomeConfig.js +61 -0
@@ -0,0 +1,408 @@
1
+ <template>
2
+ <div
3
+ ref="rootEl"
4
+ class="vi-treenav"
5
+ role="tree"
6
+ :aria-label="label ?? t('browser.navigation')"
7
+ @keydown="onKeydown"
8
+ >
9
+ <template
10
+ v-for="entry in visibleEntries"
11
+ :key="entry.node.key"
12
+ >
13
+ <div
14
+ class="vi-treenav-row"
15
+ :class="{
16
+ 'is-current': entry.node.key === location,
17
+ 'is-drop': session?.state.over?.container === containerId && session?.state.over?.key === entry.node.key,
18
+ }"
19
+ role="treeitem"
20
+ :aria-level="entry.depth + 1"
21
+ :aria-expanded="canExpand(entry.node) ? expanded.has(entry.node.key) : undefined"
22
+ :aria-selected="entry.node.key === location"
23
+ :tabindex="entry.node.key === activeKey ? 0 : -1"
24
+ :style="{ paddingInlineStart: `calc(${entry.depth} * var(--wa-space-m, 1rem))` }"
25
+ :data-tree-key="entry.node.key"
26
+ @click="open(entry.node)"
27
+ @focus="activeKey = entry.node.key"
28
+ @pointerdown="onPointerDown(entry.node, $event)"
29
+ >
30
+ <span
31
+ class="vi-treenav-arrow"
32
+ :class="{ 'is-open': expanded.has(entry.node.key), 'is-none': !canExpand(entry.node) }"
33
+ @click.stop="toggle(entry.node)"
34
+ >
35
+ <wa-spinner v-if="loading.has(entry.node.key)"></wa-spinner>
36
+ <wa-icon
37
+ v-else
38
+ name="chevron-right"
39
+ ></wa-icon>
40
+ </span>
41
+ <wa-icon
42
+ class="vi-treenav-icon"
43
+ :name="entry.node.icon || 'folder'"
44
+ ></wa-icon>
45
+ <span class="vi-treenav-name">{{ entry.node.name }}</span>
46
+ </div>
47
+ </template>
48
+ </div>
49
+ </template>
50
+
51
+ <script setup>
52
+ import { useI18n } from "vue-i18n"
53
+ import { ref, computed, watch, inject, onMounted, onBeforeUnmount } from "vue"
54
+ import { DRAG_SESSION } from "./dragSession"
55
+
56
+ // Schlanker NAVIGATIONS-Baum: nur Knoten (Name, Icon, Pfeil), Kinder werden
57
+ // über `loadChildren` nachgeladen. Bewusst keine DataTable — Navigation ist
58
+ // kein Datengrid. Als Drop-Ziel registriert er sich an der Drag-Session des
59
+ // Browsers (immer "inside": in den getroffenen Ordner).
60
+
61
+ const props = defineProps({
62
+ /** Wurzeln (z. B. die Rootnodes) — { key, name, icon?, hasChildren? } */
63
+ roots: { type: Array, default: () => [] },
64
+ /** Kinder eines Knotens holen: (node) => Promise<BrowserNode[]> */
65
+ loadChildren: { type: Function, default: null },
66
+ label: { type: [String, null], default: null },
67
+ /** Container-Id in der Drag-Session */
68
+ containerId: { type: String, default: "tree" },
69
+ })
70
+ const location = defineModel({ type: [String, Number, null], default: null })
71
+ const emit = defineEmits(["navigate", "children-loaded"])
72
+
73
+ const { t } = useI18n()
74
+
75
+ const rootEl = ref(null)
76
+ const expanded = ref(new Set())
77
+ const loading = ref(new Set())
78
+ const children = ref(new Map()) // String(key) → BrowserNode[]
79
+ // Lokal eingefügte Knoten für noch nicht geladene Ebenen: werden beim ersten
80
+ // Laden der Ebene hineingemerged (der Server kann kurz hinterherhinken).
81
+ const pendingInserts = ref(new Map()) // String(key) → BrowserNode[]
82
+ const bucketOf = (key) => children.value.get(String(key))
83
+ const activeKey = ref(null)
84
+
85
+ const session = inject(DRAG_SESSION, null)
86
+
87
+ const visibleEntries = computed(() => {
88
+ const out = []
89
+ const walk = (nodes, depth) => {
90
+ for (const node of nodes ?? []) {
91
+ out.push({ node, depth })
92
+ if (expanded.value.has(node.key)) walk(bucketOf(node.key), depth + 1)
93
+ }
94
+ }
95
+ walk(props.roots, 0)
96
+ return out
97
+ })
98
+
99
+ watch(
100
+ () => props.roots,
101
+ (roots) => {
102
+ if (activeKey.value === null && roots?.length) activeKey.value = roots[0].key
103
+ void expandRoots()
104
+ },
105
+ { immediate: true }
106
+ )
107
+
108
+ /** Die Wurzeln (Rootnodes) sind immer ausgeklappt — ohne ersten Klick. */
109
+ async function expandRoots() {
110
+ for (const root of props.roots ?? []) {
111
+ if (expanded.value.has(root.key) || root.hasChildren === false) continue
112
+ await fetchChildren(root).catch(() => null)
113
+ if ((bucketOf(root.key) ?? []).length) {
114
+ expanded.value = new Set(expanded.value).add(root.key)
115
+ }
116
+ }
117
+ }
118
+
119
+ function canExpand(node) {
120
+ if (node.hasChildren === false) return false
121
+ const known = bucketOf(node.key)
122
+ return known ? known.length > 0 : true
123
+ }
124
+
125
+ async function fetchChildren(node, force = false) {
126
+ if (!props.loadChildren || loading.value.has(node.key)) return
127
+ if (!force && children.value.has(String(node.key))) return
128
+ loading.value = new Set(loading.value).add(node.key)
129
+ try {
130
+ const result = (await props.loadChildren(node)) ?? []
131
+ // Lokal (per Drag & Drop) hierher verschobene Knoten hineinmergen — der
132
+ // Server kann den Zug noch nicht kennen. Was er schon liefert, ist damit
133
+ // angekommen und braucht keinen Merker mehr.
134
+ const bucketKey = String(node.key)
135
+ const pending = pendingInserts.value.get(bucketKey) ?? []
136
+ const merged = [...result]
137
+ for (const entry of pending) {
138
+ if (!merged.some((candidate) => String(candidate.key) === String(entry.key))) merged.push(entry)
139
+ }
140
+ const remaining = pending.filter(
141
+ (entry) => !result.some((candidate) => String(candidate.key) === String(entry.key))
142
+ )
143
+ if (remaining.length !== pending.length || !remaining.length) {
144
+ const nextPending = new Map(pendingInserts.value)
145
+ if (remaining.length) nextPending.set(bucketKey, remaining)
146
+ else nextPending.delete(bucketKey)
147
+ pendingInserts.value = nextPending
148
+ }
149
+ const next = new Map(children.value)
150
+ next.set(bucketKey, merged)
151
+ children.value = next
152
+ emit("children-loaded", { node, children: merged })
153
+ } finally {
154
+ const next = new Set(loading.value)
155
+ next.delete(node.key)
156
+ loading.value = next
157
+ }
158
+ }
159
+
160
+ async function toggle(node) {
161
+ if (!canExpand(node)) return
162
+ const next = new Set(expanded.value)
163
+ if (next.has(node.key)) {
164
+ next.delete(node.key)
165
+ expanded.value = next
166
+ return
167
+ }
168
+ try {
169
+ await fetchChildren(node)
170
+ } catch {
171
+ return // Ebene nicht ladbar → zubleiben
172
+ }
173
+ if ((bucketOf(node.key) ?? []).length) {
174
+ next.add(node.key)
175
+ expanded.value = next
176
+ }
177
+ }
178
+
179
+ function open(node) {
180
+ activeKey.value = node.key
181
+ location.value = node.key
182
+ emit("navigate", node)
183
+ void toggle(node) // aufklappen gehört zum Navigieren dazu; zuklappen macht nur der Pfeil
184
+ }
185
+
186
+ /**
187
+ * Knoten LOKAL aus seiner bisherigen Ebene nehmen und — wenn die Ziel-Ebene
188
+ * schon geladen ist — dort einhängen. Kein Request: eine noch nicht geladene
189
+ * Ziel-Ebene holt ihn beim ersten Aufklappen ohnehin frisch vom Server.
190
+ * `node` darf ein voller BrowserNode sein (für Knoten, die der Baum selbst
191
+ * noch nicht kennt, z. B. Ordnerzeilen aus der Liste) oder nur ein Schlüssel.
192
+ */
193
+ function moveNode(node, targetKey) {
194
+ const key = typeof node === "object" && node !== null ? node.key : node
195
+ const removed = removeNode(key)
196
+ const entry = removed ?? (typeof node === "object" && node !== null ? node : null)
197
+ if (entry == null || targetKey == null) return
198
+ const bucketKey = String(targetKey)
199
+ if (children.value.has(bucketKey)) {
200
+ const next = new Map(children.value)
201
+ next.set(bucketKey, [...(next.get(bucketKey) ?? []), entry])
202
+ children.value = next
203
+ } else {
204
+ // Ziel-Ebene noch nie geladen: vormerken — beim ersten Laden wird der
205
+ // Knoten hineingemerged (siehe fetchChildren). Kein Request nötig.
206
+ const next = new Map(pendingInserts.value)
207
+ next.set(bucketKey, [...(next.get(bucketKey) ?? []), entry])
208
+ pendingInserts.value = next
209
+ }
210
+ }
211
+
212
+ /** Knoten lokal aus allen geladenen Ebenen entfernen; liefert ihn zurück. */
213
+ function removeNode(key) {
214
+ let removed = null
215
+ const next = new Map(children.value)
216
+ for (const [parent, list] of next) {
217
+ const index = list.findIndex((candidate) => String(candidate.key) === String(key))
218
+ if (index === -1) continue
219
+ removed = list[index]
220
+ next.set(parent, [...list.slice(0, index), ...list.slice(index + 1)])
221
+ }
222
+ if (removed) children.value = next
223
+ for (const [parent, list] of pendingInserts.value) {
224
+ const index = list.findIndex((candidate) => String(candidate.key) === String(key))
225
+ if (index === -1) continue
226
+ removed = removed ?? list[index]
227
+ const nextPending = new Map(pendingInserts.value)
228
+ const shortened = [...list.slice(0, index), ...list.slice(index + 1)]
229
+ if (shortened.length) nextPending.set(parent, shortened)
230
+ else nextPending.delete(parent)
231
+ pendingInserts.value = nextPending
232
+ }
233
+ return removed
234
+ }
235
+
236
+ /** Ebene eines Knotens verwerfen und neu holen (z. B. nach einem Move). */
237
+ function reloadChildren(key) {
238
+ const entry = visibleEntries.value.find((candidate) => String(candidate.node.key) === String(key))
239
+ if (!entry) return Promise.resolve()
240
+ return fetchChildren(entry.node, true).catch(() => null)
241
+ }
242
+
243
+ function reset() {
244
+ expanded.value = new Set()
245
+ children.value = new Map()
246
+ pendingInserts.value = new Map()
247
+ void expandRoots()
248
+ }
249
+
250
+ // ── Tastatur (WAI-ARIA tree) ──────────────────────────────────────────────────
251
+ function onKeydown(event) {
252
+ const entries = visibleEntries.value
253
+ const index = entries.findIndex((entry) => entry.node.key === activeKey.value)
254
+ if (index === -1) return
255
+ const focusAt = (i) => {
256
+ const entry = entries[Math.max(0, Math.min(entries.length - 1, i))]
257
+ activeKey.value = entry.node.key
258
+ rootEl.value?.querySelector(`[data-tree-key="${CSS.escape(String(entry.node.key))}"]`)?.focus()
259
+ }
260
+ const entry = entries[index]
261
+ switch (event.key) {
262
+ case "ArrowDown":
263
+ focusAt(index + 1)
264
+ break
265
+ case "ArrowUp":
266
+ focusAt(index - 1)
267
+ break
268
+ case "ArrowRight":
269
+ if (!expanded.value.has(entry.node.key)) void toggle(entry.node)
270
+ else focusAt(index + 1)
271
+ break
272
+ case "ArrowLeft":
273
+ if (expanded.value.has(entry.node.key)) void toggle(entry.node)
274
+ else {
275
+ const parentIndex = entries.slice(0, index).findLastIndex((candidate) => candidate.depth === entry.depth - 1)
276
+ if (parentIndex !== -1) focusAt(parentIndex)
277
+ }
278
+ break
279
+ case "Home":
280
+ focusAt(0)
281
+ break
282
+ case "End":
283
+ focusAt(entries.length - 1)
284
+ break
285
+ case "Enter":
286
+ case " ":
287
+ open(entry.node)
288
+ break
289
+ default:
290
+ return
291
+ }
292
+ event.preventDefault()
293
+ }
294
+
295
+ // ── Drag-Quelle: Knoten in die Liste (oder andere Knoten) ziehen ─────────────
296
+ // Erst ab einer kleinen Bewegungsschwelle wird aus dem Klick ein Zug — Klicks
297
+ // zum Navigieren bleiben Klicks.
298
+ function onPointerDown(node, event) {
299
+ if (!session || event.button !== 0) return
300
+ // Wurzeln (Rootnodes) sind nicht verschiebbar.
301
+ if (props.roots.some((root) => String(root.key) === String(node.key))) return
302
+ const startX = event.clientX
303
+ const startY = event.clientY
304
+ let dragging = false
305
+ const move = (moveEvent) => {
306
+ if (!dragging && Math.hypot(moveEvent.clientX - startX, moveEvent.clientY - startY) < 5) return
307
+ if (!dragging) {
308
+ dragging = true
309
+ session.start({ keys: [node.key], rows: [node.data ?? node], source: props.containerId })
310
+ }
311
+ session.update(moveEvent.clientX, moveEvent.clientY)
312
+ }
313
+ const up = (upEvent) => {
314
+ window.removeEventListener("pointermove", move)
315
+ window.removeEventListener("pointerup", up)
316
+ if (!dragging) return
317
+ session.update(upEvent.clientX, upEvent.clientY)
318
+ if (session.state.over) session.drop()
319
+ else session.cancel()
320
+ }
321
+ window.addEventListener("pointermove", move)
322
+ window.addEventListener("pointerup", up)
323
+ }
324
+
325
+ // ── Drop-Ziel der Drag-Session ────────────────────────────────────────────────
326
+ let unregister = null
327
+ let hoverTimer = null
328
+ let hoverKey = null
329
+
330
+ onMounted(() => {
331
+ if (!session) return
332
+ unregister = session.register({
333
+ id: props.containerId,
334
+ el: () => rootEl.value,
335
+ targetFor: (hit) => {
336
+ const row = hit.closest?.("[data-tree-key]")
337
+ if (!row) return null
338
+ const key = row.dataset.treeKey
339
+ // Über einem zugeklappten Ordner verweilen klappt ihn auf.
340
+ if (hoverKey !== key) {
341
+ hoverKey = key
342
+ clearTimeout(hoverTimer)
343
+ hoverTimer = setTimeout(() => {
344
+ const entry = visibleEntries.value.find((candidate) => String(candidate.node.key) === key)
345
+ if (entry && !expanded.value.has(entry.node.key)) void toggle(entry.node)
346
+ }, 700)
347
+ }
348
+ return { container: props.containerId, key, mode: "inside" }
349
+ },
350
+ })
351
+ })
352
+ onBeforeUnmount(() => {
353
+ unregister?.()
354
+ clearTimeout(hoverTimer)
355
+ })
356
+
357
+ defineExpose({ reloadChildren, reset, moveNode, removeNode, expanded, children })
358
+ </script>
359
+
360
+ <style scoped>
361
+ .vi-treenav {
362
+ min-inline-size: 0;
363
+ overflow: auto;
364
+ padding-block: var(--wa-space-2xs, 0.25rem);
365
+ }
366
+ .vi-treenav-row {
367
+ display: flex;
368
+ align-items: center;
369
+ gap: var(--wa-space-2xs, 0.25rem);
370
+ padding-block: var(--wa-space-2xs, 0.25rem);
371
+ padding-inline-end: var(--wa-space-s, 0.5rem);
372
+ border-radius: var(--wa-border-radius-s, 0.25rem);
373
+ cursor: pointer;
374
+ user-select: none;
375
+ white-space: nowrap;
376
+ }
377
+ .vi-treenav-row:hover {
378
+ background: var(--wa-color-neutral-fill-quiet, rgba(0 0 0 / 0.05));
379
+ }
380
+ .vi-treenav-row.is-current {
381
+ background: var(--wa-color-brand-fill-quiet, rgba(0 80 255 / 0.1));
382
+ font-weight: 600;
383
+ }
384
+ .vi-treenav-row.is-drop {
385
+ outline: 2px dashed var(--wa-color-brand-50, #2563eb);
386
+ outline-offset: -2px;
387
+ }
388
+ .vi-treenav-arrow {
389
+ display: inline-flex;
390
+ inline-size: 1.25em;
391
+ justify-content: center;
392
+ transition: rotate 0.15s ease;
393
+ flex: none;
394
+ }
395
+ .vi-treenav-arrow.is-open {
396
+ rotate: 90deg;
397
+ }
398
+ .vi-treenav-arrow.is-none {
399
+ visibility: hidden;
400
+ }
401
+ .vi-treenav-icon {
402
+ flex: none;
403
+ }
404
+ .vi-treenav-name {
405
+ overflow: hidden;
406
+ text-overflow: ellipsis;
407
+ }
408
+ </style>
@@ -0,0 +1,64 @@
1
+ import type { DefineComponent } from "vue"
2
+ import type { RowKey } from "../DataTable/types"
3
+ import type { BrowserMovePayload, BrowserUploadPayload } from "./types"
4
+
5
+ /** Props von ViurBrowser.vue (siehe defineProps dort). */
6
+ export interface ViurBrowserProps {
7
+ /** Modulname bzw. Punkt-Pfad ("file", "shop.category"). */
8
+ module: string
9
+ /** Renderer der Requests; Standard: VITE_DEFAULT_RENDERER. */
10
+ renderer?: string | null
11
+ /** Fester Wurzelknoten; ohne ihn wird `/listRootNodes` befragt. */
12
+ rootNode?: string | Record<string, unknown> | null
13
+ /** Spalten; ohne Angabe aus der Struktur der Blätter abgeleitet. */
14
+ columns?: unknown[] | null
15
+ /** Nur diese Bones als Spalten (bei abgeleiteten Spalten). */
16
+ bones?: string[] | null
17
+ /** Zusätzliche Parameter für die Listen-Requests. */
18
+ params?: Record<string, unknown>
19
+ /** Feld der festen Reihenfolge (Standard "sortindex"). */
20
+ orderField?: string
21
+ /** Dateien in die Ebene hochladen erlauben (Datei-Module). */
22
+ uploads?: boolean
23
+ /** Wird an die DataTable durchgereicht. */
24
+ tableProps?: Record<string, unknown>
25
+ }
26
+
27
+ /** Ereignisse als `on*`-Props, damit `@open` & Co. im Template typen. */
28
+ export interface ViurBrowserEmitProps<R = Record<string, unknown>> {
29
+ /** Doppelklick auf ein BLATT (Ordner navigieren stattdessen hinein). */
30
+ onOpen?: (payload: { row: R; key: RowKey }) => void
31
+ /** Verschieben gespeichert. */
32
+ onMoved?: (payload: { keys: RowKey[]; target: RowKey | null; parentChanged?: boolean }) => void
33
+ onMoveFailed?: (payload: { keys: RowKey[]; target: RowKey | null; error: unknown }) => void
34
+ /** Uploads fertig (nur mit `uploads`). */
35
+ onUploaded?: (payload: { files: unknown[]; target: RowKey | null }) => void
36
+ onUploadFailed?: (payload: { file: File; error: unknown }) => void
37
+ /** v-model:location — aktueller Ort (Schlüssel des Knotens, null = Wurzel). */
38
+ onUpdateLocation?: (value: RowKey | null) => void
39
+ /** v-model:selectedKeys */
40
+ onUpdateSelectedKeys?: (value: RowKey[]) => void
41
+ }
42
+
43
+ /** Was ViurBrowser per defineExpose freigibt. */
44
+ export interface ViurBrowserApi<R = Record<string, unknown>> {
45
+ /** Der generische Browser darunter. */
46
+ browser: unknown
47
+ /** Inhalt des aktuellen Ortes (Knoten UND Blätter, je mit `skelType`). */
48
+ tree: {
49
+ data: { value: R[] }
50
+ structure: { value: { node?: Record<string, unknown>; leaf?: Record<string, unknown> } }
51
+ isFetching: { value: boolean }
52
+ [key: string]: unknown
53
+ }
54
+ /** Ort neu vom Server holen. */
55
+ refresh: () => void
56
+ }
57
+
58
+ declare const ViurBrowser: DefineComponent<
59
+ ViurBrowserProps & ViurBrowserEmitProps & { location?: RowKey | null; selectedKeys?: RowKey[] },
60
+ ViurBrowserApi,
61
+ any
62
+ >
63
+ export default ViurBrowser
64
+ export type { BrowserMovePayload, BrowserUploadPayload }