@conduction/nextcloud-vue 2.62.0 → 2.64.0

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 (209) hide show
  1. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
  2. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
  3. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
  4. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
  5. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
  6. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +425 -39
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +119 -42
  10. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +186 -9
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +65 -42
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +61 -16
  16. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  17. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  18. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +20 -0
  19. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  20. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -2
  21. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  22. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -2
  23. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  24. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  25. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  26. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
  27. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -0
  29. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +152 -135
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
  33. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
  34. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
  35. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
  36. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
  37. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
  38. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +253 -9
  39. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +126 -50
  41. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +66 -14
  43. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +23 -14
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  48. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  49. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
  50. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
  52. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
  53. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
  54. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  55. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
  56. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  57. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +81 -4
  58. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +22 -5
  60. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +81 -2
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +7 -3
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  65. package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
  66. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  67. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +41 -2
  68. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +8 -3
  70. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  71. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
  72. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
  73. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
  74. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
  75. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
  76. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  77. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
  78. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  79. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +63 -2
  80. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +96 -37
  82. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnStagesWidget/stagesModel.js +5 -2
  84. package/dist/esm/components/CnStagesWidget/stagesModel.js.map +1 -1
  85. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +52 -2
  86. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  87. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -6
  88. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  89. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +14 -2
  90. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  91. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +2 -1
  92. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  93. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
  94. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
  95. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
  96. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
  97. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
  98. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
  99. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
  100. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
  101. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
  102. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  103. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
  104. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  105. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
  106. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +73 -1
  110. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  111. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +14 -2
  112. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  113. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
  114. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  115. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
  116. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  117. package/dist/esm/composables/useDependencyCheck.js +65 -0
  118. package/dist/esm/composables/useDependencyCheck.js.map +1 -0
  119. package/dist/esm/composables/useWalkthrough.js +218 -6
  120. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  121. package/dist/esm/index.js +2 -0
  122. package/dist/esm/index.js.map +1 -1
  123. package/dist/esm/l10n/en.json.js +45 -1
  124. package/dist/esm/l10n/en.json.js.map +1 -1
  125. package/dist/esm/l10n/nl.json.js +45 -1
  126. package/dist/esm/l10n/nl.json.js.map +1 -1
  127. package/dist/esm/nextcloud-vue.css +780 -81
  128. package/dist/esm/store/useObjectStore.js +8 -0
  129. package/dist/esm/store/useObjectStore.js.map +1 -1
  130. package/dist/esm/utils/columnFilters.js +311 -0
  131. package/dist/esm/utils/columnFilters.js.map +1 -0
  132. package/dist/esm/utils/groupAutocomplete.js +123 -0
  133. package/dist/esm/utils/groupAutocomplete.js.map +1 -0
  134. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  135. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  136. package/dist/esm/utils/pickerOptions.js +198 -0
  137. package/dist/esm/utils/pickerOptions.js.map +1 -0
  138. package/dist/esm/utils/schema.js +83 -7
  139. package/dist/esm/utils/schema.js.map +1 -1
  140. package/dist/esm/utils/schemaRefSlug.js +19 -3
  141. package/dist/esm/utils/schemaRefSlug.js.map +1 -1
  142. package/dist/esm/utils/timelineEvents.js +174 -0
  143. package/dist/esm/utils/timelineEvents.js.map +1 -0
  144. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  145. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  146. package/dist/esm/utils/walkthroughSignals.js +88 -0
  147. package/dist/esm/utils/walkthroughSignals.js.map +1 -0
  148. package/dist/nextcloud-vue.cjs.js +13088 -7937
  149. package/dist/nextcloud-vue.cjs.js.map +2 -2
  150. package/dist/nextcloud-vue.css +780 -81
  151. package/l10n/en.json +45 -0
  152. package/l10n/nl.json +45 -0
  153. package/package.json +1 -1
  154. package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
  155. package/src/components/CnAdminActionCard/index.js +4 -0
  156. package/src/components/CnAppNav/CnAppNav.vue +568 -43
  157. package/src/components/CnAppRoot/CnAppRoot.md +4 -0
  158. package/src/components/CnAppRoot/CnAppRoot.vue +223 -16
  159. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
  160. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
  161. package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
  162. package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
  163. package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -4
  164. package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
  165. package/src/components/CnDataTable/CnDataTable.md +3 -0
  166. package/src/components/CnDataTable/CnDataTable.vue +305 -5
  167. package/src/components/CnDetailPage/CnDetailPage.md +4 -0
  168. package/src/components/CnDetailPage/CnDetailPage.vue +60 -1
  169. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
  170. package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
  171. package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
  172. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +116 -6
  173. package/src/components/CnIndexPage/CnIndexPage.vue +87 -4
  174. package/src/components/CnIndexPage/selfModeActions.js +3 -3
  175. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +46 -2
  176. package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
  177. package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
  178. package/src/components/CnStagesWidget/CnStagesWidget.vue +180 -2
  179. package/src/components/CnStagesWidget/stagesModel.js +5 -2
  180. package/src/components/CnStatWidget/CnStatWidget.vue +57 -3
  181. package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
  182. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
  183. package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
  184. package/src/components/CnTimelineWidget/index.js +4 -0
  185. package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
  186. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  187. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  188. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
  189. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
  190. package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
  191. package/src/components/index.js +2 -0
  192. package/src/composables/useDependencyCheck.js +61 -0
  193. package/src/composables/useWalkthrough.js +217 -5
  194. package/src/css/dashboard.css +49 -0
  195. package/src/css/detail-page.css +29 -4
  196. package/src/css/kpi-card.css +24 -1
  197. package/src/css/table.css +99 -6
  198. package/src/index.js +2 -0
  199. package/src/schemas/app-manifest-v2.schema.json +135 -3
  200. package/src/store/useObjectStore.js +8 -0
  201. package/src/utils/columnFilters.js +324 -0
  202. package/src/utils/groupAutocomplete.js +119 -0
  203. package/src/utils/libraryWidgetKeys.js +2 -0
  204. package/src/utils/pickerOptions.js +194 -0
  205. package/src/utils/schema.js +83 -7
  206. package/src/utils/schemaRefSlug.js +19 -3
  207. package/src/utils/timelineEvents.js +171 -0
  208. package/src/utils/validateManifestV2.compiled.js +2 -2
  209. package/src/utils/walkthroughSignals.js +94 -0
@@ -13,12 +13,12 @@
13
13
  <slot
14
14
  name="header"
15
15
  :title="title"
16
- :description="description"
16
+ :description="headerDescription"
17
17
  :icon="resolvedIcon"
18
18
  :showTitle="showTitle">
19
19
  <CnPageHeader
20
20
  :title="title"
21
- :description="description"
21
+ :description="headerDescription"
22
22
  :icon="resolvedIcon"
23
23
  :visuallyHidden="!showTitle" />
24
24
  </slot>
@@ -450,6 +450,10 @@
450
450
  :includeColumns="includeColumns"
451
451
  :columnOverrides="columnOverrides"
452
452
  :rowClass="rowClass"
453
+ :filterable="headerFilters && !!effectiveSchema"
454
+ :activeFilters="effectiveActiveFilters"
455
+ :filterRegister="typeof register === 'string' ? register : ''"
456
+ @columnFilter="onColumnFilterEvent"
453
457
  @sort="onSortEvent"
454
458
  @select="onSelect"
455
459
  @rowClick="onRowClick"
@@ -798,6 +802,7 @@ import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js'
798
802
  import { buildRouteQueryFromViewState, buildViewCreatePayload, extractViewState, extractViewStateFromRouteQuery, savedViewScope, viewMatchesScope } from '../../utils/savedViewHelpers.js'
799
803
  import { columnsFromSchema } from '../../utils/schema.js'
800
804
  import { resolveScopeLayout } from '../../utils/scopeListLayout.js'
805
+ import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js'
801
806
  import { CnActionsBar } from '../CnActionsBar/index.js'
802
807
  import { CnAdvancedFormDialog } from '../CnAdvancedFormDialog/index.js'
803
808
  import { CnBoardView } from '../CnBoardView/index.js'
@@ -1172,6 +1177,21 @@ export default {
1172
1177
  default: '',
1173
1178
  },
1174
1179
 
1180
+ /**
1181
+ * A description that carries the collection's total: `{total}` in
1182
+ * the text is replaced by the current pagination total ("{total}
1183
+ * open cases" reads "48 open cases"). Rendered in place of
1184
+ * `description` once a total is known; before that, `description`
1185
+ * shows. Manifest key `config.countSubtitle`. Goes through the host
1186
+ * translate function.
1187
+ *
1188
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-an-index-page-title-is-a-manifest-key
1189
+ */
1190
+ countSubtitle: {
1191
+ type: String,
1192
+ default: '',
1193
+ },
1194
+
1175
1195
  /**
1176
1196
  * Whether to show the page header (icon, title, description) inline.
1177
1197
  * When false (default), only the sidebar header shows the title
@@ -2285,6 +2305,22 @@ export default {
2285
2305
  default: false,
2286
2306
  },
2287
2307
 
2308
+ /**
2309
+ * Per-column filters in the table header: a filter button on every
2310
+ * column that can filter, opening a small panel that fits the column
2311
+ * (checkboxes, yes/no/any, from and to, a searchable reference list).
2312
+ * On by default; a column opts out with `filterable: false`, a page
2313
+ * with `pages[].config.headerFilters: false`. Filters write into the
2314
+ * same active-filter map as the facet sidebar, so they persist in the
2315
+ * route query and reach the same OpenRegister query parameters.
2316
+ *
2317
+ * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-every-backed-column-filters-from-its-header
2318
+ */
2319
+ headerFilters: {
2320
+ type: Boolean,
2321
+ default: true,
2322
+ },
2323
+
2288
2324
  /**
2289
2325
  * Show a filter menu (funnel button) in the table header, above the
2290
2326
  * row-actions column. Its menu lists every enum/badge column's values as
@@ -4210,6 +4246,21 @@ export default {
4210
4246
  return resolveDeepTokens(seed, {})
4211
4247
  },
4212
4248
 
4249
+ /**
4250
+ * The header's description line: `countSubtitle` with the total
4251
+ * filled in when one is known, else `description`.
4252
+ *
4253
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-an-index-page-title-is-a-manifest-key
4254
+ * @return {string}
4255
+ */
4256
+ headerDescription() {
4257
+ const total = this.effectivePagination?.total
4258
+ if (this.countSubtitle && typeof total === 'number' && total >= 0) {
4259
+ return this.cnTranslate(this.countSubtitle).replace('{total}', String(total))
4260
+ }
4261
+ return this.description
4262
+ },
4263
+
4213
4264
  /** Add button label — derived from schema.title if not explicitly set */
4214
4265
  resolvedAddLabel() {
4215
4266
  if (this.addLabel) {
@@ -5225,6 +5276,39 @@ export default {
5225
5276
  this.$emit('filter-change', payload)
5226
5277
  },
5227
5278
 
5279
+ /**
5280
+ * A header filter was applied or cleared. One column can own several
5281
+ * query parameters (a range is `key[gte]` and `key[lte]`), so they are
5282
+ * applied together: one fetch, one route update.
5283
+ *
5284
+ * @param {{key: string, params: object}} payload From CnDataTable's `column-filter`.
5285
+ * @return {void}
5286
+ * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-a-header-filter-speaks-the-sidebars-query-language
5287
+ */
5288
+ onColumnFilterEvent(payload) {
5289
+ const params = (payload && payload.params) || {}
5290
+ const entries = Object.entries(params).map(([key, values]) => [key, Array.isArray(values) ? values : []])
5291
+ if (this.isSelfFetchMode) {
5292
+ const next = { ...this.list.activeFilters.value }
5293
+ for (const [key, values] of entries) {
5294
+ if (values.length === 0) {
5295
+ delete next[key]
5296
+ } else {
5297
+ next[key] = values
5298
+ }
5299
+ }
5300
+ this.list.activeFilters.value = next
5301
+ this.list.refresh(1)
5302
+ this.persistViewStateToRoute(this.currentViewState())
5303
+ }
5304
+ for (const [key, values] of entries) {
5305
+ if (this.isNamedSource) {
5306
+ this.setNamedFilter(key, values)
5307
+ }
5308
+ this.$emit('filter-change', { key, values })
5309
+ }
5310
+ },
5311
+
5228
5312
  /**
5229
5313
  * Record one sidebar choice on a named-source page and mirror it into
5230
5314
  * the URL.
@@ -6460,8 +6544,7 @@ export default {
6460
6544
  if (typeof window === 'undefined' || !created) {
6461
6545
  return
6462
6546
  }
6463
- const detail = { ...created, register: this.register, schema: this.exportSchemaSlug }
6464
- window.dispatchEvent(new CustomEvent('cn-walkthrough:object-created', { detail }))
6547
+ dispatchObjectCreated({ register: this.register, schema: this.exportSchemaSlug, object: created })
6465
6548
  },
6466
6549
 
6467
6550
  /**
@@ -1,3 +1,4 @@
1
+ import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js'
1
2
  import {
2
3
  cloneObjectForCopy,
3
4
  runSelfExportRequest,
@@ -196,9 +197,8 @@ export function createSelfModeActions(ctx) {
196
197
  // @self.register/@self.schema are numeric DB ids, not slugs, so
197
198
  // override with this page's own slug props (self-fetch mode
198
199
  // guarantees ctx.schema() is already a string).
199
- if (isCreate && typeof window !== 'undefined') {
200
- const detail = { ...saved, register: ctx.register(), schema: ctx.schema() }
201
- window.dispatchEvent(new CustomEvent('cn-walkthrough:object-created', { detail }))
200
+ if (isCreate) {
201
+ dispatchObjectCreated({ register: ctx.register(), schema: ctx.schema(), object: saved })
202
202
  }
203
203
  refreshList(ctx)
204
204
  if (isCreate && typeof ctx.afterCreateSuccess === 'function') {
@@ -151,6 +151,7 @@
151
151
  :sortKey="localSort.field || null"
152
152
  :sortOrder="localSort.dir || 'asc'"
153
153
  borderless
154
+ :hideHeader="content.hideHeader === true"
154
155
  @rowClick="onRowClick"
155
156
  @rowAuxClick="onRowAuxClick"
156
157
  @select="onSelect"
@@ -172,6 +173,7 @@
172
173
  :sortKey="localSort.field || null"
173
174
  :sortOrder="localSort.dir || 'asc'"
174
175
  borderless
176
+ :hideHeader="content.hideHeader === true"
175
177
  @rowClick="onRowClick"
176
178
  @rowAuxClick="onRowAuxClick"
177
179
  @select="onSelect"
@@ -268,6 +270,9 @@
268
270
  ref="createDialog"
269
271
  :schema="createSchema"
270
272
  :item="null"
273
+ :register="content.register || ''"
274
+ :initialData="createInitialData"
275
+ :lockedFields="createLockedFields"
271
276
  :size="formSize"
272
277
  :columns="formColumns"
273
278
  :includeFields="formIncludeFields"
@@ -293,6 +298,7 @@ import { followLinkClick, openRowTarget, resolveHref } from '../../utils/linkNav
293
298
  import { objectFieldValue } from '../../utils/objectName.js'
294
299
  import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
295
300
  import { markNewTabHandled } from '../../utils/rowAuxClick.js'
301
+ import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js'
296
302
  import { CnRowActions } from '../CnRowActions/index.js'
297
303
 
298
304
  /**
@@ -415,7 +421,11 @@ export default {
415
421
  * those carrying a selected value. Client-side over the fetched page,
416
422
  * like the rest of this widget's row set.
417
423
  *
418
- * @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, prompt?: string, emptyText?: string, viewAllRoute?: string, viewAllQuery?: object}}
424
+ * `hideHeader: true` drops the table's column header row (CnDataTable's
425
+ * `hideHeader`), for a short list that reads as rows, not as a table.
426
+ *
427
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-small-opt-ins-from-the-decidiq-and-learniq-lanes
428
+ * @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, prompt?: string, emptyText?: string, viewAllRoute?: string, viewAllQuery?: object, hideHeader?: boolean}}
419
429
  */
420
430
  content: {
421
431
  type: Object,
@@ -1010,6 +1020,39 @@ export default {
1010
1020
  return Array.isArray(c.formExcludeFields) ? c.formExcludeFields : []
1011
1021
  },
1012
1022
 
1023
+ /**
1024
+ * Create-form seed values: the list's scalar filter values (the parent
1025
+ * this list is scoped to, e.g. `{ lead: '<uuid>' }`), limited to keys
1026
+ * the schema declares, so the new row shows its parent already chosen.
1027
+ *
1028
+ * @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md
1029
+ * @return {object}
1030
+ */
1031
+ createInitialData() {
1032
+ const props = (this.createSchema && this.createSchema.properties) || {}
1033
+ const out = {}
1034
+ for (const [key, value] of Object.entries(this.resolvedFilter || {})) {
1035
+ if (!(key in props) || value === null || value === undefined || value === '' || typeof value === 'object') {
1036
+ continue
1037
+ }
1038
+ if (typeof value === 'string' && value.charAt(0) === '@') {
1039
+ continue
1040
+ }
1041
+ out[key] = value
1042
+ }
1043
+ return out
1044
+ },
1045
+
1046
+ /**
1047
+ * The seeded parent keys are locked: a row added to this list belongs
1048
+ * to the record the list is scoped to.
1049
+ *
1050
+ * @return {string[]}
1051
+ */
1052
+ createLockedFields() {
1053
+ return Object.keys(this.createInitialData)
1054
+ },
1055
+
1013
1056
  /** Per-field overrides for the create dialog (`content.formFieldOverrides`). */
1014
1057
  formFieldOverrides() {
1015
1058
  const c = this.content || {}
@@ -1358,7 +1401,8 @@ export default {
1358
1401
  }
1359
1402
  }
1360
1403
  const url = generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: c.register, schema: c.schema })
1361
- await axios.post(url, payload)
1404
+ const response = await axios.post(url, payload)
1405
+ dispatchObjectCreated({ register: c.register, schema: c.schema, object: (response && response.data) || payload })
1362
1406
  if (this.$refs.createDialog) {
1363
1407
  this.$refs.createDialog.setResult({ success: true })
1364
1408
  }
@@ -13,6 +13,8 @@
13
13
  :disabled="disabled"
14
14
  :placeholder="placeholder"
15
15
  :filterable="false"
16
+ :multiple="multiple"
17
+ :keepOpen="multiple"
16
18
  label="label"
17
19
  @search="onSearch"
18
20
  @update:modelValue="onInput">
@@ -93,12 +95,21 @@ export default {
93
95
  required: true,
94
96
  },
95
97
 
96
- /** Currently-selected object id (v-model). */
98
+ /** Currently-selected object id (v-model), or an array of ids when `multiple`. */
97
99
  modelValue: {
98
- type: [String, Number],
100
+ type: [String, Number, Array],
99
101
  default: '',
100
102
  },
101
103
 
104
+ /**
105
+ * Pick several objects. `modelValue` is then an array of ids, and a
106
+ * create adds the new object to the selection.
107
+ */
108
+ multiple: {
109
+ type: Boolean,
110
+ default: false,
111
+ },
112
+
102
113
  /** Object field used as the option label AND written on create. */
103
114
  labelField: {
104
115
  type: String,
@@ -219,6 +230,9 @@ export default {
219
230
  // The selected option object kept locally so the label shows even
220
231
  // before/without a matching search result (e.g. a pre-set modelValue).
221
232
  localSelected: null,
233
+ // Options seen so far, keyed by value, so a multiple selection
234
+ // keeps its labels when the search results move on.
235
+ known: {},
222
236
  }
223
237
  },
224
238
 
@@ -232,6 +246,19 @@ export default {
232
246
  }
233
247
  },
234
248
 
249
+ /**
250
+ * The selected ids as strings (multiple mode).
251
+ *
252
+ * @return {string[]}
253
+ */
254
+ selectedIds() {
255
+ const value = this.modelValue
256
+ if (Array.isArray(value)) {
257
+ return value.filter((v) => v !== null && v !== undefined && v !== '').map((v) => String(v))
258
+ }
259
+ return value ? [String(value)] : []
260
+ },
261
+
235
262
  /** The `${register}-${schema}` object-type slug used by the store. */
236
263
  typeSlug() {
237
264
  return `${this.register}-${this.schema}`
@@ -239,6 +266,11 @@ export default {
239
266
 
240
267
  /** The currently-selected option, for NcSelect's model-value. */
241
268
  selectedOption() {
269
+ if (this.multiple) {
270
+ return this.selectedIds.map((id) => this.known[id]
271
+ || this.options.find((o) => o.value === id)
272
+ || { value: id, label: id })
273
+ }
242
274
  if (this.localSelected && this.localSelected.value === this.modelValue) {
243
275
  return this.localSelected
244
276
  }
@@ -310,9 +342,9 @@ export default {
310
342
  // The scope moved, so anything already selected may no longer be
311
343
  // in it. Drop the stale options + selection, then re-seed.
312
344
  this.options = []
313
- if (this.modelValue) {
345
+ if (this.selectedIds.length > 0) {
314
346
  this.localSelected = null
315
- this.$emit('update:modelValue', '')
347
+ this.$emit('update:modelValue', this.multiple ? [] : '')
316
348
  }
317
349
  if (this.search.trim().length >= this.minChars) {
318
350
  this.onSearch(this.search)
@@ -347,7 +379,7 @@ export default {
347
379
  const items = Array.isArray(collection)
348
380
  ? collection
349
381
  : (this.objectStore.collections[this.typeSlug] || [])
350
- this.options = items.map((o) => this.toOption(o))
382
+ this.options = this.remember(items.map((o) => this.toOption(o)))
351
383
  } catch {
352
384
  this.options = []
353
385
  } finally {
@@ -392,7 +424,7 @@ export default {
392
424
  const items = Array.isArray(collection)
393
425
  ? collection
394
426
  : (this.objectStore.collections[this.typeSlug] || [])
395
- this.options = items.map((o) => this.toOption(o))
427
+ this.options = this.remember(items.map((o) => this.toOption(o)))
396
428
  } catch {
397
429
  this.options = []
398
430
  } finally {
@@ -408,6 +440,10 @@ export default {
408
440
  * @return {Promise<void>}
409
441
  */
410
442
  async onInput(option) {
443
+ if (this.multiple) {
444
+ await this.onMultipleInput(Array.isArray(option) ? option : (option ? [option] : []))
445
+ return
446
+ }
411
447
  if (!option) {
412
448
  this.localSelected = null
413
449
  /**
@@ -425,19 +461,58 @@ export default {
425
461
  this.$emit('update:modelValue', option.value)
426
462
  },
427
463
 
464
+ /**
465
+ * Handle a change of a multiple selection. A synthetic create option
466
+ * creates the object and adds it; the rest emit their ids.
467
+ *
468
+ * @param {Array<object>} chosen The selected options.
469
+ * @return {Promise<void>}
470
+ */
471
+ async onMultipleInput(chosen) {
472
+ const createOption = chosen.find((o) => o && o.__create)
473
+ const ids = chosen.filter((o) => o && !o.__create).map((o) => String(o.value))
474
+ this.remember(chosen.filter((o) => o && !o.__create))
475
+ if (!createOption) {
476
+ this.$emit('update:modelValue', ids)
477
+ return
478
+ }
479
+ const created = await this.createFromTerm(createOption.label, ids)
480
+ if (!created) {
481
+ this.$emit('update:modelValue', ids)
482
+ }
483
+ },
484
+
485
+ /**
486
+ * Cache options by value so their labels survive a new search.
487
+ *
488
+ * @param {Array<object>} options Options to remember.
489
+ * @return {Array<object>} The same options.
490
+ */
491
+ remember(options) {
492
+ const next = { ...this.known }
493
+ for (const o of options) {
494
+ if (o && o.value) {
495
+ next[o.value] = o
496
+ }
497
+ }
498
+ this.known = next
499
+ return options
500
+ },
501
+
428
502
  /**
429
503
  * Create a new object from the typed term and select it.
430
504
  *
431
505
  * @param {string} term The term to write to `labelField`.
432
- * @return {Promise<void>}
506
+ * @param {string[]} [keep] Multiple mode: the ids already selected, which the new id joins.
507
+ * @return {Promise<object|null>} The created object, or null.
433
508
  */
434
- async createFromTerm(term) {
509
+ async createFromTerm(term, keep = []) {
435
510
  if (!this.objectStore && !this.createHandler) {
436
- return
511
+ return null
437
512
  }
438
513
  const name = (term || '').trim()
439
514
  if (!name) {
440
- return
515
+ return null
441
516
  }
442
517
  this.loading = true
443
518
  try {
@@ -456,20 +531,26 @@ export default {
456
531
  created = await this.objectStore.saveObject(this.typeSlug, payload)
457
532
  }
458
533
  if (!created) {
459
- return
534
+ return null
460
535
  }
461
536
  const option = this.toOption(created)
537
+ this.remember([option])
462
538
  this.options = [option, ...this.options.filter((o) => o.value !== option.value)]
463
539
  this.localSelected = option
464
- this.$emit('update:modelValue', option.value)
540
+ this.search = ''
541
+ this.$emit('update:modelValue', this.multiple
542
+ ? [...keep.filter((id) => id !== option.value), option.value]
543
+ : option.value)
465
544
  /**
466
545
  * @event create A new object was created from the search term.
467
546
  * @type {object} The created OpenRegister object.
468
547
  */
469
548
  this.$emit('create', created)
549
+ return created
470
550
  } catch (e) {
471
551
  // eslint-disable-next-line no-console
472
552
  console.warn('[CnResourceSelect] create failed', e)
553
+ return null
473
554
  } finally {
474
555
  this.loading = false
475
556
  }
@@ -508,6 +589,10 @@ export default {
508
589
  * @return {Promise<void>}
509
590
  */
510
591
  async ensureSelectedLoaded() {
592
+ if (this.multiple) {
593
+ await this.ensureManyLoaded()
594
+ return
595
+ }
511
596
  if (!this.modelValue || this.selectedOption || !this.objectStore) {
512
597
  return
513
598
  }
@@ -524,6 +609,32 @@ export default {
524
609
  // Leave the id un-labelled rather than crash.
525
610
  }
526
611
  },
612
+
613
+ /**
614
+ * Multiple mode: fetch each selected id whose label is not known yet.
615
+ *
616
+ * @return {Promise<void>}
617
+ */
618
+ async ensureManyLoaded() {
619
+ if (!this.objectStore || typeof this.objectStore.fetchObject !== 'function') {
620
+ return
621
+ }
622
+ const missing = this.selectedIds.filter((id) => !this.known[id] && !this.options.some((o) => o.value === id))
623
+ if (missing.length === 0) {
624
+ return
625
+ }
626
+ this.ensureRegistered()
627
+ for (const id of missing) {
628
+ try {
629
+ const obj = await this.objectStore.fetchObject(this.typeSlug, id)
630
+ if (obj) {
631
+ this.remember([this.toOption(obj)])
632
+ }
633
+ } catch {
634
+ // Leave the id un-labelled rather than crash.
635
+ }
636
+ }
637
+ },
527
638
  },
528
639
  }
529
640
  </script>