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,114 @@
1
+ <template>
2
+ <wa-input
3
+ class="bone-widget bone-widget-date bone-widget-date-default"
4
+ :class="[`bone-widget-date-${name}`]"
5
+ :disabled="boneState.readonly"
6
+ :type="state.typeString"
7
+ :name="name"
8
+ :value="state.value"
9
+ :required="
10
+ boneState.bonestructure.required && !boneState.bonestructure.multiple && !boneState.bonestructure.languages
11
+ "
12
+ :placeholder="state.placeholder"
13
+ :data-user-invalid="boneState.errorMessages.length === 0 ? undefined : true"
14
+ step="1"
15
+ @change="changeEvent"
16
+ ></wa-input>
17
+ </template>
18
+
19
+ <script setup>
20
+ import { reactive, onMounted, computed, inject } from "vue"
21
+ /**
22
+ * Datums- und/oder Zeitfeld, gesteuert durch `bonestructure.date` und `bonestructure.time`.
23
+ *
24
+ * Rendert ein `<wa-input>` mit dem passenden `type`-Attribut:
25
+ * - `date` wenn nur `date: true`
26
+ * - `time` wenn nur `time: true`
27
+ * - `datetime-local` wenn beide gesetzt sind
28
+ *
29
+ * Bereinigt Zeitzonendaten aus dem Wert, bevor er ans Input weitergegeben wird.
30
+ * Unterstützt mehrfache Werte (`multiple`), aber keine Mehrsprachigkeit.
31
+ *
32
+ * @displayName DateBone
33
+ *
34
+ * @cssClass bone-widget - Basis-Klasse aller Bone-Widgets
35
+ * @cssClass bone-widget-date - Kennzeichnet den Bone-Typ
36
+ * @cssClass bone-widget-date-default - Standard-Variante
37
+ * @cssClass bone-widget-date-{name} - Dynamisch gesetzt auf den Bone-Namen, z.B. `bone-widget-date-publishedAt`
38
+ */
39
+ defineOptions({
40
+ inheritAttrs: false,
41
+ })
42
+ const props = defineProps({
43
+ /** Bone-Name – wird in jedem `change`-Event als erstes Argument übergeben. */
44
+ name: String,
45
+ /** Aktueller Bone-Wert als ISO-String, z.B. `"2026-03-28"` oder `"10:30:00"`. */
46
+ value: [Object, String, Number, Boolean, Array],
47
+ /** Index im Multiple-Array; `undefined` bei Einzelwert. */
48
+ index: Number,
49
+ /** Sprach-Key bei mehrsprachigen Bones, z.B. `"de"`. */
50
+ lang: String,
51
+ /** Bone-Struktur (aktuell nicht aktiv genutzt). */
52
+ bone: Object,
53
+ /** Setzt den Fokus nach 600 ms auf das Input-Element. */
54
+ autofocus: Boolean,
55
+ })
56
+
57
+ const emit = defineEmits([
58
+ /**
59
+ * Wird bei jeder Wertänderung sowie beim Mount (Initialisierung) gefeuert.
60
+ * @arg {string} name - Bone-Name
61
+ * @arg {string} value - Aktueller Eingabewert als ISO-String
62
+ * @arg {string|undefined} lang - Sprach-Key
63
+ * @arg {number|undefined} index - Index im Multiple-Array
64
+ */
65
+ "change",
66
+ ])
67
+
68
+ const boneState = inject("boneState")
69
+ const state = reactive({
70
+ value: computed(() => {
71
+ // remove timezone data if timed
72
+
73
+ let boneValue = props.value
74
+ if (props.value && boneState.bonestructure["time"] && boneState.bonestructure["date"]) {
75
+ boneValue = props.value?.split("+")[0]
76
+ } else if (props.value && boneState.bonestructure["time"] && props.value.includes("T")) {
77
+ boneValue = props.value?.split("+")[0]?.split("T")[1]
78
+ } else if (props.value && props.value.includes("T")) {
79
+ //convert to readable value
80
+ boneValue = new Date(props.value).toISOString().substr(0, 10)
81
+ }
82
+
83
+ return boneValue
84
+ }),
85
+ typeString: computed(() => {
86
+ // Calculate the correct datetype String
87
+ let typeString = "datetime-local"
88
+ if (!boneState.bonestructure["time"]) {
89
+ typeString = "date"
90
+ }
91
+
92
+ if (!boneState.bonestructure["date"]) {
93
+ typeString = "time"
94
+ }
95
+ return typeString
96
+ }),
97
+ placeholder: computed(() => {
98
+ if (boneState.label !== "placeholder") return undefined
99
+ let name = boneState?.bonestructure?.descr
100
+ if (boneState.bonestructure.required) {
101
+ name += "*"
102
+ }
103
+ return name
104
+ }),
105
+ })
106
+
107
+ function changeEvent(event) {
108
+ emit("change", props.name, event.target.value, props.lang, props.index)
109
+ }
110
+
111
+ onMounted(() => {
112
+ emit("change", props.name, state.value, props.lang, props.index) //init
113
+ })
114
+ </script>
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <wa-input
3
+ ref="emailBone"
4
+ class="bone-widget bone-widget-email bone-widget-email-default"
5
+ :class="[`bone-widget-email-${name}`]"
6
+ :name="name"
7
+ :disabled="boneState.readonly"
8
+ type="email"
9
+ :value="value"
10
+ :required="
11
+ boneState.bonestructure.required && !boneState.bonestructure.multiple && !boneState.bonestructure.languages
12
+ "
13
+ :placeholder="state.placeholder"
14
+ :data-user-invalid="boneState.errorMessages.length === 0 ? undefined : true"
15
+ @change="changeEvent"
16
+ @keyup="changeEvent"
17
+ ></wa-input>
18
+ </template>
19
+
20
+ <script setup>
21
+ import { reactive, onMounted, inject, ref, watchEffect, computed } from "vue"
22
+ import { useTimeoutFn } from "@vueuse/core"
23
+ /**
24
+ * Einzeiliges E-Mail-Eingabefeld mit nativer Browser-Validierung.
25
+ *
26
+ * Rendert ein `<wa-input type="email">` und nutzt die native Constraint
27
+ * Validation API des Browsers zur Format-Prüfung. Bei
28
+ * `boneState.label === "placeholder"` wird die Bone-Beschreibung als
29
+ * Placeholder angezeigt statt als Label darüber.
30
+ *
31
+ * @displayName EmailBone
32
+ *
33
+ * @cssClass bone-widget - Basis-Klasse aller Bone-Widgets
34
+ * @cssClass bone-widget-email - Kennzeichnet den Bone-Typ
35
+ * @cssClass bone-widget-email-default - Standard-Variante
36
+ * @cssClass bone-widget-email-{name} - Dynamisch gesetzt auf den Bone-Namen, z.B. `bone-widget-email-email`
37
+ */
38
+ defineOptions({
39
+ inheritAttrs: false,
40
+ })
41
+ const props = defineProps({
42
+ /** Bone-Name – wird in jedem `change`-Event als erstes Argument übergeben. */
43
+ name: String,
44
+ /** Aktueller Bone-Wert. */
45
+ value: [Object, String, Number, Boolean, Array],
46
+ /** Index im Multiple-Array; `undefined` bei Einzelwert. */
47
+ index: Number,
48
+ /** Sprach-Key bei mehrsprachigen Bones, z.B. `"de"`. */
49
+ lang: String,
50
+ /** Bone-Struktur (aktuell nicht aktiv genutzt). */
51
+ bone: Object,
52
+ /** Setzt den Fokus nach 600 ms auf das Input-Element. */
53
+ autofocus: Boolean,
54
+ })
55
+
56
+ const emit = defineEmits([
57
+ /**
58
+ * Wird bei jeder Wertänderung sowie beim Mount (Initialisierung) gefeuert.
59
+ * @arg {string} name - Bone-Name
60
+ * @arg {string} value - Aktueller Eingabewert
61
+ * @arg {string|undefined} lang - Sprach-Key
62
+ * @arg {number|undefined} index - Index im Multiple-Array
63
+ */
64
+ "change",
65
+ ])
66
+
67
+ const boneState = inject("boneState")
68
+ const state = reactive({
69
+ placeholder: computed(() => {
70
+ if (boneState.label !== "placeholder") return undefined
71
+ let name = boneState?.bonestructure?.descr
72
+ if (boneState.bonestructure.required) {
73
+ name += "*"
74
+ }
75
+ return name
76
+ }),
77
+ })
78
+
79
+ const emailBone = ref(null)
80
+
81
+ function changeEvent(event) {
82
+ emit("change", props.name, event.target.value, props.lang, props.index)
83
+ }
84
+
85
+ watchEffect(() => {
86
+ if (props.autofocus) {
87
+ if (emailBone.value && emailBone.value !== null && emailBone.value !== null) {
88
+ const { start } = useTimeoutFn(() => {
89
+ emailBone.value.focus()
90
+ }, 600)
91
+
92
+ start()
93
+ }
94
+ }
95
+ })
96
+
97
+ onMounted(() => {
98
+ emit("change", props.name, props.value, props.lang, props.index) //init
99
+ })
100
+ </script>
@@ -0,0 +1,273 @@
1
+ <template>
2
+ <div
3
+ class="bone-widget-file-area"
4
+ :class="{
5
+ 'is-dragover': state.droparea,
6
+ 'has-value': !!value,
7
+ 'is-loading': state.loading,
8
+ 'is-readonly': boneState.readonly,
9
+ }"
10
+ :data-user-invalid="boneState.errorMessages.length === 0 ? undefined : true"
11
+ @click="openPicker"
12
+ @dragover.prevent="state.droparea = true"
13
+ @dragleave.prevent="state.droparea = false"
14
+ @drop.prevent="handleDrop"
15
+ >
16
+ <input
17
+ ref="uploadinput"
18
+ style="display: none"
19
+ type="file"
20
+ :multiple="boneState.multiple"
21
+ @change="handleUpload"
22
+ />
23
+
24
+ <div
25
+ v-if="!value"
26
+ class="wa-cluster wa-gap-s wa-align-items-center bone-widget-prompt"
27
+ >
28
+ <wa-icon name="upload"></wa-icon>
29
+ <span>{{ $t("bone.dropzone") }}</span>
30
+ </div>
31
+
32
+ <div
33
+ v-else
34
+ class="wa-flank wa-gap-xs wa-align-items-center"
35
+ >
36
+ <div
37
+ v-if="value?.['dest']?.['mimetype']?.includes('image')"
38
+ class="bone-widget-preview has-preview"
39
+ @click.stop="state.previewopen = !state.previewopen"
40
+ >
41
+ <wa-avatar
42
+ style="--size: round(calc(var(--wa-form-control-height) - 0.09em), 1px)"
43
+ class="bone-widget-preview-img"
44
+ shape="rounded"
45
+ :image="createBackgroundImage()"
46
+ alt=""
47
+ ></wa-avatar>
48
+ <wa-dialog
49
+ :label="decodeURIComponent(value?.['dest']?.['name'])"
50
+ class="bone-widget-preview-overlay"
51
+ :open="state.previewopen"
52
+ >
53
+ <img
54
+ :src="createBackgroundImage()"
55
+ alt=""
56
+ />
57
+ </wa-dialog>
58
+ </div>
59
+ <div
60
+ v-else
61
+ class="bone-widget-preview"
62
+ >
63
+ <wa-avatar
64
+ v-if="value?.['dest']?.['name']"
65
+ shape="rounded"
66
+ style="--size: round(calc(var(--wa-form-control-height) - 0.09em), 1px)"
67
+ >
68
+ <wa-icon
69
+ slot="icon"
70
+ name="file"
71
+ ></wa-icon>
72
+ </wa-avatar>
73
+ </div>
74
+ <div class="wa-flank:end wa-gap-xs wa-align-items-center bone-widget-file-row">
75
+ <div
76
+ v-if="value?.['dest']?.['name']"
77
+ class="bone-widget-filename"
78
+ :title="decodeURIComponent(value?.['dest']?.['name'])"
79
+ >
80
+ {{ decodeURIComponent(value?.["dest"]?.["name"]) }}
81
+ </div>
82
+ <div @click.stop>
83
+ <slot></slot>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <div
89
+ v-if="state.loading"
90
+ class="wa-cluster wa-align-items-center bone-widget-loader"
91
+ >
92
+ <wa-spinner></wa-spinner>
93
+ </div>
94
+ </div>
95
+ </template>
96
+
97
+ <script setup>
98
+ import { reactive, onMounted, inject, ref } from "vue"
99
+ import { downloadUrlFor, uploadFile } from "../../../request/index.js"
100
+ /**
101
+ * Datei-Upload-Widget für Bones vom Typ `relational.tree.leaf.file.file`.
102
+ *
103
+ * Stellt eine kombinierte Klick-/Drop-Fläche bereit: ein Klick öffnet den File-Picker,
104
+ * ein Drop lädt die fallengelassene Datei hoch. Bei Bilddateien wird eine klickbare
105
+ * Vorschau angezeigt, die einen Dialog öffnet. Action-Buttons (Download, Delete)
106
+ * werden über `<bone-actions>` per Default-Slot eingebracht.
107
+ *
108
+ * @displayName FileBone
109
+ *
110
+ * @cssClass bone-widget-file-area - Äußerer klickbarer Container (Drop-Zone)
111
+ * @cssClass bone-widget-prompt - Empty-State (Icon + Text)
112
+ * @cssClass bone-widget-loader - Während des Uploads sichtbar
113
+ * @cssClass bone-widget-preview - Container für das Vorschaubild oder das Datei-Icon
114
+ * @cssClass has-preview - Zusatzklasse wenn ein Bild-Vorschau vorliegt
115
+ * @cssClass is-dragover - Wenn eine Datei über die Fläche gezogen wird
116
+ * @cssClass has-value - Wenn ein Wert gesetzt ist
117
+ * @cssClass is-loading - Während ein Upload läuft
118
+ * @cssClass is-readonly - Wenn der Bone readonly ist
119
+ */
120
+ defineOptions({
121
+ inheritAttrs: false,
122
+ })
123
+ const props = defineProps({
124
+ /** Bone-Name – wird in jedem `change`-Event als erstes Argument übergeben. */
125
+ name: String,
126
+ /** Aktueller Bone-Wert; Objekt mit `dest`- und `rel`-Schlüsseln oder `null`. */
127
+ value: [Object, String, Number, Boolean, Array],
128
+ /** Index im Multiple-Array; `undefined` bei Einzelwert. */
129
+ index: Number,
130
+ /** Sprach-Key bei mehrsprachigen Bones, z.B. `"de"`. */
131
+ lang: String,
132
+ /** Bone-Struktur; `public`-Flag steuert ob der Upload öffentlich ist. */
133
+ bone: Object,
134
+ /** Aktuell nicht genutzt. */
135
+ autofocus: Boolean,
136
+ })
137
+
138
+ const emit = defineEmits([
139
+ /**
140
+ * Wird bei Datei-Upload, Datei-Drop, Löschen und beim Mount (Initialisierung) gefeuert.
141
+ * @arg {string} name - Bone-Name
142
+ * @arg {{ dest: object, rel: null }|string} value - Hochgeladenes Datei-Objekt oder `""` beim Löschen
143
+ * @arg {string|undefined} lang - Sprach-Key
144
+ * @arg {number|undefined} index - Index im Multiple-Array
145
+ */
146
+ "change",
147
+ ])
148
+
149
+ const boneState = inject("boneState")
150
+ const uploadinput = ref()
151
+ const state = reactive({
152
+ loading: false,
153
+ droparea: false,
154
+ previewopen: false,
155
+ })
156
+
157
+ onMounted(() => {
158
+ emit("change", props.name, props.value, props.lang, props.index) //init
159
+ })
160
+
161
+ /**
162
+ * Triggert den nativen File-Picker des hidden Inputs.
163
+ * Nur im Empty-State aktiv — ist bereits ein Wert gesetzt, muss zuerst per
164
+ * Delete-Action geleert oder per Drag-and-Drop ersetzt werden.
165
+ */
166
+ function openPicker() {
167
+ if (state.loading || boneState.readonly || props.value) return
168
+ uploadinput.value?.click()
169
+ }
170
+
171
+ function createBackgroundImage() {
172
+ console.log(props.value)
173
+ return downloadUrlFor(props.value, false)
174
+ }
175
+
176
+ async function handleUpload(event) {
177
+ state.loading = true
178
+ try {
179
+ for (let file of event.target.files) {
180
+ let fileresult = await uploadFile(file, { publicUpload: !!boneState?.bonestructure["public"] })
181
+ console.log(fileresult)
182
+ uploadinput.value.value = ""
183
+ emit("change", props.name, { dest: fileresult, rel: null }, props.lang, props.index)
184
+ }
185
+ } finally {
186
+ state.loading = false
187
+ }
188
+ }
189
+
190
+ async function handleDrop(event) {
191
+ state.loading = true
192
+ state.droparea = false
193
+ try {
194
+ for (let file of event.dataTransfer.files) {
195
+ let fileresult = await uploadFile(file, { publicUpload: !!boneState?.bonestructure["public"] })
196
+ uploadinput.value.value = ""
197
+ emit("change", props.name, { dest: fileresult, rel: null }, props.lang, props.index)
198
+ break
199
+ }
200
+ } finally {
201
+ state.loading = false
202
+ }
203
+ }
204
+ </script>
205
+
206
+ <style scoped>
207
+ .bone-widget-file-area {
208
+ position: relative;
209
+ padding: calc(var(--wa-space-xs) - 1px);
210
+ border: 2px dashed var(--wa-color-surface-border);
211
+ border-radius: var(--wa-border-radius-m);
212
+ cursor: pointer;
213
+ transition: all 0.15s ease;
214
+
215
+ &.has-value {
216
+ border-style: none;
217
+ padding: 0;
218
+ background-color: var(--wa-color-neutral-95);
219
+ }
220
+
221
+ &.is-dragover {
222
+ border-color: var(--wa-color-brand-50);
223
+ background-color: var(--wa-color-brand-95);
224
+ }
225
+
226
+ &.is-readonly,
227
+ &.is-loading,
228
+ &.has-value {
229
+ cursor: default;
230
+ }
231
+
232
+ &:hover:not(.is-readonly):not(.is-loading):not(.has-value) {
233
+ border-color: var(--wa-color-brand-60);
234
+ }
235
+ }
236
+
237
+ .bone-widget-prompt {
238
+ justify-content: center;
239
+ color: var(--wa-color-on-quiet);
240
+ /* Klicks fallen auf die outer Drop-Zone durch */
241
+ pointer-events: none;
242
+ }
243
+
244
+ /* Dateiname-Zeile darf schrumpfen; lange Namen werden mit Ellipsis abgekürzt */
245
+ .bone-widget-file-row {
246
+ min-width: 0;
247
+ }
248
+
249
+ .bone-widget-filename {
250
+ min-width: 0;
251
+ overflow: hidden;
252
+ white-space: nowrap;
253
+ text-overflow: ellipsis;
254
+ }
255
+
256
+ .bone-widget-loader {
257
+ position: absolute;
258
+ inset: 0;
259
+ justify-content: center;
260
+ border-radius: inherit;
261
+ background-color: color-mix(in srgb, var(--wa-color-surface-default) 70%, transparent);
262
+ }
263
+
264
+ /* Klickbare Bildvorschau: Zoom-Cursor + leichte Skalierung beim Hover */
265
+ .bone-widget-preview.has-preview {
266
+ cursor: zoom-in;
267
+ transition: transform 0.15s ease;
268
+
269
+ &:hover {
270
+ transform: scale(1.08);
271
+ }
272
+ }
273
+ </style>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <JsonEditorVue
3
+ ref="editorRef"
4
+ v-model="value_ref"
5
+ :on-change="changeEvent"
6
+ :read-only="boneState.readonly"
7
+ />
8
+ </template>
9
+
10
+ <script setup>
11
+ import JsonEditorVue from "json-editor-vue"
12
+ import { onMounted, inject, ref, watch } from "vue"
13
+ import { useTimeoutFn } from "@vueuse/core"
14
+ const value_ref = ref()
15
+ defineOptions({
16
+ inheritAttrs: false,
17
+ })
18
+ const props = defineProps({
19
+ name: String,
20
+ value: [Object, String, Number, Boolean, Array],
21
+ index: Number,
22
+ lang: String,
23
+ readonly: Boolean,
24
+ params: Object,
25
+ bone: Object,
26
+ autofocus: Boolean,
27
+ })
28
+
29
+ const editorRef = ref(null)
30
+ const emit = defineEmits(["change"])
31
+ const boneState = inject("boneState")
32
+ function changeEvent(newVal) {
33
+ emit("change", props.name, newVal.json, props.lang, props.index)
34
+ }
35
+
36
+ onMounted(() => {
37
+ emit("change", props.name, props.value, props.lang, props.index) //init
38
+ value_ref.value = props.value
39
+ })
40
+ watch(value_ref, (newVal, oldVal) => {
41
+ if (!oldVal && newVal) {
42
+ useTimeoutFn(() => {
43
+ editorRef.value.jsonEditor.collapse([], true)
44
+ }, 300)
45
+ }
46
+ })
47
+ </script>
48
+
49
+ <style scoped>
50
+ .bone-widget-box {
51
+ width: 100%;
52
+ border: 1px solid var(--wa-color-neutral-60);
53
+ border-radius: 5px;
54
+ min-height: 40px;
55
+ }
56
+ </style>
57
+ <style>
58
+ :root {
59
+ --jse-theme-color: var(--wa-color-surface-default);
60
+ --jse-menu-color: var(--wa-color-neutral-30);
61
+ --jse-theme-color-highlight: var(--wa-color-neutral-90);
62
+ }
63
+ </style>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <wa-input
3
+ class="bone-widget bone-widget-key bone-widget-key-default"
4
+ :class="[`bone-widget-key-${name}`]"
5
+ disabled
6
+ :value="value"
7
+ :data-data-invalid="boneState.errorMessages.length === 0 ? undefined : true"
8
+ @change="changeEvent"
9
+ ></wa-input>
10
+ </template>
11
+
12
+ <script setup>
13
+ import { reactive, onMounted, inject } from "vue"
14
+ /**
15
+ * Anzeige-Widget für den internen Datenbankschlüssel eines Eintrags (Typ `key`).
16
+ *
17
+ * Rendert ein immer deaktiviertes `<wa-input>`, da der Schlüssel nicht manuell
18
+ * editiert werden kann. Der Wert wird direkt aus dem Skel übernommen und dient
19
+ * ausschließlich zur Information. Unterstützt kein multiple und kein multilang.
20
+ *
21
+ * @displayName KeyBone
22
+ *
23
+ * @cssClass bone-widget - Basis-Klasse aller Bone-Widgets
24
+ * @cssClass bone-widget-key - Kennzeichnet den Bone-Typ
25
+ * @cssClass bone-widget-key-default - Standard-Variante
26
+ * @cssClass bone-widget-key-{name} - Dynamisch gesetzt auf den Bone-Namen, z.B. `bone-widget-key-key`
27
+ */
28
+ defineOptions({
29
+ inheritAttrs: false,
30
+ })
31
+ const props = defineProps({
32
+ /** Bone-Name – wird in jedem `change`-Event als erstes Argument übergeben. */
33
+ name: String,
34
+ /** Aktueller Schlüsselwert (URL-safe Base64-String). */
35
+ value: [Object, String, Number, Boolean, Array],
36
+ /** Index im Multiple-Array; nicht genutzt, da multiple nicht unterstützt wird. */
37
+ index: Number,
38
+ /** Sprach-Key; nicht genutzt, da multilang nicht unterstützt wird. */
39
+ lang: String,
40
+ /** Readonly-Flag (wird nicht ausgewertet – der Bone ist immer disabled). */
41
+ readonly: Boolean,
42
+ /** Zusätzliche Bone-Parameter. */
43
+ params: Object,
44
+ /** Bone-Struktur (aktuell nicht aktiv genutzt). */
45
+ bone: Object,
46
+ /** Setzt den Fokus (ohne Effekt, da das Element immer disabled ist). */
47
+ autofocus: Boolean,
48
+ })
49
+
50
+ const emit = defineEmits([
51
+ /**
52
+ * Wird beim Mount (Initialisierung) und bei change gefeuert.
53
+ * @arg {string} name - Bone-Name
54
+ * @arg {string} value - Aktueller Schlüsselwert
55
+ * @arg {string|undefined} lang - Sprach-Key
56
+ * @arg {number|undefined} index - Index im Multiple-Array
57
+ */
58
+ "change",
59
+ ])
60
+
61
+ const boneState = inject("boneState")
62
+ const state = reactive({})
63
+
64
+ function changeEvent(event) {
65
+ emit("change", props.name, event.target.value, props.lang, props.index)
66
+ }
67
+
68
+ onMounted(() => {
69
+ emit("change", props.name, props.value, props.lang, props.index) //init
70
+ })
71
+ </script>