viur-ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +79 -0
  2. package/index.d.ts +351 -0
  3. package/index.js +46 -0
  4. package/package.json +84 -0
  5. package/src/components/Browser/Browser.vue +496 -0
  6. package/src/components/Browser/TreeNav.vue +408 -0
  7. package/src/components/Browser/ViurBrowser.d.vue.ts +64 -0
  8. package/src/components/Browser/ViurBrowser.vue +271 -0
  9. package/src/components/Browser/dragSession.ts +134 -0
  10. package/src/components/Browser/index.ts +14 -0
  11. package/src/components/Browser/types.ts +30 -0
  12. package/src/components/DataTable/ColumnFilterPanel.vue +225 -0
  13. package/src/components/DataTable/DataTable.vue +3155 -0
  14. package/src/components/DataTable/ViurDataTable.d.vue.ts +72 -0
  15. package/src/components/DataTable/ViurDataTable.vue +331 -0
  16. package/src/components/DataTable/columns.ts +400 -0
  17. package/src/components/DataTable/index.ts +60 -0
  18. package/src/components/DataTable/rows.ts +446 -0
  19. package/src/components/DataTable/types.ts +267 -0
  20. package/src/components/DataTable/useColumnLayout.ts +294 -0
  21. package/src/components/DataTable/useGridNavigation.ts +193 -0
  22. package/src/components/DataTable/useVirtualRows.ts +126 -0
  23. package/src/components/DataTable/viurColumns.ts +330 -0
  24. package/src/forms/ViForm.d.vue.ts +106 -0
  25. package/src/forms/ViForm.vue +592 -0
  26. package/src/forms/ViFormCategory.vue +57 -0
  27. package/src/forms/bones/Bone.vue +1023 -0
  28. package/src/forms/bones/BoneActions.vue +127 -0
  29. package/src/forms/bones/BoneLabel.vue +122 -0
  30. package/src/forms/bones/BoneMultipleList.vue +91 -0
  31. package/src/forms/bones/BoneMultipleWrapper.vue +105 -0
  32. package/src/forms/bones/BoneNestedWrapper.vue +155 -0
  33. package/src/forms/bones/BoneSingleEntry.vue +51 -0
  34. package/src/forms/bones/FilterBone.vue +694 -0
  35. package/src/forms/bones/actionbar/defaultBar.vue +98 -0
  36. package/src/forms/bones/actionbar/fileBar.vue +165 -0
  37. package/src/forms/bones/actionbar/relationalBar.vue +125 -0
  38. package/src/forms/bones/actions/ActionDialog.vue +70 -0
  39. package/src/forms/bones/actions/Debug.vue +106 -0
  40. package/src/forms/bones/actions/Delete.vue +13 -0
  41. package/src/forms/bones/actions/DescribeImage.vue +201 -0
  42. package/src/forms/bones/actions/Download.vue +27 -0
  43. package/src/forms/bones/actions/Placeholder.vue +14 -0
  44. package/src/forms/bones/actions/Translation.vue +238 -0
  45. package/src/forms/bones/actions/index.js +280 -0
  46. package/src/forms/bones/bone.d.ts +45 -0
  47. package/src/forms/bones/country_information.json +779 -0
  48. package/src/forms/bones/index.js +225 -0
  49. package/src/forms/bones/uploadAdapter.js +46 -0
  50. package/src/forms/bones/utils.js +284 -0
  51. package/src/forms/bones/view/base_item.vue +35 -0
  52. package/src/forms/bones/view/boneLogic.js +202 -0
  53. package/src/forms/bones/view/index.js +5 -0
  54. package/src/forms/bones/widgets/booleanBone.vue +83 -0
  55. package/src/forms/bones/widgets/booleanBoneChoose.vue +57 -0
  56. package/src/forms/bones/widgets/booleanBoneSelect.vue +58 -0
  57. package/src/forms/bones/widgets/codeBone.vue +214 -0
  58. package/src/forms/bones/widgets/colorBone.vue +75 -0
  59. package/src/forms/bones/widgets/dateBone.vue +114 -0
  60. package/src/forms/bones/widgets/emailBone.vue +100 -0
  61. package/src/forms/bones/widgets/fileBone.vue +273 -0
  62. package/src/forms/bones/widgets/jsonBone.vue +63 -0
  63. package/src/forms/bones/widgets/keyBone.vue +71 -0
  64. package/src/forms/bones/widgets/numericBone.vue +125 -0
  65. package/src/forms/bones/widgets/passwordBone.vue +215 -0
  66. package/src/forms/bones/widgets/phoneBone.vue +173 -0
  67. package/src/forms/bones/widgets/rawBone.vue +94 -0
  68. package/src/forms/bones/widgets/recordBone.vue +64 -0
  69. package/src/forms/bones/widgets/relationalBone.vue +164 -0
  70. package/src/forms/bones/widgets/relationalBoneSelect.vue +120 -0
  71. package/src/forms/bones/widgets/selectBone.vue +122 -0
  72. package/src/forms/bones/widgets/selectBoneChoose.vue +104 -0
  73. package/src/forms/bones/widgets/spatialBone.vue +111 -0
  74. package/src/forms/bones/widgets/stringBone.vue +139 -0
  75. package/src/forms/bones/widgets/textBone.vue +641 -0
  76. package/src/forms/install.d.ts +4 -0
  77. package/src/forms/install.js +43 -0
  78. package/src/forms/layouts/LayloutList2Columns.vue +57 -0
  79. package/src/forms/layouts/LayoutCategory.vue +38 -0
  80. package/src/forms/layouts/LayoutList.vue +31 -0
  81. package/src/forms/utils.js +601 -0
  82. package/src/public/assets/icons/fontawesome-manifest.json +8 -0
  83. package/src/public/assets/icons/regular/file.svg +1 -0
  84. package/src/public/assets/icons/solid/arrows-left-right.svg +1 -0
  85. package/src/public/assets/icons/solid/bug.svg +1 -0
  86. package/src/public/assets/icons/solid/check.svg +1 -0
  87. package/src/public/assets/icons/solid/circle-info.svg +1 -0
  88. package/src/public/assets/icons/solid/compress.svg +1 -0
  89. package/src/public/assets/icons/solid/download.svg +1 -0
  90. package/src/public/assets/icons/solid/expand.svg +1 -0
  91. package/src/public/assets/icons/solid/grip-vertical.svg +1 -0
  92. package/src/public/assets/icons/solid/plus.svg +1 -0
  93. package/src/public/assets/icons/solid/question.svg +1 -0
  94. package/src/public/assets/icons/solid/triangle-exclamation.svg +1 -0
  95. package/src/public/assets/icons/solid/upload.svg +1 -0
  96. package/src/public/assets/icons/solid/xmark.svg +1 -0
  97. package/src/request/cache.js +183 -0
  98. package/src/request/configStore.js +516 -0
  99. package/src/request/envelope.js +211 -0
  100. package/src/request/index.js +35 -0
  101. package/src/request/messageStore.js +201 -0
  102. package/src/request/request.js +752 -0
  103. package/src/request/skel.js +90 -0
  104. package/src/theme/colorStore.js +168 -0
  105. package/src/translations/de.js +178 -0
  106. package/src/translations/en.js +177 -0
  107. package/src/user/LoginScreen.d.vue.ts +54 -0
  108. package/src/user/LoginScreen.vue +627 -0
  109. package/src/user/google.js +75 -0
  110. package/src/user/index.js +3 -0
  111. package/src/user/store.d.ts +59 -0
  112. package/src/user/store.js +183 -0
  113. package/src/webawesomeCompat.css +72 -0
  114. package/src/webawesomeConfig.js +61 -0
@@ -0,0 +1,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>