@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
@@ -37,6 +37,7 @@
37
37
  }
38
38
 
39
39
  .cn-form-widget__textarea {
40
+ box-sizing: border-box;
40
41
  width: 100%;
41
42
  min-height: 80px;
42
43
  padding: 8px;
@@ -58,3 +59,13 @@
58
59
  color: var(--color-error);
59
60
  margin: 0;
60
61
  }
62
+
63
+ /* A select takes its field's width, however narrow the card. NcSelect is at
64
+ least 260px wide on its own, so in pipelinq's 234px "Nieuw contactmoment"
65
+ card at 1100 wide the channel and client selects ran 40px past the card's
66
+ edge. The client picker (CnResourceSelect) is an NcSelect too.
67
+ @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language */
68
+ .cn-form-widget .v-select.select {
69
+ min-width: 0;
70
+ width: 100%;
71
+ }
@@ -104,6 +104,19 @@
104
104
  margin-top: 0;
105
105
  }
106
106
 
107
+ /* The board view's footer: how many rows it holds of how many match, and
108
+ "Load more" while the list is not complete. */
109
+ .cn-index-page__board-load {
110
+ display: flex;
111
+ flex-wrap: wrap;
112
+ align-items: center;
113
+ justify-content: space-between;
114
+ gap: 8px 16px;
115
+ padding: 12px 16px;
116
+ color: var(--color-text-maxcontrast);
117
+ font-size: 14px;
118
+ }
119
+
107
120
  /* Table view owns both scroll axes. With the page column scrolling instead, the
108
121
  table's horizontal scrollbar sat at the bottom of the (taller-than-viewport)
109
122
  table, so it only appeared after scrolling down, and the pagination was
@@ -369,3 +382,39 @@
369
382
  outline: 2px solid var(--color-primary-element);
370
383
  outline-offset: 2px;
371
384
  }
385
+
386
+ /*
387
+ * screens-board-reference-columns: the board view's controls after the
388
+ * search (board DqWerkbord): "Case type:" and "Columns:" as a 600 label and a
389
+ * 36px select, then the hint in the secondary text colour.
390
+ */
391
+ .cn-index-page__board-control {
392
+ display: inline-flex;
393
+ align-items: center;
394
+ gap: 8px;
395
+ font-size: 14px;
396
+ font-weight: 600;
397
+ color: var(--color-main-text);
398
+ }
399
+
400
+ .cn-index-page__board-select {
401
+ height: 36px;
402
+ min-height: 36px;
403
+ margin: 0;
404
+ padding: 0 10px;
405
+ border: 1px solid var(--color-border-maxcontrast);
406
+ border-radius: 8px;
407
+ background-color: var(--color-main-background);
408
+ color: var(--color-main-text);
409
+ font: inherit;
410
+ font-weight: 400;
411
+ }
412
+
413
+ .cn-index-page__board-select--strong {
414
+ font-weight: 500;
415
+ }
416
+
417
+ .cn-index-page__board-hint {
418
+ font-size: 14px;
419
+ color: var(--color-text-maxcontrast);
420
+ }
@@ -444,6 +444,19 @@ a.cn-kpi-card--stacked:focus-visible {
444
444
  color: var(--color-main-text);
445
445
  }
446
446
 
447
+ /* A board tile whose label wraps grows into its cell instead of clipping its
448
+ own last line. dossiq's "Afgehandeld deze maand" at 1100 wide held 129px of
449
+ content in a 114px tile and lost its caption's bottom. The tile still fills
450
+ a cell it fits (`min-height: 100%`), so every tile that fits keeps its
451
+ size; one that does not takes the height of its content, and the card-fit
452
+ cell's `safe center` keeps its top in view.
453
+ @spec openspec/changes/board-stat-tile-grows/specs/stat-tile-board-look/spec.md#requirement-a-board-stat-tile-shows-all-its-lines */
454
+ .cn-kpi-card--board.cn-kpi-card--stacked {
455
+ flex: 0 0 auto;
456
+ height: auto;
457
+ min-height: 100%;
458
+ }
459
+
447
460
  .cn-kpi-card--board.cn-kpi-card--stacked .cn-kpi-card__caption-line,
448
461
  .cn-kpi-card--board.cn-kpi-card--stacked .cn-stats-block__count-label {
449
462
  font-size: 13px;
@@ -115,3 +115,61 @@
115
115
  .cn-look-board .cn-reports-page:not(.cn-look-nextcloud *) .cn-dashboard-page {
116
116
  padding: 0;
117
117
  }
118
+
119
+ /* The widget area draws no panel of its own (board DcRapportages: the
120
+ highlighted report's card sits straight on the page ground). A theme's
121
+ catch-all for anything named like a widget (nldesign's element overrides:
122
+ `[class*="widget"]:not([class*="widget__"]):not(#header *)`, an id
123
+ specificity no class rule outranks) painted `__widgets` white, hence the
124
+ `!important`. */
125
+ .cn-look-board .cn-reports-page:not(.cn-look-nextcloud *) .cn-reports-page__widgets {
126
+ background: transparent !important;
127
+ }
128
+
129
+ /* The grid insets every card by its 12px margin (CnDashboardPage's default
130
+ `gridMargin`); the page padding already places the area, so the card's
131
+ edges line up with the report cards above it. */
132
+ .cn-look-board .cn-reports-page:not(.cn-look-nextcloud *) .cn-reports-page__widgets .cn-dashboard-grid {
133
+ width: calc(100% + 24px);
134
+ margin: -12px;
135
+ }
136
+
137
+ /* A report card is a link, but its title and description read as text, as on
138
+ the board: no underline, whatever a theme does to links. */
139
+ .cn-look-board .cn-reports-page:not(.cn-look-nextcloud *) .cn-reports-page__card,
140
+ .cn-look-board .cn-reports-page:not(.cn-look-nextcloud *) .cn-reports-page__card * {
141
+ text-decoration: none !important;
142
+ }
143
+
144
+ /* The header buttons beside the title (screens-reports-header-buttons, board
145
+ DcRapportages "Downloaden"): the board's 40px button with a grey border on
146
+ the main background, the primary one in the primary fill, as on the index
147
+ header. `!important` holds them under a theme that restyles every button
148
+ (nldesign). The buildiq square keeps its own rule. */
149
+ .cn-look-board .cn-reports-page:not(.cn-look-nextcloud *) .cn-reports-page__header-buttons > .button-vue--secondary {
150
+ height: var(--cn-board-control-height) !important;
151
+ min-height: var(--cn-board-control-height) !important;
152
+ padding: 0 14px !important;
153
+ border: 1px solid var(--color-border-dark) !important;
154
+ border-radius: var(--cn-board-control-radius) !important;
155
+ background: var(--color-main-background) !important;
156
+ color: var(--color-main-text) !important;
157
+ }
158
+
159
+ .cn-look-board .cn-reports-page:not(.cn-look-nextcloud *) .cn-reports-page__header-buttons > .button-vue--secondary:hover {
160
+ background: var(--color-background-hover) !important;
161
+ }
162
+
163
+ .cn-look-board .cn-reports-page:not(.cn-look-nextcloud *) .cn-reports-page__header-buttons > .button-vue--primary {
164
+ height: var(--cn-board-control-height) !important;
165
+ min-height: var(--cn-board-control-height) !important;
166
+ padding: 0 16px !important;
167
+ border: 0 !important;
168
+ border-radius: var(--cn-board-control-radius) !important;
169
+ background: var(--color-primary-element) !important;
170
+ color: var(--color-primary-element-text) !important;
171
+ }
172
+
173
+ .cn-look-board .cn-reports-page:not(.cn-look-nextcloud *) .cn-reports-page__header-buttons > .button-vue--primary:hover {
174
+ background: var(--color-primary-element-hover) !important;
175
+ }
@@ -422,6 +422,7 @@
422
422
  `.cn-look-board.cn-admin-settings-shell` is a shell told `look: "board"`
423
423
  itself, on an admin page no CnAppRoot wraps. */
424
424
  .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > .cn-admin-settings-shell__header,
425
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > :not(.settings-section):not(.cn-credentials),
425
426
  .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):not(.cn-credentials),
426
427
  .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):not(.cn-credentials) {
427
428
  grid-column: 1 / -1;
@@ -429,6 +430,7 @@
429
430
 
430
431
  .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *),
431
432
  .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *),
433
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *),
432
434
  .cn-look-board .cn-settings-page__sections:not(.cn-look-nextcloud *) {
433
435
  display: grid;
434
436
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
@@ -440,14 +442,17 @@
440
442
  Settings.vue) takes no box of its own: its sections are grid items and
441
443
  cards, as if the shell held them directly. Only an element whose own
442
444
  children are sections; anything else keeps spanning the grid. */
445
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section),
443
446
  .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section),
444
447
  .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) {
445
448
  display: contents;
446
449
  }
447
450
 
448
- /* The card: a section of the shell (also one inside a wrapping element), or a CnSettingsCard of the page. */
451
+ /* The card: a section of the shell or of one of its tab panels (also one inside a wrapping element), or a CnSettingsCard of the page. */
452
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > .settings-section,
449
453
  .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > .settings-section,
450
454
  .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > .settings-section,
455
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section,
451
456
  .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section,
452
457
  .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section,
453
458
  .cn-look-board .cn-settings-page__sections:not(.cn-look-nextcloud *) > .cn-settings-card {
@@ -483,7 +488,7 @@
483
488
  justify-content: flex-start;
484
489
  text-align: start;
485
490
  margin: 0;
486
- font-size: 19px;
491
+ font-size: 17px;
487
492
  font-weight: 700;
488
493
  line-height: 1.3;
489
494
  }
@@ -491,18 +496,83 @@
491
496
  .cn-look-board .settings-section:not(.cn-look-nextcloud *) .settings-section__desc {
492
497
  margin: 4px 0 0;
493
498
  font-size: 14px;
494
- line-height: 1.45;
499
+ line-height: 1.5;
495
500
  color: var(--color-text-maxcontrast);
496
501
  }
497
502
 
498
- /* Section actions sit at the end of the title row. */
503
+ /* Section actions sit at the end of the title row. The row is a grid, so a
504
+ long title wraps beside the buttons instead of running under them, and in a
505
+ narrow card the buttons drop under the title and description. */
506
+ .cn-look-board .settings-section:not(.cn-look-nextcloud *):has(> .cn-settings-section__actions) {
507
+ /* `!important`: the card rules above set `display: flex` at higher specificity for a section in a wrapping element. */
508
+ display: grid !important;
509
+ grid-template-columns: minmax(0, 1fr) auto;
510
+ align-items: start;
511
+ column-gap: 16px;
512
+ container-type: inline-size;
513
+ }
514
+
515
+ .cn-look-board .settings-section:not(.cn-look-nextcloud *):has(> .cn-settings-section__actions) > * {
516
+ grid-column: 1 / -1;
517
+ }
518
+
519
+ .cn-look-board .settings-section:not(.cn-look-nextcloud *):has(> .cn-settings-section__actions) > .settings-section__name {
520
+ grid-row: 1;
521
+ grid-column: 1;
522
+ }
523
+
499
524
  .cn-look-board .settings-section:not(.cn-look-nextcloud *) > .cn-settings-section__actions {
500
- position: absolute;
501
- inset-block-start: 22px;
502
- inset-inline-end: 24px;
525
+ grid-row: 1;
526
+ grid-column: 2;
527
+ position: static;
528
+ flex-wrap: wrap;
529
+ justify-self: end;
503
530
  float: none;
504
531
  }
505
532
 
533
+ @container (max-width: 520px) {
534
+ .cn-look-board .settings-section:not(.cn-look-nextcloud *) > .cn-settings-section__actions {
535
+ grid-row: auto;
536
+ grid-column: 1 / -1;
537
+ justify-self: start;
538
+ justify-content: flex-start;
539
+ }
540
+ }
541
+
542
+ /* An admin settings card as the board draws it (PtBeheerinstellingen,
543
+ KqBeheerAlgemeen, HuInstellingen): 20px 22px padding, and the title and
544
+ description one group, 4px apart, spaced from the rest of the card by the
545
+ card's 14px gap. The description is a sibling of the title in that column,
546
+ so it pulls itself back up to 4px under the title. The gap spaces the
547
+ content, so a note card adds no margin of its own (NcNoteCard draws 16px
548
+ above and below). A CnSettingsPage card keeps 22px 24px. */
549
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > .settings-section,
550
+ .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > .settings-section,
551
+ .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > .settings-section,
552
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section,
553
+ .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section,
554
+ .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section {
555
+ padding: 20px 22px;
556
+ }
557
+
558
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > .settings-section > .settings-section__desc,
559
+ .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > .settings-section > .settings-section__desc,
560
+ .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > .settings-section > .settings-section__desc,
561
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section > .settings-section__desc,
562
+ .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section > .settings-section__desc,
563
+ .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section > .settings-section__desc {
564
+ margin-top: -10px;
565
+ }
566
+
567
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > .settings-section > .notecard,
568
+ .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > .settings-section > .notecard,
569
+ .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > .settings-section > .notecard,
570
+ .cn-look-board .cn-admin-settings-shell__panel:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section > .notecard,
571
+ .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section > .notecard,
572
+ .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > :not(.settings-section):has(> .settings-section) > .settings-section > .notecard {
573
+ margin-block: 0;
574
+ }
575
+
506
576
  .cn-look-board .settings-section.cn-settings-section--wide:not(.cn-look-nextcloud *),
507
577
  .cn-look-board .cn-settings-card:has(.cn-settings-section--wide):not(.cn-look-nextcloud *) {
508
578
  grid-column: 1 / -1;
@@ -616,6 +686,18 @@
616
686
  font-weight: 600;
617
687
  }
618
688
 
689
+ /* ---- Dashboard card titles --------------------------------------------- */
690
+ /* A card's title on a dashboard (and on the widgets under a reports page) is
691
+ the board's card h2: 17px weight 700 (boards PqMijnWerk, DcRapportages and
692
+ the app dashboards; `--cn-board-heading-size`, as on a detail page's cards).
693
+ The wrapper's own 14px weight 600 stays for the Nextcloud look, and the
694
+ native-dashboard chrome keeps its 20px. */
695
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--nc-dashboard) .cn-widget-wrapper__title {
696
+ font-size: var(--cn-board-heading-size, 17px);
697
+ font-weight: 700;
698
+ line-height: 1.3;
699
+ }
700
+
619
701
  /* ---- Record list on a dashboard card (PtDashboard) ---------------------- */
620
702
  /* The dashboard's card body is flush, so the rows carry their own inset: the
621
703
  board's card pads 24px at the sides and each row 14px, a soft hairline
@@ -702,3 +784,22 @@
702
784
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__chevron {
703
785
  display: none;
704
786
  }
787
+
788
+ /* ---- Week strip (DqMijnWerk "Termijnen deze week") ---------------------- */
789
+ /* The days wrap inside the card instead of scrolling sideways: a row holds
790
+ as many days of at least 120px as the card fits, and the rest go to the
791
+ next row (dossiq at 1100 wide held 680px of days in a 494px card and
792
+ scrolled; now three days and two). 120px keeps a case title's words whole;
793
+ five 88px days fitted one row but broke "Bezwaar" mid-word. A card wide
794
+ enough for the week draws it as before, one row of equal columns. The
795
+ day's head wraps its "today" badge under the label when the day is
796
+ narrow.
797
+ @spec openspec/changes/board-week-strip-fits/specs/week-strip-board-look/spec.md#requirement-a-board-week-strip-fits-its-card */
798
+ .cn-look-board .cn-week-strip:not(.cn-look-nextcloud *) .cn-week-strip__days {
799
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 120px), 1fr));
800
+ min-width: 0;
801
+ }
802
+
803
+ .cn-look-board .cn-week-strip:not(.cn-look-nextcloud *) .cn-week-strip__head {
804
+ flex-wrap: wrap;
805
+ }
@@ -14,6 +14,7 @@
14
14
  @closing="$emit('close')">
15
15
  <div
16
16
  class="cn-transition-input"
17
+ :aria-busy="busy ? 'true' : null"
17
18
  data-testid="cn-modal"
18
19
  data-testid-modal="cn-transition-input-dialog">
19
20
  <!-- A refusal keeps this dialog open with what was typed still in
@@ -29,8 +30,18 @@
29
30
  class="cn-transition-input__field"
30
31
  :class="{ 'cn-transition-input__field--refused': isRefused(field.key) }"
31
32
  :data-testid="`cn-transition-input-${field.key}`">
33
+ <!-- An input that declares its choices is picked from them. -->
34
+ <NcSelect v-if="field.widget === 'select'"
35
+ :modelValue="selectedOption(field)"
36
+ :options="field.options"
37
+ label="label"
38
+ :inputLabel="requiredLabel(field)"
39
+ :clearable="!field.required"
40
+ :data-testid="`cn-transition-input-select-${field.key}`"
41
+ @update:modelValue="setValue(field.key, $event && $event.value !== undefined ? $event.value : '')" />
42
+
32
43
  <!-- An input that points at another record is picked, never typed. -->
33
- <div v-if="field.widget === 'reference'" class="cn-transition-input__reference">
44
+ <div v-else-if="field.widget === 'reference'" class="cn-transition-input__reference">
34
45
  <label class="cn-transition-input__label">{{ requiredLabel(field) }}</label>
35
46
  <CnResourceSelect
36
47
  :register="field.referenceRegister"
@@ -128,11 +139,18 @@
128
139
  @click="$emit('close')">
129
140
  {{ t('nextcloud-vue', 'Cancel') }}
130
141
  </NcButton>
142
+ <!-- Busy while the parent's POST runs: a transition can take
143
+ seconds, and a confirm button that only greys out reads as one
144
+ that did nothing. -->
131
145
  <NcButton
132
146
  variant="primary"
133
147
  :disabled="!canConfirm"
148
+ :aria-busy="busy ? 'true' : null"
134
149
  data-testid="cn-transition-input-confirm"
135
150
  @click="onConfirm">
151
+ <template v-if="busy" #icon>
152
+ <NcLoadingIcon :size="20" data-testid="cn-transition-input-busy" />
153
+ </template>
136
154
  {{ confirmLabel }}
137
155
  </NcButton>
138
156
  </template>
@@ -141,7 +159,7 @@
141
159
 
142
160
  <script>
143
161
  import { translate as t } from '@nextcloud/l10n'
144
- import { NcButton, NcCheckboxRadioSwitch, NcTextArea, NcTextField } from '@nextcloud/vue'
162
+ import { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'
145
163
  import CnDialog from '../components/CnDialog/CnDialog.vue'
146
164
  import CnResourceSelect from '../components/CnResourceSelect/CnResourceSelect.vue'
147
165
  import { dialogBoardMixin } from '../mixins/dialogBoard.js'
@@ -164,7 +182,13 @@ import { fieldsFromSchema } from '../utils/schema.js'
164
182
  * from the property `title`, booleans render as a switch, numbers as a number
165
183
  * field, and long text (`maxLength > 255` or a `textarea`/`markdown` format)
166
184
  * as a textarea. Anything else — including a field the schema does not
167
- * declare — falls back to a plain labelled text input. Deliberately minimal:
185
+ * declare — falls back to a plain labelled text input.
186
+ *
187
+ * An input may also say how it is asked for, which matters when the choices
188
+ * depend on the record rather than on its schema: `label` names the field,
189
+ * and `options: [{ value, label }]` turns it into a picker that shows each
190
+ * `label` and sends the chosen `value`. An input that declares neither is
191
+ * rendered from the schema as before. Deliberately minimal:
168
192
  * text, textarea, number and checkbox cover transition inputs; this is not a
169
193
  * form engine (use `CnFormDialog` for full object forms).
170
194
  *
@@ -186,9 +210,11 @@ export default {
186
210
  CnResourceSelect,
187
211
  CnDialog,
188
212
  NcButton,
213
+ NcLoadingIcon,
189
214
  NcTextField,
190
215
  NcTextArea,
191
216
  NcCheckboxRadioSwitch,
217
+ NcSelect,
192
218
  },
193
219
 
194
220
  mixins: [dialogBoardMixin],
@@ -199,7 +225,11 @@ export default {
199
225
  * `x-openregister-lifecycle.transitions.<action>.inputs` (server-derived
200
226
  * or config-declared); `label` doubles as dialog title + confirm label.
201
227
  *
202
- * @type {{action?: string, label?: string, inputs?: Array<{field: string, required?: boolean}>}}
228
+ * An input may carry a `label` for its field and `options` to pick
229
+ * from, each `{ value, label }`; the picker shows the label and sends
230
+ * the value.
231
+ *
232
+ * @type {{action?: string, label?: string, inputs?: Array<{field: string, required?: boolean, label?: string, options?: Array<{value: string, label?: string}>}>}}
203
233
  */
204
234
  transition: {
205
235
  type: Object,
@@ -296,6 +326,8 @@ export default {
296
326
  required: input.required === true,
297
327
  picker: input.picker && typeof input.picker === 'object' ? input.picker : null,
298
328
  fields: Array.isArray(input.fields) ? input.fields.filter((f) => typeof f === 'string') : null,
329
+ label: typeof input.label === 'string' && input.label.trim() !== '' ? input.label.trim() : null,
330
+ options: this.declaredOptions(input.options),
299
331
  }))
300
332
  },
301
333
 
@@ -305,26 +337,40 @@ export default {
305
337
  * widget from the type/format); the widget is then clamped to the minimal
306
338
  * set this dialog renders (text / textarea / number / checkbox). Fields
307
339
  * the schema does not declare fall back to a plain text input.
340
+ *
341
+ * An input that declares `options` is a picker whatever the schema
342
+ * says, because its choices are the ones this transition offers on
343
+ * this record. A declared `label` names the field over the schema's.
308
344
  */
309
345
  fields() {
310
346
  const keys = this.inputs.map((input) => input.field)
311
347
  const resolved = fieldsFromSchema(this.schema, { include: keys, includeReadOnly: true })
312
348
  return this.inputs.map((input) => {
313
349
  const field = resolved.find((f) => f.key === input.field)
350
+ if (input.options) {
351
+ return {
352
+ key: input.field,
353
+ label: input.label || (field && field.label) || input.field,
354
+ description: (field && field.description) || '',
355
+ widget: 'select',
356
+ options: input.options,
357
+ required: input.required,
358
+ }
359
+ }
314
360
  const prop = (this.schema && this.schema.properties && this.schema.properties[input.field]) || null
315
361
  if (prop && prop.type === 'object' && prop.properties && typeof prop.properties === 'object') {
316
362
  return this.objectField(input, prop)
317
363
  }
318
364
  if (!field) {
319
- return { key: input.field, label: input.field, description: '', widget: 'text', required: input.required }
365
+ return { key: input.field, label: input.label || input.field, description: '', widget: 'text', required: input.required }
320
366
  }
321
367
  if (field.reference && field.reference.multiple !== true) {
322
368
  const register = field.reference.register || this.register
323
369
  if (register !== '') {
324
- return { ...field, widget: 'reference', referenceRegister: register, picker: input.picker, required: input.required }
370
+ return { ...field, label: input.label || field.label, widget: 'reference', referenceRegister: register, picker: input.picker, required: input.required }
325
371
  }
326
372
  }
327
- return { ...field, widget: this.clampWidget(field.widget), required: input.required }
373
+ return { ...field, label: input.label || field.label, widget: this.clampWidget(field.widget), required: input.required }
328
374
  })
329
375
  },
330
376
 
@@ -438,6 +484,40 @@ export default {
438
484
  }
439
485
  },
440
486
 
487
+ /**
488
+ * The choices an input declares, as `[{ value, label }]`, or null when
489
+ * it declares none. An option without a value is dropped, because it
490
+ * cannot be sent; one without a label shows its value.
491
+ *
492
+ * @param {unknown} options The input's declared `options`.
493
+ * @return {Array<{value: string, label: string}>|null}
494
+ */
495
+ declaredOptions(options) {
496
+ if (!Array.isArray(options)) {
497
+ return null
498
+ }
499
+ const usable = options
500
+ .filter((option) => option && typeof option === 'object'
501
+ && (typeof option.value === 'string' || typeof option.value === 'number')
502
+ && String(option.value).trim() !== '')
503
+ .map((option) => ({
504
+ value: String(option.value),
505
+ label: typeof option.label === 'string' && option.label.trim() !== '' ? option.label.trim() : String(option.value),
506
+ }))
507
+ return usable.length > 0 ? usable : null
508
+ },
509
+
510
+ /**
511
+ * The option a picker field currently holds, or null.
512
+ *
513
+ * @param {object} field The select field descriptor.
514
+ * @return {{value: string, label: string}|null}
515
+ */
516
+ selectedOption(field) {
517
+ const value = this.values[field.key]
518
+ return field.options.find((option) => option.value === String(value ?? '')) || null
519
+ },
520
+
441
521
  /**
442
522
  * Ids a reference picker must not offer: the current record, when the
443
523
  * input's `picker.excludeSelf` says so.
package/src/index.js CHANGED
@@ -542,6 +542,8 @@ export { currentTheme, useCurrentTheme } from './composables/useCurrentTheme.js'
542
542
  export { ROADMAP_LABEL_BLOCKLIST } from './utils/roadmapLabelBlocklist.js'
543
543
  // The words a person reads for a notification rule key (never the raw key).
544
544
  export { humaniseRuleKey, notificationRuleLabel } from './utils/notificationRuleLabel.js'
545
+ // An identifier-shaped key made readable (`shortDescription` → "Short description").
546
+ export { humaniseKey } from './utils/humaniseKey.js'
545
547
 
546
548
  // Shared field validators. These are UX affordances that run in the browser —
547
549
  // they do NOT replace server-side validation, which stays authoritative at the