@conduction/nextcloud-vue 2.140.0 → 2.142.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 (123) hide show
  1. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
  2. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +10 -2
  3. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +24 -3
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +3 -3
  9. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  10. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  11. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +12 -0
  12. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  13. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +57 -40
  14. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +26 -3
  20. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  21. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +2 -1
  22. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  23. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  24. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +34 -3
  25. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  26. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  27. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  28. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +43 -3
  29. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  30. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +1 -1
  31. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +21 -1
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -1
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +14 -0
  37. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  38. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +65 -1
  39. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  40. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +120 -69
  41. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  42. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  43. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +47 -9
  44. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  47. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +3 -3
  48. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  49. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  51. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +3 -1
  52. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  53. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
  54. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  55. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -1
  56. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +31 -1
  57. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
  58. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
  59. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  60. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +2 -0
  61. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  62. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +1 -1
  63. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  64. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
  65. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
  66. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
  67. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +34 -26
  68. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
  69. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue.js +1 -1
  70. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js +52 -1
  71. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js.map +1 -1
  72. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js +119 -65
  73. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js.map +1 -1
  74. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
  75. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +86 -4
  76. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  77. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +67 -5
  78. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  79. package/dist/esm/l10n/en.json.js +11 -1
  80. package/dist/esm/l10n/en.json.js.map +1 -1
  81. package/dist/esm/l10n/nl.json.js +12 -2
  82. package/dist/esm/l10n/nl.json.js.map +1 -1
  83. package/dist/esm/library-style.js +1 -1
  84. package/dist/esm/nextcloud-vue.css +1116 -686
  85. package/dist/esm/utils/buildViewTree.js +23 -8
  86. package/dist/esm/utils/buildViewTree.js.map +1 -1
  87. package/dist/esm/utils/groupSublines.js +120 -0
  88. package/dist/esm/utils/groupSublines.js.map +1 -0
  89. package/dist/nextcloud-vue.cjs.js +2023 -1239
  90. package/dist/nextcloud-vue.cjs.js.map +2 -2
  91. package/dist/nextcloud-vue.css +1116 -686
  92. package/l10n/en.json +11 -1
  93. package/l10n/nl.json +12 -2
  94. package/package.json +1 -1
  95. package/src/components/CnAssignDialog/CnAssignDialog.vue +10 -2
  96. package/src/components/CnCellRenderer/CnCellRenderer.vue +26 -4
  97. package/src/components/CnChartWidget/CnChartWidget.md +1 -0
  98. package/src/components/CnChartWidget/CnChartWidget.vue +49 -4
  99. package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
  100. package/src/components/CnDataTable/CnDataTable.md +1 -0
  101. package/src/components/CnDataTable/CnDataTable.vue +28 -4
  102. package/src/components/CnFactsWidget/CnFactsWidget.vue +34 -3
  103. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +44 -4
  104. package/src/components/CnIndexPage/CnIndexPage.vue +22 -1
  105. package/src/components/CnIndexPage/useSelfFetchList.js +14 -0
  106. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +101 -3
  107. package/src/components/CnMapWidget/CnMapWidget.vue +47 -9
  108. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +3 -3
  109. package/src/components/CnObjectSidebar/CnFilesTab.vue +4 -1
  110. package/src/components/CnPartyPicker/CnPartyPicker.vue +31 -1
  111. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +20 -1
  112. package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +33 -9
  113. package/src/css/dialog.css +13 -1
  114. package/src/css/look-board-detail.css +107 -9
  115. package/src/css/look-board-index.css +143 -7
  116. package/src/css/look-board.css +109 -0
  117. package/src/css/table.css +12 -0
  118. package/src/integrations/builtin/contacts/CnContactsTab.vue +101 -26
  119. package/src/integrations/builtin/contacts/__tests__/CnContactsTabBoardRows.spec.js +67 -0
  120. package/src/integrations/builtin/deck/CnDeckCard.vue +121 -2
  121. package/src/integrations/builtin/deck/__tests__/CnDeckCardHeaderActions.spec.js +68 -0
  122. package/src/utils/buildViewTree.js +23 -8
  123. package/src/utils/groupSublines.js +125 -0
package/l10n/en.json CHANGED
@@ -2381,7 +2381,17 @@
2381
2381
  "me": "me",
2382
2382
  "Open profile": "Open profile",
2383
2383
  "Link a contact from your address book.": "Link a contact from your address book.",
2384
- "The value could not be shown.": "The value could not be shown."
2384
+ "The value could not be shown.": "The value could not be shown.",
2385
+ "No value": "No value",
2386
+ "Link an existing card": "Link an existing card",
2387
+ "New card": "New card",
2388
+ "Could not link card.": "Could not link card.",
2389
+ "Could not create card.": "Could not create card.",
2390
+ "This card is already linked.": "This card is already linked.",
2391
+ "From {date}": "From {date}",
2392
+ "Until {date}": "Until {date}",
2393
+ "More actions for {name}": "More actions for {name}",
2394
+ "Unlink contact": "Unlink contact"
2385
2395
  },
2386
2396
  "plurals": {
2387
2397
  "{count} member": [
package/l10n/nl.json CHANGED
@@ -1989,7 +1989,7 @@
1989
1989
  "Save current view": "Huidige weergave opslaan",
1990
1990
  "Share view: {name}": "Weergave delen: {name}",
1991
1991
  "The view \"{name}\" leaves your list and the menu on the left.": "De weergave \"{name}\" verdwijnt uit uw lijst en uit het menu links.",
1992
- "Whoever gets the view sees it under Shared with me. It is yours and stays yours.": "Wie de weergave krijgt, ziet die onder Gedeeld met mij. Die is van u en blijft dat.",
1992
+ "Whoever gets the view sees it under Shared with me. It is yours and stays yours.": "Wie de weergave krijgt, ziet hem onder Gedeeld met mij. Hij is van u en blijft dat.",
1993
1993
  "Whoever you share the view with sees the change at once.": "Iedereen met wie u de weergave deelt, ziet de wijziging meteen.",
1994
1994
  "Reason": "Reden",
1995
1995
  "Could not be read": "Kon niet worden gelezen",
@@ -2634,7 +2634,17 @@
2634
2634
  "The value could not be shown.": "De waarde kon niet worden getoond.",
2635
2635
  "Save as draft": "Opslaan als concept",
2636
2636
  "Draft saved at {time}": "Concept opgeslagen om {time}",
2637
- "The draft could not be saved": "Het concept kon niet worden opgeslagen"
2637
+ "The draft could not be saved": "Het concept kon niet worden opgeslagen",
2638
+ "No value": "Geen waarde",
2639
+ "Link an existing card": "Bestaande kaart koppelen",
2640
+ "New card": "Nieuwe kaart",
2641
+ "Could not link card.": "De kaart kon niet worden gekoppeld.",
2642
+ "Could not create card.": "De kaart kon niet worden gemaakt.",
2643
+ "This card is already linked.": "Deze kaart is al gekoppeld.",
2644
+ "From {date}": "Vanaf {date}",
2645
+ "Until {date}": "Tot {date}",
2646
+ "More actions for {name}": "Meer acties voor {name}",
2647
+ "Unlink contact": "Contact ontkoppelen"
2638
2648
  },
2639
2649
  "plurals": {
2640
2650
  "{count} member": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.140.0",
3
+ "version": "2.142.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -89,6 +89,7 @@ import { generateUrl } from '@nextcloud/router'
89
89
  import { NcButton, NcCheckboxRadioSwitch, NcTextArea } from '@nextcloud/vue'
90
90
  import { fetchEndpointSource, interpolateUrlTokens } from '../../composables/useEndpointSource.js'
91
91
  import { fillTemplate } from '../../utils/fillTemplate.js'
92
+ import { groupSublines } from '../../utils/groupSublines.js'
92
93
  import { readPath } from '../../utils/readPath.js'
93
94
  import { searchNextcloudGroups, searchNextcloudUsers } from '../../utils/userAutocomplete.js'
94
95
  import { CnDialog } from '../CnDialog/index.js'
@@ -134,7 +135,10 @@ export function withTypedAssignee(found, typed, label) {
134
135
  * `eyebrow` a template filled from the record.
135
136
  * - `source`: an endpointSource to read the record from, else `object`.
136
137
  * - `assignee`: `{ kinds?, label?, placeholder?, hint?, currentPath?,
137
- * currentHint?, currentAs?, allowTyped?, field?, kindField? }`. `kinds` is `["group"]`
138
+ * currentHint?, currentAs?, allowTyped?, field?, kindField?, describe? }`.
139
+ * `describe` (`{ register, schema, groupField?, textField? }`) names the
140
+ * records that hold a team's description; each team option then reads
141
+ * "{count} members · {description}", the count where the viewer may read it. `kinds` is `["group"]`
138
142
  * (default), `["user"]` or both; `currentPath` reads who has it now from the
139
143
  * record and `currentHint` ("Now with {current}.", the placeholder named
140
144
  * by `currentAs`) says so, in place of `hint`; `allowTyped`
@@ -398,8 +402,12 @@ export default {
398
402
  if (seq !== this.searchSeq) {
399
403
  return
400
404
  }
405
+ const lines = await groupSublines(groups.map((g) => g.id), typed, this.assigneeConfig.describe || null)
406
+ if (seq !== this.searchSeq) {
407
+ return
408
+ }
401
409
  const found = [
402
- ...groups.map((g) => ({ id: g.id, label: g.label, secondary: g.subline || '', icon: 'AccountGroupOutline', kind: 'group' })),
410
+ ...groups.map((g) => ({ id: g.id, label: g.label, secondary: lines[g.id] || g.subline || '', icon: 'AccountGroupOutline', kind: 'group' })),
403
411
  ...users.map((u) => ({ id: u.id, label: u.label, secondary: u.subline || '', avatar: true, kind: 'user' })),
404
412
  ]
405
413
  this.options = this.assigneeConfig.allowTyped ? withTypedAssignee(found, typed, t('nextcloud-vue', 'Use "{name}"', { name: typed })) : found
@@ -175,7 +175,7 @@
175
175
  </template>
176
176
  <template v-else-if="widget === 'avatar'">
177
177
  <span v-if="avatarName || avatarUserId" class="cn-cell-renderer__avatar">
178
- <span v-if="avatarUserId" class="cn-cell-renderer__avatar-picture" aria-hidden="true">
178
+ <span v-if="avatarUserId && !isBoardLook" class="cn-cell-renderer__avatar-picture" aria-hidden="true">
179
179
  <NcAvatar
180
180
  :user="avatarUserId"
181
181
  :displayName="avatarName || avatarUserId"
@@ -186,6 +186,7 @@
186
186
  <span
187
187
  v-else
188
188
  class="cn-cell-renderer__avatar-initials"
189
+ :class="avatarToneClass"
189
190
  :style="{ width: avatarSize + 'px', height: avatarSize + 'px' }"
190
191
  aria-hidden="true">{{ avatarInitials }}</span>
191
192
  <span class="cn-cell-renderer__avatar-text">
@@ -942,6 +943,19 @@ export default {
942
943
  * @return {number}
943
944
  * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells
944
945
  */
946
+ /**
947
+ * `avatar` widget: the chip's tone class in the board look,
948
+ * `widgetProps.tone` `neutral` (DqContacten's grey chip) or the
949
+ * default light primary.
950
+ *
951
+ * @return {string}
952
+ * @spec openspec/changes/board-avatar-chip/specs/data-table/spec.md#requirement-a-board-avatar-cell-draws-the-initials-chip
953
+ */
954
+ avatarToneClass() {
955
+ const tone = this.widgetProps && this.widgetProps.tone
956
+ return tone === 'neutral' ? 'cn-cell-renderer__avatar-initials--neutral' : ''
957
+ },
958
+
945
959
  avatarSize() {
946
960
  const size = Number(this.widgetProps && this.widgetProps.size)
947
961
  return Number.isFinite(size) && size > 0 ? size : (this.isBoardLook ? 32 : 24)
@@ -1737,8 +1751,9 @@ export default {
1737
1751
 
1738
1752
  /**
1739
1753
  * The swatch colour — read from the sibling row field named by
1740
- * `format.colorField` (default `'color'`). Returns null when no usable
1741
- * colour string is present (the dot is then omitted).
1754
+ * `format.colorField` (default `'color'`), through `format.colorMap`
1755
+ * when given (a stored name such as `blue` to a CSS colour). Returns
1756
+ * null when no usable colour is present (the dot is then omitted).
1742
1757
  *
1743
1758
  * @return {string|null}
1744
1759
  */
@@ -1748,7 +1763,14 @@ export default {
1748
1763
  }
1749
1764
  const field = (this.format && this.format.colorField) || 'color'
1750
1765
  const c = this.row && this.row[field]
1751
- return (typeof c === 'string' && c.trim() !== '') ? c : null
1766
+ if (typeof c !== 'string' || c.trim() === '') {
1767
+ return null
1768
+ }
1769
+ const map = this.format && this.format.colorMap
1770
+ if (map && typeof map === 'object') {
1771
+ return typeof map[c] === 'string' && map[c] !== '' ? map[c] : null
1772
+ }
1773
+ return c
1752
1774
  },
1753
1775
 
1754
1776
  formattedValue() {
@@ -135,3 +135,4 @@ The `dataSource` OBJECT-form `aggregate` (Wave 3, #91) — `{ groupBy, metric?:
135
135
  | Prop | Type | Default | Description |
136
136
  |------|------|---------|-------------|
137
137
  | `widgetId` | String | `''` | Matches `cn:widget:refresh` event-bus events; on a matching id the chart re-queries its `dataSource`. Also exposes a ref-callable `refresh()`. |
138
+ | `barValueAtEnd` | Boolean | `false` | A `bar-list` without a track: each bar as long as its share and its value right after the bar's end (DqProcesanalyse "Doorlooptijd per status"). False keeps the track and the value column. |
@@ -65,7 +65,7 @@
65
65
  share of `barMax` (default the largest value), the value at the
66
66
  end. Rows navigate when the source declares a drilldown. -->
67
67
  <div v-else-if="type === 'bar-list'" class="cn-chart-widget__html" data-testid="cn-chart-widget-bar-list">
68
- <ul class="cn-chart-widget__bar-list">
68
+ <ul class="cn-chart-widget__bar-list" :class="{ 'cn-chart-widget__bar-list--value-at-end': barValueAtEnd }">
69
69
  <li v-for="row in barListRows" :key="row.index" class="cn-chart-widget__bar-row">
70
70
  <component
71
71
  :is="row.drill ? 'button' : 'div'"
@@ -75,10 +75,16 @@
75
75
  data-testid="cn-chart-widget-bar-row"
76
76
  @click="row.drill ? onDataPointSelection(null, null, { dataPointIndex: row.index }) : null">
77
77
  <span class="cn-chart-widget__bar-label">{{ row.label }}</span>
78
- <span class="cn-chart-widget__bar-track" aria-hidden="true">
79
- <span class="cn-chart-widget__bar-fill" :style="{ width: `${row.share}%` }" />
78
+ <span v-if="barValueAtEnd" class="cn-chart-widget__bar-track">
79
+ <span class="cn-chart-widget__bar-fill" aria-hidden="true" :style="{ flexBasis: `${row.share}%` }" />
80
+ <span class="cn-chart-widget__bar-value">{{ row.text }}</span>
80
81
  </span>
81
- <span class="cn-chart-widget__bar-value">{{ row.text }}</span>
82
+ <template v-else>
83
+ <span class="cn-chart-widget__bar-track" aria-hidden="true">
84
+ <span class="cn-chart-widget__bar-fill" :style="{ width: `${row.share}%` }" />
85
+ </span>
86
+ <span class="cn-chart-widget__bar-value">{{ row.text }}</span>
87
+ </template>
82
88
  </component>
83
89
  </li>
84
90
  </ul>
@@ -468,6 +474,18 @@ export default {
468
474
  default: null,
469
475
  },
470
476
 
477
+ /**
478
+ * A `bar-list` without a track: each bar as long as its share and its
479
+ * value right after the bar's end (DqProcesanalyse "Doorlooptijd per
480
+ * status"). False, the default, keeps the track and the value column.
481
+ *
482
+ * @spec openspec/changes/chart-bar-list-value-at-end/specs/chart-bar-list-value-at-end/spec.md#requirement-a-bar-list-can-put-each-value-at-its-bars-end
483
+ */
484
+ barValueAtEnd: {
485
+ type: Boolean,
486
+ default: false,
487
+ },
488
+
471
489
  /**
472
490
  * A `matrix`'s row-label column width in px. Null, the default, sizes
473
491
  * the column to its longest label (at least 120px); a width wraps a
@@ -3006,6 +3024,33 @@ export default {
3006
3024
  white-space: nowrap;
3007
3025
  }
3008
3026
 
3027
+ /* `barValueAtEnd`: no track; the bar takes its share of the row and gives way
3028
+ to the value right after its end, so the longest bar still fits. */
3029
+ .cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-line {
3030
+ grid-template-columns: minmax(80px, 130px) minmax(0, 1fr);
3031
+ }
3032
+
3033
+ .cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-track {
3034
+ align-items: center;
3035
+ background: none;
3036
+ display: flex;
3037
+ gap: 6px;
3038
+ height: auto;
3039
+ overflow: visible;
3040
+ }
3041
+
3042
+ .cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-fill {
3043
+ border-radius: 4px;
3044
+ flex: 0 1 auto;
3045
+ height: 18px;
3046
+ min-width: 2px;
3047
+ }
3048
+
3049
+ .cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-value {
3050
+ flex: none;
3051
+ text-align: start;
3052
+ }
3053
+
3009
3054
  .cn-chart-widget__footnote {
3010
3055
  color: var(--color-text-maxcontrast);
3011
3056
  margin: 0;
@@ -1150,6 +1150,7 @@ const CHART_PROP_KEYS = [
1150
1150
  'endpointSource',
1151
1151
  // `bar-list` / `matrix` presentation keys.
1152
1152
  'barMax',
1153
+ 'barValueAtEnd',
1153
1154
  'matrixLabelWidth',
1154
1155
  'footnote',
1155
1156
  'highlight',
@@ -194,6 +194,7 @@ export default {
194
194
  | `scrollable` | Boolean | `false` | Constrain height and make the table vertically scrollable |
195
195
  | `loadingText` | String | `'Loading…'` | Text shown below the spinner during loading |
196
196
  | `rowIcon` | String \| Function | `null` | Optional leading icon for every row: a static MDI name applied to all rows, or `(row) => iconName` to vary it per row (resolved via the CnIcon registry). Unset = no icon column. |
197
+ | `rowIconTone` | String | `'primary'` | The tone of the leading icon's tile in the board look: `primary`, `teal` or `neutral` (DqOrganisaties draws a teal tile). |
197
198
  | `rowIndicators` | Array | `[]` | State indicators the page declares for its rows: `{ id, field, equals?, in?, icon, text, tooltip? }`. The condition is `equals`, `in`, or plain truthiness when neither is given. `text` is required, so an indicator is never colour and shape alone. The page declares which indicators exist; a record cannot add one it has not declared. |
198
199
  | `rowIndicatorCap` | Number | `3` | How many declared indicators render on the row before the rest move into the row menu, reachable as `indicatorsFor(row).overflow`. |
199
200
  | `selectAllLabel` | String | `'Select all rows'` | Accessible name (`aria-label`) for the header select-all checkbox, so screen readers announce a named control (WCAG 4.1.2) |
@@ -6,6 +6,7 @@
6
6
  'cn-table-container--board': isBoardLook,
7
7
  'cn-table-container--title-plain': isBoardLook && rowTitle === 'plain',
8
8
  'cn-table-container--title-text': isBoardLook && rowTitle === 'text',
9
+ 'cn-table-container--title-strong': isBoardLook && rowTitle === 'strong',
9
10
  'cn-table-container--scrollable': scrollable,
10
11
  'cn-table-container--borderless': borderless,
11
12
  'cn-table-container--fill': fillHeight,
@@ -275,7 +276,7 @@
275
276
  <td
276
277
  v-if="rowIcon"
277
278
  class="cn-table-col--icon"
278
- :class="pinClass(selectable ? 1 : 0)"
279
+ :class="[pinClass(selectable ? 1 : 0), 'cn-table-col--icon-' + iconTone]"
279
280
  :style="pinStyle(selectable ? 1 : 0)">
280
281
  <CnIcon :name="getRowIcon(row)" :size="20" />
281
282
  </td>
@@ -649,6 +650,17 @@ export default {
649
650
  default: null,
650
651
  },
651
652
 
653
+ /**
654
+ * The tone of the leading icon's tile in the board look: `primary`
655
+ * (default), `teal` or `neutral` (DqOrganisaties draws a teal tile).
656
+ *
657
+ * @type {string}
658
+ */
659
+ rowIconTone: {
660
+ type: String,
661
+ default: 'primary',
662
+ },
663
+
652
664
  /**
653
665
  * State indicators a page declares for its rows. Each entry is
654
666
  * `{ id, field, equals?, in?, icon, text, tooltip? }`: `field` is a
@@ -856,8 +868,10 @@ export default {
856
868
  * the 15px bold title without an underline and a 13px muted secondary
857
869
  * line, as the PqTickets and OcPublicaties boards draw it; `text` is an
858
870
  * ordinary cell, for a log table whose first column is a date
859
- * (DqZaakContact). The row stays clickable either way. The Nextcloud
860
- * look ignores it.
871
+ * (DqZaakContact); `strong` is the cell's own size at weight 600 in the
872
+ * main ink without an underline, the name column of a card's list
873
+ * (DqZaaktypeStatussen). The row stays clickable either way. The
874
+ * Nextcloud look ignores it.
861
875
  *
862
876
  * @spec openspec/changes/screens-table-rows-parity/specs/index-list-board-look/spec.md#requirement-a-row-title-can-be-plain-text
863
877
  * @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-a-tables-title-column-can-read-as-plain-text
@@ -865,7 +879,7 @@ export default {
865
879
  rowTitle: {
866
880
  type: String,
867
881
  default: 'link',
868
- validator: (value) => ['link', 'plain', 'text'].includes(value),
882
+ validator: (value) => ['link', 'plain', 'text', 'strong'].includes(value),
869
883
  },
870
884
 
871
885
  /**
@@ -1173,6 +1187,16 @@ export default {
1173
1187
  },
1174
1188
 
1175
1189
  computed: {
1190
+ /**
1191
+ * The leading icon tile's tone, one the board look draws.
1192
+ *
1193
+ * @return {string} `primary`, `teal` or `neutral`.
1194
+ * @spec openspec/changes/board-row-icon-tile/specs/data-table/spec.md#requirement-a-board-tables-leading-icon-sits-on-a-tile
1195
+ */
1196
+ iconTone() {
1197
+ return ['primary', 'teal', 'neutral'].includes(this.rowIconTone) ? this.rowIconTone : 'primary'
1198
+ },
1199
+
1176
1200
  /**
1177
1201
  * Whether the table is drawn as the board look's white card.
1178
1202
  *
@@ -249,7 +249,7 @@ import { formatBoardDate } from '../../utils/boardDate.js'
249
249
  import { conditionalLine } from '../../utils/conditionalLine.js'
250
250
  import { parseDateValue } from '../../utils/dateVariant.js'
251
251
  import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
252
- import { fillTemplate } from '../../utils/fillTemplate.js'
252
+ import { fillTemplate, formatLongDate } from '../../utils/fillTemplate.js'
253
253
  import { formatMoney, metricLocale, unwrapAppConfig } from '../../utils/formatMetric.js'
254
254
  import { readPath } from '../../utils/readPath.js'
255
255
  import { hasUnresolvedDeepTokens, resolveDeepTokens } from '../../utils/resolveFilterTokens.js'
@@ -416,6 +416,8 @@ export default {
416
416
  cnDispatchAction: { default: null },
417
417
  /** The page's object sidebar channel; a `link.sidebarTab` opens one of its tabs. */
418
418
  objectSidebarState: { default: null },
419
+ /** The records the detail page reads beside the record (`@context.<name>`), for a `labelPath`. */
420
+ cnDetailVisibilityRows: { default: null },
419
421
  },
420
422
 
421
423
  props: {
@@ -432,13 +434,14 @@ export default {
432
434
  * `route` (`{ name, params?, query? }`, `@source.<path>` in a param) on a `title` or a `row` links its value to that route; a param that does not fill leaves it plain.
433
435
  * `format: 'user'` shows a Nextcloud user id as the user's display name, read once per id; the entry stays empty until the name is known.
434
436
  * `template` ("{available} {unit}") fills its `{path}` placeholders from the record instead of reading `path`; it is empty when every placeholder is. `suffix` (" kg") is added to a value that is not empty.
437
+ * `labelPath` ("@context.caseType.handlerLabel") names a value on the record or the page's `@context` records that labels the row when it holds text; `label` is the fallback.
435
438
  * `emptyValue` ("No") is shown, translated, when the entry has no value, so the entry stays drawn whatever `hideEmpty` says (an override that was never set reads "No").
436
439
  * - `action` — an action drawn as a button under the facts; `headerAction` — one drawn in the card header (reported through the `header` event).
437
440
  * - `actions` — action buttons under the facts in the `headerActions` shape (`api-call`, `open-form`, `navigate`, `open-modal`, ...; `confirm`, `variant`). `@source.<field>` names a field of the record shown, and an action whose token names an empty field is not drawn. `visibleWhen` reads the record shown; `adminOnly: true` draws the action for an instance admin only (visibility, never authorisation). An api-call or a saved form refreshes the page, and the facts read their source again.
438
441
  * - `rowsPath` — a path to a list of `{ label, value }` the record holds, drawn as rows after `fields` (a preview the server builds).
439
442
  * - `headerPill` — a status pill in the card header (reported through the `header` event): `{ path?, text?, map?, variant?, variantMap?, colorMap? }`, read from the record like a `pill` entry (DqBezwaar "Advies ontvangen"); nothing shows while the value is empty.
440
443
  * - `notice` — `{ text, variant? }`, a note band inside the card under the facts (`info` by default; `success`, `warning` or `error`); the text is translated.
441
- * `format` is `text` (default), `date` (board short date), `date-long` (always with the year), `list` (an array joined by ", ", each item read at `itemPath`), `bytes` (a size, "1.5 MB"), `number` (in the reader's language, `decimals` fraction digits when set) or `currency` (a money amount in the reader's language, "€ 1.234,50": the code in `currency`, else the record's `currencyField`, else the app's reporting currency, else EUR; `decimals` defaults to 2).
444
+ * `format` is `text` (default), `date` (board short date), `date-long` (always with the year), `longdate` (the month written out, the year only outside this year: "3 oktober"), `list` (an array joined by ", ", each item read at `itemPath`), `bytes` (a size, "1.5 MB"), `number` (in the reader's language, `decimals` fraction digits when set) or `currency` (a money amount in the reader's language, "€ 1.234,50": the code in `currency`, else the record's `currencyField`, else the app's reporting currency, else EUR; `decimals` defaults to 2).
442
445
  * `map` turns a raw value into a label (translated). `parts` (on a `line`) lists paths, or `{ path, label?, format? }`, joined by `separator` (default " · "); empty parts are dropped.
443
446
  * - `source` — `{ register, schema, id }` or `{ url }`; omitted = the current object. `{ register, schema, filter, sort? }` without `id` reads the first object that matches `filter` in `sort` order (`{ field, dir }`): the latest count of a shift. An entry with `from: "object"` reads the current object even then (a client card that also shows the ticket's channel).
444
447
  * - `endpointSource` — `{ url, method?, params?, responsePath? }`, the block a stat or chart takes: the facts read an app endpoint, shared and cached like those.
@@ -722,7 +725,7 @@ export default {
722
725
  }
723
726
  if (kind === 'row') {
724
727
  const last = out[out.length - 1]
725
- const row = { key, label: this.tr(field.label || ''), value, ...this.rowExtras(record, field, value) }
728
+ const row = { key, label: this.labelOf(record, field), value, ...this.rowExtras(record, field, value) }
726
729
  if (field.route && !row.href) {
727
730
  row.to = this.resolveRoute(field.route, record)
728
731
  }
@@ -1406,6 +1409,30 @@ export default {
1406
1409
  }
1407
1410
  },
1408
1411
 
1412
+ /**
1413
+ * A row's label: the value at `labelPath` (the record, or the page's
1414
+ * `@context.<name>` records) when it holds text, else `label`, both
1415
+ * through the host translate. A case type can name its handler
1416
+ * ("Regisseur") while another keeps "Behandelaar".
1417
+ *
1418
+ * @param {object} record The record shown.
1419
+ * @param {object} field The entry.
1420
+ * @return {string}
1421
+ * @spec openspec/changes/facts-label-from-path/specs/facts-widget/spec.md#requirement-a-facts-row-can-take-its-label-from-the-record-or-its-context
1422
+ */
1423
+ labelOf(record, field) {
1424
+ if (typeof field.labelPath === 'string' && field.labelPath !== '') {
1425
+ const rows = this.cnDetailVisibilityRows && typeof this.cnDetailVisibilityRows === 'object' && 'value' in this.cnDetailVisibilityRows
1426
+ ? this.cnDetailVisibilityRows.value
1427
+ : this.cnDetailVisibilityRows
1428
+ const named = readPath({ ...(record || {}), '@context': rows || {} }, field.labelPath)
1429
+ if (typeof named === 'string' && named.trim() !== '') {
1430
+ return this.tr(named.trim())
1431
+ }
1432
+ }
1433
+ return this.tr(field.label || '')
1434
+ },
1435
+
1409
1436
  /**
1410
1437
  * Copy a row's value to the clipboard.
1411
1438
  *
@@ -1775,6 +1802,10 @@ export default {
1775
1802
  .filter((item) => item !== '')
1776
1803
  return items.join(', ')
1777
1804
  }
1805
+ // The month written out, the year only outside this year: "3 oktober".
1806
+ if (format === 'longdate') {
1807
+ return formatLongDate(raw)
1808
+ }
1778
1809
  if (format === 'date' || format === 'date-long') {
1779
1810
  const date = parseDateValue(raw)
1780
1811
  if (date === null) {
@@ -5,7 +5,7 @@
5
5
  <template>
6
6
  <div
7
7
  class="cn-files-browser"
8
- :class="{ 'cn-files-browser--dragging': dragging, 'cn-files-browser--drop-panel': dropOverlay && dropDetail !== '' }"
8
+ :class="{ 'cn-files-browser--dragging': dragging, 'cn-files-browser--drop-panel': dropOverlay && dropDetailTemplate !== '' }"
9
9
  data-testid="cn-files-browser"
10
10
  @dragenter.prevent="onDragEnter"
11
11
  @dragover.prevent="onDragOver"
@@ -1094,10 +1094,22 @@ export default {
1094
1094
  * @spec openspec/changes/files-drop-panel/specs/files-browser/spec.md#requirement-the-drop-state-fills-the-panel-and-says-where-the-files-go
1095
1095
  */
1096
1096
  dropDetail: {
1097
- type: String,
1097
+ type: [String, Array],
1098
1098
  default: '',
1099
1099
  },
1100
1100
 
1101
+ /**
1102
+ * Named values filled into `dropDetail` as `{name}`, such as the case
1103
+ * number (`{ "case": "@object.identifier" }` in a detail page's props).
1104
+ * While a value is unknown the line is left out.
1105
+ *
1106
+ * @spec openspec/changes/files-drop-detail-values/specs/files-drop-detail-values/spec.md#requirement-the-drop-detail-line-can-name-the-record
1107
+ */
1108
+ dropDetailValues: {
1109
+ type: Object,
1110
+ default: null,
1111
+ },
1112
+
1101
1113
  /** Show a line under the list saying files can be dropped on it. */
1102
1114
  dropHint: {
1103
1115
  type: Boolean,
@@ -1390,6 +1402,22 @@ export default {
1390
1402
  },
1391
1403
 
1392
1404
  computed: {
1405
+ /**
1406
+ * The `dropDetail` text: the string, or the text of the first variant
1407
+ * `{ when, text }` whose `when` is `true` or that has none.
1408
+ *
1409
+ * @return {string}
1410
+ * @spec openspec/changes/files-drop-detail-values/specs/files-drop-detail-values/spec.md#requirement-the-drop-detail-line-can-name-the-record
1411
+ */
1412
+ dropDetailTemplate() {
1413
+ if (typeof this.dropDetail === 'string') {
1414
+ return this.dropDetail
1415
+ }
1416
+ const variants = Array.isArray(this.dropDetail) ? this.dropDetail : []
1417
+ const chosen = variants.find((item) => item && typeof item.text === 'string' && (!('when' in item) || item.when === true))
1418
+ return chosen ? chosen.text : ''
1419
+ },
1420
+
1393
1421
  /**
1394
1422
  * `dropDetail` through the host translate with `{count}` filled, '' when
1395
1423
  * there is none or the count it needs is unknown.
@@ -1398,10 +1426,22 @@ export default {
1398
1426
  * @return {string}
1399
1427
  */
1400
1428
  dropDetailText() {
1401
- if (!this.dropDetail) {
1429
+ const template = this.dropDetailTemplate
1430
+ if (!template) {
1402
1431
  return ''
1403
1432
  }
1404
- const text = this.cnTranslate(this.dropDetail)
1433
+ let text = this.cnTranslate(template)
1434
+ // `dropDetailValues`: named values such as the case number; a value
1435
+ // still unknown (an unresolved `@` token, or empty) leaves the line out.
1436
+ // @spec openspec/changes/files-drop-detail-values/specs/files-drop-detail-values/spec.md#requirement-the-drop-detail-line-can-name-the-record
1437
+ const values = this.dropDetailValues && typeof this.dropDetailValues === 'object' ? this.dropDetailValues : {}
1438
+ for (const [name, value] of Object.entries(values)) {
1439
+ const shown = value === null || value === undefined ? '' : String(value)
1440
+ if (shown === '' || shown.startsWith('@')) {
1441
+ return ''
1442
+ }
1443
+ text = text.split(`{${name}}`).join(shown)
1444
+ }
1405
1445
  if (!text.includes('{count}')) {
1406
1446
  return text
1407
1447
  }
@@ -875,6 +875,7 @@
875
875
  :columns="renderedColumns"
876
876
  :pinnedCount="pinnedColumnCount"
877
877
  :rowIcon="rowIcon"
878
+ :rowIconTone="rowIconTone"
878
879
  :rowIndicators="rowIndicators"
879
880
  :rowIndicatorCap="rowIndicatorCap"
880
881
  :rows="displayObjects"
@@ -3185,6 +3186,17 @@ export default {
3185
3186
  default: null,
3186
3187
  },
3187
3188
 
3189
+ /**
3190
+ * The leading icon's tile tone in the board look (`primary`, `teal`,
3191
+ * `neutral`), from `pages[].config.rowIconTone`. Forwarded to CnDataTable.
3192
+ *
3193
+ * @type {string}
3194
+ */
3195
+ rowIconTone: {
3196
+ type: String,
3197
+ default: 'primary',
3198
+ },
3199
+
3188
3200
  /** Columns to exclude in schema mode */
3189
3201
  excludeColumns: {
3190
3202
  type: Array,
@@ -6422,6 +6434,7 @@ export default {
6422
6434
  r: this.register,
6423
6435
  s: this.exportSchemaSlug,
6424
6436
  f: this.filter || null,
6437
+ a: this.effectiveActiveFilters || null,
6425
6438
  e: this.countEntries,
6426
6439
  // The store's total, not the error override in effectivePagination,
6427
6440
  // so a failed fetch and its recovery do not refetch the counts.
@@ -10012,7 +10025,15 @@ export default {
10012
10025
  const counts = await fetchFilterCounts({
10013
10026
  register: this.register,
10014
10027
  schema,
10015
- entries: this.countEntries.map((entry) => ({ key: entry.key, filter: resolveFilterMap(entry.filter, params, ctx) })),
10028
+ // A quick filter counts within the list as filtered (the
10029
+ // active filters, its own field its own); a saved view counts
10030
+ // its own query.
10031
+ entries: this.countEntries.map((entry) => ({
10032
+ key: entry.key,
10033
+ filter: entry.key.startsWith('tab:')
10034
+ ? { ...(this.effectiveActiveFilters || {}), ...resolveFilterMap(entry.filter, params, ctx) }
10035
+ : resolveFilterMap(entry.filter, params, ctx),
10036
+ })),
10016
10037
  baseFilter: resolveFilterMap(this.filter, params, ctx),
10017
10038
  ctx,
10018
10039
  })
@@ -5,7 +5,9 @@ import { useObjectStore } from '../../store/index.js'
5
5
  // Both filter resolvers live in `utils/routeFilters.js` so CnLogsPage applies
6
6
  // the same two grammars (route-param interpolation + `?key=value` deep links)
7
7
  // without pulling in this composable's index-only sidebar/subscription wiring.
8
+ import { loadMyTeam } from '../../utils/myTeam.js'
8
9
  import { withPersonalLenses } from '../../utils/personalLenses.js'
10
+ import { dropOptionalUnresolved, MY_TEAM_TOKEN, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
9
11
  import { parseSortKeysFromQuery, resolveFilterMap, resolveQueryFilters } from '../../utils/routeFilters.js'
10
12
  import { orderToSortKeys } from '../../utils/sortOrder.js'
11
13
 
@@ -392,6 +394,18 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
392
394
  // a reactive getter so a runtime flip attaches/detaches accordingly.
393
395
  // Stores without live-updates support (no `subscribe` action) are a
394
396
  // silent no-op inside the composable, keeping this fully inert.
397
+ // An address carrying `@myTeam` (a shared link) waits for the team, as a
398
+ // saved view on it does: once read, the token becomes the team, or goes
399
+ // for a reader in none.
400
+ const carriesMyTeam = Object.values(initialActiveFilters || {})
401
+ .some((value) => value === MY_TEAM_TOKEN || (Array.isArray(value) && value.includes(MY_TEAM_TOKEN)))
402
+ if (carriesMyTeam) {
403
+ loadMyTeam().then(() => {
404
+ list.activeFilters.value = dropOptionalUnresolved(resolveFilterTokens(list.activeFilters.value, tokenCtx()))
405
+ list.refresh(1)
406
+ })
407
+ }
408
+
395
409
  useObjectSubscription(objectStore, () => objectType.value, null, {
396
410
  enabled: () => props.subscribe !== false,
397
411
  })