@conduction/nextcloud-vue 2.68.0 → 2.70.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 (137) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +25 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +155 -152
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +77 -0
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +8 -8
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  9. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -0
  10. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js.map +1 -1
  11. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +28 -0
  12. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  13. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +4 -2
  14. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  15. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +25 -1
  16. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
  17. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js +32 -17
  18. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js +14 -1
  20. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue3.js.map +1 -1
  22. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +43 -2
  23. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  24. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
  25. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +45 -35
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +19 -1
  29. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  30. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +35 -1
  31. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  32. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +94 -6
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +64 -19
  36. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +28 -0
  38. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +3 -2
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +12 -0
  42. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +9 -0
  44. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  45. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  46. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +54 -7
  47. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +26 -8
  49. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +143 -0
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  52. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +68 -25
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  54. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -1
  55. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -1
  56. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +4 -1
  57. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +17 -3
  60. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
  61. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +25 -24
  62. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
  63. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  64. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +29 -2
  66. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  67. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +7 -0
  68. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  69. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +24 -1
  70. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
  71. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +1 -1
  72. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
  73. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +81 -1
  74. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  75. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +25 -3
  76. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  77. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +15 -2
  78. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  79. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +31 -32
  80. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  81. package/dist/esm/l10n/en.json.js +3 -0
  82. package/dist/esm/l10n/en.json.js.map +1 -1
  83. package/dist/esm/l10n/nl.json.js +3 -0
  84. package/dist/esm/l10n/nl.json.js.map +1 -1
  85. package/dist/esm/mixins/pageViews.js +278 -11
  86. package/dist/esm/mixins/pageViews.js.map +1 -1
  87. package/dist/esm/nextcloud-vue.css +145 -0
  88. package/dist/esm/store/plugins/dashboardLayouts.js +35 -1
  89. package/dist/esm/store/plugins/dashboardLayouts.js.map +1 -1
  90. package/dist/esm/utils/capabilityComparison.js +29 -1
  91. package/dist/esm/utils/capabilityComparison.js.map +1 -1
  92. package/dist/esm/utils/formatMetric.js +137 -16
  93. package/dist/esm/utils/formatMetric.js.map +1 -1
  94. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  95. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  96. package/dist/nextcloud-vue.cjs.js +2840 -1477
  97. package/dist/nextcloud-vue.cjs.js.map +2 -2
  98. package/dist/nextcloud-vue.css +145 -0
  99. package/l10n/en.json +3 -0
  100. package/l10n/nl.json +3 -0
  101. package/package.json +1 -1
  102. package/src/components/CnActionsBar/CnActionsBar.md +2 -0
  103. package/src/components/CnActionsBar/CnActionsBar.vue +28 -2
  104. package/src/components/CnAppNav/CnAppNav.vue +85 -8
  105. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +45 -0
  106. package/src/components/CnCalendarWidget/CnCalendarWidget.vue +32 -4
  107. package/src/components/CnCapabilityTable/CnCapabilityTable.vue +14 -0
  108. package/src/components/CnDashboardGrid/CnDashboardGrid.md +2 -0
  109. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +54 -2
  110. package/src/components/CnDashboardPage/CnDashboardPage.md +2 -0
  111. package/src/components/CnDashboardPage/CnDashboardPage.vue +59 -35
  112. package/src/components/CnDataTable/CnDataTable.vue +35 -1
  113. package/src/components/CnDetailPage/CnDetailPage.md +8 -0
  114. package/src/components/CnDetailPage/CnDetailPage.vue +124 -8
  115. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +30 -1
  116. package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +12 -0
  117. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +9 -0
  118. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +95 -8
  119. package/src/components/CnIndexPage/CnIndexPage.vue +171 -6
  120. package/src/components/CnNextStepCard/CnNextStepCard.vue +11 -0
  121. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +4 -1
  122. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +34 -7
  123. package/src/components/CnStagesWidget/CnStagesWidget.vue +19 -0
  124. package/src/components/CnStatWidget/CnStatWidget.vue +38 -2
  125. package/src/components/CnStatsBlock/CnStatsBlock.md +2 -0
  126. package/src/components/CnStatsBlock/CnStatsBlock.vue +25 -2
  127. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +95 -3
  128. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +24 -3
  129. package/src/css/detail-page.css +10 -0
  130. package/src/css/index-page.css +12 -0
  131. package/src/css/kpi-card.css +15 -0
  132. package/src/mixins/pageViews.js +277 -10
  133. package/src/schemas/app-manifest-v2.schema.json +73 -6
  134. package/src/store/plugins/dashboardLayouts.js +34 -0
  135. package/src/utils/capabilityComparison.js +28 -0
  136. package/src/utils/formatMetric.js +143 -15
  137. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -3,8 +3,16 @@
3
3
  * SPDX-License-Identifier: EUPL-1.2
4
4
  */
5
5
 
6
+ import { getCanonicalLocale } from '@nextcloud/l10n'
6
7
  import { resolveFilterValue } from './resolveFilterTokens.js'
7
8
 
9
+ /**
10
+ * The format styles a stat or stats-block entry may name.
11
+ *
12
+ * @type {Array<string>}
13
+ */
14
+ export const METRIC_FORMAT_STYLES = ['number', 'currency', 'percent', 'duration-hours', 'decimal']
15
+
8
16
  /**
9
17
  * Unwrap a possibly-ref-wrapped `cnAppConfig` inject into a plain object.
10
18
  *
@@ -25,20 +33,25 @@ export function unwrapAppConfig(cnAppConfig) {
25
33
  * Resolve `@config.<key>` tokens in a `content.format` spec's string fields.
26
34
  *
27
35
  * Each of `currency` / `prefix` / `suffix` may be a `@config.<key>` token (e.g.
28
- * `currency: '@config.currency'`). Tokens are resolved against `configCtx`; a
29
- * token that stays unresolved (the config key is unset, or the config map has
30
- * not been injected yet during a route transition) is DROPPED so the downstream
31
- * literal default applies instead of a raw `@config.…` string leaking into
32
- * `Intl.NumberFormat`.
36
+ * `currency: '@config.currency'`), or an `@object.<field>` token read off the
37
+ * detail page's object when `object` is given. Tokens are resolved against
38
+ * `configCtx`; a token that stays unresolved (the config key is unset, or the
39
+ * config map has not been injected yet during a route transition) is DROPPED
40
+ * so the downstream literal default applies instead of a raw `@config.…`
41
+ * string leaking into `Intl.NumberFormat`.
33
42
  *
34
43
  * @param {object} format The raw `content.format` spec.
35
44
  * @param {object} configCtx The page-level app-config map (from `unwrapAppConfig`).
45
+ * @param {object|null} [object] The detail page's object, for `@object.<field>` tokens.
36
46
  * @return {object} A copy of `format` with its string tokens resolved/dropped.
37
47
  */
38
- export function resolveConfigFormat(format, configCtx) {
48
+ export function resolveConfigFormat(format, configCtx, object = null) {
39
49
  const fmt = format || {}
40
50
  const out = { ...fmt }
41
- const ctx = { config: (configCtx && typeof configCtx === 'object') ? configCtx : {} }
51
+ const ctx = {
52
+ config: (configCtx && typeof configCtx === 'object') ? configCtx : {},
53
+ object: (object && typeof object === 'object') ? object : null,
54
+ }
42
55
  for (const key of ['currency', 'prefix', 'suffix']) {
43
56
  const raw = fmt[key]
44
57
  if (typeof raw !== 'string' || raw.charAt(0) !== '@') {
@@ -68,6 +81,114 @@ export function safeCurrencyCode(currency) {
68
81
  : 'EUR'
69
82
  }
70
83
 
84
+ /**
85
+ * Whether a value is a three-letter currency code.
86
+ *
87
+ * @param {unknown} value The candidate.
88
+ * @return {boolean}
89
+ */
90
+ function isCurrencyCode(value) {
91
+ return typeof value === 'string' && /^[A-Za-z]{3}$/.test(value)
92
+ }
93
+
94
+ /**
95
+ * Read a dot-path off an object.
96
+ *
97
+ * @param {object} object The object.
98
+ * @param {string} path The path, e.g. `currency` or `price.currency`.
99
+ * @return {unknown} The value, or undefined.
100
+ */
101
+ function readPath(object, path) {
102
+ return String(path).split('.').reduce((acc, key) => (acc && typeof acc === 'object' ? acc[key] : undefined), object)
103
+ }
104
+
105
+ /**
106
+ * The currency a money value is shown in. In order:
107
+ *
108
+ * 1. `currencyField`: the field of the page's object that holds the currency
109
+ * (a detail page; e.g. a contract's own `currency`).
110
+ * 2. `currency`: a literal code, or a token (`@config.currency`,
111
+ * `@object.currency`).
112
+ * 3. The app's reporting currency, `currency` in the page's app config.
113
+ * 4. `EUR`.
114
+ *
115
+ * A step whose value is not a three-letter code is skipped, so an object
116
+ * without a currency falls back to the reporting currency instead of
117
+ * throwing in `Intl.NumberFormat`.
118
+ *
119
+ * @param {object} format The format spec (`{ currency?, currencyField? }`).
120
+ * @param {object} configCtx The page-level app-config map.
121
+ * @param {object|null} [object] The detail page's object.
122
+ * @return {string} An upper-case three-letter code.
123
+ * @spec openspec/changes/stat-currency-from-object/specs/dashboard-page/spec.md#requirement-a-money-value-is-shown-in-the-currency-the-entry-names
124
+ */
125
+ export function resolveFormatCurrency(format, configCtx, object = null) {
126
+ const fmt = format || {}
127
+ if (typeof fmt.currencyField === 'string' && fmt.currencyField !== '' && object && typeof object === 'object') {
128
+ const own = readPath(object, fmt.currencyField)
129
+ if (isCurrencyCode(own)) {
130
+ return own.toUpperCase()
131
+ }
132
+ }
133
+ const declared = resolveConfigFormat({ currency: fmt.currency }, configCtx, object).currency
134
+ if (isCurrencyCode(declared)) {
135
+ return declared.toUpperCase()
136
+ }
137
+ const reporting = configCtx && typeof configCtx === 'object' ? configCtx.currency : undefined
138
+ return safeCurrencyCode(reporting)
139
+ }
140
+
141
+ /**
142
+ * The format spec of a stat or stats-block entry in one shape.
143
+ *
144
+ * `format` may be a style name (`"currency"`) or an object (`{ style,
145
+ * currency, currencyField, decimals, prefix, suffix }`). A sibling `currency`
146
+ * or `currencyField` on the entry itself fills the same keys, and either one
147
+ * makes the style `currency` when none is named. Answers null when the entry
148
+ * declares no format at all, so the caller keeps its own rendering.
149
+ *
150
+ * @param {object} entry The declaration (`{ format?, currency?, currencyField? }`).
151
+ * @return {object|null} The format spec, or null.
152
+ * @spec openspec/changes/stat-currency-from-object/specs/dashboard-page/spec.md#requirement-a-stats-block-entry-formats-its-value
153
+ */
154
+ export function normalizeMetricFormat(entry) {
155
+ const source = entry && typeof entry === 'object' ? entry : {}
156
+ let fmt = null
157
+ if (typeof source.format === 'string' && source.format !== '') {
158
+ fmt = { style: source.format }
159
+ } else if (source.format && typeof source.format === 'object' && !Array.isArray(source.format)) {
160
+ fmt = { ...source.format }
161
+ }
162
+ for (const key of ['currency', 'currencyField']) {
163
+ if (typeof source[key] === 'string' && source[key] !== '') {
164
+ fmt = fmt || {}
165
+ if (fmt[key] === undefined) {
166
+ fmt[key] = source[key]
167
+ }
168
+ }
169
+ }
170
+ if (fmt && !fmt.style && (fmt.currency || fmt.currencyField)) {
171
+ fmt.style = 'currency'
172
+ }
173
+ return fmt
174
+ }
175
+
176
+ /**
177
+ * The user's locale for number formatting: the Nextcloud locale setting,
178
+ * else the browser's.
179
+ *
180
+ * @return {string|undefined} A BCP 47 tag, or undefined for the runtime default.
181
+ */
182
+ export function metricLocale() {
183
+ try {
184
+ const tag = getCanonicalLocale()
185
+ // A tag Intl does not accept would throw a RangeError on every value.
186
+ return tag && Intl.NumberFormat.supportedLocalesOf([tag]).length > 0 ? tag : undefined
187
+ } catch {
188
+ return undefined
189
+ }
190
+ }
191
+
71
192
  /**
72
193
  * Format a numeric value per a manifest `content.format` spec.
73
194
  *
@@ -85,12 +206,18 @@ export function safeCurrencyCode(currency) {
85
206
  * - `decimal` is a plain number with ONE fraction digit by default (the
86
207
  * fleet KPIs' `toFixed(1)` convention), where `number` defaults to 0.
87
208
  *
209
+ * The currency follows `resolveFormatCurrency`: the object's own currency
210
+ * (`currencyField`), then `currency`, then the app's reporting currency, then
211
+ * EUR. Numbers are formatted in the user's locale (`metricLocale`).
212
+ *
88
213
  * @param {unknown} value The raw value to format.
89
- * @param {object} format The `content.format` spec (`{ style, currency, decimals, prefix, suffix }`).
214
+ * @param {object} format The `content.format` spec (`{ style, currency, currencyField, decimals, prefix, suffix }`).
90
215
  * @param {object} configCtx The page-level app-config map for `@config.<key>` resolution.
216
+ * @param {object|null} [object] The detail page's object, for `currencyField` and `@object.<field>`.
91
217
  * @return {string} The formatted display string.
218
+ * @spec openspec/changes/stat-currency-from-object/specs/dashboard-page/spec.md#requirement-a-money-value-is-shown-in-the-currency-the-entry-names
92
219
  */
93
- export function formatMetricValue(value, format, configCtx) {
220
+ export function formatMetricValue(value, format, configCtx, object = null) {
94
221
  if (value === null || value === undefined) {
95
222
  return '—'
96
223
  }
@@ -99,7 +226,8 @@ export function formatMetricValue(value, format, configCtx) {
99
226
  return String(value)
100
227
  }
101
228
 
102
- const fmt = resolveConfigFormat(format, configCtx)
229
+ const fmt = resolveConfigFormat(format, configCtx, object)
230
+ const locale = metricLocale()
103
231
  // `duration-hours` and `decimal` default to ONE fraction digit; the
104
232
  // other styles keep the pre-existing default of 0.
105
233
  const oneDigitDefault = fmt.style === 'duration-hours' || fmt.style === 'decimal'
@@ -107,26 +235,26 @@ export function formatMetricValue(value, format, configCtx) {
107
235
 
108
236
  let body
109
237
  if (fmt.style === 'currency') {
110
- body = new Intl.NumberFormat(undefined, {
238
+ body = new Intl.NumberFormat(locale, {
111
239
  style: 'currency',
112
- currency: safeCurrencyCode(fmt.currency),
240
+ currency: resolveFormatCurrency(format, configCtx, object),
113
241
  minimumFractionDigits: decimals,
114
242
  maximumFractionDigits: decimals,
115
243
  }).format(num)
116
244
  } else if (fmt.style === 'percent') {
117
245
  // Values are stored as the literal percent (83.3), not a 0–1 ratio.
118
- body = new Intl.NumberFormat(undefined, {
246
+ body = new Intl.NumberFormat(locale, {
119
247
  minimumFractionDigits: decimals,
120
248
  maximumFractionDigits: decimals,
121
249
  }).format(num) + '%'
122
250
  } else if (fmt.style === 'duration-hours') {
123
251
  // An hour count (e.g. mean resolution time): `42.5h`.
124
- body = new Intl.NumberFormat(undefined, {
252
+ body = new Intl.NumberFormat(locale, {
125
253
  minimumFractionDigits: decimals,
126
254
  maximumFractionDigits: decimals,
127
255
  }).format(num) + 'h'
128
256
  } else {
129
- body = new Intl.NumberFormat(undefined, {
257
+ body = new Intl.NumberFormat(locale, {
130
258
  minimumFractionDigits: decimals,
131
259
  maximumFractionDigits: decimals,
132
260
  }).format(num)