viur-ui 0.0.1 → 0.0.2

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/README.md +67 -41
  2. package/index.d.ts +18 -9
  3. package/index.js +6 -3
  4. package/package.json +3 -5
  5. package/src/components/Browser/Browser.vue +58 -58
  6. package/src/components/Browser/TreeNav.vue +32 -32
  7. package/src/components/Browser/ViurBrowser.d.vue.ts +21 -21
  8. package/src/components/Browser/ViurBrowser.vue +47 -44
  9. package/src/components/Browser/dragSession.ts +27 -27
  10. package/src/components/Browser/index.ts +3 -3
  11. package/src/components/Browser/types.ts +9 -9
  12. package/src/components/DataTable/ColumnFilterPanel.vue +7 -7
  13. package/src/components/DataTable/DataTable.vue +346 -346
  14. package/src/components/DataTable/ViurDataTable.d.vue.ts +26 -26
  15. package/src/components/DataTable/ViurDataTable.vue +51 -50
  16. package/src/components/DataTable/columns.ts +30 -30
  17. package/src/components/DataTable/index.ts +12 -12
  18. package/src/components/DataTable/rows.ts +71 -72
  19. package/src/components/DataTable/types.ts +55 -55
  20. package/src/components/DataTable/useColumnLayout.ts +28 -28
  21. package/src/components/DataTable/useGridNavigation.ts +15 -15
  22. package/src/components/DataTable/useVirtualRows.ts +10 -10
  23. package/src/components/DataTable/viurColumns.ts +69 -69
  24. package/src/forms/ViForm.d.vue.ts +32 -32
  25. package/src/forms/ViForm.vue +17 -16
  26. package/src/forms/ViFormCategory.vue +3 -1
  27. package/src/forms/bones/Bone.vue +125 -108
  28. package/src/forms/bones/BoneActions.vue +21 -21
  29. package/src/forms/bones/BoneLabel.vue +1 -1
  30. package/src/forms/bones/BoneMultipleWrapper.vue +3 -3
  31. package/src/forms/bones/BoneNestedWrapper.vue +3 -3
  32. package/src/forms/bones/BoneSingleEntry.vue +2 -2
  33. package/src/forms/bones/FilterBone.vue +3 -3
  34. package/src/forms/bones/actions/ActionDialog.vue +2 -2
  35. package/src/forms/bones/actions/Debug.vue +6 -6
  36. package/src/forms/bones/actions/Delete.vue +2 -2
  37. package/src/forms/bones/actions/DescribeImage.vue +4 -3
  38. package/src/forms/bones/actions/Download.vue +4 -4
  39. package/src/forms/bones/actions/Placeholder.vue +2 -2
  40. package/src/forms/bones/actions/index.js +72 -74
  41. package/src/forms/bones/bone.d.ts +19 -19
  42. package/src/forms/bones/uploadAdapter.js +3 -3
  43. package/src/forms/bones/utils.js +40 -41
  44. package/src/forms/bones/view/base_item.vue +1 -1
  45. package/src/forms/bones/view/boneLogic.js +1 -1
  46. package/src/forms/bones/widgets/booleanBone.vue +19 -19
  47. package/src/forms/bones/widgets/booleanBoneChoose.vue +2 -2
  48. package/src/forms/bones/widgets/booleanBoneSelect.vue +3 -3
  49. package/src/forms/bones/widgets/codeBone.vue +22 -22
  50. package/src/forms/bones/widgets/colorBone.vue +18 -18
  51. package/src/forms/bones/widgets/dateBone.vue +22 -22
  52. package/src/forms/bones/widgets/emailBone.vue +20 -20
  53. package/src/forms/bones/widgets/fileBone.vue +32 -31
  54. package/src/forms/bones/widgets/keyBone.vue +21 -21
  55. package/src/forms/bones/widgets/numericBone.vue +21 -21
  56. package/src/forms/bones/widgets/passwordBone.vue +34 -31
  57. package/src/forms/bones/widgets/phoneBone.vue +20 -20
  58. package/src/forms/bones/widgets/rawBone.vue +19 -19
  59. package/src/forms/bones/widgets/recordBone.vue +16 -16
  60. package/src/forms/bones/widgets/relationalBone.vue +26 -26
  61. package/src/forms/bones/widgets/selectBone.vue +23 -23
  62. package/src/forms/bones/widgets/selectBoneChoose.vue +2 -2
  63. package/src/forms/bones/widgets/spatialBone.vue +21 -21
  64. package/src/forms/bones/widgets/stringBone.vue +27 -27
  65. package/src/forms/bones/widgets/textBone.vue +28 -26
  66. package/src/forms/install.d.ts +1 -1
  67. package/src/forms/install.js +7 -0
  68. package/src/forms/layouts/LayloutList2Columns.vue +1 -1
  69. package/src/forms/layouts/LayoutCategory.vue +1 -1
  70. package/src/forms/layouts/LayoutList.vue +1 -1
  71. package/src/forms/utils.js +22 -22
  72. package/src/request/configStore.js +4 -4
  73. package/src/request/envelope.js +6 -6
  74. package/src/request/request.js +22 -22
  75. package/src/translations/de.js +31 -0
  76. package/src/translations/en.js +31 -0
  77. package/src/user/LoginScreen.d.vue.ts +24 -24
  78. package/src/user/LoginScreen.vue +6 -7
  79. package/src/user/store.d.ts +16 -16
  80. package/src/vite/safeHtml.js +73 -0
  81. package/src/vite/sanitizeVHtml.d.ts +4 -0
  82. package/src/vite/sanitizeVHtml.js +15 -0
  83. package/src/webawesomeCompat.css +0 -40
@@ -51,20 +51,20 @@
51
51
  <script setup>
52
52
  import { useI18n } from "vue-i18n"
53
53
  import { ref, computed, watch, inject, onMounted, onBeforeUnmount } from "vue"
54
- import { DRAG_SESSION } from "./dragSession"
54
+ import { DRAG_SESSION } from "./dragSession.ts"
55
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).
56
+ // Lean NAVIGATION tree: only nodes (name, icon, arrow), children are loaded
57
+ // via `loadChildren`. Deliberately no DataTable — navigation is not a data
58
+ // grid. As a drop target it registers with the browser's drag session
59
+ // (always "inside": into the folder that was hit).
60
60
 
61
61
  const props = defineProps({
62
- /** Wurzeln (z. B. die Rootnodes) — { key, name, icon?, hasChildren? } */
62
+ /** Roots (e.g. the rootnodes) — { key, name, icon?, hasChildren? } */
63
63
  roots: { type: Array, default: () => [] },
64
- /** Kinder eines Knotens holen: (node) => Promise<BrowserNode[]> */
64
+ /** Fetch the children of a node: (node) => Promise<BrowserNode[]> */
65
65
  loadChildren: { type: Function, default: null },
66
66
  label: { type: [String, null], default: null },
67
- /** Container-Id in der Drag-Session */
67
+ /** Container id in the drag session */
68
68
  containerId: { type: String, default: "tree" },
69
69
  })
70
70
  const location = defineModel({ type: [String, Number, null], default: null })
@@ -76,8 +76,8 @@ const rootEl = ref(null)
76
76
  const expanded = ref(new Set())
77
77
  const loading = ref(new Set())
78
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).
79
+ // Locally inserted nodes for levels not loaded yet: merged in when the level
80
+ // is first loaded (the server may lag behind briefly).
81
81
  const pendingInserts = ref(new Map()) // String(key) → BrowserNode[]
82
82
  const bucketOf = (key) => children.value.get(String(key))
83
83
  const activeKey = ref(null)
@@ -105,7 +105,7 @@ watch(
105
105
  { immediate: true }
106
106
  )
107
107
 
108
- /** Die Wurzeln (Rootnodes) sind immer ausgeklappt — ohne ersten Klick. */
108
+ /** The roots (rootnodes) are always expanded — without a first click. */
109
109
  async function expandRoots() {
110
110
  for (const root of props.roots ?? []) {
111
111
  if (expanded.value.has(root.key) || root.hasChildren === false) continue
@@ -128,9 +128,9 @@ async function fetchChildren(node, force = false) {
128
128
  loading.value = new Set(loading.value).add(node.key)
129
129
  try {
130
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.
131
+ // Merge in nodes moved here locally (via drag & drop) — the server may
132
+ // not know about the move yet. Whatever it already returns has thus
133
+ // arrived and no longer needs a marker.
134
134
  const bucketKey = String(node.key)
135
135
  const pending = pendingInserts.value.get(bucketKey) ?? []
136
136
  const merged = [...result]
@@ -168,7 +168,7 @@ async function toggle(node) {
168
168
  try {
169
169
  await fetchChildren(node)
170
170
  } catch {
171
- return // Ebene nicht ladbar → zubleiben
171
+ return // level can't be loaded → stay collapsed
172
172
  }
173
173
  if ((bucketOf(node.key) ?? []).length) {
174
174
  next.add(node.key)
@@ -180,15 +180,15 @@ function open(node) {
180
180
  activeKey.value = node.key
181
181
  location.value = node.key
182
182
  emit("navigate", node)
183
- void toggle(node) // aufklappen gehört zum Navigieren dazu; zuklappen macht nur der Pfeil
183
+ void toggle(node) // expanding is part of navigating; only the arrow collapses
184
184
  }
185
185
 
186
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.
187
+ * Take a node LOCALLY out of its previous level and — if the target level is
188
+ * already loaded — attach it there. No request: a target level that isn't
189
+ * loaded yet fetches it fresh from the server on first expand anyway.
190
+ * `node` may be a full BrowserNode (for nodes the tree itself doesn't know
191
+ * yet, e.g. folder rows from the list) or just a key.
192
192
  */
193
193
  function moveNode(node, targetKey) {
194
194
  const key = typeof node === "object" && node !== null ? node.key : node
@@ -201,15 +201,15 @@ function moveNode(node, targetKey) {
201
201
  next.set(bucketKey, [...(next.get(bucketKey) ?? []), entry])
202
202
  children.value = next
203
203
  } else {
204
- // Ziel-Ebene noch nie geladen: vormerken — beim ersten Laden wird der
205
- // Knoten hineingemerged (siehe fetchChildren). Kein Request nötig.
204
+ // Target level never loaded: queue it — on first load the node is
205
+ // merged in (see fetchChildren). No request needed.
206
206
  const next = new Map(pendingInserts.value)
207
207
  next.set(bucketKey, [...(next.get(bucketKey) ?? []), entry])
208
208
  pendingInserts.value = next
209
209
  }
210
210
  }
211
211
 
212
- /** Knoten lokal aus allen geladenen Ebenen entfernen; liefert ihn zurück. */
212
+ /** Remove a node locally from all loaded levels; returns it. */
213
213
  function removeNode(key) {
214
214
  let removed = null
215
215
  const next = new Map(children.value)
@@ -233,7 +233,7 @@ function removeNode(key) {
233
233
  return removed
234
234
  }
235
235
 
236
- /** Ebene eines Knotens verwerfen und neu holen (z. B. nach einem Move). */
236
+ /** Discard a node's level and re-fetch it (e.g. after a move). */
237
237
  function reloadChildren(key) {
238
238
  const entry = visibleEntries.value.find((candidate) => String(candidate.node.key) === String(key))
239
239
  if (!entry) return Promise.resolve()
@@ -247,7 +247,7 @@ function reset() {
247
247
  void expandRoots()
248
248
  }
249
249
 
250
- // ── Tastatur (WAI-ARIA tree) ──────────────────────────────────────────────────
250
+ // ── Keyboard (WAI-ARIA tree) ──────────────────────────────────────────────────
251
251
  function onKeydown(event) {
252
252
  const entries = visibleEntries.value
253
253
  const index = entries.findIndex((entry) => entry.node.key === activeKey.value)
@@ -292,12 +292,12 @@ function onKeydown(event) {
292
292
  event.preventDefault()
293
293
  }
294
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.
295
+ // ── Drag source: drag nodes into the list (or onto other nodes) ───────────────
296
+ // Only past a small movement threshold does a click become a drag — clicks
297
+ // for navigating stay clicks.
298
298
  function onPointerDown(node, event) {
299
299
  if (!session || event.button !== 0) return
300
- // Wurzeln (Rootnodes) sind nicht verschiebbar.
300
+ // Roots (rootnodes) can't be moved.
301
301
  if (props.roots.some((root) => String(root.key) === String(node.key))) return
302
302
  const startX = event.clientX
303
303
  const startY = event.clientY
@@ -322,7 +322,7 @@ function onPointerDown(node, event) {
322
322
  window.addEventListener("pointerup", up)
323
323
  }
324
324
 
325
- // ── Drop-Ziel der Drag-Session ────────────────────────────────────────────────
325
+ // ── Drop target of the drag session ───────────────────────────────────────────
326
326
  let unregister = null
327
327
  let hoverTimer = null
328
328
  let hoverKey = null
@@ -336,7 +336,7 @@ onMounted(() => {
336
336
  const row = hit.closest?.("[data-tree-key]")
337
337
  if (!row) return null
338
338
  const key = row.dataset.treeKey
339
- // Über einem zugeklappten Ordner verweilen klappt ihn auf.
339
+ // Hovering over a collapsed folder expands it.
340
340
  if (hoverKey !== key) {
341
341
  hoverKey = key
342
342
  clearTimeout(hoverTimer)
@@ -1,57 +1,57 @@
1
1
  import type { DefineComponent } from "vue"
2
- import type { RowKey } from "../DataTable/types"
3
- import type { BrowserMovePayload, BrowserUploadPayload } from "./types"
2
+ import type { RowKey } from "../DataTable/types.ts"
3
+ import type { BrowserMovePayload, BrowserUploadPayload } from "./types.ts"
4
4
 
5
- /** Props von ViurBrowser.vue (siehe defineProps dort). */
5
+ /** Props of ViurBrowser.vue (see defineProps there). */
6
6
  export interface ViurBrowserProps {
7
- /** Modulname bzw. Punkt-Pfad ("file", "shop.category"). */
7
+ /** Module name or dotted path ("file", "shop.category"). */
8
8
  module: string
9
- /** Renderer der Requests; Standard: VITE_DEFAULT_RENDERER. */
9
+ /** Renderer for the requests; default: VITE_DEFAULT_RENDERER. */
10
10
  renderer?: string | null
11
- /** Fester Wurzelknoten; ohne ihn wird `/listRootNodes` befragt. */
11
+ /** Fixed root node; without it, `/listRootNodes` is queried. */
12
12
  rootNode?: string | Record<string, unknown> | null
13
- /** Spalten; ohne Angabe aus der Struktur der Blätter abgeleitet. */
13
+ /** Columns; if omitted, derived from the leaf structure. */
14
14
  columns?: unknown[] | null
15
- /** Nur diese Bones als Spalten (bei abgeleiteten Spalten). */
15
+ /** Only these bones as columns (for derived columns). */
16
16
  bones?: string[] | null
17
- /** Zusätzliche Parameter für die Listen-Requests. */
17
+ /** Additional parameters for the list requests. */
18
18
  params?: Record<string, unknown>
19
- /** Feld der festen Reihenfolge (Standard "sortindex"). */
19
+ /** Field holding the fixed order (default "sortindex"). */
20
20
  orderField?: string
21
- /** Dateien in die Ebene hochladen erlauben (Datei-Module). */
21
+ /** Allow uploading files into the level (file modules). */
22
22
  uploads?: boolean
23
- /** Wird an die DataTable durchgereicht. */
23
+ /** Passed through to the DataTable. */
24
24
  tableProps?: Record<string, unknown>
25
25
  }
26
26
 
27
- /** Ereignisse als `on*`-Props, damit `@open` & Co. im Template typen. */
27
+ /** Events as `on*` props so that `@open` & co. are typed in the template. */
28
28
  export interface ViurBrowserEmitProps<R = Record<string, unknown>> {
29
- /** Doppelklick auf ein BLATT (Ordner navigieren stattdessen hinein). */
29
+ /** Double-click on a LEAF (folders navigate into themselves instead). */
30
30
  onOpen?: (payload: { row: R; key: RowKey }) => void
31
- /** Verschieben gespeichert. */
31
+ /** Move saved. */
32
32
  onMoved?: (payload: { keys: RowKey[]; target: RowKey | null; parentChanged?: boolean }) => void
33
33
  onMoveFailed?: (payload: { keys: RowKey[]; target: RowKey | null; error: unknown }) => void
34
- /** Uploads fertig (nur mit `uploads`). */
34
+ /** Uploads finished (only with `uploads`). */
35
35
  onUploaded?: (payload: { files: unknown[]; target: RowKey | null }) => void
36
36
  onUploadFailed?: (payload: { file: File; error: unknown }) => void
37
- /** v-model:location — aktueller Ort (Schlüssel des Knotens, null = Wurzel). */
37
+ /** v-model:location — current location (key of the node, null = root). */
38
38
  onUpdateLocation?: (value: RowKey | null) => void
39
39
  /** v-model:selectedKeys */
40
40
  onUpdateSelectedKeys?: (value: RowKey[]) => void
41
41
  }
42
42
 
43
- /** Was ViurBrowser per defineExpose freigibt. */
43
+ /** What ViurBrowser exposes via defineExpose. */
44
44
  export interface ViurBrowserApi<R = Record<string, unknown>> {
45
- /** Der generische Browser darunter. */
45
+ /** The generic browser underneath. */
46
46
  browser: unknown
47
- /** Inhalt des aktuellen Ortes (Knoten UND Blätter, je mit `skelType`). */
47
+ /** Contents of the current location (nodes AND leaves, each with `skelType`). */
48
48
  tree: {
49
49
  data: { value: R[] }
50
50
  structure: { value: { node?: Record<string, unknown>; leaf?: Record<string, unknown> } }
51
51
  isFetching: { value: boolean }
52
52
  [key: string]: unknown
53
53
  }
54
- /** Ort neu vom Server holen. */
54
+ /** Re-fetch the location from the server. */
55
55
  refresh: () => void
56
56
  }
57
57
 
@@ -37,28 +37,29 @@
37
37
 
38
38
  <script setup>
39
39
  import { ref, computed, watch } from "vue"
40
+ import { useI18n } from "vue-i18n"
40
41
  import Browser from "./Browser.vue"
41
42
  import { useTreeFetch, useListFetch, useListRootNodesFetch, useMoveFetch, uploadFile } from "../../request/index.js"
42
- import { columnsFromStructure } from "../DataTable/viurColumns"
43
+ import { columnsFromStructure } from "../DataTable/viurColumns.ts"
43
44
 
44
- // Dünner ViUR-Wrapper um den generischen Browser: `module` rein, fertig.
45
- // Rootnodes über /listRootNodes, Inhalt des Ortes über useTreeFetch (Nodes +
46
- // Leaves), Baum-Ebenen über list(skelType=node), Verschieben über /move,
47
- // Uploads über uploadFile. Gespeichert wird pro Zeile; die Ansicht verschiebt
48
- // der Browser lokal — nachgeladen wird nur der Ort selbst nach Uploads.
45
+ // Thin ViUR wrapper around the generic browser: pass in `module`, done.
46
+ // Rootnodes via /listRootNodes, contents of the location via useTreeFetch
47
+ // (nodes + leaves), tree levels via list(skelType=node), moving via /move,
48
+ // uploads via uploadFile. Saving happens per row; the browser moves the view
49
+ // locally — only the location itself is reloaded, after uploads.
49
50
 
50
51
  const props = defineProps({
51
52
  module: { type: String, required: true },
52
53
  renderer: { type: String, default: null },
53
- /** Fester Rootnode; ohne ihn wird /listRootNodes befragt. */
54
+ /** Fixed rootnode; without it, /listRootNodes is queried. */
54
55
  rootNode: { type: [String, Object], default: null },
55
- /** Spalten; ohne Angabe aus der viewLeafSkel-Struktur abgeleitet. */
56
+ /** Columns; if omitted, derived from the viewLeafSkel structure. */
56
57
  columns: { type: Array, default: null },
57
- /** Nur diese Bones als Spalten (bei abgeleiteten Spalten). */
58
+ /** Only these bones as columns (for derived columns). */
58
59
  bones: { type: Array, default: null },
59
60
  /**
60
- * Anzeigesprache mehrsprachiger Bones ("de", "en" oder "*" für alle mit der
61
- * primären vorn) — bestimmt Anzeige UND lokale Sortierung der Spalten.
61
+ * Display language of multilingual bones ("de", "en" or "*" for all, with the
62
+ * primary one first) — determines display AND local sorting of the columns.
62
63
  */
63
64
  language: { type: String, default: null },
64
65
  primaryLanguage: { type: String, default: null },
@@ -68,6 +69,7 @@ const props = defineProps({
68
69
  tableProps: { type: Object, default: () => ({}) },
69
70
  })
70
71
  const emit = defineEmits(["open", "moved", "move-failed", "uploaded", "upload-failed"])
72
+ const { t } = useI18n()
71
73
 
72
74
  const browserEl = ref(null)
73
75
  const location = defineModel("location", { type: [String, Number, null], default: null })
@@ -75,24 +77,24 @@ const selectedKeys = defineModel("selectedKeys", { type: Array, default: () => [
75
77
 
76
78
  const ctx = computed(() => (props.renderer ? { renderer: props.renderer } : {}))
77
79
 
78
- // Seitengröße der Cursor-Leiste = ViUR-Parameter `limit` (core ≥ 3.9; das
79
- // frühere `amount` gibt es dort nicht mehr). Sie gilt vom ERSTEN Fetch an:
80
- // eine per tableProps vorgegebene pageSize (oder ein `limit` in den params)
81
- // bestimmt schon den Initial-Request; sonst 30 (ViUR-Standard). Angezeigt
82
- // wird immer die tatsächliche Fetch-Größe.
80
+ // Page size of the cursor bar = ViUR parameter `limit` (core ≥ 3.9; the
81
+ // former `amount` no longer exists there). It applies from the FIRST fetch:
82
+ // a pageSize given via tableProps (or a `limit` in the params) already
83
+ // determines the initial request; otherwise 30 (ViUR default). The actual
84
+ // fetch size is always what's displayed.
83
85
  const limit = ref(Math.min(99, Number(props.params?.limit ?? props.tableProps?.pageSize ?? 30) || 30))
84
86
  const baseParams = () => ({ ...props.params, orderby: props.orderField, limit: limit.value })
85
- // Für jeden Fetch nach dem Setup gilt: IMMER mit dem aktuellen Ort (Rootnode
86
- // oder navigierter Ordner) als parententry — sonst listet der Server quer
87
- // über den ganzen Baum.
87
+ // Every fetch after setup ALWAYS uses the current location (rootnode or
88
+ // navigated folder) as parententry — otherwise the server lists across the
89
+ // whole tree.
88
90
  const currentParams = () => ({
89
91
  ...baseParams(),
90
92
  ...(location.value != null ? { parententry: location.value } : {}),
91
93
  })
92
94
  const mergedTableProps = computed(() => ({
93
- // Keine Suche im Browser: navigiert wird über Baum und Breadcrumb, gesucht
94
- // wird in der Listen-Ansicht des Moduls. Wer sie doch will, setzt
95
- // `tableProps.withSearch` (lokal sinnvoll nur, wenn alles geladen ist:
95
+ // No search in the browser: navigation happens via tree and breadcrumb,
96
+ // searching in the module's list view. Anyone who wants it anyway sets
97
+ // `tableProps.withSearch` (locally only useful once everything is loaded:
96
98
  // `allLoaded`).
97
99
  ...props.tableProps,
98
100
  pageSize: limit.value,
@@ -110,7 +112,7 @@ const rootNodeList = computed(() => {
110
112
  return Array.isArray(list) ? list.map((entry) => ({ key: entry.key, name: entry.name ?? entry.key })) : []
111
113
  })
112
114
 
113
- // ── Inhalt des aktuellen Ortes (Nodes + Leaves) ───────────────────────────────
115
+ // ── Contents of the current location (nodes + leaves) ─────────────────────────
114
116
  const tree = useTreeFetch(props.module, {
115
117
  params: baseParams(),
116
118
  parententry: location.value ?? undefined,
@@ -120,9 +122,9 @@ const tree = useTreeFetch(props.module, {
120
122
  const rows = computed(() => tree.data.value)
121
123
  const rowKind = (row) => (row?.skelType === "node" ? "node" : "leaf")
122
124
 
123
- // Ohne expliziten Ort ist die ROOTNODE der Ort: sobald sie bekannt ist, wird
124
- // sie zum Standard-`parententry` für alles — Listen-Fetch, Drops auf die
125
- // freie Fläche, Reorder und Uploads. Gefetcht wird nie ohne parententry.
125
+ // Without an explicit location, the ROOTNODE is the location: as soon as it is
126
+ // known, it becomes the default `parententry` for everything — list fetch,
127
+ // drops onto the empty area, reorder and uploads. Never fetch without parententry.
126
128
  watch(
127
129
  rootNodeList,
128
130
  (roots) => {
@@ -139,8 +141,8 @@ watch(
139
141
  { immediate: true }
140
142
  )
141
143
 
142
- // Alles geladen (tree.complete)? Dann laufen Sortierung, Suche und
143
- // Spaltenfilter nur noch LOKAL auf dem vollständigen Datensatz.
144
+ // Everything loaded (tree.complete)? Then sorting, search and column
145
+ // filters run only LOCALLY on the complete dataset.
144
146
  const allLoaded = computed(() => tree.complete.value)
145
147
 
146
148
  const effectiveColumns = computed(() => {
@@ -151,15 +153,16 @@ const effectiveColumns = computed(() => {
151
153
  ...(props.language ? { language: props.language } : {}),
152
154
  ...(props.primaryLanguage ? { primaryLanguage: props.primaryLanguage } : {}),
153
155
  local: allLoaded.value,
156
+ boolTexts: [t("bones.bool.true"), t("bones.bool.false")],
154
157
  })
155
- return derived.length ? derived : [{ id: "name", label: "Name" }]
158
+ return derived.length ? derived : [{ id: "name", label: t("browser.name") }]
156
159
  })
157
160
 
158
161
  function onNavigate() {
159
162
  selectedKeys.value = []
160
163
  }
161
164
 
162
- // ── Cursor-Nachladen: 1 / 5 / 10 Seiten oder alles ───────────────────────────
165
+ // ── Cursor loading: 1 / 5 / 10 pages or everything ───────────────────────────
163
166
  async function onLoadMore({ pages }) {
164
167
  if (pages === "all") {
165
168
  await tree.nextAll().catch(() => null)
@@ -171,14 +174,14 @@ async function onLoadMore({ pages }) {
171
174
  }
172
175
  }
173
176
 
174
- /** Neue Seitengröße: aktuellen Ort mit geändertem `limit` neu holen. */
177
+ /** New page size: re-fetch the current location with the changed `limit`. */
175
178
  function onPageSizeChange({ pageSize }) {
176
179
  if (!pageSize || pageSize === limit.value) return
177
180
  limit.value = Math.min(99, pageSize)
178
181
  void tree.filter(currentParams()).catch(() => null)
179
182
  }
180
183
 
181
- // ── Verschieben (Drop auf Baum/Ordner) & Umsortieren ─────────────────────────
184
+ // ── Moving (drop on tree/folder) & reordering ────────────────────────────────
182
185
  async function saveMove(row, parentNode, sortindex, kind = rowKind(row)) {
183
186
  const data = {
184
187
  skelType: kind,
@@ -191,7 +194,7 @@ async function saveMove(row, parentNode, sortindex, kind = rowKind(row)) {
191
194
  await result.execute()
192
195
  return result.data.value
193
196
  } catch (error) {
194
- // Fehler trägt der Aufrufer aus (onMove/onReorder fangen und melden).
197
+ // The caller handles errors (onMove/onReorder catch and report).
195
198
  throw error
196
199
  }
197
200
  }
@@ -199,17 +202,17 @@ async function saveMove(row, parentNode, sortindex, kind = rowKind(row)) {
199
202
  async function onMove({ rows: movedRows, target, keys, source }) {
200
203
  try {
201
204
  for (const row of movedRows) {
202
- // Aus dem BAUM kommen BrowserNodes ({ key, name, data }) — das Skel
203
- // steckt in `data`, und ein Baum-Eintrag ist immer ein Node. Aus der
204
- // LISTE kommen Skels mit skelType.
205
+ // From the TREE come BrowserNodes ({ key, name, data }) — the skel
206
+ // lives in `data`, and a tree entry is always a node. From the
207
+ // LIST come skels with skelType.
205
208
  const raw = row?.data ?? row
206
209
  await saveMove(raw, target, undefined, source === "tree" ? "node" : rowKind(raw))
207
210
  }
208
211
  emit("moved", { keys, target })
209
- // Ansicht (Liste UND Baum) ist bereits lokal verschoben — kein Reload.
212
+ // View (list AND tree) has already been moved locally — no reload.
210
213
  } catch (error) {
211
214
  emit("move-failed", { keys, target, error })
212
- // Der lokale Stand (Liste UND Baum) ist dann falsch — einmal frisch holen.
215
+ // The local state (list AND tree) is then wrong — fetch it fresh once.
213
216
  void refresh().catch(() => null)
214
217
  }
215
218
  }
@@ -236,15 +239,15 @@ async function onUpload({ files, target }) {
236
239
  }
237
240
  if (results.length) {
238
241
  emit("uploaded", { files: results, target })
239
- // Neue Einträge gibt es nur vom Server — den aktuellen Ort einmal holen.
242
+ // New entries only come from the server — fetch the current location once.
240
243
  void tree.execute().catch(() => null)
241
244
  }
242
245
  }
243
246
 
244
- // ── Baum-Ebenen (nur Nodes) ───────────────────────────────────────────────────
247
+ // ── Tree levels (nodes only) ──────────────────────────────────────────────────
245
248
  async function loadNodeChildren(node) {
246
249
  const list = useListFetch(props.module, {
247
- // Baum-Ebenen sind Navigation: möglichst vollständig (ViUR-Maximum 99).
250
+ // Tree levels are navigation: as complete as possible (ViUR maximum 99).
248
251
  params: { skelType: "node", parententry: node.key, orderby: props.orderField, limit: 99 },
249
252
  ...(props.renderer ? { renderer: props.renderer } : {}),
250
253
  immediate: false,
@@ -258,9 +261,9 @@ async function loadNodeChildren(node) {
258
261
  }
259
262
 
260
263
  /**
261
- * Alles neu laden: die Liste des aktuellen Ortes UND den Navigations-Baum.
262
- * Der Baum verwirft seine geladenen Ebenen und klappt die Rootnodes wieder
263
- * auf (frisch vom Server); offene Unterebenen laden beim Aufklappen neu.
264
+ * Reload everything: the list of the current location AND the navigation tree.
265
+ * The tree discards its loaded levels and expands the rootnodes again
266
+ * (fresh from the server); open sublevels reload when expanded.
264
267
  */
265
268
  function refresh() {
266
269
  browserEl.value?.tree?.reset?.()
@@ -1,49 +1,49 @@
1
1
  import { reactive } from "vue"
2
2
  import type { InjectionKey } from "vue"
3
- import type { RowKey } from "../DataTable/types"
3
+ import type { RowKey } from "../DataTable/types.ts"
4
4
 
5
5
  // --------------------------------------------------------------------------- //
6
- // Drag-Session — gemeinsames Ziehen zwischen Panels (Liste ↔ Baum) //
6
+ // Drag session — shared dragging between panels (list ↔ tree) //
7
7
  // --------------------------------------------------------------------------- //
8
8
  //
9
- // Die DataTable zieht Zeilen mit Zeiger-Ereignissen (kein HTML5-D&D). Damit
10
- // eine Zeile auch AUSSERHALB der Tabelle fallen gelassen werden kann (auf einen
11
- // Ordner im Navigations-Baum oder eine Ordnerzeile der Liste), stellt der
12
- // Browser diese Session per provide() bereit:
9
+ // The DataTable drags rows with pointer events (no HTML5 D&D). So that a row
10
+ // can also be dropped OUTSIDE the table (onto a folder in the navigation tree
11
+ // or a folder row of the list), the browser provides this session via
12
+ // provide():
13
13
  //
14
- // • Die QUELLE (DataTable) meldet Beginn/Bewegung/Ende des Zugs.
15
- // • Jedes PANEL registriert sich als Container mit einer `targetFor`-Funktion,
16
- // die ein DOM-Element unter dem Zeiger in ein Drop-Ziel übersetzt (oder
17
- // null, wenn dort nichts fallen gelassen werden kann).
18
- // • Beim Loslassen über einem Ziel liefert `drop()` Nutzlast + Ziel an den
19
- // Halter der Session (Browser) — die Quelle überspringt dann ihre interne
20
- // Verschiebung.
14
+ // • The SOURCE (DataTable) reports start/move/end of the drag.
15
+ // • Every PANEL registers itself as a container with a `targetFor` function
16
+ // that translates a DOM element under the pointer into a drop target (or
17
+ // null if nothing can be dropped there).
18
+ // • On release over a target, `drop()` hands payload + target to the
19
+ // holder of the session (browser) — the source then skips its internal
20
+ // move.
21
21
  //
22
- // Die Session ist bewusst DOM-basiert (elementFromPoint statt Ziel-Rechtecke
23
- // vorab zu vermessen): Panels scrollen und virtualisieren unabhängig.
22
+ // The session is deliberately DOM-based (elementFromPoint instead of measuring
23
+ // target rectangles up front): panels scroll and virtualize independently.
24
24
 
25
25
  export interface DragPayload<R = Record<string, unknown>> {
26
26
  keys: RowKey[]
27
27
  rows: R[]
28
- /** Container-Id der Quelle (z. B. "list"). */
28
+ /** Container id of the source (e.g. "list"). */
29
29
  source: string
30
30
  }
31
31
 
32
32
  export interface DropTarget {
33
- /** Container-Id des Panels, in dem das Ziel liegt. */
33
+ /** Container id of the panel the target lies in. */
34
34
  container: string
35
- /** Schlüssel des Zielknotens; null = Wurzelebene des Containers. */
35
+ /** Key of the target node; null = root level of the container. */
36
36
  key: RowKey | null
37
37
  mode: "inside" | "before" | "after"
38
38
  }
39
39
 
40
40
  export interface DragContainer {
41
41
  id: string
42
- /** Wurzelelement des Panels — nur Treffer darin zählen. */
42
+ /** Root element of the panel — only hits inside it count. */
43
43
  el: () => HTMLElement | null
44
44
  /**
45
- * Element unter dem Zeiger → Drop-Ziel (oder null). `y`/`x` erlauben
46
- * Zonen-Entscheidungen (before/inside/after) anhand der Trefferposition.
45
+ * Element under the pointer → drop target (or null). `y`/`x` allow
46
+ * zone decisions (before/inside/after) based on the hit position.
47
47
  */
48
48
  targetFor: (hit: Element, x: number, y: number) => DropTarget | null
49
49
  }
@@ -56,9 +56,9 @@ export interface DragSession<R = Record<string, unknown>> {
56
56
  }
57
57
  register(container: DragContainer): () => void
58
58
  start(payload: DragPayload<R>): void
59
- /** Hit-Test unter (x, y); `hint` übergeht elementFromPoint (Tests). */
59
+ /** Hit test at (x, y); `hint` bypasses elementFromPoint (tests). */
60
60
  update(x: number, y: number, hint?: Element | null): DropTarget | null
61
- /** Ziel eingelöst: liefert Nutzlast+Ziel und beendet die Session. */
61
+ /** Target redeemed: returns payload+target and ends the session. */
62
62
  drop(): { payload: DragPayload<R>; target: DropTarget } | null
63
63
  cancel(): void
64
64
  }
@@ -69,9 +69,9 @@ export function createDragSession<R = Record<string, unknown>>(
69
69
  onDrop?: (payload: DragPayload<R>, target: DropTarget) => void
70
70
  ): DragSession<R> {
71
71
  const containers = new Map<string, DragContainer>()
72
- // `reactive` entpackt generische Typen (R wird zu UnwrapRefSimple<R>), womit
73
- // der Zustand nicht mehr auf DragSession<R> passt. Die Nutzlast ist reine
74
- // Datenweitergabe — deshalb hier die Form festschreiben.
72
+ // `reactive` unwraps generic types (R becomes UnwrapRefSimple<R>), so the
73
+ // state no longer matches DragSession<R>. The payload is pure data
74
+ // pass-through — hence the shape is pinned down here.
75
75
  const state = reactive({
76
76
  active: false,
77
77
  payload: null as DragPayload<R> | null,
@@ -124,7 +124,7 @@ export function createDragSession<R = Record<string, unknown>>(
124
124
  return { state, register, start, update, drop, cancel }
125
125
  }
126
126
 
127
- /** Zonen-Helfer: Position im Element → before / inside / after. */
127
+ /** Zone helper: position within the element → before / inside / after. */
128
128
  export function dropModeFor(hitRect: DOMRect, y: number, insideOnly = false): "inside" | "before" | "after" {
129
129
  if (insideOnly) return "inside"
130
130
  const offset = (y - hitRect.top) / Math.max(1, hitRect.height)
@@ -1,4 +1,4 @@
1
- // Öffentliche Oberfläche des Browser-Widgets.
1
+ // Public surface of the browser widget.
2
2
  export { default as Browser } from "./Browser.vue"
3
3
  export { default as ViurBrowser } from "./ViurBrowser.vue"
4
4
  export { default as TreeNav } from "./TreeNav.vue"
@@ -10,5 +10,5 @@ export {
10
10
  type DragPayload,
11
11
  type DragContainer,
12
12
  type DropTarget,
13
- } from "./dragSession"
14
- export type { BrowserNode, BrowserMovePayload, BrowserUploadPayload } from "./types"
13
+ } from "./dragSession.ts"
14
+ export type { BrowserNode, BrowserMovePayload, BrowserUploadPayload } from "./types.ts"