@conduction/nextcloud-vue 2.83.0 → 2.85.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 (246) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +131 -3
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +54 -18
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  7. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +1 -1
  8. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  10. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  11. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +232 -4
  12. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  13. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +95 -51
  14. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBodySections/CnBodySections.vue.js +1 -1
  16. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js +22 -1
  17. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBodySections/CnBodySections.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
  20. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +47 -4
  21. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
  22. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
  23. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
  24. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +239 -3
  25. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +5 -5
  27. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  29. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +50 -0
  30. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
  32. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue.js +1 -1
  34. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js +1 -1
  35. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue3.js.map +1 -1
  37. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +226 -8
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  41. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
  42. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  43. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  44. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  45. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
  46. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  47. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  48. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  49. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +38 -1
  50. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +45 -5
  52. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  53. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue.js +1 -1
  54. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js +1 -1
  55. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js.map +1 -1
  56. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
  58. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  59. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  61. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
  62. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  63. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +29 -6
  64. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  65. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  66. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  67. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +126 -2
  68. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
  70. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  71. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  72. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +106 -3
  73. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  74. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  75. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  76. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
  77. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  78. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
  79. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  80. package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
  81. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +135 -2
  82. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  83. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +95 -43
  84. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  85. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  86. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
  87. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  88. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  89. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue.js +1 -1
  90. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +72 -5
  91. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  92. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +33 -8
  93. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
  95. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +135 -11
  96. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  97. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
  98. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  99. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
  100. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +17 -2
  101. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  102. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +1 -1
  103. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  104. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  105. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +39 -6
  106. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  108. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
  109. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
  110. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
  111. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
  112. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  113. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
  114. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  115. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
  116. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  117. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue.js +1 -1
  118. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +16 -0
  119. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  120. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  121. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
  122. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
  123. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  124. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  125. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
  126. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
  127. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
  128. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
  129. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  130. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -0
  131. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  132. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  133. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  134. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
  135. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  136. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
  137. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  138. package/dist/esm/composables/useListView.js +21 -0
  139. package/dist/esm/composables/useListView.js.map +1 -1
  140. package/dist/esm/dialogs/CnTransitionInputDialog.vue.js +1 -1
  141. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +68 -6
  142. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
  143. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +133 -105
  144. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
  145. package/dist/esm/index.js +1 -0
  146. package/dist/esm/index.js.map +1 -1
  147. package/dist/esm/l10n/en.json.js +38 -1
  148. package/dist/esm/l10n/en.json.js.map +1 -1
  149. package/dist/esm/l10n/nl.json.js +78 -1
  150. package/dist/esm/l10n/nl.json.js.map +1 -1
  151. package/dist/esm/library-style.js +1 -1
  152. package/dist/esm/nextcloud-vue.css +1030 -533
  153. package/dist/esm/store/useObjectStore.js +16 -2
  154. package/dist/esm/store/useObjectStore.js.map +1 -1
  155. package/dist/esm/utils/boardColumns.js +8 -1
  156. package/dist/esm/utils/boardColumns.js.map +1 -1
  157. package/dist/esm/utils/boardReferenceColumns.js +278 -0
  158. package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
  159. package/dist/esm/utils/buildManifest.js +41 -4
  160. package/dist/esm/utils/buildManifest.js.map +1 -1
  161. package/dist/esm/utils/detailWidgetVisibility.js +149 -0
  162. package/dist/esm/utils/detailWidgetVisibility.js.map +1 -0
  163. package/dist/esm/utils/formatMetric.js +39 -3
  164. package/dist/esm/utils/formatMetric.js.map +1 -1
  165. package/dist/esm/utils/gridStack.js +40 -1
  166. package/dist/esm/utils/gridStack.js.map +1 -1
  167. package/dist/esm/utils/humaniseKey.js +41 -0
  168. package/dist/esm/utils/humaniseKey.js.map +1 -0
  169. package/dist/esm/utils/notificationRuleLabel.js +6 -23
  170. package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
  171. package/dist/esm/utils/resolveManifestTokens.js +1 -0
  172. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  173. package/dist/esm/utils/sentinelTokens.js +9 -0
  174. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  175. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  176. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  177. package/dist/esm/utils/widgetDispatch.js +8 -2
  178. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  179. package/dist/nextcloud-vue.cjs.js +17159 -13422
  180. package/dist/nextcloud-vue.cjs.js.map +2 -2
  181. package/dist/nextcloud-vue.css +1030 -533
  182. package/l10n/en.json +38 -1
  183. package/l10n/nl.json +78 -1
  184. package/package.json +1 -1
  185. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
  186. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
  187. package/src/components/CnBoardView/CnBoardView.vue +262 -6
  188. package/src/components/CnBodySections/CnBodySections.vue +22 -1
  189. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
  190. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +244 -8
  191. package/src/components/CnDashboardPage/CnDashboardPage.vue +62 -2
  192. package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
  193. package/src/components/CnDetailPage/CnDetailPage.md +3 -1
  194. package/src/components/CnDetailPage/CnDetailPage.vue +226 -8
  195. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
  196. package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
  197. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
  198. package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
  199. package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
  200. package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
  201. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
  202. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
  203. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +154 -5
  204. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +106 -3
  205. package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
  206. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
  207. package/src/components/CnReportsPage/CnReportsPage.vue +179 -1
  208. package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
  209. package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
  210. package/src/components/CnStatWidget/CnStatWidget.vue +137 -12
  211. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +34 -3
  212. package/src/components/CnTabsWidget/CnTabsWidget.vue +39 -6
  213. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
  214. package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
  215. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
  216. package/src/components/CnWalkthrough/CnWalkthrough.vue +16 -0
  217. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
  218. package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
  219. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
  220. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
  221. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
  222. package/src/composables/useListView.js +21 -0
  223. package/src/css/board.css +65 -4
  224. package/src/css/dialog.css +4 -2
  225. package/src/css/form-field.css +14 -0
  226. package/src/css/form-widget.css +11 -0
  227. package/src/css/index-page.css +49 -0
  228. package/src/css/kpi-card.css +13 -0
  229. package/src/css/look-board-reports.css +58 -0
  230. package/src/css/look-board.css +108 -7
  231. package/src/dialogs/CnTransitionInputDialog.vue +87 -7
  232. package/src/index.js +2 -0
  233. package/src/schemas/app-manifest-v2.schema.json +137 -6
  234. package/src/store/useObjectStore.js +16 -2
  235. package/src/utils/boardColumns.js +8 -1
  236. package/src/utils/boardReferenceColumns.js +274 -0
  237. package/src/utils/buildManifest.js +40 -3
  238. package/src/utils/detailWidgetVisibility.js +145 -0
  239. package/src/utils/formatMetric.js +38 -2
  240. package/src/utils/gridStack.js +39 -0
  241. package/src/utils/humaniseKey.js +38 -0
  242. package/src/utils/notificationRuleLabel.js +5 -23
  243. package/src/utils/resolveManifestTokens.js +1 -0
  244. package/src/utils/sentinelTokens.js +9 -0
  245. package/src/utils/validateManifestV2.compiled.js +3 -3
  246. package/src/utils/widgetDispatch.js +8 -2
@@ -28,21 +28,21 @@
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"
40
40
  :gs-x="item.gridX"
41
- :gs-y="item.gridY"
41
+ :gs-y="scaledRow(item.gridY)"
42
42
  :gs-w="item.gridWidth"
43
- :gs-h="item.gridHeight"
43
+ :gs-h="scaledRow(item.gridHeight)"
44
44
  :gs-min-w="minWidth"
45
- :gs-min-h="minHeight"
45
+ :gs-min-h="minRows(item)"
46
46
  :gs-size-to-content="sizeToContentAttr(item)"
47
47
  @keydown="onItemKeydown($event, item)">
48
48
  <div class="grid-stack-item-content">
@@ -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
@@ -99,6 +100,24 @@ let domIdCounter = 0
99
100
  */
100
101
  const MENU_ANCHOR_OFFSET = 8
101
102
 
103
+ /**
104
+ * How many GridStack rows a layout row is drawn as on a grid with
105
+ * size-to-content cells (`wantsRowSplit`): 80px rows become 10px rows.
106
+ */
107
+ const ROW_SPLIT = 8
108
+
109
+ /**
110
+ * Whether a layout holds a size-to-content cell (`sizeToContent` true or a
111
+ * positive row maximum).
112
+ *
113
+ * @param {Array<object>} layout The layout items.
114
+ * @return {boolean} True when one does.
115
+ */
116
+ function hasFitCell(layout) {
117
+ return Array.isArray(layout) && layout.some((item) => item
118
+ && (item.sizeToContent === true || (Number.isFinite(item.sizeToContent) && item.sizeToContent > 0)))
119
+ }
120
+
102
121
  /**
103
122
  * CnDashboardGrid — Low-level grid layout engine powered by GridStack.
104
123
  *
@@ -304,10 +323,62 @@ export default {
304
323
  * on that.
305
324
  */
306
325
  reflowed: false,
326
+ /**
327
+ * How many GridStack rows one layout row is drawn as: 1, or
328
+ * ROW_SPLIT while `wantsRowSplit` holds. Set before the first render,
329
+ * since GridStack reads the items' places from their attributes,
330
+ * and again when the grid is rebuilt.
331
+ */
332
+ rowSplit: (!this.editable && !this.float && hasFitCell(this.layout)) ? ROW_SPLIT : 1,
307
333
  }
308
334
  },
309
335
 
310
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
+
361
+ /**
362
+ * Whether the grid draws each layout row as ROW_SPLIT finer rows. A
363
+ * size-to-content cell can only be a whole number of rows tall, so on
364
+ * 80px rows it kept up to a row of empty space under its content and
365
+ * a packing grid (`float: false`) could not close it (pipelinq and
366
+ * dossiq Mijn werk, decidiq Mijn acties). Split into 10px rows the
367
+ * cell ends within 10px of its content and the widgets below move up.
368
+ *
369
+ * Only in view mode, only on a packing grid with a size-to-content
370
+ * cell: every other grid is built exactly as before. Item positions
371
+ * and sizes scale with the rows, so every widget keeps its pixel
372
+ * place and size until a cell shrinks; the layout itself, what edit
373
+ * mode shows and what `layout-change` reports, stays in layout rows.
374
+ *
375
+ * @spec openspec/changes/dashboard-fit-cells-close-up/specs/dashboard-grid/spec.md#requirement-a-size-to-content-cell-ends-at-its-content
376
+ * @return {boolean} True to split the rows.
377
+ */
378
+ wantsRowSplit() {
379
+ return !this.editable && !this.float && hasFitCell(this.layout)
380
+ },
381
+
311
382
  /**
312
383
  * @return {boolean} Whether the keyboard lane is live — items are
313
384
  * focusable and the arrow keys reposition. Requires the opt-in prop,
@@ -374,6 +445,12 @@ export default {
374
445
  this.syncInteractivity()
375
446
  },
376
447
 
448
+ wantsRowSplit(wants) {
449
+ if (this.grid && (wants ? ROW_SPLIT : 1) !== this.rowSplit) {
450
+ this.rebuildGrid()
451
+ }
452
+ },
453
+
377
454
  layout: {
378
455
  deep: true,
379
456
  handler(newLayout) {
@@ -433,11 +510,75 @@ export default {
433
510
  }
434
511
  // A number is GridStack's "size to content, but never taller than N rows" (a maximum).
435
512
  if (Number.isFinite(v) && v > 0) {
436
- return String(v)
513
+ return String(v * this.rowSplit)
437
514
  }
438
515
  return undefined
439
516
  },
440
517
 
518
+ /**
519
+ * A layout row coordinate or height in GridStack rows (`rowSplit`).
520
+ *
521
+ * @spec openspec/changes/dashboard-fit-cells-close-up/specs/dashboard-grid/spec.md#requirement-a-size-to-content-cell-ends-at-its-content
522
+ * @param {number|undefined} value The value in layout rows.
523
+ * @return {number|undefined} The value in grid rows, or undefined when none is set.
524
+ */
525
+ scaledRow(value) {
526
+ return Number.isFinite(value) ? value * this.rowSplit : undefined
527
+ },
528
+
529
+ /**
530
+ * The smallest height an item may take, in grid rows: `minHeight`
531
+ * layout rows, except for a size-to-content cell on split rows, which
532
+ * may shrink to its content down to one grid row. The minimum is for
533
+ * resizing by hand, and that only happens on whole rows in edit mode.
534
+ *
535
+ * @spec openspec/changes/dashboard-fit-cells-close-up/specs/dashboard-grid/spec.md#requirement-a-size-to-content-cell-ends-at-its-content
536
+ * @param {object} item The layout item.
537
+ * @return {number} The minimum height in grid rows.
538
+ */
539
+ minRows(item) {
540
+ if (this.rowSplit !== 1 && this.sizeToContentAttr(item) !== undefined) {
541
+ return 1
542
+ }
543
+ return this.minHeight * this.rowSplit
544
+ },
545
+
546
+ /**
547
+ * Build the grid again with the row split `wantsRowSplit` asks for, as
548
+ * edit mode turns on or off: the items go back to their layout places in
549
+ * the new rows, and the size-to-content cells are measured again.
550
+ *
551
+ * @spec openspec/changes/dashboard-fit-cells-close-up/specs/dashboard-grid/spec.md#requirement-a-size-to-content-cell-ends-at-its-content
552
+ * @return {void}
553
+ */
554
+ rebuildGrid() {
555
+ this.disconnectContentObserver()
556
+ this.grid.destroy(false)
557
+ this.grid = null
558
+ this.rowSplit = this.wantsRowSplit ? ROW_SPLIT : 1
559
+ this.$nextTick(() => {
560
+ if (!this.$refs.gridContainer) {
561
+ return
562
+ }
563
+ // GridStack wrote its own places onto the items; put the layout's back.
564
+ for (const item of this.layout) {
565
+ const el = this.$refs.gridContainer.querySelector(`[gs-id="${item.id}"]`)
566
+ if (!el) {
567
+ continue
568
+ }
569
+ for (const [name, value] of [['gs-y', this.scaledRow(item.gridY)], ['gs-h', this.scaledRow(item.gridHeight)], ['gs-min-h', this.minRows(item)], ['gs-size-to-content', this.sizeToContentAttr(item)]]) {
570
+ if (value === undefined) {
571
+ el.removeAttribute(name)
572
+ } else {
573
+ el.setAttribute(name, String(value))
574
+ }
575
+ }
576
+ }
577
+ this.initGrid()
578
+ this.observeContentSizes()
579
+ })
580
+ },
581
+
441
582
  /**
442
583
  * Build the accessible name for a grid item. A custom `itemLabel` wins
443
584
  * outright; otherwise the first human-readable field on the item is used
@@ -492,6 +633,13 @@ export default {
492
633
  )
493
634
  },
494
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
+ */
495
643
  initGrid() {
496
644
  if (this.cellHeightCssVar && typeof document !== 'undefined' && document.documentElement) {
497
645
  document.documentElement.style.setProperty(this.cellHeightCssVar, `${this.cellHeight}px`)
@@ -499,7 +647,7 @@ export default {
499
647
  const el = this.$refs.gridContainer.querySelector('.grid-stack')
500
648
  this.grid = GridStack.init({
501
649
  column: this.columns,
502
- cellHeight: this.cellHeight,
650
+ cellHeight: this.cellHeight / this.rowSplit,
503
651
  margin: this.margin,
504
652
  float: this.float,
505
653
  animate: true,
@@ -508,6 +656,7 @@ export default {
508
656
  removable: false,
509
657
  ...(this.columnOpts ? { columnOpts: this.columnOpts } : {}),
510
658
  }, el)
659
+ this.keepOrderOnGrowth()
511
660
 
512
661
  this.grid.on('change', (_event, items) => {
513
662
  this.handleGridChange(items)
@@ -519,6 +668,87 @@ export default {
519
668
  this.syncInteractivity()
520
669
  },
521
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
+
522
752
  /**
523
753
  * Keep both editing lanes in step with whether an edit could actually be
524
754
  * kept: the pointer's handles, and — through `reflowed` feeding
@@ -586,6 +816,12 @@ export default {
586
816
  if (this.isReflowed()) {
587
817
  return
588
818
  }
819
+ // A grid on split rows is a view-mode grid: what changes in it is
820
+ // packing and size-to-content measuring, never a user's
821
+ // arrangement, and it is not reported as one.
822
+ if (this.rowSplit !== 1) {
823
+ return
824
+ }
589
825
  if (!items || items.length === 0) {
590
826
  return
591
827
  }
@@ -986,7 +1222,7 @@ export default {
986
1222
  // grid's own position back, which matches and does
987
1223
  // nothing here.
988
1224
  const x = item.gridX ?? node.x
989
- const y = item.gridY ?? node.y
1225
+ const y = Number.isFinite(item.gridY) ? item.gridY * this.rowSplit : node.y
990
1226
  if (x !== node.x || y !== node.y) {
991
1227
  // The layout said where it goes; GridStack's `change`
992
1228
  // for it is not a user's arrangement and is not
@@ -395,6 +395,7 @@
395
395
  <CnStatsBlockWidget
396
396
  v-bind="getStatsBlockProps(item)"
397
397
  :title="getWidgetTitle(item)"
398
+ :eyebrow="getWidgetEyebrow(item)"
398
399
  :data-source="getWidgetDataSource(item)" />
399
400
  </CnWidgetWrapper>
400
401
  <CnWidgetWrapper
@@ -420,7 +421,7 @@
420
421
  :is="registryRenderer(item)"
421
422
  :widgetId="item.widgetId"
422
423
  :content="registryWidgetBindings(item)"
423
- v-bind="registryWidgetBindings(item)"
424
+ v-bind="{ ...registryWidgetBindings(item), ...containerBindings(item) }"
424
425
  @header="onWidgetHeader(item, $event)" />
425
426
  </CnWidgetWrapper>
426
427
  <CnWidgetWrapper
@@ -433,6 +434,7 @@
433
434
  <CnWidgetWrapper
434
435
  v-else
435
436
  :title="getWidgetTitle(item)"
437
+ :eyebrow="getWidgetEyebrow(item)"
436
438
  :showTitle="false"
437
439
  :showRefresh="false">
438
440
  <div class="cn-dashboard-page__unknown">
@@ -494,6 +496,7 @@
494
496
  <CnWidgetWrapper
495
497
  v-if="missingRequiredApp(item)"
496
498
  :title="getWidgetTitle(item)"
499
+ :eyebrow="getWidgetEyebrow(item)"
497
500
  :showTitle="widgetShowTitle(item)">
498
501
  <!-- Compact on purpose: a two-row tile is about 150px, less
499
502
  a title row. NcEmptyContent's 64px icon, 20px name,
@@ -532,6 +535,7 @@
532
535
  <CnWidgetWrapper
533
536
  :widgetId="item.widgetId"
534
537
  :title="getWidgetTitle(item)"
538
+ :eyebrow="getWidgetEyebrow(item)"
535
539
  :iconUrl="getWidgetIconUrl(item)"
536
540
  :iconClass="getWidgetIconClass(item)"
537
541
  :showTitle="widgetShowTitle(item)"
@@ -613,6 +617,7 @@
613
617
  :widgetId="item.widgetId"
614
618
  :class="{ 'cn-dashboard-page__chart-fit': isChartFitted(item) }"
615
619
  :title="getWidgetTitle(item)"
620
+ :eyebrow="getWidgetEyebrow(item)"
616
621
  :iconUrl="getWidgetIconUrl(item)"
617
622
  :iconClass="getWidgetIconClass(item)"
618
623
  :showTitle="widgetShowTitle(item)"
@@ -691,6 +696,7 @@
691
696
  <CnWidgetWrapper
692
697
  :widgetId="item.widgetId"
693
698
  :title="getWidgetTitle(item)"
699
+ :eyebrow="getWidgetEyebrow(item)"
694
700
  :iconUrl="getWidgetIconUrl(item)"
695
701
  :iconClass="getWidgetIconClass(item)"
696
702
  :showTitle="widgetShowTitle(item)"
@@ -718,6 +724,7 @@
718
724
  <CnWidgetWrapper
719
725
  :widgetId="item.widgetId"
720
726
  :title="getWidgetTitle(item)"
727
+ :eyebrow="getWidgetEyebrow(item)"
721
728
  :iconUrl="getWidgetIconUrl(item)"
722
729
  :iconClass="getWidgetIconClass(item)"
723
730
  :showTitle="widgetShowTitle(item)"
@@ -757,6 +764,7 @@
757
764
  <CnWidgetWrapper
758
765
  :widgetId="item.widgetId"
759
766
  :title="getWidgetTitle(item)"
767
+ :eyebrow="getWidgetEyebrow(item)"
760
768
  :iconUrl="getWidgetIconUrl(item)"
761
769
  :iconClass="getWidgetIconClass(item)"
762
770
  :showTitle="widgetShowTitle(item)"
@@ -782,6 +790,7 @@
782
790
  <CnWidgetWrapper
783
791
  :widgetId="item.widgetId"
784
792
  :title="getWidgetTitle(item)"
793
+ :eyebrow="getWidgetEyebrow(item)"
785
794
  :showTitle="widgetShowTitle(item)"
786
795
  :showActions="widgetShowActions(item)"
787
796
  :showRefresh="getWidgetShowRefresh(item)"
@@ -873,7 +882,7 @@
873
882
  :is="registryRenderer(item)"
874
883
  :widgetId="item.widgetId"
875
884
  :content="registryWidgetBindings(item)"
876
- v-bind="registryWidgetBindings(item)"
885
+ v-bind="{ ...registryWidgetBindings(item), ...containerBindings(item) }"
877
886
  @header="onWidgetHeader(item, $event)" />
878
887
  </CnWidgetWrapper>
879
888
  </template>
@@ -882,6 +891,7 @@
882
891
  <CnWidgetWrapper
883
892
  v-else
884
893
  :title="getWidgetTitle(item)"
894
+ :eyebrow="getWidgetEyebrow(item)"
885
895
  :showTitle="widgetShowTitle(item)"
886
896
  :showRefresh="false">
887
897
  <div class="cn-dashboard-page__unknown">
@@ -2944,6 +2954,7 @@ export default {
2944
2954
  return {
2945
2955
  widgetId: item.widgetId,
2946
2956
  title: this.getWidgetTitle(item),
2957
+ eyebrow: this.getWidgetEyebrow(item),
2947
2958
  showTitle: this.widgetShowTitle(item),
2948
2959
  showActions: this.widgetShowActions(item),
2949
2960
  showRefresh: this.getWidgetShowRefresh(item),
@@ -3630,6 +3641,35 @@ export default {
3630
3641
  return outcome ? { ...content, conditionOutcome: outcome } : content
3631
3642
  },
3632
3643
 
3644
+ /**
3645
+ * What a container widget (registry `container`, such as `tabs`) needs
3646
+ * beside its content: the page's widget definitions, so its tabs find
3647
+ * the widgets they name, and the consumer registry for custom child
3648
+ * types. Empty for every other widget, so a leaf gets no extra
3649
+ * attributes on its root.
3650
+ *
3651
+ * @param {object} item Layout item.
3652
+ * @return {object} The extra bindings.
3653
+ * @spec openspec/changes/dashboard-tabs-children/specs/dashboard-page/spec.md#requirement-a-tabs-widget-on-a-dashboard-renders-the-widgets-its-tabs-name
3654
+ */
3655
+ containerBindings(item) {
3656
+ const def = this.getWidgetDef(item.widgetId)
3657
+ if (!def || !def.type) {
3658
+ return {}
3659
+ }
3660
+ const entry = getWidgetTypeEntry(def.type) || getWidgetTypeEntry(canonicalWidgetType(def.type))
3661
+ if (!entry || entry.container !== true) {
3662
+ return {}
3663
+ }
3664
+ const own = Array.isArray(this.widgets) ? this.widgets.filter(Boolean) : []
3665
+ const fromView = this.activeView && Array.isArray(this.activeView.widgets) ? this.activeView.widgets.filter(Boolean) : []
3666
+ return {
3667
+ availableWidgets: [...own, ...fromView],
3668
+ cnRegistry: this.cnRegistry || {},
3669
+ surface: this.surface,
3670
+ }
3671
+ },
3672
+
3633
3673
  /**
3634
3674
  * A widget's content with the board's quiet collection footer: in the
3635
3675
  * board look a dashboard that drops the widget Actions menu
@@ -4002,6 +4042,21 @@ export default {
4002
4042
  this.showWidgetConfig = false
4003
4043
  },
4004
4044
 
4045
+ /**
4046
+ * The line above a widget's title (`eyebrow` on the widget
4047
+ * definition: "Saldo per week" above "Prognose 13 weken"), through the
4048
+ * host translate, or '' for none.
4049
+ *
4050
+ * @param {object} item Layout item.
4051
+ * @return {string}
4052
+ * @spec openspec/changes/widget-title-eyebrow/specs/dashboard-page/spec.md#requirement-a-widget-card-can-carry-a-line-above-its-title
4053
+ */
4054
+ getWidgetEyebrow(item) {
4055
+ const def = this.getWidgetDef(item.widgetId)
4056
+ const eyebrow = def && typeof def.eyebrow === 'string' ? def.eyebrow : ''
4057
+ return eyebrow ? this.effectiveTranslate(eyebrow) : ''
4058
+ },
4059
+
4005
4060
  /**
4006
4061
  * The widget's title as AUTHORED — the untranslated manifest source
4007
4062
  * string — resolved by widget id rather than by item, so a descendant
@@ -4587,6 +4642,11 @@ export default {
4587
4642
  if (columns !== undefined) {
4588
4643
  out.columns = Number(columns)
4589
4644
  }
4645
+ // `tiles: "box"`: each grid tile on a surface of its own.
4646
+ const tiles = content.tiles !== undefined ? content.tiles : props.tiles
4647
+ if (typeof tiles === 'string') {
4648
+ out.tiles = tiles
4649
+ }
4590
4650
  const contentEntries = content.entries !== undefined ? content.entries : props.entries
4591
4651
  if (contentEntries !== undefined) {
4592
4652
  out.entries = contentEntries
@@ -511,7 +511,7 @@ export default {
511
511
  * @return {string} The formatted string.
512
512
  */
513
513
  formatNumber(value) {
514
- return formatMetricValue(value, this.resolvedFormat, unwrapAppConfig(this.cnAppConfig))
514
+ return formatMetricValue(value, this.resolvedFormat, unwrapAppConfig(this.cnAppConfig), null, this.effectiveTranslate)
515
515
  },
516
516
 
517
517
  /**
@@ -446,6 +446,8 @@ When the schema-driven fetch answers 404 the page shows a not-found state whose
446
446
 
447
447
  `headerCard` (default `false`) draws the header as a bordered card. `headerWidget` (default `''`) names a widget in `widgets` to render inside the header under the title, without a card of its own and out of the body grid: the stages bars of the board's case card.
448
448
 
449
+ `visibilityContext` (default `{}`) names records the page reads beside the loaded one, `{ <name>: { field, register, schema } }`. A grid widget's `visibleWhen` is read against the record, and against those records as `@context.<name>.<path>`, so a case page can show a widget for some case types only (`{ "field": "@context.caseType.category", "op": "in", "value": ["Sociaal domein"] }`). A widget without `visibleWhen` shows as before; a conditional one stays out of the grid until the record and the named records have loaded.
450
+
449
451
  `showWidgetActions` (Boolean, default `true`): `false` (manifest `config.showWidgetActions: false`) drops the overflow Actions menu from the cards of the body grid and the side column, unless a widget definition sets `showActions: true`.
450
452
 
451
453
  ### Favourite and follow
@@ -458,4 +460,4 @@ When the schema-driven fetch answers 404 the page shows a not-found state whose
458
460
 
459
461
  ### The board look
460
462
 
461
- `look` (String, default `''`): `nextcloud` or `board`; empty takes the app's look. `headerMeta` (String): the meta line on row 2 of the board header, a field template such as `via {channel}` (manifest `config.headerMeta`). `tabsLabel` (String): the accessible name of the tab list (manifest `config.tabsLabel`). `identifierField` (String): the field named in the last line of the History side card (manifest `config.identifierField`). See `docs/components/cn-detail-page.md`.
463
+ `look` (String, default `''`): `nextcloud` or `board`; empty takes the app's look. `headerMeta` (String): the meta line on row 2 of the board header, a field template such as `via {channel}` (manifest `config.headerMeta`). `tabsLabel` (String): the accessible name of the tab list (manifest `config.tabsLabel`). `identifierField` (String): the field named in the last line of the History side card (manifest `config.identifierField`). `titleField` (String): the record field the header names the record by, such as an invoice number (manifest `config.titleField`); an empty value falls back to the display name. See `docs/components/cn-detail-page.md`.