@conduction/nextcloud-vue 0.1.0-beta.3 → 0.1.0-beta.4

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 (152) hide show
  1. package/README.md +226 -226
  2. package/dist/nextcloud-vue.cjs.js +60455 -8755
  3. package/dist/nextcloud-vue.cjs.js.map +1 -1
  4. package/dist/nextcloud-vue.css +2062 -528
  5. package/dist/nextcloud-vue.esm.js +60411 -8731
  6. package/dist/nextcloud-vue.esm.js.map +1 -1
  7. package/package.json +75 -62
  8. package/src/components/CnActionsBar/CnActionsBar.vue +235 -225
  9. package/src/components/CnActionsBar/index.js +1 -1
  10. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -0
  11. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
  12. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
  13. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -0
  14. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
  15. package/src/components/CnAdvancedFormDialog/index.js +1 -0
  16. package/src/components/CnCardGrid/CnCardGrid.vue +152 -152
  17. package/src/components/CnCardGrid/index.js +1 -1
  18. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  19. package/src/components/CnCellRenderer/index.js +1 -1
  20. package/src/components/CnChartWidget/CnChartWidget.vue +320 -0
  21. package/src/components/CnChartWidget/index.js +1 -0
  22. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  23. package/src/components/CnConfigurationCard/index.js +1 -1
  24. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -0
  25. package/src/components/CnDashboardGrid/index.js +1 -0
  26. package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -0
  27. package/src/components/CnDashboardPage/index.js +1 -0
  28. package/src/components/CnDataTable/CnDataTable.vue +349 -349
  29. package/src/components/CnDataTable/index.js +1 -1
  30. package/src/components/CnDetailCard/CnDetailCard.vue +214 -0
  31. package/src/components/CnDetailCard/index.js +1 -0
  32. package/src/components/CnDetailPage/CnDetailPage.vue +281 -0
  33. package/src/components/CnDetailPage/index.js +1 -0
  34. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +231 -223
  35. package/src/components/CnFacetSidebar/index.js +1 -1
  36. package/src/components/CnFilterBar/CnFilterBar.vue +152 -152
  37. package/src/components/CnFilterBar/index.js +1 -1
  38. package/src/components/CnIcon/CnIcon.vue +89 -89
  39. package/src/components/CnIcon/index.js +1 -1
  40. package/src/components/CnIndexPage/CnIndexPage.vue +874 -816
  41. package/src/components/CnIndexPage/index.js +1 -1
  42. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +503 -484
  43. package/src/components/CnIndexSidebar/index.js +1 -1
  44. package/src/components/CnItemCard/CnItemCard.vue +132 -0
  45. package/src/components/CnItemCard/index.js +1 -0
  46. package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -89
  47. package/src/components/CnKpiGrid/index.js +1 -1
  48. package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -160
  49. package/src/components/CnMassActionBar/index.js +1 -1
  50. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -320
  51. package/src/components/CnMassCopyDialog/index.js +1 -1
  52. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -238
  53. package/src/components/CnMassDeleteDialog/index.js +1 -1
  54. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -190
  55. package/src/components/CnMassExportDialog/index.js +1 -1
  56. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -491
  57. package/src/components/CnMassImportDialog/index.js +1 -1
  58. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  59. package/src/components/CnNoteCard/index.js +1 -0
  60. package/src/components/CnNotesCard/CnNotesCard.vue +413 -0
  61. package/src/components/CnNotesCard/index.js +1 -0
  62. package/src/components/CnObjectCard/CnObjectCard.vue +292 -292
  63. package/src/components/CnObjectCard/index.js +1 -1
  64. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -0
  65. package/src/components/CnObjectSidebar/index.js +1 -0
  66. package/src/components/CnPageHeader/CnPageHeader.vue +57 -57
  67. package/src/components/CnPageHeader/index.js +1 -1
  68. package/src/components/CnPagination/CnPagination.vue +252 -252
  69. package/src/components/CnPagination/index.js +1 -1
  70. package/src/components/CnRowActions/CnRowActions.vue +73 -73
  71. package/src/components/CnRowActions/index.js +1 -1
  72. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  73. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -0
  74. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  75. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  76. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  77. package/src/components/CnSchemaFormDialog/index.js +1 -0
  78. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  79. package/src/components/CnSettingsCard/index.js +1 -1
  80. package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
  81. package/src/components/CnSettingsSection/index.js +1 -1
  82. package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -366
  83. package/src/components/CnStatsBlock/index.js +1 -1
  84. package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
  85. package/src/components/CnStatusBadge/index.js +1 -1
  86. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -0
  87. package/src/components/CnTabbedFormDialog/index.js +1 -0
  88. package/src/components/CnTasksCard/CnTasksCard.vue +373 -0
  89. package/src/components/CnTasksCard/index.js +1 -0
  90. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  91. package/src/components/CnTileWidget/index.js +1 -0
  92. package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -0
  93. package/src/components/CnTimelineStages/index.js +1 -0
  94. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -0
  95. package/src/components/CnUserActionMenu/index.js +1 -0
  96. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -312
  97. package/src/components/CnVersionInfoCard/index.js +1 -1
  98. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  99. package/src/components/CnWidgetRenderer/index.js +1 -0
  100. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -0
  101. package/src/components/CnWidgetWrapper/index.js +1 -0
  102. package/src/components/index.js +43 -29
  103. package/src/composables/index.js +4 -3
  104. package/src/composables/useDashboardView.js +240 -0
  105. package/src/composables/useDetailView.js +289 -132
  106. package/src/composables/useListView.js +363 -362
  107. package/src/composables/useSubResource.js +142 -142
  108. package/src/constants/metadata.js +30 -30
  109. package/src/css/CnSchemaFormDialog.css +546 -0
  110. package/src/css/__sample_nextcloud_tokens.css +110 -0
  111. package/src/css/actions-bar.css +48 -48
  112. package/src/css/badge.css +51 -51
  113. package/src/css/card.css +128 -128
  114. package/src/css/dashboard.css +70 -0
  115. package/src/css/detail-page.css +168 -0
  116. package/src/css/detail.css +68 -68
  117. package/src/css/index-page.css +44 -32
  118. package/src/css/index-sidebar.css +193 -187
  119. package/src/css/index.css +16 -12
  120. package/src/css/layout.css +90 -90
  121. package/src/css/page-header.css +33 -33
  122. package/src/css/pagination.css +72 -72
  123. package/src/css/table.css +142 -142
  124. package/src/css/timeline-stages.css +218 -0
  125. package/src/css/utilities.css +46 -46
  126. package/src/index.js +72 -53
  127. package/src/store/createSubResourcePlugin.js +135 -135
  128. package/src/store/index.js +3 -3
  129. package/src/store/plugins/auditTrails.js +17 -17
  130. package/src/store/plugins/files.js +250 -186
  131. package/src/store/plugins/index.js +7 -5
  132. package/src/store/plugins/lifecycle.js +180 -180
  133. package/src/store/plugins/relations.js +68 -68
  134. package/src/store/plugins/search.js +372 -0
  135. package/src/store/plugins/selection.js +104 -0
  136. package/src/store/useObjectStore.js +829 -686
  137. package/src/types/auditTrail.d.ts +32 -32
  138. package/src/types/file.d.ts +23 -23
  139. package/src/types/index.d.ts +35 -35
  140. package/src/types/notification.d.ts +36 -36
  141. package/src/types/object.d.ts +40 -40
  142. package/src/types/organisation.d.ts +41 -41
  143. package/src/types/register.d.ts +25 -25
  144. package/src/types/schema.d.ts +39 -39
  145. package/src/types/shared.d.ts +79 -79
  146. package/src/types/source.d.ts +14 -14
  147. package/src/types/task.d.ts +31 -31
  148. package/src/utils/errors.js +96 -96
  149. package/src/utils/headers.js +68 -50
  150. package/src/utils/id.js +13 -0
  151. package/src/utils/index.js +3 -3
  152. package/src/utils/schema.js +422 -419
@@ -0,0 +1,579 @@
1
+ <template>
2
+ <NcDialog
3
+ :name="resolvedTitle"
4
+ size="large"
5
+ :can-close="!loading"
6
+ @closing="$emit('close')">
7
+ <!-- Result phase -->
8
+ <div v-if="result !== null" class="cn-advanced-form-dialog__result">
9
+ <NcNoteCard v-if="result.success" type="success">
10
+ {{ resolvedSuccessText }}
11
+ </NcNoteCard>
12
+ <NcNoteCard v-if="result.error" type="error">
13
+ {{ result.error }}
14
+ </NcNoteCard>
15
+ </div>
16
+
17
+ <!-- Form phase -->
18
+ <div v-else class="cn-advanced-form-dialog__form">
19
+ <!-- Full form override slot -->
20
+ <slot
21
+ v-if="$scopedSlots.form"
22
+ name="form"
23
+ :form-data="formData"
24
+ :update-field="updateField"
25
+ :object-properties="objectPropertiesForSlot"
26
+ :json-data="jsonData"
27
+ :update-json="updateJsonFromExternal"
28
+ :is-valid-json="isValidJson(jsonData)" />
29
+
30
+ <!-- Default content -->
31
+ <template v-else>
32
+ <!-- Register/schema selection step (optional slot) -->
33
+ <slot
34
+ v-if="$scopedSlots['register-schema-selection']"
35
+ name="register-schema-selection"
36
+ :proceed="proceedFromRegisterSchemaStep" />
37
+
38
+ <!-- Main tabs -->
39
+ <div v-else class="cn-advanced-form-dialog__tabs tabContainer">
40
+ <BTabs v-model="activeTab" content-class="mt-3" justified>
41
+ <!-- Properties tab -->
42
+ <BTab v-if="showPropertiesTable" title="Properties">
43
+ <slot
44
+ name="tab-properties"
45
+ :form-data="formData"
46
+ :update-field="updateField"
47
+ :object-properties="objectPropertiesForSlot"
48
+ :selected-property="selectedProperty"
49
+ :handle-row-click="onRowClick"
50
+ :get-property-display-name="getPropertyDisplayName"
51
+ :get-property-validation-class="getPropertyValidationClass"
52
+ :is-property-editable="isPropertyEditable"
53
+ :validation-display="validationDisplay">
54
+ <CnPropertiesTab
55
+ ref="propertiesTab"
56
+ :schema="schema"
57
+ :item="item"
58
+ :form-data="formData"
59
+ :selected-property="selectedProperty"
60
+ :editable-types="editableTypes"
61
+ :validation-display="validationDisplay"
62
+ :exclude-fields="excludeFields"
63
+ :include-fields="includeFields"
64
+ @update:property-value="onPropertyValueUpdate"
65
+ @update:selected-property="selectedProperty = $event" />
66
+ </slot>
67
+ </BTab>
68
+
69
+ <!-- Metadata tab -->
70
+ <BTab v-if="resolvedShowMetadataTab" title="Metadata">
71
+ <slot name="tab-metadata" :item="item" :form-data="formData">
72
+ <CnMetadataTab :item="item" :form-data="formData" />
73
+ </slot>
74
+ </BTab>
75
+
76
+ <!-- Data (JSON) tab -->
77
+ <BTab v-if="showJsonTab" title="Data">
78
+ <slot
79
+ name="tab-data"
80
+ :json-data="jsonData"
81
+ :update-json="updateJsonFromExternal"
82
+ :is-valid="isValidJson(jsonData)"
83
+ :format-json="formatJSON">
84
+ <CnDataTab
85
+ :value="jsonData"
86
+ :dark="jsonEditorDark"
87
+ @update:value="jsonData = $event"
88
+ @format="onFormatResult" />
89
+ </slot>
90
+ </BTab>
91
+ </BTabs>
92
+ </div>
93
+ </template>
94
+ </div>
95
+
96
+ <template #actions>
97
+ <slot name="actions-left" />
98
+ <NcButton @click="$emit('close')">
99
+ {{ result !== null ? closeLabel : cancelLabel }}
100
+ </NcButton>
101
+ <NcButton
102
+ v-if="result === null"
103
+ type="primary"
104
+ :disabled="loading"
105
+ @click="executeConfirm">
106
+ <template #icon>
107
+ <NcLoadingIcon v-if="loading" :size="20" />
108
+ <Plus v-else-if="isCreateMode" :size="20" />
109
+ <ContentSaveOutline v-else :size="20" />
110
+ </template>
111
+ {{ resolvedConfirmLabel }}
112
+ </NcButton>
113
+ <slot name="actions-right" />
114
+ </template>
115
+ </NcDialog>
116
+ </template>
117
+
118
+ <script>
119
+ import {
120
+ NcDialog,
121
+ NcButton,
122
+ NcNoteCard,
123
+ NcLoadingIcon,
124
+ } from '@nextcloud/vue'
125
+ import Plus from 'vue-material-design-icons/Plus.vue'
126
+ import ContentSaveOutline from 'vue-material-design-icons/ContentSaveOutline.vue'
127
+ import { BTabs, BTab } from 'bootstrap-vue'
128
+ import { fieldsFromSchema } from '../../utils/schema.js'
129
+ import CnPropertiesTab from './CnPropertiesTab.vue'
130
+ import CnMetadataTab from './CnMetadataTab.vue'
131
+ import CnDataTab from './CnDataTab.vue'
132
+
133
+ /** Schema types for which we have built-in inline editing support in the properties table. */
134
+ const EDITABLE_SUPPORTED_TYPES = ['string', 'number', 'integer', 'boolean']
135
+
136
+ /**
137
+ * CnAdvancedFormDialog — Create/edit dialog with properties table (click-to-edit), JSON tab, and optional store integration.
138
+ *
139
+ * When `item` is null, operates in create mode. When `item` is provided, operates in edit mode.
140
+ * Provides a richer UX than CnFormDialog: properties table with inline editing, Data (JSON) tab with CodeMirror,
141
+ * optional Metadata tab. Editable property types are determined by coded-in support; optional editablePropertyTypes
142
+ * prop can restrict or extend. Dialog size is fixed to large.
143
+ *
144
+ * @event confirm Emitted when the user confirms. Payload: formData object.
145
+ * @event close Emitted when the dialog should be closed.
146
+ */
147
+ export default {
148
+ name: 'CnAdvancedFormDialog',
149
+
150
+ components: {
151
+ NcDialog,
152
+ NcButton,
153
+ NcNoteCard,
154
+ NcLoadingIcon,
155
+ Plus,
156
+ ContentSaveOutline,
157
+ BTabs,
158
+ BTab,
159
+ CnPropertiesTab,
160
+ CnMetadataTab,
161
+ CnDataTab,
162
+ },
163
+
164
+ props: {
165
+ schema: { type: Object, default: null },
166
+ item: { type: Object, default: null },
167
+ dialogTitle: { type: String, default: '' },
168
+ nameField: { type: String, default: 'title' },
169
+ successText: { type: String, default: '' },
170
+ cancelLabel: { type: String, default: 'Cancel' },
171
+ closeLabel: { type: String, default: 'Close' },
172
+ confirmLabel: { type: String, default: '' },
173
+ excludeFields: { type: Array, default: () => [] },
174
+ includeFields: { type: Array, default: null },
175
+ fieldOverrides: { type: Object, default: () => ({}) },
176
+ showPropertiesTable: { type: Boolean, default: true },
177
+ showJsonTab: { type: Boolean, default: true },
178
+ showMetadataTab: { type: Boolean, default: null },
179
+ editablePropertyTypes: { type: Array, default: null },
180
+ validationDisplay: { type: String, default: 'indicator', validator: (v) => ['indicator', 'none'].includes(v) },
181
+ jsonEditorDark: { type: Boolean, default: false },
182
+ },
183
+
184
+ data() {
185
+ return {
186
+ formData: {},
187
+ jsonData: '',
188
+ activeTab: 0,
189
+ selectedProperty: null,
190
+ errors: {},
191
+ loading: false,
192
+ result: null,
193
+ closeTimeout: null,
194
+ isInternalUpdate: false,
195
+ }
196
+ },
197
+
198
+ computed: {
199
+ isCreateMode() {
200
+ return !this.item
201
+ },
202
+
203
+ schemaTitle() {
204
+ return (this.schema && this.schema.title) || 'Item'
205
+ },
206
+
207
+ currentSchema() {
208
+ return this.schema
209
+ },
210
+
211
+ resolvedTitle() {
212
+ if (this.dialogTitle) return this.dialogTitle
213
+ return this.isCreateMode
214
+ ? `Create ${this.schemaTitle}`
215
+ : `Edit ${this.schemaTitle}`
216
+ },
217
+
218
+ resolvedConfirmLabel() {
219
+ if (this.confirmLabel) return this.confirmLabel
220
+ return this.isCreateMode ? 'Create' : 'Save'
221
+ },
222
+
223
+ resolvedSuccessText() {
224
+ if (this.successText) return this.successText
225
+ return `${this.schemaTitle} saved successfully.`
226
+ },
227
+
228
+ resolvedShowMetadataTab() {
229
+ if (this.showMetadataTab !== null) return this.showMetadataTab
230
+ return !!this.item
231
+ },
232
+
233
+ resolvedFields() {
234
+ return fieldsFromSchema(this.schema, {
235
+ exclude: this.excludeFields,
236
+ include: this.includeFields,
237
+ overrides: this.fieldOverrides,
238
+ includeReadOnly: true,
239
+ })
240
+ },
241
+
242
+ /** objectProperties exposed to the #form and #tab-properties slot consumers */
243
+ objectPropertiesForSlot() {
244
+ const schemaProps = this.schema?.properties || {}
245
+ const obj = this.item || {}
246
+ const exclude = this.excludeFields || []
247
+ const include = this.includeFields
248
+ const filterKey = (k) => {
249
+ if (k === '@self' || k === 'id') return false
250
+ if (exclude.includes(k)) return false
251
+ if (include && !include.includes(k)) return false
252
+ return true
253
+ }
254
+ const existing = Object.entries(obj).filter(([k]) => filterKey(k))
255
+ const missing = []
256
+ for (const [key, prop] of Object.entries(schemaProps)) {
257
+ if (!filterKey(key)) continue
258
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
259
+ let def
260
+ switch (prop.type) {
261
+ case 'string': def = prop.const ?? ''; break
262
+ case 'number':
263
+ case 'integer': def = 0; break
264
+ case 'boolean': def = false; break
265
+ case 'array': def = []; break
266
+ case 'object': def = {}; break
267
+ default: def = ''
268
+ }
269
+ missing.push([key, def])
270
+ }
271
+ }
272
+ return [...existing, ...missing]
273
+ },
274
+
275
+ dataTabIndex() {
276
+ let index = 0
277
+ if (this.showPropertiesTable) index++
278
+ if (this.resolvedShowMetadataTab) index++
279
+ return index
280
+ },
281
+
282
+ isDataTabActive() {
283
+ return this.showJsonTab && this.activeTab === this.dataTabIndex
284
+ },
285
+
286
+ editableTypes() {
287
+ if (this.editablePropertyTypes && this.editablePropertyTypes.length > 0) {
288
+ return this.editablePropertyTypes
289
+ }
290
+ return EDITABLE_SUPPORTED_TYPES
291
+ },
292
+ },
293
+
294
+ watch: {
295
+ item: {
296
+ immediate: true,
297
+ handler(newItem) {
298
+ this.initFormData(newItem)
299
+ },
300
+ },
301
+ jsonData(newVal) {
302
+ if (!this.isInternalUpdate && this.isValidJson(newVal)) {
303
+ this.updateFormFromJson()
304
+ }
305
+ },
306
+ formData: {
307
+ handler() {
308
+ if (!this.isInternalUpdate) {
309
+ this.updateJsonFromForm()
310
+ }
311
+ },
312
+ deep: true,
313
+ },
314
+ },
315
+
316
+ beforeDestroy() {
317
+ if (this.closeTimeout) {
318
+ clearTimeout(this.closeTimeout)
319
+ this.closeTimeout = null
320
+ }
321
+ },
322
+
323
+ methods: {
324
+ proceedFromRegisterSchemaStep() {
325
+ // Placeholder for slot consumers
326
+ },
327
+
328
+ initFormData(item) {
329
+ if (item) {
330
+ this.formData = JSON.parse(JSON.stringify(item))
331
+ } else {
332
+ const data = {}
333
+ for (const field of this.resolvedFields) {
334
+ if (field.default !== null && field.default !== undefined) {
335
+ data[field.key] = field.default
336
+ } else if (field.widget === 'checkbox') {
337
+ data[field.key] = false
338
+ } else if (field.widget === 'tags' || field.widget === 'multiselect') {
339
+ data[field.key] = []
340
+ } else {
341
+ data[field.key] = null
342
+ }
343
+ }
344
+ this.formData = data
345
+ }
346
+ this.jsonData = JSON.stringify(this.formData, null, 2)
347
+ this.errors = {}
348
+ this.selectedProperty = null
349
+ },
350
+
351
+ updateField(key, value) {
352
+ this.$set(this.formData, key, value)
353
+ if (this.errors[key]) this.$delete(this.errors, key)
354
+ },
355
+
356
+ onPropertyValueUpdate({ key, value }) {
357
+ this.$set(this.formData, key, value)
358
+ if (this.errors[key]) this.$delete(this.errors, key)
359
+ },
360
+
361
+ onRowClick(key, event) {
362
+ // Forwarded for #tab-properties slot consumers — the sub-component handles it internally
363
+ },
364
+
365
+ /**
366
+ * Proxy for slot consumers: exposes isPropertyEditable from the tab sub-component.
367
+ * @param {string} key - Property key
368
+ * @param {*} value - Current property value
369
+ */
370
+ isPropertyEditable(key, value) {
371
+ const tab = this.$refs.propertiesTab
372
+ if (tab) return tab.isPropertyEditable(key, value)
373
+ return true
374
+ },
375
+
376
+ /**
377
+ * Proxy for slot consumers.
378
+ * @param {string} key - Property key
379
+ */
380
+ getPropertyDisplayName(key) {
381
+ const tab = this.$refs.propertiesTab
382
+ if (tab) return tab.getPropertyDisplayName(key)
383
+ return key
384
+ },
385
+
386
+ /**
387
+ * Proxy for slot consumers.
388
+ * @param {string} key - Property key
389
+ * @param {*} value - Current property value
390
+ */
391
+ getPropertyValidationClass(key, value) {
392
+ const tab = this.$refs.propertiesTab
393
+ if (tab) return tab.getPropertyValidationClass(key, value)
394
+ return ''
395
+ },
396
+
397
+ updateFormFromJson() {
398
+ if (this.isInternalUpdate) return
399
+ try {
400
+ this.isInternalUpdate = true
401
+ this.formData = JSON.parse(this.jsonData)
402
+ } catch {
403
+ // Keep previous formData
404
+ } finally {
405
+ this.$nextTick(() => { this.isInternalUpdate = false })
406
+ }
407
+ },
408
+
409
+ updateJsonFromForm() {
410
+ if (this.isInternalUpdate) return
411
+ try {
412
+ this.isInternalUpdate = true
413
+ this.jsonData = JSON.stringify(this.formData, null, 2)
414
+ } catch {
415
+ // Ignore
416
+ } finally {
417
+ this.$nextTick(() => { this.isInternalUpdate = false })
418
+ }
419
+ },
420
+
421
+ updateJsonFromExternal(newJson) {
422
+ this.jsonData = newJson
423
+ if (this.isValidJson(newJson)) this.updateFormFromJson()
424
+ },
425
+
426
+ isValidJson(str) {
427
+ if (!str || !str.trim()) return false
428
+ try {
429
+ JSON.parse(str)
430
+ return true
431
+ } catch {
432
+ return false
433
+ }
434
+ },
435
+
436
+ formatJSON() {
437
+ try {
438
+ if (this.jsonData) {
439
+ const parsed = JSON.parse(this.jsonData)
440
+ this.jsonData = JSON.stringify(parsed, null, 2)
441
+ if (!this.isInternalUpdate) {
442
+ this.isInternalUpdate = true
443
+ this.formData = parsed
444
+ this.$nextTick(() => { this.isInternalUpdate = false })
445
+ }
446
+ }
447
+ } catch {
448
+ // Keep invalid JSON as-is
449
+ }
450
+ },
451
+
452
+ onFormatResult(parsed) {
453
+ if (!this.isInternalUpdate) {
454
+ this.isInternalUpdate = true
455
+ this.formData = parsed
456
+ this.$nextTick(() => { this.isInternalUpdate = false })
457
+ }
458
+ },
459
+
460
+ validate() {
461
+ const newErrors = {}
462
+ for (const field of this.resolvedFields) {
463
+ const value = this.formData[field.key]
464
+ if (field.required && (value == null || value === '')) {
465
+ newErrors[field.key] = `${field.label} is required.`
466
+ }
467
+ }
468
+ this.errors = newErrors
469
+ return Object.keys(newErrors).length === 0
470
+ },
471
+
472
+ executeConfirm() {
473
+ if (!this.validate()) return
474
+ if (this.isDataTabActive && !this.isValidJson(this.jsonData)) return
475
+ this.$emit('confirm', JSON.parse(JSON.stringify(this.formData)))
476
+ },
477
+
478
+ setResult(resultData) {
479
+ this.loading = false
480
+ this.result = resultData
481
+ if (resultData?.success) {
482
+ this.closeTimeout = setTimeout(() => this.$emit('close'), 2000)
483
+ }
484
+ },
485
+ },
486
+ }
487
+ </script>
488
+
489
+ <style scoped>
490
+ .cn-advanced-form-dialog__form {
491
+ display: flex;
492
+ flex-direction: column;
493
+ gap: 8px;
494
+ }
495
+
496
+ .cn-advanced-form-dialog__tabs {
497
+ display: flex;
498
+ flex-direction: column;
499
+ gap: 12px;
500
+ }
501
+
502
+ /* Bootstrap-Vue tab styling to match ViewObject */
503
+ .tabContainer {
504
+ margin-top: 20px;
505
+ }
506
+
507
+ .tabContainer > * ul > li {
508
+ display: flex;
509
+ flex: 1;
510
+ }
511
+
512
+ .tabContainer > * ul > li:hover {
513
+ background-color: var(--color-background-hover);
514
+ }
515
+
516
+ .tabContainer > * ul > li > a {
517
+ flex: 1;
518
+ text-align: center;
519
+ }
520
+
521
+ .tabContainer > * ul > li > .active {
522
+ background: transparent !important;
523
+ color: var(--color-main-text) !important;
524
+ border-bottom: var(--default-grid-baseline) solid var(--color-primary-element) !important;
525
+ }
526
+
527
+ .tabContainer > * ul[role="tablist"] {
528
+ display: flex;
529
+ margin: 10px 8px 0 8px;
530
+ justify-content: space-between;
531
+ border-bottom: 1px solid var(--color-border);
532
+ }
533
+
534
+ .tabContainer > * ul[role="tablist"] > * a[role="tab"] {
535
+ padding-inline-start: 10px;
536
+ padding-inline-end: 10px;
537
+ padding-block-start: 10px;
538
+ padding-block-end: 10px;
539
+ }
540
+
541
+ .tabContainer > * div[role="tabpanel"] {
542
+ margin-block-start: var(--OR-margin-10);
543
+ }
544
+
545
+ :deep(.nav-tabs) {
546
+ border-bottom: 1px solid var(--color-border);
547
+ margin-bottom: 15px;
548
+ display: flex;
549
+ }
550
+
551
+ :deep(.nav-tabs .nav-item) {
552
+ display: flex;
553
+ flex: 1;
554
+ }
555
+
556
+ :deep(.nav-tabs .nav-link) {
557
+ flex: 1;
558
+ text-align: center;
559
+ border: none;
560
+ border-bottom: 2px solid transparent;
561
+ color: var(--color-text-maxcontrast);
562
+ padding: 8px 16px;
563
+ }
564
+
565
+ :deep(.nav-tabs .nav-link.active) {
566
+ color: var(--color-main-text);
567
+ border-bottom: 2px solid var(--color-primary);
568
+ background-color: transparent;
569
+ }
570
+
571
+ :deep(.nav-tabs .nav-link:hover) {
572
+ border-bottom: 2px solid var(--color-border);
573
+ }
574
+
575
+ :deep(.tab-content) {
576
+ padding: 16px;
577
+ background-color: var(--color-main-background);
578
+ }
579
+ </style>