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,601 @@
1
+ import { createControllerFetch, normalizeEnvelope, normalizeStructure } from "../request/index.js"
2
+ import Logics from "logics-js"
3
+ import { inject, toRaw, reactive, markRaw } from "vue"
4
+ import Utils from "./bones/utils"
5
+
6
+ /**
7
+ * Tiefe Kopie reiner Daten (Skel-Werte), die Vue-Proxies auf JEDER Ebene
8
+ * auflöst. structuredClone allein scheitert daran: ein Record-/using-Formular
9
+ * bekommt den reaktiven Wert des Eltern-Bones, und `{ ...proxy }` behält die
10
+ * verschachtelten Werte als Proxies. Andere Objekte (Date, File) bleiben geteilt.
11
+ */
12
+ function rawCopy(value) {
13
+ const raw = toRaw(value)
14
+ if (Array.isArray(raw)) return raw.map(rawCopy)
15
+ if (raw && typeof raw === "object" && Object.getPrototypeOf(raw) === Object.prototype) {
16
+ return Object.fromEntries(Object.entries(raw).map(([key, entry]) => [key, rawCopy(entry)]))
17
+ }
18
+ return raw
19
+ }
20
+
21
+ export function useFormUtils(props, state) {
22
+ function buildRequestSpec(urlOverride = null) {
23
+ let path = urlOverride
24
+
25
+ if (!path) {
26
+ path = `/${props.renderer}/${props.module}/${props.action}`
27
+
28
+ const isTree = ["node", "leaf"].includes(props.skeltype)
29
+
30
+ if (props.group) {
31
+ path += `/${props.group}`
32
+ } else if (isTree) {
33
+ path += `/${props.skeltype}`
34
+ }
35
+
36
+ // Nur mit Key anhängen: ein SINGLETON hat genau einen Datensatz, sein
37
+ // `edit` kennt keinen Key. Ohne diese Prüfung entstand `/edit/undefined`.
38
+ if (["edit", "clone"].includes(props.action) || (isTree && props.action === "add")) {
39
+ if (props.skelkey) {
40
+ path += `/${props.skelkey}`
41
+ }
42
+ }
43
+ }
44
+
45
+ const parsedUrl = new URL(path, window.location.origin)
46
+ const segments = parsedUrl.pathname.split("/").filter(Boolean)
47
+ const renderer = segments[0] || props.renderer
48
+ const requestSegments = segments[0] === renderer ? segments.slice(1) : segments
49
+
50
+ if (requestSegments.length < 2) {
51
+ throw new Error(`Invalid request path: ${path}`)
52
+ }
53
+
54
+ return {
55
+ renderer,
56
+ controller: requestSegments.slice(0, -1),
57
+ endpoint: requestSegments.at(-1),
58
+ // preserve any query string from a server-provided url (e.g. a `follow`
59
+ // step url) so it is passed through to the request
60
+ query: parsedUrl.search ? parsedUrl.search.slice(1) : "",
61
+ }
62
+ }
63
+
64
+ async function _envelopeFromResponse(response) {
65
+ // A cloned Response bypasses the request layer's .json() normalization, so
66
+ // lift it to the canonical v2 shape here too.
67
+ try {
68
+ return normalizeEnvelope(await response.clone().json())
69
+ } catch {
70
+ return null
71
+ }
72
+ }
73
+
74
+ // Apply a (normalized v2) envelope to the form state. Central place that maps
75
+ // the envelope onto skel / structure / errors and the multi-step fields.
76
+ function applyResponseState(data, { updateStructure = false, presetValues = false } = {}) {
77
+ data = data ?? {}
78
+
79
+ state.datatype = data["datatype"] ?? null
80
+
81
+ if (data["datatype"] === "list") {
82
+ // List envelope: structure is null — don't build a form. Expose the items
83
+ // for custom rendering via the `list` slot instead, and never crash on the
84
+ // null structure a list carries.
85
+ const items = data["data"] !== undefined ? data["data"] : data["skellist"]
86
+ state.list = Array.isArray(items) ? items : []
87
+ state.cursor = data["meta"]?.["cursor"] ?? data["cursor"] ?? null
88
+ state.orders = data["meta"]?.["orders"] ?? data["orders"] ?? []
89
+ state.structure = {}
90
+ state.skel = {}
91
+ } else {
92
+ // Entity: payload lives in `data` (v1 `values` is lifted to `data` by
93
+ // normalizeEnvelope; accept `values` too for a raw body).
94
+ const values = data["data"] !== undefined ? data["data"] : data["values"]
95
+ if (updateStructure && data["structure"] != null) {
96
+ // Der `values`-Prop belegt ein frisch GERENDERTES Formular vor. Die
97
+ // Antwort auf ein Absenden spiegelt, was gesendet wurde — darüber darf
98
+ // er nicht erneut schreiben.
99
+ initForm(values, data["structure"], presetValues ? state.values : null)
100
+ // Neue Struktur = Server-Flags: visibleIf/readonlyIf/requiredIf/evaluate
101
+ // gleich wieder anwenden, nicht erst bei der nächsten Eingabe.
102
+ logics()
103
+ } else if (values !== undefined) {
104
+ state.skel = values
105
+ }
106
+ state.list = null
107
+ }
108
+
109
+ state.errors = data["errors"]
110
+ state.actionparams = data["params"]
111
+ state.actionname = data["action"]
112
+
113
+ // Envelope-v2 multi-step fields. Absent on classic v1 bodies → reset to null
114
+ // (harmless for single-step forms). `follow` becomes the POST target for the
115
+ // *current* step; on a POST response it is the *next* step's URL.
116
+ state.step = data["step"] ?? null
117
+ state.stepStatus = data["step_status"] ?? null
118
+ state.steps = data["steps"] ?? null
119
+ state.status = data["status"] ?? null
120
+ if (data["follow"] !== undefined) {
121
+ state.postUrl = data["follow"]
122
+ }
123
+ state.loading = false
124
+ }
125
+
126
+ // GET-render a step of a follow-driven multi-step action. Unlike fetchData
127
+ // (which POSTs — a submit), this renders the step's *initial* form. `url` is
128
+ // the envelope `follow` / `steps[key].url`; omit it to build the entry URL
129
+ // from props.
130
+ async function loadStep(url = null) {
131
+ state.loading = true
132
+ state.failed = null
133
+ const requestSpec = buildRequestSpec(url)
134
+ const request = createControllerFetch({ endpoint: requestSpec.endpoint, method: "GET" })
135
+ const fetchResult = request(
136
+ requestSpec.controller,
137
+ "/",
138
+ {},
139
+ {
140
+ renderer: requestSpec.renderer,
141
+ version: props.version,
142
+ query: requestSpec.query,
143
+ immediate: false,
144
+ messages: props.useMessageStore !== false,
145
+ }
146
+ )
147
+ try {
148
+ await fetchResult.execute()
149
+ applyResponseState(fetchResult.data.value, { updateStructure: true, presetValues: true })
150
+ // Ein v1-Schritt bringt kein Ziel mit (kein `follow`, kein next_url, kein
151
+ // params.action_url) — dort wird der Schritt an DIESELBE URL
152
+ // zurückgeschickt, aus der er gerendert wurde. Ohne das stünde postUrl
153
+ // nach dem Rendern auf null und der Absenden-Knopf liefe ins Leere.
154
+ if (!state.postUrl && url) {
155
+ state.postUrl = url
156
+ }
157
+ return fetchResult
158
+ } catch (error) {
159
+ state.failed = error
160
+ state.loading = false
161
+ throw error
162
+ }
163
+ }
164
+
165
+ // Back one step in a multi-step flow. The target is the previous step's
166
+ // backend-provided endpoint URL (`steps[prevKey].url`) — no client-side
167
+ // history. GET-rendering it prefills from the server session. No-op on the
168
+ // first step or when the previous step carries no url.
169
+ async function back() {
170
+ if (!state.steps || !state.step) return
171
+ const keys = Object.keys(state.steps)
172
+ const idx = keys.indexOf(state.step)
173
+ if (idx <= 0) return
174
+ const prev = state.steps[keys[idx - 1]]
175
+ const url = prev && (prev.url || prev.endpoint)
176
+ if (!url) return
177
+ return loadStep(url)
178
+ }
179
+
180
+ function toFormData() {
181
+ let formdata = []
182
+
183
+ function handleEntry(result, currentFieldName, bone, val) {
184
+ if (bone["type"].startsWith("record")) {
185
+ let struct = normalizeStructure(bone["using"])
186
+ for (const [_fieldname, _bone] of Object.entries(struct)) {
187
+ result = result.concat(boneToForm(`${currentFieldName}.${_fieldname}`, _bone, val?.[_fieldname]))
188
+ }
189
+ } else if (val === Object(val) && bone["using"]) {
190
+ //recusive call for nested data
191
+ if (val["dest"]?.["key"]) {
192
+ let struct = normalizeStructure(bone["using"])
193
+ for (const [_fieldname, _bone] of Object.entries(struct)) {
194
+ result = result.concat(boneToForm(`${currentFieldName}.${_fieldname}`, _bone, val["rel"]?.[_fieldname]))
195
+ }
196
+ result.push({ [`${currentFieldName}.key`]: val["dest"]["key"] })
197
+ } else {
198
+ result.push({ [`${currentFieldName}`]: null })
199
+ }
200
+ } else if (bone["type"].startsWith("spatial") && val) {
201
+ //spatialbones
202
+ result.push({ [currentFieldName + ".lat"]: val[0] })
203
+ result.push({ [currentFieldName + ".lng"]: val[1] })
204
+ } else if (bone["type"].startsWith("raw.json") && val) {
205
+ result.push({ [currentFieldName]: JSON.stringify(val) })
206
+ } else if (val === Object(val)) {
207
+ //normal relations
208
+ result.push({ [currentFieldName]: val["dest"]?.["key"] || null })
209
+ } else {
210
+ //everything else
211
+ result.push({ [currentFieldName]: val })
212
+ }
213
+ return result
214
+ }
215
+
216
+ function boneToForm(fieldname, bone, value) {
217
+ let result = []
218
+ //only record and relational bones get indexed fields
219
+ let indexBone = bone["type"].startsWith("record")
220
+ let languages = bone["languages"] || ["none"]
221
+ let languageValue = value
222
+ for (const lang of languages) {
223
+ let currentFieldName = fieldname
224
+ if (lang !== "none") {
225
+ currentFieldName += `.${lang}` //append lang
226
+ if (languageValue) value = languageValue[lang]
227
+ }
228
+
229
+ if (bone["multiple"]) {
230
+ if (!value) value = []
231
+ for (const [idx, val] of value.entries()) {
232
+ let currentFieldnameMultiple = currentFieldName
233
+
234
+ if (indexBone || val?.["rel"] || (bone["using"] && val?.["rel"] !== null)) {
235
+ currentFieldnameMultiple = `${currentFieldName}.${idx}` //append idx
236
+ }
237
+ result = handleEntry(result, currentFieldnameMultiple, bone, val)
238
+ }
239
+ if (value.length === 0) {
240
+ result.push({ [currentFieldName]: null }) //send empty multiple fields
241
+ }
242
+ } else {
243
+ result = handleEntry(result, currentFieldName, bone, value)
244
+ }
245
+ }
246
+ return result
247
+ }
248
+
249
+ for (const [fieldname, bone] of Object.entries(state.structure)) {
250
+ if (props.sendReadOnly) {
251
+ formdata.push(boneToForm(fieldname, bone, state.skel[fieldname]))
252
+ } else if (!state.structure[fieldname]["readonly"] || bone.type === "key") {
253
+ formdata.push(boneToForm(fieldname, bone, state.skel[fieldname]))
254
+ }
255
+ }
256
+
257
+ formdata = formdata.flat(10)
258
+ return formdata
259
+ }
260
+
261
+ function sendData(alternativUrl = null, additionalData = null, headers = null, removeKeyFromDataset = true) {
262
+ state.loading = true
263
+ // No <form> element in list mode — nothing to validate.
264
+ let isValid = state.viformelement ? state.viformelement.reportValidity() : true
265
+ if (!isValid) {
266
+ state.loading = false
267
+ return new Promise((resolve, reject) => reject("Form is not valid"))
268
+ }
269
+
270
+ const requestSpec = buildRequestSpec(alternativUrl)
271
+ const request = createControllerFetch({
272
+ endpoint: requestSpec.endpoint,
273
+ method: "POST",
274
+ secure: props.secure,
275
+ })
276
+
277
+ const formData = new FormData()
278
+ for (const bone of toFormData()) {
279
+ for (const [k, v] of Object.entries(bone)) {
280
+ let val = v
281
+ if ([undefined, null].includes(v)) {
282
+ val = ""
283
+ }
284
+ formData.append(k, val)
285
+ }
286
+ }
287
+
288
+ let data = {}
289
+ for (const key of formData.keys()) {
290
+ if (key === "key" && removeKeyFromDataset) continue
291
+ data[[key]] = formData.getAll(key)
292
+ }
293
+ if (additionalData) {
294
+ data = { ...data, ...additionalData } //inject data like contexts
295
+ }
296
+
297
+ const fetchResult = request(requestSpec.controller, "/", data, {
298
+ renderer: requestSpec.renderer,
299
+ version: props.version,
300
+ query: requestSpec.query,
301
+ headers,
302
+ immediate: false,
303
+ messages: props.useMessageStore !== false,
304
+ })
305
+
306
+ return fetchResult
307
+ .execute()
308
+ .then(async () => {
309
+ // Apply the (normalized v2) envelope — updates skel / errors / action
310
+ // and, for multi-step flows, step / steps / status / follow. Re-init the
311
+ // form when the response carries a structure (e.g. the next step's form).
312
+ const responseData = fetchResult.data.value
313
+ applyResponseState(responseData, { updateStructure: responseData?.["structure"] != null })
314
+ return fetchResult
315
+ })
316
+ .catch(async (error) => {
317
+ // A rejected/failed submit may still carry a form body (errors +
318
+ // structure) on the error response — re-apply it so the form comes back
319
+ // populated. Otherwise just surface the failure.
320
+ const body = error?.response ? await _envelopeFromResponse(error.response).catch(() => null) : null
321
+ if (body) {
322
+ applyResponseState(body, { updateStructure: true })
323
+ } else {
324
+ state.loading = false
325
+ }
326
+ state.failed = error
327
+ throw error
328
+ })
329
+ }
330
+
331
+ function fetchData(alternativUrl = null, additionalData = null, headers = null) {
332
+ //fetch data
333
+ state.loading = true
334
+ const requestSpec = buildRequestSpec(alternativUrl)
335
+ const request = createControllerFetch({
336
+ endpoint: requestSpec.endpoint,
337
+ method: "POST",
338
+ secure: props.secure,
339
+ })
340
+
341
+ let data = {}
342
+ if (additionalData) {
343
+ data = { ...data, ...additionalData } //inject data like contexts
344
+ }
345
+ state.failed = null
346
+ const fetchResult = request(requestSpec.controller, "/", data, {
347
+ renderer: requestSpec.renderer,
348
+ version: props.version,
349
+ query: requestSpec.query,
350
+ headers,
351
+ immediate: false,
352
+ messages: props.useMessageStore !== false,
353
+ })
354
+
355
+ return fetchResult
356
+ .execute()
357
+ .then(async () => {
358
+ // Canonical v2 envelope: entity payload is in `data`, bone definitions
359
+ // in `structure` (both present for v1 too after normalizeEnvelope).
360
+ let responseData = fetchResult.data.value
361
+ initForm(responseData["data"], responseData["structure"], state.values)
362
+
363
+ state.errors = responseData["errors"]
364
+ state.actionparams = responseData["params"]
365
+ state.actionname = responseData["action"]
366
+ state.loading = false
367
+ return fetchResult
368
+ })
369
+ .catch(async (error) => {
370
+ state.failed = error
371
+ throw error
372
+ })
373
+ }
374
+
375
+ function reload() {
376
+ state.loading = true
377
+ if (props.multistep && props.module && props.action) {
378
+ // Follow-driven multi-step: GET-render the entry step. The submit chain
379
+ // (POST → follow → GET next) is driven by ViForm via loadStep/sendData.
380
+ loadStep()
381
+ .then(() => {
382
+ state.loading = false
383
+ })
384
+ .catch(() => {
385
+ state.loading = false
386
+ })
387
+ } else if (props.structure) {
388
+ initForm(props.skel, props.structure, state.values)
389
+ state.loading = false
390
+ } else if (props.module && props.action) {
391
+ fetchData(props.fetchUrl, props.params)
392
+ .then(async (resp) => {
393
+ state.loading = false
394
+ })
395
+ .catch(async (error) => {
396
+ state.loading = false
397
+ })
398
+ } else {
399
+ console.log(props)
400
+ console.error("Error while building Form: you need atleast module and action or structure parameters")
401
+ }
402
+ }
403
+
404
+ function updateCategories() {
405
+ if (!state.structure) {
406
+ state.structure = {}
407
+ }
408
+
409
+ let catname = state.categoryDefaultname
410
+ if (!!catname && state.categoryDefaultname.includes("$(")) {
411
+ catname = Utils.stripHtml(Utils.formatString(catname, state.skel, "Allgemein"))
412
+ }
413
+
414
+ let categories = { default: { name: catname, bones: [], visible: false, open: true } }
415
+
416
+ for (const [boneName, bone] of Object.entries(state.structure)) {
417
+ if (props.bones.length > 0) {
418
+ if (!props.bones.includes(boneName)) {
419
+ continue
420
+ }
421
+ }
422
+
423
+ let category = "default"
424
+ let boneStructure = state.structure[boneName]
425
+
426
+ if (bone?.params?.category) {
427
+ category = bone.params.category.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase()
428
+ }
429
+
430
+ if (Object.keys(categories).includes(category)) {
431
+ categories[category]["bones"].push({
432
+ name: boneName,
433
+ })
434
+ } else {
435
+ categories[category] = {
436
+ name: bone.params.category,
437
+ bones: [
438
+ {
439
+ name: boneName,
440
+ },
441
+ ],
442
+ }
443
+ }
444
+ if (boneStructure["visible"] === true) {
445
+ categories[category]["visible"] = true
446
+ }
447
+
448
+ if (
449
+ (props.collapsedCategories &&
450
+ props.collapsedCategories.map((x) => x.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase()).includes(category)) ||
451
+ category === "system" ||
452
+ category === "internal" ||
453
+ props.collapsedCategories?.[0] === "*"
454
+ ) {
455
+ categories[category]["open"] = false
456
+ } else {
457
+ categories[category]["open"] = true
458
+ }
459
+ }
460
+
461
+ let sortedCategories = {}
462
+ Object.keys(categories)
463
+ .sort()
464
+ .forEach(function (key) {
465
+ sortedCategories[key] = categories[key]
466
+ })
467
+
468
+ return sortedCategories
469
+ }
470
+
471
+ function updateSkel(data) {
472
+ const { name, lang, value, index, valid } = data
473
+ state.valids[name] = valid
474
+
475
+ let skelvalue = state.skel[name]
476
+ if (value === undefined) return false
477
+ if (state.readonly) return false
478
+
479
+ if (lang) {
480
+ if (!skelvalue) {
481
+ skelvalue = {}
482
+ }
483
+ if (Object.keys(skelvalue).includes(lang) && index !== null) {
484
+ if (!skelvalue[lang]) {
485
+ skelvalue[lang] = []
486
+ }
487
+ skelvalue[lang][index] = value
488
+ } else {
489
+ skelvalue[lang] = value
490
+ }
491
+ } else if (index !== null) {
492
+ if (!skelvalue) {
493
+ skelvalue = []
494
+ }
495
+ skelvalue[index] = value
496
+ } else {
497
+ skelvalue = value
498
+ }
499
+ state.skel[name] = skelvalue
500
+ logics() //postprocess all bones if needed
501
+ }
502
+
503
+ function _logics(structure, skel) {
504
+ for (const [boneName, bone] of Object.entries(structure)) {
505
+ if (bone?.["params"]?.["evaluate"]) {
506
+ let ex = new Logics(bone?.["params"]?.["evaluate"])
507
+ state.skel[boneName] = ex.run(skel).toString() //rule produces, valid results? multilang, multiple etc?
508
+ }
509
+
510
+ if (bone?.["params"]?.["visibleIf"]) {
511
+ try {
512
+ let ex = new Logics(bone?.["params"]?.["visibleIf"])
513
+ bone["visible"] = ex.run(skel).toBool()
514
+ } catch (error) {
515
+ //console.log(bone?.["params"]?.["visibleIf"])
516
+ }
517
+ }
518
+
519
+ if (bone?.["params"]?.["readonlyIf"]) {
520
+ let ex = new Logics(bone?.["params"]?.["readonlyIf"])
521
+ bone["readonly"] = ex.run(skel).toBool()
522
+ }
523
+ if (bone?.["params"]?.["requiredIf"]) {
524
+ try {
525
+ let ex = new Logics(bone?.["params"]?.["requiredIf"])
526
+ bone["required"] = ex.run(skel).toBool()
527
+ } catch (error) {
528
+ //console.log(bone?.["params"]?.["requiredIf"])
529
+ }
530
+ }
531
+ if (bone?.["using"]) {
532
+ _logics(normalizeStructure(bone["using"]), skel)
533
+ }
534
+ }
535
+ }
536
+
537
+ function logics() {
538
+ let skel = { ...state.skel, _skel: state.skel }
539
+
540
+ if (props.internal) {
541
+ // injet mainform with _skel
542
+ skel = { ...skel, _skel: props.internal.skel }
543
+ }
544
+ _logics(state.structure, skel)
545
+ }
546
+
547
+ function initForm(skel, structure, values = {}) {
548
+ let skeldata = skel || {}
549
+ let formvalues = {}
550
+ if (values) {
551
+ formvalues = values
552
+ }
553
+
554
+ if (structure !== undefined) {
555
+ //props are refs to a js Object, removing reactivativ is not enought, we musst create copy of that object.
556
+ //each form has its own structure and mutating dont change the structure for other forms, this is needed for multiple records with logics
557
+ let struct = {}
558
+ for (const [k, v] of Object.entries(normalizeStructure(structure))) {
559
+ struct[k] = toRaw(v)
560
+ }
561
+ state.structure = structuredClone(struct)
562
+ }
563
+
564
+ state.skel = { ...skeldata, ...formvalues }
565
+ // Stand festhalten, wie er hereinkam: `state.skel` ist die Arbeitskopie und
566
+ // wird beim Tippen verändert. resetForm() stellt daraus wieder her, ohne
567
+ // erneut zu laden ("Änderungen verwerfen").
568
+ // markRaw: reine Lesedaten, kein reaktiver Proxy.
569
+ state.pristine = markRaw(rawCopy(state.skel))
570
+
571
+ state.categories = updateCategories()
572
+ }
573
+
574
+ /**
575
+ * Änderungen verwerfen: die Werte des letzten Server-Stands wieder einsetzen,
576
+ * OHNE neuen Request (dafür ist reload() da). Struktur und Kategorien bleiben.
577
+ */
578
+ function resetForm() {
579
+ if (state.pristine === undefined || state.pristine === null) return
580
+ initForm(rawCopy(state.pristine), undefined, state.values)
581
+ state.errors = []
582
+ }
583
+
584
+ return {
585
+ fetchData,
586
+ sendData,
587
+ loadStep,
588
+ resetForm,
589
+ back,
590
+ buildRequestUrl: () => {
591
+ const requestSpec = buildRequestSpec()
592
+ return `/${requestSpec.renderer}/${requestSpec.controller.join("/")}/${requestSpec.endpoint}`
593
+ },
594
+ updateCategories,
595
+ updateSkel,
596
+ normalizeStructure,
597
+ initForm,
598
+ logics,
599
+ reload,
600
+ }
601
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "package": "@fortawesome/fontawesome-free",
3
+ "version": "7.2.0",
4
+ "totalRequested": 14,
5
+ "copied": 14,
6
+ "missing": [],
7
+ "timestamp": "2026-03-26T08:36:14.058Z"
8
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M176 48L64 48c-8.8 0-16 7.2-16 16l0 384c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-240-88 0c-39.8 0-72-32.2-72-72l0-88zM316.1 160L224 67.9 224 136c0 13.3 10.7 24 24 24l68.1 0zM0 64C0 28.7 28.7 0 64 0L197.5 0c17 0 33.3 6.7 45.3 18.7L365.3 141.3c12 12 18.7 28.3 18.7 45.3L384 448c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 64z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M470.6 374.6l96-96c12.5-12.5 12.5-32.8 0-45.3l-96-96c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l41.4 41.4-357.5 0 41.4-41.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-96 96c-6 6-9.4 14.1-9.4 22.6s3.4 16.6 9.4 22.6l96 96c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-41.4-41.4 357.5 0-41.4 41.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M192 96c0-53 43-96 96-96s96 43 96 96l0 3.6c0 15.7-12.7 28.4-28.4 28.4l-135.1 0c-15.7 0-28.4-12.7-28.4-28.4l0-3.6zm345.6 12.8c10.6 14.1 7.7 34.2-6.4 44.8l-97.8 73.3c5.3 8.9 9.3 18.7 11.8 29.1l98.8 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-96 0 0 32c0 2.6-.1 5.3-.2 7.9l83.4 62.5c14.1 10.6 17 30.7 6.4 44.8s-30.7 17-44.8 6.4l-63.1-47.3c-23.2 44.2-66.5 76.2-117.7 83.9L312 280c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 230.2c-51.2-7.7-94.5-39.7-117.7-83.9L83.2 473.6c-14.1 10.6-34.2 7.7-44.8-6.4s-7.7-34.2 6.4-44.8l83.4-62.5c-.1-2.6-.2-5.2-.2-7.9l0-32-96 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l98.8 0c2.5-10.4 6.5-20.2 11.8-29.1L44.8 153.6c-14.1-10.6-17-30.7-6.4-44.8s30.7-17 44.8-6.4L192 184c12.3-5.1 25.8-8 40-8l112 0c14.2 0 27.7 2.8 40 8l108.8-81.6c14.1-10.6 34.2-7.7 44.8 6.4z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M434.8 70.1c14.3 10.4 17.5 30.4 7.1 44.7l-256 352c-5.5 7.6-14 12.3-23.4 13.1s-18.5-2.7-25.1-9.3l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l101.5 101.5 234-321.7c10.4-14.3 30.4-17.5 44.7-7.1z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zM224 160a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm-8 64l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M160 64c0-17.7-14.3-32-32-32S96 46.3 96 64l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96zM32 320c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM352 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 320c-17.7 0-32 14.3-32 32l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-96 0z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M256 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 210.7-41.4-41.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l96 96c12.5 12.5 32.8 12.5 45.3 0l96-96c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 242.7 256 32zM64 320c-35.3 0-64 28.7-64 64l0 32c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-32c0-35.3-28.7-64-64-64l-46.9 0-56.6 56.6c-31.2 31.2-81.9 31.2-113.1 0L110.9 320 64 320zm304 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M32 32C14.3 32 0 46.3 0 64l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 32zM64 352c0-17.7-14.3-32-32-32S0 334.3 0 352l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM448 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M128 40c0-22.1-17.9-40-40-40L40 0C17.9 0 0 17.9 0 40L0 88c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zm0 192c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zM0 424l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40zM320 40c0-22.1-17.9-40-40-40L232 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zM192 232l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40zM320 424c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M256 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 160-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l160 0 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160 160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0 0-160z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M64 160c0-53 43-96 96-96s96 43 96 96c0 42.7-27.9 78.9-66.5 91.4-28.4 9.2-61.5 35.3-61.5 76.6l0 24c0 17.7 14.3 32 32 32s32-14.3 32-32l0-24c0-1.7 .6-4.1 3.5-7.3 3-3.3 7.9-6.5 13.7-8.4 64.3-20.7 110.8-81 110.8-152.3 0-88.4-71.6-160-160-160S0 71.6 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32zm96 352c22.1 0 40-17.9 40-40s-17.9-40-40-40-40 17.9-40 40 17.9 40 40 40z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M256 0c14.7 0 28.2 8.1 35.2 21l216 400c6.7 12.4 6.4 27.4-.8 39.5S486.1 480 472 480L40 480c-14.1 0-27.2-7.4-34.4-19.5s-7.5-27.1-.8-39.5l216-400c7-12.9 20.5-21 35.2-21zm0 352a32 32 0 1 0 0 64 32 32 0 1 0 0-64zm0-192c-18.2 0-32.7 15.5-31.4 33.7l7.4 104c.9 12.5 11.4 22.3 23.9 22.3 12.6 0 23-9.7 23.9-22.3l7.4-104c1.3-18.2-13.1-33.7-31.4-33.7z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M256 109.3L256 320c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-210.7-41.4 41.4c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l96-96c12.5-12.5 32.8-12.5 45.3 0l96 96c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 109.3zM224 400c44.2 0 80-35.8 80-80l80 0c35.3 0 64 28.7 64 64l0 32c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64l0-32c0-35.3 28.7-64 64-64l80 0c0 44.2 35.8 80 80 80zm144 24a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc. --><path fill="currentColor" d="M55.1 73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L147.2 256 9.9 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192.5 301.3 329.9 438.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.8 256 375.1 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192.5 210.7 55.1 73.4z"/></svg>