@fpkit/acss 0.5.13 → 0.6.0

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 (280) hide show
  1. package/libs/{chunk-PQ2K3BM6.cjs → chunk-2NRIP6RB.cjs} +3 -3
  2. package/libs/chunk-33PNJ4LO.cjs +15 -0
  3. package/libs/chunk-33PNJ4LO.cjs.map +1 -0
  4. package/libs/chunk-4BZKFPEC.cjs +17 -0
  5. package/libs/chunk-4BZKFPEC.cjs.map +1 -0
  6. package/libs/{chunk-772NRB75.js → chunk-5QD3DWFI.js} +2 -2
  7. package/libs/chunk-6SAHIYCZ.js +7 -0
  8. package/libs/chunk-6SAHIYCZ.js.map +1 -0
  9. package/libs/{chunk-3MKLDCKQ.cjs → chunk-6WTC4JXH.cjs} +3 -3
  10. package/libs/chunk-75QHTLFO.js +7 -0
  11. package/libs/chunk-75QHTLFO.js.map +1 -0
  12. package/libs/{chunk-ZANSFMTD.js → chunk-7XPFW7CB.js} +3 -3
  13. package/libs/chunk-BFK62VX5.js +5 -0
  14. package/libs/chunk-BFK62VX5.js.map +1 -0
  15. package/libs/{chunk-ROZI23GS.cjs → chunk-DKTHCQ5P.cjs} +4 -4
  16. package/libs/chunk-E2AJURUW.cjs +13 -0
  17. package/libs/chunk-E2AJURUW.cjs.map +1 -0
  18. package/libs/{chunk-L75OQKEI.cjs → chunk-ENTCUJ3A.cjs} +3 -3
  19. package/libs/chunk-ENTCUJ3A.cjs.map +1 -0
  20. package/libs/chunk-F5EYMVQM.js +10 -0
  21. package/libs/chunk-F5EYMVQM.js.map +1 -0
  22. package/libs/chunk-FVROL3V5.js +9 -0
  23. package/libs/chunk-FVROL3V5.js.map +1 -0
  24. package/libs/chunk-GT77BX4L.cjs +17 -0
  25. package/libs/chunk-GT77BX4L.cjs.map +1 -0
  26. package/libs/chunk-GUJSMQ3V.cjs +16 -0
  27. package/libs/chunk-GUJSMQ3V.cjs.map +1 -0
  28. package/libs/chunk-HHLNOC5T.js +7 -0
  29. package/libs/chunk-HHLNOC5T.js.map +1 -0
  30. package/libs/chunk-HRRHPLER.js +8 -0
  31. package/libs/chunk-HRRHPLER.js.map +1 -0
  32. package/libs/chunk-IEB64SWY.js +8 -0
  33. package/libs/chunk-IEB64SWY.js.map +1 -0
  34. package/libs/{chunk-NGTJDDFO.js → chunk-IQ76HGVP.js} +2 -2
  35. package/libs/chunk-IRLFZ3OL.js +9 -0
  36. package/libs/chunk-IRLFZ3OL.js.map +1 -0
  37. package/libs/{chunk-JJ43O4Y5.js → chunk-KK47SYZI.js} +2 -2
  38. package/libs/chunk-O3JIHC5M.cjs +15 -0
  39. package/libs/chunk-O3JIHC5M.cjs.map +1 -0
  40. package/libs/chunk-O5XAJ7BY.cjs +18 -0
  41. package/libs/chunk-O5XAJ7BY.cjs.map +1 -0
  42. package/libs/chunk-OVWLQYMK.js +10 -0
  43. package/libs/chunk-OVWLQYMK.js.map +1 -0
  44. package/libs/chunk-PNWIRCG3.cjs +7 -0
  45. package/libs/chunk-PNWIRCG3.cjs.map +1 -0
  46. package/libs/{chunk-D4YLRWAO.cjs → chunk-QVW6W76L.cjs} +6 -6
  47. package/libs/chunk-T4T6GWYQ.cjs +17 -0
  48. package/libs/chunk-T4T6GWYQ.cjs.map +1 -0
  49. package/libs/chunk-TON2YGMD.cjs +9 -0
  50. package/libs/chunk-TON2YGMD.cjs.map +1 -0
  51. package/libs/chunk-UEPAWMDF.js +8 -0
  52. package/libs/chunk-UEPAWMDF.js.map +1 -0
  53. package/libs/{chunk-LT5KZ2QW.cjs → chunk-US2I5GI7.cjs} +3 -3
  54. package/libs/{chunk-B7F5FS6D.cjs → chunk-W2UIN7EV.cjs} +3 -3
  55. package/libs/{chunk-P2DC76ZZ.cjs → chunk-W5TKWBFC.cjs} +3 -3
  56. package/libs/chunk-WXBFBWYF.cjs +16 -0
  57. package/libs/chunk-WXBFBWYF.cjs.map +1 -0
  58. package/libs/{chunk-VUH3FXGJ.js → chunk-X3JCTEPD.js} +5 -5
  59. package/libs/chunk-X5LGFCWG.js +9 -0
  60. package/libs/chunk-X5LGFCWG.js.map +1 -0
  61. package/libs/{chunk-5M57K4SW.js → chunk-Y2PFDELK.js} +2 -2
  62. package/libs/{chunk-ETFLFC2S.js → chunk-ZFJ4U45S.js} +2 -2
  63. package/libs/{component-props-a8a2f97e.d.ts → component-props-67d978a2.d.ts} +4 -4
  64. package/libs/components/alert/alert.css +1 -1
  65. package/libs/components/alert/alert.css.map +1 -1
  66. package/libs/components/alert/alert.min.css +2 -2
  67. package/libs/components/breadcrumbs/breadcrumb.cjs +6 -6
  68. package/libs/components/breadcrumbs/breadcrumb.d.cts +11 -11
  69. package/libs/components/breadcrumbs/breadcrumb.d.ts +11 -11
  70. package/libs/components/breadcrumbs/breadcrumb.js +3 -3
  71. package/libs/components/button.cjs +6 -4
  72. package/libs/components/button.d.cts +97 -4
  73. package/libs/components/button.d.ts +97 -4
  74. package/libs/components/button.js +4 -2
  75. package/libs/components/card.cjs +7 -7
  76. package/libs/components/card.d.cts +14 -14
  77. package/libs/components/card.d.ts +14 -14
  78. package/libs/components/card.js +2 -2
  79. package/libs/components/dialog/dialog.cjs +9 -7
  80. package/libs/components/dialog/dialog.d.cts +3 -3
  81. package/libs/components/dialog/dialog.d.ts +3 -3
  82. package/libs/components/dialog/dialog.js +7 -5
  83. package/libs/components/form/fields.cjs +4 -4
  84. package/libs/components/form/fields.d.cts +16 -7
  85. package/libs/components/form/fields.d.ts +16 -7
  86. package/libs/components/form/fields.js +2 -2
  87. package/libs/components/form/inputs.cjs +6 -4
  88. package/libs/components/form/inputs.d.cts +50 -2
  89. package/libs/components/form/inputs.d.ts +50 -2
  90. package/libs/components/form/inputs.js +4 -2
  91. package/libs/components/form/textarea.cjs +5 -4
  92. package/libs/components/form/textarea.d.cts +32 -23
  93. package/libs/components/form/textarea.d.ts +32 -23
  94. package/libs/components/form/textarea.js +3 -2
  95. package/libs/components/heading/heading.cjs +3 -3
  96. package/libs/components/heading/heading.d.cts +2 -2
  97. package/libs/components/heading/heading.d.ts +2 -2
  98. package/libs/components/heading/heading.js +2 -2
  99. package/libs/components/icons/icon.cjs +4 -4
  100. package/libs/components/icons/icon.d.cts +38 -38
  101. package/libs/components/icons/icon.d.ts +38 -38
  102. package/libs/components/icons/icon.js +2 -2
  103. package/libs/components/link/link.cjs +4 -4
  104. package/libs/components/link/link.css +1 -1
  105. package/libs/components/link/link.css.map +1 -1
  106. package/libs/components/link/link.d.cts +3 -19
  107. package/libs/components/link/link.d.ts +3 -19
  108. package/libs/components/link/link.js +2 -2
  109. package/libs/components/link/link.min.css +2 -2
  110. package/libs/components/list/list.cjs +5 -5
  111. package/libs/components/list/list.css +1 -0
  112. package/libs/components/list/list.css.map +1 -0
  113. package/libs/components/list/list.d.cts +120 -33
  114. package/libs/components/list/list.d.ts +120 -33
  115. package/libs/components/list/list.js +2 -2
  116. package/libs/components/list/list.min.css +3 -0
  117. package/libs/components/modal.cjs +6 -4
  118. package/libs/components/modal.d.cts +8 -8
  119. package/libs/components/modal.d.ts +8 -8
  120. package/libs/components/modal.js +5 -3
  121. package/libs/components/nav/nav.cjs +7 -7
  122. package/libs/components/nav/nav.css +1 -1
  123. package/libs/components/nav/nav.css.map +1 -1
  124. package/libs/components/nav/nav.d.cts +550 -34
  125. package/libs/components/nav/nav.d.ts +550 -34
  126. package/libs/components/nav/nav.js +3 -3
  127. package/libs/components/nav/nav.min.css +2 -2
  128. package/libs/components/popover/popover.d.cts +5 -5
  129. package/libs/components/popover/popover.d.ts +5 -5
  130. package/libs/components/tables/table.cjs +5 -5
  131. package/libs/components/tables/table.d.cts +8 -8
  132. package/libs/components/tables/table.d.ts +8 -8
  133. package/libs/components/tables/table.js +2 -2
  134. package/libs/components/tag/tag.css +1 -1
  135. package/libs/components/tag/tag.css.map +1 -1
  136. package/libs/components/tag/tag.min.css +2 -2
  137. package/libs/components/text/text.cjs +5 -5
  138. package/libs/components/text/text.d.cts +5 -5
  139. package/libs/components/text/text.d.ts +5 -5
  140. package/libs/components/text/text.js +2 -2
  141. package/libs/form.types-d25ebfac.d.ts +233 -0
  142. package/libs/{heading-3648c538.d.ts → heading-7446cb46.d.ts} +8 -8
  143. package/libs/hooks.cjs +9 -4
  144. package/libs/hooks.d.cts +137 -3
  145. package/libs/hooks.d.ts +137 -3
  146. package/libs/hooks.js +4 -3
  147. package/libs/icons.cjs +3 -3
  148. package/libs/icons.d.cts +2 -2
  149. package/libs/icons.d.ts +2 -2
  150. package/libs/icons.js +2 -2
  151. package/libs/index.cjs +53 -51
  152. package/libs/index.cjs.map +1 -1
  153. package/libs/index.css +1 -1
  154. package/libs/index.css.map +1 -1
  155. package/libs/index.d.cts +338 -49
  156. package/libs/index.d.ts +338 -49
  157. package/libs/index.js +24 -22
  158. package/libs/index.js.map +1 -1
  159. package/libs/link-5192f411.d.ts +323 -0
  160. package/libs/list.types-d26de310.d.ts +245 -0
  161. package/libs/{ui-645f95b5.d.ts → ui-d01b50d4.d.ts} +16 -12
  162. package/package.json +4 -6
  163. package/src/components/alert/alert.scss +1 -4
  164. package/src/components/breadcrumbs/breadcrumb.tsx +4 -1
  165. package/src/components/buttons/README.mdx +102 -1
  166. package/src/components/buttons/button.stories.tsx +106 -0
  167. package/src/components/buttons/button.tsx +82 -52
  168. package/src/components/dialog/dialog-a11y-review.md +653 -0
  169. package/src/components/form/README.mdx +725 -43
  170. package/src/components/form/WCAG-REVIEW.md +654 -0
  171. package/src/components/form/fields.tsx +10 -1
  172. package/src/components/form/form.stories.tsx +604 -23
  173. package/src/components/form/form.tsx +204 -63
  174. package/src/components/form/form.types.ts +378 -0
  175. package/src/components/form/input.stories.tsx +71 -3
  176. package/src/components/form/inputs.tsx +159 -67
  177. package/src/components/form/select.tsx +122 -66
  178. package/src/components/form/textarea.tsx +120 -73
  179. package/src/components/fp.tsx +86 -11
  180. package/src/components/link/README.mdx +923 -0
  181. package/src/components/link/link.scss +79 -26
  182. package/src/components/link/link.stories.tsx +383 -30
  183. package/src/components/link/link.test.tsx +677 -0
  184. package/src/components/link/link.tsx +163 -57
  185. package/src/components/link/link.types.ts +261 -0
  186. package/src/components/list/README.mdx +764 -0
  187. package/src/components/list/list.scss +285 -0
  188. package/src/components/list/list.stories.tsx +514 -27
  189. package/src/components/list/list.test.tsx +554 -0
  190. package/src/components/list/list.tsx +153 -51
  191. package/src/components/list/list.types.ts +255 -0
  192. package/src/components/nav/ACCESSIBILITY.md +649 -0
  193. package/src/components/nav/README.mdx +782 -0
  194. package/src/components/nav/nav.scss +32 -1
  195. package/src/components/nav/nav.stories.tsx +44 -6
  196. package/src/components/nav/nav.tsx +302 -51
  197. package/src/components/nav/nav.types.ts +308 -0
  198. package/src/components/tag/README.mdx +426 -0
  199. package/src/components/tag/tag.scss +101 -27
  200. package/src/components/tag/tag.stories.tsx +384 -10
  201. package/src/components/tag/tag.test.tsx +210 -0
  202. package/src/components/tag/tag.tsx +106 -9
  203. package/src/components/tag/tag.types.ts +107 -0
  204. package/src/components/ui.tsx +8 -3
  205. package/src/hooks/use-disabled-state.test.tsx +536 -0
  206. package/src/hooks/use-disabled-state.ts +246 -0
  207. package/src/hooks/useDisabledState.md +393 -0
  208. package/src/hooks.ts +6 -0
  209. package/src/index.scss +2 -0
  210. package/src/index.ts +2 -1
  211. package/src/sass/_globals.scss +2 -7
  212. package/src/styles/alert/alert.css +1 -3
  213. package/src/styles/alert/alert.css.map +1 -1
  214. package/src/styles/index.css +450 -76
  215. package/src/styles/index.css.map +1 -1
  216. package/src/styles/link/link.css +45 -28
  217. package/src/styles/link/link.css.map +1 -1
  218. package/src/styles/list/list.css +214 -0
  219. package/src/styles/list/list.css.map +1 -0
  220. package/src/styles/nav/nav.css +21 -1
  221. package/src/styles/nav/nav.css.map +1 -1
  222. package/src/styles/tag/tag.css +113 -35
  223. package/src/styles/tag/tag.css.map +1 -1
  224. package/src/styles/utilities/_disabled.scss +58 -0
  225. package/src/types/shared.ts +43 -6
  226. package/src/utils/accessibility.ts +109 -0
  227. package/libs/chunk-2LTJ7HHX.cjs +0 -18
  228. package/libs/chunk-2LTJ7HHX.cjs.map +0 -1
  229. package/libs/chunk-2Y7W75TT.js +0 -9
  230. package/libs/chunk-2Y7W75TT.js.map +0 -1
  231. package/libs/chunk-5S4ORA4C.cjs +0 -15
  232. package/libs/chunk-5S4ORA4C.cjs.map +0 -1
  233. package/libs/chunk-AHDJGCG5.cjs +0 -15
  234. package/libs/chunk-AHDJGCG5.cjs.map +0 -1
  235. package/libs/chunk-BHRQBJRY.js +0 -8
  236. package/libs/chunk-BHRQBJRY.js.map +0 -1
  237. package/libs/chunk-GZ4QFPRY.js +0 -9
  238. package/libs/chunk-GZ4QFPRY.js.map +0 -1
  239. package/libs/chunk-IYUN2EW3.cjs +0 -15
  240. package/libs/chunk-IYUN2EW3.cjs.map +0 -1
  241. package/libs/chunk-J32EZPYD.cjs +0 -15
  242. package/libs/chunk-J32EZPYD.cjs.map +0 -1
  243. package/libs/chunk-KUKIVRC2.js +0 -7
  244. package/libs/chunk-KUKIVRC2.js.map +0 -1
  245. package/libs/chunk-L75OQKEI.cjs.map +0 -1
  246. package/libs/chunk-M5RRNTVX.cjs +0 -15
  247. package/libs/chunk-M5RRNTVX.cjs.map +0 -1
  248. package/libs/chunk-OK5QEIMD.cjs +0 -17
  249. package/libs/chunk-OK5QEIMD.cjs.map +0 -1
  250. package/libs/chunk-P7TTEYCD.js +0 -7
  251. package/libs/chunk-P7TTEYCD.js.map +0 -1
  252. package/libs/chunk-QLZWHAMK.js +0 -8
  253. package/libs/chunk-QLZWHAMK.js.map +0 -1
  254. package/libs/chunk-RIVUMPOG.js +0 -8
  255. package/libs/chunk-RIVUMPOG.js.map +0 -1
  256. package/libs/chunk-S7BABR7Z.cjs +0 -13
  257. package/libs/chunk-S7BABR7Z.cjs.map +0 -1
  258. package/libs/chunk-SMYRLO3E.js +0 -8
  259. package/libs/chunk-SMYRLO3E.js.map +0 -1
  260. package/libs/chunk-TYRCEX2L.js +0 -8
  261. package/libs/chunk-TYRCEX2L.js.map +0 -1
  262. package/libs/chunk-XBA562WW.js +0 -8
  263. package/libs/chunk-XBA562WW.js.map +0 -1
  264. package/libs/chunk-XTQKWY7W.cjs +0 -32
  265. package/libs/chunk-XTQKWY7W.cjs.map +0 -1
  266. package/libs/inputs-f3a216db.d.ts +0 -45
  267. /package/libs/{chunk-PQ2K3BM6.cjs.map → chunk-2NRIP6RB.cjs.map} +0 -0
  268. /package/libs/{chunk-772NRB75.js.map → chunk-5QD3DWFI.js.map} +0 -0
  269. /package/libs/{chunk-3MKLDCKQ.cjs.map → chunk-6WTC4JXH.cjs.map} +0 -0
  270. /package/libs/{chunk-ZANSFMTD.js.map → chunk-7XPFW7CB.js.map} +0 -0
  271. /package/libs/{chunk-ROZI23GS.cjs.map → chunk-DKTHCQ5P.cjs.map} +0 -0
  272. /package/libs/{chunk-NGTJDDFO.js.map → chunk-IQ76HGVP.js.map} +0 -0
  273. /package/libs/{chunk-JJ43O4Y5.js.map → chunk-KK47SYZI.js.map} +0 -0
  274. /package/libs/{chunk-D4YLRWAO.cjs.map → chunk-QVW6W76L.cjs.map} +0 -0
  275. /package/libs/{chunk-LT5KZ2QW.cjs.map → chunk-US2I5GI7.cjs.map} +0 -0
  276. /package/libs/{chunk-B7F5FS6D.cjs.map → chunk-W2UIN7EV.cjs.map} +0 -0
  277. /package/libs/{chunk-P2DC76ZZ.cjs.map → chunk-W5TKWBFC.cjs.map} +0 -0
  278. /package/libs/{chunk-VUH3FXGJ.js.map → chunk-X3JCTEPD.js.map} +0 -0
  279. /package/libs/{chunk-5M57K4SW.js.map → chunk-Y2PFDELK.js.map} +0 -0
  280. /package/libs/{chunk-ETFLFC2S.js.map → chunk-ZFJ4U45S.js.map} +0 -0
@@ -0,0 +1,536 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { renderHook } from '@testing-library/react';
3
+ import { useDisabledState } from './use-disabled-state';
4
+
5
+ describe('useDisabledState', () => {
6
+ describe('Basic Functionality', () => {
7
+ it('should return aria-disabled false when not disabled', () => {
8
+ const { result } = renderHook(() => useDisabledState(false, {}));
9
+
10
+ expect(result.current.disabledProps['aria-disabled']).toBe(false);
11
+ });
12
+
13
+ it('should return aria-disabled true when disabled', () => {
14
+ const { result } = renderHook(() => useDisabledState(true, {}));
15
+
16
+ expect(result.current.disabledProps['aria-disabled']).toBe(true);
17
+ });
18
+
19
+ it('should treat undefined as not disabled', () => {
20
+ const { result } = renderHook(() => useDisabledState(undefined, {}));
21
+
22
+ expect(result.current.disabledProps['aria-disabled']).toBe(false);
23
+ });
24
+
25
+ it('should add is-disabled className when disabled', () => {
26
+ const { result } = renderHook(() => useDisabledState(true, {}));
27
+
28
+ expect(result.current.disabledProps.className).toBe('is-disabled');
29
+ });
30
+
31
+ it('should return empty className when not disabled', () => {
32
+ const { result } = renderHook(() => useDisabledState(false, {}));
33
+
34
+ expect(result.current.disabledProps.className).toBe('');
35
+ });
36
+ });
37
+
38
+ describe('Event Handler Wrapping - Legacy API', () => {
39
+ it('should wrap onClick handler and prevent execution when disabled', () => {
40
+ const onClick = vi.fn();
41
+ const { result } = renderHook(() =>
42
+ useDisabledState<HTMLButtonElement>(true, { onClick })
43
+ );
44
+
45
+ const mockEvent = {
46
+ preventDefault: vi.fn(),
47
+ stopPropagation: vi.fn(),
48
+ } as unknown as React.MouseEvent<HTMLButtonElement>;
49
+
50
+ result.current.handlers.onClick?.(mockEvent);
51
+
52
+ expect(onClick).not.toHaveBeenCalled();
53
+ expect(mockEvent.preventDefault).toHaveBeenCalled();
54
+ expect(mockEvent.stopPropagation).toHaveBeenCalled();
55
+ });
56
+
57
+ it('should allow onClick handler execution when not disabled', () => {
58
+ const onClick = vi.fn();
59
+ const { result } = renderHook(() =>
60
+ useDisabledState<HTMLButtonElement>(false, { onClick })
61
+ );
62
+
63
+ const mockEvent = {
64
+ preventDefault: vi.fn(),
65
+ stopPropagation: vi.fn(),
66
+ } as unknown as React.MouseEvent<HTMLButtonElement>;
67
+
68
+ result.current.handlers.onClick?.(mockEvent);
69
+
70
+ expect(onClick).toHaveBeenCalledWith(mockEvent);
71
+ expect(mockEvent.preventDefault).not.toHaveBeenCalled();
72
+ expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
73
+ });
74
+
75
+ it('should wrap onChange handler', () => {
76
+ const onChange = vi.fn();
77
+ const { result } = renderHook(() =>
78
+ useDisabledState<HTMLInputElement>(true, { onChange })
79
+ );
80
+
81
+ const mockEvent = {
82
+ preventDefault: vi.fn(),
83
+ stopPropagation: vi.fn(),
84
+ } as unknown as React.ChangeEvent<HTMLInputElement>;
85
+
86
+ result.current.handlers.onChange?.(mockEvent);
87
+
88
+ expect(onChange).not.toHaveBeenCalled();
89
+ expect(mockEvent.preventDefault).toHaveBeenCalled();
90
+ });
91
+
92
+ it('should wrap onKeyDown handler', () => {
93
+ const onKeyDown = vi.fn();
94
+ const { result } = renderHook(() =>
95
+ useDisabledState<HTMLButtonElement>(true, { onKeyDown })
96
+ );
97
+
98
+ const mockEvent = {
99
+ preventDefault: vi.fn(),
100
+ stopPropagation: vi.fn(),
101
+ } as unknown as React.KeyboardEvent<HTMLButtonElement>;
102
+
103
+ result.current.handlers.onKeyDown?.(mockEvent);
104
+
105
+ expect(onKeyDown).not.toHaveBeenCalled();
106
+ });
107
+
108
+ it('should wrap multiple handlers', () => {
109
+ const onClick = vi.fn();
110
+ const onKeyDown = vi.fn();
111
+ const onMouseDown = vi.fn();
112
+
113
+ const { result } = renderHook(() =>
114
+ useDisabledState<HTMLButtonElement>(true, {
115
+ onClick,
116
+ onKeyDown,
117
+ onMouseDown,
118
+ })
119
+ );
120
+
121
+ expect(result.current.handlers.onClick).toBeDefined();
122
+ expect(result.current.handlers.onKeyDown).toBeDefined();
123
+ expect(result.current.handlers.onMouseDown).toBeDefined();
124
+ });
125
+
126
+ it('should only include provided handlers', () => {
127
+ const onClick = vi.fn();
128
+
129
+ const { result } = renderHook(() =>
130
+ useDisabledState<HTMLButtonElement>(false, { onClick })
131
+ );
132
+
133
+ expect(result.current.handlers.onClick).toBeDefined();
134
+ expect(result.current.handlers.onChange).toBeUndefined();
135
+ expect(result.current.handlers.onKeyDown).toBeUndefined();
136
+ });
137
+ });
138
+
139
+ describe('onFocus Special Behavior', () => {
140
+ it('should always allow onFocus handler even when disabled', () => {
141
+ const onFocus = vi.fn();
142
+ const { result } = renderHook(() =>
143
+ useDisabledState<HTMLInputElement>(true, { onFocus })
144
+ );
145
+
146
+ const mockEvent = {
147
+ preventDefault: vi.fn(),
148
+ stopPropagation: vi.fn(),
149
+ } as unknown as React.FocusEvent<HTMLInputElement>;
150
+
151
+ result.current.handlers.onFocus?.(mockEvent);
152
+
153
+ // onFocus should be called even when disabled (for accessibility)
154
+ expect(onFocus).toHaveBeenCalledWith(mockEvent);
155
+ expect(mockEvent.preventDefault).not.toHaveBeenCalled();
156
+ expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
157
+ });
158
+ });
159
+
160
+ describe('Enhanced API - Configuration Options', () => {
161
+ it('should support new API with handlers property', () => {
162
+ const onClick = vi.fn();
163
+ const { result } = renderHook(() =>
164
+ useDisabledState<HTMLButtonElement>(true, {
165
+ handlers: { onClick },
166
+ })
167
+ );
168
+
169
+ expect(result.current.handlers.onClick).toBeDefined();
170
+ });
171
+
172
+ it('should merge className with disabled class', () => {
173
+ const { result } = renderHook(() =>
174
+ useDisabledState(true, {
175
+ className: 'my-button',
176
+ })
177
+ );
178
+
179
+ expect(result.current.disabledProps.className).toBe('is-disabled my-button');
180
+ });
181
+
182
+ it('should not add disabled class when not disabled', () => {
183
+ const { result } = renderHook(() =>
184
+ useDisabledState(false, {
185
+ className: 'my-button',
186
+ })
187
+ );
188
+
189
+ expect(result.current.disabledProps.className).toBe('my-button');
190
+ });
191
+
192
+ it('should support custom disabled className', () => {
193
+ const { result } = renderHook(() =>
194
+ useDisabledState(true, {
195
+ disabledClassName: 'custom-disabled',
196
+ })
197
+ );
198
+
199
+ expect(result.current.disabledProps.className).toBe('custom-disabled');
200
+ });
201
+
202
+ it('should merge custom disabled className with existing className', () => {
203
+ const { result } = renderHook(() =>
204
+ useDisabledState(true, {
205
+ className: 'my-button',
206
+ disabledClassName: 'custom-disabled',
207
+ })
208
+ );
209
+
210
+ expect(result.current.disabledProps.className).toBe('custom-disabled my-button');
211
+ });
212
+
213
+ it('should add tabIndex=-1 when removeFromTabOrder is true and disabled', () => {
214
+ const { result } = renderHook(() =>
215
+ useDisabledState(true, {
216
+ removeFromTabOrder: true,
217
+ })
218
+ );
219
+
220
+ expect(result.current.disabledProps.tabIndex).toBe(-1);
221
+ });
222
+
223
+ it('should not add tabIndex when removeFromTabOrder is true but not disabled', () => {
224
+ const { result } = renderHook(() =>
225
+ useDisabledState(false, {
226
+ removeFromTabOrder: true,
227
+ })
228
+ );
229
+
230
+ expect(result.current.disabledProps.tabIndex).toBeUndefined();
231
+ });
232
+
233
+ it('should not add tabIndex when removeFromTabOrder is false', () => {
234
+ const { result } = renderHook(() =>
235
+ useDisabledState(true, {
236
+ removeFromTabOrder: false,
237
+ })
238
+ );
239
+
240
+ expect(result.current.disabledProps.tabIndex).toBeUndefined();
241
+ });
242
+
243
+ it('should respect preventDefault option when false', () => {
244
+ const onClick = vi.fn();
245
+ const { result } = renderHook(() =>
246
+ useDisabledState<HTMLButtonElement>(true, {
247
+ handlers: { onClick },
248
+ preventDefault: false,
249
+ })
250
+ );
251
+
252
+ const mockEvent = {
253
+ preventDefault: vi.fn(),
254
+ stopPropagation: vi.fn(),
255
+ } as unknown as React.MouseEvent<HTMLButtonElement>;
256
+
257
+ result.current.handlers.onClick?.(mockEvent);
258
+
259
+ expect(onClick).not.toHaveBeenCalled();
260
+ expect(mockEvent.preventDefault).not.toHaveBeenCalled();
261
+ expect(mockEvent.stopPropagation).toHaveBeenCalled(); // Still called by default
262
+ });
263
+
264
+ it('should respect stopPropagation option when false', () => {
265
+ const onClick = vi.fn();
266
+ const { result } = renderHook(() =>
267
+ useDisabledState<HTMLButtonElement>(true, {
268
+ handlers: { onClick },
269
+ stopPropagation: false,
270
+ })
271
+ );
272
+
273
+ const mockEvent = {
274
+ preventDefault: vi.fn(),
275
+ stopPropagation: vi.fn(),
276
+ } as unknown as React.MouseEvent<HTMLButtonElement>;
277
+
278
+ result.current.handlers.onClick?.(mockEvent);
279
+
280
+ expect(onClick).not.toHaveBeenCalled();
281
+ expect(mockEvent.preventDefault).toHaveBeenCalled(); // Still called by default
282
+ expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
283
+ });
284
+
285
+ it('should support both preventDefault and stopPropagation as false', () => {
286
+ const onClick = vi.fn();
287
+ const { result } = renderHook(() =>
288
+ useDisabledState<HTMLButtonElement>(true, {
289
+ handlers: { onClick },
290
+ preventDefault: false,
291
+ stopPropagation: false,
292
+ })
293
+ );
294
+
295
+ const mockEvent = {
296
+ preventDefault: vi.fn(),
297
+ stopPropagation: vi.fn(),
298
+ } as unknown as React.MouseEvent<HTMLButtonElement>;
299
+
300
+ result.current.handlers.onClick?.(mockEvent);
301
+
302
+ expect(onClick).not.toHaveBeenCalled();
303
+ expect(mockEvent.preventDefault).not.toHaveBeenCalled();
304
+ expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
305
+ });
306
+ });
307
+
308
+ describe('State Changes and Re-renders', () => {
309
+ it('should update when disabled state changes', () => {
310
+ const { result, rerender } = renderHook(
311
+ ({ disabled }) => useDisabledState(disabled, {}),
312
+ { initialProps: { disabled: false } }
313
+ );
314
+
315
+ expect(result.current.disabledProps['aria-disabled']).toBe(false);
316
+ expect(result.current.disabledProps.className).toBe('');
317
+
318
+ rerender({ disabled: true });
319
+
320
+ expect(result.current.disabledProps['aria-disabled']).toBe(true);
321
+ expect(result.current.disabledProps.className).toBe('is-disabled');
322
+ });
323
+
324
+ it('should update wrapped handlers when disabled state toggles', () => {
325
+ const onClick = vi.fn();
326
+ const { result, rerender } = renderHook(
327
+ ({ disabled }) =>
328
+ useDisabledState<HTMLButtonElement>(disabled, { onClick }),
329
+ { initialProps: { disabled: false } }
330
+ );
331
+
332
+ const mockPreventDefault = vi.fn();
333
+ const mockEvent = {
334
+ preventDefault: mockPreventDefault,
335
+ stopPropagation: vi.fn(),
336
+ } as unknown as React.MouseEvent<HTMLButtonElement>;
337
+
338
+ // Not disabled - handler should execute
339
+ result.current.handlers.onClick?.(mockEvent);
340
+ expect(onClick).toHaveBeenCalledTimes(1);
341
+
342
+ // Become disabled
343
+ rerender({ disabled: true });
344
+
345
+ // Reset mock
346
+ onClick.mockClear();
347
+ mockPreventDefault.mockClear();
348
+
349
+ // Disabled - handler should not execute
350
+ result.current.handlers.onClick?.(mockEvent);
351
+ expect(onClick).not.toHaveBeenCalled();
352
+ expect(mockPreventDefault).toHaveBeenCalled();
353
+ });
354
+
355
+ it('should use latest handler via ref when handler changes', () => {
356
+ const onClick1 = vi.fn();
357
+ const onClick2 = vi.fn();
358
+
359
+ const { result, rerender } = renderHook(
360
+ ({ onClick }) =>
361
+ useDisabledState<HTMLButtonElement>(false, { onClick }),
362
+ { initialProps: { onClick: onClick1 } }
363
+ );
364
+
365
+ // Call with first handler
366
+ const mockEvent1 = {} as React.MouseEvent<HTMLButtonElement>;
367
+ result.current.handlers.onClick?.(mockEvent1);
368
+ expect(onClick1).toHaveBeenCalledWith(mockEvent1);
369
+ expect(onClick2).not.toHaveBeenCalled();
370
+
371
+ // Reset mock
372
+ vi.clearAllMocks();
373
+
374
+ // Rerender with different handler function
375
+ rerender({ onClick: onClick2 });
376
+
377
+ // Call with second handler - should call onClick2 via ref
378
+ const mockEvent2 = {} as React.MouseEvent<HTMLButtonElement>;
379
+ result.current.handlers.onClick?.(mockEvent2);
380
+
381
+ // The new handler should be called (from ref), not the old one
382
+ expect(onClick2).toHaveBeenCalledWith(mockEvent2);
383
+ expect(onClick1).not.toHaveBeenCalled();
384
+ });
385
+ });
386
+
387
+ describe('Backward Compatibility', () => {
388
+ it('should work with legacy API (handlers directly)', () => {
389
+ const onClick = vi.fn();
390
+ const onChange = vi.fn();
391
+
392
+ const { result } = renderHook(() =>
393
+ useDisabledState<HTMLInputElement>(true, {
394
+ onClick,
395
+ onChange,
396
+ })
397
+ );
398
+
399
+ expect(result.current.handlers.onClick).toBeDefined();
400
+ expect(result.current.handlers.onChange).toBeDefined();
401
+ expect(result.current.disabledProps['aria-disabled']).toBe(true);
402
+ });
403
+
404
+ it('should distinguish between legacy and new API', () => {
405
+ const onClickLegacy = vi.fn();
406
+ const onClickNew = vi.fn();
407
+
408
+ // Legacy API
409
+ const { result: legacyResult } = renderHook(() =>
410
+ useDisabledState<HTMLButtonElement>(false, {
411
+ onClick: onClickLegacy,
412
+ })
413
+ );
414
+
415
+ // New API
416
+ const { result: newResult } = renderHook(() =>
417
+ useDisabledState<HTMLButtonElement>(false, {
418
+ handlers: { onClick: onClickNew },
419
+ className: 'my-class',
420
+ })
421
+ );
422
+
423
+ expect(legacyResult.current.handlers.onClick).toBeDefined();
424
+ expect(legacyResult.current.disabledProps.className).toBe('');
425
+
426
+ expect(newResult.current.handlers.onClick).toBeDefined();
427
+ expect(newResult.current.disabledProps.className).toBe('my-class');
428
+ });
429
+ });
430
+
431
+ describe('Edge Cases', () => {
432
+ it('should handle empty handlers object', () => {
433
+ const { result } = renderHook(() => useDisabledState(true, {}));
434
+
435
+ expect(result.current.handlers).toEqual({});
436
+ expect(result.current.disabledProps['aria-disabled']).toBe(true);
437
+ });
438
+
439
+ it('should handle undefined handlers', () => {
440
+ const { result } = renderHook(() =>
441
+ useDisabledState<HTMLButtonElement>(false, {
442
+ onClick: undefined,
443
+ onChange: undefined,
444
+ })
445
+ );
446
+
447
+ expect(result.current.handlers.onClick).toBeUndefined();
448
+ expect(result.current.handlers.onChange).toBeUndefined();
449
+ });
450
+
451
+ it('should handle empty className', () => {
452
+ const { result } = renderHook(() =>
453
+ useDisabledState(false, {
454
+ className: '',
455
+ })
456
+ );
457
+
458
+ expect(result.current.disabledProps.className).toBe('');
459
+ });
460
+
461
+ it('should trim and clean className properly', () => {
462
+ const { result } = renderHook(() =>
463
+ useDisabledState(true, {
464
+ className: ' my-class ',
465
+ })
466
+ );
467
+
468
+ expect(result.current.disabledProps.className).toBe('is-disabled my-class');
469
+ });
470
+
471
+ it('should handle all event types', () => {
472
+ const handlers = {
473
+ onClick: vi.fn(),
474
+ onChange: vi.fn(),
475
+ onBlur: vi.fn(),
476
+ onFocus: vi.fn(),
477
+ onPointerDown: vi.fn(),
478
+ onKeyDown: vi.fn(),
479
+ onKeyUp: vi.fn(),
480
+ onMouseDown: vi.fn(),
481
+ onMouseUp: vi.fn(),
482
+ onTouchStart: vi.fn(),
483
+ onTouchEnd: vi.fn(),
484
+ };
485
+
486
+ const { result } = renderHook(() =>
487
+ useDisabledState<HTMLButtonElement>(false, handlers)
488
+ );
489
+
490
+ // All handlers should be wrapped
491
+ expect(Object.keys(result.current.handlers).length).toBe(11);
492
+ expect(result.current.handlers.onClick).toBeDefined();
493
+ expect(result.current.handlers.onChange).toBeDefined();
494
+ expect(result.current.handlers.onBlur).toBeDefined();
495
+ expect(result.current.handlers.onFocus).toBeDefined();
496
+ expect(result.current.handlers.onPointerDown).toBeDefined();
497
+ expect(result.current.handlers.onKeyDown).toBeDefined();
498
+ expect(result.current.handlers.onKeyUp).toBeDefined();
499
+ expect(result.current.handlers.onMouseDown).toBeDefined();
500
+ expect(result.current.handlers.onMouseUp).toBeDefined();
501
+ expect(result.current.handlers.onTouchStart).toBeDefined();
502
+ expect(result.current.handlers.onTouchEnd).toBeDefined();
503
+ });
504
+ });
505
+
506
+ describe('TypeScript Types', () => {
507
+ it('should support generic element types', () => {
508
+ // Button element
509
+ const { result: buttonResult } = renderHook(() =>
510
+ useDisabledState<HTMLButtonElement>(false, {
511
+ onClick: () => {},
512
+ })
513
+ );
514
+
515
+ expect(buttonResult.current.handlers.onClick).toBeDefined();
516
+
517
+ // Input element
518
+ const { result: inputResult } = renderHook(() =>
519
+ useDisabledState<HTMLInputElement>(false, {
520
+ onChange: () => {},
521
+ })
522
+ );
523
+
524
+ expect(inputResult.current.handlers.onChange).toBeDefined();
525
+
526
+ // Textarea element
527
+ const { result: textareaResult } = renderHook(() =>
528
+ useDisabledState<HTMLTextAreaElement>(false, {
529
+ onChange: () => {},
530
+ })
531
+ );
532
+
533
+ expect(textareaResult.current.handlers.onChange).toBeDefined();
534
+ });
535
+ });
536
+ });