@conduction/nextcloud-vue 2.86.0 → 2.87.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 (31) hide show
  1. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +2 -0
  2. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  3. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  4. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  5. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +36 -0
  6. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  7. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +55 -40
  8. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  9. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  10. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +3 -26
  11. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  12. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  13. package/dist/esm/library-style.js +1 -1
  14. package/dist/esm/nextcloud-vue.css +74 -53
  15. package/dist/esm/utils/boardReferenceColumns.js +63 -4
  16. package/dist/esm/utils/boardReferenceColumns.js.map +1 -1
  17. package/dist/esm/utils/contentHeaderLink.js +48 -1
  18. package/dist/esm/utils/contentHeaderLink.js.map +1 -1
  19. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  20. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  21. package/dist/nextcloud-vue.cjs.js +255 -121
  22. package/dist/nextcloud-vue.cjs.js.map +2 -2
  23. package/dist/nextcloud-vue.css +74 -53
  24. package/package.json +1 -1
  25. package/src/components/CnIndexPage/CnIndexPage.vue +2 -0
  26. package/src/components/CnTabsWidget/CnTabsWidget.vue +71 -0
  27. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +3 -26
  28. package/src/schemas/app-manifest-v2.schema.json +3 -1
  29. package/src/utils/boardReferenceColumns.js +62 -3
  30. package/src/utils/contentHeaderLink.js +47 -0
  31. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/nextcloud-vue.cjs.js.map
3
- size: 40715087 bytes
4
- sha256: 8426db04864fe758f3dab19db500166a8077780a7ff5b82ed8e0426c5a18fb9e
3
+ size: 40733961 bytes
4
+ sha256: b2553a52f3be9cab59141f90ce15f9187f55454f0b96154befc4aab1ccf8c601
@@ -21010,7 +21010,7 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21010
21010
  }
21011
21011
 
21012
21012
 
21013
- .cn-widget-wrapper[data-v-b19b1aa6] {
21013
+ .cn-widget-wrapper[data-v-4f6c55f6] {
21014
21014
  position: relative;
21015
21015
  height: 100%;
21016
21016
  display: flex;
@@ -21024,13 +21024,13 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21024
21024
  }
21025
21025
 
21026
21026
  /* Headerless date chip — floated over the top-right of a flush card. */
21027
- .cn-widget-wrapper__floating-meta[data-v-b19b1aa6] {
21027
+ .cn-widget-wrapper__floating-meta[data-v-4f6c55f6] {
21028
21028
  position: absolute;
21029
21029
  top: 8px;
21030
21030
  inset-inline-end: 10px;
21031
21031
  z-index: 2;
21032
21032
  }
21033
- .cn-widget-wrapper__content[data-v-b19b1aa6] {
21033
+ .cn-widget-wrapper__content[data-v-4f6c55f6] {
21034
21034
  flex: 1;
21035
21035
  overflow: auto;
21036
21036
  min-height: 0;
@@ -21046,24 +21046,24 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21046
21046
  So it is structural instead. `:has()` keys on the table component's own root
21047
21047
  class, which means a table is flush by construction and no caller can
21048
21048
  reintroduce the gap by forgetting a prop. */
21049
- .cn-widget-wrapper__content[data-v-b19b1aa6]:has(> .cn-table-container),
21050
- .cn-widget-wrapper__content[data-v-b19b1aa6]:has(> .cn-widget-object-table) {
21049
+ .cn-widget-wrapper__content[data-v-4f6c55f6]:has(> .cn-table-container),
21050
+ .cn-widget-wrapper__content[data-v-4f6c55f6]:has(> .cn-widget-object-table) {
21051
21051
  padding: 0;
21052
21052
  }
21053
21053
 
21054
21054
  /* Keyboard focus ring for the now-focusable scrollable content region. */
21055
- .cn-widget-wrapper__content[data-v-b19b1aa6]:focus-visible {
21055
+ .cn-widget-wrapper__content[data-v-4f6c55f6]:focus-visible {
21056
21056
  outline: 2px solid var(--color-primary-element);
21057
21057
  outline-offset: -2px;
21058
21058
  }
21059
- .cn-widget-wrapper--borderless[data-v-b19b1aa6] {
21059
+ .cn-widget-wrapper--borderless[data-v-4f6c55f6] {
21060
21060
  border: none;
21061
21061
  background: transparent;
21062
21062
  }
21063
- .cn-widget-wrapper--borderless .cn-widget-wrapper__content[data-v-b19b1aa6] {
21063
+ .cn-widget-wrapper--borderless .cn-widget-wrapper__content[data-v-4f6c55f6] {
21064
21064
  padding: 0;
21065
21065
  }
21066
- .cn-widget-wrapper--flush .cn-widget-wrapper__content[data-v-b19b1aa6] {
21066
+ .cn-widget-wrapper--flush .cn-widget-wrapper__content[data-v-4f6c55f6] {
21067
21067
  padding: 0;
21068
21068
  }
21069
21069
 
@@ -21071,8 +21071,8 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21071
21071
  above. A dashboard renders every widget `flush`, and without this their
21072
21072
  fields touch the card's edges. Borderless and chromeless cards, and the
21073
21073
  Nextcloud dashboard variant, keep their own padding. */
21074
- .cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content[data-v-b19b1aa6]:has(> .cn-form-widget),
21075
- .cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content[data-v-b19b1aa6]:has(> .cn-kb-search-widget) {
21074
+ .cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content[data-v-4f6c55f6]:has(> .cn-form-widget),
21075
+ .cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content[data-v-4f6c55f6]:has(> .cn-kb-search-widget) {
21076
21076
  padding: 16px;
21077
21077
  }
21078
21078
 
@@ -21083,7 +21083,7 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21083
21083
  a square panel, which is the last visible piece of a card that is supposed
21084
21084
  to be absent. `CnDetailCard.cn-detail-card--chromeless` zeroes the same
21085
21085
  thing for the same reason. */
21086
- .cn-widget-wrapper--chromeless[data-v-b19b1aa6] {
21086
+ .cn-widget-wrapper--chromeless[data-v-4f6c55f6] {
21087
21087
  border-radius: 0;
21088
21088
  }
21089
21089
 
@@ -21095,7 +21095,7 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21095
21095
  * so users can override any token. Combined selector raises specificity above
21096
21096
  * the base `.cn-widget-wrapper` rules it supersedes.
21097
21097
  */
21098
- .cn-widget-wrapper__header[data-v-b19b1aa6] {
21098
+ .cn-widget-wrapper__header[data-v-4f6c55f6] {
21099
21099
  display: flex;
21100
21100
  align-items: center;
21101
21101
  justify-content: space-between;
@@ -21111,7 +21111,7 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21111
21111
  /* Actions with no title beside them. `header-left` is not rendered at all in
21112
21112
  this mode, and the header is `space-between`, so a single child would sit
21113
21113
  flush LEFT where every other surface puts its actions on the right. */
21114
- .cn-widget-wrapper__header--actions-only[data-v-b19b1aa6] {
21114
+ .cn-widget-wrapper__header--actions-only[data-v-4f6c55f6] {
21115
21115
  justify-content: flex-end;
21116
21116
  }
21117
21117
 
@@ -21127,11 +21127,11 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21127
21127
  no-descending-specificity asks for. The cascade did not need it (this
21128
21128
  selector is more specific either way), a reader does: an override written
21129
21129
  above the thing it overrides has to be read twice. */
21130
- .cn-widget-wrapper--chromeless .cn-widget-wrapper__header[data-v-b19b1aa6] {
21130
+ .cn-widget-wrapper--chromeless .cn-widget-wrapper__header[data-v-4f6c55f6] {
21131
21131
  border-bottom: none;
21132
21132
  padding-inline: 0;
21133
21133
  }
21134
- .cn-widget-wrapper__header-left[data-v-b19b1aa6] {
21134
+ .cn-widget-wrapper__header-left[data-v-4f6c55f6] {
21135
21135
  display: flex;
21136
21136
  align-items: center;
21137
21137
  gap: 8px;
@@ -21140,19 +21140,19 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21140
21140
  of centering between icon and actions (header is space-between). */
21141
21141
  flex: 1 1 auto;
21142
21142
  }
21143
- .cn-widget-wrapper__icon[data-v-b19b1aa6] {
21143
+ .cn-widget-wrapper__icon[data-v-4f6c55f6] {
21144
21144
  width: 24px;
21145
21145
  height: 24px;
21146
21146
  flex-shrink: 0;
21147
21147
  color: var(--cn-widget-icon-color, var(--color-primary-element));
21148
21148
  }
21149
- .cn-widget-wrapper__title-stack[data-v-b19b1aa6] {
21149
+ .cn-widget-wrapper__title-stack[data-v-4f6c55f6] {
21150
21150
  display: flex;
21151
21151
  flex-direction: column;
21152
21152
  gap: 2px;
21153
21153
  min-width: 0;
21154
21154
  }
21155
- .cn-widget-wrapper__eyebrow[data-v-b19b1aa6] {
21155
+ .cn-widget-wrapper__eyebrow[data-v-4f6c55f6] {
21156
21156
  font-size: 13px;
21157
21157
  line-height: 1.3;
21158
21158
  color: var(--color-text-maxcontrast);
@@ -21160,7 +21160,7 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21160
21160
  overflow: hidden;
21161
21161
  text-overflow: ellipsis;
21162
21162
  }
21163
- .cn-widget-wrapper__title[data-v-b19b1aa6] {
21163
+ .cn-widget-wrapper__title[data-v-4f6c55f6] {
21164
21164
  font-weight: 600;
21165
21165
  font-size: 14px;
21166
21166
  margin: 0;
@@ -21168,34 +21168,34 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21168
21168
  overflow: hidden;
21169
21169
  text-overflow: ellipsis;
21170
21170
  }
21171
- .cn-widget-wrapper.cn-widget-wrapper--nc-dashboard[data-v-b19b1aa6] {
21171
+ .cn-widget-wrapper.cn-widget-wrapper--nc-dashboard[data-v-4f6c55f6] {
21172
21172
  background: var(--color-main-background-blur, var(--color-main-background));
21173
21173
  -webkit-backdrop-filter: var(--filter-background-blur, none);
21174
21174
  backdrop-filter: var(--filter-background-blur, none);
21175
21175
  border: none;
21176
21176
  border-radius: var(--border-radius-container-large, 16px);
21177
21177
  }
21178
- .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header[data-v-b19b1aa6] {
21178
+ .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header[data-v-4f6c55f6] {
21179
21179
  padding: 16px;
21180
21180
  border-bottom: none;
21181
21181
  }
21182
- .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header-left[data-v-b19b1aa6] {
21182
+ .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header-left[data-v-4f6c55f6] {
21183
21183
  gap: 16px;
21184
21184
  }
21185
- .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__icon[data-v-b19b1aa6] {
21185
+ .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__icon[data-v-4f6c55f6] {
21186
21186
  width: 32px;
21187
21187
  height: 32px;
21188
21188
  background-size: 32px;
21189
21189
  }
21190
- .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__title[data-v-b19b1aa6] {
21190
+ .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__title[data-v-4f6c55f6] {
21191
21191
  font-size: 20px;
21192
21192
  font-weight: 700;
21193
21193
  line-height: 24px;
21194
21194
  }
21195
- .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__content[data-v-b19b1aa6] {
21195
+ .cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__content[data-v-4f6c55f6] {
21196
21196
  padding: 0 16px 16px;
21197
21197
  }
21198
- .cn-widget-wrapper__title-meta[data-v-b19b1aa6] {
21198
+ .cn-widget-wrapper__title-meta[data-v-4f6c55f6] {
21199
21199
  display: flex;
21200
21200
  align-items: center;
21201
21201
  gap: 4px;
@@ -21204,7 +21204,7 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21204
21204
  white-space: nowrap;
21205
21205
  flex-shrink: 0;
21206
21206
  }
21207
- .cn-widget-wrapper__actions[data-v-b19b1aa6] {
21207
+ .cn-widget-wrapper__actions[data-v-4f6c55f6] {
21208
21208
  display: flex;
21209
21209
  gap: 4px;
21210
21210
  flex-shrink: 0;
@@ -21212,7 +21212,7 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21212
21212
 
21213
21213
  /* The header's text link (`headerLink`). Theme hooks: --cn-widget-header-link-size
21214
21214
  and --cn-widget-header-link-weight. */
21215
- .cn-widget-wrapper__header-link[data-v-b19b1aa6] {
21215
+ .cn-widget-wrapper__header-link[data-v-4f6c55f6] {
21216
21216
  align-self: center;
21217
21217
  padding: 0 calc(1 * var(--default-grid-baseline));
21218
21218
  color: var(--color-primary-element);
@@ -21221,11 +21221,11 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21221
21221
  text-decoration: none;
21222
21222
  white-space: nowrap;
21223
21223
  }
21224
- .cn-widget-wrapper__header-link[data-v-b19b1aa6]:hover,
21225
- .cn-widget-wrapper__header-link[data-v-b19b1aa6]:focus-visible {
21224
+ .cn-widget-wrapper__header-link[data-v-4f6c55f6]:hover,
21225
+ .cn-widget-wrapper__header-link[data-v-4f6c55f6]:focus-visible {
21226
21226
  text-decoration: underline;
21227
21227
  }
21228
- .cn-widget-wrapper__header-link[data-v-b19b1aa6]:focus-visible {
21228
+ .cn-widget-wrapper__header-link[data-v-4f6c55f6]:focus-visible {
21229
21229
  outline: 2px solid var(--color-primary-element);
21230
21230
  outline-offset: 2px;
21231
21231
  border-radius: var(--border-radius-small, 4px);
@@ -21236,7 +21236,7 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21236
21236
  wrapper is ever rendered without it. Material-design-icon components paint
21237
21237
  with `fill: currentColor`, so this reaches them; the :deep rule below makes
21238
21238
  it reach a raw <svg> in the slot too. */
21239
- .cn-widget-wrapper__title-icon[data-v-b19b1aa6] {
21239
+ .cn-widget-wrapper__title-icon[data-v-4f6c55f6] {
21240
21240
  display: flex;
21241
21241
  align-items: center;
21242
21242
  flex-shrink: 0;
@@ -21256,17 +21256,17 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21256
21256
 
21257
21257
  /* Right-positioned title icons sit AFTER the actions, so the margin belongs on
21258
21258
  the other side or it pushes the icon off the header's right edge. */
21259
- .cn-widget-wrapper__title-icon[data-v-b19b1aa6]:last-child {
21259
+ .cn-widget-wrapper__title-icon[data-v-4f6c55f6]:last-child {
21260
21260
  margin-inline-end: 0;
21261
21261
  margin-inline-start: 8px;
21262
21262
  }
21263
21263
 
21264
21264
  /* Material-design-icon components paint with `fill: currentColor`, so the
21265
21265
  colour above reaches them; this makes it reach a raw <svg> in the slot too. */
21266
- .cn-widget-wrapper__title-icon[data-v-b19b1aa6] svg {
21266
+ .cn-widget-wrapper__title-icon[data-v-4f6c55f6] svg {
21267
21267
  fill: currentcolor;
21268
21268
  }
21269
- .cn-widget-wrapper__footer[data-v-b19b1aa6] {
21269
+ .cn-widget-wrapper__footer[data-v-4f6c55f6] {
21270
21270
  display: flex;
21271
21271
  justify-content: flex-end;
21272
21272
  gap: 8px;
@@ -21274,12 +21274,12 @@ a.cn-xwiki-card__title[data-v-1a6b61fd]:hover {
21274
21274
  border-top: 1px solid var(--color-border);
21275
21275
  flex-shrink: 0;
21276
21276
  }
21277
- .cn-widget-wrapper__footer-link[data-v-b19b1aa6] {
21277
+ .cn-widget-wrapper__footer-link[data-v-4f6c55f6] {
21278
21278
  font-size: 13px;
21279
21279
  color: var(--color-primary-element);
21280
21280
  text-decoration: none;
21281
21281
  }
21282
- .cn-widget-wrapper__footer-link[data-v-b19b1aa6]:hover {
21282
+ .cn-widget-wrapper__footer-link[data-v-4f6c55f6]:hover {
21283
21283
  text-decoration: underline;
21284
21284
  }
21285
21285
 
@@ -25328,7 +25328,7 @@ button.cn-stepper__step[data-v-776581d4] {
25328
25328
 
25329
25329
 
25330
25330
  /* A stack (`widgetIds`): the panel's widget, then cards 20px apart. */
25331
- .cn-tabs-widget__stack[data-v-72f3975c] {
25331
+ .cn-tabs-widget__stack[data-v-4294c502] {
25332
25332
  display: flex;
25333
25333
  flex-direction: column;
25334
25334
  gap: 20px;
@@ -25337,19 +25337,19 @@ button.cn-stepper__step[data-v-776581d4] {
25337
25337
  /* A gridded stack (`columns` / `layout` on the tab): 12 columns, widgets wrap
25338
25338
  onto the next row, a row's cards as tall as the tallest. Narrow, it is the
25339
25339
  plain stack again. */
25340
- .cn-tabs-widget__stack--grid[data-v-72f3975c] {
25340
+ .cn-tabs-widget__stack--grid[data-v-4294c502] {
25341
25341
  display: grid;
25342
25342
  grid-template-columns: repeat(12, minmax(0, 1fr));
25343
25343
  align-items: stretch;
25344
25344
  }
25345
- .cn-tabs-widget__stack--grid[data-v-72f3975c] > * {
25345
+ .cn-tabs-widget__stack--grid[data-v-4294c502] > * {
25346
25346
  min-width: 0;
25347
25347
  }
25348
25348
  @media (max-width: 700px) {
25349
- .cn-tabs-widget__stack--grid[data-v-72f3975c] {
25349
+ .cn-tabs-widget__stack--grid[data-v-4294c502] {
25350
25350
  grid-template-columns: minmax(0, 1fr);
25351
25351
  }
25352
- .cn-tabs-widget__stack--grid[data-v-72f3975c] > * {
25352
+ .cn-tabs-widget__stack--grid[data-v-4294c502] > * {
25353
25353
  grid-column: auto !important;
25354
25354
  }
25355
25355
  }
@@ -25383,7 +25383,7 @@ button.cn-stepper__step[data-v-776581d4] {
25383
25383
  `!important` or on load order this library does not control. The same
25384
25384
  technique, for the same reason, is used on `.cn-tabs__nav .cn-tabs__nav-item`
25385
25385
  in CnTabs to beat Nextcloud's own button margin. */
25386
- .cn-tabs-widget.cn-tabs-widget[data-v-72f3975c] {
25386
+ .cn-tabs-widget.cn-tabs-widget[data-v-4294c502] {
25387
25387
  background-color: transparent;
25388
25388
  border: 0;
25389
25389
  border-radius: 0;
@@ -25407,12 +25407,12 @@ button.cn-stepper__step[data-v-776581d4] {
25407
25407
  below it: the rule ran straight on to the widget's edge and read as a
25408
25408
  border sticking out past a rounded corner (Ruben, 2026-09-13). Ending it
25409
25409
  where the curve begins makes the rule and the curve one continuous edge. */
25410
- .cn-tabs-widget__tabs[data-v-72f3975c] .cn-tabs__bar {
25410
+ .cn-tabs-widget__tabs[data-v-4294c502] .cn-tabs__bar {
25411
25411
  border-bottom: none;
25412
25412
  padding: 0;
25413
25413
  position: relative;
25414
25414
  }
25415
- .cn-tabs-widget__tabs[data-v-72f3975c] .cn-tabs__bar::after {
25415
+ .cn-tabs-widget__tabs[data-v-4294c502] .cn-tabs__bar::after {
25416
25416
  border-bottom: 1px solid var(--color-border);
25417
25417
  content: '';
25418
25418
  inset-block-end: 0;
@@ -25425,11 +25425,11 @@ button.cn-stepper__step[data-v-776581d4] {
25425
25425
  own background-coloured bottom edge, which only works if it paints ABOVE the
25426
25426
  pseudo-element: a positioned pseudo-element otherwise paints over every
25427
25427
  in-flow sibling, rule included. */
25428
- .cn-tabs-widget__tabs[data-v-72f3975c] .cn-tabs__nav-item--active {
25428
+ .cn-tabs-widget__tabs[data-v-4294c502] .cn-tabs__nav-item--active {
25429
25429
  position: relative;
25430
25430
  z-index: 1;
25431
25431
  }
25432
- .cn-tabs-widget__tabs[data-v-72f3975c] {
25432
+ .cn-tabs-widget__tabs[data-v-4294c502] {
25433
25433
  display: flex;
25434
25434
  flex-direction: column;
25435
25435
  /* Grow to the widget's height, so the panel fills the grid cell the
@@ -25447,7 +25447,7 @@ button.cn-stepper__step[data-v-776581d4] {
25447
25447
  the open tab, but padding sits inside the painted box — only a margin would
25448
25448
  have opened that gap, and a zero top put every panel's content against the
25449
25449
  strip. */
25450
- .cn-tabs-widget__tabs[data-v-72f3975c] .cn-tabs__content {
25450
+ .cn-tabs-widget__tabs[data-v-4294c502] .cn-tabs__content {
25451
25451
  background-color: var(--color-main-background);
25452
25452
  /* Three sides only: the bar's rule (redrawn above, stopping at the corner)
25453
25453
  is this sheet's top edge, and the open tab erases the slice of it
@@ -25467,7 +25467,7 @@ button.cn-stepper__step[data-v-776581d4] {
25467
25467
  overflow: auto;
25468
25468
  padding: 12px;
25469
25469
  }
25470
- .cn-tabs-widget__tabs[data-v-72f3975c] .cn-tab {
25470
+ .cn-tabs-widget__tabs[data-v-4294c502] .cn-tab {
25471
25471
  height: 100%;
25472
25472
  }
25473
25473
 
@@ -25475,15 +25475,36 @@ button.cn-stepper__step[data-v-776581d4] {
25475
25475
  (margin 0 -16px -16px). The panel of a tab pads 12px, so on a tab that
25476
25476
  holds the list directly the footer stuck out 4px on the right and at the
25477
25477
  bottom and made the panel scroll. It bleeds exactly the panel's padding. */
25478
- .cn-tabs-widget__tabs[data-v-72f3975c] .cn-tabs__content > .cn-tab > .cn-detail-widget-host--bare .cn-object-list-widget__add {
25478
+ .cn-tabs-widget__tabs[data-v-4294c502] .cn-tabs__content > .cn-tab > .cn-detail-widget-host--bare .cn-object-list-widget__add {
25479
25479
  margin: auto -12px -12px;
25480
25480
  }
25481
- .cn-tabs-widget__title[data-v-72f3975c] {
25481
+
25482
+ /* The open panel's header link, beside the strip. Same look and theme hooks
25483
+ as a card header link (CnWidgetWrapper). */
25484
+ .cn-tabs-widget__header-link[data-v-4294c502] {
25485
+ align-self: center;
25486
+ padding: 0 calc(1 * var(--default-grid-baseline));
25487
+ color: var(--color-primary-element);
25488
+ font-size: var(--cn-widget-header-link-size, 14px);
25489
+ font-weight: var(--cn-widget-header-link-weight, 600);
25490
+ text-decoration: none;
25491
+ white-space: nowrap;
25492
+ }
25493
+ .cn-tabs-widget__header-link[data-v-4294c502]:hover,
25494
+ .cn-tabs-widget__header-link[data-v-4294c502]:focus-visible {
25495
+ text-decoration: underline;
25496
+ }
25497
+ .cn-tabs-widget__header-link[data-v-4294c502]:focus-visible {
25498
+ outline: 2px solid var(--color-primary-element);
25499
+ outline-offset: 2px;
25500
+ border-radius: var(--border-radius-small, 4px);
25501
+ }
25502
+ .cn-tabs-widget__title[data-v-4294c502] {
25482
25503
  align-items: center;
25483
25504
  display: inline-flex;
25484
25505
  gap: 6px;
25485
25506
  }
25486
- .cn-tabs-widget__title-icon[data-v-72f3975c] {
25507
+ .cn-tabs-widget__title-icon[data-v-4294c502] {
25487
25508
  flex: 0 0 auto;
25488
25509
  }
25489
25510
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.86.0",
3
+ "version": "2.87.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": {
@@ -4242,6 +4242,8 @@ export default {
4242
4242
  narrowBy,
4243
4243
  narrowValue: narrowBy ? this.boardNarrowValue : '',
4244
4244
  capacityField: source.capacityField || '',
4245
+ neutralColor: typeof source.neutralColor === 'string' ? source.neutralColor : '',
4246
+ colorMap: source.colorMap && typeof source.colorMap === 'object' ? source.colorMap : null,
4245
4247
  })
4246
4248
  },
4247
4249
 
@@ -15,7 +15,19 @@
15
15
  is showing. This is the point of the component: six tabbed
16
16
  widgets used to mean six card headers stacked down the page. -->
17
17
  <template #nav-end>
18
+ <!-- The open panel's header link ("All sales invoices"): a panel
19
+ draws no card header, so the link its widget declares is
20
+ drawn here, and changes with the tab. -->
21
+ <component
22
+ :is="activeHeaderLinkTarget.tag"
23
+ v-if="activeHeaderLinkTarget.tag"
24
+ class="cn-tabs-widget__header-link"
25
+ data-testid="cn-tabs-widget-header-link"
26
+ v-bind="activeHeaderLinkTarget.attrs">
27
+ {{ cnTranslate(activeHeaderLink.label) }}
28
+ </component>
18
29
  <CnActionsMenu
30
+ v-if="showActions"
19
31
  :showRefresh="showRefresh"
20
32
  :showRequestFeature="showRequestFeature"
21
33
  :showDocumentation="showDocumentation"
@@ -169,6 +181,7 @@ import CnIcon from '../CnIcon/CnIcon.vue'
169
181
  import CnTab from '../CnTabs/CnTab.vue'
170
182
  import CnTabs from '../CnTabs/CnTabs.vue'
171
183
  import { normalizeLook } from '../../composables/useLook.js'
184
+ import { headerLinkTarget, widgetHeaderLink } from '../../utils/contentHeaderLink.js'
172
185
  import { resolveTabCount } from '../../utils/detailActionModel.js'
173
186
  import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
174
187
  import { isActivityWidget } from '../../utils/headerMeta.js'
@@ -430,6 +443,18 @@ export default {
430
443
  default: () => ({}),
431
444
  },
432
445
 
446
+ /**
447
+ * Whether the strip draws its Actions menu at all. `false` drops the
448
+ * whole menu, the open panel's own items included, for a board that
449
+ * draws none. Manifest: `content.showActions` on the tabs widget.
450
+ *
451
+ * @spec openspec/changes/tabs-widget-actions-off/specs/tabs-widget-actions-off/spec.md#requirement-a-manifest-can-turn-off-the-tab-strips-actions-menu
452
+ */
453
+ showActions: {
454
+ type: Boolean,
455
+ default: true,
456
+ },
457
+
433
458
  /** Show the Refresh entry in the hoisted Actions menu. */
434
459
  showRefresh: {
435
460
  type: Boolean,
@@ -654,6 +679,29 @@ export default {
654
679
  return this.activeTab?.widgetId || ''
655
680
  },
656
681
 
682
+ /**
683
+ * The header link the open tab's widget declares (`headerLink` on the
684
+ * definition, or in its content), or null. For a stack, the first
685
+ * widget's.
686
+ *
687
+ * @spec openspec/changes/tabs-widget-header-link/specs/tabs-widget-header-link/spec.md#requirement-the-tab-strip-draws-the-open-panels-header-link
688
+ * @return {object|null}
689
+ */
690
+ activeHeaderLink() {
691
+ return widgetHeaderLink(this.activeTab?.widget)
692
+ },
693
+
694
+ /**
695
+ * How to draw the open panel's header link: a router link for a
696
+ * route, an anchor for an href, nothing otherwise.
697
+ *
698
+ * @spec openspec/changes/tabs-widget-header-link/specs/tabs-widget-header-link/spec.md#requirement-the-tab-strip-draws-the-open-panels-header-link
699
+ * @return {{tag: string, attrs: object}}
700
+ */
701
+ activeHeaderLinkTarget() {
702
+ return headerLinkTarget(this.activeHeaderLink, Boolean(this.$router))
703
+ },
704
+
657
705
  /**
658
706
  * The active child's label, so the Actions menu names what it acts on.
659
707
  *
@@ -1231,6 +1279,29 @@ export default {
1231
1279
  margin: auto -12px -12px;
1232
1280
  }
1233
1281
 
1282
+ /* The open panel's header link, beside the strip. Same look and theme hooks
1283
+ as a card header link (CnWidgetWrapper). */
1284
+ .cn-tabs-widget__header-link {
1285
+ align-self: center;
1286
+ padding: 0 calc(1 * var(--default-grid-baseline));
1287
+ color: var(--color-primary-element);
1288
+ font-size: var(--cn-widget-header-link-size, 14px);
1289
+ font-weight: var(--cn-widget-header-link-weight, 600);
1290
+ text-decoration: none;
1291
+ white-space: nowrap;
1292
+ }
1293
+
1294
+ .cn-tabs-widget__header-link:hover,
1295
+ .cn-tabs-widget__header-link:focus-visible {
1296
+ text-decoration: underline;
1297
+ }
1298
+
1299
+ .cn-tabs-widget__header-link:focus-visible {
1300
+ outline: 2px solid var(--color-primary-element);
1301
+ outline-offset: 2px;
1302
+ border-radius: var(--border-radius-small, 4px);
1303
+ }
1304
+
1234
1305
  .cn-tabs-widget__title {
1235
1306
  align-items: center;
1236
1307
  display: inline-flex;
@@ -176,6 +176,7 @@
176
176
 
177
177
  <script>
178
178
  import { translate as t } from '@nextcloud/l10n'
179
+ import { headerLinkTarget } from '../../utils/contentHeaderLink.js'
179
180
  import { slotRenders } from '../../utils/slotContent.js'
180
181
  import { CnActionsMenu } from '../CnActionsMenu/index.js'
181
182
 
@@ -686,17 +687,7 @@ export default {
686
687
  * @return {string}
687
688
  */
688
689
  headerLinkTag() {
689
- const link = this.headerLink
690
- if (!link || typeof link.label !== 'string' || link.label === '') {
691
- return ''
692
- }
693
- if (link.route && this.$router) {
694
- return 'router-link'
695
- }
696
- if (typeof link.href === 'string' && link.href !== '') {
697
- return 'a'
698
- }
699
- return ''
690
+ return headerLinkTarget(this.headerLink, Boolean(this.$router)).tag
700
691
  },
701
692
 
702
693
  /**
@@ -706,21 +697,7 @@ export default {
706
697
  * @return {object}
707
698
  */
708
699
  headerLinkAttrs() {
709
- const link = this.headerLink || {}
710
- if (this.headerLinkTag === 'router-link') {
711
- const to = { name: link.route }
712
- if (link.params && typeof link.params === 'object') {
713
- to.params = link.params
714
- }
715
- if (link.query && typeof link.query === 'object') {
716
- to.query = link.query
717
- }
718
- return { to }
719
- }
720
- if (this.headerLinkTag === 'a') {
721
- return { href: link.href }
722
- }
723
- return {}
700
+ return headerLinkTarget(this.headerLink, Boolean(this.$router)).attrs
724
701
  },
725
702
 
726
703
  /**
@@ -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.100.0",
6
+ "version": "2.102.0",
7
7
  "type": "object",
8
8
  "required": ["$schema", "version"],
9
9
  "allOf": [
@@ -1177,6 +1177,8 @@
1177
1177
  "colorField": { "type": "string", "description": "The referenced row's field that holds the column's dot colour (a badge variant or a CSS colour)." },
1178
1178
  "filter": { "type": "object", "description": "Equality filters on the referenced rows, e.g. `{ \"isFinal\": false }` to leave out end states.", "additionalProperties": { "type": ["string", "number", "boolean"] } },
1179
1179
  "capacityField": { "type": "string", "minLength": 1, "description": "board-column-capacity (2.100.0): the referenced row's numeric field that holds the column's limit. The header reads \"count / capacity\" and turns full at the limit, and a move into a full row is refused before it is written (the server still guards). A column merged from several rows shows no limit, but each row's own limit still refuses a move into it. A row without a number above 0 has no limit." },
1180
+ "neutralColor": { "type": "string", "minLength": 1, "description": "board-neutral-column-colour (2.101.0): the colour a referenced row holds when nobody chose one (the schema default, e.g. \"grey\"). A column merged from several rows (`mergeBy`) then takes the first row colour that is not this one, and this one only when every row has it. Compared as written: \"Grey\" is not \"grey\", and a hex value is not a name. Unset, a merged column takes its first row's colour." },
1181
+ "colorMap": { "type": "object", "description": "board-column-colour-map (2.102.0): a stored row colour to the CSS colour its column is drawn in, e.g. `{ \"orange\": \"var(--nl-color-orange, #e17000)\" }`, so a palette name draws a theme token. Applied after the merge, so `neutralColor` still compares the stored values. A colour it does not map draws as before.", "additionalProperties": { "type": "string", "minLength": 1 } },
1180
1182
  "mergeBy": { "type": "string", "description": "Merge referenced rows with the same value of this field into one column (statuses of every case type by `name`). A drop then writes the row of the card's own parent (`narrowBy`), and is refused when that parent has none." },
1181
1183
  "narrowBy": {
1182
1184
  "type": "object",