@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
@@ -69,6 +69,34 @@ var script = {
69
69
  type: Boolean,
70
70
  default: false,
71
71
  },
72
+
73
+ /**
74
+ * Draw this text between the crumbs (for example `/`) instead of
75
+ * NcBreadcrumbs' chevron. Empty (the default) keeps the chevron. The
76
+ * separator is decoration: it is not read out.
77
+ *
78
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator
79
+ */
80
+ separator: {
81
+ type: String,
82
+ default: '',
83
+ },
84
+ },
85
+
86
+ computed: {
87
+ /**
88
+ * The separator handed to the stylesheet as a CSS string, or nothing
89
+ * without a `separator`.
90
+ *
91
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator
92
+ * @return {object|undefined}
93
+ */
94
+ separatorStyle() {
95
+ if (this.separator === '') {
96
+ return undefined
97
+ }
98
+ return { '--cn-breadcrumbs-separator': JSON.stringify(this.separator) }
99
+ },
72
100
  },
73
101
 
74
102
  methods: {
@@ -1 +1 @@
1
- {"version":3,"file":"CnBreadcrumbs.vue2.js","sources":["../../../../src/components/CnBreadcrumbs/CnBreadcrumbs.vue"],"sourcesContent":["<!--\n CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n\n Renders a `crumbs` array as NcBreadcrumb entries: every crumb but the\n last navigates via `to` (router target) or `href` (plain anchor); the\n LAST crumb is the current location — rendered without a link target and\n carrying aria-current=\"page\" so assistive tech announces the position\n in the trail. Crumb icons resolve by PascalCase MDI name through the\n shared CnIcon registry, so a home crumb is `{ icon: 'Home', to: … }`\n without the consumer importing an icon component.\n\n NcBreadcrumbs supplies the nav landmark, the chevron separators and the\n responsive collapse of middle crumbs into an overflow menu; this wrapper\n only owns the array-in API and the last-crumb semantics, keeping fleet\n apps from hand-rolling the same v-for.\n-->\n<template>\n\t<NcBreadcrumbs\n\t\tv-if=\"crumbs.length > 0\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t:rootIcon=\"rootText ? '' : undefined\"\n\t\tdata-testid=\"cn-breadcrumbs\">\n\t\t<!-- `name` falls back to '' because NcBreadcrumb declares it a REQUIRED\n\t\t String and warns per render on the label-less icon-only crumb. The\n\t\t empty string never shows: NcBreadcrumb prints `name` only for\n\t\t crumbs without an icon slot. -->\n\t\t<NcBreadcrumb\n\t\t\tv-for=\"(crumb, index) in crumbs\"\n\t\t\t:key=\"`${index}-${crumb.label ?? crumb.icon}`\"\n\t\t\t:name=\"crumb.label ?? ''\"\n\t\t\t:to=\"isCurrent(index) ? undefined : crumb.to\"\n\t\t\t:href=\"isCurrent(index) ? undefined : crumb.href\"\n\t\t\t:aria-current=\"isCurrent(index) ? 'page' : undefined\"\n\t\t\t:data-testid=\"`cn-breadcrumbs-crumb-${index}`\">\n\t\t\t<template v-if=\"crumb.icon\" #icon>\n\t\t\t\t<CnIcon :name=\"crumb.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t</NcBreadcrumb>\n\t</NcBreadcrumbs>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcBreadcrumb, NcBreadcrumbs } from '@nextcloud/vue'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n *\n * Feed it the whole trail as one `crumbs` array — first entry is the root\n * (typically a Home crumb), last entry is the CURRENT location. Every crumb\n * but the last links via its `to` (router) or `href` (anchor); the last one\n * is rendered unlinked with `aria-current=\"page\"`, per the WAI-ARIA\n * breadcrumb pattern.\n *\n * ```vue\n * <CnBreadcrumbs\n * :crumbs=\"[\n * { icon: 'Home', to: { name: 'SecretList' } },\n * { label: 'Team vault', to: { name: 'SecretListFolder', params: { folderId: '7' } } },\n * { label: 'Production' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnBreadcrumbs',\n\n\tcomponents: {\n\t\tNcBreadcrumbs,\n\t\tNcBreadcrumb,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The trail, root first, current location LAST. Each crumb:\n\t\t * `{ label, to?, href?, icon? }` — `label` is the visible (already\n\t\t * translated) text, `to` a vue-router target, `href` a plain URL\n\t\t * (use one or the other; both are ignored on the last crumb), and\n\t\t * `icon` a PascalCase MDI name resolved via the CnIcon registry\n\t\t * (register it with `registerIcons()` at boot).\n\t\t *\n\t\t * @type {Array<{label: string, to?: (object|string), href?: string, icon?: string}>}\n\t\t */\n\t\tcrumbs: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t\tvalidator: (value) => value.every((crumb) => crumb && typeof crumb === 'object'\n\t\t\t\t&& (typeof crumb.label === 'string' || typeof crumb.icon === 'string')),\n\t\t},\n\n\t\t/**\n\t\t * Accessible name of the breadcrumb nav landmark. Defaults to the\n\t\t * lib's translation of \"Breadcrumbs\"; override per surface when a\n\t\t * page carries more than one trail.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Breadcrumbs'),\n\t\t},\n\n\t\t/**\n\t\t * Draw the FIRST crumb's label as text. NcBreadcrumbs gives the root\n\t\t * crumb a home icon and prints its `label` only as the accessible\n\t\t * name, so \"All cases\" read as a house. Off by default, which keeps\n\t\t * the home icon. A first crumb with its own `icon` keeps that icon.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text\n\t\t */\n\t\trootText: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the crumb at `index` is the trail's last entry — the\n\t\t * current location, which renders unlinked with aria-current.\n\t\t *\n\t\t * @param {number} index Crumb position in `crumbs`.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCurrent(index) {\n\t\t\treturn index === this.crumbs.length - 1\n\t\t},\n\t},\n}\n</script>\n"],"names":["CnIcon","t"],"mappings":";;;;AA8CA;AACC;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,eAAe;;CAErB,UAAU,EAAE;EACX,aAAa;EACb,YAAY;AACZ,UAAAA,UAAM;EACN;;AAED,CAAA,KAAK,EAAE;AACN;;;;;;AAMC;AACA;AACA;;AAED,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;AACjB,GAAA,SAAS,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,KAAM,IAAG,OAAO,UAAU,QAAO;AAC7E,QAAI,OAAO,KAAK,CAAC,KAAM,KAAI,QAAO,IAAK,OAAO,KAAK,CAAC,IAAG,KAAM,QAAQ,CAAC,CAAC;GACxE;;AAED;;;;;EAKA,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,aAAa,CAAC;GAChD;;AAED;;;;;AAKC;AACA;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;;AAGC;AACA;AACA;;AAED,EAAA,SAAS,CAAC,KAAK,EAAE;AAChB,GAAA,OAAO,KAAI,KAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA;GACtC;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnBreadcrumbs.vue2.js","sources":["../../../../src/components/CnBreadcrumbs/CnBreadcrumbs.vue"],"sourcesContent":["<!--\n CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n\n Renders a `crumbs` array as NcBreadcrumb entries: every crumb but the\n last navigates via `to` (router target) or `href` (plain anchor); the\n LAST crumb is the current location — rendered without a link target and\n carrying aria-current=\"page\" so assistive tech announces the position\n in the trail. Crumb icons resolve by PascalCase MDI name through the\n shared CnIcon registry, so a home crumb is `{ icon: 'Home', to: … }`\n without the consumer importing an icon component.\n\n NcBreadcrumbs supplies the nav landmark, the chevron separators and the\n responsive collapse of middle crumbs into an overflow menu; this wrapper\n only owns the array-in API and the last-crumb semantics, keeping fleet\n apps from hand-rolling the same v-for.\n-->\n<template>\n\t<NcBreadcrumbs\n\t\tv-if=\"crumbs.length > 0\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t:rootIcon=\"rootText ? '' : undefined\"\n\t\t:class=\"{ 'cn-breadcrumbs--text-separator': separator !== '' }\"\n\t\t:style=\"separatorStyle\"\n\t\tdata-testid=\"cn-breadcrumbs\">\n\t\t<!-- `name` falls back to '' because NcBreadcrumb declares it a REQUIRED\n\t\t String and warns per render on the label-less icon-only crumb. The\n\t\t empty string never shows: NcBreadcrumb prints `name` only for\n\t\t crumbs without an icon slot. -->\n\t\t<NcBreadcrumb\n\t\t\tv-for=\"(crumb, index) in crumbs\"\n\t\t\t:key=\"`${index}-${crumb.label ?? crumb.icon}`\"\n\t\t\t:name=\"crumb.label ?? ''\"\n\t\t\t:to=\"isCurrent(index) ? undefined : crumb.to\"\n\t\t\t:href=\"isCurrent(index) ? undefined : crumb.href\"\n\t\t\t:aria-current=\"isCurrent(index) ? 'page' : undefined\"\n\t\t\t:data-testid=\"`cn-breadcrumbs-crumb-${index}`\">\n\t\t\t<template v-if=\"crumb.icon\" #icon>\n\t\t\t\t<CnIcon :name=\"crumb.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t</NcBreadcrumb>\n\t</NcBreadcrumbs>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcBreadcrumb, NcBreadcrumbs } from '@nextcloud/vue'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n *\n * Feed it the whole trail as one `crumbs` array — first entry is the root\n * (typically a Home crumb), last entry is the CURRENT location. Every crumb\n * but the last links via its `to` (router) or `href` (anchor); the last one\n * is rendered unlinked with `aria-current=\"page\"`, per the WAI-ARIA\n * breadcrumb pattern.\n *\n * ```vue\n * <CnBreadcrumbs\n * :crumbs=\"[\n * { icon: 'Home', to: { name: 'SecretList' } },\n * { label: 'Team vault', to: { name: 'SecretListFolder', params: { folderId: '7' } } },\n * { label: 'Production' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnBreadcrumbs',\n\n\tcomponents: {\n\t\tNcBreadcrumbs,\n\t\tNcBreadcrumb,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The trail, root first, current location LAST. Each crumb:\n\t\t * `{ label, to?, href?, icon? }` — `label` is the visible (already\n\t\t * translated) text, `to` a vue-router target, `href` a plain URL\n\t\t * (use one or the other; both are ignored on the last crumb), and\n\t\t * `icon` a PascalCase MDI name resolved via the CnIcon registry\n\t\t * (register it with `registerIcons()` at boot).\n\t\t *\n\t\t * @type {Array<{label: string, to?: (object|string), href?: string, icon?: string}>}\n\t\t */\n\t\tcrumbs: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t\tvalidator: (value) => value.every((crumb) => crumb && typeof crumb === 'object'\n\t\t\t\t&& (typeof crumb.label === 'string' || typeof crumb.icon === 'string')),\n\t\t},\n\n\t\t/**\n\t\t * Accessible name of the breadcrumb nav landmark. Defaults to the\n\t\t * lib's translation of \"Breadcrumbs\"; override per surface when a\n\t\t * page carries more than one trail.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Breadcrumbs'),\n\t\t},\n\n\t\t/**\n\t\t * Draw the FIRST crumb's label as text. NcBreadcrumbs gives the root\n\t\t * crumb a home icon and prints its `label` only as the accessible\n\t\t * name, so \"All cases\" read as a house. Off by default, which keeps\n\t\t * the home icon. A first crumb with its own `icon` keeps that icon.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text\n\t\t */\n\t\trootText: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw this text between the crumbs (for example `/`) instead of\n\t\t * NcBreadcrumbs' chevron. Empty (the default) keeps the chevron. The\n\t\t * separator is decoration: it is not read out.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator\n\t\t */\n\t\tseparator: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The separator handed to the stylesheet as a CSS string, or nothing\n\t\t * without a `separator`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator\n\t\t * @return {object|undefined}\n\t\t */\n\t\tseparatorStyle() {\n\t\t\tif (this.separator === '') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn { '--cn-breadcrumbs-separator': JSON.stringify(this.separator) }\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the crumb at `index` is the trail's last entry — the\n\t\t * current location, which renders unlinked with aria-current.\n\t\t *\n\t\t * @param {number} index Crumb position in `crumbs`.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCurrent(index) {\n\t\t\treturn index === this.crumbs.length - 1\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* `separator`: a text separator in place of the chevron. The chevron's own\n element stays (it carries NcBreadcrumbs' spacing and its \"no separator after\n the last crumb\" rule); only its icon gives way to the text. */\n.cn-breadcrumbs--text-separator :deep(.vue-crumb__separator svg) {\n\tdisplay: none;\n}\n\n.cn-breadcrumbs--text-separator :deep(.vue-crumb__separator)::before {\n\tcontent: var(--cn-breadcrumbs-separator);\n\tpadding-inline: var(--cn-breadcrumbs-separator-gap, 0);\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnIcon","t"],"mappings":";;;;AAgDA;AACC;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,eAAe;;CAErB,UAAU,EAAE;EACX,aAAa;EACb,YAAY;AACZ,UAAAA,UAAM;EACN;;AAED,CAAA,KAAK,EAAE;AACN;;;;;;AAMC;AACA;AACA;;AAED,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;AACjB,GAAA,SAAS,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,KAAM,IAAG,OAAO,UAAU,QAAO;AAC7E,QAAI,OAAO,KAAK,CAAC,KAAM,KAAI,QAAO,IAAK,OAAO,KAAK,CAAC,IAAG,KAAM,QAAQ,CAAC,CAAC;GACxE;;AAED;;;;;EAKA,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,aAAa,CAAC;GAChD;;AAED;;;;;AAKC;AACA;;AAED,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;;AAGC;AACA;AACA;;EAED,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;EACD;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;AACA;AACA;;;AAGD,EAAA,cAAc,GAAG;GAChB,IAAI,IAAI,CAAC,SAAQ,KAAM,EAAE,EAAE;AAC1B,IAAA,OAAO,SAAQ;AAChB,IAAA;AACA,GAAA,OAAO,EAAE,4BAA4B,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAE,EAAA;GACtE;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;;AAGC;AACA;AACA;;AAED,EAAA,SAAS,CAAC,KAAK,EAAE;AAChB,GAAA,OAAO,KAAI,KAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA;GACtC;EACD;AACF;;;;"}
@@ -1,4 +1,4 @@
1
- import { resolveComponent, openBlock, createBlock, withCtx, createElementBlock, Fragment, renderList, createSlots, createVNode, createCommentVNode } from 'vue';
1
+ import { resolveComponent, openBlock, createBlock, normalizeClass, normalizeStyle, withCtx, createElementBlock, Fragment, renderList, createSlots, createVNode, createCommentVNode } from 'vue';
2
2
 
3
3
  function render(_ctx, _cache, $props, $setup, $data, $options) {
4
4
  const _component_CnIcon = resolveComponent("CnIcon");
@@ -10,6 +10,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
10
10
  key: 0,
11
11
  "aria-label": $props.ariaLabel,
12
12
  rootIcon: $props.rootText ? '' : undefined,
13
+ class: normalizeClass({ 'cn-breadcrumbs--text-separator': $props.separator !== '' }),
14
+ style: normalizeStyle($options.separatorStyle),
13
15
  "data-testid": "cn-breadcrumbs"
14
16
  }, {
15
17
  default: withCtx(() => [
@@ -38,7 +40,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
38
40
  }), 128 /* KEYED_FRAGMENT */))
39
41
  ]),
40
42
  _: 1 /* STABLE */
41
- }, 8 /* PROPS */, ["aria-label", "rootIcon"]))
43
+ }, 8 /* PROPS */, ["aria-label", "rootIcon", "class", "style"]))
42
44
  : createCommentVNode("v-if", true)
43
45
  }
44
46
 
@@ -1 +1 @@
1
- {"version":3,"file":"CnBreadcrumbs.vue3.js","sources":["../../../../src/components/CnBreadcrumbs/CnBreadcrumbs.vue?vue&type=template&id=296fde9b&lang.js"],"sourcesContent":["<!--\n CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n\n Renders a `crumbs` array as NcBreadcrumb entries: every crumb but the\n last navigates via `to` (router target) or `href` (plain anchor); the\n LAST crumb is the current location — rendered without a link target and\n carrying aria-current=\"page\" so assistive tech announces the position\n in the trail. Crumb icons resolve by PascalCase MDI name through the\n shared CnIcon registry, so a home crumb is `{ icon: 'Home', to: … }`\n without the consumer importing an icon component.\n\n NcBreadcrumbs supplies the nav landmark, the chevron separators and the\n responsive collapse of middle crumbs into an overflow menu; this wrapper\n only owns the array-in API and the last-crumb semantics, keeping fleet\n apps from hand-rolling the same v-for.\n-->\n<template>\n\t<NcBreadcrumbs\n\t\tv-if=\"crumbs.length > 0\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t:rootIcon=\"rootText ? '' : undefined\"\n\t\tdata-testid=\"cn-breadcrumbs\">\n\t\t<!-- `name` falls back to '' because NcBreadcrumb declares it a REQUIRED\n\t\t String and warns per render on the label-less icon-only crumb. The\n\t\t empty string never shows: NcBreadcrumb prints `name` only for\n\t\t crumbs without an icon slot. -->\n\t\t<NcBreadcrumb\n\t\t\tv-for=\"(crumb, index) in crumbs\"\n\t\t\t:key=\"`${index}-${crumb.label ?? crumb.icon}`\"\n\t\t\t:name=\"crumb.label ?? ''\"\n\t\t\t:to=\"isCurrent(index) ? undefined : crumb.to\"\n\t\t\t:href=\"isCurrent(index) ? undefined : crumb.href\"\n\t\t\t:aria-current=\"isCurrent(index) ? 'page' : undefined\"\n\t\t\t:data-testid=\"`cn-breadcrumbs-crumb-${index}`\">\n\t\t\t<template v-if=\"crumb.icon\" #icon>\n\t\t\t\t<CnIcon :name=\"crumb.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t</NcBreadcrumb>\n\t</NcBreadcrumbs>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcBreadcrumb, NcBreadcrumbs } from '@nextcloud/vue'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n *\n * Feed it the whole trail as one `crumbs` array — first entry is the root\n * (typically a Home crumb), last entry is the CURRENT location. Every crumb\n * but the last links via its `to` (router) or `href` (anchor); the last one\n * is rendered unlinked with `aria-current=\"page\"`, per the WAI-ARIA\n * breadcrumb pattern.\n *\n * ```vue\n * <CnBreadcrumbs\n * :crumbs=\"[\n * { icon: 'Home', to: { name: 'SecretList' } },\n * { label: 'Team vault', to: { name: 'SecretListFolder', params: { folderId: '7' } } },\n * { label: 'Production' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnBreadcrumbs',\n\n\tcomponents: {\n\t\tNcBreadcrumbs,\n\t\tNcBreadcrumb,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The trail, root first, current location LAST. Each crumb:\n\t\t * `{ label, to?, href?, icon? }` — `label` is the visible (already\n\t\t * translated) text, `to` a vue-router target, `href` a plain URL\n\t\t * (use one or the other; both are ignored on the last crumb), and\n\t\t * `icon` a PascalCase MDI name resolved via the CnIcon registry\n\t\t * (register it with `registerIcons()` at boot).\n\t\t *\n\t\t * @type {Array<{label: string, to?: (object|string), href?: string, icon?: string}>}\n\t\t */\n\t\tcrumbs: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t\tvalidator: (value) => value.every((crumb) => crumb && typeof crumb === 'object'\n\t\t\t\t&& (typeof crumb.label === 'string' || typeof crumb.icon === 'string')),\n\t\t},\n\n\t\t/**\n\t\t * Accessible name of the breadcrumb nav landmark. Defaults to the\n\t\t * lib's translation of \"Breadcrumbs\"; override per surface when a\n\t\t * page carries more than one trail.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Breadcrumbs'),\n\t\t},\n\n\t\t/**\n\t\t * Draw the FIRST crumb's label as text. NcBreadcrumbs gives the root\n\t\t * crumb a home icon and prints its `label` only as the accessible\n\t\t * name, so \"All cases\" read as a house. Off by default, which keeps\n\t\t * the home icon. A first crumb with its own `icon` keeps that icon.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text\n\t\t */\n\t\trootText: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the crumb at `index` is the trail's last entry — the\n\t\t * current location, which renders unlinked with aria-current.\n\t\t *\n\t\t * @param {number} index Crumb position in `crumbs`.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCurrent(index) {\n\t\t\treturn index === this.crumbs.length - 1\n\t\t},\n\t},\n}\n</script>\n"],"names":[],"mappings":";;;;;;;AAkCQ,EAAA,OAAA,CAAA,MAAA,CAAA,MAAM,CAAC,MAAM,GAAA,CAAA;;;AACjB,QAAA,YAAS,EAAE,MAAS,CAAA,SAAA;QACrB,QAAQ,EAAE,MAAA,CAAA,QAAA,GAAA,EAAA,GAAA,SAAA;AACX,QAAA,aAAW,EAAC,gBAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnBreadcrumbs.vue3.js","sources":["../../../../src/components/CnBreadcrumbs/CnBreadcrumbs.vue?vue&type=template&id=296fde9b&lang.js"],"sourcesContent":["<!--\n CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n\n Renders a `crumbs` array as NcBreadcrumb entries: every crumb but the\n last navigates via `to` (router target) or `href` (plain anchor); the\n LAST crumb is the current location — rendered without a link target and\n carrying aria-current=\"page\" so assistive tech announces the position\n in the trail. Crumb icons resolve by PascalCase MDI name through the\n shared CnIcon registry, so a home crumb is `{ icon: 'Home', to: … }`\n without the consumer importing an icon component.\n\n NcBreadcrumbs supplies the nav landmark, the chevron separators and the\n responsive collapse of middle crumbs into an overflow menu; this wrapper\n only owns the array-in API and the last-crumb semantics, keeping fleet\n apps from hand-rolling the same v-for.\n-->\n<template>\n\t<NcBreadcrumbs\n\t\tv-if=\"crumbs.length > 0\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t:rootIcon=\"rootText ? '' : undefined\"\n\t\t:class=\"{ 'cn-breadcrumbs--text-separator': separator !== '' }\"\n\t\t:style=\"separatorStyle\"\n\t\tdata-testid=\"cn-breadcrumbs\">\n\t\t<!-- `name` falls back to '' because NcBreadcrumb declares it a REQUIRED\n\t\t String and warns per render on the label-less icon-only crumb. The\n\t\t empty string never shows: NcBreadcrumb prints `name` only for\n\t\t crumbs without an icon slot. -->\n\t\t<NcBreadcrumb\n\t\t\tv-for=\"(crumb, index) in crumbs\"\n\t\t\t:key=\"`${index}-${crumb.label ?? crumb.icon}`\"\n\t\t\t:name=\"crumb.label ?? ''\"\n\t\t\t:to=\"isCurrent(index) ? undefined : crumb.to\"\n\t\t\t:href=\"isCurrent(index) ? undefined : crumb.href\"\n\t\t\t:aria-current=\"isCurrent(index) ? 'page' : undefined\"\n\t\t\t:data-testid=\"`cn-breadcrumbs-crumb-${index}`\">\n\t\t\t<template v-if=\"crumb.icon\" #icon>\n\t\t\t\t<CnIcon :name=\"crumb.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t</NcBreadcrumb>\n\t</NcBreadcrumbs>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcBreadcrumb, NcBreadcrumbs } from '@nextcloud/vue'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n *\n * Feed it the whole trail as one `crumbs` array — first entry is the root\n * (typically a Home crumb), last entry is the CURRENT location. Every crumb\n * but the last links via its `to` (router) or `href` (anchor); the last one\n * is rendered unlinked with `aria-current=\"page\"`, per the WAI-ARIA\n * breadcrumb pattern.\n *\n * ```vue\n * <CnBreadcrumbs\n * :crumbs=\"[\n * { icon: 'Home', to: { name: 'SecretList' } },\n * { label: 'Team vault', to: { name: 'SecretListFolder', params: { folderId: '7' } } },\n * { label: 'Production' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnBreadcrumbs',\n\n\tcomponents: {\n\t\tNcBreadcrumbs,\n\t\tNcBreadcrumb,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The trail, root first, current location LAST. Each crumb:\n\t\t * `{ label, to?, href?, icon? }` — `label` is the visible (already\n\t\t * translated) text, `to` a vue-router target, `href` a plain URL\n\t\t * (use one or the other; both are ignored on the last crumb), and\n\t\t * `icon` a PascalCase MDI name resolved via the CnIcon registry\n\t\t * (register it with `registerIcons()` at boot).\n\t\t *\n\t\t * @type {Array<{label: string, to?: (object|string), href?: string, icon?: string}>}\n\t\t */\n\t\tcrumbs: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t\tvalidator: (value) => value.every((crumb) => crumb && typeof crumb === 'object'\n\t\t\t\t&& (typeof crumb.label === 'string' || typeof crumb.icon === 'string')),\n\t\t},\n\n\t\t/**\n\t\t * Accessible name of the breadcrumb nav landmark. Defaults to the\n\t\t * lib's translation of \"Breadcrumbs\"; override per surface when a\n\t\t * page carries more than one trail.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Breadcrumbs'),\n\t\t},\n\n\t\t/**\n\t\t * Draw the FIRST crumb's label as text. NcBreadcrumbs gives the root\n\t\t * crumb a home icon and prints its `label` only as the accessible\n\t\t * name, so \"All cases\" read as a house. Off by default, which keeps\n\t\t * the home icon. A first crumb with its own `icon` keeps that icon.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text\n\t\t */\n\t\trootText: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw this text between the crumbs (for example `/`) instead of\n\t\t * NcBreadcrumbs' chevron. Empty (the default) keeps the chevron. The\n\t\t * separator is decoration: it is not read out.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator\n\t\t */\n\t\tseparator: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The separator handed to the stylesheet as a CSS string, or nothing\n\t\t * without a `separator`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator\n\t\t * @return {object|undefined}\n\t\t */\n\t\tseparatorStyle() {\n\t\t\tif (this.separator === '') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn { '--cn-breadcrumbs-separator': JSON.stringify(this.separator) }\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the crumb at `index` is the trail's last entry — the\n\t\t * current location, which renders unlinked with aria-current.\n\t\t *\n\t\t * @param {number} index Crumb position in `crumbs`.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCurrent(index) {\n\t\t\treturn index === this.crumbs.length - 1\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* `separator`: a text separator in place of the chevron. The chevron's own\n element stays (it carries NcBreadcrumbs' spacing and its \"no separator after\n the last crumb\" rule); only its icon gives way to the text. */\n.cn-breadcrumbs--text-separator :deep(.vue-crumb__separator svg) {\n\tdisplay: none;\n}\n\n.cn-breadcrumbs--text-separator :deep(.vue-crumb__separator)::before {\n\tcontent: var(--cn-breadcrumbs-separator);\n\tpadding-inline: var(--cn-breadcrumbs-separator-gap, 0);\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createBlock"],"mappings":";;;;;;;AAkCQ,EAAA,OAAA,CAAA,MAAA,CAAA,MAAM,CAAC,MAAM,GAAA,CAAA;oBADlBA;;AAEA,QAAA,YAAU,EAAE,MAAS,CAAA,SAAA;QACrB,QAAQ,EAAE,MAAQ,CAAA,QAAA,GAAA,EAAA,GAAQ,SAAO;AAChC,QAAA,KAAI,qDAAmC,MAAA,CAAA,SAAA,KAAA,EAAA,EAAA,CAAA;AACxC,QAAA,KAAK,iBAAE,QAAG,CAAA,cAAA,CAAA;AACX,QAAA,aAAW,EAAC,gBAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -33,7 +33,10 @@ var script = {
33
33
 
34
34
  props: {
35
35
  /**
36
- * Persisted widget content `{viewMode, daysAhead, colorByCalendar}`.
36
+ * Persisted widget content `{viewMode, daysAhead, colorByCalendar,
37
+ * showTitle, showViewModes}`. `showTitle: false` drops the "Calendar"
38
+ * sub-heading and `showViewModes: false` the Month / Week / Agenda
39
+ * buttons (both shown by default).
37
40
  *
38
41
  * @type {object}
39
42
  */
@@ -70,6 +73,27 @@ var script = {
70
73
  },
71
74
 
72
75
  computed: {
76
+ /**
77
+ * Whether the "Calendar" sub-heading renders (`content.showTitle`).
78
+ *
79
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-agenda-placement-can-drop-the-calendar-chrome
80
+ * @return {boolean}
81
+ */
82
+ showsTitle() {
83
+ return !(this.content && this.content.showTitle === false)
84
+ },
85
+
86
+ /**
87
+ * Whether the Month / Week / Agenda buttons render
88
+ * (`content.showViewModes`).
89
+ *
90
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-agenda-placement-can-drop-the-calendar-chrome
91
+ * @return {boolean}
92
+ */
93
+ showsViewModes() {
94
+ return !(this.content && this.content.showViewModes === false)
95
+ },
96
+
73
97
  /**
74
98
  * The available view modes.
75
99
  *
@@ -1 +1 @@
1
- {"version":3,"file":"CnCalendarWidget.vue2.js","sources":["../../../../src/components/CnCalendarWidget/CnCalendarWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-calendar-widget\">\n\t\t<header class=\"cn-calendar-widget__header\">\n\t\t\t<span class=\"cn-calendar-widget__title\">{{ t('nextcloud-vue', 'Calendar') }}</span>\n\t\t\t<div class=\"cn-calendar-widget__modes\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"mode in viewModes\"\n\t\t\t\t\t:key=\"mode\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-calendar-widget__mode-btn\"\n\t\t\t\t\t:class=\"{ 'is-active': activeMode === mode }\"\n\t\t\t\t\t@click=\"setMode(mode)\">\n\t\t\t\t\t{{ modeLabel(mode) }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<div class=\"cn-calendar-widget__body\">\n\t\t\t<div v-if=\"loading\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'Loading calendars…') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"error\" class=\"cn-calendar-widget__state cn-calendar-widget__state--error\">\n\t\t\t\t<p>{{ t('nextcloud-vue', 'Failed to load events') }}</p>\n\t\t\t\t<button type=\"button\" class=\"cn-calendar-widget__retry\" @click=\"fetchEvents\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Retry') }}\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"!hasSource\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'No calendar available') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"hasNoCalendarChosen\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'No calendar is selected yet. Choose one in this widget\\u2019s settings.') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"events.length === 0 && activeMode === 'agenda'\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ emptyMessage }}\n\t\t\t</div>\n\n\t\t\t<!-- Month view: 7-column grid padded to whole weeks. -->\n\t\t\t<div v-else-if=\"activeMode === 'month'\" class=\"cn-calendar-widget__month\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(weekday, idx) in weekdayHeaders\"\n\t\t\t\t\t:key=\"'wh-' + idx\"\n\t\t\t\t\tclass=\"cn-calendar-widget__month-header\">\n\t\t\t\t\t{{ weekday }}\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"day in monthGrid\"\n\t\t\t\t\t:key=\"day.iso\"\n\t\t\t\t\tclass=\"cn-calendar-widget__month-cell\"\n\t\t\t\t\t:class=\"{ 'is-today': day.isToday, 'is-other-month': day.isOtherMonth }\">\n\t\t\t\t\t<span class=\"cn-calendar-widget__month-day\">{{ day.dayNum }}</span>\n\t\t\t\t\t<ul v-if=\"day.events.length\" class=\"cn-calendar-widget__month-events\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"event in day.events.slice(0, 3)\"\n\t\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__month-event\"\n\t\t\t\t\t\t\t:title=\"event.title\"\n\t\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t\t{{ event.title }}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-if=\"day.events.length > 3\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__month-overflow\">\n\t\t\t\t\t\t\t+{{ day.events.length - 3 }}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Week view: 7 day columns. -->\n\t\t\t<div v-else-if=\"activeMode === 'week'\" class=\"cn-calendar-widget__week\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"day in weekDays\"\n\t\t\t\t\t:key=\"day.iso\"\n\t\t\t\t\tclass=\"cn-calendar-widget__week-col\"\n\t\t\t\t\t:class=\"{ 'is-today': day.isToday }\">\n\t\t\t\t\t<header class=\"cn-calendar-widget__week-day\">\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-name\">{{ day.weekday }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-num\">{{ day.dayNum }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul v-if=\"day.events.length\" class=\"cn-calendar-widget__week-events\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"event in day.events\"\n\t\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__week-event\"\n\t\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-time\">{{ formatTime(event) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-title\">{{ event.title }}</span>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t\t<p v-else class=\"cn-calendar-widget__week-empty\">\n\t\t\t\t\t\t—\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Agenda view: chronological list grouped by day. -->\n\t\t\t<ul v-else class=\"cn-calendar-widget__agenda\">\n\t\t\t\t<template v-for=\"group in agendaGroups\" :key=\"group.iso\">\n\t\t\t\t\t<li class=\"cn-calendar-widget__agenda-header\">\n\t\t\t\t\t\t{{ group.label }}\n\t\t\t\t\t</li>\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"event in group.events\"\n\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\tclass=\"cn-calendar-widget__agenda-row\"\n\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__agenda-time\">{{ formatTime(event) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__agenda-title\">{{ event.title }}</span>\n\t\t\t\t\t\t<span v-if=\"event.calendarName\" class=\"cn-calendar-widget__agenda-cal\">{{ event.calendarName }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</template>\n\t\t\t</ul>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\n\nconst VIEW_MODES = ['month', 'week', 'agenda']\nconst DEFAULT_DAYS_AHEAD = 14\n\n/**\n * CnCalendarWidget — renders aggregated upcoming events as a chronological,\n * per-day grouped agenda.\n *\n * Data origin (decoupling seam): events are read through a consumer-supplied\n * `dataSource` prop OR the `cnCalendarSource` injection — an object exposing\n * `fetchEvents({ from, to }) => Promise<{ events, failures }>`. The canonical\n * backing is the Nextcloud Calendar app (internal calendars + external ICS\n * feeds) surfaced by the consuming app; this renderer never imports a\n * sibling-app module path nor assumes a backend route. When neither prop nor\n * injection is supplied the widget shows an empty agenda (\"No calendar\n * available\") and performs no request; an empty result simply renders the\n * \"no events\" empty state.\n *\n * No event field is ever rendered via `v-html` — all values flow through Vue\n * interpolation, so a malicious ICS feed cannot inject markup.\n *\n * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n */\nexport default {\n\tname: 'CnCalendarWidget',\n\n\tinject: {\n\t\tcnCalendarSource: {\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted widget content `{viewMode, daysAhead, colorByCalendar}`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Consumer-supplied data source overriding the `cnCalendarSource`\n\t\t * injection. Must expose `fetchEvents({from, to}) => Promise<{events,\n\t\t * failures}>`. When `null` the injection (then the empty state) is used.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tdataSource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\tconst initialMode = VIEW_MODES.includes(this.content && this.content.viewMode)\n\t\t\t? this.content.viewMode\n\t\t\t: 'agenda'\n\t\treturn {\n\t\t\tactiveMode: initialMode,\n\t\t\tevents: [],\n\t\t\tfailures: [],\n\t\t\tloading: false,\n\t\t\terror: null,\n\t\t\ttoday: new Date(),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The available view modes.\n\t\t *\n\t\t * @return {string[]} the mode keys.\n\t\t */\n\t\tviewModes() {\n\t\t\treturn VIEW_MODES\n\t\t},\n\n\t\t/**\n\t\t * The active data source (prop wins over injection).\n\t\t *\n\t\t * @return {object|null} the resolved source, or `null`.\n\t\t */\n\t\tsource() {\n\t\t\tconst candidate = this.dataSource || this.cnCalendarSource\n\t\t\treturn candidate && typeof candidate.fetchEvents === 'function' ? candidate : null\n\t\t},\n\n\t\t/**\n\t\t * Whether a usable calendar source is configured.\n\t\t *\n\t\t * @return {boolean} true when a source exists.\n\t\t */\n\t\thasSource() {\n\t\t\treturn this.source !== null\n\t\t},\n\n\t\t/**\n\t\t * The validated look-ahead window in days.\n\t\t *\n\t\t * @return {number} the days-ahead value.\n\t\t */\n\t\tdaysAhead() {\n\t\t\tconst value = parseInt((this.content && this.content.daysAhead) ?? DEFAULT_DAYS_AHEAD, 10)\n\t\t\treturn Number.isFinite(value) && value > 0 ? value : DEFAULT_DAYS_AHEAD\n\t\t},\n\n\t\t/**\n\t\t * Whether events are tinted by their calendar colour.\n\t\t *\n\t\t * @return {boolean} the colorByCalendar flag.\n\t\t */\n\t\tcolorByCalendar() {\n\t\t\treturn !(this.content && this.content.colorByCalendar === false)\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget is configured with no calendar at all.\n\t\t *\n\t\t * The host fetches events for the calendars named in\n\t\t * `content.internalCalendars` / `content.externalIcsUrls`, and it\n\t\t * skips the fetch entirely when both are empty: LaunchPad's\n\t\t * `CalendarWidgetService::getEvents()` guards each branch with\n\t\t * `!== []`, so an unconfigured widget returns zero events AND zero\n\t\t * failures. That is indistinguishable from a genuinely empty week.\n\t\t *\n\t\t * It is not a rare state. The registry's own `defaultContent` for\n\t\t * this widget type is `internalCalendars: []`, so EVERY freshly added\n\t\t * Calendar widget starts here and says \"No events in the next 14\n\t\t * days\" forever, which is a false statement about the user's diary\n\t\t * rather than a prompt to finish configuring it.\n\t\t *\n\t\t * Only claims the state when both lists are present and empty. A host\n\t\t * that drives the widget through `dataSource` alone, with no content\n\t\t * blob, keeps the ordinary empty message.\n\t\t *\n\t\t * @return {boolean} true when no calendar has been chosen.\n\t\t */\n\t\thasNoCalendarChosen() {\n\t\t\tconst c = this.content\n\t\t\tif (!c) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst internal = c.internalCalendars\n\t\t\tconst external = c.externalIcsUrls\n\t\t\tif (!Array.isArray(internal) && !Array.isArray(external)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn (internal || []).length === 0 && (external || []).length === 0\n\t\t},\n\n\t\t/**\n\t\t * The empty-agenda message.\n\t\t *\n\t\t * @return {string} the empty message.\n\t\t */\n\t\temptyMessage() {\n\t\t\treturn t('nextcloud-vue', 'No events in the next {n} days').replace('{n}', String(this.daysAhead))\n\t\t},\n\n\t\t/**\n\t\t * Localised short weekday names (Sun-first).\n\t\t *\n\t\t * @return {string[]} the weekday labels.\n\t\t */\n\t\tweekdayHeaders() {\n\t\t\treturn [\n\t\t\t\tt('nextcloud-vue', 'Sun'),\n\t\t\t\tt('nextcloud-vue', 'Mon'),\n\t\t\t\tt('nextcloud-vue', 'Tue'),\n\t\t\t\tt('nextcloud-vue', 'Wed'),\n\t\t\t\tt('nextcloud-vue', 'Thu'),\n\t\t\t\tt('nextcloud-vue', 'Fri'),\n\t\t\t\tt('nextcloud-vue', 'Sat'),\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The first/last day of the current month (month-view window).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the month window.\n\t\t */\n\t\tmonthRange() {\n\t\t\tconst today = this.today\n\t\t\tconst first = new Date(today.getFullYear(), today.getMonth(), 1)\n\t\t\tconst last = new Date(today.getFullYear(), today.getMonth() + 1, 0)\n\t\t\treturn { from: first, to: last }\n\t\t},\n\n\t\t/**\n\t\t * The Sun–Sat window containing today (week-view window).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the week window.\n\t\t */\n\t\tweekRange() {\n\t\t\tconst today = this.today\n\t\t\tconst start = new Date(today)\n\t\t\tstart.setDate(today.getDate() - today.getDay())\n\t\t\tstart.setHours(0, 0, 0, 0)\n\t\t\tconst end = new Date(start)\n\t\t\tend.setDate(start.getDate() + 6)\n\t\t\tend.setHours(23, 59, 59, 999)\n\t\t\treturn { from: start, to: end }\n\t\t},\n\n\t\t/**\n\t\t * Events bucketed by `YYYY-MM-DD` for grid lookup.\n\t\t *\n\t\t * @return {Record<string, object[]>} iso-date → events.\n\t\t */\n\t\teventsByDay() {\n\t\t\tconst buckets = {}\n\t\t\tfor (const event of this.events) {\n\t\t\t\tconst iso = this.toIsoDate(this.parseDate(event.start))\n\t\t\t\tif (!buckets[iso]) {\n\t\t\t\t\tbuckets[iso] = []\n\t\t\t\t}\n\t\t\t\tbuckets[iso].push(event)\n\t\t\t}\n\t\t\treturn buckets\n\t\t},\n\n\t\t/**\n\t\t * The 7-column month grid (leading/trailing days padded to whole weeks).\n\t\t *\n\t\t * @return {Array<object>} grid cells with day metadata + events.\n\t\t */\n\t\tmonthGrid() {\n\t\t\tconst { from, to } = this.monthRange\n\t\t\tconst cells = []\n\t\t\tconst gridStart = new Date(from)\n\t\t\tgridStart.setDate(from.getDate() - from.getDay())\n\t\t\tconst totalDays = Math.ceil((to.getDate() + from.getDay()) / 7) * 7\n\t\t\tfor (let i = 0; i < totalDays; i++) {\n\t\t\t\tconst cellDate = new Date(gridStart)\n\t\t\t\tcellDate.setDate(gridStart.getDate() + i)\n\t\t\t\tconst iso = this.toIsoDate(cellDate)\n\t\t\t\tcells.push({\n\t\t\t\t\tiso,\n\t\t\t\t\tdayNum: cellDate.getDate(),\n\t\t\t\t\tisToday: this.toIsoDate(this.today) === iso,\n\t\t\t\t\tisOtherMonth: cellDate.getMonth() !== from.getMonth(),\n\t\t\t\t\tevents: this.eventsByDay[iso] || [],\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn cells\n\t\t},\n\n\t\t/**\n\t\t * The 7 day-columns for the week view.\n\t\t *\n\t\t * @return {Array<object>} day columns with metadata + events.\n\t\t */\n\t\tweekDays() {\n\t\t\tconst { from } = this.weekRange\n\t\t\tconst out = []\n\t\t\tfor (let i = 0; i < 7; i++) {\n\t\t\t\tconst day = new Date(from)\n\t\t\t\tday.setDate(from.getDate() + i)\n\t\t\t\tconst iso = this.toIsoDate(day)\n\t\t\t\tout.push({\n\t\t\t\t\tiso,\n\t\t\t\t\tweekday: this.weekdayHeaders[day.getDay()],\n\t\t\t\t\tdayNum: day.getDate(),\n\t\t\t\t\tisToday: this.toIsoDate(this.today) === iso,\n\t\t\t\t\tevents: this.eventsByDay[iso] || [],\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Events grouped into per-day buckets in chronological order.\n\t\t *\n\t\t * @return {Array<{iso: string, label: string, events: object[]}>} the groups.\n\t\t */\n\t\tagendaGroups() {\n\t\t\tconst groups = []\n\t\t\tconst seen = {}\n\t\t\tfor (const event of this.events) {\n\t\t\t\tconst date = this.parseDate(event.start)\n\t\t\t\tconst iso = this.toIsoDate(date)\n\t\t\t\tif (!seen[iso]) {\n\t\t\t\t\tseen[iso] = { iso, label: this.formatDayHeader(date), events: [] }\n\t\t\t\t\tgroups.push(seen[iso])\n\t\t\t\t}\n\t\t\t\tseen[iso].events.push(event)\n\t\t\t}\n\t\t\treturn groups\n\t\t},\n\t},\n\n\twatch: {\n\t\tcontent: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchEvents()\n\t\t\t},\n\t\t},\n\n\t\t// Month/week/agenda use different fetch windows — refetch on switch.\n\t\tactiveMode() {\n\t\t\tthis.fetchEvents()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchEvents()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Switch the active view mode.\n\t\t *\n\t\t * @param {string} mode the mode to activate.\n\t\t * @return {void}\n\t\t */\n\t\tsetMode(mode) {\n\t\t\tif (VIEW_MODES.includes(mode)) {\n\t\t\t\tthis.activeMode = mode\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The label for a view mode.\n\t\t *\n\t\t * @param {string} mode the mode key.\n\t\t * @return {string} the localised label.\n\t\t */\n\t\tmodeLabel(mode) {\n\t\t\tif (mode === 'month') {\n\t\t\t\treturn t('nextcloud-vue', 'Month')\n\t\t\t}\n\t\t\tif (mode === 'week') {\n\t\t\t\treturn t('nextcloud-vue', 'Week')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Agenda')\n\t\t},\n\n\t\t/**\n\t\t * Compute the fetch window for the active view: the whole month in\n\t\t * month view, the Sun–Sat week in week view, else the daysAhead\n\t\t * look-ahead (agenda).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the window.\n\t\t */\n\t\tcomputeRange() {\n\t\t\tif (this.activeMode === 'month') {\n\t\t\t\tconst { from, to } = this.monthRange\n\t\t\t\tconst start = new Date(from)\n\t\t\t\tstart.setHours(0, 0, 0, 0)\n\t\t\t\tconst end = new Date(to)\n\t\t\t\tend.setHours(23, 59, 59, 999)\n\t\t\t\treturn { from: start, to: end }\n\t\t\t}\n\t\t\tif (this.activeMode === 'week') {\n\t\t\t\treturn this.weekRange\n\t\t\t}\n\t\t\tconst from = new Date(this.today)\n\t\t\tfrom.setHours(0, 0, 0, 0)\n\t\t\tconst to = new Date(from)\n\t\t\tto.setDate(from.getDate() + this.daysAhead)\n\t\t\tto.setHours(23, 59, 59, 999)\n\t\t\treturn { from, to }\n\t\t},\n\n\t\t/**\n\t\t * Load events from the configured source. A missing source or a fetch\n\t\t * failure degrades to the empty / error state without throwing.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEvents() {\n\t\t\tif (!this.source) {\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failures = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { from, to } = this.computeRange()\n\t\t\tthis.loading = true\n\t\t\tthis.error = null\n\t\t\ttry {\n\t\t\t\tconst payload = await this.source.fetchEvents({\n\t\t\t\t\tfrom: from.toISOString(),\n\t\t\t\t\tto: to.toISOString(),\n\t\t\t\t}) || {}\n\t\t\t\tthis.events = Array.isArray(payload.events) ? payload.events : []\n\t\t\t\tthis.failures = Array.isArray(payload.failures) ? payload.failures : []\n\t\t\t} catch (err) {\n\t\t\t\tthis.error = err\n\t\t\t\tthis.events = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Parse a date value, falling back to now on invalid input.\n\t\t *\n\t\t * @param {string|number|Date} value the value to parse.\n\t\t * @return {Date} the parsed date.\n\t\t */\n\t\tparseDate(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn new Date()\n\t\t\t}\n\t\t\tconst d = new Date(value)\n\t\t\treturn Number.isNaN(d.getTime()) ? new Date() : d\n\t\t},\n\n\t\t/**\n\t\t * Format a date as `YYYY-MM-DD`.\n\t\t *\n\t\t * @param {Date} date the date.\n\t\t * @return {string} the ISO date string.\n\t\t */\n\t\ttoIsoDate(date) {\n\t\t\tconst yyyy = date.getFullYear()\n\t\t\tconst mm = String(date.getMonth() + 1).padStart(2, '0')\n\t\t\tconst dd = String(date.getDate()).padStart(2, '0')\n\t\t\treturn `${yyyy}-${mm}-${dd}`\n\t\t},\n\n\t\t/**\n\t\t * Format an event's start time (or \"All day\").\n\t\t *\n\t\t * @param {object} event the event.\n\t\t * @return {string} the time label.\n\t\t */\n\t\tformatTime(event) {\n\t\t\tif (event.allDay) {\n\t\t\t\treturn t('nextcloud-vue', 'All day')\n\t\t\t}\n\t\t\tconst start = this.parseDate(event.start)\n\t\t\tconst hh = String(start.getHours()).padStart(2, '0')\n\t\t\tconst mm = String(start.getMinutes()).padStart(2, '0')\n\t\t\treturn `${hh}:${mm}`\n\t\t},\n\n\t\t/**\n\t\t * Format a per-day agenda group header.\n\t\t *\n\t\t * @param {Date} date the group date.\n\t\t * @return {string} the header label.\n\t\t */\n\t\tformatDayHeader(date) {\n\t\t\tconst month = date.toLocaleString(undefined, { month: 'short' })\n\t\t\treturn `${this.weekdayHeaders[date.getDay()]} ${date.getDate()} ${month}`\n\t\t},\n\n\t\t/**\n\t\t * Compute the left-border colour style for an event row.\n\t\t *\n\t\t * @param {object} event the event.\n\t\t * @return {object} the style object (empty when colouring is off).\n\t\t */\n\t\teventStyle(event) {\n\t\t\tif (!this.colorByCalendar) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst color = event.color\n\t\t\tif (typeof color === 'string' && color !== '') {\n\t\t\t\treturn { 'border-left': `3px solid ${color}` }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 100%;\n\theight: 100%;\n\toverflow: hidden;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-calendar-widget__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-calendar-widget__title {\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__modes {\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n.cn-calendar-widget__mode-btn {\n\tborder: 1px solid var(--color-border);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tborder-radius: var(--border-radius);\n\tpadding: 2px 8px;\n\tfont-size: 12px;\n\tcursor: pointer;\n}\n\n.cn-calendar-widget__mode-btn.is-active {\n\tbackground: var(--color-primary-element-light, var(--color-primary-element));\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-calendar-widget__body {\n\tflex: 1;\n\toverflow: auto;\n\tpadding: 8px 12px;\n}\n\n.cn-calendar-widget__state {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tpadding: 16px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__retry {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-calendar-widget__month {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(7, 1fr);\n\tgap: 2px;\n}\n\n.cn-calendar-widget__month-header {\n\tfont-size: 11px;\n\tfont-weight: 600;\n\ttext-align: center;\n\tpadding: 4px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__month-cell {\n\tmin-height: 60px;\n\tpadding: 2px 4px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 2px;\n\tbackground: var(--color-main-background);\n}\n\n.cn-calendar-widget__month-cell.is-today {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-widget__month-cell.is-other-month {\n\topacity: 0.45;\n}\n\n.cn-calendar-widget__month-day {\n\tfont-size: 11px;\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__month-events {\n\tlist-style: none;\n\tmargin: 2px 0 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__month-event {\n\tfont-size: 10px;\n\tpadding: 1px 3px;\n\tmargin-bottom: 1px;\n\tborder-radius: 2px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-widget__month-overflow {\n\tfont-size: 10px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__week {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(7, 1fr);\n\tgap: 4px;\n}\n\n.cn-calendar-widget__week-col {\n\tdisplay: flex;\n\tflex-direction: column;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 4px;\n}\n\n.cn-calendar-widget__week-col.is-today {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-widget__week-day {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tborder-bottom: 1px solid var(--color-border);\n\tpadding-bottom: 2px;\n\tmargin-bottom: 4px;\n}\n\n.cn-calendar-widget__week-name {\n\tfont-size: 10px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__week-num {\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__week-events {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__week-event {\n\tfont-size: 11px;\n\tpadding: 2px 4px;\n\tmargin-bottom: 2px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 2px;\n}\n\n.cn-calendar-widget__week-time {\n\tfont-weight: 600;\n\tmargin-right: 4px;\n}\n\n.cn-calendar-widget__week-empty {\n\tfont-size: 11px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__agenda {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__agenda-header {\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-top: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-calendar-widget__agenda-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 6px;\n\tmargin-bottom: 2px;\n\tbackground: var(--color-background-hover);\n\tborder-radius: 2px;\n}\n\n.cn-calendar-widget__agenda-time {\n\tfont-weight: 600;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-calendar-widget__agenda-title {\n\tflex: 1;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-widget__agenda-cal {\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t"],"mappings":";;AAiIA,MAAM,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAA;AAC7C,MAAM,kBAAmB,GAAE,GAAC;;AAE5B;AACC;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;;;AAGA;AACA;;AAED,aAAe;CACd,IAAI,EAAE,kBAAkB;;AAExB,CAAA,MAAM,EAAE;AACP,EAAA,gBAAgB,EAAE;GACjB,OAAO,EAAE,IAAI;GACb;EACD;;AAED,CAAA,KAAK,EAAE;AACN;;AAEC;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,CAAC;GACnB;;AAED;;;;AAIC;AACA;;EAED,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;EACD;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,MAAM,WAAY,GAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAM,IAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAA;AAC5E,KAAE,IAAI,CAAC,OAAO,CAAC,QAAO;AACtB,KAAE,SAAO;AACV,EAAA,OAAO;GACN,UAAU,EAAE,WAAW;AACvB,GAAA,MAAM,EAAE,EAAE;GACV,QAAQ,EAAE,EAAE;GACZ,OAAO,EAAE,KAAK;GACd,KAAK,EAAE,IAAI;AACX,GAAA,KAAK,EAAE,IAAI,IAAI,EAAE;AAClB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;;;EAGD,SAAS,GAAG;AACX,GAAA,OAAO,UAAS;GAChB;;AAED;AACC;AACA;;;AAGD,EAAA,MAAM,GAAG;GACR,MAAM,SAAU,GAAE,IAAI,CAAC,cAAc,IAAI,CAAC,iBAAe;AACzD,GAAA,OAAO,aAAa,OAAO,SAAS,CAAC,WAAU,KAAM,UAAS,GAAI,SAAQ,GAAI,IAAG;GACjF;;AAED;AACC;AACA;AACA;;EAED,SAAS,GAAG;GACX,OAAO,IAAI,CAAC,MAAK,KAAM,IAAG;GAC1B;;AAED;AACC;AACA;AACA;;EAED,SAAS,GAAG;AACX,GAAA,MAAM,KAAM,GAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAQ,IAAG,IAAI,CAAC,OAAO,CAAC,SAAS,KAAK,kBAAkB,EAAE,EAAE,EAAA;GACzF,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAA,IAAK,KAAM,GAAE,CAAE,GAAE,KAAM,GAAE,kBAAiB;GACtE;;AAED;AACC;AACA;AACA;;AAED,EAAA,eAAe,GAAG;GACjB,OAAO,EAAE,IAAI,CAAC,OAAQ,IAAG,IAAI,CAAC,OAAO,CAAC,eAAgB,KAAI,KAAK,CAAA;GAC/D;;AAED;;AAEC;AACA;;;;;;AAMA;;;;;AAKA;AACA;;;AAGA;AACA;;;AAGD,EAAA,mBAAmB,GAAG;AACrB,GAAA,MAAM,CAAA,GAAI,IAAI,CAAC,QAAM;AACrB,GAAA,IAAI,CAAC,CAAC,EAAE;IACP,OAAO,KAAI;AACZ,IAAA;AACA,GAAA,MAAM,QAAO,GAAI,CAAC,CAAC,kBAAgB;AACnC,GAAA,MAAM,QAAO,GAAI,CAAC,CAAC,gBAAc;GACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAA,IAAK,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;IACzD,OAAO,KAAI;AACZ,IAAA;GACA,OAAO,CAAC,QAAS,IAAG,EAAE,EAAE,MAAO,KAAI,CAAA,IAAK,CAAC,QAAS,IAAG,EAAE,EAAE,MAAK,KAAM,CAAA;GACpE;;AAED;;AAEC;AACA;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,gCAAgC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;GACjG;;AAED;AACC;AACA;AACA;;AAED,EAAA,cAAc,GAAG;AAChB,GAAA,OAAO;IACNA,SAAC,CAAC,eAAe,EAAE,KAAK,CAAC;IACzBA,SAAC,CAAC,eAAe,EAAE,KAAK,CAAC;IACzBA,SAAC,CAAC,eAAe,EAAE,KAAK,CAAC;IACzBA,SAAC,CAAC,eAAe,EAAE,KAAK,CAAC;IACzBA,SAAC,CAAC,eAAe,EAAE,KAAK,CAAC;IACzBA,SAAC,CAAC,eAAe,EAAE,KAAK,CAAC;IACzBA,SAAC,CAAC,eAAe,EAAE,KAAK,CAAC;AAC1B,IAAA;GACA;;AAED;;AAEC;AACA;;EAED,UAAU,GAAG;AACZ,GAAA,MAAM,KAAI,GAAI,IAAI,CAAC,MAAI;GACvB,MAAM,KAAM,GAAE,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAA;GAC/D,MAAM,IAAK,GAAE,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAG,GAAE,CAAC,EAAE,CAAC,EAAA;GAClE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,IAAK,EAAA;GAC/B;;AAED;;AAEC;AACA;;EAED,SAAS,GAAG;AACX,GAAA,MAAM,KAAI,GAAI,IAAI,CAAC,MAAI;GACvB,MAAM,KAAI,GAAI,IAAI,IAAI,CAAC,KAAK,EAAA;AAC5B,GAAA,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAG,GAAE,KAAK,CAAC,MAAM,EAAE,EAAA;GAC9C,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAA;GACzB,MAAM,GAAE,GAAI,IAAI,IAAI,CAAC,KAAK,EAAA;GAC1B,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAG,GAAE,CAAC,EAAA;GAC/B,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAA;GAC5B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,GAAI,EAAA;GAC9B;;AAED;AACC;AACA;;;EAGD,WAAW,GAAG;AACb,GAAA,MAAM,OAAQ,GAAE,GAAC;GACjB,KAAK,MAAM,KAAI,IAAK,IAAI,CAAC,MAAM,EAAE;IAChC,MAAM,GAAE,GAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAA;AACtD,IAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;AAClB,KAAA,OAAO,CAAC,GAAG,CAAE,GAAE,GAAC;AACjB,KAAA;IACA,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,EAAA;AACxB,IAAA;GACA,OAAO,OAAM;GACb;;AAED;;AAEC;;;EAGD,SAAS,GAAG;GACX,MAAM,EAAE,IAAI,EAAE,EAAG,EAAA,GAAI,IAAI,CAAC,WAAS;GACnC,MAAM,KAAM,GAAE,GAAC;GACf,MAAM,YAAY,IAAI,IAAI,CAAC,IAAI,EAAA;GAC/B,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAC,GAAI,IAAI,CAAC,MAAM,EAAE,EAAA;GAChD,MAAM,YAAY,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,EAAG,GAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA,GAAI,EAAA;GAClE,KAAK,IAAI,CAAE,GAAE,CAAC,EAAE,CAAE,GAAE,SAAS,EAAE,CAAC,EAAE,EAAE;AACnC,IAAA,MAAM,QAAO,GAAI,IAAI,IAAI,CAAC,SAAS,EAAA;AACnC,IAAA,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,EAAC,GAAI,CAAC,EAAA;AACxC,IAAA,MAAM,GAAI,GAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAA;IACnC,KAAK,CAAC,IAAI,CAAC;AACV,KAAA,GAAG;KACH,MAAM,EAAE,QAAQ,CAAC,OAAO,EAAE;AAC1B,KAAA,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAE,KAAI,GAAG;KAC3C,YAAY,EAAE,QAAQ,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,EAAE;AACrD,KAAA,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAA,IAAK,EAAE;KACnC,EAAA;AACF,IAAA;GACA,OAAO,KAAI;GACX;;AAED;AACC;AACA;;;EAGD,QAAQ,GAAG;GACV,MAAM,EAAE,IAAG,EAAI,GAAE,IAAI,CAAC,UAAQ;GAC9B,MAAM,GAAE,GAAI,GAAC;AACb,GAAA,KAAK,IAAI,CAAE,GAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;AAC3B,IAAA,MAAM,MAAM,IAAI,IAAI,CAAC,IAAI,EAAA;IACzB,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAG,GAAE,CAAC,EAAA;IAC9B,MAAM,GAAI,GAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAA;AAC9B,IAAA,GAAG,CAAC,IAAI,CAAC;AACR,KAAA,GAAG;AACH,KAAA,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;AAC1C,KAAA,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE;AACrB,KAAA,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAE,KAAI,GAAG;AAC3C,KAAA,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAA,IAAK,EAAE;KACnC,EAAA;AACF,IAAA;AACA,GAAA,OAAO,GAAE;GACT;;AAED;;AAEC;AACA;;AAED,EAAA,YAAY,GAAG;GACd,MAAM,MAAO,GAAE,GAAC;GAChB,MAAM,IAAK,GAAE,GAAC;GACd,KAAK,MAAM,KAAI,IAAK,IAAI,CAAC,MAAM,EAAE;AAChC,IAAA,MAAM,IAAK,GAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,EAAA;IACvC,MAAM,GAAE,GAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAA;AAC/B,IAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;KACf,IAAI,CAAC,GAAG,CAAA,GAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAG,GAAA;AACjE,KAAA,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAA;AACtB,KAAA;IACA,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAA;AAC5B,IAAA;GACA,OAAO,MAAK;GACZ;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,OAAO,EAAE;AACR,GAAA,IAAI,EAAE,IAAI;AACV,GAAA,OAAO,GAAG;AACT,IAAA,IAAI,CAAC,WAAW,GAAC;IACjB;GACD;;;EAGD,UAAU,GAAG;AACZ,GAAA,IAAI,CAAC,WAAW,GAAC;GACjB;EACD;;AAED,CAAA,OAAO,GAAG;AACT,EAAA,IAAI,CAAC,WAAW,GAAC;EACjB;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;;AAED;;AAEC;AACA;;;EAGD,OAAO,CAAC,IAAI,EAAE;GACb,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;AAC9B,IAAA,IAAI,CAAC,aAAa,KAAG;AACtB,IAAA;GACA;;AAED;AACC;AACA;;AAEA;;AAED,EAAA,SAAS,CAAC,IAAI,EAAE;AACf,GAAA,IAAI,SAAS,OAAO,EAAE;AACrB,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA;AAClC,IAAA;AACA,GAAA,IAAI,IAAK,KAAI,MAAM,EAAE;AACpB,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA;AACjC,IAAA;AACA,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAA;GAClC;;AAED;;;AAGC;AACA;AACA;;AAED,EAAA,YAAY,GAAG;GACd,IAAI,IAAI,CAAC,UAAS,KAAM,OAAO,EAAE;IAChC,MAAM,EAAE,IAAI,EAAE,EAAG,EAAA,GAAI,IAAI,CAAC,WAAS;IACnC,MAAM,KAAM,GAAE,IAAI,IAAI,CAAC,IAAI,EAAA;IAC3B,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAA;AACzB,IAAA,MAAM,GAAI,GAAE,IAAI,IAAI,CAAC,EAAE,EAAA;IACvB,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAA;IAC5B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,GAAI,EAAA;AAC/B,IAAA;GACA,IAAI,IAAI,CAAC,UAAW,KAAI,MAAM,EAAE;AAC/B,IAAA,OAAO,IAAI,CAAC,SAAQ;AACrB,IAAA;GACA,MAAM,IAAK,GAAE,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAA;AAChC,GAAA,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAA;AACxB,GAAA,MAAM,EAAC,GAAI,IAAI,IAAI,CAAC,IAAI,EAAA;AACxB,GAAA,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAC,GAAI,IAAI,CAAC,SAAS,EAAA;GAC1C,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAA;AAC3B,GAAA,OAAO,EAAE,IAAI,EAAE,EAAG,EAAA;GAClB;;AAED;;;AAGC;;;AAGD,EAAA,MAAM,WAAW,GAAG;AACnB,GAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;IACjB,IAAI,CAAC,MAAO,GAAE,GAAC;AACf,IAAA,IAAI,CAAC,QAAO,GAAI,GAAC;IACjB,MAAK;AACN,IAAA;AACA,GAAA,MAAM,EAAE,IAAI,EAAE,EAAC,KAAM,IAAI,CAAC,YAAY,GAAC;AACvC,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,KAAM,GAAE,KAAG;AAChB,GAAA,IAAI;AACH,IAAA,MAAM,OAAM,GAAI,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;KAC7C,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE;AACxB,KAAA,EAAE,EAAE,EAAE,CAAC,WAAW,EAAE;KACpB,CAAA,IAAK,GAAC;IACP,IAAI,CAAC,MAAO,GAAE,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAE,GAAE,OAAO,CAAC,MAAO,GAAE,GAAC;AAChE,IAAA,IAAI,CAAC,QAAO,GAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAE,GAAE,OAAO,CAAC,QAAO,GAAI,GAAC;IACrE,CAAA,OAAO,GAAG,EAAE;IACb,IAAI,CAAC,KAAI,GAAI,IAAE;IACf,IAAI,CAAC,MAAO,GAAE,GAAC;AAChB,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED;;AAEC;;;;AAID,EAAA,SAAS,CAAC,KAAK,EAAE;GAChB,IAAI,CAAC,KAAK,EAAE;AACX,IAAA,OAAO,IAAI,IAAI,EAAC;AACjB,IAAA;AACA,GAAA,MAAM,CAAE,GAAE,IAAI,IAAI,CAAC,KAAK,EAAA;AACxB,GAAA,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAE,GAAE,IAAI,IAAI,KAAK,CAAA;GAChD;;AAED;;AAEC;;AAEA;;AAED,EAAA,SAAS,CAAC,IAAI,EAAE;GACf,MAAM,IAAG,GAAI,IAAI,CAAC,WAAW,GAAC;GAC9B,MAAM,EAAG,GAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAC,GAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAA;AACtD,GAAA,MAAM,EAAC,GAAI,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAA;GACjD,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;GAC3B;;AAED;AACC;AACA;;;;AAID,EAAA,UAAU,CAAC,KAAK,EAAE;AACjB,GAAA,IAAI,KAAK,CAAC,MAAM,EAAE;AACjB,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;AACpC,IAAA;AACA,GAAA,MAAM,KAAI,GAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,EAAA;GACxC,MAAM,EAAG,GAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAA;GACnD,MAAM,EAAG,GAAE,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAA;AACrD,GAAA,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;GACnB;;AAED;AACC;AACA;;AAEA;;AAED,EAAA,eAAe,CAAC,IAAI,EAAE;GACrB,MAAM,KAAM,GAAE,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,OAAM,EAAG,EAAA;AAC/D,GAAA,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAE,CAAA,EAAE,IAAI,CAAC,OAAO,EAAE,CAAA,CAAE,EAAE,KAAK,CAAC,CAAA;GACxE;;AAED;;AAEC;;;;AAID,EAAA,UAAU,CAAC,KAAK,EAAE;GACjB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE;AAC1B,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,MAAM,KAAI,GAAI,KAAK,CAAC,MAAI;AACxB,GAAA,IAAI,OAAO,KAAI,KAAM,QAAS,IAAG,UAAU,EAAE,EAAE;AAC9C,IAAA,OAAO,EAAE,aAAa,EAAE,CAAC,UAAU,EAAE,KAAK,CAAC,CAAE,EAAA;AAC9C,IAAA;AACA,GAAA,OAAO,EAAC;GACR;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnCalendarWidget.vue2.js","sources":["../../../../src/components/CnCalendarWidget/CnCalendarWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-calendar-widget\">\n\t\t<!-- `content.showTitle: false` and `content.showViewModes: false` drop\n\t\t the sub-heading and the Month / Week / Agenda buttons, for an agenda\n\t\t placement whose card already names it; without both the header\n\t\t row goes. -->\n\t\t<header v-if=\"showsTitle || showsViewModes\" class=\"cn-calendar-widget__header\">\n\t\t\t<span v-if=\"showsTitle\" class=\"cn-calendar-widget__title\">{{ t('nextcloud-vue', 'Calendar') }}</span>\n\t\t\t<div v-if=\"showsViewModes\" class=\"cn-calendar-widget__modes\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"mode in viewModes\"\n\t\t\t\t\t:key=\"mode\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-calendar-widget__mode-btn\"\n\t\t\t\t\t:class=\"{ 'is-active': activeMode === mode }\"\n\t\t\t\t\t@click=\"setMode(mode)\">\n\t\t\t\t\t{{ modeLabel(mode) }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<div class=\"cn-calendar-widget__body\">\n\t\t\t<div v-if=\"loading\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'Loading calendars…') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"error\" class=\"cn-calendar-widget__state cn-calendar-widget__state--error\">\n\t\t\t\t<p>{{ t('nextcloud-vue', 'Failed to load events') }}</p>\n\t\t\t\t<button type=\"button\" class=\"cn-calendar-widget__retry\" @click=\"fetchEvents\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Retry') }}\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"!hasSource\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'No calendar available') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"hasNoCalendarChosen\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'No calendar is selected yet. Choose one in this widget\\u2019s settings.') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"events.length === 0 && activeMode === 'agenda'\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ emptyMessage }}\n\t\t\t</div>\n\n\t\t\t<!-- Month view: 7-column grid padded to whole weeks. -->\n\t\t\t<div v-else-if=\"activeMode === 'month'\" class=\"cn-calendar-widget__month\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(weekday, idx) in weekdayHeaders\"\n\t\t\t\t\t:key=\"'wh-' + idx\"\n\t\t\t\t\tclass=\"cn-calendar-widget__month-header\">\n\t\t\t\t\t{{ weekday }}\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"day in monthGrid\"\n\t\t\t\t\t:key=\"day.iso\"\n\t\t\t\t\tclass=\"cn-calendar-widget__month-cell\"\n\t\t\t\t\t:class=\"{ 'is-today': day.isToday, 'is-other-month': day.isOtherMonth }\">\n\t\t\t\t\t<span class=\"cn-calendar-widget__month-day\">{{ day.dayNum }}</span>\n\t\t\t\t\t<ul v-if=\"day.events.length\" class=\"cn-calendar-widget__month-events\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"event in day.events.slice(0, 3)\"\n\t\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__month-event\"\n\t\t\t\t\t\t\t:title=\"event.title\"\n\t\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t\t{{ event.title }}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-if=\"day.events.length > 3\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__month-overflow\">\n\t\t\t\t\t\t\t+{{ day.events.length - 3 }}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Week view: 7 day columns. -->\n\t\t\t<div v-else-if=\"activeMode === 'week'\" class=\"cn-calendar-widget__week\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"day in weekDays\"\n\t\t\t\t\t:key=\"day.iso\"\n\t\t\t\t\tclass=\"cn-calendar-widget__week-col\"\n\t\t\t\t\t:class=\"{ 'is-today': day.isToday }\">\n\t\t\t\t\t<header class=\"cn-calendar-widget__week-day\">\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-name\">{{ day.weekday }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-num\">{{ day.dayNum }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul v-if=\"day.events.length\" class=\"cn-calendar-widget__week-events\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"event in day.events\"\n\t\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__week-event\"\n\t\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-time\">{{ formatTime(event) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-title\">{{ event.title }}</span>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t\t<p v-else class=\"cn-calendar-widget__week-empty\">\n\t\t\t\t\t\t—\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Agenda view: chronological list grouped by day. -->\n\t\t\t<ul v-else class=\"cn-calendar-widget__agenda\">\n\t\t\t\t<template v-for=\"group in agendaGroups\" :key=\"group.iso\">\n\t\t\t\t\t<li class=\"cn-calendar-widget__agenda-header\">\n\t\t\t\t\t\t{{ group.label }}\n\t\t\t\t\t</li>\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"event in group.events\"\n\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\tclass=\"cn-calendar-widget__agenda-row\"\n\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__agenda-time\">{{ formatTime(event) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__agenda-title\">{{ event.title }}</span>\n\t\t\t\t\t\t<span v-if=\"event.calendarName\" class=\"cn-calendar-widget__agenda-cal\">{{ event.calendarName }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</template>\n\t\t\t</ul>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\n\nconst VIEW_MODES = ['month', 'week', 'agenda']\nconst DEFAULT_DAYS_AHEAD = 14\n\n/**\n * CnCalendarWidget — renders aggregated upcoming events as a chronological,\n * per-day grouped agenda.\n *\n * Data origin (decoupling seam): events are read through a consumer-supplied\n * `dataSource` prop OR the `cnCalendarSource` injection — an object exposing\n * `fetchEvents({ from, to }) => Promise<{ events, failures }>`. The canonical\n * backing is the Nextcloud Calendar app (internal calendars + external ICS\n * feeds) surfaced by the consuming app; this renderer never imports a\n * sibling-app module path nor assumes a backend route. When neither prop nor\n * injection is supplied the widget shows an empty agenda (\"No calendar\n * available\") and performs no request; an empty result simply renders the\n * \"no events\" empty state.\n *\n * No event field is ever rendered via `v-html` — all values flow through Vue\n * interpolation, so a malicious ICS feed cannot inject markup.\n *\n * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n */\nexport default {\n\tname: 'CnCalendarWidget',\n\n\tinject: {\n\t\tcnCalendarSource: {\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted widget content `{viewMode, daysAhead, colorByCalendar,\n\t\t * showTitle, showViewModes}`. `showTitle: false` drops the \"Calendar\"\n\t\t * sub-heading and `showViewModes: false` the Month / Week / Agenda\n\t\t * buttons (both shown by default).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Consumer-supplied data source overriding the `cnCalendarSource`\n\t\t * injection. Must expose `fetchEvents({from, to}) => Promise<{events,\n\t\t * failures}>`. When `null` the injection (then the empty state) is used.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tdataSource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\tconst initialMode = VIEW_MODES.includes(this.content && this.content.viewMode)\n\t\t\t? this.content.viewMode\n\t\t\t: 'agenda'\n\t\treturn {\n\t\t\tactiveMode: initialMode,\n\t\t\tevents: [],\n\t\t\tfailures: [],\n\t\t\tloading: false,\n\t\t\terror: null,\n\t\t\ttoday: new Date(),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the \"Calendar\" sub-heading renders (`content.showTitle`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-agenda-placement-can-drop-the-calendar-chrome\n\t\t * @return {boolean}\n\t\t */\n\t\tshowsTitle() {\n\t\t\treturn !(this.content && this.content.showTitle === false)\n\t\t},\n\n\t\t/**\n\t\t * Whether the Month / Week / Agenda buttons render\n\t\t * (`content.showViewModes`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-agenda-placement-can-drop-the-calendar-chrome\n\t\t * @return {boolean}\n\t\t */\n\t\tshowsViewModes() {\n\t\t\treturn !(this.content && this.content.showViewModes === false)\n\t\t},\n\n\t\t/**\n\t\t * The available view modes.\n\t\t *\n\t\t * @return {string[]} the mode keys.\n\t\t */\n\t\tviewModes() {\n\t\t\treturn VIEW_MODES\n\t\t},\n\n\t\t/**\n\t\t * The active data source (prop wins over injection).\n\t\t *\n\t\t * @return {object|null} the resolved source, or `null`.\n\t\t */\n\t\tsource() {\n\t\t\tconst candidate = this.dataSource || this.cnCalendarSource\n\t\t\treturn candidate && typeof candidate.fetchEvents === 'function' ? candidate : null\n\t\t},\n\n\t\t/**\n\t\t * Whether a usable calendar source is configured.\n\t\t *\n\t\t * @return {boolean} true when a source exists.\n\t\t */\n\t\thasSource() {\n\t\t\treturn this.source !== null\n\t\t},\n\n\t\t/**\n\t\t * The validated look-ahead window in days.\n\t\t *\n\t\t * @return {number} the days-ahead value.\n\t\t */\n\t\tdaysAhead() {\n\t\t\tconst value = parseInt((this.content && this.content.daysAhead) ?? DEFAULT_DAYS_AHEAD, 10)\n\t\t\treturn Number.isFinite(value) && value > 0 ? value : DEFAULT_DAYS_AHEAD\n\t\t},\n\n\t\t/**\n\t\t * Whether events are tinted by their calendar colour.\n\t\t *\n\t\t * @return {boolean} the colorByCalendar flag.\n\t\t */\n\t\tcolorByCalendar() {\n\t\t\treturn !(this.content && this.content.colorByCalendar === false)\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget is configured with no calendar at all.\n\t\t *\n\t\t * The host fetches events for the calendars named in\n\t\t * `content.internalCalendars` / `content.externalIcsUrls`, and it\n\t\t * skips the fetch entirely when both are empty: LaunchPad's\n\t\t * `CalendarWidgetService::getEvents()` guards each branch with\n\t\t * `!== []`, so an unconfigured widget returns zero events AND zero\n\t\t * failures. That is indistinguishable from a genuinely empty week.\n\t\t *\n\t\t * It is not a rare state. The registry's own `defaultContent` for\n\t\t * this widget type is `internalCalendars: []`, so EVERY freshly added\n\t\t * Calendar widget starts here and says \"No events in the next 14\n\t\t * days\" forever, which is a false statement about the user's diary\n\t\t * rather than a prompt to finish configuring it.\n\t\t *\n\t\t * Only claims the state when both lists are present and empty. A host\n\t\t * that drives the widget through `dataSource` alone, with no content\n\t\t * blob, keeps the ordinary empty message.\n\t\t *\n\t\t * @return {boolean} true when no calendar has been chosen.\n\t\t */\n\t\thasNoCalendarChosen() {\n\t\t\tconst c = this.content\n\t\t\tif (!c) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst internal = c.internalCalendars\n\t\t\tconst external = c.externalIcsUrls\n\t\t\tif (!Array.isArray(internal) && !Array.isArray(external)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn (internal || []).length === 0 && (external || []).length === 0\n\t\t},\n\n\t\t/**\n\t\t * The empty-agenda message.\n\t\t *\n\t\t * @return {string} the empty message.\n\t\t */\n\t\temptyMessage() {\n\t\t\treturn t('nextcloud-vue', 'No events in the next {n} days').replace('{n}', String(this.daysAhead))\n\t\t},\n\n\t\t/**\n\t\t * Localised short weekday names (Sun-first).\n\t\t *\n\t\t * @return {string[]} the weekday labels.\n\t\t */\n\t\tweekdayHeaders() {\n\t\t\treturn [\n\t\t\t\tt('nextcloud-vue', 'Sun'),\n\t\t\t\tt('nextcloud-vue', 'Mon'),\n\t\t\t\tt('nextcloud-vue', 'Tue'),\n\t\t\t\tt('nextcloud-vue', 'Wed'),\n\t\t\t\tt('nextcloud-vue', 'Thu'),\n\t\t\t\tt('nextcloud-vue', 'Fri'),\n\t\t\t\tt('nextcloud-vue', 'Sat'),\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The first/last day of the current month (month-view window).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the month window.\n\t\t */\n\t\tmonthRange() {\n\t\t\tconst today = this.today\n\t\t\tconst first = new Date(today.getFullYear(), today.getMonth(), 1)\n\t\t\tconst last = new Date(today.getFullYear(), today.getMonth() + 1, 0)\n\t\t\treturn { from: first, to: last }\n\t\t},\n\n\t\t/**\n\t\t * The Sun–Sat window containing today (week-view window).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the week window.\n\t\t */\n\t\tweekRange() {\n\t\t\tconst today = this.today\n\t\t\tconst start = new Date(today)\n\t\t\tstart.setDate(today.getDate() - today.getDay())\n\t\t\tstart.setHours(0, 0, 0, 0)\n\t\t\tconst end = new Date(start)\n\t\t\tend.setDate(start.getDate() + 6)\n\t\t\tend.setHours(23, 59, 59, 999)\n\t\t\treturn { from: start, to: end }\n\t\t},\n\n\t\t/**\n\t\t * Events bucketed by `YYYY-MM-DD` for grid lookup.\n\t\t *\n\t\t * @return {Record<string, object[]>} iso-date → events.\n\t\t */\n\t\teventsByDay() {\n\t\t\tconst buckets = {}\n\t\t\tfor (const event of this.events) {\n\t\t\t\tconst iso = this.toIsoDate(this.parseDate(event.start))\n\t\t\t\tif (!buckets[iso]) {\n\t\t\t\t\tbuckets[iso] = []\n\t\t\t\t}\n\t\t\t\tbuckets[iso].push(event)\n\t\t\t}\n\t\t\treturn buckets\n\t\t},\n\n\t\t/**\n\t\t * The 7-column month grid (leading/trailing days padded to whole weeks).\n\t\t *\n\t\t * @return {Array<object>} grid cells with day metadata + events.\n\t\t */\n\t\tmonthGrid() {\n\t\t\tconst { from, to } = this.monthRange\n\t\t\tconst cells = []\n\t\t\tconst gridStart = new Date(from)\n\t\t\tgridStart.setDate(from.getDate() - from.getDay())\n\t\t\tconst totalDays = Math.ceil((to.getDate() + from.getDay()) / 7) * 7\n\t\t\tfor (let i = 0; i < totalDays; i++) {\n\t\t\t\tconst cellDate = new Date(gridStart)\n\t\t\t\tcellDate.setDate(gridStart.getDate() + i)\n\t\t\t\tconst iso = this.toIsoDate(cellDate)\n\t\t\t\tcells.push({\n\t\t\t\t\tiso,\n\t\t\t\t\tdayNum: cellDate.getDate(),\n\t\t\t\t\tisToday: this.toIsoDate(this.today) === iso,\n\t\t\t\t\tisOtherMonth: cellDate.getMonth() !== from.getMonth(),\n\t\t\t\t\tevents: this.eventsByDay[iso] || [],\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn cells\n\t\t},\n\n\t\t/**\n\t\t * The 7 day-columns for the week view.\n\t\t *\n\t\t * @return {Array<object>} day columns with metadata + events.\n\t\t */\n\t\tweekDays() {\n\t\t\tconst { from } = this.weekRange\n\t\t\tconst out = []\n\t\t\tfor (let i = 0; i < 7; i++) {\n\t\t\t\tconst day = new Date(from)\n\t\t\t\tday.setDate(from.getDate() + i)\n\t\t\t\tconst iso = this.toIsoDate(day)\n\t\t\t\tout.push({\n\t\t\t\t\tiso,\n\t\t\t\t\tweekday: this.weekdayHeaders[day.getDay()],\n\t\t\t\t\tdayNum: day.getDate(),\n\t\t\t\t\tisToday: this.toIsoDate(this.today) === iso,\n\t\t\t\t\tevents: this.eventsByDay[iso] || [],\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Events grouped into per-day buckets in chronological order.\n\t\t *\n\t\t * @return {Array<{iso: string, label: string, events: object[]}>} the groups.\n\t\t */\n\t\tagendaGroups() {\n\t\t\tconst groups = []\n\t\t\tconst seen = {}\n\t\t\tfor (const event of this.events) {\n\t\t\t\tconst date = this.parseDate(event.start)\n\t\t\t\tconst iso = this.toIsoDate(date)\n\t\t\t\tif (!seen[iso]) {\n\t\t\t\t\tseen[iso] = { iso, label: this.formatDayHeader(date), events: [] }\n\t\t\t\t\tgroups.push(seen[iso])\n\t\t\t\t}\n\t\t\t\tseen[iso].events.push(event)\n\t\t\t}\n\t\t\treturn groups\n\t\t},\n\t},\n\n\twatch: {\n\t\tcontent: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchEvents()\n\t\t\t},\n\t\t},\n\n\t\t// Month/week/agenda use different fetch windows — refetch on switch.\n\t\tactiveMode() {\n\t\t\tthis.fetchEvents()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchEvents()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Switch the active view mode.\n\t\t *\n\t\t * @param {string} mode the mode to activate.\n\t\t * @return {void}\n\t\t */\n\t\tsetMode(mode) {\n\t\t\tif (VIEW_MODES.includes(mode)) {\n\t\t\t\tthis.activeMode = mode\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The label for a view mode.\n\t\t *\n\t\t * @param {string} mode the mode key.\n\t\t * @return {string} the localised label.\n\t\t */\n\t\tmodeLabel(mode) {\n\t\t\tif (mode === 'month') {\n\t\t\t\treturn t('nextcloud-vue', 'Month')\n\t\t\t}\n\t\t\tif (mode === 'week') {\n\t\t\t\treturn t('nextcloud-vue', 'Week')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Agenda')\n\t\t},\n\n\t\t/**\n\t\t * Compute the fetch window for the active view: the whole month in\n\t\t * month view, the Sun–Sat week in week view, else the daysAhead\n\t\t * look-ahead (agenda).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the window.\n\t\t */\n\t\tcomputeRange() {\n\t\t\tif (this.activeMode === 'month') {\n\t\t\t\tconst { from, to } = this.monthRange\n\t\t\t\tconst start = new Date(from)\n\t\t\t\tstart.setHours(0, 0, 0, 0)\n\t\t\t\tconst end = new Date(to)\n\t\t\t\tend.setHours(23, 59, 59, 999)\n\t\t\t\treturn { from: start, to: end }\n\t\t\t}\n\t\t\tif (this.activeMode === 'week') {\n\t\t\t\treturn this.weekRange\n\t\t\t}\n\t\t\tconst from = new Date(this.today)\n\t\t\tfrom.setHours(0, 0, 0, 0)\n\t\t\tconst to = new Date(from)\n\t\t\tto.setDate(from.getDate() + this.daysAhead)\n\t\t\tto.setHours(23, 59, 59, 999)\n\t\t\treturn { from, to }\n\t\t},\n\n\t\t/**\n\t\t * Load events from the configured source. A missing source or a fetch\n\t\t * failure degrades to the empty / error state without throwing.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEvents() {\n\t\t\tif (!this.source) {\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failures = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { from, to } = this.computeRange()\n\t\t\tthis.loading = true\n\t\t\tthis.error = null\n\t\t\ttry {\n\t\t\t\tconst payload = await this.source.fetchEvents({\n\t\t\t\t\tfrom: from.toISOString(),\n\t\t\t\t\tto: to.toISOString(),\n\t\t\t\t}) || {}\n\t\t\t\tthis.events = Array.isArray(payload.events) ? payload.events : []\n\t\t\t\tthis.failures = Array.isArray(payload.failures) ? payload.failures : []\n\t\t\t} catch (err) {\n\t\t\t\tthis.error = err\n\t\t\t\tthis.events = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Parse a date value, falling back to now on invalid input.\n\t\t *\n\t\t * @param {string|number|Date} value the value to parse.\n\t\t * @return {Date} the parsed date.\n\t\t */\n\t\tparseDate(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn new Date()\n\t\t\t}\n\t\t\tconst d = new Date(value)\n\t\t\treturn Number.isNaN(d.getTime()) ? new Date() : d\n\t\t},\n\n\t\t/**\n\t\t * Format a date as `YYYY-MM-DD`.\n\t\t *\n\t\t * @param {Date} date the date.\n\t\t * @return {string} the ISO date string.\n\t\t */\n\t\ttoIsoDate(date) {\n\t\t\tconst yyyy = date.getFullYear()\n\t\t\tconst mm = String(date.getMonth() + 1).padStart(2, '0')\n\t\t\tconst dd = String(date.getDate()).padStart(2, '0')\n\t\t\treturn `${yyyy}-${mm}-${dd}`\n\t\t},\n\n\t\t/**\n\t\t * Format an event's start time (or \"All day\").\n\t\t *\n\t\t * @param {object} event the event.\n\t\t * @return {string} the time label.\n\t\t */\n\t\tformatTime(event) {\n\t\t\tif (event.allDay) {\n\t\t\t\treturn t('nextcloud-vue', 'All day')\n\t\t\t}\n\t\t\tconst start = this.parseDate(event.start)\n\t\t\tconst hh = String(start.getHours()).padStart(2, '0')\n\t\t\tconst mm = String(start.getMinutes()).padStart(2, '0')\n\t\t\treturn `${hh}:${mm}`\n\t\t},\n\n\t\t/**\n\t\t * Format a per-day agenda group header.\n\t\t *\n\t\t * @param {Date} date the group date.\n\t\t * @return {string} the header label.\n\t\t */\n\t\tformatDayHeader(date) {\n\t\t\tconst month = date.toLocaleString(undefined, { month: 'short' })\n\t\t\treturn `${this.weekdayHeaders[date.getDay()]} ${date.getDate()} ${month}`\n\t\t},\n\n\t\t/**\n\t\t * Compute the left-border colour style for an event row.\n\t\t *\n\t\t * @param {object} event the event.\n\t\t * @return {object} the style object (empty when colouring is off).\n\t\t */\n\t\teventStyle(event) {\n\t\t\tif (!this.colorByCalendar) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst color = event.color\n\t\t\tif (typeof color === 'string' && color !== '') {\n\t\t\t\treturn { 'border-left': `3px solid ${color}` }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 100%;\n\theight: 100%;\n\toverflow: hidden;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-calendar-widget__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-calendar-widget__title {\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__modes {\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n.cn-calendar-widget__mode-btn {\n\tborder: 1px solid var(--color-border);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tborder-radius: var(--border-radius);\n\tpadding: 2px 8px;\n\tfont-size: 12px;\n\tcursor: pointer;\n}\n\n.cn-calendar-widget__mode-btn.is-active {\n\tbackground: var(--color-primary-element-light, var(--color-primary-element));\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-calendar-widget__body {\n\tflex: 1;\n\toverflow: auto;\n\tpadding: 8px 12px;\n}\n\n.cn-calendar-widget__state {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tpadding: 16px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__retry {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-calendar-widget__month {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(7, 1fr);\n\tgap: 2px;\n}\n\n.cn-calendar-widget__month-header {\n\tfont-size: 11px;\n\tfont-weight: 600;\n\ttext-align: center;\n\tpadding: 4px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__month-cell {\n\tmin-height: 60px;\n\tpadding: 2px 4px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 2px;\n\tbackground: var(--color-main-background);\n}\n\n.cn-calendar-widget__month-cell.is-today {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-widget__month-cell.is-other-month {\n\topacity: 0.45;\n}\n\n.cn-calendar-widget__month-day {\n\tfont-size: 11px;\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__month-events {\n\tlist-style: none;\n\tmargin: 2px 0 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__month-event {\n\tfont-size: 10px;\n\tpadding: 1px 3px;\n\tmargin-bottom: 1px;\n\tborder-radius: 2px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-widget__month-overflow {\n\tfont-size: 10px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__week {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(7, 1fr);\n\tgap: 4px;\n}\n\n.cn-calendar-widget__week-col {\n\tdisplay: flex;\n\tflex-direction: column;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 4px;\n}\n\n.cn-calendar-widget__week-col.is-today {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-widget__week-day {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tborder-bottom: 1px solid var(--color-border);\n\tpadding-bottom: 2px;\n\tmargin-bottom: 4px;\n}\n\n.cn-calendar-widget__week-name {\n\tfont-size: 10px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__week-num {\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__week-events {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__week-event {\n\tfont-size: 11px;\n\tpadding: 2px 4px;\n\tmargin-bottom: 2px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 2px;\n}\n\n.cn-calendar-widget__week-time {\n\tfont-weight: 600;\n\tmargin-right: 4px;\n}\n\n.cn-calendar-widget__week-empty {\n\tfont-size: 11px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__agenda {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__agenda-header {\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-top: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-calendar-widget__agenda-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 6px;\n\tmargin-bottom: 2px;\n\tbackground: var(--color-background-hover);\n\tborder-radius: 2px;\n}\n\n.cn-calendar-widget__agenda-time {\n\tfont-weight: 600;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-calendar-widget__agenda-title {\n\tflex: 1;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-widget__agenda-cal {\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -1,9 +1,18 @@
1
- import { openBlock, createElementBlock, createElementVNode, toDisplayString, Fragment, renderList, normalizeClass, normalizeStyle, createCommentVNode } from 'vue';
1
+ import { openBlock, createElementBlock, toDisplayString, createCommentVNode, Fragment, renderList, normalizeClass, createElementVNode, normalizeStyle } from 'vue';
2
2
 
3
3
  const _hoisted_1 = { class: "cn-calendar-widget" };
4
- const _hoisted_2 = { class: "cn-calendar-widget__header" };
5
- const _hoisted_3 = { class: "cn-calendar-widget__title" };
6
- const _hoisted_4 = { class: "cn-calendar-widget__modes" };
4
+ const _hoisted_2 = {
5
+ key: 0,
6
+ class: "cn-calendar-widget__header"
7
+ };
8
+ const _hoisted_3 = {
9
+ key: 0,
10
+ class: "cn-calendar-widget__title"
11
+ };
12
+ const _hoisted_4 = {
13
+ key: 1,
14
+ class: "cn-calendar-widget__modes"
15
+ };
7
16
  const _hoisted_5 = ["onClick"];
8
17
  const _hoisted_6 = { class: "cn-calendar-widget__body" };
9
18
  const _hoisted_7 = {
@@ -71,19 +80,25 @@ const _hoisted_29 = {
71
80
 
72
81
  function render(_ctx, _cache, $props, $setup, $data, $options) {
73
82
  return (openBlock(), createElementBlock("div", _hoisted_1, [
74
- createElementVNode("header", _hoisted_2, [
75
- createElementVNode("span", _hoisted_3, toDisplayString($options.t('nextcloud-vue', 'Calendar')), 1 /* TEXT */),
76
- createElementVNode("div", _hoisted_4, [
77
- (openBlock(true), createElementBlock(Fragment, null, renderList($options.viewModes, (mode) => {
78
- return (openBlock(), createElementBlock("button", {
79
- key: mode,
80
- type: "button",
81
- class: normalizeClass(["cn-calendar-widget__mode-btn", { 'is-active': $data.activeMode === mode }]),
82
- onClick: $event => ($options.setMode(mode))
83
- }, toDisplayString($options.modeLabel(mode)), 11 /* TEXT, CLASS, PROPS */, _hoisted_5))
84
- }), 128 /* KEYED_FRAGMENT */))
85
- ])
86
- ]),
83
+ ($options.showsTitle || $options.showsViewModes)
84
+ ? (openBlock(), createElementBlock("header", _hoisted_2, [
85
+ ($options.showsTitle)
86
+ ? (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString($options.t('nextcloud-vue', 'Calendar')), 1 /* TEXT */))
87
+ : createCommentVNode("v-if", true),
88
+ ($options.showsViewModes)
89
+ ? (openBlock(), createElementBlock("div", _hoisted_4, [
90
+ (openBlock(true), createElementBlock(Fragment, null, renderList($options.viewModes, (mode) => {
91
+ return (openBlock(), createElementBlock("button", {
92
+ key: mode,
93
+ type: "button",
94
+ class: normalizeClass(["cn-calendar-widget__mode-btn", { 'is-active': $data.activeMode === mode }]),
95
+ onClick: $event => ($options.setMode(mode))
96
+ }, toDisplayString($options.modeLabel(mode)), 11 /* TEXT, CLASS, PROPS */, _hoisted_5))
97
+ }), 128 /* KEYED_FRAGMENT */))
98
+ ]))
99
+ : createCommentVNode("v-if", true)
100
+ ]))
101
+ : createCommentVNode("v-if", true),
87
102
  createElementVNode("div", _hoisted_6, [
88
103
  ($data.loading)
89
104
  ? (openBlock(), createElementBlock("div", _hoisted_7, toDisplayString($options.t('nextcloud-vue', 'Loading calendars…')), 1 /* TEXT */))
@@ -1 +1 @@
1
- {"version":3,"file":"CnCalendarWidget.vue3.js","sources":["../../../../src/components/CnCalendarWidget/CnCalendarWidget.vue?vue&type=template&id=41197e6f&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-calendar-widget\">\n\t\t<header class=\"cn-calendar-widget__header\">\n\t\t\t<span class=\"cn-calendar-widget__title\">{{ t('nextcloud-vue', 'Calendar') }}</span>\n\t\t\t<div class=\"cn-calendar-widget__modes\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"mode in viewModes\"\n\t\t\t\t\t:key=\"mode\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-calendar-widget__mode-btn\"\n\t\t\t\t\t:class=\"{ 'is-active': activeMode === mode }\"\n\t\t\t\t\t@click=\"setMode(mode)\">\n\t\t\t\t\t{{ modeLabel(mode) }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<div class=\"cn-calendar-widget__body\">\n\t\t\t<div v-if=\"loading\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'Loading calendars…') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"error\" class=\"cn-calendar-widget__state cn-calendar-widget__state--error\">\n\t\t\t\t<p>{{ t('nextcloud-vue', 'Failed to load events') }}</p>\n\t\t\t\t<button type=\"button\" class=\"cn-calendar-widget__retry\" @click=\"fetchEvents\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Retry') }}\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"!hasSource\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'No calendar available') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"hasNoCalendarChosen\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'No calendar is selected yet. Choose one in this widget\\u2019s settings.') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"events.length === 0 && activeMode === 'agenda'\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ emptyMessage }}\n\t\t\t</div>\n\n\t\t\t<!-- Month view: 7-column grid padded to whole weeks. -->\n\t\t\t<div v-else-if=\"activeMode === 'month'\" class=\"cn-calendar-widget__month\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(weekday, idx) in weekdayHeaders\"\n\t\t\t\t\t:key=\"'wh-' + idx\"\n\t\t\t\t\tclass=\"cn-calendar-widget__month-header\">\n\t\t\t\t\t{{ weekday }}\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"day in monthGrid\"\n\t\t\t\t\t:key=\"day.iso\"\n\t\t\t\t\tclass=\"cn-calendar-widget__month-cell\"\n\t\t\t\t\t:class=\"{ 'is-today': day.isToday, 'is-other-month': day.isOtherMonth }\">\n\t\t\t\t\t<span class=\"cn-calendar-widget__month-day\">{{ day.dayNum }}</span>\n\t\t\t\t\t<ul v-if=\"day.events.length\" class=\"cn-calendar-widget__month-events\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"event in day.events.slice(0, 3)\"\n\t\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__month-event\"\n\t\t\t\t\t\t\t:title=\"event.title\"\n\t\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t\t{{ event.title }}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-if=\"day.events.length > 3\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__month-overflow\">\n\t\t\t\t\t\t\t+{{ day.events.length - 3 }}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Week view: 7 day columns. -->\n\t\t\t<div v-else-if=\"activeMode === 'week'\" class=\"cn-calendar-widget__week\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"day in weekDays\"\n\t\t\t\t\t:key=\"day.iso\"\n\t\t\t\t\tclass=\"cn-calendar-widget__week-col\"\n\t\t\t\t\t:class=\"{ 'is-today': day.isToday }\">\n\t\t\t\t\t<header class=\"cn-calendar-widget__week-day\">\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-name\">{{ day.weekday }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-num\">{{ day.dayNum }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul v-if=\"day.events.length\" class=\"cn-calendar-widget__week-events\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"event in day.events\"\n\t\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__week-event\"\n\t\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-time\">{{ formatTime(event) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-title\">{{ event.title }}</span>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t\t<p v-else class=\"cn-calendar-widget__week-empty\">\n\t\t\t\t\t\t—\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Agenda view: chronological list grouped by day. -->\n\t\t\t<ul v-else class=\"cn-calendar-widget__agenda\">\n\t\t\t\t<template v-for=\"group in agendaGroups\" :key=\"group.iso\">\n\t\t\t\t\t<li class=\"cn-calendar-widget__agenda-header\">\n\t\t\t\t\t\t{{ group.label }}\n\t\t\t\t\t</li>\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"event in group.events\"\n\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\tclass=\"cn-calendar-widget__agenda-row\"\n\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__agenda-time\">{{ formatTime(event) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__agenda-title\">{{ event.title }}</span>\n\t\t\t\t\t\t<span v-if=\"event.calendarName\" class=\"cn-calendar-widget__agenda-cal\">{{ event.calendarName }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</template>\n\t\t\t</ul>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\n\nconst VIEW_MODES = ['month', 'week', 'agenda']\nconst DEFAULT_DAYS_AHEAD = 14\n\n/**\n * CnCalendarWidget — renders aggregated upcoming events as a chronological,\n * per-day grouped agenda.\n *\n * Data origin (decoupling seam): events are read through a consumer-supplied\n * `dataSource` prop OR the `cnCalendarSource` injection — an object exposing\n * `fetchEvents({ from, to }) => Promise<{ events, failures }>`. The canonical\n * backing is the Nextcloud Calendar app (internal calendars + external ICS\n * feeds) surfaced by the consuming app; this renderer never imports a\n * sibling-app module path nor assumes a backend route. When neither prop nor\n * injection is supplied the widget shows an empty agenda (\"No calendar\n * available\") and performs no request; an empty result simply renders the\n * \"no events\" empty state.\n *\n * No event field is ever rendered via `v-html` — all values flow through Vue\n * interpolation, so a malicious ICS feed cannot inject markup.\n *\n * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n */\nexport default {\n\tname: 'CnCalendarWidget',\n\n\tinject: {\n\t\tcnCalendarSource: {\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted widget content `{viewMode, daysAhead, colorByCalendar}`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Consumer-supplied data source overriding the `cnCalendarSource`\n\t\t * injection. Must expose `fetchEvents({from, to}) => Promise<{events,\n\t\t * failures}>`. When `null` the injection (then the empty state) is used.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tdataSource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\tconst initialMode = VIEW_MODES.includes(this.content && this.content.viewMode)\n\t\t\t? this.content.viewMode\n\t\t\t: 'agenda'\n\t\treturn {\n\t\t\tactiveMode: initialMode,\n\t\t\tevents: [],\n\t\t\tfailures: [],\n\t\t\tloading: false,\n\t\t\terror: null,\n\t\t\ttoday: new Date(),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The available view modes.\n\t\t *\n\t\t * @return {string[]} the mode keys.\n\t\t */\n\t\tviewModes() {\n\t\t\treturn VIEW_MODES\n\t\t},\n\n\t\t/**\n\t\t * The active data source (prop wins over injection).\n\t\t *\n\t\t * @return {object|null} the resolved source, or `null`.\n\t\t */\n\t\tsource() {\n\t\t\tconst candidate = this.dataSource || this.cnCalendarSource\n\t\t\treturn candidate && typeof candidate.fetchEvents === 'function' ? candidate : null\n\t\t},\n\n\t\t/**\n\t\t * Whether a usable calendar source is configured.\n\t\t *\n\t\t * @return {boolean} true when a source exists.\n\t\t */\n\t\thasSource() {\n\t\t\treturn this.source !== null\n\t\t},\n\n\t\t/**\n\t\t * The validated look-ahead window in days.\n\t\t *\n\t\t * @return {number} the days-ahead value.\n\t\t */\n\t\tdaysAhead() {\n\t\t\tconst value = parseInt((this.content && this.content.daysAhead) ?? DEFAULT_DAYS_AHEAD, 10)\n\t\t\treturn Number.isFinite(value) && value > 0 ? value : DEFAULT_DAYS_AHEAD\n\t\t},\n\n\t\t/**\n\t\t * Whether events are tinted by their calendar colour.\n\t\t *\n\t\t * @return {boolean} the colorByCalendar flag.\n\t\t */\n\t\tcolorByCalendar() {\n\t\t\treturn !(this.content && this.content.colorByCalendar === false)\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget is configured with no calendar at all.\n\t\t *\n\t\t * The host fetches events for the calendars named in\n\t\t * `content.internalCalendars` / `content.externalIcsUrls`, and it\n\t\t * skips the fetch entirely when both are empty: LaunchPad's\n\t\t * `CalendarWidgetService::getEvents()` guards each branch with\n\t\t * `!== []`, so an unconfigured widget returns zero events AND zero\n\t\t * failures. That is indistinguishable from a genuinely empty week.\n\t\t *\n\t\t * It is not a rare state. The registry's own `defaultContent` for\n\t\t * this widget type is `internalCalendars: []`, so EVERY freshly added\n\t\t * Calendar widget starts here and says \"No events in the next 14\n\t\t * days\" forever, which is a false statement about the user's diary\n\t\t * rather than a prompt to finish configuring it.\n\t\t *\n\t\t * Only claims the state when both lists are present and empty. A host\n\t\t * that drives the widget through `dataSource` alone, with no content\n\t\t * blob, keeps the ordinary empty message.\n\t\t *\n\t\t * @return {boolean} true when no calendar has been chosen.\n\t\t */\n\t\thasNoCalendarChosen() {\n\t\t\tconst c = this.content\n\t\t\tif (!c) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst internal = c.internalCalendars\n\t\t\tconst external = c.externalIcsUrls\n\t\t\tif (!Array.isArray(internal) && !Array.isArray(external)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn (internal || []).length === 0 && (external || []).length === 0\n\t\t},\n\n\t\t/**\n\t\t * The empty-agenda message.\n\t\t *\n\t\t * @return {string} the empty message.\n\t\t */\n\t\temptyMessage() {\n\t\t\treturn t('nextcloud-vue', 'No events in the next {n} days').replace('{n}', String(this.daysAhead))\n\t\t},\n\n\t\t/**\n\t\t * Localised short weekday names (Sun-first).\n\t\t *\n\t\t * @return {string[]} the weekday labels.\n\t\t */\n\t\tweekdayHeaders() {\n\t\t\treturn [\n\t\t\t\tt('nextcloud-vue', 'Sun'),\n\t\t\t\tt('nextcloud-vue', 'Mon'),\n\t\t\t\tt('nextcloud-vue', 'Tue'),\n\t\t\t\tt('nextcloud-vue', 'Wed'),\n\t\t\t\tt('nextcloud-vue', 'Thu'),\n\t\t\t\tt('nextcloud-vue', 'Fri'),\n\t\t\t\tt('nextcloud-vue', 'Sat'),\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The first/last day of the current month (month-view window).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the month window.\n\t\t */\n\t\tmonthRange() {\n\t\t\tconst today = this.today\n\t\t\tconst first = new Date(today.getFullYear(), today.getMonth(), 1)\n\t\t\tconst last = new Date(today.getFullYear(), today.getMonth() + 1, 0)\n\t\t\treturn { from: first, to: last }\n\t\t},\n\n\t\t/**\n\t\t * The Sun–Sat window containing today (week-view window).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the week window.\n\t\t */\n\t\tweekRange() {\n\t\t\tconst today = this.today\n\t\t\tconst start = new Date(today)\n\t\t\tstart.setDate(today.getDate() - today.getDay())\n\t\t\tstart.setHours(0, 0, 0, 0)\n\t\t\tconst end = new Date(start)\n\t\t\tend.setDate(start.getDate() + 6)\n\t\t\tend.setHours(23, 59, 59, 999)\n\t\t\treturn { from: start, to: end }\n\t\t},\n\n\t\t/**\n\t\t * Events bucketed by `YYYY-MM-DD` for grid lookup.\n\t\t *\n\t\t * @return {Record<string, object[]>} iso-date → events.\n\t\t */\n\t\teventsByDay() {\n\t\t\tconst buckets = {}\n\t\t\tfor (const event of this.events) {\n\t\t\t\tconst iso = this.toIsoDate(this.parseDate(event.start))\n\t\t\t\tif (!buckets[iso]) {\n\t\t\t\t\tbuckets[iso] = []\n\t\t\t\t}\n\t\t\t\tbuckets[iso].push(event)\n\t\t\t}\n\t\t\treturn buckets\n\t\t},\n\n\t\t/**\n\t\t * The 7-column month grid (leading/trailing days padded to whole weeks).\n\t\t *\n\t\t * @return {Array<object>} grid cells with day metadata + events.\n\t\t */\n\t\tmonthGrid() {\n\t\t\tconst { from, to } = this.monthRange\n\t\t\tconst cells = []\n\t\t\tconst gridStart = new Date(from)\n\t\t\tgridStart.setDate(from.getDate() - from.getDay())\n\t\t\tconst totalDays = Math.ceil((to.getDate() + from.getDay()) / 7) * 7\n\t\t\tfor (let i = 0; i < totalDays; i++) {\n\t\t\t\tconst cellDate = new Date(gridStart)\n\t\t\t\tcellDate.setDate(gridStart.getDate() + i)\n\t\t\t\tconst iso = this.toIsoDate(cellDate)\n\t\t\t\tcells.push({\n\t\t\t\t\tiso,\n\t\t\t\t\tdayNum: cellDate.getDate(),\n\t\t\t\t\tisToday: this.toIsoDate(this.today) === iso,\n\t\t\t\t\tisOtherMonth: cellDate.getMonth() !== from.getMonth(),\n\t\t\t\t\tevents: this.eventsByDay[iso] || [],\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn cells\n\t\t},\n\n\t\t/**\n\t\t * The 7 day-columns for the week view.\n\t\t *\n\t\t * @return {Array<object>} day columns with metadata + events.\n\t\t */\n\t\tweekDays() {\n\t\t\tconst { from } = this.weekRange\n\t\t\tconst out = []\n\t\t\tfor (let i = 0; i < 7; i++) {\n\t\t\t\tconst day = new Date(from)\n\t\t\t\tday.setDate(from.getDate() + i)\n\t\t\t\tconst iso = this.toIsoDate(day)\n\t\t\t\tout.push({\n\t\t\t\t\tiso,\n\t\t\t\t\tweekday: this.weekdayHeaders[day.getDay()],\n\t\t\t\t\tdayNum: day.getDate(),\n\t\t\t\t\tisToday: this.toIsoDate(this.today) === iso,\n\t\t\t\t\tevents: this.eventsByDay[iso] || [],\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Events grouped into per-day buckets in chronological order.\n\t\t *\n\t\t * @return {Array<{iso: string, label: string, events: object[]}>} the groups.\n\t\t */\n\t\tagendaGroups() {\n\t\t\tconst groups = []\n\t\t\tconst seen = {}\n\t\t\tfor (const event of this.events) {\n\t\t\t\tconst date = this.parseDate(event.start)\n\t\t\t\tconst iso = this.toIsoDate(date)\n\t\t\t\tif (!seen[iso]) {\n\t\t\t\t\tseen[iso] = { iso, label: this.formatDayHeader(date), events: [] }\n\t\t\t\t\tgroups.push(seen[iso])\n\t\t\t\t}\n\t\t\t\tseen[iso].events.push(event)\n\t\t\t}\n\t\t\treturn groups\n\t\t},\n\t},\n\n\twatch: {\n\t\tcontent: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchEvents()\n\t\t\t},\n\t\t},\n\n\t\t// Month/week/agenda use different fetch windows — refetch on switch.\n\t\tactiveMode() {\n\t\t\tthis.fetchEvents()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchEvents()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Switch the active view mode.\n\t\t *\n\t\t * @param {string} mode the mode to activate.\n\t\t * @return {void}\n\t\t */\n\t\tsetMode(mode) {\n\t\t\tif (VIEW_MODES.includes(mode)) {\n\t\t\t\tthis.activeMode = mode\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The label for a view mode.\n\t\t *\n\t\t * @param {string} mode the mode key.\n\t\t * @return {string} the localised label.\n\t\t */\n\t\tmodeLabel(mode) {\n\t\t\tif (mode === 'month') {\n\t\t\t\treturn t('nextcloud-vue', 'Month')\n\t\t\t}\n\t\t\tif (mode === 'week') {\n\t\t\t\treturn t('nextcloud-vue', 'Week')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Agenda')\n\t\t},\n\n\t\t/**\n\t\t * Compute the fetch window for the active view: the whole month in\n\t\t * month view, the Sun–Sat week in week view, else the daysAhead\n\t\t * look-ahead (agenda).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the window.\n\t\t */\n\t\tcomputeRange() {\n\t\t\tif (this.activeMode === 'month') {\n\t\t\t\tconst { from, to } = this.monthRange\n\t\t\t\tconst start = new Date(from)\n\t\t\t\tstart.setHours(0, 0, 0, 0)\n\t\t\t\tconst end = new Date(to)\n\t\t\t\tend.setHours(23, 59, 59, 999)\n\t\t\t\treturn { from: start, to: end }\n\t\t\t}\n\t\t\tif (this.activeMode === 'week') {\n\t\t\t\treturn this.weekRange\n\t\t\t}\n\t\t\tconst from = new Date(this.today)\n\t\t\tfrom.setHours(0, 0, 0, 0)\n\t\t\tconst to = new Date(from)\n\t\t\tto.setDate(from.getDate() + this.daysAhead)\n\t\t\tto.setHours(23, 59, 59, 999)\n\t\t\treturn { from, to }\n\t\t},\n\n\t\t/**\n\t\t * Load events from the configured source. A missing source or a fetch\n\t\t * failure degrades to the empty / error state without throwing.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEvents() {\n\t\t\tif (!this.source) {\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failures = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { from, to } = this.computeRange()\n\t\t\tthis.loading = true\n\t\t\tthis.error = null\n\t\t\ttry {\n\t\t\t\tconst payload = await this.source.fetchEvents({\n\t\t\t\t\tfrom: from.toISOString(),\n\t\t\t\t\tto: to.toISOString(),\n\t\t\t\t}) || {}\n\t\t\t\tthis.events = Array.isArray(payload.events) ? payload.events : []\n\t\t\t\tthis.failures = Array.isArray(payload.failures) ? payload.failures : []\n\t\t\t} catch (err) {\n\t\t\t\tthis.error = err\n\t\t\t\tthis.events = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Parse a date value, falling back to now on invalid input.\n\t\t *\n\t\t * @param {string|number|Date} value the value to parse.\n\t\t * @return {Date} the parsed date.\n\t\t */\n\t\tparseDate(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn new Date()\n\t\t\t}\n\t\t\tconst d = new Date(value)\n\t\t\treturn Number.isNaN(d.getTime()) ? new Date() : d\n\t\t},\n\n\t\t/**\n\t\t * Format a date as `YYYY-MM-DD`.\n\t\t *\n\t\t * @param {Date} date the date.\n\t\t * @return {string} the ISO date string.\n\t\t */\n\t\ttoIsoDate(date) {\n\t\t\tconst yyyy = date.getFullYear()\n\t\t\tconst mm = String(date.getMonth() + 1).padStart(2, '0')\n\t\t\tconst dd = String(date.getDate()).padStart(2, '0')\n\t\t\treturn `${yyyy}-${mm}-${dd}`\n\t\t},\n\n\t\t/**\n\t\t * Format an event's start time (or \"All day\").\n\t\t *\n\t\t * @param {object} event the event.\n\t\t * @return {string} the time label.\n\t\t */\n\t\tformatTime(event) {\n\t\t\tif (event.allDay) {\n\t\t\t\treturn t('nextcloud-vue', 'All day')\n\t\t\t}\n\t\t\tconst start = this.parseDate(event.start)\n\t\t\tconst hh = String(start.getHours()).padStart(2, '0')\n\t\t\tconst mm = String(start.getMinutes()).padStart(2, '0')\n\t\t\treturn `${hh}:${mm}`\n\t\t},\n\n\t\t/**\n\t\t * Format a per-day agenda group header.\n\t\t *\n\t\t * @param {Date} date the group date.\n\t\t * @return {string} the header label.\n\t\t */\n\t\tformatDayHeader(date) {\n\t\t\tconst month = date.toLocaleString(undefined, { month: 'short' })\n\t\t\treturn `${this.weekdayHeaders[date.getDay()]} ${date.getDate()} ${month}`\n\t\t},\n\n\t\t/**\n\t\t * Compute the left-border colour style for an event row.\n\t\t *\n\t\t * @param {object} event the event.\n\t\t * @return {object} the style object (empty when colouring is off).\n\t\t */\n\t\teventStyle(event) {\n\t\t\tif (!this.colorByCalendar) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst color = event.color\n\t\t\tif (typeof color === 'string' && color !== '') {\n\t\t\t\treturn { 'border-left': `3px solid ${color}` }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 100%;\n\theight: 100%;\n\toverflow: hidden;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-calendar-widget__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-calendar-widget__title {\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__modes {\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n.cn-calendar-widget__mode-btn {\n\tborder: 1px solid var(--color-border);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tborder-radius: var(--border-radius);\n\tpadding: 2px 8px;\n\tfont-size: 12px;\n\tcursor: pointer;\n}\n\n.cn-calendar-widget__mode-btn.is-active {\n\tbackground: var(--color-primary-element-light, var(--color-primary-element));\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-calendar-widget__body {\n\tflex: 1;\n\toverflow: auto;\n\tpadding: 8px 12px;\n}\n\n.cn-calendar-widget__state {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tpadding: 16px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__retry {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-calendar-widget__month {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(7, 1fr);\n\tgap: 2px;\n}\n\n.cn-calendar-widget__month-header {\n\tfont-size: 11px;\n\tfont-weight: 600;\n\ttext-align: center;\n\tpadding: 4px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__month-cell {\n\tmin-height: 60px;\n\tpadding: 2px 4px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 2px;\n\tbackground: var(--color-main-background);\n}\n\n.cn-calendar-widget__month-cell.is-today {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-widget__month-cell.is-other-month {\n\topacity: 0.45;\n}\n\n.cn-calendar-widget__month-day {\n\tfont-size: 11px;\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__month-events {\n\tlist-style: none;\n\tmargin: 2px 0 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__month-event {\n\tfont-size: 10px;\n\tpadding: 1px 3px;\n\tmargin-bottom: 1px;\n\tborder-radius: 2px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-widget__month-overflow {\n\tfont-size: 10px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__week {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(7, 1fr);\n\tgap: 4px;\n}\n\n.cn-calendar-widget__week-col {\n\tdisplay: flex;\n\tflex-direction: column;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 4px;\n}\n\n.cn-calendar-widget__week-col.is-today {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-widget__week-day {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tborder-bottom: 1px solid var(--color-border);\n\tpadding-bottom: 2px;\n\tmargin-bottom: 4px;\n}\n\n.cn-calendar-widget__week-name {\n\tfont-size: 10px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__week-num {\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__week-events {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__week-event {\n\tfont-size: 11px;\n\tpadding: 2px 4px;\n\tmargin-bottom: 2px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 2px;\n}\n\n.cn-calendar-widget__week-time {\n\tfont-weight: 600;\n\tmargin-right: 4px;\n}\n\n.cn-calendar-widget__week-empty {\n\tfont-size: 11px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__agenda {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__agenda-header {\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-top: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-calendar-widget__agenda-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 6px;\n\tmargin-bottom: 2px;\n\tbackground: var(--color-background-hover);\n\tborder-radius: 2px;\n}\n\n.cn-calendar-widget__agenda-time {\n\tfont-weight: 600;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-calendar-widget__agenda-title {\n\tflex: 1;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-widget__agenda-cal {\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_toDisplayString","_createElementVNode","_createElementBlock","_Fragment","_renderList","_normalizeClass","_openBlock","_normalizeStyle"],"mappings":"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"}
1
+ {"version":3,"file":"CnCalendarWidget.vue3.js","sources":["../../../../src/components/CnCalendarWidget/CnCalendarWidget.vue?vue&type=template&id=41197e6f&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-calendar-widget\">\n\t\t<!-- `content.showTitle: false` and `content.showViewModes: false` drop\n\t\t the sub-heading and the Month / Week / Agenda buttons, for an agenda\n\t\t placement whose card already names it; without both the header\n\t\t row goes. -->\n\t\t<header v-if=\"showsTitle || showsViewModes\" class=\"cn-calendar-widget__header\">\n\t\t\t<span v-if=\"showsTitle\" class=\"cn-calendar-widget__title\">{{ t('nextcloud-vue', 'Calendar') }}</span>\n\t\t\t<div v-if=\"showsViewModes\" class=\"cn-calendar-widget__modes\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"mode in viewModes\"\n\t\t\t\t\t:key=\"mode\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-calendar-widget__mode-btn\"\n\t\t\t\t\t:class=\"{ 'is-active': activeMode === mode }\"\n\t\t\t\t\t@click=\"setMode(mode)\">\n\t\t\t\t\t{{ modeLabel(mode) }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<div class=\"cn-calendar-widget__body\">\n\t\t\t<div v-if=\"loading\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'Loading calendars…') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"error\" class=\"cn-calendar-widget__state cn-calendar-widget__state--error\">\n\t\t\t\t<p>{{ t('nextcloud-vue', 'Failed to load events') }}</p>\n\t\t\t\t<button type=\"button\" class=\"cn-calendar-widget__retry\" @click=\"fetchEvents\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Retry') }}\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"!hasSource\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'No calendar available') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"hasNoCalendarChosen\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ t('nextcloud-vue', 'No calendar is selected yet. Choose one in this widget\\u2019s settings.') }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"events.length === 0 && activeMode === 'agenda'\" class=\"cn-calendar-widget__state\">\n\t\t\t\t{{ emptyMessage }}\n\t\t\t</div>\n\n\t\t\t<!-- Month view: 7-column grid padded to whole weeks. -->\n\t\t\t<div v-else-if=\"activeMode === 'month'\" class=\"cn-calendar-widget__month\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(weekday, idx) in weekdayHeaders\"\n\t\t\t\t\t:key=\"'wh-' + idx\"\n\t\t\t\t\tclass=\"cn-calendar-widget__month-header\">\n\t\t\t\t\t{{ weekday }}\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"day in monthGrid\"\n\t\t\t\t\t:key=\"day.iso\"\n\t\t\t\t\tclass=\"cn-calendar-widget__month-cell\"\n\t\t\t\t\t:class=\"{ 'is-today': day.isToday, 'is-other-month': day.isOtherMonth }\">\n\t\t\t\t\t<span class=\"cn-calendar-widget__month-day\">{{ day.dayNum }}</span>\n\t\t\t\t\t<ul v-if=\"day.events.length\" class=\"cn-calendar-widget__month-events\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"event in day.events.slice(0, 3)\"\n\t\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__month-event\"\n\t\t\t\t\t\t\t:title=\"event.title\"\n\t\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t\t{{ event.title }}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-if=\"day.events.length > 3\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__month-overflow\">\n\t\t\t\t\t\t\t+{{ day.events.length - 3 }}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Week view: 7 day columns. -->\n\t\t\t<div v-else-if=\"activeMode === 'week'\" class=\"cn-calendar-widget__week\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"day in weekDays\"\n\t\t\t\t\t:key=\"day.iso\"\n\t\t\t\t\tclass=\"cn-calendar-widget__week-col\"\n\t\t\t\t\t:class=\"{ 'is-today': day.isToday }\">\n\t\t\t\t\t<header class=\"cn-calendar-widget__week-day\">\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-name\">{{ day.weekday }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-num\">{{ day.dayNum }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul v-if=\"day.events.length\" class=\"cn-calendar-widget__week-events\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"event in day.events\"\n\t\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\t\tclass=\"cn-calendar-widget__week-event\"\n\t\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-time\">{{ formatTime(event) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-calendar-widget__week-title\">{{ event.title }}</span>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t\t<p v-else class=\"cn-calendar-widget__week-empty\">\n\t\t\t\t\t\t—\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Agenda view: chronological list grouped by day. -->\n\t\t\t<ul v-else class=\"cn-calendar-widget__agenda\">\n\t\t\t\t<template v-for=\"group in agendaGroups\" :key=\"group.iso\">\n\t\t\t\t\t<li class=\"cn-calendar-widget__agenda-header\">\n\t\t\t\t\t\t{{ group.label }}\n\t\t\t\t\t</li>\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"event in group.events\"\n\t\t\t\t\t\t:key=\"event.uid + '-' + event.start\"\n\t\t\t\t\t\tclass=\"cn-calendar-widget__agenda-row\"\n\t\t\t\t\t\t:style=\"eventStyle(event)\">\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__agenda-time\">{{ formatTime(event) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-widget__agenda-title\">{{ event.title }}</span>\n\t\t\t\t\t\t<span v-if=\"event.calendarName\" class=\"cn-calendar-widget__agenda-cal\">{{ event.calendarName }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</template>\n\t\t\t</ul>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\n\nconst VIEW_MODES = ['month', 'week', 'agenda']\nconst DEFAULT_DAYS_AHEAD = 14\n\n/**\n * CnCalendarWidget — renders aggregated upcoming events as a chronological,\n * per-day grouped agenda.\n *\n * Data origin (decoupling seam): events are read through a consumer-supplied\n * `dataSource` prop OR the `cnCalendarSource` injection — an object exposing\n * `fetchEvents({ from, to }) => Promise<{ events, failures }>`. The canonical\n * backing is the Nextcloud Calendar app (internal calendars + external ICS\n * feeds) surfaced by the consuming app; this renderer never imports a\n * sibling-app module path nor assumes a backend route. When neither prop nor\n * injection is supplied the widget shows an empty agenda (\"No calendar\n * available\") and performs no request; an empty result simply renders the\n * \"no events\" empty state.\n *\n * No event field is ever rendered via `v-html` — all values flow through Vue\n * interpolation, so a malicious ICS feed cannot inject markup.\n *\n * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n */\nexport default {\n\tname: 'CnCalendarWidget',\n\n\tinject: {\n\t\tcnCalendarSource: {\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted widget content `{viewMode, daysAhead, colorByCalendar,\n\t\t * showTitle, showViewModes}`. `showTitle: false` drops the \"Calendar\"\n\t\t * sub-heading and `showViewModes: false` the Month / Week / Agenda\n\t\t * buttons (both shown by default).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Consumer-supplied data source overriding the `cnCalendarSource`\n\t\t * injection. Must expose `fetchEvents({from, to}) => Promise<{events,\n\t\t * failures}>`. When `null` the injection (then the empty state) is used.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tdataSource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\tconst initialMode = VIEW_MODES.includes(this.content && this.content.viewMode)\n\t\t\t? this.content.viewMode\n\t\t\t: 'agenda'\n\t\treturn {\n\t\t\tactiveMode: initialMode,\n\t\t\tevents: [],\n\t\t\tfailures: [],\n\t\t\tloading: false,\n\t\t\terror: null,\n\t\t\ttoday: new Date(),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the \"Calendar\" sub-heading renders (`content.showTitle`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-agenda-placement-can-drop-the-calendar-chrome\n\t\t * @return {boolean}\n\t\t */\n\t\tshowsTitle() {\n\t\t\treturn !(this.content && this.content.showTitle === false)\n\t\t},\n\n\t\t/**\n\t\t * Whether the Month / Week / Agenda buttons render\n\t\t * (`content.showViewModes`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-agenda-placement-can-drop-the-calendar-chrome\n\t\t * @return {boolean}\n\t\t */\n\t\tshowsViewModes() {\n\t\t\treturn !(this.content && this.content.showViewModes === false)\n\t\t},\n\n\t\t/**\n\t\t * The available view modes.\n\t\t *\n\t\t * @return {string[]} the mode keys.\n\t\t */\n\t\tviewModes() {\n\t\t\treturn VIEW_MODES\n\t\t},\n\n\t\t/**\n\t\t * The active data source (prop wins over injection).\n\t\t *\n\t\t * @return {object|null} the resolved source, or `null`.\n\t\t */\n\t\tsource() {\n\t\t\tconst candidate = this.dataSource || this.cnCalendarSource\n\t\t\treturn candidate && typeof candidate.fetchEvents === 'function' ? candidate : null\n\t\t},\n\n\t\t/**\n\t\t * Whether a usable calendar source is configured.\n\t\t *\n\t\t * @return {boolean} true when a source exists.\n\t\t */\n\t\thasSource() {\n\t\t\treturn this.source !== null\n\t\t},\n\n\t\t/**\n\t\t * The validated look-ahead window in days.\n\t\t *\n\t\t * @return {number} the days-ahead value.\n\t\t */\n\t\tdaysAhead() {\n\t\t\tconst value = parseInt((this.content && this.content.daysAhead) ?? DEFAULT_DAYS_AHEAD, 10)\n\t\t\treturn Number.isFinite(value) && value > 0 ? value : DEFAULT_DAYS_AHEAD\n\t\t},\n\n\t\t/**\n\t\t * Whether events are tinted by their calendar colour.\n\t\t *\n\t\t * @return {boolean} the colorByCalendar flag.\n\t\t */\n\t\tcolorByCalendar() {\n\t\t\treturn !(this.content && this.content.colorByCalendar === false)\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget is configured with no calendar at all.\n\t\t *\n\t\t * The host fetches events for the calendars named in\n\t\t * `content.internalCalendars` / `content.externalIcsUrls`, and it\n\t\t * skips the fetch entirely when both are empty: LaunchPad's\n\t\t * `CalendarWidgetService::getEvents()` guards each branch with\n\t\t * `!== []`, so an unconfigured widget returns zero events AND zero\n\t\t * failures. That is indistinguishable from a genuinely empty week.\n\t\t *\n\t\t * It is not a rare state. The registry's own `defaultContent` for\n\t\t * this widget type is `internalCalendars: []`, so EVERY freshly added\n\t\t * Calendar widget starts here and says \"No events in the next 14\n\t\t * days\" forever, which is a false statement about the user's diary\n\t\t * rather than a prompt to finish configuring it.\n\t\t *\n\t\t * Only claims the state when both lists are present and empty. A host\n\t\t * that drives the widget through `dataSource` alone, with no content\n\t\t * blob, keeps the ordinary empty message.\n\t\t *\n\t\t * @return {boolean} true when no calendar has been chosen.\n\t\t */\n\t\thasNoCalendarChosen() {\n\t\t\tconst c = this.content\n\t\t\tif (!c) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst internal = c.internalCalendars\n\t\t\tconst external = c.externalIcsUrls\n\t\t\tif (!Array.isArray(internal) && !Array.isArray(external)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn (internal || []).length === 0 && (external || []).length === 0\n\t\t},\n\n\t\t/**\n\t\t * The empty-agenda message.\n\t\t *\n\t\t * @return {string} the empty message.\n\t\t */\n\t\temptyMessage() {\n\t\t\treturn t('nextcloud-vue', 'No events in the next {n} days').replace('{n}', String(this.daysAhead))\n\t\t},\n\n\t\t/**\n\t\t * Localised short weekday names (Sun-first).\n\t\t *\n\t\t * @return {string[]} the weekday labels.\n\t\t */\n\t\tweekdayHeaders() {\n\t\t\treturn [\n\t\t\t\tt('nextcloud-vue', 'Sun'),\n\t\t\t\tt('nextcloud-vue', 'Mon'),\n\t\t\t\tt('nextcloud-vue', 'Tue'),\n\t\t\t\tt('nextcloud-vue', 'Wed'),\n\t\t\t\tt('nextcloud-vue', 'Thu'),\n\t\t\t\tt('nextcloud-vue', 'Fri'),\n\t\t\t\tt('nextcloud-vue', 'Sat'),\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The first/last day of the current month (month-view window).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the month window.\n\t\t */\n\t\tmonthRange() {\n\t\t\tconst today = this.today\n\t\t\tconst first = new Date(today.getFullYear(), today.getMonth(), 1)\n\t\t\tconst last = new Date(today.getFullYear(), today.getMonth() + 1, 0)\n\t\t\treturn { from: first, to: last }\n\t\t},\n\n\t\t/**\n\t\t * The Sun–Sat window containing today (week-view window).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the week window.\n\t\t */\n\t\tweekRange() {\n\t\t\tconst today = this.today\n\t\t\tconst start = new Date(today)\n\t\t\tstart.setDate(today.getDate() - today.getDay())\n\t\t\tstart.setHours(0, 0, 0, 0)\n\t\t\tconst end = new Date(start)\n\t\t\tend.setDate(start.getDate() + 6)\n\t\t\tend.setHours(23, 59, 59, 999)\n\t\t\treturn { from: start, to: end }\n\t\t},\n\n\t\t/**\n\t\t * Events bucketed by `YYYY-MM-DD` for grid lookup.\n\t\t *\n\t\t * @return {Record<string, object[]>} iso-date → events.\n\t\t */\n\t\teventsByDay() {\n\t\t\tconst buckets = {}\n\t\t\tfor (const event of this.events) {\n\t\t\t\tconst iso = this.toIsoDate(this.parseDate(event.start))\n\t\t\t\tif (!buckets[iso]) {\n\t\t\t\t\tbuckets[iso] = []\n\t\t\t\t}\n\t\t\t\tbuckets[iso].push(event)\n\t\t\t}\n\t\t\treturn buckets\n\t\t},\n\n\t\t/**\n\t\t * The 7-column month grid (leading/trailing days padded to whole weeks).\n\t\t *\n\t\t * @return {Array<object>} grid cells with day metadata + events.\n\t\t */\n\t\tmonthGrid() {\n\t\t\tconst { from, to } = this.monthRange\n\t\t\tconst cells = []\n\t\t\tconst gridStart = new Date(from)\n\t\t\tgridStart.setDate(from.getDate() - from.getDay())\n\t\t\tconst totalDays = Math.ceil((to.getDate() + from.getDay()) / 7) * 7\n\t\t\tfor (let i = 0; i < totalDays; i++) {\n\t\t\t\tconst cellDate = new Date(gridStart)\n\t\t\t\tcellDate.setDate(gridStart.getDate() + i)\n\t\t\t\tconst iso = this.toIsoDate(cellDate)\n\t\t\t\tcells.push({\n\t\t\t\t\tiso,\n\t\t\t\t\tdayNum: cellDate.getDate(),\n\t\t\t\t\tisToday: this.toIsoDate(this.today) === iso,\n\t\t\t\t\tisOtherMonth: cellDate.getMonth() !== from.getMonth(),\n\t\t\t\t\tevents: this.eventsByDay[iso] || [],\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn cells\n\t\t},\n\n\t\t/**\n\t\t * The 7 day-columns for the week view.\n\t\t *\n\t\t * @return {Array<object>} day columns with metadata + events.\n\t\t */\n\t\tweekDays() {\n\t\t\tconst { from } = this.weekRange\n\t\t\tconst out = []\n\t\t\tfor (let i = 0; i < 7; i++) {\n\t\t\t\tconst day = new Date(from)\n\t\t\t\tday.setDate(from.getDate() + i)\n\t\t\t\tconst iso = this.toIsoDate(day)\n\t\t\t\tout.push({\n\t\t\t\t\tiso,\n\t\t\t\t\tweekday: this.weekdayHeaders[day.getDay()],\n\t\t\t\t\tdayNum: day.getDate(),\n\t\t\t\t\tisToday: this.toIsoDate(this.today) === iso,\n\t\t\t\t\tevents: this.eventsByDay[iso] || [],\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Events grouped into per-day buckets in chronological order.\n\t\t *\n\t\t * @return {Array<{iso: string, label: string, events: object[]}>} the groups.\n\t\t */\n\t\tagendaGroups() {\n\t\t\tconst groups = []\n\t\t\tconst seen = {}\n\t\t\tfor (const event of this.events) {\n\t\t\t\tconst date = this.parseDate(event.start)\n\t\t\t\tconst iso = this.toIsoDate(date)\n\t\t\t\tif (!seen[iso]) {\n\t\t\t\t\tseen[iso] = { iso, label: this.formatDayHeader(date), events: [] }\n\t\t\t\t\tgroups.push(seen[iso])\n\t\t\t\t}\n\t\t\t\tseen[iso].events.push(event)\n\t\t\t}\n\t\t\treturn groups\n\t\t},\n\t},\n\n\twatch: {\n\t\tcontent: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchEvents()\n\t\t\t},\n\t\t},\n\n\t\t// Month/week/agenda use different fetch windows — refetch on switch.\n\t\tactiveMode() {\n\t\t\tthis.fetchEvents()\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchEvents()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Switch the active view mode.\n\t\t *\n\t\t * @param {string} mode the mode to activate.\n\t\t * @return {void}\n\t\t */\n\t\tsetMode(mode) {\n\t\t\tif (VIEW_MODES.includes(mode)) {\n\t\t\t\tthis.activeMode = mode\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The label for a view mode.\n\t\t *\n\t\t * @param {string} mode the mode key.\n\t\t * @return {string} the localised label.\n\t\t */\n\t\tmodeLabel(mode) {\n\t\t\tif (mode === 'month') {\n\t\t\t\treturn t('nextcloud-vue', 'Month')\n\t\t\t}\n\t\t\tif (mode === 'week') {\n\t\t\t\treturn t('nextcloud-vue', 'Week')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Agenda')\n\t\t},\n\n\t\t/**\n\t\t * Compute the fetch window for the active view: the whole month in\n\t\t * month view, the Sun–Sat week in week view, else the daysAhead\n\t\t * look-ahead (agenda).\n\t\t *\n\t\t * @return {{from: Date, to: Date}} the window.\n\t\t */\n\t\tcomputeRange() {\n\t\t\tif (this.activeMode === 'month') {\n\t\t\t\tconst { from, to } = this.monthRange\n\t\t\t\tconst start = new Date(from)\n\t\t\t\tstart.setHours(0, 0, 0, 0)\n\t\t\t\tconst end = new Date(to)\n\t\t\t\tend.setHours(23, 59, 59, 999)\n\t\t\t\treturn { from: start, to: end }\n\t\t\t}\n\t\t\tif (this.activeMode === 'week') {\n\t\t\t\treturn this.weekRange\n\t\t\t}\n\t\t\tconst from = new Date(this.today)\n\t\t\tfrom.setHours(0, 0, 0, 0)\n\t\t\tconst to = new Date(from)\n\t\t\tto.setDate(from.getDate() + this.daysAhead)\n\t\t\tto.setHours(23, 59, 59, 999)\n\t\t\treturn { from, to }\n\t\t},\n\n\t\t/**\n\t\t * Load events from the configured source. A missing source or a fetch\n\t\t * failure degrades to the empty / error state without throwing.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEvents() {\n\t\t\tif (!this.source) {\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failures = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { from, to } = this.computeRange()\n\t\t\tthis.loading = true\n\t\t\tthis.error = null\n\t\t\ttry {\n\t\t\t\tconst payload = await this.source.fetchEvents({\n\t\t\t\t\tfrom: from.toISOString(),\n\t\t\t\t\tto: to.toISOString(),\n\t\t\t\t}) || {}\n\t\t\t\tthis.events = Array.isArray(payload.events) ? payload.events : []\n\t\t\t\tthis.failures = Array.isArray(payload.failures) ? payload.failures : []\n\t\t\t} catch (err) {\n\t\t\t\tthis.error = err\n\t\t\t\tthis.events = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Parse a date value, falling back to now on invalid input.\n\t\t *\n\t\t * @param {string|number|Date} value the value to parse.\n\t\t * @return {Date} the parsed date.\n\t\t */\n\t\tparseDate(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn new Date()\n\t\t\t}\n\t\t\tconst d = new Date(value)\n\t\t\treturn Number.isNaN(d.getTime()) ? new Date() : d\n\t\t},\n\n\t\t/**\n\t\t * Format a date as `YYYY-MM-DD`.\n\t\t *\n\t\t * @param {Date} date the date.\n\t\t * @return {string} the ISO date string.\n\t\t */\n\t\ttoIsoDate(date) {\n\t\t\tconst yyyy = date.getFullYear()\n\t\t\tconst mm = String(date.getMonth() + 1).padStart(2, '0')\n\t\t\tconst dd = String(date.getDate()).padStart(2, '0')\n\t\t\treturn `${yyyy}-${mm}-${dd}`\n\t\t},\n\n\t\t/**\n\t\t * Format an event's start time (or \"All day\").\n\t\t *\n\t\t * @param {object} event the event.\n\t\t * @return {string} the time label.\n\t\t */\n\t\tformatTime(event) {\n\t\t\tif (event.allDay) {\n\t\t\t\treturn t('nextcloud-vue', 'All day')\n\t\t\t}\n\t\t\tconst start = this.parseDate(event.start)\n\t\t\tconst hh = String(start.getHours()).padStart(2, '0')\n\t\t\tconst mm = String(start.getMinutes()).padStart(2, '0')\n\t\t\treturn `${hh}:${mm}`\n\t\t},\n\n\t\t/**\n\t\t * Format a per-day agenda group header.\n\t\t *\n\t\t * @param {Date} date the group date.\n\t\t * @return {string} the header label.\n\t\t */\n\t\tformatDayHeader(date) {\n\t\t\tconst month = date.toLocaleString(undefined, { month: 'short' })\n\t\t\treturn `${this.weekdayHeaders[date.getDay()]} ${date.getDate()} ${month}`\n\t\t},\n\n\t\t/**\n\t\t * Compute the left-border colour style for an event row.\n\t\t *\n\t\t * @param {object} event the event.\n\t\t * @return {object} the style object (empty when colouring is off).\n\t\t */\n\t\teventStyle(event) {\n\t\t\tif (!this.colorByCalendar) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst color = event.color\n\t\t\tif (typeof color === 'string' && color !== '') {\n\t\t\t\treturn { 'border-left': `3px solid ${color}` }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 100%;\n\theight: 100%;\n\toverflow: hidden;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-calendar-widget__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-calendar-widget__title {\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__modes {\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n.cn-calendar-widget__mode-btn {\n\tborder: 1px solid var(--color-border);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tborder-radius: var(--border-radius);\n\tpadding: 2px 8px;\n\tfont-size: 12px;\n\tcursor: pointer;\n}\n\n.cn-calendar-widget__mode-btn.is-active {\n\tbackground: var(--color-primary-element-light, var(--color-primary-element));\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-calendar-widget__body {\n\tflex: 1;\n\toverflow: auto;\n\tpadding: 8px 12px;\n}\n\n.cn-calendar-widget__state {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tpadding: 16px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__retry {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-calendar-widget__month {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(7, 1fr);\n\tgap: 2px;\n}\n\n.cn-calendar-widget__month-header {\n\tfont-size: 11px;\n\tfont-weight: 600;\n\ttext-align: center;\n\tpadding: 4px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__month-cell {\n\tmin-height: 60px;\n\tpadding: 2px 4px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 2px;\n\tbackground: var(--color-main-background);\n}\n\n.cn-calendar-widget__month-cell.is-today {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-widget__month-cell.is-other-month {\n\topacity: 0.45;\n}\n\n.cn-calendar-widget__month-day {\n\tfont-size: 11px;\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__month-events {\n\tlist-style: none;\n\tmargin: 2px 0 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__month-event {\n\tfont-size: 10px;\n\tpadding: 1px 3px;\n\tmargin-bottom: 1px;\n\tborder-radius: 2px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-widget__month-overflow {\n\tfont-size: 10px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__week {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(7, 1fr);\n\tgap: 4px;\n}\n\n.cn-calendar-widget__week-col {\n\tdisplay: flex;\n\tflex-direction: column;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 4px;\n}\n\n.cn-calendar-widget__week-col.is-today {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-widget__week-day {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tborder-bottom: 1px solid var(--color-border);\n\tpadding-bottom: 2px;\n\tmargin-bottom: 4px;\n}\n\n.cn-calendar-widget__week-name {\n\tfont-size: 10px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__week-num {\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-calendar-widget__week-events {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__week-event {\n\tfont-size: 11px;\n\tpadding: 2px 4px;\n\tmargin-bottom: 2px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: 2px;\n}\n\n.cn-calendar-widget__week-time {\n\tfont-weight: 600;\n\tmargin-right: 4px;\n}\n\n.cn-calendar-widget__week-empty {\n\tfont-size: 11px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-widget__agenda {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-calendar-widget__agenda-header {\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-top: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-calendar-widget__agenda-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 6px;\n\tmargin-bottom: 2px;\n\tbackground: var(--color-background-hover);\n\tborder-radius: 2px;\n}\n\n.cn-calendar-widget__agenda-time {\n\tfont-weight: 600;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.cn-calendar-widget__agenda-title {\n\tflex: 1;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-widget__agenda-cal {\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_Fragment","_renderList","_normalizeClass","_createElementVNode","_normalizeStyle"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { getLanguage, translatePlural, translate } from '@nextcloud/l10n';
2
2
  import { NcButton, NcEmptyContent, NcTextField } from '@nextcloud/vue';
3
3
  import Magnify from 'vue-material-design-icons/Magnify.vue';
4
- import { defaultGroupMode, hasProviders, groupCapabilities, resolveProvider, RATINGS } from '../../utils/capabilityComparison.js';
4
+ import { defaultGroupMode, hasProviders, groupCapabilities, warnCapabilityComparisonDeprecated, resolveProvider, RATINGS } from '../../utils/capabilityComparison.js';
5
5
 
6
6
  /**
7
7
  * How long the table waits after the last keystroke before it filters.
@@ -26,6 +26,13 @@ var script = {
26
26
  * document without them renders the table this component's consumers
27
27
  * already had.
28
28
  *
29
+ * Deprecated, with the whole component: publish the comparison on the
30
+ * app's docs site and link to it (as dossiq #3312 and pipelinq #2195
31
+ * did). Still rendered; removal waits for a major version. A
32
+ * development build warns once when a comparison is given.
33
+ *
34
+ * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.
35
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated
29
36
  * @type {object}
30
37
  */
31
38
  comparison: {
@@ -251,6 +258,12 @@ var script = {
251
258
  }
252
259
  },
253
260
 
261
+ mounted() {
262
+ if (this.comparison) {
263
+ warnCapabilityComparisonDeprecated('CnCapabilityTable');
264
+ }
265
+ },
266
+
254
267
  methods: {
255
268
  /**
256
269
  * Debounce the reader's typing into `query`.