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

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