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
@@ -62,7 +62,7 @@
62
62
  class="bone-inner wa-stack wa-gap-xs"
63
63
  :class="{ 'is-required': state.required }"
64
64
  >
65
- <!-- Multilanguage: Tab per Sprache -->
65
+ <!-- Multilanguage: one tab per language -->
66
66
  <div
67
67
  v-if="state.multilanguage"
68
68
  class="bone-row wa-flank:end wa-gap-xs wa-align-items-start"
@@ -98,7 +98,7 @@
98
98
  :readonly="state.readonly"
99
99
  :actionbar="state.actionbar"
100
100
  />
101
- <!-- Wert-Actions (Löschen, Download …) je Sprache am Eintrag -->
101
+ <!-- Value actions (delete, download …) per language on the entry -->
102
102
  <bone-single-entry
103
103
  :is="is"
104
104
  v-else
@@ -111,7 +111,7 @@
111
111
  </wa-tab-panel>
112
112
  </template>
113
113
  </wa-tab-group>
114
- <!-- Bone-Actions über alle Sprachen (Übersetzen …) -->
114
+ <!-- Bone actions across all languages (translate …) -->
115
115
  <div
116
116
  v-if="state.boneactions && !state.multiple && state.hasOptionalActions"
117
117
  class="wa-cluster wa-gap-xs"
@@ -127,7 +127,7 @@
127
127
  </div>
128
128
  </div>
129
129
 
130
- <!-- Einsprachig -->
130
+ <!-- Single-language -->
131
131
  <template v-else>
132
132
  <bone-multiple-list
133
133
  :is="is"
@@ -149,7 +149,7 @@
149
149
  />
150
150
  </template>
151
151
 
152
- <!-- Fehlermeldungen -->
152
+ <!-- Error messages -->
153
153
  <template v-if="errorStyle === 'default'">
154
154
  <wa-callout
155
155
  v-for="message in state.errorMessages"
@@ -185,120 +185,120 @@
185
185
  /** @displayName Bone */
186
186
  import { reactive, computed, onBeforeMount, provide, watch, watchEffect, inject, unref } from "vue"
187
187
  import BoneLabel from "./BoneLabel.vue"
188
- import { BoneHasMultipleHandling, getBoneActionbar } from "./index"
188
+ import { BoneHasMultipleHandling, getBoneActionbar } from "./index.js"
189
189
  import rawBone from "./widgets/rawBone.vue"
190
- import Utils, { buildBoneDomId } from "./utils"
190
+ import Utils, { buildBoneDomId } from "./utils.js"
191
191
  import BoneActions from "./BoneActions.vue"
192
- import { formConfiguredActions, resolveActions } from "./actions"
192
+ import { formConfiguredActions, resolveActions } from "./actions/index.js"
193
193
  import BoneMultipleList from "./BoneMultipleList.vue"
194
194
  import BoneSingleEntry from "./BoneSingleEntry.vue"
195
195
  import { useI18n } from "vue-i18n"
196
196
 
197
197
  /**
198
- * @emits change - Historisches Relikt, wird nicht direkt genutzt
199
- * @emits change-internal {Object} - Internes Event für ViForm: { name, value, lang, index, valid }
200
- * @emits handleClick - Historisches Relikt, wird nicht direkt genutzt
198
+ * @emits change - Historical relic, not used directly
199
+ * @emits change-internal {Object} - Internal event for ViForm: { name, value, lang, index, valid }
200
+ * @emits handleClick - Historical relic, not used directly
201
201
  */
202
202
  const emit = defineEmits(["change", "change-internal", "handleClick"])
203
203
 
204
204
  const props = defineProps({
205
205
  is: {
206
206
  type: Object,
207
- default: rawBone, // Das zu rendernde Bone-Widget (z.B. StringBone), typischerweise von ViForm gesetzt
207
+ default: rawBone, // The bone widget to render (e.g. StringBone), typically set by ViForm
208
208
  },
209
209
  name: {
210
210
  type: String,
211
- required: true, // Bone-Identifier; muss einem Key in structure und skel entsprechen
211
+ required: true, // Bone identifier; must match a key in structure and skel
212
212
  },
213
213
  languages: {
214
214
  type: Array,
215
- default: undefined, // Explizite Sprach-Liste; überschreibt bonestructure.languages
215
+ default: undefined, // Explicit language list; overrides bonestructure.languages
216
216
  },
217
217
  multiple: {
218
218
  type: Boolean,
219
- default: false, // Expliziter Multiple-Flag; überschreibt bonestructure.multiple
219
+ default: false, // Explicit multiple flag; overrides bonestructure.multiple
220
220
  },
221
221
  readonly: {
222
222
  type: Boolean,
223
- default: false, // Expliziter Readonly-Flag; überschreibt bonestructure.readonly
223
+ default: false, // Explicit readonly flag; overrides bonestructure.readonly
224
224
  },
225
225
  required: {
226
226
  type: Boolean,
227
- default: false, // Expliziter Required-Flag; überschreibt bonestructure.required
227
+ default: false, // Explicit required flag; overrides bonestructure.required
228
228
  },
229
229
  params: {
230
230
  type: Object,
231
- default: undefined, // Zusätzliche Parameter; überschreibt bonestructure.params
231
+ default: undefined, // Additional parameters; overrides bonestructure.params
232
232
  },
233
233
  value: {
234
234
  type: [Object, String, Number, Boolean, Array],
235
- default: undefined, // Expliziter Wert; überschreibt skel[name] wenn gesetzt
235
+ default: undefined, // Explicit value; overrides skel[name] if set
236
236
  },
237
237
  structure: {
238
238
  type: Object,
239
- required: true, // Vollständige Skeleton-Struktur (alle Bone-Definitionen des Formulars)
239
+ required: true, // Complete skeleton structure (all bone definitions of the form)
240
240
  },
241
241
  skel: {
242
242
  type: null,
243
- required: true, // Skeleton-Datenobjekt mit den aktuellen Werten
243
+ required: true, // Skeleton data object with the current values
244
244
  },
245
245
  errors: {
246
246
  type: Array,
247
- default: () => [], // Fehler-Array aus der Server-Antwort; Einträge mit passendem fieldPath werden angezeigt
247
+ default: () => [], // Error array from the server response; entries with a matching fieldPath are displayed
248
248
  },
249
249
  label: {
250
250
  type: String,
251
- default: "normal", // Label-Modus: "normal" | "top" | "hide" | "placeholder"
251
+ default: "normal", // Label mode: "normal" | "top" | "hide" | "placeholder"
252
252
  validator(value, props) {
253
253
  return ["normal", "top", "hide", "placeholder"].includes(value)
254
254
  },
255
255
  },
256
256
  boneactions: {
257
257
  type: Boolean,
258
- default: true, // Aktiviert BoneActions (Defaults pro Bone-Type + bone.params.actions). Explizit false um Actions zu unterdrücken.
258
+ default: true, // Enables BoneActions (defaults per bone type + bone.params.actions). Explicitly false to suppress actions.
259
259
  },
260
260
  showLabelInfo: {
261
261
  type: Boolean,
262
- default: false, // Zeigt Tooltip-Inhalt als wa-alert statt als Tooltip
262
+ default: false, // Shows tooltip content as wa-alert instead of a tooltip
263
263
  },
264
264
  autofocus: {
265
265
  type: Boolean,
266
- default: false, // Gibt autofocus an das Bone-Widget weiter
266
+ default: false, // Passes autofocus on to the bone widget
267
267
  },
268
268
  defaultLanguage: {
269
269
  type: String,
270
- default: "de", // Standardmäßig aktive Sprache im Multilang-Tab
270
+ default: "de", // Language active by default in the multilang tab
271
271
  },
272
272
  showBoneTooltip: {
273
273
  type: Boolean,
274
- default: false, // Erzwingt den Tooltip auf dem Input-Bereich
274
+ default: false, // Forces the tooltip on the input area
275
275
  },
276
276
  errorStyle: {
277
277
  type: String,
278
- default: "default", // Fehler-Darstellungsstil: "default" (wa-alert) | "decent" (kleiner roter Text)
278
+ default: "default", // Error display style: "default" (wa-alert) | "decent" (small red text)
279
279
  validator(value, props) {
280
280
  return ["default", "decent"].includes(value)
281
281
  },
282
282
  },
283
283
  debug: {
284
284
  type: Boolean,
285
- default: false, // Aktiviert Debug-Modus: Bone-Name als Tooltip am Label
285
+ default: false, // Enables debug mode: bone name as tooltip on the label
286
286
  },
287
287
  })
288
288
  const { t } = useI18n()
289
289
 
290
290
  const parentBonePath = inject("bonePath", [])
291
- // Für die Sichtbarkeit der Relations-Auswahl (Action-`when` prüft darauf).
291
+ // For the visibility of the relation selector (action `when` checks for it).
292
292
  const relationSelector = inject("viurRelationSelector", null)
293
- // Formular-weite Action-Konfiguration (ViForm `actions`-Prop).
293
+ // Form-wide action configuration (ViForm `actions` prop).
294
294
  const boneActionsConfig = inject("boneActionsConfig", null)
295
- /** Vollständiger Bone-Pfad als Array für verschachtelte Bone-DOM-IDs. @returns {ComputedRef<string[]>} */
295
+ /** Full bone path as an array for nested bone DOM IDs. @returns {ComputedRef<string[]>} */
296
296
  const bonePath = computed(() => [...(unref(parentBonePath) ?? []), props.name])
297
297
  provide("bonePath", bonePath)
298
298
 
299
299
  /**
300
- * Prüft ob ein Objekt ausschließlich null-Werte oder leere Arrays enthält.
301
- * @param {Object} obj - Das zu prüfende Objekt
300
+ * Checks whether an object contains only null values or empty arrays.
301
+ * @param {Object} obj - The object to check
302
302
  * @returns {boolean}
303
303
  */
304
304
  function isObjectEmpty(obj) {
@@ -315,15 +315,15 @@ function isObjectEmpty(obj) {
315
315
  }
316
316
 
317
317
  const state = reactive({
318
- boneactions: computed(() => props.boneactions), // Ob BoneActions-Schaltflächen angezeigt werden
318
+ boneactions: computed(() => props.boneactions), // Whether BoneActions buttons are shown
319
319
 
320
- // Die Bone-Definition aus der Struktur (z.B. type, params, languages)
320
+ // The bone definition from the structure (e.g. type, params, languages)
321
321
  bonestructure: computed(() => {
322
322
  return props.structure?.[props.name]
323
323
  }),
324
- bonevalue: null, // Aktueller Bone-Wert; wird aus skel initialisiert und bei Änderungen aktualisiert
325
- multilanguage: computed(() => state.languages?.length && state.languages.length > 0), // Ob der Bone mehrsprachig ist (mindestens eine Sprache konfiguriert)
326
- // Aktive Sprach-Liste: aus props.languages oder bonestructure.languages
324
+ bonevalue: null, // Current bone value; initialized from skel and updated on changes
325
+ multilanguage: computed(() => state.languages?.length && state.languages.length > 0), // Whether the bone is multilingual (at least one language configured)
326
+ // Active language list: from props.languages or bonestructure.languages
327
327
  languages: computed(() => {
328
328
  if (props.languages) {
329
329
  return props.languages
@@ -332,7 +332,7 @@ const state = reactive({
332
332
  ? state.bonestructure["languages"]
333
333
  : []
334
334
  }),
335
- // Readonly-Status: props.readonly hat Vorrang vor bonestructure.readonly
335
+ // Readonly status: props.readonly takes precedence over bonestructure.readonly
336
336
  readonly: computed(() => {
337
337
  if (props.readonly) {
338
338
  return props.readonly
@@ -341,7 +341,7 @@ const state = reactive({
341
341
  ? state.bonestructure["readonly"]
342
342
  : false
343
343
  }),
344
- // Required-Status: props.required hat Vorrang vor bonestructure.required
344
+ // Required status: props.required takes precedence over bonestructure.required
345
345
  required: computed(() => {
346
346
  if (props.required) {
347
347
  return props.required
@@ -350,13 +350,13 @@ const state = reactive({
350
350
  ? state.bonestructure["required"]
351
351
  : false
352
352
  }),
353
- // Ob ein Tooltip-Text in bonestructure.params vorhanden ist
353
+ // Whether a tooltip text exists in bonestructure.params
354
354
  hasTooltip: computed(() => {
355
- // `params` kommt von viur-core immer als Objekt; fehlt es (eigener Renderer,
356
- // handgebaute Struktur), darf das Formular davon nicht sterben.
355
+ // `params` always comes from viur-core as an object; if it's missing (custom renderer,
356
+ // hand-built structure), the form must not die from it.
357
357
  return state.bonestructure && Object.keys(state.bonestructure["params"] ?? {}).includes("tooltip") ? true : false
358
358
  }),
359
- // Multiple-Status: props.multiple hat Vorrang vor bonestructure.multiple
359
+ // Multiple status: props.multiple takes precedence over bonestructure.multiple
360
360
  multiple: computed(() => {
361
361
  if (props.multiple) {
362
362
  return props.multiple
@@ -365,7 +365,7 @@ const state = reactive({
365
365
  ? state.bonestructure["multiple"]
366
366
  : false
367
367
  }),
368
- // Bone-Parameter: props.params hat Vorrang vor bonestructure.params
368
+ // Bone parameters: props.params take precedence over bonestructure.params
369
369
  params: computed(() => {
370
370
  if (props.params) {
371
371
  return props.params
@@ -374,11 +374,11 @@ const state = reactive({
374
374
  ? state.bonestructure["params"]
375
375
  : {}
376
376
  }),
377
- // Die Actionbar-Komponente passend zum Bone-Typ (z.B. für File, Relational)
377
+ // The actionbar component matching the bone type (e.g. for file, relational)
378
378
  actionbar: computed(() => {
379
379
  return getBoneActionbar(state.bonestructure?.["type"])
380
380
  }),
381
- // Ob der Bone-Wert leer ist (null, leeres Array, leeres Objekt). Im Readonly-Modus immer false
381
+ // Whether the bone value is empty (null, empty array, empty object). Always false in readonly mode
382
382
  isEmpty: computed(() => {
383
383
  // Ignore the computation when the state is readonly
384
384
  if (state.readonly) return false
@@ -395,7 +395,7 @@ const state = reactive({
395
395
 
396
396
  return false
397
397
  }),
398
- // Fehlermeldungen aus props.errors, gefiltert auf diesen Bone (fieldPath + severity)
398
+ // Error messages from props.errors, filtered to this bone (fieldPath + severity)
399
399
  errorMessages: computed(() => {
400
400
  let errors = []
401
401
  for (let error of props.errors) {
@@ -412,8 +412,8 @@ const state = reactive({
412
412
  }
413
413
  return errors
414
414
  }),
415
- label: computed(() => props.label), // Aktueller Label-Modus (gespiegelt von props.label)
416
- // Ob der Bone-Tooltip angezeigt wird (bei hidden/placeholder-Label oder showBoneTooltip=true)
415
+ label: computed(() => props.label), // Current label mode (mirrored from props.label)
416
+ // Whether the bone tooltip is shown (with hidden/placeholder label or showBoneTooltip=true)
417
417
  showtooltip: computed(() => {
418
418
  if (props.showBoneTooltip) {
419
419
  return true
@@ -423,16 +423,16 @@ const state = reactive({
423
423
  }
424
424
  return false
425
425
  }),
426
- defaultLanguage: computed(() => props.defaultLanguage), // Standardsprache (gespiegelt von props.defaultLanguage)
427
- currentLanguage: null, // Aktuell aktiver Sprach-Tab
428
- debug: computed(() => props.debug), // Debug-Modus (gespiegelt von props.debug)
429
- labelTooltipTargetId: computed(() => buildBoneDomId(unref(bonePath) ?? [], "label-tooltip")), // Eindeutige DOM-ID für den Label-Tooltip-Anker
430
- boneTooltipTargetId: computed(() => buildBoneDomId(unref(bonePath) ?? [], "field-tooltip")), // Eindeutige DOM-ID für den Bone-Tooltip-Anker
431
- // Ob für diesen Bone überhaupt Actions auflösbar sind (für UI-Wrapper-Sichtbarkeit).
432
- // MIT demselben Extra-Kontext und derselben Konfigurations-Merge-Reihenfolge
433
- // wie BoneActions selbst — sonst fällt z.B. die Relations-Auswahl
434
- // (when: relationSelector) hier raus und der Wrapper versteckt eine real
435
- // vorhandene Action, oder eine per ViForm abgewählte bleibt "sichtbar".
426
+ defaultLanguage: computed(() => props.defaultLanguage), // Default language (mirrored from props.defaultLanguage)
427
+ currentLanguage: null, // Currently active language tab
428
+ debug: computed(() => props.debug), // Debug mode (mirrored from props.debug)
429
+ labelTooltipTargetId: computed(() => buildBoneDomId(unref(bonePath) ?? [], "label-tooltip")), // Unique DOM ID for the label tooltip anchor
430
+ boneTooltipTargetId: computed(() => buildBoneDomId(unref(bonePath) ?? [], "field-tooltip")), // Unique DOM ID for the bone tooltip anchor
431
+ // Whether any actions can be resolved for this bone at all (for UI wrapper visibility).
432
+ // WITH the same extra context and the same config merge order
433
+ // as BoneActions itself — otherwise e.g. the relation selector
434
+ // (when: relationSelector) drops out here and the wrapper hides an actually
435
+ // existing action, or one deselected via ViForm stays "visible".
436
436
  resolvedActions: computed(() =>
437
437
  resolveActions(
438
438
  state.bonestructure,
@@ -442,16 +442,16 @@ const state = reactive({
442
442
  )
443
443
  ),
444
444
  hasActions: computed(() => state.resolvedActions.length > 0),
445
- // Pflicht-Actions (Löschen, Download …) erscheinen auch ohne `boneactions`
445
+ // Required actions (delete, download …) appear even without `boneactions`
446
446
  hasRequiredActions: computed(() => state.resolvedActions.some((action) => action.required)),
447
447
  hasOptionalActions: computed(() => state.resolvedActions.some((action) => !action.required)),
448
448
  })
449
449
  provide("boneState", state)
450
450
 
451
451
  /**
452
- * Weist jedem Objekt im Array ein stabiles _key-Attribut (UUID) zu, falls noch keines vorhanden ist.
453
- * Wird via watchEffect aufgerufen um stabile :key-Bindings für vue-draggable sicherzustellen.
454
- * @param {Array<Object>} arr - Multiple-Array dessen Einträge Objekte sind
452
+ * Assigns a stable _key attribute (UUID) to each object in the array if it doesn't have one yet.
453
+ * Called via watchEffect to ensure stable :key bindings for vue-draggable.
454
+ * @param {Array<Object>} arr - Multiple array whose entries are objects
455
455
  */
456
456
  function ensureKeys(arr) {
457
457
  for (const it of arr) {
@@ -467,14 +467,14 @@ watchEffect(() => {
467
467
  })
468
468
 
469
469
  /**
470
- * Aktualisiert state.bonevalue anhand von lang/index-Kontext und feuert change-internal.
471
- * Bei valid=false wird der Wert nicht gespeichert, das Event aber trotzdem gefeuert.
472
- * Bei state.readonly wird kein Event gefeuert.
473
- * @param {string} name - Bone-Name
474
- * @param {unknown} val - Neuer Wert
475
- * @param {string|null} lang - Sprach-Key (bei Multilang-Bones)
476
- * @param {number|null} index - Index (bei Multiple-Bones)
477
- * @param {boolean} valid - Ob der Wert valide ist
470
+ * Updates state.bonevalue based on the lang/index context and fires change-internal.
471
+ * With valid=false the value is not stored, but the event is still fired.
472
+ * With state.readonly no event is fired.
473
+ * @param {string} name - bone name
474
+ * @param {unknown} val - new value
475
+ * @param {string|null} lang - language key (for multilang bones)
476
+ * @param {number|null} index - index (for multiple bones)
477
+ * @param {boolean} valid - whether the value is valid
478
478
  * @returns {false|void}
479
479
  */
480
480
  function updateValue(name, val, lang = null, index = null, valid = true) {
@@ -510,10 +510,10 @@ function updateValue(name, val, lang = null, index = null, valid = true) {
510
510
  }
511
511
 
512
512
  /**
513
- * Fügt einen neuen Eintrag zum Multiple-Array hinzu.
514
- * Wird via provide("addMultipleEntry") an Actionbars weitergegeben.
515
- * @param {string|null} lang - Sprach-Key (bei Multilang-Bones)
516
- * @param {unknown} data - Initialer Wert des neuen Eintrags
513
+ * Adds a new entry to the multiple array.
514
+ * Passed on to actionbars via provide("addMultipleEntry").
515
+ * @param {string|null} lang - language key (for multilang bones)
516
+ * @param {unknown} data - initial value of the new entry
517
517
  */
518
518
  function addMultipleEntry(lang = null, data = "") {
519
519
  if (lang) {
@@ -532,8 +532,8 @@ function addMultipleEntry(lang = null, data = "") {
532
532
  }
533
533
 
534
534
  /**
535
- * Feuert updateValue nach einer Multiple-Array-Mutation (splice).
536
- * @param {string|null} lang - Sprach-Key oder null
535
+ * Fires updateValue after a multiple array mutation (splice).
536
+ * @param {string|null} lang - language key or null
537
537
  */
538
538
  function emitMultipleUpdate(lang) {
539
539
  if (lang) {
@@ -544,9 +544,9 @@ function emitMultipleUpdate(lang) {
544
544
  }
545
545
 
546
546
  /**
547
- * Entfernt einen einzelnen Eintrag aus dem Multiple-Array per Index.
548
- * @param {number} index - Index des zu entfernenden Eintrags
549
- * @param {string|null} lang - Sprach-Key (bei Multilang-Bones)
547
+ * Removes a single entry from the multiple array by index.
548
+ * @param {number} index - index of the entry to remove
549
+ * @param {string|null} lang - language key (for multilang bones)
550
550
  */
551
551
  function removeMultipleEntry(index, lang = null) {
552
552
  if (lang) {
@@ -558,9 +558,9 @@ function removeMultipleEntry(index, lang = null) {
558
558
  }
559
559
 
560
560
  /**
561
- * Entfernt alle Einträge aus dem Multiple-Array.
562
- * Wird via provide("removeMultipleEntries") an Actionbars weitergegeben.
563
- * @param {string|null} lang - Sprach-Key (bei Multilang-Bones)
561
+ * Removes all entries from the multiple array.
562
+ * Passed on to actionbars via provide("removeMultipleEntries").
563
+ * @param {string|null} lang - language key (for multilang bones)
564
564
  */
565
565
  function removeMultipleEntries(lang = null) {
566
566
  if (lang) {
@@ -572,17 +572,17 @@ function removeMultipleEntries(lang = null) {
572
572
  }
573
573
 
574
574
  /**
575
- * Wechselt die aktive Sprache anhand des data-lang-Attributs des geklickten Tab-Elements.
576
- * @param {Event} e - Click-Event vom wa-tab
575
+ * Switches the active language based on the data-lang attribute of the clicked tab element.
576
+ * @param {Event} e - click event from the wa-tab
577
577
  */
578
578
  function updateLanguage(e) {
579
579
  state.currentLanguage = e.target.dataset.lang
580
580
  }
581
581
 
582
582
  /**
583
- * Delegiert an Utils.formatString — wird via provide("formatString") für Relational-Bones bereitgestellt.
584
- * @param {string} formatstr - Format-Template
585
- * @param {unknown} boneValue - Bone-Wert der in das Template eingesetzt wird
583
+ * Delegates to Utils.formatString — provided via provide("formatString") for relational bones.
584
+ * @param {string} formatstr - format template
585
+ * @param {unknown} boneValue - bone value inserted into the template
586
586
  * @returns {string}
587
587
  */
588
588
  function formatString(formatstr, boneValue) {
@@ -590,8 +590,8 @@ function formatString(formatstr, boneValue) {
590
590
  }
591
591
 
592
592
  /**
593
- * Übersetzt einen Fehler-Key via vue-i18n (errors.{key}). Fällt auf den rohen String zurück.
594
- * @param {string} error - Fehler-Key oder roher Fehlertext
593
+ * Translates an error key via vue-i18n (errors.{key}). Falls back to the raw string.
594
+ * @param {string} error - error key or raw error text
595
595
  * @returns {string}
596
596
  */
597
597
  function errorMessage(error) {
@@ -610,8 +610,8 @@ watch(
610
610
  }
611
611
  )
612
612
 
613
- // Sprach-Tab wählen: die gewünschte Sprache — führt der Bone sie nicht,
614
- // die erste seiner Sprachen (sonst wäre KEIN Tab aktiv).
613
+ // Pick the language tab: the requested language — if the bone doesn't have it,
614
+ // the first of its languages (otherwise NO tab would be active).
615
615
  function pickLanguage(wanted) {
616
616
  if (!state.languages?.length || state.languages.includes(wanted)) return wanted
617
617
  return state.languages[0]
@@ -635,7 +635,7 @@ onBeforeMount(() => {
635
635
 
636
636
  provide("updateValue", updateValue)
637
637
  provide("addMultipleEntry", addMultipleEntry)
638
- // Für die Pflicht-Action "removeEntry" (Zeilen-X der Multiple-Listen).
638
+ // For the required action "removeEntry" (row X of the multiple lists).
639
639
  provide("removeMultipleEntry", removeMultipleEntry)
640
640
  provide("removeMultipleEntries", removeMultipleEntries)
641
641
  provide("formatString", formatString)
@@ -643,21 +643,38 @@ provide("formatString", formatString)
643
643
 
644
644
  <style scoped>
645
645
  @layer foundation.form {
646
- /* Bone-Wrapper: Label links, Inhalt rechts (auf Mobile gestapelt) */
646
+ /* Bone wrapper: label left, content right. Wraps below the label as soon as the
647
+ content gets less than 20rem in its own container (not the viewport), so nested
648
+ forms stack too. The content's huge flex-grow keeps the label at ~235px while
649
+ side by side; once wrapped, the label spans the full width. */
647
650
  .bone {
648
- display: grid;
649
- grid-template-columns: 235px 1fr;
650
- grid-gap: var(--wa-space-s);
651
+ display: flex;
652
+ flex-wrap: wrap;
653
+ gap: var(--wa-space-s);
651
654
  margin-bottom: 20px;
652
655
 
653
- /* Label über dem Inhalt oder gar keins: eine Spalte auf jeder Breite */
656
+ & > :deep(.bone-label) {
657
+ flex: 1 1 235px;
658
+ }
659
+
660
+ & > .bone-inner {
661
+ flex: 999 1 20rem;
662
+ min-width: 0;
663
+ }
664
+
665
+ & > .bone-label-info {
666
+ flex-basis: 100%;
667
+ }
668
+
669
+ /* Label above the content or none at all: one column at every width */
654
670
  &.is-label-top,
655
671
  &.is-label-hide {
656
- grid-template-columns: 1fr;
657
- }
672
+ flex-direction: column;
673
+ flex-wrap: nowrap;
658
674
 
659
- @media (max-width: 900px) {
660
- grid-template-columns: 1fr;
675
+ & > .bone-inner {
676
+ flex: 1 1 auto;
677
+ }
661
678
  }
662
679
  }
663
680
  .bone-label-required {
@@ -665,7 +682,7 @@ provide("formatString", formatString)
665
682
  font-weight: 700;
666
683
  }
667
684
 
668
- /* Fehler-Callout unter dem Bone-Inhalt */
685
+ /* Error callout below the bone content */
669
686
  wa-callout.bone-error {
670
687
  padding: var(--wa-space-xs);
671
688
  border: 1px solid var(--wa-color-border-loud);
@@ -677,7 +694,7 @@ provide("formatString", formatString)
677
694
  }
678
695
  }
679
696
 
680
- /* Sprachumschaltung: Tabs unter dem Eingabefeld (Multilang-Bones) */
697
+ /* Language switcher: tabs below the input field (multilang bones) */
681
698
  .bone-lang-tabs {
682
699
  width: 100%;
683
700
  --track-width: 0;
@@ -710,7 +727,7 @@ provide("formatString", formatString)
710
727
  }
711
728
  }
712
729
 
713
- /* abgerundete Außenecken; Nachbar-Tabs überlappen sich um 1px */
730
+ /* rounded outer corners; neighboring tabs overlap by 1px */
714
731
  &:first-child::part(base) {
715
732
  margin-left: 1rem;
716
733
  border-bottom-left-radius: 5px;
@@ -3,8 +3,8 @@
3
3
  v-for="(action, i) in state.actions"
4
4
  :key="action.arg + '_' + name + '_' + lang + '_' + i"
5
5
  >
6
- <!-- title darf ein i18n-Key sein (z.B. "bone.del") — Klartext bleibt
7
- Klartext ($te-Prüfung, keine missing-Key-Warnungen). -->
6
+ <!-- title may be an i18n key (e.g. "bone.del") — plain text stays
7
+ plain text ($te check, no missing-key warnings). -->
8
8
  <wa-button
9
9
  :disabled="isActionDisabled(action, ctx)"
10
10
  size="medium"
@@ -31,7 +31,7 @@
31
31
 
32
32
  <script setup>
33
33
  import { reactive, computed, onMounted, inject } from "vue"
34
- import { resolveActions, isActionDisabled, getActionHandler, formConfiguredActions } from "./actions"
34
+ import { resolveActions, isActionDisabled, getActionHandler, formConfiguredActions } from "./actions/index.js"
35
35
  import ActionDialog from "./actions/ActionDialog.vue"
36
36
 
37
37
  const emit = defineEmits(["change"])
@@ -43,11 +43,11 @@ const props = defineProps({
43
43
  lang: String,
44
44
  bone: Object,
45
45
  /**
46
- * Welche Actions diese Leiste zeigt:
47
- * - "entry": alle bei `boneactions`, sonst nur die Pflicht-Actions (Standard)
48
- * - "value": nur die Pflicht-Actions — am Wert EINER Sprache eines
49
- * mehrsprachigen Bones (Löschen/Download betreffen nur diese Sprache)
50
- * - "bone": nur die übrigen — die Leiste neben den Sprach-Tabs (Übersetzen …)
46
+ * Which actions this bar shows:
47
+ * - "entry": all with `boneactions`, otherwise only the required actions (default)
48
+ * - "value": only the required actions — on the value of ONE language of a
49
+ * multilingual bone (delete/download only affect this language)
50
+ * - "bone": only the rest — the bar next to the language tabs (translate …)
51
51
  */
52
52
  scope: {
53
53
  type: String,
@@ -57,22 +57,22 @@ const props = defineProps({
57
57
  })
58
58
 
59
59
  const boneState = inject("boneState")
60
- // Vom HOST bereitgestellte Auswahl für relationale Bones (z.B. ein Admin, der
61
- // einen Listen-Dialog öffnet): `(request) => Promise<Row[]|null>`. Ohne
62
- // Provider existiert die zugehörige Action schlicht nicht (`when`-Prädikat).
60
+ // Selector for relational bones provided by the HOST (e.g. an admin that
61
+ // opens a list dialog): `(request) => Promise<Row[]|null>`. Without a
62
+ // provider the corresponding action simply doesn't exist (`when` predicate).
63
63
  const relationSelector = inject("viurRelationSelector", null)
64
- // Für Mehrfach-Übernahme in multiple-Bones (hängt weitere Einträge an) und
65
- // die Pflicht-Action "Zeile entfernen".
64
+ // For multi-selection into multiple bones (appends further entries) and
65
+ // the required action "remove row".
66
66
  const addMultipleEntry = inject("addMultipleEntry", null)
67
67
  const removeMultipleEntry = inject("removeMultipleEntry", null)
68
- // Formular-weite Action-Konfiguration (ViForm `actions`-Prop).
68
+ // Form-wide action configuration (ViForm `actions` prop).
69
69
  const boneActionsConfig = inject("boneActionsConfig", null)
70
70
 
71
71
  const state = reactive({
72
72
  actions: [],
73
73
  })
74
74
 
75
- /** Steckt diese Action-Leiste in einer ZEILE eines multiple-Bones? */
75
+ /** Is this action bar inside a ROW of a multiple bone? */
76
76
  const multipleRow = props.index !== null && props.index !== undefined
77
77
 
78
78
  const ctx = computed(() => ({
@@ -85,19 +85,19 @@ const ctx = computed(() => ({
85
85
  relationSelector,
86
86
  multipleRow,
87
87
  update: (val) => emit("change", props.name, val, props.lang, props.index),
88
- /** Weiteren Eintrag ans Multiple-Array hängen (Mehrfach-Auswahl). */
88
+ /** Append another entry to the multiple array (multi-selection). */
89
89
  appendEntry: (val) => addMultipleEntry?.(props.lang ?? null, val),
90
- /** DIESE Zeile aus dem Multiple-Array entfernen (Pflicht-Action). */
90
+ /** Remove THIS row from the multiple array (required action). */
91
91
  removeEntry: () => removeMultipleEntry?.(props.index, props.lang ?? null),
92
92
  }))
93
93
 
94
94
  onMounted(() => {
95
- // Formular-Konfiguration VOR den Bone-eigenen params.actions (Bone-Lokales
96
- // ist spezifischer); Minus-Einträge gewinnen quellenunabhängig immer.
95
+ // Form configuration BEFORE the bone's own params.actions (bone-local
96
+ // is more specific); minus entries always win regardless of source.
97
97
  const configured = [...formConfiguredActions(boneActionsConfig, props.name), ...(props.bone?.params?.actions ?? [])]
98
98
  const all = resolveActions(props.bone, configured, boneState, { relationSelector, multipleRow })
99
- // Ohne `boneactions` bleiben nur die PFLICHT-Actions (Gerüst, z.B. das
100
- // Zeilen-X) — die Leiste als Ganzes ist Opt-in, das Gerüst nicht.
99
+ // Without `boneactions` only the REQUIRED actions remain (scaffolding, e.g. the
100
+ // row X) — the bar as a whole is opt-in, the scaffolding is not.
101
101
  if (props.scope === "bone") {
102
102
  state.actions = all.filter((action) => !action.required)
103
103
  } else if (props.scope === "value" || !boneState?.boneactions) {