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.
- package/README.md +67 -41
- package/index.d.ts +18 -9
- package/index.js +6 -3
- package/package.json +3 -5
- package/src/components/Browser/Browser.vue +58 -58
- package/src/components/Browser/TreeNav.vue +32 -32
- package/src/components/Browser/ViurBrowser.d.vue.ts +21 -21
- package/src/components/Browser/ViurBrowser.vue +47 -44
- package/src/components/Browser/dragSession.ts +27 -27
- package/src/components/Browser/index.ts +3 -3
- package/src/components/Browser/types.ts +9 -9
- package/src/components/DataTable/ColumnFilterPanel.vue +7 -7
- package/src/components/DataTable/DataTable.vue +346 -346
- package/src/components/DataTable/ViurDataTable.d.vue.ts +26 -26
- package/src/components/DataTable/ViurDataTable.vue +51 -50
- package/src/components/DataTable/columns.ts +30 -30
- package/src/components/DataTable/index.ts +12 -12
- package/src/components/DataTable/rows.ts +71 -72
- package/src/components/DataTable/types.ts +55 -55
- package/src/components/DataTable/useColumnLayout.ts +28 -28
- package/src/components/DataTable/useGridNavigation.ts +15 -15
- package/src/components/DataTable/useVirtualRows.ts +10 -10
- package/src/components/DataTable/viurColumns.ts +69 -69
- package/src/forms/ViForm.d.vue.ts +32 -32
- package/src/forms/ViForm.vue +17 -16
- package/src/forms/ViFormCategory.vue +3 -1
- package/src/forms/bones/Bone.vue +125 -108
- package/src/forms/bones/BoneActions.vue +21 -21
- package/src/forms/bones/BoneLabel.vue +1 -1
- package/src/forms/bones/BoneMultipleWrapper.vue +3 -3
- package/src/forms/bones/BoneNestedWrapper.vue +3 -3
- package/src/forms/bones/BoneSingleEntry.vue +2 -2
- package/src/forms/bones/FilterBone.vue +3 -3
- package/src/forms/bones/actions/ActionDialog.vue +2 -2
- package/src/forms/bones/actions/Debug.vue +6 -6
- package/src/forms/bones/actions/Delete.vue +2 -2
- package/src/forms/bones/actions/DescribeImage.vue +4 -3
- package/src/forms/bones/actions/Download.vue +4 -4
- package/src/forms/bones/actions/Placeholder.vue +2 -2
- package/src/forms/bones/actions/index.js +72 -74
- package/src/forms/bones/bone.d.ts +19 -19
- package/src/forms/bones/uploadAdapter.js +3 -3
- package/src/forms/bones/utils.js +40 -41
- package/src/forms/bones/view/base_item.vue +1 -1
- package/src/forms/bones/view/boneLogic.js +1 -1
- package/src/forms/bones/widgets/booleanBone.vue +19 -19
- package/src/forms/bones/widgets/booleanBoneChoose.vue +2 -2
- package/src/forms/bones/widgets/booleanBoneSelect.vue +3 -3
- package/src/forms/bones/widgets/codeBone.vue +22 -22
- package/src/forms/bones/widgets/colorBone.vue +18 -18
- package/src/forms/bones/widgets/dateBone.vue +22 -22
- package/src/forms/bones/widgets/emailBone.vue +20 -20
- package/src/forms/bones/widgets/fileBone.vue +32 -31
- package/src/forms/bones/widgets/keyBone.vue +21 -21
- package/src/forms/bones/widgets/numericBone.vue +21 -21
- package/src/forms/bones/widgets/passwordBone.vue +34 -31
- package/src/forms/bones/widgets/phoneBone.vue +20 -20
- package/src/forms/bones/widgets/rawBone.vue +19 -19
- package/src/forms/bones/widgets/recordBone.vue +16 -16
- package/src/forms/bones/widgets/relationalBone.vue +26 -26
- package/src/forms/bones/widgets/selectBone.vue +23 -23
- package/src/forms/bones/widgets/selectBoneChoose.vue +2 -2
- package/src/forms/bones/widgets/spatialBone.vue +21 -21
- package/src/forms/bones/widgets/stringBone.vue +27 -27
- package/src/forms/bones/widgets/textBone.vue +28 -26
- package/src/forms/install.d.ts +1 -1
- package/src/forms/install.js +7 -0
- package/src/forms/layouts/LayloutList2Columns.vue +1 -1
- package/src/forms/layouts/LayoutCategory.vue +1 -1
- package/src/forms/layouts/LayoutList.vue +1 -1
- package/src/forms/utils.js +22 -22
- package/src/request/configStore.js +4 -4
- package/src/request/envelope.js +6 -6
- package/src/request/request.js +22 -22
- package/src/translations/de.js +31 -0
- package/src/translations/en.js +31 -0
- package/src/user/LoginScreen.d.vue.ts +24 -24
- package/src/user/LoginScreen.vue +6 -7
- package/src/user/store.d.ts +16 -16
- package/src/vite/safeHtml.js +73 -0
- package/src/vite/sanitizeVHtml.d.ts +4 -0
- package/src/vite/sanitizeVHtml.js +15 -0
- package/src/webawesomeCompat.css +0 -40
package/src/forms/bones/Bone.vue
CHANGED
|
@@ -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:
|
|
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
|
-
<!--
|
|
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
|
|
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
|
-
<!--
|
|
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
|
-
<!--
|
|
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 -
|
|
199
|
-
* @emits change-internal {Object} -
|
|
200
|
-
* @emits handleClick -
|
|
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, //
|
|
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
|
|
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, //
|
|
215
|
+
default: undefined, // Explicit language list; overrides bonestructure.languages
|
|
216
216
|
},
|
|
217
217
|
multiple: {
|
|
218
218
|
type: Boolean,
|
|
219
|
-
default: false, //
|
|
219
|
+
default: false, // Explicit multiple flag; overrides bonestructure.multiple
|
|
220
220
|
},
|
|
221
221
|
readonly: {
|
|
222
222
|
type: Boolean,
|
|
223
|
-
default: false, //
|
|
223
|
+
default: false, // Explicit readonly flag; overrides bonestructure.readonly
|
|
224
224
|
},
|
|
225
225
|
required: {
|
|
226
226
|
type: Boolean,
|
|
227
|
-
default: false, //
|
|
227
|
+
default: false, // Explicit required flag; overrides bonestructure.required
|
|
228
228
|
},
|
|
229
229
|
params: {
|
|
230
230
|
type: Object,
|
|
231
|
-
default: undefined, //
|
|
231
|
+
default: undefined, // Additional parameters; overrides bonestructure.params
|
|
232
232
|
},
|
|
233
233
|
value: {
|
|
234
234
|
type: [Object, String, Number, Boolean, Array],
|
|
235
|
-
default: undefined, //
|
|
235
|
+
default: undefined, // Explicit value; overrides skel[name] if set
|
|
236
236
|
},
|
|
237
237
|
structure: {
|
|
238
238
|
type: Object,
|
|
239
|
-
required: true, //
|
|
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
|
|
243
|
+
required: true, // Skeleton data object with the current values
|
|
244
244
|
},
|
|
245
245
|
errors: {
|
|
246
246
|
type: Array,
|
|
247
|
-
default: () => [], //
|
|
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
|
|
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, //
|
|
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, //
|
|
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, //
|
|
266
|
+
default: false, // Passes autofocus on to the bone widget
|
|
267
267
|
},
|
|
268
268
|
defaultLanguage: {
|
|
269
269
|
type: String,
|
|
270
|
-
default: "de", //
|
|
270
|
+
default: "de", // Language active by default in the multilang tab
|
|
271
271
|
},
|
|
272
272
|
showBoneTooltip: {
|
|
273
273
|
type: Boolean,
|
|
274
|
-
default: false, //
|
|
274
|
+
default: false, // Forces the tooltip on the input area
|
|
275
275
|
},
|
|
276
276
|
errorStyle: {
|
|
277
277
|
type: String,
|
|
278
|
-
default: "default", //
|
|
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, //
|
|
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
|
-
//
|
|
291
|
+
// For the visibility of the relation selector (action `when` checks for it).
|
|
292
292
|
const relationSelector = inject("viurRelationSelector", null)
|
|
293
|
-
//
|
|
293
|
+
// Form-wide action configuration (ViForm `actions` prop).
|
|
294
294
|
const boneActionsConfig = inject("boneActionsConfig", null)
|
|
295
|
-
/**
|
|
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
|
-
*
|
|
301
|
-
* @param {Object} obj -
|
|
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), //
|
|
318
|
+
boneactions: computed(() => props.boneactions), // Whether BoneActions buttons are shown
|
|
319
319
|
|
|
320
|
-
//
|
|
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, //
|
|
325
|
-
multilanguage: computed(() => state.languages?.length && state.languages.length > 0), //
|
|
326
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
353
|
+
// Whether a tooltip text exists in bonestructure.params
|
|
354
354
|
hasTooltip: computed(() => {
|
|
355
|
-
// `params`
|
|
356
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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), //
|
|
416
|
-
//
|
|
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), //
|
|
427
|
-
currentLanguage: null, //
|
|
428
|
-
debug: computed(() => props.debug), // Debug
|
|
429
|
-
labelTooltipTargetId: computed(() => buildBoneDomId(unref(bonePath) ?? [], "label-tooltip")), //
|
|
430
|
-
boneTooltipTargetId: computed(() => buildBoneDomId(unref(bonePath) ?? [], "field-tooltip")), //
|
|
431
|
-
//
|
|
432
|
-
//
|
|
433
|
-
//
|
|
434
|
-
// (when: relationSelector)
|
|
435
|
-
//
|
|
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
|
-
//
|
|
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
|
-
*
|
|
453
|
-
*
|
|
454
|
-
* @param {Array<Object>} arr - Multiple
|
|
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
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
* @param {string} name -
|
|
474
|
-
* @param {unknown} val -
|
|
475
|
-
* @param {string|null} lang -
|
|
476
|
-
* @param {number|null} index -
|
|
477
|
-
* @param {boolean} valid -
|
|
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
|
-
*
|
|
514
|
-
*
|
|
515
|
-
* @param {string|null} lang -
|
|
516
|
-
* @param {unknown} data -
|
|
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
|
-
*
|
|
536
|
-
* @param {string|null} lang -
|
|
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
|
-
*
|
|
548
|
-
* @param {number} index -
|
|
549
|
-
* @param {string|null} lang -
|
|
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
|
-
*
|
|
562
|
-
*
|
|
563
|
-
* @param {string|null} lang -
|
|
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
|
-
*
|
|
576
|
-
* @param {Event} e -
|
|
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
|
-
*
|
|
584
|
-
* @param {string} formatstr -
|
|
585
|
-
* @param {unknown} boneValue -
|
|
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
|
-
*
|
|
594
|
-
* @param {string} error -
|
|
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
|
-
//
|
|
614
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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:
|
|
649
|
-
|
|
650
|
-
|
|
651
|
+
display: flex;
|
|
652
|
+
flex-wrap: wrap;
|
|
653
|
+
gap: var(--wa-space-s);
|
|
651
654
|
margin-bottom: 20px;
|
|
652
655
|
|
|
653
|
-
|
|
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
|
-
|
|
657
|
-
|
|
672
|
+
flex-direction: column;
|
|
673
|
+
flex-wrap: nowrap;
|
|
658
674
|
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
/*
|
|
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
|
-
/*
|
|
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
|
-
/*
|
|
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
|
|
7
|
-
|
|
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
|
-
*
|
|
47
|
-
* - "entry":
|
|
48
|
-
* - "value":
|
|
49
|
-
*
|
|
50
|
-
* - "bone":
|
|
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
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
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
|
-
//
|
|
65
|
-
//
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
88
|
+
/** Append another entry to the multiple array (multi-selection). */
|
|
89
89
|
appendEntry: (val) => addMultipleEntry?.(props.lang ?? null, val),
|
|
90
|
-
/**
|
|
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
|
-
//
|
|
96
|
-
//
|
|
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
|
-
//
|
|
100
|
-
//
|
|
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) {
|