@conduction/nextcloud-vue 2.56.0-beta.35 → 2.56.0-beta.36

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 (37) hide show
  1. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
  2. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +114 -0
  3. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  4. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +2 -2
  5. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  6. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  7. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +95 -0
  8. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  9. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
  10. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  11. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
  12. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
  13. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  15. package/dist/esm/index.js +1 -0
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/library-style.js +1 -1
  18. package/dist/esm/nextcloud-vue.css +120 -65
  19. package/dist/esm/utils/gridStack.js +40 -1
  20. package/dist/esm/utils/gridStack.js.map +1 -1
  21. package/dist/esm/utils/humaniseKey.js +41 -0
  22. package/dist/esm/utils/humaniseKey.js.map +1 -0
  23. package/dist/esm/utils/notificationRuleLabel.js +6 -23
  24. package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
  25. package/dist/nextcloud-vue.cjs.js +546 -256
  26. package/dist/nextcloud-vue.cjs.js.map +2 -2
  27. package/dist/nextcloud-vue.css +120 -65
  28. package/package.json +1 -1
  29. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +116 -2
  30. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +123 -3
  31. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
  32. package/src/css/kpi-card.css +13 -0
  33. package/src/css/look-board.css +19 -0
  34. package/src/index.js +2 -0
  35. package/src/utils/gridStack.js +39 -0
  36. package/src/utils/humaniseKey.js +38 -0
  37. package/src/utils/notificationRuleLabel.js +5 -23
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/nextcloud-vue.cjs.js.map
3
- size: 40322776 bytes
4
- sha256: fe28d43fed94e91ceb0fddfbc8246cdd8b80464acec068fbd89bd0a6b10bac77
3
+ size: 40354945 bytes
4
+ sha256: 3646bf9e9127562ff3251fd58ad2051364849075f480ed05940d41a724726042
@@ -781,6 +781,25 @@
781
781
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__chevron {
782
782
  display: none;
783
783
  }
784
+
785
+ /* ---- Week strip (DqMijnWerk "Termijnen deze week") ---------------------- */
786
+ /* The days wrap inside the card instead of scrolling sideways: a row holds
787
+ as many days of at least 120px as the card fits, and the rest go to the
788
+ next row (dossiq at 1100 wide held 680px of days in a 494px card and
789
+ scrolled; now three days and two). 120px keeps a case title's words whole;
790
+ five 88px days fitted one row but broke "Bezwaar" mid-word. A card wide
791
+ enough for the week draws it as before, one row of equal columns. The
792
+ day's head wraps its "today" badge under the label when the day is
793
+ narrow.
794
+ @spec openspec/changes/board-week-strip-fits/specs/week-strip-board-look/spec.md#requirement-a-board-week-strip-fits-its-card */
795
+ .cn-look-board .cn-week-strip:not(.cn-look-nextcloud *) .cn-week-strip__days {
796
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 120px), 1fr));
797
+ min-width: 0;
798
+ }
799
+
800
+ .cn-look-board .cn-week-strip:not(.cn-look-nextcloud *) .cn-week-strip__head {
801
+ flex-wrap: wrap;
802
+ }
784
803
  /*
785
804
  * The board dialog (screens-dialog-parity, canon section 5).
786
805
  *
@@ -3949,6 +3968,19 @@ a.cn-kpi-card--stacked:focus-visible {
3949
3968
  color: var(--color-main-text);
3950
3969
  }
3951
3970
 
3971
+ /* A board tile whose label wraps grows into its cell instead of clipping its
3972
+ own last line. dossiq's "Afgehandeld deze maand" at 1100 wide held 129px of
3973
+ content in a 114px tile and lost its caption's bottom. The tile still fills
3974
+ a cell it fits (`min-height: 100%`), so every tile that fits keeps its
3975
+ size; one that does not takes the height of its content, and the card-fit
3976
+ cell's `safe center` keeps its top in view.
3977
+ @spec openspec/changes/board-stat-tile-grows/specs/stat-tile-board-look/spec.md#requirement-a-board-stat-tile-shows-all-its-lines */
3978
+ .cn-kpi-card--board.cn-kpi-card--stacked {
3979
+ flex: 0 0 auto;
3980
+ height: auto;
3981
+ min-height: 100%;
3982
+ }
3983
+
3952
3984
  .cn-kpi-card--board.cn-kpi-card--stacked .cn-kpi-card__caption-line,
3953
3985
  .cn-kpi-card--board.cn-kpi-card--stacked .cn-stats-block__count-label {
3954
3986
  font-size: 13px;
@@ -22309,6 +22341,19 @@ a.cn-kpi-card--stacked:focus-visible {
22309
22341
  color: var(--color-main-text);
22310
22342
  }
22311
22343
 
22344
+ /* A board tile whose label wraps grows into its cell instead of clipping its
22345
+ own last line. dossiq's "Afgehandeld deze maand" at 1100 wide held 129px of
22346
+ content in a 114px tile and lost its caption's bottom. The tile still fills
22347
+ a cell it fits (`min-height: 100%`), so every tile that fits keeps its
22348
+ size; one that does not takes the height of its content, and the card-fit
22349
+ cell's `safe center` keeps its top in view.
22350
+ @spec openspec/changes/board-stat-tile-grows/specs/stat-tile-board-look/spec.md#requirement-a-board-stat-tile-shows-all-its-lines */
22351
+ .cn-kpi-card--board.cn-kpi-card--stacked {
22352
+ flex: 0 0 auto;
22353
+ height: auto;
22354
+ min-height: 100%;
22355
+ }
22356
+
22312
22357
  .cn-kpi-card--board.cn-kpi-card--stacked .cn-kpi-card__caption-line,
22313
22358
  .cn-kpi-card--board.cn-kpi-card--stacked .cn-stats-block__count-label {
22314
22359
  font-size: 13px;
@@ -23136,7 +23181,7 @@ a.cn-kpi-card--board * {
23136
23181
  }
23137
23182
 
23138
23183
 
23139
- .cn-object-data-widget__image[data-v-76edf75d] {
23184
+ .cn-object-data-widget__image[data-v-324e1a00] {
23140
23185
  max-width: 100%;
23141
23186
  max-height: 160px;
23142
23187
  border-radius: 8px;
@@ -23144,33 +23189,33 @@ a.cn-kpi-card--board * {
23144
23189
  }
23145
23190
 
23146
23191
  /* Shimmering placeholder while a relation's display name resolves. */
23147
- .cn-object-data-widget__skeleton[data-v-76edf75d] {
23192
+ .cn-object-data-widget__skeleton[data-v-324e1a00] {
23148
23193
  display: inline-block;
23149
23194
  width: 90px;
23150
23195
  height: 1em;
23151
23196
  border-radius: 4px;
23152
23197
  background: linear-gradient(90deg, var(--color-background-dark) 25%, var(--color-background-hover) 50%, var(--color-background-dark) 75%);
23153
23198
  background-size: 200% 100%;
23154
- animation: cn-odw-shimmer-76edf75d 1.2s ease-in-out infinite;
23199
+ animation: cn-odw-shimmer-324e1a00 1.2s ease-in-out infinite;
23155
23200
  }
23156
- @keyframes cn-odw-shimmer-76edf75d {
23201
+ @keyframes cn-odw-shimmer-324e1a00 {
23157
23202
  from { background-position: 200% 0;
23158
23203
  }
23159
23204
  to { background-position: -200% 0;
23160
23205
  }
23161
23206
  }
23162
23207
  @media (prefers-reduced-motion: reduce) {
23163
- .cn-object-data-widget__skeleton[data-v-76edf75d] { animation: none;
23208
+ .cn-object-data-widget__skeleton[data-v-324e1a00] { animation: none;
23164
23209
  }
23165
23210
  }
23166
- .cn-object-data-widget__grid[data-v-76edf75d] {
23211
+ .cn-object-data-widget__grid[data-v-324e1a00] {
23167
23212
  display: grid;
23168
23213
  gap: calc(2 * var(--default-grid-baseline, 4px)) calc(4 * var(--default-grid-baseline, 4px));
23169
23214
  }
23170
- .cn-object-data-widget__toggle[data-v-76edf75d] {
23215
+ .cn-object-data-widget__toggle[data-v-324e1a00] {
23171
23216
  margin-top: 4px;
23172
23217
  }
23173
- .cn-object-data-widget__cell[data-v-76edf75d] {
23218
+ .cn-object-data-widget__cell[data-v-324e1a00] {
23174
23219
  display: flex;
23175
23220
  flex-direction: column;
23176
23221
  gap: 2px;
@@ -23178,12 +23223,12 @@ to { background-position: -200% 0;
23178
23223
  border-bottom: 1px solid var(--color-border-dark);
23179
23224
  min-width: 0;
23180
23225
  }
23181
- .cn-object-data-widget__label[data-v-76edf75d] {
23226
+ .cn-object-data-widget__label[data-v-324e1a00] {
23182
23227
  font-size: 0.85em;
23183
23228
  color: var(--color-text-maxcontrast);
23184
23229
  font-weight: 500;
23185
23230
  }
23186
- .cn-object-data-widget__value[data-v-76edf75d] {
23231
+ .cn-object-data-widget__value[data-v-324e1a00] {
23187
23232
  font-size: 1em;
23188
23233
  color: var(--color-main-text);
23189
23234
  overflow-wrap: anywhere;
@@ -23197,24 +23242,24 @@ to { background-position: -200% 0;
23197
23242
  which drew a horizontal scrollbar under every data widget in a tab (dossiq's
23198
23243
  case page, Data tab). The end padding drops by the same 4px so the text and
23199
23244
  the edit icon sit where they sat. */
23200
- .cn-object-data-widget__value--editable[data-v-76edf75d] {
23245
+ .cn-object-data-widget__value--editable[data-v-324e1a00] {
23201
23246
  cursor: pointer;
23202
23247
  border-radius: var(--border-radius);
23203
23248
  padding: 4px 20px 4px 4px;
23204
23249
  margin: -4px 0 -4px -4px;
23205
23250
  }
23206
- .cn-object-data-widget__value--editable[data-v-76edf75d]:hover {
23251
+ .cn-object-data-widget__value--editable[data-v-324e1a00]:hover {
23207
23252
  background: var(--color-background-dark);
23208
23253
  }
23209
- .cn-object-data-widget__value--editable[data-v-76edf75d]:focus-visible {
23254
+ .cn-object-data-widget__value--editable[data-v-324e1a00]:focus-visible {
23210
23255
  outline: 2px solid var(--color-primary-element);
23211
23256
  outline-offset: 2px;
23212
23257
  }
23213
- .cn-object-data-widget__value--empty[data-v-76edf75d] {
23258
+ .cn-object-data-widget__value--empty[data-v-324e1a00] {
23214
23259
  color: var(--color-text-maxcontrast);
23215
23260
  font-style: italic;
23216
23261
  }
23217
- .cn-object-data-widget__edit-icon[data-v-76edf75d] {
23262
+ .cn-object-data-widget__edit-icon[data-v-324e1a00] {
23218
23263
  position: absolute;
23219
23264
  right: 4px;
23220
23265
  top: 50%;
@@ -23223,21 +23268,21 @@ to { background-position: -200% 0;
23223
23268
  opacity: 0;
23224
23269
  transition: opacity 0.15s ease;
23225
23270
  }
23226
- .cn-object-data-widget__value--editable:hover .cn-object-data-widget__edit-icon[data-v-76edf75d],
23227
- .cn-object-data-widget__value--editable:focus-visible .cn-object-data-widget__edit-icon[data-v-76edf75d] {
23271
+ .cn-object-data-widget__value--editable:hover .cn-object-data-widget__edit-icon[data-v-324e1a00],
23272
+ .cn-object-data-widget__value--editable:focus-visible .cn-object-data-widget__edit-icon[data-v-324e1a00] {
23228
23273
  opacity: 1;
23229
23274
  }
23230
- .cn-object-data-widget__editor[data-v-76edf75d] {
23275
+ .cn-object-data-widget__editor[data-v-324e1a00] {
23231
23276
  display: flex;
23232
23277
  flex-direction: column;
23233
23278
  gap: 4px;
23234
23279
  }
23235
- .cn-object-data-widget__editor-actions[data-v-76edf75d] {
23280
+ .cn-object-data-widget__editor-actions[data-v-324e1a00] {
23236
23281
  display: flex;
23237
23282
  gap: 2px;
23238
23283
  justify-content: flex-end;
23239
23284
  }
23240
- .cn-object-data-widget__textarea[data-v-76edf75d] {
23285
+ .cn-object-data-widget__textarea[data-v-324e1a00] {
23241
23286
  width: 100%;
23242
23287
  min-height: 80px;
23243
23288
  padding: 8px;
@@ -23249,11 +23294,11 @@ to { background-position: -200% 0;
23249
23294
  font-size: inherit;
23250
23295
  resize: vertical;
23251
23296
  }
23252
- .cn-object-data-widget__textarea[data-v-76edf75d]:focus {
23297
+ .cn-object-data-widget__textarea[data-v-324e1a00]:focus {
23253
23298
  border-color: var(--color-primary-element);
23254
23299
  outline: none;
23255
23300
  }
23256
- .cn-object-data-widget__empty[data-v-76edf75d] {
23301
+ .cn-object-data-widget__empty[data-v-324e1a00] {
23257
23302
  color: var(--color-text-maxcontrast);
23258
23303
  font-style: italic;
23259
23304
  padding: calc(2 * var(--default-grid-baseline, 4px));
@@ -23261,16 +23306,16 @@ to { background-position: -200% 0;
23261
23306
 
23262
23307
  /* Compact inline table for array-of-objects values. Dense + unstyled-simple;
23263
23308
  fits the card and participates in the whole-row overflow handling. */
23264
- .cn-object-data-widget__mini-table-wrap[data-v-76edf75d] {
23309
+ .cn-object-data-widget__mini-table-wrap[data-v-324e1a00] {
23265
23310
  max-width: 100%;
23266
23311
  }
23267
- .cn-object-data-widget__mini-table[data-v-76edf75d] {
23312
+ .cn-object-data-widget__mini-table[data-v-324e1a00] {
23268
23313
  width: 100%;
23269
23314
  border-collapse: collapse;
23270
23315
  font-size: 0.9em;
23271
23316
  }
23272
- .cn-object-data-widget__mini-table th[data-v-76edf75d],
23273
- .cn-object-data-widget__mini-table td[data-v-76edf75d] {
23317
+ .cn-object-data-widget__mini-table th[data-v-324e1a00],
23318
+ .cn-object-data-widget__mini-table td[data-v-324e1a00] {
23274
23319
  text-align: start;
23275
23320
  padding: 2px 8px 2px 0;
23276
23321
  border-bottom: 1px solid var(--color-border);
@@ -23281,23 +23326,23 @@ to { background-position: -200% 0;
23281
23326
  cell, and the card 8px wider than its cell (pipelinq's ticket page). */
23282
23327
  white-space: normal;
23283
23328
  }
23284
- .cn-object-data-widget__mini-table th[data-v-76edf75d] {
23329
+ .cn-object-data-widget__mini-table th[data-v-324e1a00] {
23285
23330
  color: var(--color-text-maxcontrast);
23286
23331
  font-weight: 500;
23287
23332
  }
23288
- .cn-object-data-widget__more[data-v-76edf75d] {
23333
+ .cn-object-data-widget__more[data-v-324e1a00] {
23289
23334
  margin-top: 2px;
23290
23335
  font-size: 0.85em;
23291
23336
  color: var(--color-text-maxcontrast);
23292
23337
  }
23293
23338
 
23294
23339
  /* Comma-less chip row for array-of-scalars values. */
23295
- .cn-object-data-widget__chips[data-v-76edf75d] {
23340
+ .cn-object-data-widget__chips[data-v-324e1a00] {
23296
23341
  display: flex;
23297
23342
  flex-wrap: wrap;
23298
23343
  gap: 4px;
23299
23344
  }
23300
- .cn-object-data-widget__chip[data-v-76edf75d] {
23345
+ .cn-object-data-widget__chip[data-v-324e1a00] {
23301
23346
  padding: 1px 8px;
23302
23347
  border-radius: 12px;
23303
23348
  background: var(--color-background-dark);
@@ -23305,27 +23350,37 @@ to { background-position: -200% 0;
23305
23350
  }
23306
23351
 
23307
23352
  /* Key: value definition list for a single plain-object value. */
23308
- .cn-object-data-widget__deflist[data-v-76edf75d] {
23353
+ .cn-object-data-widget__deflist[data-v-324e1a00] {
23309
23354
  display: grid;
23310
23355
  grid-template-columns: auto 1fr;
23311
23356
  gap: 2px 8px;
23312
23357
  margin: 0;
23313
23358
  }
23314
- .cn-object-data-widget__deflist dt[data-v-76edf75d] {
23359
+ .cn-object-data-widget__deflist dt[data-v-324e1a00] {
23315
23360
  color: var(--color-text-maxcontrast);
23316
23361
  font-size: 0.9em;
23317
23362
  }
23318
- .cn-object-data-widget__deflist dd[data-v-76edf75d] {
23363
+ .cn-object-data-widget__deflist dd[data-v-324e1a00] {
23319
23364
  margin: 0;
23320
23365
  overflow-wrap: anywhere;
23321
23366
  }
23322
23367
 
23368
+ /* A long description keeps its own paragraphs. Set on an inline span with no
23369
+ surrounding template whitespace, so only the value's own line breaks show. */
23370
+ .cn-object-data-widget__deflist-text[data-v-324e1a00] {
23371
+ white-space: pre-wrap;
23372
+ }
23373
+ .cn-object-data-widget__deflist--nested[data-v-324e1a00] {
23374
+ padding-inline-start: 8px;
23375
+ border-inline-start: 2px solid var(--color-border);
23376
+ }
23377
+
23323
23378
  /* Responsive: collapse to single column on narrow widths */
23324
23379
  @media (max-width: 600px) {
23325
- .cn-object-data-widget__grid[data-v-76edf75d] {
23380
+ .cn-object-data-widget__grid[data-v-324e1a00] {
23326
23381
  grid-template-columns: 1fr !important;
23327
23382
  }
23328
- .cn-object-data-widget__cell[data-v-76edf75d] {
23383
+ .cn-object-data-widget__cell[data-v-324e1a00] {
23329
23384
  grid-column: span 1 !important;
23330
23385
  grid-row: span 1 !important;
23331
23386
  }
@@ -25216,18 +25271,18 @@ button.cn-stepper__step[data-v-776581d4] {
25216
25271
  }
25217
25272
 
25218
25273
 
25219
- .cn-week-strip[data-v-b6c5e274] {
25274
+ .cn-week-strip[data-v-27a717bd] {
25220
25275
  width: 100%;
25221
25276
  }
25222
25277
 
25223
25278
  /* `content.inset: true`: the board's inset inside the card (zuiddrecht-pixel-
25224
25279
  gaps-3). A dashboard renders its widgets flush, so without it the strip ran
25225
25280
  from card edge to card edge. Theme hook: --cn-widget-board-inset. */
25226
- .cn-week-strip--inset[data-v-b6c5e274] {
25281
+ .cn-week-strip--inset[data-v-27a717bd] {
25227
25282
  box-sizing: border-box;
25228
25283
  padding: var(--cn-widget-board-inset, 16px 24px 22px);
25229
25284
  }
25230
- .cn-week-strip__state[data-v-b6c5e274] {
25285
+ .cn-week-strip__state[data-v-27a717bd] {
25231
25286
  display: flex;
25232
25287
  align-items: center;
25233
25288
  gap: 8px;
@@ -25235,15 +25290,15 @@ button.cn-stepper__step[data-v-776581d4] {
25235
25290
  padding: 12px 4px;
25236
25291
  color: var(--color-text-maxcontrast);
25237
25292
  }
25238
- .cn-week-strip__scroll[data-v-b6c5e274] {
25293
+ .cn-week-strip__scroll[data-v-27a717bd] {
25239
25294
  overflow-x: auto;
25240
25295
  border-radius: var(--border-radius-large, 8px);
25241
25296
  }
25242
- .cn-week-strip__scroll[data-v-b6c5e274]:focus-visible {
25297
+ .cn-week-strip__scroll[data-v-27a717bd]:focus-visible {
25243
25298
  outline: 2px solid var(--color-primary-element);
25244
25299
  outline-offset: 2px;
25245
25300
  }
25246
- .cn-week-strip__days[data-v-b6c5e274] {
25301
+ .cn-week-strip__days[data-v-27a717bd] {
25247
25302
  display: grid;
25248
25303
  grid-template-columns: repeat(var(--cn-week-strip-columns, 5), minmax(128px, 1fr));
25249
25304
  gap: 10px;
@@ -25253,7 +25308,7 @@ button.cn-stepper__step[data-v-776581d4] {
25253
25308
  padding: 0;
25254
25309
  list-style: none;
25255
25310
  }
25256
- .cn-week-strip__day[data-v-b6c5e274] {
25311
+ .cn-week-strip__day[data-v-27a717bd] {
25257
25312
  display: flex;
25258
25313
  flex-direction: column;
25259
25314
  gap: 8px;
@@ -25263,11 +25318,11 @@ button.cn-stepper__step[data-v-776581d4] {
25263
25318
  border-radius: var(--border-radius-large, 8px);
25264
25319
  background: var(--color-background-hover);
25265
25320
  }
25266
- .cn-week-strip__day--today[data-v-b6c5e274] {
25321
+ .cn-week-strip__day--today[data-v-27a717bd] {
25267
25322
  border-color: var(--color-primary-element);
25268
25323
  background: var(--color-primary-element-light);
25269
25324
  }
25270
- .cn-week-strip__head[data-v-b6c5e274] {
25325
+ .cn-week-strip__head[data-v-27a717bd] {
25271
25326
  display: flex;
25272
25327
  align-items: center;
25273
25328
  justify-content: space-between;
@@ -25276,7 +25331,7 @@ button.cn-stepper__step[data-v-776581d4] {
25276
25331
  font-weight: 600;
25277
25332
  color: var(--color-main-text);
25278
25333
  }
25279
- .cn-week-strip__today[data-v-b6c5e274] {
25334
+ .cn-week-strip__today[data-v-27a717bd] {
25280
25335
  padding: 1px 8px;
25281
25336
  border-radius: var(--border-radius-pill, 10px);
25282
25337
  background: var(--color-primary-element);
@@ -25284,7 +25339,7 @@ button.cn-stepper__step[data-v-776581d4] {
25284
25339
  font-size: 0.85em;
25285
25340
  font-weight: 700;
25286
25341
  }
25287
- .cn-week-strip__items[data-v-b6c5e274] {
25342
+ .cn-week-strip__items[data-v-27a717bd] {
25288
25343
  display: flex;
25289
25344
  flex-direction: column;
25290
25345
  gap: 6px;
@@ -25292,7 +25347,7 @@ button.cn-stepper__step[data-v-776581d4] {
25292
25347
  padding: 0;
25293
25348
  list-style: none;
25294
25349
  }
25295
- .cn-week-strip__item[data-v-b6c5e274] {
25350
+ .cn-week-strip__item[data-v-27a717bd] {
25296
25351
  display: flex;
25297
25352
  flex-direction: column;
25298
25353
  gap: 4px;
@@ -25303,41 +25358,41 @@ button.cn-stepper__step[data-v-776581d4] {
25303
25358
  color: var(--color-main-text);
25304
25359
  text-decoration: none;
25305
25360
  }
25306
- .cn-week-strip__item--link[data-v-b6c5e274]:hover {
25361
+ .cn-week-strip__item--link[data-v-27a717bd]:hover {
25307
25362
  border-color: var(--color-primary-element);
25308
25363
  }
25309
- .cn-week-strip__item--link[data-v-b6c5e274]:focus-visible {
25364
+ .cn-week-strip__item--link[data-v-27a717bd]:focus-visible {
25310
25365
  outline: 2px solid var(--color-primary-element);
25311
25366
  outline-offset: 2px;
25312
25367
  }
25313
25368
 
25314
25369
  /* The edge is drawn inside the box, so a late item is as wide as the others. */
25315
- .cn-week-strip__item--late[data-v-b6c5e274] {
25370
+ .cn-week-strip__item--late[data-v-27a717bd] {
25316
25371
  border-color: var(--color-element-error, var(--color-error));
25317
25372
  box-shadow: inset 3px 0 0 0 var(--color-element-error, var(--color-error));
25318
25373
  }
25319
- .cn-week-strip__title[data-v-b6c5e274] {
25374
+ .cn-week-strip__title[data-v-27a717bd] {
25320
25375
  font-size: 0.95em;
25321
25376
  line-height: 1.3;
25322
25377
  overflow-wrap: anywhere;
25323
25378
  }
25324
- .cn-week-strip__meta[data-v-b6c5e274] {
25379
+ .cn-week-strip__meta[data-v-27a717bd] {
25325
25380
  font-size: 0.85em;
25326
25381
  color: var(--color-text-maxcontrast);
25327
25382
  overflow-wrap: anywhere;
25328
25383
  }
25329
- .cn-week-strip__late[data-v-b6c5e274] {
25384
+ .cn-week-strip__late[data-v-27a717bd] {
25330
25385
  font-size: 0.8em;
25331
25386
  font-weight: 600;
25332
25387
  color: var(--color-text-error, var(--color-error-text));
25333
25388
  }
25334
- .cn-week-strip__empty[data-v-b6c5e274] {
25389
+ .cn-week-strip__empty[data-v-27a717bd] {
25335
25390
  font-size: 0.9em;
25336
25391
  color: var(--color-text-maxcontrast);
25337
25392
  }
25338
25393
 
25339
25394
  /* "+N more" under a capped day: a quiet text control, 24px high to click. */
25340
- .cn-week-strip__more[data-v-b6c5e274] {
25395
+ .cn-week-strip__more[data-v-27a717bd] {
25341
25396
  align-self: flex-start;
25342
25397
  min-height: 24px;
25343
25398
  margin: 0;
@@ -25352,10 +25407,10 @@ button.cn-stepper__step[data-v-776581d4] {
25352
25407
  text-decoration: none;
25353
25408
  cursor: pointer;
25354
25409
  }
25355
- .cn-week-strip__more[data-v-b6c5e274]:hover {
25410
+ .cn-week-strip__more[data-v-27a717bd]:hover {
25356
25411
  text-decoration: underline;
25357
25412
  }
25358
- .cn-week-strip__more[data-v-b6c5e274]:focus-visible {
25413
+ .cn-week-strip__more[data-v-27a717bd]:focus-visible {
25359
25414
  outline: 2px solid var(--color-primary-element);
25360
25415
  outline-offset: 2px;
25361
25416
  }
@@ -37533,17 +37588,17 @@ button.cn-object-calendar__overflow[data-v-2735e5ef] {
37533
37588
  }
37534
37589
 
37535
37590
 
37536
- .cn-dashboard-grid[data-v-ca2ae49c] {
37591
+ .cn-dashboard-grid[data-v-a91998fc] {
37537
37592
  width: 100%;
37538
37593
  min-height: 200px;
37539
37594
  }
37540
- .grid-stack[data-v-ca2ae49c] {
37595
+ .grid-stack[data-v-a91998fc] {
37541
37596
  background: transparent;
37542
37597
  }
37543
37598
 
37544
37599
  /* Screen-reader-only text: the keyboard key map and the live region. Kept in
37545
37600
  the layout (not `display: none`) so assistive tech still reads it. */
37546
- .cn-dashboard-grid__sr-only[data-v-ca2ae49c] {
37601
+ .cn-dashboard-grid__sr-only[data-v-a91998fc] {
37547
37602
  position: absolute;
37548
37603
  width: 1px;
37549
37604
  height: 1px;
@@ -37558,7 +37613,7 @@ button.cn-object-calendar__overflow[data-v-2735e5ef] {
37558
37613
  /* Grid items become tab stops in edit mode — give them a visible focus ring
37559
37614
  that satisfies WCAG 2.1 SC 2.4.7 without relying on the widget's own
37560
37615
  styling. `outline-offset` keeps the ring outside the rounded card. */
37561
- .grid-stack-item[data-v-ca2ae49c]:focus-visible {
37616
+ .grid-stack-item[data-v-a91998fc]:focus-visible {
37562
37617
  outline: 2px solid var(--color-primary-element, #0082c9);
37563
37618
  outline-offset: 2px;
37564
37619
  border-radius: var(--border-radius-container-large, 16px);
@@ -37570,19 +37625,19 @@ button.cn-object-calendar__overflow[data-v-2735e5ef] {
37570
37625
  cell never shrank: "Waiting for me" stayed a tall card around one row. In
37571
37626
  such a cell the child takes its own height. A cell without the key keeps
37572
37627
  the filling child. */
37573
- .cn-dashboard-grid__item--fit[data-v-ca2ae49c] > .grid-stack-item-content > * {
37628
+ .cn-dashboard-grid__item--fit[data-v-a91998fc] > .grid-stack-item-content > * {
37574
37629
  height: auto;
37575
37630
  }
37576
- [data-v-ca2ae49c] .grid-stack-item-content {
37631
+ [data-v-a91998fc] .grid-stack-item-content {
37577
37632
  background: var(--color-main-background);
37578
37633
  border: none;
37579
37634
  box-shadow: none;
37580
37635
  overflow: hidden;
37581
37636
  }
37582
- [data-v-ca2ae49c] .grid-stack-item-content:has(.cn-widget-wrapper--borderless) {
37637
+ [data-v-a91998fc] .grid-stack-item-content:has(.cn-widget-wrapper--borderless) {
37583
37638
  background: transparent;
37584
37639
  }
37585
- [data-v-ca2ae49c] .grid-stack-placeholder > .placeholder-content {
37640
+ [data-v-a91998fc] .grid-stack-placeholder > .placeholder-content {
37586
37641
  background: var(--color-primary-element-light);
37587
37642
  border: 2px dashed var(--color-primary-element);
37588
37643
  border-radius: var(--border-radius-large);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.56.0-beta.35",
3
+ "version": "2.56.0-beta.36",
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": {
@@ -28,12 +28,12 @@
28
28
 
29
29
  <div class="grid-stack">
30
30
  <div
31
- v-for="(item, index) in layout"
31
+ v-for="item in itemsInRowOrder"
32
32
  :key="resolveItemKey(item)"
33
33
  class="grid-stack-item"
34
34
  :class="{ 'cn-dashboard-grid__item--fit': sizeToContentAttr(item) !== undefined }"
35
35
  role="group"
36
- :aria-label="resolveItemLabel(item, index)"
36
+ :aria-label="resolveItemLabel(item, layout.indexOf(item))"
37
37
  :aria-describedby="itemDescribedBy"
38
38
  :tabindex="itemTabindex"
39
39
  :gs-id="item.id"
@@ -71,6 +71,7 @@
71
71
  <script>
72
72
  import { translate as t } from '@nextcloud/l10n'
73
73
  import { GridStack } from 'gridstack'
74
+ import { planGrowth } from '../../utils/gridStack.js'
74
75
  // `gridstack` is a peerDependency (see package.json), not bundled — this
75
76
  // component's JS and the consumer's GridStack stylesheet MUST come from the
76
77
  // exact same installed copy, because GridStack's JS writes layout state
@@ -333,6 +334,30 @@ export default {
333
334
  },
334
335
 
335
336
  computed: {
337
+ /**
338
+ * The layout's items top to bottom, then left to right: the order the
339
+ * items are in the page, so GridStack places them in that order.
340
+ *
341
+ * GridStack sizes a size-to-content cell while it adds the items, one
342
+ * at a time, and an item it adds onto a cell that has already grown
343
+ * moves itself below whatever it then collides with. In the layout's
344
+ * own order a card declared after the lists (pipelinq's full-width
345
+ * "client in focus" card) was added last, so when the banner above
346
+ * it grew it skipped past the stat tiles AND the lists and landed at
347
+ * the bottom. Added top to bottom, every card lands under the one
348
+ * above it. It is also the order a screen reader and the Tab key
349
+ * follow. An item without a row or column sorts after the items
350
+ * that have one, in the layout's order.
351
+ *
352
+ * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
353
+ * @return {Array<object>} The items in row order.
354
+ */
355
+ itemsInRowOrder() {
356
+ const items = Array.isArray(this.layout) ? [...this.layout] : []
357
+ const at = (value) => (Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER)
358
+ return items.sort((a, b) => (at(a.gridY) - at(b.gridY)) || (at(a.gridX) - at(b.gridX)))
359
+ },
360
+
336
361
  /**
337
362
  * Whether the grid draws each layout row as ROW_SPLIT finer rows. A
338
363
  * size-to-content cell can only be a whole number of rows tall, so on
@@ -608,6 +633,13 @@ export default {
608
633
  )
609
634
  },
610
635
 
636
+ /**
637
+ * Build the GridStack grid on the rendered items, install the growth
638
+ * wrapper (`keepOrderOnGrowth`) and listen for its changes.
639
+ *
640
+ * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
641
+ * @return {void}
642
+ */
611
643
  initGrid() {
612
644
  if (this.cellHeightCssVar && typeof document !== 'undefined' && document.documentElement) {
613
645
  document.documentElement.style.setProperty(this.cellHeightCssVar, `${this.cellHeight}px`)
@@ -624,6 +656,7 @@ export default {
624
656
  removable: false,
625
657
  ...(this.columnOpts ? { columnOpts: this.columnOpts } : {}),
626
658
  }, el)
659
+ this.keepOrderOnGrowth()
627
660
 
628
661
  this.grid.on('change', (_event, items) => {
629
662
  this.handleGridChange(items)
@@ -635,6 +668,87 @@ export default {
635
668
  this.syncInteractivity()
636
669
  },
637
670
 
671
+ /**
672
+ * Let a size-to-content cell that grows push the cards under it down in
673
+ * their order. GridStack resolves that collision itself, but not in
674
+ * order: when pipelinq's banner grew from two rows to three, the
675
+ * full-width card under the stat tiles landed below the lists, also
676
+ * with `float: false`. Every grow after the grid is built, whether
677
+ * GridStack measures it on resize or this grid's content observer asks
678
+ * for it, now first moves the cards the growth reaches down by the
679
+ * growth (`planGrowth`), bottom-most first, and only then grows the
680
+ * cell, so GridStack has no collision left to resolve. The wrapper is
681
+ * installed after `GridStack.init` has already measured the cells on
682
+ * load; the order on load comes from adding the items in row order
683
+ * (`itemsInRowOrder`). A cell that shrinks is left to GridStack as
684
+ * before.
685
+ *
686
+ * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
687
+ * @return {void}
688
+ */
689
+ keepOrderOnGrowth() {
690
+ const grid = this.grid
691
+ if (!grid || typeof grid.resizeToContent !== 'function' || typeof grid.update !== 'function') {
692
+ return
693
+ }
694
+ const resize = grid.resizeToContent.bind(grid)
695
+ grid.resizeToContent = (el) => {
696
+ const node = el && el.gridstackNode
697
+ const nodes = grid.engine && Array.isArray(grid.engine.nodes) ? grid.engine.nodes : null
698
+ const rows = node && nodes ? this.contentRows(el, node) : null
699
+ const plan = rows ? planGrowth(nodes, node, rows) : []
700
+ if (plan.length === 0) {
701
+ resize(el)
702
+ return
703
+ }
704
+ // Inside a batch the moves are not packed until the cell has grown.
705
+ const batching = Boolean(grid.engine.batchMode)
706
+ if (!batching) {
707
+ grid.batchUpdate()
708
+ }
709
+ for (const step of plan) {
710
+ grid.update(step.node.el, { y: step.y })
711
+ }
712
+ resize(el)
713
+ if (!batching) {
714
+ grid.batchUpdate(false)
715
+ }
716
+ }
717
+ },
718
+
719
+ /**
720
+ * The rows a size-to-content cell needs for its content, measured the
721
+ * way GridStack's `resizeToContent` measures it (the content's first
722
+ * child, plus the cell's own padding), within its size-to-content row
723
+ * maximum and its minimum and maximum height.
724
+ *
725
+ * @spec openspec/changes/dashboard-growth-keeps-order/specs/dashboard-grid/spec.md#requirement-a-growing-cell-keeps-the-order-of-the-cards-under-it
726
+ * @param {HTMLElement} el The cell.
727
+ * @param {object} node Its GridStack node.
728
+ * @return {number|null} The rows, or null when it cannot be measured.
729
+ */
730
+ contentRows(el, node) {
731
+ const cell = this.grid.getCellHeight(true)
732
+ const item = el.querySelector('.grid-stack-item-content')
733
+ const child = item && item.firstElementChild
734
+ if (!el.clientHeight || !cell || !child || !Number.isFinite(node.h)) {
735
+ return null
736
+ }
737
+ const padding = el.clientHeight - item.clientHeight
738
+ const itemHeight = node.h * cell - padding
739
+ const wanted = child.getBoundingClientRect().height || itemHeight
740
+ let rows = Math.ceil((node.h * cell + wanted - itemHeight) / cell)
741
+ if (Number.isInteger(node.sizeToContent) && rows > node.sizeToContent) {
742
+ rows = node.sizeToContent
743
+ }
744
+ if (node.minH && rows < node.minH) {
745
+ rows = node.minH
746
+ } else if (node.maxH && rows > node.maxH) {
747
+ rows = node.maxH
748
+ }
749
+ return rows
750
+ },
751
+
638
752
  /**
639
753
  * Keep both editing lanes in step with whether an edit could actually be
640
754
  * kept: the pointer's handles, and — through `reflowed` feeding