@pyreon/elements 0.11.4 → 0.11.6
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 +38 -35
- package/lib/index.d.ts +15 -15
- package/package.json +24 -24
- package/src/Element/component.tsx +14 -14
- package/src/Element/constants.ts +25 -25
- package/src/Element/index.ts +2 -2
- package/src/Element/types.ts +3 -3
- package/src/Element/utils.ts +1 -1
- package/src/List/component.tsx +7 -7
- package/src/List/index.ts +2 -2
- package/src/Overlay/component.tsx +22 -22
- package/src/Overlay/context.tsx +2 -2
- package/src/Overlay/index.ts +3 -3
- package/src/Overlay/useOverlay.tsx +97 -97
- package/src/Portal/component.tsx +6 -6
- package/src/Portal/index.ts +2 -2
- package/src/Text/component.tsx +6 -6
- package/src/Text/index.ts +2 -2
- package/src/Text/styled.ts +4 -4
- package/src/Util/component.tsx +5 -5
- package/src/Util/index.ts +2 -2
- package/src/__tests__/Content.test.tsx +46 -46
- package/src/__tests__/Element.test.ts +251 -251
- package/src/__tests__/Iterator.test.ts +142 -142
- package/src/__tests__/List.test.ts +61 -61
- package/src/__tests__/Overlay.test.ts +125 -125
- package/src/__tests__/Portal.test.ts +33 -33
- package/src/__tests__/Text.test.ts +128 -128
- package/src/__tests__/Util.test.ts +31 -31
- package/src/__tests__/Wrapper.test.tsx +60 -60
- package/src/__tests__/equalBeforeAfter.test.ts +41 -41
- package/src/__tests__/helpers.test.ts +29 -29
- package/src/__tests__/overlayContext.test.tsx +14 -14
- package/src/__tests__/responsiveProps.test.ts +116 -116
- package/src/__tests__/useOverlay.test.ts +283 -283
- package/src/__tests__/utils.test.ts +43 -43
- package/src/constants.ts +1 -1
- package/src/helpers/Content/component.tsx +5 -5
- package/src/helpers/Content/index.ts +1 -1
- package/src/helpers/Content/styled.ts +16 -16
- package/src/helpers/Content/types.ts +7 -7
- package/src/helpers/Iterator/component.tsx +28 -28
- package/src/helpers/Iterator/index.ts +2 -2
- package/src/helpers/Iterator/types.ts +3 -3
- package/src/helpers/Wrapper/component.tsx +6 -6
- package/src/helpers/Wrapper/index.ts +1 -1
- package/src/helpers/Wrapper/styled.ts +8 -8
- package/src/helpers/Wrapper/types.ts +3 -3
- package/src/helpers/Wrapper/utils.ts +1 -1
- package/src/helpers/index.ts +2 -2
- package/src/index.ts +16 -16
- package/src/types.ts +7 -7
- package/src/utils.ts +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
// Mocks
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
|
|
7
|
-
vi.mock(
|
|
7
|
+
vi.mock('@pyreon/reactivity', () => {
|
|
8
8
|
const signal = <T>(initial: T) => {
|
|
9
9
|
let value = initial
|
|
10
10
|
const s = (() => value) as (() => T) & {
|
|
@@ -37,7 +37,7 @@ vi.mock("@pyreon/reactivity", () => {
|
|
|
37
37
|
return { signal }
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
vi.mock(
|
|
40
|
+
vi.mock('@pyreon/core', async (importOriginal) => {
|
|
41
41
|
const actual = (await importOriginal()) as Record<string, unknown>
|
|
42
42
|
return {
|
|
43
43
|
...actual,
|
|
@@ -47,7 +47,7 @@ vi.mock("@pyreon/core", async (importOriginal) => {
|
|
|
47
47
|
}
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
-
vi.mock(
|
|
50
|
+
vi.mock('@pyreon/ui-core', async () => {
|
|
51
51
|
const throttle = <F extends (...args: any[]) => any>(fn: F, _delay: number) => {
|
|
52
52
|
const wrapped = (...args: any[]) => fn(...args)
|
|
53
53
|
wrapped.cancel = () => {
|
|
@@ -59,14 +59,14 @@ vi.mock("@pyreon/ui-core", async () => {
|
|
|
59
59
|
return { render: vi.fn(), throttle }
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
-
vi.mock(
|
|
63
|
-
value: (v: unknown, _base?: number) => (typeof v ===
|
|
62
|
+
vi.mock('@pyreon/unistyle', () => ({
|
|
63
|
+
value: (v: unknown, _base?: number) => (typeof v === 'number' ? `${v}px` : v),
|
|
64
64
|
}))
|
|
65
65
|
|
|
66
66
|
const mockSetBlocked = vi.fn()
|
|
67
67
|
const mockSetUnblocked = vi.fn()
|
|
68
68
|
|
|
69
|
-
vi.mock(
|
|
69
|
+
vi.mock('../Overlay/context', async (importOriginal) => {
|
|
70
70
|
const actual = (await importOriginal()) as Record<string, unknown>
|
|
71
71
|
return {
|
|
72
72
|
...actual,
|
|
@@ -77,18 +77,18 @@ vi.mock("../Overlay/context", async (importOriginal) => {
|
|
|
77
77
|
}
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
-
vi.mock(
|
|
80
|
+
vi.mock('~/utils', () => ({
|
|
81
81
|
IS_DEVELOPMENT: false,
|
|
82
82
|
}))
|
|
83
83
|
|
|
84
|
-
import { useOverlay } from
|
|
84
|
+
import { useOverlay } from '../Overlay'
|
|
85
85
|
|
|
86
86
|
// ---------------------------------------------------------------------------
|
|
87
87
|
// Helpers
|
|
88
88
|
// ---------------------------------------------------------------------------
|
|
89
89
|
|
|
90
90
|
const mockElement = (rect: Partial<DOMRect> = {}): HTMLElement => {
|
|
91
|
-
const el = document.createElement(
|
|
91
|
+
const el = document.createElement('div')
|
|
92
92
|
el.getBoundingClientRect = () => ({
|
|
93
93
|
top: 0,
|
|
94
94
|
bottom: 0,
|
|
@@ -106,8 +106,8 @@ const mockElement = (rect: Partial<DOMRect> = {}): HTMLElement => {
|
|
|
106
106
|
|
|
107
107
|
// Set viewport dimensions for position tests
|
|
108
108
|
const setViewport = (width: number, height: number) => {
|
|
109
|
-
Object.defineProperty(window,
|
|
110
|
-
Object.defineProperty(window,
|
|
109
|
+
Object.defineProperty(window, 'innerWidth', { value: width, configurable: true })
|
|
110
|
+
Object.defineProperty(window, 'innerHeight', { value: height, configurable: true })
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
beforeEach(() => {
|
|
@@ -124,32 +124,32 @@ afterEach(() => {
|
|
|
124
124
|
// Tests
|
|
125
125
|
// ---------------------------------------------------------------------------
|
|
126
126
|
|
|
127
|
-
describe(
|
|
127
|
+
describe('useOverlay', () => {
|
|
128
128
|
// =========================================================================
|
|
129
129
|
// 1. Default state
|
|
130
130
|
// =========================================================================
|
|
131
|
-
describe(
|
|
132
|
-
it(
|
|
131
|
+
describe('default state', () => {
|
|
132
|
+
it('active is false by default', () => {
|
|
133
133
|
const o = useOverlay()
|
|
134
134
|
expect(o.active()).toBe(false)
|
|
135
135
|
})
|
|
136
136
|
|
|
137
|
-
it(
|
|
137
|
+
it('align defaults to bottom', () => {
|
|
138
138
|
const o = useOverlay()
|
|
139
|
-
expect(o.align).toBe(
|
|
139
|
+
expect(o.align).toBe('bottom')
|
|
140
140
|
})
|
|
141
141
|
|
|
142
|
-
it(
|
|
142
|
+
it('alignX defaults to left', () => {
|
|
143
143
|
const o = useOverlay()
|
|
144
|
-
expect(o.alignX()).toBe(
|
|
144
|
+
expect(o.alignX()).toBe('left')
|
|
145
145
|
})
|
|
146
146
|
|
|
147
|
-
it(
|
|
147
|
+
it('alignY defaults to bottom', () => {
|
|
148
148
|
const o = useOverlay()
|
|
149
|
-
expect(o.alignY()).toBe(
|
|
149
|
+
expect(o.alignY()).toBe('bottom')
|
|
150
150
|
})
|
|
151
151
|
|
|
152
|
-
it(
|
|
152
|
+
it('blocked is false by default', () => {
|
|
153
153
|
const o = useOverlay()
|
|
154
154
|
expect(o.blocked()).toBe(false)
|
|
155
155
|
})
|
|
@@ -158,8 +158,8 @@ describe("useOverlay", () => {
|
|
|
158
158
|
// =========================================================================
|
|
159
159
|
// 2. isOpen=true
|
|
160
160
|
// =========================================================================
|
|
161
|
-
describe(
|
|
162
|
-
it(
|
|
161
|
+
describe('isOpen=true', () => {
|
|
162
|
+
it('active starts true when isOpen is true', () => {
|
|
163
163
|
const o = useOverlay({ isOpen: true })
|
|
164
164
|
expect(o.active()).toBe(true)
|
|
165
165
|
})
|
|
@@ -168,21 +168,21 @@ describe("useOverlay", () => {
|
|
|
168
168
|
// =========================================================================
|
|
169
169
|
// 3. Disabled state
|
|
170
170
|
// =========================================================================
|
|
171
|
-
describe(
|
|
172
|
-
it(
|
|
171
|
+
describe('disabled', () => {
|
|
172
|
+
it('forces active to false when disabled is true', () => {
|
|
173
173
|
const o = useOverlay({ isOpen: true, disabled: true })
|
|
174
174
|
expect(o.active()).toBe(false)
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
-
it(
|
|
177
|
+
it('prevents event handling when disabled', () => {
|
|
178
178
|
const onOpen = vi.fn()
|
|
179
|
-
const o = useOverlay({ openOn:
|
|
179
|
+
const o = useOverlay({ openOn: 'click', disabled: true, onOpen })
|
|
180
180
|
const triggerEl = mockElement()
|
|
181
181
|
o.triggerRef(triggerEl)
|
|
182
182
|
const cleanup = o.setupListeners()
|
|
183
183
|
|
|
184
|
-
const click = new MouseEvent(
|
|
185
|
-
Object.defineProperty(click,
|
|
184
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
185
|
+
Object.defineProperty(click, 'target', { value: triggerEl })
|
|
186
186
|
window.dispatchEvent(click)
|
|
187
187
|
|
|
188
188
|
expect(o.active()).toBe(false)
|
|
@@ -194,35 +194,35 @@ describe("useOverlay", () => {
|
|
|
194
194
|
// =========================================================================
|
|
195
195
|
// 4. triggerRef / contentRef
|
|
196
196
|
// =========================================================================
|
|
197
|
-
describe(
|
|
198
|
-
it(
|
|
197
|
+
describe('triggerRef and contentRef', () => {
|
|
198
|
+
it('triggerRef is a callable function', () => {
|
|
199
199
|
const o = useOverlay()
|
|
200
|
-
expect(typeof o.triggerRef).toBe(
|
|
200
|
+
expect(typeof o.triggerRef).toBe('function')
|
|
201
201
|
})
|
|
202
202
|
|
|
203
|
-
it(
|
|
203
|
+
it('contentRef is a callable function', () => {
|
|
204
204
|
const o = useOverlay()
|
|
205
|
-
expect(typeof o.contentRef).toBe(
|
|
205
|
+
expect(typeof o.contentRef).toBe('function')
|
|
206
206
|
})
|
|
207
207
|
|
|
208
|
-
it(
|
|
208
|
+
it('triggerRef accepts an HTMLElement', () => {
|
|
209
209
|
const o = useOverlay()
|
|
210
210
|
const el = mockElement()
|
|
211
211
|
expect(() => o.triggerRef(el)).not.toThrow()
|
|
212
212
|
})
|
|
213
213
|
|
|
214
|
-
it(
|
|
214
|
+
it('contentRef accepts an HTMLElement', () => {
|
|
215
215
|
const o = useOverlay()
|
|
216
216
|
const el = mockElement()
|
|
217
217
|
expect(() => o.contentRef(el)).not.toThrow()
|
|
218
218
|
})
|
|
219
219
|
|
|
220
|
-
it(
|
|
220
|
+
it('triggerRef accepts null', () => {
|
|
221
221
|
const o = useOverlay()
|
|
222
222
|
expect(() => o.triggerRef(null)).not.toThrow()
|
|
223
223
|
})
|
|
224
224
|
|
|
225
|
-
it(
|
|
225
|
+
it('contentRef accepts null', () => {
|
|
226
226
|
const o = useOverlay()
|
|
227
227
|
expect(() => o.contentRef(null)).not.toThrow()
|
|
228
228
|
})
|
|
@@ -231,46 +231,46 @@ describe("useOverlay", () => {
|
|
|
231
231
|
// =========================================================================
|
|
232
232
|
// 5. showContent / hideContent
|
|
233
233
|
// =========================================================================
|
|
234
|
-
describe(
|
|
235
|
-
it(
|
|
234
|
+
describe('showContent / hideContent', () => {
|
|
235
|
+
it('showContent sets active to true', () => {
|
|
236
236
|
const o = useOverlay()
|
|
237
237
|
o.showContent()
|
|
238
238
|
expect(o.active()).toBe(true)
|
|
239
239
|
})
|
|
240
240
|
|
|
241
|
-
it(
|
|
241
|
+
it('hideContent sets active to false', () => {
|
|
242
242
|
const o = useOverlay({ isOpen: true })
|
|
243
243
|
o.hideContent()
|
|
244
244
|
expect(o.active()).toBe(false)
|
|
245
245
|
})
|
|
246
246
|
|
|
247
|
-
it(
|
|
247
|
+
it('showContent calls onOpen callback', () => {
|
|
248
248
|
const onOpen = vi.fn()
|
|
249
249
|
const o = useOverlay({ onOpen })
|
|
250
250
|
o.showContent()
|
|
251
251
|
expect(onOpen).toHaveBeenCalledOnce()
|
|
252
252
|
})
|
|
253
253
|
|
|
254
|
-
it(
|
|
254
|
+
it('hideContent calls onClose callback', () => {
|
|
255
255
|
const onClose = vi.fn()
|
|
256
256
|
const o = useOverlay({ isOpen: true, onClose })
|
|
257
257
|
o.hideContent()
|
|
258
258
|
expect(onClose).toHaveBeenCalledOnce()
|
|
259
259
|
})
|
|
260
260
|
|
|
261
|
-
it(
|
|
261
|
+
it('showContent calls ctx.setBlocked', () => {
|
|
262
262
|
const o = useOverlay()
|
|
263
263
|
o.showContent()
|
|
264
264
|
expect(mockSetBlocked).toHaveBeenCalledOnce()
|
|
265
265
|
})
|
|
266
266
|
|
|
267
|
-
it(
|
|
267
|
+
it('hideContent calls ctx.setUnblocked', () => {
|
|
268
268
|
const o = useOverlay({ isOpen: true })
|
|
269
269
|
o.hideContent()
|
|
270
270
|
expect(mockSetUnblocked).toHaveBeenCalledOnce()
|
|
271
271
|
})
|
|
272
272
|
|
|
273
|
-
it(
|
|
273
|
+
it('toggle between show and hide works', () => {
|
|
274
274
|
const o = useOverlay()
|
|
275
275
|
o.showContent()
|
|
276
276
|
expect(o.active()).toBe(true)
|
|
@@ -284,21 +284,21 @@ describe("useOverlay", () => {
|
|
|
284
284
|
// =========================================================================
|
|
285
285
|
// 6. Blocked state
|
|
286
286
|
// =========================================================================
|
|
287
|
-
describe(
|
|
288
|
-
it(
|
|
287
|
+
describe('blocked state', () => {
|
|
288
|
+
it('setBlocked increments blocked count', () => {
|
|
289
289
|
const o = useOverlay()
|
|
290
290
|
o.setBlocked()
|
|
291
291
|
expect(o.blocked()).toBe(true)
|
|
292
292
|
})
|
|
293
293
|
|
|
294
|
-
it(
|
|
294
|
+
it('setUnblocked decrements blocked count', () => {
|
|
295
295
|
const o = useOverlay()
|
|
296
296
|
o.setBlocked()
|
|
297
297
|
o.setUnblocked()
|
|
298
298
|
expect(o.blocked()).toBe(false)
|
|
299
299
|
})
|
|
300
300
|
|
|
301
|
-
it(
|
|
301
|
+
it('multiple setBlocked calls require matching setUnblocked calls', () => {
|
|
302
302
|
const o = useOverlay()
|
|
303
303
|
o.setBlocked()
|
|
304
304
|
o.setBlocked()
|
|
@@ -308,16 +308,16 @@ describe("useOverlay", () => {
|
|
|
308
308
|
expect(o.blocked()).toBe(false)
|
|
309
309
|
})
|
|
310
310
|
|
|
311
|
-
it(
|
|
311
|
+
it('setUnblocked does not go below zero', () => {
|
|
312
312
|
const o = useOverlay()
|
|
313
313
|
o.setUnblocked()
|
|
314
314
|
o.setUnblocked()
|
|
315
315
|
expect(o.blocked()).toBe(false)
|
|
316
316
|
})
|
|
317
317
|
|
|
318
|
-
it(
|
|
318
|
+
it('blocked overlay ignores click events', () => {
|
|
319
319
|
const onOpen = vi.fn()
|
|
320
|
-
const o = useOverlay({ openOn:
|
|
320
|
+
const o = useOverlay({ openOn: 'click', onOpen })
|
|
321
321
|
const triggerEl = mockElement()
|
|
322
322
|
o.triggerRef(triggerEl)
|
|
323
323
|
const cleanup = o.setupListeners()
|
|
@@ -325,8 +325,8 @@ describe("useOverlay", () => {
|
|
|
325
325
|
// Block the overlay
|
|
326
326
|
o.setBlocked()
|
|
327
327
|
|
|
328
|
-
const click = new MouseEvent(
|
|
329
|
-
Object.defineProperty(click,
|
|
328
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
329
|
+
Object.defineProperty(click, 'target', { value: triggerEl })
|
|
330
330
|
window.dispatchEvent(click)
|
|
331
331
|
|
|
332
332
|
expect(o.active()).toBe(false)
|
|
@@ -338,21 +338,21 @@ describe("useOverlay", () => {
|
|
|
338
338
|
// =========================================================================
|
|
339
339
|
// 7. setupListeners
|
|
340
340
|
// =========================================================================
|
|
341
|
-
describe(
|
|
342
|
-
it(
|
|
341
|
+
describe('setupListeners', () => {
|
|
342
|
+
it('returns a cleanup function', () => {
|
|
343
343
|
const o = useOverlay()
|
|
344
344
|
const cleanup = o.setupListeners()
|
|
345
|
-
expect(typeof cleanup).toBe(
|
|
345
|
+
expect(typeof cleanup).toBe('function')
|
|
346
346
|
cleanup()
|
|
347
347
|
})
|
|
348
348
|
|
|
349
|
-
it(
|
|
349
|
+
it('cleanup removes event listeners without error', () => {
|
|
350
350
|
const o = useOverlay()
|
|
351
351
|
const cleanup = o.setupListeners()
|
|
352
352
|
expect(() => cleanup()).not.toThrow()
|
|
353
353
|
})
|
|
354
354
|
|
|
355
|
-
it(
|
|
355
|
+
it('cleanup can be called multiple times safely', () => {
|
|
356
356
|
const o = useOverlay()
|
|
357
357
|
const cleanup = o.setupListeners()
|
|
358
358
|
cleanup()
|
|
@@ -363,125 +363,125 @@ describe("useOverlay", () => {
|
|
|
363
363
|
// =========================================================================
|
|
364
364
|
// 8. Click handling
|
|
365
365
|
// =========================================================================
|
|
366
|
-
describe(
|
|
367
|
-
it(
|
|
368
|
-
const o = useOverlay({ openOn:
|
|
366
|
+
describe('click handling', () => {
|
|
367
|
+
it('openOn=click: clicking trigger when inactive opens overlay', () => {
|
|
368
|
+
const o = useOverlay({ openOn: 'click' })
|
|
369
369
|
const triggerEl = mockElement()
|
|
370
370
|
o.triggerRef(triggerEl)
|
|
371
371
|
const cleanup = o.setupListeners()
|
|
372
372
|
|
|
373
|
-
const click = new MouseEvent(
|
|
374
|
-
Object.defineProperty(click,
|
|
373
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
374
|
+
Object.defineProperty(click, 'target', { value: triggerEl })
|
|
375
375
|
window.dispatchEvent(click)
|
|
376
376
|
|
|
377
377
|
expect(o.active()).toBe(true)
|
|
378
378
|
cleanup()
|
|
379
379
|
})
|
|
380
380
|
|
|
381
|
-
it(
|
|
382
|
-
const o = useOverlay({ openOn:
|
|
381
|
+
it('openOn=click: clicking non-trigger when inactive does not open', () => {
|
|
382
|
+
const o = useOverlay({ openOn: 'click' })
|
|
383
383
|
const triggerEl = mockElement()
|
|
384
384
|
o.triggerRef(triggerEl)
|
|
385
385
|
const cleanup = o.setupListeners()
|
|
386
386
|
|
|
387
|
-
const outsideEl = document.createElement(
|
|
388
|
-
const click = new MouseEvent(
|
|
389
|
-
Object.defineProperty(click,
|
|
387
|
+
const outsideEl = document.createElement('div')
|
|
388
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
389
|
+
Object.defineProperty(click, 'target', { value: outsideEl })
|
|
390
390
|
window.dispatchEvent(click)
|
|
391
391
|
|
|
392
392
|
expect(o.active()).toBe(false)
|
|
393
393
|
cleanup()
|
|
394
394
|
})
|
|
395
395
|
|
|
396
|
-
it(
|
|
397
|
-
const o = useOverlay({ openOn:
|
|
396
|
+
it('closeOn=click: any click when active closes overlay', () => {
|
|
397
|
+
const o = useOverlay({ openOn: 'click', closeOn: 'click', isOpen: true })
|
|
398
398
|
const triggerEl = mockElement()
|
|
399
399
|
o.triggerRef(triggerEl)
|
|
400
400
|
const cleanup = o.setupListeners()
|
|
401
401
|
|
|
402
|
-
const outsideEl = document.createElement(
|
|
403
|
-
const click = new MouseEvent(
|
|
404
|
-
Object.defineProperty(click,
|
|
402
|
+
const outsideEl = document.createElement('div')
|
|
403
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
404
|
+
Object.defineProperty(click, 'target', { value: outsideEl })
|
|
405
405
|
window.dispatchEvent(click)
|
|
406
406
|
|
|
407
407
|
expect(o.active()).toBe(false)
|
|
408
408
|
cleanup()
|
|
409
409
|
})
|
|
410
410
|
|
|
411
|
-
it(
|
|
412
|
-
const o = useOverlay({ openOn:
|
|
411
|
+
it('closeOn=clickOnTrigger: clicking trigger when active closes overlay', () => {
|
|
412
|
+
const o = useOverlay({ openOn: 'click', closeOn: 'clickOnTrigger', isOpen: true })
|
|
413
413
|
const triggerEl = mockElement()
|
|
414
414
|
o.triggerRef(triggerEl)
|
|
415
415
|
const cleanup = o.setupListeners()
|
|
416
416
|
|
|
417
|
-
const click = new MouseEvent(
|
|
418
|
-
Object.defineProperty(click,
|
|
417
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
418
|
+
Object.defineProperty(click, 'target', { value: triggerEl })
|
|
419
419
|
window.dispatchEvent(click)
|
|
420
420
|
|
|
421
421
|
expect(o.active()).toBe(false)
|
|
422
422
|
cleanup()
|
|
423
423
|
})
|
|
424
424
|
|
|
425
|
-
it(
|
|
426
|
-
const o = useOverlay({ openOn:
|
|
425
|
+
it('closeOn=clickOnTrigger: clicking outside does not close overlay', () => {
|
|
426
|
+
const o = useOverlay({ openOn: 'click', closeOn: 'clickOnTrigger', isOpen: true })
|
|
427
427
|
const triggerEl = mockElement()
|
|
428
428
|
o.triggerRef(triggerEl)
|
|
429
429
|
const cleanup = o.setupListeners()
|
|
430
430
|
|
|
431
|
-
const outsideEl = document.createElement(
|
|
432
|
-
const click = new MouseEvent(
|
|
433
|
-
Object.defineProperty(click,
|
|
431
|
+
const outsideEl = document.createElement('div')
|
|
432
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
433
|
+
Object.defineProperty(click, 'target', { value: outsideEl })
|
|
434
434
|
window.dispatchEvent(click)
|
|
435
435
|
|
|
436
436
|
expect(o.active()).toBe(true)
|
|
437
437
|
cleanup()
|
|
438
438
|
})
|
|
439
439
|
|
|
440
|
-
it(
|
|
441
|
-
const o = useOverlay({ openOn:
|
|
440
|
+
it('closeOn=clickOutsideContent: click outside content closes overlay', () => {
|
|
441
|
+
const o = useOverlay({ openOn: 'click', closeOn: 'clickOutsideContent', isOpen: true })
|
|
442
442
|
const triggerEl = mockElement()
|
|
443
443
|
const contentEl = mockElement()
|
|
444
444
|
o.triggerRef(triggerEl)
|
|
445
445
|
o.contentRef(contentEl)
|
|
446
446
|
const cleanup = o.setupListeners()
|
|
447
447
|
|
|
448
|
-
const outsideEl = document.createElement(
|
|
449
|
-
const click = new MouseEvent(
|
|
450
|
-
Object.defineProperty(click,
|
|
448
|
+
const outsideEl = document.createElement('div')
|
|
449
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
450
|
+
Object.defineProperty(click, 'target', { value: outsideEl })
|
|
451
451
|
window.dispatchEvent(click)
|
|
452
452
|
|
|
453
453
|
expect(o.active()).toBe(false)
|
|
454
454
|
cleanup()
|
|
455
455
|
})
|
|
456
456
|
|
|
457
|
-
it(
|
|
458
|
-
const o = useOverlay({ openOn:
|
|
457
|
+
it('closeOn=clickOutsideContent: click inside content does not close overlay', () => {
|
|
458
|
+
const o = useOverlay({ openOn: 'click', closeOn: 'clickOutsideContent', isOpen: true })
|
|
459
459
|
const triggerEl = mockElement()
|
|
460
460
|
const contentEl = mockElement()
|
|
461
|
-
const childEl = document.createElement(
|
|
461
|
+
const childEl = document.createElement('span')
|
|
462
462
|
contentEl.appendChild(childEl)
|
|
463
463
|
o.triggerRef(triggerEl)
|
|
464
464
|
o.contentRef(contentEl)
|
|
465
465
|
const cleanup = o.setupListeners()
|
|
466
466
|
|
|
467
|
-
const click = new MouseEvent(
|
|
468
|
-
Object.defineProperty(click,
|
|
467
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
468
|
+
Object.defineProperty(click, 'target', { value: childEl })
|
|
469
469
|
window.dispatchEvent(click)
|
|
470
470
|
|
|
471
471
|
expect(o.active()).toBe(true)
|
|
472
472
|
cleanup()
|
|
473
473
|
})
|
|
474
474
|
|
|
475
|
-
it(
|
|
476
|
-
const o = useOverlay({ openOn:
|
|
475
|
+
it('click on trigger child element opens overlay (contains check)', () => {
|
|
476
|
+
const o = useOverlay({ openOn: 'click' })
|
|
477
477
|
const triggerEl = mockElement()
|
|
478
|
-
const innerEl = document.createElement(
|
|
478
|
+
const innerEl = document.createElement('span')
|
|
479
479
|
triggerEl.appendChild(innerEl)
|
|
480
480
|
o.triggerRef(triggerEl)
|
|
481
481
|
const cleanup = o.setupListeners()
|
|
482
482
|
|
|
483
|
-
const click = new MouseEvent(
|
|
484
|
-
Object.defineProperty(click,
|
|
483
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
484
|
+
Object.defineProperty(click, 'target', { value: innerEl })
|
|
485
485
|
window.dispatchEvent(click)
|
|
486
486
|
|
|
487
487
|
expect(o.active()).toBe(true)
|
|
@@ -492,24 +492,24 @@ describe("useOverlay", () => {
|
|
|
492
492
|
// =========================================================================
|
|
493
493
|
// 9. ESC handling
|
|
494
494
|
// =========================================================================
|
|
495
|
-
describe(
|
|
496
|
-
it(
|
|
495
|
+
describe('ESC handling', () => {
|
|
496
|
+
it('closeOnEsc=true: Escape key when active closes overlay', () => {
|
|
497
497
|
const o = useOverlay({ closeOnEsc: true, isOpen: true })
|
|
498
498
|
const cleanup = o.setupListeners()
|
|
499
499
|
|
|
500
|
-
const esc = new KeyboardEvent(
|
|
500
|
+
const esc = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })
|
|
501
501
|
window.dispatchEvent(esc)
|
|
502
502
|
|
|
503
503
|
expect(o.active()).toBe(false)
|
|
504
504
|
cleanup()
|
|
505
505
|
})
|
|
506
506
|
|
|
507
|
-
it(
|
|
507
|
+
it('closeOnEsc=true: Escape key when inactive does nothing', () => {
|
|
508
508
|
const onClose = vi.fn()
|
|
509
509
|
const o = useOverlay({ closeOnEsc: true, onClose })
|
|
510
510
|
const cleanup = o.setupListeners()
|
|
511
511
|
|
|
512
|
-
const esc = new KeyboardEvent(
|
|
512
|
+
const esc = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })
|
|
513
513
|
window.dispatchEvent(esc)
|
|
514
514
|
|
|
515
515
|
expect(o.active()).toBe(false)
|
|
@@ -517,34 +517,34 @@ describe("useOverlay", () => {
|
|
|
517
517
|
cleanup()
|
|
518
518
|
})
|
|
519
519
|
|
|
520
|
-
it(
|
|
520
|
+
it('closeOnEsc=false: Escape key does not close overlay', () => {
|
|
521
521
|
const o = useOverlay({ closeOnEsc: false, isOpen: true })
|
|
522
522
|
const cleanup = o.setupListeners()
|
|
523
523
|
|
|
524
|
-
const esc = new KeyboardEvent(
|
|
524
|
+
const esc = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })
|
|
525
525
|
window.dispatchEvent(esc)
|
|
526
526
|
|
|
527
527
|
expect(o.active()).toBe(true)
|
|
528
528
|
cleanup()
|
|
529
529
|
})
|
|
530
530
|
|
|
531
|
-
it(
|
|
531
|
+
it('closeOnEsc: Escape does not close when blocked', () => {
|
|
532
532
|
const o = useOverlay({ closeOnEsc: true, isOpen: true })
|
|
533
533
|
const cleanup = o.setupListeners()
|
|
534
534
|
o.setBlocked()
|
|
535
535
|
|
|
536
|
-
const esc = new KeyboardEvent(
|
|
536
|
+
const esc = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })
|
|
537
537
|
window.dispatchEvent(esc)
|
|
538
538
|
|
|
539
539
|
expect(o.active()).toBe(true)
|
|
540
540
|
cleanup()
|
|
541
541
|
})
|
|
542
542
|
|
|
543
|
-
it(
|
|
543
|
+
it('non-Escape key does not close overlay', () => {
|
|
544
544
|
const o = useOverlay({ closeOnEsc: true, isOpen: true })
|
|
545
545
|
const cleanup = o.setupListeners()
|
|
546
546
|
|
|
547
|
-
const enter = new KeyboardEvent(
|
|
547
|
+
const enter = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })
|
|
548
548
|
window.dispatchEvent(enter)
|
|
549
549
|
|
|
550
550
|
expect(o.active()).toBe(true)
|
|
@@ -555,31 +555,31 @@ describe("useOverlay", () => {
|
|
|
555
555
|
// =========================================================================
|
|
556
556
|
// 10. Hover handling
|
|
557
557
|
// =========================================================================
|
|
558
|
-
describe(
|
|
559
|
-
it(
|
|
560
|
-
const o = useOverlay({ openOn:
|
|
558
|
+
describe('hover handling', () => {
|
|
559
|
+
it('openOn=hover: mouseenter on trigger opens overlay', () => {
|
|
560
|
+
const o = useOverlay({ openOn: 'hover', closeOn: 'hover' })
|
|
561
561
|
const triggerEl = mockElement()
|
|
562
562
|
o.triggerRef(triggerEl)
|
|
563
563
|
const cleanup = o.setupListeners()
|
|
564
564
|
|
|
565
|
-
triggerEl.dispatchEvent(new MouseEvent(
|
|
565
|
+
triggerEl.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
566
566
|
|
|
567
567
|
expect(o.active()).toBe(true)
|
|
568
568
|
cleanup()
|
|
569
569
|
})
|
|
570
570
|
|
|
571
|
-
it(
|
|
572
|
-
const o = useOverlay({ openOn:
|
|
571
|
+
it('closeOn=hover: mouseleave from trigger schedules hide with hoverDelay', () => {
|
|
572
|
+
const o = useOverlay({ openOn: 'hover', closeOn: 'hover', hoverDelay: 100 })
|
|
573
573
|
const triggerEl = mockElement()
|
|
574
574
|
o.triggerRef(triggerEl)
|
|
575
575
|
const cleanup = o.setupListeners()
|
|
576
576
|
|
|
577
577
|
// Open first
|
|
578
|
-
triggerEl.dispatchEvent(new MouseEvent(
|
|
578
|
+
triggerEl.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
579
579
|
expect(o.active()).toBe(true)
|
|
580
580
|
|
|
581
581
|
// Leave trigger
|
|
582
|
-
triggerEl.dispatchEvent(new MouseEvent(
|
|
582
|
+
triggerEl.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
583
583
|
// Should still be active (delay not elapsed)
|
|
584
584
|
expect(o.active()).toBe(true)
|
|
585
585
|
|
|
@@ -589,8 +589,8 @@ describe("useOverlay", () => {
|
|
|
589
589
|
cleanup()
|
|
590
590
|
})
|
|
591
591
|
|
|
592
|
-
it(
|
|
593
|
-
const o = useOverlay({ openOn:
|
|
592
|
+
it('mouseenter on content cancels hide timeout', () => {
|
|
593
|
+
const o = useOverlay({ openOn: 'hover', closeOn: 'hover', hoverDelay: 100 })
|
|
594
594
|
const triggerEl = mockElement()
|
|
595
595
|
const contentEl = mockElement()
|
|
596
596
|
o.triggerRef(triggerEl)
|
|
@@ -598,14 +598,14 @@ describe("useOverlay", () => {
|
|
|
598
598
|
const cleanup = o.setupListeners()
|
|
599
599
|
|
|
600
600
|
// Open
|
|
601
|
-
triggerEl.dispatchEvent(new MouseEvent(
|
|
601
|
+
triggerEl.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
602
602
|
expect(o.active()).toBe(true)
|
|
603
603
|
|
|
604
604
|
// Leave trigger (starts hide timer)
|
|
605
|
-
triggerEl.dispatchEvent(new MouseEvent(
|
|
605
|
+
triggerEl.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
606
606
|
|
|
607
607
|
// Enter content (cancels hide timer)
|
|
608
|
-
contentEl.dispatchEvent(new MouseEvent(
|
|
608
|
+
contentEl.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
609
609
|
|
|
610
610
|
// Advance past delay
|
|
611
611
|
vi.advanceTimersByTime(200)
|
|
@@ -613,8 +613,8 @@ describe("useOverlay", () => {
|
|
|
613
613
|
cleanup()
|
|
614
614
|
})
|
|
615
615
|
|
|
616
|
-
it(
|
|
617
|
-
const o = useOverlay({ openOn:
|
|
616
|
+
it('mouseleave from content schedules hide', () => {
|
|
617
|
+
const o = useOverlay({ openOn: 'hover', closeOn: 'hover', hoverDelay: 50 })
|
|
618
618
|
const triggerEl = mockElement()
|
|
619
619
|
const contentEl = mockElement()
|
|
620
620
|
o.triggerRef(triggerEl)
|
|
@@ -622,41 +622,41 @@ describe("useOverlay", () => {
|
|
|
622
622
|
const cleanup = o.setupListeners()
|
|
623
623
|
|
|
624
624
|
// Open
|
|
625
|
-
triggerEl.dispatchEvent(new MouseEvent(
|
|
625
|
+
triggerEl.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
626
626
|
expect(o.active()).toBe(true)
|
|
627
627
|
|
|
628
628
|
// Leave content
|
|
629
|
-
contentEl.dispatchEvent(new MouseEvent(
|
|
629
|
+
contentEl.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
630
630
|
vi.advanceTimersByTime(50)
|
|
631
631
|
expect(o.active()).toBe(false)
|
|
632
632
|
cleanup()
|
|
633
633
|
})
|
|
634
634
|
|
|
635
|
-
it(
|
|
636
|
-
const o = useOverlay({ openOn:
|
|
635
|
+
it('hover: scroll event closes overlay when closeOn=hover and active', () => {
|
|
636
|
+
const o = useOverlay({ openOn: 'hover', closeOn: 'hover' })
|
|
637
637
|
const triggerEl = mockElement()
|
|
638
638
|
o.triggerRef(triggerEl)
|
|
639
639
|
const cleanup = o.setupListeners()
|
|
640
640
|
|
|
641
641
|
// Open
|
|
642
|
-
triggerEl.dispatchEvent(new MouseEvent(
|
|
642
|
+
triggerEl.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
643
643
|
expect(o.active()).toBe(true)
|
|
644
644
|
|
|
645
645
|
// Scroll (should trigger processVisibilityEvent with closeOn=hover + scroll)
|
|
646
|
-
window.dispatchEvent(new Event(
|
|
646
|
+
window.dispatchEvent(new Event('scroll'))
|
|
647
647
|
expect(o.active()).toBe(false)
|
|
648
648
|
cleanup()
|
|
649
649
|
})
|
|
650
650
|
|
|
651
|
-
it(
|
|
651
|
+
it('openOn=hover: mouseenter when already active does not call onOpen again', () => {
|
|
652
652
|
const onOpen = vi.fn()
|
|
653
|
-
const o = useOverlay({ openOn:
|
|
653
|
+
const o = useOverlay({ openOn: 'hover', closeOn: 'hover', onOpen })
|
|
654
654
|
const triggerEl = mockElement()
|
|
655
655
|
o.triggerRef(triggerEl)
|
|
656
656
|
const cleanup = o.setupListeners()
|
|
657
657
|
|
|
658
|
-
triggerEl.dispatchEvent(new MouseEvent(
|
|
659
|
-
triggerEl.dispatchEvent(new MouseEvent(
|
|
658
|
+
triggerEl.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
659
|
+
triggerEl.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
660
660
|
|
|
661
661
|
expect(onOpen).toHaveBeenCalledOnce()
|
|
662
662
|
cleanup()
|
|
@@ -666,12 +666,12 @@ describe("useOverlay", () => {
|
|
|
666
666
|
// =========================================================================
|
|
667
667
|
// 11. Position calculation (dropdown)
|
|
668
668
|
// =========================================================================
|
|
669
|
-
describe(
|
|
669
|
+
describe('position calculation - dropdown', () => {
|
|
670
670
|
const setupDropdown = (opts: Parameters<typeof useOverlay>[0] = {}) => {
|
|
671
671
|
const o = useOverlay({
|
|
672
|
-
type:
|
|
673
|
-
align:
|
|
674
|
-
alignX:
|
|
672
|
+
type: 'dropdown',
|
|
673
|
+
align: 'bottom',
|
|
674
|
+
alignX: 'left',
|
|
675
675
|
isOpen: true,
|
|
676
676
|
...opts,
|
|
677
677
|
})
|
|
@@ -699,8 +699,8 @@ describe("useOverlay", () => {
|
|
|
699
699
|
return { o, triggerEl, contentEl }
|
|
700
700
|
}
|
|
701
701
|
|
|
702
|
-
it(
|
|
703
|
-
const { o, contentEl } = setupDropdown({ align:
|
|
702
|
+
it('bottom align: positions content below trigger', () => {
|
|
703
|
+
const { o, contentEl } = setupDropdown({ align: 'bottom', alignX: 'left' })
|
|
704
704
|
|
|
705
705
|
// Trigger setContentPosition by calling showContent (which sets active)
|
|
706
706
|
// active is already true and contentRef is set, but isContentLoaded is separate
|
|
@@ -709,31 +709,31 @@ describe("useOverlay", () => {
|
|
|
709
709
|
// After contentRef callback, isContentLoaded is true. We need to trigger position calc.
|
|
710
710
|
// The hook doesn't auto-trigger - it exposes setupListeners. Let's simulate resize.
|
|
711
711
|
const cleanup = o.setupListeners()
|
|
712
|
-
window.dispatchEvent(new Event(
|
|
712
|
+
window.dispatchEvent(new Event('resize'))
|
|
713
713
|
|
|
714
714
|
// Content should be positioned: top = trigger.bottom + offsetY = 130
|
|
715
|
-
expect(contentEl.style.top).toBe(
|
|
715
|
+
expect(contentEl.style.top).toBe('130px')
|
|
716
716
|
// left = trigger.left + offsetX = 50
|
|
717
|
-
expect(contentEl.style.left).toBe(
|
|
717
|
+
expect(contentEl.style.left).toBe('50px')
|
|
718
718
|
cleanup()
|
|
719
719
|
})
|
|
720
720
|
|
|
721
|
-
it(
|
|
722
|
-
const { o, contentEl } = setupDropdown({ align:
|
|
721
|
+
it('top align: positions content above trigger', () => {
|
|
722
|
+
const { o, contentEl } = setupDropdown({ align: 'top', alignX: 'left' })
|
|
723
723
|
const cleanup = o.setupListeners()
|
|
724
|
-
window.dispatchEvent(new Event(
|
|
724
|
+
window.dispatchEvent(new Event('resize'))
|
|
725
725
|
|
|
726
726
|
// top = trigger.top - offsetY - content.height = 100 - 0 - 200 = -100
|
|
727
727
|
// Doesn't fit top (-100 < 0), so falls back to bottom: trigger.bottom + offsetY = 130
|
|
728
|
-
expect(contentEl.style.top).toBe(
|
|
728
|
+
expect(contentEl.style.top).toBe('130px')
|
|
729
729
|
cleanup()
|
|
730
730
|
})
|
|
731
731
|
|
|
732
|
-
it(
|
|
732
|
+
it('top align with room above: positions content above trigger', () => {
|
|
733
733
|
const o = useOverlay({
|
|
734
|
-
type:
|
|
735
|
-
align:
|
|
736
|
-
alignX:
|
|
734
|
+
type: 'dropdown',
|
|
735
|
+
align: 'top',
|
|
736
|
+
alignX: 'left',
|
|
737
737
|
isOpen: true,
|
|
738
738
|
})
|
|
739
739
|
const triggerEl = mockElement({
|
|
@@ -755,41 +755,41 @@ describe("useOverlay", () => {
|
|
|
755
755
|
o.triggerRef(triggerEl)
|
|
756
756
|
o.contentRef(contentEl)
|
|
757
757
|
const cleanup = o.setupListeners()
|
|
758
|
-
window.dispatchEvent(new Event(
|
|
758
|
+
window.dispatchEvent(new Event('resize'))
|
|
759
759
|
|
|
760
760
|
// top = trigger.top - offsetY - content.height = 400 - 0 - 100 = 300
|
|
761
|
-
expect(contentEl.style.top).toBe(
|
|
761
|
+
expect(contentEl.style.top).toBe('300px')
|
|
762
762
|
cleanup()
|
|
763
763
|
})
|
|
764
764
|
|
|
765
|
-
it(
|
|
766
|
-
const { o, contentEl } = setupDropdown({ align:
|
|
765
|
+
it('alignX=right: positions content aligned to right edge', () => {
|
|
766
|
+
const { o, contentEl } = setupDropdown({ align: 'bottom', alignX: 'right' })
|
|
767
767
|
const cleanup = o.setupListeners()
|
|
768
|
-
window.dispatchEvent(new Event(
|
|
768
|
+
window.dispatchEvent(new Event('resize'))
|
|
769
769
|
|
|
770
770
|
// right pos = trigger.right - offsetX - content.width = 150 - 0 - 200 = -50
|
|
771
771
|
// fitsRight = -50 >= 0 → false, falls back to leftPos = trigger.left + offsetX = 50
|
|
772
|
-
expect(contentEl.style.left).toBe(
|
|
772
|
+
expect(contentEl.style.left).toBe('50px')
|
|
773
773
|
cleanup()
|
|
774
774
|
})
|
|
775
775
|
|
|
776
|
-
it(
|
|
777
|
-
const { o, contentEl } = setupDropdown({ align:
|
|
776
|
+
it('alignX=center: centers content horizontally under trigger', () => {
|
|
777
|
+
const { o, contentEl } = setupDropdown({ align: 'bottom', alignX: 'center' })
|
|
778
778
|
const cleanup = o.setupListeners()
|
|
779
|
-
window.dispatchEvent(new Event(
|
|
779
|
+
window.dispatchEvent(new Event('resize'))
|
|
780
780
|
|
|
781
781
|
// center = trigger.left + (trigger.right - trigger.left) / 2 - content.width / 2
|
|
782
782
|
// = 50 + (150 - 50) / 2 - 200 / 2 = 50 + 50 - 100 = 0
|
|
783
783
|
// fitsCL = 0 >= 0 → true, fitsCR = 0 + 200 <= 1024 → true
|
|
784
|
-
expect(contentEl.style.left).toBe(
|
|
784
|
+
expect(contentEl.style.left).toBe('0px')
|
|
785
785
|
cleanup()
|
|
786
786
|
})
|
|
787
787
|
|
|
788
|
-
it(
|
|
788
|
+
it('with offsets: applies offsetX and offsetY', () => {
|
|
789
789
|
const o = useOverlay({
|
|
790
|
-
type:
|
|
791
|
-
align:
|
|
792
|
-
alignX:
|
|
790
|
+
type: 'dropdown',
|
|
791
|
+
align: 'bottom',
|
|
792
|
+
alignX: 'left',
|
|
793
793
|
offsetX: 10,
|
|
794
794
|
offsetY: 5,
|
|
795
795
|
isOpen: true,
|
|
@@ -813,12 +813,12 @@ describe("useOverlay", () => {
|
|
|
813
813
|
o.triggerRef(triggerEl)
|
|
814
814
|
o.contentRef(contentEl)
|
|
815
815
|
const cleanup = o.setupListeners()
|
|
816
|
-
window.dispatchEvent(new Event(
|
|
816
|
+
window.dispatchEvent(new Event('resize'))
|
|
817
817
|
|
|
818
818
|
// top = trigger.bottom + offsetY = 130 + 5 = 135
|
|
819
|
-
expect(contentEl.style.top).toBe(
|
|
819
|
+
expect(contentEl.style.top).toBe('135px')
|
|
820
820
|
// left = trigger.left + offsetX = 50 + 10 = 60
|
|
821
|
-
expect(contentEl.style.left).toBe(
|
|
821
|
+
expect(contentEl.style.left).toBe('60px')
|
|
822
822
|
cleanup()
|
|
823
823
|
})
|
|
824
824
|
})
|
|
@@ -826,12 +826,12 @@ describe("useOverlay", () => {
|
|
|
826
826
|
// =========================================================================
|
|
827
827
|
// 11b. Position calculation - horizontal dropdown
|
|
828
828
|
// =========================================================================
|
|
829
|
-
describe(
|
|
830
|
-
it(
|
|
829
|
+
describe('position calculation - horizontal dropdown', () => {
|
|
830
|
+
it('align=right: positions content to the right of trigger', () => {
|
|
831
831
|
const o = useOverlay({
|
|
832
|
-
type:
|
|
833
|
-
align:
|
|
834
|
-
alignY:
|
|
832
|
+
type: 'dropdown',
|
|
833
|
+
align: 'right',
|
|
834
|
+
alignY: 'top',
|
|
835
835
|
isOpen: true,
|
|
836
836
|
})
|
|
837
837
|
const triggerEl = mockElement({
|
|
@@ -853,22 +853,22 @@ describe("useOverlay", () => {
|
|
|
853
853
|
o.triggerRef(triggerEl)
|
|
854
854
|
o.contentRef(contentEl)
|
|
855
855
|
const cleanup = o.setupListeners()
|
|
856
|
-
window.dispatchEvent(new Event(
|
|
856
|
+
window.dispatchEvent(new Event('resize'))
|
|
857
857
|
|
|
858
858
|
// rightPos = trigger.right + offsetX = 150 + 0 = 150
|
|
859
859
|
// fitsRight = 150 + 100 <= 1024 → true
|
|
860
|
-
expect(contentEl.style.left).toBe(
|
|
860
|
+
expect(contentEl.style.left).toBe('150px')
|
|
861
861
|
// topPos = trigger.top + offsetY = 100
|
|
862
862
|
// fitsTop = 100 + 100 <= 768 → true
|
|
863
|
-
expect(contentEl.style.top).toBe(
|
|
863
|
+
expect(contentEl.style.top).toBe('100px')
|
|
864
864
|
cleanup()
|
|
865
865
|
})
|
|
866
866
|
|
|
867
|
-
it(
|
|
867
|
+
it('align=left: positions content to the left of trigger', () => {
|
|
868
868
|
const o = useOverlay({
|
|
869
|
-
type:
|
|
870
|
-
align:
|
|
871
|
-
alignY:
|
|
869
|
+
type: 'dropdown',
|
|
870
|
+
align: 'left',
|
|
871
|
+
alignY: 'top',
|
|
872
872
|
isOpen: true,
|
|
873
873
|
})
|
|
874
874
|
const triggerEl = mockElement({
|
|
@@ -890,19 +890,19 @@ describe("useOverlay", () => {
|
|
|
890
890
|
o.triggerRef(triggerEl)
|
|
891
891
|
o.contentRef(contentEl)
|
|
892
892
|
const cleanup = o.setupListeners()
|
|
893
|
-
window.dispatchEvent(new Event(
|
|
893
|
+
window.dispatchEvent(new Event('resize'))
|
|
894
894
|
|
|
895
895
|
// leftPos = trigger.left - offsetX - content.width = 300 - 0 - 100 = 200
|
|
896
896
|
// fitsLeft = 200 >= 0 → true
|
|
897
|
-
expect(contentEl.style.left).toBe(
|
|
897
|
+
expect(contentEl.style.left).toBe('200px')
|
|
898
898
|
cleanup()
|
|
899
899
|
})
|
|
900
900
|
|
|
901
|
-
it(
|
|
901
|
+
it('align=right, alignY=center: vertically centers content', () => {
|
|
902
902
|
const o = useOverlay({
|
|
903
|
-
type:
|
|
904
|
-
align:
|
|
905
|
-
alignY:
|
|
903
|
+
type: 'dropdown',
|
|
904
|
+
align: 'right',
|
|
905
|
+
alignY: 'center',
|
|
906
906
|
isOpen: true,
|
|
907
907
|
})
|
|
908
908
|
const triggerEl = mockElement({
|
|
@@ -924,19 +924,19 @@ describe("useOverlay", () => {
|
|
|
924
924
|
o.triggerRef(triggerEl)
|
|
925
925
|
o.contentRef(contentEl)
|
|
926
926
|
const cleanup = o.setupListeners()
|
|
927
|
-
window.dispatchEvent(new Event(
|
|
927
|
+
window.dispatchEvent(new Event('resize'))
|
|
928
928
|
|
|
929
929
|
// center = trigger.top + (trigger.bottom - trigger.top) / 2 - content.height / 2
|
|
930
930
|
// = 300 + (330 - 300) / 2 - 100 / 2 = 300 + 15 - 50 = 265
|
|
931
|
-
expect(contentEl.style.top).toBe(
|
|
931
|
+
expect(contentEl.style.top).toBe('265px')
|
|
932
932
|
cleanup()
|
|
933
933
|
})
|
|
934
934
|
|
|
935
|
-
it(
|
|
935
|
+
it('align=right, alignY=bottom: positions from bottom', () => {
|
|
936
936
|
const o = useOverlay({
|
|
937
|
-
type:
|
|
938
|
-
align:
|
|
939
|
-
alignY:
|
|
937
|
+
type: 'dropdown',
|
|
938
|
+
align: 'right',
|
|
939
|
+
alignY: 'bottom',
|
|
940
940
|
isOpen: true,
|
|
941
941
|
})
|
|
942
942
|
const triggerEl = mockElement({
|
|
@@ -958,11 +958,11 @@ describe("useOverlay", () => {
|
|
|
958
958
|
o.triggerRef(triggerEl)
|
|
959
959
|
o.contentRef(contentEl)
|
|
960
960
|
const cleanup = o.setupListeners()
|
|
961
|
-
window.dispatchEvent(new Event(
|
|
961
|
+
window.dispatchEvent(new Event('resize'))
|
|
962
962
|
|
|
963
963
|
// bottomPos = trigger.bottom - offsetY - content.height = 330 - 0 - 100 = 230
|
|
964
964
|
// fitsBottom = 230 >= 0 → true
|
|
965
|
-
expect(contentEl.style.top).toBe(
|
|
965
|
+
expect(contentEl.style.top).toBe('230px')
|
|
966
966
|
cleanup()
|
|
967
967
|
})
|
|
968
968
|
})
|
|
@@ -970,12 +970,12 @@ describe("useOverlay", () => {
|
|
|
970
970
|
// =========================================================================
|
|
971
971
|
// 12. Modal positioning
|
|
972
972
|
// =========================================================================
|
|
973
|
-
describe(
|
|
974
|
-
it(
|
|
973
|
+
describe('position calculation - modal', () => {
|
|
974
|
+
it('modal type: centers content by default', () => {
|
|
975
975
|
const o = useOverlay({
|
|
976
|
-
type:
|
|
977
|
-
alignX:
|
|
978
|
-
alignY:
|
|
976
|
+
type: 'modal',
|
|
977
|
+
alignX: 'center',
|
|
978
|
+
alignY: 'center',
|
|
979
979
|
isOpen: true,
|
|
980
980
|
})
|
|
981
981
|
const contentEl = mockElement({
|
|
@@ -988,20 +988,20 @@ describe("useOverlay", () => {
|
|
|
988
988
|
})
|
|
989
989
|
o.contentRef(contentEl)
|
|
990
990
|
const cleanup = o.setupListeners()
|
|
991
|
-
window.dispatchEvent(new Event(
|
|
991
|
+
window.dispatchEvent(new Event('resize'))
|
|
992
992
|
|
|
993
993
|
// left = innerWidth / 2 - width / 2 = 1024 / 2 - 300 / 2 = 362
|
|
994
|
-
expect(contentEl.style.left).toBe(
|
|
994
|
+
expect(contentEl.style.left).toBe('362px')
|
|
995
995
|
// top = innerHeight / 2 - height / 2 = 768 / 2 - 200 / 2 = 284
|
|
996
|
-
expect(contentEl.style.top).toBe(
|
|
996
|
+
expect(contentEl.style.top).toBe('284px')
|
|
997
997
|
cleanup()
|
|
998
998
|
})
|
|
999
999
|
|
|
1000
|
-
it(
|
|
1000
|
+
it('modal type: alignX=left positions left edge', () => {
|
|
1001
1001
|
const o = useOverlay({
|
|
1002
|
-
type:
|
|
1003
|
-
alignX:
|
|
1004
|
-
alignY:
|
|
1002
|
+
type: 'modal',
|
|
1003
|
+
alignX: 'left',
|
|
1004
|
+
alignY: 'top',
|
|
1005
1005
|
offsetX: 20,
|
|
1006
1006
|
offsetY: 10,
|
|
1007
1007
|
isOpen: true,
|
|
@@ -1009,18 +1009,18 @@ describe("useOverlay", () => {
|
|
|
1009
1009
|
const contentEl = mockElement({ width: 300, height: 200 })
|
|
1010
1010
|
o.contentRef(contentEl)
|
|
1011
1011
|
const cleanup = o.setupListeners()
|
|
1012
|
-
window.dispatchEvent(new Event(
|
|
1012
|
+
window.dispatchEvent(new Event('resize'))
|
|
1013
1013
|
|
|
1014
|
-
expect(contentEl.style.left).toBe(
|
|
1015
|
-
expect(contentEl.style.top).toBe(
|
|
1014
|
+
expect(contentEl.style.left).toBe('20px')
|
|
1015
|
+
expect(contentEl.style.top).toBe('10px')
|
|
1016
1016
|
cleanup()
|
|
1017
1017
|
})
|
|
1018
1018
|
|
|
1019
|
-
it(
|
|
1019
|
+
it('modal type: alignX=right positions right edge', () => {
|
|
1020
1020
|
const o = useOverlay({
|
|
1021
|
-
type:
|
|
1022
|
-
alignX:
|
|
1023
|
-
alignY:
|
|
1021
|
+
type: 'modal',
|
|
1022
|
+
alignX: 'right',
|
|
1023
|
+
alignY: 'bottom',
|
|
1024
1024
|
offsetX: 15,
|
|
1025
1025
|
offsetY: 25,
|
|
1026
1026
|
isOpen: true,
|
|
@@ -1028,42 +1028,42 @@ describe("useOverlay", () => {
|
|
|
1028
1028
|
const contentEl = mockElement({ width: 300, height: 200 })
|
|
1029
1029
|
o.contentRef(contentEl)
|
|
1030
1030
|
const cleanup = o.setupListeners()
|
|
1031
|
-
window.dispatchEvent(new Event(
|
|
1031
|
+
window.dispatchEvent(new Event('resize'))
|
|
1032
1032
|
|
|
1033
|
-
expect(contentEl.style.right).toBe(
|
|
1034
|
-
expect(contentEl.style.bottom).toBe(
|
|
1033
|
+
expect(contentEl.style.right).toBe('15px')
|
|
1034
|
+
expect(contentEl.style.bottom).toBe('25px')
|
|
1035
1035
|
cleanup()
|
|
1036
1036
|
})
|
|
1037
1037
|
|
|
1038
|
-
it(
|
|
1039
|
-
const o = useOverlay({ type:
|
|
1038
|
+
it('modal type: sets document.body overflow to hidden', () => {
|
|
1039
|
+
const o = useOverlay({ type: 'modal', isOpen: true })
|
|
1040
1040
|
const contentEl = mockElement({ width: 300, height: 200 })
|
|
1041
1041
|
o.contentRef(contentEl)
|
|
1042
1042
|
const cleanup = o.setupListeners()
|
|
1043
1043
|
|
|
1044
|
-
expect(document.body.style.overflow).toBe(
|
|
1044
|
+
expect(document.body.style.overflow).toBe('hidden')
|
|
1045
1045
|
cleanup()
|
|
1046
|
-
expect(document.body.style.overflow).toBe(
|
|
1046
|
+
expect(document.body.style.overflow).toBe('')
|
|
1047
1047
|
})
|
|
1048
1048
|
})
|
|
1049
1049
|
|
|
1050
1050
|
// =========================================================================
|
|
1051
1051
|
// 13. Position - custom type
|
|
1052
1052
|
// =========================================================================
|
|
1053
|
-
describe(
|
|
1054
|
-
it(
|
|
1053
|
+
describe('position calculation - custom type', () => {
|
|
1054
|
+
it('custom type: does not set position styles', () => {
|
|
1055
1055
|
const o = useOverlay({
|
|
1056
|
-
type:
|
|
1056
|
+
type: 'custom',
|
|
1057
1057
|
isOpen: true,
|
|
1058
1058
|
})
|
|
1059
1059
|
const contentEl = mockElement({ width: 100, height: 100 })
|
|
1060
1060
|
o.contentRef(contentEl)
|
|
1061
1061
|
const cleanup = o.setupListeners()
|
|
1062
|
-
window.dispatchEvent(new Event(
|
|
1062
|
+
window.dispatchEvent(new Event('resize'))
|
|
1063
1063
|
|
|
1064
1064
|
// computePosition returns {} for custom type
|
|
1065
|
-
expect(contentEl.style.top).toBe(
|
|
1066
|
-
expect(contentEl.style.left).toBe(
|
|
1065
|
+
expect(contentEl.style.top).toBe('')
|
|
1066
|
+
expect(contentEl.style.left).toBe('')
|
|
1067
1067
|
cleanup()
|
|
1068
1068
|
})
|
|
1069
1069
|
})
|
|
@@ -1071,13 +1071,13 @@ describe("useOverlay", () => {
|
|
|
1071
1071
|
// =========================================================================
|
|
1072
1072
|
// 14. Alignment signal updates after positioning
|
|
1073
1073
|
// =========================================================================
|
|
1074
|
-
describe(
|
|
1075
|
-
it(
|
|
1074
|
+
describe('alignment signal updates', () => {
|
|
1075
|
+
it('updates alignX signal when position flips horizontally', () => {
|
|
1076
1076
|
setViewport(200, 768)
|
|
1077
1077
|
const o = useOverlay({
|
|
1078
|
-
type:
|
|
1079
|
-
align:
|
|
1080
|
-
alignX:
|
|
1078
|
+
type: 'dropdown',
|
|
1079
|
+
align: 'bottom',
|
|
1080
|
+
alignX: 'left',
|
|
1081
1081
|
isOpen: true,
|
|
1082
1082
|
})
|
|
1083
1083
|
const triggerEl = mockElement({
|
|
@@ -1096,22 +1096,22 @@ describe("useOverlay", () => {
|
|
|
1096
1096
|
o.triggerRef(triggerEl)
|
|
1097
1097
|
o.contentRef(contentEl)
|
|
1098
1098
|
const cleanup = o.setupListeners()
|
|
1099
|
-
window.dispatchEvent(new Event(
|
|
1099
|
+
window.dispatchEvent(new Event('resize'))
|
|
1100
1100
|
|
|
1101
1101
|
// leftPos = 50 + 0 = 50, fitsLeft = 50 + 200 <= 200 → false
|
|
1102
1102
|
// rightPos = 150 - 0 - 200 = -50, fitsRight = -50 >= 0 → false
|
|
1103
1103
|
// Falls back: sel(fitsLeft, leftPos, rightPos) → rightPos = -50
|
|
1104
1104
|
// resolvedAlignX = sel(fitsLeft, "left", "right") → "right"
|
|
1105
|
-
expect(o.alignX()).toBe(
|
|
1105
|
+
expect(o.alignX()).toBe('right')
|
|
1106
1106
|
cleanup()
|
|
1107
1107
|
})
|
|
1108
1108
|
|
|
1109
|
-
it(
|
|
1109
|
+
it('updates alignY signal when vertical position flips to top', () => {
|
|
1110
1110
|
setViewport(1024, 200)
|
|
1111
1111
|
const o = useOverlay({
|
|
1112
|
-
type:
|
|
1113
|
-
align:
|
|
1114
|
-
alignX:
|
|
1112
|
+
type: 'dropdown',
|
|
1113
|
+
align: 'bottom',
|
|
1114
|
+
alignX: 'left',
|
|
1115
1115
|
isOpen: true,
|
|
1116
1116
|
})
|
|
1117
1117
|
const triggerEl = mockElement({
|
|
@@ -1129,12 +1129,12 @@ describe("useOverlay", () => {
|
|
|
1129
1129
|
o.triggerRef(triggerEl)
|
|
1130
1130
|
o.contentRef(contentEl)
|
|
1131
1131
|
const cleanup = o.setupListeners()
|
|
1132
|
-
window.dispatchEvent(new Event(
|
|
1132
|
+
window.dispatchEvent(new Event('resize'))
|
|
1133
1133
|
|
|
1134
1134
|
// bottomPos = 130, fitsBottom = 130 + 100 <= 200 → false
|
|
1135
1135
|
// useTop = sel(align === "top", fitsTop, !fitsBottom) = sel(false, _, !false) = true
|
|
1136
1136
|
// resolvedAlignY = "top"
|
|
1137
|
-
expect(o.alignY()).toBe(
|
|
1137
|
+
expect(o.alignY()).toBe('top')
|
|
1138
1138
|
cleanup()
|
|
1139
1139
|
})
|
|
1140
1140
|
})
|
|
@@ -1142,12 +1142,12 @@ describe("useOverlay", () => {
|
|
|
1142
1142
|
// =========================================================================
|
|
1143
1143
|
// 15. Resize reposition
|
|
1144
1144
|
// =========================================================================
|
|
1145
|
-
describe(
|
|
1146
|
-
it(
|
|
1145
|
+
describe('resize handling', () => {
|
|
1146
|
+
it('recalculates position on window resize', () => {
|
|
1147
1147
|
const o = useOverlay({
|
|
1148
|
-
type:
|
|
1149
|
-
align:
|
|
1150
|
-
alignX:
|
|
1148
|
+
type: 'dropdown',
|
|
1149
|
+
align: 'bottom',
|
|
1150
|
+
alignX: 'left',
|
|
1151
1151
|
isOpen: true,
|
|
1152
1152
|
})
|
|
1153
1153
|
const triggerEl = mockElement({
|
|
@@ -1167,8 +1167,8 @@ describe("useOverlay", () => {
|
|
|
1167
1167
|
const cleanup = o.setupListeners()
|
|
1168
1168
|
|
|
1169
1169
|
// First resize
|
|
1170
|
-
window.dispatchEvent(new Event(
|
|
1171
|
-
expect(contentEl.style.top).toBe(
|
|
1170
|
+
window.dispatchEvent(new Event('resize'))
|
|
1171
|
+
expect(contentEl.style.top).toBe('130px')
|
|
1172
1172
|
|
|
1173
1173
|
// Change trigger position and resize again
|
|
1174
1174
|
triggerEl.getBoundingClientRect = () => ({
|
|
@@ -1182,8 +1182,8 @@ describe("useOverlay", () => {
|
|
|
1182
1182
|
y: 200,
|
|
1183
1183
|
toJSON: () => {},
|
|
1184
1184
|
})
|
|
1185
|
-
window.dispatchEvent(new Event(
|
|
1186
|
-
expect(contentEl.style.top).toBe(
|
|
1185
|
+
window.dispatchEvent(new Event('resize'))
|
|
1186
|
+
expect(contentEl.style.top).toBe('230px')
|
|
1187
1187
|
cleanup()
|
|
1188
1188
|
})
|
|
1189
1189
|
})
|
|
@@ -1191,23 +1191,23 @@ describe("useOverlay", () => {
|
|
|
1191
1191
|
// =========================================================================
|
|
1192
1192
|
// 16. Parent container
|
|
1193
1193
|
// =========================================================================
|
|
1194
|
-
describe(
|
|
1195
|
-
it(
|
|
1196
|
-
const parent = document.createElement(
|
|
1197
|
-
const o = useOverlay({ parentContainer: parent, closeOn:
|
|
1194
|
+
describe('parentContainer', () => {
|
|
1195
|
+
it('sets overflow hidden on parent when closeOn is not hover', () => {
|
|
1196
|
+
const parent = document.createElement('div')
|
|
1197
|
+
const o = useOverlay({ parentContainer: parent, closeOn: 'click' })
|
|
1198
1198
|
const cleanup = o.setupListeners()
|
|
1199
1199
|
|
|
1200
|
-
expect(parent.style.overflow).toBe(
|
|
1200
|
+
expect(parent.style.overflow).toBe('hidden')
|
|
1201
1201
|
cleanup()
|
|
1202
|
-
expect(parent.style.overflow).toBe(
|
|
1202
|
+
expect(parent.style.overflow).toBe('')
|
|
1203
1203
|
})
|
|
1204
1204
|
|
|
1205
|
-
it(
|
|
1206
|
-
const parent = document.createElement(
|
|
1207
|
-
const o = useOverlay({ parentContainer: parent, closeOn:
|
|
1205
|
+
it('does not set overflow hidden on parent when closeOn is hover', () => {
|
|
1206
|
+
const parent = document.createElement('div')
|
|
1207
|
+
const o = useOverlay({ parentContainer: parent, closeOn: 'hover', openOn: 'hover' })
|
|
1208
1208
|
const cleanup = o.setupListeners()
|
|
1209
1209
|
|
|
1210
|
-
expect(parent.style.overflow).not.toBe(
|
|
1210
|
+
expect(parent.style.overflow).not.toBe('hidden')
|
|
1211
1211
|
cleanup()
|
|
1212
1212
|
})
|
|
1213
1213
|
})
|
|
@@ -1215,18 +1215,18 @@ describe("useOverlay", () => {
|
|
|
1215
1215
|
// =========================================================================
|
|
1216
1216
|
// 17. Provider
|
|
1217
1217
|
// =========================================================================
|
|
1218
|
-
describe(
|
|
1219
|
-
it(
|
|
1218
|
+
describe('Provider', () => {
|
|
1219
|
+
it('exposes Provider component', () => {
|
|
1220
1220
|
const o = useOverlay()
|
|
1221
|
-
expect(typeof o.Provider).toBe(
|
|
1221
|
+
expect(typeof o.Provider).toBe('function')
|
|
1222
1222
|
})
|
|
1223
1223
|
})
|
|
1224
1224
|
|
|
1225
1225
|
// =========================================================================
|
|
1226
1226
|
// 18. Independent instances
|
|
1227
1227
|
// =========================================================================
|
|
1228
|
-
describe(
|
|
1229
|
-
it(
|
|
1228
|
+
describe('independent instances', () => {
|
|
1229
|
+
it('two useOverlay instances do not share state', () => {
|
|
1230
1230
|
const o1 = useOverlay()
|
|
1231
1231
|
const o2 = useOverlay()
|
|
1232
1232
|
|
|
@@ -1244,23 +1244,23 @@ describe("useOverlay", () => {
|
|
|
1244
1244
|
// =========================================================================
|
|
1245
1245
|
// 19. Manual open/close mode
|
|
1246
1246
|
// =========================================================================
|
|
1247
|
-
describe(
|
|
1248
|
-
it(
|
|
1249
|
-
const o = useOverlay({ openOn:
|
|
1247
|
+
describe('manual mode', () => {
|
|
1248
|
+
it('openOn=manual: click on trigger does not open', () => {
|
|
1249
|
+
const o = useOverlay({ openOn: 'manual', closeOn: 'manual' })
|
|
1250
1250
|
const triggerEl = mockElement()
|
|
1251
1251
|
o.triggerRef(triggerEl)
|
|
1252
1252
|
const cleanup = o.setupListeners()
|
|
1253
1253
|
|
|
1254
|
-
const click = new MouseEvent(
|
|
1255
|
-
Object.defineProperty(click,
|
|
1254
|
+
const click = new MouseEvent('click', { bubbles: true })
|
|
1255
|
+
Object.defineProperty(click, 'target', { value: triggerEl })
|
|
1256
1256
|
window.dispatchEvent(click)
|
|
1257
1257
|
|
|
1258
1258
|
expect(o.active()).toBe(false)
|
|
1259
1259
|
cleanup()
|
|
1260
1260
|
})
|
|
1261
1261
|
|
|
1262
|
-
it(
|
|
1263
|
-
const o = useOverlay({ openOn:
|
|
1262
|
+
it('manual mode: only showContent/hideContent toggle state', () => {
|
|
1263
|
+
const o = useOverlay({ openOn: 'manual', closeOn: 'manual' })
|
|
1264
1264
|
const cleanup = o.setupListeners()
|
|
1265
1265
|
|
|
1266
1266
|
o.showContent()
|
|
@@ -1275,13 +1275,13 @@ describe("useOverlay", () => {
|
|
|
1275
1275
|
// =========================================================================
|
|
1276
1276
|
// 20. Position with absolute + ancestor offset
|
|
1277
1277
|
// =========================================================================
|
|
1278
|
-
describe(
|
|
1279
|
-
it(
|
|
1278
|
+
describe('position absolute with ancestor offset', () => {
|
|
1279
|
+
it('adjusts position for offset parent', () => {
|
|
1280
1280
|
const o = useOverlay({
|
|
1281
|
-
type:
|
|
1282
|
-
align:
|
|
1283
|
-
alignX:
|
|
1284
|
-
position:
|
|
1281
|
+
type: 'dropdown',
|
|
1282
|
+
align: 'bottom',
|
|
1283
|
+
alignX: 'left',
|
|
1284
|
+
position: 'absolute',
|
|
1285
1285
|
isOpen: true,
|
|
1286
1286
|
})
|
|
1287
1287
|
const triggerEl = mockElement({
|
|
@@ -1298,7 +1298,7 @@ describe("useOverlay", () => {
|
|
|
1298
1298
|
})
|
|
1299
1299
|
|
|
1300
1300
|
// Mock offsetParent
|
|
1301
|
-
const offsetParent = document.createElement(
|
|
1301
|
+
const offsetParent = document.createElement('div')
|
|
1302
1302
|
offsetParent.getBoundingClientRect = () => ({
|
|
1303
1303
|
top: 50,
|
|
1304
1304
|
bottom: 400,
|
|
@@ -1310,7 +1310,7 @@ describe("useOverlay", () => {
|
|
|
1310
1310
|
y: 50,
|
|
1311
1311
|
toJSON: () => {},
|
|
1312
1312
|
})
|
|
1313
|
-
Object.defineProperty(contentEl,
|
|
1313
|
+
Object.defineProperty(contentEl, 'offsetParent', {
|
|
1314
1314
|
value: offsetParent,
|
|
1315
1315
|
configurable: true,
|
|
1316
1316
|
})
|
|
@@ -1318,12 +1318,12 @@ describe("useOverlay", () => {
|
|
|
1318
1318
|
o.triggerRef(triggerEl)
|
|
1319
1319
|
o.contentRef(contentEl)
|
|
1320
1320
|
const cleanup = o.setupListeners()
|
|
1321
|
-
window.dispatchEvent(new Event(
|
|
1321
|
+
window.dispatchEvent(new Event('resize'))
|
|
1322
1322
|
|
|
1323
1323
|
// Without ancestor: top = 230, left = 100
|
|
1324
1324
|
// Adjusted: top = 230 - 50 = 180, left = 100 - 30 = 70
|
|
1325
|
-
expect(contentEl.style.top).toBe(
|
|
1326
|
-
expect(contentEl.style.left).toBe(
|
|
1325
|
+
expect(contentEl.style.top).toBe('180px')
|
|
1326
|
+
expect(contentEl.style.left).toBe('70px')
|
|
1327
1327
|
cleanup()
|
|
1328
1328
|
})
|
|
1329
1329
|
})
|