@conduction/nextcloud-vue 2.61.0 → 2.63.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 (186) 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 +394 -27
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +79 -2
  15. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  16. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +38 -25
  17. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  18. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +21 -1
  19. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -1
  20. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +4 -1
  21. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -1
  22. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  23. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
  24. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +30 -6
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
  30. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
  31. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
  32. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
  33. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
  34. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
  35. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +256 -23
  36. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +119 -51
  38. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  39. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  41. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  42. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
  43. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
  45. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
  47. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
  49. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +51 -2
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  52. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  54. package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
  55. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  56. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
  57. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
  58. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +36 -1
  59. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
  61. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  62. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +53 -0
  63. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  64. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +22 -5
  65. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  66. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  67. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
  69. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
  70. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
  71. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
  72. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
  73. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  74. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
  75. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  76. package/dist/esm/components/CnStorePage/CnStorePage.vue2.js.map +1 -1
  77. package/dist/esm/components/CnStorePage/CnStorePage.vue3.js.map +1 -1
  78. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
  79. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
  80. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
  81. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
  82. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
  83. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
  84. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
  85. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
  86. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
  87. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  88. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
  89. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  90. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
  91. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  92. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  93. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  94. package/dist/esm/components/CnWikiPage/CnWikiPage.vue2.js.map +1 -1
  95. package/dist/esm/components/CnWikiPage/CnWikiPage.vue3.js.map +1 -1
  96. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
  97. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  98. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
  99. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  100. package/dist/esm/composables/useDependencyCheck.js +65 -0
  101. package/dist/esm/composables/useDependencyCheck.js.map +1 -0
  102. package/dist/esm/composables/useWalkthrough.js +218 -6
  103. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  104. package/dist/esm/index.js +2 -0
  105. package/dist/esm/index.js.map +1 -1
  106. package/dist/esm/l10n/en.json.js +48 -0
  107. package/dist/esm/l10n/en.json.js.map +1 -1
  108. package/dist/esm/l10n/nl.json.js +48 -0
  109. package/dist/esm/l10n/nl.json.js.map +1 -1
  110. package/dist/esm/nextcloud-vue.css +557 -9
  111. package/dist/esm/store/useObjectStore.js +8 -0
  112. package/dist/esm/store/useObjectStore.js.map +1 -1
  113. package/dist/esm/utils/columnFilters.js +311 -0
  114. package/dist/esm/utils/columnFilters.js.map +1 -0
  115. package/dist/esm/utils/groupAutocomplete.js +123 -0
  116. package/dist/esm/utils/groupAutocomplete.js.map +1 -0
  117. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  118. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  119. package/dist/esm/utils/pickerOptions.js +198 -0
  120. package/dist/esm/utils/pickerOptions.js.map +1 -0
  121. package/dist/esm/utils/schema.js +83 -7
  122. package/dist/esm/utils/schema.js.map +1 -1
  123. package/dist/esm/utils/timelineEvents.js +174 -0
  124. package/dist/esm/utils/timelineEvents.js.map +1 -0
  125. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  126. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  127. package/dist/esm/utils/walkthroughSignals.js +88 -0
  128. package/dist/esm/utils/walkthroughSignals.js.map +1 -0
  129. package/dist/nextcloud-vue.cjs.js +9779 -5236
  130. package/dist/nextcloud-vue.cjs.js.map +2 -2
  131. package/dist/nextcloud-vue.css +557 -9
  132. package/l10n/en.json +48 -0
  133. package/l10n/nl.json +48 -0
  134. package/package.json +3 -2
  135. package/scripts/manifest-schema-hash.js +41 -0
  136. package/scripts/record-manifest-schema-hash.js +52 -0
  137. package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
  138. package/src/components/CnAdminActionCard/index.js +4 -0
  139. package/src/components/CnAppNav/CnAppNav.vue +394 -27
  140. package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
  141. package/src/components/CnBannerWidget/CnBannerWidget.vue +113 -2
  142. package/src/components/CnBrandStripe/CnBrandStripe.md +16 -0
  143. package/src/components/CnBrandStripe/CnBrandStripe.vue +58 -5
  144. package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
  145. package/src/components/CnDashboardPage/CnDashboardPage.vue +32 -8
  146. package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
  147. package/src/components/CnDataTable/CnDataTable.md +4 -1
  148. package/src/components/CnDataTable/CnDataTable.vue +302 -21
  149. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
  150. package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
  151. package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
  152. package/src/components/CnIndexPage/CnIndexPage.vue +55 -2
  153. package/src/components/CnIndexPage/selfModeActions.js +3 -3
  154. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +35 -2
  155. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
  156. package/src/components/CnObjectSidebar/CnFilesTab.vue +65 -0
  157. package/src/components/CnReportsPage/CnReportsPage.vue +7 -0
  158. package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
  159. package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
  160. package/src/components/CnStorePage/CnStorePage.vue +7 -0
  161. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
  162. package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
  163. package/src/components/CnTimelineWidget/index.js +4 -0
  164. package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
  165. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  166. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  167. package/src/components/CnWikiPage/CnWikiPage.vue +7 -0
  168. package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
  169. package/src/components/index.js +2 -0
  170. package/src/composables/useDependencyCheck.js +61 -0
  171. package/src/composables/useWalkthrough.js +217 -5
  172. package/src/css/dashboard.css +49 -0
  173. package/src/css/detail-page.css +19 -0
  174. package/src/css/table.css +82 -2
  175. package/src/index.js +2 -0
  176. package/src/public/index.js +5 -0
  177. package/src/schemas/app-manifest-v2.schema.json +23 -1
  178. package/src/store/useObjectStore.js +8 -0
  179. package/src/utils/columnFilters.js +324 -0
  180. package/src/utils/groupAutocomplete.js +119 -0
  181. package/src/utils/libraryWidgetKeys.js +2 -0
  182. package/src/utils/pickerOptions.js +194 -0
  183. package/src/utils/schema.js +83 -7
  184. package/src/utils/timelineEvents.js +171 -0
  185. package/src/utils/validateManifestV2.compiled.js +2 -2
  186. package/src/utils/walkthroughSignals.js +94 -0
@@ -1,16 +1,20 @@
1
1
  import axios__default from '@nextcloud/axios';
2
- import { translate } from '@nextcloud/l10n';
2
+ import { translate, translatePlural } from '@nextcloud/l10n';
3
3
  import { generateUrl } from '@nextcloud/router';
4
4
  import { NcLoadingIcon, NcCheckboxRadioSwitch } from '@nextcloud/vue';
5
+ import FilterIcon from 'vue-material-design-icons/Filter.vue';
6
+ import FilterOutline from 'vue-material-design-icons/FilterOutline.vue';
7
+ import __sfc_main$1 from './CnColumnFilterPopover.vue.js';
5
8
  import { useClickDragGuard } from '../../composables/useClickDragGuard.js';
9
+ import { columnFilterDef, columnFilterState, isColumnFilterActive, columnFilterParams, clearedColumnFilterParams, isColumnSortable } from '../../utils/columnFilters.js';
6
10
  import { resolveHref, openRowTarget, followLinkClick } from '../../utils/linkNavigation.js';
7
11
  import { nextSortState } from '../../utils/multiColumnSort.js';
8
12
  import { preventMiddleClickAutoscroll, isRowMiddleClick, isNewTabHandled, markNewTabHandled, isNewTabClick } from '../../utils/rowAuxClick.js';
9
13
  import { DEFAULT_ROW_INDICATOR_CAP, resolveRowIndicators } from '../../utils/rowIndicators.js';
10
14
  import { columnsFromSchema } from '../../utils/schema.js';
11
15
  import __sfc_main from '../CnCellRenderer/CnCellRenderer.vue.js';
12
- import __sfc_main$1 from '../CnIcon/CnIcon.vue.js';
13
- import __sfc_main$2 from '../CnLockIndicator/CnLockIndicator.vue.js';
16
+ import __sfc_main$2 from '../CnIcon/CnIcon.vue.js';
17
+ import __sfc_main$3 from '../CnLockIndicator/CnLockIndicator.vue.js';
14
18
 
15
19
  /**
16
20
  * CnDataTable — Generic sortable data table for list views.
@@ -20,15 +24,12 @@ import __sfc_main$2 from '../CnLockIndicator/CnLockIndicator.vue.js';
20
24
  * row selection, custom cell rendering via scoped slots, loading states,
21
25
  * and empty states.
22
26
  *
23
- * Sorting: a plain click on a sortable header is single-sort (cycle asc →
24
- * desc → cleared), unchanged from before. Shift+click (or Shift+Enter on a
25
- * focused header) appends the column as a secondary/tertiary sort key —
26
- * capped at 3 — with numbered priority badges (1, 2, 3) once more than one
27
- * key is active. Pass `sortKeys: [{key, order}, ...]` for multi-sort (falls
28
- * back to the legacy `sortKey`/`sortOrder` props when empty). The `sort`
29
- * event payload is extended, not replaced: `{key, order}` still mirrors the
30
- * primary key exactly as before; a new `keys` field carries the full
31
- * ordered list. See `src/utils/multiColumnSort.js` for the state machine.
27
+ * Sorting: a plain click on a sortable header is single-sort (cycle asc → desc → cleared), unchanged from before.
28
+ * Shift+click (or Shift+Enter on a focused header) appends the column as a secondary/tertiary sort key, capped at 3, with numbered priority badges (1, 2, 3) once two or more rendered, sortable columns are sorted.
29
+ * A sort key whose column is not rendered, or not sortable, still sorts, but is not counted or numbered.
30
+ * Pass `sortKeys: [{key, order}, ...]` for multi-sort (falls back to the legacy `sortKey`/`sortOrder` props when empty).
31
+ * The `sort` event payload is extended, not replaced: `{key, order}` still mirrors the primary key exactly as before; a new `keys` field carries the full ordered list.
32
+ * See `src/utils/multiColumnSort.js` for the state machine.
32
33
  *
33
34
  * When a `schema` prop is provided, columns are auto-generated from schema
34
35
  * properties and cells render through CnCellRenderer for type-aware formatting
@@ -71,8 +72,11 @@ var script = {
71
72
  NcLoadingIcon,
72
73
  NcCheckboxRadioSwitch,
73
74
  CnCellRenderer: __sfc_main,
74
- CnIcon: __sfc_main$1,
75
- CnLockIndicator: __sfc_main$2,
75
+ CnColumnFilterPopover: __sfc_main$1,
76
+ CnIcon: __sfc_main$2,
77
+ CnLockIndicator: __sfc_main$3,
78
+ FilterIcon,
79
+ FilterOutline,
76
80
  },
77
81
 
78
82
  inject: {
@@ -441,9 +445,39 @@ var script = {
441
445
  type: Boolean,
442
446
  default: false,
443
447
  },
448
+
449
+ /**
450
+ * Show a filter button in every header whose column can filter (see
451
+ * `columnFilterDef`): enum, boolean, text, number, date and reference
452
+ * columns backed by a schema property. A column opts out with
453
+ * `filterable: false`. Applying a filter emits `column-filter`; the
454
+ * host owns the filter state and passes it back as `activeFilters`.
455
+ *
456
+ * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-every-backed-column-filters-from-its-header
457
+ */
458
+ filterable: {
459
+ type: Boolean,
460
+ default: false,
461
+ },
462
+
463
+ /**
464
+ * The active filter map, `{ paramKey: values[] }`: the same map the
465
+ * facet sidebar writes, so a header filter and a sidebar filter on one
466
+ * field show the same state.
467
+ */
468
+ activeFilters: {
469
+ type: Object,
470
+ default: () => ({}),
471
+ },
472
+
473
+ /** Register slug a reference column's filter searches in when the column names none. */
474
+ filterRegister: {
475
+ type: String,
476
+ default: '',
477
+ },
444
478
  },
445
479
 
446
- emits: ['row-click', 'row-aux-click', 'row-context-menu', 'select', 'select-all', 'sort', 'view-all'],
480
+ emits: ['row-click', 'row-aux-click', 'row-context-menu', 'select', 'select-all', 'sort', 'view-all', 'column-filter'],
447
481
 
448
482
  setup() {
449
483
  // Tell a deliberate row click apart from a text-selection drag.
@@ -452,6 +486,10 @@ var script = {
452
486
 
453
487
  data() {
454
488
  return {
489
+ /** Key of the column whose filter panel is open, or ''. */
490
+ openFilterKey: '',
491
+ /** The open filter button's bounding rect, to place the panel. */
492
+ filterAnchorRect: null,
455
493
  /**
456
494
  * Resolved aggregate-column values, keyed by `String(row[rowKey])`
457
495
  * then by column key. Populated by `loadAggregates()` for columns
@@ -569,8 +607,12 @@ var script = {
569
607
  overrides: this.columnOverrides,
570
608
  })
571
609
  : this.columns;
610
+ // Every column backed by a schema property sorts unless it says
611
+ // `sortable: false`; object columns from a manifest used to need an
612
+ // explicit flag and so could not sort at all.
613
+ const withSort = (c) => (c && typeof c === 'object' ? { ...c, sortable: isColumnSortable(c, this.schema) } : c);
572
614
  if (!(cols || []).some((c) => typeof c === 'string')) {
573
- return cols || []
615
+ return (cols || []).map(withSort)
574
616
  }
575
617
  const schemaCols = this.schema
576
618
  ? columnsFromSchema(this.schema, { overrides: this.columnOverrides })
@@ -578,7 +620,7 @@ var script = {
578
620
  const byKey = new Map(schemaCols.map((c) => [c.key, c]));
579
621
  return (cols || []).map((c) => {
580
622
  if (typeof c !== 'string') {
581
- return c
623
+ return withSort(c)
582
624
  }
583
625
  return byKey.get(c) || { key: c, label: c, sortable: true }
584
626
  })
@@ -619,6 +661,40 @@ var script = {
619
661
  return links
620
662
  },
621
663
 
664
+ /**
665
+ * The column whose filter panel is open, or null.
666
+ *
667
+ * @return {object|null}
668
+ */
669
+ openFilterColumn() {
670
+ if (!this.openFilterKey) {
671
+ return null
672
+ }
673
+ return this.effectiveColumns.find((c) => c && c.key === this.openFilterKey && this.filterDefFor(c)) || null
674
+ },
675
+
676
+ /**
677
+ * One chip per column with an active header filter.
678
+ *
679
+ * @return {Array<{key: string, col: object, text: string, removeLabel: string}>}
680
+ */
681
+ activeColumnFilterChips() {
682
+ if (!this.filterable) {
683
+ return []
684
+ }
685
+ return this.effectiveColumns
686
+ .filter((c) => c && this.isColumnFiltered(c))
687
+ .map((col) => {
688
+ const label = this.translateLabel(col.label);
689
+ const text = translate('nextcloud-vue', '{column}: {value}', { column: label, value: this.columnFilterSummary(col) });
690
+ return { key: col.key, col, text, removeLabel: translate('nextcloud-vue', 'Remove filter {column}', { column: label }) }
691
+ })
692
+ },
693
+
694
+ activeFiltersLabel() {
695
+ return translate('nextcloud-vue', 'Active filters')
696
+ },
697
+
622
698
  effectiveSortKeys() {
623
699
  if (this.sortKeys && this.sortKeys.length > 0) {
624
700
  return this.sortKeys
@@ -629,6 +705,23 @@ var script = {
629
705
  return []
630
706
  },
631
707
 
708
+ /**
709
+ * The active sort keys whose column renders a sort indicator, in priority order and deduplicated by first occurrence. Only these are counted and numbered by the priority badge.
710
+ *
711
+ * @return {Array<{key: string, order: 'asc'|'desc'}>}
712
+ */
713
+ renderedSortKeys() {
714
+ const shown = new Set(this.effectiveColumns.filter((c) => c && c.sortable).map((c) => c.key));
715
+ const seen = new Set();
716
+ return this.effectiveSortKeys.filter((k) => {
717
+ if (!k || !shown.has(k.key) || seen.has(k.key)) {
718
+ return false
719
+ }
720
+ seen.add(k.key);
721
+ return true
722
+ })
723
+ },
724
+
632
725
  totalColumns() {
633
726
  let count = this.effectiveColumns.length;
634
727
  if (this.selectable) {
@@ -1224,7 +1317,7 @@ var script = {
1224
1317
 
1225
1318
  /**
1226
1319
  * Index of `key` within `effectiveSortKeys`, or -1 when not active.
1227
- * Used by the template for the arrow, the numbered badge, and aria-sort.
1320
+ * Used by the template for the arrow.
1228
1321
  *
1229
1322
  * @param {string} key Column key.
1230
1323
  * @return {number}
@@ -1234,11 +1327,22 @@ var script = {
1234
1327
  },
1235
1328
 
1236
1329
  /**
1237
- * `aria-sort` value for a column header: `'ascending'`/`'descending'`
1238
- * for the PRIMARY (index 0) active sort key only — secondary/tertiary
1239
- * keys carry the visible numbered badge instead, per WCAG guidance
1240
- * that `aria-sort` describes single-column sort state. `null` omits
1241
- * the attribute entirely (unsorted / not sortable).
1330
+ * The 1-based priority badge for `key` among the rendered sort keys, or null when fewer than two rendered columns are sorted or `key` is not one of them.
1331
+ *
1332
+ * @param {string} key Column key.
1333
+ * @return {number|null}
1334
+ */
1335
+ sortBadgeFor(key) {
1336
+ if (this.renderedSortKeys.length < 2) {
1337
+ return null
1338
+ }
1339
+ const index = this.renderedSortKeys.findIndex((k) => k.key === key);
1340
+ return index === -1 ? null : index + 1
1341
+ },
1342
+
1343
+ /**
1344
+ * `aria-sort` value for a column header: `'ascending'`/`'descending'` for the PRIMARY (index 0) active sort key only, per WCAG guidance that `aria-sort` describes single-column sort state.
1345
+ * `null` omits the attribute entirely (unsorted, not sortable, or not the primary key).
1242
1346
  *
1243
1347
  * @param {object} col Column definition.
1244
1348
  * @return {string|null}
@@ -1262,6 +1366,135 @@ var script = {
1262
1366
  * @param {string} key Column key.
1263
1367
  * @param {MouseEvent} [event] The originating click event.
1264
1368
  */
1369
+ /**
1370
+ * The filter definition for a column, or null when it does not filter
1371
+ * (header filters off, `filterable: false`, or nothing to filter on).
1372
+ *
1373
+ * @param {object} col The column.
1374
+ * @return {object|null}
1375
+ */
1376
+ filterDefFor(col) {
1377
+ if (!this.filterable || !col) {
1378
+ return null
1379
+ }
1380
+ return columnFilterDef(col, this.schema)
1381
+ },
1382
+
1383
+ columnFilterStateFor(col) {
1384
+ return columnFilterState(this.filterDefFor(col), this.activeFilters)
1385
+ },
1386
+
1387
+ isColumnFiltered(col) {
1388
+ const def = this.filterDefFor(col);
1389
+ return !!def && isColumnFilterActive(def, this.activeFilters)
1390
+ },
1391
+
1392
+ filterButtonLabel(col) {
1393
+ const column = this.translateLabel(col.label);
1394
+ return this.isColumnFiltered(col)
1395
+ ? translate('nextcloud-vue', 'Filter {column}, active', { column })
1396
+ : translate('nextcloud-vue', 'Filter {column}', { column })
1397
+ },
1398
+
1399
+ /**
1400
+ * Short text for an active filter: the picked labels, or the range.
1401
+ *
1402
+ * @param {object} col The column.
1403
+ * @return {string}
1404
+ */
1405
+ columnFilterSummary(col) {
1406
+ const def = this.filterDefFor(col);
1407
+ const state = this.columnFilterStateFor(col);
1408
+ if (!def) {
1409
+ return ''
1410
+ }
1411
+ if (def.kind === 'enum') {
1412
+ const labels = Object.fromEntries((def.options || []).map((o) => [o.value, o.label]));
1413
+ return state.values.map((v) => this.translateLabel(labels[v] || v)).join(', ')
1414
+ }
1415
+ if (def.kind === 'reference') {
1416
+ return translatePlural('nextcloud-vue', '{count} selected', '{count} selected', state.values.length, { count: state.values.length })
1417
+ }
1418
+ if (def.kind === 'number' || def.kind === 'date') {
1419
+ if (state.from && state.to) {
1420
+ return translate('nextcloud-vue', '{from} to {to}', { from: state.from, to: state.to })
1421
+ }
1422
+ return state.from
1423
+ ? translate('nextcloud-vue', 'from {value}', { value: state.from })
1424
+ : translate('nextcloud-vue', 'up to {value}', { value: state.to })
1425
+ }
1426
+ if (def.kind === 'boolean') {
1427
+ return state.value === 'true' ? translate('nextcloud-vue', 'Yes') : translate('nextcloud-vue', 'No')
1428
+ }
1429
+ return state.value
1430
+ },
1431
+
1432
+ /**
1433
+ * Open or close a column's filter panel under its button.
1434
+ *
1435
+ * @param {object} col The column.
1436
+ * @param {Event} event The click.
1437
+ * @return {void}
1438
+ */
1439
+ toggleColumnFilter(col, event) {
1440
+ if (this.openFilterKey === col.key) {
1441
+ this.closeColumnFilter();
1442
+ return
1443
+ }
1444
+ const target = event && event.currentTarget;
1445
+ this.filterAnchorRect = target && typeof target.getBoundingClientRect === 'function'
1446
+ ? target.getBoundingClientRect()
1447
+ : null;
1448
+ this.openFilterKey = col.key;
1449
+ },
1450
+
1451
+ /**
1452
+ * Close the open panel and give focus back to its button.
1453
+ *
1454
+ * @return {void}
1455
+ */
1456
+ closeColumnFilter() {
1457
+ const key = this.openFilterKey;
1458
+ this.openFilterKey = '';
1459
+ if (!key) {
1460
+ return
1461
+ }
1462
+ this.$nextTick(() => {
1463
+ const ref = this.$refs['filterButton-' + key];
1464
+ const button = Array.isArray(ref) ? ref[0] : ref;
1465
+ if (button && typeof button.focus === 'function') {
1466
+ button.focus();
1467
+ }
1468
+ });
1469
+ },
1470
+
1471
+ /**
1472
+ * Apply a panel's state: emit the query parameters it stands for.
1473
+ *
1474
+ * @param {object} col The column.
1475
+ * @param {object} state The panel state.
1476
+ * @return {void}
1477
+ * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-a-header-filter-speaks-the-sidebars-query-language
1478
+ */
1479
+ applyColumnFilter(col, state) {
1480
+ const def = this.filterDefFor(col);
1481
+ if (def) {
1482
+ /**
1483
+ * @event column-filter A header filter was applied or cleared.
1484
+ * @type {{ key: string, params: object }} `params` maps each query parameter the column owns to its values; an empty list clears it.
1485
+ */
1486
+ this.$emit('column-filter', { key: col.key, params: columnFilterParams(def, state) });
1487
+ }
1488
+ this.closeColumnFilter();
1489
+ },
1490
+
1491
+ clearColumnFilter(col) {
1492
+ const def = this.filterDefFor(col);
1493
+ if (def) {
1494
+ this.$emit('column-filter', { key: col.key, params: clearedColumnFilterParams(def) });
1495
+ }
1496
+ },
1497
+
1265
1498
  onHeaderClick(key, event) {
1266
1499
  this.applySort(key, !!(event && event.shiftKey));
1267
1500
  },