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
@@ -1,10 +1,10 @@
1
- // Reine Zeilen-Pipeline: Filter → Suche → Sortierung → Baum/Gruppen →
2
- // Paginierung → flache Anzeigezeilen. Ebenfalls Vue-frei und damit direkt
3
- // testbar; die Komponente ruft das Ganze nur in einem computed auf.
1
+ // Pure row pipeline: filter → search → sort → tree/groups →
2
+ // pagination → flat display rows. Also Vue-free and therefore directly
3
+ // testable; the component just calls the whole thing inside a computed.
4
4
 
5
- import { aggregateColumn, defaultText, findColumn, rowMatchesFilters, rowMatchesSearch, sortRows } from "./columns"
6
- import type { ColumnFacets, DisplayRow, FilterEntry, ResolvedColumn, Row, RowKey, RowNode, SortEntry } from "./types"
7
- import { computeFacets, isEmptyFilter } from "./columns"
5
+ import { aggregateColumn, defaultText, findColumn, rowMatchesFilters, rowMatchesSearch, sortRows } from "./columns.ts"
6
+ import type { ColumnFacets, DisplayRow, FilterEntry, ResolvedColumn, Row, RowKey, RowNode, SortEntry } from "./types.ts"
7
+ import { computeFacets, isEmptyFilter } from "./columns.ts"
8
8
 
9
9
  export type ChildRowsAccessor<R extends Row = Row> = string | ((row: R) => R[] | undefined | null)
10
10
 
@@ -15,24 +15,24 @@ export interface BuildRowsOptions<R extends Row = Row> {
15
15
  filters?: FilterEntry[]
16
16
  search?: string
17
17
  searchFn?: ((value: unknown, term: string, row: R) => boolean) | null
18
- /** Baumdaten: Feldname oder Funktion, die die Kinder liefert. */
18
+ /** Tree data: field name or function that returns the children. */
19
19
  childRows?: ChildRowsAccessor<R> | null
20
- /** Elternzeile bleibt sichtbar, wenn ein Nachfahre passt. */
20
+ /** Parent row stays visible when a descendant matches. */
21
21
  filterFromLeafRows?: boolean
22
- /** Gruppierung nach einer oder mehreren Spalten-ids (ohne Wirkung bei Baumdaten). */
22
+ /** Grouping by one or more column ids (no effect on tree data). */
23
23
  groupBy?: string[]
24
- /** Feld mit stabiler Zeilen-id. Ohne das werden Indexpfade benutzt ("0", "0.1"). */
24
+ /** Field with a stable row id. Without it, index paths are used ("0", "0.1"). */
25
25
  rowKey?: string | null
26
26
  }
27
27
 
28
28
  export interface BuiltRows<R extends Row = Row> {
29
- /** Hierarchie nach Filter/Sortierung/Gruppierung. */
29
+ /** Hierarchy after filtering/sorting/grouping. */
30
30
  nodes: RowNode<R>[]
31
- /** Alle Datenzeilen in Anzeigereihenfolge (auch eingeklappte). */
31
+ /** All data rows in display order (including collapsed ones). */
32
32
  dataRows: R[]
33
- /** Nur oberste Ebene — für Fußzeilen-Funktionen bei Baumdaten. */
33
+ /** Top level only — for footer functions with tree data. */
34
34
  topLevelRows: R[]
35
- /** Zeilen im Ergebnis (oberste Ebene bzw. Gruppen) über alle Seiten. */
35
+ /** Rows in the result (top level or groups) across all pages. */
36
36
  filteredCount: number
37
37
  facets: Record<string, ColumnFacets>
38
38
  }
@@ -51,7 +51,7 @@ function keyOf<R extends Row>(row: R, rowKey: string | null | undefined, path: s
51
51
  return path
52
52
  }
53
53
 
54
- /** Datenzeilen (mit Kindern) in Knoten überführen — Filter und Sortierung inklusive. */
54
+ /** Convert data rows (with children) into nodes — filtering and sorting included. */
55
55
  function buildDataNodes<R extends Row>(
56
56
  rows: R[],
57
57
  options: BuildRowsOptions<R>,
@@ -67,8 +67,8 @@ function buildDataNodes<R extends Row>(
67
67
  const selfMatches = rowMatchesFilters(row, filters, columns) && rowMatchesSearch(row, search, columns, searchFn)
68
68
 
69
69
  const rawChildren = childrenOf(row, childRows)
70
- // Bei filterFromLeafRows zählt der Treffer eines Nachfahren; sonst nimmt
71
- // eine nicht passende Zeile ihren ganzen Teilbaum mit.
70
+ // With filterFromLeafRows a descendant's match counts; otherwise a
71
+ // non-matching row takes its whole subtree with it.
72
72
  const children = rawChildren.length ? buildDataNodes(rawChildren, options, depth + 1, path) : []
73
73
 
74
74
  if (!selfMatches && !(filterFromLeafRows && children.length)) return
@@ -84,7 +84,7 @@ function buildDataNodes<R extends Row>(
84
84
  return nodes
85
85
  }
86
86
 
87
- /** Datenknoten nach einer oder mehreren Spalten gruppieren. */
87
+ /** Group data nodes by one or more columns. */
88
88
  function groupNodes<R extends Row>(
89
89
  nodes: RowNode<R>[],
90
90
  groupBy: string[],
@@ -139,7 +139,7 @@ function collectDataRows<R extends Row>(nodes: RowNode<R>[], into: R[] = []): R[
139
139
  return into
140
140
  }
141
141
 
142
- /** Facetten je filterbarer Spalte — jeweils ohne den eigenen Filter. */
142
+ /** Facets per filterable column — each without its own filter. */
143
143
  function buildFacets<R extends Row>(options: BuildRowsOptions<R>): Record<string, ColumnFacets> {
144
144
  const { columns, filters = [], search = "", searchFn, childRows } = options
145
145
  const flat = flattenData(options.data, childRows)
@@ -182,7 +182,7 @@ export function buildRows<R extends Row>(options: BuildRowsOptions<R>): BuiltRow
182
182
  }
183
183
  }
184
184
 
185
- /** Eine Seite aus der obersten Ebene schneiden (Teilbäume bleiben bei ihrem Elternteil). */
185
+ /** Slice one page from the top level (subtrees stay with their parent). */
186
186
  export function paginateNodes<R extends Row>(nodes: RowNode<R>[], page: number, pageSize: number): RowNode<R>[] {
187
187
  if (pageSize <= 0) return nodes
188
188
  const start = page * pageSize
@@ -191,29 +191,29 @@ export function paginateNodes<R extends Row>(nodes: RowNode<R>[], page: number,
191
191
 
192
192
  export interface FlattenOptions<R extends Row = Row> {
193
193
  isExpanded: (key: RowKey) => boolean
194
- /** Zeilen bekommen ein Detail-Panel (eigene Zeile unter der Datenzeile). */
194
+ /** Rows get a detail panel (a separate row below the data row). */
195
195
  withDetail?: boolean
196
196
  /**
197
- * Für nachgeladene Ebenen: Zeile gilt als aufklappbar, obwohl (noch) keine
198
- * Kinder in den Daten stehen. Beim Nachladen weiß man vorher nicht, ob es
199
- * welche gibt — der Pfeil muss trotzdem von Anfang an da sein.
197
+ * For lazily loaded levels: the row counts as expandable even though there
198
+ * are no children in the data (yet). When lazy loading, you don't know in
199
+ * advance whether there are any — the arrow must still be there from the start.
200
200
  */
201
201
  mayHaveChildren?: (node: RowNode<R>) => boolean
202
202
  }
203
203
 
204
204
  /**
205
- * Hierarchie zu einer flachen Renderliste machen: eingeklappte Teilbäume
206
- * fallen weg, aufgeklappte Detail-Panels kommen als eigene Zeile dazu.
205
+ * Turn the hierarchy into a flat render list: collapsed subtrees are
206
+ * dropped, expanded detail panels are added as separate rows.
207
207
  */
208
208
  export function flattenNodes<R extends Row>(nodes: RowNode<R>[], options: FlattenOptions<R>): DisplayRow<R>[] {
209
209
  const out: DisplayRow<R>[] = []
210
210
  let dataIndex = 0
211
211
 
212
212
  /**
213
- * `lines` sind die Durchlauf-Spalten links des eigenen Winkels: je Eintrag
214
- * eine Spalte, in der eine senkrechte Linie durchläuft, weil der Vorfahre der
215
- * NÄCHSTEN Ebene noch ein Geschwister hat (siehe `DisplayRow.ancestorLines`).
216
- * Nach dem letzten Kind bleibt die Spalte leer.
213
+ * `lines` are the pass-through columns left of the row's own connector: one
214
+ * column per entry in which a vertical line runs through, because the ancestor
215
+ * of the NEXT level still has a sibling (see `DisplayRow.ancestorLines`).
216
+ * After the last child the column stays empty.
217
217
  */
218
218
  const walk = (list: RowNode<R>[], lines: boolean[]): void => {
219
219
  list.forEach((node, position) => {
@@ -254,8 +254,8 @@ export function flattenNodes<R extends Row>(nodes: RowNode<R>[], options: Flatte
254
254
  })
255
255
 
256
256
  if (node.kind === "data" && options.withDetail && !hasChildren) out.push(detailRow())
257
- // Wurzeln erzeugen keine Durchlauf-Spalte (zwischen Wurzeln gibt es keine
258
- // Linie); ab Ebene 1 kommt je Ebene eine dazu.
257
+ // Roots create no pass-through column (there is no line between roots);
258
+ // from level 1 on, one is added per level.
259
259
  else if (hasChildren) {
260
260
  walk(node.children, node.depth === 0 ? [] : [...lines, !isLastChild])
261
261
  } else if (options.withDetail) out.push(detailRow())
@@ -266,9 +266,9 @@ export function flattenNodes<R extends Row>(nodes: RowNode<R>[], options: Flatte
266
266
  return out
267
267
  }
268
268
 
269
- // ── Auswahl (Baum/Gruppen) ────────────────────────────────────────────
269
+ // ── Selection (tree/groups) ───────────────────────────────────────────
270
270
 
271
- /** Alle Datenschlüssel eines Knotens inklusive Nachfahren. */
271
+ /** All data keys of a node including descendants. */
272
272
  export function collectKeys<R extends Row>(node: RowNode<R>, into: RowKey[] = []): RowKey[] {
273
273
  if (node.kind === "data") into.push(node.key)
274
274
  for (const child of node.children) collectKeys(child, into)
@@ -277,7 +277,7 @@ export function collectKeys<R extends Row>(node: RowNode<R>, into: RowKey[] = []
277
277
 
278
278
  export type SelectionState = "checked" | "indeterminate" | "unchecked"
279
279
 
280
- /** Zustand der Checkbox eines Knotens (Elternteil spiegelt seine Kinder). */
280
+ /** Checkbox state of a node (parent mirrors its children). */
281
281
  export function selectionStateFor<R extends Row>(node: RowNode<R>, selected: Set<RowKey>): SelectionState {
282
282
  const keys = collectKeys(node)
283
283
  if (!keys.length) return "unchecked"
@@ -286,24 +286,23 @@ export function selectionStateFor<R extends Row>(node: RowNode<R>, selected: Set
286
286
  return hits === keys.length ? "checked" : "indeterminate"
287
287
  }
288
288
 
289
- // ── Flache Liste zu Baum ──────────────────────────────────────────────
289
+ // ── Flat list to tree ─────────────────────────────────────────────────
290
290
 
291
291
  export interface NestedTree<R extends Row = Row> {
292
- /** Zeilen der obersten Ebene, in Eingabereihenfolge. */
292
+ /** Top-level rows, in input order. */
293
293
  roots: R[]
294
- /** Kinder je Zeilenschlüssel. */
294
+ /** Children per row key. */
295
295
  children: Map<RowKey, R[]>
296
296
  }
297
297
 
298
298
  /**
299
- * Aus einer FLACHEN Liste mit Eltern-Verweis (ViUR: `parententry`) die
300
- * Hierarchie bauen.
299
+ * Build the hierarchy from a FLAT list with parent references (ViUR:
300
+ * `parententry`).
301
301
  *
302
- * Grundsatz: es geht nichts verloren. Zeilen ohne Elternwert sind Wurzeln;
303
- * Zeilen, deren Elternteil nicht in den Daten steckt (Waisen — etwa weil nur
304
- * eine Seite geladen ist), ebenfalls. Auch Zyklen (A → B → A, oder eine Zeile
305
- * als ihr eigener Elternteil) landen als Wurzel, statt die Liste zu sprengen
306
- * oder Zeilen verschwinden zu lassen.
302
+ * Principle: nothing gets lost. Rows without a parent value are roots; so are
303
+ * rows whose parent isn't in the data (orphans — e.g. because only one page
304
+ * is loaded). Cycles (A → B → A, or a row that is its own parent) also end
305
+ * up as roots, instead of blowing up the list or making rows disappear.
307
306
  */
308
307
  export function nestByParent<R extends Row>(rows: R[], rowKey: string, parentField: string): NestedTree<R> {
309
308
  const byKey = new Map<RowKey, R>()
@@ -331,8 +330,8 @@ export function nestByParent<R extends Row>(rows: R[], rowKey: string, parentFie
331
330
  attached.add(row)
332
331
  }
333
332
 
334
- // Zyklen: alles, was zwar einen Elternteil hat, aber von keiner Wurzel aus
335
- // erreichbar ist, wird selbst zur Wurzel — sonst wäre es unsichtbar.
333
+ // Cycles: anything that has a parent but isn't reachable from any root
334
+ // becomes a root itself — otherwise it would be invisible.
336
335
  const reachable = new Set<R>()
337
336
  const walk = (list: R[]): void => {
338
337
  for (const row of list) {
@@ -346,7 +345,7 @@ export function nestByParent<R extends Row>(rows: R[], rowKey: string, parentFie
346
345
 
347
346
  for (const row of rows) {
348
347
  if (!attached.has(row) || reachable.has(row)) continue
349
- // Kante zum (kreisenden) Elternteil kappen und als Wurzel führen.
348
+ // Cut the edge to the (cyclic) parent and treat it as a root.
350
349
  const parent = row[parentField] as RowKey
351
350
  const siblings = children.get(parent)
352
351
  if (siblings) {
@@ -360,18 +359,18 @@ export function nestByParent<R extends Row>(rows: R[], rowKey: string, parentFie
360
359
  return { roots, children }
361
360
  }
362
361
 
363
- // ── Position im Baum ──────────────────────────────────────────────────
362
+ // ── Position in the tree ──────────────────────────────────────────────
364
363
 
365
364
  export interface NodeLocation<R extends Row = Row> {
366
365
  node: RowNode<R>
367
- /** Schlüssel der Elternzeile; null auf der obersten Ebene. */
366
+ /** Key of the parent row; null on the top level. */
368
367
  parentKey: RowKey | null
369
- /** Geschwister einschließlich des Knotens selbst, in Anzeigereihenfolge. */
368
+ /** Siblings including the node itself, in display order. */
370
369
  siblings: RowNode<R>[]
371
370
  index: number
372
371
  }
373
372
 
374
- /** Knoten samt Elternteil und Geschwistern suchen. */
373
+ /** Find a node along with its parent and siblings. */
375
374
  export function locateNode<R extends Row>(
376
375
  nodes: RowNode<R>[],
377
376
  key: RowKey,
@@ -388,9 +387,9 @@ export function locateNode<R extends Row>(
388
387
  }
389
388
 
390
389
  /**
391
- * Liegt `candidateKey` im Teilbaum von `ancestorKey`? Verhindert, dass eine
392
- * Zeile in ihren eigenen Nachfahren gezogen wird — der Teilbaum hinge sonst
393
- * an sich selbst und wäre aus der Ansicht verschwunden.
390
+ * Is `candidateKey` in the subtree of `ancestorKey`? Prevents a row from
391
+ * being dragged into its own descendant — the subtree would otherwise hang
392
+ * from itself and disappear from the view.
394
393
  */
395
394
  export function isDescendant<R extends Row>(nodes: RowNode<R>[], ancestorKey: RowKey, candidateKey: RowKey): boolean {
396
395
  const ancestor = locateNode(nodes, ancestorKey)
@@ -399,32 +398,32 @@ export function isDescendant<R extends Row>(nodes: RowNode<R>[], ancestorKey: Ro
399
398
  return walk(ancestor.node.children)
400
399
  }
401
400
 
402
- // ── Feste Reihenfolge (numerische Ordnungsspalte) ─────────────────────
401
+ // ── Fixed order (numeric order column) ────────────────────────────────
403
402
 
404
403
  export interface OrderValue {
405
- /** Neuer Wert für die Ordnungsspalte der verschobenen Zeile. */
404
+ /** New value for the order column of the moved row. */
406
405
  order: number
407
406
  /**
408
- * true, wenn die beiden Nachbarn NICHT unterscheidbar sind — praktisch immer
409
- * dann, wenn mehrere Datensätze denselben Ordnungswert tragen (frisch
410
- * angelegte Einträge mit Default 0 etwa). Die Mitte wäre dort wieder derselbe
411
- * Wert, die Zeile würde also nicht sichtbar wandern; stattdessen wird hinter
412
- * dem Block eingeordnet und die Anwendung kann neu durchnummerieren.
407
+ * true if the two neighbors are NOT distinguishable — in practice whenever
408
+ * several records carry the same order value (e.g. freshly created entries
409
+ * with default 0). The midpoint there would be the same value again, so the
410
+ * row wouldn't visibly move; instead it is placed after the block and the
411
+ * application can renumber.
413
412
  *
414
- * Bei verteilten Fließkommawerten passiert das nicht: zwischen zwei Werten
415
- * liegen rund 50 Halbierungen, bevor die Genauigkeit ausgeht.
413
+ * This doesn't happen with spread-out floating-point values: there are about
414
+ * 50 halvings between two values before precision runs out.
416
415
  */
417
416
  needsReindex: boolean
418
417
  }
419
418
 
420
419
  /**
421
- * Neuen Ordnungswert für eine Einfügeposition bestimmen — dieselbe Rechnung wie
422
- * im admin4: `(prev + next) / 2`, an den Rändern ±Schritt.
420
+ * Determine a new order value for an insert position — the same calculation as
421
+ * in admin4: `(prev + next) / 2`, ±step at the edges.
423
422
  *
424
- * `values` sind die Ordnungswerte der Zielliste OHNE die verschobene Zeile,
425
- * aufsteigend; `index` ist die Einfügeposition (0 = ganz vorn, values.length =
426
- * ganz hinten). Die Mitte zwischen den Nachbarn heißt: beim Verschieben muss
427
- * genau EIN Datensatz gespeichert werden, nicht die ganze Liste.
423
+ * `values` are the order values of the target list WITHOUT the moved row,
424
+ * ascending; `index` is the insert position (0 = very front, values.length =
425
+ * very end). Using the midpoint between neighbors means: when moving, exactly
426
+ * ONE record has to be saved, not the whole list.
428
427
  */
429
428
  export function orderValueFor(values: number[], index: number, step = 1): OrderValue {
430
429
  const clean = values.filter((value) => Number.isFinite(value))
@@ -439,8 +438,8 @@ export function orderValueFor(values: number[], index: number, step = 1): OrderV
439
438
  const previous = clean[position - 1]
440
439
  const next = clean[position]
441
440
  const middle = previous + (next - previous) / 2
442
- // Gleiche (oder nicht unterscheidbare) Nachbarn: die Mitte träfe einen von
443
- // beiden — dann hinter dem Block einordnen, statt still nichts zu tun.
441
+ // Equal (or indistinguishable) neighbors: the midpoint would hit one of
442
+ // them — so place it after the block instead of silently doing nothing.
444
443
  const needsReindex = middle <= previous || middle >= next
445
444
  return { order: needsReindex ? previous + step : middle, needsReindex }
446
445
  }
@@ -1,6 +1,6 @@
1
- // Typen des DataTable-Widgets — nachgebaut nach <wa-data-grid> (WebAwesome Pro
2
- // v12), aber mit Vue-Mitteln: Zell-Inhalte kommen über Slots statt über
3
- // Lit-Templates, Zustand über v-model statt über Properties am Element.
1
+ // Types of the DataTable widget — modeled after <wa-data-grid> (WebAwesome Pro
2
+ // v12), but with Vue means: cell content comes via slots instead of
3
+ // Lit templates, state via v-model instead of properties on the element.
4
4
 
5
5
  export type Row = Record<string, unknown>
6
6
 
@@ -8,7 +8,7 @@ export type Align = "start" | "center" | "end"
8
8
  export type PinSide = "start" | "end"
9
9
 
10
10
  export type SortMode =
11
- | "auto" // Zahlen numerisch, Datum chronologisch, sonst alphanumerisch
11
+ | "auto" // numbers numerically, dates chronologically, otherwise alphanumerically
12
12
  | "text"
13
13
  | "textCaseSensitive"
14
14
  | "number"
@@ -16,13 +16,13 @@ export type SortMode =
16
16
  | "basic"
17
17
 
18
18
  export type FilterType =
19
- | "text" // Teilstring, case-insensitive (Default)
19
+ | "text" // substring, case-insensitive (default)
20
20
  | "equals"
21
21
  | "number-range" // [min, max]
22
- | "date-range" // [von, bis], inklusive nach Kalendertag
23
- | "set" // Wert ist einer der gewählten
24
- | "includes-any" // Array-Zelle enthält einen der gewählten
25
- | "includes-all" // Array-Zelle enthält alle gewählten
22
+ | "date-range" // [from, to], inclusive by calendar day
23
+ | "set" // value is one of the selected ones
24
+ | "includes-any" // array cell contains one of the selected ones
25
+ | "includes-all" // array cell contains all selected ones
26
26
 
27
27
  export type AggregationKind = "sum" | "min" | "max" | "extent" | "mean" | "median" | "count" | "unique" | "uniqueCount"
28
28
 
@@ -32,34 +32,34 @@ export interface FilterOption {
32
32
  count?: number
33
33
  }
34
34
 
35
- /** Eine Spaltendefinition. Reines Datenobjekt — reaktiv, ohne Render-Logik. */
35
+ /** A column definition. Plain data object — reactive, without render logic. */
36
36
  export interface DataTableColumn<R extends Row = Row> {
37
- /** Pfad in die Zeile, Punktpfade erlaubt ("user.name"). Dient als id, wenn `id` fehlt. */
37
+ /** Path into the row, dot paths allowed ("user.name"). Serves as the id if `id` is missing. */
38
38
  field?: string
39
- /** Explizite id. Pflicht für Spalten ohne `field` (Aktionen, berechnete Spalten). */
39
+ /** Explicit id. Required for columns without `field` (actions, computed columns). */
40
40
  id?: string
41
41
  /**
42
- * Berechnet den Zellwert aus der Zeile. Anders als `formatter` (nur Darstellung)
43
- * geht dieser Wert in Sortierung, Filter, Suche und Export ein.
42
+ * Computes the cell value from the row. Unlike `formatter` (display only),
43
+ * this value is used for sorting, filtering, search and export.
44
44
  */
45
45
  value?: (row: R) => unknown
46
46
  label?: string
47
47
  align?: Align
48
48
  headerAlign?: Align
49
- /** Default: true, sobald `field` oder `value` vorhanden ist. */
49
+ /** Default: true as soon as `field` or `value` is present. */
50
50
  sortable?: boolean
51
51
  sortFn?: SortMode
52
- /** Wohin leere Werte sortieren. */
52
+ /** Where empty values sort to. */
53
53
  sortUndefined?: false | "first" | "last"
54
- /** Erste Sortierung dieser Spalte absteigend. */
54
+ /** First sort of this column is descending. */
55
55
  sortDescFirst?: boolean
56
56
  comparator?: (a: unknown, b: unknown, rowA: R, rowB: R) => number
57
- /** Default: true, sobald `field` oder `value` vorhanden ist. */
57
+ /** Default: true as soon as `field` or `value` is present. */
58
58
  searchable?: boolean
59
59
  filterable?: boolean
60
60
  filterType?: FilterType
61
61
  filterFn?: (value: unknown, filterValue: unknown, row: R) => boolean
62
- /** Ersetzt die berechneten Facetten im set/includes-Filter (z. B. im Server-Modus). */
62
+ /** Replaces the computed facets in the set/includes filter (e.g. in server mode). */
63
63
  filterOptions?: FilterOption[]
64
64
  hidden?: boolean
65
65
  hideable?: boolean
@@ -70,28 +70,28 @@ export interface DataTableColumn<R extends Row = Row> {
70
70
  width?: number
71
71
  minWidth?: number
72
72
  maxWidth?: number
73
- /** Anteil am freien Platz; schlägt `width`. */
73
+ /** Share of the free space; beats `width`. */
74
74
  flex?: number
75
- /** Zelltext. Für reichen Inhalt den Slot `cell:<id>` verwenden. */
75
+ /** Cell text. For rich content use the `cell:<id>` slot. */
76
76
  formatter?: (value: unknown, row: R) => string
77
77
  /**
78
- * Wert(e) als farbige Plakette(n) statt als Text rendern. Positive Werte
79
- * grün, negative rot, alles dazwischen regenbogen-abgestuft — siehe
80
- * `badgesForBone` in viurColumns.ts, das diese Funktion für reine
81
- * select- und bool-Bones erzeugt.
78
+ * Render value(s) as colored badge(s) instead of text. Positive values
79
+ * green, negative red, everything in between rainbow-graded — see
80
+ * `badgesForBone` in viurColumns.ts, which creates this function for plain
81
+ * select and bool bones.
82
82
  */
83
83
  badge?: (value: unknown, row: R) => CellBadge[] | null
84
- /** Fußzeile: Text oder Funktion über die gefilterten Zeilen (alle Seiten). */
84
+ /** Footer: text or function over the filtered rows (all pages). */
85
85
  footer?: string | ((rows: R[]) => string)
86
86
  aggregation?: AggregationKind | ((values: unknown[], rows: R[]) => unknown)
87
87
  cellClass?: string | ((value: unknown, row: R) => string)
88
88
  }
89
89
 
90
- /** Spalte mit aufgelösten Defaults — was die Komponente intern benutzt. */
91
- /** Eine farbige Zell-Plakette (siehe DataTableColumn.badge). */
90
+ /** Column with resolved defaults — what the component uses internally. */
91
+ /** A colored cell badge (see DataTableColumn.badge). */
92
92
  export interface CellBadge {
93
93
  label: string
94
- /** CSS-Farbe (Basiston); Hintergrund/Rand mischt die Tabelle selbst. */
94
+ /** CSS color (base tone); the table mixes background/border itself. */
95
95
  color: string
96
96
  }
97
97
 
@@ -107,9 +107,9 @@ export interface ResolvedColumn<R extends Row = Row> {
107
107
  filterable: boolean
108
108
  filterType: FilterType
109
109
  hideable: boolean
110
- /** Zellwert einer Zeile (value() oder Punktpfad). */
110
+ /** Cell value of a row (value() or dot path). */
111
111
  get: (row: R) => unknown
112
- /** Zelltext für Anzeige, Suche als Text, Kopieren und CSV. */
112
+ /** Cell text for display, text search, copying and CSV. */
113
113
  text: (row: R) => string
114
114
  }
115
115
 
@@ -124,13 +124,13 @@ export interface FilterEntry {
124
124
  }
125
125
 
126
126
  export interface ColumnFacets {
127
- /** Verschiedene Werte mit Trefferzahl, absteigend nach Häufigkeit. */
127
+ /** Distinct values with hit count, descending by frequency. */
128
128
  values: { value: string; count: number }[]
129
129
  min?: number
130
130
  max?: number
131
131
  }
132
132
 
133
- /** Anfrage an den Server (dataSource / data-request). */
133
+ /** Request to the server (dataSource / data-request). */
134
134
  export interface DataTableRequest {
135
135
  sort: SortEntry[]
136
136
  filters: FilterEntry[]
@@ -145,7 +145,7 @@ export interface DataTableResponse<R extends Row = Row> {
145
145
  total: number
146
146
  }
147
147
 
148
- /** Serialisierbarer Schnappschuss des vom Nutzer verstellbaren Zustands. */
148
+ /** Serializable snapshot of the user-adjustable state. */
149
149
  export interface DataTableState {
150
150
  version: 1
151
151
  columnOrder?: string[]
@@ -163,7 +163,7 @@ export interface DataTableState {
163
163
 
164
164
  export type RowKey = string | number
165
165
 
166
- /** Knoten der (Baum-/Gruppen-)Hierarchie nach Filter und Sortierung. */
166
+ /** Node of the (tree/group) hierarchy after filtering and sorting. */
167
167
  export interface RowNode<R extends Row = Row> {
168
168
  kind: "data" | "group"
169
169
  key: RowKey
@@ -179,48 +179,48 @@ export interface RowNode<R extends Row = Row> {
179
179
  aggregates?: Record<string, unknown>
180
180
  }
181
181
 
182
- /** Eine gerenderte Zeile (flach, inklusive Detail-Panels). */
182
+ /** A rendered row (flat, including detail panels). */
183
183
  export interface DisplayRow<R extends Row = Row> {
184
184
  kind: "data" | "group" | "detail"
185
- /** Eindeutig im Render-Durchlauf (Detailzeilen haben ein Suffix). */
185
+ /** Unique within the render pass (detail rows have a suffix). */
186
186
  key: string
187
- /** Schlüssel der zugehörigen Zeile/Gruppe. */
187
+ /** Key of the associated row/group. */
188
188
  nodeKey: RowKey
189
189
  depth: number
190
190
  expandable: boolean
191
191
  expanded: boolean
192
- /** Laufender Index der Datenzeilen (nur kind "data"). */
192
+ /** Running index of the data rows (kind "data" only). */
193
193
  index: number
194
194
  /**
195
- * Die DURCHLAUF-Spalten links des eigenen Winkels (Länge = `depth - 1`).
196
- * `true` heißt: dort läuft eine senkrechte Linie durch, weil der Vorfahre
197
- * dieser Spalte noch ein Geschwister nach sich hat.
195
+ * The PASS-THROUGH columns left of the row's own connector (length = `depth - 1`).
196
+ * `true` means: a vertical line runs through there, because the ancestor
197
+ * of this column still has a sibling after it.
198
198
  *
199
- * Wichtig ist die Zuordnung: die Linie in einer Spalte verbindet nicht den
200
- * Vorfahren DIESER Ebene mit seinem Geschwister, sondern den Vorfahren der
201
- * NÄCHSTEN Ebene — genau wie in `tree`-Ausgaben:
199
+ * The mapping matters: the line in a column does not connect the ancestor
200
+ * of THIS level with its sibling, but the ancestor of the
201
+ * NEXT level — just like in `tree` output:
202
202
  *
203
203
  * ```
204
204
  * A
205
205
  * ├── B
206
- * │ └── C ← das "│" steht hier, weil nach B noch D kommt
206
+ * │ └── C ← the "│" is here because D still comes after B
207
207
  * └── D
208
208
  * ```
209
209
  *
210
- * Wurzeln erzeugen keine Durchlauf-Spalte: zwischen zwei Wurzeln wird keine
211
- * Linie gezogen.
210
+ * Roots create no pass-through column: no line is drawn between two
211
+ * roots.
212
212
  */
213
213
  ancestorLines: boolean[]
214
- /** Letztes Kind seines Elternteils → Winkel „└" statt „├". */
214
+ /** Last child of its parent → connector "└" instead of "├". */
215
215
  isLastChild: boolean
216
216
  row?: R
217
217
  node?: RowNode<R>
218
218
  }
219
219
 
220
220
  /**
221
- * Die per `defineExpose` freigegebene API der Komponente. Als Typ für
222
- * Template-Refs verwenden — `InstanceType<typeof DataTable>` funktioniert bei
223
- * generischen Komponenten nicht.
221
+ * The component API exposed via `defineExpose`. Use as the type for
222
+ * template refs — `InstanceType<typeof DataTable>` doesn't work with
223
+ * generic components.
224
224
  */
225
225
  export interface DataTableApi<R extends Row = Row> {
226
226
  getVisibleRows: () => R[]
@@ -240,11 +240,11 @@ export interface DataTableApi<R extends Row = Row> {
240
240
  resetColumns: () => void
241
241
  getColumnFacets: (id: string) => ColumnFacets
242
242
  reload: () => void
243
- /** Nachgeladene Kinder einer Zeile verwerfen und erneut holen. */
243
+ /** Discard a row's lazily loaded children and fetch them again. */
244
244
  reloadChildren: (key: RowKey) => Promise<void>
245
- /** Nachgeladene Kinder einer Zeile (undefined = noch nicht geladen). */
245
+ /** Lazily loaded children of a row (undefined = not loaded yet). */
246
246
  getLoadedChildren: (key: RowKey) => R[] | undefined
247
- /** Nachgeladene Kinder einer Zeile ersetzen, ohne zu laden. */
247
+ /** Replace a row's lazily loaded children without loading. */
248
248
  setLoadedChildren: (key: RowKey, rows: R[]) => void
249
249
  getDataAsCsv: (options?: {
250
250
  columnIds?: string[]