svelte-ag 0.0.2-dev.72
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/README.md +6 -0
- package/dist/app.css +209 -0
- package/dist/app.d.ts +13 -0
- package/dist/app.html +12 -0
- package/dist/icons.css +6 -0
- package/dist/lib/bits/internal/arrays.d.ts +95 -0
- package/dist/lib/bits/internal/arrays.js +250 -0
- package/dist/lib/bits/internal/arrays.test.d.ts +1 -0
- package/dist/lib/bits/internal/arrays.test.js +366 -0
- package/dist/lib/bits/internal/attrs.d.ts +22 -0
- package/dist/lib/bits/internal/attrs.js +69 -0
- package/dist/lib/bits/internal/box-auto-reset.svelte.d.ts +8 -0
- package/dist/lib/bits/internal/box-auto-reset.svelte.js +31 -0
- package/dist/lib/bits/internal/box.svelte.d.ts +21 -0
- package/dist/lib/bits/internal/box.svelte.js +26 -0
- package/dist/lib/bits/internal/clamp.d.ts +4 -0
- package/dist/lib/bits/internal/clamp.js +6 -0
- package/dist/lib/bits/internal/clamp.test.d.ts +1 -0
- package/dist/lib/bits/internal/clamp.test.js +31 -0
- package/dist/lib/bits/internal/create-event-hook.svelte.d.ts +18 -0
- package/dist/lib/bits/internal/create-event-hook.svelte.js +29 -0
- package/dist/lib/bits/internal/create-shared-hook.svelte.d.ts +2 -0
- package/dist/lib/bits/internal/create-shared-hook.svelte.js +27 -0
- package/dist/lib/bits/internal/date-time/announcer.d.ts +7 -0
- package/dist/lib/bits/internal/date-time/announcer.js +82 -0
- package/dist/lib/bits/internal/date-time/calendar-helpers.svelte.d.ts +201 -0
- package/dist/lib/bits/internal/date-time/calendar-helpers.svelte.js +510 -0
- package/dist/lib/bits/internal/date-time/field/helpers.d.ts +76 -0
- package/dist/lib/bits/internal/date-time/field/helpers.js +378 -0
- package/dist/lib/bits/internal/date-time/field/parts.d.ts +6 -0
- package/dist/lib/bits/internal/date-time/field/parts.js +9 -0
- package/dist/lib/bits/internal/date-time/field/segments.d.ts +51 -0
- package/dist/lib/bits/internal/date-time/field/segments.js +128 -0
- package/dist/lib/bits/internal/date-time/field/types.d.ts +25 -0
- package/dist/lib/bits/internal/date-time/field/types.js +1 -0
- package/dist/lib/bits/internal/date-time/formatter.d.ts +24 -0
- package/dist/lib/bits/internal/date-time/formatter.js +97 -0
- package/dist/lib/bits/internal/date-time/placeholders.d.ts +8 -0
- package/dist/lib/bits/internal/date-time/placeholders.js +129 -0
- package/dist/lib/bits/internal/date-time/utils.d.ts +69 -0
- package/dist/lib/bits/internal/date-time/utils.js +212 -0
- package/dist/lib/bits/internal/debounce.d.ts +4 -0
- package/dist/lib/bits/internal/debounce.js +19 -0
- package/dist/lib/bits/internal/debounce.test.d.ts +1 -0
- package/dist/lib/bits/internal/debounce.test.js +50 -0
- package/dist/lib/bits/internal/dom.d.ts +10 -0
- package/dist/lib/bits/internal/dom.js +38 -0
- package/dist/lib/bits/internal/elements.d.ts +2 -0
- package/dist/lib/bits/internal/elements.js +6 -0
- package/dist/lib/bits/internal/events.d.ts +21 -0
- package/dist/lib/bits/internal/events.js +49 -0
- package/dist/lib/bits/internal/floating-svelte/floating-utils.svelte.d.ts +7 -0
- package/dist/lib/bits/internal/floating-svelte/floating-utils.svelte.js +24 -0
- package/dist/lib/bits/internal/floating-svelte/types.d.ts +85 -0
- package/dist/lib/bits/internal/floating-svelte/types.js +1 -0
- package/dist/lib/bits/internal/floating-svelte/use-floating.svelte.d.ts +2 -0
- package/dist/lib/bits/internal/floating-svelte/use-floating.svelte.js +112 -0
- package/dist/lib/bits/internal/focus.d.ts +46 -0
- package/dist/lib/bits/internal/focus.js +109 -0
- package/dist/lib/bits/internal/get-directional-keys.d.ts +21 -0
- package/dist/lib/bits/internal/get-directional-keys.js +37 -0
- package/dist/lib/bits/internal/get-directional-keys.test.d.ts +1 -0
- package/dist/lib/bits/internal/get-directional-keys.test.js +46 -0
- package/dist/lib/bits/internal/is.d.ts +25 -0
- package/dist/lib/bits/internal/is.js +62 -0
- package/dist/lib/bits/internal/is.test.d.ts +1 -0
- package/dist/lib/bits/internal/is.test.js +34 -0
- package/dist/lib/bits/internal/kbd-constants.d.ts +40 -0
- package/dist/lib/bits/internal/kbd-constants.js +40 -0
- package/dist/lib/bits/internal/kbd.d.ts +1 -0
- package/dist/lib/bits/internal/kbd.js +1 -0
- package/dist/lib/bits/internal/locale.d.ts +6 -0
- package/dist/lib/bits/internal/locale.js +9 -0
- package/dist/lib/bits/internal/math.d.ts +5 -0
- package/dist/lib/bits/internal/math.js +43 -0
- package/dist/lib/bits/internal/math.test.d.ts +1 -0
- package/dist/lib/bits/internal/math.test.js +71 -0
- package/dist/lib/bits/internal/noop.d.ts +4 -0
- package/dist/lib/bits/internal/noop.js +4 -0
- package/dist/lib/bits/internal/should-trap-focus.d.ts +6 -0
- package/dist/lib/bits/internal/should-trap-focus.js +6 -0
- package/dist/lib/bits/internal/sleep.d.ts +1 -0
- package/dist/lib/bits/internal/sleep.js +3 -0
- package/dist/lib/bits/internal/tabbable.d.ts +10 -0
- package/dist/lib/bits/internal/tabbable.js +66 -0
- package/dist/lib/bits/internal/types.d.ts +92 -0
- package/dist/lib/bits/internal/types.js +1 -0
- package/dist/lib/bits/internal/use-after-animations.svelte.d.ts +5 -0
- package/dist/lib/bits/internal/use-after-animations.svelte.js +27 -0
- package/dist/lib/bits/internal/use-arrow-navigation.d.ts +62 -0
- package/dist/lib/bits/internal/use-arrow-navigation.js +76 -0
- package/dist/lib/bits/internal/use-body-scroll-lock.svelte.d.ts +6 -0
- package/dist/lib/bits/internal/use-body-scroll-lock.svelte.js +106 -0
- package/dist/lib/bits/internal/use-data-typeahead.svelte.d.ts +14 -0
- package/dist/lib/bits/internal/use-data-typeahead.svelte.js +31 -0
- package/dist/lib/bits/internal/use-dom-typeahead.svelte.d.ts +11 -0
- package/dist/lib/bits/internal/use-dom-typeahead.svelte.js +30 -0
- package/dist/lib/bits/internal/use-form-control.svelte.d.ts +4 -0
- package/dist/lib/bits/internal/use-form-control.svelte.js +16 -0
- package/dist/lib/bits/internal/use-grace-area.svelte.d.ts +12 -0
- package/dist/lib/bits/internal/use-grace-area.svelte.js +197 -0
- package/dist/lib/bits/internal/use-id.d.ts +4 -0
- package/dist/lib/bits/internal/use-id.js +8 -0
- package/dist/lib/bits/internal/use-resize-observer.svelte.d.ts +2 -0
- package/dist/lib/bits/internal/use-resize-observer.svelte.js +17 -0
- package/dist/lib/bits/internal/use-roving-focus.svelte.d.ts +38 -0
- package/dist/lib/bits/internal/use-roving-focus.svelte.js +91 -0
- package/dist/lib/bits/internal/use-size.svelte.d.ts +7 -0
- package/dist/lib/bits/internal/use-size.svelte.js +54 -0
- package/dist/lib/bits/internal/use-state-machine.svelte.d.ts +24 -0
- package/dist/lib/bits/internal/use-state-machine.svelte.js +28 -0
- package/dist/lib/bits/internal/use-timeout-fn.svelte.d.ts +25 -0
- package/dist/lib/bits/internal/use-timeout-fn.svelte.js +39 -0
- package/dist/lib/components/Typeahead.svelte.d.ts +47 -0
- package/dist/lib/components/Typeahead.svelte.js +150 -0
- package/dist/lib/components/animated/animated.svelte +244 -0
- package/dist/lib/components/animated/animated.svelte.d.ts +61 -0
- package/dist/lib/components/animated/index.d.ts +2 -0
- package/dist/lib/components/animated/index.js +2 -0
- package/dist/lib/components/combo/combo.svelte +186 -0
- package/dist/lib/components/combo/combo.svelte.d.ts +21 -0
- package/dist/lib/components/combo/index.d.ts +2 -0
- package/dist/lib/components/combo/index.js +2 -0
- package/dist/lib/components/dnd/Droppable.svelte +25 -0
- package/dist/lib/components/dnd/Droppable.svelte.d.ts +10 -0
- package/dist/lib/components/dnd/context.svelte.d.ts +22 -0
- package/dist/lib/components/dnd/context.svelte.js +25 -0
- package/dist/lib/components/dnd/dnd-context.svelte +45 -0
- package/dist/lib/components/dnd/dnd-context.svelte.d.ts +30 -0
- package/dist/lib/components/dnd/dnd-drag-overlay.svelte +44 -0
- package/dist/lib/components/dnd/dnd-drag-overlay.svelte.d.ts +27 -0
- package/dist/lib/components/dnd/dnd-drag-placeholder.svelte +24 -0
- package/dist/lib/components/dnd/dnd-drag-placeholder.svelte.d.ts +9 -0
- package/dist/lib/components/dnd/dnd-draghandle.svelte +30 -0
- package/dist/lib/components/dnd/dnd-draghandle.svelte.d.ts +6 -0
- package/dist/lib/components/dnd/dnd-overlay.svelte +0 -0
- package/dist/lib/components/dnd/dnd-overlay.svelte.d.ts +26 -0
- package/dist/lib/components/dnd/dnd-sortable-context.svelte +18 -0
- package/dist/lib/components/dnd/dnd-sortable-context.svelte.d.ts +8 -0
- package/dist/lib/components/dnd/dnd-sortable-item.svelte +68 -0
- package/dist/lib/components/dnd/dnd-sortable-item.svelte.d.ts +23 -0
- package/dist/lib/components/dnd/example.svelte +109 -0
- package/dist/lib/components/dnd/example.svelte.d.ts +3 -0
- package/dist/lib/components/dnd/exports.d.ts +9 -0
- package/dist/lib/components/dnd/exports.js +9 -0
- package/dist/lib/components/dnd/index.d.ts +1 -0
- package/dist/lib/components/dnd/index.js +1 -0
- package/dist/lib/components/dnd/sortable.svelte.d.ts +13 -0
- package/dist/lib/components/dnd/sortable.svelte.js +70 -0
- package/dist/lib/components/dnd/utils.svelte.d.ts +20 -0
- package/dist/lib/components/dnd/utils.svelte.js +20 -0
- package/dist/lib/components/search/combinations/searchPopover.svelte +68 -0
- package/dist/lib/components/search/combinations/searchPopover.svelte.d.ts +22 -0
- package/dist/lib/components/search/components/search-empty.svelte +28 -0
- package/dist/lib/components/search/components/search-empty.svelte.d.ts +4 -0
- package/dist/lib/components/search/components/search-input.svelte +53 -0
- package/dist/lib/components/search/components/search-input.svelte.d.ts +4 -0
- package/dist/lib/components/search/components/search-list.svelte +46 -0
- package/dist/lib/components/search/components/search-list.svelte.d.ts +4 -0
- package/dist/lib/components/search/components/search-pagnation.svelte +68 -0
- package/dist/lib/components/search/components/search-pagnation.svelte.d.ts +8 -0
- package/dist/lib/components/search/components/search.svelte +47 -0
- package/dist/lib/components/search/components/search.svelte.d.ts +4 -0
- package/dist/lib/components/search/exports.d.ts +6 -0
- package/dist/lib/components/search/exports.js +5 -0
- package/dist/lib/components/search/index.d.ts +2 -0
- package/dist/lib/components/search/index.js +2 -0
- package/dist/lib/components/search/search.svelte.d.ts +102 -0
- package/dist/lib/components/search/search.svelte.js +202 -0
- package/dist/lib/components/search/types.d.ts +28 -0
- package/dist/lib/components/search/types.js +1 -0
- package/dist/lib/components/utilities/arrow/arrow.svelte +23 -0
- package/dist/lib/components/utilities/arrow/arrow.svelte.d.ts +3 -0
- package/dist/lib/components/utilities/arrow/index.d.ts +2 -0
- package/dist/lib/components/utilities/arrow/index.js +1 -0
- package/dist/lib/components/utilities/arrow/types.d.ts +17 -0
- package/dist/lib/components/utilities/arrow/types.js +1 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer-anchor.svelte +15 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer-anchor.svelte.d.ts +4 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer-arrow.svelte +20 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer-arrow.svelte.d.ts +3 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer-content-static.svelte +19 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer-content-static.svelte.d.ts +13 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer-content.svelte +61 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer-content.svelte.d.ts +4 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer.svelte +10 -0
- package/dist/lib/components/utilities/floating-layer/components/floating-layer.svelte.d.ts +7 -0
- package/dist/lib/components/utilities/floating-layer/components/index.d.ts +6 -0
- package/dist/lib/components/utilities/floating-layer/components/index.js +5 -0
- package/dist/lib/components/utilities/floating-layer/index.d.ts +1 -0
- package/dist/lib/components/utilities/floating-layer/index.js +1 -0
- package/dist/lib/components/utilities/floating-layer/types.d.ts +115 -0
- package/dist/lib/components/utilities/floating-layer/types.js +1 -0
- package/dist/lib/components/utilities/floating-layer/use-floating-layer.svelte.d.ts +118 -0
- package/dist/lib/components/utilities/floating-layer/use-floating-layer.svelte.js +311 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/utils/asyncDerived.svelte.d.ts +12 -0
- package/dist/lib/utils/asyncDerived.svelte.js +26 -0
- package/dist/lib/utils/bits.d.ts +39 -0
- package/dist/lib/utils/bits.js +69 -0
- package/dist/lib/utils/index.d.ts +3 -0
- package/dist/lib/utils/index.js +3 -0
- package/dist/lib/utils/utils.d.ts +21 -0
- package/dist/lib/utils/utils.js +72 -0
- package/dist/routes/+layout.svelte +0 -0
- package/dist/routes/+layout.svelte.d.ts +26 -0
- package/package.json +79 -0
- package/src/app.css +209 -0
- package/src/app.d.ts +13 -0
- package/src/app.html +12 -0
- package/src/icons.css +6 -0
- package/src/lib/bits/internal/arrays.test.ts +460 -0
- package/src/lib/bits/internal/arrays.ts +301 -0
- package/src/lib/bits/internal/attrs.ts +97 -0
- package/src/lib/bits/internal/box-auto-reset.svelte.ts +40 -0
- package/src/lib/bits/internal/box.svelte.ts +60 -0
- package/src/lib/bits/internal/clamp.test.ts +37 -0
- package/src/lib/bits/internal/clamp.ts +6 -0
- package/src/lib/bits/internal/create-event-hook.svelte.ts +64 -0
- package/src/lib/bits/internal/create-shared-hook.svelte.ts +33 -0
- package/src/lib/bits/internal/date-time/announcer.ts +88 -0
- package/src/lib/bits/internal/date-time/calendar-helpers.svelte.ts +815 -0
- package/src/lib/bits/internal/date-time/field/helpers.ts +441 -0
- package/src/lib/bits/internal/date-time/field/parts.ts +9 -0
- package/src/lib/bits/internal/date-time/field/segments.ts +126 -0
- package/src/lib/bits/internal/date-time/field/types.ts +35 -0
- package/src/lib/bits/internal/date-time/formatter.ts +116 -0
- package/src/lib/bits/internal/date-time/placeholders.ts +143 -0
- package/src/lib/bits/internal/date-time/utils.ts +261 -0
- package/src/lib/bits/internal/debounce.test.ts +67 -0
- package/src/lib/bits/internal/debounce.ts +22 -0
- package/src/lib/bits/internal/dom.ts +47 -0
- package/src/lib/bits/internal/elements.ts +7 -0
- package/src/lib/bits/internal/events.ts +89 -0
- package/src/lib/bits/internal/floating-svelte/floating-utils.svelte.ts +28 -0
- package/src/lib/bits/internal/floating-svelte/types.ts +108 -0
- package/src/lib/bits/internal/floating-svelte/use-floating.svelte.ts +133 -0
- package/src/lib/bits/internal/focus.ts +111 -0
- package/src/lib/bits/internal/get-directional-keys.test.ts +51 -0
- package/src/lib/bits/internal/get-directional-keys.ts +43 -0
- package/src/lib/bits/internal/is.test.ts +40 -0
- package/src/lib/bits/internal/is.ts +78 -0
- package/src/lib/bits/internal/kbd-constants.ts +40 -0
- package/src/lib/bits/internal/kbd.ts +1 -0
- package/src/lib/bits/internal/locale.ts +13 -0
- package/src/lib/bits/internal/math.test.ts +88 -0
- package/src/lib/bits/internal/math.ts +50 -0
- package/src/lib/bits/internal/noop.ts +4 -0
- package/src/lib/bits/internal/should-trap-focus.ts +16 -0
- package/src/lib/bits/internal/sleep.ts +3 -0
- package/src/lib/bits/internal/tabbable.ts +76 -0
- package/src/lib/bits/internal/types.ts +91 -0
- package/src/lib/bits/internal/use-after-animations.svelte.ts +30 -0
- package/src/lib/bits/internal/use-arrow-navigation.ts +168 -0
- package/src/lib/bits/internal/use-body-scroll-lock.svelte.ts +138 -0
- package/src/lib/bits/internal/use-data-typeahead.svelte.ts +44 -0
- package/src/lib/bits/internal/use-dom-typeahead.svelte.ts +44 -0
- package/src/lib/bits/internal/use-form-control.svelte.ts +17 -0
- package/src/lib/bits/internal/use-grace-area.svelte.ts +229 -0
- package/src/lib/bits/internal/use-id.ts +9 -0
- package/src/lib/bits/internal/use-resize-observer.svelte.ts +19 -0
- package/src/lib/bits/internal/use-roving-focus.svelte.ts +141 -0
- package/src/lib/bits/internal/use-size.svelte.ts +60 -0
- package/src/lib/bits/internal/use-state-machine.svelte.ts +46 -0
- package/src/lib/bits/internal/use-timeout-fn.svelte.ts +80 -0
- package/src/lib/components/Typeahead.svelte.ts +200 -0
- package/src/lib/components/animated/animated.svelte +244 -0
- package/src/lib/components/animated/index.ts +3 -0
- package/src/lib/components/combo/combo.svelte +186 -0
- package/src/lib/components/combo/index.ts +3 -0
- package/src/lib/components/dnd/Droppable.svelte +25 -0
- package/src/lib/components/dnd/context.svelte.ts +30 -0
- package/src/lib/components/dnd/dnd-context.svelte +45 -0
- package/src/lib/components/dnd/dnd-drag-overlay.svelte +44 -0
- package/src/lib/components/dnd/dnd-drag-placeholder.svelte +24 -0
- package/src/lib/components/dnd/dnd-draghandle.svelte +30 -0
- package/src/lib/components/dnd/dnd-overlay.svelte +0 -0
- package/src/lib/components/dnd/dnd-sortable-context.svelte +18 -0
- package/src/lib/components/dnd/dnd-sortable-item.svelte +68 -0
- package/src/lib/components/dnd/example.svelte +109 -0
- package/src/lib/components/dnd/exports.ts +12 -0
- package/src/lib/components/dnd/index.ts +1 -0
- package/src/lib/components/dnd/sortable.svelte.ts +82 -0
- package/src/lib/components/dnd/utils.svelte.ts +29 -0
- package/src/lib/components/search/combinations/searchPopover.svelte +68 -0
- package/src/lib/components/search/components/search-empty.svelte +28 -0
- package/src/lib/components/search/components/search-input.svelte +53 -0
- package/src/lib/components/search/components/search-list.svelte +46 -0
- package/src/lib/components/search/components/search-pagnation.svelte +68 -0
- package/src/lib/components/search/components/search.svelte +47 -0
- package/src/lib/components/search/exports.ts +13 -0
- package/src/lib/components/search/index.ts +2 -0
- package/src/lib/components/search/search.svelte.ts +286 -0
- package/src/lib/components/search/types.ts +48 -0
- package/src/lib/components/utilities/arrow/arrow.svelte +23 -0
- package/src/lib/components/utilities/arrow/index.ts +2 -0
- package/src/lib/components/utilities/arrow/types.ts +20 -0
- package/src/lib/components/utilities/floating-layer/components/floating-layer-anchor.svelte +15 -0
- package/src/lib/components/utilities/floating-layer/components/floating-layer-arrow.svelte +20 -0
- package/src/lib/components/utilities/floating-layer/components/floating-layer-content-static.svelte +19 -0
- package/src/lib/components/utilities/floating-layer/components/floating-layer-content.svelte +61 -0
- package/src/lib/components/utilities/floating-layer/components/floating-layer.svelte +10 -0
- package/src/lib/components/utilities/floating-layer/components/index.ts +11 -0
- package/src/lib/components/utilities/floating-layer/index.ts +1 -0
- package/src/lib/components/utilities/floating-layer/types.ts +133 -0
- package/src/lib/components/utilities/floating-layer/use-floating-layer.svelte.ts +406 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/utils/asyncDerived.svelte.ts +38 -0
- package/src/lib/utils/bits.ts +93 -0
- package/src/lib/utils/index.ts +3 -0
- package/src/lib/utils/utils.ts +97 -0
- package/src/routes/+layout.svelte +0 -0
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DateValue,
|
|
3
|
+
endOfMonth,
|
|
4
|
+
isSameDay,
|
|
5
|
+
isSameMonth,
|
|
6
|
+
startOfMonth,
|
|
7
|
+
} from "@internationalized/date";
|
|
8
|
+
import { type ReadableBox, type WritableBox, afterTick, styleToString } from "svelte-toolbelt";
|
|
9
|
+
import { untrack } from "svelte";
|
|
10
|
+
import {
|
|
11
|
+
getDaysInMonth,
|
|
12
|
+
getLastFirstDayOfWeek,
|
|
13
|
+
getNextLastDayOfWeek,
|
|
14
|
+
hasTime,
|
|
15
|
+
isAfter,
|
|
16
|
+
isBefore,
|
|
17
|
+
parseAnyDateValue,
|
|
18
|
+
parseStringToDateValue,
|
|
19
|
+
toDate,
|
|
20
|
+
} from "./utils.js";
|
|
21
|
+
import type { Formatter } from "./formatter.js";
|
|
22
|
+
import { getDataDisabled, getDataInvalid, getDataReadonly } from "$lib/internal/attrs.js";
|
|
23
|
+
import { chunk, isValidIndex } from "$lib/internal/arrays.js";
|
|
24
|
+
import { isBrowser, isHTMLElement } from "$lib/internal/is.js";
|
|
25
|
+
import { kbd } from "$lib/internal/kbd.js";
|
|
26
|
+
import type { DateMatcher, Month } from "$lib/shared/index.js";
|
|
27
|
+
import { watch } from "runed";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Checks if a given node is a calendar cell element.
|
|
31
|
+
*
|
|
32
|
+
* @param node - The node to check.
|
|
33
|
+
*/
|
|
34
|
+
export function isCalendarDayNode(node: unknown): node is HTMLElement {
|
|
35
|
+
if (!isHTMLElement(node)) return false;
|
|
36
|
+
if (!node.hasAttribute("data-bits-day")) return false;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Retrieves an array of date values representing the days between
|
|
42
|
+
* the provided start and end dates.
|
|
43
|
+
*/
|
|
44
|
+
export function getDaysBetween(start: DateValue, end: DateValue) {
|
|
45
|
+
const days: DateValue[] = [];
|
|
46
|
+
let dCurrent = start.add({ days: 1 });
|
|
47
|
+
const dEnd = end;
|
|
48
|
+
while (dCurrent.compare(dEnd) < 0) {
|
|
49
|
+
days.push(dCurrent);
|
|
50
|
+
dCurrent = dCurrent.add({ days: 1 });
|
|
51
|
+
}
|
|
52
|
+
return days;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type CreateMonthProps = {
|
|
56
|
+
/**
|
|
57
|
+
* The date object representing the month's date (usually the first day of the month).
|
|
58
|
+
*/
|
|
59
|
+
dateObj: DateValue;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The day of the week to start the calendar on (0 for Sunday, 1 for Monday, etc.).
|
|
63
|
+
*/
|
|
64
|
+
weekStartsOn: number;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Whether to always render 6 weeks in the calendar, even if the month doesn't
|
|
68
|
+
* span 6 weeks.
|
|
69
|
+
*/
|
|
70
|
+
fixedWeeks: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The locale to use when creating the calendar month.
|
|
74
|
+
*/
|
|
75
|
+
locale: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Creates a calendar month object.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* Given a date, this function returns an object containing
|
|
83
|
+
* the necessary values to render a calendar month, including
|
|
84
|
+
* the month's date (the first day of that month), which can be
|
|
85
|
+
* used to render the name of the month, an array of all dates
|
|
86
|
+
* in that month, and an array of weeks. Each week is an array
|
|
87
|
+
* of dates, useful for rendering an accessible calendar grid
|
|
88
|
+
* using a loop and table elements.
|
|
89
|
+
*
|
|
90
|
+
*/
|
|
91
|
+
function createMonth(props: CreateMonthProps): Month<DateValue> {
|
|
92
|
+
const { dateObj, weekStartsOn, fixedWeeks, locale } = props;
|
|
93
|
+
const daysInMonth = getDaysInMonth(dateObj);
|
|
94
|
+
|
|
95
|
+
const datesArray = Array.from({ length: daysInMonth }, (_, i) => dateObj.set({ day: i + 1 }));
|
|
96
|
+
|
|
97
|
+
const firstDayOfMonth = startOfMonth(dateObj);
|
|
98
|
+
const lastDayOfMonth = endOfMonth(dateObj);
|
|
99
|
+
|
|
100
|
+
const lastSunday = getLastFirstDayOfWeek(firstDayOfMonth, weekStartsOn, locale);
|
|
101
|
+
const nextSaturday = getNextLastDayOfWeek(lastDayOfMonth, weekStartsOn, locale);
|
|
102
|
+
|
|
103
|
+
const lastMonthDays = getDaysBetween(lastSunday.subtract({ days: 1 }), firstDayOfMonth);
|
|
104
|
+
const nextMonthDays = getDaysBetween(lastDayOfMonth, nextSaturday.add({ days: 1 }));
|
|
105
|
+
|
|
106
|
+
const totalDays = lastMonthDays.length + datesArray.length + nextMonthDays.length;
|
|
107
|
+
|
|
108
|
+
if (fixedWeeks && totalDays < 42) {
|
|
109
|
+
const extraDays = 42 - totalDays;
|
|
110
|
+
|
|
111
|
+
let startFrom = nextMonthDays[nextMonthDays.length - 1];
|
|
112
|
+
|
|
113
|
+
if (!startFrom) {
|
|
114
|
+
startFrom = dateObj.add({ months: 1 }).set({ day: 1 });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let length = extraDays;
|
|
118
|
+
if (nextMonthDays.length === 0) {
|
|
119
|
+
length = extraDays - 1;
|
|
120
|
+
nextMonthDays.push(startFrom);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const extraDaysArray = Array.from({ length }, (_, i) => {
|
|
124
|
+
const incr = i + 1;
|
|
125
|
+
return startFrom.add({ days: incr });
|
|
126
|
+
});
|
|
127
|
+
nextMonthDays.push(...extraDaysArray);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const allDays = lastMonthDays.concat(datesArray, nextMonthDays);
|
|
131
|
+
|
|
132
|
+
const weeks = chunk(allDays, 7);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
value: dateObj,
|
|
136
|
+
dates: allDays,
|
|
137
|
+
weeks,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
type SetMonthProps = CreateMonthProps & {
|
|
142
|
+
numberOfMonths: number | undefined;
|
|
143
|
+
currentMonths?: Month<DateValue>[];
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export function createMonths(props: SetMonthProps) {
|
|
147
|
+
const { numberOfMonths, dateObj, ...monthProps } = props;
|
|
148
|
+
|
|
149
|
+
const months: Month<DateValue>[] = [];
|
|
150
|
+
|
|
151
|
+
if (!numberOfMonths || numberOfMonths === 1) {
|
|
152
|
+
months.push(
|
|
153
|
+
createMonth({
|
|
154
|
+
...monthProps,
|
|
155
|
+
dateObj,
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
return months;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
months.push(
|
|
162
|
+
createMonth({
|
|
163
|
+
...monthProps,
|
|
164
|
+
dateObj,
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Create all the months, starting with the current month
|
|
169
|
+
for (let i = 1; i < numberOfMonths; i++) {
|
|
170
|
+
const nextMonth = dateObj.add({ months: i });
|
|
171
|
+
months.push(
|
|
172
|
+
createMonth({
|
|
173
|
+
...monthProps,
|
|
174
|
+
dateObj: nextMonth,
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return months;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function getSelectableCells(calendarNode: HTMLElement | null) {
|
|
183
|
+
if (!calendarNode) return [];
|
|
184
|
+
const selectableSelector = `[data-bits-day]:not([data-disabled]):not([data-outside-visible-months])`;
|
|
185
|
+
|
|
186
|
+
return Array.from(calendarNode.querySelectorAll(selectableSelector)).filter(
|
|
187
|
+
(el): el is HTMLElement => isHTMLElement(el)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A helper function to extract the date from the `data-value`
|
|
193
|
+
* attribute of a date cell and set it as the placeholder value.
|
|
194
|
+
*
|
|
195
|
+
* Shared between the calendar and range calendar builders.
|
|
196
|
+
*
|
|
197
|
+
* @param node - The node to extract the date from.
|
|
198
|
+
* @param placeholder - The placeholder value store which will be set to the extracted date.
|
|
199
|
+
*/
|
|
200
|
+
export function setPlaceholderToNodeValue(node: HTMLElement, placeholder: WritableBox<DateValue>) {
|
|
201
|
+
const cellValue = node.getAttribute("data-value");
|
|
202
|
+
if (!cellValue) return;
|
|
203
|
+
placeholder.current = parseStringToDateValue(cellValue, placeholder.current);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
type ShiftCalendarFocusProps = {
|
|
207
|
+
/**
|
|
208
|
+
* The day node with current focus.
|
|
209
|
+
*/
|
|
210
|
+
node: HTMLElement;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The number of days to shift the focus by.
|
|
214
|
+
*/
|
|
215
|
+
add: number;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The `placeholder` value box
|
|
219
|
+
*/
|
|
220
|
+
placeholder: WritableBox<DateValue>;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The calendar node.
|
|
224
|
+
*/
|
|
225
|
+
calendarNode: HTMLElement | null;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Whether the previous button is disabled.
|
|
229
|
+
*/
|
|
230
|
+
isPrevButtonDisabled: boolean;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Whether the next button is disabled.
|
|
234
|
+
*/
|
|
235
|
+
isNextButtonDisabled: boolean;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The months array of the calendar.
|
|
239
|
+
*/
|
|
240
|
+
months: Month<DateValue>[];
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The number of months being displayed in the calendar.
|
|
244
|
+
*/
|
|
245
|
+
numberOfMonths: number;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Shared logic for shifting focus between cells in the
|
|
250
|
+
* calendar and range calendar.
|
|
251
|
+
*/
|
|
252
|
+
export function shiftCalendarFocus({
|
|
253
|
+
node,
|
|
254
|
+
add,
|
|
255
|
+
placeholder,
|
|
256
|
+
calendarNode,
|
|
257
|
+
isPrevButtonDisabled,
|
|
258
|
+
isNextButtonDisabled,
|
|
259
|
+
months,
|
|
260
|
+
numberOfMonths,
|
|
261
|
+
}: ShiftCalendarFocusProps) {
|
|
262
|
+
const candidateCells = getSelectableCells(calendarNode);
|
|
263
|
+
if (!candidateCells.length) return;
|
|
264
|
+
|
|
265
|
+
const index = candidateCells.indexOf(node);
|
|
266
|
+
const nextIndex = index + add;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* If the next cell is within the bounds of the displayed cells,
|
|
270
|
+
* easy day, we just focus it.
|
|
271
|
+
*/
|
|
272
|
+
if (isValidIndex(nextIndex, candidateCells)) {
|
|
273
|
+
const nextCell = candidateCells[nextIndex]!;
|
|
274
|
+
setPlaceholderToNodeValue(nextCell, placeholder);
|
|
275
|
+
return nextCell.focus();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* When the next cell falls outside the displayed cells range,
|
|
280
|
+
* we update the focus to the previous or next month based on the
|
|
281
|
+
* direction, and then focus on the relevant cell.
|
|
282
|
+
*/
|
|
283
|
+
|
|
284
|
+
if (nextIndex < 0) {
|
|
285
|
+
/**
|
|
286
|
+
* To handle negative indices, we rewind by one month,
|
|
287
|
+
* retrieve candidate cells for that month, and shift focus
|
|
288
|
+
* by the difference between the nextIndex starting from the end
|
|
289
|
+
* of the array.
|
|
290
|
+
*/
|
|
291
|
+
|
|
292
|
+
// shift the calendar back a month unless prev month is disabled
|
|
293
|
+
if (isPrevButtonDisabled) return;
|
|
294
|
+
|
|
295
|
+
const firstMonth = months[0]?.value;
|
|
296
|
+
if (!firstMonth) return;
|
|
297
|
+
placeholder.current = firstMonth.subtract({ months: numberOfMonths });
|
|
298
|
+
|
|
299
|
+
// Without a tick here, it seems to be too quick for the DOM to update
|
|
300
|
+
|
|
301
|
+
afterTick(() => {
|
|
302
|
+
const newCandidateCells = getSelectableCells(calendarNode);
|
|
303
|
+
if (!newCandidateCells.length) return;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Starting at the end of the array, shift focus by the diff
|
|
307
|
+
* between the nextIndex and the length of the array, since the
|
|
308
|
+
* nextIndex is negative.
|
|
309
|
+
*/
|
|
310
|
+
const newIndex = newCandidateCells.length - Math.abs(nextIndex);
|
|
311
|
+
if (isValidIndex(newIndex, newCandidateCells)) {
|
|
312
|
+
const newCell = newCandidateCells[newIndex]!;
|
|
313
|
+
setPlaceholderToNodeValue(newCell, placeholder);
|
|
314
|
+
return newCell.focus();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (nextIndex >= candidateCells.length) {
|
|
320
|
+
/**
|
|
321
|
+
* Since we're in the positive index range, we need to go forward
|
|
322
|
+
* a month, refetch the candidate cells within that month, and then
|
|
323
|
+
* starting at the beginning of the array, shift focus by the nextIndex
|
|
324
|
+
* amount.
|
|
325
|
+
*/
|
|
326
|
+
|
|
327
|
+
// shift the calendar forward a month unless next month is disabled
|
|
328
|
+
if (isNextButtonDisabled) return;
|
|
329
|
+
|
|
330
|
+
const firstMonth = months[0]?.value;
|
|
331
|
+
if (!firstMonth) return;
|
|
332
|
+
placeholder.current = firstMonth.add({ months: numberOfMonths });
|
|
333
|
+
|
|
334
|
+
afterTick(() => {
|
|
335
|
+
const newCandidateCells = getSelectableCells(calendarNode);
|
|
336
|
+
if (!newCandidateCells.length) return;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* We need to determine how far into the next month we need to go
|
|
340
|
+
* to get the next index. So if we only went over the previous month
|
|
341
|
+
* by one, we need to go into the next month by 1 to get the right index.
|
|
342
|
+
*/
|
|
343
|
+
const newIndex = nextIndex - candidateCells.length;
|
|
344
|
+
|
|
345
|
+
if (isValidIndex(newIndex, newCandidateCells)) {
|
|
346
|
+
const nextCell = newCandidateCells[newIndex]!;
|
|
347
|
+
return nextCell.focus();
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
type HandleCalendarKeydownProps = {
|
|
354
|
+
event: KeyboardEvent;
|
|
355
|
+
handleCellClick: (event: Event, date: DateValue) => void;
|
|
356
|
+
shiftFocus: (node: HTMLElement, add: number) => void;
|
|
357
|
+
placeholderValue: DateValue;
|
|
358
|
+
};
|
|
359
|
+
const ARROW_KEYS = [kbd.ARROW_DOWN, kbd.ARROW_UP, kbd.ARROW_LEFT, kbd.ARROW_RIGHT] as const;
|
|
360
|
+
const SELECT_KEYS = [kbd.ENTER, kbd.SPACE];
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Shared keyboard event handler for the calendar and range calendar.
|
|
364
|
+
*/
|
|
365
|
+
export function handleCalendarKeydown({
|
|
366
|
+
event,
|
|
367
|
+
handleCellClick,
|
|
368
|
+
shiftFocus,
|
|
369
|
+
placeholderValue,
|
|
370
|
+
}: HandleCalendarKeydownProps) {
|
|
371
|
+
const currentCell = event.target;
|
|
372
|
+
if (!isCalendarDayNode(currentCell)) return;
|
|
373
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
374
|
+
if (!ARROW_KEYS.includes(event.key as any) && !SELECT_KEYS.includes(event.key)) return;
|
|
375
|
+
|
|
376
|
+
event.preventDefault();
|
|
377
|
+
|
|
378
|
+
const kbdFocusMap: Record<(typeof ARROW_KEYS)[number], number> = {
|
|
379
|
+
[kbd.ARROW_DOWN]: 7,
|
|
380
|
+
[kbd.ARROW_UP]: -7,
|
|
381
|
+
[kbd.ARROW_LEFT]: -1,
|
|
382
|
+
[kbd.ARROW_RIGHT]: 1,
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
386
|
+
if (ARROW_KEYS.includes(event.key as any)) {
|
|
387
|
+
const add = kbdFocusMap[event.key as (typeof ARROW_KEYS)[number]];
|
|
388
|
+
if (add !== undefined) {
|
|
389
|
+
shiftFocus(currentCell, add);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (SELECT_KEYS.includes(event.key)) {
|
|
394
|
+
const cellValue = currentCell.getAttribute("data-value");
|
|
395
|
+
if (!cellValue) return;
|
|
396
|
+
handleCellClick(event, parseStringToDateValue(cellValue, placeholderValue));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
type HandleCalendarPageProps = {
|
|
401
|
+
months: Month<DateValue>[];
|
|
402
|
+
setMonths: (months: Month<DateValue>[]) => void;
|
|
403
|
+
numberOfMonths: number;
|
|
404
|
+
pagedNavigation: boolean;
|
|
405
|
+
weekStartsOn: number;
|
|
406
|
+
locale: string;
|
|
407
|
+
fixedWeeks: boolean;
|
|
408
|
+
setPlaceholder: (date: DateValue) => void;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
export function handleCalendarNextPage({
|
|
412
|
+
months,
|
|
413
|
+
setMonths,
|
|
414
|
+
numberOfMonths,
|
|
415
|
+
pagedNavigation,
|
|
416
|
+
weekStartsOn,
|
|
417
|
+
locale,
|
|
418
|
+
fixedWeeks,
|
|
419
|
+
setPlaceholder,
|
|
420
|
+
}: HandleCalendarPageProps) {
|
|
421
|
+
const firstMonth = months[0]?.value;
|
|
422
|
+
if (!firstMonth) return;
|
|
423
|
+
if (pagedNavigation) {
|
|
424
|
+
setPlaceholder(firstMonth.add({ months: numberOfMonths }));
|
|
425
|
+
} else {
|
|
426
|
+
const newMonths = createMonths({
|
|
427
|
+
dateObj: firstMonth.add({ months: 1 }),
|
|
428
|
+
weekStartsOn,
|
|
429
|
+
locale,
|
|
430
|
+
fixedWeeks,
|
|
431
|
+
numberOfMonths,
|
|
432
|
+
});
|
|
433
|
+
setMonths(newMonths);
|
|
434
|
+
|
|
435
|
+
const firstNewMonth = newMonths[0];
|
|
436
|
+
if (!firstNewMonth) return;
|
|
437
|
+
setPlaceholder(firstNewMonth.value.set({ day: 1 }));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function handleCalendarPrevPage({
|
|
442
|
+
months,
|
|
443
|
+
setMonths,
|
|
444
|
+
numberOfMonths,
|
|
445
|
+
pagedNavigation,
|
|
446
|
+
weekStartsOn,
|
|
447
|
+
locale,
|
|
448
|
+
fixedWeeks,
|
|
449
|
+
setPlaceholder,
|
|
450
|
+
}: HandleCalendarPageProps) {
|
|
451
|
+
const firstMonth = months[0]?.value;
|
|
452
|
+
if (!firstMonth) return;
|
|
453
|
+
if (pagedNavigation) {
|
|
454
|
+
setPlaceholder(firstMonth.subtract({ months: numberOfMonths }));
|
|
455
|
+
} else {
|
|
456
|
+
const newMonths = createMonths({
|
|
457
|
+
dateObj: firstMonth.subtract({ months: 1 }),
|
|
458
|
+
weekStartsOn,
|
|
459
|
+
locale,
|
|
460
|
+
fixedWeeks,
|
|
461
|
+
numberOfMonths,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
setMonths(newMonths);
|
|
465
|
+
const firstNewMonth = newMonths[0];
|
|
466
|
+
if (!firstNewMonth) return;
|
|
467
|
+
setPlaceholder(firstNewMonth.value.set({ day: 1 }));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
type GetWeekdaysProps = {
|
|
472
|
+
months: Month<DateValue>[];
|
|
473
|
+
weekdayFormat: Intl.DateTimeFormatOptions["weekday"];
|
|
474
|
+
formatter: Formatter;
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
export function getWeekdays({ months, formatter, weekdayFormat }: GetWeekdaysProps) {
|
|
478
|
+
if (!months.length) return [];
|
|
479
|
+
const firstMonth = months[0]!;
|
|
480
|
+
const firstWeek = firstMonth.weeks[0];
|
|
481
|
+
if (!firstWeek) return [];
|
|
482
|
+
return firstWeek.map((date) => formatter.dayOfWeek(toDate(date), weekdayFormat));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
type UseMonthViewSyncProps = {
|
|
486
|
+
weekStartsOn: ReadableBox<number>;
|
|
487
|
+
locale: ReadableBox<string>;
|
|
488
|
+
fixedWeeks: ReadableBox<boolean>;
|
|
489
|
+
numberOfMonths: ReadableBox<number>;
|
|
490
|
+
placeholder: WritableBox<DateValue>;
|
|
491
|
+
setMonths: (months: Month<DateValue>[]) => void;
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Updates the displayed months based on changes in the options values,
|
|
496
|
+
* which determines the month to show in the calendar.
|
|
497
|
+
*/
|
|
498
|
+
export function useMonthViewOptionsSync(props: UseMonthViewSyncProps) {
|
|
499
|
+
const weekStartsOn = props.weekStartsOn.current;
|
|
500
|
+
const locale = props.locale.current;
|
|
501
|
+
const fixedWeeks = props.fixedWeeks.current;
|
|
502
|
+
const numberOfMonths = props.numberOfMonths.current;
|
|
503
|
+
|
|
504
|
+
untrack(() => {
|
|
505
|
+
const placeholder = props.placeholder.current;
|
|
506
|
+
if (!placeholder) return;
|
|
507
|
+
const defaultMonthProps = {
|
|
508
|
+
weekStartsOn,
|
|
509
|
+
locale,
|
|
510
|
+
fixedWeeks,
|
|
511
|
+
numberOfMonths,
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
props.setMonths(createMonths({ ...defaultMonthProps, dateObj: placeholder }));
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
type CreateAccessibleHeadingProps = {
|
|
519
|
+
calendarNode: HTMLElement;
|
|
520
|
+
label: string;
|
|
521
|
+
accessibleHeadingId: string;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Creates an accessible heading element for the calendar.
|
|
526
|
+
* Returns a function that removes the heading element.
|
|
527
|
+
*/
|
|
528
|
+
export function createAccessibleHeading({
|
|
529
|
+
calendarNode,
|
|
530
|
+
label,
|
|
531
|
+
accessibleHeadingId,
|
|
532
|
+
}: CreateAccessibleHeadingProps) {
|
|
533
|
+
const div = document.createElement("div");
|
|
534
|
+
div.style.cssText = styleToString({
|
|
535
|
+
border: "0px",
|
|
536
|
+
clip: "rect(0px, 0px, 0px, 0px)",
|
|
537
|
+
clipPath: "inset(50%)",
|
|
538
|
+
height: "1px",
|
|
539
|
+
margin: "-1px",
|
|
540
|
+
overflow: "hidden",
|
|
541
|
+
padding: "0px",
|
|
542
|
+
position: "absolute",
|
|
543
|
+
whiteSpace: "nowrap",
|
|
544
|
+
width: "1px",
|
|
545
|
+
});
|
|
546
|
+
const h2 = document.createElement("div");
|
|
547
|
+
h2.textContent = label;
|
|
548
|
+
h2.id = accessibleHeadingId;
|
|
549
|
+
h2.role = "heading";
|
|
550
|
+
h2.ariaLevel = "2";
|
|
551
|
+
calendarNode.insertBefore(div, calendarNode.firstChild);
|
|
552
|
+
div.appendChild(h2);
|
|
553
|
+
|
|
554
|
+
return () => {
|
|
555
|
+
const h2 = document.getElementById(accessibleHeadingId);
|
|
556
|
+
if (!h2) return;
|
|
557
|
+
div.parentElement?.removeChild(div);
|
|
558
|
+
h2.remove();
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
type UseMonthViewPlaceholderSyncProps = {
|
|
563
|
+
placeholder: WritableBox<DateValue>;
|
|
564
|
+
getVisibleMonths: () => DateValue[];
|
|
565
|
+
weekStartsOn: ReadableBox<number>;
|
|
566
|
+
locale: ReadableBox<string>;
|
|
567
|
+
fixedWeeks: ReadableBox<boolean>;
|
|
568
|
+
numberOfMonths: ReadableBox<number>;
|
|
569
|
+
setMonths: (months: Month<DateValue>[]) => void;
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
export function useMonthViewPlaceholderSync({
|
|
573
|
+
placeholder,
|
|
574
|
+
getVisibleMonths,
|
|
575
|
+
weekStartsOn,
|
|
576
|
+
locale,
|
|
577
|
+
fixedWeeks,
|
|
578
|
+
numberOfMonths,
|
|
579
|
+
setMonths,
|
|
580
|
+
}: UseMonthViewPlaceholderSyncProps) {
|
|
581
|
+
$effect(() => {
|
|
582
|
+
placeholder.current;
|
|
583
|
+
untrack(() => {
|
|
584
|
+
/**
|
|
585
|
+
* If the placeholder's month is already in this visible months,
|
|
586
|
+
* we don't need to do anything.
|
|
587
|
+
*/
|
|
588
|
+
if (getVisibleMonths().some((month) => isSameMonth(month, placeholder.current))) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const defaultMonthProps = {
|
|
593
|
+
weekStartsOn: weekStartsOn.current,
|
|
594
|
+
locale: locale.current,
|
|
595
|
+
fixedWeeks: fixedWeeks.current,
|
|
596
|
+
numberOfMonths: numberOfMonths.current,
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
setMonths(createMonths({ ...defaultMonthProps, dateObj: placeholder.current }));
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
type GetIsNextButtonDisabledProps = {
|
|
605
|
+
maxValue: DateValue | undefined;
|
|
606
|
+
months: Month<DateValue>[];
|
|
607
|
+
disabled: boolean;
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
export function getIsNextButtonDisabled({
|
|
611
|
+
maxValue,
|
|
612
|
+
months,
|
|
613
|
+
disabled,
|
|
614
|
+
}: GetIsNextButtonDisabledProps) {
|
|
615
|
+
if (!maxValue || !months.length) return false;
|
|
616
|
+
if (disabled) return true;
|
|
617
|
+
const lastMonthInView = months[months.length - 1]?.value;
|
|
618
|
+
if (!lastMonthInView) return false;
|
|
619
|
+
const firstMonthOfNextPage = lastMonthInView
|
|
620
|
+
.add({
|
|
621
|
+
months: 1,
|
|
622
|
+
})
|
|
623
|
+
.set({ day: 1 });
|
|
624
|
+
return isAfter(firstMonthOfNextPage, maxValue);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
type GetIsPrevButtonDisabledProps = {
|
|
628
|
+
minValue: DateValue | undefined;
|
|
629
|
+
months: Month<DateValue>[];
|
|
630
|
+
disabled: boolean;
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
export function getIsPrevButtonDisabled({
|
|
634
|
+
minValue,
|
|
635
|
+
months,
|
|
636
|
+
disabled,
|
|
637
|
+
}: GetIsPrevButtonDisabledProps) {
|
|
638
|
+
if (!minValue || !months.length) return false;
|
|
639
|
+
if (disabled) return true;
|
|
640
|
+
const firstMonthInView = months[0]?.value;
|
|
641
|
+
if (!firstMonthInView) return false;
|
|
642
|
+
const lastMonthOfPrevPage = firstMonthInView
|
|
643
|
+
.subtract({
|
|
644
|
+
months: 1,
|
|
645
|
+
})
|
|
646
|
+
.set({ day: 35 });
|
|
647
|
+
return isBefore(lastMonthOfPrevPage, minValue);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
type GetCalendarHeadingValueProps = {
|
|
651
|
+
months: Month<DateValue>[];
|
|
652
|
+
formatter: Formatter;
|
|
653
|
+
locale: string;
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
export function getCalendarHeadingValue({
|
|
657
|
+
months,
|
|
658
|
+
locale,
|
|
659
|
+
formatter,
|
|
660
|
+
}: GetCalendarHeadingValueProps) {
|
|
661
|
+
if (!months.length) return "";
|
|
662
|
+
if (locale !== formatter.getLocale()) {
|
|
663
|
+
formatter.setLocale(locale);
|
|
664
|
+
}
|
|
665
|
+
if (months.length === 1) {
|
|
666
|
+
const month = toDate(months[0]!.value);
|
|
667
|
+
return `${formatter.fullMonthAndYear(month)}`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const startMonth = toDate(months[0]!.value);
|
|
671
|
+
const endMonth = toDate(months[months.length - 1]!.value);
|
|
672
|
+
|
|
673
|
+
const startMonthName = formatter.fullMonth(startMonth);
|
|
674
|
+
const endMonthName = formatter.fullMonth(endMonth);
|
|
675
|
+
|
|
676
|
+
const startMonthYear = formatter.fullYear(startMonth);
|
|
677
|
+
const endMonthYear = formatter.fullYear(endMonth);
|
|
678
|
+
|
|
679
|
+
const content =
|
|
680
|
+
startMonthYear === endMonthYear
|
|
681
|
+
? `${startMonthName} - ${endMonthName} ${endMonthYear}`
|
|
682
|
+
: `${startMonthName} ${startMonthYear} - ${endMonthName} ${endMonthYear}`;
|
|
683
|
+
|
|
684
|
+
return content;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
type GetCalendarElementProps = {
|
|
688
|
+
fullCalendarLabel: string;
|
|
689
|
+
id: string;
|
|
690
|
+
isInvalid: boolean;
|
|
691
|
+
disabled: boolean;
|
|
692
|
+
readonly: boolean;
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
export function getCalendarElementProps({
|
|
696
|
+
fullCalendarLabel,
|
|
697
|
+
id,
|
|
698
|
+
isInvalid,
|
|
699
|
+
disabled,
|
|
700
|
+
readonly,
|
|
701
|
+
}: GetCalendarElementProps) {
|
|
702
|
+
return {
|
|
703
|
+
id,
|
|
704
|
+
role: "application",
|
|
705
|
+
"aria-label": fullCalendarLabel,
|
|
706
|
+
"data-invalid": getDataInvalid(isInvalid),
|
|
707
|
+
"data-disabled": getDataDisabled(disabled),
|
|
708
|
+
"data-readonly": getDataReadonly(readonly),
|
|
709
|
+
} as const;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export type CalendarParts =
|
|
713
|
+
| "root"
|
|
714
|
+
| "grid"
|
|
715
|
+
| "cell"
|
|
716
|
+
| "next-button"
|
|
717
|
+
| "prev-button"
|
|
718
|
+
| "day"
|
|
719
|
+
| "grid-body"
|
|
720
|
+
| "grid-head"
|
|
721
|
+
| "grid-row"
|
|
722
|
+
| "head-cell"
|
|
723
|
+
| "header"
|
|
724
|
+
| "heading";
|
|
725
|
+
|
|
726
|
+
export function pickerOpenFocus(e: Event) {
|
|
727
|
+
const nodeToFocus = document.querySelector<HTMLElement>("[data-bits-day][data-focused]");
|
|
728
|
+
if (nodeToFocus) {
|
|
729
|
+
e.preventDefault();
|
|
730
|
+
nodeToFocus?.focus();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export function getFirstNonDisabledDateInView(calendarRef: HTMLElement): DateValue | undefined {
|
|
735
|
+
if (!isBrowser) return;
|
|
736
|
+
const daysInView = Array.from(
|
|
737
|
+
calendarRef.querySelectorAll<HTMLElement>("[data-bits-day]:not([aria-disabled=true])")
|
|
738
|
+
);
|
|
739
|
+
if (daysInView.length === 0) return;
|
|
740
|
+
const element = daysInView[0];
|
|
741
|
+
const value = element?.getAttribute("data-value");
|
|
742
|
+
const type = element?.getAttribute("data-type");
|
|
743
|
+
if (!value || !type) return;
|
|
744
|
+
return parseAnyDateValue(value, type);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Ensures the placeholder is not set to a disabled date,
|
|
749
|
+
* which would prevent the user from entering the Calendar
|
|
750
|
+
* via the keyboard.
|
|
751
|
+
*/
|
|
752
|
+
export function useEnsureNonDisabledPlaceholder({
|
|
753
|
+
ref,
|
|
754
|
+
placeholder,
|
|
755
|
+
defaultPlaceholder,
|
|
756
|
+
minValue,
|
|
757
|
+
maxValue,
|
|
758
|
+
isDateDisabled,
|
|
759
|
+
}: {
|
|
760
|
+
ref: WritableBox<HTMLElement | null>;
|
|
761
|
+
placeholder: WritableBox<DateValue | undefined>;
|
|
762
|
+
isDateDisabled: ReadableBox<DateMatcher>;
|
|
763
|
+
minValue: ReadableBox<DateValue | undefined>;
|
|
764
|
+
maxValue: ReadableBox<DateValue | undefined>;
|
|
765
|
+
defaultPlaceholder: DateValue;
|
|
766
|
+
}) {
|
|
767
|
+
function isDisabled(date: DateValue) {
|
|
768
|
+
if (isDateDisabled.current(date)) return true;
|
|
769
|
+
if (minValue.current && isBefore(date, minValue.current)) return true;
|
|
770
|
+
if (maxValue.current && isBefore(maxValue.current, date)) return true;
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
watch(
|
|
775
|
+
() => ref.current,
|
|
776
|
+
() => {
|
|
777
|
+
if (!ref.current) return;
|
|
778
|
+
/**
|
|
779
|
+
* If the placeholder is still the default placeholder and it's a disabled date, find
|
|
780
|
+
* the first available date in the calendar view and set it as the placeholder.
|
|
781
|
+
*
|
|
782
|
+
* This prevents the placeholder from being a disabled date and no date being tabbable
|
|
783
|
+
* preventing the user from entering the Calendar. If all dates in the view are
|
|
784
|
+
* disabled, currently that is considered an error on the developer's part and should
|
|
785
|
+
* be handled by them.
|
|
786
|
+
*
|
|
787
|
+
* Perhaps in the future we can introduce a dev-only log message to prevent this from
|
|
788
|
+
* being a silent error.
|
|
789
|
+
*/
|
|
790
|
+
if (
|
|
791
|
+
placeholder.current &&
|
|
792
|
+
isSameDay(placeholder.current, defaultPlaceholder) &&
|
|
793
|
+
isDisabled(defaultPlaceholder)
|
|
794
|
+
) {
|
|
795
|
+
placeholder.current =
|
|
796
|
+
getFirstNonDisabledDateInView(ref.current) ?? defaultPlaceholder;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
export function getDateWithPreviousTime(date: DateValue | undefined, prev: DateValue | undefined) {
|
|
803
|
+
if (!date || !prev) return date;
|
|
804
|
+
|
|
805
|
+
if (hasTime(date) && hasTime(prev)) {
|
|
806
|
+
return date.set({
|
|
807
|
+
hour: prev.hour,
|
|
808
|
+
minute: prev.minute,
|
|
809
|
+
millisecond: prev.millisecond,
|
|
810
|
+
second: prev.second,
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return date;
|
|
815
|
+
}
|