@conduction/nextcloud-vue 2.77.0 → 2.79.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.
- package/dist/bundled-packages.json +0 -2
- package/dist/esm/bundled-packages.json +0 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +113 -104
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js +23 -0
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +2 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +122 -97
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +37 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +2 -2
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +168 -5
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +84 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue.js +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js +45 -2
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js.map +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +61 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +8 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +4 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +31 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +3 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +36 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +35 -23
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js +6 -2
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js.map +1 -1
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +30 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/composables/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.js.map +1 -1
- package/dist/esm/composables/useFormDraft.js +11 -1
- package/dist/esm/composables/useFormDraft.js.map +1 -1
- package/dist/esm/composables/useObjectPresence.js +15 -4
- package/dist/esm/composables/useObjectPresence.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +68 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +113 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +526 -16
- package/dist/esm/utils/badgeVariants.js +32 -0
- package/dist/esm/utils/badgeVariants.js.map +1 -0
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.js.map +1 -0
- package/dist/esm/utils/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2569 -811
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +526 -16
- package/l10n/en.json +68 -1
- package/l10n/nl.json +113 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +48 -39
- package/src/components/CnAiCompanion/CnAiCompanion.vue +23 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnBoardView/CnBoardView.vue +2 -1
- package/src/components/CnCellRenderer/CnCellRenderer.md +1 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +169 -10
- package/src/components/CnChartWidget/CnChartWidget.vue +39 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +248 -8
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +88 -4
- package/src/components/CnDateRangePicker/CnDateRangePicker.vue +44 -1
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +61 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
- package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
- package/src/components/CnFilesBrowser/index.js +1 -0
- package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +8 -2
- package/src/components/CnFormPage/CnFormPage.vue +4 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +33 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnStatWidget/CnStatWidget.vue +52 -2
- package/src/components/CnStatusBadge/CnStatusBadge.vue +6 -2
- package/src/components/CnTabsWidget/CnTabsWidget.vue +30 -1
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/index.js +1 -1
- package/src/composables/__tests__/useObjectPresence.spec.js +24 -0
- package/src/composables/useAppManifest.js +8 -3
- package/src/composables/useFormDraft.js +11 -1
- package/src/composables/useObjectPresence.js +15 -4
- package/src/css/badge.css +31 -0
- package/src/css/kpi-card.css +51 -0
- package/src/css/look-board-detail.css +10 -0
- package/src/css/look-board-index.css +128 -14
- package/src/css/look-board.css +85 -0
- package/src/css/table.css +37 -0
- package/src/index.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +50 -5
- package/src/utils/badgeVariants.js +29 -0
- package/src/utils/boardDate.js +189 -0
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/validateManifestV2.compiled.js +57 -2
- package/dist/esm/_virtual/equal.js +0 -4
- package/dist/esm/_virtual/equal.js.map +0 -1
- package/dist/esm/_virtual/ucs2length.js +0 -4
- package/dist/esm/_virtual/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
- package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
|
@@ -16,6 +16,44 @@ const DEFAULT_DATE_RANGE_PRESETS = Object.freeze([
|
|
|
16
16
|
{ id: 'custom', label: 'Custom range', days: null },
|
|
17
17
|
]);
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The library's own translations of the default preset labels. Literal
|
|
21
|
+
* `t()` calls, so the strings are extracted into the `nextcloud-vue`
|
|
22
|
+
* catalogue.
|
|
23
|
+
*
|
|
24
|
+
* @type {{[label: string]: function(): string}}
|
|
25
|
+
*/
|
|
26
|
+
const LIBRARY_PRESET_LABELS = Object.freeze({
|
|
27
|
+
'Last 8 hours': () => translate('nextcloud-vue', 'Last 8 hours'),
|
|
28
|
+
'Last 24 hours': () => translate('nextcloud-vue', 'Last 24 hours'),
|
|
29
|
+
Today: () => translate('nextcloud-vue', 'Today'),
|
|
30
|
+
'Last 7 days': () => translate('nextcloud-vue', 'Last 7 days'),
|
|
31
|
+
'Last 30 days': () => translate('nextcloud-vue', 'Last 30 days'),
|
|
32
|
+
'Last 90 days': () => translate('nextcloud-vue', 'Last 90 days'),
|
|
33
|
+
'Custom range': () => translate('nextcloud-vue', 'Custom range'),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A preset label in the user's language: the host translation when the app's
|
|
38
|
+
* catalogue has the key, else the library's own translation of a default
|
|
39
|
+
* label ("Last 30 days"), else the label as written.
|
|
40
|
+
*
|
|
41
|
+
* @spec openspec/changes/screens-dashboard-i18n/specs/dashboard-page/spec.md#requirement-date-range-preset-labels-are-translated
|
|
42
|
+
* @param {string} label The preset label (an i18n key).
|
|
43
|
+
* @param {function(string): string} [translate] The host translate function.
|
|
44
|
+
* @return {string} The translated label.
|
|
45
|
+
*/
|
|
46
|
+
function translatePresetLabel(label, translate) {
|
|
47
|
+
if (typeof label !== 'string' || label === '') {
|
|
48
|
+
return ''
|
|
49
|
+
}
|
|
50
|
+
const own = typeof translate === 'function' ? translate(label) : label;
|
|
51
|
+
if (typeof own === 'string' && own !== '' && own !== label) {
|
|
52
|
+
return own
|
|
53
|
+
}
|
|
54
|
+
return Object.hasOwn(LIBRARY_PRESET_LABELS, label) ? LIBRARY_PRESET_LABELS[label]() : label
|
|
55
|
+
}
|
|
56
|
+
|
|
19
57
|
/**
|
|
20
58
|
* Resolve a `period` preset into its calendar-aligned `{ from, to }`
|
|
21
59
|
* window — the CURRENT week / month / quarter / year to date, not a
|
|
@@ -143,6 +181,11 @@ var script = {
|
|
|
143
181
|
NcSelect,
|
|
144
182
|
},
|
|
145
183
|
|
|
184
|
+
inject: {
|
|
185
|
+
/** Host translate function provided by CnAppRoot, for app-authored preset labels. */
|
|
186
|
+
cnTranslate: { default: () => (key) => key },
|
|
187
|
+
},
|
|
188
|
+
|
|
146
189
|
props: {
|
|
147
190
|
/**
|
|
148
191
|
* Current range value. `from` / `to` are ISO-8601 UTC strings;
|
|
@@ -274,7 +317,7 @@ var script = {
|
|
|
274
317
|
},
|
|
275
318
|
|
|
276
319
|
presetOptions() {
|
|
277
|
-
return this.presets.map((p) => ({ id: p.id, label: p.label, days: p.days }))
|
|
320
|
+
return this.presets.map((p) => ({ id: p.id, label: translatePresetLabel(p.label, this.cnTranslate), days: p.days }))
|
|
278
321
|
},
|
|
279
322
|
|
|
280
323
|
selectedPresetOption() {
|
|
@@ -415,5 +458,5 @@ function toIsoEndOfDay(date) {
|
|
|
415
458
|
return d.toISOString()
|
|
416
459
|
}
|
|
417
460
|
|
|
418
|
-
export { DEFAULT_DATE_RANGE_PRESETS, script as default, resolvePresetWindow };
|
|
461
|
+
export { DEFAULT_DATE_RANGE_PRESETS, script as default, resolvePresetWindow, translatePresetLabel };
|
|
419
462
|
//# sourceMappingURL=CnDateRangePicker.vue2.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnDateRangePicker.vue2.js","sources":["../../../../src/components/CnDateRangePicker/CnDateRangePicker.vue"],"sourcesContent":["<!--\n CnDateRangePicker — preset-driven date range selector.\n\n Wraps two NcDateTimePicker instances + a preset NcSelect dropdown\n behind a single v-model. Selecting a preset (other than `custom`)\n auto-fills both pickers to `now − days` → `now`; selecting `custom`\n keeps the pickers manually editable so the user can dial in any\n span.\n\n Used internally by CnDashboardPage's date-range header and exposed\n publicly so bespoke screens (filter sidebars, report builders) can\n reuse the same control without composing the primitives themselves.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<div class=\"cn-date-range-picker\" data-testid=\"cn-date-range-picker\">\n\t\t<NcSelect\n\t\t\tclass=\"cn-date-range-picker__preset\"\n\t\t\t:modelValue=\"selectedPresetOption\"\n\t\t\t:options=\"presetOptions\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:clearable=\"false\"\n\t\t\t:searchable=\"false\"\n\t\t\t:aria-label=\"presetLabel\"\n\t\t\t:inputLabel=\"presetLabel\"\n\t\t\tlabel=\"label\"\n\t\t\tdata-testid=\"cn-date-range-picker-preset\"\n\t\t\t@update:modelValue=\"onPresetInput\" />\n\t\t<NcDateTimePicker\n\t\t\tclass=\"cn-date-range-picker__from\"\n\t\t\t:modelValue=\"fromDate\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:format=\"dateFormat\"\n\t\t\t:aria-label=\"fromLabel\"\n\t\t\t:placeholder=\"fromLabel\"\n\t\t\tdata-testid=\"cn-date-range-picker-from\"\n\t\t\t@change=\"onFromChange\" />\n\t\t<span class=\"cn-date-range-picker__separator\" aria-hidden=\"true\">→</span>\n\t\t<NcDateTimePicker\n\t\t\tclass=\"cn-date-range-picker__to\"\n\t\t\t:modelValue=\"toDate\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:format=\"dateFormat\"\n\t\t\t:aria-label=\"toLabel\"\n\t\t\t:placeholder=\"toLabel\"\n\t\t\tdata-testid=\"cn-date-range-picker-to\"\n\t\t\t@change=\"onToChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcDateTimePicker, NcSelect } from '@nextcloud/vue'\n\n/**\n * Default preset list used when the consumer doesn't provide one.\n * `days: null` marks the preset as a manual / custom range — selecting\n * it keeps the pickers editable without forcing a recomputed window.\n */\nexport const DEFAULT_DATE_RANGE_PRESETS = Object.freeze([\n\t{ id: 'last-8h', label: 'Last 8 hours', hours: 8 },\n\t{ id: 'last-24h', label: 'Last 24 hours', hours: 24 },\n\t{ id: 'today', label: 'Today', days: 1 },\n\t{ id: 'last-7', label: 'Last 7 days', days: 7 },\n\t{ id: 'last-30', label: 'Last 30 days', days: 30 },\n\t{ id: 'last-90', label: 'Last 90 days', days: 90 },\n\t{ id: 'custom', label: 'Custom range', days: null },\n])\n\n/**\n * Resolve a `period` preset into its calendar-aligned `{ from, to }`\n * window — the CURRENT week / month / quarter / year to date, not a\n * rolling span. ISO week semantics: weeks start on Monday.\n *\n * @param {'week'|'month'|'quarter'|'year'} period The calendar unit.\n * @param {Date} now Reference instant.\n * @return {{ from: string, to: string } | null} ISO-8601 UTC window or null.\n */\nfunction resolveCalendarPeriod(period, now) {\n\t// LOCAL calendar components, not UTC. \"Current month\" is a statement about\n\t// the reader's calendar, and every surface around this one is local: the\n\t// `datetime-local` inputs render these instants in local time, and\n\t// `localDateTimeInputToIso` reads them back the same way.\n\t//\n\t// Built from UTC, the window ended at 23:59:59.999Z on today's UTC date,\n\t// which in any zone ahead of UTC displays as the NEXT day — and on the last\n\t// day of a month, the next MONTH. Measured 2026-08-31 in CEST: \"Current\n\t// month\" showed a To of 1 September, and the range leaked two hours of\n\t// September into an August total.\n\tconst y = now.getFullYear()\n\tconst m = now.getMonth()\n\tconst d = now.getDate()\n\tlet start\n\tif (period === 'week') {\n\t\t// getDay() is 0 for Sunday; shift so Monday is the first day.\n\t\tconst offset = (now.getDay() + 6) % 7\n\t\tstart = new Date(y, m, (d - offset), 0, 0, 0, 0)\n\t} else if (period === 'month') {\n\t\tstart = new Date(y, m, 1, 0, 0, 0, 0)\n\t} else if (period === 'quarter') {\n\t\tstart = new Date(y, (Math.floor(m / 3) * 3), 1, 0, 0, 0, 0)\n\t} else if (period === 'year') {\n\t\tstart = new Date(y, 0, 1, 0, 0, 0, 0)\n\t} else {\n\t\treturn null\n\t}\n\n\tconst end = new Date(y, m, d, 23, 59, 59, 999)\n\treturn { from: start.toISOString(), to: end.toISOString() }\n}\n\n/**\n * Resolve a preset id into a `{ from, to }` ISO-8601 UTC window\n * ending at end-of-day today. `custom` returns `null` (the caller\n * SHALL preserve the previously selected dates).\n *\n * Three preset kinds, checked in order — a preset SHALL declare only one:\n * `period` (calendar-aligned: current week/month/quarter/year to date),\n * `hours` (rolling window ending now), `days` (rolling window of whole\n * days ending end-of-day today).\n *\n * @param {string|null} presetId Preset id to resolve.\n * @param {Array<{ id: string, days?: number|null, hours?: number, period?: string }>} presets Preset list.\n * @param {Date} [now] Override `now` for deterministic tests.\n * @return {{ from: string, to: string } | null} ISO-8601 UTC window or null.\n */\nexport function resolvePresetWindow(presetId, presets, now = new Date()) {\n\tif (!presetId || presetId === 'custom') {\n\t\treturn null\n\t}\n\tconst preset = (presets || []).find((p) => p.id === presetId)\n\tif (!preset) {\n\t\treturn null\n\t}\n\t// Calendar-aligned presets (\"Current month\") anchor to the start of the\n\t// calendar unit, NOT to `now − N days`. Checked first so a preset that\n\t// carries both `period` and a legacy `days` hint resolves as a period.\n\tif (typeof preset.period === 'string') {\n\t\treturn resolveCalendarPeriod(preset.period, now)\n\t}\n\t// Hour-granularity presets are ROLLING windows ending at the exact\n\t// current instant — `now − N hours → now` — so \"Last 8 hours\" means\n\t// the trailing 8h, not a calendar-day-aligned span.\n\tif (typeof preset.hours === 'number') {\n\t\tconst end = new Date(now.getTime())\n\t\tconst start = new Date(now.getTime() - preset.hours * 3600000)\n\t\treturn { from: start.toISOString(), to: end.toISOString() }\n\t}\n\tif (typeof preset.days !== 'number') {\n\t\treturn null\n\t}\n\t// Day-granularity presets are calendar-aligned: midnight UTC start of\n\t// the (days-1)-th day back through end-of-day UTC today. `today`\n\t// (days=1) resolves to \"00:00 → 23:59 of today\".\n\tconst end = new Date(Date.UTC(\n\t\tnow.getUTCFullYear(),\n\t\tnow.getUTCMonth(),\n\t\tnow.getUTCDate(),\n\t\t23,\n\t\t59,\n\t\t59,\n\t\t999,\n\t))\n\tconst start = new Date(end)\n\tstart.setUTCDate(start.getUTCDate() - (preset.days - 1))\n\tstart.setUTCHours(0, 0, 0, 0)\n\treturn { from: start.toISOString(), to: end.toISOString() }\n}\n\n/**\n * CnDateRangePicker — preset-driven date range selector.\n *\n * ```vue\n * <CnDateRangePicker\n * v-model=\"range\"\n * :presets=\"presets\"\n * :disabled=\"loading\" />\n * ```\n *\n * The component owns layout. Selecting a preset (other than `custom`)\n * auto-fills both pickers from `now − days` → `now`. Selecting\n * `custom` keeps both pickers editable so the user can dial in any\n * arbitrary range; typing in either picker emits a `custom` value.\n *\n * The exported `DEFAULT_DATE_RANGE_PRESETS` list mirrors the defaults\n * applied by `CnDashboardPage` when its `dateRange.presets` is\n * omitted: today / last 7 / last 30 / last 90 / custom.\n */\nexport default {\n\tname: 'CnDateRangePicker',\n\n\tcomponents: {\n\t\tNcDateTimePicker,\n\t\tNcSelect,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Current range value. `from` / `to` are ISO-8601 UTC strings;\n\t\t * `preset` is the id of the preset that produced the range (or\n\t\t * `'custom'` when the user typed values by hand).\n\t\t *\n\t\t * @type {{ from: string, to: string, preset: string }|null}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The same value as `value`, under Vue 3's own v-model name.\n\t\t *\n\t\t * ⚠️ WITHOUT THIS, `v-model` ON THIS COMPONENT DOES NOTHING. Vue 3\n\t\t * compiles `v-model=\"x\"` to `:modelValue` + `@update:modelValue`, so a\n\t\t * component declaring only `value`/`input` never receives the prop and\n\t\t * its emit is never heard — silently, looking exactly like a component\n\t\t * that works.\n\t\t *\n\t\t * `value` stays the public name; both are accepted. The default is\n\t\t * `undefined` so \"not passed\" is distinguishable from \"passed empty\".\n\t\t *\n\t\t * @type {object|string}\n\t\t */\n\t\tmodelValue: { type: [Object, String], default: undefined },\n\t\t/**\n\t\t * Preset list. Each entry declares ONE window kind: `period`\n\t\t * (`'week'|'month'|'quarter'|'year'` — the current calendar unit\n\t\t * to date, e.g. \"Current month\" = the 1st through today), `hours`\n\t\t * (rolling window ending now), or `days` (rolling whole days\n\t\t * ending today). Use `days: null` to mark a preset as manual (e.g.\n\t\t * the canonical `custom` entry). Defaults to\n\t\t * `DEFAULT_DATE_RANGE_PRESETS`.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, days?: number|null, hours?: number, period?: 'week'|'month'|'quarter'|'year' }>}\n\t\t */\n\t\tpresets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => DEFAULT_DATE_RANGE_PRESETS.map((p) => ({ ...p })),\n\t\t},\n\n\t\t/**\n\t\t * When `true`, both pickers AND the preset select are\n\t\t * disabled. Mirrors `NcDateTimePicker.disabled`.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Date input visual format string, forwarded to the underlying\n\t\t * `NcDateTimePicker`s. Default: `'yyyy-MM-dd'`.\n\t\t *\n\t\t * DATE-FNS TOKENS, not moment's. `@nextcloud/vue` 9 replaced the\n\t\t * picker's moment.js backend with date-fns, which rejects `YYYY`\n\t\t * (year-of-week) and `DD` (day-of-year) where `yyyy` / `dd` are meant —\n\t\t * and it THROWS rather than warning, so the whole picker fails to render.\n\t\t * This default was `'YYYY-MM-DD'`, correct under v8 and broken under v9:\n\t\t * the component threw with its own default props until the real-render\n\t\t * smoke lane surfaced it. A consumer still passing a moment-style string\n\t\t * needs the same substitution.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdateFormat: {\n\t\t\ttype: String,\n\t\t\tdefault: 'yyyy-MM-dd',\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the preset dropdown.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tpresetLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Range preset'),\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the start-of-range picker.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfromLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'From'),\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the end-of-range picker.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttoLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'To'),\n\t\t},\n\t},\n\n\temits: ['input', 'update:modelValue'],\n\n\tcomputed: {\n\t\t/**\n\t\t * The value the consumer actually bound, whichever prop they used.\n\t\t *\n\t\t * @return {object|string|null} The bound value.\n\t\t */\n\t\tboundValue() {\n\t\t\treturn this.modelValue !== undefined ? this.modelValue : this.value\n\t\t},\n\n\t\tfromDate() {\n\t\t\treturn this.boundValue?.from ? new Date(this.boundValue.from) : null\n\t\t},\n\n\t\ttoDate() {\n\t\t\treturn this.boundValue?.to ? new Date(this.boundValue.to) : null\n\t\t},\n\n\t\tselectedPresetId() {\n\t\t\treturn this.boundValue?.preset || 'custom'\n\t\t},\n\n\t\tpresetOptions() {\n\t\t\treturn this.presets.map((p) => ({ id: p.id, label: p.label, days: p.days }))\n\t\t},\n\n\t\tselectedPresetOption() {\n\t\t\tconst match = this.presetOptions.find((p) => p.id === this.selectedPresetId)\n\t\t\t// Fall back to the first option if the current preset id\n\t\t\t// isn't in the provided list (e.g. consumer removed `today`).\n\t\t\treturn match || this.presetOptions[0] || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Tell the consumer the value changed, in both v-model dialects.\n\t\t *\n\t\t * BOTH are emitted, always: a consumer on `@input` and a consumer on\n\t\t * `v-model` are the same consumer as far as this component knows, and\n\t\t * emitting only one silently breaks half of them.\n\t\t *\n\t\t * @param {object|string|null} next The new value.\n\t\t * @return {void}\n\t\t */\n\t\temitValue(next) {\n\t\t\t/**\n\t\t\t * @event input The value changed. Vue 2's v-model dialect, kept for\n\t\t\t * existing consumers.\n\t\t\t * @type {object|string|null}\n\t\t\t */\n\t\t\tthis.$emit('input', next)\n\t\t\t/**\n\t\t\t * @event update:modelValue The value changed. Vue 3's v-model\n\t\t\t * dialect — what a plain `v-model` listens for.\n\t\t\t * @type {object|string|null}\n\t\t\t */\n\t\t\tthis.$emit('update:modelValue', next)\n\t\t},\n\n\t\t/**\n\t\t * Handle a preset selection. When the preset has a numeric\n\t\t * `days` value we recompute the window; for `custom` (or any\n\t\t * preset with `days: null`) we keep the current `from` / `to`\n\t\t * and only flip `preset` so the parent knows the user opted\n\t\t * into manual editing.\n\t\t *\n\t\t * @param {{ id: string, days: number|null } | null} option\n\t\t * The selected option (NcSelect emits the full option object).\n\t\t */\n\t\tonPresetInput(option) {\n\t\t\tif (!option) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst win = resolvePresetWindow(option.id, this.presets)\n\t\t\tif (win) {\n\t\t\t\t/**\n\t\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t\t * @type {{ from: string|null, to: string|null, preset: string }}\n\t\t\t\t */\n\t\t\t\tthis.emitValue({ from: win.from, to: win.to, preset: option.id })\n\t\t\t} else {\n\t\t\t\t// Custom or null-days preset → keep the existing window,\n\t\t\t\t// just change the preset id.\n\t\t\t\tconst from = this.boundValue?.from || null\n\t\t\t\tconst to = this.boundValue?.to || null\n\t\t\t\t/**\n\t\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t\t */\n\t\t\t\tthis.emitValue({ from, to, preset: option.id })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a manual edit of the `from` date input. Typing into\n\t\t * either picker switches the preset id to `custom` so the\n\t\t * parent / dashboard knows the user dialled in a bespoke\n\t\t * window.\n\t\t *\n\t\t * @param {Date|null} date The new date.\n\t\t */\n\t\tonFromChange(date) {\n\t\t\tconst from = date ? toIsoStartOfDay(date) : null\n\t\t\tconst to = this.boundValue?.to || null\n\t\t\t/**\n\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t */\n\t\t\tthis.emitValue({ from, to, preset: 'custom' })\n\t\t},\n\n\t\t/**\n\t\t * Handle a manual edit of the `to` date input.\n\t\t *\n\t\t * @param {Date|null} date The new date.\n\t\t */\n\t\tonToChange(date) {\n\t\t\tconst from = this.boundValue?.from || null\n\t\t\tconst to = date ? toIsoEndOfDay(date) : null\n\t\t\t/**\n\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t */\n\t\t\tthis.emitValue({ from, to, preset: 'custom' })\n\t\t},\n\t},\n}\n\n/**\n * Coerce a JS Date to the ISO-8601 string at start-of-day UTC.\n *\n * @param {Date} date Source date.\n * @return {string} ISO-8601 UTC string at 00:00:00.000Z.\n */\nfunction toIsoStartOfDay(date) {\n\tconst d = new Date(Date.UTC(\n\t\tdate.getUTCFullYear(),\n\t\tdate.getUTCMonth(),\n\t\tdate.getUTCDate(),\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0,\n\t))\n\treturn d.toISOString()\n}\n\n/**\n * Coerce a JS Date to the ISO-8601 string at end-of-day UTC.\n *\n * @param {Date} date Source date.\n * @return {string} ISO-8601 UTC string at 23:59:59.999Z.\n */\nfunction toIsoEndOfDay(date) {\n\tconst d = new Date(Date.UTC(\n\t\tdate.getUTCFullYear(),\n\t\tdate.getUTCMonth(),\n\t\tdate.getUTCDate(),\n\t\t23,\n\t\t59,\n\t\t59,\n\t\t999,\n\t))\n\treturn d.toISOString()\n}\n</script>\n\n<style scoped>\n.cn-date-range-picker {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-date-range-picker__preset {\n\tmin-width: 180px;\n}\n\n.cn-date-range-picker__from,\n.cn-date-range-picker__to {\n\tmin-width: 160px;\n}\n\n.cn-date-range-picker__separator {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnDateRangePicker.vue2.js","sources":["../../../../src/components/CnDateRangePicker/CnDateRangePicker.vue"],"sourcesContent":["<!--\n CnDateRangePicker — preset-driven date range selector.\n\n Wraps two NcDateTimePicker instances + a preset NcSelect dropdown\n behind a single v-model. Selecting a preset (other than `custom`)\n auto-fills both pickers to `now − days` → `now`; selecting `custom`\n keeps the pickers manually editable so the user can dial in any\n span.\n\n Used internally by CnDashboardPage's date-range header and exposed\n publicly so bespoke screens (filter sidebars, report builders) can\n reuse the same control without composing the primitives themselves.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<div class=\"cn-date-range-picker\" data-testid=\"cn-date-range-picker\">\n\t\t<NcSelect\n\t\t\tclass=\"cn-date-range-picker__preset\"\n\t\t\t:modelValue=\"selectedPresetOption\"\n\t\t\t:options=\"presetOptions\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:clearable=\"false\"\n\t\t\t:searchable=\"false\"\n\t\t\t:aria-label=\"presetLabel\"\n\t\t\t:inputLabel=\"presetLabel\"\n\t\t\tlabel=\"label\"\n\t\t\tdata-testid=\"cn-date-range-picker-preset\"\n\t\t\t@update:modelValue=\"onPresetInput\" />\n\t\t<NcDateTimePicker\n\t\t\tclass=\"cn-date-range-picker__from\"\n\t\t\t:modelValue=\"fromDate\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:format=\"dateFormat\"\n\t\t\t:aria-label=\"fromLabel\"\n\t\t\t:placeholder=\"fromLabel\"\n\t\t\tdata-testid=\"cn-date-range-picker-from\"\n\t\t\t@change=\"onFromChange\" />\n\t\t<span class=\"cn-date-range-picker__separator\" aria-hidden=\"true\">→</span>\n\t\t<NcDateTimePicker\n\t\t\tclass=\"cn-date-range-picker__to\"\n\t\t\t:modelValue=\"toDate\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:format=\"dateFormat\"\n\t\t\t:aria-label=\"toLabel\"\n\t\t\t:placeholder=\"toLabel\"\n\t\t\tdata-testid=\"cn-date-range-picker-to\"\n\t\t\t@change=\"onToChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcDateTimePicker, NcSelect } from '@nextcloud/vue'\n\n/**\n * Default preset list used when the consumer doesn't provide one.\n * `days: null` marks the preset as a manual / custom range — selecting\n * it keeps the pickers editable without forcing a recomputed window.\n */\nexport const DEFAULT_DATE_RANGE_PRESETS = Object.freeze([\n\t{ id: 'last-8h', label: 'Last 8 hours', hours: 8 },\n\t{ id: 'last-24h', label: 'Last 24 hours', hours: 24 },\n\t{ id: 'today', label: 'Today', days: 1 },\n\t{ id: 'last-7', label: 'Last 7 days', days: 7 },\n\t{ id: 'last-30', label: 'Last 30 days', days: 30 },\n\t{ id: 'last-90', label: 'Last 90 days', days: 90 },\n\t{ id: 'custom', label: 'Custom range', days: null },\n])\n\n/**\n * The library's own translations of the default preset labels. Literal\n * `t()` calls, so the strings are extracted into the `nextcloud-vue`\n * catalogue.\n *\n * @type {{[label: string]: function(): string}}\n */\nconst LIBRARY_PRESET_LABELS = Object.freeze({\n\t'Last 8 hours': () => t('nextcloud-vue', 'Last 8 hours'),\n\t'Last 24 hours': () => t('nextcloud-vue', 'Last 24 hours'),\n\tToday: () => t('nextcloud-vue', 'Today'),\n\t'Last 7 days': () => t('nextcloud-vue', 'Last 7 days'),\n\t'Last 30 days': () => t('nextcloud-vue', 'Last 30 days'),\n\t'Last 90 days': () => t('nextcloud-vue', 'Last 90 days'),\n\t'Custom range': () => t('nextcloud-vue', 'Custom range'),\n})\n\n/**\n * A preset label in the user's language: the host translation when the app's\n * catalogue has the key, else the library's own translation of a default\n * label (\"Last 30 days\"), else the label as written.\n *\n * @spec openspec/changes/screens-dashboard-i18n/specs/dashboard-page/spec.md#requirement-date-range-preset-labels-are-translated\n * @param {string} label The preset label (an i18n key).\n * @param {function(string): string} [translate] The host translate function.\n * @return {string} The translated label.\n */\nexport function translatePresetLabel(label, translate) {\n\tif (typeof label !== 'string' || label === '') {\n\t\treturn ''\n\t}\n\tconst own = typeof translate === 'function' ? translate(label) : label\n\tif (typeof own === 'string' && own !== '' && own !== label) {\n\t\treturn own\n\t}\n\treturn Object.hasOwn(LIBRARY_PRESET_LABELS, label) ? LIBRARY_PRESET_LABELS[label]() : label\n}\n\n/**\n * Resolve a `period` preset into its calendar-aligned `{ from, to }`\n * window — the CURRENT week / month / quarter / year to date, not a\n * rolling span. ISO week semantics: weeks start on Monday.\n *\n * @param {'week'|'month'|'quarter'|'year'} period The calendar unit.\n * @param {Date} now Reference instant.\n * @return {{ from: string, to: string } | null} ISO-8601 UTC window or null.\n */\nfunction resolveCalendarPeriod(period, now) {\n\t// LOCAL calendar components, not UTC. \"Current month\" is a statement about\n\t// the reader's calendar, and every surface around this one is local: the\n\t// `datetime-local` inputs render these instants in local time, and\n\t// `localDateTimeInputToIso` reads them back the same way.\n\t//\n\t// Built from UTC, the window ended at 23:59:59.999Z on today's UTC date,\n\t// which in any zone ahead of UTC displays as the NEXT day — and on the last\n\t// day of a month, the next MONTH. Measured 2026-08-31 in CEST: \"Current\n\t// month\" showed a To of 1 September, and the range leaked two hours of\n\t// September into an August total.\n\tconst y = now.getFullYear()\n\tconst m = now.getMonth()\n\tconst d = now.getDate()\n\tlet start\n\tif (period === 'week') {\n\t\t// getDay() is 0 for Sunday; shift so Monday is the first day.\n\t\tconst offset = (now.getDay() + 6) % 7\n\t\tstart = new Date(y, m, (d - offset), 0, 0, 0, 0)\n\t} else if (period === 'month') {\n\t\tstart = new Date(y, m, 1, 0, 0, 0, 0)\n\t} else if (period === 'quarter') {\n\t\tstart = new Date(y, (Math.floor(m / 3) * 3), 1, 0, 0, 0, 0)\n\t} else if (period === 'year') {\n\t\tstart = new Date(y, 0, 1, 0, 0, 0, 0)\n\t} else {\n\t\treturn null\n\t}\n\n\tconst end = new Date(y, m, d, 23, 59, 59, 999)\n\treturn { from: start.toISOString(), to: end.toISOString() }\n}\n\n/**\n * Resolve a preset id into a `{ from, to }` ISO-8601 UTC window\n * ending at end-of-day today. `custom` returns `null` (the caller\n * SHALL preserve the previously selected dates).\n *\n * Three preset kinds, checked in order — a preset SHALL declare only one:\n * `period` (calendar-aligned: current week/month/quarter/year to date),\n * `hours` (rolling window ending now), `days` (rolling window of whole\n * days ending end-of-day today).\n *\n * @param {string|null} presetId Preset id to resolve.\n * @param {Array<{ id: string, days?: number|null, hours?: number, period?: string }>} presets Preset list.\n * @param {Date} [now] Override `now` for deterministic tests.\n * @return {{ from: string, to: string } | null} ISO-8601 UTC window or null.\n */\nexport function resolvePresetWindow(presetId, presets, now = new Date()) {\n\tif (!presetId || presetId === 'custom') {\n\t\treturn null\n\t}\n\tconst preset = (presets || []).find((p) => p.id === presetId)\n\tif (!preset) {\n\t\treturn null\n\t}\n\t// Calendar-aligned presets (\"Current month\") anchor to the start of the\n\t// calendar unit, NOT to `now − N days`. Checked first so a preset that\n\t// carries both `period` and a legacy `days` hint resolves as a period.\n\tif (typeof preset.period === 'string') {\n\t\treturn resolveCalendarPeriod(preset.period, now)\n\t}\n\t// Hour-granularity presets are ROLLING windows ending at the exact\n\t// current instant — `now − N hours → now` — so \"Last 8 hours\" means\n\t// the trailing 8h, not a calendar-day-aligned span.\n\tif (typeof preset.hours === 'number') {\n\t\tconst end = new Date(now.getTime())\n\t\tconst start = new Date(now.getTime() - preset.hours * 3600000)\n\t\treturn { from: start.toISOString(), to: end.toISOString() }\n\t}\n\tif (typeof preset.days !== 'number') {\n\t\treturn null\n\t}\n\t// Day-granularity presets are calendar-aligned: midnight UTC start of\n\t// the (days-1)-th day back through end-of-day UTC today. `today`\n\t// (days=1) resolves to \"00:00 → 23:59 of today\".\n\tconst end = new Date(Date.UTC(\n\t\tnow.getUTCFullYear(),\n\t\tnow.getUTCMonth(),\n\t\tnow.getUTCDate(),\n\t\t23,\n\t\t59,\n\t\t59,\n\t\t999,\n\t))\n\tconst start = new Date(end)\n\tstart.setUTCDate(start.getUTCDate() - (preset.days - 1))\n\tstart.setUTCHours(0, 0, 0, 0)\n\treturn { from: start.toISOString(), to: end.toISOString() }\n}\n\n/**\n * CnDateRangePicker — preset-driven date range selector.\n *\n * ```vue\n * <CnDateRangePicker\n * v-model=\"range\"\n * :presets=\"presets\"\n * :disabled=\"loading\" />\n * ```\n *\n * The component owns layout. Selecting a preset (other than `custom`)\n * auto-fills both pickers from `now − days` → `now`. Selecting\n * `custom` keeps both pickers editable so the user can dial in any\n * arbitrary range; typing in either picker emits a `custom` value.\n *\n * The exported `DEFAULT_DATE_RANGE_PRESETS` list mirrors the defaults\n * applied by `CnDashboardPage` when its `dateRange.presets` is\n * omitted: today / last 7 / last 30 / last 90 / custom.\n */\nexport default {\n\tname: 'CnDateRangePicker',\n\n\tcomponents: {\n\t\tNcDateTimePicker,\n\t\tNcSelect,\n\t},\n\n\tinject: {\n\t\t/** Host translate function provided by CnAppRoot, for app-authored preset labels. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Current range value. `from` / `to` are ISO-8601 UTC strings;\n\t\t * `preset` is the id of the preset that produced the range (or\n\t\t * `'custom'` when the user typed values by hand).\n\t\t *\n\t\t * @type {{ from: string, to: string, preset: string }|null}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The same value as `value`, under Vue 3's own v-model name.\n\t\t *\n\t\t * ⚠️ WITHOUT THIS, `v-model` ON THIS COMPONENT DOES NOTHING. Vue 3\n\t\t * compiles `v-model=\"x\"` to `:modelValue` + `@update:modelValue`, so a\n\t\t * component declaring only `value`/`input` never receives the prop and\n\t\t * its emit is never heard — silently, looking exactly like a component\n\t\t * that works.\n\t\t *\n\t\t * `value` stays the public name; both are accepted. The default is\n\t\t * `undefined` so \"not passed\" is distinguishable from \"passed empty\".\n\t\t *\n\t\t * @type {object|string}\n\t\t */\n\t\tmodelValue: { type: [Object, String], default: undefined },\n\t\t/**\n\t\t * Preset list. Each entry declares ONE window kind: `period`\n\t\t * (`'week'|'month'|'quarter'|'year'` — the current calendar unit\n\t\t * to date, e.g. \"Current month\" = the 1st through today), `hours`\n\t\t * (rolling window ending now), or `days` (rolling whole days\n\t\t * ending today). Use `days: null` to mark a preset as manual (e.g.\n\t\t * the canonical `custom` entry). Defaults to\n\t\t * `DEFAULT_DATE_RANGE_PRESETS`.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, days?: number|null, hours?: number, period?: 'week'|'month'|'quarter'|'year' }>}\n\t\t */\n\t\tpresets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => DEFAULT_DATE_RANGE_PRESETS.map((p) => ({ ...p })),\n\t\t},\n\n\t\t/**\n\t\t * When `true`, both pickers AND the preset select are\n\t\t * disabled. Mirrors `NcDateTimePicker.disabled`.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Date input visual format string, forwarded to the underlying\n\t\t * `NcDateTimePicker`s. Default: `'yyyy-MM-dd'`.\n\t\t *\n\t\t * DATE-FNS TOKENS, not moment's. `@nextcloud/vue` 9 replaced the\n\t\t * picker's moment.js backend with date-fns, which rejects `YYYY`\n\t\t * (year-of-week) and `DD` (day-of-year) where `yyyy` / `dd` are meant —\n\t\t * and it THROWS rather than warning, so the whole picker fails to render.\n\t\t * This default was `'YYYY-MM-DD'`, correct under v8 and broken under v9:\n\t\t * the component threw with its own default props until the real-render\n\t\t * smoke lane surfaced it. A consumer still passing a moment-style string\n\t\t * needs the same substitution.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdateFormat: {\n\t\t\ttype: String,\n\t\t\tdefault: 'yyyy-MM-dd',\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the preset dropdown.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tpresetLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Range preset'),\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the start-of-range picker.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfromLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'From'),\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the end-of-range picker.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttoLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'To'),\n\t\t},\n\t},\n\n\temits: ['input', 'update:modelValue'],\n\n\tcomputed: {\n\t\t/**\n\t\t * The value the consumer actually bound, whichever prop they used.\n\t\t *\n\t\t * @return {object|string|null} The bound value.\n\t\t */\n\t\tboundValue() {\n\t\t\treturn this.modelValue !== undefined ? this.modelValue : this.value\n\t\t},\n\n\t\tfromDate() {\n\t\t\treturn this.boundValue?.from ? new Date(this.boundValue.from) : null\n\t\t},\n\n\t\ttoDate() {\n\t\t\treturn this.boundValue?.to ? new Date(this.boundValue.to) : null\n\t\t},\n\n\t\tselectedPresetId() {\n\t\t\treturn this.boundValue?.preset || 'custom'\n\t\t},\n\n\t\tpresetOptions() {\n\t\t\treturn this.presets.map((p) => ({ id: p.id, label: translatePresetLabel(p.label, this.cnTranslate), days: p.days }))\n\t\t},\n\n\t\tselectedPresetOption() {\n\t\t\tconst match = this.presetOptions.find((p) => p.id === this.selectedPresetId)\n\t\t\t// Fall back to the first option if the current preset id\n\t\t\t// isn't in the provided list (e.g. consumer removed `today`).\n\t\t\treturn match || this.presetOptions[0] || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Tell the consumer the value changed, in both v-model dialects.\n\t\t *\n\t\t * BOTH are emitted, always: a consumer on `@input` and a consumer on\n\t\t * `v-model` are the same consumer as far as this component knows, and\n\t\t * emitting only one silently breaks half of them.\n\t\t *\n\t\t * @param {object|string|null} next The new value.\n\t\t * @return {void}\n\t\t */\n\t\temitValue(next) {\n\t\t\t/**\n\t\t\t * @event input The value changed. Vue 2's v-model dialect, kept for\n\t\t\t * existing consumers.\n\t\t\t * @type {object|string|null}\n\t\t\t */\n\t\t\tthis.$emit('input', next)\n\t\t\t/**\n\t\t\t * @event update:modelValue The value changed. Vue 3's v-model\n\t\t\t * dialect — what a plain `v-model` listens for.\n\t\t\t * @type {object|string|null}\n\t\t\t */\n\t\t\tthis.$emit('update:modelValue', next)\n\t\t},\n\n\t\t/**\n\t\t * Handle a preset selection. When the preset has a numeric\n\t\t * `days` value we recompute the window; for `custom` (or any\n\t\t * preset with `days: null`) we keep the current `from` / `to`\n\t\t * and only flip `preset` so the parent knows the user opted\n\t\t * into manual editing.\n\t\t *\n\t\t * @param {{ id: string, days: number|null } | null} option\n\t\t * The selected option (NcSelect emits the full option object).\n\t\t */\n\t\tonPresetInput(option) {\n\t\t\tif (!option) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst win = resolvePresetWindow(option.id, this.presets)\n\t\t\tif (win) {\n\t\t\t\t/**\n\t\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t\t * @type {{ from: string|null, to: string|null, preset: string }}\n\t\t\t\t */\n\t\t\t\tthis.emitValue({ from: win.from, to: win.to, preset: option.id })\n\t\t\t} else {\n\t\t\t\t// Custom or null-days preset → keep the existing window,\n\t\t\t\t// just change the preset id.\n\t\t\t\tconst from = this.boundValue?.from || null\n\t\t\t\tconst to = this.boundValue?.to || null\n\t\t\t\t/**\n\t\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t\t */\n\t\t\t\tthis.emitValue({ from, to, preset: option.id })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a manual edit of the `from` date input. Typing into\n\t\t * either picker switches the preset id to `custom` so the\n\t\t * parent / dashboard knows the user dialled in a bespoke\n\t\t * window.\n\t\t *\n\t\t * @param {Date|null} date The new date.\n\t\t */\n\t\tonFromChange(date) {\n\t\t\tconst from = date ? toIsoStartOfDay(date) : null\n\t\t\tconst to = this.boundValue?.to || null\n\t\t\t/**\n\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t */\n\t\t\tthis.emitValue({ from, to, preset: 'custom' })\n\t\t},\n\n\t\t/**\n\t\t * Handle a manual edit of the `to` date input.\n\t\t *\n\t\t * @param {Date|null} date The new date.\n\t\t */\n\t\tonToChange(date) {\n\t\t\tconst from = this.boundValue?.from || null\n\t\t\tconst to = date ? toIsoEndOfDay(date) : null\n\t\t\t/**\n\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t */\n\t\t\tthis.emitValue({ from, to, preset: 'custom' })\n\t\t},\n\t},\n}\n\n/**\n * Coerce a JS Date to the ISO-8601 string at start-of-day UTC.\n *\n * @param {Date} date Source date.\n * @return {string} ISO-8601 UTC string at 00:00:00.000Z.\n */\nfunction toIsoStartOfDay(date) {\n\tconst d = new Date(Date.UTC(\n\t\tdate.getUTCFullYear(),\n\t\tdate.getUTCMonth(),\n\t\tdate.getUTCDate(),\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0,\n\t))\n\treturn d.toISOString()\n}\n\n/**\n * Coerce a JS Date to the ISO-8601 string at end-of-day UTC.\n *\n * @param {Date} date Source date.\n * @return {string} ISO-8601 UTC string at 23:59:59.999Z.\n */\nfunction toIsoEndOfDay(date) {\n\tconst d = new Date(Date.UTC(\n\t\tdate.getUTCFullYear(),\n\t\tdate.getUTCMonth(),\n\t\tdate.getUTCDate(),\n\t\t23,\n\t\t59,\n\t\t59,\n\t\t999,\n\t))\n\treturn d.toISOString()\n}\n</script>\n\n<style scoped>\n.cn-date-range-picker {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-date-range-picker__preset {\n\tmin-width: 180px;\n}\n\n.cn-date-range-picker__from,\n.cn-date-range-picker__to {\n\tmin-width: 160px;\n}\n\n.cn-date-range-picker__separator {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnDateRangePicker.vue3.js","sources":["../../../../src/components/CnDateRangePicker/CnDateRangePicker.vue?vue&type=template&id=2257a5f5&lang.js"],"sourcesContent":["<!--\n CnDateRangePicker — preset-driven date range selector.\n\n Wraps two NcDateTimePicker instances + a preset NcSelect dropdown\n behind a single v-model. Selecting a preset (other than `custom`)\n auto-fills both pickers to `now − days` → `now`; selecting `custom`\n keeps the pickers manually editable so the user can dial in any\n span.\n\n Used internally by CnDashboardPage's date-range header and exposed\n publicly so bespoke screens (filter sidebars, report builders) can\n reuse the same control without composing the primitives themselves.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<div class=\"cn-date-range-picker\" data-testid=\"cn-date-range-picker\">\n\t\t<NcSelect\n\t\t\tclass=\"cn-date-range-picker__preset\"\n\t\t\t:modelValue=\"selectedPresetOption\"\n\t\t\t:options=\"presetOptions\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:clearable=\"false\"\n\t\t\t:searchable=\"false\"\n\t\t\t:aria-label=\"presetLabel\"\n\t\t\t:inputLabel=\"presetLabel\"\n\t\t\tlabel=\"label\"\n\t\t\tdata-testid=\"cn-date-range-picker-preset\"\n\t\t\t@update:modelValue=\"onPresetInput\" />\n\t\t<NcDateTimePicker\n\t\t\tclass=\"cn-date-range-picker__from\"\n\t\t\t:modelValue=\"fromDate\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:format=\"dateFormat\"\n\t\t\t:aria-label=\"fromLabel\"\n\t\t\t:placeholder=\"fromLabel\"\n\t\t\tdata-testid=\"cn-date-range-picker-from\"\n\t\t\t@change=\"onFromChange\" />\n\t\t<span class=\"cn-date-range-picker__separator\" aria-hidden=\"true\">→</span>\n\t\t<NcDateTimePicker\n\t\t\tclass=\"cn-date-range-picker__to\"\n\t\t\t:modelValue=\"toDate\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:format=\"dateFormat\"\n\t\t\t:aria-label=\"toLabel\"\n\t\t\t:placeholder=\"toLabel\"\n\t\t\tdata-testid=\"cn-date-range-picker-to\"\n\t\t\t@change=\"onToChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcDateTimePicker, NcSelect } from '@nextcloud/vue'\n\n/**\n * Default preset list used when the consumer doesn't provide one.\n * `days: null` marks the preset as a manual / custom range — selecting\n * it keeps the pickers editable without forcing a recomputed window.\n */\nexport const DEFAULT_DATE_RANGE_PRESETS = Object.freeze([\n\t{ id: 'last-8h', label: 'Last 8 hours', hours: 8 },\n\t{ id: 'last-24h', label: 'Last 24 hours', hours: 24 },\n\t{ id: 'today', label: 'Today', days: 1 },\n\t{ id: 'last-7', label: 'Last 7 days', days: 7 },\n\t{ id: 'last-30', label: 'Last 30 days', days: 30 },\n\t{ id: 'last-90', label: 'Last 90 days', days: 90 },\n\t{ id: 'custom', label: 'Custom range', days: null },\n])\n\n/**\n * Resolve a `period` preset into its calendar-aligned `{ from, to }`\n * window — the CURRENT week / month / quarter / year to date, not a\n * rolling span. ISO week semantics: weeks start on Monday.\n *\n * @param {'week'|'month'|'quarter'|'year'} period The calendar unit.\n * @param {Date} now Reference instant.\n * @return {{ from: string, to: string } | null} ISO-8601 UTC window or null.\n */\nfunction resolveCalendarPeriod(period, now) {\n\t// LOCAL calendar components, not UTC. \"Current month\" is a statement about\n\t// the reader's calendar, and every surface around this one is local: the\n\t// `datetime-local` inputs render these instants in local time, and\n\t// `localDateTimeInputToIso` reads them back the same way.\n\t//\n\t// Built from UTC, the window ended at 23:59:59.999Z on today's UTC date,\n\t// which in any zone ahead of UTC displays as the NEXT day — and on the last\n\t// day of a month, the next MONTH. Measured 2026-08-31 in CEST: \"Current\n\t// month\" showed a To of 1 September, and the range leaked two hours of\n\t// September into an August total.\n\tconst y = now.getFullYear()\n\tconst m = now.getMonth()\n\tconst d = now.getDate()\n\tlet start\n\tif (period === 'week') {\n\t\t// getDay() is 0 for Sunday; shift so Monday is the first day.\n\t\tconst offset = (now.getDay() + 6) % 7\n\t\tstart = new Date(y, m, (d - offset), 0, 0, 0, 0)\n\t} else if (period === 'month') {\n\t\tstart = new Date(y, m, 1, 0, 0, 0, 0)\n\t} else if (period === 'quarter') {\n\t\tstart = new Date(y, (Math.floor(m / 3) * 3), 1, 0, 0, 0, 0)\n\t} else if (period === 'year') {\n\t\tstart = new Date(y, 0, 1, 0, 0, 0, 0)\n\t} else {\n\t\treturn null\n\t}\n\n\tconst end = new Date(y, m, d, 23, 59, 59, 999)\n\treturn { from: start.toISOString(), to: end.toISOString() }\n}\n\n/**\n * Resolve a preset id into a `{ from, to }` ISO-8601 UTC window\n * ending at end-of-day today. `custom` returns `null` (the caller\n * SHALL preserve the previously selected dates).\n *\n * Three preset kinds, checked in order — a preset SHALL declare only one:\n * `period` (calendar-aligned: current week/month/quarter/year to date),\n * `hours` (rolling window ending now), `days` (rolling window of whole\n * days ending end-of-day today).\n *\n * @param {string|null} presetId Preset id to resolve.\n * @param {Array<{ id: string, days?: number|null, hours?: number, period?: string }>} presets Preset list.\n * @param {Date} [now] Override `now` for deterministic tests.\n * @return {{ from: string, to: string } | null} ISO-8601 UTC window or null.\n */\nexport function resolvePresetWindow(presetId, presets, now = new Date()) {\n\tif (!presetId || presetId === 'custom') {\n\t\treturn null\n\t}\n\tconst preset = (presets || []).find((p) => p.id === presetId)\n\tif (!preset) {\n\t\treturn null\n\t}\n\t// Calendar-aligned presets (\"Current month\") anchor to the start of the\n\t// calendar unit, NOT to `now − N days`. Checked first so a preset that\n\t// carries both `period` and a legacy `days` hint resolves as a period.\n\tif (typeof preset.period === 'string') {\n\t\treturn resolveCalendarPeriod(preset.period, now)\n\t}\n\t// Hour-granularity presets are ROLLING windows ending at the exact\n\t// current instant — `now − N hours → now` — so \"Last 8 hours\" means\n\t// the trailing 8h, not a calendar-day-aligned span.\n\tif (typeof preset.hours === 'number') {\n\t\tconst end = new Date(now.getTime())\n\t\tconst start = new Date(now.getTime() - preset.hours * 3600000)\n\t\treturn { from: start.toISOString(), to: end.toISOString() }\n\t}\n\tif (typeof preset.days !== 'number') {\n\t\treturn null\n\t}\n\t// Day-granularity presets are calendar-aligned: midnight UTC start of\n\t// the (days-1)-th day back through end-of-day UTC today. `today`\n\t// (days=1) resolves to \"00:00 → 23:59 of today\".\n\tconst end = new Date(Date.UTC(\n\t\tnow.getUTCFullYear(),\n\t\tnow.getUTCMonth(),\n\t\tnow.getUTCDate(),\n\t\t23,\n\t\t59,\n\t\t59,\n\t\t999,\n\t))\n\tconst start = new Date(end)\n\tstart.setUTCDate(start.getUTCDate() - (preset.days - 1))\n\tstart.setUTCHours(0, 0, 0, 0)\n\treturn { from: start.toISOString(), to: end.toISOString() }\n}\n\n/**\n * CnDateRangePicker — preset-driven date range selector.\n *\n * ```vue\n * <CnDateRangePicker\n * v-model=\"range\"\n * :presets=\"presets\"\n * :disabled=\"loading\" />\n * ```\n *\n * The component owns layout. Selecting a preset (other than `custom`)\n * auto-fills both pickers from `now − days` → `now`. Selecting\n * `custom` keeps both pickers editable so the user can dial in any\n * arbitrary range; typing in either picker emits a `custom` value.\n *\n * The exported `DEFAULT_DATE_RANGE_PRESETS` list mirrors the defaults\n * applied by `CnDashboardPage` when its `dateRange.presets` is\n * omitted: today / last 7 / last 30 / last 90 / custom.\n */\nexport default {\n\tname: 'CnDateRangePicker',\n\n\tcomponents: {\n\t\tNcDateTimePicker,\n\t\tNcSelect,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Current range value. `from` / `to` are ISO-8601 UTC strings;\n\t\t * `preset` is the id of the preset that produced the range (or\n\t\t * `'custom'` when the user typed values by hand).\n\t\t *\n\t\t * @type {{ from: string, to: string, preset: string }|null}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The same value as `value`, under Vue 3's own v-model name.\n\t\t *\n\t\t * ⚠️ WITHOUT THIS, `v-model` ON THIS COMPONENT DOES NOTHING. Vue 3\n\t\t * compiles `v-model=\"x\"` to `:modelValue` + `@update:modelValue`, so a\n\t\t * component declaring only `value`/`input` never receives the prop and\n\t\t * its emit is never heard — silently, looking exactly like a component\n\t\t * that works.\n\t\t *\n\t\t * `value` stays the public name; both are accepted. The default is\n\t\t * `undefined` so \"not passed\" is distinguishable from \"passed empty\".\n\t\t *\n\t\t * @type {object|string}\n\t\t */\n\t\tmodelValue: { type: [Object, String], default: undefined },\n\t\t/**\n\t\t * Preset list. Each entry declares ONE window kind: `period`\n\t\t * (`'week'|'month'|'quarter'|'year'` — the current calendar unit\n\t\t * to date, e.g. \"Current month\" = the 1st through today), `hours`\n\t\t * (rolling window ending now), or `days` (rolling whole days\n\t\t * ending today). Use `days: null` to mark a preset as manual (e.g.\n\t\t * the canonical `custom` entry). Defaults to\n\t\t * `DEFAULT_DATE_RANGE_PRESETS`.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, days?: number|null, hours?: number, period?: 'week'|'month'|'quarter'|'year' }>}\n\t\t */\n\t\tpresets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => DEFAULT_DATE_RANGE_PRESETS.map((p) => ({ ...p })),\n\t\t},\n\n\t\t/**\n\t\t * When `true`, both pickers AND the preset select are\n\t\t * disabled. Mirrors `NcDateTimePicker.disabled`.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Date input visual format string, forwarded to the underlying\n\t\t * `NcDateTimePicker`s. Default: `'yyyy-MM-dd'`.\n\t\t *\n\t\t * DATE-FNS TOKENS, not moment's. `@nextcloud/vue` 9 replaced the\n\t\t * picker's moment.js backend with date-fns, which rejects `YYYY`\n\t\t * (year-of-week) and `DD` (day-of-year) where `yyyy` / `dd` are meant —\n\t\t * and it THROWS rather than warning, so the whole picker fails to render.\n\t\t * This default was `'YYYY-MM-DD'`, correct under v8 and broken under v9:\n\t\t * the component threw with its own default props until the real-render\n\t\t * smoke lane surfaced it. A consumer still passing a moment-style string\n\t\t * needs the same substitution.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdateFormat: {\n\t\t\ttype: String,\n\t\t\tdefault: 'yyyy-MM-dd',\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the preset dropdown.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tpresetLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Range preset'),\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the start-of-range picker.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfromLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'From'),\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the end-of-range picker.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttoLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'To'),\n\t\t},\n\t},\n\n\temits: ['input', 'update:modelValue'],\n\n\tcomputed: {\n\t\t/**\n\t\t * The value the consumer actually bound, whichever prop they used.\n\t\t *\n\t\t * @return {object|string|null} The bound value.\n\t\t */\n\t\tboundValue() {\n\t\t\treturn this.modelValue !== undefined ? this.modelValue : this.value\n\t\t},\n\n\t\tfromDate() {\n\t\t\treturn this.boundValue?.from ? new Date(this.boundValue.from) : null\n\t\t},\n\n\t\ttoDate() {\n\t\t\treturn this.boundValue?.to ? new Date(this.boundValue.to) : null\n\t\t},\n\n\t\tselectedPresetId() {\n\t\t\treturn this.boundValue?.preset || 'custom'\n\t\t},\n\n\t\tpresetOptions() {\n\t\t\treturn this.presets.map((p) => ({ id: p.id, label: p.label, days: p.days }))\n\t\t},\n\n\t\tselectedPresetOption() {\n\t\t\tconst match = this.presetOptions.find((p) => p.id === this.selectedPresetId)\n\t\t\t// Fall back to the first option if the current preset id\n\t\t\t// isn't in the provided list (e.g. consumer removed `today`).\n\t\t\treturn match || this.presetOptions[0] || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Tell the consumer the value changed, in both v-model dialects.\n\t\t *\n\t\t * BOTH are emitted, always: a consumer on `@input` and a consumer on\n\t\t * `v-model` are the same consumer as far as this component knows, and\n\t\t * emitting only one silently breaks half of them.\n\t\t *\n\t\t * @param {object|string|null} next The new value.\n\t\t * @return {void}\n\t\t */\n\t\temitValue(next) {\n\t\t\t/**\n\t\t\t * @event input The value changed. Vue 2's v-model dialect, kept for\n\t\t\t * existing consumers.\n\t\t\t * @type {object|string|null}\n\t\t\t */\n\t\t\tthis.$emit('input', next)\n\t\t\t/**\n\t\t\t * @event update:modelValue The value changed. Vue 3's v-model\n\t\t\t * dialect — what a plain `v-model` listens for.\n\t\t\t * @type {object|string|null}\n\t\t\t */\n\t\t\tthis.$emit('update:modelValue', next)\n\t\t},\n\n\t\t/**\n\t\t * Handle a preset selection. When the preset has a numeric\n\t\t * `days` value we recompute the window; for `custom` (or any\n\t\t * preset with `days: null`) we keep the current `from` / `to`\n\t\t * and only flip `preset` so the parent knows the user opted\n\t\t * into manual editing.\n\t\t *\n\t\t * @param {{ id: string, days: number|null } | null} option\n\t\t * The selected option (NcSelect emits the full option object).\n\t\t */\n\t\tonPresetInput(option) {\n\t\t\tif (!option) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst win = resolvePresetWindow(option.id, this.presets)\n\t\t\tif (win) {\n\t\t\t\t/**\n\t\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t\t * @type {{ from: string|null, to: string|null, preset: string }}\n\t\t\t\t */\n\t\t\t\tthis.emitValue({ from: win.from, to: win.to, preset: option.id })\n\t\t\t} else {\n\t\t\t\t// Custom or null-days preset → keep the existing window,\n\t\t\t\t// just change the preset id.\n\t\t\t\tconst from = this.boundValue?.from || null\n\t\t\t\tconst to = this.boundValue?.to || null\n\t\t\t\t/**\n\t\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t\t */\n\t\t\t\tthis.emitValue({ from, to, preset: option.id })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a manual edit of the `from` date input. Typing into\n\t\t * either picker switches the preset id to `custom` so the\n\t\t * parent / dashboard knows the user dialled in a bespoke\n\t\t * window.\n\t\t *\n\t\t * @param {Date|null} date The new date.\n\t\t */\n\t\tonFromChange(date) {\n\t\t\tconst from = date ? toIsoStartOfDay(date) : null\n\t\t\tconst to = this.boundValue?.to || null\n\t\t\t/**\n\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t */\n\t\t\tthis.emitValue({ from, to, preset: 'custom' })\n\t\t},\n\n\t\t/**\n\t\t * Handle a manual edit of the `to` date input.\n\t\t *\n\t\t * @param {Date|null} date The new date.\n\t\t */\n\t\tonToChange(date) {\n\t\t\tconst from = this.boundValue?.from || null\n\t\t\tconst to = date ? toIsoEndOfDay(date) : null\n\t\t\t/**\n\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t */\n\t\t\tthis.emitValue({ from, to, preset: 'custom' })\n\t\t},\n\t},\n}\n\n/**\n * Coerce a JS Date to the ISO-8601 string at start-of-day UTC.\n *\n * @param {Date} date Source date.\n * @return {string} ISO-8601 UTC string at 00:00:00.000Z.\n */\nfunction toIsoStartOfDay(date) {\n\tconst d = new Date(Date.UTC(\n\t\tdate.getUTCFullYear(),\n\t\tdate.getUTCMonth(),\n\t\tdate.getUTCDate(),\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0,\n\t))\n\treturn d.toISOString()\n}\n\n/**\n * Coerce a JS Date to the ISO-8601 string at end-of-day UTC.\n *\n * @param {Date} date Source date.\n * @return {string} ISO-8601 UTC string at 23:59:59.999Z.\n */\nfunction toIsoEndOfDay(date) {\n\tconst d = new Date(Date.UTC(\n\t\tdate.getUTCFullYear(),\n\t\tdate.getUTCMonth(),\n\t\tdate.getUTCDate(),\n\t\t23,\n\t\t59,\n\t\t59,\n\t\t999,\n\t))\n\treturn d.toISOString()\n}\n</script>\n\n<style scoped>\n.cn-date-range-picker {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-date-range-picker__preset {\n\tmin-width: 180px;\n}\n\n.cn-date-range-picker__from,\n.cn-date-range-picker__to {\n\tmin-width: 160px;\n}\n\n.cn-date-range-picker__separator {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode"],"mappings":";;;AAiCM,EAAA,KAAK,EAAC,sBAAU;AAAA,EAAA,aAAA,EAAA,sBAAA;;;;;;;AAAnB,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;IACAC,WAWoB,CAAA,mBAAA,EAAA;AAVpB,MAAA,KAAK,EAAC,8BAAgB;AACrB,MAAA,UAAU,EAAE,QAAU,CAAA,oBAAA;AACtB,MAAA,OAAO,EAAE,QAAa,CAAA,aAAA;AACtB,MAAA,QAAQ,EAAE,MAAQ,CAAA,QAAA;AAClB,MAAA,SAAS,EAAE,KAAK;AAChB,MAAA,UAAU,EAAE,KAAE;AACd,MAAA,YAAU,EAAE,MAAW,CAAA,WAAA;AACvB,MAAA,UAAU,EAAE,MAAM,CAAA,WAAA;AACnB,MAAA,KAAK,EAAC,OAAO;AACb,MAAA,aAAW,EAAC,6BAAO;AAClB,MAAA,qBAAiB,EAAE,QAAA,CAAA,aAAA;;IACpBA;AACA,MAAA,KAAK,EAAC,4BAA4B;AACjC,MAAA,UAAU,EAAE,QAAQ,CAAA,QAAA;AACpB,MAAA,QAAE,EAAA,MAAA,CAAA,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnDateRangePicker.vue3.js","sources":["../../../../src/components/CnDateRangePicker/CnDateRangePicker.vue?vue&type=template&id=2257a5f5&lang.js"],"sourcesContent":["<!--\n CnDateRangePicker — preset-driven date range selector.\n\n Wraps two NcDateTimePicker instances + a preset NcSelect dropdown\n behind a single v-model. Selecting a preset (other than `custom`)\n auto-fills both pickers to `now − days` → `now`; selecting `custom`\n keeps the pickers manually editable so the user can dial in any\n span.\n\n Used internally by CnDashboardPage's date-range header and exposed\n publicly so bespoke screens (filter sidebars, report builders) can\n reuse the same control without composing the primitives themselves.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<div class=\"cn-date-range-picker\" data-testid=\"cn-date-range-picker\">\n\t\t<NcSelect\n\t\t\tclass=\"cn-date-range-picker__preset\"\n\t\t\t:modelValue=\"selectedPresetOption\"\n\t\t\t:options=\"presetOptions\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:clearable=\"false\"\n\t\t\t:searchable=\"false\"\n\t\t\t:aria-label=\"presetLabel\"\n\t\t\t:inputLabel=\"presetLabel\"\n\t\t\tlabel=\"label\"\n\t\t\tdata-testid=\"cn-date-range-picker-preset\"\n\t\t\t@update:modelValue=\"onPresetInput\" />\n\t\t<NcDateTimePicker\n\t\t\tclass=\"cn-date-range-picker__from\"\n\t\t\t:modelValue=\"fromDate\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:format=\"dateFormat\"\n\t\t\t:aria-label=\"fromLabel\"\n\t\t\t:placeholder=\"fromLabel\"\n\t\t\tdata-testid=\"cn-date-range-picker-from\"\n\t\t\t@change=\"onFromChange\" />\n\t\t<span class=\"cn-date-range-picker__separator\" aria-hidden=\"true\">→</span>\n\t\t<NcDateTimePicker\n\t\t\tclass=\"cn-date-range-picker__to\"\n\t\t\t:modelValue=\"toDate\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t:format=\"dateFormat\"\n\t\t\t:aria-label=\"toLabel\"\n\t\t\t:placeholder=\"toLabel\"\n\t\t\tdata-testid=\"cn-date-range-picker-to\"\n\t\t\t@change=\"onToChange\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcDateTimePicker, NcSelect } from '@nextcloud/vue'\n\n/**\n * Default preset list used when the consumer doesn't provide one.\n * `days: null` marks the preset as a manual / custom range — selecting\n * it keeps the pickers editable without forcing a recomputed window.\n */\nexport const DEFAULT_DATE_RANGE_PRESETS = Object.freeze([\n\t{ id: 'last-8h', label: 'Last 8 hours', hours: 8 },\n\t{ id: 'last-24h', label: 'Last 24 hours', hours: 24 },\n\t{ id: 'today', label: 'Today', days: 1 },\n\t{ id: 'last-7', label: 'Last 7 days', days: 7 },\n\t{ id: 'last-30', label: 'Last 30 days', days: 30 },\n\t{ id: 'last-90', label: 'Last 90 days', days: 90 },\n\t{ id: 'custom', label: 'Custom range', days: null },\n])\n\n/**\n * The library's own translations of the default preset labels. Literal\n * `t()` calls, so the strings are extracted into the `nextcloud-vue`\n * catalogue.\n *\n * @type {{[label: string]: function(): string}}\n */\nconst LIBRARY_PRESET_LABELS = Object.freeze({\n\t'Last 8 hours': () => t('nextcloud-vue', 'Last 8 hours'),\n\t'Last 24 hours': () => t('nextcloud-vue', 'Last 24 hours'),\n\tToday: () => t('nextcloud-vue', 'Today'),\n\t'Last 7 days': () => t('nextcloud-vue', 'Last 7 days'),\n\t'Last 30 days': () => t('nextcloud-vue', 'Last 30 days'),\n\t'Last 90 days': () => t('nextcloud-vue', 'Last 90 days'),\n\t'Custom range': () => t('nextcloud-vue', 'Custom range'),\n})\n\n/**\n * A preset label in the user's language: the host translation when the app's\n * catalogue has the key, else the library's own translation of a default\n * label (\"Last 30 days\"), else the label as written.\n *\n * @spec openspec/changes/screens-dashboard-i18n/specs/dashboard-page/spec.md#requirement-date-range-preset-labels-are-translated\n * @param {string} label The preset label (an i18n key).\n * @param {function(string): string} [translate] The host translate function.\n * @return {string} The translated label.\n */\nexport function translatePresetLabel(label, translate) {\n\tif (typeof label !== 'string' || label === '') {\n\t\treturn ''\n\t}\n\tconst own = typeof translate === 'function' ? translate(label) : label\n\tif (typeof own === 'string' && own !== '' && own !== label) {\n\t\treturn own\n\t}\n\treturn Object.hasOwn(LIBRARY_PRESET_LABELS, label) ? LIBRARY_PRESET_LABELS[label]() : label\n}\n\n/**\n * Resolve a `period` preset into its calendar-aligned `{ from, to }`\n * window — the CURRENT week / month / quarter / year to date, not a\n * rolling span. ISO week semantics: weeks start on Monday.\n *\n * @param {'week'|'month'|'quarter'|'year'} period The calendar unit.\n * @param {Date} now Reference instant.\n * @return {{ from: string, to: string } | null} ISO-8601 UTC window or null.\n */\nfunction resolveCalendarPeriod(period, now) {\n\t// LOCAL calendar components, not UTC. \"Current month\" is a statement about\n\t// the reader's calendar, and every surface around this one is local: the\n\t// `datetime-local` inputs render these instants in local time, and\n\t// `localDateTimeInputToIso` reads them back the same way.\n\t//\n\t// Built from UTC, the window ended at 23:59:59.999Z on today's UTC date,\n\t// which in any zone ahead of UTC displays as the NEXT day — and on the last\n\t// day of a month, the next MONTH. Measured 2026-08-31 in CEST: \"Current\n\t// month\" showed a To of 1 September, and the range leaked two hours of\n\t// September into an August total.\n\tconst y = now.getFullYear()\n\tconst m = now.getMonth()\n\tconst d = now.getDate()\n\tlet start\n\tif (period === 'week') {\n\t\t// getDay() is 0 for Sunday; shift so Monday is the first day.\n\t\tconst offset = (now.getDay() + 6) % 7\n\t\tstart = new Date(y, m, (d - offset), 0, 0, 0, 0)\n\t} else if (period === 'month') {\n\t\tstart = new Date(y, m, 1, 0, 0, 0, 0)\n\t} else if (period === 'quarter') {\n\t\tstart = new Date(y, (Math.floor(m / 3) * 3), 1, 0, 0, 0, 0)\n\t} else if (period === 'year') {\n\t\tstart = new Date(y, 0, 1, 0, 0, 0, 0)\n\t} else {\n\t\treturn null\n\t}\n\n\tconst end = new Date(y, m, d, 23, 59, 59, 999)\n\treturn { from: start.toISOString(), to: end.toISOString() }\n}\n\n/**\n * Resolve a preset id into a `{ from, to }` ISO-8601 UTC window\n * ending at end-of-day today. `custom` returns `null` (the caller\n * SHALL preserve the previously selected dates).\n *\n * Three preset kinds, checked in order — a preset SHALL declare only one:\n * `period` (calendar-aligned: current week/month/quarter/year to date),\n * `hours` (rolling window ending now), `days` (rolling window of whole\n * days ending end-of-day today).\n *\n * @param {string|null} presetId Preset id to resolve.\n * @param {Array<{ id: string, days?: number|null, hours?: number, period?: string }>} presets Preset list.\n * @param {Date} [now] Override `now` for deterministic tests.\n * @return {{ from: string, to: string } | null} ISO-8601 UTC window or null.\n */\nexport function resolvePresetWindow(presetId, presets, now = new Date()) {\n\tif (!presetId || presetId === 'custom') {\n\t\treturn null\n\t}\n\tconst preset = (presets || []).find((p) => p.id === presetId)\n\tif (!preset) {\n\t\treturn null\n\t}\n\t// Calendar-aligned presets (\"Current month\") anchor to the start of the\n\t// calendar unit, NOT to `now − N days`. Checked first so a preset that\n\t// carries both `period` and a legacy `days` hint resolves as a period.\n\tif (typeof preset.period === 'string') {\n\t\treturn resolveCalendarPeriod(preset.period, now)\n\t}\n\t// Hour-granularity presets are ROLLING windows ending at the exact\n\t// current instant — `now − N hours → now` — so \"Last 8 hours\" means\n\t// the trailing 8h, not a calendar-day-aligned span.\n\tif (typeof preset.hours === 'number') {\n\t\tconst end = new Date(now.getTime())\n\t\tconst start = new Date(now.getTime() - preset.hours * 3600000)\n\t\treturn { from: start.toISOString(), to: end.toISOString() }\n\t}\n\tif (typeof preset.days !== 'number') {\n\t\treturn null\n\t}\n\t// Day-granularity presets are calendar-aligned: midnight UTC start of\n\t// the (days-1)-th day back through end-of-day UTC today. `today`\n\t// (days=1) resolves to \"00:00 → 23:59 of today\".\n\tconst end = new Date(Date.UTC(\n\t\tnow.getUTCFullYear(),\n\t\tnow.getUTCMonth(),\n\t\tnow.getUTCDate(),\n\t\t23,\n\t\t59,\n\t\t59,\n\t\t999,\n\t))\n\tconst start = new Date(end)\n\tstart.setUTCDate(start.getUTCDate() - (preset.days - 1))\n\tstart.setUTCHours(0, 0, 0, 0)\n\treturn { from: start.toISOString(), to: end.toISOString() }\n}\n\n/**\n * CnDateRangePicker — preset-driven date range selector.\n *\n * ```vue\n * <CnDateRangePicker\n * v-model=\"range\"\n * :presets=\"presets\"\n * :disabled=\"loading\" />\n * ```\n *\n * The component owns layout. Selecting a preset (other than `custom`)\n * auto-fills both pickers from `now − days` → `now`. Selecting\n * `custom` keeps both pickers editable so the user can dial in any\n * arbitrary range; typing in either picker emits a `custom` value.\n *\n * The exported `DEFAULT_DATE_RANGE_PRESETS` list mirrors the defaults\n * applied by `CnDashboardPage` when its `dateRange.presets` is\n * omitted: today / last 7 / last 30 / last 90 / custom.\n */\nexport default {\n\tname: 'CnDateRangePicker',\n\n\tcomponents: {\n\t\tNcDateTimePicker,\n\t\tNcSelect,\n\t},\n\n\tinject: {\n\t\t/** Host translate function provided by CnAppRoot, for app-authored preset labels. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Current range value. `from` / `to` are ISO-8601 UTC strings;\n\t\t * `preset` is the id of the preset that produced the range (or\n\t\t * `'custom'` when the user typed values by hand).\n\t\t *\n\t\t * @type {{ from: string, to: string, preset: string }|null}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The same value as `value`, under Vue 3's own v-model name.\n\t\t *\n\t\t * ⚠️ WITHOUT THIS, `v-model` ON THIS COMPONENT DOES NOTHING. Vue 3\n\t\t * compiles `v-model=\"x\"` to `:modelValue` + `@update:modelValue`, so a\n\t\t * component declaring only `value`/`input` never receives the prop and\n\t\t * its emit is never heard — silently, looking exactly like a component\n\t\t * that works.\n\t\t *\n\t\t * `value` stays the public name; both are accepted. The default is\n\t\t * `undefined` so \"not passed\" is distinguishable from \"passed empty\".\n\t\t *\n\t\t * @type {object|string}\n\t\t */\n\t\tmodelValue: { type: [Object, String], default: undefined },\n\t\t/**\n\t\t * Preset list. Each entry declares ONE window kind: `period`\n\t\t * (`'week'|'month'|'quarter'|'year'` — the current calendar unit\n\t\t * to date, e.g. \"Current month\" = the 1st through today), `hours`\n\t\t * (rolling window ending now), or `days` (rolling whole days\n\t\t * ending today). Use `days: null` to mark a preset as manual (e.g.\n\t\t * the canonical `custom` entry). Defaults to\n\t\t * `DEFAULT_DATE_RANGE_PRESETS`.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, days?: number|null, hours?: number, period?: 'week'|'month'|'quarter'|'year' }>}\n\t\t */\n\t\tpresets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => DEFAULT_DATE_RANGE_PRESETS.map((p) => ({ ...p })),\n\t\t},\n\n\t\t/**\n\t\t * When `true`, both pickers AND the preset select are\n\t\t * disabled. Mirrors `NcDateTimePicker.disabled`.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Date input visual format string, forwarded to the underlying\n\t\t * `NcDateTimePicker`s. Default: `'yyyy-MM-dd'`.\n\t\t *\n\t\t * DATE-FNS TOKENS, not moment's. `@nextcloud/vue` 9 replaced the\n\t\t * picker's moment.js backend with date-fns, which rejects `YYYY`\n\t\t * (year-of-week) and `DD` (day-of-year) where `yyyy` / `dd` are meant —\n\t\t * and it THROWS rather than warning, so the whole picker fails to render.\n\t\t * This default was `'YYYY-MM-DD'`, correct under v8 and broken under v9:\n\t\t * the component threw with its own default props until the real-render\n\t\t * smoke lane surfaced it. A consumer still passing a moment-style string\n\t\t * needs the same substitution.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdateFormat: {\n\t\t\ttype: String,\n\t\t\tdefault: 'yyyy-MM-dd',\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the preset dropdown.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tpresetLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Range preset'),\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the start-of-range picker.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tfromLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'From'),\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for the end-of-range picker.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttoLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'To'),\n\t\t},\n\t},\n\n\temits: ['input', 'update:modelValue'],\n\n\tcomputed: {\n\t\t/**\n\t\t * The value the consumer actually bound, whichever prop they used.\n\t\t *\n\t\t * @return {object|string|null} The bound value.\n\t\t */\n\t\tboundValue() {\n\t\t\treturn this.modelValue !== undefined ? this.modelValue : this.value\n\t\t},\n\n\t\tfromDate() {\n\t\t\treturn this.boundValue?.from ? new Date(this.boundValue.from) : null\n\t\t},\n\n\t\ttoDate() {\n\t\t\treturn this.boundValue?.to ? new Date(this.boundValue.to) : null\n\t\t},\n\n\t\tselectedPresetId() {\n\t\t\treturn this.boundValue?.preset || 'custom'\n\t\t},\n\n\t\tpresetOptions() {\n\t\t\treturn this.presets.map((p) => ({ id: p.id, label: translatePresetLabel(p.label, this.cnTranslate), days: p.days }))\n\t\t},\n\n\t\tselectedPresetOption() {\n\t\t\tconst match = this.presetOptions.find((p) => p.id === this.selectedPresetId)\n\t\t\t// Fall back to the first option if the current preset id\n\t\t\t// isn't in the provided list (e.g. consumer removed `today`).\n\t\t\treturn match || this.presetOptions[0] || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Tell the consumer the value changed, in both v-model dialects.\n\t\t *\n\t\t * BOTH are emitted, always: a consumer on `@input` and a consumer on\n\t\t * `v-model` are the same consumer as far as this component knows, and\n\t\t * emitting only one silently breaks half of them.\n\t\t *\n\t\t * @param {object|string|null} next The new value.\n\t\t * @return {void}\n\t\t */\n\t\temitValue(next) {\n\t\t\t/**\n\t\t\t * @event input The value changed. Vue 2's v-model dialect, kept for\n\t\t\t * existing consumers.\n\t\t\t * @type {object|string|null}\n\t\t\t */\n\t\t\tthis.$emit('input', next)\n\t\t\t/**\n\t\t\t * @event update:modelValue The value changed. Vue 3's v-model\n\t\t\t * dialect — what a plain `v-model` listens for.\n\t\t\t * @type {object|string|null}\n\t\t\t */\n\t\t\tthis.$emit('update:modelValue', next)\n\t\t},\n\n\t\t/**\n\t\t * Handle a preset selection. When the preset has a numeric\n\t\t * `days` value we recompute the window; for `custom` (or any\n\t\t * preset with `days: null`) we keep the current `from` / `to`\n\t\t * and only flip `preset` so the parent knows the user opted\n\t\t * into manual editing.\n\t\t *\n\t\t * @param {{ id: string, days: number|null } | null} option\n\t\t * The selected option (NcSelect emits the full option object).\n\t\t */\n\t\tonPresetInput(option) {\n\t\t\tif (!option) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst win = resolvePresetWindow(option.id, this.presets)\n\t\t\tif (win) {\n\t\t\t\t/**\n\t\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t\t * @type {{ from: string|null, to: string|null, preset: string }}\n\t\t\t\t */\n\t\t\t\tthis.emitValue({ from: win.from, to: win.to, preset: option.id })\n\t\t\t} else {\n\t\t\t\t// Custom or null-days preset → keep the existing window,\n\t\t\t\t// just change the preset id.\n\t\t\t\tconst from = this.boundValue?.from || null\n\t\t\t\tconst to = this.boundValue?.to || null\n\t\t\t\t/**\n\t\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t\t */\n\t\t\t\tthis.emitValue({ from, to, preset: option.id })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a manual edit of the `from` date input. Typing into\n\t\t * either picker switches the preset id to `custom` so the\n\t\t * parent / dashboard knows the user dialled in a bespoke\n\t\t * window.\n\t\t *\n\t\t * @param {Date|null} date The new date.\n\t\t */\n\t\tonFromChange(date) {\n\t\t\tconst from = date ? toIsoStartOfDay(date) : null\n\t\t\tconst to = this.boundValue?.to || null\n\t\t\t/**\n\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t */\n\t\t\tthis.emitValue({ from, to, preset: 'custom' })\n\t\t},\n\n\t\t/**\n\t\t * Handle a manual edit of the `to` date input.\n\t\t *\n\t\t * @param {Date|null} date The new date.\n\t\t */\n\t\tonToChange(date) {\n\t\t\tconst from = this.boundValue?.from || null\n\t\t\tconst to = date ? toIsoEndOfDay(date) : null\n\t\t\t/**\n\t\t\t * @event input v-model emit. Payload: `{ from, to, preset }` where `from`/`to` are ISO-8601 timestamps (or null) and `preset` is one of the preset ids or `'custom'`.\n\t\t\t */\n\t\t\tthis.emitValue({ from, to, preset: 'custom' })\n\t\t},\n\t},\n}\n\n/**\n * Coerce a JS Date to the ISO-8601 string at start-of-day UTC.\n *\n * @param {Date} date Source date.\n * @return {string} ISO-8601 UTC string at 00:00:00.000Z.\n */\nfunction toIsoStartOfDay(date) {\n\tconst d = new Date(Date.UTC(\n\t\tdate.getUTCFullYear(),\n\t\tdate.getUTCMonth(),\n\t\tdate.getUTCDate(),\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0,\n\t))\n\treturn d.toISOString()\n}\n\n/**\n * Coerce a JS Date to the ISO-8601 string at end-of-day UTC.\n *\n * @param {Date} date Source date.\n * @return {string} ISO-8601 UTC string at 23:59:59.999Z.\n */\nfunction toIsoEndOfDay(date) {\n\tconst d = new Date(Date.UTC(\n\t\tdate.getUTCFullYear(),\n\t\tdate.getUTCMonth(),\n\t\tdate.getUTCDate(),\n\t\t23,\n\t\t59,\n\t\t59,\n\t\t999,\n\t))\n\treturn d.toISOString()\n}\n</script>\n\n<style scoped>\n.cn-date-range-picker {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-date-range-picker__preset {\n\tmin-width: 180px;\n}\n\n.cn-date-range-picker__from,\n.cn-date-range-picker__to {\n\tmin-width: 160px;\n}\n\n.cn-date-range-picker__separator {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode"],"mappings":";;;AAiCM,EAAA,KAAK,EAAC,sBAAU;AAAA,EAAA,aAAA,EAAA,sBAAA;;;;;;;AAAnB,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;IACAC,WAWoB,CAAA,mBAAA,EAAA;AAVpB,MAAA,KAAK,EAAC,8BAAgB;AACrB,MAAA,UAAU,EAAE,QAAU,CAAA,oBAAA;AACtB,MAAA,OAAO,EAAE,QAAa,CAAA,aAAA;AACtB,MAAA,QAAQ,EAAE,MAAQ,CAAA,QAAA;AAClB,MAAA,SAAS,EAAE,KAAK;AAChB,MAAA,UAAU,EAAE,KAAE;AACd,MAAA,YAAU,EAAE,MAAW,CAAA,WAAA;AACvB,MAAA,UAAU,EAAE,MAAM,CAAA,WAAA;AACnB,MAAA,KAAK,EAAC,OAAO;AACb,MAAA,aAAW,EAAC,6BAAO;AAClB,MAAA,qBAAiB,EAAE,QAAA,CAAA,aAAA;;IACpBA;AACA,MAAA,KAAK,EAAC,4BAA4B;AACjC,MAAA,UAAU,EAAE,QAAQ,CAAA,QAAA;AACpB,MAAA,QAAE,EAAA,MAAA,CAAA,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -9,7 +9,9 @@ import __sfc_main$7 from '../CnObjectFilesWidget/CnObjectFilesWidget.vue.js';
|
|
|
9
9
|
import __sfc_main$3 from '../CnObjectGeoWidget/CnObjectGeoWidget.vue.js';
|
|
10
10
|
import __sfc_main$4 from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js';
|
|
11
11
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js';
|
|
12
|
+
import { normalizeLook } from '../../composables/useLook.js';
|
|
12
13
|
import { isAppInstalled } from '../../utils/appInstalled.js';
|
|
14
|
+
import { isActivityWidget } from '../../utils/headerMeta.js';
|
|
13
15
|
import { PANEL_ACTION_SINK } from '../../utils/panelActions.js';
|
|
14
16
|
import { widgetContentOf, widgetTitleOf, isDataWidgetDef, isRelatedWidgetDef, isGeoWidgetDef, isIntegrationWidgetDef, isContentOnlyWidgetDef, isCardWidgetDef, resolveRegistryRenderer } from '../../utils/widgetDispatch.js';
|
|
15
17
|
import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js';
|
|
@@ -87,6 +89,11 @@ var script = {
|
|
|
87
89
|
from: PANEL_ACTION_SINK,
|
|
88
90
|
default: null,
|
|
89
91
|
},
|
|
92
|
+
|
|
93
|
+
/** The app's look, provided by CnAppRoot or CnPageRenderer (`nextcloud` or `board`). */
|
|
94
|
+
cnLook: { default: 'nextcloud' },
|
|
95
|
+
/** The host's translate (CnAppRoot provides `cnTranslate`). */
|
|
96
|
+
cnTranslate: { default: () => (key) => key },
|
|
90
97
|
},
|
|
91
98
|
|
|
92
99
|
/**
|
|
@@ -535,7 +542,9 @@ var script = {
|
|
|
535
542
|
integrationProps() {
|
|
536
543
|
return {
|
|
537
544
|
surface: this.surface,
|
|
538
|
-
|
|
545
|
+
// Through the host's translate: an integration card (CnDetailCard)
|
|
546
|
+
// prints its title as given.
|
|
547
|
+
title: this.translateLabel(this.widget?.title || ''),
|
|
539
548
|
// A provider opting into `bareWidget` is being rendered where the
|
|
540
549
|
// tab strip already supplies the card and the title, so tell it to
|
|
541
550
|
// drop its own. Without this the panel gets a titled card inside
|
|
@@ -591,9 +600,46 @@ var script = {
|
|
|
591
600
|
surface: this.surface,
|
|
592
601
|
});
|
|
593
602
|
}
|
|
603
|
+
// The board look: a self-titled card (an audit trail, a calendar)
|
|
604
|
+
// takes the manifest title, so the side column's History card says
|
|
605
|
+
// what the board says instead of the widget's own default.
|
|
606
|
+
const title = this.cardTitle;
|
|
607
|
+
if (title) {
|
|
608
|
+
base.title = title;
|
|
609
|
+
}
|
|
594
610
|
return { ...base, ...this.content }
|
|
595
611
|
},
|
|
596
612
|
|
|
613
|
+
/**
|
|
614
|
+
* The title a self-titled renderer gets under the board look: the
|
|
615
|
+
* manifest title through the host's translate, else History for the
|
|
616
|
+
* activity widget. Empty outside the board look, in a tab panel, and
|
|
617
|
+
* when `content.title` already names the card.
|
|
618
|
+
*
|
|
619
|
+
* @return {string} The title, or ''.
|
|
620
|
+
* @spec openspec/changes/screens-detail-labels-parity/specs/detail-labels-board-look/spec.md#requirement-a-side-card-takes-its-manifest-title
|
|
621
|
+
*/
|
|
622
|
+
cardTitle() {
|
|
623
|
+
if (normalizeLook(this.cnLook) !== 'board' || this.isBare || !this.widget || this.isContentOnly || this.isCard) {
|
|
624
|
+
return ''
|
|
625
|
+
}
|
|
626
|
+
// Only a renderer that takes a `title` prop: on any other the value
|
|
627
|
+
// would land on its root element as a tooltip.
|
|
628
|
+
const declared = this.renderer && this.renderer.props;
|
|
629
|
+
const takesTitle = Array.isArray(declared) ? declared.includes('title') : Boolean(declared && Object.hasOwn(declared, 'title'));
|
|
630
|
+
if (!takesTitle) {
|
|
631
|
+
return ''
|
|
632
|
+
}
|
|
633
|
+
if (typeof this.content.title === 'string' && this.content.title !== '') {
|
|
634
|
+
return ''
|
|
635
|
+
}
|
|
636
|
+
const title = widgetTitleOf(this.widget);
|
|
637
|
+
if (title) {
|
|
638
|
+
return this.translateLabel(title)
|
|
639
|
+
}
|
|
640
|
+
return isActivityWidget(this.widget) ? translate('nextcloud-vue', 'History') : ''
|
|
641
|
+
},
|
|
642
|
+
|
|
597
643
|
/**
|
|
598
644
|
* Whether the type declares itself a container (registry `container`).
|
|
599
645
|
*
|
|
@@ -701,6 +747,20 @@ var script = {
|
|
|
701
747
|
},
|
|
702
748
|
|
|
703
749
|
methods: {
|
|
750
|
+
/**
|
|
751
|
+
* A manifest label through the host's translate; '' stays ''.
|
|
752
|
+
*
|
|
753
|
+
* @param {string} text The label as written in the manifest.
|
|
754
|
+
* @return {string} The label in the user's language.
|
|
755
|
+
* @spec openspec/changes/screens-detail-labels-parity/specs/detail-labels-board-look/spec.md#requirement-detail-labels-read-in-the-users-language
|
|
756
|
+
*/
|
|
757
|
+
translateLabel(text) {
|
|
758
|
+
if (typeof text !== 'string' || text === '') {
|
|
759
|
+
return ''
|
|
760
|
+
}
|
|
761
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
762
|
+
},
|
|
763
|
+
|
|
704
764
|
/**
|
|
705
765
|
* Re-key a call on the channel this host provides onto the one it holds.
|
|
706
766
|
* A widget publishes `set(items)`; a host nested in this one speaks the
|