@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.
Files changed (53) hide show
  1. package/README.md +38 -35
  2. package/lib/index.d.ts +15 -15
  3. package/package.json +24 -24
  4. package/src/Element/component.tsx +14 -14
  5. package/src/Element/constants.ts +25 -25
  6. package/src/Element/index.ts +2 -2
  7. package/src/Element/types.ts +3 -3
  8. package/src/Element/utils.ts +1 -1
  9. package/src/List/component.tsx +7 -7
  10. package/src/List/index.ts +2 -2
  11. package/src/Overlay/component.tsx +22 -22
  12. package/src/Overlay/context.tsx +2 -2
  13. package/src/Overlay/index.ts +3 -3
  14. package/src/Overlay/useOverlay.tsx +97 -97
  15. package/src/Portal/component.tsx +6 -6
  16. package/src/Portal/index.ts +2 -2
  17. package/src/Text/component.tsx +6 -6
  18. package/src/Text/index.ts +2 -2
  19. package/src/Text/styled.ts +4 -4
  20. package/src/Util/component.tsx +5 -5
  21. package/src/Util/index.ts +2 -2
  22. package/src/__tests__/Content.test.tsx +46 -46
  23. package/src/__tests__/Element.test.ts +251 -251
  24. package/src/__tests__/Iterator.test.ts +142 -142
  25. package/src/__tests__/List.test.ts +61 -61
  26. package/src/__tests__/Overlay.test.ts +125 -125
  27. package/src/__tests__/Portal.test.ts +33 -33
  28. package/src/__tests__/Text.test.ts +128 -128
  29. package/src/__tests__/Util.test.ts +31 -31
  30. package/src/__tests__/Wrapper.test.tsx +60 -60
  31. package/src/__tests__/equalBeforeAfter.test.ts +41 -41
  32. package/src/__tests__/helpers.test.ts +29 -29
  33. package/src/__tests__/overlayContext.test.tsx +14 -14
  34. package/src/__tests__/responsiveProps.test.ts +116 -116
  35. package/src/__tests__/useOverlay.test.ts +283 -283
  36. package/src/__tests__/utils.test.ts +43 -43
  37. package/src/constants.ts +1 -1
  38. package/src/helpers/Content/component.tsx +5 -5
  39. package/src/helpers/Content/index.ts +1 -1
  40. package/src/helpers/Content/styled.ts +16 -16
  41. package/src/helpers/Content/types.ts +7 -7
  42. package/src/helpers/Iterator/component.tsx +28 -28
  43. package/src/helpers/Iterator/index.ts +2 -2
  44. package/src/helpers/Iterator/types.ts +3 -3
  45. package/src/helpers/Wrapper/component.tsx +6 -6
  46. package/src/helpers/Wrapper/index.ts +1 -1
  47. package/src/helpers/Wrapper/styled.ts +8 -8
  48. package/src/helpers/Wrapper/types.ts +3 -3
  49. package/src/helpers/Wrapper/utils.ts +1 -1
  50. package/src/helpers/index.ts +2 -2
  51. package/src/index.ts +16 -16
  52. package/src/types.ts +7 -7
  53. package/src/utils.ts +1 -1
@@ -1,10 +1,10 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
2
 
3
3
  // ---------------------------------------------------------------------------
4
4
  // Mocks
5
5
  // ---------------------------------------------------------------------------
6
6
 
7
- vi.mock("@pyreon/reactivity", () => {
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("@pyreon/core", async (importOriginal) => {
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("@pyreon/ui-core", async () => {
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("@pyreon/unistyle", () => ({
63
- value: (v: unknown, _base?: number) => (typeof v === "number" ? `${v}px` : 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("../Overlay/context", async (importOriginal) => {
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("~/utils", () => ({
80
+ vi.mock('~/utils', () => ({
81
81
  IS_DEVELOPMENT: false,
82
82
  }))
83
83
 
84
- import { useOverlay } from "../Overlay"
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("div")
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, "innerWidth", { value: width, configurable: true })
110
- Object.defineProperty(window, "innerHeight", { value: height, configurable: true })
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("useOverlay", () => {
127
+ describe('useOverlay', () => {
128
128
  // =========================================================================
129
129
  // 1. Default state
130
130
  // =========================================================================
131
- describe("default state", () => {
132
- it("active is false by default", () => {
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("align defaults to bottom", () => {
137
+ it('align defaults to bottom', () => {
138
138
  const o = useOverlay()
139
- expect(o.align).toBe("bottom")
139
+ expect(o.align).toBe('bottom')
140
140
  })
141
141
 
142
- it("alignX defaults to left", () => {
142
+ it('alignX defaults to left', () => {
143
143
  const o = useOverlay()
144
- expect(o.alignX()).toBe("left")
144
+ expect(o.alignX()).toBe('left')
145
145
  })
146
146
 
147
- it("alignY defaults to bottom", () => {
147
+ it('alignY defaults to bottom', () => {
148
148
  const o = useOverlay()
149
- expect(o.alignY()).toBe("bottom")
149
+ expect(o.alignY()).toBe('bottom')
150
150
  })
151
151
 
152
- it("blocked is false by default", () => {
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("isOpen=true", () => {
162
- it("active starts true when isOpen is true", () => {
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("disabled", () => {
172
- it("forces active to false when disabled is true", () => {
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("prevents event handling when disabled", () => {
177
+ it('prevents event handling when disabled', () => {
178
178
  const onOpen = vi.fn()
179
- const o = useOverlay({ openOn: "click", disabled: true, onOpen })
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("click", { bubbles: true })
185
- Object.defineProperty(click, "target", { value: triggerEl })
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("triggerRef and contentRef", () => {
198
- it("triggerRef is a callable function", () => {
197
+ describe('triggerRef and contentRef', () => {
198
+ it('triggerRef is a callable function', () => {
199
199
  const o = useOverlay()
200
- expect(typeof o.triggerRef).toBe("function")
200
+ expect(typeof o.triggerRef).toBe('function')
201
201
  })
202
202
 
203
- it("contentRef is a callable function", () => {
203
+ it('contentRef is a callable function', () => {
204
204
  const o = useOverlay()
205
- expect(typeof o.contentRef).toBe("function")
205
+ expect(typeof o.contentRef).toBe('function')
206
206
  })
207
207
 
208
- it("triggerRef accepts an HTMLElement", () => {
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("contentRef accepts an HTMLElement", () => {
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("triggerRef accepts null", () => {
220
+ it('triggerRef accepts null', () => {
221
221
  const o = useOverlay()
222
222
  expect(() => o.triggerRef(null)).not.toThrow()
223
223
  })
224
224
 
225
- it("contentRef accepts null", () => {
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("showContent / hideContent", () => {
235
- it("showContent sets active to true", () => {
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("hideContent sets active to false", () => {
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("showContent calls onOpen callback", () => {
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("hideContent calls onClose callback", () => {
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("showContent calls ctx.setBlocked", () => {
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("hideContent calls ctx.setUnblocked", () => {
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("toggle between show and hide works", () => {
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("blocked state", () => {
288
- it("setBlocked increments blocked count", () => {
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("setUnblocked decrements blocked count", () => {
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("multiple setBlocked calls require matching setUnblocked calls", () => {
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("setUnblocked does not go below zero", () => {
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("blocked overlay ignores click events", () => {
318
+ it('blocked overlay ignores click events', () => {
319
319
  const onOpen = vi.fn()
320
- const o = useOverlay({ openOn: "click", onOpen })
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("click", { bubbles: true })
329
- Object.defineProperty(click, "target", { value: triggerEl })
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("setupListeners", () => {
342
- it("returns a cleanup function", () => {
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("function")
345
+ expect(typeof cleanup).toBe('function')
346
346
  cleanup()
347
347
  })
348
348
 
349
- it("cleanup removes event listeners without error", () => {
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("cleanup can be called multiple times safely", () => {
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("click handling", () => {
367
- it("openOn=click: clicking trigger when inactive opens overlay", () => {
368
- const o = useOverlay({ openOn: "click" })
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("click", { bubbles: true })
374
- Object.defineProperty(click, "target", { value: triggerEl })
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("openOn=click: clicking non-trigger when inactive does not open", () => {
382
- const o = useOverlay({ openOn: "click" })
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("div")
388
- const click = new MouseEvent("click", { bubbles: true })
389
- Object.defineProperty(click, "target", { value: outsideEl })
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("closeOn=click: any click when active closes overlay", () => {
397
- const o = useOverlay({ openOn: "click", closeOn: "click", isOpen: true })
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("div")
403
- const click = new MouseEvent("click", { bubbles: true })
404
- Object.defineProperty(click, "target", { value: outsideEl })
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("closeOn=clickOnTrigger: clicking trigger when active closes overlay", () => {
412
- const o = useOverlay({ openOn: "click", closeOn: "clickOnTrigger", isOpen: true })
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("click", { bubbles: true })
418
- Object.defineProperty(click, "target", { value: triggerEl })
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("closeOn=clickOnTrigger: clicking outside does not close overlay", () => {
426
- const o = useOverlay({ openOn: "click", closeOn: "clickOnTrigger", isOpen: true })
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("div")
432
- const click = new MouseEvent("click", { bubbles: true })
433
- Object.defineProperty(click, "target", { value: outsideEl })
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("closeOn=clickOutsideContent: click outside content closes overlay", () => {
441
- const o = useOverlay({ openOn: "click", closeOn: "clickOutsideContent", isOpen: true })
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("div")
449
- const click = new MouseEvent("click", { bubbles: true })
450
- Object.defineProperty(click, "target", { value: outsideEl })
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("closeOn=clickOutsideContent: click inside content does not close overlay", () => {
458
- const o = useOverlay({ openOn: "click", closeOn: "clickOutsideContent", isOpen: true })
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("span")
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("click", { bubbles: true })
468
- Object.defineProperty(click, "target", { value: childEl })
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("click on trigger child element opens overlay (contains check)", () => {
476
- const o = useOverlay({ openOn: "click" })
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("span")
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("click", { bubbles: true })
484
- Object.defineProperty(click, "target", { value: innerEl })
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("ESC handling", () => {
496
- it("closeOnEsc=true: Escape key when active closes overlay", () => {
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("keydown", { key: "Escape", bubbles: true })
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("closeOnEsc=true: Escape key when inactive does nothing", () => {
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("keydown", { key: "Escape", bubbles: true })
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("closeOnEsc=false: Escape key does not close overlay", () => {
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("keydown", { key: "Escape", bubbles: true })
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("closeOnEsc: Escape does not close when blocked", () => {
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("keydown", { key: "Escape", bubbles: true })
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("non-Escape key does not close overlay", () => {
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("keydown", { key: "Enter", bubbles: true })
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("hover handling", () => {
559
- it("openOn=hover: mouseenter on trigger opens overlay", () => {
560
- const o = useOverlay({ openOn: "hover", closeOn: "hover" })
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("mouseenter", { bubbles: true }))
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("closeOn=hover: mouseleave from trigger schedules hide with hoverDelay", () => {
572
- const o = useOverlay({ openOn: "hover", closeOn: "hover", hoverDelay: 100 })
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("mouseenter", { bubbles: true }))
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("mouseleave", { bubbles: true }))
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("mouseenter on content cancels hide timeout", () => {
593
- const o = useOverlay({ openOn: "hover", closeOn: "hover", hoverDelay: 100 })
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("mouseenter", { bubbles: true }))
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("mouseleave", { bubbles: true }))
605
+ triggerEl.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
606
606
 
607
607
  // Enter content (cancels hide timer)
608
- contentEl.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }))
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("mouseleave from content schedules hide", () => {
617
- const o = useOverlay({ openOn: "hover", closeOn: "hover", hoverDelay: 50 })
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("mouseenter", { bubbles: true }))
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("mouseleave", { bubbles: true }))
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("hover: scroll event closes overlay when closeOn=hover and active", () => {
636
- const o = useOverlay({ openOn: "hover", closeOn: "hover" })
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("mouseenter", { bubbles: true }))
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("scroll"))
646
+ window.dispatchEvent(new Event('scroll'))
647
647
  expect(o.active()).toBe(false)
648
648
  cleanup()
649
649
  })
650
650
 
651
- it("openOn=hover: mouseenter when already active does not call onOpen again", () => {
651
+ it('openOn=hover: mouseenter when already active does not call onOpen again', () => {
652
652
  const onOpen = vi.fn()
653
- const o = useOverlay({ openOn: "hover", closeOn: "hover", onOpen })
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("mouseenter", { bubbles: true }))
659
- triggerEl.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }))
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("position calculation - dropdown", () => {
669
+ describe('position calculation - dropdown', () => {
670
670
  const setupDropdown = (opts: Parameters<typeof useOverlay>[0] = {}) => {
671
671
  const o = useOverlay({
672
- type: "dropdown",
673
- align: "bottom",
674
- alignX: "left",
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("bottom align: positions content below trigger", () => {
703
- const { o, contentEl } = setupDropdown({ align: "bottom", alignX: "left" })
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("resize"))
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("130px")
715
+ expect(contentEl.style.top).toBe('130px')
716
716
  // left = trigger.left + offsetX = 50
717
- expect(contentEl.style.left).toBe("50px")
717
+ expect(contentEl.style.left).toBe('50px')
718
718
  cleanup()
719
719
  })
720
720
 
721
- it("top align: positions content above trigger", () => {
722
- const { o, contentEl } = setupDropdown({ align: "top", alignX: "left" })
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("resize"))
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("130px")
728
+ expect(contentEl.style.top).toBe('130px')
729
729
  cleanup()
730
730
  })
731
731
 
732
- it("top align with room above: positions content above trigger", () => {
732
+ it('top align with room above: positions content above trigger', () => {
733
733
  const o = useOverlay({
734
- type: "dropdown",
735
- align: "top",
736
- alignX: "left",
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("resize"))
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("300px")
761
+ expect(contentEl.style.top).toBe('300px')
762
762
  cleanup()
763
763
  })
764
764
 
765
- it("alignX=right: positions content aligned to right edge", () => {
766
- const { o, contentEl } = setupDropdown({ align: "bottom", alignX: "right" })
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("resize"))
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("50px")
772
+ expect(contentEl.style.left).toBe('50px')
773
773
  cleanup()
774
774
  })
775
775
 
776
- it("alignX=center: centers content horizontally under trigger", () => {
777
- const { o, contentEl } = setupDropdown({ align: "bottom", alignX: "center" })
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("resize"))
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("0px")
784
+ expect(contentEl.style.left).toBe('0px')
785
785
  cleanup()
786
786
  })
787
787
 
788
- it("with offsets: applies offsetX and offsetY", () => {
788
+ it('with offsets: applies offsetX and offsetY', () => {
789
789
  const o = useOverlay({
790
- type: "dropdown",
791
- align: "bottom",
792
- alignX: "left",
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("resize"))
816
+ window.dispatchEvent(new Event('resize'))
817
817
 
818
818
  // top = trigger.bottom + offsetY = 130 + 5 = 135
819
- expect(contentEl.style.top).toBe("135px")
819
+ expect(contentEl.style.top).toBe('135px')
820
820
  // left = trigger.left + offsetX = 50 + 10 = 60
821
- expect(contentEl.style.left).toBe("60px")
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("position calculation - horizontal dropdown", () => {
830
- it("align=right: positions content to the right of trigger", () => {
829
+ describe('position calculation - horizontal dropdown', () => {
830
+ it('align=right: positions content to the right of trigger', () => {
831
831
  const o = useOverlay({
832
- type: "dropdown",
833
- align: "right",
834
- alignY: "top",
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("resize"))
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("150px")
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("100px")
863
+ expect(contentEl.style.top).toBe('100px')
864
864
  cleanup()
865
865
  })
866
866
 
867
- it("align=left: positions content to the left of trigger", () => {
867
+ it('align=left: positions content to the left of trigger', () => {
868
868
  const o = useOverlay({
869
- type: "dropdown",
870
- align: "left",
871
- alignY: "top",
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("resize"))
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("200px")
897
+ expect(contentEl.style.left).toBe('200px')
898
898
  cleanup()
899
899
  })
900
900
 
901
- it("align=right, alignY=center: vertically centers content", () => {
901
+ it('align=right, alignY=center: vertically centers content', () => {
902
902
  const o = useOverlay({
903
- type: "dropdown",
904
- align: "right",
905
- alignY: "center",
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("resize"))
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("265px")
931
+ expect(contentEl.style.top).toBe('265px')
932
932
  cleanup()
933
933
  })
934
934
 
935
- it("align=right, alignY=bottom: positions from bottom", () => {
935
+ it('align=right, alignY=bottom: positions from bottom', () => {
936
936
  const o = useOverlay({
937
- type: "dropdown",
938
- align: "right",
939
- alignY: "bottom",
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("resize"))
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("230px")
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("position calculation - modal", () => {
974
- it("modal type: centers content by default", () => {
973
+ describe('position calculation - modal', () => {
974
+ it('modal type: centers content by default', () => {
975
975
  const o = useOverlay({
976
- type: "modal",
977
- alignX: "center",
978
- alignY: "center",
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("resize"))
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("362px")
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("284px")
996
+ expect(contentEl.style.top).toBe('284px')
997
997
  cleanup()
998
998
  })
999
999
 
1000
- it("modal type: alignX=left positions left edge", () => {
1000
+ it('modal type: alignX=left positions left edge', () => {
1001
1001
  const o = useOverlay({
1002
- type: "modal",
1003
- alignX: "left",
1004
- alignY: "top",
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("resize"))
1012
+ window.dispatchEvent(new Event('resize'))
1013
1013
 
1014
- expect(contentEl.style.left).toBe("20px")
1015
- expect(contentEl.style.top).toBe("10px")
1014
+ expect(contentEl.style.left).toBe('20px')
1015
+ expect(contentEl.style.top).toBe('10px')
1016
1016
  cleanup()
1017
1017
  })
1018
1018
 
1019
- it("modal type: alignX=right positions right edge", () => {
1019
+ it('modal type: alignX=right positions right edge', () => {
1020
1020
  const o = useOverlay({
1021
- type: "modal",
1022
- alignX: "right",
1023
- alignY: "bottom",
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("resize"))
1031
+ window.dispatchEvent(new Event('resize'))
1032
1032
 
1033
- expect(contentEl.style.right).toBe("15px")
1034
- expect(contentEl.style.bottom).toBe("25px")
1033
+ expect(contentEl.style.right).toBe('15px')
1034
+ expect(contentEl.style.bottom).toBe('25px')
1035
1035
  cleanup()
1036
1036
  })
1037
1037
 
1038
- it("modal type: sets document.body overflow to hidden", () => {
1039
- const o = useOverlay({ type: "modal", isOpen: true })
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("hidden")
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("position calculation - custom type", () => {
1054
- it("custom type: does not set position styles", () => {
1053
+ describe('position calculation - custom type', () => {
1054
+ it('custom type: does not set position styles', () => {
1055
1055
  const o = useOverlay({
1056
- type: "custom",
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("resize"))
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("alignment signal updates", () => {
1075
- it("updates alignX signal when position flips horizontally", () => {
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: "dropdown",
1079
- align: "bottom",
1080
- alignX: "left",
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("resize"))
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("right")
1105
+ expect(o.alignX()).toBe('right')
1106
1106
  cleanup()
1107
1107
  })
1108
1108
 
1109
- it("updates alignY signal when vertical position flips to top", () => {
1109
+ it('updates alignY signal when vertical position flips to top', () => {
1110
1110
  setViewport(1024, 200)
1111
1111
  const o = useOverlay({
1112
- type: "dropdown",
1113
- align: "bottom",
1114
- alignX: "left",
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("resize"))
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("top")
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("resize handling", () => {
1146
- it("recalculates position on window resize", () => {
1145
+ describe('resize handling', () => {
1146
+ it('recalculates position on window resize', () => {
1147
1147
  const o = useOverlay({
1148
- type: "dropdown",
1149
- align: "bottom",
1150
- alignX: "left",
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("resize"))
1171
- expect(contentEl.style.top).toBe("130px")
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("resize"))
1186
- expect(contentEl.style.top).toBe("230px")
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("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" })
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("hidden")
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("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" })
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("hidden")
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("Provider", () => {
1219
- it("exposes Provider component", () => {
1218
+ describe('Provider', () => {
1219
+ it('exposes Provider component', () => {
1220
1220
  const o = useOverlay()
1221
- expect(typeof o.Provider).toBe("function")
1221
+ expect(typeof o.Provider).toBe('function')
1222
1222
  })
1223
1223
  })
1224
1224
 
1225
1225
  // =========================================================================
1226
1226
  // 18. Independent instances
1227
1227
  // =========================================================================
1228
- describe("independent instances", () => {
1229
- it("two useOverlay instances do not share state", () => {
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("manual mode", () => {
1248
- it("openOn=manual: click on trigger does not open", () => {
1249
- const o = useOverlay({ openOn: "manual", closeOn: "manual" })
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("click", { bubbles: true })
1255
- Object.defineProperty(click, "target", { value: triggerEl })
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("manual mode: only showContent/hideContent toggle state", () => {
1263
- const o = useOverlay({ openOn: "manual", closeOn: "manual" })
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("position absolute with ancestor offset", () => {
1279
- it("adjusts position for offset parent", () => {
1278
+ describe('position absolute with ancestor offset', () => {
1279
+ it('adjusts position for offset parent', () => {
1280
1280
  const o = useOverlay({
1281
- type: "dropdown",
1282
- align: "bottom",
1283
- alignX: "left",
1284
- position: "absolute",
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("div")
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, "offsetParent", {
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("resize"))
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("180px")
1326
- expect(contentEl.style.left).toBe("70px")
1325
+ expect(contentEl.style.top).toBe('180px')
1326
+ expect(contentEl.style.left).toBe('70px')
1327
1327
  cleanup()
1328
1328
  })
1329
1329
  })