@flux-ui/internals 3.0.0-next.7 → 3.0.0-next.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +36 -0
  2. package/dist/composable/index.d.ts +2 -17
  3. package/dist/composable/index.js +1 -0
  4. package/dist/composable-5ooZTjm_.js +2 -0
  5. package/dist/composable-5ooZTjm_.js.map +1 -0
  6. package/dist/data/index.d.ts +246 -1
  7. package/dist/data/index.d.ts.map +1 -0
  8. package/dist/data/index.js +2 -0
  9. package/dist/data/index.js.map +1 -0
  10. package/dist/directive/index.d.ts +2 -3
  11. package/dist/directive/index.js +1 -0
  12. package/dist/directive-Dprka-AO.js +2 -0
  13. package/dist/directive-Dprka-AO.js.map +1 -0
  14. package/dist/index-7w0u9Q_D.d.ts +138 -0
  15. package/dist/index-7w0u9Q_D.d.ts.map +1 -0
  16. package/dist/index-B79OP5Th.d.ts +70 -0
  17. package/dist/index-B79OP5Th.d.ts.map +1 -0
  18. package/dist/index-BRS2s2tX.d.ts +14 -0
  19. package/dist/index-BRS2s2tX.d.ts.map +1 -0
  20. package/dist/index.d.ts +5 -4
  21. package/dist/index.js +1 -4
  22. package/dist/util/index.d.ts +2 -13
  23. package/dist/util/index.js +1 -0
  24. package/dist/util-CwaOsSvo.js +2 -0
  25. package/dist/util-CwaOsSvo.js.map +1 -0
  26. package/package.json +30 -13
  27. package/src/composable/index.ts +3 -5
  28. package/src/composable/useCalendar.ts +1 -1
  29. package/src/composable/useCalendarMonthSwitcher.ts +1 -2
  30. package/src/composable/useCalendarTimeGrid.ts +103 -0
  31. package/src/composable/useCalendarYearSwitcher.ts +1 -2
  32. package/src/composable/useFocusTrap.ts +26 -20
  33. package/src/composable/useFocusTrapLock.ts +1 -2
  34. package/src/composable/useFocusTrapReturn.ts +11 -7
  35. package/src/composable/useFocusTrapSubscription.ts +1 -2
  36. package/src/composable/useFocusZone.ts +10 -9
  37. package/src/composable/useInView.ts +2 -4
  38. package/src/composable/useKeyboardGrab.ts +152 -0
  39. package/src/composable/useRemembered.ts +1 -2
  40. package/src/composable/useScrollEdges.ts +76 -0
  41. package/src/composable/useScrollPosition.ts +1 -2
  42. package/src/directive/focusTrap.ts +4 -0
  43. package/src/directive/heightTransition.ts +6 -2
  44. package/src/directive/index.ts +1 -1
  45. package/src/util/animationFrameDebounce.ts +15 -0
  46. package/src/util/flattenVNodeTree.ts +1 -2
  47. package/src/util/focusTrap.ts +2 -1
  48. package/src/util/index.ts +2 -1
  49. package/src/util/unrefTemplateElement.ts +1 -1
  50. package/dist/composable/useCalendar.d.ts +0 -20
  51. package/dist/composable/useCalendarMonthSwitcher.d.ts +0 -10
  52. package/dist/composable/useCalendarYearSwitcher.d.ts +0 -8
  53. package/dist/composable/useClickOutside.d.ts +0 -4
  54. package/dist/composable/useComponentId.d.ts +0 -2
  55. package/dist/composable/useDebouncedRef.d.ts +0 -2
  56. package/dist/composable/useEventListener.d.ts +0 -2
  57. package/dist/composable/useFocusTrap.d.ts +0 -8
  58. package/dist/composable/useFocusTrapLock.d.ts +0 -2
  59. package/dist/composable/useFocusTrapReturn.d.ts +0 -2
  60. package/dist/composable/useFocusTrapSubscription.d.ts +0 -2
  61. package/dist/composable/useFocusZone.d.ts +0 -6
  62. package/dist/composable/useInView.d.ts +0 -6
  63. package/dist/composable/useInterval.d.ts +0 -2
  64. package/dist/composable/useMutationObserver.d.ts +0 -2
  65. package/dist/composable/useRemembered.d.ts +0 -2
  66. package/dist/composable/useScrollPosition.d.ts +0 -7
  67. package/dist/data/color.d.ts +0 -242
  68. package/dist/directive/focusTrap.d.ts +0 -5
  69. package/dist/directive/heightTransition.d.ts +0 -5
  70. package/dist/index.js.map +0 -42
  71. package/dist/util/flattenVNodeTree.d.ts +0 -2
  72. package/dist/util/focusTrap.d.ts +0 -8
  73. package/dist/util/getBidirectionalFocusElement.d.ts +0 -1
  74. package/dist/util/getComponentName.d.ts +0 -7
  75. package/dist/util/getComponentProps.d.ts +0 -1
  76. package/dist/util/getExposedRef.d.ts +0 -2
  77. package/dist/util/getFocusableElement.d.ts +0 -1
  78. package/dist/util/getFocusableElements.d.ts +0 -1
  79. package/dist/util/getKeyboardFocusableElements.d.ts +0 -1
  80. package/dist/util/unrefTemplateElement.d.ts +0 -4
  81. package/dist/util/warn.d.ts +0 -1
  82. package/dist/util/wrapFocus.d.ts +0 -1
  83. package/src/composable/useClickOutside.ts +0 -38
  84. package/src/composable/useComponentId.ts +0 -8
  85. package/src/composable/useDebouncedRef.ts +0 -38
  86. package/src/composable/useInterval.ts +0 -23
  87. package/src/composable/useMutationObserver.ts +0 -38
@@ -0,0 +1,103 @@
1
+ import type { DateTime } from 'luxon';
2
+ import { computed, type ComputedRef, type MaybeRefOrGetter, ref, type Ref, toValue, unref, watch } from 'vue';
3
+
4
+ export type UseCalendarTimeGridDayCount = 1 | 2 | 7;
5
+
6
+ export type UseCalendarTimeGridReturn = {
7
+ readonly isTransitioningToPast: Ref<boolean>;
8
+ readonly viewDate: Ref<DateTime>;
9
+ readonly viewDates: ComputedRef<DateTime[]>;
10
+ readonly rangeLabel: ComputedRef<string>;
11
+
12
+ setViewDate(date: DateTime): void;
13
+ next(): void;
14
+ previous(): void;
15
+ };
16
+
17
+ function getAnchor(date: DateTime, dayCount: UseCalendarTimeGridDayCount): DateTime {
18
+ return dayCount === 7 ? date.startOf('week') : date.startOf('day');
19
+ }
20
+
21
+ function stepDuration(dayCount: UseCalendarTimeGridDayCount): { day?: number; week?: number } {
22
+ if (dayCount === 7) {
23
+ return {week: 1};
24
+ }
25
+
26
+ if (dayCount === 2) {
27
+ return {day: 2};
28
+ }
29
+
30
+ return {day: 1};
31
+ }
32
+
33
+ export default function (
34
+ initialDate: DateTime,
35
+ dayCount: MaybeRefOrGetter<UseCalendarTimeGridDayCount>
36
+ ): UseCalendarTimeGridReturn {
37
+ const isTransitioningToPast = ref(false);
38
+ const viewDate = ref<DateTime>(getAnchor(initialDate, toValue(dayCount)));
39
+
40
+ // Re-anchor on dayCount change (e.g. switching from week to two-days view).
41
+ watch(() => toValue(dayCount), (count) => {
42
+ viewDate.value = getAnchor(unref(viewDate), count);
43
+ });
44
+
45
+ const viewDates = computed<DateTime[]>(() => {
46
+ const anchor = unref(viewDate);
47
+ const count = toValue(dayCount);
48
+ const out: DateTime[] = [];
49
+
50
+ for (let i = 0; i < count; ++i) {
51
+ out.push(anchor.plus({day: i}));
52
+ }
53
+
54
+ return out;
55
+ });
56
+
57
+ const rangeLabel = computed<string>(() => {
58
+ const dates = unref(viewDates);
59
+ const first = dates[0];
60
+ const last = dates[dates.length - 1];
61
+ const count = toValue(dayCount);
62
+
63
+ if (count === 1) {
64
+ return first.toLocaleString({weekday: 'long', day: 'numeric', month: 'long', year: 'numeric'});
65
+ }
66
+
67
+ const sameMonth = first.month === last.month;
68
+ const sameYear = first.year === last.year;
69
+
70
+ const firstFmt = first.toLocaleString(
71
+ sameMonth && sameYear
72
+ ? {weekday: 'short', day: 'numeric'}
73
+ : {weekday: 'short', day: 'numeric', month: 'short'}
74
+ );
75
+ const lastFmt = last.toLocaleString({weekday: 'short', day: 'numeric', month: 'short', year: sameYear ? undefined : 'numeric'});
76
+
77
+ return `${firstFmt} – ${lastFmt}`;
78
+ });
79
+
80
+ function setViewDate(date: DateTime): void {
81
+ const anchor = getAnchor(date, toValue(dayCount));
82
+ isTransitioningToPast.value = unref(viewDate) > anchor;
83
+ viewDate.value = anchor;
84
+ }
85
+
86
+ function next(): void {
87
+ setViewDate(unref(viewDate).plus(stepDuration(toValue(dayCount))));
88
+ }
89
+
90
+ function previous(): void {
91
+ setViewDate(unref(viewDate).minus(stepDuration(toValue(dayCount))));
92
+ }
93
+
94
+ return {
95
+ isTransitioningToPast,
96
+ viewDate,
97
+ viewDates,
98
+ rangeLabel,
99
+ setViewDate,
100
+ next,
101
+ previous
102
+ };
103
+ }
@@ -1,6 +1,5 @@
1
1
  import type { DateTime } from 'luxon';
2
- import type { Ref } from 'vue';
3
- import { computed, ref, unref, watch } from 'vue';
2
+ import { computed, ref, type Ref, unref, watch } from 'vue';
4
3
 
5
4
  export default function (currentDate: Ref<DateTime>, limit: number = 10): UseCalendarYearSwitcherReturn {
6
5
  const index = ref(0);
@@ -1,7 +1,5 @@
1
- import type { Ref } from 'vue';
2
- import { ref, watch } from 'vue';
3
- import type { TemplateRef } from '../util';
4
- import { getFocusableElements, isSSR, unrefTemplateElement, wrapFocus } from '../util';
1
+ import { ref, type Ref, watch } from 'vue';
2
+ import { getFocusableElements, isSSR, type TemplateRef, unrefTemplateElement, wrapFocus } from '../util';
5
3
  import useFocusTrapLock from './useFocusTrapLock';
6
4
  import useFocusTrapReturn from './useFocusTrapReturn';
7
5
 
@@ -13,7 +11,7 @@ export default function (containerRef: TemplateRef<HTMLElement>, options: UseFoc
13
11
  const {disable = ref(false), disableReturn = ref(false), attachTo = null} = options;
14
12
  const enabled = useFocusTrapLock(!disable);
15
13
 
16
- useFocusTrapReturn(disableReturn);
14
+ useFocusTrapReturn(containerRef, disableReturn);
17
15
 
18
16
  watch(containerRef, (_, __, onCleanup) => {
19
17
  const container = unrefTemplateElement(containerRef);
@@ -63,21 +61,29 @@ export default function (containerRef: TemplateRef<HTMLElement>, options: UseFoc
63
61
  attach.addEventListener('focusout', onFocusOut as EventListener, {capture: true});
64
62
 
65
63
  if (container) {
66
- const elements = getFocusableElements(container);
67
- const isActiveIndex = elements.findIndex(e => e.classList.contains('is-active'));
68
- const notDisabledIndex = elements.findIndex(e => !e.hasAttribute('aria-disabled'));
69
- let element = elements[0];
70
-
71
- if (isActiveIndex > -1) {
72
- element = elements[isActiveIndex];
73
- }
74
-
75
- if (notDisabledIndex > -1) {
76
- element = elements[notDisabledIndex];
77
- }
78
-
79
- if (element) {
80
- element.focus();
64
+ const autofocusElement = container.querySelector<HTMLElement>('[autofocus]');
65
+
66
+ if (autofocusElement) {
67
+ autofocusElement.focus();
68
+ } else {
69
+ const elements = getFocusableElements(container);
70
+ const isActiveIndex = elements.findIndex(e => e.classList.contains('is-active'));
71
+ const notDisabledIndex = elements.findIndex(e => !e.hasAttribute('aria-disabled'));
72
+ let element = elements[0];
73
+
74
+ if (isActiveIndex > -1) {
75
+ element = elements[isActiveIndex];
76
+ }
77
+
78
+ if (notDisabledIndex > -1) {
79
+ element = elements[notDisabledIndex];
80
+ }
81
+
82
+ if (element) {
83
+ element.focus();
84
+ } else {
85
+ container.focus();
86
+ }
81
87
  }
82
88
  }
83
89
 
@@ -1,5 +1,4 @@
1
- import type { Ref } from 'vue';
2
- import { onMounted, onUnmounted, ref, unref } from 'vue';
1
+ import { onMounted, onUnmounted, ref, type Ref, unref } from 'vue';
3
2
  import { FOCUS_TRAP_LOCKS } from '../util';
4
3
 
5
4
  let lockId = 0;
@@ -1,14 +1,18 @@
1
- import type { Ref } from 'vue';
2
- import { onUnmounted, ref, unref } from 'vue';
1
+ import { type Ref, unref, watch } from 'vue';
2
+ import { type TemplateRef, unrefTemplateElement } from '../util';
3
3
 
4
- export default function (disabled: Ref<boolean>): void {
5
- const target = ref<HTMLElement | null>(document.activeElement as HTMLElement | null);
4
+ export default function (containerRef: TemplateRef<HTMLElement>, disabled: Ref<boolean>): void {
5
+ watch(containerRef, (_, __, onCleanup) => {
6
+ const container = unrefTemplateElement(containerRef);
6
7
 
7
- onUnmounted(() => {
8
- if (unref(disabled)) {
8
+ if (!container || unref(disabled)) {
9
9
  return;
10
10
  }
11
11
 
12
- requestAnimationFrame(() => unref(target)?.focus());
12
+ const previousTarget = document.activeElement as HTMLElement | null;
13
+
14
+ onCleanup(() => {
15
+ requestAnimationFrame(() => previousTarget?.focus());
16
+ });
13
17
  });
14
18
  }
@@ -1,6 +1,5 @@
1
1
  import { onMounted, onUnmounted, ref } from 'vue';
2
- import type { FocusTrapListener } from '../util';
3
- import { FOCUS_TRAP_LOCKS } from '../util';
2
+ import { FOCUS_TRAP_LOCKS, type FocusTrapListener } from '../util';
4
3
 
5
4
  export default function (listener: FocusTrapListener): void {
6
5
  const unsubscribe = ref<Function | null>(null);
@@ -1,13 +1,14 @@
1
- import { watch } from 'vue';
2
- import type { TemplateRef } from '../util';
3
- import { getBidirectionalFocusElement, getFocusableElement, getFocusableElements, unrefTemplateElement } from '../util';
4
- import useMutationObserver from './useMutationObserver';
1
+ import { unwrapElement, useMutationObserver } from '@basmilius/common';
2
+ import { type ComponentPublicInstance, type Ref, watch } from 'vue';
3
+ import { getBidirectionalFocusElement, getFocusableElement, getFocusableElements } from '../util';
5
4
 
6
- export default function <TElement extends HTMLElement>(containerRef: TemplateRef<TElement>, {cycle = true, direction = 'bidirectional'}: UseFocusZoneOptions = {}): void {
5
+ type EligibleElement = ComponentPublicInstance | HTMLElement;
6
+
7
+ export default function <TElement extends EligibleElement>(containerRef: Ref<TElement>, {cycle = true, direction = 'bidirectional'}: UseFocusZoneOptions = {}): void {
7
8
  useMutationObserver(containerRef, () => updateFocus(findInitialIndex(), false));
8
9
 
9
10
  function findInitialIndex(): number {
10
- const container = unrefTemplateElement(containerRef)!;
11
+ const container = unwrapElement(containerRef);
11
12
  const elements = getFocusableElements(container);
12
13
  const isActiveIndex = elements.findIndex(e => e.classList.contains('is-active'));
13
14
  const notDisabledIndex = elements.findIndex(e => !e.hasAttribute('aria-disabled'));
@@ -24,7 +25,7 @@ export default function <TElement extends HTMLElement>(containerRef: TemplateRef
24
25
  }
25
26
 
26
27
  function updateFocus(elementIndex: number, doFocus: boolean = true): void {
27
- const container = unrefTemplateElement(containerRef)!;
28
+ const container = unwrapElement(containerRef)!;
28
29
  const elements = getFocusableElements(container);
29
30
  elements.forEach((elm, index) => elm.tabIndex = index === elementIndex ? 0 : -1);
30
31
 
@@ -32,7 +33,7 @@ export default function <TElement extends HTMLElement>(containerRef: TemplateRef
32
33
  }
33
34
 
34
35
  function onKeyDown(evt: KeyboardEvent): void {
35
- const container = unrefTemplateElement(containerRef)!;
36
+ const container = unwrapElement(containerRef)!;
36
37
  const elements = getFocusableElements(container);
37
38
 
38
39
  if (['Enter', ' '].includes(evt.key)) {
@@ -52,7 +53,7 @@ export default function <TElement extends HTMLElement>(containerRef: TemplateRef
52
53
  }
53
54
 
54
55
  watch(containerRef, (_, __, onCleanup) => {
55
- const container = unrefTemplateElement(containerRef);
56
+ const container = unwrapElement(containerRef);
56
57
 
57
58
  if (!container) {
58
59
  return;
@@ -1,7 +1,5 @@
1
- import type { Ref } from 'vue';
2
- import { ref, watch } from 'vue';
3
- import type { TemplateRef } from '../util';
4
- import { unrefTemplateElement } from '../util';
1
+ import { ref, type Ref, watch } from 'vue';
2
+ import { type TemplateRef, unrefTemplateElement } from '../util';
5
3
 
6
4
  export default function <TElement extends HTMLElement>(containerRef: TemplateRef<TElement>, options: UseInViewOptions = {}): Ref<boolean> {
7
5
  const inView = ref(options.initial ?? false);
@@ -0,0 +1,152 @@
1
+ import { computed, type ComputedRef, ref, type Ref, unref } from 'vue';
2
+
3
+ export type KeyboardGrabDirection = 'up' | 'down' | 'left' | 'right';
4
+
5
+ export type UseKeyboardGrabOptions<TPos> = {
6
+ readonly isDraggable: Ref<boolean>;
7
+ readonly itemId: Ref<string | number | null | undefined>;
8
+ readonly grabbedId: Ref<string | number | null>;
9
+ onGrab(): TPos;
10
+ onMove(direction: KeyboardGrabDirection): void;
11
+ onCommit(origin: TPos): void;
12
+ onCancel(origin: TPos): void;
13
+ announce?(message: string): void;
14
+ };
15
+
16
+ export type UseKeyboardGrabReturn = {
17
+ readonly isGrabbed: ComputedRef<boolean>;
18
+ handleKeyDown(evt: KeyboardEvent): void;
19
+ release(): void;
20
+ };
21
+
22
+ let liveRegion: HTMLElement | null = null;
23
+
24
+ function ensureLiveRegion(): HTMLElement {
25
+ if (liveRegion) {
26
+ return liveRegion;
27
+ }
28
+
29
+ if (typeof document === 'undefined') {
30
+ return null as unknown as HTMLElement;
31
+ }
32
+
33
+ const region = document.createElement('div');
34
+ region.setAttribute('role', 'status');
35
+ region.setAttribute('aria-live', 'polite');
36
+ region.setAttribute('aria-atomic', 'true');
37
+ region.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0';
38
+ document.body.appendChild(region);
39
+ liveRegion = region;
40
+ return region;
41
+ }
42
+
43
+ export function defaultAnnounce(message: string): void {
44
+ const region = ensureLiveRegion();
45
+
46
+ if (!region) {
47
+ return;
48
+ }
49
+
50
+ region.textContent = '';
51
+ requestAnimationFrame(() => {
52
+ region.textContent = message;
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Generic keyboard-grab state machine. Maps Space/Enter/Escape/Arrow keys
58
+ * onto grab/commit/cancel/move callbacks. The actual movement logic is
59
+ * delegated to `onMove` since it depends on the host component's topology.
60
+ */
61
+ export default function useKeyboardGrab<TPos>(options: UseKeyboardGrabOptions<TPos>): UseKeyboardGrabReturn {
62
+ const origin = ref<TPos | null>(null);
63
+ const announce = options.announce ?? defaultAnnounce;
64
+
65
+ const isGrabbed = computed<boolean>(() => {
66
+ const id = unref(options.itemId);
67
+ const grabbed = unref(options.grabbedId);
68
+
69
+ return id != null && grabbed === id;
70
+ });
71
+
72
+ function release(): void {
73
+ origin.value = null;
74
+ }
75
+
76
+ function handleKeyDown(evt: KeyboardEvent): void {
77
+ if (!unref(options.isDraggable)) {
78
+ return;
79
+ }
80
+
81
+ const id = unref(options.itemId);
82
+
83
+ if (id == null) {
84
+ return;
85
+ }
86
+
87
+ if (!isGrabbed.value) {
88
+ if (evt.key === ' ' || evt.key === 'Enter') {
89
+ evt.preventDefault();
90
+ origin.value = options.onGrab() as TPos;
91
+ }
92
+
93
+ return;
94
+ }
95
+
96
+ switch (evt.key) {
97
+ case 'ArrowUp':
98
+ evt.preventDefault();
99
+ options.onMove('up');
100
+ break;
101
+
102
+ case 'ArrowDown':
103
+ evt.preventDefault();
104
+ options.onMove('down');
105
+ break;
106
+
107
+ case 'ArrowLeft':
108
+ evt.preventDefault();
109
+ options.onMove('left');
110
+ break;
111
+
112
+ case 'ArrowRight':
113
+ evt.preventDefault();
114
+ options.onMove('right');
115
+ break;
116
+
117
+ case ' ':
118
+ case 'Enter': {
119
+ evt.preventDefault();
120
+ const o = origin.value;
121
+ origin.value = null;
122
+
123
+ if (o !== null) {
124
+ options.onCommit(o);
125
+ }
126
+
127
+ break;
128
+ }
129
+
130
+ case 'Escape': {
131
+ evt.preventDefault();
132
+ const o = origin.value;
133
+ origin.value = null;
134
+
135
+ if (o !== null) {
136
+ options.onCancel(o);
137
+ }
138
+
139
+ break;
140
+ }
141
+ }
142
+
143
+ // Suppress unused-warning when announce is unused inside the function.
144
+ void announce;
145
+ }
146
+
147
+ return {
148
+ isGrabbed,
149
+ handleKeyDown,
150
+ release
151
+ };
152
+ }
@@ -1,6 +1,5 @@
1
1
  import { DateTime } from 'luxon';
2
- import type { Ref } from 'vue';
3
- import { ref, watch } from 'vue';
2
+ import { ref, type Ref, watch } from 'vue';
4
3
 
5
4
  export default function <T>(key: string, initialValue: T): Ref<T> {
6
5
  const realKey = `flux/${key}`;
@@ -0,0 +1,76 @@
1
+ import { ref, type Ref, watch } from 'vue';
2
+ import { type TemplateRef, unrefTemplateElement } from '../util';
3
+
4
+ export default function <TElement extends HTMLElement>(elementRef: TemplateRef<TElement>): UseScrollEdgesReturn {
5
+ const isAtStart = ref(true);
6
+ const isAtEnd = ref(true);
7
+ const isAtLeft = ref(true);
8
+ const isAtRight = ref(true);
9
+
10
+ watch(elementRef, (_, __, onCleanup) => {
11
+ const element = unrefTemplateElement(elementRef);
12
+
13
+ if (!element) {
14
+ return;
15
+ }
16
+
17
+ const update = (): void => {
18
+ const {scrollTop, scrollHeight, clientHeight, scrollLeft, scrollWidth, clientWidth} = element;
19
+
20
+ isAtStart.value = scrollTop <= 0;
21
+ isAtEnd.value = Math.ceil(scrollTop + clientHeight) >= scrollHeight;
22
+ isAtLeft.value = scrollLeft <= 0;
23
+ isAtRight.value = Math.ceil(scrollLeft + clientWidth) >= scrollWidth;
24
+ };
25
+
26
+ const resizeObserver = new ResizeObserver(update);
27
+ resizeObserver.observe(element);
28
+
29
+ for (const child of Array.from(element.children)) {
30
+ resizeObserver.observe(child);
31
+ }
32
+
33
+ const mutationObserver = new MutationObserver(mutations => {
34
+ for (const mutation of mutations) {
35
+ for (const node of Array.from(mutation.addedNodes)) {
36
+ if (node instanceof Element) {
37
+ resizeObserver.observe(node);
38
+ }
39
+ }
40
+
41
+ for (const node of Array.from(mutation.removedNodes)) {
42
+ if (node instanceof Element) {
43
+ resizeObserver.unobserve(node);
44
+ }
45
+ }
46
+ }
47
+
48
+ update();
49
+ });
50
+
51
+ mutationObserver.observe(element, {childList: true});
52
+ element.addEventListener('scroll', update, {passive: true});
53
+
54
+ update();
55
+
56
+ onCleanup(() => {
57
+ element.removeEventListener('scroll', update);
58
+ resizeObserver.disconnect();
59
+ mutationObserver.disconnect();
60
+ });
61
+ }, {immediate: true});
62
+
63
+ return {
64
+ isAtStart,
65
+ isAtEnd,
66
+ isAtLeft,
67
+ isAtRight
68
+ };
69
+ }
70
+
71
+ export type UseScrollEdgesReturn = {
72
+ readonly isAtStart: Ref<boolean>;
73
+ readonly isAtEnd: Ref<boolean>;
74
+ readonly isAtLeft: Ref<boolean>;
75
+ readonly isAtRight: Ref<boolean>;
76
+ };
@@ -1,5 +1,4 @@
1
- import type { Ref } from 'vue';
2
- import { ref, unref } from 'vue';
1
+ import { ref, type Ref, unref } from 'vue';
3
2
  import type { TemplateRef } from '../util';
4
3
  import useEventListener from './useEventListener';
5
4
 
@@ -71,6 +71,10 @@ export default {
71
71
  const focusTrap = new FocusTrap(elm);
72
72
  focusTrap.register();
73
73
  focusTraps.set(elm, focusTrap);
74
+ },
75
+
76
+ getSSRProps(): Record<string, unknown> {
77
+ return {};
74
78
  }
75
79
  } satisfies Directive;
76
80
 
@@ -34,7 +34,7 @@ class HeightTransition {
34
34
  }
35
35
 
36
36
  getComputedStyle(this.#root);
37
- requestAnimationFrame(() => requestAnimationFrame(() => this.#root.style.height = height));
37
+ requestAnimationFrame(() => this.#root.style.height = height);
38
38
  }
39
39
  }
40
40
 
@@ -49,7 +49,11 @@ export default {
49
49
  const heightTransition = new HeightTransition(elm);
50
50
  heightTransition.register();
51
51
  heightTransitions.set(elm, heightTransition);
52
+ },
53
+
54
+ getSSRProps(): Record<string, unknown> {
55
+ return {};
52
56
  }
53
- } satisfies Directive;
57
+ } satisfies Directive as Directive;
54
58
 
55
59
  const heightTransitions: WeakMap<HTMLElement, HeightTransition> = new WeakMap();
@@ -4,4 +4,4 @@ import vHeightTransition from './heightTransition';
4
4
  export {
5
5
  vFocusTrap,
6
6
  vHeightTransition
7
- }
7
+ };
@@ -0,0 +1,15 @@
1
+ import type { FluxMaybePromise } from '@flux-ui/types';
2
+
3
+ export default function <T extends () => FluxMaybePromise<void>>(fn: T): T {
4
+ if (typeof requestAnimationFrame === 'undefined') {
5
+ return (() => {
6
+ }) as T;
7
+ }
8
+
9
+ let animationFrame = 0;
10
+
11
+ return (() => {
12
+ cancelAnimationFrame(animationFrame);
13
+ animationFrame = requestAnimationFrame(fn);
14
+ }) as T;
15
+ }
@@ -1,5 +1,4 @@
1
- import type { VNode } from 'vue';
2
- import { Fragment } from 'vue';
1
+ import { Fragment, type VNode } from 'vue';
3
2
 
4
3
  export default function (vnodes: VNode[]): VNode[] {
5
4
  const flattened: VNode[] = [];
@@ -59,4 +59,5 @@ interface FocusTrap {
59
59
 
60
60
  export type FocusTrapListener = (isEnabled: boolean, focusTraps: FocusTrap[]) => void;
61
61
 
62
- export default new FocusTrapLockStack();
62
+ const _default: FocusTrapLockStack = new FocusTrapLockStack();
63
+ export default _default;
package/src/util/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { default as animationFrameDebounce } from './animationFrameDebounce';
1
2
  export { default as flattenVNodeTree } from './flattenVNodeTree';
2
3
  export { default as getBidirectionalFocusElement } from './getBidirectionalFocusElement';
3
4
  export { default as getComponentName } from './getComponentName';
@@ -12,4 +13,4 @@ export { default as wrapFocus } from './wrapFocus';
12
13
 
13
14
  export { default as FOCUS_TRAP_LOCKS, type FocusTrapListener } from './focusTrap';
14
15
 
15
- export const isSSR = !globalThis.document;
16
+ export const isSSR: boolean = !globalThis.document;
@@ -1,5 +1,5 @@
1
1
  import { isHtmlElement } from '@basmilius/utils';
2
- import { ComponentPublicInstance, ShallowRef, unref } from 'vue';
2
+ import { type ComponentPublicInstance, type ShallowRef, unref } from 'vue';
3
3
 
4
4
  export type TemplateElement<TElement extends HTMLElement> = ComponentPublicInstance<any, any, any, any, any, any, any, any, any, any, any, any, any, any, TElement> | TElement | null;
5
5
  export type TemplateRef<TElement extends HTMLElement> = Readonly<ShallowRef<TemplateElement<TElement>>>;
@@ -1,20 +0,0 @@
1
- import type { DateTime } from "luxon";
2
- import { type Ref } from "vue";
3
- export default function(initialDate: DateTime, options?: UseCalendarOptions): UseCalendarReturn;
4
- type UseCalendarOptions = {
5
- readonly monthLength?: "short" | "long"
6
- readonly weekDayLength?: "short" | "long"
7
- };
8
- type UseCalendarReturn = {
9
- readonly isTransitioningToPast: Ref<boolean>
10
- readonly viewDate: Ref<DateTime>
11
- readonly viewDateNext: Ref<DateTime>
12
- readonly viewDatePrevious: Ref<DateTime>
13
- readonly viewMonth: Ref<string>
14
- readonly viewYear: Ref<string>
15
- readonly dates: Ref<DateTime[]>
16
- readonly days: Ref<string[]>
17
- setViewDate(date: DateTime): void
18
- nextMonth(): void
19
- previousMonth(): void
20
- };
@@ -1,10 +0,0 @@
1
- import type { DateTime } from "luxon";
2
- import type { Ref } from "vue";
3
- export default function(currentDate: Ref<DateTime>, displayLength: "short" | "long"): UseCalendarMonthSwitcherReturn;
4
- type MonthEntry = {
5
- readonly date: DateTime
6
- readonly label: string
7
- };
8
- type UseCalendarMonthSwitcherReturn = {
9
- readonly months: Ref<MonthEntry[]>
10
- };
@@ -1,8 +0,0 @@
1
- import type { DateTime } from "luxon";
2
- import type { Ref } from "vue";
3
- export default function(currentDate: Ref<DateTime>, limit?: number): UseCalendarYearSwitcherReturn;
4
- type UseCalendarYearSwitcherReturn = {
5
- readonly years: Ref<number[]>
6
- next(): void
7
- previous(): void
8
- };
@@ -1,4 +0,0 @@
1
- import type { Ref } from "vue";
2
- import type { TemplateRef } from "../util";
3
- type Handler = ((evt: PointerEvent) => void) | ((evt: PointerEvent) => Promise<void>);
4
- export default function<TElement extends HTMLElement>(elementRefs: TemplateRef<TElement> | TemplateRef<TElement>[], enabled: boolean | Ref<boolean>, onOutsideClick: Handler): void;