@conduction/nextcloud-vue 2.111.0 → 2.112.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 (127) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +3 -2
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +4 -2
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
  7. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +3 -2
  8. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -1
  10. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
  11. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  12. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +54 -5
  13. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +16 -2
  15. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  16. package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
  17. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +10 -0
  18. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  19. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +2 -1
  20. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  21. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  22. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +22 -2
  23. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  24. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +17 -3
  25. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  26. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  27. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  28. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +32 -8
  30. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +31 -3
  32. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  34. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +48 -11
  35. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +160 -97
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  38. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  39. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  40. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +2 -2
  41. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  42. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +166 -2
  43. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +19 -4
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +1 -1
  47. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +21 -1
  48. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -1
  49. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -1
  50. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  51. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +36 -2
  52. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  53. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +44 -5
  54. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  55. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -0
  56. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -1
  57. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +22 -2
  58. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
  59. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +36 -16
  60. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
  61. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +1 -1
  62. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -1
  63. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -1
  64. package/dist/esm/composables/useRefLabels.js +26 -6
  65. package/dist/esm/composables/useRefLabels.js.map +1 -1
  66. package/dist/esm/integrations/registry.js +4 -0
  67. package/dist/esm/integrations/registry.js.map +1 -1
  68. package/dist/esm/l10n/en.json.js +3 -1
  69. package/dist/esm/l10n/en.json.js.map +1 -1
  70. package/dist/esm/l10n/nl.json.js +3 -1
  71. package/dist/esm/l10n/nl.json.js.map +1 -1
  72. package/dist/esm/library-style.js +1 -1
  73. package/dist/esm/nextcloud-vue.css +533 -273
  74. package/dist/esm/store/plugins/liveUpdates.js +3 -1
  75. package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
  76. package/dist/esm/store/plugins/logs.js +2 -1
  77. package/dist/esm/store/plugins/logs.js.map +1 -1
  78. package/dist/esm/utils/detailActionModel.js +28 -1
  79. package/dist/esm/utils/detailActionModel.js.map +1 -1
  80. package/dist/esm/utils/fillTemplate.js +13 -5
  81. package/dist/esm/utils/fillTemplate.js.map +1 -1
  82. package/dist/esm/utils/headerFieldChips.js +21 -2
  83. package/dist/esm/utils/headerFieldChips.js.map +1 -1
  84. package/dist/esm/utils/mapList.js +26 -11
  85. package/dist/esm/utils/mapList.js.map +1 -1
  86. package/dist/esm/utils/savedViewMenu.js +12 -5
  87. package/dist/esm/utils/savedViewMenu.js.map +1 -1
  88. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  89. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  90. package/dist/nextcloud-vue.cjs.js +2562 -1886
  91. package/dist/nextcloud-vue.cjs.js.map +2 -2
  92. package/dist/nextcloud-vue.css +533 -273
  93. package/l10n/en.json +3 -1
  94. package/l10n/nl.json +3 -1
  95. package/package.json +1 -1
  96. package/src/components/CnActionDialog/CnActionDialog.vue +32 -9
  97. package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -2
  98. package/src/components/CnBoardView/CnBoardView.vue +58 -5
  99. package/src/components/CnCardGrid/CnCardGrid.md +1 -0
  100. package/src/components/CnCardGrid/CnCardGrid.vue +11 -0
  101. package/src/components/CnCellRenderer/CnCellRenderer.vue +27 -3
  102. package/src/components/CnChartWidget/CnChartWidget.vue +6 -0
  103. package/src/components/CnDetailPage/CnDetailPage.vue +42 -9
  104. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
  105. package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
  106. package/src/components/CnIndexPage/CnIndexPage.vue +174 -4
  107. package/src/components/CnNcDashboardList/CnNcDashboardList.vue +21 -1
  108. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  109. package/src/components/CnObjectCard/CnObjectCard.vue +51 -5
  110. package/src/components/CnPartyPicker/CnPartyPicker.vue +62 -16
  111. package/src/components/CnSearchSelect/CnSearchSelect.vue +8 -0
  112. package/src/composables/useRefLabels.js +25 -5
  113. package/src/css/dialog.css +53 -3
  114. package/src/css/index-page.css +26 -1
  115. package/src/css/look-board-detail.css +36 -15
  116. package/src/css/look-board-index.css +69 -3
  117. package/src/css/look-board.css +30 -2
  118. package/src/integrations/registry.js +4 -0
  119. package/src/schemas/app-manifest-v2.schema.json +11 -5
  120. package/src/store/plugins/liveUpdates.js +3 -1
  121. package/src/store/plugins/logs.js +2 -1
  122. package/src/utils/detailActionModel.js +27 -0
  123. package/src/utils/fillTemplate.js +13 -5
  124. package/src/utils/headerFieldChips.js +21 -2
  125. package/src/utils/mapList.js +26 -11
  126. package/src/utils/savedViewMenu.js +12 -5
  127. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -20,7 +20,7 @@
20
20
  --nldesign-component-dialog-border-radius: var(--cn-board-card-radius, 12px);
21
21
  --cn-dialog-width: 640px;
22
22
  --cn-dialog-danger: var(--color-error);
23
- --cn-dialog-eyebrow-color: var(--color-primary-element-text, var(--color-main-text));
23
+ --cn-dialog-eyebrow-color: var(--color-primary-element-light-text, var(--color-main-text));
24
24
  --cn-dialog-eyebrow-transform: none;
25
25
  --cn-dialog-eyebrow-tracking: 0;
26
26
  }
@@ -86,6 +86,7 @@
86
86
  }
87
87
 
88
88
  .cn-dialog-header__eyebrow {
89
+ --nldesign-color-on-surface: var(--cn-dialog-eyebrow-color, var(--color-main-text));
89
90
  margin-bottom: 4px;
90
91
  font-size: 13px;
91
92
  font-weight: 700;
@@ -101,13 +102,24 @@
101
102
  color: var(--color-main-text);
102
103
  }
103
104
 
105
+ /* The subtitle is a <p>, which the nldesign theme sizes and inks with
106
+ `!important` through its paragraph tokens: the board's 14px muted line
107
+ goes to those tokens too (DqOverdragen, DqDocumentGenereren). */
104
108
  .cn-dialog-header__subtitle {
109
+ --nldesign-component-paragraph-font-size: 14px;
110
+ --nldesign-component-paragraph-color: var(--color-text-maxcontrast);
111
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
105
112
  margin: 4px 0 0;
106
113
  font-size: 14px;
107
114
  color: var(--color-text-maxcontrast);
108
115
  }
109
116
 
110
- .cn-dialog-header__close {
117
+ /* The close button is a 36px circle. NcButton sizes an icon-only button by
118
+ --button-size with `!important`, so the size goes through that; the radius
119
+ also goes to the nldesign theme's button radius token. */
120
+ .cn-dialog-header .cn-dialog-header__close.button-vue {
121
+ --button-size: 36px;
122
+ --nldesign-component-button-border-radius: 50%;
111
123
  flex: 0 0 auto;
112
124
  width: 36px;
113
125
  min-width: 36px;
@@ -147,6 +159,28 @@
147
159
  border-radius: var(--cn-board-control-radius, 8px);
148
160
  }
149
161
 
162
+ /* The footer buttons set 14px text, 16px in at both sides, and 18px when
163
+ they carry an icon (DqOverdragen "Annuleren" and "Overdragen"). The padding
164
+ goes through NcButton's --button-padding. The mask class and the doubled
165
+ class outrank NcButton's text-only rule, `.button-vue[data-v-…]:has(…:empty)`
166
+ (four classes, in a stylesheet a spawned dialog loads later), and the board
167
+ look's general icon-and-label rule.
168
+ @spec openspec/changes/board-dialog-controls/specs/board-dialog-controls/spec.md#requirement-a-board-dialog-draws-its-controls-as-the-boards-do */
169
+ .modal-mask.cn-dialog-board .dialog__actions .button-vue.button-vue {
170
+ --button-padding: 16px;
171
+ font-size: 14px;
172
+ }
173
+
174
+ .modal-mask.cn-dialog-board .dialog__actions .button-vue.button-vue:has(> .button-vue__wrapper > .button-vue__icon:not(:empty)):has(> .button-vue__wrapper > .button-vue__text:not(:empty)) {
175
+ --button-padding: 18px;
176
+ }
177
+
178
+ /* NcButton pads a text-only label 4px a side inside the button's own padding;
179
+ the board's 16px is the whole inset. */
180
+ .modal-mask.cn-dialog-board .dialog__actions .button-vue .button-vue__text.button-vue__text {
181
+ padding-inline: 0;
182
+ }
183
+
150
184
  /* Cancel is an outlined secondary, never a text link. */
151
185
  .cn-dialog-board .dialog__actions .button-vue--vue-secondary,
152
186
  .cn-dialog-board .dialog__actions .button-vue--secondary {
@@ -223,15 +257,31 @@
223
257
  height: calc(var(--cn-field-height, 40px) + 2 * var(--border-width-input-focused, 2px));
224
258
  }
225
259
 
260
+ /* The board draws the control's edge as a 1px border (DqOverdragen "Waarom de
261
+ zaak verhuist", DqBesluit); NcInputField and NcTextArea draw it as a shadow.
262
+ The colour is the one that meets WCAG's 3:1 for a control's edge, as the
263
+ footer's secondary button. Focus keeps NcInputField's own ring, which
264
+ carries `!important`. */
226
265
  .cn-dialog-board .dialog__content .input-field__input,
227
266
  .cn-dialog-board .dialog__content .input-field__input:hover:not([disabled]),
228
267
  .cn-dialog-board .dialog__content .textarea__input,
229
268
  .cn-dialog-board .dialog__content .textarea__input:hover:not([disabled]) {
269
+ border: 1px solid var(--color-border-maxcontrast);
230
270
  border-radius: 8px;
231
- box-shadow: 0 0 0 1px var(--color-border-dark);
271
+ box-shadow: none;
232
272
  font-size: var(--cn-field-text-size, 15px);
233
273
  }
234
274
 
275
+ /* A plain input an app puts in a board dialog (a date, a number) is the same
276
+ 40px control; Nextcloud's core stylesheet makes it 34px. */
277
+ .cn-dialog-board .dialog__content input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=range]):not([type=color]):not([type=submit]):not([type=button]):not([type=reset]):not([type=image]):not(.input-field__input) {
278
+ box-sizing: border-box;
279
+ height: var(--cn-field-height, 40px);
280
+ min-height: var(--cn-field-height, 40px);
281
+ border: 1px solid var(--color-border-maxcontrast);
282
+ border-radius: 8px;
283
+ }
284
+
235
285
  .cn-dialog-board .dialog__content .input-field:has(.input-field__label) .input-field__input:not(:focus)::placeholder,
236
286
  .cn-dialog-board .dialog__content .textarea__input:not(:focus)::placeholder {
237
287
  opacity: 1 !important;
@@ -102,11 +102,17 @@
102
102
  overflow-y: auto;
103
103
  }
104
104
 
105
+ /* The list scrolls inside the side column and the card for the rows without
106
+ a place stays under it, as the board draws it (DqZakenKaart). Scrolling the
107
+ column as a whole pushed that card below a page's worth of rows. */
105
108
  .cn-index-page__map-list {
106
109
  display: flex;
110
+ flex: 0 1 auto;
107
111
  flex-direction: column;
112
+ min-height: 0;
108
113
  margin: 0;
109
114
  padding: 0;
115
+ overflow-y: auto;
110
116
  border: 1px solid var(--color-border);
111
117
  border-radius: 10px;
112
118
  list-style: none;
@@ -114,6 +120,7 @@
114
120
 
115
121
  .cn-index-page__map-item {
116
122
  display: flex;
123
+ flex: none;
117
124
  gap: 12px;
118
125
  padding: 12px 14px;
119
126
  }
@@ -152,11 +159,22 @@
152
159
  min-width: 0;
153
160
  }
154
161
 
155
- .cn-index-page__map-title {
162
+ /* The title is a button for the keyboard but reads as the row's title text.
163
+ Nextcloud styles every `button:not(.button-vue, ...)` (specificity 0,2,1)
164
+ as a 34px tinted pill with a 3px margin, which outranked a single class and
165
+ turned each title into a blue block (DqZakenKaart rows 111px against 89).
166
+ Three classes outrank it, and the box properties it sets are reset.
167
+ @spec openspec/changes/map-list-title-button/specs/map-list-title-button/spec.md#requirement-a-map-list-title-reads-as-text-not-as-a-button */
168
+ .cn-index-page__map-list .cn-index-page__map-item .cn-index-page__map-title {
169
+ width: auto;
170
+ height: auto;
171
+ min-height: 0;
156
172
  margin: 0;
157
173
  padding: 0;
158
174
  border: 0;
175
+ border-radius: 0;
159
176
  background: none;
177
+ box-shadow: none;
160
178
  color: var(--color-main-text);
161
179
  font: inherit;
162
180
  font-size: 15px;
@@ -166,6 +184,12 @@
166
184
  cursor: pointer;
167
185
  }
168
186
 
187
+ .cn-index-page__map-list .cn-index-page__map-item .cn-index-page__map-title:hover,
188
+ .cn-index-page__map-list .cn-index-page__map-item .cn-index-page__map-title:focus {
189
+ background: none;
190
+ color: var(--color-main-text);
191
+ }
192
+
169
193
  .cn-index-page__map-sub,
170
194
  .cn-index-page__map-due {
171
195
  color: var(--color-text-maxcontrast);
@@ -181,6 +205,7 @@
181
205
 
182
206
  .cn-index-page__map-missing {
183
207
  display: flex;
208
+ flex: none;
184
209
  flex-direction: column;
185
210
  align-items: flex-start;
186
211
  gap: 8px;
@@ -20,6 +20,12 @@
20
20
  /* Card h2 and side heading. */
21
21
  --cn-board-heading-size: 17px;
22
22
  --cn-board-side-heading-size: 15px;
23
+ /* The boards set text at the font's normal line height (1.2 for Fira
24
+ Sans), Nextcloud at 1.5: a card body's first line carries this much
25
+ half-leading above its glyphs that a board does not draw. A gap the
26
+ boards measure from a title to the text under it is the board's value
27
+ minus this. Resolved in the element that uses it. */
28
+ --cn-board-first-line-leading: 0.15em;
23
29
  }
24
30
 
25
31
  /* ---- The header: two rows on the ground ------------------------------- */
@@ -212,24 +218,27 @@
212
218
  widget's 4px pad is room for the 32px circles, which the bars variant does
213
219
  not draw), the label 8px under its bar and the date 8px under the label at
214
220
  the normal line height, open steps muted and the done and current ones in
215
- the main text. Each hands its ink to --nldesign-color-on-surface, which a
216
- theme that forces the body ink onto every span (thematiq) reads instead. */
221
+ the main text. Each sets its ink as `color`, and also hands it to
222
+ --nldesign-color-on-surface for a theme that still forces the body ink. */
217
223
  .cn-look-board .cn-stages-widget:has(> .cn-stages-widget__bars):not(.cn-look-nextcloud *) {
218
224
  padding: 0;
219
225
  }
220
226
 
221
227
  .cn-look-board .cn-stages-widget__bars .cn-stages-widget__bar-label:not(.cn-look-nextcloud *) {
222
228
  --nldesign-color-on-surface: var(--color-text-maxcontrast);
229
+ color: var(--color-text-maxcontrast);
223
230
  line-height: normal;
224
231
  }
225
232
 
226
233
  .cn-look-board .cn-stages-widget__bars .cn-stages-widget__bar-step--done .cn-stages-widget__bar-label:not(.cn-look-nextcloud *),
227
234
  .cn-look-board .cn-stages-widget__bars .cn-stages-widget__bar-step--current .cn-stages-widget__bar-label:not(.cn-look-nextcloud *) {
228
235
  --nldesign-color-on-surface: var(--color-main-text);
236
+ color: var(--color-main-text);
229
237
  }
230
238
 
231
239
  .cn-look-board .cn-stages-widget__bars .cn-stages-widget__bar-date:not(.cn-look-nextcloud *) {
232
240
  --nldesign-color-on-surface: var(--color-text-maxcontrast);
241
+ color: var(--color-text-maxcontrast);
233
242
  line-height: normal;
234
243
  }
235
244
 
@@ -367,13 +376,15 @@
367
376
  The nldesign theme sets every button's weight and radius with `!important`
368
377
  through its button tokens, which drew every tab bold with four rounded
369
378
  corners; the tab hands the board's values to those tokens. A tab is 37px
370
- with or without a count (DqZaak), not 34px without one.
379
+ with or without a count (DqZaak), not 34px without one. Its outline is
380
+ the widget card's border, so tab and card meet in one line.
381
+ @spec openspec/changes/board-tabs-widget-border/specs/board-tabs-widget-border/spec.md#requirement-board-folder-tabs-draw-the-widget-card-border
371
382
  @spec openspec/changes/board-tabs-theme/specs/board-tabs-theme/spec.md#requirement-board-folder-tabs-keep-the-boards-weight-shape-and-height-under-a-theme */
372
383
  .cn-look-board .cn-tabs--board .cn-tabs__nav-item {
373
384
  --nldesign-component-button-font-weight: 400;
374
385
  --nldesign-component-button-border-radius: 8px 8px 0 0;
375
386
  background-color: var(--color-background-dark);
376
- border: 1px solid var(--color-border-dark);
387
+ border: 1px solid var(--color-border);
377
388
  border-bottom: none;
378
389
  border-radius: 8px 8px 0 0;
379
390
  font-size: 14px;
@@ -388,7 +399,7 @@
388
399
  --nldesign-component-button-font-weight: 700;
389
400
  background-color: var(--color-main-background);
390
401
  border-bottom: 1px solid var(--color-main-background);
391
- border-top: 1px solid var(--color-border-dark);
402
+ border-top: 1px solid var(--color-border);
392
403
  font-weight: 700;
393
404
  min-height: 38px;
394
405
  padding-bottom: 9px;
@@ -499,15 +510,17 @@
499
510
  .cn-look-board .cn-detail-card .cn-detail-card__title {
500
511
  font-size: var(--cn-board-heading-size);
501
512
  font-weight: 700;
502
- line-height: 1.3;
513
+ line-height: normal;
503
514
  }
504
515
 
516
+ /* 12px from the title's glyphs to the first line's glyphs, as drawn.
517
+ @spec openspec/changes/board-card-title-gap/specs/board-card-title-gap/spec.md#requirement-a-board-cards-body-starts-the-boards-gap-below-its-title */
505
518
  .cn-look-board .cn-detail-page .cn-widget-wrapper__content {
506
- padding: 12px 22px 20px;
519
+ padding: calc(12px - var(--cn-board-first-line-leading)) 22px 20px;
507
520
  }
508
521
 
509
522
  .cn-look-board .cn-detail-card .cn-detail-card__content {
510
- padding: 12px 22px 20px;
523
+ padding: calc(12px - var(--cn-board-first-line-leading)) 22px 20px;
511
524
  }
512
525
 
513
526
  /* A header-less card keeps the full 20px 22px. */
@@ -515,13 +528,18 @@
515
528
  padding: var(--cn-board-card-padding);
516
529
  }
517
530
 
518
- /* The data card: 200px columns, a 12px 24px gap, label over value, hairline. */
519
- .cn-look-board .cn-object-data-widget__grid {
531
+ /* The data card: 200px columns, a 12px 24px gap, label over value, hairline.
532
+ Each selector holds one class more than the component's scoped rule
533
+ (`.cn-object-data-widget__cell[data-v-...]`), which loads after this sheet:
534
+ at equal specificity the cell kept its 8px top padding and the grid its 8px
535
+ gap, so the first label sat 8px further below the card's title.
536
+ @spec openspec/changes/board-card-title-gap/specs/board-card-title-gap/spec.md#requirement-the-data-cards-grid-keeps-the-boards-gap-and-cell-padding */
537
+ .cn-look-board .cn-object-data-widget__grid-wrap .cn-object-data-widget__grid {
520
538
  font-size: 15px;
521
539
  gap: 12px 24px;
522
540
  }
523
541
 
524
- .cn-look-board .cn-object-data-widget__cell {
542
+ .cn-look-board .cn-object-data-widget__grid .cn-object-data-widget__cell {
525
543
  border-bottom: 1px solid var(--cn-board-hairline);
526
544
  gap: 2px;
527
545
  padding: 0 0 10px;
@@ -569,7 +587,7 @@
569
587
  }
570
588
 
571
589
  .cn-look-board .cn-detail-page__side .cn-widget-wrapper__content {
572
- padding: 10px 22px 20px;
590
+ padding: calc(10px - var(--cn-board-first-line-leading)) 22px 20px;
573
591
  }
574
592
 
575
593
  /* A stat tile in a side card (DqZaak "Termijn") sits on the card's inset as
@@ -686,21 +704,24 @@
686
704
  font-size: 13px;
687
705
  }
688
706
 
689
- /* Done and open steps are muted, the current one in the main text. Each hands
690
- its ink to --nldesign-color-on-surface, which a theme that forces the body
691
- ink onto every li and span (thematiq) reads instead. */
707
+ /* Done and open steps are muted, the current one in the main text. Each sets
708
+ its ink as `color`, and also hands it to --nldesign-color-on-surface for a
709
+ theme that still forces the body ink. */
692
710
  .cn-look-board .cn-next-step-card--board .cn-next-step-card__item,
693
711
  .cn-look-board .cn-next-step-card--board .cn-next-step-card__hint,
694
712
  .cn-look-board .cn-next-step-card--board .cn-next-step-card__after {
695
713
  --nldesign-color-on-surface: var(--color-text-maxcontrast);
714
+ color: var(--color-text-maxcontrast);
696
715
  }
697
716
 
698
717
  .cn-look-board .cn-next-step-card--board .cn-next-step-card__item--current {
699
718
  --nldesign-color-on-surface: var(--color-main-text);
719
+ color: var(--color-main-text);
700
720
  }
701
721
 
702
722
  .cn-look-board .cn-next-step-card--board .cn-next-step-card__item--current .cn-next-step-card__hint {
703
723
  --nldesign-color-on-surface: var(--color-text-maxcontrast);
724
+ color: var(--color-text-maxcontrast);
704
725
  }
705
726
 
706
727
  /* ---- History: the activity tab ------------------------------------------ */
@@ -219,14 +219,16 @@
219
219
  color: var(--color-main-background);
220
220
  }
221
221
 
222
- /* The count badge: grey on an idle chip, soft text colour on the selected one. */
222
+ /* The count badge: grey on an idle chip, soft text colour on the selected one.
223
+ Its ink also goes to --nldesign-color-on-surface, which a theme that forces
224
+ the body ink onto every span (thematiq) reads. */
223
225
  .cn-look-board .cn-saved-views__chip .cn-saved-views__chip-count,
224
226
  .cn-look-board .cn-quick-filter-bar .cn-quick-filter-bar__count {
227
+ --nldesign-color-on-surface: var(--cn-board-text-soft);
225
228
  min-width: 0;
226
229
  padding: 1px 7px;
227
230
  border: 0;
228
231
  border-radius: 10px;
229
- --nldesign-color-on-surface: var(--cn-board-text-soft);
230
232
  background: var(--color-border);
231
233
  color: var(--cn-board-text-soft);
232
234
  font-size: 12px;
@@ -246,8 +248,10 @@
246
248
  }
247
249
 
248
250
  /* Save view: a ghost button, 34px, radius 17, no border, on the light primary
249
- tint, a 14px icon 6px before the label, on one line. */
251
+ tint, a 14px icon 6px before the label, on one line. The label is a span,
252
+ so its ink also goes to --nldesign-color-on-surface. */
250
253
  .cn-look-board .cn-saved-views__save .button-vue {
254
+ --nldesign-color-on-surface: var(--color-primary-element-light-text);
251
255
  min-height: 34px;
252
256
  height: 34px;
253
257
  padding: 0 12px;
@@ -288,6 +292,7 @@
288
292
  @spec openspec/changes/board-toolbar-buttons-theme/specs/board-toolbar-buttons-theme/spec.md#requirement-board-toolbar-buttons-are-neutral-under-a-theme */
289
293
  .cn-look-board .cn-actions-bar:not(.cn-look-nextcloud *) {
290
294
  --nldesign-component-button-font-weight: 600;
295
+ --nldesign-component-button-color: var(--color-main-text);
291
296
  --nldesign-component-button-secondary-action-color: var(--color-main-text);
292
297
  --nldesign-component-button-secondary-action-background-color: var(--color-main-background);
293
298
  --nldesign-component-button-secondary-action-border-color: var(--cn-board-control-border);
@@ -437,7 +442,10 @@
437
442
  font-size: 14px;
438
443
  }
439
444
 
445
+ /* An active filter: the light primary tint, its label (a span) in the tint's
446
+ ink through --nldesign-color-on-surface too. */
440
447
  .cn-look-board .cn-actions-bar--board .cn-actions-bar__filter-chip {
448
+ --nldesign-color-on-surface: var(--color-primary-element-light-text);
441
449
  display: inline-flex;
442
450
  align-items: center;
443
451
  gap: 6px;
@@ -581,9 +589,13 @@
581
589
  line-height: normal;
582
590
  }
583
591
 
592
+ /* The sort button is the bare label, square (the boards draw no box round
593
+ it); the funnel after it is a 22px button with a 4px radius. Both outrank
594
+ the 8px element radius the table and Nextcloud's `button` rule give them. */
584
595
  .cn-look-board .cn-table-container--board .cn-data-table th .cn-table-header__sort {
585
596
  min-height: 0;
586
597
  height: auto;
598
+ border-radius: 0;
587
599
  line-height: inherit;
588
600
  }
589
601
 
@@ -639,6 +651,7 @@
639
651
  min-width: 22px;
640
652
  height: 22px;
641
653
  min-height: 22px;
654
+ border-radius: 4px;
642
655
  margin-inline-start: 4px;
643
656
  }
644
657
 
@@ -847,6 +860,8 @@
847
860
  margin-inline-start: auto;
848
861
  }
849
862
 
863
+ /* A page link is a <button>: Nextcloud's `button` rule sets it bold, the
864
+ boards set the links at the regular weight and only the current page at 600. */
850
865
  .cn-look-board .cn-pagination--board .cn-pagination__board-link {
851
866
  box-sizing: border-box;
852
867
  min-width: 34px;
@@ -859,6 +874,7 @@
859
874
  background: var(--color-main-background);
860
875
  color: var(--color-main-text);
861
876
  font-size: 14px;
877
+ font-weight: 400;
862
878
  cursor: pointer;
863
879
  }
864
880
 
@@ -1279,3 +1295,53 @@
1279
1295
  font-size: 20px;
1280
1296
  font-weight: 700;
1281
1297
  }
1298
+
1299
+ /* ---- Side panel cards --------------------------------------------------- */
1300
+ /* A card beside the list is the board's side card (DqTeamwachtrij "Team
1301
+ vandaag", "Werkvoorraad per zaaktype"): no rule under the header, the
1302
+ title a 15px muted label on the card's 20px 22px inset, 12px above the
1303
+ body, 20px between cards. The wrapper drew the Nextcloud panel: a 14px
1304
+ dark title on a 12px 16px header over a rule, 16px between cards. The
1305
+ tone carries `!important` against a theme that colours every h3.
1306
+ @spec openspec/changes/board-index-side-panel-cards/specs/board-index-side-panel-cards/spec.md#requirement-a-board-index-side-panel-card-is-the-boards-side-card */
1307
+ .cn-look-board .cn-index-page-widgets--side-panel:not(.cn-look-nextcloud *) {
1308
+ gap: var(--cn-board-section-gap, 20px);
1309
+ }
1310
+
1311
+ .cn-look-board .cn-index-page-widgets--side-panel:not(.cn-look-nextcloud *) .cn-widget-wrapper__header {
1312
+ border-bottom: none;
1313
+ padding: 20px 22px 0;
1314
+ }
1315
+
1316
+ .cn-look-board .cn-index-page-widgets--side-panel:not(.cn-look-nextcloud *) .cn-widget-wrapper__title {
1317
+ color: var(--color-text-maxcontrast) !important;
1318
+ font-size: var(--cn-board-side-heading-size, 15px);
1319
+ font-weight: 600;
1320
+ line-height: normal;
1321
+ }
1322
+
1323
+ .cn-look-board .cn-index-page-widgets--side-panel:not(.cn-look-nextcloud *) .cn-widget-wrapper__content {
1324
+ padding: calc(12px - var(--cn-board-first-line-leading, 0.15em)) 22px 20px;
1325
+ }
1326
+
1327
+ .cn-look-board .cn-index-page-widgets--side-panel:not(.cn-look-nextcloud *) .cn-index-page-widgets__note {
1328
+ font-size: 13px;
1329
+ }
1330
+
1331
+ /* A record list drawn as a table (DqTeamwachtrij "Werkvoorraad per
1332
+ zaaktype") is the board's compact grid: a 13px muted header at regular
1333
+ weight, rows 8px apart, no rules between them. Line heights differ from
1334
+ the board's normal ones, so the cell padding sets the board's row pitch.
1335
+ One class more than the component's scoped rules, which load later.
1336
+ @spec openspec/changes/board-index-side-panel-cards/specs/board-index-side-panel-cards/spec.md#requirement-a-board-record-list-table-is-the-boards-compact-grid */
1337
+ .cn-look-board .cn-record-list--table .cn-record-list__table th {
1338
+ border-bottom: none;
1339
+ font-size: 13px;
1340
+ font-weight: 400;
1341
+ padding-block: 0 2px;
1342
+ }
1343
+
1344
+ .cn-look-board .cn-record-list--table .cn-record-list__table td {
1345
+ border-bottom: none;
1346
+ padding-block: 2px;
1347
+ }
@@ -219,6 +219,16 @@
219
219
  height: 16px;
220
220
  }
221
221
 
222
+ /* ---- A single select -------------------------------------------------- */
223
+ /* NcSelect draws a single select as its search field with the clear and
224
+ open buttons laid over its end. A legacy vue-select rule that Nextcloud
225
+ still loads caps the field at the width less one clickable area, which
226
+ left those buttons outside the field's border (DqNieuweZaak "Zaaktype",
227
+ "Intakekanaal"). */
228
+ .cn-look-board .nc-select.v-select.select.vs--single .vs__selected-options {
229
+ max-width: none;
230
+ }
231
+
222
232
  /* ---- The header button ------------------------------------------------- */
223
233
  .cn-look-board .cn-index-page__header-buttons:not(.cn-look-nextcloud *),
224
234
  .cn-look-board .cn-detail-page__header-actions:not(.cn-look-nextcloud *),
@@ -1278,7 +1288,7 @@
1278
1288
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--nc-dashboard) .cn-widget-wrapper__title {
1279
1289
  font-size: var(--cn-board-heading-size, 17px);
1280
1290
  font-weight: 700;
1281
- line-height: 1.3;
1291
+ line-height: normal;
1282
1292
  }
1283
1293
 
1284
1294
  /* The board draws no rule under a dashboard card's title (DqMijnWerk,
@@ -1292,7 +1302,7 @@
1292
1302
  }
1293
1303
 
1294
1304
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--nc-dashboard):not(.cn-widget-wrapper--borderless) .cn-widget-wrapper__header {
1295
- padding: 20px var(--cn-board-dashboard-card-inset) 12px;
1305
+ padding: 20px var(--cn-board-dashboard-card-inset) calc(12px - var(--cn-board-first-line-leading, 0.15em));
1296
1306
  }
1297
1307
 
1298
1308
  /* ---- Record list on a dashboard card (PtDashboard) ---------------------- */
@@ -1487,3 +1497,21 @@
1487
1497
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-stats-block-widget--tiles-box {
1488
1498
  padding: 14px 22px 20px;
1489
1499
  }
1500
+
1501
+ /* ---- Matrix chart (DqProcesanalyse "Dagen per fase") -------------------- */
1502
+ /* The board sets the matrix at 14px and the font's normal line height: the
1503
+ column heads and the cell figures at 600, a cell 37px with its 10px
1504
+ padding. Nextcloud's 15px at 1.5 made each row 42.5px.
1505
+ @spec openspec/changes/board-matrix-cells/specs/board-matrix-cells/spec.md#requirement-a-board-matrix-takes-the-boards-type */
1506
+ .cn-look-board .cn-chart-widget__matrix:not(.cn-look-nextcloud *) {
1507
+ font-size: 14px;
1508
+ line-height: normal;
1509
+ }
1510
+
1511
+ .cn-look-board .cn-chart-widget__matrix:not(.cn-look-nextcloud *) .cn-chart-widget__matrix-head {
1512
+ font-weight: 600;
1513
+ }
1514
+
1515
+ .cn-look-board .cn-chart-widget__matrix:not(.cn-look-nextcloud *) .cn-chart-widget__matrix-cell:not(.cn-chart-widget__matrix-cell--current) {
1516
+ font-weight: 600;
1517
+ }
@@ -89,6 +89,7 @@ export function createIntegrationRegistry() {
89
89
  * @param {?object} [entry.offlineConfig] Opaque per-integration config bag forwarded verbatim to the integration's components (e.g. the field-inspection leaf's offline schema/filter config). A consuming app overrides it by pre-registering the same id.
90
90
  * @param {'component'|'mount'} [entry.renderMode] Render strategy (openregister#2127 / ADR-066). `'component'` (default) interprets the SFC `tab`/`widget` under the host's own Vue runtime. `'mount'` hands the leaf a bare host-owned DOM element via `mount`/`unmount`, so a leaf built against a different Vue major than the host renders its own framework instance inside that element.
91
91
  * @param {(el: Element, props: object) => void} [entry.mount] Mount hand-off. Required together with `unmount` when `renderMode` is `'mount'`; the host calls it against a bare element with the same context an SFC widget/tab receives (`{ register, schema, objectId, surface, integrationContext, … }`). May also be supplied alongside an SFC pair as a same-major fast path.
92
+ * @param {'card'} [entry.chrome] `'card'` when a mount-mode leaf draws no card of its own: a detail page then hosts it in the library card and hands it `onHeader(header)` to report its count, actions and empty state.
92
93
  * @param {(el: Element) => void} [entry.unmount] Teardown hand-off, called by the host before it removes the element and on surface hide / bound-object change. Travels as a pair with `mount`.
93
94
  *
94
95
  * @return {?object} Normalised entry, or null on collision / malformed mount pair in prod.
@@ -209,6 +210,9 @@ export function createIntegrationRegistry() {
209
210
  // registration, with the tabbed panel silently keeping the sidebar
210
211
  // component.
211
212
  bareWidget: entry.bareWidget === true,
213
+ // `card`: a mount-mode leaf that draws no card of its own, so a
214
+ // detail page hosts it in the library card (CnDetailWidgetHost).
215
+ chrome: entry.chrome === 'card' ? 'card' : null,
212
216
  // Marker set by `registerBuiltinIntegrations` /
213
217
  // `registerLeafIntegrations` so `useIntegrationRegistry` can
214
218
  // distinguish lib-owned registrations (whose components must
@@ -3,7 +3,7 @@
3
3
  "$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
4
4
  "title": "Conduction App Manifest v2",
5
5
  "description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
6
- "version": "2.133.0",
6
+ "version": "2.134.0",
7
7
  "type": "object",
8
8
  "required": ["$schema", "version"],
9
9
  "allOf": [
@@ -1212,6 +1212,7 @@
1212
1212
  ]
1213
1213
  },
1214
1214
  "pill": { "type": "string", "description": "The field shown as a status pill." },
1215
+ "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the `pill` field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } },
1215
1216
  "pillColors": {
1216
1217
  "type": "object",
1217
1218
  "description": "Pill value to badge variant. A value without an entry takes the default variant.",
@@ -1378,7 +1379,7 @@
1378
1379
  "filter": { "type": "object", "additionalProperties": true }
1379
1380
  }
1380
1381
  },
1381
- "resolve": { "type": "object", "additionalProperties": false, "required": ["register", "schema"], "description": "detail-pill-resolve: the field holds the id of a record of this register and schema; the pill shows that record's `labelField` (else its title or name) and keys `colorMap` by it. The raw value shows while the lookup runs and when it finds nothing.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "labelField": { "type": "string", "minLength": 1 } } },
1382
+ "resolve": { "type": "object", "additionalProperties": false, "required": ["register", "schema"], "description": "detail-pill-resolve: the field holds the id of a record of this register and schema; the pill shows that record's `labelField` (else its title or name) and keys `colorMap` by it. The raw value shows while the lookup runs and when it finds nothing.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "labelField": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1, "description": "pill-color-field: the referenced record's property holding a colour name (an NL Design System hue such as `red` or `blue-light`, or a badge role); the pill takes the tone it maps onto." }, "iconField": { "type": "string", "minLength": 1, "description": "pill-color-field: the referenced record's property holding a registered icon name, shown before the label." } } },
1382
1383
  "countLabel": {
1383
1384
  "type": "object",
1384
1385
  "additionalProperties": false,
@@ -4694,6 +4695,11 @@
4694
4695
  "type": "string",
4695
4696
  "description": "One label for the control's label filter."
4696
4697
  },
4698
+ "icon": {
4699
+ "type": "string",
4700
+ "minLength": 1,
4701
+ "description": "pill-color-field: a registered icon name the view's My views menu entry shows instead of the filter icon (a case type's own icon, say)."
4702
+ },
4697
4703
  "overrides": {
4698
4704
  "type": "object",
4699
4705
  "additionalProperties": false,
@@ -5339,14 +5345,14 @@
5339
5345
  "additionalProperties": false,
5340
5346
  "description": "type='index', board look: the record layout of each card in the cards view (board-record-card-layout): a pill and the number on the head row, the title, a meta line, and a footer with a pill and a date. Each part names a field (a dot path) and is left out while it is empty; a pill maps values to a badge variant (`colorMap`, ignoring case) and to shown text (`labels`). Maps to CnIndexPage `cardLayout`.",
5341
5347
  "properties": {
5342
- "headPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
5348
+ "headPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
5343
5349
  "number": { "type": "string", "minLength": 1 },
5344
5350
  "title": { "type": "string", "minLength": 1 },
5345
5351
  "meta": { "type": "array", "description": "The meta line's parts: a field name reads through the page column it names; `{ field, formatter?, formatterOptions? }` reads as plain text through its own formatter (card-layout-part-formatter).", "items": { "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" } } } ] } },
5346
- "footerPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
5352
+ "footerPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
5347
5353
  "footerDate": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "overdueVariant": { "type": "string", "enum": ["error", "warning"], "description": "The date's tone once it has passed (2.132.0)." } } },
5348
5354
  "description": { "description": "A line under the meta line: a field, or `{ field, maxLength? }` cut at maxLength characters (140 by default) with an ellipsis (2.132.0).", "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "maxLength": { "type": "integer", "minimum": 1 } } }] },
5349
- "chips": { "type": "array", "description": "Pills under the description, one per entry whose field holds a value; the text reads through the entry's formatter, `colorMap` and `labels` work as on a pill (2.132.0).", "items": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } } }] } }
5355
+ "chips": { "type": "array", "description": "Pills under the description, one per entry whose field holds a value; the text reads through the entry's formatter, `colorMap` and `labels` work as on a pill (2.132.0).", "items": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "resolve": { "type": "object", "additionalProperties": false, "description": "pill-color-field: the field holds the id of a record; the pill takes the tone that record's colour maps onto and shows its icon. Register and schema default to the field's reference.", "properties": { "register": { "type": "string", "minLength": 1 }, "schema": { "type": "string", "minLength": 1 }, "colorField": { "type": "string", "minLength": 1 }, "iconField": { "type": "string", "minLength": 1 } } }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } } }] } }
5350
5356
  }
5351
5357
  },
5352
5358
  "afterList": { "type": "array", "description": "type='index': cards after the list, stacked full width in the listed order, the counterpart of statRow above it (index-scope-presentation). Each entry is a widget definition resolved like statRow's. A query preset or a folder scope can set its own. Maps to CnIndexPage `afterList`.", "items": { "type": "object", "required": ["id", "type"], "properties": { "id": { "type": "string", "minLength": 1 }, "type": { "type": "string", "minLength": 1 }, "title": { "type": "string" }, "subtitle": { "type": "string" }, "content": { "type": "object" }, "headerLink": { "type": "object" } } } },
@@ -399,6 +399,8 @@ export function liveUpdatesPlugin(opts = {}) {
399
399
  // Plain (non-reactive) Map: type → last params object
400
400
  store.__lastCollectionParams = new Map()
401
401
 
402
+ // Detached: the store outlives the component whose setup first
403
+ // used it, and an attached observer is removed with that scope.
402
404
  store.$onAction(({ name, args, after }) => {
403
405
  if (name === 'fetchCollection') {
404
406
  const [type, params = {}] = args
@@ -406,7 +408,7 @@ export function liveUpdatesPlugin(opts = {}) {
406
408
  store.__lastCollectionParams.set(type, params)
407
409
  })
408
410
  }
409
- })
411
+ }, true)
410
412
 
411
413
  // -- In-flight dedup for fetchObject --
412
414
  // Plain Map: dedupKey → Promise