viur-ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +79 -0
  2. package/index.d.ts +351 -0
  3. package/index.js +46 -0
  4. package/package.json +84 -0
  5. package/src/components/Browser/Browser.vue +496 -0
  6. package/src/components/Browser/TreeNav.vue +408 -0
  7. package/src/components/Browser/ViurBrowser.d.vue.ts +64 -0
  8. package/src/components/Browser/ViurBrowser.vue +271 -0
  9. package/src/components/Browser/dragSession.ts +134 -0
  10. package/src/components/Browser/index.ts +14 -0
  11. package/src/components/Browser/types.ts +30 -0
  12. package/src/components/DataTable/ColumnFilterPanel.vue +225 -0
  13. package/src/components/DataTable/DataTable.vue +3155 -0
  14. package/src/components/DataTable/ViurDataTable.d.vue.ts +72 -0
  15. package/src/components/DataTable/ViurDataTable.vue +331 -0
  16. package/src/components/DataTable/columns.ts +400 -0
  17. package/src/components/DataTable/index.ts +60 -0
  18. package/src/components/DataTable/rows.ts +446 -0
  19. package/src/components/DataTable/types.ts +267 -0
  20. package/src/components/DataTable/useColumnLayout.ts +294 -0
  21. package/src/components/DataTable/useGridNavigation.ts +193 -0
  22. package/src/components/DataTable/useVirtualRows.ts +126 -0
  23. package/src/components/DataTable/viurColumns.ts +330 -0
  24. package/src/forms/ViForm.d.vue.ts +106 -0
  25. package/src/forms/ViForm.vue +592 -0
  26. package/src/forms/ViFormCategory.vue +57 -0
  27. package/src/forms/bones/Bone.vue +1023 -0
  28. package/src/forms/bones/BoneActions.vue +127 -0
  29. package/src/forms/bones/BoneLabel.vue +122 -0
  30. package/src/forms/bones/BoneMultipleList.vue +91 -0
  31. package/src/forms/bones/BoneMultipleWrapper.vue +105 -0
  32. package/src/forms/bones/BoneNestedWrapper.vue +155 -0
  33. package/src/forms/bones/BoneSingleEntry.vue +51 -0
  34. package/src/forms/bones/FilterBone.vue +694 -0
  35. package/src/forms/bones/actionbar/defaultBar.vue +98 -0
  36. package/src/forms/bones/actionbar/fileBar.vue +165 -0
  37. package/src/forms/bones/actionbar/relationalBar.vue +125 -0
  38. package/src/forms/bones/actions/ActionDialog.vue +70 -0
  39. package/src/forms/bones/actions/Debug.vue +106 -0
  40. package/src/forms/bones/actions/Delete.vue +13 -0
  41. package/src/forms/bones/actions/DescribeImage.vue +201 -0
  42. package/src/forms/bones/actions/Download.vue +27 -0
  43. package/src/forms/bones/actions/Placeholder.vue +14 -0
  44. package/src/forms/bones/actions/Translation.vue +238 -0
  45. package/src/forms/bones/actions/index.js +280 -0
  46. package/src/forms/bones/bone.d.ts +45 -0
  47. package/src/forms/bones/country_information.json +779 -0
  48. package/src/forms/bones/index.js +225 -0
  49. package/src/forms/bones/uploadAdapter.js +46 -0
  50. package/src/forms/bones/utils.js +284 -0
  51. package/src/forms/bones/view/base_item.vue +35 -0
  52. package/src/forms/bones/view/boneLogic.js +202 -0
  53. package/src/forms/bones/view/index.js +5 -0
  54. package/src/forms/bones/widgets/booleanBone.vue +83 -0
  55. package/src/forms/bones/widgets/booleanBoneChoose.vue +57 -0
  56. package/src/forms/bones/widgets/booleanBoneSelect.vue +58 -0
  57. package/src/forms/bones/widgets/codeBone.vue +214 -0
  58. package/src/forms/bones/widgets/colorBone.vue +75 -0
  59. package/src/forms/bones/widgets/dateBone.vue +114 -0
  60. package/src/forms/bones/widgets/emailBone.vue +100 -0
  61. package/src/forms/bones/widgets/fileBone.vue +273 -0
  62. package/src/forms/bones/widgets/jsonBone.vue +63 -0
  63. package/src/forms/bones/widgets/keyBone.vue +71 -0
  64. package/src/forms/bones/widgets/numericBone.vue +125 -0
  65. package/src/forms/bones/widgets/passwordBone.vue +215 -0
  66. package/src/forms/bones/widgets/phoneBone.vue +173 -0
  67. package/src/forms/bones/widgets/rawBone.vue +94 -0
  68. package/src/forms/bones/widgets/recordBone.vue +64 -0
  69. package/src/forms/bones/widgets/relationalBone.vue +164 -0
  70. package/src/forms/bones/widgets/relationalBoneSelect.vue +120 -0
  71. package/src/forms/bones/widgets/selectBone.vue +122 -0
  72. package/src/forms/bones/widgets/selectBoneChoose.vue +104 -0
  73. package/src/forms/bones/widgets/spatialBone.vue +111 -0
  74. package/src/forms/bones/widgets/stringBone.vue +139 -0
  75. package/src/forms/bones/widgets/textBone.vue +641 -0
  76. package/src/forms/install.d.ts +4 -0
  77. package/src/forms/install.js +43 -0
  78. package/src/forms/layouts/LayloutList2Columns.vue +57 -0
  79. package/src/forms/layouts/LayoutCategory.vue +38 -0
  80. package/src/forms/layouts/LayoutList.vue +31 -0
  81. package/src/forms/utils.js +601 -0
  82. package/src/public/assets/icons/fontawesome-manifest.json +8 -0
  83. package/src/public/assets/icons/regular/file.svg +1 -0
  84. package/src/public/assets/icons/solid/arrows-left-right.svg +1 -0
  85. package/src/public/assets/icons/solid/bug.svg +1 -0
  86. package/src/public/assets/icons/solid/check.svg +1 -0
  87. package/src/public/assets/icons/solid/circle-info.svg +1 -0
  88. package/src/public/assets/icons/solid/compress.svg +1 -0
  89. package/src/public/assets/icons/solid/download.svg +1 -0
  90. package/src/public/assets/icons/solid/expand.svg +1 -0
  91. package/src/public/assets/icons/solid/grip-vertical.svg +1 -0
  92. package/src/public/assets/icons/solid/plus.svg +1 -0
  93. package/src/public/assets/icons/solid/question.svg +1 -0
  94. package/src/public/assets/icons/solid/triangle-exclamation.svg +1 -0
  95. package/src/public/assets/icons/solid/upload.svg +1 -0
  96. package/src/public/assets/icons/solid/xmark.svg +1 -0
  97. package/src/request/cache.js +183 -0
  98. package/src/request/configStore.js +516 -0
  99. package/src/request/envelope.js +211 -0
  100. package/src/request/index.js +35 -0
  101. package/src/request/messageStore.js +201 -0
  102. package/src/request/request.js +752 -0
  103. package/src/request/skel.js +90 -0
  104. package/src/theme/colorStore.js +168 -0
  105. package/src/translations/de.js +178 -0
  106. package/src/translations/en.js +177 -0
  107. package/src/user/LoginScreen.d.vue.ts +54 -0
  108. package/src/user/LoginScreen.vue +627 -0
  109. package/src/user/google.js +75 -0
  110. package/src/user/index.js +3 -0
  111. package/src/user/store.d.ts +59 -0
  112. package/src/user/store.js +183 -0
  113. package/src/webawesomeCompat.css +72 -0
  114. package/src/webawesomeConfig.js +61 -0
@@ -0,0 +1,72 @@
1
+ import type { DefineComponent, Ref } from "vue"
2
+ import type { Row, RowKey } from "./types"
3
+
4
+ /** Props von ViurDataTable.vue (siehe defineProps dort). */
5
+ export interface ViurDataTableProps {
6
+ /** Modulname bzw. Punkt-Pfad ("user", "shop.order"). */
7
+ module: string
8
+ /** Renderer der Requests; Standard: VITE_DEFAULT_RENDERER. */
9
+ renderer?: string | null
10
+ /** Untertyp gruppierter Listen (Handler `list.grouped`). */
11
+ group?: string | null
12
+ /** Zusätzliche Request-Parameter — bei jedem Abruf gespreadet. */
13
+ params?: Record<string, unknown>
14
+ /** Spalten; ohne Angabe aus der viewSkel-Struktur abgeleitet. */
15
+ columns?: unknown[] | null
16
+ /** Nur diese Bones als Spalten (bei abgeleiteten Spalten). */
17
+ bones?: string[] | null
18
+ label?: string | null
19
+ selectable?: false | "single" | "multiple"
20
+ /** Einträge je Nachladen (ViUR `limit`). */
21
+ pageSize?: number
22
+ /** Obergrenze des Lokal-Modus (alles geladen → Client-Pipeline). */
23
+ localModeLimit?: number
24
+ /** X-VIUR-BONELIST senden, wenn Felder abgewählt sind (Server braucht ein "*"-Subskel). */
25
+ bonelist?: boolean
26
+ /** localStorage-Key der Feld-Sichtbarkeit; null = keine Persistenz. */
27
+ storageKey?: string | null
28
+ language?: string | null
29
+ /** Steht bei language="*" vorn (üblicherweise die UI-Sprache). */
30
+ primaryLanguage?: string | null
31
+ locale?: string | null
32
+ /** Spaltenbreite ziehbar (Default true — anders als die nackte DataTable). */
33
+ resizable?: boolean
34
+ /** Spalten per Drag umsortierbar (Default true). */
35
+ reorderable?: boolean
36
+ /** Spalten anpinnbar (Default true). */
37
+ pinnable?: boolean
38
+ /** Wird an die DataTable durchgereicht. */
39
+ tableProps?: Record<string, unknown>
40
+ }
41
+
42
+ export interface ViurDataTableEmitProps<R extends Row = Row> {
43
+ /** Doppelklick auf eine Zeile. */
44
+ onOpen?: (payload: { row: R; key: RowKey }) => void
45
+ /** v-model:selectedKeys */
46
+ "onUpdate:selectedKeys"?: (value: RowKey[]) => void
47
+ }
48
+
49
+ /** Was ViurDataTable per defineExpose freigibt. */
50
+ export interface ViurDataTableApi<R extends Row = Row> {
51
+ /** Der ListRequest (useListFetch): data, structure, cursor, execute, … */
52
+ list: Record<string, unknown>
53
+ /** Template-Ref auf die DataTable (DataTableApi). */
54
+ table: Ref<unknown>
55
+ /** Von vorn laden — mit den aktuellen props.params. */
56
+ refresh: () => Promise<unknown>
57
+ /** Suche: serverseitig (ViUR `search`) bzw. lokal, wenn alles geladen. */
58
+ search: (term: string) => void
59
+ /** Feld-Sichtbarkeit (z.B. für einen „Felder"-Dialog). */
60
+ columns: {
61
+ all: () => { id: string; label: string; hidden: boolean }[]
62
+ apply: (visibleIds: string[]) => void
63
+ }
64
+ /** Beschriftung eines Eintrags (erster Textwert seiner Spalten). */
65
+ entryTitle: (key: RowKey) => string
66
+ /** Alles geladen → Sortieren/Filtern/Suchen laufen lokal. */
67
+ allLoaded: Ref<boolean>
68
+ rows: Ref<R[]>
69
+ }
70
+
71
+ declare const ViurDataTable: DefineComponent<ViurDataTableProps & ViurDataTableEmitProps>
72
+ export default ViurDataTable
@@ -0,0 +1,331 @@
1
+ <template>
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. -->
5
+ <wa-callout
6
+ v-if="notice"
7
+ class="vi-vdt-notice"
8
+ variant="warning"
9
+ >
10
+ {{ notice }}
11
+ </wa-callout>
12
+
13
+ <DataTable
14
+ ref="tableEl"
15
+ v-model:sort="sort"
16
+ v-model:selected-keys="selectedKeys"
17
+ v-model:page-size="limit"
18
+ v-model:search="clientSearch"
19
+ v-model:filters="clientFilters"
20
+ class="vi-vdt-table"
21
+ :server="!allLoaded"
22
+ :columns="effectiveColumns"
23
+ :data="rows"
24
+ :label="label ?? module"
25
+ row-key="key"
26
+ :selectable="selectable"
27
+ :loading="list.isFetching.value"
28
+ :has-more="Boolean(list.cursor.value)"
29
+ :resizable="resizable"
30
+ :reorderable="reorderable"
31
+ :pinnable="pinnable"
32
+ v-bind="tableProps"
33
+ @data-request="onRequest"
34
+ @load-more="onLoadMore"
35
+ @page-change="onPageSizeChange"
36
+ @row-dblclick="$emit('open', { row: $event.row, key: $event.key })"
37
+ >
38
+ <template
39
+ v-for="(_, slotName) in $slots"
40
+ #[slotName]="scope"
41
+ >
42
+ <slot
43
+ :name="slotName"
44
+ v-bind="scope"
45
+ ></slot>
46
+ </template>
47
+ </DataTable>
48
+ </div>
49
+ </template>
50
+
51
+ <script setup>
52
+ import { computed, ref, watch } from "vue"
53
+ import { useListFetch } from "../../request/index.js"
54
+ import { useI18n } from "vue-i18n"
55
+ import DataTable from "./DataTable.vue"
56
+ import { columnsFromStructure } from "./viurColumns"
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).
64
+
65
+ const props = defineProps({
66
+ module: { type: String, required: true },
67
+ renderer: { type: String, default: null },
68
+ /** Untertyp gruppierter Listen (Handler `list.grouped`). */
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()`. */
73
+ params: { type: Object, default: () => ({}) },
74
+ /** Spalten; ohne Angabe aus der viewSkel-Struktur abgeleitet. */
75
+ columns: { type: Array, default: null },
76
+ /** Nur diese Bones als Spalten (bei abgeleiteten Spalten). */
77
+ bones: { type: Array, default: null },
78
+ /** Zugängliche Beschriftung; Standard: Modulname. */
79
+ label: { type: String, default: null },
80
+ selectable: { type: [String, Boolean], default: "multiple" },
81
+ /** Einträge je Nachladen (ViUR `limit`). */
82
+ pageSize: { type: Number, default: 50 },
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).
87
+ */
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. */
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. */
96
+ storageKey: { type: String, default: null },
97
+ /** Sprache/Locale für Bone-Darstellung in den Spalten. */
98
+ language: { type: String, default: null },
99
+ /** Steht bei language="*" vorn (üblicherweise die UI-Sprache). */
100
+ primaryLanguage: { type: String, default: null },
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. */
105
+ resizable: { type: Boolean, default: true },
106
+ /** Spalten per Drag umsortieren. */
107
+ reorderable: { type: Boolean, default: true },
108
+ /** Spalten links/rechts anpinnen. */
109
+ pinnable: { type: Boolean, default: true },
110
+ /** Wird an die DataTable durchgereicht (überschreibt Einzel-Props). */
111
+ tableProps: { type: Object, default: () => ({}) },
112
+ })
113
+
114
+ defineEmits(["open"])
115
+
116
+ const { t } = useI18n()
117
+ const tableEl = ref(null)
118
+ const selectedKeys = defineModel("selectedKeys", { type: Array, default: () => [] })
119
+
120
+ const sort = ref([])
121
+ const limit = ref(Math.min(99, Number(props.params?.limit ?? props.pageSize) || 50))
122
+ /** Serverseitiger Suchbegriff (ViUR `search`); leer = aus. */
123
+ const serverSearch = ref("")
124
+ /** Client-Suche/-Filter — greifen NUR im vollständig geladenen Zustand. */
125
+ const clientSearch = ref("")
126
+ const clientFilters = ref([])
127
+ /** Ausgeblendete Spalten (Feldnamen). */
128
+ const hiddenFields = ref(loadHiddenFields())
129
+
130
+ const list = useListFetch(props.module, {
131
+ group: props.group ?? null,
132
+ params: { limit: limit.value },
133
+ immediate: false,
134
+ ...(props.renderer ? { renderer: props.renderer } : {}),
135
+ headers: () => bonelistHeader(),
136
+ })
137
+
138
+ const rows = computed(() => list.data.value)
139
+
140
+ /** Alles geladen und klein genug → Lokal-Modus (Client-Pipeline der Tabelle). */
141
+ const allLoaded = computed(
142
+ () =>
143
+ rows.value.length > 0 &&
144
+ rows.value.length <= props.localModeLimit &&
145
+ list.complete.value &&
146
+ !serverSearch.value
147
+ )
148
+
149
+ const effectiveColumns = computed(() => {
150
+ if (props.columns) return props.columns
151
+ const derived = columnsFromStructure(list.structure.value, {
152
+ ...(props.bones ? { bones: props.bones } : {}),
153
+ ...(props.language ? { language: props.language } : {}),
154
+ ...(props.primaryLanguage ? { primaryLanguage: props.primaryLanguage } : {}),
155
+ ...(props.locale ? { locale: props.locale } : {}),
156
+ exclude: ["key"],
157
+ local: allLoaded.value,
158
+ })
159
+ if (derived.length) {
160
+ return derived.map((column) => ({
161
+ ...column,
162
+ hidden: hiddenFields.value.includes(String(column.field ?? column.id)),
163
+ }))
164
+ }
165
+ return fallbackColumns(rows.value[0])
166
+ })
167
+
168
+ const notice = computed(() =>
169
+ rows.value.length && !columnsFromStructure(list.structure.value).length
170
+ ? t("dataTable.structureNotice")
171
+ : ""
172
+ )
173
+
174
+ /** Notfall-Spalten: die Felder des ersten Datensatzes, unformatiert. */
175
+ function fallbackColumns(row) {
176
+ if (!row) return []
177
+ return Object.keys(row)
178
+ .filter((field) => field !== "key")
179
+ .map((field) => ({ field, label: field, sortable: false, minWidth: 120, flex: 1 }))
180
+ }
181
+
182
+ /** Abrufparameter: props.params (Context etc.) + Suche + Sortierung + limit. */
183
+ function requestParams(sortEntries = sort.value) {
184
+ const params = { ...props.params, limit: limit.value }
185
+ if (serverSearch.value) params.search = serverSearch.value
186
+ const [first] = sortEntries
187
+ if (first) {
188
+ params.orderby = first.id
189
+ if (first.desc) params.orderdir = "1"
190
+ }
191
+ return params
192
+ }
193
+
194
+ function onRequest(request) {
195
+ if (request.pageSize) limit.value = Math.min(99, request.pageSize)
196
+ void list.filter(requestParams(request.sort)).catch(() => null)
197
+ }
198
+
199
+ function onPageSizeChange({ pageSize }) {
200
+ if (!pageSize || pageSize === limit.value) return
201
+ limit.value = Math.min(99, pageSize)
202
+ void list.filter(requestParams()).catch(() => null)
203
+ }
204
+
205
+ async function onLoadMore({ pages }) {
206
+ if (pages === "all") {
207
+ await list.nextAll().catch(() => null)
208
+ return
209
+ }
210
+ await list.next(pages).catch(() => null)
211
+ }
212
+
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).
216
+ watch(allLoaded, (local) => {
217
+ if (local) return
218
+ clientSearch.value = ""
219
+ clientFilters.value = []
220
+ sort.value = sort.value.filter((entry) => {
221
+ const bone = list.structure.value?.[entry.id]
222
+ return bone?.indexed === true && !bone?.multiple && !bone?.languages?.length
223
+ })
224
+ })
225
+
226
+ // ── Feld-Sichtbarkeit (+ optionale Persistenz, + optionale Bonelist) ─────────
227
+ function loadHiddenFields() {
228
+ if (!props.storageKey) return []
229
+ try {
230
+ const parsed = JSON.parse(localStorage.getItem(props.storageKey) ?? "[]")
231
+ return Array.isArray(parsed) ? parsed.map(String) : []
232
+ } catch {
233
+ return []
234
+ }
235
+ }
236
+
237
+ function bonelistHeader() {
238
+ if (!props.bonelist || !hiddenFields.value.length) return null
239
+ const fields = Object.keys(list.structure.value ?? {}).filter(
240
+ (name) => !hiddenFields.value.includes(name)
241
+ )
242
+ if (!fields.length) return null
243
+ return { "X-VIUR-BONELIST": ["key", ...fields].join(",") }
244
+ }
245
+
246
+ const columnsApi = {
247
+ /** Alle ein-/ausblendbaren Spalten mit aktuellem Zustand. */
248
+ all: () =>
249
+ effectiveColumns.value
250
+ .filter((column) => column.hideable !== false)
251
+ .map((column) => ({
252
+ id: String(column.field ?? column.id),
253
+ label: String(column.label ?? column.field ?? column.id),
254
+ hidden: column.hidden === true,
255
+ })),
256
+ /** Sichtbare Spalten setzen (persistiert; mit Bonelist folgt ein Reload). */
257
+ apply: (visibleIds) => {
258
+ const visible = new Set(visibleIds)
259
+ hiddenFields.value = effectiveColumns.value
260
+ .map((column) => String(column.field ?? column.id))
261
+ .filter((id) => !visible.has(id))
262
+ if (props.storageKey) {
263
+ try {
264
+ localStorage.setItem(props.storageKey, JSON.stringify(hiddenFields.value))
265
+ } catch {
266
+ // localStorage kann fehlen (Privatmodus) — dann eben nicht persistent.
267
+ }
268
+ }
269
+ if (props.bonelist) void list.filter(requestParams()).catch(() => null)
270
+ },
271
+ }
272
+
273
+ /** Serverseitige Suche — bzw. lokal, wenn alles geladen ist. */
274
+ function search(term) {
275
+ const trimmed = String(term ?? "").trim()
276
+ if (allLoaded.value) {
277
+ clientSearch.value = trimmed
278
+ } else {
279
+ serverSearch.value = trimmed
280
+ void list.filter(requestParams()).catch(() => null)
281
+ }
282
+ }
283
+
284
+ /** Von vorn laden — mit den AKTUELLEN props.params (Context etc.). */
285
+ function refresh() {
286
+ return list.filter(requestParams())
287
+ }
288
+
289
+ /** Beschriftung eines Eintrags: erster Textwert seiner Spalten, sonst der Key. */
290
+ function entryTitle(key) {
291
+ const row = rows.value.find((entry) => String(entry.key) === String(key))
292
+ if (!row) return String(key)
293
+ for (const column of effectiveColumns.value) {
294
+ const field = column.field
295
+ if (!field) continue
296
+ const text = column.formatter
297
+ ? column.formatter(row[field], row)
298
+ : row[field] === null || row[field] === undefined
299
+ ? ""
300
+ : String(row[field])
301
+ if (text) return text
302
+ }
303
+ return String(key)
304
+ }
305
+
306
+ defineExpose({
307
+ list,
308
+ table: tableEl,
309
+ refresh,
310
+ search,
311
+ columns: columnsApi,
312
+ entryTitle,
313
+ allLoaded,
314
+ rows,
315
+ })
316
+ </script>
317
+
318
+ <style scoped>
319
+ .vi-vdt {
320
+ display: flex;
321
+ flex-direction: column;
322
+ gap: var(--wa-space-s);
323
+ flex: 1;
324
+ min-block-size: 0;
325
+ }
326
+
327
+ .vi-vdt-table {
328
+ flex: 1;
329
+ min-block-size: 0;
330
+ }
331
+ </style>