@conduction/nextcloud-vue 2.84.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 (166) 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/CnBoardView/CnBoardView.vue.js +1 -1
  7. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +232 -4
  8. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  9. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +95 -51
  10. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  11. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
  12. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +114 -0
  13. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +2 -2
  15. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +45 -0
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +23 -4
  22. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  25. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
  26. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  28. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  29. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
  30. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  31. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
  37. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  38. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  39. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +95 -0
  40. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
  42. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  43. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  44. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +70 -2
  45. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  46. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  47. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  48. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
  49. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  50. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
  51. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  52. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  53. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
  54. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  55. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  56. package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
  57. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +134 -10
  58. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
  60. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  62. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +6 -3
  63. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  64. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
  66. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
  67. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
  68. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
  69. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  70. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
  71. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  72. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
  73. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  74. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
  75. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
  76. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  77. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  78. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
  79. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
  80. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
  81. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
  82. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  83. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -0
  84. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  85. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  86. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  87. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
  88. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  89. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
  90. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  91. package/dist/esm/composables/useListView.js +21 -0
  92. package/dist/esm/composables/useListView.js.map +1 -1
  93. package/dist/esm/index.js +1 -0
  94. package/dist/esm/index.js.map +1 -1
  95. package/dist/esm/l10n/en.json.js +23 -1
  96. package/dist/esm/l10n/en.json.js.map +1 -1
  97. package/dist/esm/l10n/nl.json.js +41 -1
  98. package/dist/esm/l10n/nl.json.js.map +1 -1
  99. package/dist/esm/library-style.js +1 -1
  100. package/dist/esm/nextcloud-vue.css +742 -397
  101. package/dist/esm/store/useObjectStore.js +16 -2
  102. package/dist/esm/store/useObjectStore.js.map +1 -1
  103. package/dist/esm/utils/boardColumns.js +8 -1
  104. package/dist/esm/utils/boardColumns.js.map +1 -1
  105. package/dist/esm/utils/boardReferenceColumns.js +278 -0
  106. package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
  107. package/dist/esm/utils/buildManifest.js +4 -1
  108. package/dist/esm/utils/buildManifest.js.map +1 -1
  109. package/dist/esm/utils/gridStack.js +40 -1
  110. package/dist/esm/utils/gridStack.js.map +1 -1
  111. package/dist/esm/utils/humaniseKey.js +41 -0
  112. package/dist/esm/utils/humaniseKey.js.map +1 -0
  113. package/dist/esm/utils/notificationRuleLabel.js +6 -23
  114. package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
  115. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  116. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  117. package/dist/esm/utils/widgetDispatch.js +8 -2
  118. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  119. package/dist/nextcloud-vue.cjs.js +15075 -12590
  120. package/dist/nextcloud-vue.cjs.js.map +2 -2
  121. package/dist/nextcloud-vue.css +742 -397
  122. package/l10n/en.json +23 -1
  123. package/l10n/nl.json +41 -1
  124. package/package.json +1 -1
  125. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
  126. package/src/components/CnBoardView/CnBoardView.vue +262 -6
  127. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +116 -2
  128. package/src/components/CnDashboardPage/CnDashboardPage.vue +57 -2
  129. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  130. package/src/components/CnDetailPage/CnDetailPage.vue +23 -4
  131. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
  132. package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
  133. package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
  134. package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
  135. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +123 -3
  136. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +70 -2
  137. package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
  138. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
  139. package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
  140. package/src/components/CnStatWidget/CnStatWidget.vue +136 -11
  141. package/src/components/CnTabsWidget/CnTabsWidget.vue +6 -3
  142. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
  143. package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
  144. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
  145. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
  146. package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
  147. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
  148. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
  149. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
  150. package/src/composables/useListView.js +21 -0
  151. package/src/css/board.css +65 -4
  152. package/src/css/dialog.css +4 -2
  153. package/src/css/index-page.css +49 -0
  154. package/src/css/kpi-card.css +13 -0
  155. package/src/css/look-board.css +96 -7
  156. package/src/index.js +2 -0
  157. package/src/schemas/app-manifest-v2.schema.json +115 -5
  158. package/src/store/useObjectStore.js +16 -2
  159. package/src/utils/boardColumns.js +8 -1
  160. package/src/utils/boardReferenceColumns.js +274 -0
  161. package/src/utils/buildManifest.js +4 -1
  162. package/src/utils/gridStack.js +39 -0
  163. package/src/utils/humaniseKey.js +38 -0
  164. package/src/utils/notificationRuleLabel.js +5 -23
  165. package/src/utils/validateManifestV2.compiled.js +3 -3
  166. package/src/utils/widgetDispatch.js +8 -2
@@ -14,7 +14,7 @@ import { resolveObjectOpType } from '../../utils/actionsDispatcher.js';
14
14
  import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js';
15
15
  import { aggFilterParam } from '../../utils/fetchAggregate.js';
16
16
  import { needsListEndpoint, fetchListTotal } from '../../utils/fetchFilterCounts.js';
17
- import { unwrapAppConfig, formatMetricValue } from '../../utils/formatMetric.js';
17
+ import { unwrapAppConfig, formatMetricValue, metricLocale } from '../../utils/formatMetric.js';
18
18
  import { dropOptionalUnresolved, resolveFilterTokens } from '../../utils/resolveFilterTokens.js';
19
19
  import { STATUS_TEXT_COLORS } from '../../utils/statusColors.js';
20
20
  import { readVisibleWhenPath, evaluateVisibleWhenLocal } from '../../utils/visibleWhen.js';
@@ -63,6 +63,9 @@ const DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
63
63
  /** Milliseconds in a day, used only between two UTC-normalised midnights. */
64
64
  const DAY_MS = 86400000;
65
65
 
66
+ /** The smallest size `fitValue` steps a number down to, in px; below it the number wraps. */
67
+ const MIN_FIT_FONT_SIZE = 14;
68
+
66
69
  /**
67
70
  * The local calendar day a value names, as a UTC midnight timestamp.
68
71
  *
@@ -357,7 +360,7 @@ var script = {
357
360
  * counted between LOCAL CALENDAR DAYS, not as elapsed milliseconds, so
358
361
  * the time of day on a deadline never moves the answer.
359
362
  *
360
- * @type {{label?: string, icon?: string, iconColor?: string, valueColor?: string, caption?: string, route?: (object|string), clickRoute?: (object|string), link?: string, format?: {style?: string, currency?: string, decimals?: number, prefix?: string, suffix?: string}, source?: {kind?: string, register?: string, schema?: string, metric?: string, field?: string, filter?: object, url?: string, path?: string, params?: object}, endpointSource?: {url: string, method?: string, params?: object, responsePath?: string}, valueField?: string, limitField?: string, limit?: number, dateRange?: {presets?: Array<{id: string, label?: string, from?: string, to?: string}>, default?: string}, previousField?: string, deltaField?: string, goodDirection?: ('up'|'down'), variant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), variantWhen?: Array<{op: string, value: unknown, variant: string, icon?: string}>, objectField?: (string|{field: string, resolve?: {register: string, schema: string, labelField?: string, variantField?: string, variantMap?: {[key: string]: string}}}), display?: ('text'|'badge'|'countdown'), countdown?: {unit?: 'days', warnAt?: number, dangerAt?: number, futureLabel?: string, todayLabel?: string, pastLabel?: string, emptyText?: string}, emptyText?: string, captionVariant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), captionVariantWhen?: Array<{op: string, value: unknown, variant: string}>, captionSource?: {register?: string, schema?: string, metric?: string, field?: string, filter?: object, via?: string, zeroText?: string}, overrides?: Array<{when: {field: string, op?: string, value?: unknown}, label?: string, variant?: string, icon?: string, caption?: string, captionVariant?: string}>}}
363
+ * @type {{label?: string, icon?: string, iconColor?: string, valueColor?: string, caption?: string, route?: (object|string), clickRoute?: (object|string), link?: string, format?: {style?: string, currency?: string, decimals?: number, prefix?: string, suffix?: string}, source?: {kind?: string, register?: string, schema?: string, metric?: string, field?: string, filter?: object, url?: string, path?: string, params?: object}, endpointSource?: {url: string, method?: string, params?: object, responsePath?: string}, valueField?: string, limitField?: string, limit?: number, dateRange?: {presets?: Array<{id: string, label?: string, from?: string, to?: string}>, default?: string}, previousField?: string, deltaField?: string, goodDirection?: ('up'|'down'), variant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), variantWhen?: Array<{op: string, value: unknown, variant: string, icon?: string}>, objectField?: (string|{field: string, resolve?: {register: string, schema: string, labelField?: string, variantField?: string, variantMap?: {[key: string]: string}}}), display?: ('text'|'badge'|'countdown'), countdown?: {unit?: 'days', warnAt?: number, dangerAt?: number, futureLabel?: string, todayLabel?: string, pastLabel?: string, emptyText?: string}, emptyText?: string, captionVariant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), captionVariantWhen?: Array<{op: string, value: unknown, variant: string}>, captionSource?: {kind?: 'ratio', register?: string, schema?: string, metric?: string, field?: string, filter?: object, via?: string, zeroText?: string, numerator?: {metric?: string, field?: string, filter?: object, via?: string}, denominator?: {metric?: string, field?: string, filter?: object, via?: string}, decimals?: number}, overrides?: Array<{when: {field: string, op?: string, value?: unknown}, label?: string, variant?: string, icon?: string, caption?: string, captionVariant?: string}>}}
361
364
  */
362
365
  content: {
363
366
  type: Object,
@@ -432,6 +435,9 @@ var script = {
432
435
 
433
436
  data() {
434
437
  return {
438
+ // The value's font size while the number would not fit its row at
439
+ // its own size; null when it fits.
440
+ fitFontSize: null,
435
441
  // Resolved display label for an `objectField` that holds a reference
436
442
  // uuid. Null until resolved, and null forever when it cannot be.
437
443
  referenceLabel: null,
@@ -449,6 +455,9 @@ var script = {
449
455
  value: null,
450
456
  // The caption's own count (`content.captionSource`), null until read.
451
457
  captionValue: null,
458
+ // The parts of a ratio caption (`captionSource.kind: "ratio"`): the
459
+ // numerator read (null: the tile's own value) and the denominator.
460
+ captionRatioParts: null,
452
461
  loading: false,
453
462
  error: '',
454
463
  }
@@ -524,8 +533,9 @@ var script = {
524
533
  // nothing to say while it is not read, and at zero the `zeroText`
525
534
  // (or nothing) instead of "0 waiting".
526
535
  const counted = this.hasCaptionSource;
527
- if (counted && !(Number(this.captionValue) > 0)) {
528
- const zero = this.captionValue !== null && this.content.captionSource.zeroText;
536
+ const count = this.captionCount;
537
+ if (counted && !(Number(count) > 0)) {
538
+ const zero = count !== null && this.content.captionSource.zeroText;
529
539
  return typeof zero === 'string' && zero !== '' ? this.effectiveTranslate(zero) : ''
530
540
  }
531
541
  const translated = this.effectiveTranslate(caption);
@@ -534,9 +544,10 @@ var script = {
534
544
  }
535
545
  const payload = this.endpointMode ? this.epData : null;
536
546
  return translated.replace(/\{([A-Za-z0-9_.]+)\}/g, (whole, path) => {
537
- // `{count}` is the caption's own count.
538
- if (path === 'count' && counted) {
539
- return String(this.captionValue)
547
+ // `{count}` is the caption's own count; `{ratio}` the same
548
+ // for a ratio caption, as a percentage.
549
+ if ((path === 'count' || (path === 'ratio' && this.isCaptionRatio)) && counted) {
550
+ return this.isCaptionRatio ? this.formatCaptionRatio(count) : String(count)
540
551
  }
541
552
  // `{value}` is the tile's own number, so a caption can read
542
553
  // "{value} due today" on a register-counted tile that has no
@@ -549,6 +560,42 @@ var script = {
549
560
  }).replace(/\s{2,}/g, ' ').trim()
550
561
  },
551
562
 
563
+ /**
564
+ * Whether the caption is a share (`captionSource.kind: "ratio"`).
565
+ *
566
+ * @return {boolean}
567
+ * @spec openspec/changes/stat-caption-ratio/specs/stat-widget/spec.md#requirement-a-stat-tiles-caption-can-show-a-share
568
+ */
569
+ isCaptionRatio() {
570
+ const own = this.content.captionSource;
571
+ return !!own && typeof own === 'object' && own.kind === 'ratio'
572
+ },
573
+
574
+ /**
575
+ * The caption's own number: its count, or for a ratio caption the
576
+ * numerator (the tile's own value when it names none) over the
577
+ * denominator, times 100. Null while unread, and for a zero or failed
578
+ * denominator.
579
+ *
580
+ * @return {number|null}
581
+ * @spec openspec/changes/stat-caption-ratio/specs/stat-widget/spec.md#requirement-a-stat-tiles-caption-can-show-a-share
582
+ */
583
+ captionCount() {
584
+ if (!this.isCaptionRatio) {
585
+ return this.captionValue
586
+ }
587
+ const parts = this.captionRatioParts;
588
+ if (!parts) {
589
+ return null
590
+ }
591
+ const num = parts.num === null ? this.displayValue : parts.num;
592
+ const den = Number(parts.den);
593
+ if (num === null || num === undefined || num === '' || !Number.isFinite(Number(num)) || !Number.isFinite(den) || den === 0) {
594
+ return null
595
+ }
596
+ return (Number(num) / den) * 100
597
+ },
598
+
552
599
  /**
553
600
  * Whether the caption has a count of its own (`content.captionSource`
554
601
  * with a register and schema, its own or the tile's).
@@ -577,7 +624,7 @@ var script = {
577
624
  return null
578
625
  }
579
626
  // A caption with its own count is coloured by that count.
580
- const current = this.hasCaptionSource ? this.captionValue : this.displayValue;
627
+ const current = this.hasCaptionSource ? this.captionCount : this.displayValue;
581
628
  if (current === null || current === undefined) {
582
629
  return null
583
630
  }
@@ -1287,7 +1334,16 @@ var script = {
1287
1334
  /** Inline style for the value text (variant rule wins over valueColor). */
1288
1335
  valueStyle() {
1289
1336
  const color = this.variantColor || this.content.valueColor;
1290
- return color ? { color } : {}
1337
+ const style = color ? { color } : {};
1338
+ if (this.fitFontSize !== null) {
1339
+ style.fontSize = `${this.fitFontSize}px`;
1340
+ // At the smallest size a number that still does not fit wraps rather than clips.
1341
+ if (this.fitFontSize <= MIN_FIT_FONT_SIZE) {
1342
+ style.whiteSpace = 'normal';
1343
+ style.overflowWrap = 'anywhere';
1344
+ }
1345
+ }
1346
+ return style
1291
1347
  },
1292
1348
 
1293
1349
  /**
@@ -1396,6 +1452,10 @@ var script = {
1396
1452
  this.fetchValue();
1397
1453
  },
1398
1454
 
1455
+ shownValue() {
1456
+ this.$nextTick(() => this.fitValue());
1457
+ },
1458
+
1399
1459
  objectFieldRaw: {
1400
1460
  immediate: true,
1401
1461
  handler() {
@@ -1416,13 +1476,50 @@ var script = {
1416
1476
  payload?.waitUntil?.(done);
1417
1477
  };
1418
1478
  subscribe(PAGE_REFRESH_CHANNEL, this._onPageRefresh);
1479
+ if (typeof ResizeObserver === 'function' && this.$refs.valueRow) {
1480
+ this._fitObserver = new ResizeObserver(() => this.fitValue());
1481
+ this._fitObserver.observe(this.$refs.valueRow);
1482
+ }
1483
+ this.$nextTick(() => this.fitValue());
1419
1484
  },
1420
1485
 
1421
1486
  beforeUnmount() {
1422
1487
  unsubscribe(PAGE_REFRESH_CHANNEL, this._onPageRefresh);
1488
+ this._fitObserver?.disconnect();
1423
1489
  },
1424
1490
 
1425
1491
  methods: {
1492
+ /**
1493
+ * Step the number's font down until it fits its row, instead of the
1494
+ * card clipping it ("€ 1.012.4" in a narrow tile). A value that fits
1495
+ * keeps its own size; text values wrap and clamp on their own.
1496
+ *
1497
+ * @return {void}
1498
+ * @spec openspec/changes/stat-value-fits/specs/stat-tile-board-look/spec.md#requirement-a-stat-value-shrinks-instead-of-clipping
1499
+ */
1500
+ fitValue() {
1501
+ const row = this.$refs.valueRow;
1502
+ const el = this.$refs.value;
1503
+ if (!row || !el || el.classList.contains('cn-kpi-card__value--text') || typeof getComputedStyle !== 'function') {
1504
+ this.fitFontSize = null;
1505
+ return
1506
+ }
1507
+ const applied = el.style.fontSize;
1508
+ const whiteSpace = el.style.whiteSpace;
1509
+ el.style.fontSize = '';
1510
+ el.style.whiteSpace = '';
1511
+ const natural = parseFloat(getComputedStyle(el).fontSize);
1512
+ const overflow = row.scrollWidth - row.clientWidth;
1513
+ const width = el.offsetWidth;
1514
+ el.style.fontSize = applied;
1515
+ el.style.whiteSpace = whiteSpace;
1516
+ if (!(overflow > 0.5) || !(width > 0) || !(natural > 0)) {
1517
+ this.fitFontSize = null;
1518
+ return
1519
+ }
1520
+ this.fitFontSize = Math.max(MIN_FIT_FONT_SIZE, Math.floor(natural * ((width - overflow) / width) * 10) / 10);
1521
+ },
1522
+
1426
1523
  /**
1427
1524
  * Resolve an `objectField` that holds a reference uuid to the referenced
1428
1525
  * object's label, through the shared object store (per-schema caching and
@@ -1683,7 +1780,9 @@ var script = {
1683
1780
  /**
1684
1781
  * Read the caption's own count (`content.captionSource`:
1685
1782
  * `{ register?, schema?, metric?, field?, filter?, via?, zeroText? }`,
1686
- * register and schema default to the tile's source). A failed read
1783
+ * register and schema default to the tile's source). With `kind:
1784
+ * "ratio"` it reads the `denominator` part, and the `numerator` part
1785
+ * when one is named, as the tile's `ratio` source does. A failed read
1687
1786
  * leaves the caption out; it never shows a zero it did not count.
1688
1787
  *
1689
1788
  * @spec openspec/changes/screens-stat-caption-source/specs/stat-widget/spec.md#requirement-a-stat-tiles-caption-can-carry-its-own-count
@@ -1692,6 +1791,7 @@ var script = {
1692
1791
  async fetchCaptionValue() {
1693
1792
  if (!this.hasCaptionSource) {
1694
1793
  this.captionValue = null;
1794
+ this.captionRatioParts = null;
1695
1795
  return
1696
1796
  }
1697
1797
  const own = this.content.captionSource;
@@ -1702,13 +1802,37 @@ var script = {
1702
1802
  import('@nextcloud/axios'),
1703
1803
  import('@nextcloud/router'),
1704
1804
  ]);
1805
+ if (own.kind === 'ratio') {
1806
+ // The parts of the tile's own `ratio` source: each names its
1807
+ // metric, field, filter and `via`.
1808
+ const part = (p) => this.fetchAggregate(axios, generateUrl, source, (p && p.metric) || own.metric, (p && p.field) || own.field, (p && p.filter) || {}, (p && p.via) || own.via);
1809
+ const num = own.numerator ? await part(own.numerator) : null;
1810
+ const den = await part(own.denominator);
1811
+ this.captionRatioParts = { num: num === undefined ? null : num, den };
1812
+ return
1813
+ }
1705
1814
  const found = await this.fetchAggregate(axios, generateUrl, source, own.metric, own.field, own.filter || {}, own.via);
1706
1815
  this.captionValue = found === null || found === undefined || Number.isNaN(Number(found)) ? null : Number(found);
1707
1816
  } catch {
1708
1817
  this.captionValue = null;
1818
+ this.captionRatioParts = null;
1709
1819
  }
1710
1820
  },
1711
1821
 
1822
+ /**
1823
+ * A ratio caption's share as the reader's number, with
1824
+ * `captionSource.decimals` fraction digits (0 by default).
1825
+ *
1826
+ * @param {number} share The percentage.
1827
+ * @return {string}
1828
+ * @spec openspec/changes/stat-caption-ratio/specs/stat-widget/spec.md#requirement-a-stat-tiles-caption-can-show-a-share
1829
+ */
1830
+ formatCaptionRatio(share) {
1831
+ const raw = Number(this.content.captionSource && this.content.captionSource.decimals);
1832
+ const digits = Number.isInteger(raw) && raw >= 0 && raw <= 4 ? raw : 0;
1833
+ return new Intl.NumberFormat(metricLocale(), { minimumFractionDigits: digits, maximumFractionDigits: digits }).format(share)
1834
+ },
1835
+
1712
1836
  /**
1713
1837
  * Resolve the widget's value from its `source`. Supports three source
1714
1838
  * kinds (ADR-041): a plain `aggregate` (count/sum/avg/min/max with