@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,286 @@
1
+ import { ref, computed, onMounted } from 'vue'
2
+ import axios from '@nextcloud/axios'
3
+ import { generateOcsUrl } from '@nextcloud/router'
4
+ import { filterWidgetsByVisibility } from '../utils/widgetVisibility.js'
5
+
6
+ /**
7
+ * Composable for managing dashboard view state.
8
+ *
9
+ * Handles widget definition loading (including NC Dashboard API widgets),
10
+ * layout management, edit mode, and role-based widget visibility filtering.
11
+ *
12
+ * Widgets can specify a `visibility` property to control which users see them:
13
+ * ```js
14
+ * {
15
+ * id: 'kcc-search',
16
+ * type: 'custom',
17
+ * title: 'Quick Search',
18
+ * visibility: {
19
+ * users: ['admin'], // specific user IDs (optional)
20
+ * groups: ['KCC', 'Admins'], // Nextcloud group names (optional)
21
+ * }
22
+ * }
23
+ * ```
24
+ * If `visibility` is not set or both arrays are empty, the widget is visible to everyone.
25
+ *
26
+ * @param {object} [options] Configuration options
27
+ * @param {Array} [options.widgets] Static widget definitions from the app
28
+ * @param {Array} [options.defaultLayout] Default layout if no saved layout exists
29
+ * @param {Function} [options.loadLayout] Async function that returns saved layout array, or null
30
+ * @param {Function} [options.saveLayout] Async function that persists layout: (layout) => Promise
31
+ * @param {boolean} [options.includeNcWidgets] Whether to also load NC Dashboard API widgets
32
+ * @param {number} [options.columns] Grid columns
33
+ * @return {object} Reactive state and methods for CnDashboardPage
34
+ *
35
+ * @example Basic usage with static widgets
36
+ * const { widgets, layout, loading, onLayoutChange } = useDashboardView({
37
+ * widgets: [
38
+ * { id: 'kpis', title: 'KPIs', type: 'custom' },
39
+ * { id: 'chart', title: 'Status Chart', type: 'custom' },
40
+ * ],
41
+ * defaultLayout: [
42
+ * { id: 1, widgetId: 'kpis', gridX: 0, gridY: 0, gridWidth: 12, gridHeight: 2 },
43
+ * { id: 2, widgetId: 'chart', gridX: 0, gridY: 2, gridWidth: 6, gridHeight: 4 },
44
+ * ],
45
+ * })
46
+ *
47
+ * @example With persistence and NC widgets
48
+ * const dashboard = useDashboardView({
49
+ * widgets: myWidgets,
50
+ * defaultLayout: defaultLayout,
51
+ * loadLayout: () => fetch('/api/dashboard-layout').then(r => r.json()),
52
+ * saveLayout: (layout) => fetch('/api/dashboard-layout', { method: 'PUT', body: JSON.stringify(layout) }),
53
+ * includeNcWidgets: true,
54
+ * })
55
+ *
56
+ * @example With role-based visibility
57
+ * const dashboard = useDashboardView({
58
+ * widgets: [
59
+ * { id: 'admin-panel', title: 'Admin Panel', type: 'custom', visibility: { groups: ['admin'] } },
60
+ * { id: 'kcc-search', title: 'KCC Search', type: 'custom', visibility: { groups: ['KCC'] } },
61
+ * { id: 'public-info', title: 'Info', type: 'custom' }, // visible to everyone
62
+ * ],
63
+ * defaultLayout: [...],
64
+ * })
65
+ */
66
+ export function useDashboardView(options = {}) {
67
+ const opts = {
68
+ widgets: [],
69
+ defaultLayout: [],
70
+ loadLayout: null,
71
+ saveLayout: null,
72
+ includeNcWidgets: false,
73
+ columns: 12,
74
+ ...options,
75
+ }
76
+
77
+ // ── State ────────────────────────────────────────────────────────────
78
+ const appWidgets = ref(opts.widgets)
79
+ const ncWidgets = ref([])
80
+ const visibleAppWidgets = ref([])
81
+ const visibleNcWidgets = ref([])
82
+ const layout = ref([])
83
+ const loading = ref(false)
84
+ const saving = ref(false)
85
+ const isEditing = ref(false)
86
+
87
+ // ── Computed ─────────────────────────────────────────────────────────
88
+
89
+ /** All available widgets (app + NC Dashboard API), filtered by visibility */
90
+ const widgets = computed(() => {
91
+ return [...visibleAppWidgets.value, ...visibleNcWidgets.value]
92
+ })
93
+
94
+ /** Widget IDs currently on the dashboard */
95
+ const activeWidgetIds = computed(() => {
96
+ return layout.value.map(item => item.widgetId)
97
+ })
98
+
99
+ /** Widgets not yet placed on the dashboard */
100
+ const availableWidgets = computed(() => {
101
+ return widgets.value.filter(w => !activeWidgetIds.value.includes(w.id))
102
+ })
103
+
104
+ // ── Methods ──────────────────────────────────────────────────────────
105
+
106
+ /**
107
+ * Apply visibility filtering to the current widget sets and update
108
+ * the layout to remove items whose widgets are no longer visible.
109
+ */
110
+ async function applyVisibilityFilter() {
111
+ visibleAppWidgets.value = await filterWidgetsByVisibility(appWidgets.value)
112
+ visibleNcWidgets.value = await filterWidgetsByVisibility(ncWidgets.value)
113
+
114
+ // Remove layout items that reference widgets the user cannot see
115
+ const visibleIds = new Set(widgets.value.map(w => w.id))
116
+ const filteredLayout = layout.value.filter(item => visibleIds.has(item.widgetId))
117
+ if (filteredLayout.length !== layout.value.length) {
118
+ layout.value = filteredLayout
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Load NC Dashboard API widgets from the OCS endpoint.
124
+ */
125
+ async function loadNcWidgets() {
126
+ try {
127
+ const url = generateOcsUrl('/apps/dashboard/api/v1/widgets')
128
+ const response = await axios.get(url)
129
+ const data = response.data?.ocs?.data || {}
130
+
131
+ ncWidgets.value = Object.values(data).map(w => ({
132
+ id: w.id,
133
+ title: w.title,
134
+ iconClass: w.icon_class,
135
+ iconUrl: w.icon_url,
136
+ widgetUrl: w.widget_url,
137
+ itemApiVersions: w.item_api_versions || [],
138
+ itemIconsRound: w.item_icons_round || false,
139
+ reloadInterval: w.reload_interval || 0,
140
+ buttons: w.buttons || [],
141
+ type: 'nc-widget',
142
+ }))
143
+ } catch (error) {
144
+ console.error('[useDashboardView] Failed to load NC widgets:', error)
145
+ ncWidgets.value = []
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Initialize the dashboard: load layout and optionally NC widgets.
151
+ */
152
+ async function init() {
153
+ loading.value = true
154
+ try {
155
+ const tasks = []
156
+
157
+ if (opts.includeNcWidgets) {
158
+ tasks.push(loadNcWidgets())
159
+ }
160
+
161
+ if (opts.loadLayout) {
162
+ tasks.push(
163
+ opts.loadLayout().then(saved => {
164
+ if (saved && saved.length > 0) {
165
+ layout.value = saved
166
+ } else {
167
+ layout.value = [...opts.defaultLayout]
168
+ }
169
+ }),
170
+ )
171
+ } else {
172
+ layout.value = [...opts.defaultLayout]
173
+ }
174
+
175
+ await Promise.all(tasks)
176
+
177
+ // Apply visibility filtering after all data is loaded
178
+ await applyVisibilityFilter()
179
+ } catch (error) {
180
+ console.error('[useDashboardView] Init failed:', error)
181
+ layout.value = [...opts.defaultLayout]
182
+ } finally {
183
+ loading.value = false
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Handle layout change from the grid. Persists if saveLayout is provided.
189
+ *
190
+ * @param {Array} newLayout Updated layout array
191
+ */
192
+ async function onLayoutChange(newLayout) {
193
+ layout.value = newLayout
194
+
195
+ if (opts.saveLayout) {
196
+ saving.value = true
197
+ try {
198
+ await opts.saveLayout(newLayout)
199
+ } catch (error) {
200
+ console.error('[useDashboardView] Failed to save layout:', error)
201
+ } finally {
202
+ saving.value = false
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Add a widget to the dashboard at the next available position.
209
+ *
210
+ * @param {string} widgetId Widget ID to add
211
+ * @param {object} [position] Override position { gridX, gridY, gridWidth, gridHeight }
212
+ */
213
+ function addWidget(widgetId, position = {}) {
214
+ const maxId = layout.value.reduce((max, item) => {
215
+ const num = typeof item.id === 'number' ? item.id : 0
216
+ return num > max ? num : max
217
+ }, 0)
218
+
219
+ const maxY = layout.value.reduce((max, item) => {
220
+ const bottom = (item.gridY || 0) + (item.gridHeight || 3)
221
+ return bottom > max ? bottom : max
222
+ }, 0)
223
+
224
+ const newItem = {
225
+ id: maxId + 1,
226
+ widgetId,
227
+ gridX: position.gridX ?? 0,
228
+ gridY: position.gridY ?? maxY,
229
+ gridWidth: position.gridWidth ?? 6,
230
+ gridHeight: position.gridHeight ?? 3,
231
+ }
232
+
233
+ const newLayout = [...layout.value, newItem]
234
+ onLayoutChange(newLayout)
235
+ }
236
+
237
+ /**
238
+ * Remove a widget from the dashboard by layout item ID.
239
+ *
240
+ * @param {string|number} itemId Layout item ID to remove
241
+ */
242
+ function removeWidget(itemId) {
243
+ const newLayout = layout.value.filter(item => item.id !== itemId)
244
+ onLayoutChange(newLayout)
245
+ }
246
+
247
+ /**
248
+ * Update app widget definitions (e.g., when data changes).
249
+ * Re-applies visibility filtering after update.
250
+ *
251
+ * @param {Array} newWidgets Updated widget definitions
252
+ */
253
+ async function setWidgets(newWidgets) {
254
+ appWidgets.value = newWidgets
255
+ await applyVisibilityFilter()
256
+ }
257
+
258
+ // ── Lifecycle ────────────────────────────────────────────────────────
259
+
260
+ onMounted(async () => {
261
+ await init()
262
+ })
263
+
264
+ // ── Return ───────────────────────────────────────────────────────────
265
+
266
+ return {
267
+ // State
268
+ widgets,
269
+ layout,
270
+ loading,
271
+ saving,
272
+ isEditing,
273
+
274
+ // Derived
275
+ activeWidgetIds,
276
+ availableWidgets,
277
+ ncWidgets,
278
+
279
+ // Methods
280
+ onLayoutChange,
281
+ addWidget,
282
+ removeWidget,
283
+ setWidgets,
284
+ init,
285
+ }
286
+ }