@jmruthers/pace-core 0.5.118 → 0.5.120

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 (181) hide show
  1. package/dist/{DataTable-ZOAKQ3SU.js → DataTable-DGZDJUYM.js} +7 -7
  2. package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
  3. package/dist/{chunk-7OTQLFVI.js → chunk-B4GZ2BXO.js} +3 -3
  4. package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
  5. package/dist/chunk-BHWIUEYH.js.map +1 -0
  6. package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
  7. package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
  8. package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
  9. package/dist/{chunk-2GJ5GL77.js → chunk-GKHF54DI.js} +2 -2
  10. package/dist/chunk-GKHF54DI.js.map +1 -0
  11. package/dist/{chunk-UKZWNQMB.js → chunk-HFBOFZ3Z.js} +5 -18
  12. package/dist/chunk-HFBOFZ3Z.js.map +1 -0
  13. package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
  14. package/dist/{chunk-2LM4QQGH.js → chunk-QPI2CCBA.js} +9 -9
  15. package/dist/chunk-QPI2CCBA.js.map +1 -0
  16. package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
  17. package/dist/{chunk-HIWXXDXO.js → chunk-TDNI6ZWL.js} +5 -5
  18. package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
  19. package/dist/components.d.ts +1 -1
  20. package/dist/components.js +9 -9
  21. package/dist/hooks.d.ts +1 -1
  22. package/dist/hooks.js +8 -8
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +12 -12
  25. package/dist/providers.js +2 -2
  26. package/dist/rbac/index.js +7 -7
  27. package/dist/{useToast-Cs_g32bg.d.ts → useToast-C8gR5ir4.d.ts} +2 -2
  28. package/dist/utils.js +1 -1
  29. package/docs/api/classes/ColumnFactory.md +1 -1
  30. package/docs/api/classes/ErrorBoundary.md +1 -1
  31. package/docs/api/classes/InvalidScopeError.md +1 -1
  32. package/docs/api/classes/MissingUserContextError.md +1 -1
  33. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  34. package/docs/api/classes/PermissionDeniedError.md +1 -1
  35. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  36. package/docs/api/classes/RBACAuditManager.md +1 -1
  37. package/docs/api/classes/RBACCache.md +1 -1
  38. package/docs/api/classes/RBACEngine.md +1 -1
  39. package/docs/api/classes/RBACError.md +1 -1
  40. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  41. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  42. package/docs/api/classes/StorageUtils.md +1 -1
  43. package/docs/api/enums/FileCategory.md +1 -1
  44. package/docs/api/interfaces/AggregateConfig.md +1 -1
  45. package/docs/api/interfaces/ButtonProps.md +1 -1
  46. package/docs/api/interfaces/CardProps.md +1 -1
  47. package/docs/api/interfaces/ColorPalette.md +1 -1
  48. package/docs/api/interfaces/ColorShade.md +1 -1
  49. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  50. package/docs/api/interfaces/DataRecord.md +1 -1
  51. package/docs/api/interfaces/DataTableAction.md +1 -1
  52. package/docs/api/interfaces/DataTableColumn.md +1 -1
  53. package/docs/api/interfaces/DataTableProps.md +1 -1
  54. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  55. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  56. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  57. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  58. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  59. package/docs/api/interfaces/FileMetadata.md +1 -1
  60. package/docs/api/interfaces/FileReference.md +1 -1
  61. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  62. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  63. package/docs/api/interfaces/FileUploadProps.md +1 -1
  64. package/docs/api/interfaces/FooterProps.md +1 -1
  65. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  66. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  67. package/docs/api/interfaces/InputProps.md +1 -1
  68. package/docs/api/interfaces/LabelProps.md +1 -1
  69. package/docs/api/interfaces/LoginFormProps.md +1 -1
  70. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  71. package/docs/api/interfaces/NavigationContextType.md +1 -1
  72. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  73. package/docs/api/interfaces/NavigationItem.md +1 -1
  74. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  75. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  76. package/docs/api/interfaces/Organisation.md +1 -1
  77. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  78. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  79. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  80. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  81. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  82. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  83. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  84. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  85. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  86. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  87. package/docs/api/interfaces/PaletteData.md +1 -1
  88. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  89. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  90. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  91. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  92. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  93. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  94. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  95. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  96. package/docs/api/interfaces/RBACConfig.md +1 -1
  97. package/docs/api/interfaces/RBACLogger.md +1 -1
  98. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  99. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  100. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  101. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  102. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  103. package/docs/api/interfaces/RouteConfig.md +1 -1
  104. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  105. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  106. package/docs/api/interfaces/StorageConfig.md +1 -1
  107. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  108. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  109. package/docs/api/interfaces/StorageListOptions.md +1 -1
  110. package/docs/api/interfaces/StorageListResult.md +1 -1
  111. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  112. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  113. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  114. package/docs/api/interfaces/StyleImport.md +1 -1
  115. package/docs/api/interfaces/SwitchProps.md +1 -1
  116. package/docs/api/interfaces/ToastActionElement.md +1 -1
  117. package/docs/api/interfaces/ToastProps.md +1 -1
  118. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  119. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  120. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  121. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  122. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  123. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  124. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  125. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  126. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  127. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  128. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  129. package/docs/api/interfaces/UserEventAccess.md +1 -1
  130. package/docs/api/interfaces/UserMenuProps.md +1 -1
  131. package/docs/api/interfaces/UserProfile.md +1 -1
  132. package/docs/api/modules.md +2 -2
  133. package/package.json +1 -1
  134. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
  135. package/src/components/DataTable/components/DataTableCore.tsx +5 -0
  136. package/src/components/DataTable/components/EditableRow.tsx +9 -18
  137. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +616 -9
  138. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
  139. package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
  140. package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
  141. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
  142. package/src/components/Toast/Toast.tsx +1 -1
  143. package/src/hooks/__tests__/index.unit.test.ts +223 -0
  144. package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
  145. package/src/hooks/__tests__/useEvents.unit.test.ts +251 -0
  146. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
  147. package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
  148. package/src/hooks/__tests__/useFocusManagement.unit.test.ts +19 -9
  149. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
  150. package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
  151. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
  152. package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
  153. package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +661 -0
  154. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
  155. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +371 -0
  156. package/src/hooks/__tests__/useToast.unit.test.tsx +449 -30
  157. package/src/hooks/useSecureDataAccess.test.ts +1 -0
  158. package/src/hooks/useToast.ts +4 -4
  159. package/src/rbac/audit-enhanced.ts +339 -0
  160. package/src/services/EventService.ts +1 -0
  161. package/src/services/__tests__/AuthService.test.ts +473 -0
  162. package/src/services/__tests__/EventService.test.ts +390 -0
  163. package/src/services/__tests__/InactivityService.test.ts +217 -0
  164. package/src/services/__tests__/OrganisationService.test.ts +371 -0
  165. package/src/styles/core.css +1 -0
  166. package/dist/chunk-2GJ5GL77.js.map +0 -1
  167. package/dist/chunk-2LM4QQGH.js.map +0 -1
  168. package/dist/chunk-KA3PSVNV.js.map +0 -1
  169. package/dist/chunk-UKZWNQMB.js.map +0 -1
  170. package/src/components/DataTable/utils/debugTools.ts +0 -609
  171. package/src/rbac/testing/index.tsx +0 -340
  172. /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-DGZDJUYM.js.map} +0 -0
  173. /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
  174. /package/dist/{chunk-7OTQLFVI.js.map → chunk-B4GZ2BXO.js.map} +0 -0
  175. /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
  176. /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
  177. /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
  178. /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
  179. /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
  180. /package/dist/{chunk-HIWXXDXO.js.map → chunk-TDNI6ZWL.js.map} +0 -0
  181. /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { renderHook } from '@testing-library/react';
11
11
  import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
12
- import { useKeyboardShortcuts } from '../useKeyboardShortcuts';
12
+ import { useKeyboardShortcuts, useAccessibilityShortcuts } from '../useKeyboardShortcuts';
13
13
  import { fireEvent } from '@testing-library/react';
14
14
 
15
15
  describe('useKeyboardShortcuts', () => {
@@ -291,5 +291,620 @@ describe('useKeyboardShortcuts', () => {
291
291
  expect(handler).toHaveBeenCalledTimes(1);
292
292
  });
293
293
  });
294
+
295
+ describe('Complex Modifier Combinations', () => {
296
+ it('handles Ctrl+Alt+key combination', () => {
297
+ const handler = vi.fn();
298
+ const shortcuts = [
299
+ {
300
+ key: 'ctrl+alt+s',
301
+ handler
302
+ }
303
+ ];
304
+
305
+ renderHook(() => useKeyboardShortcuts(shortcuts));
306
+
307
+ const event = new KeyboardEvent('keydown', {
308
+ key: 's',
309
+ ctrlKey: true,
310
+ altKey: true,
311
+ bubbles: true
312
+ });
313
+ document.dispatchEvent(event);
314
+
315
+ expect(handler).toHaveBeenCalledTimes(1);
316
+ });
317
+
318
+ it('handles Ctrl+Shift+key combination', () => {
319
+ const handler = vi.fn();
320
+ const shortcuts = [
321
+ {
322
+ key: 'ctrl+shift+s',
323
+ handler
324
+ }
325
+ ];
326
+
327
+ renderHook(() => useKeyboardShortcuts(shortcuts));
328
+
329
+ const event = new KeyboardEvent('keydown', {
330
+ key: 's',
331
+ ctrlKey: true,
332
+ shiftKey: true,
333
+ bubbles: true
334
+ });
335
+ document.dispatchEvent(event);
336
+
337
+ expect(handler).toHaveBeenCalledTimes(1);
338
+ });
339
+
340
+ it('handles Meta+key combination (Mac)', () => {
341
+ const handler = vi.fn();
342
+ const shortcuts = [
343
+ {
344
+ key: 'meta+s',
345
+ handler
346
+ }
347
+ ];
348
+
349
+ renderHook(() => useKeyboardShortcuts(shortcuts));
350
+
351
+ const event = new KeyboardEvent('keydown', {
352
+ key: 's',
353
+ metaKey: true,
354
+ bubbles: true
355
+ });
356
+ document.dispatchEvent(event);
357
+
358
+ expect(handler).toHaveBeenCalledTimes(1);
359
+ });
360
+
361
+ it('handles Alt+key combination', () => {
362
+ const handler = vi.fn();
363
+ const shortcuts = [
364
+ {
365
+ key: 'alt+f',
366
+ handler
367
+ }
368
+ ];
369
+
370
+ renderHook(() => useKeyboardShortcuts(shortcuts));
371
+
372
+ const event = new KeyboardEvent('keydown', {
373
+ key: 'f',
374
+ altKey: true,
375
+ bubbles: true
376
+ });
377
+ document.dispatchEvent(event);
378
+
379
+ expect(handler).toHaveBeenCalledTimes(1);
380
+ });
381
+
382
+ it('handles Shift+key combination', () => {
383
+ const handler = vi.fn();
384
+ const shortcuts = [
385
+ {
386
+ key: 'shift+arrowup',
387
+ handler
388
+ }
389
+ ];
390
+
391
+ renderHook(() => useKeyboardShortcuts(shortcuts));
392
+
393
+ const event = new KeyboardEvent('keydown', {
394
+ key: 'ArrowUp',
395
+ shiftKey: true,
396
+ bubbles: true
397
+ });
398
+ document.dispatchEvent(event);
399
+
400
+ expect(handler).toHaveBeenCalledTimes(1);
401
+ });
402
+
403
+ it('does not trigger when modifiers do not match', () => {
404
+ const handler = vi.fn();
405
+ const shortcuts = [
406
+ {
407
+ key: 'ctrl+alt+s',
408
+ handler
409
+ }
410
+ ];
411
+
412
+ renderHook(() => useKeyboardShortcuts(shortcuts));
413
+
414
+ const event = new KeyboardEvent('keydown', {
415
+ key: 's',
416
+ ctrlKey: true,
417
+ altKey: false, // Missing Alt
418
+ bubbles: true
419
+ });
420
+ document.dispatchEvent(event);
421
+
422
+ expect(handler).not.toHaveBeenCalled();
423
+ });
424
+
425
+ it('does not trigger when extra modifiers are pressed', () => {
426
+ const handler = vi.fn();
427
+ const shortcuts = [
428
+ {
429
+ key: 'ctrl+s',
430
+ handler
431
+ }
432
+ ];
433
+
434
+ renderHook(() => useKeyboardShortcuts(shortcuts));
435
+
436
+ const event = new KeyboardEvent('keydown', {
437
+ key: 's',
438
+ ctrlKey: true,
439
+ altKey: true, // Extra modifier
440
+ bubbles: true
441
+ });
442
+ document.dispatchEvent(event);
443
+
444
+ expect(handler).not.toHaveBeenCalled();
445
+ });
446
+ });
447
+
448
+ describe('Key Parsing Edge Cases', () => {
449
+ it('handles cmd alias for meta key', () => {
450
+ const handler = vi.fn();
451
+ const shortcuts = [
452
+ {
453
+ key: 'cmd+s',
454
+ handler
455
+ }
456
+ ];
457
+
458
+ renderHook(() => useKeyboardShortcuts(shortcuts));
459
+
460
+ const event = new KeyboardEvent('keydown', {
461
+ key: 's',
462
+ metaKey: true,
463
+ bubbles: true
464
+ });
465
+ document.dispatchEvent(event);
466
+
467
+ expect(handler).toHaveBeenCalledTimes(1);
468
+ });
469
+
470
+ it('handles control alias for ctrl key', () => {
471
+ const handler = vi.fn();
472
+ const shortcuts = [
473
+ {
474
+ key: 'control+s',
475
+ handler
476
+ }
477
+ ];
478
+
479
+ renderHook(() => useKeyboardShortcuts(shortcuts));
480
+
481
+ const event = new KeyboardEvent('keydown', {
482
+ key: 's',
483
+ ctrlKey: true,
484
+ bubbles: true
485
+ });
486
+ document.dispatchEvent(event);
487
+
488
+ expect(handler).toHaveBeenCalledTimes(1);
489
+ });
490
+
491
+ it('handles case-insensitive key matching', () => {
492
+ const handler = vi.fn();
493
+ const shortcuts = [
494
+ {
495
+ key: 'CTRL+S',
496
+ handler
497
+ }
498
+ ];
499
+
500
+ renderHook(() => useKeyboardShortcuts(shortcuts));
501
+
502
+ const event = new KeyboardEvent('keydown', {
503
+ key: 's',
504
+ ctrlKey: true,
505
+ bubbles: true
506
+ });
507
+ document.dispatchEvent(event);
508
+
509
+ expect(handler).toHaveBeenCalledTimes(1);
510
+ });
511
+
512
+ it('handles keys with special characters', () => {
513
+ const handler = vi.fn();
514
+ const shortcuts = [
515
+ {
516
+ key: '?',
517
+ handler
518
+ }
519
+ ];
520
+
521
+ renderHook(() => useKeyboardShortcuts(shortcuts));
522
+
523
+ const event = new KeyboardEvent('keydown', {
524
+ key: '?',
525
+ bubbles: true
526
+ });
527
+ document.dispatchEvent(event);
528
+
529
+ expect(handler).toHaveBeenCalledTimes(1);
530
+ });
531
+
532
+ it('handles space key', () => {
533
+ const handler = vi.fn();
534
+ const shortcuts = [
535
+ {
536
+ key: ' ',
537
+ handler
538
+ }
539
+ ];
540
+
541
+ renderHook(() => useKeyboardShortcuts(shortcuts));
542
+
543
+ const event = new KeyboardEvent('keydown', {
544
+ key: ' ',
545
+ bubbles: true
546
+ });
547
+ document.dispatchEvent(event);
548
+
549
+ expect(handler).toHaveBeenCalledTimes(1);
550
+ });
551
+ });
552
+
553
+ describe('Event Handling', () => {
554
+ it('stopPropagation option works correctly', () => {
555
+ const handler = vi.fn((e) => {
556
+ // Handler should not affect propagation test
557
+ });
558
+ const parentHandler = vi.fn();
559
+
560
+ const shortcuts = [
561
+ {
562
+ key: 'Escape',
563
+ handler,
564
+ stopPropagation: true
565
+ }
566
+ ];
567
+
568
+ document.addEventListener('keydown', parentHandler);
569
+
570
+ renderHook(() => useKeyboardShortcuts(shortcuts));
571
+
572
+ const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
573
+ document.dispatchEvent(event);
574
+
575
+ expect(handler).toHaveBeenCalledTimes(1);
576
+ // Note: stopPropagation is called on the event, but we can't easily test
577
+ // event propagation in this context without more complex setup
578
+
579
+ document.removeEventListener('keydown', parentHandler);
580
+ });
581
+
582
+ it('events propagate when stopPropagation is false', () => {
583
+ const handler = vi.fn();
584
+ const parentHandler = vi.fn();
585
+
586
+ const shortcuts = [
587
+ {
588
+ key: 'Escape',
589
+ handler,
590
+ stopPropagation: false
591
+ }
592
+ ];
593
+
594
+ document.addEventListener('keydown', parentHandler);
595
+
596
+ renderHook(() => useKeyboardShortcuts(shortcuts));
597
+
598
+ const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
599
+ document.dispatchEvent(event);
600
+
601
+ expect(handler).toHaveBeenCalledTimes(1);
602
+ // Parent handler should also be called since we're not stopping propagation
603
+ expect(parentHandler).toHaveBeenCalled();
604
+
605
+ document.removeEventListener('keydown', parentHandler);
606
+ });
607
+
608
+ it('multiple shortcuts with same key - first match wins', () => {
609
+ const handler1 = vi.fn();
610
+ const handler2 = vi.fn();
611
+ const handler3 = vi.fn();
612
+
613
+ const shortcuts = [
614
+ {
615
+ key: 'Escape',
616
+ handler: handler1
617
+ },
618
+ {
619
+ key: 'Escape',
620
+ handler: handler2
621
+ },
622
+ {
623
+ key: 'Escape',
624
+ handler: handler3
625
+ }
626
+ ];
627
+
628
+ renderHook(() => useKeyboardShortcuts(shortcuts));
629
+
630
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
631
+ document.dispatchEvent(event);
632
+
633
+ expect(handler1).toHaveBeenCalledTimes(1);
634
+ expect(handler2).not.toHaveBeenCalled();
635
+ expect(handler3).not.toHaveBeenCalled();
636
+ });
637
+
638
+ it('shortcuts with different keys do not interfere', () => {
639
+ const escapeHandler = vi.fn();
640
+ const enterHandler = vi.fn();
641
+ const spaceHandler = vi.fn();
642
+
643
+ const shortcuts = [
644
+ {
645
+ key: 'Escape',
646
+ handler: escapeHandler
647
+ },
648
+ {
649
+ key: 'Enter',
650
+ handler: enterHandler
651
+ },
652
+ {
653
+ key: ' ',
654
+ handler: spaceHandler
655
+ }
656
+ ];
657
+
658
+ renderHook(() => useKeyboardShortcuts(shortcuts));
659
+
660
+ // Test Escape
661
+ const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' });
662
+ document.dispatchEvent(escapeEvent);
663
+ expect(escapeHandler).toHaveBeenCalledTimes(1);
664
+ expect(enterHandler).not.toHaveBeenCalled();
665
+ expect(spaceHandler).not.toHaveBeenCalled();
666
+
667
+ // Test Enter
668
+ const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' });
669
+ document.dispatchEvent(enterEvent);
670
+ expect(enterHandler).toHaveBeenCalledTimes(1);
671
+ expect(escapeHandler).toHaveBeenCalledTimes(1); // Still 1
672
+
673
+ // Test Space
674
+ const spaceEvent = new KeyboardEvent('keydown', { key: ' ' });
675
+ document.dispatchEvent(spaceEvent);
676
+ expect(spaceHandler).toHaveBeenCalledTimes(1);
677
+ });
678
+ });
679
+
680
+ describe('useAccessibilityShortcuts Hook', () => {
681
+ it('creates shortcuts for all provided handlers', () => {
682
+ const onEscape = vi.fn();
683
+ const onEnter = vi.fn();
684
+ const onSpace = vi.fn();
685
+ const onArrowUp = vi.fn();
686
+ const onArrowDown = vi.fn();
687
+ const onArrowLeft = vi.fn();
688
+ const onArrowRight = vi.fn();
689
+ const onHome = vi.fn();
690
+ const onEnd = vi.fn();
691
+ const onTab = vi.fn();
692
+ const onShiftTab = vi.fn();
693
+
694
+ renderHook(() =>
695
+ useAccessibilityShortcuts({
696
+ onEscape,
697
+ onEnter,
698
+ onSpace,
699
+ onArrowUp,
700
+ onArrowDown,
701
+ onArrowLeft,
702
+ onArrowRight,
703
+ onHome,
704
+ onEnd,
705
+ onTab,
706
+ onShiftTab
707
+ })
708
+ );
709
+
710
+ // Test each handler
711
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
712
+ expect(onEscape).toHaveBeenCalledTimes(1);
713
+
714
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
715
+ expect(onEnter).toHaveBeenCalledTimes(1);
716
+
717
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }));
718
+ expect(onSpace).toHaveBeenCalledTimes(1);
719
+
720
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }));
721
+ expect(onArrowUp).toHaveBeenCalledTimes(1);
722
+
723
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
724
+ expect(onArrowDown).toHaveBeenCalledTimes(1);
725
+
726
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }));
727
+ expect(onArrowLeft).toHaveBeenCalledTimes(1);
728
+
729
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
730
+ expect(onArrowRight).toHaveBeenCalledTimes(1);
731
+
732
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home' }));
733
+ expect(onHome).toHaveBeenCalledTimes(1);
734
+
735
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'End' }));
736
+ expect(onEnd).toHaveBeenCalledTimes(1);
737
+
738
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
739
+ expect(onTab).toHaveBeenCalledTimes(1);
740
+
741
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true }));
742
+ expect(onShiftTab).toHaveBeenCalledTimes(1);
743
+ });
744
+
745
+ it('handles Escape key handler', () => {
746
+ const onEscape = vi.fn();
747
+
748
+ renderHook(() => useAccessibilityShortcuts({ onEscape }));
749
+
750
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
751
+ document.dispatchEvent(event);
752
+
753
+ expect(onEscape).toHaveBeenCalledTimes(1);
754
+ });
755
+
756
+ it('handles Enter key handler', () => {
757
+ const onEnter = vi.fn();
758
+
759
+ renderHook(() => useAccessibilityShortcuts({ onEnter }));
760
+
761
+ const event = new KeyboardEvent('keydown', { key: 'Enter' });
762
+ document.dispatchEvent(event);
763
+
764
+ expect(onEnter).toHaveBeenCalledTimes(1);
765
+ });
766
+
767
+ it('handles Space key handler with preventDefault', () => {
768
+ const onSpace = vi.fn();
769
+ const preventDefaultSpy = vi.fn();
770
+
771
+ renderHook(() => useAccessibilityShortcuts({ onSpace }));
772
+
773
+ const event = new KeyboardEvent('keydown', { key: ' ', cancelable: true });
774
+ event.preventDefault = preventDefaultSpy;
775
+ document.dispatchEvent(event);
776
+
777
+ expect(onSpace).toHaveBeenCalledTimes(1);
778
+ expect(preventDefaultSpy).toHaveBeenCalled();
779
+ });
780
+
781
+ it('handles Arrow key handlers', () => {
782
+ const onArrowUp = vi.fn();
783
+ const onArrowDown = vi.fn();
784
+ const onArrowLeft = vi.fn();
785
+ const onArrowRight = vi.fn();
786
+
787
+ renderHook(() =>
788
+ useAccessibilityShortcuts({
789
+ onArrowUp,
790
+ onArrowDown,
791
+ onArrowLeft,
792
+ onArrowRight
793
+ })
794
+ );
795
+
796
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }));
797
+ expect(onArrowUp).toHaveBeenCalledTimes(1);
798
+
799
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
800
+ expect(onArrowDown).toHaveBeenCalledTimes(1);
801
+
802
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }));
803
+ expect(onArrowLeft).toHaveBeenCalledTimes(1);
804
+
805
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
806
+ expect(onArrowRight).toHaveBeenCalledTimes(1);
807
+ });
808
+
809
+ it('handles Home and End keys', () => {
810
+ const onHome = vi.fn();
811
+ const onEnd = vi.fn();
812
+
813
+ renderHook(() => useAccessibilityShortcuts({ onHome, onEnd }));
814
+
815
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home' }));
816
+ expect(onHome).toHaveBeenCalledTimes(1);
817
+
818
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'End' }));
819
+ expect(onEnd).toHaveBeenCalledTimes(1);
820
+ });
821
+
822
+ it('handles Tab and Shift+Tab keys', () => {
823
+ const onTab = vi.fn();
824
+ const onShiftTab = vi.fn();
825
+
826
+ renderHook(() => useAccessibilityShortcuts({ onTab, onShiftTab }));
827
+
828
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
829
+ expect(onTab).toHaveBeenCalledTimes(1);
830
+
831
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true }));
832
+ expect(onShiftTab).toHaveBeenCalledTimes(1);
833
+ });
834
+
835
+ it('only creates shortcuts for provided handlers', () => {
836
+ const onEscape = vi.fn();
837
+ const onEnter = vi.fn();
838
+
839
+ renderHook(() => useAccessibilityShortcuts({ onEscape, onEnter }));
840
+
841
+ // Should only respond to Escape and Enter
842
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
843
+ expect(onEscape).toHaveBeenCalledTimes(1);
844
+
845
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
846
+ expect(onEnter).toHaveBeenCalledTimes(1);
847
+
848
+ // Should not respond to other keys
849
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Space' }));
850
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }));
851
+
852
+ // Only Escape and Enter should have been called
853
+ expect(onEscape).toHaveBeenCalledTimes(1);
854
+ expect(onEnter).toHaveBeenCalledTimes(1);
855
+ });
856
+ });
857
+
858
+ describe('Integration', () => {
859
+ it('useAccessibilityShortcuts integrates with useKeyboardShortcuts', () => {
860
+ const onEscape = vi.fn();
861
+ const customHandler = vi.fn();
862
+
863
+ // Use accessibility shortcuts
864
+ renderHook(() => useAccessibilityShortcuts({ onEscape }));
865
+
866
+ // Also use regular shortcuts
867
+ renderHook(() =>
868
+ useKeyboardShortcuts([
869
+ {
870
+ key: 'Enter',
871
+ handler: customHandler
872
+ }
873
+ ])
874
+ );
875
+
876
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
877
+ expect(onEscape).toHaveBeenCalledTimes(1);
878
+
879
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
880
+ expect(customHandler).toHaveBeenCalledTimes(1);
881
+ });
882
+
883
+ it('multiple accessibility shortcuts work together', () => {
884
+ const handlers1 = {
885
+ onEscape: vi.fn(),
886
+ onEnter: vi.fn()
887
+ };
888
+ const handlers2 = {
889
+ onSpace: vi.fn(),
890
+ onArrowUp: vi.fn()
891
+ };
892
+
893
+ renderHook(() => useAccessibilityShortcuts(handlers1));
894
+ renderHook(() => useAccessibilityShortcuts(handlers2));
895
+
896
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
897
+ expect(handlers1.onEscape).toHaveBeenCalledTimes(1);
898
+
899
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
900
+ expect(handlers1.onEnter).toHaveBeenCalledTimes(1);
901
+
902
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }));
903
+ expect(handlers2.onSpace).toHaveBeenCalledTimes(1);
904
+
905
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }));
906
+ expect(handlers2.onArrowUp).toHaveBeenCalledTimes(1);
907
+ });
908
+ });
294
909
  });
295
910