@conduction/nextcloud-vue 0.1.0-beta.6 → 0.1.0-beta.7

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 (82) hide show
  1. package/dist/nextcloud-vue.cjs.js +13606 -1918
  2. package/dist/nextcloud-vue.cjs.js.map +1 -1
  3. package/dist/nextcloud-vue.css +1238 -270
  4. package/dist/nextcloud-vue.esm.js +13548 -1880
  5. package/dist/nextcloud-vue.esm.js.map +1 -1
  6. package/package.json +9 -4
  7. package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
  8. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +1 -11
  9. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +5 -1
  10. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +1 -1
  11. package/src/components/CnCard/CnCard.vue +415 -0
  12. package/src/components/CnCard/index.js +1 -0
  13. package/src/components/CnCardGrid/CnCardGrid.vue +20 -20
  14. package/src/components/CnChartWidget/CnChartWidget.vue +3 -1
  15. package/src/components/CnCopyDialog/CnCopyDialog.vue +7 -1
  16. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +4 -0
  17. package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -0
  18. package/src/components/CnDataTable/CnDataTable.vue +6 -2
  19. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +7 -1
  20. package/src/components/CnDetailCard/CnDetailCard.vue +12 -1
  21. package/src/components/CnDetailGrid/CnDetailGrid.vue +254 -0
  22. package/src/components/CnDetailGrid/index.js +1 -0
  23. package/src/components/CnDetailPage/CnDetailPage.vue +157 -11
  24. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +3 -1
  25. package/src/components/CnFormDialog/CnFormDialog.vue +934 -920
  26. package/src/components/CnIcon/CnIcon.vue +1 -1
  27. package/src/components/CnIndexPage/CnIndexPage.vue +51 -9
  28. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +37 -9
  29. package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
  30. package/src/components/CnInfoWidget/index.js +1 -0
  31. package/src/components/CnJsonViewer/CnJsonViewer.vue +283 -0
  32. package/src/components/CnJsonViewer/index.js +1 -0
  33. package/src/components/CnKpiGrid/CnKpiGrid.vue +5 -1
  34. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +7 -1
  35. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +7 -1
  36. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +1 -1
  37. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +1 -1
  38. package/src/components/CnObjectCard/CnObjectCard.vue +1 -1
  39. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +368 -0
  40. package/src/components/CnObjectSidebar/CnFilesTab.vue +286 -0
  41. package/src/components/CnObjectSidebar/CnNotesTab.vue +249 -0
  42. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +45 -668
  43. package/src/components/CnObjectSidebar/CnTagsTab.vue +258 -0
  44. package/src/components/CnObjectSidebar/CnTasksTab.vue +482 -0
  45. package/src/components/CnObjectSidebar/index.js +5 -0
  46. package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
  47. package/src/components/CnProgressBar/index.js +1 -0
  48. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +1 -1
  49. package/src/components/CnStatsBlock/CnStatsBlock.vue +27 -11
  50. package/src/components/CnStatsPanel/CnStatsPanel.vue +320 -0
  51. package/src/components/CnStatsPanel/index.js +1 -0
  52. package/src/components/CnStatusBadge/CnStatusBadge.vue +15 -2
  53. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +5 -1
  54. package/src/components/CnTableWidget/CnTableWidget.vue +332 -0
  55. package/src/components/CnTableWidget/index.js +1 -0
  56. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +36 -1
  57. package/src/components/index.js +11 -0
  58. package/src/composables/useDashboardView.js +58 -12
  59. package/src/composables/useDetailView.js +3 -2
  60. package/src/composables/useListView.js +7 -6
  61. package/src/composables/useSubResource.js +3 -3
  62. package/src/css/badge.css +32 -0
  63. package/src/css/card.css +1 -0
  64. package/src/css/detail-page.css +74 -7
  65. package/src/index.js +16 -0
  66. package/src/mixins/gridLayout.js +118 -0
  67. package/src/store/createCrudStore.js +360 -0
  68. package/src/store/createSubResourcePlugin.js +5 -15
  69. package/src/store/index.js +1 -0
  70. package/src/store/plugins/auditTrails.js +346 -6
  71. package/src/store/plugins/lifecycle.js +4 -4
  72. package/src/store/plugins/registerMapping.js +18 -8
  73. package/src/store/plugins/relations.js +1 -1
  74. package/src/store/plugins/search.js +21 -8
  75. package/src/store/useObjectStore.js +30 -36
  76. package/src/utils/getTheme.js +9 -0
  77. package/src/utils/headers.js +13 -3
  78. package/src/utils/index.js +1 -0
  79. package/src/utils/schema.js +3 -3
  80. package/src/utils/widgetVisibility.js +162 -0
  81. package/src/components/CnObjectCard/eslint-setup.md +0 -235
  82. package/src/components/CnObjectCard/package.json-or.json +0 -132
@@ -91,6 +91,11 @@ export default {
91
91
  type: String,
92
92
  default: 'title',
93
93
  },
94
+ /** Optional function to format the item name. Receives the item, returns a string. Overrides nameField when provided. */
95
+ nameFormatter: {
96
+ type: Function,
97
+ default: null,
98
+ },
94
99
  /** Dialog title */
95
100
  dialogTitle: {
96
101
  type: String,
@@ -121,6 +126,7 @@ export default {
121
126
 
122
127
  computed: {
123
128
  itemName() {
129
+ if (this.nameFormatter) return this.nameFormatter(this.item)
124
130
  return this.item[this.nameField] || this.item.name || this.item.title || this.item.id
125
131
  },
126
132
  resolvedWarningText() {
@@ -147,7 +153,7 @@ export default {
147
153
  * Set the result of the delete operation. Call this from the parent
148
154
  * after the API call completes.
149
155
  *
150
- * @param {{ success?: boolean, error?: string }} resultData
156
+ * @param {{ success?: boolean, error?: string }} resultData - Result data to pass to the dialog
151
157
  * @public
152
158
  */
153
159
  setResult(resultData) {
@@ -38,7 +38,7 @@
38
38
  </div>
39
39
 
40
40
  <!-- Content -->
41
- <div v-show="!isCollapsed" class="cn-detail-card__content">
41
+ <div v-show="!isCollapsed" class="cn-detail-card__content" :class="{ 'cn-detail-card__content--flush': flush }">
42
42
  <slot />
43
43
  </div>
44
44
 
@@ -101,6 +101,13 @@ export default {
101
101
  type: Boolean,
102
102
  default: false,
103
103
  },
104
+ /**
105
+ * Remove content padding — allows tables and lists to go edge-to-edge.
106
+ */
107
+ flush: {
108
+ type: Boolean,
109
+ default: false,
110
+ },
104
111
  },
105
112
 
106
113
  data() {
@@ -207,6 +214,10 @@ export default {
207
214
  padding: 16px;
208
215
  }
209
216
 
217
+ .cn-detail-card__content--flush {
218
+ padding: 0;
219
+ }
220
+
210
221
  .cn-detail-card__footer {
211
222
  padding: 8px 16px;
212
223
  border-top: 1px solid var(--color-border);
@@ -0,0 +1,254 @@
1
+ <!--
2
+ CnDetailGrid — Data-driven label-value grid for detail/info sections.
3
+
4
+ Supports two layout modes:
5
+ - grid (default): Responsive card grid with label stacked above value
6
+ - horizontal: Vertical list of rows with label on left, value on right
7
+
8
+ Items can be data-driven via the `items` prop, or customized per-item
9
+ via named scoped slots (#item-{index}, #label-{index}, #item-actions-{index}).
10
+ -->
11
+ <template>
12
+ <div
13
+ class="cn-detail-grid"
14
+ :class="rootClasses"
15
+ :style="rootStyles">
16
+ <!-- Empty state -->
17
+ <div v-if="!items.length && !$scopedSlots.default" class="cn-detail-grid__empty">
18
+ <slot name="empty">
19
+ {{ emptyLabel }}
20
+ </slot>
21
+ </div>
22
+
23
+ <!-- Data-driven items -->
24
+ <div
25
+ v-for="(item, index) in items"
26
+ :key="index"
27
+ class="cn-detail-grid__item"
28
+ :class="itemClasses">
29
+ <!-- Label -->
30
+ <div class="cn-detail-grid__label">
31
+ <slot :name="'label-' + index" :item="item" :index="index">
32
+ {{ item.label }}
33
+ </slot>
34
+ </div>
35
+
36
+ <!-- Value -->
37
+ <div class="cn-detail-grid__value">
38
+ <slot :name="'item-' + index" :item="item" :index="index">
39
+ {{ item.value !== undefined && item.value !== null ? item.value : '-' }}
40
+ </slot>
41
+ </div>
42
+
43
+ <!-- Optional per-item actions -->
44
+ <div v-if="$scopedSlots['item-actions-' + index]" class="cn-detail-grid__actions">
45
+ <slot :name="'item-actions-' + index" :item="item" :index="index" />
46
+ </div>
47
+ </div>
48
+
49
+ <!-- Append slot for manual items -->
50
+ <slot />
51
+ </div>
52
+ </template>
53
+
54
+ <script>
55
+ /**
56
+ * CnDetailGrid — Data-driven label-value grid for detail/info sections.
57
+ *
58
+ * @example Simple data-driven grid
59
+ * <CnDetailGrid :items="[
60
+ * { label: 'ID', value: '12345' },
61
+ * { label: 'Status', value: 'Active' },
62
+ * { label: 'Created', value: '2024-01-15' },
63
+ * ]" />
64
+ *
65
+ * @example Grid with custom slot content
66
+ * <CnDetailGrid :items="[
67
+ * { label: 'ID', value: item.id },
68
+ * { label: 'Action' },
69
+ * ]">
70
+ * <template #item-1>
71
+ * <CnStatusBadge :label="item.action" />
72
+ * </template>
73
+ * </CnDetailGrid>
74
+ *
75
+ * @example Horizontal row layout
76
+ * <CnDetailGrid layout="horizontal" :items="fields" />
77
+ */
78
+ export default {
79
+ name: 'CnDetailGrid',
80
+
81
+ props: {
82
+ /**
83
+ * Array of detail items to render.
84
+ * @type {Array<{ label: string, value?: string|number }>}
85
+ */
86
+ items: {
87
+ type: Array,
88
+ default: () => [],
89
+ },
90
+ /**
91
+ * Layout mode.
92
+ * - 'grid': Responsive card grid, label stacked above value
93
+ * - 'horizontal': Vertical list of rows, label on left, value on right
94
+ */
95
+ layout: {
96
+ type: String,
97
+ default: 'grid',
98
+ validator: (v) => ['grid', 'horizontal'].includes(v),
99
+ },
100
+ /**
101
+ * Number of fixed grid columns. Set to 0 (default) for responsive auto-fit.
102
+ * Only applies to layout="grid".
103
+ */
104
+ columns: {
105
+ type: Number,
106
+ default: 0,
107
+ },
108
+ /**
109
+ * Minimum width (px) for auto-fit grid items.
110
+ * Only applies when columns is 0 and layout is 'grid'.
111
+ */
112
+ minItemWidth: {
113
+ type: Number,
114
+ default: 250,
115
+ },
116
+ /**
117
+ * Minimum width (px) for labels in horizontal mode.
118
+ */
119
+ labelWidth: {
120
+ type: Number,
121
+ default: 150,
122
+ },
123
+ /**
124
+ * Whether to show the left accent border on items.
125
+ */
126
+ accent: {
127
+ type: Boolean,
128
+ default: true,
129
+ },
130
+ /**
131
+ * Text shown when the items array is empty.
132
+ */
133
+ emptyLabel: {
134
+ type: String,
135
+ default: 'No details available',
136
+ },
137
+ },
138
+
139
+ computed: {
140
+ rootClasses() {
141
+ return {
142
+ 'cn-detail-grid--grid': this.layout === 'grid',
143
+ 'cn-detail-grid--horizontal': this.layout === 'horizontal',
144
+ 'cn-detail-grid--accent': this.accent,
145
+ }
146
+ },
147
+ rootStyles() {
148
+ if (this.layout === 'grid') {
149
+ if (this.columns > 0) {
150
+ return { 'grid-template-columns': `repeat(${this.columns}, 1fr)` }
151
+ }
152
+ return { 'grid-template-columns': `repeat(auto-fit, minmax(${this.minItemWidth}px, 1fr))` }
153
+ }
154
+ if (this.layout === 'horizontal') {
155
+ return { '--cn-detail-grid-label-width': this.labelWidth + 'px' }
156
+ }
157
+ return {}
158
+ },
159
+ itemClasses() {
160
+ return {
161
+ 'cn-detail-grid__item--horizontal': this.layout === 'horizontal',
162
+ }
163
+ },
164
+ },
165
+ }
166
+ </script>
167
+
168
+ <style scoped>
169
+ /* ===== Grid layout (default) ===== */
170
+ .cn-detail-grid--grid {
171
+ display: grid;
172
+ gap: calc(4 * var(--default-grid-baseline, 4px));
173
+ }
174
+
175
+ /* ===== Horizontal layout ===== */
176
+ .cn-detail-grid--horizontal {
177
+ display: flex;
178
+ flex-direction: column;
179
+ gap: calc(3 * var(--default-grid-baseline, 4px));
180
+ }
181
+
182
+ /* ===== Item (card style) ===== */
183
+ .cn-detail-grid__item {
184
+ display: flex;
185
+ flex-direction: column;
186
+ gap: var(--default-grid-baseline, 4px);
187
+ padding: calc(2 * var(--default-grid-baseline, 4px)) calc(3 * var(--default-grid-baseline, 4px));
188
+ background: var(--color-background-hover);
189
+ border-radius: 0 var(--border-radius) var(--border-radius) 0;
190
+ }
191
+
192
+ /* Accent border */
193
+ .cn-detail-grid--accent .cn-detail-grid__item {
194
+ border-left: 3px solid var(--color-primary-element);
195
+ }
196
+
197
+ /* Horizontal item: row direction */
198
+ .cn-detail-grid__item--horizontal {
199
+ flex-direction: row;
200
+ align-items: center;
201
+ gap: calc(4 * var(--default-grid-baseline, 4px));
202
+ border-radius: var(--border-radius);
203
+ }
204
+
205
+ /* ===== Label ===== */
206
+ .cn-detail-grid__label {
207
+ font-size: 0.85em;
208
+ color: var(--color-text-maxcontrast);
209
+ font-weight: 500;
210
+ }
211
+
212
+ .cn-detail-grid--horizontal .cn-detail-grid__label {
213
+ min-width: var(--cn-detail-grid-label-width, 150px);
214
+ flex-shrink: 0;
215
+ }
216
+
217
+ /* ===== Value ===== */
218
+ .cn-detail-grid__value {
219
+ font-size: 1em;
220
+ color: var(--color-main-text);
221
+ word-break: break-word;
222
+ margin: 0.5rem;
223
+ }
224
+
225
+ .cn-detail-grid--horizontal .cn-detail-grid__value {
226
+ flex: 1;
227
+ }
228
+
229
+ /* ===== Actions ===== */
230
+ .cn-detail-grid__actions {
231
+ flex-shrink: 0;
232
+ display: flex;
233
+ align-items: center;
234
+ }
235
+
236
+ /* ===== Empty state ===== */
237
+ .cn-detail-grid__empty {
238
+ color: var(--color-text-maxcontrast);
239
+ font-style: italic;
240
+ padding: calc(2 * var(--default-grid-baseline, 4px));
241
+ }
242
+
243
+ /* ===== Responsive ===== */
244
+ @media (max-width: 600px) {
245
+ .cn-detail-grid--grid {
246
+ grid-template-columns: 1fr !important;
247
+ }
248
+
249
+ .cn-detail-grid__item--horizontal {
250
+ flex-direction: column;
251
+ align-items: flex-start;
252
+ }
253
+ }
254
+ </style>
@@ -0,0 +1 @@
1
+ export { default as CnDetailGrid } from './CnDetailGrid.vue'
@@ -1,6 +1,18 @@
1
1
  <!--
2
2
  CnDetailPage — Generic detail/overview page.
3
3
 
4
+ The detail page equivalent of CnDashboardPage. Assembles a complete entity detail
5
+ view from card-based sections, matching the dashboard visual style (rounded cards
6
+ with headers). Uses a fixed declarative layout (no drag-and-drop).
7
+
8
+ Features:
9
+ - Header with back button, title, subtitle, and action buttons
10
+ - Card-based content area (via default slot with CnDetailCard components)
11
+ - Optional 12-column CSS grid layout mode (via layout + widgets props)
12
+ - Optional right sidebar (CnObjectSidebar) for files, notes, tags, tasks, audit trail
13
+ - Loading and error states
14
+ - Edit mode toggle
15
+
4
16
  A simpler alternative to CnIndexPage for detail, stats, and overview pages.
5
17
  No multi-object table, no CRUD dialogs — just a clean layout with:
6
18
  - Header (title, description, icon, action buttons)
@@ -76,6 +88,27 @@
76
88
 
77
89
  <!-- Main content -->
78
90
  <div v-else class="cn-detail-page__body">
91
+ <!-- Grid layout mode -->
92
+ <div v-if="hasGridLayout" class="cn-detail-page__content cn-detail-page__content--grid">
93
+ <section
94
+ v-for="item in sortedLayout"
95
+ :key="item.id"
96
+ :style="widgetGridStyle(item)"
97
+ class="cn-detail-page__grid-item"
98
+ :aria-labelledby="item.showTitle !== false && findWidget(item) ? `widget-title-${item.id}` : undefined">
99
+ <h3
100
+ v-if="item.showTitle !== false && findWidget(item)"
101
+ :id="`widget-title-${item.id}`"
102
+ class="cn-detail-page__widget-title">
103
+ {{ findWidget(item).title }}
104
+ </h3>
105
+ <slot
106
+ :name="`widget-${item.widgetId}`"
107
+ :item="item"
108
+ :widget="findWidget(item)" />
109
+ </section>
110
+ </div>
111
+
79
112
  <!-- Statistics table -->
80
113
  <div v-if="hasStats" class="cn-detail-page__stats">
81
114
  <slot name="stats-header">
@@ -103,20 +136,23 @@
103
136
  </table>
104
137
  </div>
105
138
 
106
- <!-- Default content -->
107
- <div class="cn-detail-page__content">
108
- <slot />
109
- </div>
139
+ <!-- Default vertical stacking mode -->
140
+ <div v-else class="cn-detail-page__content">
141
+ <!-- Default content -->
142
+ <div class="cn-detail-page__content">
143
+ <slot />
144
+ </div>
110
145
 
111
- <!-- Sections slot — additional content below stats -->
112
- <div v-if="$slots.sections" class="cn-detail-page__sections">
113
- <slot name="sections" />
146
+ <!-- Sections slot — additional content below stats -->
147
+ <div v-if="$slots.sections" class="cn-detail-page__sections">
148
+ <slot name="sections" />
149
+ </div>
114
150
  </div>
115
- </div>
116
151
 
117
- <!-- Footer -->
118
- <div v-if="$slots.footer" class="cn-detail-page__footer">
119
- <slot name="footer" />
152
+ <!-- Footer -->
153
+ <div v-if="$slots.footer" class="cn-detail-page__footer">
154
+ <slot name="footer" />
155
+ </div>
120
156
  </div>
121
157
  </div>
122
158
  </template>
@@ -126,11 +162,19 @@ import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
126
162
  import { CnIcon } from '../CnIcon/index.js'
127
163
  import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
128
164
  import InformationOutline from 'vue-material-design-icons/InformationOutline.vue'
165
+ import { gridLayout } from '../../mixins/gridLayout.js'
129
166
  import Refresh from 'vue-material-design-icons/Refresh.vue'
130
167
 
131
168
  /**
132
169
  * CnDetailPage — Generic detail/overview page.
133
170
  *
171
+ * Supports two layout modes:
172
+ * 1. **Default (vertical stacking):** Content provided via default slot, cards stack vertically.
173
+ * 2. **Grid layout:** When `layout` and `widgets` props are provided, content renders in a
174
+ * 12-column CSS grid with `#widget-{widgetId}` scoped slots. Same API as CnDashboardPage.
175
+ *
176
+ * @example Basic usage (vertical stacking)
177
+ *
134
178
  * A simpler alternative to CnIndexPage for pages that display detail info,
135
179
  * statistics, charts, or card grids — without multi-object tables or CRUD
136
180
  * dialogs. Provides a consistent layout with header, loading/error/empty
@@ -156,6 +200,24 @@ import Refresh from 'vue-material-design-icons/Refresh.vue'
156
200
  * <SchemaCards :schemas="schemas" />
157
201
  * </CnDetailPage>
158
202
  *
203
+ * @example Grid layout mode
204
+ * <CnDetailPage
205
+ * title="Character Detail"
206
+ * :layout="[
207
+ * { id: 1, widgetId: 'info', gridX: 0, gridY: 0, gridWidth: 8 },
208
+ * { id: 2, widgetId: 'stats', gridX: 8, gridY: 0, gridWidth: 4 },
209
+ * ]"
210
+ * :widgets="[
211
+ * { id: 'info', title: 'Character Info' },
212
+ * { id: 'stats', title: 'Statistics' },
213
+ * ]">
214
+ * <template #widget-info="{ item, widget }">
215
+ * <CharacterInfoCard :character="character" />
216
+ * </template>
217
+ * <template #widget-stats="{ item, widget }">
218
+ * <StatsCard :stats="character.stats" />
219
+ * </template>
220
+ *
159
221
  * @example With header actions and error handling
160
222
  * <CnDetailPage
161
223
  * title="Schema Details"
@@ -181,6 +243,12 @@ export default {
181
243
  Refresh,
182
244
  },
183
245
 
246
+ mixins: [gridLayout],
247
+
248
+ inject: {
249
+ objectSidebarState: { default: null },
250
+ },
251
+
184
252
  props: {
185
253
  /** Page title */
186
254
  title: {
@@ -212,6 +280,36 @@ export default {
212
280
  type: String,
213
281
  default: 'Loading...',
214
282
  },
283
+ /** Whether to activate the external sidebar (via objectSidebarState inject) */
284
+ sidebar: {
285
+ type: Boolean,
286
+ default: false,
287
+ },
288
+ /** Whether the sidebar is open (expanded) */
289
+ sidebarOpen: {
290
+ type: Boolean,
291
+ default: true,
292
+ },
293
+ /** The registered object type slug for the sidebar */
294
+ objectType: {
295
+ type: String,
296
+ default: '',
297
+ },
298
+ /** The object ID to display in the sidebar */
299
+ objectId: {
300
+ type: [String, Number],
301
+ default: '',
302
+ },
303
+ /** Subtitle shown in the sidebar header */
304
+ subtitle: {
305
+ type: String,
306
+ default: '',
307
+ },
308
+ /** Additional sidebar configuration (register, schema, hiddenTabs, title, subtitle) */
309
+ sidebarProps: {
310
+ type: Object,
311
+ default: () => ({}),
312
+ },
215
313
  /** Whether the page is in an error state */
216
314
  error: {
217
315
  type: Boolean,
@@ -275,10 +373,58 @@ export default {
275
373
  },
276
374
 
277
375
  computed: {
376
+ /**
377
+ * Whether the sidebar is rendered externally (via objectSidebarState inject)
378
+ * rather than inline. When external, CnDetailPage only manages state —
379
+ * the parent App renders the actual NcAppSidebar.
380
+ */
381
+ hasExternalSidebar() {
382
+ return !!this.objectSidebarState
383
+ },
278
384
  hasStats() {
279
385
  return this.statsColumns.length > 0 && (this.statsRows.length > 0 || !!this.$slots['stats-rows'])
280
386
  },
281
387
  },
388
+
389
+ watch: {
390
+ sidebar: {
391
+ immediate: true,
392
+ handler() { this.syncSidebarState() },
393
+ },
394
+ title() { this.syncSidebarState() },
395
+ subtitle() { this.syncSidebarState() },
396
+ objectType() { this.syncSidebarState() },
397
+ objectId() { this.syncSidebarState() },
398
+ sidebarProps: {
399
+ deep: true,
400
+ handler() { this.syncSidebarState() },
401
+ },
402
+ },
403
+
404
+ beforeDestroy() {
405
+ if (this.hasExternalSidebar) {
406
+ this.objectSidebarState.active = false
407
+ }
408
+ },
409
+
410
+ methods: {
411
+ syncSidebarState() {
412
+ if (!this.hasExternalSidebar) return
413
+ if (this.sidebar && this.objectType && this.objectId) {
414
+ this.objectSidebarState.active = true
415
+ this.objectSidebarState.open = this.sidebarOpen
416
+ this.objectSidebarState.objectType = this.objectType
417
+ this.objectSidebarState.objectId = this.objectId
418
+ this.objectSidebarState.title = this.sidebarProps.title || this.title || ''
419
+ this.objectSidebarState.subtitle = this.sidebarProps.subtitle || this.subtitle || ''
420
+ this.objectSidebarState.register = this.sidebarProps.register || ''
421
+ this.objectSidebarState.schema = this.sidebarProps.schema || ''
422
+ this.objectSidebarState.hiddenTabs = this.sidebarProps.hiddenTabs || []
423
+ } else {
424
+ this.objectSidebarState.active = false
425
+ }
426
+ },
427
+ },
282
428
  }
283
429
  </script>
284
430
 
@@ -1,7 +1,9 @@
1
1
  <template>
2
2
  <div class="cn-facet-sidebar">
3
3
  <div class="cn-facet-sidebar__header">
4
- <h3 class="cn-facet-sidebar__title">{{ title }}</h3>
4
+ <h3 class="cn-facet-sidebar__title">
5
+ {{ title }}
6
+ </h3>
5
7
  <NcButton
6
8
  v-if="hasActiveFilters"
7
9
  type="tertiary"