@conduction/nextcloud-vue 0.1.0-beta.1 → 0.1.0-beta.10

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