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,69 +1,69 @@
1
1
  import type { DefineComponent, Ref } from "vue"
2
- import type { Row, RowKey } from "./types"
2
+ import type { Row, RowKey } from "./types.ts"
3
3
 
4
- /** Props von ViurDataTable.vue (siehe defineProps dort). */
4
+ /** Props of ViurDataTable.vue (see defineProps there). */
5
5
  export interface ViurDataTableProps {
6
- /** Modulname bzw. Punkt-Pfad ("user", "shop.order"). */
6
+ /** Module name or dot path ("user", "shop.order"). */
7
7
  module: string
8
- /** Renderer der Requests; Standard: VITE_DEFAULT_RENDERER. */
8
+ /** Renderer for the requests; default: VITE_DEFAULT_RENDERER. */
9
9
  renderer?: string | null
10
- /** Untertyp gruppierter Listen (Handler `list.grouped`). */
10
+ /** Subtype of grouped lists (handler `list.grouped`). */
11
11
  group?: string | null
12
- /** Zusätzliche Request-Parameter — bei jedem Abruf gespreadet. */
12
+ /** Additional request parameters — spread into every fetch. */
13
13
  params?: Record<string, unknown>
14
- /** Spalten; ohne Angabe aus der viewSkel-Struktur abgeleitet. */
14
+ /** Columns; if not given, derived from the viewSkel structure. */
15
15
  columns?: unknown[] | null
16
- /** Nur diese Bones als Spalten (bei abgeleiteten Spalten). */
16
+ /** Only these bones as columns (for derived columns). */
17
17
  bones?: string[] | null
18
18
  label?: string | null
19
19
  selectable?: false | "single" | "multiple"
20
- /** Einträge je Nachladen (ViUR `limit`). */
20
+ /** Entries per load (ViUR `limit`). */
21
21
  pageSize?: number
22
- /** Obergrenze des Lokal-Modus (alles geladen → Client-Pipeline). */
22
+ /** Upper limit for local mode (everything loaded → client pipeline). */
23
23
  localModeLimit?: number
24
- /** X-VIUR-BONELIST senden, wenn Felder abgewählt sind (Server braucht ein "*"-Subskel). */
24
+ /** Send X-VIUR-BONELIST when fields are deselected (server needs a "*" subskel). */
25
25
  bonelist?: boolean
26
- /** localStorage-Key der Feld-Sichtbarkeit; null = keine Persistenz. */
26
+ /** localStorage key for field visibility; null = no persistence. */
27
27
  storageKey?: string | null
28
28
  language?: string | null
29
- /** Steht bei language="*" vorn (üblicherweise die UI-Sprache). */
29
+ /** Comes first with language="*" (usually the UI language). */
30
30
  primaryLanguage?: string | null
31
31
  locale?: string | null
32
- /** Spaltenbreite ziehbar (Default true — anders als die nackte DataTable). */
32
+ /** Column width draggable (default true — unlike the bare DataTable). */
33
33
  resizable?: boolean
34
- /** Spalten per Drag umsortierbar (Default true). */
34
+ /** Columns reorderable via drag (default true). */
35
35
  reorderable?: boolean
36
- /** Spalten anpinnbar (Default true). */
36
+ /** Columns pinnable (default true). */
37
37
  pinnable?: boolean
38
- /** Wird an die DataTable durchgereicht. */
38
+ /** Passed through to the DataTable. */
39
39
  tableProps?: Record<string, unknown>
40
40
  }
41
41
 
42
42
  export interface ViurDataTableEmitProps<R extends Row = Row> {
43
- /** Doppelklick auf eine Zeile. */
43
+ /** Double-click on a row. */
44
44
  onOpen?: (payload: { row: R; key: RowKey }) => void
45
45
  /** v-model:selectedKeys */
46
46
  "onUpdate:selectedKeys"?: (value: RowKey[]) => void
47
47
  }
48
48
 
49
- /** Was ViurDataTable per defineExpose freigibt. */
49
+ /** What ViurDataTable exposes via defineExpose. */
50
50
  export interface ViurDataTableApi<R extends Row = Row> {
51
- /** Der ListRequest (useListFetch): data, structure, cursor, execute, … */
51
+ /** The ListRequest (useListFetch): data, structure, cursor, execute, … */
52
52
  list: Record<string, unknown>
53
- /** Template-Ref auf die DataTable (DataTableApi). */
53
+ /** Template ref to the DataTable (DataTableApi). */
54
54
  table: Ref<unknown>
55
- /** Von vorn laden — mit den aktuellen props.params. */
55
+ /** Load from the start — with the current props.params. */
56
56
  refresh: () => Promise<unknown>
57
- /** Suche: serverseitig (ViUR `search`) bzw. lokal, wenn alles geladen. */
57
+ /** Search: server-side (ViUR `search`) or local, if everything is loaded. */
58
58
  search: (term: string) => void
59
- /** Feld-Sichtbarkeit (z.B. für einen „Felder"-Dialog). */
59
+ /** Field visibility (e.g. for a "Fields" dialog). */
60
60
  columns: {
61
61
  all: () => { id: string; label: string; hidden: boolean }[]
62
62
  apply: (visibleIds: string[]) => void
63
63
  }
64
- /** Beschriftung eines Eintrags (erster Textwert seiner Spalten). */
64
+ /** Label of an entry (first text value of its columns). */
65
65
  entryTitle: (key: RowKey) => string
66
- /** Alles geladen → Sortieren/Filtern/Suchen laufen lokal. */
66
+ /** Everything loaded → sorting/filtering/searching run locally. */
67
67
  allLoaded: Ref<boolean>
68
68
  rows: Ref<R[]>
69
69
  }
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="vi-vdt">
3
- <!-- Struktur kam nicht an (Recht fehlt, unerwartete Antwort): Spalten sind
4
- dann aus den Daten abgeleitet — Hinweis bleibt stehen. -->
3
+ <!-- Structure didn't arrive (missing permission, unexpected response): columns
4
+ are then derived from the data — the notice stays visible. -->
5
5
  <wa-callout
6
6
  v-if="notice"
7
7
  class="vi-vdt-notice"
@@ -53,61 +53,61 @@ import { computed, ref, watch } from "vue"
53
53
  import { useListFetch } from "../../request/index.js"
54
54
  import { useI18n } from "vue-i18n"
55
55
  import DataTable from "./DataTable.vue"
56
- import { columnsFromStructure } from "./viurColumns"
56
+ import { columnsFromStructure } from "./viurColumns.ts"
57
57
 
58
- // ViurDataTable: dünner ViUR-Wrapper um die DataTable für LIST-Module
59
- // (Pendant zum ViurBrowser für Baum-Module): `module` rein, fertig. Enthält die komplette
60
- // Listen-Mechanik — Server-Sortierung/-Suche, Cursor-Nachladen, Lokal-Modus
61
- // (alles geladen → Sortieren/Filtern/Suchen clientseitig), Spalten aus der
62
- // Struktur, Feld-Sichtbarkeit inkl. optionaler Persistenz und optionalem
63
- // X-VIUR-BONELIST (Server serialisiert nur die sichtbaren Felder).
58
+ // ViurDataTable: thin ViUR wrapper around the DataTable for LIST modules
59
+ // (counterpart to ViurBrowser for tree modules): pass `module`, done. Contains the complete
60
+ // list mechanics — server sorting/search, cursor-based loading, local mode
61
+ // (everything loaded → sorting/filtering/searching client-side), columns from the
62
+ // structure, field visibility incl. optional persistence and optional
63
+ // X-VIUR-BONELIST (server serializes only the visible fields).
64
64
 
65
65
  const props = defineProps({
66
66
  module: { type: String, required: true },
67
67
  renderer: { type: String, default: null },
68
- /** Untertyp gruppierter Listen (Handler `list.grouped`). */
68
+ /** Subtype of grouped lists (handler `list.grouped`). */
69
69
  group: { type: String, default: null },
70
- /** Zusätzliche Request-Parameter — bei JEDEM Abruf gespreadet (z.B. der
71
- * Server-Context als @-Parameter). Änderungen greifen ab dem nächsten
72
- * Abruf; sofort neu laden macht `refresh()`. */
70
+ /** Additional request parameters — spread into EVERY fetch (e.g. the
71
+ * server context as @ parameters). Changes take effect from the next
72
+ * fetch; `refresh()` reloads immediately. */
73
73
  params: { type: Object, default: () => ({}) },
74
- /** Spalten; ohne Angabe aus der viewSkel-Struktur abgeleitet. */
74
+ /** Columns; if not given, derived from the viewSkel structure. */
75
75
  columns: { type: Array, default: null },
76
- /** Nur diese Bones als Spalten (bei abgeleiteten Spalten). */
76
+ /** Only these bones as columns (for derived columns). */
77
77
  bones: { type: Array, default: null },
78
- /** Zugängliche Beschriftung; Standard: Modulname. */
78
+ /** Accessible label; default: module name. */
79
79
  label: { type: String, default: null },
80
80
  selectable: { type: [String, Boolean], default: "multiple" },
81
- /** Einträge je Nachladen (ViUR `limit`). */
81
+ /** Entries per load (ViUR `limit`). */
82
82
  pageSize: { type: Number, default: 50 },
83
83
  /**
84
- * Obergrenze für den Lokal-Modus: darüber bleiben Sortieren/Filtern/Suchen
85
- * beim Server (der erste lokale Suchlauf schickt jede Zeile durch den
86
- * Bone-Formatter — fünfstellig friert das den Main-Thread ein).
84
+ * Upper limit for local mode: above it, sorting/filtering/searching stay
85
+ * with the server (the first local search sends every row through the
86
+ * bone formatter — at five digits that freezes the main thread).
87
87
  */
88
88
  localModeLimit: { type: Number, default: 5_000 },
89
- /** X-VIUR-BONELIST senden, wenn Felder abgewählt sind. NUR aktivieren, wenn
90
- * das Modul es serverseitig trägt ("*"-Subskel) — sonst antwortet der
91
- * Core mit 400. */
89
+ /** Send X-VIUR-BONELIST when fields are deselected. ONLY enable if
90
+ * the module supports it server-side ("*" subskel) — otherwise the
91
+ * core responds with 400. */
92
92
  bonelist: { type: Boolean, default: false },
93
- /** localStorage-Key der Feld-Sichtbarkeit (gespeichert wird die
94
- * HIDDEN-Liste: neue Server-Felder erscheinen automatisch); null = keine
95
- * Persistenz. */
93
+ /** localStorage key for field visibility (the HIDDEN list is
94
+ * stored: new server fields appear automatically); null = no
95
+ * persistence. */
96
96
  storageKey: { type: String, default: null },
97
- /** Sprache/Locale für Bone-Darstellung in den Spalten. */
97
+ /** Language/locale for bone rendering in the columns. */
98
98
  language: { type: String, default: null },
99
- /** Steht bei language="*" vorn (üblicherweise die UI-Sprache). */
99
+ /** Comes first with language="*" (usually the UI language). */
100
100
  primaryLanguage: { type: String, default: null },
101
101
  locale: { type: String, default: null },
102
- /** Spalten per Griff in der Kopfzeile breiter/schmaler ziehen. Anders als
103
- * bei der nackten DataTable (dort Opt-in) hier standardmäßig AN — die
104
- * ViurDataTable ist das Komfort-Widget. */
102
+ /** Drag columns wider/narrower via a handle in the header. Unlike the
103
+ * bare DataTable (opt-in there), ON by default here — the
104
+ * ViurDataTable is the convenience widget. */
105
105
  resizable: { type: Boolean, default: true },
106
- /** Spalten per Drag umsortieren. */
106
+ /** Reorder columns via drag. */
107
107
  reorderable: { type: Boolean, default: true },
108
- /** Spalten links/rechts anpinnen. */
108
+ /** Pin columns left/right. */
109
109
  pinnable: { type: Boolean, default: true },
110
- /** Wird an die DataTable durchgereicht (überschreibt Einzel-Props). */
110
+ /** Passed through to the DataTable (overrides individual props). */
111
111
  tableProps: { type: Object, default: () => ({}) },
112
112
  })
113
113
 
@@ -119,12 +119,12 @@ const selectedKeys = defineModel("selectedKeys", { type: Array, default: () => [
119
119
 
120
120
  const sort = ref([])
121
121
  const limit = ref(Math.min(99, Number(props.params?.limit ?? props.pageSize) || 50))
122
- /** Serverseitiger Suchbegriff (ViUR `search`); leer = aus. */
122
+ /** Server-side search term (ViUR `search`); empty = off. */
123
123
  const serverSearch = ref("")
124
- /** Client-Suche/-Filter — greifen NUR im vollständig geladenen Zustand. */
124
+ /** Client search/filters — apply ONLY in the fully loaded state. */
125
125
  const clientSearch = ref("")
126
126
  const clientFilters = ref([])
127
- /** Ausgeblendete Spalten (Feldnamen). */
127
+ /** Hidden columns (field names). */
128
128
  const hiddenFields = ref(loadHiddenFields())
129
129
 
130
130
  const list = useListFetch(props.module, {
@@ -137,7 +137,7 @@ const list = useListFetch(props.module, {
137
137
 
138
138
  const rows = computed(() => list.data.value)
139
139
 
140
- /** Alles geladen und klein genug → Lokal-Modus (Client-Pipeline der Tabelle). */
140
+ /** Everything loaded and small enough → local mode (the table's client pipeline). */
141
141
  const allLoaded = computed(
142
142
  () =>
143
143
  rows.value.length > 0 &&
@@ -155,6 +155,7 @@ const effectiveColumns = computed(() => {
155
155
  ...(props.locale ? { locale: props.locale } : {}),
156
156
  exclude: ["key"],
157
157
  local: allLoaded.value,
158
+ boolTexts: [t("bones.bool.true"), t("bones.bool.false")],
158
159
  })
159
160
  if (derived.length) {
160
161
  return derived.map((column) => ({
@@ -171,7 +172,7 @@ const notice = computed(() =>
171
172
  : ""
172
173
  )
173
174
 
174
- /** Notfall-Spalten: die Felder des ersten Datensatzes, unformatiert. */
175
+ /** Fallback columns: the fields of the first record, unformatted. */
175
176
  function fallbackColumns(row) {
176
177
  if (!row) return []
177
178
  return Object.keys(row)
@@ -179,7 +180,7 @@ function fallbackColumns(row) {
179
180
  .map((field) => ({ field, label: field, sortable: false, minWidth: 120, flex: 1 }))
180
181
  }
181
182
 
182
- /** Abrufparameter: props.params (Context etc.) + Suche + Sortierung + limit. */
183
+ /** Fetch parameters: props.params (context etc.) + search + sorting + limit. */
183
184
  function requestParams(sortEntries = sort.value) {
184
185
  const params = { ...props.params, limit: limit.value }
185
186
  if (serverSearch.value) params.search = serverSearch.value
@@ -210,9 +211,9 @@ async function onLoadMore({ pages }) {
210
211
  await list.next(pages).catch(() => null)
211
212
  }
212
213
 
213
- // Zurück im Server-Modus (Reload, neuer Cursor, Server-Suche): lokale
214
- // Verfeinerungen zurücksetzen und Sortierungen entfernen, die der Server
215
- // nicht kann (nur indizierte, einwertige Bones).
214
+ // Back in server mode (reload, new cursor, server search): reset local
215
+ // refinements and remove sortings the server can't handle
216
+ // (only indexed, single-valued bones).
216
217
  watch(allLoaded, (local) => {
217
218
  if (local) return
218
219
  clientSearch.value = ""
@@ -223,7 +224,7 @@ watch(allLoaded, (local) => {
223
224
  })
224
225
  })
225
226
 
226
- // ── Feld-Sichtbarkeit (+ optionale Persistenz, + optionale Bonelist) ─────────
227
+ // ── Field visibility (+ optional persistence, + optional bonelist) ──────────
227
228
  function loadHiddenFields() {
228
229
  if (!props.storageKey) return []
229
230
  try {
@@ -244,7 +245,7 @@ function bonelistHeader() {
244
245
  }
245
246
 
246
247
  const columnsApi = {
247
- /** Alle ein-/ausblendbaren Spalten mit aktuellem Zustand. */
248
+ /** All showable/hideable columns with their current state. */
248
249
  all: () =>
249
250
  effectiveColumns.value
250
251
  .filter((column) => column.hideable !== false)
@@ -253,7 +254,7 @@ const columnsApi = {
253
254
  label: String(column.label ?? column.field ?? column.id),
254
255
  hidden: column.hidden === true,
255
256
  })),
256
- /** Sichtbare Spalten setzen (persistiert; mit Bonelist folgt ein Reload). */
257
+ /** Set visible columns (persisted; with bonelist a reload follows). */
257
258
  apply: (visibleIds) => {
258
259
  const visible = new Set(visibleIds)
259
260
  hiddenFields.value = effectiveColumns.value
@@ -263,14 +264,14 @@ const columnsApi = {
263
264
  try {
264
265
  localStorage.setItem(props.storageKey, JSON.stringify(hiddenFields.value))
265
266
  } catch {
266
- // localStorage kann fehlen (Privatmodus) — dann eben nicht persistent.
267
+ // localStorage may be missing (private mode) — then just not persistent.
267
268
  }
268
269
  }
269
270
  if (props.bonelist) void list.filter(requestParams()).catch(() => null)
270
271
  },
271
272
  }
272
273
 
273
- /** Serverseitige Suche — bzw. lokal, wenn alles geladen ist. */
274
+ /** Server-side search — or local, if everything is loaded. */
274
275
  function search(term) {
275
276
  const trimmed = String(term ?? "").trim()
276
277
  if (allLoaded.value) {
@@ -281,12 +282,12 @@ function search(term) {
281
282
  }
282
283
  }
283
284
 
284
- /** Von vorn laden — mit den AKTUELLEN props.params (Context etc.). */
285
+ /** Load from the start — with the CURRENT props.params (context etc.). */
285
286
  function refresh() {
286
287
  return list.filter(requestParams())
287
288
  }
288
289
 
289
- /** Beschriftung eines Eintrags: erster Textwert seiner Spalten, sonst der Key. */
290
+ /** Label of an entry: first text value of its columns, otherwise the key. */
290
291
  function entryTitle(key) {
291
292
  const row = rows.value.find((entry) => String(entry.key) === String(key))
292
293
  if (!row) return String(key)
@@ -1,6 +1,6 @@
1
- // Reine Spalten-/Zell-Logik: Auflösen der Definitionen, Vergleich, Filter,
2
- // Facetten, Aggregation. Absichtlich frei von Vue — so ist der ganze
3
- // Datenpfad ohne DOM testbar.
1
+ // Pure column/cell logic: resolving definitions, comparison, filters,
2
+ // facets, aggregation. Deliberately free of Vue — so the whole
3
+ // data path is testable without a DOM.
4
4
 
5
5
  import type {
6
6
  AggregationKind,
@@ -13,9 +13,9 @@ import type {
13
13
  Row,
14
14
  SortEntry,
15
15
  SortMode,
16
- } from "./types"
16
+ } from "./types.ts"
17
17
 
18
- /** Wert über einen Punktpfad lesen ("user.name"). */
18
+ /** Read a value via a dot path ("user.name"). */
19
19
  export function getPath(row: Row, path: string): unknown {
20
20
  if (!path.includes(".")) return row[path]
21
21
  let current: unknown = row
@@ -75,7 +75,7 @@ export function findColumn<R extends Row>(columns: ResolvedColumn<R>[], id: stri
75
75
  return columns.find((column) => column.id === id)
76
76
  }
77
77
 
78
- // ── Sortierung ────────────────────────────────────────────────────────
78
+ // ── Sorting ───────────────────────────────────────────────────────────
79
79
 
80
80
  const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" })
81
81
  const collatorCase = new Intl.Collator(undefined, { numeric: true })
@@ -92,11 +92,11 @@ export function isEmptyValue(value: unknown): boolean {
92
92
  }
93
93
 
94
94
  /**
95
- * Zusammengesetzte Zellwerte aufs Sortier-Atom reduzieren: bei Arrays zählt
96
- * der ERSTE Eintrag, bei einfachen Objekten der ERSTE Wert — rekursiv, bis ein
97
- * Primitivwert (oder Date) übrig ist. Damit sortieren `multiple`-Bones,
98
- * mehrsprachige Werte ({ de: "…" }) und relationale Bones ({ dest: { … } })
99
- * nach ihrem jeweils ersten Wert statt nach "[object Object]".
95
+ * Reduce composite cell values to the sort atom: for arrays the FIRST entry
96
+ * counts, for plain objects the FIRST value — recursively, until a primitive
97
+ * (or Date) remains. This way `multiple` bones, multilingual values
98
+ * ({ de: "…" }) and relational bones ({ dest: { … } }) sort by their
99
+ * respective first value instead of by "[object Object]".
100
100
  */
101
101
  export function firstValueOf(value: unknown, depth = 0): unknown {
102
102
  if (depth > 10 || value === null || value === undefined) return value
@@ -108,7 +108,7 @@ export function firstValueOf(value: unknown, depth = 0): unknown {
108
108
  return value
109
109
  }
110
110
 
111
- /** Vergleich zweier Zellwerte in einem der eingebauten Modi (aufsteigend). */
111
+ /** Compare two cell values in one of the built-in modes (ascending). */
112
112
  export function compareValues(a: unknown, b: unknown, mode: SortMode = "auto"): number {
113
113
  switch (mode) {
114
114
  case "basic":
@@ -135,7 +135,7 @@ export function compareValues(a: unknown, b: unknown, mode: SortMode = "auto"):
135
135
  return collatorCase.compare(defaultText(a), defaultText(b))
136
136
  case "auto":
137
137
  default: {
138
- // Arrays/Objekte: nach dem ersten Wert sortieren (siehe firstValueOf).
138
+ // Arrays/objects: sort by the first value (see firstValueOf).
139
139
  if (a !== null && typeof a === "object" && !(a instanceof Date)) a = firstValueOf(a)
140
140
  if (b !== null && typeof b === "object" && !(b instanceof Date)) b = firstValueOf(b)
141
141
  if (typeof a === "number" && typeof b === "number") return a - b
@@ -147,8 +147,8 @@ export function compareValues(a: unknown, b: unknown, mode: SortMode = "auto"):
147
147
  }
148
148
 
149
149
  /**
150
- * Sortiert eine Zeilenliste stabil nach mehreren Spalten. Leere Werte landen
151
- * gemäß `sortUndefined` der Spalte vorn oder hinten (Default: normal vergleicht).
150
+ * Stably sorts a row list by multiple columns. Empty values end up at the
151
+ * front or back according to the column's `sortUndefined` (default: compared normally).
152
152
  */
153
153
  export function sortRows<R extends Row>(rows: R[], sort: SortEntry[], columns: ResolvedColumn<R>[]): R[] {
154
154
  if (!sort.length) return rows
@@ -157,7 +157,7 @@ export function sortRows<R extends Row>(rows: R[], sort: SortEntry[], columns: R
157
157
  .filter((item): item is { entry: SortEntry; column: ResolvedColumn<R> } => Boolean(item.column))
158
158
  if (!entries.length) return rows
159
159
 
160
- // Index mitführen → stabile Sortierung unabhängig von der Engine.
160
+ // Carry the index along → stable sorting regardless of the engine.
161
161
  return rows
162
162
  .map((row, index) => ({ row, index }))
163
163
  .sort((left, right) => {
@@ -181,9 +181,9 @@ export function sortRows<R extends Row>(rows: R[], sort: SortEntry[], columns: R
181
181
  }
182
182
 
183
183
  /**
184
- * Nächster Sortierzustand beim Klick auf einen Spaltenkopf.
185
- * asc → desc → aus (bzw. endlos wechselnd bei `withoutSortRemoval`),
186
- * mit Shift mehrspaltig (begrenzt durch `maxMultiSort`, 0 = unbegrenzt).
184
+ * Next sort state when clicking a column header.
185
+ * asc → desc → off (or cycling endlessly with `withoutSortRemoval`),
186
+ * multi-column with Shift (limited by `maxMultiSort`, 0 = unlimited).
187
187
  */
188
188
  export function nextSortState(
189
189
  current: SortEntry[],
@@ -211,7 +211,7 @@ export function nextSortState(
211
211
  return max > 0 ? combined.slice(-max) : combined
212
212
  }
213
213
 
214
- // ── Filter & Suche ────────────────────────────────────────────────────
214
+ // ── Filter & search ───────────────────────────────────────────────────
215
215
 
216
216
  function dayStart(value: unknown): number {
217
217
  const time = toTime(value)
@@ -220,7 +220,7 @@ function dayStart(value: unknown): number {
220
220
  return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()
221
221
  }
222
222
 
223
- /** Ist ein Filterwert „leer" (dann filtert die Spalte nicht)? */
223
+ /** Is a filter value "empty" (then the column doesn't filter)? */
224
224
  export function isEmptyFilter(value: unknown): boolean {
225
225
  if (value === null || value === undefined || value === "") return true
226
226
  if (Array.isArray(value)) return value.every((item) => isEmptyFilter(item))
@@ -286,11 +286,11 @@ export function rowMatchesFilters<R extends Row>(
286
286
  return true
287
287
  }
288
288
 
289
- // Suchtext-Cache je (Spalte, Zeile): `column.text` läuft durch den Formatter
290
- // (bei ViUR-Spalten Sprachauflösung, multiple-Joins, Format-Strings) — ohne
291
- // Cache friert die Suche über einen großen, voll geladenen Datensatz den
292
- // Browser ein (Zeilen × Spalten × Tastendruck). WeakMaps: der Cache lebt so
293
- // lange wie Spalte bzw. Zeile und räumt sich selbst ab.
289
+ // Search text cache per (column, row): `column.text` runs through the formatter
290
+ // (for ViUR columns: language resolution, multiple joins, format strings) — without
291
+ // a cache, searching a large, fully loaded dataset freezes the
292
+ // browser (rows × columns × keystroke). WeakMaps: the cache lives as
293
+ // long as the column or row and cleans up after itself.
294
294
  const searchTextCache = new WeakMap<object, WeakMap<object, string>>()
295
295
 
296
296
  function searchTextOf<R extends Row>(column: ResolvedColumn<R>, row: R): string {
@@ -323,9 +323,9 @@ export function rowMatchesSearch<R extends Row>(
323
323
  }
324
324
 
325
325
  /**
326
- * Facetten einer Spalte: verschiedene Werte mit Trefferzahl und numerischer
327
- * Spanne — berechnet über die Zeilen, die alle ANDEREN Filter (und die Suche)
328
- * passieren. Genau das braucht ein set-Filter, damit er sich nicht selbst leert.
326
+ * Facets of a column: distinct values with hit count and numeric
327
+ * range — computed over the rows that pass all OTHER filters (and the search).
328
+ * That's exactly what a set filter needs so it doesn't empty itself.
329
329
  */
330
330
  export function computeFacets<R extends Row>(rows: R[], column: ResolvedColumn<R>): ColumnFacets {
331
331
  const counts = new Map<string, number>()
@@ -354,7 +354,7 @@ export function computeFacets<R extends Row>(rows: R[], column: ResolvedColumn<R
354
354
  return { values, min, max }
355
355
  }
356
356
 
357
- // ── Aggregation (Gruppenzeilen) ───────────────────────────────────────
357
+ // ── Aggregation (group rows) ──────────────────────────────────────────
358
358
 
359
359
  function numbers(values: unknown[]): number[] {
360
360
  return values.map((value) => Number(value)).filter((value) => !Number.isNaN(value))
@@ -1,7 +1,7 @@
1
- // Öffentliche Oberfläche des DataTable-Widgets.
1
+ // Public surface of the DataTable widget.
2
2
  export { default as DataTable } from "./DataTable.vue"
3
3
  export { default as ColumnFilterPanel } from "./ColumnFilterPanel.vue"
4
- export * from "./types"
4
+ export * from "./types.ts"
5
5
  export {
6
6
  aggregate,
7
7
  aggregateColumn,
@@ -19,7 +19,7 @@ export {
19
19
  rowMatchesFilters,
20
20
  rowMatchesSearch,
21
21
  sortRows,
22
- } from "./columns"
22
+ } from "./columns.ts"
23
23
  export {
24
24
  buildRows,
25
25
  collectKeys,
@@ -37,10 +37,10 @@ export {
37
37
  type NodeLocation,
38
38
  type OrderValue,
39
39
  type SelectionState,
40
- } from "./rows"
41
- export { useColumnLayout, type ColumnLayout, type LayoutColumn } from "./useColumnLayout"
42
- export { useGridNavigation } from "./useGridNavigation"
43
- export { useVirtualRows } from "./useVirtualRows"
40
+ } from "./rows.ts"
41
+ export { useColumnLayout, type ColumnLayout, type LayoutColumn } from "./useColumnLayout.ts"
42
+ export { useGridNavigation } from "./useGridNavigation.ts"
43
+ export { useVirtualRows } from "./useVirtualRows.ts"
44
44
  export {
45
45
  badgesForBone,
46
46
  boneFamily,
@@ -52,9 +52,9 @@ export {
52
52
  type BoneDescription,
53
53
  type BoneStructure,
54
54
  type ColumnsFromStructureOptions,
55
- } from "./viurColumns"
56
- // ViurDataTable ist (wie ViurBrowser) ein JS-SFC — seine Typen liegen in
57
- // ViurDataTable.d.vue.ts (Import: ".../DataTable/ViurDataTable.d.vue", wie
58
- // bei ViForm).
59
- // @ts-expect-error — JS-SFC ohne eingebettetes Typing
55
+ } from "./viurColumns.ts"
56
+ // ViurDataTable is (like ViurBrowser) a JS SFC — its types live in
57
+ // ViurDataTable.d.vue.ts (import: ".../DataTable/ViurDataTable.d.vue", as
58
+ // with ViForm).
59
+ // @ts-expect-error — JS SFC without embedded typing
60
60
  export { default as ViurDataTable } from "./ViurDataTable.vue"