@conduction/nextcloud-vue 2.101.0 → 2.103.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 (140) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +22 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +8 -6
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  6. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +29 -4
  7. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  8. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +9 -1
  9. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  10. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  11. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +73 -0
  12. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  13. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +99 -64
  14. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  15. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
  16. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +46 -0
  17. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  18. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +37 -2
  19. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  21. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +49 -6
  22. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +1 -1
  24. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  25. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  26. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +113 -20
  27. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  28. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +387 -359
  29. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  30. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +84 -4
  31. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +8 -6
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/useNamedSource.js +26 -2
  35. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  36. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue.js +1 -1
  37. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js +25 -1
  38. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js.map +1 -1
  39. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue3.js.map +1 -1
  40. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  41. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +10 -1
  42. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  43. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  44. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue.js +1 -1
  45. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +106 -4
  46. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  47. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +70 -25
  48. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  49. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  50. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +97 -3
  51. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  52. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +46 -2
  53. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  54. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +9 -0
  55. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js.map +1 -0
  56. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js +165 -0
  57. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -0
  58. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js +85 -0
  59. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -0
  60. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  61. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +54 -6
  62. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  63. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +3 -1
  64. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  65. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  66. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +8 -1
  67. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  68. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  69. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  70. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +2 -23
  71. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  72. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  73. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
  74. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
  75. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  76. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
  77. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  78. package/dist/esm/composables/indexSources.js +9 -0
  79. package/dist/esm/composables/indexSources.js.map +1 -1
  80. package/dist/esm/index.js +1 -0
  81. package/dist/esm/index.js.map +1 -1
  82. package/dist/esm/l10n/en.json.js +5 -1
  83. package/dist/esm/l10n/en.json.js.map +1 -1
  84. package/dist/esm/l10n/nl.json.js +5 -1
  85. package/dist/esm/l10n/nl.json.js.map +1 -1
  86. package/dist/esm/library-style.js +1 -1
  87. package/dist/esm/mixins/pageViews.js +8 -1
  88. package/dist/esm/mixins/pageViews.js.map +1 -1
  89. package/dist/esm/nextcloud-vue.css +593 -459
  90. package/dist/esm/utils/builtInFormatters.js +24 -4
  91. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  92. package/dist/esm/utils/rowClassRules.js +43 -0
  93. package/dist/esm/utils/rowClassRules.js.map +1 -0
  94. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  95. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  96. package/dist/esm/utils/visibleWhen.js +32 -4
  97. package/dist/esm/utils/visibleWhen.js.map +1 -1
  98. package/dist/nextcloud-vue.cjs.js +10093 -8858
  99. package/dist/nextcloud-vue.cjs.js.map +2 -2
  100. package/dist/nextcloud-vue.css +593 -459
  101. package/l10n/en.json +5 -1
  102. package/l10n/nl.json +5 -1
  103. package/package.json +1 -1
  104. package/src/components/CnActionsBar/CnActionsBar.md +1 -0
  105. package/src/components/CnActionsBar/CnActionsBar.vue +31 -4
  106. package/src/components/CnChartWidget/CnChartWidget.md +3 -2
  107. package/src/components/CnChartWidget/CnChartWidget.vue +42 -4
  108. package/src/components/CnConversationThread/CnConversationThread.md +17 -0
  109. package/src/components/CnConversationThread/CnConversationThread.vue +113 -8
  110. package/src/components/CnConversationThread/CnConversationWidget.vue +60 -1
  111. package/src/components/CnDashboardPage/CnDashboardPage.md +4 -1
  112. package/src/components/CnDashboardPage/CnDashboardPage.vue +50 -7
  113. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  114. package/src/components/CnFormDialog/CnFormDialog.md +1 -1
  115. package/src/components/CnFormDialog/CnFormDialog.vue +127 -0
  116. package/src/components/CnIndexPage/CnIndexPage.md +15 -1
  117. package/src/components/CnIndexPage/CnIndexPage.vue +92 -9
  118. package/src/components/CnIndexPage/useNamedSource.js +26 -2
  119. package/src/components/CnMenuWidget/CnMenuWidget.vue +25 -1
  120. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +10 -1
  121. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +1 -0
  122. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +159 -5
  123. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +134 -2
  124. package/src/components/CnSearchSelect/CnSearchSelect.vue +283 -0
  125. package/src/components/CnSearchSelect/index.js +12 -0
  126. package/src/components/CnSetupWizard/CnSetupWizard.vue +56 -6
  127. package/src/components/CnTabsWidget/CnTabsWidget.vue +8 -1
  128. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +2 -23
  129. package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
  130. package/src/components/index.js +1 -0
  131. package/src/composables/indexSources.js +9 -0
  132. package/src/css/dialog.css +5 -3
  133. package/src/css/look-board-index.css +10 -0
  134. package/src/index.js +1 -0
  135. package/src/mixins/pageViews.js +8 -1
  136. package/src/schemas/app-manifest-v2.schema.json +107 -8
  137. package/src/utils/builtInFormatters.js +24 -4
  138. package/src/utils/rowClassRules.js +39 -0
  139. package/src/utils/validateManifestV2.compiled.js +3 -3
  140. package/src/utils/visibleWhen.js +32 -4
@@ -111,7 +111,7 @@
111
111
  <Pencil v-if="!isEditing" :size="20" />
112
112
  <Check v-else :size="20" />
113
113
  </template>
114
- {{ isEditing ? doneLabel : editLabel }}
114
+ {{ isEditing ? shownDoneLabel : shownEditLabel }}
115
115
  </NcButton>
116
116
  <!-- With `userLayout`, edit mode offers a way back to the
117
117
  manifest: the page's own grid and the chosen view return
@@ -1105,6 +1105,7 @@ const CHART_PROP_KEYS = [
1105
1105
  'footnote',
1106
1106
  'highlight',
1107
1107
  'cellSource',
1108
+ 'emptyCellLabel',
1108
1109
  ]
1109
1110
 
1110
1111
  /**
@@ -1838,6 +1839,9 @@ export default {
1838
1839
  * and renders exactly as it did before this existed, which is the
1839
1840
  * property that makes shipping it safe on every dashboard at once.
1840
1841
  *
1842
+ * Deprecated: layout belongs to Buildiq. Kept working for one release.
1843
+ *
1844
+ * @deprecated
1841
1845
  * @type {boolean}
1842
1846
  */
1843
1847
  userLayout: {
@@ -2215,9 +2219,25 @@ export default {
2215
2219
  * @return {boolean}
2216
2220
  */
2217
2221
  gridEditable() {
2222
+ return this.isEditing || this.editingBody
2223
+ },
2224
+
2225
+ /**
2226
+ * Whether Buildiq is editing the page (the injected `cnEditingBody`
2227
+ * ref, or a plain boolean). Buildiq is the layout editor: while it
2228
+ * edits, the grid shows and changes the manifest layout, also on a
2229
+ * page with `userLayout`.
2230
+ *
2231
+ * @return {boolean}
2232
+ */
2233
+ editingBody() {
2218
2234
  const e = this.cnEditingBody
2219
- const buildiqEditing = Boolean(e && typeof e === 'object' && 'value' in e ? e.value : e)
2220
- return this.isEditing || buildiqEditing
2235
+ return Boolean(e && typeof e === 'object' && 'value' in e ? e.value : e)
2236
+ },
2237
+
2238
+ /** @return {boolean} Whether the grid shows this user's arrangement. */
2239
+ showsUserLayout() {
2240
+ return this.userLayout && !this.editingBody && Array.isArray(this.userLayoutItems)
2221
2241
  },
2222
2242
 
2223
2243
  /**
@@ -2278,9 +2298,7 @@ export default {
2278
2298
  * @return {Array<object>} The layout.
2279
2299
  */
2280
2300
  renderedLayout() {
2281
- const base = (this.userLayout && Array.isArray(this.userLayoutItems))
2282
- ? this.userLayoutItems
2283
- : this.layout
2301
+ const base = this.showsUserLayout ? this.userLayoutItems : this.layout
2284
2302
  // The KPI row is not part of the grid.
2285
2303
  if (Array.isArray(this.kpiRow) && this.kpiRow.length > 0 && Array.isArray(base)) {
2286
2304
  return base.filter((item) => !this.kpiRow.includes(item.widgetId))
@@ -2400,6 +2418,25 @@ export default {
2400
2418
  return (this.pageFilters || []).filter((pf) => pf && pf.key && !inDescription.includes(pf))
2401
2419
  },
2402
2420
 
2421
+ /**
2422
+ * The edit button's label through the app's translate: a manifest's
2423
+ * `editLabel` ("Edit layout", DqDashboard) reads in the user's language.
2424
+ *
2425
+ * @return {string}
2426
+ */
2427
+ shownEditLabel() {
2428
+ return this.effectiveTranslate(this.editLabel)
2429
+ },
2430
+
2431
+ /**
2432
+ * The done button's label through the app's translate.
2433
+ *
2434
+ * @return {string}
2435
+ */
2436
+ shownDoneLabel() {
2437
+ return this.effectiveTranslate(this.doneLabel)
2438
+ },
2439
+
2403
2440
  /**
2404
2441
  * The preset list passed to the picker. Falls back to the
2405
2442
  * `DEFAULT_DATE_RANGE_PRESETS` constant when the consumer
@@ -3557,7 +3594,8 @@ export default {
3557
3594
  // write below is for the admin editor, which edits the page for
3558
3595
  // everybody; doing it for a user rearranging their own dashboard
3559
3596
  // would rewrite the page for everyone who opens it after them.
3560
- if (this.userLayout && Array.isArray(this.userLayoutItems)) {
3597
+ // While Buildiq edits, the drag is the admin's and goes below.
3598
+ if (this.showsUserLayout) {
3561
3599
  this.userLayoutDirty = true
3562
3600
  this.userLayoutItems = this.userLayoutItems.map((item) => {
3563
3601
  const u = (Array.isArray(updated) ? updated : []).find((candidate) => String(candidate.id) === String(item.id)
@@ -3566,6 +3604,11 @@ export default {
3566
3604
  return (u ? { ...item, ...this.geometryFrom(u) } : item)
3567
3605
  })
3568
3606
  this.$emit('layout-change', this.userLayoutItems)
3607
+ // Outside an in-page edit session no Done will come to save
3608
+ // the arrangement, so it is saved now.
3609
+ if (!this.isEditing) {
3610
+ this.saveUserLayout()
3611
+ }
3569
3612
 
3570
3613
  return
3571
3614
  }
@@ -215,7 +215,7 @@ export default {
215
215
 
216
216
  | Prop | Type | Default | Description |
217
217
  |------|------|---------|-------------|
218
- | `userLayout` | Boolean | `false` | Let the user arrange each of the page's `views` for themselves. The header shows **Arrange view** next to the view switch; **Done** stores the arrangement per view and user (a user preference, mirrored in the browser), **Reset view** returns the chosen view to its manifest layout. The manifest `layout` is never changed. See [the guide](../../../docs/components/cn-detail-page.md#each-view-arranged-per-user-userlayout). |
218
+ | `userLayout` | Boolean | `false` | **Deprecated** (layout belongs to Buildiq; removed after this release). Let the user arrange each of the page's `views` for themselves. The header shows **Arrange view** next to the view switch; **Done** stores the arrangement per view and user (a user preference, mirrored in the browser), **Reset view** returns the chosen view to its manifest layout. The manifest `layout` is never changed. See [the guide](../../../docs/components/cn-detail-page.md#each-view-arranged-per-user-userlayout). |
219
219
 
220
220
  ## Slots
221
221
 
@@ -100,7 +100,7 @@ export default {
100
100
  | `lockedFields` | `[]` | Field keys rendered read-only (disabled) and immutable — typically the parent reference seeded via `initialData` so the user can't repoint a child away from the record it was created under. |
101
101
  | `excludeFields` | `[]` | Array of field keys to exclude from the auto-generated form. |
102
102
  | `includeFields` | `null` | Array of field keys to include (whitelist mode). Null means all fields. |
103
- | `fieldOverrides` | `{}` | Per-field override objects passed to `fieldsFromSchema`. |
103
+ | `fieldOverrides` | `{}` | Per-field override objects passed to `fieldsFromSchema`. A `widget` that names a registered component (v2 registry, then `customComponents`) mounts it as the field's control, with `value`/`modelValue`, `field`, `error` and `disabled`, writing back on `input` or `update:modelValue`. |
104
104
  | `nameField` | `'title'` | Which field is the "name" of the item (used in result messages). |
105
105
  | `size` | `'normal'` | NcDialog size — `'small'`, `'normal'`, or `'large'`. |
106
106
  | `dynamicLoadingLabel` | `'Loading the fields this choice adds.'` | Text shown while the fields a chosen value brings with it are being fetched. See *Fields the data decides* below. |
@@ -131,6 +131,22 @@
131
131
  :error="errors[field.key]"
132
132
  :updateField="updateField" />
133
133
 
134
+ <!-- A `widget` naming a registered component (v2 registry or
135
+ customComponents) mounts that component as the control.
136
+ It gets `value`/`modelValue`, `field`, `error` and
137
+ `disabled`, and writes back with `input` or
138
+ `update:modelValue`. -->
139
+ <component
140
+ :is="resolveFieldComponent(field)"
141
+ v-else-if="resolveFieldComponent(field)"
142
+ :value="formData[field.key]"
143
+ :modelValue="formData[field.key]"
144
+ :field="field"
145
+ :error="errors[field.key] || ''"
146
+ :disabled="!!field.readOnly"
147
+ @input="value => updateField(field.key, value)"
148
+ @update:modelValue="value => updateField(field.key, value)" />
149
+
134
150
  <!-- referenceType (AD-18): render the integration's
135
151
  single-entity widget instead of a plain input. -->
136
152
  <component
@@ -314,8 +330,26 @@
314
330
  plain select. NcSelectUsers wraps NcSelect and merges the
315
331
  rest of these bindings through, so the async load, search
316
332
  and select machinery is unchanged either way. -->
333
+ <!-- `look: "search"` (a `fieldOverrides` key): a search field
334
+ with rows of icon or initials, name, a secondary line from
335
+ `optionSecondary` and a check on the chosen one
336
+ (DqOverdragen "Nieuw team"). -->
337
+ <CnSearchSelect
338
+ v-if="field.look === 'search' && !isUserField(field)"
339
+ :inputId="'cn-form-' + field.key"
340
+ :inputLabel="fieldLabelText(field)"
341
+ :labelOutside="isBoardLook"
342
+ :options="searchSelectOptions(field)"
343
+ :modelValue="getEffectiveSelectedOption(field)"
344
+ :clearable="!field.required"
345
+ :disabled="field.readOnly"
346
+ :loading="isFieldLoading(field)"
347
+ :filterable="!isAsyncEnum(field)"
348
+ @update:modelValue="onSearchSelectChange(field, $event)"
349
+ @search="isAsyncEnum(field) ? onAsyncSearch(field, $event) : undefined" />
317
350
  <component
318
351
  :is="isUserField(field) ? 'NcSelectUsers' : 'NcSelect'"
352
+ v-else
319
353
  v-cn-select-aria="fieldAria(field)"
320
354
  :inputId="'cn-form-' + field.key"
321
355
  :inputLabel="fieldLabelText(field)"
@@ -799,6 +833,7 @@ import CnIconBrowser from '../CnIconBrowser/CnIconBrowser.vue'
799
833
  import CnJsonViewer from '../CnJsonViewer/CnJsonViewer.vue'
800
834
  import CnPropertySourceField from '../CnPropertySourceField/CnPropertySourceField.vue'
801
835
  import CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'
836
+ import CnSearchSelect from '../CnSearchSelect/CnSearchSelect.vue'
802
837
  import CnSubObjectsField from '../CnSubObjectsField/CnSubObjectsField.vue'
803
838
  import { describeRowProblem, useChildRecords } from '../../composables/useChildRecords.js'
804
839
  import { clearAnonymousDrafts, draftIndicatorText, draftKey, formDraftMixin, hasDraftChanges, readDraft, resolveDraftUserId } from '../../composables/useFormDraft.js'
@@ -821,6 +856,7 @@ import {
821
856
  splitDynamicFormData,
822
857
  } from '../../utils/dynamicProperties.js'
823
858
  import { shouldShow } from '../../utils/fieldCondition.js'
859
+ import { fillTemplate } from '../../utils/fillTemplate.js'
824
860
  import { computeAssignments } from '../../utils/formAssign.js'
825
861
  import { resolveNextcloudGroup, searchNextcloudGroups } from '../../utils/groupAutocomplete.js'
826
862
  import { durationSeconds } from '../../utils/isoDuration.js'
@@ -844,6 +880,36 @@ import { resolveNextcloudUser, searchNextcloudUsers } from '../../utils/userAuto
844
880
  */
845
881
  const WIDE_WIDGETS = ['textarea', 'json', 'code']
846
882
 
883
+ // The widget names the form draws itself; a registered component cannot take one over.
884
+ const BUILT_IN_WIDGETS = new Set([
885
+ 'text',
886
+ 'email',
887
+ 'url',
888
+ 'number',
889
+ 'textarea',
890
+ 'switch',
891
+ 'select',
892
+ 'user',
893
+ 'group',
894
+ 'multiselect',
895
+ 'user-multiselect',
896
+ 'group-multiselect',
897
+ 'tags',
898
+ 'checkbox',
899
+ 'date',
900
+ 'datetime',
901
+ 'json',
902
+ 'code',
903
+ 'file',
904
+ 'duration',
905
+ 'child-records',
906
+ 'sub-objects',
907
+ 'property-source',
908
+ 'icon',
909
+ 'language',
910
+ 'timezone',
911
+ ])
912
+
847
913
  /** Largest file carried inline in the saved payload; bigger ones upload after save. */
848
914
  const INLINE_FILE_CAP = 1024 * 1024
849
915
 
@@ -1033,6 +1099,7 @@ export default {
1033
1099
  CnFileField,
1034
1100
  CnDurationField,
1035
1101
  CnPropertySourceField,
1102
+ CnSearchSelect,
1036
1103
  CnReplaceValuesDialog,
1037
1104
  CnSubObjectsField,
1038
1105
  CnChildRecordsField,
@@ -2029,6 +2096,28 @@ export default {
2029
2096
  },
2030
2097
 
2031
2098
  methods: {
2099
+ /**
2100
+ * Resolve the registered component a field's `widget` names: the v2
2101
+ * registry (any entry with a `component`) first, then the legacy
2102
+ * customComponents map. Built-in widget names never resolve here.
2103
+ *
2104
+ * @param {object} field A resolved field descriptor.
2105
+ * @return {object|null} Vue component, or null.
2106
+ */
2107
+ resolveFieldComponent(field) {
2108
+ const name = field && field.widget
2109
+ if (typeof name !== 'string' || name === '' || BUILT_IN_WIDGETS.has(name)) {
2110
+ return null
2111
+ }
2112
+ const entry = this.cnRegistry && this.cnRegistry[name]
2113
+ if (entry && entry.component) {
2114
+ return entry.component
2115
+ }
2116
+ const legacy = this.cnCustomComponents && this.cnCustomComponents[name]
2117
+ // A bare function in the legacy map is a handler, not a component.
2118
+ return legacy && typeof legacy === 'object' ? legacy : null
2119
+ },
2120
+
2032
2121
  /**
2033
2122
  * Resolve a field's reference integration widget, if any.
2034
2123
  * Returns the integration's single-entity widget component
@@ -4337,6 +4426,44 @@ export default {
4337
4426
  * @param {object} field The field definition
4338
4427
  * @return {Array}
4339
4428
  */
4429
+ /**
4430
+ * The options of a `look: "search"` field as CnSearchSelect rows: each
4431
+ * option's own `secondary` / `icon` / `avatar`, else the field's
4432
+ * `optionSecondary` template (filled from the option), `optionIcon` and
4433
+ * `optionAvatar`.
4434
+ *
4435
+ * @spec openspec/changes/search-select-look/specs/search-select/spec.md#requirement-a-single-picker-can-take-the-search-look
4436
+ * @param {object} field The field definition.
4437
+ * @return {Array<object>}
4438
+ */
4439
+ searchSelectOptions(field) {
4440
+ const template = typeof field.optionSecondary === 'string' ? field.optionSecondary : ''
4441
+ return this.getEffectiveOptions(field).map((option) => ({
4442
+ ...option,
4443
+ secondary: option.secondary || (template ? fillTemplate(template, option).trim() : ''),
4444
+ icon: option.icon || field.optionIcon || '',
4445
+ avatar: option.avatar ?? (field.optionAvatar === true),
4446
+ }))
4447
+ },
4448
+
4449
+ /**
4450
+ * Pick the option a CnSearchSelect field emitted by its id, and hand
4451
+ * it to the select change handler.
4452
+ *
4453
+ * @spec openspec/changes/search-select-look/specs/search-select/spec.md#requirement-a-single-picker-can-take-the-search-look
4454
+ * @param {object} field The field definition.
4455
+ * @param {string|number|null} id The chosen option's id.
4456
+ */
4457
+ onSearchSelectChange(field, id) {
4458
+ if (id === null || id === undefined) {
4459
+ this.onEffectiveSelectChange(field, null)
4460
+ return
4461
+ }
4462
+ const option = this.searchSelectOptions(field).find((o) => String(o.id ?? o.value) === String(id)) || { id, label: String(id) }
4463
+ const { secondary, icon, avatar, ...plain } = option
4464
+ this.onEffectiveSelectChange(field, plain)
4465
+ },
4466
+
4340
4467
  getEffectiveOptions(field) {
4341
4468
  if (this.isAsyncEnum(field)) {
4342
4469
  // TODO: restore to `this.asyncState[field.key]?.options || []` once on Vue 3 (buble doesn't support optional chaining)
@@ -362,7 +362,7 @@ export default {
362
362
  | `lensReasonTexts` | Object \| null | `null` | App wording for an unavailable lens, keyed `<lens>.<reason>` or `<reason>`. |
363
363
  | `loadingText` | String | `'Loading…'` | Accessible label for the loading spinner (NcLoadingIcon aria-label) |
364
364
  | `rowIcon` | String \| Function | `null` | Optional leading icon for every table row — a static MDI name or `(row) => iconName`. Forwarded to CnDataTable; fed from the manifest as `pages[].config.rowIcon`. Unset = no icon column. |
365
- | `rowClass` | Function | `null` | Callback returning CSS class(es) for a row |
365
+ | `rowClass` | Function \| Array | `null` | Callback returning CSS class(es) for a row, or (manifest `config.rowClass`) rules `[{ when: { field, op?, value }, class }]` compiled into one: each adds its class to a row whose field compares to the value (`visibleWhen` operators; two dates compare as moments, `@today` included). |
366
366
  | `rowIcon` | String \| Function | `null` | Optional leading row icon forwarded to CnDataTable — a static MDI icon name or `(row) => iconName`. Fed from the manifest as `pages[].config.rowIcon`; unset = no icon column |
367
367
  | `inlineActionCount` | Number | `2` | How many row actions to show inline (rest go in overflow menu) |
368
368
  | `showMassImport` | Boolean | `true` | Whether to show the mass Import action |
@@ -414,6 +414,7 @@ export default {
414
414
  | `quickFilterMode` | String | `'chips'` | How the quick filters render: `'chips'` (pill strip) or `'dropdown'` (a single `NcSelect`). Fed from `pages[].config.quickFilterMode`. |
415
415
  | `quickFilterMultiple` | Boolean | `false` | Allow several quick filters active at once; selected tabs' filters are OR-ed into the fetch (same field → `field[]=` array). Fed from `pages[].config.quickFilterMultiple`. |
416
416
  | `quickFilterMaxVisible` | Number | `0` | Chips mode only — how many quick-filter pills render inline before the rest move behind one more chip — a `⋯` pill that opens a panel of the hidden lenses. `0` renders every tab, which wraps the actions bar onto a second line once a page declares more than a handful. The visible set is the first *n* entries of `quickFilters`. Fed from `pages[].config.quickFilterMaxVisible`. |
417
+ | `quickFilterPlacement` | String | `''` | Where the quick-filter chips sit. `'toolbar'` keeps them on one row with the Filter button and the view switch, in whatever width that row has beside a side panel (DqTeamwachtrij): the chips take the room the buttons leave, and those that do not fit move behind the overflow chip. Fed from `pages[].config.quickFilterPlacement`. |
417
418
  | `cardComponent` | String | `''` | Optional name of a consumer-provided card component (registered in the v2 `registry` — any kind carrying a `component` — or in the legacy `customComponents` map on `CnAppRoot`) to render in place of the default `CnObjectCard` when the page is in card-grid view mode. Resolution priority: `#card` scoped slot → `cardComponent` registry entry → default `CnObjectCard`. Unknown names log `console.warn` once and fall back to the default. |
418
419
  | `customComponents` | Object | `null` | Optional explicit `customComponents` registry. Overrides the registry injected from `CnAppRoot` via `cnCustomComponents`. Mostly used by unit tests; production consumers register components on `CnAppRoot` instead. |
419
420
  | `subscribe` | Boolean | `true` | Self-fetch mode only. When `register` + `schema` are set (and no `objects` prop), the page subscribes to the collection's `or-collection-{register}-{schema}` live-update scope on mount and refetches on an update event; released on unmount. Set `false` (manifest: `config.subscribe: false`) for static / read-once views. No-op in consumer-managed mode or on stores without live-updates support. |
@@ -1093,6 +1094,16 @@ Sources: `register` (fetch the folder list from an OpenRegister `register`/`sche
1093
1094
  }
1094
1095
  ```
1095
1096
 
1097
+ `pane: false` draws no pane. The folder still comes from the deep-link filter (`/cases?caseType=<uuid>`) and its scope look (title, count line, columns, quick filters, `afterList` from the folder or its row's `x-index`) still applies, so a list narrowed to one case type looks like that case type's list without a folder column (boards DqWooVerzoeken, DqKlachten):
1098
+
1099
+ ```json
1100
+ "folderSidebar": {
1101
+ "source": "register", "register": "dossiq", "schema": "caseType",
1102
+ "idField": "@self.uuid", "nameField": "title",
1103
+ "filterField": "caseType", "pane": false
1104
+ }
1105
+ ```
1106
+
1096
1107
  ### A column set per folder
1097
1108
 
1098
1109
  A folder is also a scope. Give it `columns`, `defaultSort` or `searchFields`
@@ -1136,3 +1147,6 @@ columns are right.
1136
1147
  ## Board look
1137
1148
 
1138
1149
  With `look: "board"` (the page's `look` prop, `config.look`, or the app's) the index page takes the DqZaken setup: a header with the count line from `countText` (`{shown}`, `{total}`), the buttons Download, Actions, the buildiq square and the primary one in that order, a two-row toolbar (saved-view chips, Filter with a count and the view switch; then search and active filter chips), a bulk band between toolbar and table (`bulkHint`), the table as a white card with one menu button per row, and a footer inside the card (`footerNote`). `cardFields` names the facts of each card in the cards view. Without the look nothing changes.
1150
+
1151
+ `bulkBandAlways` (`config.bulkBandAlways`) draws the board bulk band before a selection too, its buttons
1152
+ disabled until rows are selected (DqZaken, DqWerkbord).
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="cn-index-page"
3
- :class="[lookClass, { 'cn-index-page--with-side-panel': hasSidePanel }]"
3
+ :class="[lookClass, { 'cn-index-page--with-side-panel': hasSidePanel, 'cn-index-page--quick-filters-toolbar': quickFilterPlacement === 'toolbar' }]"
4
4
  data-testid="cn-index-page"
5
5
  @keydown="onListKeydown">
6
6
  <!-- Header — overridable via #header slot. CnPageHeader ALWAYS renders:
@@ -163,6 +163,7 @@
163
163
  :showBuildiqButton="!buildiqInHeader"
164
164
  :bulkNoun="boardBulkNoun"
165
165
  :bulkHint="bulkHint ? cnTranslate(bulkHint) : bulkHint"
166
+ :bulkBandAlways="bulkBandAlways"
166
167
  :pagination="effectivePagination"
167
168
  :objectCount="effectiveObjects.length"
168
169
  :selectable="selectable"
@@ -369,6 +370,7 @@
369
370
  :tabs="tabStripEntries"
370
371
  :mode="quickFilterMode"
371
372
  :maxVisible="quickFilterMaxVisible"
373
+ :fit="quickFilterPlacement === 'toolbar'"
372
374
  :multiple="quickFilterMultiple"
373
375
  :activeIndex="activeQuickFilterIndex"
374
376
  :selectedIndices="selectedQuickFilterIndices"
@@ -658,11 +660,12 @@
658
660
  </slot>
659
661
 
660
662
  <!-- Body -->
661
- <div class="cn-index-page__body" :class="{ 'cn-index-page__body--with-folders': folderSidebar }">
663
+ <div class="cn-index-page__body" :class="{ 'cn-index-page__body--with-folders': showsFolderPane }">
662
664
  <!-- Optional folder navigation pane (opt-in via the `folderSidebar`
663
665
  config). Selecting a folder filters the list by the config's
664
- `filterField`; "All" clears it. -->
665
- <div v-if="folderSidebar" class="cn-index-page__folder-pane">
666
+ `filterField`; "All" clears it. `pane: false` keeps the scope
667
+ from the deep link and draws no pane. -->
668
+ <div v-if="showsFolderPane" class="cn-index-page__folder-pane">
666
669
  <CnFolderSidebar
667
670
  :source="folderSidebarSource"
668
671
  :folders="folderSidebarFolders"
@@ -755,7 +758,7 @@
755
758
  :excludeColumns="excludeColumns"
756
759
  :includeColumns="includeColumns"
757
760
  :columnOverrides="columnOverrides"
758
- :rowClass="rowClass"
761
+ :rowClass="compiledRowClass"
759
762
  :rowTitle="rowTitle"
760
763
  :showUnread="showUnreadRows"
761
764
  :filterable="tableHeaderFilters"
@@ -1292,6 +1295,7 @@ import { resolveFilterMap } from '../../utils/routeFilters.js'
1292
1295
  import { availableRowActions, DEFAULT_ROW_ACTION_FIELD, refusalReasonFor, undeclaredRowActions, withoutViewWhenRowOpensDetail } from '../../utils/rowActionAvailability.js'
1293
1296
  import { isRowActionVisible, rowActionPayload } from '../../utils/rowActionItem.js'
1294
1297
  import { isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
1298
+ import { compileRowClass } from '../../utils/rowClassRules.js'
1295
1299
  import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js'
1296
1300
  import { buildRouteQueryFromViewState, buildViewCreatePayload, extractViewState, extractViewStateFromRouteQuery, normalizeSharedWith, savedViewScope, viewMatchesScope } from '../../utils/savedViewHelpers.js'
1297
1301
  import { columnsFromSchema, fieldsFromSchema } from '../../utils/schema.js'
@@ -1938,6 +1942,17 @@ export default {
1938
1942
  default: '',
1939
1943
  },
1940
1944
 
1945
+ /**
1946
+ * Board look: draw the bulk band before anything is selected too, its
1947
+ * buttons disabled until there is a selection (DqZaken, DqWerkbord).
1948
+ * Manifest key `config.bulkBandAlways`. Only when the band has an
1949
+ * action to offer.
1950
+ */
1951
+ bulkBandAlways: {
1952
+ type: Boolean,
1953
+ default: false,
1954
+ },
1955
+
1941
1956
  /**
1942
1957
  * Board look: the 13px hint after the bulk band's buttons ("See what
1943
1958
  * changes first, then run it"). Manifest key `config.bulkHint`.
@@ -2119,6 +2134,21 @@ export default {
2119
2134
  default: null,
2120
2135
  },
2121
2136
 
2137
+ /**
2138
+ * Where the quick-filter chips sit (manifest `config.quickFilterPlacement`).
2139
+ * `''` (the default) as before. `'toolbar'` keeps them on one row with
2140
+ * the Filter button and the view switch, in whatever width that row has
2141
+ * beside a side panel (DqTeamwachtrij): the chips take the room the
2142
+ * buttons leave, and those that do not fit move into the overflow chip.
2143
+ *
2144
+ * @type {''|'toolbar'}
2145
+ */
2146
+ quickFilterPlacement: {
2147
+ type: String,
2148
+ default: '',
2149
+ validator: (v) => ['', 'toolbar'].includes(v),
2150
+ },
2151
+
2122
2152
  /**
2123
2153
  * How the quick filters render: `'chips'` (pill strip, default) or
2124
2154
  * `'dropdown'` (a single `NcSelect`). Sourced from the manifest as
@@ -2695,9 +2725,18 @@ export default {
2695
2725
  default: 'Loading…',
2696
2726
  },
2697
2727
 
2698
- /** Function returning CSS class(es) for a row */
2728
+ /**
2729
+ * CSS class(es) for a row: a function `(row) => string`, or (manifest
2730
+ * `config.rowClass`) rules `[{ when: { field, op?, value }, class }]`,
2731
+ * each adding its class to a row whose field compares to the value
2732
+ * (the `visibleWhen` operators, `@today` included). DqTaken marks an
2733
+ * overdue task: `{ when: { field: "dueAt", op: "lt", value: "@today" },
2734
+ * class: "cn-row--overdue" }`.
2735
+ *
2736
+ * @type {((row: object) => string)|Array<{when: {field: string, op?: string, value: unknown}, class: string}>|null}
2737
+ */
2699
2738
  rowClass: {
2700
- type: Function,
2739
+ type: [Function, Array],
2701
2740
  default: null,
2702
2741
  },
2703
2742
 
@@ -3162,6 +3201,9 @@ export default {
3162
3201
  * defaults to `field`).
3163
3202
  * - `folders` — explicit folder list for `source:'custom'`.
3164
3203
  * - `allLabel` / `title` / `allowCreate` — passed to CnFolderSidebar.
3204
+ * - `pane` — `false` draws no pane: the scope still comes from the
3205
+ * deep-link filter (`?caseType=<id>`) and still applies its look, as
3206
+ * a list narrowed to one case type does without a folder column.
3165
3207
  *
3166
3208
  * A folder entry is also a SCOPE: it may carry `columns`, `defaultSort`
3167
3209
  * and `searchFields`, and while it is selected the list is shown that
@@ -3779,6 +3821,10 @@ export default {
3779
3821
  //
3780
3822
  // Seeded from the route query so a shared link lands filtered, the
3781
3823
  // same way `_order` and a deep-link filter already do for self-fetch.
3824
+ // The page a paging named source shows: page 1 at the source config's
3825
+ // `limit` (20 by default), moved by the pager.
3826
+ const namedPageSize = Number(props.sourceConfig && props.sourceConfig.limit) > 0 ? Number(props.sourceConfig.limit) : 20
3827
+ const namedPaging = ref({ page: 1, limit: namedPageSize })
3782
3828
  const namedActiveFilters = ref(namedFiltersFromRoute(getCurrentInstance(), props))
3783
3829
 
3784
3830
  const {
@@ -3787,7 +3833,7 @@ export default {
3787
3833
  namedRows,
3788
3834
  namedLoading,
3789
3835
  namedQuickFilters,
3790
- } = useNamedSource(props, { activeQuickFilterIndex, activeFilters: namedActiveFilters })
3836
+ } = useNamedSource(props, { activeQuickFilterIndex, activeFilters: namedActiveFilters, paging: namedPaging })
3791
3837
 
3792
3838
  return {
3793
3839
  isBoard: isBoardLook,
@@ -3799,6 +3845,7 @@ export default {
3799
3845
  namedLoading,
3800
3846
  namedQuickFilters,
3801
3847
  namedActiveFilters,
3848
+ namedPaging,
3802
3849
  contextMenuOpen,
3803
3850
  contextMenuRow,
3804
3851
  openContextMenu,
@@ -4761,6 +4808,9 @@ export default {
4761
4808
  const loaded = this.effectivePagination || {}
4762
4809
  return Number(loaded.page || 1) < Number(loaded.pages || 1)
4763
4810
  }
4811
+ if (this.isNamedSource && this.namedSource && this.namedSource.pages === true) {
4812
+ return Number(this.effectivePagination?.pages || 1) > 1
4813
+ }
4764
4814
  return Number(this.pagination?.pages || 1) > 1
4765
4815
  },
4766
4816
 
@@ -4787,6 +4837,17 @@ export default {
4787
4837
  return { shown, total: Math.max(shown, Number(this.effectivePagination?.total || 0)) }
4788
4838
  },
4789
4839
 
4840
+ /**
4841
+ * Whether the folder pane is drawn: a `folderSidebar` without
4842
+ * `pane: false`.
4843
+ *
4844
+ * @spec openspec/changes/folder-sidebar-pane-off/specs/index-page/spec.md#requirement-a-folder-scope-without-a-pane
4845
+ * @return {boolean}
4846
+ */
4847
+ showsFolderPane() {
4848
+ return Boolean(this.folderSidebar) && this.folderSidebar.pane !== false
4849
+ },
4850
+
4790
4851
  /**
4791
4852
  * The CnFolderSidebar source. A `register` source is fetched by this
4792
4853
  * component and handed to CnFolderSidebar as a `custom` folder list.
@@ -5800,7 +5861,13 @@ export default {
5800
5861
  // A named source that reports its own total (the search source)
5801
5862
  // gives the count line its number.
5802
5863
  if (this.isNamedSource && this.namedSource && typeof this.namedSource.total === 'function') {
5803
- return { ...(this.pagination || {}), total: this.namedSource.total() }
5864
+ const total = this.namedSource.total()
5865
+ // A source with `pages: true` gets the pager the self-fetched lists draw.
5866
+ if (this.namedSource.pages === true) {
5867
+ const { page, limit } = this.namedPaging
5868
+ return { page, limit, total, pages: Math.max(1, Math.ceil(total / limit)) }
5869
+ }
5870
+ return { ...(this.pagination || {}), total }
5804
5871
  }
5805
5872
  return this.isSelfFetchMode ? this.list.pagination.value : this.pagination
5806
5873
  },
@@ -6053,6 +6120,16 @@ export default {
6053
6120
  return (this.effectiveSchema && this.effectiveSchema.slug) || ''
6054
6121
  },
6055
6122
 
6123
+ /**
6124
+ * The row-class function the table takes: a function as given, or the
6125
+ * manifest's rules compiled.
6126
+ *
6127
+ * @return {?(row: object) => string}
6128
+ */
6129
+ compiledRowClass() {
6130
+ return compileRowClass(this.rowClass)
6131
+ },
6132
+
6056
6133
  /** @return {number} How many leading columns the person pinned (0 while a saved view's columns are applied). */
6057
6134
  pinnedColumnCount() {
6058
6135
  if (!this.personalColumns || Array.isArray(this.appliedViewColumns) || !this.personalLayout) {
@@ -8777,6 +8854,9 @@ export default {
8777
8854
  if (this.isSelfFetchMode && typeof this.list.onPageChange === 'function') {
8778
8855
  this.list.onPageChange(page)
8779
8856
  }
8857
+ if (this.isNamedSource && this.namedSource && this.namedSource.pages === true) {
8858
+ this.namedPaging = { ...this.namedPaging, page }
8859
+ }
8780
8860
  this.$emit('page-changed', page)
8781
8861
  },
8782
8862
 
@@ -8792,6 +8872,9 @@ export default {
8792
8872
  if (this.isSelfFetchMode && typeof this.list.onPageSizeChange === 'function') {
8793
8873
  this.list.onPageSizeChange(size)
8794
8874
  }
8875
+ if (this.isNamedSource && this.namedSource && this.namedSource.pages === true) {
8876
+ this.namedPaging = { page: 1, limit: size }
8877
+ }
8795
8878
  this.$emit('page-size-changed', size)
8796
8879
  },
8797
8880
 
@@ -105,6 +105,9 @@ export function useNamedSource(props, options = {}) {
105
105
  const tabs = manifestTabs || sourceTabs
106
106
  const activeIndex = options.activeQuickFilterIndex || null
107
107
  const activeFilters = options.activeFilters || null
108
+ // A source that pages (`pages: true`) gets the page as `limit` and
109
+ // `offset`, from the pager the page draws for it.
110
+ const paging = source.pages === true ? (options.paging || null) : null
108
111
 
109
112
  // Seed the default tab before the watcher below registers: the mount load
110
113
  // already carries this tab's filter, so the seed must not fire a second
@@ -165,6 +168,7 @@ export function useNamedSource(props, options = {}) {
165
168
  activeFilters ? activeFilters.value : null,
166
169
  props.entitySource,
167
170
  ),
171
+ ...(paging ? { limit: paging.value.limit, offset: (paging.value.page - 1) * paging.value.limit } : {}),
168
172
  },
169
173
  tokenCtx(),
170
174
  )
@@ -182,15 +186,35 @@ export function useNamedSource(props, options = {}) {
182
186
 
183
187
  // Registered AFTER the seed above, so only a person switching tabs
184
188
  // reloads. Without tabs there is nothing to watch.
189
+ /**
190
+ * Reload for a new query: on a paging source back to page 1, which
191
+ * reloads through the paging watcher below.
192
+ *
193
+ * @return {void}
194
+ */
195
+ function reloadFromFirstPage() {
196
+ if (paging && paging.value.page !== 1) {
197
+ paging.value = { ...paging.value, page: 1 }
198
+ return
199
+ }
200
+ loadActive()
201
+ }
202
+
185
203
  if (tabs && activeIndex) {
186
- watch(activeIndex, loadActive)
204
+ watch(activeIndex, reloadFromFirstPage)
187
205
  }
188
206
 
189
207
  // A sidebar choice is a new QUERY, not a new view of the page already
190
208
  // fetched, so it reloads. `deep` because the map is rewritten key by key
191
209
  // and a shallow watch misses a second value added to the same field.
192
210
  if (activeFilters) {
193
- watch(activeFilters, loadActive, { deep: true })
211
+ watch(activeFilters, reloadFromFirstPage, { deep: true })
212
+ }
213
+
214
+ // A new page, or a new page size, is a new request. A new tab or filter
215
+ // first returns to page 1, which reloads through here.
216
+ if (paging) {
217
+ watch(paging, loadActive, { deep: true })
194
218
  }
195
219
 
196
220
  // No manual invalidation tick here on purpose. The adapters read from a