@fiscozen/composables 0.1.37 → 0.1.38
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/package.json +4 -4
- package/src/FzFloating.vue +2 -0
- package/src/__tests__/FzFloating.spec.ts +15 -0
- package/src/__tests__/__snapshots__/FzFloating.spec.ts.snap +24 -24
- package/src/__tests__/useFloating.spec.ts +339 -0
- package/src/composables/useFloating.ts +320 -243
- package/src/index.ts +1 -1
- package/src/types.ts +1 -1
- package/dist/composables.js +0 -465
- package/dist/composables.umd.cjs +0 -1
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiscozen/composables",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"description": "Design System utility composables",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": [],
|
|
8
8
|
"author": "Riccardo Agnoletto",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fiscozen/style": "^0.1.
|
|
10
|
+
"@fiscozen/style": "^0.1.8-next.0"
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
13
13
|
"tailwindcss": "^3.4.1",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"vitest": "^1.2.0",
|
|
30
30
|
"vue-tsc": "^1.8.25",
|
|
31
31
|
"@fiscozen/tsconfig": "^0.1.0",
|
|
32
|
-
"@fiscozen/
|
|
33
|
-
"@fiscozen/
|
|
32
|
+
"@fiscozen/eslint-config": "^0.1.0",
|
|
33
|
+
"@fiscozen/prettier-config": "^0.1.0"
|
|
34
34
|
},
|
|
35
35
|
"license": "ISC",
|
|
36
36
|
"scripts": {
|
package/src/FzFloating.vue
CHANGED
|
@@ -76,6 +76,8 @@ watch(
|
|
|
76
76
|
}
|
|
77
77
|
window.addEventListener('scroll', setPositionWhenOpen)
|
|
78
78
|
const openerRect = opener.value?.getBoundingClientRect()
|
|
79
|
+
// CRITICAL: Set position fixed immediately to prevent layout shift
|
|
80
|
+
content.value.style.position = 'fixed'
|
|
79
81
|
content.value.style.top = '0px'
|
|
80
82
|
content.value.style.left = '0px'
|
|
81
83
|
content.value.style.transform = 'none'
|
|
@@ -26,6 +26,21 @@ beforeEach(() => {
|
|
|
26
26
|
disconnect: () => null
|
|
27
27
|
})
|
|
28
28
|
window.IntersectionObserver = mockIntersectionObserver
|
|
29
|
+
|
|
30
|
+
// Mock matchMedia for useMediaQuery composable
|
|
31
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
32
|
+
writable: true,
|
|
33
|
+
value: vi.fn().mockImplementation((query) => ({
|
|
34
|
+
matches: false,
|
|
35
|
+
media: query,
|
|
36
|
+
onchange: null,
|
|
37
|
+
addListener: vi.fn(),
|
|
38
|
+
removeListener: vi.fn(),
|
|
39
|
+
addEventListener: vi.fn(),
|
|
40
|
+
removeEventListener: vi.fn(),
|
|
41
|
+
dispatchEvent: vi.fn()
|
|
42
|
+
}))
|
|
43
|
+
})
|
|
29
44
|
})
|
|
30
45
|
describe('FzFloating', () => {
|
|
31
46
|
positions.forEach((pos) => {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`FzFloating > should match snapshot 1`] = `
|
|
4
4
|
"<div>
|
|
5
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
6
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
5
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
6
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
7
7
|
<div>content</div>
|
|
8
8
|
</div>
|
|
9
9
|
<!--v-if-->
|
|
@@ -12,8 +12,8 @@ exports[`FzFloating > should match snapshot 1`] = `
|
|
|
12
12
|
|
|
13
13
|
exports[`FzFloating > should match snapshot 2`] = `
|
|
14
14
|
"<div>
|
|
15
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
16
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
15
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
16
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
17
17
|
<div>content</div>
|
|
18
18
|
</div>
|
|
19
19
|
<!--v-if-->
|
|
@@ -22,8 +22,8 @@ exports[`FzFloating > should match snapshot 2`] = `
|
|
|
22
22
|
|
|
23
23
|
exports[`FzFloating > should match snapshot 3`] = `
|
|
24
24
|
"<div>
|
|
25
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
26
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
25
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
26
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
27
27
|
<div>content</div>
|
|
28
28
|
</div>
|
|
29
29
|
<!--v-if-->
|
|
@@ -32,8 +32,8 @@ exports[`FzFloating > should match snapshot 3`] = `
|
|
|
32
32
|
|
|
33
33
|
exports[`FzFloating > should match snapshot 4`] = `
|
|
34
34
|
"<div>
|
|
35
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
36
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
35
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
36
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
37
37
|
<div>content</div>
|
|
38
38
|
</div>
|
|
39
39
|
<!--v-if-->
|
|
@@ -42,8 +42,8 @@ exports[`FzFloating > should match snapshot 4`] = `
|
|
|
42
42
|
|
|
43
43
|
exports[`FzFloating > should match snapshot 5`] = `
|
|
44
44
|
"<div>
|
|
45
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
46
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
45
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
46
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
47
47
|
<div>content</div>
|
|
48
48
|
</div>
|
|
49
49
|
<!--v-if-->
|
|
@@ -52,8 +52,8 @@ exports[`FzFloating > should match snapshot 5`] = `
|
|
|
52
52
|
|
|
53
53
|
exports[`FzFloating > should match snapshot 6`] = `
|
|
54
54
|
"<div>
|
|
55
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
56
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
55
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
56
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
57
57
|
<div>content</div>
|
|
58
58
|
</div>
|
|
59
59
|
<!--v-if-->
|
|
@@ -62,8 +62,8 @@ exports[`FzFloating > should match snapshot 6`] = `
|
|
|
62
62
|
|
|
63
63
|
exports[`FzFloating > should match snapshot 7`] = `
|
|
64
64
|
"<div>
|
|
65
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
66
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
65
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
66
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
67
67
|
<div>content</div>
|
|
68
68
|
</div>
|
|
69
69
|
<!--v-if-->
|
|
@@ -72,8 +72,8 @@ exports[`FzFloating > should match snapshot 7`] = `
|
|
|
72
72
|
|
|
73
73
|
exports[`FzFloating > should match snapshot 8`] = `
|
|
74
74
|
"<div>
|
|
75
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
76
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
75
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
76
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
77
77
|
<div>content</div>
|
|
78
78
|
</div>
|
|
79
79
|
<!--v-if-->
|
|
@@ -82,8 +82,8 @@ exports[`FzFloating > should match snapshot 8`] = `
|
|
|
82
82
|
|
|
83
83
|
exports[`FzFloating > should match snapshot 9`] = `
|
|
84
84
|
"<div>
|
|
85
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
86
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
85
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
86
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
87
87
|
<div>content</div>
|
|
88
88
|
</div>
|
|
89
89
|
<!--v-if-->
|
|
@@ -92,8 +92,8 @@ exports[`FzFloating > should match snapshot 9`] = `
|
|
|
92
92
|
|
|
93
93
|
exports[`FzFloating > should match snapshot 10`] = `
|
|
94
94
|
"<div>
|
|
95
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
96
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
95
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
96
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
97
97
|
<div>content</div>
|
|
98
98
|
</div>
|
|
99
99
|
<!--v-if-->
|
|
@@ -102,8 +102,8 @@ exports[`FzFloating > should match snapshot 10`] = `
|
|
|
102
102
|
|
|
103
103
|
exports[`FzFloating > should match snapshot 11`] = `
|
|
104
104
|
"<div>
|
|
105
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
106
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
105
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
106
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
107
107
|
<div>content</div>
|
|
108
108
|
</div>
|
|
109
109
|
<!--v-if-->
|
|
@@ -112,8 +112,8 @@ exports[`FzFloating > should match snapshot 11`] = `
|
|
|
112
112
|
|
|
113
113
|
exports[`FzFloating > should match snapshot 12`] = `
|
|
114
114
|
"<div>
|
|
115
|
-
<div class="inline-flex"><button>opener</button></div>
|
|
116
|
-
<div class="fz__floating__content bg-core-white fixed p-4
|
|
115
|
+
<div class="inline-flex w-full sm:w-auto"><button>opener</button></div>
|
|
116
|
+
<div class="fz__floating__content w-full sm:w-auto bg-core-white fixed p-4">
|
|
117
117
|
<div>content</div>
|
|
118
118
|
</div>
|
|
119
119
|
<!--v-if-->
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
|
|
2
|
+
import { ref, toRefs, nextTick } from 'vue'
|
|
3
|
+
import { useFloating } from '../composables/useFloating'
|
|
4
|
+
import { FzFloatingPosition } from '../types'
|
|
5
|
+
|
|
6
|
+
describe('useFloating', () => {
|
|
7
|
+
let mockElement: HTMLElement
|
|
8
|
+
let mockOpener: HTMLElement
|
|
9
|
+
let mockContainer: HTMLElement
|
|
10
|
+
let mockIntersectionObserver: any
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Mock IntersectionObserver
|
|
14
|
+
mockIntersectionObserver = vi.fn()
|
|
15
|
+
mockIntersectionObserver.mockReturnValue({
|
|
16
|
+
observe: vi.fn(),
|
|
17
|
+
unobserve: vi.fn(),
|
|
18
|
+
disconnect: vi.fn()
|
|
19
|
+
})
|
|
20
|
+
window.IntersectionObserver = mockIntersectionObserver
|
|
21
|
+
|
|
22
|
+
// Mock DOM elements
|
|
23
|
+
mockElement = document.createElement('div')
|
|
24
|
+
mockElement.style.width = '200px'
|
|
25
|
+
mockElement.style.height = '100px'
|
|
26
|
+
document.body.appendChild(mockElement)
|
|
27
|
+
|
|
28
|
+
mockOpener = document.createElement('button')
|
|
29
|
+
mockOpener.style.width = '100px'
|
|
30
|
+
mockOpener.style.height = '40px'
|
|
31
|
+
document.body.appendChild(mockOpener)
|
|
32
|
+
|
|
33
|
+
mockContainer = document.body
|
|
34
|
+
|
|
35
|
+
// Mock getBoundingClientRect
|
|
36
|
+
mockElement.getBoundingClientRect = vi.fn(() => ({
|
|
37
|
+
width: 200,
|
|
38
|
+
height: 100,
|
|
39
|
+
top: 0,
|
|
40
|
+
left: 0,
|
|
41
|
+
right: 200,
|
|
42
|
+
bottom: 100,
|
|
43
|
+
x: 0,
|
|
44
|
+
y: 0,
|
|
45
|
+
toJSON: () => {}
|
|
46
|
+
})) as any
|
|
47
|
+
|
|
48
|
+
mockOpener.getBoundingClientRect = vi.fn(() => ({
|
|
49
|
+
width: 100,
|
|
50
|
+
height: 40,
|
|
51
|
+
top: 100,
|
|
52
|
+
left: 100,
|
|
53
|
+
right: 200,
|
|
54
|
+
bottom: 140,
|
|
55
|
+
x: 100,
|
|
56
|
+
y: 100,
|
|
57
|
+
toJSON: () => {}
|
|
58
|
+
})) as any
|
|
59
|
+
|
|
60
|
+
mockContainer.getBoundingClientRect = vi.fn(() => ({
|
|
61
|
+
width: 1024,
|
|
62
|
+
height: 768,
|
|
63
|
+
top: 0,
|
|
64
|
+
left: 0,
|
|
65
|
+
right: 1024,
|
|
66
|
+
bottom: 768,
|
|
67
|
+
x: 0,
|
|
68
|
+
y: 0,
|
|
69
|
+
toJSON: () => {}
|
|
70
|
+
})) as any
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
document.body.innerHTML = ''
|
|
75
|
+
vi.clearAllMocks()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('initialization', () => {
|
|
79
|
+
it('should initialize with correct default values', () => {
|
|
80
|
+
const args = toRefs({
|
|
81
|
+
position: ref<FzFloatingPosition>('auto'),
|
|
82
|
+
opener: { domRef: ref(mockOpener) },
|
|
83
|
+
element: { domRef: ref(mockElement) }
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const floating = useFloating(args)
|
|
87
|
+
|
|
88
|
+
expect(floating.float).toBeDefined()
|
|
89
|
+
expect(floating.rect).toBeDefined()
|
|
90
|
+
expect(floating.setPosition).toBeDefined()
|
|
91
|
+
expect(floating.position).toBeDefined()
|
|
92
|
+
expect(floating.actualPosition).toBeDefined()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should set up intersection observer', async () => {
|
|
96
|
+
const args = toRefs({
|
|
97
|
+
position: ref<FzFloatingPosition>('bottom'),
|
|
98
|
+
opener: { domRef: ref(mockOpener) },
|
|
99
|
+
element: { domRef: ref(mockElement) }
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const floating = useFloating(args)
|
|
103
|
+
await floating.setPosition()
|
|
104
|
+
await nextTick()
|
|
105
|
+
|
|
106
|
+
expect(mockIntersectionObserver).toHaveBeenCalled()
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('position calculation', () => {
|
|
111
|
+
const positions: FzFloatingPosition[] = [
|
|
112
|
+
'bottom',
|
|
113
|
+
'bottom-start',
|
|
114
|
+
'bottom-end',
|
|
115
|
+
'top',
|
|
116
|
+
'top-start',
|
|
117
|
+
'top-end',
|
|
118
|
+
'left',
|
|
119
|
+
'left-start',
|
|
120
|
+
'left-end',
|
|
121
|
+
'right',
|
|
122
|
+
'right-start',
|
|
123
|
+
'right-end'
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
positions.forEach((position) => {
|
|
127
|
+
it(`should calculate ${position} position correctly`, async () => {
|
|
128
|
+
const args = toRefs({
|
|
129
|
+
position: ref(position),
|
|
130
|
+
element: { domRef: ref(mockElement) },
|
|
131
|
+
opener: { domRef: ref(mockOpener) }
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const floating = useFloating(args)
|
|
135
|
+
await floating.setPosition()
|
|
136
|
+
await nextTick()
|
|
137
|
+
|
|
138
|
+
expect(floating.float.position).toBeDefined()
|
|
139
|
+
expect(typeof floating.float.position.x).toBe('number')
|
|
140
|
+
expect(typeof floating.float.position.y).toBe('number')
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('auto positioning', () => {
|
|
146
|
+
it('should resolve auto position based on available space', async () => {
|
|
147
|
+
const args = toRefs({
|
|
148
|
+
position: ref<FzFloatingPosition>('auto'),
|
|
149
|
+
element: { domRef: ref(mockElement) },
|
|
150
|
+
opener: { domRef: ref(mockOpener) }
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const floating = useFloating(args)
|
|
154
|
+
await floating.setPosition()
|
|
155
|
+
await nextTick()
|
|
156
|
+
|
|
157
|
+
expect(floating.actualPosition?.value).toBeDefined()
|
|
158
|
+
expect(floating.actualPosition?.value).not.toBe('auto')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should resolve auto-start position', async () => {
|
|
162
|
+
const args = toRefs({
|
|
163
|
+
position: ref<FzFloatingPosition>('auto-start'),
|
|
164
|
+
element: { domRef: ref(mockElement) },
|
|
165
|
+
opener: { domRef: ref(mockOpener) }
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const floating = useFloating(args)
|
|
169
|
+
await floating.setPosition()
|
|
170
|
+
await nextTick()
|
|
171
|
+
|
|
172
|
+
expect(floating.actualPosition?.value).toBeDefined()
|
|
173
|
+
expect(floating.actualPosition?.value).not.toBe('auto-start')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('should resolve auto-end position', async () => {
|
|
177
|
+
const args = toRefs({
|
|
178
|
+
position: ref<FzFloatingPosition>('auto-end'),
|
|
179
|
+
element: { domRef: ref(mockElement) },
|
|
180
|
+
opener: { domRef: ref(mockOpener) }
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const floating = useFloating(args)
|
|
184
|
+
await floating.setPosition()
|
|
185
|
+
await nextTick()
|
|
186
|
+
|
|
187
|
+
expect(floating.actualPosition?.value).toBeDefined()
|
|
188
|
+
expect(floating.actualPosition?.value).not.toBe('auto-end')
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('boundary corrections', () => {
|
|
193
|
+
it('should keep element within container bounds', async () => {
|
|
194
|
+
// Position opener at edge of container
|
|
195
|
+
mockOpener.getBoundingClientRect = vi.fn(() => ({
|
|
196
|
+
width: 100,
|
|
197
|
+
height: 40,
|
|
198
|
+
top: 10,
|
|
199
|
+
left: 950,
|
|
200
|
+
right: 1050,
|
|
201
|
+
bottom: 50,
|
|
202
|
+
x: 950,
|
|
203
|
+
y: 10,
|
|
204
|
+
toJSON: () => {}
|
|
205
|
+
})) as any
|
|
206
|
+
|
|
207
|
+
const args = toRefs({
|
|
208
|
+
position: ref<FzFloatingPosition>('bottom-end'),
|
|
209
|
+
element: { domRef: ref(mockElement) },
|
|
210
|
+
opener: { domRef: ref(mockOpener) }
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const floating = useFloating(args)
|
|
214
|
+
await floating.setPosition()
|
|
215
|
+
await nextTick()
|
|
216
|
+
|
|
217
|
+
// Element should be adjusted to fit within container
|
|
218
|
+
expect(floating.float.position.x).toBeLessThanOrEqual(1024)
|
|
219
|
+
expect(floating.float.position.y).toBeLessThanOrEqual(768)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe('element styling', () => {
|
|
224
|
+
it('should apply position fixed immediately', async () => {
|
|
225
|
+
const args = toRefs({
|
|
226
|
+
position: ref<FzFloatingPosition>('bottom'),
|
|
227
|
+
element: { domRef: ref(mockElement) },
|
|
228
|
+
opener: { domRef: ref(mockOpener) }
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
const floating = useFloating(args)
|
|
232
|
+
await floating.setPosition()
|
|
233
|
+
await nextTick()
|
|
234
|
+
|
|
235
|
+
expect(mockElement.style.position).toBe('fixed')
|
|
236
|
+
expect(mockElement.style.display).toBe('flex')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('should update position styles', async () => {
|
|
240
|
+
const args = toRefs({
|
|
241
|
+
position: ref<FzFloatingPosition>('bottom'),
|
|
242
|
+
element: { domRef: ref(mockElement) },
|
|
243
|
+
opener: { domRef: ref(mockOpener) }
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const floating = useFloating(args)
|
|
247
|
+
await floating.setPosition()
|
|
248
|
+
await nextTick()
|
|
249
|
+
|
|
250
|
+
expect(mockElement.style.top).toBeTruthy()
|
|
251
|
+
expect(mockElement.style.left).toBeTruthy()
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
describe('callback handling', () => {
|
|
256
|
+
it('should call callback when provided', async () => {
|
|
257
|
+
const callback = vi.fn()
|
|
258
|
+
const args = toRefs({
|
|
259
|
+
position: ref<FzFloatingPosition>('bottom'),
|
|
260
|
+
element: { domRef: ref(mockElement) },
|
|
261
|
+
opener: { domRef: ref(mockOpener) },
|
|
262
|
+
callback: ref(callback)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
const floating = useFloating(args)
|
|
266
|
+
await floating.setPosition()
|
|
267
|
+
await nextTick()
|
|
268
|
+
|
|
269
|
+
expect(callback).toHaveBeenCalled()
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should not call callback when ignoreCallback is true', async () => {
|
|
273
|
+
const callback = vi.fn()
|
|
274
|
+
const args = toRefs({
|
|
275
|
+
position: ref<FzFloatingPosition>('bottom'),
|
|
276
|
+
element: { domRef: ref(mockElement) },
|
|
277
|
+
opener: { domRef: ref(mockOpener) },
|
|
278
|
+
callback: ref(callback)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
const floating = useFloating(args)
|
|
282
|
+
await floating.setPosition(true)
|
|
283
|
+
await nextTick()
|
|
284
|
+
|
|
285
|
+
expect(callback).not.toHaveBeenCalled()
|
|
286
|
+
})
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
describe('without opener', () => {
|
|
290
|
+
it('should position element relative to container when no opener', async () => {
|
|
291
|
+
const args = toRefs({
|
|
292
|
+
position: ref<FzFloatingPosition>('bottom'),
|
|
293
|
+
opener: undefined,
|
|
294
|
+
element: { domRef: ref(mockElement) }
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
const floating = useFloating(args)
|
|
298
|
+
await floating.setPosition()
|
|
299
|
+
await nextTick()
|
|
300
|
+
|
|
301
|
+
expect(floating.float.position).toBeDefined()
|
|
302
|
+
expect(typeof floating.float.position.x).toBe('number')
|
|
303
|
+
expect(typeof floating.float.position.y).toBe('number')
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
describe('error handling', () => {
|
|
308
|
+
it('should throw error when element ref is missing', async () => {
|
|
309
|
+
const args = toRefs({
|
|
310
|
+
position: ref<FzFloatingPosition>('bottom'),
|
|
311
|
+
opener: { domRef: ref(mockOpener) },
|
|
312
|
+
element: { domRef: ref(null) }
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
const floating = useFloating(args)
|
|
316
|
+
|
|
317
|
+
await expect(async () => {
|
|
318
|
+
await floating.setPosition()
|
|
319
|
+
await nextTick()
|
|
320
|
+
}).rejects.toThrow('missing reference element for floating behavior')
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
describe('cleanup', () => {
|
|
325
|
+
it('should disconnect intersection observer on unmount', () => {
|
|
326
|
+
const args = toRefs({
|
|
327
|
+
position: ref<FzFloatingPosition>('bottom'),
|
|
328
|
+
opener: { domRef: ref(mockOpener) },
|
|
329
|
+
element: { domRef: ref(mockElement) }
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
useFloating(args)
|
|
333
|
+
|
|
334
|
+
// Simulate component unmount
|
|
335
|
+
// Note: In a real scenario, this would be handled by Vue's lifecycle
|
|
336
|
+
expect(mockIntersectionObserver).toHaveBeenCalled()
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
})
|