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.
- package/README.md +79 -0
- package/index.d.ts +351 -0
- package/index.js +46 -0
- package/package.json +84 -0
- package/src/components/Browser/Browser.vue +496 -0
- package/src/components/Browser/TreeNav.vue +408 -0
- package/src/components/Browser/ViurBrowser.d.vue.ts +64 -0
- package/src/components/Browser/ViurBrowser.vue +271 -0
- package/src/components/Browser/dragSession.ts +134 -0
- package/src/components/Browser/index.ts +14 -0
- package/src/components/Browser/types.ts +30 -0
- package/src/components/DataTable/ColumnFilterPanel.vue +225 -0
- package/src/components/DataTable/DataTable.vue +3155 -0
- package/src/components/DataTable/ViurDataTable.d.vue.ts +72 -0
- package/src/components/DataTable/ViurDataTable.vue +331 -0
- package/src/components/DataTable/columns.ts +400 -0
- package/src/components/DataTable/index.ts +60 -0
- package/src/components/DataTable/rows.ts +446 -0
- package/src/components/DataTable/types.ts +267 -0
- package/src/components/DataTable/useColumnLayout.ts +294 -0
- package/src/components/DataTable/useGridNavigation.ts +193 -0
- package/src/components/DataTable/useVirtualRows.ts +126 -0
- package/src/components/DataTable/viurColumns.ts +330 -0
- package/src/forms/ViForm.d.vue.ts +106 -0
- package/src/forms/ViForm.vue +592 -0
- package/src/forms/ViFormCategory.vue +57 -0
- package/src/forms/bones/Bone.vue +1023 -0
- package/src/forms/bones/BoneActions.vue +127 -0
- package/src/forms/bones/BoneLabel.vue +122 -0
- package/src/forms/bones/BoneMultipleList.vue +91 -0
- package/src/forms/bones/BoneMultipleWrapper.vue +105 -0
- package/src/forms/bones/BoneNestedWrapper.vue +155 -0
- package/src/forms/bones/BoneSingleEntry.vue +51 -0
- package/src/forms/bones/FilterBone.vue +694 -0
- package/src/forms/bones/actionbar/defaultBar.vue +98 -0
- package/src/forms/bones/actionbar/fileBar.vue +165 -0
- package/src/forms/bones/actionbar/relationalBar.vue +125 -0
- package/src/forms/bones/actions/ActionDialog.vue +70 -0
- package/src/forms/bones/actions/Debug.vue +106 -0
- package/src/forms/bones/actions/Delete.vue +13 -0
- package/src/forms/bones/actions/DescribeImage.vue +201 -0
- package/src/forms/bones/actions/Download.vue +27 -0
- package/src/forms/bones/actions/Placeholder.vue +14 -0
- package/src/forms/bones/actions/Translation.vue +238 -0
- package/src/forms/bones/actions/index.js +280 -0
- package/src/forms/bones/bone.d.ts +45 -0
- package/src/forms/bones/country_information.json +779 -0
- package/src/forms/bones/index.js +225 -0
- package/src/forms/bones/uploadAdapter.js +46 -0
- package/src/forms/bones/utils.js +284 -0
- package/src/forms/bones/view/base_item.vue +35 -0
- package/src/forms/bones/view/boneLogic.js +202 -0
- package/src/forms/bones/view/index.js +5 -0
- package/src/forms/bones/widgets/booleanBone.vue +83 -0
- package/src/forms/bones/widgets/booleanBoneChoose.vue +57 -0
- package/src/forms/bones/widgets/booleanBoneSelect.vue +58 -0
- package/src/forms/bones/widgets/codeBone.vue +214 -0
- package/src/forms/bones/widgets/colorBone.vue +75 -0
- package/src/forms/bones/widgets/dateBone.vue +114 -0
- package/src/forms/bones/widgets/emailBone.vue +100 -0
- package/src/forms/bones/widgets/fileBone.vue +273 -0
- package/src/forms/bones/widgets/jsonBone.vue +63 -0
- package/src/forms/bones/widgets/keyBone.vue +71 -0
- package/src/forms/bones/widgets/numericBone.vue +125 -0
- package/src/forms/bones/widgets/passwordBone.vue +215 -0
- package/src/forms/bones/widgets/phoneBone.vue +173 -0
- package/src/forms/bones/widgets/rawBone.vue +94 -0
- package/src/forms/bones/widgets/recordBone.vue +64 -0
- package/src/forms/bones/widgets/relationalBone.vue +164 -0
- package/src/forms/bones/widgets/relationalBoneSelect.vue +120 -0
- package/src/forms/bones/widgets/selectBone.vue +122 -0
- package/src/forms/bones/widgets/selectBoneChoose.vue +104 -0
- package/src/forms/bones/widgets/spatialBone.vue +111 -0
- package/src/forms/bones/widgets/stringBone.vue +139 -0
- package/src/forms/bones/widgets/textBone.vue +641 -0
- package/src/forms/install.d.ts +4 -0
- package/src/forms/install.js +43 -0
- package/src/forms/layouts/LayloutList2Columns.vue +57 -0
- package/src/forms/layouts/LayoutCategory.vue +38 -0
- package/src/forms/layouts/LayoutList.vue +31 -0
- package/src/forms/utils.js +601 -0
- package/src/public/assets/icons/fontawesome-manifest.json +8 -0
- package/src/public/assets/icons/regular/file.svg +1 -0
- package/src/public/assets/icons/solid/arrows-left-right.svg +1 -0
- package/src/public/assets/icons/solid/bug.svg +1 -0
- package/src/public/assets/icons/solid/check.svg +1 -0
- package/src/public/assets/icons/solid/circle-info.svg +1 -0
- package/src/public/assets/icons/solid/compress.svg +1 -0
- package/src/public/assets/icons/solid/download.svg +1 -0
- package/src/public/assets/icons/solid/expand.svg +1 -0
- package/src/public/assets/icons/solid/grip-vertical.svg +1 -0
- package/src/public/assets/icons/solid/plus.svg +1 -0
- package/src/public/assets/icons/solid/question.svg +1 -0
- package/src/public/assets/icons/solid/triangle-exclamation.svg +1 -0
- package/src/public/assets/icons/solid/upload.svg +1 -0
- package/src/public/assets/icons/solid/xmark.svg +1 -0
- package/src/request/cache.js +183 -0
- package/src/request/configStore.js +516 -0
- package/src/request/envelope.js +211 -0
- package/src/request/index.js +35 -0
- package/src/request/messageStore.js +201 -0
- package/src/request/request.js +752 -0
- package/src/request/skel.js +90 -0
- package/src/theme/colorStore.js +168 -0
- package/src/translations/de.js +178 -0
- package/src/translations/en.js +177 -0
- package/src/user/LoginScreen.d.vue.ts +54 -0
- package/src/user/LoginScreen.vue +627 -0
- package/src/user/google.js +75 -0
- package/src/user/index.js +3 -0
- package/src/user/store.d.ts +59 -0
- package/src/user/store.js +183 -0
- package/src/webawesomeCompat.css +72 -0
- package/src/webawesomeConfig.js +61 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { DefineComponent } from "vue"
|
|
2
|
+
|
|
3
|
+
/** Props von ViForm.vue (siehe defineProps dort — hier die gebräuchlichen). */
|
|
4
|
+
export interface ViFormProps {
|
|
5
|
+
/** Modulname bzw. Punkt-Pfad ("user", "shop.order"). */
|
|
6
|
+
module?: string
|
|
7
|
+
/** Aktion unterhalb des Moduls ("add", "edit", "login", "auth_.../login"). */
|
|
8
|
+
action?: string | null
|
|
9
|
+
/** Untertyp gruppierter Listen. */
|
|
10
|
+
group?: string | null
|
|
11
|
+
/** Schlüssel des Datensatzes für edit/clone — ein Singleton braucht keinen. */
|
|
12
|
+
skelkey?: string | null
|
|
13
|
+
/** Baum-Typ ("node" | "leaf"). */
|
|
14
|
+
skeltype?: string | null
|
|
15
|
+
/** skey mitsenden (Standard: true). */
|
|
16
|
+
secure?: boolean
|
|
17
|
+
/**
|
|
18
|
+
* Feedback in den Message-Store schreiben (Speichern erfolgreich, Login
|
|
19
|
+
* fehlgeschlagen, …). Wirkt nur, wenn die App Pinia installiert hat;
|
|
20
|
+
* `false` hält dieses Formular stumm. Standard: true.
|
|
21
|
+
*/
|
|
22
|
+
useMessageStore?: boolean
|
|
23
|
+
/** Fertige Struktur/Werte statt eines Requests. */
|
|
24
|
+
skel?: Record<string, any> | string | null
|
|
25
|
+
structure?: Record<string, any> | unknown[] | null
|
|
26
|
+
values?: Record<string, any> | null
|
|
27
|
+
/** Nur diese Bones zeigen. */
|
|
28
|
+
bones?: string[]
|
|
29
|
+
/** Zusätzliche Daten für den AUFBAU des Formulars (z. B. `{ bounce: 1 }`). */
|
|
30
|
+
params?: Record<string, any>
|
|
31
|
+
/** Abweichende Adresse für den Aufbau. */
|
|
32
|
+
fetchUrl?: string | null
|
|
33
|
+
/** Renderer der Requests (Standard: VITE_DEFAULT_RENDERER). */
|
|
34
|
+
renderer?: string
|
|
35
|
+
/** Protokollversion festnageln. */
|
|
36
|
+
version?: string | number | null
|
|
37
|
+
useCategories?: boolean
|
|
38
|
+
collapsedCategories?: string[]
|
|
39
|
+
categoryDefaultname?: string
|
|
40
|
+
readonly?: boolean
|
|
41
|
+
/** Bone-Action-Leisten anzeigen (Pflicht-Actions erscheinen immer). */
|
|
42
|
+
boneactions?: boolean
|
|
43
|
+
/**
|
|
44
|
+
* Formular-weite Action-Konfiguration: Einträge wie `bone.params.actions`
|
|
45
|
+
* (`"-name"` deaktiviert explizit, auch Pflicht-Actions). Array = alle
|
|
46
|
+
* Bones; Objekt = pro Bone-Name, `"*"` für alle.
|
|
47
|
+
*/
|
|
48
|
+
actions?: (string | Record<string, any>)[] | Record<string, (string | Record<string, any>)[]> | null
|
|
49
|
+
/** Mehrstufigen Ablauf fahren (Login, Assistenten). */
|
|
50
|
+
multistep?: boolean
|
|
51
|
+
showSteps?: boolean
|
|
52
|
+
submitLabel?: string
|
|
53
|
+
nextLabel?: string
|
|
54
|
+
backLabel?: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Zustand des Formulars, wie ihn `state` freigibt. */
|
|
58
|
+
export interface ViFormState {
|
|
59
|
+
loading: boolean
|
|
60
|
+
failed: unknown
|
|
61
|
+
skel: Record<string, any>
|
|
62
|
+
/** Werte des letzten Server-Stands (Grundlage von resetForm). */
|
|
63
|
+
pristine: Record<string, any> | null
|
|
64
|
+
structure: Record<string, any> | null
|
|
65
|
+
errors: { fieldPath?: string[]; errorMessage?: string; severity?: number }[]
|
|
66
|
+
/** v2-Status der letzten Antwort: "init" | "success" | "rejected" | … */
|
|
67
|
+
status: string | null
|
|
68
|
+
actionname: string | null
|
|
69
|
+
actionparams: Record<string, any> | null
|
|
70
|
+
step: string | null
|
|
71
|
+
steps: Record<string, any> | null
|
|
72
|
+
postUrl: string | null
|
|
73
|
+
[key: string]: unknown
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Was ViForm per defineExpose freigibt. */
|
|
77
|
+
export interface ViFormApi {
|
|
78
|
+
/** Werte abschicken (POST auf die Adresse des Formulars). */
|
|
79
|
+
sendData: (
|
|
80
|
+
alternativeUrl?: string | null,
|
|
81
|
+
additionalData?: Record<string, any> | null,
|
|
82
|
+
headers?: Record<string, string> | null,
|
|
83
|
+
removeKeyFromDataset?: boolean
|
|
84
|
+
) => Promise<unknown>
|
|
85
|
+
/** Formular (neu) vom Server holen. */
|
|
86
|
+
fetchData: (
|
|
87
|
+
alternativeUrl?: string | null,
|
|
88
|
+
additionalData?: Record<string, any> | null,
|
|
89
|
+
headers?: Record<string, string> | null
|
|
90
|
+
) => Promise<unknown>
|
|
91
|
+
/** Schritt eines mehrstufigen Ablaufs rendern. */
|
|
92
|
+
loadStep: (url?: string | null) => Promise<unknown>
|
|
93
|
+
/** Aktuellen Schritt abschicken und ggf. zum nächsten gehen. */
|
|
94
|
+
submitStep: () => Promise<unknown>
|
|
95
|
+
/** Einen Schritt zurück. */
|
|
96
|
+
back: () => Promise<unknown> | void
|
|
97
|
+
updateSkel: (values: Record<string, any>) => void
|
|
98
|
+
/** Von vorn aufbauen — holt das Formular neu vom Server. */
|
|
99
|
+
reload: () => void
|
|
100
|
+
/** Änderungen verwerfen: Werte des letzten Server-Stands ohne neuen Request. */
|
|
101
|
+
resetForm: () => void
|
|
102
|
+
state: ViFormState
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
declare const ViForm: DefineComponent<ViFormProps, ViFormApi, any>
|
|
106
|
+
export default ViForm
|
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Multi-step progress bar. Fully overridable via the `steps` slot — the
|
|
3
|
+
slot props expose the envelope's step data plus helpers so a consumer can
|
|
4
|
+
render any custom stepper. The default content is the built-in bar,
|
|
5
|
+
shown when `showSteps` is set. -->
|
|
6
|
+
<slot
|
|
7
|
+
name="steps"
|
|
8
|
+
:steps="state.steps"
|
|
9
|
+
:current="state.step"
|
|
10
|
+
:step-status="state.stepStatus"
|
|
11
|
+
:status="state.status"
|
|
12
|
+
:is-last-step="state.isLastStep"
|
|
13
|
+
:step-index="stepIndex"
|
|
14
|
+
:is-active="isStepActive"
|
|
15
|
+
:is-done="isStepDone"
|
|
16
|
+
:submit="submitStep"
|
|
17
|
+
:loading="state.loading"
|
|
18
|
+
>
|
|
19
|
+
<div
|
|
20
|
+
v-if="showSteps && state.steps"
|
|
21
|
+
class="viform-steps"
|
|
22
|
+
role="list"
|
|
23
|
+
>
|
|
24
|
+
<div
|
|
25
|
+
v-for="(step, key) in state.steps"
|
|
26
|
+
:key="key"
|
|
27
|
+
class="viform-step"
|
|
28
|
+
role="listitem"
|
|
29
|
+
:class="{ 'is-active': isStepActive(key), 'is-done': isStepDone(key) }"
|
|
30
|
+
>
|
|
31
|
+
<span class="viform-step__marker">
|
|
32
|
+
<wa-icon
|
|
33
|
+
v-if="step['icon']"
|
|
34
|
+
:name="step['icon']"
|
|
35
|
+
:library="step['icon_library'] || 'default'"
|
|
36
|
+
></wa-icon>
|
|
37
|
+
</span>
|
|
38
|
+
<span class="viform-step__label">{{ step["label"] }}</span>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</slot>
|
|
42
|
+
|
|
43
|
+
<!-- List envelope (datatype === "list"): no form — expose the items for
|
|
44
|
+
custom rendering. Slot props carry the list + flow actions. -->
|
|
45
|
+
<slot
|
|
46
|
+
v-if="state.datatype === 'list'"
|
|
47
|
+
name="list"
|
|
48
|
+
:items="state.list"
|
|
49
|
+
:cursor="state.cursor"
|
|
50
|
+
:orders="state.orders"
|
|
51
|
+
:next="submitStep"
|
|
52
|
+
:back="back"
|
|
53
|
+
:is-first-step="state.isFirstStep"
|
|
54
|
+
:is-last-step="state.isLastStep"
|
|
55
|
+
:loading="state.loading"
|
|
56
|
+
:current="state.step"
|
|
57
|
+
:steps="state.steps"
|
|
58
|
+
:errors="state.errors"
|
|
59
|
+
>
|
|
60
|
+
<ul class="viform-list">
|
|
61
|
+
<li
|
|
62
|
+
v-for="(item, i) in state.list || []"
|
|
63
|
+
:key="item?.['key'] ?? i"
|
|
64
|
+
class="viform-list__item"
|
|
65
|
+
>
|
|
66
|
+
{{ listLabel(item, i) }}
|
|
67
|
+
</li>
|
|
68
|
+
</ul>
|
|
69
|
+
</slot>
|
|
70
|
+
|
|
71
|
+
<form
|
|
72
|
+
v-else
|
|
73
|
+
:ref="(el) => (state.viformelement = el)"
|
|
74
|
+
@submit.prevent.stop="() => {}"
|
|
75
|
+
>
|
|
76
|
+
<slot
|
|
77
|
+
:structure="state.structure"
|
|
78
|
+
:skel="state.skel"
|
|
79
|
+
:errors="state.errors"
|
|
80
|
+
:categories="state.categories"
|
|
81
|
+
>
|
|
82
|
+
<component
|
|
83
|
+
:is="layout"
|
|
84
|
+
v-slot="{ boneName, widget, visible, label }"
|
|
85
|
+
>
|
|
86
|
+
<bone
|
|
87
|
+
:is="widget"
|
|
88
|
+
v-if="widget !== undefined"
|
|
89
|
+
v-show="visible === undefined ? state.structure[boneName]['visible'] : visible"
|
|
90
|
+
:name="boneName"
|
|
91
|
+
:autofocus="autofocus"
|
|
92
|
+
:structure="state.structure"
|
|
93
|
+
:skel="state.skel"
|
|
94
|
+
:errors="state.errors"
|
|
95
|
+
:boneactions="state.boneactions"
|
|
96
|
+
:default-language="defaultLanguage"
|
|
97
|
+
:label="label === undefined ? state.label : label"
|
|
98
|
+
:error-style="errorStyle"
|
|
99
|
+
:readonly="readonly"
|
|
100
|
+
:debug="state.debug"
|
|
101
|
+
@keypress.enter="handlePressEnter($event, boneName)"
|
|
102
|
+
@change-internal="formUpdate"
|
|
103
|
+
></bone>
|
|
104
|
+
</component>
|
|
105
|
+
</slot>
|
|
106
|
+
</form>
|
|
107
|
+
|
|
108
|
+
<!-- Footer / action bar for the multi-step flow. Override via the `actions`
|
|
109
|
+
slot; slot props expose the `next` (submit/advance) and `back` actions
|
|
110
|
+
plus flow state. The default is a Zurück / Weiter|Absenden bar. -->
|
|
111
|
+
<slot
|
|
112
|
+
name="actions"
|
|
113
|
+
:next="submitStep"
|
|
114
|
+
:back="back"
|
|
115
|
+
:is-first-step="state.isFirstStep"
|
|
116
|
+
:is-last-step="state.isLastStep"
|
|
117
|
+
:loading="state.loading"
|
|
118
|
+
:current="state.step"
|
|
119
|
+
:steps="state.steps"
|
|
120
|
+
:next-label="effectiveNextLabel"
|
|
121
|
+
:submit-label="effectiveSubmitLabel"
|
|
122
|
+
:back-label="effectiveBackLabel"
|
|
123
|
+
>
|
|
124
|
+
<div
|
|
125
|
+
v-if="multistep"
|
|
126
|
+
class="viform-actions"
|
|
127
|
+
>
|
|
128
|
+
<wa-button
|
|
129
|
+
v-if="!state.isFirstStep"
|
|
130
|
+
variant="neutral"
|
|
131
|
+
:disabled="state.loading"
|
|
132
|
+
@click="back"
|
|
133
|
+
>
|
|
134
|
+
{{ effectiveBackLabel }}
|
|
135
|
+
</wa-button>
|
|
136
|
+
<wa-button
|
|
137
|
+
variant="brand"
|
|
138
|
+
:loading="state.loading"
|
|
139
|
+
@click="submitStep"
|
|
140
|
+
>
|
|
141
|
+
{{ state.isLastStep ? effectiveSubmitLabel : effectiveNextLabel }}
|
|
142
|
+
</wa-button>
|
|
143
|
+
</div>
|
|
144
|
+
</slot>
|
|
145
|
+
</template>
|
|
146
|
+
|
|
147
|
+
<script setup>
|
|
148
|
+
import { useI18n } from "vue-i18n"
|
|
149
|
+
import { useFormUtils } from "./utils"
|
|
150
|
+
import { v1SelectionFollow } from "../request/index.js"
|
|
151
|
+
import { reactive, watch, onBeforeMount, computed, provide, ref } from "vue"
|
|
152
|
+
import { useDebounceFn } from "@vueuse/core"
|
|
153
|
+
import LayoutCategory from "./layouts/LayoutCategory.vue"
|
|
154
|
+
|
|
155
|
+
const emit = defineEmits(["change", "keypress-enter", "failed", "success", "step-change"])
|
|
156
|
+
|
|
157
|
+
const { t } = useI18n()
|
|
158
|
+
const effectiveNextLabel = computed(() => props.nextLabel ?? t("form.next"))
|
|
159
|
+
const effectiveSubmitLabel = computed(() => props.submitLabel ?? t("form.submit"))
|
|
160
|
+
const effectiveBackLabel = computed(() => props.backLabel ?? t("form.back"))
|
|
161
|
+
const props = defineProps({
|
|
162
|
+
//modulename
|
|
163
|
+
module: {
|
|
164
|
+
type: String,
|
|
165
|
+
},
|
|
166
|
+
//like add, edit, clone ...
|
|
167
|
+
action: null,
|
|
168
|
+
// groupedlists subtype
|
|
169
|
+
group: null,
|
|
170
|
+
// edit or clone need a existing skelkey
|
|
171
|
+
skelkey: null,
|
|
172
|
+
// trees need a skeltype like leaf or node
|
|
173
|
+
skeltype: null,
|
|
174
|
+
// use skey or not
|
|
175
|
+
secure: {
|
|
176
|
+
type: Boolean,
|
|
177
|
+
default: true,
|
|
178
|
+
},
|
|
179
|
+
// feed the message store (feedback after saving, login errors, …) — only
|
|
180
|
+
// effective when the app installed Pinia; false keeps this form silent
|
|
181
|
+
useMessageStore: {
|
|
182
|
+
type: Boolean,
|
|
183
|
+
default: true,
|
|
184
|
+
},
|
|
185
|
+
//the above fields are needed for normal form.
|
|
186
|
+
|
|
187
|
+
//if skel and structure defined this will be used instead of fetchData
|
|
188
|
+
skel: {
|
|
189
|
+
type: [Object, null, String],
|
|
190
|
+
default: null,
|
|
191
|
+
},
|
|
192
|
+
structure: {
|
|
193
|
+
type: [Object, Array],
|
|
194
|
+
default: null,
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// show only these bones
|
|
198
|
+
bones: {
|
|
199
|
+
type: Array,
|
|
200
|
+
default: [],
|
|
201
|
+
},
|
|
202
|
+
//override server defaultvalues
|
|
203
|
+
values: {
|
|
204
|
+
type: Object,
|
|
205
|
+
default: null,
|
|
206
|
+
},
|
|
207
|
+
//used for fetch data
|
|
208
|
+
params: {
|
|
209
|
+
type: Object,
|
|
210
|
+
default: {},
|
|
211
|
+
},
|
|
212
|
+
// define the renderer default is json
|
|
213
|
+
renderer: {
|
|
214
|
+
type: String,
|
|
215
|
+
default: import.meta.env.VITE_DEFAULT_RENDERER || "json",
|
|
216
|
+
},
|
|
217
|
+
// optionally pin the protocol version alongside the renderer (e.g. 2 / "v2"
|
|
218
|
+
// / "v1"). Default null hits the unversioned endpoint (server aliases to the
|
|
219
|
+
// newest wire format, envelope v2).
|
|
220
|
+
version: {
|
|
221
|
+
type: [String, Number, null],
|
|
222
|
+
default: null,
|
|
223
|
+
},
|
|
224
|
+
useCategories: {
|
|
225
|
+
type: Boolean,
|
|
226
|
+
default: true,
|
|
227
|
+
},
|
|
228
|
+
collapsedCategories: {
|
|
229
|
+
type: Array,
|
|
230
|
+
default: [],
|
|
231
|
+
},
|
|
232
|
+
categoryDefaultname: {
|
|
233
|
+
type: String,
|
|
234
|
+
default: "Allgemein",
|
|
235
|
+
},
|
|
236
|
+
sendReadOnly: {
|
|
237
|
+
type: Boolean,
|
|
238
|
+
default: false,
|
|
239
|
+
},
|
|
240
|
+
internal: {
|
|
241
|
+
type: [Object, null],
|
|
242
|
+
default: null,
|
|
243
|
+
},
|
|
244
|
+
boneactions: {
|
|
245
|
+
type: Boolean,
|
|
246
|
+
default: false,
|
|
247
|
+
},
|
|
248
|
+
/**
|
|
249
|
+
* Formular-weite Action-Konfiguration für die Bones — dieselben Einträge
|
|
250
|
+
* wie `bone.params.actions`: Namen/Inline-Objekte AKTIVIEREN zusätzlich,
|
|
251
|
+
* `"-name"` DEAKTIVIERT explizit (schlägt auch Pflicht-Actions).
|
|
252
|
+
*
|
|
253
|
+
* :actions="['-removeEntry']" // ALLE Bones
|
|
254
|
+
* :actions="{ '*': ['debug'], tags: ['-removeEntry'] }" // pro Bone
|
|
255
|
+
*
|
|
256
|
+
* Wird per provide("boneActionsConfig") an die Bones gereicht und dort VOR
|
|
257
|
+
* `bone.params.actions` gemerged (Bone-Lokales ist spezifischer; ein Minus
|
|
258
|
+
* gewinnt immer, egal aus welcher Quelle).
|
|
259
|
+
*/
|
|
260
|
+
actions: {
|
|
261
|
+
type: [Array, Object],
|
|
262
|
+
default: null,
|
|
263
|
+
},
|
|
264
|
+
layout: {
|
|
265
|
+
type: Object,
|
|
266
|
+
default: LayoutCategory,
|
|
267
|
+
},
|
|
268
|
+
label: {
|
|
269
|
+
type: String,
|
|
270
|
+
default: "normal",
|
|
271
|
+
validator(value, props) {
|
|
272
|
+
return ["normal", "top", "hide", "placeholder"].includes(value)
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
defaultLanguage: {
|
|
276
|
+
type: String,
|
|
277
|
+
default: "de",
|
|
278
|
+
},
|
|
279
|
+
fetchUrl: {
|
|
280
|
+
type: [String, null],
|
|
281
|
+
default: null,
|
|
282
|
+
},
|
|
283
|
+
errorStyle: {
|
|
284
|
+
type: String,
|
|
285
|
+
default: "default",
|
|
286
|
+
validator(value, props) {
|
|
287
|
+
return ["default", "decent"].includes(value)
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
allowEnter: {
|
|
291
|
+
type: Boolean,
|
|
292
|
+
default: false,
|
|
293
|
+
},
|
|
294
|
+
autofocus: {
|
|
295
|
+
default: false,
|
|
296
|
+
type: Boolean,
|
|
297
|
+
},
|
|
298
|
+
readonly: {
|
|
299
|
+
type: [Boolean, null],
|
|
300
|
+
default: null,
|
|
301
|
+
},
|
|
302
|
+
debug: {
|
|
303
|
+
type: Boolean,
|
|
304
|
+
default: false,
|
|
305
|
+
},
|
|
306
|
+
// Follow-driven multi-step mode: GET-render the entry step, then POST each
|
|
307
|
+
// step to the envelope's `follow` URL and advance until `status: "success"`.
|
|
308
|
+
multistep: {
|
|
309
|
+
type: Boolean,
|
|
310
|
+
default: false,
|
|
311
|
+
},
|
|
312
|
+
// Render the built-in progress bar from the envelope's `steps` map. For a
|
|
313
|
+
// custom stepper use the `steps` slot instead (its slot props expose steps,
|
|
314
|
+
// current, stepStatus, status, isLastStep, stepIndex, isActive, isDone,
|
|
315
|
+
// submit, loading); `showSteps` only gates the default built-in bar.
|
|
316
|
+
showSteps: {
|
|
317
|
+
type: Boolean,
|
|
318
|
+
default: false,
|
|
319
|
+
},
|
|
320
|
+
// Labels for the built-in multistep action buttons.
|
|
321
|
+
nextLabel: {
|
|
322
|
+
type: [String, null],
|
|
323
|
+
default: null,
|
|
324
|
+
},
|
|
325
|
+
submitLabel: {
|
|
326
|
+
type: [String, null],
|
|
327
|
+
default: null,
|
|
328
|
+
},
|
|
329
|
+
backLabel: {
|
|
330
|
+
type: [String, null],
|
|
331
|
+
default: null,
|
|
332
|
+
},
|
|
333
|
+
// add errors, from the outside (maybe relevant if form is build with slots)
|
|
334
|
+
// errors: []
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
const state = reactive({
|
|
338
|
+
skel: {}, // working data
|
|
339
|
+
pristine: null, // Werte des letzten Server-Stands (für resetForm)
|
|
340
|
+
structure: {}, // working data, use dict!
|
|
341
|
+
errors: [], // working data
|
|
342
|
+
actionparams: {}, //actionskel parameters
|
|
343
|
+
actionname: null, //lastaction name
|
|
344
|
+
valids: {}, // local validation states
|
|
345
|
+
loading: false, //loading state
|
|
346
|
+
categories: [], //categories to render
|
|
347
|
+
values: computed(() => props.values),
|
|
348
|
+
internal: computed(() => props.internal),
|
|
349
|
+
useCategories: computed(() => props.useCategories),
|
|
350
|
+
label: computed(() => props.label),
|
|
351
|
+
bones: computed(() => props.bones),
|
|
352
|
+
boneactions: computed(() => props.boneactions),
|
|
353
|
+
isValid: computed(() => {
|
|
354
|
+
// is form valid?
|
|
355
|
+
let validstate = true
|
|
356
|
+
for (const [key, value] of Object.entries(state.valids)) {
|
|
357
|
+
if (!value) {
|
|
358
|
+
validstate = false
|
|
359
|
+
break
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return validstate
|
|
363
|
+
}),
|
|
364
|
+
viformelement: ref(null),
|
|
365
|
+
debug: computed(() => props.debug),
|
|
366
|
+
readonly: computed(() => props.readonly),
|
|
367
|
+
categoryDefaultname: computed(() => props.categoryDefaultname),
|
|
368
|
+
failed: null,
|
|
369
|
+
// multi-step envelope state (populated by applyResponseState)
|
|
370
|
+
step: null, // current step key
|
|
371
|
+
stepStatus: null, // current step outcome
|
|
372
|
+
steps: null, // map of all steps {key: {icon, icon_library, label}}
|
|
373
|
+
status: null, // overall action status
|
|
374
|
+
postUrl: null, // where the current step submits to (envelope `follow`)
|
|
375
|
+
datatype: null, // "entity" | "list" — shape of the current response
|
|
376
|
+
list: null, // list items when datatype === "list" (else null)
|
|
377
|
+
cursor: null, // list-only pagination cursor
|
|
378
|
+
orders: [], // list-only ordering hints
|
|
379
|
+
isLastStep: computed(() => {
|
|
380
|
+
if (!state.steps) return false
|
|
381
|
+
const keys = Object.keys(state.steps)
|
|
382
|
+
return keys.length > 0 && state.step === keys[keys.length - 1]
|
|
383
|
+
}),
|
|
384
|
+
isFirstStep: computed(() => {
|
|
385
|
+
if (!state.steps) return true
|
|
386
|
+
const keys = Object.keys(state.steps)
|
|
387
|
+
return keys.length === 0 || state.step === keys[0]
|
|
388
|
+
}),
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
function stepIndex(key) {
|
|
392
|
+
return state.steps ? Object.keys(state.steps).indexOf(key) : -1
|
|
393
|
+
}
|
|
394
|
+
function isStepActive(key) {
|
|
395
|
+
return key === state.step
|
|
396
|
+
}
|
|
397
|
+
function isStepDone(key) {
|
|
398
|
+
return stepIndex(key) < stepIndex(state.step)
|
|
399
|
+
}
|
|
400
|
+
// Best-effort label for the default #list rendering (overridden via the slot).
|
|
401
|
+
function listLabel(item, i) {
|
|
402
|
+
if (item == null || typeof item !== "object") return String(item)
|
|
403
|
+
if (typeof item["name"] === "string" && item["name"]) return item["name"]
|
|
404
|
+
if (typeof item["key"] === "string" && item["key"]) return item["key"]
|
|
405
|
+
return `#${i + 1}`
|
|
406
|
+
}
|
|
407
|
+
provide("formState", state)
|
|
408
|
+
// Formular-weite Action-Konfiguration (siehe actions-Prop) — computed, damit
|
|
409
|
+
// eine reaktive Prop-Änderung bei den Bones ankommt.
|
|
410
|
+
provide("boneActionsConfig", computed(() => props.actions))
|
|
411
|
+
if (!props.internal) {
|
|
412
|
+
provide("mainformState", state)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const { fetchData, sendData, loadStep, resetForm, back, updateSkel, initForm, logics, reload } = useFormUtils(
|
|
416
|
+
props,
|
|
417
|
+
state
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
const formUpdateEvent = useDebounceFn((data) => {
|
|
421
|
+
emit("change", data)
|
|
422
|
+
}, 500)
|
|
423
|
+
|
|
424
|
+
onBeforeMount(() => {
|
|
425
|
+
reload()
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
watch(
|
|
429
|
+
() => state.failed,
|
|
430
|
+
(newVal, oldVal) => {
|
|
431
|
+
emit("failed", state.failed)
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
watch(
|
|
436
|
+
() => props.skel,
|
|
437
|
+
(oldVal, newVal) => {
|
|
438
|
+
//dragging internalforms
|
|
439
|
+
// structure wont be updated, cause of logic changes on structures
|
|
440
|
+
initForm(props.skel, undefined, state.value)
|
|
441
|
+
//rerun logics after dragging
|
|
442
|
+
logics()
|
|
443
|
+
},
|
|
444
|
+
{ deep: true }
|
|
445
|
+
)
|
|
446
|
+
watch(
|
|
447
|
+
() => props.structure,
|
|
448
|
+
(oldVal, newVal) => {
|
|
449
|
+
//rerun logics if structure on mainform changes
|
|
450
|
+
logics()
|
|
451
|
+
},
|
|
452
|
+
{ deep: true }
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
function formUpdate(data) {
|
|
456
|
+
updateSkel(data)
|
|
457
|
+
formUpdateEvent(data)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function handlePressEnter(ev, name) {
|
|
461
|
+
// IF FLAG IS SET
|
|
462
|
+
if (!props.allowEnter) {
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const value = ev.originalTarget.value
|
|
467
|
+
|
|
468
|
+
// intercept event to adjust it so it triggers the same change event like @change-internal
|
|
469
|
+
let data = {
|
|
470
|
+
name: name,
|
|
471
|
+
value: value,
|
|
472
|
+
// TODO: needs to be implemented naturally and not interceptive
|
|
473
|
+
lang: null,
|
|
474
|
+
index: null,
|
|
475
|
+
valid: true,
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
formUpdate(data)
|
|
479
|
+
|
|
480
|
+
emit("keypress-enter")
|
|
481
|
+
}
|
|
482
|
+
provide("formUpdate", formUpdate)
|
|
483
|
+
|
|
484
|
+
// Multi-step driver: POST the current step to its `follow` URL, then advance.
|
|
485
|
+
// - rejected → stay on the step (errors were already applied by sendData)
|
|
486
|
+
// - success → emit("success")
|
|
487
|
+
// - continue → GET the next step's `follow` and render it
|
|
488
|
+
async function submitStep() {
|
|
489
|
+
if (state.loading) return
|
|
490
|
+
try {
|
|
491
|
+
// sendData returns the request-layer fetch result; its `.data.value` holds
|
|
492
|
+
// the normalized v2 envelope.
|
|
493
|
+
const fetchResult = await sendData(state.postUrl)
|
|
494
|
+
const data = fetchResult?.data?.value
|
|
495
|
+
// v2 liefert das Ziel des nächsten Schritts als `follow`. In der klassischen
|
|
496
|
+
// v1-Login-Kette gibt es das nicht — dort ist das Ziel der gewählte Wert
|
|
497
|
+
// (die Provider-Auswahl listet URLs).
|
|
498
|
+
const follow = data?.["follow"] ?? v1SelectionFollow(data)
|
|
499
|
+
if (data?.["status"] === "success") {
|
|
500
|
+
emit("success", data)
|
|
501
|
+
} else if (data?.["status"] !== "rejected" && follow) {
|
|
502
|
+
await loadStep(follow)
|
|
503
|
+
emit("step-change", { step: state.step, steps: state.steps })
|
|
504
|
+
}
|
|
505
|
+
return data
|
|
506
|
+
} catch (error) {
|
|
507
|
+
// Invalid form (client-side) or a rejection/network error — sendData has
|
|
508
|
+
// already surfaced it via state.errors / state.failed. Stay on the step.
|
|
509
|
+
return
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
defineExpose({ sendData, fetchData, loadStep, submitStep, back, updateSkel, reload, resetForm, state })
|
|
514
|
+
</script>
|
|
515
|
+
|
|
516
|
+
<style scoped>
|
|
517
|
+
.viform-steps {
|
|
518
|
+
display: flex;
|
|
519
|
+
gap: 0.5rem;
|
|
520
|
+
align-items: center;
|
|
521
|
+
margin-block-end: 1.5rem;
|
|
522
|
+
flex-wrap: wrap;
|
|
523
|
+
padding: var(--wa-space-m);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.viform-step {
|
|
527
|
+
display: flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
gap: 0.5rem;
|
|
530
|
+
flex: 1 1 0;
|
|
531
|
+
min-width: max-content;
|
|
532
|
+
color: var(--wa-color-neutral-60, #71717a);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.viform-step:not(:last-child)::after {
|
|
536
|
+
content: "";
|
|
537
|
+
flex: 1;
|
|
538
|
+
height: 2px;
|
|
539
|
+
background: var(--wa-color-neutral-90, #e4e4e7);
|
|
540
|
+
margin-inline-start: 0.5rem;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.viform-step__marker {
|
|
544
|
+
display: inline-flex;
|
|
545
|
+
align-items: center;
|
|
546
|
+
justify-content: center;
|
|
547
|
+
inline-size: 2rem;
|
|
548
|
+
block-size: 2rem;
|
|
549
|
+
border-radius: 50%;
|
|
550
|
+
border: 2px solid currentColor;
|
|
551
|
+
font-size: 1rem;
|
|
552
|
+
flex: 0 0 auto;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.viform-step__label {
|
|
556
|
+
font-weight: 500;
|
|
557
|
+
white-space: nowrap;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.viform-step.is-active {
|
|
561
|
+
color: var(--wa-color-brand-60, #2563eb);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.viform-step.is-done {
|
|
565
|
+
color: var(--wa-color-success-40, #16a34a);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.viform-step.is-done .viform-step__marker,
|
|
569
|
+
.viform-step.is-active .viform-step__marker {
|
|
570
|
+
background: color-mix(in srgb, currentColor 12%, transparent);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.viform-actions {
|
|
574
|
+
display: flex;
|
|
575
|
+
justify-content: flex-end;
|
|
576
|
+
margin-block-start: 1.5rem;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/* Minimal default #list rendering — overridable via the `list` slot. */
|
|
580
|
+
.viform-list {
|
|
581
|
+
list-style: none;
|
|
582
|
+
margin: 0;
|
|
583
|
+
padding: 0;
|
|
584
|
+
display: flex;
|
|
585
|
+
flex-direction: column;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.viform-list__item {
|
|
589
|
+
padding: 0.5rem 0.25rem;
|
|
590
|
+
border-block-end: 1px solid var(--wa-color-neutral-90, #e4e4e7);
|
|
591
|
+
}
|
|
592
|
+
</style>
|