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,138 @@
|
|
|
1
|
+
import { SvelteMap } from "svelte/reactivity";
|
|
2
|
+
import { type Getter, afterSleep, afterTick, box } from "svelte-toolbelt";
|
|
3
|
+
import { untrack } from "svelte";
|
|
4
|
+
import type { Fn } from "./types.js";
|
|
5
|
+
import { isBrowser, isIOS } from "./is.js";
|
|
6
|
+
import { addEventListener } from "./events.js";
|
|
7
|
+
import { useId } from "./use-id.js";
|
|
8
|
+
import { createSharedHook } from "./create-shared-hook.svelte.js";
|
|
9
|
+
|
|
10
|
+
export type ScrollBodyOption = {
|
|
11
|
+
padding?: boolean | number;
|
|
12
|
+
margin?: boolean | number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const useBodyLockStackCount = createSharedHook(() => {
|
|
16
|
+
const map = new SvelteMap<string, boolean>();
|
|
17
|
+
|
|
18
|
+
const locked = $derived.by(() => {
|
|
19
|
+
for (const value of map.values()) {
|
|
20
|
+
if (value) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
let initialBodyStyle: string | null = $state<string | null>(null);
|
|
28
|
+
|
|
29
|
+
let stopTouchMoveListener: Fn | null = null;
|
|
30
|
+
|
|
31
|
+
function resetBodyStyle() {
|
|
32
|
+
if (!isBrowser) return;
|
|
33
|
+
document.body.setAttribute("style", initialBodyStyle ?? "");
|
|
34
|
+
document.body.style.removeProperty("--scrollbar-width");
|
|
35
|
+
isIOS && stopTouchMoveListener?.();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
$effect(() => {
|
|
39
|
+
const curr = locked;
|
|
40
|
+
return untrack(() => {
|
|
41
|
+
if (!curr) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
initialBodyStyle = document.body.getAttribute("style");
|
|
46
|
+
const bodyStyle = getComputedStyle(document.body);
|
|
47
|
+
|
|
48
|
+
// TODO: account for RTL direction, etc.
|
|
49
|
+
const verticalScrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
50
|
+
const paddingRight = Number.parseInt(bodyStyle.paddingRight ?? "0", 10);
|
|
51
|
+
|
|
52
|
+
const config = {
|
|
53
|
+
padding: paddingRight + verticalScrollbarWidth,
|
|
54
|
+
margin: Number.parseInt(bodyStyle.marginRight ?? "0", 10),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (verticalScrollbarWidth > 0) {
|
|
58
|
+
document.body.style.paddingRight = `${config.padding}px`;
|
|
59
|
+
document.body.style.marginRight = `${config.margin}px`;
|
|
60
|
+
document.body.style.setProperty("--scrollbar-width", `${verticalScrollbarWidth}px`);
|
|
61
|
+
document.body.style.overflow = "hidden";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (isIOS) {
|
|
65
|
+
stopTouchMoveListener = addEventListener(
|
|
66
|
+
document,
|
|
67
|
+
"touchmove",
|
|
68
|
+
(e) => {
|
|
69
|
+
if (e.target !== document.documentElement) return;
|
|
70
|
+
|
|
71
|
+
if (e.touches.length > 1) return;
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
},
|
|
74
|
+
{ passive: false }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
afterTick(() => {
|
|
79
|
+
document.body.style.pointerEvents = "none";
|
|
80
|
+
document.body.style.overflow = "hidden";
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
$effect(() => {
|
|
86
|
+
return () => {
|
|
87
|
+
stopTouchMoveListener?.();
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
get map() {
|
|
93
|
+
return map;
|
|
94
|
+
},
|
|
95
|
+
resetBodyStyle,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export function useBodyScrollLock(
|
|
100
|
+
initialState?: boolean | undefined,
|
|
101
|
+
restoreScrollDelay: Getter<number | null> = () => null
|
|
102
|
+
) {
|
|
103
|
+
const id = useId();
|
|
104
|
+
const countState = useBodyLockStackCount();
|
|
105
|
+
if (!countState) return;
|
|
106
|
+
const _restoreScrollDelay = $derived(restoreScrollDelay());
|
|
107
|
+
|
|
108
|
+
countState.map.set(id, initialState ?? false);
|
|
109
|
+
|
|
110
|
+
const locked = box.with(
|
|
111
|
+
() => countState.map.get(id) ?? false,
|
|
112
|
+
(v) => countState.map.set(id, v)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
$effect(() => {
|
|
116
|
+
return () => {
|
|
117
|
+
countState.map.delete(id);
|
|
118
|
+
// if any locks are still active, we don't reset the body style
|
|
119
|
+
if (isAnyLocked(countState.map)) return;
|
|
120
|
+
|
|
121
|
+
// if no locks are active (meaning this was the last lock), we reset the body style
|
|
122
|
+
if (_restoreScrollDelay === null) {
|
|
123
|
+
requestAnimationFrame(() => countState.resetBodyStyle());
|
|
124
|
+
} else {
|
|
125
|
+
afterSleep(_restoreScrollDelay, () => countState.resetBodyStyle());
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return locked;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isAnyLocked(map: Map<string, boolean>) {
|
|
134
|
+
for (const [_, value] of map) {
|
|
135
|
+
if (value) return true;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Getter } from "svelte-toolbelt";
|
|
2
|
+
import { getNextMatch } from "./arrays.js";
|
|
3
|
+
import { boxAutoReset } from "./box-auto-reset.svelte.js";
|
|
4
|
+
|
|
5
|
+
export type DataTypeahead = ReturnType<typeof useDataTypeahead>;
|
|
6
|
+
|
|
7
|
+
type UseDataTypeaheadOpts = {
|
|
8
|
+
onMatch: (value: string) => void;
|
|
9
|
+
getCurrentItem: () => string;
|
|
10
|
+
candidateValues: Getter<string[]>;
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function useDataTypeahead(opts: UseDataTypeaheadOpts) {
|
|
15
|
+
// Reset `search` 1 second after it was last updated
|
|
16
|
+
const search = boxAutoReset("", 1000);
|
|
17
|
+
const candidateValues = $derived(opts.candidateValues());
|
|
18
|
+
|
|
19
|
+
function handleTypeaheadSearch(key: string) {
|
|
20
|
+
if (!opts.enabled) return;
|
|
21
|
+
if (!candidateValues.length) return;
|
|
22
|
+
|
|
23
|
+
search.current = search.current + key;
|
|
24
|
+
const currentItem = opts.getCurrentItem();
|
|
25
|
+
const currentMatch = candidateValues.find((item) => item === currentItem) ?? "";
|
|
26
|
+
const values = candidateValues.map((item) => item ?? "");
|
|
27
|
+
const nextMatch = getNextMatch(values, search.current, currentMatch);
|
|
28
|
+
const newItem = candidateValues.find((item) => item === nextMatch);
|
|
29
|
+
if (newItem) {
|
|
30
|
+
opts.onMatch(newItem);
|
|
31
|
+
}
|
|
32
|
+
return newItem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resetTypeahead() {
|
|
36
|
+
search.current = "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
search,
|
|
41
|
+
handleTypeaheadSearch,
|
|
42
|
+
resetTypeahead,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getNextMatch } from "./arrays.js";
|
|
2
|
+
import { boxAutoReset } from "./box-auto-reset.svelte.js";
|
|
3
|
+
|
|
4
|
+
export type DOMTypeahead = ReturnType<typeof useDOMTypeahead>;
|
|
5
|
+
|
|
6
|
+
type UseDOMTypeaheadOpts = {
|
|
7
|
+
onMatch?: (item: HTMLElement) => void;
|
|
8
|
+
getCurrentItem?: () => HTMLElement | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function useDOMTypeahead(opts?: UseDOMTypeaheadOpts) {
|
|
12
|
+
// Reset `search` 1 second after it was last updated
|
|
13
|
+
const search = boxAutoReset("", 1000);
|
|
14
|
+
|
|
15
|
+
const onMatch = opts?.onMatch ?? ((node) => node.focus());
|
|
16
|
+
const getCurrentItem =
|
|
17
|
+
opts?.getCurrentItem ?? (() => document.activeElement as HTMLElement | null);
|
|
18
|
+
|
|
19
|
+
function handleTypeaheadSearch(key: string, candidates: HTMLElement[]) {
|
|
20
|
+
if (!candidates.length) return;
|
|
21
|
+
|
|
22
|
+
search.current = search.current + key;
|
|
23
|
+
const currentItem = getCurrentItem();
|
|
24
|
+
|
|
25
|
+
const currentMatch = candidates.find((item) => item === currentItem)?.textContent ?? "";
|
|
26
|
+
const values = candidates.map((item) => item.textContent ?? "");
|
|
27
|
+
const nextMatch = getNextMatch(values, search.current, currentMatch);
|
|
28
|
+
const newItem = candidates.find((item) => item.textContent === nextMatch);
|
|
29
|
+
if (newItem) {
|
|
30
|
+
onMatch(newItem);
|
|
31
|
+
}
|
|
32
|
+
return newItem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resetTypeahead() {
|
|
36
|
+
search.current = "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
search,
|
|
41
|
+
handleTypeaheadSearch,
|
|
42
|
+
resetTypeahead,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Getter } from "svelte-toolbelt";
|
|
2
|
+
import { isBrowser } from "./is.js";
|
|
3
|
+
|
|
4
|
+
export function useFormControl(getNode: Getter<HTMLElement | null>) {
|
|
5
|
+
const isInForm = $derived.by(() => {
|
|
6
|
+
if (!isBrowser) return false;
|
|
7
|
+
const node = getNode();
|
|
8
|
+
if (!node) return false;
|
|
9
|
+
return Boolean(node.closest("form"));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
get current() {
|
|
14
|
+
return isInForm;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { type Getter, executeCallbacks } from "svelte-toolbelt";
|
|
2
|
+
import { on } from "svelte/events";
|
|
3
|
+
import { watch } from "runed";
|
|
4
|
+
import { boxAutoReset } from "./box-auto-reset.svelte.js";
|
|
5
|
+
import { isElement, isHTMLElement } from "./is.js";
|
|
6
|
+
import type { Side } from "$lib/bits/utilities/floating-layer/use-floating-layer.svelte.js";
|
|
7
|
+
interface UseGraceAreaOpts {
|
|
8
|
+
enabled: Getter<boolean>;
|
|
9
|
+
triggerNode: Getter<HTMLElement | null>;
|
|
10
|
+
contentNode: Getter<HTMLElement | null>;
|
|
11
|
+
onPointerExit: () => void;
|
|
12
|
+
setIsPointerInTransit?: (value: boolean) => void;
|
|
13
|
+
}
|
|
14
|
+
export function useGraceArea(opts: UseGraceAreaOpts) {
|
|
15
|
+
const enabled = $derived(opts.enabled());
|
|
16
|
+
|
|
17
|
+
const isPointerInTransit = boxAutoReset(false as boolean, 300, (value) => {
|
|
18
|
+
if (enabled) {
|
|
19
|
+
opts.setIsPointerInTransit?.(value);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let pointerGraceArea = $state<Polygon | null>(null);
|
|
24
|
+
|
|
25
|
+
function handleRemoveGraceArea() {
|
|
26
|
+
pointerGraceArea = null;
|
|
27
|
+
isPointerInTransit.current = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function handleCreateGraceArea(e: PointerEvent, hoverTarget: HTMLElement) {
|
|
31
|
+
const currentTarget = e.currentTarget;
|
|
32
|
+
if (!isHTMLElement(currentTarget)) return;
|
|
33
|
+
const exitPoint = { x: e.clientX, y: e.clientY };
|
|
34
|
+
const exitSide = getExitSideFromRect(exitPoint, currentTarget.getBoundingClientRect());
|
|
35
|
+
const paddedExitPoints = getPaddedExitPoints(exitPoint, exitSide);
|
|
36
|
+
const hoverTargetPoints = getPointsFromRect(hoverTarget.getBoundingClientRect());
|
|
37
|
+
const graceArea = getHull([...paddedExitPoints, ...hoverTargetPoints]);
|
|
38
|
+
pointerGraceArea = graceArea;
|
|
39
|
+
isPointerInTransit.current = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
watch(
|
|
43
|
+
[opts.triggerNode, opts.contentNode, opts.enabled],
|
|
44
|
+
([triggerNode, contentNode, enabled]) => {
|
|
45
|
+
if (!triggerNode || !contentNode || !enabled) return;
|
|
46
|
+
const handleTriggerLeave = (e: PointerEvent) => {
|
|
47
|
+
handleCreateGraceArea(e, contentNode!);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleContentLeave = (e: PointerEvent) => {
|
|
51
|
+
handleCreateGraceArea(e, triggerNode!);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return executeCallbacks(
|
|
55
|
+
on(triggerNode, "pointerleave", handleTriggerLeave),
|
|
56
|
+
on(contentNode, "pointerleave", handleContentLeave)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
watch(
|
|
62
|
+
() => pointerGraceArea,
|
|
63
|
+
() => {
|
|
64
|
+
const handleTrackPointerGrace = (e: PointerEvent) => {
|
|
65
|
+
if (!pointerGraceArea) return;
|
|
66
|
+
const target = e.target;
|
|
67
|
+
if (!isElement(target)) return;
|
|
68
|
+
const pointerPosition = { x: e.clientX, y: e.clientY };
|
|
69
|
+
const hasEnteredTarget =
|
|
70
|
+
opts.triggerNode()?.contains(target) || opts.contentNode()?.contains(target);
|
|
71
|
+
const isPointerOutsideGraceArea = !isPointInPolygon(
|
|
72
|
+
pointerPosition,
|
|
73
|
+
pointerGraceArea
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (hasEnteredTarget) {
|
|
77
|
+
handleRemoveGraceArea();
|
|
78
|
+
} else if (isPointerOutsideGraceArea) {
|
|
79
|
+
handleRemoveGraceArea();
|
|
80
|
+
opts.onPointerExit();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return on(document, "pointermove", handleTrackPointerGrace);
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
isPointerInTransit,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type Point = { x: number; y: number };
|
|
94
|
+
type Polygon = Point[];
|
|
95
|
+
|
|
96
|
+
function getExitSideFromRect(point: Point, rect: DOMRect): Side {
|
|
97
|
+
const top = Math.abs(rect.top - point.y);
|
|
98
|
+
const bottom = Math.abs(rect.bottom - point.y);
|
|
99
|
+
const right = Math.abs(rect.right - point.x);
|
|
100
|
+
const left = Math.abs(rect.left - point.x);
|
|
101
|
+
|
|
102
|
+
switch (Math.min(top, bottom, right, left)) {
|
|
103
|
+
case left:
|
|
104
|
+
return "left";
|
|
105
|
+
case right:
|
|
106
|
+
return "right";
|
|
107
|
+
case top:
|
|
108
|
+
return "top";
|
|
109
|
+
case bottom:
|
|
110
|
+
return "bottom";
|
|
111
|
+
default:
|
|
112
|
+
throw new Error("unreachable");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getPaddedExitPoints(exitPoint: Point, exitSide: Side, padding = 5) {
|
|
117
|
+
// we extend the tip of the exit point to make it easier to navigate without
|
|
118
|
+
// a minor jitter triggering a pointer exit
|
|
119
|
+
const tipPadding = padding * 1.5;
|
|
120
|
+
switch (exitSide) {
|
|
121
|
+
case "top":
|
|
122
|
+
return [
|
|
123
|
+
{ x: exitPoint.x - padding, y: exitPoint.y + padding },
|
|
124
|
+
{ x: exitPoint.x, y: exitPoint.y - tipPadding },
|
|
125
|
+
{ x: exitPoint.x + padding, y: exitPoint.y + padding },
|
|
126
|
+
];
|
|
127
|
+
case "bottom":
|
|
128
|
+
return [
|
|
129
|
+
{ x: exitPoint.x - padding, y: exitPoint.y - padding },
|
|
130
|
+
{ x: exitPoint.x, y: exitPoint.y + tipPadding },
|
|
131
|
+
{ x: exitPoint.x + padding, y: exitPoint.y - padding },
|
|
132
|
+
];
|
|
133
|
+
case "left":
|
|
134
|
+
return [
|
|
135
|
+
{ x: exitPoint.x + padding, y: exitPoint.y - padding },
|
|
136
|
+
{ x: exitPoint.x - tipPadding, y: exitPoint.y },
|
|
137
|
+
{ x: exitPoint.x + padding, y: exitPoint.y + padding },
|
|
138
|
+
];
|
|
139
|
+
case "right":
|
|
140
|
+
return [
|
|
141
|
+
{ x: exitPoint.x - padding, y: exitPoint.y - padding },
|
|
142
|
+
{ x: exitPoint.x + tipPadding, y: exitPoint.y },
|
|
143
|
+
{ x: exitPoint.x - padding, y: exitPoint.y + padding },
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getPointsFromRect(rect: DOMRect) {
|
|
149
|
+
const { top, right, bottom, left } = rect;
|
|
150
|
+
return [
|
|
151
|
+
{ x: left, y: top },
|
|
152
|
+
{ x: right, y: top },
|
|
153
|
+
{ x: right, y: bottom },
|
|
154
|
+
{ x: left, y: bottom },
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Determine if a point is inside of a polygon.
|
|
159
|
+
// Based on https://github.com/substack/point-in-polygon
|
|
160
|
+
function isPointInPolygon(point: Point, polygon: Polygon) {
|
|
161
|
+
const { x, y } = point;
|
|
162
|
+
let inside = false;
|
|
163
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
164
|
+
const xi = polygon[i]!.x;
|
|
165
|
+
const yi = polygon[i]!.y;
|
|
166
|
+
const xj = polygon[j]!.x;
|
|
167
|
+
const yj = polygon[j]!.y;
|
|
168
|
+
|
|
169
|
+
// prettier-ignore
|
|
170
|
+
const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)
|
|
171
|
+
if (intersect) inside = !inside;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return inside;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Returns a new array of points representing the convex hull of the given set of points.
|
|
178
|
+
// https://www.nayuki.io/page/convex-hull-algorithm
|
|
179
|
+
function getHull<P extends Point>(points: Readonly<Array<P>>): Array<P> {
|
|
180
|
+
const newPoints: Array<P> = points.slice();
|
|
181
|
+
newPoints.sort((a: Point, b: Point) => {
|
|
182
|
+
if (a.x < b.x) return -1;
|
|
183
|
+
else if (a.x > b.x) return +1;
|
|
184
|
+
else if (a.y < b.y) return -1;
|
|
185
|
+
else if (a.y > b.y) return +1;
|
|
186
|
+
else return 0;
|
|
187
|
+
});
|
|
188
|
+
return getHullPresorted(newPoints);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Returns the convex hull, assuming that each points[i] <= points[i + 1]. Runs in O(n) time.
|
|
192
|
+
function getHullPresorted<P extends Point>(points: Readonly<Array<P>>): Array<P> {
|
|
193
|
+
if (points.length <= 1) return points.slice();
|
|
194
|
+
|
|
195
|
+
const upperHull: Array<P> = [];
|
|
196
|
+
for (let i = 0; i < points.length; i++) {
|
|
197
|
+
const p = points[i]!;
|
|
198
|
+
while (upperHull.length >= 2) {
|
|
199
|
+
const q = upperHull[upperHull.length - 1]!;
|
|
200
|
+
const r = upperHull[upperHull.length - 2]!;
|
|
201
|
+
if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) upperHull.pop();
|
|
202
|
+
else break;
|
|
203
|
+
}
|
|
204
|
+
upperHull.push(p);
|
|
205
|
+
}
|
|
206
|
+
upperHull.pop();
|
|
207
|
+
|
|
208
|
+
const lowerHull: Array<P> = [];
|
|
209
|
+
for (let i = points.length - 1; i >= 0; i--) {
|
|
210
|
+
const p = points[i]!;
|
|
211
|
+
while (lowerHull.length >= 2) {
|
|
212
|
+
const q = lowerHull[lowerHull.length - 1]!;
|
|
213
|
+
const r = lowerHull[lowerHull.length - 2]!;
|
|
214
|
+
if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) lowerHull.pop();
|
|
215
|
+
else break;
|
|
216
|
+
}
|
|
217
|
+
lowerHull.push(p);
|
|
218
|
+
}
|
|
219
|
+
lowerHull.pop();
|
|
220
|
+
|
|
221
|
+
if (
|
|
222
|
+
upperHull.length === 1 &&
|
|
223
|
+
lowerHull.length === 1 &&
|
|
224
|
+
upperHull[0]!.x === lowerHull[0]!.x &&
|
|
225
|
+
upperHull[0]!.y === lowerHull[0]!.y
|
|
226
|
+
)
|
|
227
|
+
return upperHull;
|
|
228
|
+
else return upperHull.concat(lowerHull);
|
|
229
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Getter } from "svelte-toolbelt";
|
|
2
|
+
|
|
3
|
+
export function useResizeObserver(node: Getter<HTMLElement | null>, onResize: () => void) {
|
|
4
|
+
$effect(() => {
|
|
5
|
+
let rAF = 0;
|
|
6
|
+
const _node = node();
|
|
7
|
+
if (!_node) return;
|
|
8
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
9
|
+
cancelAnimationFrame(rAF);
|
|
10
|
+
rAF = window.requestAnimationFrame(onResize);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
resizeObserver.observe(_node);
|
|
14
|
+
return () => {
|
|
15
|
+
window.cancelAnimationFrame(rAF);
|
|
16
|
+
resizeObserver.unobserve(_node);
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { type ReadableBox, box } from "svelte-toolbelt";
|
|
2
|
+
import { getElemDirection } from "./locale.js";
|
|
3
|
+
import { getDirectionalKeys } from "./get-directional-keys.js";
|
|
4
|
+
import { kbd } from "./kbd.js";
|
|
5
|
+
import { isBrowser } from "./is.js";
|
|
6
|
+
import type { Orientation } from "$lib/shared/index.js";
|
|
7
|
+
|
|
8
|
+
type UseRovingFocusProps = {
|
|
9
|
+
/**
|
|
10
|
+
* The selector used to find the focusable candidates.
|
|
11
|
+
*/
|
|
12
|
+
candidateAttr: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Custom candidate selector
|
|
16
|
+
*/
|
|
17
|
+
candidateSelector?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The id of the root node
|
|
21
|
+
*/
|
|
22
|
+
rootNodeId: ReadableBox<string>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether to loop through the candidates when reaching the end.
|
|
26
|
+
*/
|
|
27
|
+
loop: ReadableBox<boolean>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The orientation of the roving focus group. Used
|
|
31
|
+
* to determine how keyboard navigation should work.
|
|
32
|
+
*/
|
|
33
|
+
orientation: ReadableBox<Orientation>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A callback function called when a candidate is focused.
|
|
37
|
+
*/
|
|
38
|
+
onCandidateFocus?: (node: HTMLElement) => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type UseRovingFocusReturn = ReturnType<typeof useRovingFocus>;
|
|
42
|
+
|
|
43
|
+
export function useRovingFocus(props: UseRovingFocusProps) {
|
|
44
|
+
const currentTabStopId = box<string | null>(null);
|
|
45
|
+
|
|
46
|
+
function getCandidateNodes() {
|
|
47
|
+
if (!isBrowser) return [];
|
|
48
|
+
const node = document.getElementById(props.rootNodeId.current);
|
|
49
|
+
if (!node) return [];
|
|
50
|
+
|
|
51
|
+
if (props.candidateSelector) {
|
|
52
|
+
const candidates = Array.from(
|
|
53
|
+
node.querySelectorAll<HTMLElement>(props.candidateSelector)
|
|
54
|
+
);
|
|
55
|
+
return candidates;
|
|
56
|
+
} else {
|
|
57
|
+
const candidates = Array.from(
|
|
58
|
+
node.querySelectorAll<HTMLElement>(`[${props.candidateAttr}]:not([data-disabled])`)
|
|
59
|
+
);
|
|
60
|
+
return candidates;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function focusFirstCandidate() {
|
|
65
|
+
const items = getCandidateNodes();
|
|
66
|
+
if (!items.length) return;
|
|
67
|
+
items[0]?.focus();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleKeydown(
|
|
71
|
+
node: HTMLElement | null | undefined,
|
|
72
|
+
e: KeyboardEvent,
|
|
73
|
+
both: boolean = false
|
|
74
|
+
) {
|
|
75
|
+
const rootNode = document.getElementById(props.rootNodeId.current);
|
|
76
|
+
if (!rootNode || !node) return;
|
|
77
|
+
|
|
78
|
+
const items = getCandidateNodes();
|
|
79
|
+
if (!items.length) return;
|
|
80
|
+
|
|
81
|
+
const currentIndex = items.indexOf(node);
|
|
82
|
+
const dir = getElemDirection(rootNode);
|
|
83
|
+
const { nextKey, prevKey } = getDirectionalKeys(dir, props.orientation.current);
|
|
84
|
+
const loop = props.loop.current;
|
|
85
|
+
|
|
86
|
+
const keyToIndex = {
|
|
87
|
+
[nextKey]: currentIndex + 1,
|
|
88
|
+
[prevKey]: currentIndex - 1,
|
|
89
|
+
[kbd.HOME]: 0,
|
|
90
|
+
[kbd.END]: items.length - 1,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (both) {
|
|
94
|
+
const altNextKey = nextKey === kbd.ARROW_DOWN ? kbd.ARROW_RIGHT : kbd.ARROW_DOWN;
|
|
95
|
+
const altPrevKey = prevKey === kbd.ARROW_UP ? kbd.ARROW_LEFT : kbd.ARROW_UP;
|
|
96
|
+
keyToIndex[altNextKey] = currentIndex + 1;
|
|
97
|
+
keyToIndex[altPrevKey] = currentIndex - 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let itemIndex = keyToIndex[e.key];
|
|
101
|
+
if (itemIndex === undefined) return;
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
|
|
104
|
+
if (itemIndex < 0 && loop) {
|
|
105
|
+
itemIndex = items.length - 1;
|
|
106
|
+
} else if (itemIndex === items.length && loop) {
|
|
107
|
+
itemIndex = 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const itemToFocus = items[itemIndex];
|
|
111
|
+
if (!itemToFocus) return;
|
|
112
|
+
itemToFocus.focus();
|
|
113
|
+
currentTabStopId.current = itemToFocus.id;
|
|
114
|
+
props.onCandidateFocus?.(itemToFocus);
|
|
115
|
+
return itemToFocus;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getTabIndex(node: HTMLElement | null | undefined) {
|
|
119
|
+
const items = getCandidateNodes();
|
|
120
|
+
const anyActive = currentTabStopId.current !== null;
|
|
121
|
+
|
|
122
|
+
if (node && !anyActive && items[0] === node) {
|
|
123
|
+
currentTabStopId.current = node.id;
|
|
124
|
+
return 0;
|
|
125
|
+
} else if (node?.id === currentTabStopId.current) {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return -1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
setCurrentTabStopId(id: string) {
|
|
134
|
+
currentTabStopId.current = id;
|
|
135
|
+
},
|
|
136
|
+
getTabIndex,
|
|
137
|
+
handleKeydown,
|
|
138
|
+
focusFirstCandidate,
|
|
139
|
+
currentTabStopId,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/// <reference types="resize-observer-browser" />
|
|
2
|
+
|
|
3
|
+
import { untrack } from "svelte";
|
|
4
|
+
import { type WritableBox, afterTick } from "svelte-toolbelt";
|
|
5
|
+
|
|
6
|
+
export function useSize(node: WritableBox<HTMLElement | null>) {
|
|
7
|
+
let size = $state<{ width: number; height: number } | undefined>(undefined);
|
|
8
|
+
|
|
9
|
+
$effect(() => {
|
|
10
|
+
const currNode = node.current;
|
|
11
|
+
if (!currNode) {
|
|
12
|
+
size = undefined;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
afterTick(() => {
|
|
16
|
+
if (!currNode) return;
|
|
17
|
+
size = {
|
|
18
|
+
width: currNode.offsetWidth,
|
|
19
|
+
height: currNode.offsetHeight,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
24
|
+
if (!Array.isArray(entries) || !entries.length) return;
|
|
25
|
+
|
|
26
|
+
const entry = entries[0];
|
|
27
|
+
if (!entry) return;
|
|
28
|
+
|
|
29
|
+
let width: number;
|
|
30
|
+
let height: number;
|
|
31
|
+
|
|
32
|
+
if ("borderBoxSize" in entry) {
|
|
33
|
+
const borderSizeEntry = entry.borderBoxSize;
|
|
34
|
+
const borderSize = Array.isArray(borderSizeEntry)
|
|
35
|
+
? borderSizeEntry[0]
|
|
36
|
+
: borderSizeEntry;
|
|
37
|
+
width = borderSize.inlineSize;
|
|
38
|
+
height = borderSize.blockSize;
|
|
39
|
+
} else {
|
|
40
|
+
width = currNode.offsetWidth;
|
|
41
|
+
height = currNode.offsetHeight;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
untrack(() => (size = { width, height }));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
resizeObserver.observe(currNode, { box: "border-box" });
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
if (!currNode) return;
|
|
51
|
+
resizeObserver.unobserve(currNode);
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
get value() {
|
|
57
|
+
return size;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|