@conduction/nextcloud-vue 2.98.0 → 2.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +12 -0
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +11 -2
  4. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  5. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -1
  6. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  7. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +1 -1
  8. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  9. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  10. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -1
  11. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  12. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  13. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +26 -7
  14. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  15. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +78 -17
  16. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  17. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +9 -0
  18. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js.map +1 -0
  19. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js +265 -0
  20. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -0
  21. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +191 -0
  22. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -0
  23. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +9 -0
  24. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js.map +1 -0
  25. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +508 -0
  26. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -0
  27. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js +99 -0
  28. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -0
  29. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  30. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +18 -70
  31. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  32. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +6 -44
  33. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  34. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  35. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  36. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js +9 -0
  37. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js.map +1 -0
  38. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js +472 -0
  39. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js.map +1 -0
  40. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js +231 -0
  41. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js.map +1 -0
  42. package/dist/esm/components/CnWorkingCalendarWidget/index.js +27 -0
  43. package/dist/esm/components/CnWorkingCalendarWidget/index.js.map +1 -0
  44. package/dist/esm/composables/useFlowStore.js +2 -1
  45. package/dist/esm/composables/useFlowStore.js.map +1 -1
  46. package/dist/esm/index.js +3 -0
  47. package/dist/esm/index.js.map +1 -1
  48. package/dist/esm/l10n/en.json.js +95 -1
  49. package/dist/esm/l10n/en.json.js.map +1 -1
  50. package/dist/esm/l10n/nl.json.js +109 -1
  51. package/dist/esm/l10n/nl.json.js.map +1 -1
  52. package/dist/esm/library-style.js +1 -1
  53. package/dist/esm/nextcloud-vue.css +660 -81
  54. package/dist/esm/utils/libraryWidgetKeys.js +1 -0
  55. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  56. package/dist/esm/utils/workingCalendarMonth.js +85 -0
  57. package/dist/esm/utils/workingCalendarMonth.js.map +1 -0
  58. package/dist/nextcloud-vue.cjs.js +9355 -7303
  59. package/dist/nextcloud-vue.cjs.js.map +2 -2
  60. package/dist/nextcloud-vue.css +660 -81
  61. package/l10n/en.json +95 -1
  62. package/l10n/nl.json +109 -1
  63. package/package.json +1 -1
  64. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  65. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +1 -0
  66. package/src/components/CnDetailPage/CnDetailPage.vue +1 -0
  67. package/src/components/CnOrderedListField/CnOrderedListField.vue +81 -9
  68. package/src/components/CnSettingsField/CnSettingsField.vue +449 -0
  69. package/src/components/CnSettingsField/index.js +4 -0
  70. package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +613 -0
  71. package/src/components/CnSettingsFieldsSection/index.js +4 -0
  72. package/src/components/CnSettingsPage/CnSettingsPage.vue +6 -86
  73. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  74. package/src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue +725 -0
  75. package/src/components/CnWorkingCalendarWidget/index.js +26 -0
  76. package/src/components/index.js +3 -0
  77. package/src/composables/useFlowStore.js +2 -1
  78. package/src/css/badge.css +32 -16
  79. package/src/css/look-board-detail.css +80 -8
  80. package/src/css/look-board-index.css +52 -8
  81. package/src/css/look-board.css +219 -1
  82. package/src/index.js +3 -0
  83. package/src/utils/libraryWidgetKeys.js +1 -0
  84. package/src/utils/workingCalendarMonth.js +82 -0
@@ -141,6 +141,50 @@
141
141
  color: var(--color-primary-element-text);
142
142
  }
143
143
 
144
+ /* ---- Header action buttons (CnActionButtons) ----------------------------- */
145
+ /* The board draws a page's secondary actions as neutral 40px buttons: the
146
+ main background, a grey border, the main text at 600 (DqZaak "Bericht",
147
+ "Document", "Contact vastleggen", "Bewerken"). Nextcloud's secondary button
148
+ is a primary tint. The label hands its ink to --nldesign-color-on-surface,
149
+ which a theme that forces the body ink onto every span (thematiq) reads. */
150
+ .cn-look-board .cn-action-buttons .button-vue--secondary:not(.cn-look-nextcloud *),
151
+ .cn-look-board .cn-action-buttons .button-vue--vue-secondary:not(.cn-look-nextcloud *) {
152
+ --nldesign-color-on-surface: var(--color-main-text);
153
+ height: var(--cn-board-control-height);
154
+ min-height: var(--cn-board-control-height);
155
+ padding: 0 14px;
156
+ border: 1px solid var(--color-border-dark);
157
+ border-radius: var(--cn-board-control-radius);
158
+ background: var(--color-main-background);
159
+ color: var(--color-main-text);
160
+ font-size: 14px;
161
+ font-weight: 600;
162
+ }
163
+
164
+ /* Buttons 10px apart, each icon 16px with 8px to its label (DqZaak's header,
165
+ DqZaken's "Downloaden"); NcButton draws a 20px icon in a 30px slot. */
166
+ .cn-look-board .cn-action-buttons:not(.cn-look-nextcloud *) {
167
+ gap: 10px;
168
+ }
169
+
170
+ .cn-look-board .cn-action-buttons .button-vue .button-vue__icon:not(.cn-look-nextcloud *) {
171
+ width: 16px;
172
+ min-width: 16px;
173
+ height: 16px;
174
+ min-height: 16px;
175
+ margin-inline-end: 8px;
176
+ }
177
+
178
+ .cn-look-board .cn-action-buttons .button-vue .button-vue__icon:not(.cn-look-nextcloud *) svg {
179
+ width: 16px;
180
+ height: 16px;
181
+ }
182
+
183
+ .cn-look-board .cn-action-buttons .button-vue--secondary:not(.cn-look-nextcloud *):hover,
184
+ .cn-look-board .cn-action-buttons .button-vue--vue-secondary:not(.cn-look-nextcloud *):hover {
185
+ background: var(--color-background-hover);
186
+ }
187
+
144
188
  /* ---- The buildiq square ------------------------------------------------ */
145
189
  .cn-look-board .cn-buildiq-edit .cn-buildiq-edit__actions .button-vue {
146
190
  box-sizing: border-box;
@@ -153,6 +197,34 @@
153
197
  border-radius: var(--cn-board-control-radius);
154
198
  }
155
199
 
200
+ /* ---- The Actions menu trigger (CnActionsMenu) ---------------------------- */
201
+ /* A menu asked for as `secondary` ("Meer" in a detail header, "Acties" at the
202
+ end of a tab strip) is the board's neutral control: the main background, a
203
+ grey border, the main text at 600 (DqZaak), not Nextcloud's primary tint.
204
+ NcActions marks a secondary menu `action-item--secondary` on its root and
205
+ keeps a class given to it off, so the rule reads that marker. The label
206
+ hands its ink to --nldesign-color-on-surface for a theme that forces the
207
+ body ink onto every span (thematiq). */
208
+ .cn-look-board .action-item.action-item--secondary .action-item__menutoggle.button-vue--secondary:not(.cn-look-nextcloud *) {
209
+ --nldesign-color-on-surface: var(--color-main-text);
210
+ border: 1px solid var(--color-border-dark);
211
+ border-radius: var(--cn-board-control-radius);
212
+ background: var(--color-main-background);
213
+ color: var(--color-main-text);
214
+ font-size: 14px;
215
+ font-weight: 600;
216
+ }
217
+
218
+ .cn-look-board .action-item.action-item--secondary .action-item__menutoggle.button-vue--secondary:not(.cn-look-nextcloud *):hover {
219
+ background: var(--color-background-hover);
220
+ }
221
+
222
+ /* A trailing chevron ("Meer ⌄") follows the label, 6px after it. */
223
+ .cn-look-board .action-item.action-item--secondary .action-item__menutoggle:has(.chevron-down-icon) .button-vue__wrapper:not(.cn-look-nextcloud *) {
224
+ flex-direction: row-reverse;
225
+ gap: 6px;
226
+ }
227
+
156
228
  /* ---- The navigation ---------------------------------------------------- */
157
229
  .cn-look-board .app-navigation[data-testid="cn-nav"] {
158
230
  --app-navigation-padding: 0px;
@@ -374,6 +446,71 @@
374
446
  font-weight: 600;
375
447
  }
376
448
 
449
+ /* ---- Breadcrumbs (CnBreadcrumbs) ----------------------------------------- */
450
+ /* The board draws a trail as a line of 14px text, 8px apart: the crumbs that
451
+ lead somewhere as underlined links in the primary colour, the current one
452
+ as plain muted text (DqZaak "Alle zaken / 2026-0082"). NcBreadcrumbs draws
453
+ each crumb as a 34px tertiary button. The doubled classes outrank its
454
+ scoped rules; the ink goes through --nldesign-color-on-surface as well, for
455
+ a theme that forces the body ink onto every link and span (thematiq). */
456
+ .cn-look-board .cn-breadcrumbs.cn-breadcrumbs:not(.cn-look-nextcloud *) {
457
+ min-height: 0;
458
+ height: auto;
459
+ color: var(--color-text-maxcontrast);
460
+ font-size: 14px;
461
+ line-height: normal;
462
+ }
463
+
464
+ .cn-look-board .cn-breadcrumbs .breadcrumb__crumbs:not(.cn-look-nextcloud *) {
465
+ gap: 8px;
466
+ min-height: 0;
467
+ margin: 0;
468
+ padding: 0;
469
+ }
470
+
471
+ .cn-look-board .cn-breadcrumbs .vue-crumb.vue-crumb:not(.cn-look-nextcloud *) {
472
+ gap: 8px;
473
+ min-width: 0;
474
+ min-height: 0;
475
+ height: auto;
476
+ }
477
+
478
+ .cn-look-board .cn-breadcrumbs .vue-crumb .button-vue.button-vue:not(.cn-look-nextcloud *) {
479
+ --nldesign-color-on-surface: var(--color-primary-element);
480
+ min-width: 0;
481
+ min-height: 0;
482
+ height: auto;
483
+ padding: 0;
484
+ border: 0;
485
+ background: transparent;
486
+ color: var(--color-primary-element);
487
+ font-size: 14px;
488
+ font-weight: 400;
489
+ line-height: inherit;
490
+ }
491
+
492
+ .cn-look-board .cn-breadcrumbs .vue-crumb .button-vue.button-vue:not(.cn-look-nextcloud *) .button-vue__text {
493
+ padding: 0;
494
+ font-weight: inherit;
495
+ line-height: inherit;
496
+ text-decoration: underline;
497
+ }
498
+
499
+ .cn-look-board .cn-breadcrumbs .vue-crumb .button-vue.button-vue[aria-current='page']:not(.cn-look-nextcloud *) {
500
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
501
+ color: var(--color-text-maxcontrast);
502
+ cursor: default;
503
+ }
504
+
505
+ .cn-look-board .cn-breadcrumbs .vue-crumb .button-vue.button-vue[aria-current='page']:not(.cn-look-nextcloud *) .button-vue__text {
506
+ text-decoration: none;
507
+ }
508
+
509
+ .cn-look-board .cn-breadcrumbs .vue-crumb__separator:not(.cn-look-nextcloud *) {
510
+ margin: 0;
511
+ color: var(--color-text-maxcontrast);
512
+ }
513
+
377
514
  /* ---- Settings: header, cards in a grid, version card ------------------- */
378
515
  .cn-look-board .cn-admin-settings-shell__header:not(.cn-look-nextcloud *),
379
516
  .cn-look-board .cn-settings-page__header:not(.cn-look-nextcloud *) {
@@ -792,7 +929,26 @@
792
929
  ground ("Verzoek", "Nieuw"); the default look keeps the brighter
793
930
  primary-element text. The other tones already match the board. */
794
931
  .cn-look-board .cn-status-badge--primary:not(.cn-status-badge--solid):not(.cn-look-nextcloud *) {
795
- color: var(--color-primary-element-light-text);
932
+ --cn-status-badge-ink: var(--color-primary-element-light-text);
933
+ color: var(--cn-status-badge-ink);
934
+ }
935
+
936
+ /* Every board pill is 13px at 600 on a 3px 10px pad with a 12px corner
937
+ (DqZaak's header pills, DqZaken's status column). */
938
+ .cn-look-board .cn-status-badge:not(.cn-status-badge--small):not(.cn-look-nextcloud *) {
939
+ padding: 3px 10px;
940
+ border-radius: 12px;
941
+ font-size: 13px;
942
+ font-weight: 600;
943
+ line-height: normal;
944
+ }
945
+
946
+ /* A pill paints its own pairing of ink and fill. A theme that forces the body
947
+ ink onto every span (thematiq's text rule, `!important` at id level) reads
948
+ that ink from --nldesign-color-on-surface when the surface sets it, so the
949
+ pill hands it its own ink: "In behandeling" stays purple on purple. */
950
+ .cn-look-board .cn-status-badge:not(.cn-look-nextcloud *) {
951
+ --nldesign-color-on-surface: var(--cn-status-badge-ink);
796
952
  }
797
953
 
798
954
  /* ---- Date and age cells (screens-cell-date-parity) ---------------------- */
@@ -802,6 +958,68 @@
802
958
  font-weight: 600;
803
959
  }
804
960
 
961
+ /* ---- Label and value pairs (CnDetailGrid) -------------------------------- */
962
+ /* The board draws pairs as plain text, not as grey cards with an accent bar:
963
+ the grid as a 13px muted label over a 15px value at weight 500 with a
964
+ hairline under each pair (DqZaak "Zaakgegevens"), the horizontal layout as
965
+ two columns of 14px text, the label column as wide as its longest label
966
+ (the side cards). Both at the board's line height, 1.2, not Nextcloud's 1.5:
967
+ a pair is 47px tall on the board. The muted label also hands its ink to
968
+ --nldesign-color-on-surface, which a theme that forces the body ink onto
969
+ every div (thematiq) reads. */
970
+ .cn-look-board .cn-detail-grid.cn-detail-grid--grid:not(.cn-look-nextcloud *) {
971
+ gap: 12px 24px;
972
+ font-size: 15px;
973
+ }
974
+
975
+ .cn-look-board .cn-detail-grid.cn-detail-grid--horizontal:not(.cn-look-nextcloud *) {
976
+ display: grid;
977
+ grid-template-columns: max-content 1fr;
978
+ gap: 8px 16px;
979
+ font-size: 14px;
980
+ }
981
+
982
+ .cn-look-board .cn-detail-grid .cn-detail-grid__item:not(.cn-look-nextcloud *) {
983
+ gap: 3px;
984
+ padding: 0;
985
+ border-inline-start: none;
986
+ border-radius: 0;
987
+ background: none;
988
+ }
989
+
990
+ .cn-look-board .cn-detail-grid.cn-detail-grid--grid .cn-detail-grid__item:not(.cn-look-nextcloud *) {
991
+ padding-bottom: 10px;
992
+ border-bottom: 1px solid var(--cn-board-hairline);
993
+ }
994
+
995
+ .cn-look-board .cn-detail-grid.cn-detail-grid--horizontal .cn-detail-grid__item:not(.cn-look-nextcloud *) {
996
+ display: contents;
997
+ }
998
+
999
+ .cn-look-board .cn-detail-grid .cn-detail-grid__label:not(.cn-look-nextcloud *) {
1000
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
1001
+ min-width: 0;
1002
+ color: var(--color-text-maxcontrast);
1003
+ font-size: 13px;
1004
+ font-weight: 400;
1005
+ line-height: 1.2;
1006
+ }
1007
+
1008
+ .cn-look-board .cn-detail-grid.cn-detail-grid--horizontal .cn-detail-grid__label:not(.cn-look-nextcloud *) {
1009
+ font-size: inherit;
1010
+ }
1011
+
1012
+ .cn-look-board .cn-detail-grid .cn-detail-grid__value:not(.cn-look-nextcloud *) {
1013
+ margin: 0;
1014
+ font-size: inherit;
1015
+ font-weight: 500;
1016
+ line-height: 1.2;
1017
+ }
1018
+
1019
+ .cn-look-board .cn-detail-grid.cn-detail-grid--horizontal .cn-detail-grid__value:not(.cn-look-nextcloud *) {
1020
+ font-weight: 400;
1021
+ }
1022
+
805
1023
  /* ---- Dashboard card titles --------------------------------------------- */
806
1024
  /* A card's title on a dashboard (and on the widgets under a reports page) is
807
1025
  the board's card h2: 17px weight 700 (boards PqMijnWerk, DcRapportages and
package/src/index.js CHANGED
@@ -280,6 +280,8 @@ export {
280
280
  CnSearchPage,
281
281
  CnSectionsWidget,
282
282
  CnSettingsCard,
283
+ CnSettingsField,
284
+ CnSettingsFieldsSection,
283
285
  CnSettingsFormWidget,
284
286
  CnSettingsPage,
285
287
  CnSettingsSection,
@@ -344,6 +346,7 @@ export {
344
346
  CnWidgetVisibilityRulesModal,
345
347
  CnWidgetWrapper,
346
348
  CnWizardDialog,
349
+ CnWorkingCalendarWidget,
347
350
  CnWorkspaceFilterWidget,
348
351
  DEFAULT_DATE_RANGE_PRESETS,
349
352
  defaultPageTypes,
@@ -152,6 +152,7 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
152
152
  'toggle-table',
153
153
  'video',
154
154
  'week-strip',
155
+ 'working-calendar',
155
156
  'workspace-filter',
156
157
  ])
157
158
 
@@ -0,0 +1,82 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * workingCalendarMonth: the weeks of one month as a working calendar draws
6
+ * them, Monday first, each day marked working or not and with the closures
7
+ * that fall on it.
8
+ *
9
+ * Pure: the closures come in from the caller (OpenRegister's calendar preview
10
+ * computes them), nothing here knows a holiday.
11
+ *
12
+ * @module utils/workingCalendarMonth
13
+ * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-working-calendar-shows-a-month
14
+ */
15
+
16
+ /**
17
+ * A date as `YYYY-MM-DD`.
18
+ *
19
+ * @param {number} year The year.
20
+ * @param {number} month The month, 0 to 11.
21
+ * @param {number} day The day of the month.
22
+ * @return {string} The date.
23
+ */
24
+ export function isoDate(year, month, day) {
25
+ return `${String(year).padStart(4, '0')}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
26
+ }
27
+
28
+ /**
29
+ * The stored exceptions of a calendar as `{ date, name }`: entries are a
30
+ * `YYYY-MM-DD` string or an object with `date` and an optional `name`.
31
+ *
32
+ * @param {unknown} exceptions The stored list.
33
+ * @return {Array<{date: string, name: string}>} The exceptions.
34
+ */
35
+ export function normaliseExceptions(exceptions) {
36
+ return (Array.isArray(exceptions) ? exceptions : []).map((entry) => {
37
+ if (typeof entry === 'string') {
38
+ return { date: entry, name: '' }
39
+ }
40
+ if (entry && typeof entry === 'object' && typeof entry.date === 'string') {
41
+ return { date: entry.date, name: typeof entry.name === 'string' ? entry.name : '' }
42
+ }
43
+ return null
44
+ }).filter((entry) => entry && /^\d{4}-\d{2}-\d{2}$/.test(entry.date))
45
+ }
46
+
47
+ /**
48
+ * The weeks of a month, Monday first. A slot outside the month is null.
49
+ *
50
+ * @param {number} year The year.
51
+ * @param {number} month The month, 0 to 11.
52
+ * @param {object} options The calendar.
53
+ * @param {number[]} [options.workingWeekdays] ISO weekdays that work (1 = Monday); Monday to Friday by default.
54
+ * @param {Map<string, {name: string, kind: string}>} [options.closures] Closed dates, by `YYYY-MM-DD`.
55
+ * @param {string} [options.today] Today as `YYYY-MM-DD`.
56
+ * @return {Array<Array<null|{date: string, day: number, weekday: number, working: boolean, closure: (null|{name: string, kind: string}), today: boolean}>>} The weeks.
57
+ */
58
+ export function buildMonth(year, month, { workingWeekdays = [1, 2, 3, 4, 5], closures = new Map(), today = '' } = {}) {
59
+ const days = new Date(Date.UTC(year, month + 1, 0)).getUTCDate()
60
+ const firstWeekday = ((new Date(Date.UTC(year, month, 1)).getUTCDay() + 6) % 7) + 1
61
+ const slots = Array.from({ length: firstWeekday - 1 }, () => null)
62
+ for (let day = 1; day <= days; day += 1) {
63
+ const date = isoDate(year, month, day)
64
+ const weekday = ((firstWeekday - 1 + day - 1) % 7) + 1
65
+ slots.push({
66
+ date,
67
+ day,
68
+ weekday,
69
+ working: workingWeekdays.includes(weekday) && !closures.has(date),
70
+ closure: closures.get(date) || null,
71
+ today: date === today,
72
+ })
73
+ }
74
+ while (slots.length % 7 !== 0) {
75
+ slots.push(null)
76
+ }
77
+ const weeks = []
78
+ for (let i = 0; i < slots.length; i += 7) {
79
+ weeks.push(slots.slice(i, i + 7))
80
+ }
81
+ return weeks
82
+ }