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,25 +1,25 @@
1
- // Spalten-Layout: Reihenfolge, Breiten, Sichtbarkeit, Pinning — plus die
2
- // Zeigergesten für Resize und Reorder. Alles in EINEM reaktiven State-Objekt
3
- // (analog zur Komponenten-Regel), damit die Komponente nur noch rendert.
1
+ // Column layout: order, widths, visibility, pinning — plus the
2
+ // pointer gestures for resize and reorder. Everything in ONE reactive state object
3
+ // (analogous to the component rule), so the component only has to render.
4
4
 
5
5
  import { computed, reactive } from "vue"
6
- import type { PinSide, ResolvedColumn, Row } from "./types"
6
+ import type { PinSide, ResolvedColumn, Row } from "./types.ts"
7
7
 
8
8
  const DEFAULT_WIDTH = 160
9
9
  const DEFAULT_MIN_WIDTH = 64
10
10
 
11
11
  export interface LayoutColumn<R extends Row = Row> {
12
12
  column: ResolvedColumn<R>
13
- /** Index in der sichtbaren Reihenfolge. */
13
+ /** Index in the visible order. */
14
14
  index: number
15
- /** Effektive Breite in px (gemessen oder gesetzt). */
15
+ /** Effective width in px (measured or set). */
16
16
  width: number
17
- /** Spaltenbreite für grid-template-columns. */
17
+ /** Column width for grid-template-columns. */
18
18
  track: string
19
19
  pinned: PinSide | false
20
- /** Sticky-Abstand zur jeweiligen Kante (px). */
20
+ /** Sticky offset to the respective edge (px). */
21
21
  offset: number
22
- /** Letzte am Start bzw. erste am Ende gepinnte Spalte (für die Trennlinie). */
22
+ /** Last column pinned at the start or first pinned at the end (for the divider). */
23
23
  pinEdge: boolean
24
24
  resizable: boolean
25
25
  movable: boolean
@@ -45,19 +45,19 @@ function clampWidth<R extends Row>(column: ResolvedColumn<R>, width: number): nu
45
45
 
46
46
  export function useColumnLayout<R extends Row = Row>(options: ColumnLayoutOptions<R>) {
47
47
  const state = reactive({
48
- /** Vom Nutzer gesetzte Reihenfolge (ids). Leer = Reihenfolge der Definitionen. */
48
+ /** User-set order (ids). Empty = order of the definitions. */
49
49
  order: [] as string[],
50
- /** Vom Nutzer gezogene Breiten. */
50
+ /** Widths dragged by the user. */
51
51
  widths: {} as Record<string, number>,
52
- /** Sichtbarkeit; fehlt ein Eintrag, gilt `hidden` aus der Definition. */
52
+ /** Visibility; if an entry is missing, `hidden` from the definition applies. */
53
53
  visibility: {} as Record<string, boolean>,
54
- /** Pinning; fehlt ein Eintrag, gilt `pinned` aus der Definition. */
54
+ /** Pinning; if an entry is missing, `pinned` from the definition applies. */
55
55
  pinning: {} as Record<string, PinSide | false>,
56
- /** Im DOM gemessene Breiten — Grundlage der Sticky-Abstände. */
56
+ /** Widths measured in the DOM — basis for the sticky offsets. */
57
57
  measured: {} as Record<string, number>,
58
- /** Laufende Resize-Geste. */
58
+ /** Ongoing resize gesture. */
59
59
  resizing: null as { id: string; startX: number; startWidth: number } | null,
60
- /** Laufende Reorder-Geste. */
60
+ /** Ongoing reorder gesture. */
61
61
  dragging: null as { id: string; overId: string | null; x: number; y: number } | null,
62
62
 
63
63
  ordered: computed((): ResolvedColumn<R>[] => {
@@ -72,11 +72,11 @@ export function useColumnLayout<R extends Row = Row>(options: ColumnLayoutOption
72
72
  byId.delete(id)
73
73
  }
74
74
  }
75
- // Neue Spalten, die die gespeicherte Reihenfolge nicht kennt, hinten dran.
75
+ // New columns unknown to the saved order go at the end.
76
76
  return [...sorted, ...byId.values()]
77
77
  }),
78
78
 
79
- /** Sichtbare Spalten in Anzeigereihenfolge: gepinnt (Start) → frei → gepinnt (Ende). */
79
+ /** Visible columns in display order: pinned (start) → free → pinned (end). */
80
80
  visibleColumns: computed((): LayoutColumn<R>[] => {
81
81
  const visible = state.ordered.filter((column: ResolvedColumn<R>) => isVisible(column))
82
82
  const pinnedStart = visible.filter((column: ResolvedColumn<R>) => pinOf(column) === "start")
@@ -128,15 +128,15 @@ export function useColumnLayout<R extends Row = Row>(options: ColumnLayoutOption
128
128
  return state.pinning[column.id] ?? column.def.pinned ?? false
129
129
  }
130
130
 
131
- /** Effektive Breite: gezogen > gemessen > definiert > Default. */
131
+ /** Effective width: dragged > measured > defined > default. */
132
132
  function widthOf(column: ResolvedColumn<R>): number {
133
133
  const width = state.widths[column.id] ?? state.measured[column.id] ?? column.def.width ?? DEFAULT_WIDTH
134
134
  return clampWidth(column, width)
135
135
  }
136
136
 
137
137
  function trackOf(column: ResolvedColumn<R>): string {
138
- // Eine vom Nutzer gezogene Breite gewinnt IMMER — auch gegen flex, sonst
139
- // ließe sich eine flex-Spalte nicht von Hand skalieren.
138
+ // A width dragged by the user ALWAYS wins — even over flex, otherwise
139
+ // a flex column couldn't be resized by hand.
140
140
  const dragged = state.widths[column.id]
141
141
  if (dragged !== undefined) return `${clampWidth(column, dragged)}px`
142
142
  const fixed = column.def.width
@@ -153,7 +153,7 @@ export function useColumnLayout<R extends Row = Row>(options: ColumnLayoutOption
153
153
  state.measured[id] = width
154
154
  }
155
155
 
156
- // ── Sichtbarkeit ────────────────────────────────────────────────────
156
+ // ── Visibility ──────────────────────────────────────────────────────
157
157
  function toggleColumn(id: string, visible?: boolean, silent = false): void {
158
158
  const column = options.columns().find((item) => item.id === id)
159
159
  if (!column) return
@@ -164,8 +164,8 @@ export function useColumnLayout<R extends Row = Row>(options: ColumnLayoutOption
164
164
 
165
165
  // ── Pinning ─────────────────────────────────────────────────────────
166
166
  function pinColumn(id: string, side: PinSide | false, silent = false): void {
167
- // Ohne feste Breite die aktuelle Breite einfrieren — sonst springt die
168
- // Spalte beim Pinnen, weil der Sticky-Abstand eine px-Breite braucht.
167
+ // Without a fixed width, freeze the current width — otherwise the column
168
+ // jumps when pinned, because the sticky offset needs a px width.
169
169
  const column = options.columns().find((item) => item.id === id)
170
170
  if (column && side && state.widths[id] === undefined && column.def.width === undefined) {
171
171
  state.widths[id] = widthOf(column)
@@ -179,7 +179,7 @@ export function useColumnLayout<R extends Row = Row>(options: ColumnLayoutOption
179
179
  return column ? pinOf(column) : false
180
180
  }
181
181
 
182
- // ── Reihenfolge ─────────────────────────────────────────────────────
182
+ // ── Order ───────────────────────────────────────────────────────────
183
183
  function currentOrder(): string[] {
184
184
  return state.ordered.map((column: ResolvedColumn<R>) => column.id)
185
185
  }
@@ -195,7 +195,7 @@ export function useColumnLayout<R extends Row = Row>(options: ColumnLayoutOption
195
195
  options.onMove?.(order, finished)
196
196
  }
197
197
 
198
- /** Spalte um `delta` Positionen verschieben (Tastatur: Shift + ←/→). */
198
+ /** Move a column by `delta` positions (keyboard: Shift + ←/→). */
199
199
  function shiftColumn(id: string, delta: number): void {
200
200
  const visible = state.visibleColumns.map((item: LayoutColumn<R>) => item.column.id)
201
201
  const index = visible.indexOf(id)
@@ -250,7 +250,7 @@ export function useColumnLayout<R extends Row = Row>(options: ColumnLayoutOption
250
250
  if (active?.overId && active.overId !== active.id) moveColumn(active.id, active.overId, true)
251
251
  }
252
252
 
253
- // ── Zurücksetzen / Zustand ──────────────────────────────────────────
253
+ // ── Reset / state ───────────────────────────────────────────────────
254
254
  function resetColumns(): void {
255
255
  state.order = []
256
256
  state.widths = {}
@@ -258,7 +258,7 @@ export function useColumnLayout<R extends Row = Row>(options: ColumnLayoutOption
258
258
  state.pinning = {}
259
259
  }
260
260
 
261
- /** Spalten-ids, die es nicht mehr gibt, aus dem Zustand entfernen. */
261
+ /** Remove column ids that no longer exist from the state. */
262
262
  function pruneUnknown(): void {
263
263
  const ids = new Set(options.columns().map((column) => column.id))
264
264
  state.order = state.order.filter((id) => ids.has(id))
@@ -1,35 +1,35 @@
1
- // Tastaturnavigation nach dem ARIA-Grid-Muster: Das ganze Gitter ist EIN
2
- // Tab-Stop, innerhalb wandert ein Roving-Tabindex über die Zellen.
1
+ // Keyboard navigation following the ARIA grid pattern: the whole grid is ONE
2
+ // tab stop; inside it a roving tabindex moves across the cells.
3
3
  //
4
- // Die Composable kennt nur Koordinaten (Zeile -1 = Kopfzeile, 0..n-1 = Body)
5
- // und ruft für alles Fachliche Callbacks der Komponente.
4
+ // The composable only knows coordinates (row -1 = header row, 0..n-1 = body)
5
+ // and calls the component's callbacks for all domain logic.
6
6
 
7
7
  import { nextTick, reactive, type Ref } from "vue"
8
8
 
9
9
  export interface GridNavigationOptions {
10
10
  rowCount: () => number
11
11
  colCount: () => number
12
- /** Zeilen pro Seite für Bild auf/ab. */
12
+ /** Rows per page for Page Up/Down. */
13
13
  pageRows?: () => number
14
- /** Enter auf einer Kopfzelle. */
14
+ /** Enter on a header cell. */
15
15
  onHeaderActivate?: (col: number, shift: boolean) => void
16
- /** Enter auf einer Datenzelle. */
16
+ /** Enter on a data cell. */
17
17
  onCellActivate?: (row: number, col: number) => void
18
- /** Leertaste auf einer Zeile. */
18
+ /** Space on a row. */
19
19
  onToggleSelect?: (row: number) => void
20
20
  /** Shift + ↑/↓ */
21
21
  onExtendSelection?: (row: number) => void
22
- /** Strg + A */
22
+ /** Ctrl + A */
23
23
  onSelectAll?: () => void
24
- /** Strg + C */
24
+ /** Ctrl + C */
25
25
  onCopy?: () => void
26
- /** Shift + F10 / Kontextmenü-Taste */
26
+ /** Shift + F10 / context menu key */
27
27
  onContextMenu?: (row: number, col: number) => void
28
- /** Alt + ↑/↓ auf einer Datenzeile (feste Reihenfolge). */
28
+ /** Alt + ↑/↓ on a data row (fixed order). */
29
29
  onMoveRow?: (row: number, delta: number) => void
30
- /** Shift + ←/→ auf einer Kopfzelle. */
30
+ /** Shift + ←/→ on a header cell. */
31
31
  onMoveColumn?: (col: number, delta: number) => void
32
- /** Alt + ←/→ auf einer Kopfzelle. */
32
+ /** Alt + ←/→ on a header cell. */
33
33
  onResizeColumn?: (col: number, delta: number) => void
34
34
  }
35
35
 
@@ -37,7 +37,7 @@ const RESIZE_STEP = 24
37
37
 
38
38
  export function useGridNavigation(scroller: Ref<HTMLElement | undefined>, options: GridNavigationOptions) {
39
39
  const state = reactive({
40
- /** -1 = Kopfzeile. */
40
+ /** -1 = header row. */
41
41
  row: -1,
42
42
  col: 0,
43
43
  })
@@ -1,13 +1,13 @@
1
- // Virtualisierung: nur das sichtbare Fenster (plus Overscan) rendert.
1
+ // Virtualization: only the visible window (plus overscan) is rendered.
2
2
  //
3
- // Höhen sind nicht überall gleich — Detail-Panels dürfen beliebig hoch sein.
4
- // Deshalb wird ein Präfixsummen-Array über die Zeilenhöhen geführt (gemessene
5
- // Höhen überschreiben die Standardhöhe) und darin binär gesucht. Das Fenster
6
- // wird über Padding oben/unten positioniert, nicht über absolute Positionen —
7
- // so bleiben Grid-Spalten und Sticky-Pinning unangetastet.
3
+ // Heights are not the same everywhere — detail panels may be arbitrarily tall.
4
+ // That's why a prefix-sum array over the row heights is maintained (measured
5
+ // heights override the default height) and binary-searched. The window
6
+ // is positioned via top/bottom padding, not via absolute positions —
7
+ // so grid columns and sticky pinning remain untouched.
8
8
 
9
9
  import { computed, reactive, type Ref } from "vue"
10
- import type { DisplayRow, Row } from "./types"
10
+ import type { DisplayRow, Row } from "./types.ts"
11
11
 
12
12
  export interface VirtualRowsOptions<R extends Row = Row> {
13
13
  rows: () => DisplayRow<R>[]
@@ -25,7 +25,7 @@ export function useVirtualRows<R extends Row = Row>(
25
25
  const state = reactive({
26
26
  scrollTop: 0,
27
27
  viewportHeight: 0,
28
- /** Gemessene Höhen (nur für Zeilen, die von der Standardhöhe abweichen). */
28
+ /** Measured heights (only for rows that deviate from the default height). */
29
29
  heights: {} as Record<string, number>,
30
30
 
31
31
  offsets: computed((): number[] => {
@@ -74,7 +74,7 @@ export function useVirtualRows<R extends Row = Row>(
74
74
  }),
75
75
  })
76
76
 
77
- /** Größter Index, dessen Offset <= position ist (binäre Suche). */
77
+ /** Largest index whose offset is <= position (binary search). */
78
78
  function findIndex(offsets: number[], position: number): number {
79
79
  let low = 0
80
80
  let high = offsets.length - 1
@@ -97,7 +97,7 @@ export function useVirtualRows<R extends Row = Row>(
97
97
  syncViewport()
98
98
  }
99
99
 
100
- /** Höhe einer abweichenden Zeile (Detail-Panel) hinterlegen. */
100
+ /** Store the height of a deviating row (detail panel). */
101
101
  function measureRow(key: string, height: number): void {
102
102
  if (height <= 0) return
103
103
  if (Math.abs((state.heights[key] ?? 0) - height) < 1) return
@@ -1,38 +1,38 @@
1
- import type { CellBadge, DataTableColumn, Row } from "./types"
2
- import { compareValues, firstValueOf } from "./columns"
3
- // @ts-expect-error – JS-Modul ohne Typen
1
+ import type { CellBadge, DataTableColumn, Row } from "./types.ts"
2
+ import { compareValues, firstValueOf } from "./columns.ts"
3
+ // @ts-expect-error – JS module without types
4
4
  import Utils from "../../forms/bones/utils.js"
5
5
 
6
6
  // --------------------------------------------------------------------------- //
7
- // ViUR-Struktur → Spalten der DataTable //
7
+ // ViUR structure → DataTable columns //
8
8
  // --------------------------------------------------------------------------- //
9
9
  //
10
- // Eine ViUR-Liste liefert neben den Datensätzen ihre `structure`: je Bone ein
11
- // Objekt mit `descr` (Beschriftung), `type` ("str", "str.email", "numeric",
12
- // "select", "relational.…"), `multiple`, `languages`, `indexed`, `visible` und
13
- // typabhängigen Zusätzen (`values` bei select, `precision` bei numeric,
14
- // `format` bei relationalen Bones).
10
+ // Besides the records, a ViUR list delivers its `structure`: per bone an
11
+ // object with `descr` (label), `type` ("str", "str.email", "numeric",
12
+ // "select", "relational.…"), `multiple`, `languages`, `indexed`, `visible` and
13
+ // type-specific extras (`values` for select, `precision` for numeric,
14
+ // `format` for relational bones).
15
15
  //
16
- // Hier entstehen daraus Spalten: Beschriftung, Ausrichtung, Sortierbarkeit und
17
- // ein `formatter`. Die Übersetzung Wert → Text macht `Utils.boneToString`
18
- // (src/forms/bones/utils.js) — die EINE Stelle dafür, die auch die Formulare
19
- // und Formatstrings der Bones benutzen.
16
+ // This turns them into columns: label, alignment, sortability and
17
+ // a `formatter`. The value → text conversion is done by `Utils.boneToString`
18
+ // (src/forms/bones/utils.js) — the ONE place for it, which the forms and the
19
+ // bones' format strings use as well.
20
20
 
21
- /** Ein Bone der Struktur, wie der Renderer ihn liefert. */
21
+ /** A bone of the structure, as delivered by the renderer. */
22
22
  export interface BoneDescription {
23
23
  descr?: string
24
24
  type?: string
25
25
  multiple?: boolean
26
26
  languages?: string[] | null
27
- /** Nur indizierte Bones lassen sich serverseitig sortieren. */
27
+ /** Only indexed bones can be sorted server-side. */
28
28
  indexed?: boolean
29
29
  visible?: boolean
30
30
  readonly?: boolean
31
- /** select: Wert → Beschriftung. */
31
+ /** select: value → label. */
32
32
  values?: Record<string, string> | [string, string][]
33
- /** numeric: Nachkommastellen. */
33
+ /** numeric: decimal places. */
34
34
  precision?: number
35
- /** date: was der Bone überhaupt führt. */
35
+ /** date: what the bone actually holds. */
36
36
  date?: boolean
37
37
  time?: boolean
38
38
  params?: Record<string, unknown> | null
@@ -43,49 +43,49 @@ export type BoneStructure = Record<string, BoneDescription>
43
43
 
44
44
  export interface ColumnsFromStructureOptions<R extends Row = Row> {
45
45
  /**
46
- * Sprache mehrsprachiger Bones: ein Code zeigt diese Sprache (leer →
47
- * erste befüllte, mit ihrer Flagge gekennzeichnet), `"*"` zeigt ALLE
48
- * befüllten Sprachen mit Flaggen-Präfix; ohne Angabe die erste des Bones.
46
+ * Language of multilingual bones: a code shows that language (empty →
47
+ * first filled one, marked with its flag), `"*"` shows ALL filled
48
+ * languages with a flag prefix; if unset, the bone's first language.
49
49
  */
50
50
  language?: string | null
51
- /** Steht im `"*"`-Modus vorn — üblicherweise die UI-Sprache. */
51
+ /** Comes first in `"*"` mode — usually the UI language. */
52
52
  primaryLanguage?: string | null
53
- /** Gebietseinstellung für Zahlen und Datumsangaben. */
53
+ /** Locale for numbers and dates. */
54
54
  locale?: string
55
- /** Nur diese Bones, in dieser Reihenfolge (Standard: alle sichtbaren). */
55
+ /** Only these bones, in this order (default: all visible ones). */
56
56
  bones?: string[]
57
- /** Diese Bones nie zeigen (zusätzlich zu den ohnehin verborgenen). */
57
+ /** Never show these bones (in addition to the ones hidden anyway). */
58
58
  exclude?: string[]
59
- /** Beschriftung für `bool`-Werte. */
59
+ /** Labels for `bool` values. */
60
60
  boolTexts?: [string, string]
61
- /** Ergänzt/überschreibt die erzeugte Spalte je Bone. */
61
+ /** Extends/overrides the generated column per bone. */
62
62
  overrides?: Record<string, Partial<DataTableColumn<R>>>
63
63
  /**
64
- * Der VOLLSTÄNDIGE Datensatz liegt im Client (alles geladen): Sortieren,
65
- * Suchen und Spaltenfilter laufen dann lokal auf allen Spalten — ohne den
66
- * Server. Ohne das Flag bleibt beides aus, denn auf einem ANGELADENEN
67
- * Ausschnitt wären lokale Sortierung/Filter eine Lüge; dort sortiert der
68
- * Server (nur indizierte Bones, über die Tabellen-Ereignisse).
64
+ * The COMPLETE dataset is in the client (everything loaded): sorting,
65
+ * searching and column filters then run locally on all columns — without
66
+ * the server. Without the flag both stay off, because on a PARTIALLY loaded
67
+ * slice local sorting/filtering would be a lie; there the server sorts
68
+ * (indexed bones only, via the table events).
69
69
  */
70
70
  local?: boolean
71
71
  }
72
72
 
73
- /** Bones, die in einer Liste nichts zu suchen haben. */
73
+ /** Bones that have no place in a list. */
74
74
  const HIDDEN_TYPES = ["password", "credential"]
75
75
 
76
76
  // --------------------------------------------------------------------------- //
77
- // Plaketten für reine select- und bool-Bones //
77
+ // Badges for plain select and bool bones //
78
78
  // --------------------------------------------------------------------------- //
79
79
  //
80
- // Reine Auswahlwerte ("select" ohne Untertyp — country & Co. bleiben Text) und
81
- // bool-Werte werden als farbige Plakette gerendert: POSITIVE Werte grün,
82
- // NEGATIVE rot, alle übrigen Optionen bekommen regenbogen-abgestufte Farbtöne
83
- // (gleichmäßig über den Farbkreis, stabil je Optionsreihenfolge). Ob ein Wert
84
- // positiv/negativ ist, entscheidet die Schlüsselwortliste unten — geprüft
85
- // werden Options-SCHLÜSSEL und -BESCHRIFTUNG, unabhängig von Groß-/
86
- // Kleinschreibung ("Ja", "ja", "yes", "Aktiv", "visible", …).
80
+ // Plain choice values ("select" without subtype — country & co. stay text) and
81
+ // bool values are rendered as colored badges: POSITIVE values green,
82
+ // NEGATIVE red, all other options get rainbow-graded hues
83
+ // (evenly across the color wheel, stable per option order). Whether a value
84
+ // is positive/negative is decided by the keyword list below — both option
85
+ // KEYS and LABELS are checked, regardless of upper/lower
86
+ // case ("Ja", "ja", "yes", "Aktiv", "visible", …).
87
87
 
88
- /** Positive Werte → grün. Erweiterbar (z. B. `POSITIVE_KEYWORDS.add("live")`). */
88
+ /** Positive values → green. Extensible (e.g. `POSITIVE_KEYWORDS.add("live")`). */
89
89
  export const POSITIVE_KEYWORDS = new Set([
90
90
  "yes",
91
91
  "ja",
@@ -109,7 +109,7 @@ export const POSITIVE_KEYWORDS = new Set([
109
109
  "online",
110
110
  ])
111
111
 
112
- /** Negative Werte → rot. */
112
+ /** Negative values → red. */
113
113
  export const NEGATIVE_KEYWORDS = new Set([
114
114
  "no",
115
115
  "nein",
@@ -151,7 +151,7 @@ function keywordTone(...candidates: unknown[]): string | null {
151
151
  return null
152
152
  }
153
153
 
154
- /** select-`values` vereinheitlichen: [ [schlüssel, beschriftung], … ]. */
154
+ /** Normalize select `values`: [ [key, label], … ]. */
155
155
  function selectOptions(bone: BoneDescription): [string, string][] {
156
156
  const values = bone.values
157
157
  if (Array.isArray(values)) return values.map(([key, label]) => [String(key), String(label)])
@@ -160,9 +160,9 @@ function selectOptions(bone: BoneDescription): [string, string][] {
160
160
  }
161
161
 
162
162
  /**
163
- * Farbzuordnung der Optionen eines reinen select-Bones: Schlüsselwörter
164
- * entscheiden grün/rot, die übrigen Optionen teilen sich den Farbkreis
165
- * (Regenbogen), in der Reihenfolge ihrer Definition.
163
+ * Color mapping for the options of a plain select bone: keywords
164
+ * decide green/red, the remaining options share the color wheel
165
+ * (rainbow), in the order of their definition.
166
166
  */
167
167
  export function selectToneMap(bone: BoneDescription): Map<string, { label: string; color: string }> {
168
168
  const options = selectOptions(bone)
@@ -174,7 +174,7 @@ export function selectToneMap(bone: BoneDescription): Map<string, { label: strin
174
174
  else neutrals.push([key, label])
175
175
  }
176
176
  neutrals.forEach(([key, label], index) => {
177
- // Regenbogen: gleichmäßig verteilte Farbtöne, Sättigung/Helligkeit fest.
177
+ // Rainbow: evenly distributed hues, fixed saturation/lightness.
178
178
  const hue = Math.round((360 / Math.max(1, neutrals.length)) * index)
179
179
  map.set(key, { label, color: `oklch(0.62 0.16 ${hue})` })
180
180
  })
@@ -182,8 +182,8 @@ export function selectToneMap(bone: BoneDescription): Map<string, { label: strin
182
182
  }
183
183
 
184
184
  /**
185
- * Plaketten-Funktion für einen Bone — oder null, wenn der Bone keine bekommt.
186
- * Nur REINE selects (`type === "select"`, kein select.country o. Ä.) und bools.
185
+ * Badge function for a bone — or null if the bone doesn't get any.
186
+ * Only PLAIN selects (`type === "select"`, no select.country or similar) and bools.
187
187
  */
188
188
  export function badgesForBone(
189
189
  bone: BoneDescription,
@@ -211,21 +211,21 @@ export function badgesForBone(
211
211
  }
212
212
  }
213
213
 
214
- /** Typfamilie eines Bones: "str.email" → "str". */
214
+ /** Type family of a bone: "str.email" → "str". */
215
215
  export function boneFamily(type: string | undefined | null): string {
216
216
  return Utils.boneFamily(type) as string
217
217
  }
218
218
 
219
219
  /**
220
- * Bone-Wert eines Datensatzes als Text — mit Sprachen, `multiple` und dem
221
- * Formatstring relationaler Bones. Dünne Hülle um `Utils.boneToString`, damit
222
- * Tabellen-Code bei den DataTable-Typen bleiben kann.
220
+ * Bone value of a record as text — with languages, `multiple` and the
221
+ * format string of relational bones. Thin wrapper around `Utils.boneToString`
222
+ * so table code can stay with the DataTable types.
223
223
  */
224
224
  export function boneText(value: unknown, bone: BoneDescription, options: ColumnsFromStructureOptions = {}): string {
225
225
  return Utils.boneToString(value, bone, options) as string
226
226
  }
227
227
 
228
- /** Lokaler Spaltenfilter passend zum Bone-Typ. */
228
+ /** Local column filter matching the bone type. */
229
229
  function localFilterFor(bone: BoneDescription, options: ColumnsFromStructureOptions): Partial<DataTableColumn> {
230
230
  const family = boneFamily(bone.type)
231
231
  if (bone.type === "bool") {
@@ -251,10 +251,10 @@ function localFilterFor(bone: BoneDescription, options: ColumnsFromStructureOpti
251
251
  const isEmptyValue = (value: unknown): boolean => value === null || value === undefined || value === ""
252
252
 
253
253
  /**
254
- * Sortierwert eines MEHRSPRACHIGEN Bones: dieselbe Sprache, die die Anzeige
255
- * nach vorn stellt — die gewünschte (bzw. bei `"*"` die primäre), und wenn
256
- * die leer ist, die erste befüllte. So sortiert die Spalte nach dem Text, den
257
- * die Zelle tatsächlich zeigt, nicht stur nach der ersten Bone-Sprache.
254
+ * Sort value of a MULTILINGUAL bone: the same language the display puts
255
+ * first — the requested one (or the primary one for `"*"`), and if that is
256
+ * empty, the first filled one. This way the column sorts by the text the
257
+ * cell actually shows, not blindly by the bone's first language.
258
258
  */
259
259
  function languageSortValue(value: unknown, languages: string[], options: ColumnsFromStructureOptions): unknown {
260
260
  if (!value || typeof value !== "object" || Array.isArray(value)) return value
@@ -267,18 +267,18 @@ function languageSortValue(value: unknown, languages: string[], options: Columns
267
267
  return record[lang]
268
268
  }
269
269
 
270
- /** Zahlen rechts, alles andere links — wie in Tabellen üblich. */
270
+ /** Numbers right, everything else left — as usual in tables. */
271
271
  function alignOf(bone: BoneDescription): "start" | "end" {
272
272
  return boneFamily(bone.type) === "numeric" && !bone.multiple ? "end" : "start"
273
273
  }
274
274
 
275
275
  /**
276
- * Spalten aus einer ViUR-Struktur.
276
+ * Columns from a ViUR structure.
277
277
  *
278
- * Sortierbar wird eine Spalte nur, wenn der Bone `indexed` ist und weder
279
- * `multiple` noch mehrsprachig — nach allem anderen kann der Server nicht
280
- * ordnen. Suche und Spaltenfilter bleiben aus: die macht bei einer Liste der
281
- * Server, der Aufrufer verdrahtet sie über die Ereignisse der Tabelle.
278
+ * A column only becomes sortable if the bone is `indexed` and neither
279
+ * `multiple` nor multilingual — the server can't order by anything else.
280
+ * Search and column filters stay off: for a list the server handles those,
281
+ * the caller wires them up via the table's events.
282
282
  */
283
283
  export function columnsFromStructure<R extends Row = Row>(
284
284
  structure: BoneStructure | null | undefined,
@@ -295,8 +295,8 @@ export function columnsFromStructure<R extends Row = Row>(
295
295
  if (bone.visible === false) continue
296
296
  if (HIDDEN_TYPES.includes(boneFamily(bone.type))) continue
297
297
 
298
- // Server-Modus: nur indizierte Bones sind sortierbar; lokal (alles
299
- // geladen) darf nach jeder Spalte sortiert, gesucht und gefiltert werden.
298
+ // Server mode: only indexed bones are sortable; locally (everything
299
+ // loaded) every column may be sorted, searched and filtered.
300
300
  const sortable = options.local === true || (bone.indexed === true && !bone.multiple && !bone.languages?.length)
301
301
  const column: DataTableColumn<R> = {
302
302
  field: name,
@@ -309,7 +309,7 @@ export function columnsFromStructure<R extends Row = Row>(
309
309
  minWidth: 120,
310
310
  flex: 1,
311
311
  }
312
- // Mehrsprachig: nach der ANGEZEIGTEN Sprache sortieren (siehe oben).
312
+ // Multilingual: sort by the DISPLAYED language (see above).
313
313
  if (bone.languages?.length) {
314
314
  const languages = bone.languages
315
315
  column.comparator = (a, b) =>
@@ -320,7 +320,7 @@ export function columnsFromStructure<R extends Row = Row>(
320
320
  )
321
321
  }
322
322
 
323
- // Reine selects und bools als farbige Plaketten (siehe badgesForBone).
323
+ // Plain selects and bools as colored badges (see badgesForBone).
324
324
  const badge = badgesForBone(bone, options as ColumnsFromStructureOptions)
325
325
  if (badge) column.badge = (value) => badge(value)
326
326
  if (options.local === true) Object.assign(column, localFilterFor(bone, options as ColumnsFromStructureOptions))