@conduction/nextcloud-vue 2.69.0 → 2.71.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 (153) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +25 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +155 -152
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +77 -0
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +8 -8
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +151 -13
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  13. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -0
  14. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js.map +1 -1
  15. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +28 -0
  16. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  17. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +4 -2
  18. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +25 -1
  20. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js +32 -17
  22. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
  23. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js +14 -1
  24. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js.map +1 -1
  25. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue3.js.map +1 -1
  26. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +43 -2
  27. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  28. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
  29. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  30. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -1
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  32. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +40 -43
  33. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  34. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +35 -1
  35. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  37. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +54 -3
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +6 -3
  40. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  41. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +28 -0
  42. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  43. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +3 -2
  44. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  45. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +12 -0
  46. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +9 -0
  48. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  49. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +29 -2
  51. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  52. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +71 -34
  53. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  54. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +54 -7
  55. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  56. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +26 -8
  57. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  58. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +165 -0
  59. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +69 -26
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  62. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -1
  63. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -1
  64. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +4 -1
  65. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  66. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  67. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +1 -1
  68. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  69. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  70. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  71. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +46 -32
  72. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  73. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +17 -3
  74. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
  75. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +25 -24
  76. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
  77. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  78. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  79. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +27 -0
  80. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +7 -0
  82. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +24 -1
  84. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
  85. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +1 -1
  86. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
  87. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +11 -0
  88. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  89. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +4 -2
  90. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  91. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +30 -9
  92. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  93. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  94. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +15 -2
  95. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  96. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +31 -32
  97. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  98. package/dist/esm/composables/useWalkthrough.js +11 -3
  99. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  100. package/dist/esm/nextcloud-vue.css +198 -0
  101. package/dist/esm/utils/capabilityComparison.js +29 -1
  102. package/dist/esm/utils/capabilityComparison.js.map +1 -1
  103. package/dist/esm/utils/columnFilters.js +54 -4
  104. package/dist/esm/utils/columnFilters.js.map +1 -1
  105. package/dist/esm/utils/userAutocomplete.js +69 -3
  106. package/dist/esm/utils/userAutocomplete.js.map +1 -1
  107. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  108. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  109. package/dist/nextcloud-vue.cjs.js +2778 -1630
  110. package/dist/nextcloud-vue.cjs.js.map +2 -2
  111. package/dist/nextcloud-vue.css +198 -0
  112. package/package.json +1 -1
  113. package/src/components/CnActionsBar/CnActionsBar.md +2 -0
  114. package/src/components/CnActionsBar/CnActionsBar.vue +28 -2
  115. package/src/components/CnAppNav/CnAppNav.vue +85 -8
  116. package/src/components/CnAppRoot/CnAppRoot.vue +153 -13
  117. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +45 -0
  118. package/src/components/CnCalendarWidget/CnCalendarWidget.vue +32 -4
  119. package/src/components/CnCapabilityTable/CnCapabilityTable.vue +14 -0
  120. package/src/components/CnDashboardGrid/CnDashboardGrid.md +2 -0
  121. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +54 -2
  122. package/src/components/CnDashboardPage/CnDashboardPage.vue +46 -19
  123. package/src/components/CnDataTable/CnDataTable.vue +35 -1
  124. package/src/components/CnDetailPage/CnDetailPage.md +2 -0
  125. package/src/components/CnDetailPage/CnDetailPage.vue +57 -3
  126. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +30 -1
  127. package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +12 -0
  128. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +9 -0
  129. package/src/components/CnFormDialog/CnFormDialog.vue +62 -2
  130. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +95 -8
  131. package/src/components/CnIndexPage/CnIndexPage.vue +194 -7
  132. package/src/components/CnNextStepCard/CnNextStepCard.vue +11 -0
  133. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +4 -1
  134. package/src/components/CnObjectSidebar/CnNotesTab.vue +1 -1
  135. package/src/components/CnSetupWizard/CnSetupWizard.vue +7 -0
  136. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +34 -7
  137. package/src/components/CnStagesWidget/CnStagesWidget.vue +19 -0
  138. package/src/components/CnStatWidget/CnStatWidget.vue +36 -0
  139. package/src/components/CnStatsBlock/CnStatsBlock.md +2 -0
  140. package/src/components/CnStatsBlock/CnStatsBlock.vue +25 -2
  141. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +13 -0
  142. package/src/components/CnWalkthrough/CnWalkthrough.vue +30 -9
  143. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +24 -3
  144. package/src/composables/useWalkthrough.js +11 -3
  145. package/src/css/dashboard.css +44 -0
  146. package/src/css/detail-page.css +10 -0
  147. package/src/css/index-page.css +12 -0
  148. package/src/css/kpi-card.css +15 -0
  149. package/src/schemas/app-manifest-v2.schema.json +47 -5
  150. package/src/utils/capabilityComparison.js +28 -0
  151. package/src/utils/columnFilters.js +54 -4
  152. package/src/utils/userAutocomplete.js +68 -2
  153. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1330,6 +1330,21 @@ a.cn-kpi-card--stacked:focus-visible {
1330
1330
  text-decoration: none !important;
1331
1331
  }
1332
1332
 
1333
+ /* A stacked stats block (CnStatsBlock `layout: "stacked"`): its unit
1334
+ ("decisions") goes on a muted line of its own under the number, where the
1335
+ stat tile puts its caption (zuiddrecht-pixel-gaps-3). */
1336
+ .cn-kpi-card--stacked .cn-stats-block__count {
1337
+ flex-direction: column;
1338
+ align-items: flex-start;
1339
+ gap: var(--cn-kpi-stacked-gap, 6px);
1340
+ }
1341
+
1342
+ .cn-kpi-card--stacked .cn-stats-block__count-label {
1343
+ font-size: var(--cn-kpi-label-size, 13px);
1344
+ font-weight: 500;
1345
+ color: var(--color-text-maxcontrast);
1346
+ }
1347
+
1333
1348
  /* Filled: the card draws its own grey box, for a KPI that is NOT already
1334
1349
  inside a wrapper that draws one. Opt-in via `content.flat: false`. */
1335
1350
  .cn-kpi-card--filled {
@@ -2283,6 +2298,18 @@ a.cn-kpi-card--stacked:focus-visible {
2283
2298
  padding: calc(5 * var(--default-grid-baseline));
2284
2299
  }
2285
2300
 
2301
+ /* The board's header buttons (`headerButtons`) at the end of the title row,
2302
+ centred on the title and its count line. They wrap under the title on a
2303
+ narrow page. */
2304
+ .cn-index-page__header-buttons {
2305
+ display: flex;
2306
+ flex-wrap: wrap;
2307
+ align-items: center;
2308
+ align-self: center;
2309
+ gap: calc(2 * var(--default-grid-baseline));
2310
+ margin-inline-start: auto;
2311
+ }
2312
+
2286
2313
  .cn-index-page__below-header {
2287
2314
  margin-bottom: calc(4 * var(--default-grid-baseline));
2288
2315
  }
@@ -2814,6 +2841,50 @@ a.cn-kpi-card--stacked:focus-visible {
2814
2841
  .cn-requires-app.cn-requires-app .empty-content__action {
2815
2842
  margin-top: var(--default-grid-baseline, 4px);
2816
2843
  }
2844
+
2845
+ /* NcEmptyContent forces its icon's svg to 64px with !important while the
2846
+ * rule above lets the icon box shrink, so the svg spilled out of its box and
2847
+ * over the name. Hold the svg to the size the caller asked for. */
2848
+ .cn-requires-app.cn-requires-app .empty-content__icon svg {
2849
+ width: 32px !important;
2850
+ height: 32px !important;
2851
+ max-width: 32px !important;
2852
+ max-height: 32px !important;
2853
+ }
2854
+
2855
+ /* The dashboard's compact "Install <app>" placeholder: two lines of text and
2856
+ * the button, nothing else, so it fits a two-row tile even under a title row.
2857
+ * `safe center` keeps the top in view when a tile is smaller still, and the
2858
+ * body scrolls rather than clipping the button. */
2859
+ .cn-requires-app--compact {
2860
+ display: flex;
2861
+ flex-direction: column;
2862
+ align-items: center;
2863
+ justify-content: safe center;
2864
+ gap: calc(2 * var(--default-grid-baseline, 4px));
2865
+ box-sizing: border-box;
2866
+ height: 100%;
2867
+ min-height: 0;
2868
+ overflow: auto;
2869
+ padding: calc(2 * var(--default-grid-baseline, 4px));
2870
+ text-align: center;
2871
+ }
2872
+
2873
+ .cn-requires-app--compact .cn-requires-app__description {
2874
+ display: -webkit-box;
2875
+ margin: 0;
2876
+ overflow: hidden;
2877
+ color: var(--color-text-maxcontrast);
2878
+ font-size: 0.9em;
2879
+ line-height: 1.4;
2880
+ -webkit-box-orient: vertical;
2881
+ -webkit-line-clamp: 2;
2882
+ line-clamp: 2;
2883
+ }
2884
+
2885
+ .cn-requires-app--compact .button-vue {
2886
+ flex-shrink: 0;
2887
+ }
2817
2888
  /* ========================================
2818
2889
  @conduction/nextcloud-vue — CnDetailPage Styles
2819
2890
  ======================================== */
@@ -2862,6 +2933,16 @@ a.cn-kpi-card--stacked:focus-visible {
2862
2933
  row-gap: var(--cn-detail-header-row-gap, 22px);
2863
2934
  }
2864
2935
 
2936
+ /* That wrap must not take the actions off the title row. With `flex-basis:
2937
+ auto` a long title claimed its whole text width, so the actions wrapped
2938
+ under it. From a zero basis the title takes what the actions leave and
2939
+ wraps (two lines, then an ellipsis) inside it; only on a page too narrow
2940
+ for 16rem of title beside the actions do the actions drop a row. */
2941
+ .cn-detail-page__header--with-widget > :first-child {
2942
+ flex: 1 1 0;
2943
+ min-width: min(100%, 16rem);
2944
+ }
2945
+
2865
2946
  .cn-detail-page__header-widget {
2866
2947
  flex: 1 0 100%;
2867
2948
  min-width: 0;
@@ -6173,6 +6254,17 @@ svg.leaflet-image-layer.leaflet-interactive path {
6173
6254
  margin: 0;
6174
6255
  text-transform: uppercase;
6175
6256
  }
6257
+
6258
+ /* The title is a small kicker, not a section heading, but it is an <h3> (or
6259
+ whatever `titleTag` says) for the outline, and a theme that sizes every
6260
+ heading with !important (thematiq's nldesign sheet: `h3 { font-size:
6261
+ var(--nldesign-component-heading-3-font-size) !important }`) blew it up to
6262
+ an 18px uppercase heading. Only !important beats !important; the doubled
6263
+ class makes this the more specific rule. Without such a theme the size is
6264
+ the 0.85em above, unchanged. */
6265
+ .cn-next-step-card__title.cn-next-step-card__title[data-v-3a9ce962] {
6266
+ font-size: 0.85em !important;
6267
+ }
6176
6268
  .cn-next-step-card__list[data-v-3a9ce962] {
6177
6269
  display: grid;
6178
6270
  gap: calc(2 * var(--default-grid-baseline));
@@ -15597,6 +15689,15 @@ a.cn-xwiki-card__title[data-v-54e85fe3]:hover {
15597
15689
  }
15598
15690
 
15599
15691
 
15692
+ /* A select option breaks between words only, never inside one. */
15693
+ .cn-form-dialog__option[data-v-c3eeb0a2] {
15694
+ display: block;
15695
+ min-width: 0;
15696
+ white-space: normal;
15697
+ overflow-wrap: normal;
15698
+ word-break: normal;
15699
+ hyphens: manual;
15700
+ }
15600
15701
  .cn-form-dialog__draft-actions[data-v-c3eeb0a2] {
15601
15702
  display: flex;
15602
15703
  gap: 8px;
@@ -16465,6 +16566,14 @@ a.cn-xwiki-card__title[data-v-54e85fe3]:hover {
16465
16566
  gap: 16px;
16466
16567
  width: 100%;
16467
16568
  }
16569
+
16570
+ /* `content.inset: true`: the board's inset inside the card (zuiddrecht-pixel-
16571
+ gaps-3). A dashboard renders its widgets flush, so without it the strip ran
16572
+ from card edge to card edge. Theme hook: --cn-widget-board-inset. */
16573
+ .cn-stacked-bar--inset[data-v-c0bac1a2] {
16574
+ box-sizing: border-box;
16575
+ padding: var(--cn-widget-board-inset, 16px 24px 22px);
16576
+ }
16468
16577
  .cn-stacked-bar__state[data-v-c0bac1a2] {
16469
16578
  display: flex;
16470
16579
  align-items: center;
@@ -16980,6 +17089,25 @@ button.cn-stages-widget__bar-label[data-v-358fde0f] {
16980
17089
  line-height: inherit;
16981
17090
  cursor: pointer;
16982
17091
  }
17092
+
17093
+ /* The same label under a theme that restyles every button with !important
17094
+ (thematiq's nldesign sheet: `button:not(.action-button)` gets 8px 16px
17095
+ padding, `min-width: max-content` and `overflow: visible`), and under core's
17096
+ `margin: 3px 3px 3px 0`, which outranks the scoped base rule. Together they
17097
+ put each clickable label about 10px below the current step's <span> and let
17098
+ a long label run over its neighbours, so eight Woo steps overlapped instead
17099
+ of ending in an ellipsis. Only !important beats !important; this selector is
17100
+ the more specific of the two. Without such a theme nothing changes: these
17101
+ are the values the base rule already sets. */
17102
+ button.cn-stages-widget__bar-label.cn-stages-widget__bar-label[data-v-358fde0f] {
17103
+ min-width: 0 !important;
17104
+ margin: 0 !important;
17105
+ padding: 0 !important;
17106
+ border-width: 0 !important;
17107
+ overflow: hidden !important;
17108
+ text-overflow: ellipsis !important;
17109
+ white-space: nowrap !important;
17110
+ }
16983
17111
  button.cn-stages-widget__bar-label[aria-disabled="true"][data-v-358fde0f] {
16984
17112
  cursor: not-allowed;
16985
17113
  }
@@ -17459,6 +17587,21 @@ a.cn-kpi-card--stacked:focus-visible {
17459
17587
  text-decoration: none !important;
17460
17588
  }
17461
17589
 
17590
+ /* A stacked stats block (CnStatsBlock `layout: "stacked"`): its unit
17591
+ ("decisions") goes on a muted line of its own under the number, where the
17592
+ stat tile puts its caption (zuiddrecht-pixel-gaps-3). */
17593
+ .cn-kpi-card--stacked .cn-stats-block__count {
17594
+ flex-direction: column;
17595
+ align-items: flex-start;
17596
+ gap: var(--cn-kpi-stacked-gap, 6px);
17597
+ }
17598
+
17599
+ .cn-kpi-card--stacked .cn-stats-block__count-label {
17600
+ font-size: var(--cn-kpi-label-size, 13px);
17601
+ font-weight: 500;
17602
+ color: var(--color-text-maxcontrast);
17603
+ }
17604
+
17462
17605
  /* Filled: the card draws its own grey box, for a KPI that is NOT already
17463
17606
  inside a wrapper that draws one. Opt-in via `content.flat: false`. */
17464
17607
  .cn-kpi-card--filled {
@@ -18405,6 +18548,30 @@ to { background-position: -200% 0;
18405
18548
  flex: 1 1 320px;
18406
18549
  width: auto;
18407
18550
  }
18551
+
18552
+ /* The emblem beside the greeting (`content.emblem`): on one row with the
18553
+ date line and heading, centred on them, as LpStart draws it. */
18554
+ .cn-header-widget--with-emblem[data-v-1b97430f] {
18555
+ display: flex;
18556
+ column-gap: var(--cn-header-emblem-gap, 16px);
18557
+ }
18558
+ .cn-header-widget--with-emblem .cn-header-widget__content[data-v-1b97430f] {
18559
+ flex: 1 1 auto;
18560
+ width: auto;
18561
+ min-width: 0;
18562
+ }
18563
+ .cn-header-widget__emblem[data-v-1b97430f] {
18564
+ position: relative;
18565
+ z-index: 1;
18566
+ flex: none;
18567
+ align-self: center;
18568
+ height: var(--cn-header-emblem-size, 52px);
18569
+ width: auto;
18570
+ }
18571
+ .cn-header-widget__emblem--theme[data-v-1b97430f] {
18572
+ width: var(--cn-header-emblem-size, 52px);
18573
+ background: var(--nldesign-emblem-url) center / contain no-repeat;
18574
+ }
18408
18575
  .cn-header-widget__views[data-v-1b97430f] {
18409
18576
  position: relative;
18410
18577
  z-index: 1;
@@ -19507,6 +19674,14 @@ to { background-position: -200% 0;
19507
19674
  .cn-week-strip[data-v-153dd4cb] {
19508
19675
  width: 100%;
19509
19676
  }
19677
+
19678
+ /* `content.inset: true`: the board's inset inside the card (zuiddrecht-pixel-
19679
+ gaps-3). A dashboard renders its widgets flush, so without it the strip ran
19680
+ from card edge to card edge. Theme hook: --cn-widget-board-inset. */
19681
+ .cn-week-strip--inset[data-v-153dd4cb] {
19682
+ box-sizing: border-box;
19683
+ padding: var(--cn-widget-board-inset, 16px 24px 22px);
19684
+ }
19510
19685
  .cn-week-strip__state[data-v-153dd4cb] {
19511
19686
  display: flex;
19512
19687
  align-items: center;
@@ -30052,6 +30227,19 @@ to {
30052
30227
  }
30053
30228
 
30054
30229
 
30230
+ /* `separator`: a text separator in place of the chevron. The chevron's own
30231
+ element stays (it carries NcBreadcrumbs' spacing and its "no separator after
30232
+ the last crumb" rule); only its icon gives way to the text. */
30233
+ .cn-breadcrumbs--text-separator[data-v-296fde9b] .vue-crumb__separator svg {
30234
+ display: none;
30235
+ }
30236
+ .cn-breadcrumbs--text-separator[data-v-296fde9b] .vue-crumb__separator::before {
30237
+ content: var(--cn-breadcrumbs-separator);
30238
+ padding-inline: var(--cn-breadcrumbs-separator-gap, 0);
30239
+ color: var(--color-text-maxcontrast);
30240
+ }
30241
+
30242
+
30055
30243
  .cn-run-node-dialog__form[data-v-3eb7c55d] {
30056
30244
  display: flex;
30057
30245
  flex-direction: column;
@@ -30223,6 +30411,16 @@ to {
30223
30411
  outline-offset: 2px;
30224
30412
  border-radius: var(--border-radius-container-large, 16px);
30225
30413
  }
30414
+
30415
+ /* A size-to-content cell (`sizeToContent`) is measured by GridStack from the
30416
+ content's first child, and a widget wrapper there filled the cell
30417
+ (`height: 100%`), so the measure returned the cell's own height and the
30418
+ cell never shrank: "Waiting for me" stayed a tall card around one row. In
30419
+ such a cell the child takes its own height. A cell without the key keeps
30420
+ the filling child. */
30421
+ .cn-dashboard-grid__item--fit[data-v-9f6b1a22] > .grid-stack-item-content > * {
30422
+ height: auto;
30423
+ }
30226
30424
  [data-v-9f6b1a22] .grid-stack-item-content {
30227
30425
  background: var(--color-main-background);
30228
30426
  border: none;
@@ -22,6 +22,34 @@
22
22
  * @module utils/capabilityComparison
23
23
  */
24
24
 
25
+ /*
26
+ * `process.env.NODE_ENV` is substituted by every consuming app's bundler at
27
+ * build time (webpack's DefinePlugin, rollup's replace); see NcSelectTags.js.
28
+ */
29
+ /* global process */
30
+
31
+ let deprecationWarned = false;
32
+
33
+ /**
34
+ * Warn once per page load, in a development build only, that the in-app
35
+ * capability comparison is deprecated. Called by CnFeaturesAndRoadmapPage,
36
+ * CnFeaturesAndRoadmapView and CnCapabilityTable when a comparison is
37
+ * actually given, so an app that passes none hears nothing.
38
+ *
39
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated
40
+ * @param {string} source What carried the comparison, for the message.
41
+ * @return {boolean} True when this call printed the warning.
42
+ */
43
+ function warnCapabilityComparisonDeprecated(source) {
44
+ if (deprecationWarned || process.env.NODE_ENV === 'production') {
45
+ return false
46
+ }
47
+ deprecationWarned = true;
48
+ // eslint-disable-next-line no-console -- a deliberate deprecation notice to the developer integrating the component
49
+ console.warn(`[nextcloud-vue] ${source} is deprecated: the in-app capability comparison (capabilityComparison, the features_roadmap_capabilities initial state, CnCapabilityTable) will be removed in a future major version. Publish the comparison on the app's docs site and link to it instead.`);
50
+ return true
51
+ }
52
+
25
53
  /**
26
54
  * Ratings in the order they are counted and rendered.
27
55
  *
@@ -371,5 +399,5 @@ function groupCapabilities(comparison, options = {}) {
371
399
  return groups
372
400
  }
373
401
 
374
- export { PROVIDER_KINDS, RATINGS, UNMAPPED_GROUP_KEY, capabilitySearchIndex, defaultGroupMode, filterCapabilities, groupCapabilities, hasProviders, labelFor, matchesQuery, normaliseText, overallTallies, resolveProvider, tally };
402
+ export { PROVIDER_KINDS, RATINGS, UNMAPPED_GROUP_KEY, capabilitySearchIndex, defaultGroupMode, filterCapabilities, groupCapabilities, hasProviders, labelFor, matchesQuery, normaliseText, overallTallies, resolveProvider, tally, warnCapabilityComparisonDeprecated };
375
403
  //# sourceMappingURL=capabilityComparison.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"capabilityComparison.js","sources":["../../../src/utils/capabilityComparison.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * Presentation logic for the capability comparison rendered by\n * `CnCapabilityTable`. Pure functions over the comparison document the host\n * app supplies, kept out of the SFC so the search predicate, the grouping\n * switch and the provider fallback are unit-testable without a DOM.\n *\n * THE ONE RULE THIS MODULE ENFORCES. A row never disappears. Not for an\n * unknown provider key, not for an area the document forgot to declare, not\n * for a feature nobody has mapped yet, and not for a rating outside the three\n * the audit emits. Every one of those lands in a named bucket with the raw\n * value visible, because a silently dropped row reads exactly like a row that\n * was never written.\n *\n * Tallies are DERIVED here rather than read from the document. A stored total\n * is a second copy of the truth that goes stale the moment a row is corrected,\n * and nothing would fail: the page would show a number that no longer counts\n * its own rows.\n *\n * @module utils/capabilityComparison\n */\n\n/**\n * Ratings in the order they are counted and rendered.\n *\n * @type {ReadonlyArray<string>}\n */\nexport const RATINGS = Object.freeze(['yes', 'partial', 'no'])\n\n/**\n * The columns the totals table and the legend show: the ratings plus\n * `unknown`. `unknown` is a real answer, not a data error. A later audit round\n * can add a capability row without re-reading the products an earlier round\n * rated, and the honest cell for those columns is empty.\n *\n * @type {ReadonlyArray<string>}\n */\nexport const RATING_COLUMNS = Object.freeze([...RATINGS, 'unknown'])\n\n/**\n * The provider kinds the document may declare. Anything else is carried\n * through as `unknown`, which renders as a plain chip rather than a styled\n * one.\n *\n * @type {ReadonlyArray<string>}\n */\nexport const PROVIDER_KINDS = Object.freeze(['self', 'app', 'platform'])\n\n/**\n * Group key for rows that carry no `feature`. It is a bucket, never a filter:\n * the rows render under a heading that says they are not mapped yet.\n *\n * @type {string}\n */\nexport const UNMAPPED_GROUP_KEY = '__cn-unmapped__'\n\n/**\n * Lower-case and strip diacritics so a reader typing `zaken` finds `Zaken` and\n * a reader typing `een` finds `Eén`.\n *\n * `normalize('NFD')` splits a letter from its accent and the range strips the\n * combining marks. Both have been in every browser and in Node since long\n * before this library's floor, so the accent-insensitive half costs one regex.\n *\n * @param {unknown} value Any value; non-strings are coerced.\n * @return {string} The comparable form.\n */\nexport function normaliseText(value) {\n\tif (value === null || value === undefined) {\n\t\treturn ''\n\t}\n\treturn String(value)\n\t\t.normalize('NFD')\n\t\t.replace(/[\\u0300-\\u036f]/g, '')\n\t\t.toLowerCase()\n\t\t.trim()\n}\n\n/**\n * Pick the reader's language variant of a labelled entry.\n *\n * The document carries `name` (English) and `name_nl` (Dutch) side by side.\n * Dutch wins only for a Dutch locale AND only when the Dutch string is\n * actually there, so a half-translated document degrades to English rather\n * than to a blank cell.\n *\n * @param {{name?: string, name_nl?: string}} entry Labelled entry.\n * @param {string} [locale] BCP 47 locale, e.g. `nl`, `nl_NL`, `en-GB`.\n * @return {string} The label to render.\n */\nexport function labelFor(entry, locale = 'en') {\n\tif (!entry) {\n\t\treturn ''\n\t}\n\tconst dutch = String(locale || '').toLowerCase().startsWith('nl')\n\tif (dutch && typeof entry.name_nl === 'string' && entry.name_nl.trim() !== '') {\n\t\treturn entry.name_nl\n\t}\n\treturn entry.name ?? ''\n}\n\n/**\n * Index the document's `providers` list by key.\n *\n * @param {object} comparison Parsed comparison document.\n * @return {Map<string, object>} Provider entries by key.\n */\nfunction providerIndex(comparison) {\n\tconst map = new Map()\n\tfor (const provider of comparison?.providers ?? []) {\n\t\tif (provider && typeof provider.key === 'string') {\n\t\t\tmap.set(provider.key, provider)\n\t\t}\n\t}\n\treturn map\n}\n\n/**\n * Resolve the provider of one capability row.\n *\n * An unrecognised key is NOT an error and never costs the row its place: the\n * raw key becomes the label and the kind becomes `unknown`, so a reader sees\n * that the document names a provider the dictionary forgot rather than seeing\n * nothing at all.\n *\n * @param {object} row One capability row.\n * @param {object} comparison Parsed comparison document.\n * @param {string} [locale] BCP 47 locale used to pick the provider name.\n * @return {{key: string, name: string, kind: string, declared: boolean}|null}\n * The provider, or null when the row names none.\n */\nexport function resolveProvider(row, comparison, locale = 'en') {\n\tconst key = row?.provider\n\tif (typeof key !== 'string' || key.trim() === '') {\n\t\treturn null\n\t}\n\tconst declared = providerIndex(comparison).get(key)\n\tif (!declared) {\n\t\treturn { key, name: key, kind: 'unknown', declared: false }\n\t}\n\tconst kind = PROVIDER_KINDS.includes(declared.kind) ? declared.kind : 'unknown'\n\treturn {\n\t\tkey,\n\t\tname: labelFor(declared, locale) || key,\n\t\tkind,\n\t\tdeclared: true,\n\t}\n}\n\n/**\n * Every string a search should match on for one row, joined.\n *\n * Both languages are indexed whatever the reader's locale, because a Dutch\n * reader searching for the English term they read in a tender document should\n * still find the row.\n *\n * @param {object} row One capability row.\n * @param {object} comparison Parsed comparison document.\n * @return {string} The haystack, already normalised.\n */\nexport function capabilitySearchIndex(row, comparison) {\n\tconst areas = comparison?.areas ?? []\n\tconst features = comparison?.features ?? []\n\tconst area = areas.find((entry) => entry?.key === row?.area)\n\tconst feature = features.find((entry) => entry?.key === row?.feature)\n\tconst provider = resolveProvider(row, comparison)\n\t// The declared entry and not `provider.name`: that one is already resolved\n\t// to a single language, so indexing it would honour the rule above for the\n\t// row, the area and the feature and quietly break it for the provider.\n\tconst declared = providerIndex(comparison).get(provider?.key)\n\tconst parts = [\n\t\trow?.id,\n\t\trow?.name,\n\t\trow?.name_nl,\n\t\trow?.area,\n\t\tarea?.name,\n\t\tarea?.name_nl,\n\t\trow?.feature,\n\t\tfeature?.name,\n\t\tfeature?.name_nl,\n\t\tprovider?.key,\n\t\tprovider?.name,\n\t\tdeclared?.name,\n\t\tdeclared?.name_nl,\n\t]\n\treturn parts.filter((part) => part !== null && part !== undefined && part !== '')\n\t\t.map((part) => normaliseText(part))\n\t\t.join(' ')\n}\n\n/**\n * Does one row match the reader's query?\n *\n * Every whitespace-separated term must appear somewhere in the row's index, so\n * `intake dossiq` narrows rather than widens. An empty query matches\n * everything, which is what keeps the unsearched page identical to the\n * searched-for-nothing page.\n *\n * @param {object} row One capability row.\n * @param {string} query The reader's raw input.\n * @param {object} comparison Parsed comparison document.\n * @return {boolean} True when the row should stay on the page.\n */\nexport function matchesQuery(row, query, comparison) {\n\tconst terms = normaliseText(query).split(/\\s+/).filter(Boolean)\n\tif (terms.length === 0) {\n\t\treturn true\n\t}\n\tconst haystack = capabilitySearchIndex(row, comparison)\n\treturn terms.every((term) => haystack.includes(term))\n}\n\n/**\n * The rows that survive the reader's query, in document order.\n *\n * @param {object} comparison Parsed comparison document.\n * @param {string} [query] The reader's raw input.\n * @return {Array<object>} Matching rows.\n */\nexport function filterCapabilities(comparison, query = '') {\n\treturn (comparison?.capabilities ?? []).filter((row) => matchesQuery(row, query, comparison))\n}\n\n/**\n * Count one system's ratings across a set of capability rows.\n *\n * A rating outside `RATINGS` is counted under `unknown` rather than dropped,\n * so a bad row shows up as a number that does not add up instead of\n * disappearing.\n *\n * @param {Array<object>} capabilities Capability rows.\n * @param {string} systemKey Key of the system column, e.g. `dossiq`.\n * @return {{yes: number, partial: number, no: number, unknown: number, total: number}} The tally.\n */\nexport function tally(capabilities, systemKey) {\n\tconst counts = { yes: 0, partial: 0, no: 0, unknown: 0, total: 0 }\n\tfor (const row of capabilities ?? []) {\n\t\tconst rating = row?.[systemKey]\n\t\tcounts[RATINGS.includes(rating) ? rating : 'unknown'] += 1\n\t\tcounts.total += 1\n\t}\n\treturn counts\n}\n\n/**\n * Tally every system over a set of rows.\n *\n * @param {object} comparison Parsed comparison document.\n * @param {Array<object>} [rows] Rows to count; defaults to all of them.\n * @return {Record<string, object>} Tally per system key.\n */\nexport function overallTallies(comparison, rows = null) {\n\tconst counted = rows ?? comparison?.capabilities ?? []\n\tconst out = {}\n\tfor (const system of comparison?.systems ?? []) {\n\t\tout[system.key] = tally(counted, system.key)\n\t}\n\treturn out\n}\n\n/**\n * Which grouping a document should open on.\n *\n * Feature grouping is the default the moment ANY row carries a `feature`,\n * because a document that bothered to map features wants them shown. A\n * document without them groups by area, which is what every consumer does\n * today.\n *\n * @param {object} comparison Parsed comparison document.\n * @return {'feature'|'area'} The opening mode.\n */\nexport function defaultGroupMode(comparison) {\n\tconst mapped = (comparison?.capabilities ?? []).some((row) => typeof row?.feature === 'string' && row.feature.trim() !== '')\n\treturn mapped ? 'feature' : 'area'\n}\n\n/**\n * Does any row in the document name a provider?\n *\n * Drives whether the provided-by column renders at all. A consumer whose\n * document predates the field gets exactly the columns it has today.\n *\n * @param {object} comparison Parsed comparison document.\n * @return {boolean} True when at least one row names a provider.\n */\nexport function hasProviders(comparison) {\n\treturn (comparison?.capabilities ?? []).some((row) => typeof row?.provider === 'string' && row.provider.trim() !== '')\n}\n\n/**\n * Group the rows for rendering, in the order a reader should meet them.\n *\n * Declared groups come first, in the order the document declares them: for\n * areas that is the audit's own numbering, and re-sorting would put `13.1`\n * above `1.1` on a table whose first column is that number. Then come keys the\n * rows use that the document never declared, labelled with the raw key. Last\n * comes the bucket for rows that name no group at all.\n *\n * An empty group is dropped, because a search that matches nothing in an area\n * should not leave an empty heading behind. The unmapped bucket follows the\n * same rule.\n *\n * @param {object} comparison Parsed comparison document.\n * @param {object} [options] Options.\n * @param {'feature'|'area'} [options.mode] Grouping mode.\n * @param {string} [options.locale] BCP 47 locale used for labels.\n * @param {string} [options.query] The reader's search input.\n * @param {string} [options.unmappedLabel] Heading for rows that name no group.\n * @return {Array<{key: string, label: string, declared: boolean, capabilities: Array<object>, tallies: object}>}\n * One entry per non-empty group.\n */\nexport function groupCapabilities(comparison, options = {}) {\n\tconst mode = options.mode === 'feature' ? 'feature' : 'area'\n\tconst locale = options.locale ?? 'en'\n\tconst unmappedLabel = options.unmappedLabel ?? ''\n\tconst declared = mode === 'feature' ? (comparison?.features ?? []) : (comparison?.areas ?? [])\n\tconst rows = filterCapabilities(comparison, options.query ?? '')\n\n\tconst buckets = new Map()\n\t/**\n\t * Fetch or create a bucket, keeping insertion order.\n\t *\n\t * @param {string} key Bucket key.\n\t * @param {string} label Bucket heading.\n\t * @param {boolean} isDeclared Whether the document declares this key.\n\t * @return {object} The bucket.\n\t */\n\tconst bucket = (key, label, isDeclared) => {\n\t\tif (!buckets.has(key)) {\n\t\t\tbuckets.set(key, { key, label, declared: isDeclared, capabilities: [] })\n\t\t}\n\t\treturn buckets.get(key)\n\t}\n\n\t// Seed the declared groups first so they keep the document's order even\n\t// when the rows arrive in another one.\n\tfor (const entry of declared) {\n\t\tif (entry && typeof entry.key === 'string') {\n\t\t\tbucket(entry.key, labelFor(entry, locale) || entry.key, true)\n\t\t}\n\t}\n\n\tconst unmapped = []\n\tfor (const row of rows) {\n\t\tconst key = row?.[mode]\n\t\tif (typeof key !== 'string' || key.trim() === '') {\n\t\t\tunmapped.push({ ...row, label: labelFor(row, locale) })\n\t\t\tcontinue\n\t\t}\n\t\t// An undeclared key gets a bucket of its own, labelled with the raw\n\t\t// key. Dropping the row here is the silent failure this module exists\n\t\t// to prevent.\n\t\tbucket(key, key, false).capabilities.push({ ...row, label: labelFor(row, locale) })\n\t}\n\n\tconst groups = [...buckets.values()].filter((entry) => entry.capabilities.length > 0)\n\tif (unmapped.length > 0) {\n\t\tgroups.push({\n\t\t\tkey: UNMAPPED_GROUP_KEY,\n\t\t\tlabel: unmappedLabel,\n\t\t\tdeclared: false,\n\t\t\tcapabilities: unmapped,\n\t\t})\n\t}\n\n\tfor (const group of groups) {\n\t\tgroup.tallies = overallTallies(comparison, group.capabilities)\n\t}\n\treturn groups\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"capabilityComparison.js","sources":["../../../src/utils/capabilityComparison.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * Presentation logic for the capability comparison rendered by\n * `CnCapabilityTable`. Pure functions over the comparison document the host\n * app supplies, kept out of the SFC so the search predicate, the grouping\n * switch and the provider fallback are unit-testable without a DOM.\n *\n * THE ONE RULE THIS MODULE ENFORCES. A row never disappears. Not for an\n * unknown provider key, not for an area the document forgot to declare, not\n * for a feature nobody has mapped yet, and not for a rating outside the three\n * the audit emits. Every one of those lands in a named bucket with the raw\n * value visible, because a silently dropped row reads exactly like a row that\n * was never written.\n *\n * Tallies are DERIVED here rather than read from the document. A stored total\n * is a second copy of the truth that goes stale the moment a row is corrected,\n * and nothing would fail: the page would show a number that no longer counts\n * its own rows.\n *\n * @module utils/capabilityComparison\n */\n\n/*\n * `process.env.NODE_ENV` is substituted by every consuming app's bundler at\n * build time (webpack's DefinePlugin, rollup's replace); see NcSelectTags.js.\n */\n/* global process */\n\nlet deprecationWarned = false\n\n/**\n * Warn once per page load, in a development build only, that the in-app\n * capability comparison is deprecated. Called by CnFeaturesAndRoadmapPage,\n * CnFeaturesAndRoadmapView and CnCapabilityTable when a comparison is\n * actually given, so an app that passes none hears nothing.\n *\n * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated\n * @param {string} source What carried the comparison, for the message.\n * @return {boolean} True when this call printed the warning.\n */\nexport function warnCapabilityComparisonDeprecated(source) {\n\tif (deprecationWarned || process.env.NODE_ENV === 'production') {\n\t\treturn false\n\t}\n\tdeprecationWarned = true\n\t// eslint-disable-next-line no-console -- a deliberate deprecation notice to the developer integrating the component\n\tconsole.warn(`[nextcloud-vue] ${source} is deprecated: the in-app capability comparison (capabilityComparison, the features_roadmap_capabilities initial state, CnCapabilityTable) will be removed in a future major version. Publish the comparison on the app's docs site and link to it instead.`)\n\treturn true\n}\n\n/**\n * Ratings in the order they are counted and rendered.\n *\n * @type {ReadonlyArray<string>}\n */\nexport const RATINGS = Object.freeze(['yes', 'partial', 'no'])\n\n/**\n * The columns the totals table and the legend show: the ratings plus\n * `unknown`. `unknown` is a real answer, not a data error. A later audit round\n * can add a capability row without re-reading the products an earlier round\n * rated, and the honest cell for those columns is empty.\n *\n * @type {ReadonlyArray<string>}\n */\nexport const RATING_COLUMNS = Object.freeze([...RATINGS, 'unknown'])\n\n/**\n * The provider kinds the document may declare. Anything else is carried\n * through as `unknown`, which renders as a plain chip rather than a styled\n * one.\n *\n * @type {ReadonlyArray<string>}\n */\nexport const PROVIDER_KINDS = Object.freeze(['self', 'app', 'platform'])\n\n/**\n * Group key for rows that carry no `feature`. It is a bucket, never a filter:\n * the rows render under a heading that says they are not mapped yet.\n *\n * @type {string}\n */\nexport const UNMAPPED_GROUP_KEY = '__cn-unmapped__'\n\n/**\n * Lower-case and strip diacritics so a reader typing `zaken` finds `Zaken` and\n * a reader typing `een` finds `Eén`.\n *\n * `normalize('NFD')` splits a letter from its accent and the range strips the\n * combining marks. Both have been in every browser and in Node since long\n * before this library's floor, so the accent-insensitive half costs one regex.\n *\n * @param {unknown} value Any value; non-strings are coerced.\n * @return {string} The comparable form.\n */\nexport function normaliseText(value) {\n\tif (value === null || value === undefined) {\n\t\treturn ''\n\t}\n\treturn String(value)\n\t\t.normalize('NFD')\n\t\t.replace(/[\\u0300-\\u036f]/g, '')\n\t\t.toLowerCase()\n\t\t.trim()\n}\n\n/**\n * Pick the reader's language variant of a labelled entry.\n *\n * The document carries `name` (English) and `name_nl` (Dutch) side by side.\n * Dutch wins only for a Dutch locale AND only when the Dutch string is\n * actually there, so a half-translated document degrades to English rather\n * than to a blank cell.\n *\n * @param {{name?: string, name_nl?: string}} entry Labelled entry.\n * @param {string} [locale] BCP 47 locale, e.g. `nl`, `nl_NL`, `en-GB`.\n * @return {string} The label to render.\n */\nexport function labelFor(entry, locale = 'en') {\n\tif (!entry) {\n\t\treturn ''\n\t}\n\tconst dutch = String(locale || '').toLowerCase().startsWith('nl')\n\tif (dutch && typeof entry.name_nl === 'string' && entry.name_nl.trim() !== '') {\n\t\treturn entry.name_nl\n\t}\n\treturn entry.name ?? ''\n}\n\n/**\n * Index the document's `providers` list by key.\n *\n * @param {object} comparison Parsed comparison document.\n * @return {Map<string, object>} Provider entries by key.\n */\nfunction providerIndex(comparison) {\n\tconst map = new Map()\n\tfor (const provider of comparison?.providers ?? []) {\n\t\tif (provider && typeof provider.key === 'string') {\n\t\t\tmap.set(provider.key, provider)\n\t\t}\n\t}\n\treturn map\n}\n\n/**\n * Resolve the provider of one capability row.\n *\n * An unrecognised key is NOT an error and never costs the row its place: the\n * raw key becomes the label and the kind becomes `unknown`, so a reader sees\n * that the document names a provider the dictionary forgot rather than seeing\n * nothing at all.\n *\n * @param {object} row One capability row.\n * @param {object} comparison Parsed comparison document.\n * @param {string} [locale] BCP 47 locale used to pick the provider name.\n * @return {{key: string, name: string, kind: string, declared: boolean}|null}\n * The provider, or null when the row names none.\n */\nexport function resolveProvider(row, comparison, locale = 'en') {\n\tconst key = row?.provider\n\tif (typeof key !== 'string' || key.trim() === '') {\n\t\treturn null\n\t}\n\tconst declared = providerIndex(comparison).get(key)\n\tif (!declared) {\n\t\treturn { key, name: key, kind: 'unknown', declared: false }\n\t}\n\tconst kind = PROVIDER_KINDS.includes(declared.kind) ? declared.kind : 'unknown'\n\treturn {\n\t\tkey,\n\t\tname: labelFor(declared, locale) || key,\n\t\tkind,\n\t\tdeclared: true,\n\t}\n}\n\n/**\n * Every string a search should match on for one row, joined.\n *\n * Both languages are indexed whatever the reader's locale, because a Dutch\n * reader searching for the English term they read in a tender document should\n * still find the row.\n *\n * @param {object} row One capability row.\n * @param {object} comparison Parsed comparison document.\n * @return {string} The haystack, already normalised.\n */\nexport function capabilitySearchIndex(row, comparison) {\n\tconst areas = comparison?.areas ?? []\n\tconst features = comparison?.features ?? []\n\tconst area = areas.find((entry) => entry?.key === row?.area)\n\tconst feature = features.find((entry) => entry?.key === row?.feature)\n\tconst provider = resolveProvider(row, comparison)\n\t// The declared entry and not `provider.name`: that one is already resolved\n\t// to a single language, so indexing it would honour the rule above for the\n\t// row, the area and the feature and quietly break it for the provider.\n\tconst declared = providerIndex(comparison).get(provider?.key)\n\tconst parts = [\n\t\trow?.id,\n\t\trow?.name,\n\t\trow?.name_nl,\n\t\trow?.area,\n\t\tarea?.name,\n\t\tarea?.name_nl,\n\t\trow?.feature,\n\t\tfeature?.name,\n\t\tfeature?.name_nl,\n\t\tprovider?.key,\n\t\tprovider?.name,\n\t\tdeclared?.name,\n\t\tdeclared?.name_nl,\n\t]\n\treturn parts.filter((part) => part !== null && part !== undefined && part !== '')\n\t\t.map((part) => normaliseText(part))\n\t\t.join(' ')\n}\n\n/**\n * Does one row match the reader's query?\n *\n * Every whitespace-separated term must appear somewhere in the row's index, so\n * `intake dossiq` narrows rather than widens. An empty query matches\n * everything, which is what keeps the unsearched page identical to the\n * searched-for-nothing page.\n *\n * @param {object} row One capability row.\n * @param {string} query The reader's raw input.\n * @param {object} comparison Parsed comparison document.\n * @return {boolean} True when the row should stay on the page.\n */\nexport function matchesQuery(row, query, comparison) {\n\tconst terms = normaliseText(query).split(/\\s+/).filter(Boolean)\n\tif (terms.length === 0) {\n\t\treturn true\n\t}\n\tconst haystack = capabilitySearchIndex(row, comparison)\n\treturn terms.every((term) => haystack.includes(term))\n}\n\n/**\n * The rows that survive the reader's query, in document order.\n *\n * @param {object} comparison Parsed comparison document.\n * @param {string} [query] The reader's raw input.\n * @return {Array<object>} Matching rows.\n */\nexport function filterCapabilities(comparison, query = '') {\n\treturn (comparison?.capabilities ?? []).filter((row) => matchesQuery(row, query, comparison))\n}\n\n/**\n * Count one system's ratings across a set of capability rows.\n *\n * A rating outside `RATINGS` is counted under `unknown` rather than dropped,\n * so a bad row shows up as a number that does not add up instead of\n * disappearing.\n *\n * @param {Array<object>} capabilities Capability rows.\n * @param {string} systemKey Key of the system column, e.g. `dossiq`.\n * @return {{yes: number, partial: number, no: number, unknown: number, total: number}} The tally.\n */\nexport function tally(capabilities, systemKey) {\n\tconst counts = { yes: 0, partial: 0, no: 0, unknown: 0, total: 0 }\n\tfor (const row of capabilities ?? []) {\n\t\tconst rating = row?.[systemKey]\n\t\tcounts[RATINGS.includes(rating) ? rating : 'unknown'] += 1\n\t\tcounts.total += 1\n\t}\n\treturn counts\n}\n\n/**\n * Tally every system over a set of rows.\n *\n * @param {object} comparison Parsed comparison document.\n * @param {Array<object>} [rows] Rows to count; defaults to all of them.\n * @return {Record<string, object>} Tally per system key.\n */\nexport function overallTallies(comparison, rows = null) {\n\tconst counted = rows ?? comparison?.capabilities ?? []\n\tconst out = {}\n\tfor (const system of comparison?.systems ?? []) {\n\t\tout[system.key] = tally(counted, system.key)\n\t}\n\treturn out\n}\n\n/**\n * Which grouping a document should open on.\n *\n * Feature grouping is the default the moment ANY row carries a `feature`,\n * because a document that bothered to map features wants them shown. A\n * document without them groups by area, which is what every consumer does\n * today.\n *\n * @param {object} comparison Parsed comparison document.\n * @return {'feature'|'area'} The opening mode.\n */\nexport function defaultGroupMode(comparison) {\n\tconst mapped = (comparison?.capabilities ?? []).some((row) => typeof row?.feature === 'string' && row.feature.trim() !== '')\n\treturn mapped ? 'feature' : 'area'\n}\n\n/**\n * Does any row in the document name a provider?\n *\n * Drives whether the provided-by column renders at all. A consumer whose\n * document predates the field gets exactly the columns it has today.\n *\n * @param {object} comparison Parsed comparison document.\n * @return {boolean} True when at least one row names a provider.\n */\nexport function hasProviders(comparison) {\n\treturn (comparison?.capabilities ?? []).some((row) => typeof row?.provider === 'string' && row.provider.trim() !== '')\n}\n\n/**\n * Group the rows for rendering, in the order a reader should meet them.\n *\n * Declared groups come first, in the order the document declares them: for\n * areas that is the audit's own numbering, and re-sorting would put `13.1`\n * above `1.1` on a table whose first column is that number. Then come keys the\n * rows use that the document never declared, labelled with the raw key. Last\n * comes the bucket for rows that name no group at all.\n *\n * An empty group is dropped, because a search that matches nothing in an area\n * should not leave an empty heading behind. The unmapped bucket follows the\n * same rule.\n *\n * @param {object} comparison Parsed comparison document.\n * @param {object} [options] Options.\n * @param {'feature'|'area'} [options.mode] Grouping mode.\n * @param {string} [options.locale] BCP 47 locale used for labels.\n * @param {string} [options.query] The reader's search input.\n * @param {string} [options.unmappedLabel] Heading for rows that name no group.\n * @return {Array<{key: string, label: string, declared: boolean, capabilities: Array<object>, tallies: object}>}\n * One entry per non-empty group.\n */\nexport function groupCapabilities(comparison, options = {}) {\n\tconst mode = options.mode === 'feature' ? 'feature' : 'area'\n\tconst locale = options.locale ?? 'en'\n\tconst unmappedLabel = options.unmappedLabel ?? ''\n\tconst declared = mode === 'feature' ? (comparison?.features ?? []) : (comparison?.areas ?? [])\n\tconst rows = filterCapabilities(comparison, options.query ?? '')\n\n\tconst buckets = new Map()\n\t/**\n\t * Fetch or create a bucket, keeping insertion order.\n\t *\n\t * @param {string} key Bucket key.\n\t * @param {string} label Bucket heading.\n\t * @param {boolean} isDeclared Whether the document declares this key.\n\t * @return {object} The bucket.\n\t */\n\tconst bucket = (key, label, isDeclared) => {\n\t\tif (!buckets.has(key)) {\n\t\t\tbuckets.set(key, { key, label, declared: isDeclared, capabilities: [] })\n\t\t}\n\t\treturn buckets.get(key)\n\t}\n\n\t// Seed the declared groups first so they keep the document's order even\n\t// when the rows arrive in another one.\n\tfor (const entry of declared) {\n\t\tif (entry && typeof entry.key === 'string') {\n\t\t\tbucket(entry.key, labelFor(entry, locale) || entry.key, true)\n\t\t}\n\t}\n\n\tconst unmapped = []\n\tfor (const row of rows) {\n\t\tconst key = row?.[mode]\n\t\tif (typeof key !== 'string' || key.trim() === '') {\n\t\t\tunmapped.push({ ...row, label: labelFor(row, locale) })\n\t\t\tcontinue\n\t\t}\n\t\t// An undeclared key gets a bucket of its own, labelled with the raw\n\t\t// key. Dropping the row here is the silent failure this module exists\n\t\t// to prevent.\n\t\tbucket(key, key, false).capabilities.push({ ...row, label: labelFor(row, locale) })\n\t}\n\n\tconst groups = [...buckets.values()].filter((entry) => entry.capabilities.length > 0)\n\tif (unmapped.length > 0) {\n\t\tgroups.push({\n\t\t\tkey: UNMAPPED_GROUP_KEY,\n\t\t\tlabel: unmappedLabel,\n\t\t\tdeclared: false,\n\t\t\tcapabilities: unmapped,\n\t\t})\n\t}\n\n\tfor (const group of groups) {\n\t\tgroup.tallies = overallTallies(comparison, group.capabilities)\n\t}\n\treturn groups\n}\n"],"names":[],"mappings":"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"}
@@ -142,15 +142,65 @@ function referenceTarget(column, prop) {
142
142
  return { schema, register, labelField }
143
143
  }
144
144
 
145
+ /**
146
+ * Whether a schema lists its properties. Without that the table knows its
147
+ * columns but not what backs them.
148
+ *
149
+ * @param {object|null} schema The table's schema.
150
+ * @return {boolean} True when the schema has a properties map.
151
+ */
152
+ function hasProperties(schema) {
153
+ return !!(schema && typeof schema === 'object' && schema.properties && typeof schema.properties === 'object')
154
+ }
155
+
156
+ /**
157
+ * A filter for a column when the table has no schema to read (a manifest
158
+ * page whose schema did not resolve, a host-fed table). The column's own
159
+ * hints decide the kind: an `fkResolve` widget gives a reference filter, a
160
+ * `type` or `format` on the column gives number, date or yes/no, and every
161
+ * other plain stored key gets a text filter on contains (`[like]`). A key
162
+ * that is a path (`a.b`) or a metadata field (`@self`, `_id`) gets none,
163
+ * because OpenRegister does not filter those by that name.
164
+ *
165
+ * @spec openspec/changes/audit-round-lib-fixes/specs/cn-data-table/spec.md
166
+ * @param {object} column The column definition.
167
+ * @param {object} base The shared part of the definition.
168
+ * @return {object|null} The filter definition.
169
+ */
170
+ function columnHintFilterDef(column, base) {
171
+ const key = column.key;
172
+ if (key.includes('.') || key.charAt(0) === '@' || key.charAt(0) === '_') {
173
+ return null
174
+ }
175
+ const reference = referenceTarget(column, null);
176
+ if (reference) {
177
+ return { ...base, kind: 'reference', reference }
178
+ }
179
+ const type = String(column.type || '').toLowerCase();
180
+ const format = String(column.format || '').toLowerCase();
181
+ if (type === 'boolean') {
182
+ return { ...base, kind: 'boolean' }
183
+ }
184
+ if (type === 'number' || type === 'integer') {
185
+ return { ...base, kind: 'number' }
186
+ }
187
+ if (type === 'date' || type === 'date-time' || format === 'date' || format === 'date-time') {
188
+ return { ...base, kind: 'date', dateTime: type === 'date-time' || format === 'date-time' }
189
+ }
190
+ return { ...base, kind: 'string' }
191
+ }
192
+
145
193
  /**
146
194
  * How a column filters, or null when it does not.
147
195
  *
148
196
  * Kinds: `enum` (checkbox list), `boolean` (yes, no, any), `reference`
149
197
  * (searchable list of the referenced objects), `number` and `date` (from and
150
198
  * to), and `string` (contains, through OpenRegister's `[like]`). `filterable: false` on the column turns it
151
- * off. A column with no schema property behind it only filters when it
152
- * carries an enum hint (`enum`, or a badge widget's colour map), because the
153
- * server cannot filter on a value it does not store.
199
+ * off. When the schema lists its properties, a column with no property
200
+ * behind it only filters when it carries an enum hint (`enum`, or a badge
201
+ * widget's colour map), because the server cannot filter on a value it does
202
+ * not store. When there is no schema to read, the column's own hints decide
203
+ * (see `columnHintFilterDef`), falling back to a text filter on contains.
154
204
  *
155
205
  * @param {object} column The column definition.
156
206
  * @param {object|null} schema The table's schema.
@@ -172,7 +222,7 @@ function columnFilterDef(column, schema) {
172
222
  return { ...base, kind: 'enum', options }
173
223
  }
174
224
  if (!prop) {
175
- return null
225
+ return hasProperties(schema) ? null : columnHintFilterDef(column, base)
176
226
  }
177
227
  const reference = referenceTarget(column, prop);
178
228
  if (reference) {
@@ -1 +1 @@
1
- {"version":3,"file":"columnFilters.js","sources":["../../../src/utils/columnFilters.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\n/**\n * Per-column sorting and filtering for table headers.\n *\n * Pure helpers: which column can sort, which can filter and how, and the\n * translation between a header filter's state and the OpenRegister query\n * parameters the facet sidebar already sends. A header filter writes into the\n * SAME active-filter map (`{ paramKey: values[] }`) the sidebar uses, so the\n * fetch, the route query and saved views need nothing new.\n *\n * Parameter shapes, as OpenRegister reads them:\n * - equality and \"any of\": `key=value`, `key[]=a&key[]=b`\n * - ranges: `key[gte]=from`, `key[lte]=to`\n * - contains (text): `key[like]=term`, case-insensitive (openregister#4430);\n * the server escapes `%`, `_` and `\\`, so the raw term goes out as typed\n *\n * @module utils/columnFilters\n * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md\n * @spec openspec/changes/header-filter-contains/specs/cn-data-table/spec.md#requirement-a-text-header-filter-matches-on-contains\n */\n\nimport { schemaRefSlug } from './schemaRefSlug.js'\n\n/** OpenRegister's case-insensitive contains operator, used by the text filter. */\nexport const CONTAINS_OPERATOR = 'like'\n\n/** Column keys that mark a value computed on the client, never stored. */\nconst COMPUTED_MARKERS = ['aggregate', 'compute', 'computed', 'virtual']\n\n/**\n * Normalise a `$ref` to the referenced schema slug, or null.\n *\n * @param {unknown} ref A `$ref` value.\n * @return {string|number|null} The slug.\n */\nfunction refSlug(ref) {\n\tif (typeof ref === 'number' && !Number.isNaN(ref)) {\n\t\treturn ref\n\t}\n\tif (typeof ref !== 'string' || ref === '') {\n\t\treturn null\n\t}\n\tconst tail = ref.includes('/') ? ref.substring(ref.lastIndexOf('/') + 1) : ref\n\tconst slug = tail === '' ? '' : schemaRefSlug(tail)\n\treturn slug !== '' ? slug : null\n}\n\n/**\n * The schema property behind a column, or null when the column has none.\n *\n * @param {object} column The column definition.\n * @param {object|null} schema The table's schema.\n * @return {object|null} The property.\n */\nexport function columnProperty(column, schema) {\n\tif (!column || !schema || !schema.properties || typeof column.key !== 'string') {\n\t\treturn null\n\t}\n\tconst prop = schema.properties[column.key]\n\treturn (prop && typeof prop === 'object') ? prop : null\n}\n\n/**\n * Whether a column computes its value on the client, so the server cannot\n * sort or filter on it.\n *\n * @param {object} column The column definition.\n * @return {boolean} True for a computed column.\n */\nfunction isComputedColumn(column) {\n\treturn COMPUTED_MARKERS.some((marker) => column[marker] !== undefined && column[marker] !== null && column[marker] !== false)\n}\n\n/**\n * Whether a column header sorts.\n *\n * `sortable: false` always wins, and so does an explicit `sortable: true`.\n * Otherwise, with a schema, a column sorts when a stored schema property backs\n * it: OpenRegister orders on schema properties, so a widget or computed column\n * with no property behind it stays unsortable. Without a schema nothing says\n * the server can sort, so only an explicit flag makes the column sortable,\n * which is how such tables behaved before.\n *\n * @param {object} column The column definition.\n * @param {object|null} schema The table's schema.\n * @return {boolean} True when the header sorts.\n * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-every-backed-column-sorts-by-default\n */\nexport function isColumnSortable(column, schema) {\n\tif (!column || typeof column !== 'object') {\n\t\treturn false\n\t}\n\tif (column.sortable === false) {\n\t\treturn false\n\t}\n\tif (column.sortable === true) {\n\t\treturn true\n\t}\n\tif (!schema || isComputedColumn(column)) {\n\t\treturn false\n\t}\n\tconst prop = columnProperty(column, schema)\n\treturn !!prop && prop.type !== 'object'\n}\n\n/**\n * Display options for an enum filter: schema `enum` (or `items.enum`) with\n * `x-enum-labels`, else a column `enum` hint, else a badge column's colour\n * map keys.\n *\n * @param {object} column The column definition.\n * @param {object|null} prop The schema property.\n * @return {Array<{value: string, label: string}>} The options, or [].\n */\nfunction enumOptions(column, prop) {\n\tconst p = prop || {}\n\tconst labels = p.enumLabels || p['x-enum-labels'] || (p.items && (p.items.enumLabels || p.items['x-enum-labels'])) || {}\n\tlet values = null\n\tif (Array.isArray(p.enum) && p.enum.length > 0) {\n\t\tvalues = p.enum\n\t} else if (p.items && Array.isArray(p.items.enum) && p.items.enum.length > 0) {\n\t\tvalues = p.items.enum\n\t} else if (Array.isArray(column.enum) && column.enum.length > 0) {\n\t\tvalues = column.enum\n\t} else if (column.widget === 'badge' && column.widgetProps && column.widgetProps.colorMap) {\n\t\tvalues = Object.keys(column.widgetProps.colorMap)\n\t}\n\tif (!values) {\n\t\treturn []\n\t}\n\treturn values\n\t\t.filter((v) => v !== null && v !== undefined && v !== '')\n\t\t.map((v) => ({ value: String(v), label: String(labels[v] !== undefined ? labels[v] : v) }))\n}\n\n/**\n * The reference a column points at, for a searchable object filter: a schema\n * `$ref` (or `items.$ref`), else an `fkResolve` cell widget's config.\n *\n * @param {object} column The column definition.\n * @param {object|null} prop The schema property.\n * @return {{schema: (string|number), register: string, labelField: string}|null} The reference.\n */\nfunction referenceTarget(column, prop) {\n\tconst widgetProps = (column.widget === 'fkResolve' && column.widgetProps) || null\n\tconst p = prop || {}\n\tconst schemaRef = refSlug(p.$ref) ?? (p.items ? refSlug(p.items.$ref) : null)\n\tconst schema = schemaRef ?? (widgetProps && widgetProps.schema ? widgetProps.schema : null)\n\tif (schema === null || schema === undefined || schema === '') {\n\t\treturn null\n\t}\n\tconst register = (widgetProps && widgetProps.register)\n\t\t|| p['x-external-register']\n\t\t|| (p.items && p.items['x-external-register'])\n\t\t|| ''\n\tconst labelField = (widgetProps && widgetProps.labelField) || ''\n\treturn { schema, register, labelField }\n}\n\n/**\n * How a column filters, or null when it does not.\n *\n * Kinds: `enum` (checkbox list), `boolean` (yes, no, any), `reference`\n * (searchable list of the referenced objects), `number` and `date` (from and\n * to), and `string` (contains, through OpenRegister's `[like]`). `filterable: false` on the column turns it\n * off. A column with no schema property behind it only filters when it\n * carries an enum hint (`enum`, or a badge widget's colour map), because the\n * server cannot filter on a value it does not store.\n *\n * @param {object} column The column definition.\n * @param {object|null} schema The table's schema.\n * @return {{key: string, kind: string, label: string, options: Array<{value: string, label: string}>, reference: object|null, dateTime: boolean}|null} The filter definition.\n * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-every-backed-column-filters-from-its-header\n */\nexport function columnFilterDef(column, schema) {\n\tif (!column || typeof column !== 'object' || typeof column.key !== 'string' || column.key === '') {\n\t\treturn null\n\t}\n\tif (column.filterable === false || isComputedColumn(column)) {\n\t\treturn null\n\t}\n\tconst prop = columnProperty(column, schema)\n\tconst base = { key: column.key, label: column.label || (prop && prop.title) || column.key, options: [], reference: null, dateTime: false }\n\n\tconst options = enumOptions(column, prop)\n\tif (options.length > 0) {\n\t\treturn { ...base, kind: 'enum', options }\n\t}\n\tif (!prop) {\n\t\treturn null\n\t}\n\tconst reference = referenceTarget(column, prop)\n\tif (reference) {\n\t\treturn { ...base, kind: 'reference', reference }\n\t}\n\tconst type = Array.isArray(prop.type) ? prop.type.find((t) => t !== 'null') : prop.type\n\tconst format = prop.format || ''\n\tif (type === 'boolean') {\n\t\treturn { ...base, kind: 'boolean' }\n\t}\n\tif (type === 'number' || type === 'integer') {\n\t\treturn { ...base, kind: 'number' }\n\t}\n\tif (type === 'string' && (format === 'date' || format === 'date-time')) {\n\t\treturn { ...base, kind: 'date', dateTime: format === 'date-time' }\n\t}\n\tif (type === 'string') {\n\t\treturn { ...base, kind: 'string' }\n\t}\n\treturn null\n}\n\n/**\n * Read one active-filter entry as a list of strings.\n *\n * @param {unknown} raw An active-filter value: an array, a scalar, or absent.\n * @return {Array<string>} The values.\n */\nfunction asList(raw) {\n\tif (raw === undefined || raw === null || raw === '') {\n\t\treturn []\n\t}\n\treturn (Array.isArray(raw) ? raw : [raw]).filter((v) => v !== undefined && v !== null && v !== '').map((v) => String(v))\n}\n\n/**\n * The parameter keys one column filter owns.\n *\n * @param {object} def A filter definition from `columnFilterDef`.\n * @return {Array<string>} The keys.\n */\nexport function columnFilterParamKeys(def) {\n\tif (!def) {\n\t\treturn []\n\t}\n\tif (def.kind === 'number' || def.kind === 'date') {\n\t\treturn [`${def.key}[gte]`, `${def.key}[lte]`]\n\t}\n\tif (def.kind === 'string') {\n\t\treturn [containsKey(def)]\n\t}\n\treturn [def.key]\n}\n\n/**\n * The parameter a text filter writes: `{key}[like]`. An exact `key=value`\n * from the facet sidebar or a fixed filter is a different key, so the header\n * neither shows nor clears it.\n *\n * @param {object} def A filter definition of kind `string`.\n * @return {string} The parameter key.\n * @spec openspec/changes/header-filter-contains/specs/cn-data-table/spec.md#requirement-a-text-header-filter-matches-on-contains\n */\nfunction containsKey(def) {\n\treturn `${def.key}[${CONTAINS_OPERATOR}]`\n}\n\n/**\n * A column filter's current state, read from the active-filter map.\n *\n * @param {object} def A filter definition.\n * @param {object} activeFilters The active-filter map (`{ paramKey: values }`).\n * @return {object} `{ values }` for enum and reference, `{ value }` for boolean and string, `{ from, to }` for number and date.\n */\nexport function columnFilterState(def, activeFilters) {\n\tconst filters = (activeFilters && typeof activeFilters === 'object') ? activeFilters : {}\n\tif (!def) {\n\t\treturn {}\n\t}\n\tif (def.kind === 'enum' || def.kind === 'reference') {\n\t\treturn { values: asList(filters[def.key]) }\n\t}\n\tif (def.kind === 'number' || def.kind === 'date') {\n\t\tconst from = asList(filters[`${def.key}[gte]`])[0] || ''\n\t\tconst to = asList(filters[`${def.key}[lte]`])[0] || ''\n\t\t// A date-time column is queried up to the end of the day; the input\n\t\t// shows the date the person picked.\n\t\treturn {\n\t\t\tfrom: def.kind === 'date' ? from.slice(0, 10) : from,\n\t\t\tto: def.kind === 'date' ? to.slice(0, 10) : to,\n\t\t}\n\t}\n\tif (def.kind === 'string') {\n\t\treturn { value: asList(filters[containsKey(def)])[0] || '' }\n\t}\n\treturn { value: asList(filters[def.key])[0] || '' }\n}\n\n/**\n * Whether a column filter is active.\n *\n * @param {object} def A filter definition.\n * @param {object} activeFilters The active-filter map.\n * @return {boolean} True when any of its parameters is set.\n */\nexport function isColumnFilterActive(def, activeFilters) {\n\tconst filters = (activeFilters && typeof activeFilters === 'object') ? activeFilters : {}\n\treturn columnFilterParamKeys(def).some((key) => asList(filters[key]).length > 0)\n}\n\n/**\n * The active-filter entries a column filter state produces. Every key the\n * column owns is present; an empty list clears that key, so changing a range\n * from \"from and to\" to \"from\" drops the old upper bound.\n *\n * @param {object} def A filter definition.\n * @param {object} state The column filter state (see `columnFilterState`).\n * @return {object} `{ paramKey: values[] }`.\n * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-a-header-filter-speaks-the-sidebars-query-language\n */\nexport function columnFilterParams(def, state) {\n\tconst s = state || {}\n\tif (!def) {\n\t\treturn {}\n\t}\n\tif (def.kind === 'enum' || def.kind === 'reference') {\n\t\treturn { [def.key]: asList(s.values) }\n\t}\n\tif (def.kind === 'number' || def.kind === 'date') {\n\t\tconst from = s.from === undefined || s.from === null ? '' : String(s.from).trim()\n\t\tlet to = s.to === undefined || s.to === null ? '' : String(s.to).trim()\n\t\tif (def.kind === 'date' && def.dateTime && to !== '' && to.length === 10) {\n\t\t\tto = `${to}T23:59:59`\n\t\t}\n\t\treturn {\n\t\t\t[`${def.key}[gte]`]: from === '' ? [] : [from],\n\t\t\t[`${def.key}[lte]`]: to === '' ? [] : [to],\n\t\t}\n\t}\n\tif (def.kind === 'boolean') {\n\t\treturn { [def.key]: (s.value === 'true' || s.value === 'false') ? [s.value] : [] }\n\t}\n\t// Text: the raw term under `[like]`. No wildcards: OpenRegister wraps\n\t// the term itself and escapes `%`, `_` and `\\` in it.\n\tconst value = s.value === undefined || s.value === null ? '' : String(s.value).trim()\n\treturn { [containsKey(def)]: value === '' ? [] : [value] }\n}\n\n/**\n * Clear-all entries for one column: every key it owns, emptied.\n *\n * @param {object} def A filter definition.\n * @return {object} `{ paramKey: [] }`.\n */\nexport function clearedColumnFilterParams(def) {\n\treturn Object.fromEntries(columnFilterParamKeys(def).map((key) => [key, []]))\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"columnFilters.js","sources":["../../../src/utils/columnFilters.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\n/**\n * Per-column sorting and filtering for table headers.\n *\n * Pure helpers: which column can sort, which can filter and how, and the\n * translation between a header filter's state and the OpenRegister query\n * parameters the facet sidebar already sends. A header filter writes into the\n * SAME active-filter map (`{ paramKey: values[] }`) the sidebar uses, so the\n * fetch, the route query and saved views need nothing new.\n *\n * Parameter shapes, as OpenRegister reads them:\n * - equality and \"any of\": `key=value`, `key[]=a&key[]=b`\n * - ranges: `key[gte]=from`, `key[lte]=to`\n * - contains (text): `key[like]=term`, case-insensitive (openregister#4430);\n * the server escapes `%`, `_` and `\\`, so the raw term goes out as typed\n *\n * @module utils/columnFilters\n * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md\n * @spec openspec/changes/header-filter-contains/specs/cn-data-table/spec.md#requirement-a-text-header-filter-matches-on-contains\n */\n\nimport { schemaRefSlug } from './schemaRefSlug.js'\n\n/** OpenRegister's case-insensitive contains operator, used by the text filter. */\nexport const CONTAINS_OPERATOR = 'like'\n\n/** Column keys that mark a value computed on the client, never stored. */\nconst COMPUTED_MARKERS = ['aggregate', 'compute', 'computed', 'virtual']\n\n/**\n * Normalise a `$ref` to the referenced schema slug, or null.\n *\n * @param {unknown} ref A `$ref` value.\n * @return {string|number|null} The slug.\n */\nfunction refSlug(ref) {\n\tif (typeof ref === 'number' && !Number.isNaN(ref)) {\n\t\treturn ref\n\t}\n\tif (typeof ref !== 'string' || ref === '') {\n\t\treturn null\n\t}\n\tconst tail = ref.includes('/') ? ref.substring(ref.lastIndexOf('/') + 1) : ref\n\tconst slug = tail === '' ? '' : schemaRefSlug(tail)\n\treturn slug !== '' ? slug : null\n}\n\n/**\n * The schema property behind a column, or null when the column has none.\n *\n * @param {object} column The column definition.\n * @param {object|null} schema The table's schema.\n * @return {object|null} The property.\n */\nexport function columnProperty(column, schema) {\n\tif (!column || !schema || !schema.properties || typeof column.key !== 'string') {\n\t\treturn null\n\t}\n\tconst prop = schema.properties[column.key]\n\treturn (prop && typeof prop === 'object') ? prop : null\n}\n\n/**\n * Whether a column computes its value on the client, so the server cannot\n * sort or filter on it.\n *\n * @param {object} column The column definition.\n * @return {boolean} True for a computed column.\n */\nfunction isComputedColumn(column) {\n\treturn COMPUTED_MARKERS.some((marker) => column[marker] !== undefined && column[marker] !== null && column[marker] !== false)\n}\n\n/**\n * Whether a column header sorts.\n *\n * `sortable: false` always wins, and so does an explicit `sortable: true`.\n * Otherwise, with a schema, a column sorts when a stored schema property backs\n * it: OpenRegister orders on schema properties, so a widget or computed column\n * with no property behind it stays unsortable. Without a schema nothing says\n * the server can sort, so only an explicit flag makes the column sortable,\n * which is how such tables behaved before.\n *\n * @param {object} column The column definition.\n * @param {object|null} schema The table's schema.\n * @return {boolean} True when the header sorts.\n * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-every-backed-column-sorts-by-default\n */\nexport function isColumnSortable(column, schema) {\n\tif (!column || typeof column !== 'object') {\n\t\treturn false\n\t}\n\tif (column.sortable === false) {\n\t\treturn false\n\t}\n\tif (column.sortable === true) {\n\t\treturn true\n\t}\n\tif (!schema || isComputedColumn(column)) {\n\t\treturn false\n\t}\n\tconst prop = columnProperty(column, schema)\n\treturn !!prop && prop.type !== 'object'\n}\n\n/**\n * Display options for an enum filter: schema `enum` (or `items.enum`) with\n * `x-enum-labels`, else a column `enum` hint, else a badge column's colour\n * map keys.\n *\n * @param {object} column The column definition.\n * @param {object|null} prop The schema property.\n * @return {Array<{value: string, label: string}>} The options, or [].\n */\nfunction enumOptions(column, prop) {\n\tconst p = prop || {}\n\tconst labels = p.enumLabels || p['x-enum-labels'] || (p.items && (p.items.enumLabels || p.items['x-enum-labels'])) || {}\n\tlet values = null\n\tif (Array.isArray(p.enum) && p.enum.length > 0) {\n\t\tvalues = p.enum\n\t} else if (p.items && Array.isArray(p.items.enum) && p.items.enum.length > 0) {\n\t\tvalues = p.items.enum\n\t} else if (Array.isArray(column.enum) && column.enum.length > 0) {\n\t\tvalues = column.enum\n\t} else if (column.widget === 'badge' && column.widgetProps && column.widgetProps.colorMap) {\n\t\tvalues = Object.keys(column.widgetProps.colorMap)\n\t}\n\tif (!values) {\n\t\treturn []\n\t}\n\treturn values\n\t\t.filter((v) => v !== null && v !== undefined && v !== '')\n\t\t.map((v) => ({ value: String(v), label: String(labels[v] !== undefined ? labels[v] : v) }))\n}\n\n/**\n * The reference a column points at, for a searchable object filter: a schema\n * `$ref` (or `items.$ref`), else an `fkResolve` cell widget's config.\n *\n * @param {object} column The column definition.\n * @param {object|null} prop The schema property.\n * @return {{schema: (string|number), register: string, labelField: string}|null} The reference.\n */\nfunction referenceTarget(column, prop) {\n\tconst widgetProps = (column.widget === 'fkResolve' && column.widgetProps) || null\n\tconst p = prop || {}\n\tconst schemaRef = refSlug(p.$ref) ?? (p.items ? refSlug(p.items.$ref) : null)\n\tconst schema = schemaRef ?? (widgetProps && widgetProps.schema ? widgetProps.schema : null)\n\tif (schema === null || schema === undefined || schema === '') {\n\t\treturn null\n\t}\n\tconst register = (widgetProps && widgetProps.register)\n\t\t|| p['x-external-register']\n\t\t|| (p.items && p.items['x-external-register'])\n\t\t|| ''\n\tconst labelField = (widgetProps && widgetProps.labelField) || ''\n\treturn { schema, register, labelField }\n}\n\n/**\n * Whether a schema lists its properties. Without that the table knows its\n * columns but not what backs them.\n *\n * @param {object|null} schema The table's schema.\n * @return {boolean} True when the schema has a properties map.\n */\nfunction hasProperties(schema) {\n\treturn !!(schema && typeof schema === 'object' && schema.properties && typeof schema.properties === 'object')\n}\n\n/**\n * A filter for a column when the table has no schema to read (a manifest\n * page whose schema did not resolve, a host-fed table). The column's own\n * hints decide the kind: an `fkResolve` widget gives a reference filter, a\n * `type` or `format` on the column gives number, date or yes/no, and every\n * other plain stored key gets a text filter on contains (`[like]`). A key\n * that is a path (`a.b`) or a metadata field (`@self`, `_id`) gets none,\n * because OpenRegister does not filter those by that name.\n *\n * @spec openspec/changes/audit-round-lib-fixes/specs/cn-data-table/spec.md\n * @param {object} column The column definition.\n * @param {object} base The shared part of the definition.\n * @return {object|null} The filter definition.\n */\nfunction columnHintFilterDef(column, base) {\n\tconst key = column.key\n\tif (key.includes('.') || key.charAt(0) === '@' || key.charAt(0) === '_') {\n\t\treturn null\n\t}\n\tconst reference = referenceTarget(column, null)\n\tif (reference) {\n\t\treturn { ...base, kind: 'reference', reference }\n\t}\n\tconst type = String(column.type || '').toLowerCase()\n\tconst format = String(column.format || '').toLowerCase()\n\tif (type === 'boolean') {\n\t\treturn { ...base, kind: 'boolean' }\n\t}\n\tif (type === 'number' || type === 'integer') {\n\t\treturn { ...base, kind: 'number' }\n\t}\n\tif (type === 'date' || type === 'date-time' || format === 'date' || format === 'date-time') {\n\t\treturn { ...base, kind: 'date', dateTime: type === 'date-time' || format === 'date-time' }\n\t}\n\treturn { ...base, kind: 'string' }\n}\n\n/**\n * How a column filters, or null when it does not.\n *\n * Kinds: `enum` (checkbox list), `boolean` (yes, no, any), `reference`\n * (searchable list of the referenced objects), `number` and `date` (from and\n * to), and `string` (contains, through OpenRegister's `[like]`). `filterable: false` on the column turns it\n * off. When the schema lists its properties, a column with no property\n * behind it only filters when it carries an enum hint (`enum`, or a badge\n * widget's colour map), because the server cannot filter on a value it does\n * not store. When there is no schema to read, the column's own hints decide\n * (see `columnHintFilterDef`), falling back to a text filter on contains.\n *\n * @param {object} column The column definition.\n * @param {object|null} schema The table's schema.\n * @return {{key: string, kind: string, label: string, options: Array<{value: string, label: string}>, reference: object|null, dateTime: boolean}|null} The filter definition.\n * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-every-backed-column-filters-from-its-header\n */\nexport function columnFilterDef(column, schema) {\n\tif (!column || typeof column !== 'object' || typeof column.key !== 'string' || column.key === '') {\n\t\treturn null\n\t}\n\tif (column.filterable === false || isComputedColumn(column)) {\n\t\treturn null\n\t}\n\tconst prop = columnProperty(column, schema)\n\tconst base = { key: column.key, label: column.label || (prop && prop.title) || column.key, options: [], reference: null, dateTime: false }\n\n\tconst options = enumOptions(column, prop)\n\tif (options.length > 0) {\n\t\treturn { ...base, kind: 'enum', options }\n\t}\n\tif (!prop) {\n\t\treturn hasProperties(schema) ? null : columnHintFilterDef(column, base)\n\t}\n\tconst reference = referenceTarget(column, prop)\n\tif (reference) {\n\t\treturn { ...base, kind: 'reference', reference }\n\t}\n\tconst type = Array.isArray(prop.type) ? prop.type.find((t) => t !== 'null') : prop.type\n\tconst format = prop.format || ''\n\tif (type === 'boolean') {\n\t\treturn { ...base, kind: 'boolean' }\n\t}\n\tif (type === 'number' || type === 'integer') {\n\t\treturn { ...base, kind: 'number' }\n\t}\n\tif (type === 'string' && (format === 'date' || format === 'date-time')) {\n\t\treturn { ...base, kind: 'date', dateTime: format === 'date-time' }\n\t}\n\tif (type === 'string') {\n\t\treturn { ...base, kind: 'string' }\n\t}\n\treturn null\n}\n\n/**\n * Read one active-filter entry as a list of strings.\n *\n * @param {unknown} raw An active-filter value: an array, a scalar, or absent.\n * @return {Array<string>} The values.\n */\nfunction asList(raw) {\n\tif (raw === undefined || raw === null || raw === '') {\n\t\treturn []\n\t}\n\treturn (Array.isArray(raw) ? raw : [raw]).filter((v) => v !== undefined && v !== null && v !== '').map((v) => String(v))\n}\n\n/**\n * The parameter keys one column filter owns.\n *\n * @param {object} def A filter definition from `columnFilterDef`.\n * @return {Array<string>} The keys.\n */\nexport function columnFilterParamKeys(def) {\n\tif (!def) {\n\t\treturn []\n\t}\n\tif (def.kind === 'number' || def.kind === 'date') {\n\t\treturn [`${def.key}[gte]`, `${def.key}[lte]`]\n\t}\n\tif (def.kind === 'string') {\n\t\treturn [containsKey(def)]\n\t}\n\treturn [def.key]\n}\n\n/**\n * The parameter a text filter writes: `{key}[like]`. An exact `key=value`\n * from the facet sidebar or a fixed filter is a different key, so the header\n * neither shows nor clears it.\n *\n * @param {object} def A filter definition of kind `string`.\n * @return {string} The parameter key.\n * @spec openspec/changes/header-filter-contains/specs/cn-data-table/spec.md#requirement-a-text-header-filter-matches-on-contains\n */\nfunction containsKey(def) {\n\treturn `${def.key}[${CONTAINS_OPERATOR}]`\n}\n\n/**\n * A column filter's current state, read from the active-filter map.\n *\n * @param {object} def A filter definition.\n * @param {object} activeFilters The active-filter map (`{ paramKey: values }`).\n * @return {object} `{ values }` for enum and reference, `{ value }` for boolean and string, `{ from, to }` for number and date.\n */\nexport function columnFilterState(def, activeFilters) {\n\tconst filters = (activeFilters && typeof activeFilters === 'object') ? activeFilters : {}\n\tif (!def) {\n\t\treturn {}\n\t}\n\tif (def.kind === 'enum' || def.kind === 'reference') {\n\t\treturn { values: asList(filters[def.key]) }\n\t}\n\tif (def.kind === 'number' || def.kind === 'date') {\n\t\tconst from = asList(filters[`${def.key}[gte]`])[0] || ''\n\t\tconst to = asList(filters[`${def.key}[lte]`])[0] || ''\n\t\t// A date-time column is queried up to the end of the day; the input\n\t\t// shows the date the person picked.\n\t\treturn {\n\t\t\tfrom: def.kind === 'date' ? from.slice(0, 10) : from,\n\t\t\tto: def.kind === 'date' ? to.slice(0, 10) : to,\n\t\t}\n\t}\n\tif (def.kind === 'string') {\n\t\treturn { value: asList(filters[containsKey(def)])[0] || '' }\n\t}\n\treturn { value: asList(filters[def.key])[0] || '' }\n}\n\n/**\n * Whether a column filter is active.\n *\n * @param {object} def A filter definition.\n * @param {object} activeFilters The active-filter map.\n * @return {boolean} True when any of its parameters is set.\n */\nexport function isColumnFilterActive(def, activeFilters) {\n\tconst filters = (activeFilters && typeof activeFilters === 'object') ? activeFilters : {}\n\treturn columnFilterParamKeys(def).some((key) => asList(filters[key]).length > 0)\n}\n\n/**\n * The active-filter entries a column filter state produces. Every key the\n * column owns is present; an empty list clears that key, so changing a range\n * from \"from and to\" to \"from\" drops the old upper bound.\n *\n * @param {object} def A filter definition.\n * @param {object} state The column filter state (see `columnFilterState`).\n * @return {object} `{ paramKey: values[] }`.\n * @spec openspec/changes/table-header-sort-and-filter/specs/cn-data-table/spec.md#requirement-a-header-filter-speaks-the-sidebars-query-language\n */\nexport function columnFilterParams(def, state) {\n\tconst s = state || {}\n\tif (!def) {\n\t\treturn {}\n\t}\n\tif (def.kind === 'enum' || def.kind === 'reference') {\n\t\treturn { [def.key]: asList(s.values) }\n\t}\n\tif (def.kind === 'number' || def.kind === 'date') {\n\t\tconst from = s.from === undefined || s.from === null ? '' : String(s.from).trim()\n\t\tlet to = s.to === undefined || s.to === null ? '' : String(s.to).trim()\n\t\tif (def.kind === 'date' && def.dateTime && to !== '' && to.length === 10) {\n\t\t\tto = `${to}T23:59:59`\n\t\t}\n\t\treturn {\n\t\t\t[`${def.key}[gte]`]: from === '' ? [] : [from],\n\t\t\t[`${def.key}[lte]`]: to === '' ? [] : [to],\n\t\t}\n\t}\n\tif (def.kind === 'boolean') {\n\t\treturn { [def.key]: (s.value === 'true' || s.value === 'false') ? [s.value] : [] }\n\t}\n\t// Text: the raw term under `[like]`. No wildcards: OpenRegister wraps\n\t// the term itself and escapes `%`, `_` and `\\` in it.\n\tconst value = s.value === undefined || s.value === null ? '' : String(s.value).trim()\n\treturn { [containsKey(def)]: value === '' ? [] : [value] }\n}\n\n/**\n * Clear-all entries for one column: every key it owns, emptied.\n *\n * @param {object} def A filter definition.\n * @return {object} `{ paramKey: [] }`.\n */\nexport function clearedColumnFilterParams(def) {\n\treturn Object.fromEntries(columnFilterParamKeys(def).map((key) => [key, []]))\n}\n"],"names":[],"mappings":"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"}