@ryuzaki13/react-foundation-ui 1.8.5 → 1.8.7

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 (312) hide show
  1. package/dist/advanced-search/index.js +2 -2
  2. package/dist/base-table/index.js +1 -1
  3. package/dist/chunks/{base-table-CsnUiMTe.js → base-table-CYbtifH0.js} +2 -2
  4. package/dist/chunks/{base-table-CsnUiMTe.js.map → base-table-CYbtifH0.js.map} +1 -1
  5. package/dist/chunks/{context-menu-C8sA7xCN.js → context-menu-L4KM0FRD.js} +2 -2
  6. package/dist/chunks/{context-menu-C8sA7xCN.js.map → context-menu-L4KM0FRD.js.map} +1 -1
  7. package/dist/chunks/date-input-g6l1iDmf.js.map +1 -1
  8. package/dist/chunks/{multi-select-Cvjz9W07.js → multi-select-CCgoTHWI.js} +2 -2
  9. package/dist/chunks/{multi-select-Cvjz9W07.js.map → multi-select-CCgoTHWI.js.map} +1 -1
  10. package/dist/chunks/{picker-0I1yMKIY.js → picker-Cb3D2Hts.js} +6 -2
  11. package/dist/chunks/picker-Cb3D2Hts.js.map +1 -0
  12. package/dist/chunks/{select-CUQ91FIc.js → select-Baek-IUe.js} +2 -2
  13. package/dist/chunks/{select-CUQ91FIc.js.map → select-Baek-IUe.js.map} +1 -1
  14. package/dist/chunks/{table-z9kyd9U3.js → table-CrQiurtA.js} +2 -2
  15. package/dist/chunks/{table-z9kyd9U3.js.map → table-CrQiurtA.js.map} +1 -1
  16. package/dist/context-menu/index.js +1 -1
  17. package/dist/layout-picker/index.js +1 -1
  18. package/dist/listbox/index.js +1 -1
  19. package/dist/multi-select/index.js +1 -1
  20. package/dist/picker/index.js +1 -1
  21. package/dist/preset-select/index.js +1 -1
  22. package/dist/select/index.js +1 -1
  23. package/dist/simple-table/index.js +1 -1
  24. package/dist/slider/index.js +1 -1
  25. package/dist/state-select/index.js +1 -1
  26. package/dist/styles.css +1 -1
  27. package/dist/table/index.js +1 -1
  28. package/dist/table-sorting/index.js +1 -1
  29. package/dist/tree-select/index.js +1 -1
  30. package/dist/tree-table/index.js +1 -1
  31. package/package.json +4 -4
  32. package/storybook-static/assets/{Badge-CzxxuE7D.js → Badge-C4U4sxtF.js} +1 -1
  33. package/storybook-static/assets/{Badge.stories-RaI7qvpZ.js → Badge.stories-DHmJTpdf.js} +1 -1
  34. package/storybook-static/assets/{BadgeList-B7cF3h_F.js → BadgeList-BOs5ycWn.js} +1 -1
  35. package/storybook-static/assets/{Button-Cjmm8oDV.js → Button-BTtYeCS_.js} +1 -1
  36. package/storybook-static/assets/{Button-6z1R7ZA8.js → Button-Bkb3wQw6.js} +1 -1
  37. package/storybook-static/assets/{Button.stories-B5fUA2Gu.js → Button.stories-DgFLAdcg.js} +1 -1
  38. package/storybook-static/assets/{CalendarView-Dlt0f4Jr.js → CalendarView-BIFHbOTr.js} +1 -1
  39. package/storybook-static/assets/{CheckBox-L4324ZCs.js → CheckBox-76M-N56J.js} +1 -1
  40. package/storybook-static/assets/{CheckBox-BOSDcvCd.js → CheckBox-D4u-58g8.js} +1 -1
  41. package/storybook-static/assets/{CheckBox.module-CoYygdZx.js → CheckBox.module-BOAVN-Ny.js} +1 -1
  42. package/storybook-static/assets/{CheckBox.stories-DALRNTV0.js → CheckBox.stories-Caw6-hSQ.js} +1 -1
  43. package/storybook-static/assets/{Color-6AOWCN3U-UmlmbwVs.js → Color-6AOWCN3U-CPYGaU3y.js} +1 -1
  44. package/storybook-static/assets/{ContextMenu-giZ56GIC.js → ContextMenu-CUWt-eg1.js} +1 -1
  45. package/storybook-static/assets/{ContextMenu-BOez7LwB.js → ContextMenu-gfYYiVdR.js} +1 -1
  46. package/storybook-static/assets/{ContextMenu.stories-BnoaSq3Q.js → ContextMenu.stories-BuQS57wW.js} +1 -1
  47. package/storybook-static/assets/{CustomOptionButton-C1VgtQgI.js → CustomOptionButton-Be7sd0Lb.js} +1 -1
  48. package/storybook-static/assets/{DateInput-A3468S6P.js → DateInput-B4oviF7J.js} +1 -1
  49. package/storybook-static/assets/{DateInput-oWtyLo3Q.js → DateInput-BQGUMYsW.js} +1 -1
  50. package/storybook-static/assets/{DateInput.stories-BeGG7Vzf.js → DateInput.stories-D6q7J1C4.js} +1 -1
  51. package/storybook-static/assets/{DateRangePresetSelect--h5MdeWg.js → DateRangePresetSelect-BAxaG0HP.js} +1 -1
  52. package/storybook-static/assets/{DateRangePresetSelect-DbKRsjNX.js → DateRangePresetSelect-Cq6pOZPu.js} +1 -1
  53. package/storybook-static/assets/{DateRangePresetSelect.stories-BS1ll9RK.js → DateRangePresetSelect.stories-d4HFbao6.js} +1 -1
  54. package/storybook-static/assets/{Detail-FEi6FSkD.js → Detail-DlETcWVp.js} +1 -1
  55. package/storybook-static/assets/{Detail.stories-Dy2yKBDx.js → Detail.stories-BEwFXEZH.js} +1 -1
  56. package/storybook-static/assets/{Dialog-C7Zfcyjf.js → Dialog-Bk8aoiOf.js} +1 -1
  57. package/storybook-static/assets/{Dialog-CXnHtk_h.js → Dialog-D63TJr1M.js} +1 -1
  58. package/storybook-static/assets/{Dialog.stories-BeUqdA_f.js → Dialog.stories-BR7NqeZj.js} +1 -1
  59. package/storybook-static/assets/{Disclosure-BqsgTrM0.js → Disclosure-zQK54WKU.js} +1 -1
  60. package/storybook-static/assets/{Disclosure.stories-D3LzXhet.js → Disclosure.stories-CNLpB5YJ.js} +1 -1
  61. package/storybook-static/assets/{DisclosureGroup-BAF5Untb.js → DisclosureGroup-C_nZSLZw.js} +1 -1
  62. package/storybook-static/assets/{DocsRenderer-JROSPFPF-luRRUpJ_.js → DocsRenderer-JROSPFPF-CoU8mWAw.js} +2 -2
  63. package/storybook-static/assets/{DropZone-B-YA4zyu.js → DropZone-CUz1Q4bA.js} +1 -1
  64. package/storybook-static/assets/{DropZone-DKrOIaeH.js → DropZone-Y5hvED_x.js} +1 -1
  65. package/storybook-static/assets/{DropZone.stories-wf2ubfGc.js → DropZone.stories-BH4FSM4P.js} +1 -1
  66. package/storybook-static/assets/{DropdownMenu-BiWUESj0.js → DropdownMenu-Bi6-5Gqf.js} +1 -1
  67. package/storybook-static/assets/{ExpandableActionPanel-Dtfw5TWs.js → ExpandableActionPanel-BfWj887r.js} +1 -1
  68. package/storybook-static/assets/{ExpandableActionPanel.stories-BOdw0CLN.js → ExpandableActionPanel.stories-CGxcwRMU.js} +1 -1
  69. package/storybook-static/assets/{Flex-BaSYiAMD.js → Flex-Dm-Qfvzk.js} +1 -1
  70. package/storybook-static/assets/{Flex.stories-CD_e9R5B.js → Flex.stories-B3zMz03s.js} +1 -1
  71. package/storybook-static/assets/{FlexContainer-LmZIdhq2.js → FlexContainer-BWyPlTMB.js} +1 -1
  72. package/storybook-static/assets/{FlexItem-vkDYa5tA.js → FlexItem-pu3E11Sf.js} +1 -1
  73. package/storybook-static/assets/{FloatingPopover-C65hSmop.js → FloatingPopover-NMRTnqfo.js} +1 -1
  74. package/storybook-static/assets/FormattedTableCell-BISjHrBJ.js +15 -0
  75. package/storybook-static/assets/{Grid-BCzygAqy.js → Grid-CzO0Ae1r.js} +1 -1
  76. package/storybook-static/assets/{Grid-8eLmW2AB.js → Grid-vZyA-712.js} +1 -1
  77. package/storybook-static/assets/{Grid.stories-DULQOmbC.js → Grid.stories-BQqK0zom.js} +1 -1
  78. package/storybook-static/assets/{GridContainer-qDguE1HS.js → GridContainer-fKYgDsLl.js} +1 -1
  79. package/storybook-static/assets/{ImageCropper.stories-C1O-Zv4X.js → ImageCropper.stories-Ca4MZWOW.js} +3 -3
  80. package/storybook-static/assets/{ImageRenderer-BOyGnl1v.js → ImageRenderer-BWIk_76e.js} +1 -1
  81. package/storybook-static/assets/{ImageRenderer.stories-Cms0kMXA.js → ImageRenderer.stories-C1FfOcFD.js} +1 -1
  82. package/storybook-static/assets/{ImageView.stories-CUR6bAyn.js → ImageView.stories-DyuR7kdq.js} +1 -1
  83. package/storybook-static/assets/{ImageViewer.stories-BwJKAIT7.js → ImageViewer.stories-PcvOFYqC.js} +2 -2
  84. package/storybook-static/assets/{ImageViewerRuntime-CzkvEHax.js → ImageViewerRuntime-BiI7Xukq.js} +2 -2
  85. package/storybook-static/assets/{Input-B2_RznVE.js → Input-CsD-x1BQ.js} +1 -1
  86. package/storybook-static/assets/{Input-ebp78QN1.js → Input-D_4GoCLl.js} +1 -1
  87. package/storybook-static/assets/{Input.stories-BKwTRsUL.js → Input.stories-D-1urO1N.js} +1 -1
  88. package/storybook-static/assets/{InputDateTime-pK4mlxxm.js → InputDateTime-CFQanHxB.js} +1 -1
  89. package/storybook-static/assets/{InputDateTime.stories-J4Gd0DaU.js → InputDateTime.stories-Q4Ao5Tat.js} +1 -1
  90. package/storybook-static/assets/{InputFile-D1x1T--f.js → InputFile-Bj8PXGY3.js} +1 -1
  91. package/storybook-static/assets/{InputFile-BYcgJx2f.js → InputFile-DM6fBXUx.js} +1 -1
  92. package/storybook-static/assets/{InputFile.stories-DAYDiSFH.js → InputFile.stories-DC27BYoQ.js} +1 -1
  93. package/storybook-static/assets/{InputFiles-D7kcGvWK.js → InputFiles-D59MGS0Z.js} +1 -1
  94. package/storybook-static/assets/{InputFiles-C9HH7fIG.js → InputFiles-teFj6f6X.js} +1 -1
  95. package/storybook-static/assets/{InputFiles.stories-CJTwQutb.js → InputFiles.stories-DX5UEvYR.js} +1 -1
  96. package/storybook-static/assets/{InputFilesList-ChI0hQ07.js → InputFilesList-BD4Y0gN-.js} +1 -1
  97. package/storybook-static/assets/{InputImage-B3KFrWqa.js → InputImage-DmsFGNLJ.js} +1 -1
  98. package/storybook-static/assets/{InputImage-C0-U_QHU.js → InputImage-Du6jGDmY.js} +1 -1
  99. package/storybook-static/assets/{InputImage.stories-QEtqeok5.js → InputImage.stories-u0hrr-Da.js} +1 -1
  100. package/storybook-static/assets/{InteractiveComponents-DB71eb-1.js → InteractiveComponents-BLQiSO3p.js} +1 -1
  101. package/storybook-static/assets/{InteractiveComponents.stories-DdZpmMis.js → InteractiveComponents.stories-BxIVvRz5.js} +1 -1
  102. package/storybook-static/assets/{LayoutPicker-CicZY4X9.js → LayoutPicker-TecE4x6w.js} +1 -1
  103. package/storybook-static/assets/{LayoutPicker-DBEYC_LA.js → LayoutPicker-xUooYcbS.js} +1 -1
  104. package/storybook-static/assets/{LayoutPicker.stories-BFtP0gOy.js → LayoutPicker.stories-YDMnmhJ4.js} +1 -1
  105. package/storybook-static/assets/{Listbox-B0Nqys9f.js → Listbox-D2k8QfTs.js} +1 -1
  106. package/storybook-static/assets/{Listbox-CLP0Cpin.js → Listbox-DplAn6ND.js} +1 -1
  107. package/storybook-static/assets/{Listbox.stories-BHeIn0H8.js → Listbox.stories-Bg3_UQD4.js} +1 -1
  108. package/storybook-static/assets/{Modal-D4jDp5U4.js → Modal-4OdkCwCv.js} +1 -1
  109. package/storybook-static/assets/{Modal.stories-CMHJdaj1.js → Modal.stories-BwawDSGR.js} +1 -1
  110. package/storybook-static/assets/{ModalManagerProvider-D8tCMe07.js → ModalManagerProvider-CYR_xbpx.js} +1 -1
  111. package/storybook-static/assets/{MultiSelect-Bmb0qTzT.js → MultiSelect-CXn1CY9G.js} +1 -1
  112. package/storybook-static/assets/{MultiSelect-y6FjtCjy.js → MultiSelect-iwbCRD9H.js} +1 -1
  113. package/storybook-static/assets/{MultiSelect.stories-B9TZNWng.js → MultiSelect.stories-BSjAL2ua.js} +1 -1
  114. package/storybook-static/assets/{NoData-CzlhZzHI.js → NoData-6Scjk2OD.js} +1 -1
  115. package/storybook-static/assets/{NotificationsHost-AE9gk5zq.js → NotificationsHost-DK3A-iq7.js} +1 -1
  116. package/storybook-static/assets/{NotificationsHost.stories-Bu0__u38.js → NotificationsHost.stories-BRmPf9Q7.js} +1 -1
  117. package/storybook-static/assets/{ODataMultiSelect-DJ6zie6W.js → ODataMultiSelect-CuNGV1QU.js} +1 -1
  118. package/storybook-static/assets/{ODataMultiSelect.stories-BjgJGbWL.js → ODataMultiSelect.stories-CyAonfZC.js} +1 -1
  119. package/storybook-static/assets/{ODataSelect-V1JJpKB9.js → ODataSelect-vhh4NJS_.js} +1 -1
  120. package/storybook-static/assets/{ODataSelect.stories-B2INiDvW.js → ODataSelect.stories-aJBFF45H.js} +1 -1
  121. package/storybook-static/assets/{ODataTreeMultiSelect-BKMrgz8e.js → ODataTreeMultiSelect-vPtlTloF.js} +1 -1
  122. package/storybook-static/assets/{ODataTreeMultiSelect.stories-CjLPeqio.js → ODataTreeMultiSelect.stories-DTBAF628.js} +1 -1
  123. package/storybook-static/assets/{ODataTreeSelect-C2iGJdYk.js → ODataTreeSelect-BCVVLB9M.js} +1 -1
  124. package/storybook-static/assets/{ODataTreeSelect.stories-ReyoXlcX.js → ODataTreeSelect.stories-CptC0p6O.js} +1 -1
  125. package/storybook-static/assets/{OneStepScroller-knlVIvl2.js → OneStepScroller-DerjCiL3.js} +1 -1
  126. package/storybook-static/assets/{Option-CJBK9i2B.css → Option-CwetjUM8.css} +1 -1
  127. package/storybook-static/assets/{Option-KVcu4DcX.js → Option-DkAw9S3l.js} +4 -4
  128. package/storybook-static/assets/{OptionButton-D2ZcZtPU.js → OptionButton-CGurT23Z.js} +1 -1
  129. package/storybook-static/assets/{PeriodSelect-BsET8TST.js → PeriodSelect-DOO139ke.js} +1 -1
  130. package/storybook-static/assets/{PeriodSelect-Bm_VPE_5.js → PeriodSelect-TrGTMIid.js} +1 -1
  131. package/storybook-static/assets/{PeriodSelect.stories-Due2DawD.js → PeriodSelect.stories-B3GEgBZ1.js} +1 -1
  132. package/storybook-static/assets/{PickerTriggerActions-KgIZBVSt.js → PickerTriggerActions-CKVPuKEC.js} +1 -1
  133. package/storybook-static/assets/{Popover-Cq1q1mW3.js → Popover-BNItmUQ9.js} +1 -1
  134. package/storybook-static/assets/{Popover-D38IExq9.js → Popover-DgXqO0PN.js} +1 -1
  135. package/storybook-static/assets/{Popover.stories-B5MXPFi2.js → Popover.stories-bX5S41pH.js} +1 -1
  136. package/storybook-static/assets/{PresetRangeDateInput-dWe-Zcrb.js → PresetRangeDateInput-B4FYPy8g.js} +1 -1
  137. package/storybook-static/assets/{PresetRangeDateInput-C4iOgcKK.js → PresetRangeDateInput-GSnnpZzy.js} +1 -1
  138. package/storybook-static/assets/{PresetRangeDateInput.stories-C8JiJCpi.js → PresetRangeDateInput.stories-Ca0mIo33.js} +1 -1
  139. package/storybook-static/assets/{PresetSelect-CIqamiLP.js → PresetSelect-B_KRl4SQ.js} +1 -1
  140. package/storybook-static/assets/README-1RIcD7RV.js +694 -0
  141. package/storybook-static/assets/README-B1XUsXZj.js +334 -0
  142. package/storybook-static/assets/README-BH78sIZ_.js +288 -0
  143. package/storybook-static/assets/README-BgeYwR6t.js +347 -0
  144. package/storybook-static/assets/README-BhsUMTSk.js +860 -0
  145. package/storybook-static/assets/README-Bj88Ecb7.js +876 -0
  146. package/storybook-static/assets/README-C9saKIyr.js +417 -0
  147. package/storybook-static/assets/README-CP1S4LYF.js +1309 -0
  148. package/storybook-static/assets/README-Cc-guFb0.js +1487 -0
  149. package/storybook-static/assets/README-CheO0E1A.js +282 -0
  150. package/storybook-static/assets/README-CiuVVUgE.js +420 -0
  151. package/storybook-static/assets/README-CvRg9lqh.js +273 -0
  152. package/storybook-static/assets/README-DCdV2RQL.js +1093 -0
  153. package/storybook-static/assets/README-DyNQqotW.js +338 -0
  154. package/storybook-static/assets/README-W1rhe8Sk.js +648 -0
  155. package/storybook-static/assets/{RadioButton-CTA-WBmG.js → RadioButton-CA9DN5bY.js} +1 -1
  156. package/storybook-static/assets/{RadioButton-DfRRX6Id.js → RadioButton-DcXbcFW1.js} +1 -1
  157. package/storybook-static/assets/{RadioButton.stories-D6MO9y2H.js → RadioButton.stories-1_kzxnXY.js} +1 -1
  158. package/storybook-static/assets/{RadioGroup-CiEUg_Oc.js → RadioGroup-bEwTHCyS.js} +1 -1
  159. package/storybook-static/assets/{RadioGroup-BAoD6ZDR.js → RadioGroup-mfYgdfXM.js} +1 -1
  160. package/storybook-static/assets/{RadioGroup.stories-CCjYE-3G.js → RadioGroup.stories-CecdqPms.js} +1 -1
  161. package/storybook-static/assets/{Scrollable-ns4PHUAk.js → Scrollable-1fdTDJHK.js} +1 -1
  162. package/storybook-static/assets/{Select-Bj9NxEJa.js → Select-CvQZGS1G.js} +1 -1
  163. package/storybook-static/assets/{Select-D2aDjIb_.js → Select-Df5Md2Cm.js} +1 -1
  164. package/storybook-static/assets/{Select.stories-zErlbrrD.js → Select.stories-M4M80KTq.js} +1 -1
  165. package/storybook-static/assets/{SerializableSelect-P7iNwTbc.js → SerializableSelect-CnsGo9Ep.js} +1 -1
  166. package/storybook-static/assets/{SerializableSelect.stories-DUAK_mo8.js → SerializableSelect.stories-BJ6MlNDx.js} +1 -1
  167. package/storybook-static/assets/{SingleDateTimeInput-vvyw15zU.js → SingleDateTimeInput-BCV0gfB9.js} +1 -1
  168. package/storybook-static/assets/{SingleDateTimeInput-CkMMccYd.js → SingleDateTimeInput-DJH7zSIz.js} +1 -1
  169. package/storybook-static/assets/{SingleDateTimeInput.stories-CSW9M9hH.js → SingleDateTimeInput.stories-DgEwAFe8.js} +1 -1
  170. package/storybook-static/assets/{Slider-Dftgrmfi.js → Slider-D504FPH3.js} +1 -1
  171. package/storybook-static/assets/{Slider-CkUdrb57.js → Slider-cLRAHPLi.js} +1 -1
  172. package/storybook-static/assets/{Slider.stories-zVUgi1Wb.js → Slider.stories-EI314Iff.js} +1 -1
  173. package/storybook-static/assets/{SliderInput-BINdZsru.js → SliderInput-Bt0H8rgN.js} +1 -1
  174. package/storybook-static/assets/{SliderInput-DnVNzY_q.js → SliderInput-E5p3n3VY.js} +1 -1
  175. package/storybook-static/assets/{SliderInput.stories-CfY1B4PZ.js → SliderInput.stories-CDYPq7fP.js} +1 -1
  176. package/storybook-static/assets/{Sortable-BnixGZFs.js → Sortable-Bzd7JK4E.js} +1 -1
  177. package/storybook-static/assets/{Sortable.stories-DFzv9lnf.js → Sortable.stories-DNGbmn2a.js} +1 -1
  178. package/storybook-static/assets/{Splitter-BtDFBrQ3.js → Splitter-DSf48AOW.js} +1 -1
  179. package/storybook-static/assets/{Splitter.stories-CipjzPJH.js → Splitter.stories-DUKsOqNN.js} +1 -1
  180. package/storybook-static/assets/{StateSelect-DagWIbnr.js → StateSelect-BxuKIsLa.js} +1 -1
  181. package/storybook-static/assets/{StateSelect-J5sxWYjq.js → StateSelect-HKOT6kq9.js} +1 -1
  182. package/storybook-static/assets/{StateSelect.stories-DDIqhm2K.js → StateSelect.stories-CbLvq7ou.js} +1 -1
  183. package/storybook-static/assets/{StringToggle.stories-CkYGiOe-.js → StringToggle.stories-DOKL-aWy.js} +1 -1
  184. package/storybook-static/assets/{Switch-B5TS1mrR.js → Switch-CLAfhk73.js} +1 -1
  185. package/storybook-static/assets/{Switch-N-Lxev-q.js → Switch-CVTjSEq1.js} +1 -1
  186. package/storybook-static/assets/{Switch.stories-HNOx5mPv.js → Switch.stories-CL5Xe_HQ.js} +1 -1
  187. package/storybook-static/assets/{Table-DTzmRzDD.js → Table-DKc19RME.js} +1 -1
  188. package/storybook-static/assets/Table.stories-CJ9DR1S-.js +207 -0
  189. package/storybook-static/assets/{Tabs-Bc5dgtoT.js → Tabs-De_4F0H3.js} +1 -1
  190. package/storybook-static/assets/{Tabs.stories-BgLnKKFv.js → Tabs.stories-DPAvvkh9.js} +2 -2
  191. package/storybook-static/assets/{TabsBox-sTVLs8k0.js → TabsBox-Cy5CICcb.js} +1 -1
  192. package/storybook-static/assets/{TagInput-yPLHo0PJ.js → TagInput-7n98ftvD.js} +1 -1
  193. package/storybook-static/assets/{TagInput.stories-B4C-230C.js → TagInput.stories-B6JAb3XA.js} +1 -1
  194. package/storybook-static/assets/{Text-C2-mep6V.js → Text-CjSG8mvD.js} +1 -1
  195. package/storybook-static/assets/{Textarea-bqvYs13v.js → Textarea-CQqwaJ0Q.js} +1 -1
  196. package/storybook-static/assets/{Textarea-B7pXYTGv.js → Textarea-Dea29_yQ.js} +1 -1
  197. package/storybook-static/assets/{Textarea.stories-BSSaqnZI.js → Textarea.stories-BZf3Cu4S.js} +1 -1
  198. package/storybook-static/assets/{TimePanel-pMBlR0ZN.js → TimePanel-CPZ4jOw2.js} +1 -1
  199. package/storybook-static/assets/{TimePanel.stories-Cn5IEcsV.js → TimePanel.stories-y5Lp9sMG.js} +1 -1
  200. package/storybook-static/assets/{Toggle-E4xV63pY.js → Toggle-CJHzo9mB.js} +1 -1
  201. package/storybook-static/assets/{Toggle-h6ESIcwX.js → Toggle-s7SzQ11W.js} +1 -1
  202. package/storybook-static/assets/{Toggle.stories-9uqFBAAr.js → Toggle.stories-Dk3C5Le5.js} +1 -1
  203. package/storybook-static/assets/{TreeMultiSelect-DBGlliij.js → TreeMultiSelect-CxXF7pQE.js} +1 -1
  204. package/storybook-static/assets/{TreeMultiSelect-J8Tlnv3Y.js → TreeMultiSelect-DfeggvLr.js} +1 -1
  205. package/storybook-static/assets/{TreeMultiSelect.stories-CYDuvIk7.js → TreeMultiSelect.stories-DKiHT_6L.js} +1 -1
  206. package/storybook-static/assets/{TreePickerBase-BswrqgW3.js → TreePickerBase-DKJJDVC_.js} +1 -1
  207. package/storybook-static/assets/{TreeSelect-B1Buti64.js → TreeSelect-2E1KhJMi.js} +1 -1
  208. package/storybook-static/assets/{TreeSelect-Jchj-RXf.js → TreeSelect-h23orAoA.js} +1 -1
  209. package/storybook-static/assets/{TreeSelect.stories-DzCqueCG.js → TreeSelect.stories-DO06yaS0.js} +1 -1
  210. package/storybook-static/assets/{TreeTable-oYCdUTsS.js → TreeTable-ByV2_E0S.js} +1 -1
  211. package/storybook-static/assets/{TreeTable.stories-L4FHah8x.js → TreeTable.stories-D4gbQx59.js} +2 -2
  212. package/storybook-static/assets/{WithTooltip-7X4HQR7L-Cakt_JaX.js → WithTooltip-7X4HQR7L-DQ4Qthn2.js} +1 -1
  213. package/storybook-static/assets/{array-D-_yMMUK-CIRmlF93.js → array-D-_yMMUK-BQ0uu1hZ.js} +1 -1
  214. package/storybook-static/assets/{async-Bsdhj8g_.js → async-BI7SlYp0.js} +1 -1
  215. package/storybook-static/assets/axe-_9eNhXQJ.js +20 -0
  216. package/storybook-static/assets/{blocks-BA_1CXsr.js → blocks-B8F5vBhs.js} +6 -6
  217. package/storybook-static/assets/check-mlY_aVEI.js +1 -0
  218. package/storybook-static/assets/chevron-down-C7c-TcEc.js +1 -0
  219. package/storybook-static/assets/chevron-left-2LrGBhDY.js +1 -0
  220. package/storybook-static/assets/chevron-right-D6NN1_UQ.js +1 -0
  221. package/storybook-static/assets/{chunk-242VQQM5-BMWWiybC.js → chunk-242VQQM5-bHL6qIJq.js} +1 -1
  222. package/storybook-static/assets/{chunk-DRQKCCAG-CqE-Zrl9.js → chunk-DRQKCCAG-DvsmU7ha.js} +1 -1
  223. package/storybook-static/assets/{chunk-IMSF75WX-yvVUwIQZ.js → chunk-IMSF75WX-CGYlPTyG.js} +1 -1
  224. package/storybook-static/assets/{chunk-KJHJLCBK-D-yQfkOi.js → chunk-KJHJLCBK-yRlRMfNs.js} +1 -1
  225. package/storybook-static/assets/{chunk-QQBDHPVZ-B98qboss.js → chunk-QQBDHPVZ-DC5n6Pix.js} +1 -1
  226. package/storybook-static/assets/{chunk-W22LQPXL-B40Li12y.js → chunk-W22LQPXL-5sD3hVeE.js} +1 -1
  227. package/storybook-static/assets/{circle-alert-C855gqBz.js → circle-alert-H_7GSXFp.js} +1 -1
  228. package/storybook-static/assets/circle-check-B0voRIBT.js +1 -0
  229. package/storybook-static/assets/{components-DSUjzUVy.js → components-Bw-2TeQP.js} +12 -12
  230. package/storybook-static/assets/{contextMenu-DCSPTQNQ.js → contextMenu-B8JusItT.js} +1 -1
  231. package/storybook-static/assets/{createControlledStoryRender-DHsyXc1B.js → createControlledStoryRender-BouhvX-R.js} +1 -1
  232. package/storybook-static/assets/{date-segments-D3HouZ37.js → date-segments-DeoMkKv9.js} +1 -1
  233. package/storybook-static/assets/{defaultRenderers-V5HI39JH.js → defaultRenderers-BHXboKuy.js} +1 -1
  234. package/storybook-static/assets/{dom-B3G_GtJw.js → dom-DBd_DWF8.js} +1 -1
  235. package/storybook-static/assets/{download-B3oJ9vHV.js → download-CSY7aIz0.js} +1 -1
  236. package/storybook-static/assets/{error-BZrNlEUO.js → error-De9R4E_F.js} +1 -1
  237. package/storybook-static/assets/{file-vPGnhPr-.js → file-CwRoWxeB.js} +1 -1
  238. package/storybook-static/assets/floating-ui.react-D7RwPYeO.js +1 -0
  239. package/storybook-static/assets/formatter-L2YTDE3Q-CRGuSxAF.js +1 -0
  240. package/storybook-static/assets/{formatters-CBgrBeSA-DY6OIAc8.js → formatters-CBgrBeSA-CqB1pZxS.js} +1 -1
  241. package/storybook-static/assets/{foundationLib-BFrQ5jez.js → foundationLib-BoYGVeXe.js} +1 -1
  242. package/storybook-static/assets/{hooks-_jPrECht.js → hooks-DQXvhAPr.js} +2 -2
  243. package/storybook-static/assets/iframe-B8PAuhnL.js +1 -0
  244. package/storybook-static/assets/{iframe-Cq8y-8O_.js → iframe-CViB2sFm.js} +23 -23
  245. package/storybook-static/assets/{info-CiT6g2aP.js → info-MrQcS8jn.js} +1 -1
  246. package/storybook-static/assets/{matchers-JIUQ3HVY-DvR2XErZ.js → matchers-JIUQ3HVY-C789_MAK.js} +1 -1
  247. package/storybook-static/assets/{media-BycWpjAl.js → media-D9cE2tzi.js} +1 -1
  248. package/storybook-static/assets/odata-CBGaUmRT.js +1 -0
  249. package/storybook-static/assets/{odata-service-B6TxBSLm-BW9QLSrS.js → odata-service-B6TxBSLm-Bq8Ya67E.js} +1 -1
  250. package/storybook-static/assets/{odataFetchFn-C6ps_31t.js → odataFetchFn-CPO8XIYX.js} +1 -1
  251. package/storybook-static/assets/{odataStoryFixtures-BcNo-Wxr.js → odataStoryFixtures--SWu2yC_.js} +2 -2
  252. package/storybook-static/assets/{persisted-B6h1b3UG.js → persisted-B2dfNRCz.js} +1 -1
  253. package/storybook-static/assets/{preload-helper-BHmFeTtP.js → preload-helper-wdlQj8DP.js} +1 -1
  254. package/storybook-static/assets/react-18-D4o7zN8K.js +9 -0
  255. package/storybook-static/assets/{react-C21x__mS.js → react-BZJXY1be.js} +1 -1
  256. package/storybook-static/assets/{react-qN2cStNd.js → react-Bl2r1tuC.js} +1 -1
  257. package/storybook-static/assets/{react-CvkUhHUq.js → react-GkniIHEK.js} +1 -1
  258. package/storybook-static/assets/{react-dom-2o2phaJG.js → react-dom-BzEl8usk.js} +1 -1
  259. package/storybook-static/assets/react-iQQwtFPL.js +9 -0
  260. package/storybook-static/assets/{refresh-cw-DIeuulxV.js → refresh-cw-_wTFfhR2.js} +1 -1
  261. package/storybook-static/assets/{rolldown-runtime-C0FnF6B9.js → rolldown-runtime-DkW27tQK.js} +1 -1
  262. package/storybook-static/assets/{settings-2-Gfu_uw21.js → settings-2-BqduNJjJ.js} +1 -1
  263. package/storybook-static/assets/{shim-BUMlC1GG.js → shim-DlYORV19.js} +1 -1
  264. package/storybook-static/assets/{sortable-DxRr6Rgq.js → sortable-D-m6SEp7.js} +1 -1
  265. package/storybook-static/assets/syntaxhighlighter-QEQV7K2Y-CEo9I1qZ.js +6 -0
  266. package/storybook-static/assets/{theming-mTWhffkp.js → theming-Cvtrnj7n.js} +1 -1
  267. package/storybook-static/assets/{trash-2-CqKAB1Q0.js → trash-2-65ZF92zp.js} +1 -1
  268. package/storybook-static/assets/{treeStoryFixtures-Ci7STpiz.js → treeStoryFixtures-DXHh9GL3.js} +1 -1
  269. package/storybook-static/assets/{triangle-alert-D4omsgZJ.js → triangle-alert-k_CRNi0k.js} +1 -1
  270. package/storybook-static/assets/{ui.module-D4CuGr8r.js → ui.module-Cat84Nza.js} +1 -1
  271. package/storybook-static/assets/{uiClasses-CVGbL980.js → uiClasses-B9c6dZuu.js} +1 -1
  272. package/storybook-static/assets/{useFlexClasses-Gn9spXEB.js → useFlexClasses-SgVG37Bb.js} +1 -1
  273. package/storybook-static/assets/{useODataSelectBase-DacE6X5d.js → useODataSelectBase-Ck_hduYv.js} +1 -1
  274. package/storybook-static/assets/{useODataTreeData-BEYyje5V.js → useODataTreeData-DTrxPY_k.js} +1 -1
  275. package/storybook-static/assets/usePickerDefaultFilter-5Q1pAV4d.js +1 -0
  276. package/storybook-static/assets/{usePickerQuery-BRsuRnoH.js → usePickerQuery-DdeWtJWc.js} +1 -1
  277. package/storybook-static/assets/{usePickerTriggerController-DyFq40t1.js → usePickerTriggerController-D8hxWmGo.js} +2 -2
  278. package/storybook-static/assets/{utils-DUM89eIj.js → utils-tg3ibbzi.js} +1 -1
  279. package/storybook-static/assets/x-C5XG0bD4.js +1 -0
  280. package/storybook-static/iframe.html +7 -7
  281. package/storybook-static/index.json +1 -1
  282. package/storybook-static/project.json +1 -1
  283. package/storybook-static/sb-addons/chromatic-com-storybook-1/manager-bundle.js +35 -33
  284. package/storybook-static/sb-manager/globals-runtime.js +7 -7
  285. package/storybook-static/sb-manager/globals.js +1 -1
  286. package/storybook-static/sb-manager/runtime.js +88 -89
  287. package/storybook-static/vite-inject-mocker-entry.js +2 -2
  288. package/dist/chunks/picker-0I1yMKIY.js.map +0 -1
  289. package/storybook-static/assets/FormattedTableCell-BdA15EY1.js +0 -15
  290. package/storybook-static/assets/Table.stories-DY5NfoVd.js +0 -207
  291. package/storybook-static/assets/axe-Bq-2xuso.js +0 -20
  292. package/storybook-static/assets/browserAndReact-fXgh8QfO.js +0 -70
  293. package/storybook-static/assets/check-wNoLJyvD.js +0 -1
  294. package/storybook-static/assets/chevron-down-DjM4Yyml.js +0 -1
  295. package/storybook-static/assets/chevron-left-BT138_KT.js +0 -1
  296. package/storybook-static/assets/chevron-right-DEvxOiq2.js +0 -1
  297. package/storybook-static/assets/circle-check-BDVZ6tz8.js +0 -1
  298. package/storybook-static/assets/filesAndExcel-Bj4tkdkM.js +0 -92
  299. package/storybook-static/assets/floating-ui.react-BewtdZNK.js +0 -1
  300. package/storybook-static/assets/formatter-L2YTDE3Q-x0raBH34.js +0 -1
  301. package/storybook-static/assets/formatters-DYZ7voq3.js +0 -202
  302. package/storybook-static/assets/formatters-runtime-Ddmbt5yI.js +0 -329
  303. package/storybook-static/assets/iframe-C7ggO0sY.js +0 -1
  304. package/storybook-static/assets/odata-ClP2t_-O.js +0 -1
  305. package/storybook-static/assets/odataAndTables-Cjwl3uWE.js +0 -77
  306. package/storybook-static/assets/pureUtilities-CO1AJvbl.js +0 -30
  307. package/storybook-static/assets/react-18-CgkwBzTo.js +0 -9
  308. package/storybook-static/assets/react-D4nkG-T7.js +0 -9
  309. package/storybook-static/assets/runtimeAndState-TcnlLgJx.js +0 -93
  310. package/storybook-static/assets/syntaxhighlighter-T5IQBZDB-BIwIp4cU.js +0 -6
  311. package/storybook-static/assets/usePickerDefaultFilter-Be420WNF.js +0 -1
  312. package/storybook-static/assets/x-zmaXH4MN.js +0 -1
@@ -0,0 +1,1487 @@
1
+ import{n as e}from"./rolldown-runtime-DkW27tQK.js";import{n as t}from"./iframe-CViB2sFm.js";import{i as n,r}from"./react-Bl2r1tuC.js";import{l as i,m as a}from"./blocks-B8F5vBhs.js";function o(e){let t={a:`a`,code:`code`,h1:`h1`,h2:`h2`,h3:`h3`,h4:`h4`,li:`li`,ol:`ol`,p:`p`,pre:`pre`,table:`table`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,...n(),...e.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(i,{title:`Foundation Lib/API/DOM`}),`
2
+ `,(0,c.jsxs)(t.h1,{id:`browser-dom-и-focus-helpers-через-ryuzaki13react-foundation-libdom`,children:[`Browser DOM и focus helpers через `,(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/dom`})]}),`
3
+ `,(0,c.jsxs)(t.p,{children:[`Модуль `,(0,c.jsx)(t.code,{children:`dom`}),` содержит небольшие browser helpers и React hooks для типовых инфраструктурных задач:`]}),`
4
+ `,(0,c.jsxs)(t.ul,{children:[`
5
+ `,(0,c.jsxs)(t.li,{children:[`скачать `,(0,c.jsx)(t.code,{children:`Blob`}),`, JSON или уже созданный object URL;`]}),`
6
+ `,(0,c.jsx)(t.li,{children:`получить общий DOM-контейнер для React portal;`}),`
7
+ `,(0,c.jsxs)(t.li,{children:[`отреагировать на `,(0,c.jsx)(t.code,{children:`mousedown`}),` за пределами одного или нескольких элементов;`]}),`
8
+ `,(0,c.jsxs)(t.li,{children:[`закрыть активный overlay по `,(0,c.jsx)(t.code,{children:`Escape`}),`;`]}),`
9
+ `,(0,c.jsxs)(t.li,{children:[`установить начальный фокус, зациклить `,(0,c.jsx)(t.code,{children:`Tab`}),` и восстановить прежний фокус;`]}),`
10
+ `,(0,c.jsxs)(t.li,{children:[`узнать высоту элемента через `,(0,c.jsx)(t.code,{children:`ResizeObserver`}),`;`]}),`
11
+ `,(0,c.jsx)(t.li,{children:`узнать, пересекается ли элемент с viewport или другим root;`}),`
12
+ `,(0,c.jsx)(t.li,{children:`определить наличие touch/coarse-pointer возможностей.`}),`
13
+ `]}),`
14
+ `,(0,c.jsx)(t.p,{children:`Модуль является низкоуровневым browser foundation-слоем. Он не создаёт готовые кнопки, dialog, modal, popover, tooltip или download UI. Визуальный компонент по-прежнему отвечает за семантический HTML, ARIA, стили, анимацию, стек overlay, сообщения об ошибках и пользовательский сценарий.`}),`
15
+ `,(0,c.jsx)(t.p,{children:`README рассчитан на разработчика, который использует опубликованный пакет без прямого доступа к исходному коду. Здесь объясняются базовые понятия DOM и React, все публичные функции, точный lifecycle, defaults, cleanup, ограничения доступности, тестирование и частые ошибки.`}),`
16
+ `,(0,c.jsx)(t.h2,{id:`содержание`,children:`Содержание`}),`
17
+ `,(0,c.jsxs)(t.ul,{children:[`
18
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BD%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8-%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B-%D0%BC%D0%BE%D0%B4%D1%83%D0%BB%D1%8F`,children:`Назначение и границы модуля`})}),`
19
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D0%B8-%D0%B8%D0%BC%D0%BF%D0%BE%D1%80%D1%82`,children:`Установка и импорт`})}),`
20
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#runtime-%D0%B8-browser-boundary`,children:`Runtime и browser boundary`})}),`
21
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D0%BF%D0%BE%D0%BD%D1%8F%D1%82%D0%B8%D1%8F`,children:`Минимальные понятия`})}),`
22
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BA%D0%B0%D0%BA-%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C-api`,children:`Как выбрать API`})}),`
23
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D0%B9-%D1%81%D1%82%D0%B0%D1%80%D1%82`,children:`Быстрый старт`})}),`
24
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%81%D0%BA%D0%B0%D1%87%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%84%D0%B0%D0%B9%D0%BB%D0%BE%D0%B2`,children:`Скачивание файлов`})}),`
25
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#getorcreateportalroot`,children:(0,c.jsx)(t.code,{children:`getOrCreatePortalRoot`})})}),`
26
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#useclickoutside`,children:(0,c.jsx)(t.code,{children:`useClickOutside`})})}),`
27
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#useescapedismiss`,children:(0,c.jsx)(t.code,{children:`useEscapeDismiss`})})}),`
28
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#useelementheightobserver`,children:(0,c.jsx)(t.code,{children:`useElementHeightObserver`})})}),`
29
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#useintersectionobserver`,children:(0,c.jsx)(t.code,{children:`useIntersectionObserver`})})}),`
30
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#useistouchdevice`,children:(0,c.jsx)(t.code,{children:`useIsTouchDevice`})})}),`
31
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#useoverlayfocus`,children:(0,c.jsx)(t.code,{children:`useOverlayFocus`})})}),`
32
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#usefocustrap`,children:(0,c.jsx)(t.code,{children:`useFocusTrap`})})}),`
33
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%81%D0%B1%D0%BE%D1%80%D0%BA%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%BD%D0%BE%D0%B3%D0%BE-dialog`,children:`Сборка доступного dialog`})}),`
34
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8F-overlay-hooks`,children:`Композиция overlay hooks`})}),`
35
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#ssr-strict-mode-%D0%B8-lifecycle`,children:`SSR, Strict Mode и lifecycle`})}),`
36
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%BE%D0%B4%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C`,children:`Производительность`})}),`
37
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5`,children:`Тестирование`})}),`
38
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BE%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B8-%D1%87%D0%B0%D1%81%D1%82%D1%8B%D0%B5-%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B8`,children:`Ограничения и частые ошибки`})}),`
39
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BA%D1%80%D0%B0%D1%82%D0%BA%D0%B8%D0%B9-%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D1%87%D0%BD%D0%B8%D0%BA-api`,children:`Краткий справочник API`})}),`
40
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D1%8B-%D0%B8-%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%8B`,children:`Вопросы и ответы`})}),`
41
+ `]}),`
42
+ `,(0,c.jsx)(t.h2,{id:`назначение-и-границы-модуля`,children:`Назначение и границы модуля`}),`
43
+ `,(0,c.jsxs)(t.h3,{id:`что-делает-dom`,children:[`Что делает `,(0,c.jsx)(t.code,{children:`dom`})]}),`
44
+ `,(0,c.jsxs)(t.ul,{children:[`
45
+ `,(0,c.jsxs)(t.li,{children:[`создаёт браузерную загрузку через временный `,(0,c.jsx)(t.code,{children:`<a download>`}),`;`]}),`
46
+ `,(0,c.jsx)(t.li,{children:`берёт на себя создание и последующее освобождение object URL в download-сценариях;`}),`
47
+ `,(0,c.jsx)(t.li,{children:`сериализует JSON с отступом в два пробела;`}),`
48
+ `,(0,c.jsxs)(t.li,{children:[`создаёт или переиспользует `,(0,c.jsx)(t.code,{children:`HTMLElement`}),` в `,(0,c.jsx)(t.code,{children:`document.body`}),`;`]}),`
49
+ `,(0,c.jsxs)(t.li,{children:[`подписывает React-компонент на глобальные `,(0,c.jsx)(t.code,{children:`mousedown`}),`, `,(0,c.jsx)(t.code,{children:`keydown`}),` и `,(0,c.jsx)(t.code,{children:`resize`}),`;`]}),`
50
+ `,(0,c.jsxs)(t.li,{children:[`подключает `,(0,c.jsx)(t.code,{children:`ResizeObserver`}),` и `,(0,c.jsx)(t.code,{children:`IntersectionObserver`}),` с cleanup;`]}),`
51
+ `,(0,c.jsx)(t.li,{children:`управляет начальными и возвратными переходами focus;`}),`
52
+ `,(0,c.jsx)(t.li,{children:`предоставляет простую focus trap для modal-like overlay.`}),`
53
+ `]}),`
54
+ `,(0,c.jsx)(t.h3,{id:`чего-модуль-не-делает`,children:`Чего модуль не делает`}),`
55
+ `,(0,c.jsxs)(t.ul,{children:[`
56
+ `,(0,c.jsx)(t.li,{children:`не получает файл по HTTP/OData;`}),`
57
+ `,(0,c.jsxs)(t.li,{children:[`не декодирует base64 в `,(0,c.jsx)(t.code,{children:`Blob`}),`;`]}),`
58
+ `,(0,c.jsxs)(t.li,{children:[`не читает пользовательский `,(0,c.jsx)(t.code,{children:`File`}),`;`]}),`
59
+ `,(0,c.jsx)(t.li,{children:`не проверяет MIME или содержимое скачиваемого файла;`}),`
60
+ `,(0,c.jsx)(t.li,{children:`не показывает progress, toast или ошибку скачивания;`}),`
61
+ `,(0,c.jsx)(t.li,{children:`не создаёт React portal самостоятельно;`}),`
62
+ `,(0,c.jsx)(t.li,{children:`не удаляет portal root после использования;`}),`
63
+ `,(0,c.jsx)(t.li,{children:`не реализует полноценный dialog/modal/popover;`}),`
64
+ `,(0,c.jsxs)(t.li,{children:[`не назначает `,(0,c.jsx)(t.code,{children:`role`}),`, `,(0,c.jsx)(t.code,{children:`aria-modal`}),`, `,(0,c.jsx)(t.code,{children:`aria-labelledby`}),` или `,(0,c.jsx)(t.code,{children:`aria-describedby`}),`;`]}),`
65
+ `,(0,c.jsx)(t.li,{children:`не блокирует прокрутку страницы;`}),`
66
+ `,(0,c.jsxs)(t.li,{children:[`не делает фон `,(0,c.jsx)(t.code,{children:`inert`}),`;`]}),`
67
+ `,(0,c.jsx)(t.li,{children:`не управляет стеком вложенных overlay;`}),`
68
+ `,(0,c.jsx)(t.li,{children:`не блокирует программный уход фокуса;`}),`
69
+ `,(0,c.jsx)(t.li,{children:`не предоставляет polyfill для observer APIs;`}),`
70
+ `,(0,c.jsx)(t.li,{children:`не определяет фактический текущий способ ввода пользователя;`}),`
71
+ `,(0,c.jsx)(t.li,{children:`не заменяет CSS media queries.`}),`
72
+ `]}),`
73
+ `,(0,c.jsx)(t.h3,{id:`соседние-модули`,children:`Соседние модули`}),`
74
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Задача`}),(0,c.jsx)(t.th,{children:`Предпочтительный владелец`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Base64/data URL → `,(0,c.jsx)(t.code,{children:`Blob`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/binary`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Чтение выбранного пользователем файла`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/file`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Скачивание готового `,(0,c.jsx)(t.code,{children:`Blob`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/dom`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Готовый dialog/modal/popover`}),(0,c.jsx)(t.td,{children:`UI-пакет или UI-слой приложения`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Низкоуровневый focus/outside/Escape contract`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/dom`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Responsive styling`}),(0,c.jsxs)(t.td,{children:[`CSS и `,(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/media`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Копирование plain text в clipboard`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/copy`})})]})]})]}),`
75
+ `,(0,c.jsxs)(t.p,{children:[`Если приложению нужен готовый UI primitive, сначала ищите его в `,(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-ui`}),`. Модуль `,(0,c.jsx)(t.code,{children:`dom`}),` нужен авторам primitives и для специальных browser-сценариев, а не для повторной сборки локальных modal/dialog в каждом feature.`]}),`
76
+ `,(0,c.jsx)(t.h2,{id:`установка-и-импорт`,children:`Установка и импорт`}),`
77
+ `,(0,c.jsx)(t.p,{children:`Установите пакет:`}),`
78
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-bash`,children:`npm install @ryuzaki13/react-foundation-lib
79
+ `})}),`
80
+ `,(0,c.jsx)(t.p,{children:`Entrypoint экспортирует React hooks, поэтому host-приложение должно предоставить совместимые peer-зависимости:`}),`
81
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-bash`,children:`npm install react react-dom
82
+ `})}),`
83
+ `,(0,c.jsxs)(t.p,{children:[`Текущий контракт пакета требует React `,(0,c.jsx)(t.code,{children:`>=19.2.0 <20.0.0`}),`.`]}),`
84
+ `,(0,c.jsxs)(t.p,{children:[`Импортируйте API только из опубликованного subpath `,(0,c.jsx)(t.code,{children:`/dom`}),`:`]}),`
85
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import {
86
+ downloadFileFromBlob,
87
+ downloadFileFromJson,
88
+ downloadFileFromObjectURL,
89
+ getOrCreatePortalRoot,
90
+ useClickOutside,
91
+ useElementHeightObserver,
92
+ useEscapeDismiss,
93
+ useFocusTrap,
94
+ useIntersectionObserver,
95
+ useIsTouchDevice,
96
+ useOverlayFocus
97
+ } from "@ryuzaki13/react-foundation-lib/dom";
98
+
99
+ import type { UseOverlayFocusOptions } from "@ryuzaki13/react-foundation-lib/dom";
100
+ `})}),`
101
+ `,(0,c.jsx)(t.p,{children:`Корневой импорт пакета не поддерживается:`}),`
102
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Неправильно.
103
+ import { useOverlayFocus } from "@ryuzaki13/react-foundation-lib";
104
+
105
+ // Правильно.
106
+ import { useOverlayFocus } from "@ryuzaki13/react-foundation-lib/dom";
107
+ `})}),`
108
+ `,(0,c.jsx)(t.p,{children:`Модуль поставляется как ESM.`}),`
109
+ `,(0,c.jsx)(t.h2,{id:`runtime-и-browser-boundary`,children:`Runtime и browser boundary`}),`
110
+ `,(0,c.jsx)(t.p,{children:`Почти весь модуль опирается на Web APIs:`}),`
111
+ `,(0,c.jsxs)(t.ul,{children:[`
112
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`window`}),`;`]}),`
113
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`document`}),`;`]}),`
114
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`HTMLElement`}),` и `,(0,c.jsx)(t.code,{children:`Node`}),`;`]}),`
115
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`Blob`}),`;`]}),`
116
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`URL.createObjectURL`}),` и `,(0,c.jsx)(t.code,{children:`URL.revokeObjectURL`}),`;`]}),`
117
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`ResizeObserver`}),`;`]}),`
118
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`IntersectionObserver`}),`;`]}),`
119
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`matchMedia`}),`;`]}),`
120
+ `,(0,c.jsx)(t.li,{children:`focus и keyboard events;`}),`
121
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`requestAnimationFrame`}),`.`]}),`
122
+ `]}),`
123
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`getOrCreatePortalRoot`}),` имеет явную проверку отсутствующего `,(0,c.jsx)(t.code,{children:`document`}),` и возвращает `,(0,c.jsx)(t.code,{children:`null`}),`. Остальные helpers не превращаются в универсальный server API: вызывайте их на browser boundary.`]}),`
124
+ `,(0,c.jsx)(t.p,{children:`React effects не выполняются во время обычного server render, но это не означает, что любой импорт или вызов автоматически безопасен в любой SSR/test среде. Для Node-only кода используйте соответствующий server API, а для тестов предоставляйте DOM и нужные mocks.`}),`
125
+ `,(0,c.jsx)(t.h2,{id:`минимальные-понятия`,children:`Минимальные понятия`}),`
126
+ `,(0,c.jsx)(t.h3,{id:`dom`,children:`DOM`}),`
127
+ `,(0,c.jsx)(t.p,{children:`DOM — объектное представление HTML-документа. Браузер превращает разметку:`}),`
128
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-html`,children:`<button type="button">Сохранить</button>
129
+ `})}),`
130
+ `,(0,c.jsxs)(t.p,{children:[`в объект `,(0,c.jsx)(t.code,{children:`HTMLButtonElement`}),`, у которого есть свойства, методы, focus и события.`]}),`
131
+ `,(0,c.jsxs)(t.h3,{id:`window-и-document`,children:[(0,c.jsx)(t.code,{children:`window`}),` и `,(0,c.jsx)(t.code,{children:`document`})]}),`
132
+ `,(0,c.jsxs)(t.ul,{children:[`
133
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`window`}),` представляет браузерное окно и глобальные browser APIs;`]}),`
134
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`document`}),` представляет текущий HTML-документ;`]}),`
135
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`document.body`}),` — элемент `,(0,c.jsx)(t.code,{children:`<body>`}),`.`]}),`
136
+ `]}),`
137
+ `,(0,c.jsx)(t.p,{children:`Эти объекты отсутствуют в обычном Node.js process и могут быть частично реализованы в тестовой DOM-среде.`}),`
138
+ `,(0,c.jsx)(t.h3,{id:`react-ref`,children:`React ref`}),`
139
+ `,(0,c.jsx)(t.p,{children:`Ref хранит ссылку на реальный DOM-элемент:`}),`
140
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const buttonRef = useRef<HTMLButtonElement>(null);
141
+
142
+ return <button ref={buttonRef}>Кнопка</button>;
143
+ `})}),`
144
+ `,(0,c.jsxs)(t.p,{children:[`До commit/mount `,(0,c.jsx)(t.code,{children:`buttonRef.current`}),` равен `,(0,c.jsx)(t.code,{children:`null`}),`. После commit React помещает туда `,(0,c.jsx)(t.code,{children:`HTMLButtonElement`}),`. Читайте ref в event handler или effect, а не во время render.`]}),`
145
+ `,(0,c.jsx)(t.h3,{id:`effect-и-cleanup`,children:`Effect и cleanup`}),`
146
+ `,(0,c.jsx)(t.p,{children:`Effect синхронизирует компонент с внешней системой:`}),`
147
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`useEffect(() => {
148
+ document.addEventListener("example", handler);
149
+
150
+ return () => {
151
+ document.removeEventListener("example", handler);
152
+ };
153
+ }, [handler]);
154
+ `})}),`
155
+ `,(0,c.jsx)(t.p,{children:`Возвращаемая функция называется cleanup. React вызывает её перед повторной подпиской и при unmount. Hooks этого модуля сами очищают свои listeners и observers, если component lifecycle дошёл до cleanup.`}),`
156
+ `,(0,c.jsx)(t.h3,{id:`event-bubbling-и-capture`,children:`Event bubbling и capture`}),`
157
+ `,(0,c.jsx)(t.p,{children:`DOM-событие проходит несколько фаз:`}),`
158
+ `,(0,c.jsxs)(t.ol,{children:[`
159
+ `,(0,c.jsx)(t.li,{children:`capture — от документа к target;`}),`
160
+ `,(0,c.jsx)(t.li,{children:`target;`}),`
161
+ `,(0,c.jsx)(t.li,{children:`bubbling — от target обратно к документу.`}),`
162
+ `]}),`
163
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useEscapeDismiss`}),` слушает `,(0,c.jsx)(t.code,{children:`keydown`}),` в capture-фазе. `,(0,c.jsx)(t.code,{children:`useClickOutside`}),` слушает `,(0,c.jsx)(t.code,{children:`mousedown`}),` в обычной bubbling-фазе.`]}),`
164
+ `,(0,c.jsx)(t.h3,{id:`portal`,children:`Portal`}),`
165
+ `,(0,c.jsx)(t.p,{children:`React portal рендерит subtree в другом месте DOM:`}),`
166
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`createPortal(<Dialog />, portalRoot);
167
+ `})}),`
168
+ `,(0,c.jsxs)(t.p,{children:[`Компонент остаётся частью того же React tree, но его DOM может находиться прямо в `,(0,c.jsx)(t.code,{children:`document.body`}),`, вне layout-контейнера приложения.`]}),`
169
+ `,(0,c.jsx)(t.h3,{id:`blob`,children:(0,c.jsx)(t.code,{children:`Blob`})}),`
170
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`Blob`}),` хранит последовательность байтов и MIME:`]}),`
171
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const blob = new Blob(["Пример"], {
172
+ type: "text/plain;charset=utf-8"
173
+ });
174
+ `})}),`
175
+ `,(0,c.jsx)(t.p,{children:`Blob сам по себе не является URL. Для browser download из него создаётся временный object URL.`}),`
176
+ `,(0,c.jsx)(t.h3,{id:`object-url`,children:`Object URL`}),`
177
+ `,(0,c.jsxs)(t.p,{children:[`Object URL — временная browser-ссылка на `,(0,c.jsx)(t.code,{children:`Blob`}),`:`]}),`
178
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const objectUrl = URL.createObjectURL(blob);
179
+ `})}),`
180
+ `,(0,c.jsx)(t.p,{children:`Примерный вид:`}),`
181
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`blob:https://example.test/...
182
+ `})}),`
183
+ `,(0,c.jsxs)(t.p,{children:[`Object URL удерживает ресурс в памяти, пока не будет вызван `,(0,c.jsx)(t.code,{children:`URL.revokeObjectURL`}),` либо пока документ не будет выгружен.`]}),`
184
+ `,(0,c.jsx)(t.h3,{id:`observer`,children:`Observer`}),`
185
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`ResizeObserver`}),` сообщает об изменении размера элемента. `,(0,c.jsx)(t.code,{children:`IntersectionObserver`}),` сообщает, пересекается ли элемент с viewport или заданным root.`]}),`
186
+ `,(0,c.jsx)(t.p,{children:`Оба API работают асинхронно: первое значение hook может быть default, а реальное состояние приходит позднее через callback.`}),`
187
+ `,(0,c.jsx)(t.h3,{id:`focus`,children:`Focus`}),`
188
+ `,(0,c.jsxs)(t.p,{children:[`Focus определяет, какой элемент получает keyboard input. `,(0,c.jsx)(t.code,{children:`document.activeElement`}),` показывает текущий focus target.`]}),`
189
+ `,(0,c.jsxs)(t.p,{children:[`Кнопки, ссылки и form controls обычно focusable сами. Обычный `,(0,c.jsx)(t.code,{children:`<div>`}),` не становится focusable автоматически. Для программного focus контейнеру часто нужен:`]}),`
190
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`<div tabIndex={-1}>...</div>
191
+ `})}),`
192
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`tabIndex={-1}`}),` разрешает `,(0,c.jsx)(t.code,{children:`.focus()`}),`, но не добавляет контейнер в обычный Tab-порядок.`]}),`
193
+ `,(0,c.jsx)(t.h2,{id:`как-выбрать-api`,children:`Как выбрать API`}),`
194
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Нужно`}),(0,c.jsx)(t.th,{children:`API`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Скачать уже созданный object URL и сразу передать ownership helper-у`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`downloadFileFromObjectURL`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Скачать готовый `,(0,c.jsx)(t.code,{children:`Blob`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`downloadFileFromBlob`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Сериализовать значение в readable JSON и скачать`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`downloadFileFromJson`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Получить общий container для `,(0,c.jsx)(t.code,{children:`createPortal`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`getOrCreatePortalRoot`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Закрыть UI по `,(0,c.jsx)(t.code,{children:`mousedown`}),` вне одного элемента`]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useClickOutside`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Считать trigger и portal content одной «внутренней» областью`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`useClickOutside`}),` с массивом refs`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Закрыть активный overlay по `,(0,c.jsx)(t.code,{children:`Escape`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useEscapeDismiss`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Измерять высоту элемента`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useElementHeightObserver`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Узнать, пересекается ли элемент с viewport/root`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useIntersectionObserver`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Получить touch/coarse-pointer heuristic`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useIsTouchDevice`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Настроить initial focus и optional trap/restore`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useOverlayFocus`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Быстро подключить фиксированную focus trap для `,(0,c.jsx)(t.code,{children:`HTMLDivElement`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useFocusTrap`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Получить полностью доступный modal/dialog`}),(0,c.jsx)(t.td,{children:`Готовый UI primitive`})]})]})]}),`
195
+ `,(0,c.jsx)(t.h2,{id:`быстрый-старт`,children:`Быстрый старт`}),`
196
+ `,(0,c.jsxs)(t.h3,{id:`скачать-blob`,children:[`Скачать `,(0,c.jsx)(t.code,{children:`Blob`})]}),`
197
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { downloadFileFromBlob } from "@ryuzaki13/react-foundation-lib/dom";
198
+
199
+ export function DownloadTextButton() {
200
+ const handleDownload = () => {
201
+ const blob = new Blob(["Привет!"], {
202
+ type: "text/plain;charset=utf-8"
203
+ });
204
+
205
+ downloadFileFromBlob("hello.txt", blob);
206
+ };
207
+
208
+ return (
209
+ <button type="button" onClick={handleDownload}>
210
+ Скачать
211
+ </button>
212
+ );
213
+ }
214
+ `})}),`
215
+ `,(0,c.jsx)(t.h3,{id:`скачать-json`,children:`Скачать JSON`}),`
216
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { downloadFileFromJson } from "@ryuzaki13/react-foundation-lib/dom";
217
+
218
+ const settings = {
219
+ theme: "dark",
220
+ pageSize: 50
221
+ };
222
+
223
+ export function ExportSettingsButton() {
224
+ return (
225
+ <button type="button" onClick={() => downloadFileFromJson("settings.json", settings)}>
226
+ Экспортировать настройки
227
+ </button>
228
+ );
229
+ }
230
+ `})}),`
231
+ `,(0,c.jsx)(t.h3,{id:`создать-portal-root-во-время-browser-bootstrap`,children:`Создать portal root во время browser bootstrap`}),`
232
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { createRoot } from "react-dom/client";
233
+ import { getOrCreatePortalRoot } from "@ryuzaki13/react-foundation-lib/dom";
234
+
235
+ const appRoot = document.getElementById("root");
236
+ const overlayRoot = getOrCreatePortalRoot("app-overlay-root");
237
+
238
+ if (!appRoot || !overlayRoot) {
239
+ throw new Error("Не удалось подготовить DOM roots приложения");
240
+ }
241
+
242
+ createRoot(appRoot).render(<App overlayRoot={overlayRoot} />);
243
+ `})}),`
244
+ `,(0,c.jsx)(t.p,{children:`Так DOM mutation выполняется на явной application boundary, а не скрывается внутри render компонента.`}),`
245
+ `,(0,c.jsx)(t.h3,{id:`закрыть-popover-по-клику-снаружи`,children:`Закрыть popover по клику снаружи`}),`
246
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { useCallback, useRef, useState } from "react";
247
+ import { useClickOutside } from "@ryuzaki13/react-foundation-lib/dom";
248
+
249
+ export function SimplePopover() {
250
+ const [open, setOpen] = useState(false);
251
+ const panelRef = useRef<HTMLDivElement>(null);
252
+
253
+ const close = useCallback(() => {
254
+ setOpen(false);
255
+ }, []);
256
+
257
+ useClickOutside(panelRef, close);
258
+
259
+ return (
260
+ <div>
261
+ <button type="button" onClick={() => setOpen(true)}>
262
+ Открыть
263
+ </button>
264
+ {open ? <div ref={panelRef}>Содержимое</div> : null}
265
+ </div>
266
+ );
267
+ }
268
+ `})}),`
269
+ `,(0,c.jsxs)(t.p,{children:[`В этом упрощённом примере trigger не включён во внутреннюю область. `,(0,c.jsx)(t.code,{children:`mousedown`}),` по trigger может сначала вызвать outside handler, а затем его `,(0,c.jsx)(t.code,{children:`click`}),`. Для toggle/popover обычно передают два refs.`]}),`
270
+ `,(0,c.jsx)(t.h3,{id:`наблюдать-пересечение`,children:`Наблюдать пересечение`}),`
271
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { useMemo, useRef } from "react";
272
+ import { useIntersectionObserver } from "@ryuzaki13/react-foundation-lib/dom";
273
+
274
+ export function LazySection() {
275
+ const sectionRef = useRef<HTMLElement>(null);
276
+ const options = useMemo<IntersectionObserverInit>(
277
+ () => ({
278
+ rootMargin: "200px 0px",
279
+ threshold: 0.1
280
+ }),
281
+ []
282
+ );
283
+ const { isIntersecting } = useIntersectionObserver(sectionRef, options);
284
+
285
+ return <section ref={sectionRef}>{isIntersecting ? <ExpensiveContent /> : <Placeholder />}</section>;
286
+ }
287
+ `})}),`
288
+ `,(0,c.jsxs)(t.p,{children:[`Стабильный `,(0,c.jsx)(t.code,{children:`options`}),` не заставляет effect пересоздавать observer при каждом render.`]}),`
289
+ `,(0,c.jsx)(t.h2,{id:`скачивание-файлов`,children:`Скачивание файлов`}),`
290
+ `,(0,c.jsx)(t.p,{children:`Модуль экспортирует три синхронные функции:`}),`
291
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function downloadFileFromObjectURL(filename: string, objectUrl: string): void;
292
+ declare function downloadFileFromBlob(filename: string, blob: Blob): void;
293
+ declare function downloadFileFromJson(filename: string, payload: unknown): void;
294
+ `})}),`
295
+ `,(0,c.jsxs)(t.p,{children:[`Они запускают browser download и ничего не возвращают. Результат `,(0,c.jsx)(t.code,{children:`void`}),` не означает, что пользователь сохранил файл: helper не получает подтверждение от download manager браузера.`]}),`
296
+ `,(0,c.jsx)(t.h3,{id:`downloadfilefromobjecturl`,children:(0,c.jsx)(t.code,{children:`downloadFileFromObjectURL`})}),`
297
+ `,(0,c.jsxs)(t.p,{children:[`Создаёт `,(0,c.jsx)(t.code,{children:`<a>`}),`, устанавливает:`]}),`
298
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`link.href = objectUrl;
299
+ link.download = filename;
300
+ `})}),`
301
+ `,(0,c.jsxs)(t.p,{children:[`затем вызывает `,(0,c.jsx)(t.code,{children:`link.click()`}),`. Элемент не добавляется в `,(0,c.jsx)(t.code,{children:`document.body`}),`.`]}),`
302
+ `,(0,c.jsxs)(t.p,{children:[`После вызова `,(0,c.jsx)(t.code,{children:`click`}),` helper планирует:`]}),`
303
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`window.setTimeout(() => {
304
+ window.URL.revokeObjectURL(objectUrl);
305
+ }, 0);
306
+ `})}),`
307
+ `,(0,c.jsx)(t.h4,{id:`ownership-object-url`,children:`Ownership object URL`}),`
308
+ `,(0,c.jsxs)(t.p,{children:[`Передавая URL в `,(0,c.jsx)(t.code,{children:`downloadFileFromObjectURL`}),`, считайте, что отдаёте helper-у ownership. Он освободит URL в следующем macrotask:`]}),`
309
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const objectUrl = URL.createObjectURL(blob);
310
+ downloadFileFromObjectURL("report.pdf", objectUrl);
311
+
312
+ // Не используйте objectUrl для preview или второго download:
313
+ // helper уже запланировал revokeObjectURL.
314
+ `})}),`
315
+ `,(0,c.jsx)(t.p,{children:`Если один URL нужен и для preview, и для download, создайте отдельный URL для каждой lifetime-задачи либо управляйте download самостоятельно.`}),`
316
+ `,(0,c.jsxs)(t.h4,{id:`параметр-filename`,children:[`Параметр `,(0,c.jsx)(t.code,{children:`filename`})]}),`
317
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`filename`}),` передаётся в свойство `,(0,c.jsx)(t.code,{children:`download`}),` буквально:`]}),`
318
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`downloadFileFromObjectURL("Отчёт за август.pdf", objectUrl);
319
+ `})}),`
320
+ `,(0,c.jsx)(t.p,{children:`Helper:`}),`
321
+ `,(0,c.jsxs)(t.ul,{children:[`
322
+ `,(0,c.jsx)(t.li,{children:`не добавляет расширение;`}),`
323
+ `,(0,c.jsx)(t.li,{children:`не удаляет path separators;`}),`
324
+ `,(0,c.jsx)(t.li,{children:`не исправляет недопустимые для ОС символы;`}),`
325
+ `,(0,c.jsx)(t.li,{children:`не проверяет MIME;`}),`
326
+ `,(0,c.jsx)(t.li,{children:`не гарантирует точное финальное имя.`}),`
327
+ `]}),`
328
+ `,(0,c.jsx)(t.p,{children:`Браузер и ОС могут изменить или проигнорировать предложенное имя.`}),`
329
+ `,(0,c.jsx)(t.h4,{id:`это-именно-object-url`,children:`Это именно object URL`}),`
330
+ `,(0,c.jsxs)(t.p,{children:[`Функция вызывает `,(0,c.jsx)(t.code,{children:`URL.revokeObjectURL`}),`, поэтому предназначена для URL, созданного через `,(0,c.jsx)(t.code,{children:`URL.createObjectURL`}),`. Не используйте её как общий helper для произвольного HTTP URL.`]}),`
331
+ `,(0,c.jsx)(t.h3,{id:`downloadfilefromblob`,children:(0,c.jsx)(t.code,{children:`downloadFileFromBlob`})}),`
332
+ `,(0,c.jsxs)(t.p,{children:[`Принимает готовый `,(0,c.jsx)(t.code,{children:`Blob`}),`:`]}),`
333
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`downloadFileFromBlob("report.txt", blob);
334
+ `})}),`
335
+ `,(0,c.jsx)(t.p,{children:`Алгоритм:`}),`
336
+ `,(0,c.jsxs)(t.ol,{children:[`
337
+ `,(0,c.jsxs)(t.li,{children:[`создаёт object URL через `,(0,c.jsx)(t.code,{children:`window.URL.createObjectURL(blob)`}),`;`]}),`
338
+ `,(0,c.jsxs)(t.li,{children:[`передаёт его в `,(0,c.jsx)(t.code,{children:`downloadFileFromObjectURL`}),`;`]}),`
339
+ `,(0,c.jsx)(t.li,{children:`запускает click;`}),`
340
+ `,(0,c.jsx)(t.li,{children:`планирует освобождение URL.`}),`
341
+ `]}),`
342
+ `,(0,c.jsxs)(t.p,{children:[`Это предпочтительный API, если вызывающий код уже имеет `,(0,c.jsx)(t.code,{children:`Blob`}),`.`]}),`
343
+ `,(0,c.jsx)(t.h4,{id:`blob-не-уничтожается`,children:`Blob не уничтожается`}),`
344
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`revokeObjectURL`}),` освобождает связь URL с данными. Он не изменяет сам `,(0,c.jsx)(t.code,{children:`Blob`}),`:`]}),`
345
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`downloadFileFromBlob("first.bin", blob);
346
+
347
+ // Сам blob можно передать повторно:
348
+ downloadFileFromBlob("second.bin", blob);
349
+ `})}),`
350
+ `,(0,c.jsx)(t.p,{children:`Каждый вызов создаст собственный временный URL.`}),`
351
+ `,(0,c.jsx)(t.h3,{id:`downloadfilefromjson`,children:(0,c.jsx)(t.code,{children:`downloadFileFromJson`})}),`
352
+ `,(0,c.jsx)(t.p,{children:`Сериализует значение:`}),`
353
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`JSON.stringify(payload, null, 2);
354
+ `})}),`
355
+ `,(0,c.jsx)(t.p,{children:`Затем создаёт:`}),`
356
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`new Blob([serialized], {
357
+ type: "application/json;charset=utf-8"
358
+ });
359
+ `})}),`
360
+ `,(0,c.jsxs)(t.p,{children:[`и делегирует скачивание `,(0,c.jsx)(t.code,{children:`downloadFileFromBlob`}),`.`]}),`
361
+ `,(0,c.jsx)(t.h4,{id:`формат`,children:`Формат`}),`
362
+ `,(0,c.jsxs)(t.ul,{children:[`
363
+ `,(0,c.jsx)(t.li,{children:`отступ — два пробела;`}),`
364
+ `,(0,c.jsx)(t.li,{children:`encoding Blob — UTF-8-compatible browser string;`}),`
365
+ `,(0,c.jsxs)(t.li,{children:[`MIME — `,(0,c.jsx)(t.code,{children:`application/json;charset=utf-8`}),`;`]}),`
366
+ `,(0,c.jsx)(t.li,{children:`BOM не добавляется;`}),`
367
+ `,(0,c.jsx)(t.li,{children:`перевод строки в конец специально не добавляется.`}),`
368
+ `]}),`
369
+ `,(0,c.jsx)(t.p,{children:`Пример:`}),`
370
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`downloadFileFromJson("user.json", {
371
+ id: 42,
372
+ name: "Анна"
373
+ });
374
+ `})}),`
375
+ `,(0,c.jsx)(t.p,{children:`Содержимое:`}),`
376
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-json`,children:`{
377
+ "id": 42,
378
+ "name": "Анна"
379
+ }
380
+ `})}),`
381
+ `,(0,c.jsxs)(t.h4,{id:`правила-jsonstringify`,children:[`Правила `,(0,c.jsx)(t.code,{children:`JSON.stringify`})]}),`
382
+ `,(0,c.jsx)(t.p,{children:`Helper наследует стандартное поведение JavaScript:`}),`
383
+ `,(0,c.jsxs)(t.ul,{children:[`
384
+ `,(0,c.jsxs)(t.li,{children:[`object properties со значением `,(0,c.jsx)(t.code,{children:`undefined`}),`, function или `,(0,c.jsx)(t.code,{children:`symbol`}),` пропускаются;`]}),`
385
+ `,(0,c.jsxs)(t.li,{children:[`такие значения внутри array превращаются в `,(0,c.jsx)(t.code,{children:`null`}),`;`]}),`
386
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`Date`}),` обычно превращается в ISO string через `,(0,c.jsx)(t.code,{children:`toJSON`}),`;`]}),`
387
+ `,(0,c.jsxs)(t.li,{children:[`custom `,(0,c.jsx)(t.code,{children:`toJSON`}),` выполняется;`]}),`
388
+ `,(0,c.jsxs)(t.li,{children:[`circular reference вызывает `,(0,c.jsx)(t.code,{children:`TypeError`}),`;`]}),`
389
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`BigInt`}),` без custom serialization вызывает `,(0,c.jsx)(t.code,{children:`TypeError`}),`;`]}),`
390
+ `,(0,c.jsxs)(t.li,{children:[`для root `,(0,c.jsx)(t.code,{children:`undefined`}),`, function или symbol `,(0,c.jsx)(t.code,{children:`JSON.stringify`}),` возвращает `,(0,c.jsx)(t.code,{children:`undefined`}),`; browser преобразует этот BlobPart в текст `,(0,c.jsx)(t.code,{children:`"undefined"`}),`, то есть скачанный файл не будет корректным JSON.`]}),`
391
+ `]}),`
392
+ `,(0,c.jsx)(t.p,{children:`Ошибки сериализации не перехватываются:`}),`
393
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const cyclic: Record<string, unknown> = {};
394
+ cyclic.self = cyclic;
395
+
396
+ downloadFileFromJson("cyclic.json", cyclic); // TypeError.
397
+ `})}),`
398
+ `,(0,c.jsx)(t.p,{children:`Если payload пришёл с внешней boundary, валидируйте и подготавливайте его до вызова.`}),`
399
+ `,(0,c.jsx)(t.h3,{id:`пользовательское-действие`,children:`Пользовательское действие`}),`
400
+ `,(0,c.jsx)(t.p,{children:`Браузер может блокировать автоматические downloads, не связанные с user activation. Предпочтительно вызывать helper из click/keyboard command:`}),`
401
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`<button type="button" onClick={handleDownload}>
402
+ Скачать
403
+ </button>
404
+ `})}),`
405
+ `,(0,c.jsx)(t.p,{children:`Не запускайте download во время render.`}),`
406
+ `,(0,c.jsx)(t.h3,{id:`ошибки-download-helpers`,children:`Ошибки download helpers`}),`
407
+ `,(0,c.jsxs)(t.p,{children:[`Функции не используют `,(0,c.jsx)(t.code,{children:`try/catch`}),` и не возвращают `,(0,c.jsx)(t.code,{children:`Promise`}),`. Ошибка доступа к DOM, создания URL, сериализации или синтетического click будет выброшена синхронно.`]}),`
408
+ `,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`link.click()`}),` выбросит ошибку, timer освобождения URL ещё не будет запланирован. Вызывающий boundary должен обработать ошибку и cleanup, если это значимо для среды.`]}),`
409
+ `,(0,c.jsx)(t.h2,{id:`getorcreateportalroot`,children:(0,c.jsx)(t.code,{children:`getOrCreatePortalRoot`})}),`
410
+ `,(0,c.jsxs)(t.p,{children:[`Возвращает существующий `,(0,c.jsx)(t.code,{children:`HTMLElement`}),` с заданным ID либо создаёт новый `,(0,c.jsx)(t.code,{children:`<div>`}),` в `,(0,c.jsx)(t.code,{children:`document.body`}),`.`]}),`
411
+ `,(0,c.jsx)(t.h3,{id:`сигнатура`,children:`Сигнатура`}),`
412
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function getOrCreatePortalRoot(id: string): HTMLElement | null;
413
+ `})}),`
414
+ `,(0,c.jsx)(t.h3,{id:`алгоритм`,children:`Алгоритм`}),`
415
+ `,(0,c.jsxs)(t.ol,{children:[`
416
+ `,(0,c.jsxs)(t.li,{children:[`Если global `,(0,c.jsx)(t.code,{children:`document`}),` отсутствует, возвращает `,(0,c.jsx)(t.code,{children:`null`}),`.`]}),`
417
+ `,(0,c.jsxs)(t.li,{children:[`Выполняет `,(0,c.jsx)(t.code,{children:`document.getElementById(id)`}),`.`]}),`
418
+ `,(0,c.jsxs)(t.li,{children:[`Если результат является `,(0,c.jsx)(t.code,{children:`HTMLElement`}),`, возвращает его.`]}),`
419
+ `,(0,c.jsxs)(t.li,{children:[`Иначе создаёт `,(0,c.jsx)(t.code,{children:`document.createElement("div")`}),`.`]}),`
420
+ `,(0,c.jsxs)(t.li,{children:[`Устанавливает `,(0,c.jsx)(t.code,{children:`portalRoot.id = id`}),`.`]}),`
421
+ `,(0,c.jsxs)(t.li,{children:[`Добавляет элемент в `,(0,c.jsx)(t.code,{children:`document.body`}),`.`]}),`
422
+ `,(0,c.jsx)(t.li,{children:`Возвращает созданный element.`}),`
423
+ `]}),`
424
+ `,(0,c.jsx)(t.h3,{id:`повторное-использование`,children:`Повторное использование`}),`
425
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const first = getOrCreatePortalRoot("dialog-root");
426
+ const second = getOrCreatePortalRoot("dialog-root");
427
+
428
+ console.log(first === second); // true в обычном browser document.
429
+ `})}),`
430
+ `,(0,c.jsx)(t.p,{children:`Idempotency действует, когда:`}),`
431
+ `,(0,c.jsxs)(t.ul,{children:[`
432
+ `,(0,c.jsx)(t.li,{children:`ID непустой и стабильный;`}),`
433
+ `,(0,c.jsxs)(t.li,{children:[`существующий element является `,(0,c.jsx)(t.code,{children:`HTMLElement`}),`;`]}),`
434
+ `,(0,c.jsx)(t.li,{children:`никто не удалил или не заменил element между вызовами.`}),`
435
+ `]}),`
436
+ `,(0,c.jsxs)(t.h3,{id:`использование-с-createportal`,children:[`Использование с `,(0,c.jsx)(t.code,{children:`createPortal`})]}),`
437
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { createPortal } from "react-dom";
438
+
439
+ function Overlay({ portalRoot }: { portalRoot: HTMLElement }) {
440
+ return createPortal(<div role="presentation">Overlay content</div>, portalRoot);
441
+ }
442
+ `})}),`
443
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`getOrCreatePortalRoot`}),` только возвращает container. Сам React portal создаётся функцией `,(0,c.jsx)(t.code,{children:`createPortal`}),` из `,(0,c.jsx)(t.code,{children:`react-dom`}),`.`]}),`
444
+ `,(0,c.jsx)(t.h3,{id:`browser-bootstrap-предпочтительнее-render`,children:`Browser bootstrap предпочтительнее render`}),`
445
+ `,(0,c.jsx)(t.p,{children:`Функция изменяет DOM. Render React-компонента должен оставаться чистым, поэтому предпочтительно:`}),`
446
+ `,(0,c.jsxs)(t.ul,{children:[`
447
+ `,(0,c.jsx)(t.li,{children:`объявить portal container в HTML shell;`}),`
448
+ `,(0,c.jsx)(t.li,{children:`получить его во время application bootstrap;`}),`
449
+ `,(0,c.jsx)(t.li,{children:`создать его на явной browser boundary;`}),`
450
+ `,(0,c.jsx)(t.li,{children:`передать готовый element компонентам или provider.`}),`
451
+ `]}),`
452
+ `,(0,c.jsxs)(t.p,{children:[`Не вызывайте функцию в `,(0,c.jsx)(t.code,{children:`useMemo`}),`: memoization не предназначена для side effects.`]}),`
453
+ `,(0,c.jsx)(t.h3,{id:`функция-не-удаляет-root`,children:`Функция не удаляет root`}),`
454
+ `,(0,c.jsxs)(t.p,{children:[`Созданный element остаётся в `,(0,c.jsx)(t.code,{children:`document.body`}),`, даже если все portals закрыты. Это подходит для application-wide roots:`]}),`
455
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`dialog-root
456
+ popover-root
457
+ notifications-root
458
+ `})}),`
459
+ `,(0,c.jsx)(t.p,{children:`Для временного root с ограниченным lifetime вызывающий код сам владеет удалением.`}),`
460
+ `,(0,c.jsx)(t.h3,{id:`ограничения-id-и-body`,children:`Ограничения ID и body`}),`
461
+ `,(0,c.jsxs)(t.ul,{children:[`
462
+ `,(0,c.jsx)(t.li,{children:`ID не очищается и не валидируется;`}),`
463
+ `,(0,c.jsx)(t.li,{children:`пустой ID не является безопасным application contract;`}),`
464
+ `,(0,c.jsx)(t.li,{children:`пользовательский ввод не должен становиться ID без validation;`}),`
465
+ `,(0,c.jsxs)(t.li,{children:[`если element с тем же ID существует, но не является `,(0,c.jsx)(t.code,{children:`HTMLElement`}),` (например, SVG element), helper создаст новый `,(0,c.jsx)(t.code,{children:`div`}),` с тем же ID;`]}),`
466
+ `,(0,c.jsx)(t.li,{children:`duplicate IDs делают DOM невалидным и ломают однозначный поиск;`}),`
467
+ `,(0,c.jsxs)(t.li,{children:[`если `,(0,c.jsx)(t.code,{children:`document`}),` существует, но `,(0,c.jsx)(t.code,{children:`document.body`}),` ещё не создан, append завершится ошибкой.`]}),`
468
+ `]}),`
469
+ `,(0,c.jsxs)(t.p,{children:[`Вызывайте helper после подготовки `,(0,c.jsx)(t.code,{children:`body`}),` и используйте контролируемый уникальный ID.`]}),`
470
+ `,(0,c.jsx)(t.h3,{id:`ssr`,children:`SSR`}),`
471
+ `,(0,c.jsx)(t.p,{children:`На server:`}),`
472
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`getOrCreatePortalRoot("dialog-root"); // null, если document отсутствует.
473
+ `})}),`
474
+ `,(0,c.jsxs)(t.p,{children:[`Не кешируйте этот `,(0,c.jsx)(t.code,{children:`null`}),` в server-loaded module и не ожидайте, что он автоматически станет HTMLElement на client. Portal target должен разрешаться на корректной browser lifecycle boundary.`]}),`
475
+ `,(0,c.jsx)(t.h2,{id:`useclickoutside`,children:(0,c.jsx)(t.code,{children:`useClickOutside`})}),`
476
+ `,(0,c.jsxs)(t.p,{children:[`Вызывает handler на `,(0,c.jsx)(t.code,{children:`mousedown`}),`, если target находится вне одного ref или вне всех refs из массива.`]}),`
477
+ `,(0,c.jsx)(t.h3,{id:`сигнатура-1`,children:`Сигнатура`}),`
478
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function useClickOutside(ref: RefObject<HTMLElement | null> | RefObject<HTMLElement | null>[], handler: () => void): void;
479
+ `})}),`
480
+ `,(0,c.jsx)(t.h3,{id:`один-ref`,children:`Один ref`}),`
481
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const panelRef = useRef<HTMLDivElement>(null);
482
+
483
+ useClickOutside(panelRef, close);
484
+
485
+ return <div ref={panelRef}>...</div>;
486
+ `})}),`
487
+ `,(0,c.jsx)(t.p,{children:`Handler вызывается, когда:`}),`
488
+ `,(0,c.jsxs)(t.ul,{children:[`
489
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`panelRef.current`}),` существует;`]}),`
490
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`panelRef.current.contains(event.target)`}),` возвращает `,(0,c.jsx)(t.code,{children:`false`}),`.`]}),`
491
+ `]}),`
492
+ `,(0,c.jsxs)(t.p,{children:[`Если единственный ref равен `,(0,c.jsx)(t.code,{children:`null`}),`, handler не вызывается.`]}),`
493
+ `,(0,c.jsx)(t.h3,{id:`несколько-refs`,children:`Несколько refs`}),`
494
+ `,(0,c.jsx)(t.p,{children:`Массив нужен, когда несколько разнесённых DOM-subtrees считаются одной внутренней областью. Типичный popover:`}),`
495
+ `,(0,c.jsxs)(t.ul,{children:[`
496
+ `,(0,c.jsx)(t.li,{children:`trigger находится в обычном application tree;`}),`
497
+ `,(0,c.jsx)(t.li,{children:`floating content находится в portal.`}),`
498
+ `]}),`
499
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const triggerRef = useRef<HTMLButtonElement>(null);
500
+ const panelRef = useRef<HTMLDivElement>(null);
501
+ const insideRefs = useMemo<RefObject<HTMLElement | null>[]>(() => [triggerRef, panelRef], []);
502
+
503
+ useClickOutside(insideRefs, close);
504
+ `})}),`
505
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`mousedown`}),` внутри любого непустого ref не вызывает handler. Событие снаружи всех refs вызывает.`]}),`
506
+ `,(0,c.jsx)(t.h3,{id:`важное-поведение-null-refs-в-массиве`,children:`Важное поведение null refs в массиве`}),`
507
+ `,(0,c.jsx)(t.p,{children:`Для массива алгоритм начинает с предположения «снаружи» и проверяет только непустые refs. Поэтому:`}),`
508
+ `,(0,c.jsxs)(t.ul,{children:[`
509
+ `,(0,c.jsx)(t.li,{children:`null-элементы массива игнорируются;`}),`
510
+ `,(0,c.jsxs)(t.li,{children:[`если все refs равны `,(0,c.jsx)(t.code,{children:`null`}),`, любой `,(0,c.jsx)(t.code,{children:`mousedown`}),` будет считаться внешним;`]}),`
511
+ `,(0,c.jsx)(t.li,{children:`это отличается от single-ref режима, где null ref блокирует вызов.`}),`
512
+ `]}),`
513
+ `,(0,c.jsx)(t.p,{children:`Не передавайте массив refs до того, как жизненный цикл open/closed состояния продуман. Handler должен уметь безопасно проигнорировать событие, когда overlay закрыт.`}),`
514
+ `,(0,c.jsxs)(t.h3,{id:`событие-mousedown`,children:[`Событие `,(0,c.jsx)(t.code,{children:`mousedown`})]}),`
515
+ `,(0,c.jsx)(t.p,{children:`Hook слушает:`}),`
516
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`document.addEventListener("mousedown", handleClickOutside);
517
+ `})}),`
518
+ `,(0,c.jsx)(t.p,{children:`Следствия:`}),`
519
+ `,(0,c.jsxs)(t.ul,{children:[`
520
+ `,(0,c.jsxs)(t.li,{children:[`handler срабатывает раньше последующего `,(0,c.jsx)(t.code,{children:`click`}),`;`]}),`
521
+ `,(0,c.jsx)(t.li,{children:`touch/pointer abstraction отдельно не используется;`}),`
522
+ `,(0,c.jsxs)(t.li,{children:[`hook не передаёт исходный `,(0,c.jsx)(t.code,{children:`MouseEvent`}),` в callback;`]}),`
523
+ `,(0,c.jsxs)(t.li,{children:[`hook не вызывает `,(0,c.jsx)(t.code,{children:`preventDefault`}),`;`]}),`
524
+ `,(0,c.jsxs)(t.li,{children:[`hook не вызывает `,(0,c.jsx)(t.code,{children:`stopPropagation`}),`.`]}),`
525
+ `]}),`
526
+ `,(0,c.jsx)(t.p,{children:`Если продукту нужен единый pointer contract, right-click policy или event metadata, текущий API может быть недостаточен.`}),`
527
+ `,(0,c.jsx)(t.h3,{id:`descendants-считаются-внутренними`,children:`Descendants считаются внутренними`}),`
528
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`HTMLElement.contains`}),` учитывает сам element и всех DOM-потомков:`]}),`
529
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`<div ref={panelRef}>
530
+ <button type="button">Внутренняя кнопка</button>
531
+ </div>
532
+ `})}),`
533
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`mousedown`}),` по кнопке считается внутренним.`]}),`
534
+ `,(0,c.jsxs)(t.p,{children:[`React ownership не влияет на `,(0,c.jsx)(t.code,{children:`contains`}),`. Portal content является отдельным DOM-subtree и требует отдельного ref.`]}),`
535
+ `,(0,c.jsx)(t.h3,{id:`stable-handler-и-массив`,children:`Stable handler и массив`}),`
536
+ `,(0,c.jsxs)(t.p,{children:[`Effect зависит от `,(0,c.jsx)(t.code,{children:`ref`}),` и `,(0,c.jsx)(t.code,{children:`handler`}),`. Новая ссылка приводит к remove/add listener.`]}),`
537
+ `,(0,c.jsxs)(t.p,{children:[`Для handler используйте `,(0,c.jsx)(t.code,{children:`useCallback`}),`, когда referential stability важна:`]}),`
538
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const close = useCallback(() => {
539
+ setOpen(false);
540
+ }, []);
541
+ `})}),`
542
+ `,(0,c.jsxs)(t.p,{children:[`Массив refs также следует стабилизировать через `,(0,c.jsx)(t.code,{children:`useMemo`}),` либо хранить вне частых renders.`]}),`
543
+ `,(0,c.jsx)(t.h3,{id:`как-условно-отключить`,children:`Как условно отключить`}),`
544
+ `,(0,c.jsx)(t.p,{children:`Hooks нельзя вызывать условно:`}),`
545
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`// Неправильно.
546
+ if (open) {
547
+ useClickOutside(panelRef, close);
548
+ }
549
+ `})}),`
550
+ `,(0,c.jsxs)(t.p,{children:[`У `,(0,c.jsx)(t.code,{children:`useClickOutside`}),` нет параметра `,(0,c.jsx)(t.code,{children:`enabled`}),`. Сохраняйте вызов hook и проверяйте state внутри callback:`]}),`
551
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const closeOutside = useCallback(() => {
552
+ if (open) {
553
+ setOpen(false);
554
+ }
555
+ }, [open]);
556
+
557
+ useClickOutside(panelRef, closeOutside);
558
+ `})}),`
559
+ `,(0,c.jsx)(t.p,{children:`Либо монтируйте отдельный component с hook только на время жизни overlay.`}),`
560
+ `,(0,c.jsx)(t.h3,{id:`cleanup`,children:`Cleanup`}),`
561
+ `,(0,c.jsx)(t.p,{children:`При unmount или изменении dependencies удаляется тот же document listener.`}),`
562
+ `,(0,c.jsxs)(t.p,{children:[`Hook всегда использует global `,(0,c.jsx)(t.code,{children:`document`}),`. Передать document iframe или другого realm нельзя.`]}),`
563
+ `,(0,c.jsx)(t.h2,{id:`useescapedismiss`,children:(0,c.jsx)(t.code,{children:`useEscapeDismiss`})}),`
564
+ `,(0,c.jsxs)(t.p,{children:[`Подписывает активный overlay на закрытие по клавише `,(0,c.jsx)(t.code,{children:`Escape`}),`.`]}),`
565
+ `,(0,c.jsx)(t.h3,{id:`сигнатура-2`,children:`Сигнатура`}),`
566
+ `,(0,c.jsx)(t.p,{children:`Публичная функция принимает объект:`}),`
567
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function useEscapeDismiss(options: {
568
+ active: boolean;
569
+ onDismiss: () => void;
570
+ enabled?: boolean;
571
+ documentTarget?: Document | null;
572
+ containerRef?: RefObject<HTMLElement | null>;
573
+ }): void;
574
+ `})}),`
575
+ `,(0,c.jsx)(t.p,{children:`Named type options отдельно не экспортируется текущим public API.`}),`
576
+ `,(0,c.jsx)(t.h3,{id:`параметры`,children:`Параметры`}),`
577
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Параметр`}),(0,c.jsx)(t.th,{children:`Тип`}),(0,c.jsx)(t.th,{children:`Default`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`active`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`boolean`})}),(0,c.jsx)(t.td,{children:`Обязательный`}),(0,c.jsx)(t.td,{children:`Подключать ли listener для текущего overlay.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onDismiss`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`() => void`})}),(0,c.jsx)(t.td,{children:`Обязательный`}),(0,c.jsx)(t.td,{children:`Команда закрытия.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`enabled`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`boolean`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`true`})}),(0,c.jsx)(t.td,{children:`Разрешено ли закрытие по Escape.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`documentTarget`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`Document | null`})}),(0,c.jsx)(t.td,{children:`Global document`}),(0,c.jsx)(t.td,{children:`Document, на котором слушать keydown.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`containerRef`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`RefObject<HTMLElement | null>`})}),(0,c.jsx)(t.td,{children:`Не задан`}),(0,c.jsx)(t.td,{children:`Ограничить dismiss focus-областью container.`})]})]})]}),`
578
+ `,(0,c.jsx)(t.h3,{id:`условия-подписки`,children:`Условия подписки`}),`
579
+ `,(0,c.jsx)(t.p,{children:`Listener не устанавливается, если:`}),`
580
+ `,(0,c.jsxs)(t.ul,{children:[`
581
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`active === false`}),`;`]}),`
582
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`enabled === false`}),`;`]}),`
583
+ `,(0,c.jsx)(t.li,{children:`не удалось получить target document.`}),`
584
+ `]}),`
585
+ `,(0,c.jsx)(t.p,{children:`В остальных случаях подписка использует capture:`}),`
586
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`targetDocument.addEventListener("keydown", handleKeyDown, true);
587
+ `})}),`
588
+ `,(0,c.jsx)(t.h3,{id:`проверка-клавиши`,children:`Проверка клавиши`}),`
589
+ `,(0,c.jsx)(t.p,{children:`Обрабатывается только:`}),`
590
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`event.key === "Escape";
591
+ `})}),`
592
+ `,(0,c.jsxs)(t.p,{children:[`Legacy значение `,(0,c.jsx)(t.code,{children:`"Esc"`}),` не обрабатывается.`]}),`
593
+ `,(0,c.jsx)(t.p,{children:`При dismiss hook:`}),`
594
+ `,(0,c.jsxs)(t.ol,{children:[`
595
+ `,(0,c.jsxs)(t.li,{children:[`вызывает `,(0,c.jsx)(t.code,{children:`event.preventDefault()`}),`;`]}),`
596
+ `,(0,c.jsxs)(t.li,{children:[`вызывает актуальный `,(0,c.jsx)(t.code,{children:`onDismiss`}),`.`]}),`
597
+ `]}),`
598
+ `,(0,c.jsxs)(t.p,{children:[`Он не вызывает `,(0,c.jsx)(t.code,{children:`stopPropagation`}),`.`]}),`
599
+ `,(0,c.jsxs)(t.h3,{id:`ondismiss-всегда-актуален`,children:[(0,c.jsx)(t.code,{children:`onDismiss`}),` всегда актуален`]}),`
600
+ `,(0,c.jsxs)(t.p,{children:[`Внутри используется React `,(0,c.jsx)(t.code,{children:`useEffectEvent`}),`. Listener не обязан пересоздаваться только потому, что callback получил новую ссылку: при событии вызывается последняя committed версия `,(0,c.jsx)(t.code,{children:`onDismiss`}),`.`]}),`
601
+ `,(0,c.jsx)(t.p,{children:`Не передавайте Effect Event наружу и не вызывайте hook вне React-компонента/custom hook.`}),`
602
+ `,(0,c.jsxs)(t.h3,{id:`ограничение-через-containerref`,children:[`Ограничение через `,(0,c.jsx)(t.code,{children:`containerRef`})]}),`
603
+ `,(0,c.jsxs)(t.p,{children:[`Если container существует, Escape срабатывает только когда `,(0,c.jsx)(t.code,{children:`targetDocument.activeElement`}),`:`]}),`
604
+ `,(0,c.jsxs)(t.ul,{children:[`
605
+ `,(0,c.jsx)(t.li,{children:`равен самому container;`}),`
606
+ `,(0,c.jsx)(t.li,{children:`является его DOM-потомком.`}),`
607
+ `]}),`
608
+ `,(0,c.jsx)(t.p,{children:`Если focus находится снаружи, dismiss игнорируется.`}),`
609
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const panelRef = useRef<HTMLDivElement>(null);
610
+
611
+ useEscapeDismiss({
612
+ active: open,
613
+ onDismiss: close,
614
+ containerRef: panelRef
615
+ });
616
+ `})}),`
617
+ `,(0,c.jsx)(t.p,{children:`Это помогает нескольким обычным overlay не реагировать на Escape, когда keyboard focus принадлежит другой области.`}),`
618
+ `,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`containerRef.current`}),` равен `,(0,c.jsx)(t.code,{children:`null`}),`, focus scope не применяется и Escape может вызвать dismiss.`]}),`
619
+ `,(0,c.jsxs)(t.h3,{id:`без-containerref`,children:[`Без `,(0,c.jsx)(t.code,{children:`containerRef`})]}),`
620
+ `,(0,c.jsx)(t.p,{children:`Любой Escape в target document вызывает dismiss, пока hook active/enabled. Для нескольких активных hooks это может привести к нескольким вызовам.`}),`
621
+ `,(0,c.jsxs)(t.p,{children:[`Вложенный overlay manager должен передавать `,(0,c.jsx)(t.code,{children:`active: true`}),` только верхнему слою.`]}),`
622
+ `,(0,c.jsx)(t.h3,{id:`documenttarget`,children:(0,c.jsx)(t.code,{children:`documentTarget`})}),`
623
+ `,(0,c.jsxs)(t.p,{children:[`Если параметр не передан, используется global `,(0,c.jsx)(t.code,{children:`document`}),`.`]}),`
624
+ `,(0,c.jsx)(t.p,{children:`Реализация использует nullish fallback:`}),`
625
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`documentTarget ?? document;
626
+ `})}),`
627
+ `,(0,c.jsxs)(t.p,{children:[`Поэтому явный `,(0,c.jsx)(t.code,{children:`documentTarget: null`}),` при наличии global document не отключает hook. Для отключения используйте `,(0,c.jsx)(t.code,{children:`active: false`}),` или `,(0,c.jsx)(t.code,{children:`enabled: false`}),`.`]}),`
628
+ `,(0,c.jsx)(t.p,{children:`Custom document полезен для специальных same-origin document scenarios, но container/focus elements должны принадлежать согласованной DOM-среде.`}),`
629
+ `,(0,c.jsx)(t.h3,{id:`уже-отменённое-событие`,children:`Уже отменённое событие`}),`
630
+ `,(0,c.jsxs)(t.p,{children:[`Текущий контракт не проверяет `,(0,c.jsx)(t.code,{children:`event.defaultPrevented`}),`. Даже если предыдущий capture listener уже вызвал `,(0,c.jsx)(t.code,{children:`preventDefault`}),`, hook всё равно может выполнить dismiss.`]}),`
631
+ `,(0,c.jsxs)(t.p,{children:[`Если приложение требует приоритетов между overlay, задавайте их через `,(0,c.jsx)(t.code,{children:`active`}),`, а не только через `,(0,c.jsx)(t.code,{children:`preventDefault`}),`.`]}),`
632
+ `,(0,c.jsx)(t.h3,{id:`hook-не-управляет-фокусом`,children:`Hook не управляет фокусом`}),`
633
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useEscapeDismiss`}),` только вызывает callback. Начальный focus и restore выполняет `,(0,c.jsx)(t.code,{children:`useOverlayFocus`}),` либо UI-owner.`]}),`
634
+ `,(0,c.jsx)(t.h2,{id:`useelementheightobserver`,children:(0,c.jsx)(t.code,{children:`useElementHeightObserver`})}),`
635
+ `,(0,c.jsxs)(t.p,{children:[`Возвращает актуальную высоту element, полученную через `,(0,c.jsx)(t.code,{children:`ResizeObserver`}),`.`]}),`
636
+ `,(0,c.jsx)(t.h3,{id:`сигнатура-3`,children:`Сигнатура`}),`
637
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function useElementHeightObserver<TElement extends HTMLElement>(elementRef: RefObject<TElement | null>): number;
638
+ `})}),`
639
+ `,(0,c.jsx)(t.h3,{id:`начальное-значение`,children:`Начальное значение`}),`
640
+ `,(0,c.jsx)(t.p,{children:`До первого observer callback возвращается:`}),`
641
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`0;
642
+ `})}),`
643
+ `,(0,c.jsxs)(t.p,{children:[`Hook не вызывает `,(0,c.jsx)(t.code,{children:`getBoundingClientRect`}),` синхронно при setup только для initial value. В обычном браузере ResizeObserver вскоре сообщает текущее измерение, но consumer должен корректно обработать `,(0,c.jsx)(t.code,{children:`0`}),`.`]}),`
644
+ `,(0,c.jsx)(t.h3,{id:`источник-высоты`,children:`Источник высоты`}),`
645
+ `,(0,c.jsx)(t.p,{children:`Callback выбирает:`}),`
646
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`entries[0]?.contentRect.height;
647
+ `})}),`
648
+ `,(0,c.jsx)(t.p,{children:`Если первая entry отсутствует, используется:`}),`
649
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`element.getBoundingClientRect().height;
650
+ `})}),`
651
+ `,(0,c.jsxs)(t.p,{children:[`Обычно `,(0,c.jsx)(t.code,{children:`contentRect.height`}),` описывает content box. Это не обязательно равно:`]}),`
652
+ `,(0,c.jsxs)(t.ul,{children:[`
653
+ `,(0,c.jsxs)(t.li,{children:[`CSS `,(0,c.jsx)(t.code,{children:`height`}),`;`]}),`
654
+ `,(0,c.jsx)(t.li,{children:`border-box высоте;`}),`
655
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`offsetHeight`}),`;`]}),`
656
+ `,(0,c.jsx)(t.li,{children:`визуальной высоте с transform;`}),`
657
+ `,(0,c.jsx)(t.li,{children:`целому числу.`}),`
658
+ `]}),`
659
+ `,(0,c.jsx)(t.p,{children:`Результат может быть дробным.`}),`
660
+ `,(0,c.jsx)(t.h3,{id:`пример`,children:`Пример`}),`
661
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`function HeightLabel() {
662
+ const panelRef = useRef<HTMLDivElement>(null);
663
+ const height = useElementHeightObserver(panelRef);
664
+
665
+ return (
666
+ <div>
667
+ <div ref={panelRef}>Изменяемое содержимое</div>
668
+ <output>Высота content box: {height}px</output>
669
+ </div>
670
+ );
671
+ }
672
+ `})}),`
673
+ `,(0,c.jsx)(t.h3,{id:`lifecycle`,children:`Lifecycle`}),`
674
+ `,(0,c.jsxs)(t.p,{children:[`Hook использует `,(0,c.jsx)(t.code,{children:`useLayoutEffect`}),`:`]}),`
675
+ `,(0,c.jsxs)(t.ol,{children:[`
676
+ `,(0,c.jsxs)(t.li,{children:[`после DOM commit получает `,(0,c.jsx)(t.code,{children:`elementRef.current`}),`;`]}),`
677
+ `,(0,c.jsx)(t.li,{children:`если element отсутствует, ничего не наблюдает;`}),`
678
+ `,(0,c.jsxs)(t.li,{children:[`создаёт `,(0,c.jsx)(t.code,{children:`ResizeObserver`}),`;`]}),`
679
+ `,(0,c.jsxs)(t.li,{children:[`вызывает `,(0,c.jsx)(t.code,{children:`observer.observe(element)`}),`;`]}),`
680
+ `,(0,c.jsxs)(t.li,{children:[`при cleanup вызывает `,(0,c.jsx)(t.code,{children:`observer.disconnect()`}),`.`]}),`
681
+ `]}),`
682
+ `,(0,c.jsx)(t.h3,{id:`стабильный-ref-и-смена-элемента`,children:`Стабильный ref и смена элемента`}),`
683
+ `,(0,c.jsxs)(t.p,{children:[`Hook рассчитан на React ref, установленный на актуальный element. Простое изменение `,(0,c.jsx)(t.code,{children:`ref.current`}),` само по себе не вызывает render. Для динамической замены target используйте понятную component boundary/key или обеспечьте render, на котором hook сможет переподключиться.`]}),`
684
+ `,(0,c.jsx)(t.p,{children:`Не мутируйте ref вручную во время render.`}),`
685
+ `,(0,c.jsxs)(t.h3,{id:`отсутствующий-resizeobserver`,children:[`Отсутствующий `,(0,c.jsx)(t.code,{children:`ResizeObserver`})]}),`
686
+ `,(0,c.jsxs)(t.p,{children:[`Fallback/polyfill отсутствует. Если global `,(0,c.jsx)(t.code,{children:`ResizeObserver`}),` не определён, effect завершится runtime error.`]}),`
687
+ `,(0,c.jsx)(t.p,{children:`Для старой browser/test среды подключите совместимый polyfill на уровне application environment либо mock в тесте.`}),`
688
+ `,(0,c.jsx)(t.h3,{id:`не-создавайте-feedback-loop`,children:`Не создавайте feedback loop`}),`
689
+ `,(0,c.jsx)(t.p,{children:`Осторожно с кодом, где измеренная высота сразу меняет CSS-высоту того же элемента. Это может создать цепочку:`}),`
690
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`resize → setState → layout change → resize → ...
691
+ `})}),`
692
+ `,(0,c.jsx)(t.p,{children:`Используйте измерение только там, где оно действительно необходимо, и не дублируйте задачу, решаемую CSS.`}),`
693
+ `,(0,c.jsx)(t.h2,{id:`useintersectionobserver`,children:(0,c.jsx)(t.code,{children:`useIntersectionObserver`})}),`
694
+ `,(0,c.jsxs)(t.p,{children:[`Возвращает boolean `,(0,c.jsx)(t.code,{children:`isIntersecting`}),` для одного DOM `,(0,c.jsx)(t.code,{children:`Element`}),`.`]}),`
695
+ `,(0,c.jsx)(t.h3,{id:`сигнатура-4`,children:`Сигнатура`}),`
696
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function useIntersectionObserver(
697
+ ref: React.RefObject<Element | null>,
698
+ options?: IntersectionObserverInit
699
+ ): {
700
+ isIntersecting: boolean;
701
+ };
702
+ `})}),`
703
+ `,(0,c.jsx)(t.h3,{id:`начальное-значение-1`,children:`Начальное значение`}),`
704
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`false;
705
+ `})}),`
706
+ `,(0,c.jsxs)(t.p,{children:[`Даже если element видим, первый render возвращает `,(0,c.jsx)(t.code,{children:`false`}),`, пока native observer не вызовет callback.`]}),`
707
+ `,(0,c.jsxs)(t.p,{children:[`Не интерпретируйте initial `,(0,c.jsx)(t.code,{children:`false`}),` как доказательство, что element точно находится вне viewport.`]}),`
708
+ `,(0,c.jsx)(t.h3,{id:`options`,children:`Options`}),`
709
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`IntersectionObserverInit`}),` — native browser contract:`]}),`
710
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Поле`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`root`})}),(0,c.jsxs)(t.td,{children:[`Scroll container; `,(0,c.jsx)(t.code,{children:`null`}),`/отсутствие означает viewport.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`rootMargin`})}),(0,c.jsx)(t.td,{children:`Внешний/внутренний отступ области наблюдения в CSS-like формате.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`threshold`})}),(0,c.jsxs)(t.td,{children:[`Доля пересечения или массив порогов от `,(0,c.jsx)(t.code,{children:`0`}),` до `,(0,c.jsx)(t.code,{children:`1`}),`.`]})]})]})]}),`
711
+ `,(0,c.jsx)(t.p,{children:`Пример preloading до появления в viewport:`}),`
712
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const options = useMemo<IntersectionObserverInit>(
713
+ () => ({
714
+ root: null,
715
+ rootMargin: "300px 0px",
716
+ threshold: 0
717
+ }),
718
+ []
719
+ );
720
+ `})}),`
721
+ `,(0,c.jsx)(t.h3,{id:`stable-options`,children:`Stable options`}),`
722
+ `,(0,c.jsxs)(t.p,{children:[`Effect зависит от ссылки `,(0,c.jsx)(t.code,{children:`options`}),`. Такой код создаёт новый object на каждом render:`]}),`
723
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`useIntersectionObserver(ref, {
724
+ rootMargin: "100px"
725
+ });
726
+ `})}),`
727
+ `,(0,c.jsx)(t.p,{children:`Это приводит к disconnect и созданию нового observer после каждого render.`}),`
728
+ `,(0,c.jsx)(t.p,{children:`Стабилизируйте options:`}),`
729
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const options = useMemo(
730
+ () => ({
731
+ rootMargin: "100px"
732
+ }),
733
+ []
734
+ );
735
+
736
+ const { isIntersecting } = useIntersectionObserver(ref, options);
737
+ `})}),`
738
+ `,(0,c.jsx)(t.h3,{id:`lifecycle-1`,children:`Lifecycle`}),`
739
+ `,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`ref.current`}),` существует:`]}),`
740
+ `,(0,c.jsxs)(t.ol,{children:[`
741
+ `,(0,c.jsxs)(t.li,{children:[`создаётся `,(0,c.jsx)(t.code,{children:`IntersectionObserver(callback, options)`}),`;`]}),`
742
+ `,(0,c.jsxs)(t.li,{children:[`вызывается `,(0,c.jsx)(t.code,{children:`observe(element)`}),`;`]}),`
743
+ `,(0,c.jsx)(t.li,{children:`callback берёт первую entry;`}),`
744
+ `,(0,c.jsxs)(t.li,{children:[`state обновляется значением `,(0,c.jsx)(t.code,{children:`entry.isIntersecting`}),`;`]}),`
745
+ `,(0,c.jsxs)(t.li,{children:[`cleanup вызывает `,(0,c.jsx)(t.code,{children:`unobserve(element)`}),`;`]}),`
746
+ `,(0,c.jsxs)(t.li,{children:[`затем вызывает `,(0,c.jsx)(t.code,{children:`disconnect()`}),`.`]}),`
747
+ `]}),`
748
+ `,(0,c.jsx)(t.p,{children:`Если ref пустой во время effect, observer не создаётся.`}),`
749
+ `,(0,c.jsx)(t.h3,{id:`возвращается-только-boolean`,children:`Возвращается только boolean`}),`
750
+ `,(0,c.jsx)(t.p,{children:`Hook не возвращает:`}),`
751
+ `,(0,c.jsxs)(t.ul,{children:[`
752
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`intersectionRatio`}),`;`]}),`
753
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`boundingClientRect`}),`;`]}),`
754
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`rootBounds`}),`;`]}),`
755
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`time`}),`;`]}),`
756
+ `,(0,c.jsxs)(t.li,{children:[`саму `,(0,c.jsx)(t.code,{children:`IntersectionObserverEntry`}),`;`]}),`
757
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`observe/unobserve`}),` commands.`]}),`
758
+ `]}),`
759
+ `,(0,c.jsx)(t.p,{children:`Если они нужны, текущий API не подходит.`}),`
760
+ `,(0,c.jsx)(t.h3,{id:`видимость-и-бизнес-истина`,children:`Видимость и бизнес-истина`}),`
761
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`isIntersecting`}),` является browser layout signal, а не security/business fact. Его можно использовать для:`]}),`
762
+ `,(0,c.jsxs)(t.ul,{children:[`
763
+ `,(0,c.jsx)(t.li,{children:`lazy rendering;`}),`
764
+ `,(0,c.jsx)(t.li,{children:`предварительной загрузки;`}),`
765
+ `,(0,c.jsx)(t.li,{children:`запуска/остановки лёгкой анимации;`}),`
766
+ `,(0,c.jsx)(t.li,{children:`UI-оптимизации.`}),`
767
+ `]}),`
768
+ `,(0,c.jsx)(t.p,{children:`Нельзя использовать пересечение как authorization, подтверждение просмотра юридического текста или гарантию, что пользователь действительно увидел содержимое.`}),`
769
+ `,(0,c.jsxs)(t.h3,{id:`отсутствующий-intersectionobserver`,children:[`Отсутствующий `,(0,c.jsx)(t.code,{children:`IntersectionObserver`})]}),`
770
+ `,(0,c.jsx)(t.p,{children:`Hook не содержит fallback. В среде без API effect выбросит ошибку.`}),`
771
+ `,(0,c.jsx)(t.h2,{id:`useistouchdevice`,children:(0,c.jsx)(t.code,{children:`useIsTouchDevice`})}),`
772
+ `,(0,c.jsx)(t.p,{children:`Возвращает heuristic наличия touch/coarse-pointer возможностей.`}),`
773
+ `,(0,c.jsx)(t.h3,{id:`сигнатура-5`,children:`Сигнатура`}),`
774
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare const useIsTouchDevice: () => boolean;
775
+ `})}),`
776
+ `,(0,c.jsx)(t.h3,{id:`алгоритм-1`,children:`Алгоритм`}),`
777
+ `,(0,c.jsxs)(t.p,{children:[`Результат становится `,(0,c.jsx)(t.code,{children:`true`}),`, если выполняется хотя бы одно условие:`]}),`
778
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`window.matchMedia("(pointer: coarse)").matches || "ontouchstart" in window || navigator.maxTouchPoints > 0;
779
+ `})}),`
780
+ `,(0,c.jsx)(t.h3,{id:`начальное-значение-2`,children:`Начальное значение`}),`
781
+ `,(0,c.jsx)(t.p,{children:`Первый render возвращает:`}),`
782
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`false;
783
+ `})}),`
784
+ `,(0,c.jsxs)(t.p,{children:[`После mount effect запускает detection и обновляет state. Поэтому возможен переход `,(0,c.jsx)(t.code,{children:`false → true`}),` и один дополнительный render.`]}),`
785
+ `,(0,c.jsxs)(t.p,{children:[`Не используйте initial `,(0,c.jsx)(t.code,{children:`false`}),` для необратимой команды.`]}),`
786
+ `,(0,c.jsx)(t.h3,{id:`обновление`,children:`Обновление`}),`
787
+ `,(0,c.jsx)(t.p,{children:`Hook повторяет detection на событии:`}),`
788
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`window.addEventListener("resize", checkTouchDevice);
789
+ `})}),`
790
+ `,(0,c.jsx)(t.p,{children:`При unmount listener удаляется.`}),`
791
+ `,(0,c.jsxs)(t.p,{children:[`Hook не подписывается напрямую на `,(0,c.jsx)(t.code,{children:`MediaQueryList.change`}),`, `,(0,c.jsx)(t.code,{children:`pointerchange`}),` или подключение внешнего устройства. Изменение обнаруживается при mount или следующем `,(0,c.jsx)(t.code,{children:`resize`}),`.`]}),`
792
+ `,(0,c.jsxs)(t.h3,{id:`что-означает-true`,children:[`Что означает `,(0,c.jsx)(t.code,{children:`true`})]}),`
793
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`true`}),` означает, что среда выглядит touch-capable/coarse-pointer-capable. Это не гарантирует, что:`]}),`
794
+ `,(0,c.jsxs)(t.ul,{children:[`
795
+ `,(0,c.jsx)(t.li,{children:`пользователь сейчас касается экрана;`}),`
796
+ `,(0,c.jsx)(t.li,{children:`touch является primary input;`}),`
797
+ `,(0,c.jsx)(t.li,{children:`мыши нет;`}),`
798
+ `,(0,c.jsx)(t.li,{children:`hover недоступен;`}),`
799
+ `,(0,c.jsx)(t.li,{children:`viewport маленький;`}),`
800
+ `,(0,c.jsx)(t.li,{children:`устройство является телефоном;`}),`
801
+ `,(0,c.jsx)(t.li,{children:`пользователь не использует keyboard или assistive technology.`}),`
802
+ `]}),`
803
+ `,(0,c.jsx)(t.p,{children:`Hybrid laptop может одновременно поддерживать touch, mouse и keyboard.`}),`
804
+ `,(0,c.jsx)(t.h3,{id:`не-скрывайте-функциональность`,children:`Не скрывайте функциональность`}),`
805
+ `,(0,c.jsx)(t.p,{children:`Плохой пример:`}),`
806
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`{
807
+ isTouchDevice ? null : <button>Важная команда</button>;
808
+ }
809
+ `})}),`
810
+ `,(0,c.jsx)(t.p,{children:`Touch heuristic нельзя использовать, чтобы лишить пользователя функции. Адаптируйте удобство interaction, сохраняя keyboard/pointer доступность.`}),`
811
+ `,(0,c.jsx)(t.p,{children:`Для responsive layout и hover styling предпочитайте CSS:`}),`
812
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-css`,children:`@media (pointer: coarse) {
813
+ .target {
814
+ min-block-size: 44px;
815
+ }
816
+ }
817
+ `})}),`
818
+ `,(0,c.jsx)(t.h3,{id:`runtime`,children:`Runtime`}),`
819
+ `,(0,c.jsxs)(t.p,{children:[`Hook ожидает `,(0,c.jsx)(t.code,{children:`window.matchMedia`}),` и browser globals. В test environment их нужно mock.`]}),`
820
+ `,(0,c.jsx)(t.h2,{id:`useoverlayfocus`,children:(0,c.jsx)(t.code,{children:`useOverlayFocus`})}),`
821
+ `,(0,c.jsx)(t.p,{children:`Управляет focus lifecycle одного overlay:`}),`
822
+ `,(0,c.jsxs)(t.ul,{children:[`
823
+ `,(0,c.jsx)(t.li,{children:`запоминает ранее активный element;`}),`
824
+ `,(0,c.jsx)(t.li,{children:`ждёт появления container ref;`}),`
825
+ `,(0,c.jsx)(t.li,{children:`устанавливает initial focus;`}),`
826
+ `,(0,c.jsxs)(t.li,{children:[`опционально зацикливает `,(0,c.jsx)(t.code,{children:`Tab`}),`;`]}),`
827
+ `,(0,c.jsx)(t.li,{children:`опционально возвращает focus при cleanup.`}),`
828
+ `]}),`
829
+ `,(0,c.jsx)(t.h3,{id:`сигнатура-6`,children:`Сигнатура`}),`
830
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`export interface UseOverlayFocusOptions<T extends HTMLElement> {
831
+ active: boolean;
832
+ trapFocus?: boolean;
833
+ restoreFocus?: boolean;
834
+ initialFocus?: "auto" | "container" | ((container: T) => HTMLElement | null | undefined);
835
+ restoreFocusTarget?: () => HTMLElement | null | undefined;
836
+ containerRef?: RefObject<T | null>;
837
+ }
838
+
839
+ declare function useOverlayFocus<T extends HTMLElement>(options: UseOverlayFocusOptions<T>): RefObject<T | null>;
840
+ `})}),`
841
+ `,(0,c.jsx)(t.h3,{id:`options-1`,children:`Options`}),`
842
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Option`}),(0,c.jsx)(t.th,{children:`Default`}),(0,c.jsx)(t.th,{children:`Поведение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`active`})}),(0,c.jsx)(t.td,{children:`Required`}),(0,c.jsx)(t.td,{children:`Запускает focus lifecycle.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`trapFocus`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`false`})}),(0,c.jsx)(t.td,{children:`Зацикливает Tab на границах container.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`restoreFocus`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`true`})}),(0,c.jsx)(t.td,{children:`Возвращает focus при cleanup.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`initialFocus`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"auto"`})}),(0,c.jsx)(t.td,{children:`Выбирает первый focus target.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`restoreFocusTarget`})}),(0,c.jsx)(t.td,{children:`Не задан`}),(0,c.jsx)(t.td,{children:`Позволяет вернуть focus в явно вычисленный element.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`containerRef`})}),(0,c.jsx)(t.td,{children:`Не задан`}),(0,c.jsx)(t.td,{children:`Позволяет передать существующий ref вместо внутреннего.`})]})]})]}),`
843
+ `,(0,c.jsx)(t.p,{children:`Hook возвращает ref, который нужно установить на overlay container:`}),`
844
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const overlayRef = useOverlayFocus<HTMLDivElement>({
845
+ active: open
846
+ });
847
+
848
+ return open ? <div ref={overlayRef}>...</div> : null;
849
+ `})}),`
850
+ `,(0,c.jsx)(t.h3,{id:`internal-и-external-ref`,children:`Internal и external ref`}),`
851
+ `,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`containerRef`}),` не передан, hook создаёт собственный `,(0,c.jsx)(t.code,{children:`useRef`}),`.`]}),`
852
+ `,(0,c.jsx)(t.p,{children:`Если передан, используется и возвращается именно он:`}),`
853
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const existingRef = useRef<HTMLDivElement>(null);
854
+ const overlayRef = useOverlayFocus({
855
+ active: open,
856
+ containerRef: existingRef
857
+ });
858
+
859
+ console.log(overlayRef === existingRef); // true.
860
+ `})}),`
861
+ `,(0,c.jsx)(t.p,{children:`Это полезно для объединения с Floating UI callback ref или другим imperative owner.`}),`
862
+ `,(0,c.jsx)(t.h3,{id:`activation-lifecycle`,children:`Activation lifecycle`}),`
863
+ `,(0,c.jsx)(t.p,{children:`При переходе в active effect:`}),`
864
+ `,(0,c.jsxs)(t.ol,{children:[`
865
+ `,(0,c.jsxs)(t.li,{children:[`сохраняет текущий `,(0,c.jsx)(t.code,{children:`document.activeElement`}),`, если это `,(0,c.jsx)(t.code,{children:`HTMLElement`}),`;`]}),`
866
+ `,(0,c.jsxs)(t.li,{children:[`пытается прочитать `,(0,c.jsx)(t.code,{children:`overlayRef.current`}),`;`]}),`
867
+ `,(0,c.jsxs)(t.li,{children:[`если ref ещё пустой, повторяет попытку через `,(0,c.jsx)(t.code,{children:`requestAnimationFrame`}),`;`]}),`
868
+ `,(0,c.jsx)(t.li,{children:`когда container найден, вычисляет initial focus;`}),`
869
+ `,(0,c.jsxs)(t.li,{children:[`вызывает `,(0,c.jsx)(t.code,{children:`.focus()`}),`, только если target — подключённый `,(0,c.jsx)(t.code,{children:`HTMLElement`}),`;`]}),`
870
+ `,(0,c.jsxs)(t.li,{children:[`при `,(0,c.jsx)(t.code,{children:`trapFocus: true`}),` добавляет `,(0,c.jsx)(t.code,{children:`keydown`}),` listener на container.`]}),`
871
+ `]}),`
872
+ `,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`active: true`}),`, но container никогда не монтируется, requestAnimationFrame retry продолжается до cleanup. Всегда согласовывайте `,(0,c.jsx)(t.code,{children:`active`}),` с реальным mount overlay.`]}),`
873
+ `,(0,c.jsx)(t.h3,{id:`initialfocus-auto`,children:(0,c.jsx)(t.code,{children:`initialFocus: "auto"`})}),`
874
+ `,(0,c.jsx)(t.p,{children:`Это default. Hook собирает descendants по selector:`}),`
875
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-css`,children:`button:not([disabled]),
876
+ [href],
877
+ input:not([disabled]),
878
+ select:not([disabled]),
879
+ textarea:not([disabled]),
880
+ [tabindex]:not([tabindex="-1"]):not([disabled])
881
+ `})}),`
882
+ `,(0,c.jsx)(t.p,{children:`Затем:`}),`
883
+ `,(0,c.jsxs)(t.ol,{children:[`
884
+ `,(0,c.jsxs)(t.li,{children:[`удаляет elements, у которых на самом element есть `,(0,c.jsx)(t.code,{children:`aria-hidden`}),`;`]}),`
885
+ `,(0,c.jsxs)(t.li,{children:[`ищет element с `,(0,c.jsx)(t.code,{children:`autofocus`}),`/`,(0,c.jsx)(t.code,{children:`autoFocus`}),` attribute;`]}),`
886
+ `,(0,c.jsx)(t.li,{children:`иначе берёт первый focusable element в DOM-порядке;`}),`
887
+ `,(0,c.jsx)(t.li,{children:`если candidates нет, использует container.`}),`
888
+ `]}),`
889
+ `,(0,c.jsx)(t.p,{children:`Пример:`}),`
890
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`<div ref={overlayRef} tabIndex={-1}>
891
+ <button type="button" autoFocus>
892
+ Основное действие
893
+ </button>
894
+ <button type="button">Отмена</button>
895
+ </div>
896
+ `})}),`
897
+ `,(0,c.jsxs)(t.p,{children:[`React `,(0,c.jsx)(t.code,{children:`autoFocus`}),` имеет собственное commit-поведение, а hook дополнительно ищет соответствующий DOM attribute. Для однозначного выбора initial target используйте callback.`]}),`
898
+ `,(0,c.jsx)(t.h3,{id:`ограничения-auto-selector`,children:`Ограничения auto selector`}),`
899
+ `,(0,c.jsx)(t.p,{children:`Это компактный selector, а не полноценный tabbability engine. Он:`}),`
900
+ `,(0,c.jsxs)(t.ul,{children:[`
901
+ `,(0,c.jsxs)(t.li,{children:[`не проверяет `,(0,c.jsx)(t.code,{children:`display: none`}),` или `,(0,c.jsx)(t.code,{children:`visibility: hidden`}),`;`]}),`
902
+ `,(0,c.jsx)(t.li,{children:`не проверяет hidden/inert ancestors;`}),`
903
+ `,(0,c.jsxs)(t.li,{children:[`не учитывает disabled `,(0,c.jsx)(t.code,{children:`fieldset`}),` полностью;`]}),`
904
+ `,(0,c.jsx)(t.li,{children:`не проверяет фактический layout box;`}),`
905
+ `,(0,c.jsx)(t.li,{children:`не включает все возможные focusable browser элементы;`}),`
906
+ `,(0,c.jsxs)(t.li,{children:[`не исключает произвольный negative `,(0,c.jsx)(t.code,{children:`tabindex`}),`, кроме точного `,(0,c.jsx)(t.code,{children:`-1`}),`;`]}),`
907
+ `,(0,c.jsxs)(t.li,{children:[`проверяет `,(0,c.jsx)(t.code,{children:`aria-hidden`}),` только на candidate, а не на ancestors.`]}),`
908
+ `]}),`
909
+ `,(0,c.jsx)(t.p,{children:`Для сложного accessibility primitive используйте проверенный UI-компонент или специализированный focus manager.`}),`
910
+ `,(0,c.jsx)(t.h3,{id:`initialfocus-container`,children:(0,c.jsx)(t.code,{children:`initialFocus: "container"`})}),`
911
+ `,(0,c.jsx)(t.p,{children:`Hook вызывает focus у container:`}),`
912
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const overlayRef = useOverlayFocus<HTMLDivElement>({
913
+ active: open,
914
+ initialFocus: "container"
915
+ });
916
+
917
+ return (
918
+ <div ref={overlayRef} tabIndex={-1}>
919
+ ...
920
+ </div>
921
+ );
922
+ `})}),`
923
+ `,(0,c.jsxs)(t.p,{children:[`Без `,(0,c.jsx)(t.code,{children:`tabIndex={-1}`}),` обычный `,(0,c.jsx)(t.code,{children:`div`}),` может не принять focus.`]}),`
924
+ `,(0,c.jsx)(t.h3,{id:`custom-initial-focus`,children:`Custom initial focus`}),`
925
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const overlayRef = useOverlayFocus<HTMLDivElement>({
926
+ active: open,
927
+ initialFocus: (container) => container.querySelector<HTMLElement>("[data-initial-focus]")
928
+ });
929
+ `})}),`
930
+ `,(0,c.jsxs)(t.p,{children:[`Если callback возвращает `,(0,c.jsx)(t.code,{children:`null`}),` или `,(0,c.jsx)(t.code,{children:`undefined`}),`, используется container.`]}),`
931
+ `,(0,c.jsxs)(t.p,{children:[`Если callback возвращает подключённый `,(0,c.jsx)(t.code,{children:`HTMLElement`}),` вне container, hook всё равно сфокусирует его. Ответственность за корректность target лежит на consumer.`]}),`
932
+ `,(0,c.jsxs)(t.p,{children:[`Если возвращён disconnected HTMLElement, focus не выполняется; дополнительного fallback после проверки `,(0,c.jsx)(t.code,{children:`isConnected`}),` нет.`]}),`
933
+ `,(0,c.jsx)(t.h3,{id:`focus-trap`,children:`Focus trap`}),`
934
+ `,(0,c.jsxs)(t.p,{children:[`При `,(0,c.jsx)(t.code,{children:`trapFocus: true`}),` container получает bubbling `,(0,c.jsx)(t.code,{children:`keydown`}),` listener.`]}),`
935
+ `,(0,c.jsxs)(t.p,{children:[`На каждый `,(0,c.jsx)(t.code,{children:`Tab`}),` focusable elements вычисляются заново, поэтому динамически добавленные controls могут участвовать.`]}),`
936
+ `,(0,c.jsx)(t.h4,{id:`обычный-tab`,children:`Обычный Tab`}),`
937
+ `,(0,c.jsx)(t.p,{children:`Если focus находится на последнем candidate:`}),`
938
+ `,(0,c.jsxs)(t.ol,{children:[`
939
+ `,(0,c.jsxs)(t.li,{children:[`вызывается `,(0,c.jsx)(t.code,{children:`preventDefault()`}),`;`]}),`
940
+ `,(0,c.jsx)(t.li,{children:`focus переходит на первый candidate.`}),`
941
+ `]}),`
942
+ `,(0,c.jsx)(t.h4,{id:`shifttab`,children:`Shift+Tab`}),`
943
+ `,(0,c.jsx)(t.p,{children:`Если focus находится:`}),`
944
+ `,(0,c.jsxs)(t.ul,{children:[`
945
+ `,(0,c.jsx)(t.li,{children:`на первом candidate;`}),`
946
+ `,(0,c.jsx)(t.li,{children:`на самом container;`}),`
947
+ `]}),`
948
+ `,(0,c.jsx)(t.p,{children:`то event отменяется и focus переходит на последний candidate.`}),`
949
+ `,(0,c.jsx)(t.h4,{id:`нет-candidates`,children:`Нет candidates`}),`
950
+ `,(0,c.jsx)(t.p,{children:`Hook отменяет Tab и вызывает:`}),`
951
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`container.focus();
952
+ `})}),`
953
+ `,(0,c.jsx)(t.p,{children:`Container должен быть программно focusable.`}),`
954
+ `,(0,c.jsx)(t.h3,{id:`граница-focus-trap`,children:`Граница focus trap`}),`
955
+ `,(0,c.jsx)(t.p,{children:`Это boundary wrap, а не абсолютная focus containment:`}),`
956
+ `,(0,c.jsxs)(t.ul,{children:[`
957
+ `,(0,c.jsx)(t.li,{children:`listener срабатывает только для keydown, прошедшего через container;`}),`
958
+ `,(0,c.jsxs)(t.li,{children:[`программный `,(0,c.jsx)(t.code,{children:`.focus()`}),` снаружи не перехватывается;`]}),`
959
+ `,(0,c.jsx)(t.li,{children:`mouse/pointer focus снаружи не возвращается;`}),`
960
+ `,(0,c.jsx)(t.li,{children:`focus в другом portal не является descendant;`}),`
961
+ `,(0,c.jsx)(t.li,{children:`iframe и shadow DOM требуют отдельного проектирования;`}),`
962
+ `,(0,c.jsxs)(t.li,{children:[`background не становится `,(0,c.jsx)(t.code,{children:`inert`}),`;`]}),`
963
+ `,(0,c.jsx)(t.li,{children:`browser chrome и assistive technology имеют собственное поведение.`}),`
964
+ `]}),`
965
+ `,(0,c.jsx)(t.p,{children:`Не используйте этот hook как единственную гарантию доступности или безопасности.`}),`
966
+ `,(0,c.jsx)(t.h3,{id:`restore-focus`,children:`Restore focus`}),`
967
+ `,(0,c.jsxs)(t.p,{children:[`При activation hook запоминает ранее активный `,(0,c.jsx)(t.code,{children:`HTMLElement`}),`.`]}),`
968
+ `,(0,c.jsxs)(t.p,{children:[`При cleanup с `,(0,c.jsx)(t.code,{children:`restoreFocus: true`}),` выбирается:`]}),`
969
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`restoreFocusTarget?.() ?? previousActiveElement;
970
+ `})}),`
971
+ `,(0,c.jsxs)(t.p,{children:[`Focus возвращается, только если выбранный element всё ещё `,(0,c.jsx)(t.code,{children:`isConnected`}),`.`]}),`
972
+ `,(0,c.jsx)(t.p,{children:`Типичный сценарий:`}),`
973
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const triggerRef = useRef<HTMLButtonElement>(null);
974
+
975
+ const overlayRef = useOverlayFocus<HTMLDivElement>({
976
+ active: open,
977
+ restoreFocus: true,
978
+ restoreFocusTarget: () => triggerRef.current
979
+ });
980
+ `})}),`
981
+ `,(0,c.jsxs)(t.p,{children:[`Если custom callback возвращает `,(0,c.jsx)(t.code,{children:`null`}),`/`,(0,c.jsx)(t.code,{children:`undefined`}),`, используется ранее активный element.`]}),`
982
+ `,(0,c.jsxs)(t.p,{children:[`Если callback возвращает disconnected element, fallback к предыдущему element уже не выполняется: target был выбран, но проверку `,(0,c.jsx)(t.code,{children:`isConnected`}),` не прошёл.`]}),`
983
+ `,(0,c.jsx)(t.h3,{id:`когда-выполняется-cleanup`,children:`Когда выполняется cleanup`}),`
984
+ `,(0,c.jsx)(t.p,{children:`Restore может произойти:`}),`
985
+ `,(0,c.jsxs)(t.ul,{children:[`
986
+ `,(0,c.jsxs)(t.li,{children:[`при `,(0,c.jsx)(t.code,{children:`active: true → false`}),`;`]}),`
987
+ `,(0,c.jsx)(t.li,{children:`при unmount;`}),`
988
+ `,(0,c.jsxs)(t.li,{children:[`при изменении effect dependencies, например `,(0,c.jsx)(t.code,{children:`trapFocus`}),`, `,(0,c.jsx)(t.code,{children:`restoreFocus`}),` или ссылки `,(0,c.jsx)(t.code,{children:`containerRef`}),`.`]}),`
989
+ `]}),`
990
+ `,(0,c.jsx)(t.p,{children:`Не меняйте эти options без необходимости во время открытого overlay.`}),`
991
+ `,(0,c.jsx)(t.h3,{id:`exit-animation`,children:`Exit animation`}),`
992
+ `,(0,c.jsxs)(t.p,{children:[`Если DOM overlay остаётся видимым на время exit animation, но `,(0,c.jsx)(t.code,{children:`active`}),` уже стал `,(0,c.jsx)(t.code,{children:`false`}),`, hook восстановит focus при cleanup сразу, а не после окончания animation.`]}),`
993
+ `,(0,c.jsx)(t.p,{children:`Для такого UI:`}),`
994
+ `,(0,c.jsxs)(t.ul,{children:[`
995
+ `,(0,c.jsxs)(t.li,{children:[`передайте `,(0,c.jsx)(t.code,{children:`restoreFocus: false`}),`;`]}),`
996
+ `,(0,c.jsx)(t.li,{children:`сохраните trigger отдельно;`}),`
997
+ `,(0,c.jsx)(t.li,{children:`восстановите focus в callback окончания exit animation;`}),`
998
+ `,(0,c.jsx)(t.li,{children:`убедитесь, что background уже перестал быть inert.`}),`
999
+ `]}),`
1000
+ `,(0,c.jsxs)(t.h3,{id:`изменение-initialfocus`,children:[`Изменение `,(0,c.jsx)(t.code,{children:`initialFocus`})]}),`
1001
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`initialFocus`}),` читается через Effect Event и не входит в effect dependencies. Изменение option во время уже подключённого active overlay само по себе не запускает повторный initial focus. Initial focus — действие открытия, а не derived state каждого render.`]}),`
1002
+ `,(0,c.jsx)(t.h3,{id:`nested-overlays`,children:`Nested overlays`}),`
1003
+ `,(0,c.jsxs)(t.p,{children:[`Hook не знает о стеке modal/dialog/popover. Если несколько hooks имеют `,(0,c.jsx)(t.code,{children:`active: true`}),`, каждый управляет своей подпиской и restore lifecycle.`]}),`
1004
+ `,(0,c.jsx)(t.p,{children:`Application/UI manager должен:`}),`
1005
+ `,(0,c.jsxs)(t.ul,{children:[`
1006
+ `,(0,c.jsx)(t.li,{children:`определить top overlay;`}),`
1007
+ `,(0,c.jsx)(t.li,{children:`активировать Escape/trap только для него;`}),`
1008
+ `,(0,c.jsx)(t.li,{children:`согласовать inert и z-index;`}),`
1009
+ `,(0,c.jsx)(t.li,{children:`не позволить нижнему overlay восстановить focus поверх верхнего.`}),`
1010
+ `]}),`
1011
+ `,(0,c.jsx)(t.h2,{id:`usefocustrap`,children:(0,c.jsx)(t.code,{children:`useFocusTrap`})}),`
1012
+ `,(0,c.jsx)(t.p,{children:`Совместимый wrapper с фиксированными options.`}),`
1013
+ `,(0,c.jsx)(t.h3,{id:`сигнатура-7`,children:`Сигнатура`}),`
1014
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare const useFocusTrap: (isOpen: boolean) => RefObject<HTMLDivElement | null>;
1015
+ `})}),`
1016
+ `,(0,c.jsx)(t.p,{children:`Эквивалентен:`}),`
1017
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`useOverlayFocus<HTMLDivElement>({
1018
+ active: isOpen,
1019
+ trapFocus: true,
1020
+ restoreFocus: true,
1021
+ initialFocus: "auto"
1022
+ });
1023
+ `})}),`
1024
+ `,(0,c.jsx)(t.h3,{id:`пример-1`,children:`Пример`}),`
1025
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`function LegacyPanel({ open }: { open: boolean }) {
1026
+ const panelRef = useFocusTrap(open);
1027
+
1028
+ return open ? (
1029
+ <div ref={panelRef} tabIndex={-1}>
1030
+ <button type="button">Подтвердить</button>
1031
+ <button type="button">Отмена</button>
1032
+ </div>
1033
+ ) : null;
1034
+ }
1035
+ `})}),`
1036
+ `,(0,c.jsx)(t.p,{children:`Wrapper не позволяет:`}),`
1037
+ `,(0,c.jsxs)(t.ul,{children:[`
1038
+ `,(0,c.jsx)(t.li,{children:`отключить restore;`}),`
1039
+ `,(0,c.jsx)(t.li,{children:`выбрать custom initial focus;`}),`
1040
+ `,(0,c.jsx)(t.li,{children:`передать existing ref;`}),`
1041
+ `,(0,c.jsx)(t.li,{children:`отключить trap при active panel;`}),`
1042
+ `,(0,c.jsx)(t.li,{children:`выбрать другой HTMLElement generic.`}),`
1043
+ `]}),`
1044
+ `,(0,c.jsxs)(t.p,{children:[`Для нового настраиваемого кода используйте `,(0,c.jsx)(t.code,{children:`useOverlayFocus`}),`.`]}),`
1045
+ `,(0,c.jsx)(t.h2,{id:`сборка-доступного-dialog`,children:`Сборка доступного dialog`}),`
1046
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useOverlayFocus`}),` и `,(0,c.jsx)(t.code,{children:`useEscapeDismiss`}),` решают только часть задачи. Минимальный dialog должен также иметь:`]}),`
1047
+ `,(0,c.jsxs)(t.ul,{children:[`
1048
+ `,(0,c.jsxs)(t.li,{children:[`семантический `,(0,c.jsx)(t.code,{children:`role="dialog"`}),`;`]}),`
1049
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`aria-modal="true"`}),` только для действительно modal поведения;`]}),`
1050
+ `,(0,c.jsxs)(t.li,{children:[`accessible name через `,(0,c.jsx)(t.code,{children:`aria-labelledby`}),` или `,(0,c.jsx)(t.code,{children:`aria-label`}),`;`]}),`
1051
+ `,(0,c.jsxs)(t.li,{children:[`при необходимости description через `,(0,c.jsx)(t.code,{children:`aria-describedby`}),`;`]}),`
1052
+ `,(0,c.jsx)(t.li,{children:`доступную кнопку закрытия;`}),`
1053
+ `,(0,c.jsx)(t.li,{children:`focusable container fallback;`}),`
1054
+ `,(0,c.jsx)(t.li,{children:`top-layer policy;`}),`
1055
+ `,(0,c.jsx)(t.li,{children:`background/inert policy;`}),`
1056
+ `,(0,c.jsx)(t.li,{children:`scroll lock при необходимости;`}),`
1057
+ `,(0,c.jsx)(t.li,{children:`восстановление focus;`}),`
1058
+ `,(0,c.jsx)(t.li,{children:`keyboard и pointer способы закрытия;`}),`
1059
+ `,(0,c.jsx)(t.li,{children:`визуальный focus indicator.`}),`
1060
+ `]}),`
1061
+ `,(0,c.jsx)(t.p,{children:`Ниже учебный пример. Для production сначала ищите готовый UI primitive.`}),`
1062
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { useId } from "react";
1063
+ import { createPortal } from "react-dom";
1064
+ import { useEscapeDismiss, useOverlayFocus } from "@ryuzaki13/react-foundation-lib/dom";
1065
+
1066
+ type ConfirmDialogProps = {
1067
+ description: string;
1068
+ onClose: () => void;
1069
+ onConfirm: () => void;
1070
+ open: boolean;
1071
+ portalRoot: HTMLElement;
1072
+ title: string;
1073
+ };
1074
+
1075
+ export function ConfirmDialog({ description, onClose, onConfirm, open, portalRoot, title }: ConfirmDialogProps) {
1076
+ const titleId = useId();
1077
+ const descriptionId = useId();
1078
+ const panelRef = useOverlayFocus<HTMLDivElement>({
1079
+ active: open,
1080
+ trapFocus: true,
1081
+ restoreFocus: true,
1082
+ initialFocus: (container) => container.querySelector<HTMLButtonElement>("[data-confirm]")
1083
+ });
1084
+
1085
+ useEscapeDismiss({
1086
+ active: open,
1087
+ onDismiss: onClose,
1088
+ containerRef: panelRef
1089
+ });
1090
+
1091
+ if (!open) {
1092
+ return null;
1093
+ }
1094
+
1095
+ return createPortal(
1096
+ <div className="dialogBackdrop">
1097
+ <div ref={panelRef} role="dialog" aria-modal="true" aria-labelledby={titleId} aria-describedby={descriptionId} tabIndex={-1}>
1098
+ <h2 id={titleId}>{title}</h2>
1099
+ <p id={descriptionId}>{description}</p>
1100
+ <button type="button" data-confirm onClick={onConfirm}>
1101
+ Подтвердить
1102
+ </button>
1103
+ <button type="button" onClick={onClose}>
1104
+ Отмена
1105
+ </button>
1106
+ </div>
1107
+ </div>,
1108
+ portalRoot
1109
+ );
1110
+ }
1111
+ `})}),`
1112
+ `,(0,c.jsx)(t.p,{children:`Этот пример всё ещё не реализует inert/background manager, scroll lock, nested modal stack и animation exit coordination. Эти обязанности должны находиться у UI-owner.`}),`
1113
+ `,(0,c.jsxs)(t.h3,{id:`почему-нужен-useid`,children:[`Почему нужен `,(0,c.jsx)(t.code,{children:`useId`})]}),`
1114
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useId`}),` создаёт React ID для accessibility connections:`]}),`
1115
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`<h2 id={titleId}>...</h2>
1116
+ <div aria-labelledby={titleId}>...</div>
1117
+ `})}),`
1118
+ `,(0,c.jsx)(t.p,{children:`Не используйте случайный UUID на каждом render.`}),`
1119
+ `,(0,c.jsxs)(t.h3,{id:`почему-container-имеет-tabindex-1`,children:[`Почему container имеет `,(0,c.jsx)(t.code,{children:`tabIndex={-1}`})]}),`
1120
+ `,(0,c.jsx)(t.p,{children:`Если custom initial target отсутствует либо focusable elements нет, hook использует container. Без программной focusability fallback может ничего не сделать.`}),`
1121
+ `,(0,c.jsxs)(t.h3,{id:`почему-active-совпадает-с-mount`,children:[`Почему `,(0,c.jsx)(t.code,{children:`active`}),` совпадает с mount`]}),`
1122
+ `,(0,c.jsxs)(t.p,{children:[`Когда `,(0,c.jsx)(t.code,{children:`open === false`}),`, container не рендерится и hook inactive. Когда `,(0,c.jsx)(t.code,{children:`open === true`}),`, container появляется и hook может получить ref. Это предотвращает бесконечный requestAnimationFrame retry.`]}),`
1123
+ `,(0,c.jsx)(t.h2,{id:`композиция-overlay-hooks`,children:`Композиция overlay hooks`}),`
1124
+ `,(0,c.jsx)(t.h3,{id:`popover-trigger-и-panel-считаются-внутренними`,children:`Popover: trigger и panel считаются внутренними`}),`
1125
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { type RefObject, useCallback, useMemo, useRef, useState } from "react";
1126
+ import { useClickOutside, useEscapeDismiss, useOverlayFocus } from "@ryuzaki13/react-foundation-lib/dom";
1127
+
1128
+ function Popover() {
1129
+ const [open, setOpen] = useState(false);
1130
+ const triggerRef = useRef<HTMLButtonElement>(null);
1131
+ const panelRef = useOverlayFocus<HTMLDivElement>({
1132
+ active: open,
1133
+ initialFocus: "container",
1134
+ restoreFocus: true
1135
+ });
1136
+ const insideRefs = useMemo<RefObject<HTMLElement | null>[]>(() => [triggerRef, panelRef], [panelRef]);
1137
+
1138
+ const close = useCallback(() => {
1139
+ setOpen(false);
1140
+ }, []);
1141
+
1142
+ const closeOutside = useCallback(() => {
1143
+ if (open) {
1144
+ close();
1145
+ }
1146
+ }, [close, open]);
1147
+
1148
+ useClickOutside(insideRefs, closeOutside);
1149
+ useEscapeDismiss({
1150
+ active: open,
1151
+ onDismiss: close,
1152
+ containerRef: panelRef
1153
+ });
1154
+
1155
+ return (
1156
+ <>
1157
+ <button ref={triggerRef} type="button" aria-expanded={open} onClick={() => setOpen((current) => !current)}>
1158
+ Открыть
1159
+ </button>
1160
+ {open ? (
1161
+ <div ref={panelRef} tabIndex={-1}>
1162
+ Содержимое
1163
+ </div>
1164
+ ) : null}
1165
+ </>
1166
+ );
1167
+ }
1168
+ `})}),`
1169
+ `,(0,c.jsxs)(t.p,{children:[`Если panel находится в portal, логика refs не меняется: `,(0,c.jsx)(t.code,{children:`contains`}),` проверяет реальный DOM каждого ref.`]}),`
1170
+ `,(0,c.jsx)(t.h3,{id:`только-верхний-overlay-активен`,children:`Только верхний overlay активен`}),`
1171
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`useEscapeDismiss({
1172
+ active: open && isTopOverlay,
1173
+ onDismiss: close,
1174
+ containerRef: panelRef
1175
+ });
1176
+ `})}),`
1177
+ `,(0,c.jsxs)(t.p,{children:[`Конкретная stack и focus policy принадлежит UI manager. Нельзя механически переключать `,(0,c.jsx)(t.code,{children:`useOverlayFocus.active`}),` у нижнего слоя при каждом открытии верхнего: cleanup нижнего hook может преждевременно восстановить focus. Менеджер должен согласовать activation, restore, inert и exit lifecycle всей стопки. Модуль `,(0,c.jsx)(t.code,{children:`dom`}),` не предоставляет store/registry overlay.`]}),`
1178
+ `,(0,c.jsx)(t.h3,{id:`non-modal-popover`,children:`Non-modal popover`}),`
1179
+ `,(0,c.jsx)(t.p,{children:`Не каждому popover нужна focus trap:`}),`
1180
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const panelRef = useOverlayFocus<HTMLDivElement>({
1181
+ active: open,
1182
+ trapFocus: false,
1183
+ restoreFocus: true,
1184
+ initialFocus: "container"
1185
+ });
1186
+ `})}),`
1187
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`aria-modal="true"`}),` также не следует ставить на non-modal UI автоматически.`]}),`
1188
+ `,(0,c.jsx)(t.h2,{id:`ssr-strict-mode-и-lifecycle`,children:`SSR, Strict Mode и lifecycle`}),`
1189
+ `,(0,c.jsx)(t.h3,{id:`browser-only-side-effects`,children:`Browser-only side effects`}),`
1190
+ `,(0,c.jsx)(t.p,{children:`Не вызывайте download helpers или DOM mutations:`}),`
1191
+ `,(0,c.jsxs)(t.ul,{children:[`
1192
+ `,(0,c.jsx)(t.li,{children:`во время render;`}),`
1193
+ `,(0,c.jsx)(t.li,{children:`в Node-only utility;`}),`
1194
+ `,(0,c.jsx)(t.li,{children:`в server loader;`}),`
1195
+ `,(0,c.jsxs)(t.li,{children:[`до появления `,(0,c.jsx)(t.code,{children:`document.body`}),`;`]}),`
1196
+ `,(0,c.jsx)(t.li,{children:`без проверки целевого browser runtime.`}),`
1197
+ `]}),`
1198
+ `,(0,c.jsx)(t.p,{children:`User-triggered effects выполняйте в handlers. Subscriptions и observers уже находятся внутри hooks.`}),`
1199
+ `,(0,c.jsx)(t.h3,{id:`первый-render-может-содержать-default`,children:`Первый render может содержать default`}),`
1200
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Hook`}),(0,c.jsx)(t.th,{children:`Initial render`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useElementHeightObserver`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`0`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useIntersectionObserver`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`false`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useIsTouchDevice`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`false`})})]})]})]}),`
1201
+ `,(0,c.jsx)(t.p,{children:`После mount/observer callback state может измениться. UI должен выдерживать этот переход без опасного решения и сильного layout shift.`}),`
1202
+ `,(0,c.jsx)(t.h3,{id:`strict-mode`,children:`Strict Mode`}),`
1203
+ `,(0,c.jsx)(t.p,{children:`React development Strict Mode может выполнить setup → cleanup → setup для проверки симметрии effects.`}),`
1204
+ `,(0,c.jsx)(t.p,{children:`Hooks удаляют listeners и disconnect observers в cleanup. Consumer callbacks также должны быть безопасны при повторном lifecycle.`}),`
1205
+ `,(0,c.jsx)(t.p,{children:`Не используйте render для:`}),`
1206
+ `,(0,c.jsxs)(t.ul,{children:[`
1207
+ `,(0,c.jsx)(t.li,{children:`скачивания;`}),`
1208
+ `,(0,c.jsx)(t.li,{children:`создания случайного URL;`}),`
1209
+ `,(0,c.jsx)(t.li,{children:`focus;`}),`
1210
+ `,(0,c.jsx)(t.li,{children:`append в body;`}),`
1211
+ `,(0,c.jsx)(t.li,{children:`ручной мутации ref.`}),`
1212
+ `]}),`
1213
+ `,(0,c.jsx)(t.h3,{id:`conditional-hooks-запрещены`,children:`Conditional hooks запрещены`}),`
1214
+ `,(0,c.jsx)(t.p,{children:`Нельзя:`}),`
1215
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`if (open) {
1216
+ useEscapeDismiss({ active: true, onDismiss: close });
1217
+ }
1218
+ `})}),`
1219
+ `,(0,c.jsx)(t.p,{children:`Правильно:`}),`
1220
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`useEscapeDismiss({
1221
+ active: open,
1222
+ onDismiss: close
1223
+ });
1224
+ `})}),`
1225
+ `,(0,c.jsx)(t.h3,{id:`ref-становится-доступен-после-commit`,children:`Ref становится доступен после commit`}),`
1226
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useOverlayFocus`}),` умеет ждать ref через requestAnimationFrame. Observer hooks читают ref в effect. Не пытайтесь читать `,(0,c.jsx)(t.code,{children:`ref.current`}),` во время render для условного вызова hook.`]}),`
1227
+ `,(0,c.jsx)(t.h2,{id:`производительность`,children:`Производительность`}),`
1228
+ `,(0,c.jsx)(t.h3,{id:`global-listeners`,children:`Global listeners`}),`
1229
+ `,(0,c.jsx)(t.p,{children:`Каждый экземпляр:`}),`
1230
+ `,(0,c.jsxs)(t.ul,{children:[`
1231
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`useClickOutside`}),` добавляет свой `,(0,c.jsx)(t.code,{children:`document.mousedown`}),`;`]}),`
1232
+ `,(0,c.jsxs)(t.li,{children:[`активный `,(0,c.jsx)(t.code,{children:`useEscapeDismiss`}),` добавляет свой `,(0,c.jsx)(t.code,{children:`document.keydown`}),`;`]}),`
1233
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`useIsTouchDevice`}),` добавляет свой `,(0,c.jsx)(t.code,{children:`window.resize`}),`.`]}),`
1234
+ `]}),`
1235
+ `,(0,c.jsx)(t.p,{children:`Для десятков/сотен экземпляров оцените общий owner или монтируйте interaction hook только в реально активном UI component.`}),`
1236
+ `,(0,c.jsx)(t.h3,{id:`stable-dependencies`,children:`Stable dependencies`}),`
1237
+ `,(0,c.jsxs)(t.ul,{children:[`
1238
+ `,(0,c.jsxs)(t.li,{children:[`стабилизируйте массив refs для `,(0,c.jsx)(t.code,{children:`useClickOutside`}),`;`]}),`
1239
+ `,(0,c.jsxs)(t.li,{children:[`используйте `,(0,c.jsx)(t.code,{children:`useCallback`}),` для handler, когда нужно избежать listener churn;`]}),`
1240
+ `,(0,c.jsxs)(t.li,{children:[`стабилизируйте `,(0,c.jsx)(t.code,{children:`IntersectionObserverInit`}),`;`]}),`
1241
+ `,(0,c.jsx)(t.li,{children:`не меняйте focus options без причины во время active overlay.`}),`
1242
+ `]}),`
1243
+ `,(0,c.jsx)(t.p,{children:`Не добавляйте memoization механически: она нужна там, где ссылка является effect contract.`}),`
1244
+ `,(0,c.jsx)(t.h3,{id:`observer-callbacks`,children:`Observer callbacks`}),`
1245
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`ResizeObserver`}),` и `,(0,c.jsx)(t.code,{children:`IntersectionObserver`}),` могут вызывать state update много раз. Не выполняйте тяжёлую синхронную работу прямо во время каждого render, зависящего от observer state.`]}),`
1246
+ `,(0,c.jsx)(t.h3,{id:`css-прежде-измерений`,children:`CSS прежде измерений`}),`
1247
+ `,(0,c.jsxs)(t.p,{children:[`Если layout можно выразить через Grid, Flexbox, container queries, `,(0,c.jsx)(t.code,{children:`min/max/clamp`}),` или media queries, CSS обычно проще и дешевле `,(0,c.jsx)(t.code,{children:`ResizeObserver → state → render`}),`.`]}),`
1248
+ `,(0,c.jsx)(t.h3,{id:`object-url-1`,children:`Object URL`}),`
1249
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`downloadFileFromBlob`}),` освобождает созданный URL автоматически. Не создавайте ещё один URL вокруг него:`]}),`
1250
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Лишнее создание URL.
1251
+ const url = URL.createObjectURL(blob);
1252
+ downloadFileFromBlob("file.bin", blob);
1253
+ `})}),`
1254
+ `,(0,c.jsxs)(t.p,{children:[`Либо передайте Blob, либо создайте URL и передайте его в `,(0,c.jsx)(t.code,{children:`downloadFileFromObjectURL`}),`.`]}),`
1255
+ `,(0,c.jsx)(t.h2,{id:`тестирование`,children:`Тестирование`}),`
1256
+ `,(0,c.jsx)(t.p,{children:`DOM hooks требуют DOM test environment. Для Vitest файл может начинаться:`}),`
1257
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// @vitest-environment jsdom
1258
+ `})}),`
1259
+ `,(0,c.jsx)(t.h3,{id:`cleanup-react-root`,children:`Cleanup React root`}),`
1260
+ `,(0,c.jsx)(t.p,{children:`После каждого теста:`}),`
1261
+ `,(0,c.jsxs)(t.ul,{children:[`
1262
+ `,(0,c.jsx)(t.li,{children:`unmount React root;`}),`
1263
+ `,(0,c.jsx)(t.li,{children:`удаляйте container;`}),`
1264
+ `,(0,c.jsx)(t.li,{children:`восстанавливайте mocks;`}),`
1265
+ `,(0,c.jsx)(t.li,{children:`возвращайте real timers;`}),`
1266
+ `,(0,c.jsxs)(t.li,{children:[`удаляйте portal roots из `,(0,c.jsx)(t.code,{children:`document.body`}),`.`]}),`
1267
+ `]}),`
1268
+ `,(0,c.jsx)(t.h3,{id:`download-helpers`,children:`Download helpers`}),`
1269
+ `,(0,c.jsx)(t.p,{children:`Mock:`}),`
1270
+ `,(0,c.jsxs)(t.ul,{children:[`
1271
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`document.createElement`}),`/anchor `,(0,c.jsx)(t.code,{children:`click`}),`;`]}),`
1272
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`URL.createObjectURL`}),`;`]}),`
1273
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`URL.revokeObjectURL`}),`;`]}),`
1274
+ `,(0,c.jsx)(t.li,{children:`timers.`}),`
1275
+ `]}),`
1276
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`vi.useFakeTimers();
1277
+
1278
+ const click = vi.fn();
1279
+ const revokeObjectURL = vi.fn();
1280
+
1281
+ // После вызова helper:
1282
+ expect(click).toHaveBeenCalledOnce();
1283
+
1284
+ vi.runAllTimers();
1285
+ expect(revokeObjectURL).toHaveBeenCalledWith("blob:test");
1286
+ `})}),`
1287
+ `,(0,c.jsx)(t.p,{children:`Проверяйте revoke после выполнения timer, а не сразу после функции.`}),`
1288
+ `,(0,c.jsx)(t.h3,{id:`resizeobserver`,children:(0,c.jsx)(t.code,{children:`ResizeObserver`})}),`
1289
+ `,(0,c.jsx)(t.p,{children:`JSDOM не выполняет реальный layout. Используйте controllable fake:`}),`
1290
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`let resizeCallback: ResizeObserverCallback | undefined;
1291
+
1292
+ class FakeResizeObserver {
1293
+ constructor(callback: ResizeObserverCallback) {
1294
+ resizeCallback = callback;
1295
+ }
1296
+
1297
+ observe = vi.fn();
1298
+ disconnect = vi.fn();
1299
+ }
1300
+
1301
+ vi.stubGlobal("ResizeObserver", FakeResizeObserver);
1302
+ `})}),`
1303
+ `,(0,c.jsxs)(t.p,{children:[`Затем вызовите callback внутри React `,(0,c.jsx)(t.code,{children:`act`}),`:`]}),`
1304
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await act(async () => {
1305
+ resizeCallback?.([{ contentRect: { height: 42 } } as ResizeObserverEntry], {} as ResizeObserver);
1306
+ });
1307
+ `})}),`
1308
+ `,(0,c.jsx)(t.h3,{id:`intersectionobserver`,children:(0,c.jsx)(t.code,{children:`IntersectionObserver`})}),`
1309
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`let intersectionCallback: IntersectionObserverCallback | undefined;
1310
+
1311
+ class FakeIntersectionObserver {
1312
+ constructor(callback: IntersectionObserverCallback) {
1313
+ intersectionCallback = callback;
1314
+ }
1315
+
1316
+ observe = vi.fn();
1317
+ unobserve = vi.fn();
1318
+ disconnect = vi.fn();
1319
+ }
1320
+
1321
+ vi.stubGlobal("IntersectionObserver", FakeIntersectionObserver);
1322
+ `})}),`
1323
+ `,(0,c.jsx)(t.p,{children:`Передавайте fake entry:`}),`
1324
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await act(async () => {
1325
+ intersectionCallback?.([{ isIntersecting: true } as IntersectionObserverEntry], {} as IntersectionObserver);
1326
+ });
1327
+ `})}),`
1328
+ `,(0,c.jsx)(t.h3,{id:`useclickoutside-1`,children:(0,c.jsx)(t.code,{children:`useClickOutside`})}),`
1329
+ `,(0,c.jsxs)(t.p,{children:[`Dispatch именно `,(0,c.jsx)(t.code,{children:`mousedown`}),`, а не только `,(0,c.jsx)(t.code,{children:`click`}),`:`]}),`
1330
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`outside.dispatchEvent(
1331
+ new MouseEvent("mousedown", {
1332
+ bubbles: true
1333
+ })
1334
+ );
1335
+ `})}),`
1336
+ `,(0,c.jsx)(t.p,{children:`Проверьте:`}),`
1337
+ `,(0,c.jsxs)(t.ul,{children:[`
1338
+ `,(0,c.jsx)(t.li,{children:`inside одного ref;`}),`
1339
+ `,(0,c.jsx)(t.li,{children:`inside второго ref;`}),`
1340
+ `,(0,c.jsx)(t.li,{children:`outside всех refs;`}),`
1341
+ `,(0,c.jsx)(t.li,{children:`null refs;`}),`
1342
+ `,(0,c.jsx)(t.li,{children:`cleanup после unmount.`}),`
1343
+ `]}),`
1344
+ `,(0,c.jsx)(t.h3,{id:`useescapedismiss-1`,children:(0,c.jsx)(t.code,{children:`useEscapeDismiss`})}),`
1345
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const event = new KeyboardEvent("keydown", {
1346
+ key: "Escape",
1347
+ bubbles: true,
1348
+ cancelable: true
1349
+ });
1350
+
1351
+ document.dispatchEvent(event);
1352
+
1353
+ expect(onDismiss).toHaveBeenCalledOnce();
1354
+ expect(event.defaultPrevented).toBe(true);
1355
+ `})}),`
1356
+ `,(0,c.jsx)(t.p,{children:`Отдельно проверьте focus внутри и снаружи container.`}),`
1357
+ `,(0,c.jsx)(t.h3,{id:`useoverlayfocus-1`,children:(0,c.jsx)(t.code,{children:`useOverlayFocus`})}),`
1358
+ `,(0,c.jsx)(t.p,{children:`JSDOM не имеет полноценной browser Tab navigation. Проверяйте управляемые boundary cases:`}),`
1359
+ `,(0,c.jsxs)(t.ul,{children:[`
1360
+ `,(0,c.jsx)(t.li,{children:`initial focus;`}),`
1361
+ `,(0,c.jsx)(t.li,{children:`Tab на последнем element;`}),`
1362
+ `,(0,c.jsx)(t.li,{children:`Shift+Tab на первом;`}),`
1363
+ `,(0,c.jsx)(t.li,{children:`container fallback без candidates;`}),`
1364
+ `,(0,c.jsx)(t.li,{children:`restore target;`}),`
1365
+ `,(0,c.jsx)(t.li,{children:`disconnected restore target;`}),`
1366
+ `,(0,c.jsx)(t.li,{children:`cleanup listener.`}),`
1367
+ `]}),`
1368
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`requestAnimationFrame`}),` можно mock, но callback должен выполняться в контролируемом `,(0,c.jsx)(t.code,{children:`act`}),`.`]}),`
1369
+ `,(0,c.jsx)(t.h3,{id:`useistouchdevice-1`,children:(0,c.jsx)(t.code,{children:`useIsTouchDevice`})}),`
1370
+ `,(0,c.jsx)(t.p,{children:`Mock:`}),`
1371
+ `,(0,c.jsxs)(t.ul,{children:[`
1372
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`window.matchMedia`}),`;`]}),`
1373
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`navigator.maxTouchPoints`}),`;`]}),`
1374
+ `,(0,c.jsxs)(t.li,{children:[`при необходимости наличие `,(0,c.jsx)(t.code,{children:`ontouchstart`}),`;`]}),`
1375
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`window.resize`}),`.`]}),`
1376
+ `]}),`
1377
+ `,(0,c.jsxs)(t.p,{children:[`Проверяйте initial `,(0,c.jsx)(t.code,{children:`false`}),` отдельно от состояния после effect.`]}),`
1378
+ `,(0,c.jsx)(t.h2,{id:`ограничения-и-частые-ошибки`,children:`Ограничения и частые ошибки`}),`
1379
+ `,(0,c.jsx)(t.h3,{id:`вызов-download-во-время-render`,children:`Вызов download во время render`}),`
1380
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`function BadComponent({ blob }: { blob: Blob }) {
1381
+ downloadFileFromBlob("file.bin", blob);
1382
+ return null;
1383
+ }
1384
+ `})}),`
1385
+ `,(0,c.jsx)(t.p,{children:`Render может выполняться повторно. Download является user-facing side effect и должен запускаться из command handler.`}),`
1386
+ `,(0,c.jsx)(t.h3,{id:`повторное-использование-отозванного-object-url`,children:`Повторное использование отозванного object URL`}),`
1387
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`downloadFileFromObjectURL`}),` планирует revoke. Не сохраняйте переданный URL как долговечный preview source.`]}),`
1388
+ `,(0,c.jsx)(t.h3,{id:`ожидание-promise-или-подтверждения-download`,children:`Ожидание Promise или подтверждения download`}),`
1389
+ `,(0,c.jsxs)(t.p,{children:[`Helpers возвращают `,(0,c.jsx)(t.code,{children:`void`}),`. Они не знают, сохранил ли пользователь файл и куда.`]}),`
1390
+ `,(0,c.jsx)(t.h3,{id:`невалидный-json`,children:`Невалидный JSON`}),`
1391
+ `,(0,c.jsxs)(t.p,{children:[`Root `,(0,c.jsx)(t.code,{children:`undefined`}),`, circular data и `,(0,c.jsx)(t.code,{children:`BigInt`}),` не превращаются в надёжный JSON export автоматически. Подготавливайте serializable payload.`]}),`
1392
+ `,(0,c.jsx)(t.h3,{id:`portal-root-внутри-pure-render`,children:`Portal root внутри pure render`}),`
1393
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`getOrCreatePortalRoot`}),` меняет `,(0,c.jsx)(t.code,{children:`document.body`}),`. Создавайте root на browser bootstrap или используйте element из HTML shell.`]}),`
1394
+ `,(0,c.jsx)(t.h3,{id:`случайныйпустой-portal-id`,children:`Случайный/пустой portal ID`}),`
1395
+ `,(0,c.jsx)(t.p,{children:`Это ломает reuse и может создать duplicate IDs. Используйте стабильную application constant.`}),`
1396
+ `,(0,c.jsx)(t.h3,{id:`trigger-не-включён-в-outside-refs`,children:`Trigger не включён в outside refs`}),`
1397
+ `,(0,c.jsxs)(t.p,{children:[`Для toggle popover `,(0,c.jsx)(t.code,{children:`mousedown`}),` по trigger может считаться внешним, а следующий `,(0,c.jsx)(t.code,{children:`click`}),` снова изменить state. Передайте trigger и panel refs как единую область.`]}),`
1398
+ `,(0,c.jsx)(t.h3,{id:`inline-refs-array`,children:`Inline refs array`}),`
1399
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`useClickOutside([triggerRef, panelRef], close);
1400
+ `})}),`
1401
+ `,(0,c.jsx)(t.p,{children:`Функционально понятно, но новая array reference пересоздаёт listener после каждого render. Для часто обновляемого компонента стабилизируйте массив.`}),`
1402
+ `,(0,c.jsx)(t.h3,{id:`условный-вызов-hook`,children:`Условный вызов hook`}),`
1403
+ `,(0,c.jsxs)(t.p,{children:[`Не помещайте hooks внутрь `,(0,c.jsx)(t.code,{children:`if (open)`}),`. Передавайте `,(0,c.jsx)(t.code,{children:`active`}),`/`,(0,c.jsx)(t.code,{children:`enabled`}),` либо выделите mounted overlay component.`]}),`
1404
+ `,(0,c.jsx)(t.h3,{id:`несколько-active-escape-hooks`,children:`Несколько active Escape hooks`}),`
1405
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`preventDefault`}),` не останавливает другие listeners. Только top overlay должен быть active.`]}),`
1406
+ `,(0,c.jsx)(t.h3,{id:`container-без-focus`,children:`Container без focus`}),`
1407
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`initialFocus: "container"`}),` и zero-candidate trap требуют `,(0,c.jsx)(t.code,{children:`tabIndex={-1}`}),` для обычного `,(0,c.jsx)(t.code,{children:`div`}),`.`]}),`
1408
+ `,(0,c.jsx)(t.h3,{id:`focus-trap-без-семантики`,children:`Focus trap без семантики`}),`
1409
+ `,(0,c.jsxs)(t.p,{children:[`Focus trap не добавляет `,(0,c.jsx)(t.code,{children:`role="dialog"`}),`, accessible name, inert background и close command.`]}),`
1410
+ `,(0,c.jsx)(t.h3,{id:`focus-trap-на-non-modal-ui`,children:`Focus trap на non-modal UI`}),`
1411
+ `,(0,c.jsx)(t.p,{children:`Не зацикливайте focus автоматически в tooltip или простом non-modal popover. Это может мешать keyboard navigation.`}),`
1412
+ `,(0,c.jsx)(t.h3,{id:`restore-до-завершения-animation`,children:`Restore до завершения animation`}),`
1413
+ `,(0,c.jsx)(t.p,{children:`Cleanup выполняется при деактивации hook. Если exit animation ещё идёт, управляйте restore отдельно.`}),`
1414
+ `,(0,c.jsx)(t.h3,{id:`active-overlay-без-mounted-ref`,children:`Active overlay без mounted ref`}),`
1415
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useOverlayFocus`}),` будет повторять requestAnimationFrame до появления element или cleanup.`]}),`
1416
+ `,(0,c.jsx)(t.h3,{id:`inline-observer-options`,children:`Inline observer options`}),`
1417
+ `,(0,c.jsxs)(t.p,{children:[`Новый object на каждом render пересоздаёт `,(0,c.jsx)(t.code,{children:`IntersectionObserver`}),`.`]}),`
1418
+ `,(0,c.jsx)(t.h3,{id:`observer-как-синхронное-измерение`,children:`Observer как синхронное измерение`}),`
1419
+ `,(0,c.jsx)(t.p,{children:`Initial height/visibility — defaults. Реальный callback приходит позднее.`}),`
1420
+ `,(0,c.jsx)(t.h3,{id:`отсутствующие-observer-apis`,children:`Отсутствующие observer APIs`}),`
1421
+ `,(0,c.jsx)(t.p,{children:`Hooks не предоставляют polyfills. Настройте target browsers и test environment.`}),`
1422
+ `,(0,c.jsx)(t.h3,{id:`touch-равен-mobile`,children:`Touch равен mobile`}),`
1423
+ `,(0,c.jsx)(t.p,{children:`Touch-capable laptop не обязательно mobile. Не связывайте бизнес/UI доступность только с результатом heuristic.`}),`
1424
+ `,(0,c.jsx)(t.h3,{id:`resizeobserver-вместо-css`,children:`ResizeObserver вместо CSS`}),`
1425
+ `,(0,c.jsx)(t.p,{children:`Не измеряйте layout через JavaScript, если задача полностью решается CSS.`}),`
1426
+ `,(0,c.jsx)(t.h2,{id:`краткий-справочник-api`,children:`Краткий справочник API`}),`
1427
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вход`}),(0,c.jsx)(t.th,{children:`Результат`}),(0,c.jsx)(t.th,{children:`Default/событие`}),(0,c.jsx)(t.th,{children:`Главное ограничение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`downloadFileFromObjectURL`})}),(0,c.jsx)(t.td,{children:`filename, object URL`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`void`})}),(0,c.jsxs)(t.td,{children:[`revoke через `,(0,c.jsx)(t.code,{children:`setTimeout(0)`})]}),(0,c.jsx)(t.td,{children:`Принимает ownership URL; нет подтверждения download.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`downloadFileFromBlob`})}),(0,c.jsxs)(t.td,{children:[`filename, `,(0,c.jsx)(t.code,{children:`Blob`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`void`})}),(0,c.jsx)(t.td,{children:`Сам создаёт/revokes URL`}),(0,c.jsx)(t.td,{children:`Browser-only, ошибки синхронные.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`downloadFileFromJson`})}),(0,c.jsxs)(t.td,{children:[`filename, `,(0,c.jsx)(t.code,{children:`unknown`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`void`})}),(0,c.jsx)(t.td,{children:`JSON с 2 spaces, UTF-8 MIME`}),(0,c.jsxs)(t.td,{children:[`Наследует ошибки и потери `,(0,c.jsx)(t.code,{children:`JSON.stringify`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`getOrCreatePortalRoot`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`id: string`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`HTMLElement | null`})}),(0,c.jsxs)(t.td,{children:[`Создаёт `,(0,c.jsx)(t.code,{children:`div`}),` в body`]}),(0,c.jsx)(t.td,{children:`DOM mutation; root не удаляется.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useClickOutside`})}),(0,c.jsx)(t.td,{children:`один ref/массив refs, handler`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`void`})}),(0,c.jsxs)(t.td,{children:[`Global bubbling `,(0,c.jsx)(t.code,{children:`mousedown`})]}),(0,c.jsx)(t.td,{children:`Нет enabled/event arg/custom document.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useEscapeDismiss`})}),(0,c.jsx)(t.td,{children:`options object`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`void`})}),(0,c.jsxs)(t.td,{children:[`enabled=true, capture `,(0,c.jsx)(t.code,{children:`keydown`})]}),(0,c.jsx)(t.td,{children:`Не управляет stack/focus; defaultPrevented игнорируется.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useElementHeightObserver`})}),(0,c.jsx)(t.td,{children:`element ref`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`number`})}),(0,c.jsxs)(t.td,{children:[`Initial `,(0,c.jsx)(t.code,{children:`0`})]}),(0,c.jsx)(t.td,{children:`ContentRect, требует ResizeObserver.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useIntersectionObserver`})}),(0,c.jsx)(t.td,{children:`element ref, native options`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`{ isIntersecting }`})}),(0,c.jsxs)(t.td,{children:[`Initial `,(0,c.jsx)(t.code,{children:`false`})]}),(0,c.jsx)(t.td,{children:`Только boolean, требует IntersectionObserver.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useIsTouchDevice`})}),(0,c.jsx)(t.td,{children:`Нет`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`boolean`})}),(0,c.jsx)(t.td,{children:`Initial false, update на mount/resize`}),(0,c.jsx)(t.td,{children:`Capability heuristic, не текущий input mode.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useOverlayFocus`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`UseOverlayFocusOptions<T>`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`RefObject<T | null>`})}),(0,c.jsx)(t.td,{children:`auto focus, no trap, restore=true`}),(0,c.jsx)(t.td,{children:`Не полноценный accessibility/overlay manager.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useFocusTrap`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`isOpen: boolean`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`RefObject<HTMLDivElement | null>`})}),(0,c.jsx)(t.td,{children:`auto + trap + restore`}),(0,c.jsx)(t.td,{children:`Фиксированный compatibility wrapper.`})]})]})]}),`
1428
+ `,(0,c.jsx)(t.h2,{id:`вопросы-и-ответы`,children:`Вопросы и ответы`}),`
1429
+ `,(0,c.jsx)(t.h3,{id:`как-скачать-base64`,children:`Как скачать base64?`}),`
1430
+ `,(0,c.jsxs)(t.p,{children:[`Сначала преобразуйте base64 в `,(0,c.jsx)(t.code,{children:`Blob`}),` через модуль `,(0,c.jsx)(t.code,{children:`/binary`}),`, затем передайте Blob:`]}),`
1431
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const blob = binaryToBlob(base64, "application/pdf", true);
1432
+ downloadFileFromBlob("report.pdf", blob);
1433
+ `})}),`
1434
+ `,(0,c.jsxs)(t.h3,{id:`нужно-ли-самостоятельно-вызывать-urlrevokeobjecturl-после-downloadfilefromblob`,children:[`Нужно ли самостоятельно вызывать `,(0,c.jsx)(t.code,{children:`URL.revokeObjectURL`}),` после `,(0,c.jsx)(t.code,{children:`downloadFileFromBlob`}),`?`]}),`
1435
+ `,(0,c.jsx)(t.p,{children:`Нет. Helper создаёт URL и планирует revoke самостоятельно.`}),`
1436
+ `,(0,c.jsxs)(t.h3,{id:`нужно-ли-вызывать-revoke-после-downloadfilefromobjecturl`,children:[`Нужно ли вызывать revoke после `,(0,c.jsx)(t.code,{children:`downloadFileFromObjectURL`}),`?`]}),`
1437
+ `,(0,c.jsx)(t.p,{children:`Нет, если URL передан этому helper: ownership уже отдан. Не используйте URL повторно.`}),`
1438
+ `,(0,c.jsx)(t.h3,{id:`можно-ли-передать-обычный-https-url`,children:`Можно ли передать обычный HTTPS URL?`}),`
1439
+ `,(0,c.jsxs)(t.p,{children:[`API предназначен для object URL и вызывает `,(0,c.jsx)(t.code,{children:`revokeObjectURL`}),`. Для навигации/скачивания remote URL используйте отдельный contract с учётом same-origin, headers и browser policy.`]}),`
1440
+ `,(0,c.jsx)(t.h3,{id:`почему-json-export-упал-на-объекте`,children:`Почему JSON export упал на объекте?`}),`
1441
+ `,(0,c.jsxs)(t.p,{children:[`Наиболее частые причины — circular reference, `,(0,c.jsx)(t.code,{children:`BigInt`}),` или исключение в custom `,(0,c.jsx)(t.code,{children:`toJSON`}),`.`]}),`
1442
+ `,(0,c.jsxs)(t.h3,{id:`создаёт-ли-getorcreateportalroot-react-portal`,children:[`Создаёт ли `,(0,c.jsx)(t.code,{children:`getOrCreatePortalRoot`}),` React portal?`]}),`
1443
+ `,(0,c.jsxs)(t.p,{children:[`Нет. Он создаёт только DOM container. Portal создаётся через `,(0,c.jsx)(t.code,{children:`createPortal`}),`.`]}),`
1444
+ `,(0,c.jsxs)(t.h3,{id:`почему-функция-вернула-null`,children:[`Почему функция вернула `,(0,c.jsx)(t.code,{children:`null`}),`?`]}),`
1445
+ `,(0,c.jsxs)(t.p,{children:[`Global `,(0,c.jsx)(t.code,{children:`document`}),` отсутствовал. Обычно это server/Node boundary.`]}),`
1446
+ `,(0,c.jsx)(t.h3,{id:`нужно-ли-удалять-portal-root-при-закрытии-dialog`,children:`Нужно ли удалять portal root при закрытии dialog?`}),`
1447
+ `,(0,c.jsx)(t.p,{children:`Обычно application-wide root оставляют до выгрузки страницы. Helper не удаляет его. Временный root должен очищать его owner.`}),`
1448
+ `,(0,c.jsx)(t.h3,{id:`почему-outside-handler-сработал-при-клике-по-popover`,children:`Почему outside handler сработал при клике по popover?`}),`
1449
+ `,(0,c.jsx)(t.p,{children:`Portal content или trigger мог не входить в refs. Передайте все DOM-subtrees, считающиеся внутренними.`}),`
1450
+ `,(0,c.jsx)(t.h3,{id:`почему-outside-handler-сработал-пока-elements-ещё-не-mounted`,children:`Почему outside handler сработал, пока elements ещё не mounted?`}),`
1451
+ `,(0,c.jsxs)(t.p,{children:[`В array mode все null refs считаются отсутствующими внутренними областями, поэтому событие является outside. Проверяйте `,(0,c.jsx)(t.code,{children:`open`}),` в callback или монтируйте hook вместе с overlay component.`]}),`
1452
+ `,(0,c.jsx)(t.h3,{id:`почему-escape-не-закрыл-overlay`,children:`Почему Escape не закрыл overlay?`}),`
1453
+ `,(0,c.jsx)(t.p,{children:`Проверьте:`}),`
1454
+ `,(0,c.jsxs)(t.ul,{children:[`
1455
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`active`}),`;`]}),`
1456
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`enabled`}),`;`]}),`
1457
+ `,(0,c.jsxs)(t.li,{children:[`точное `,(0,c.jsx)(t.code,{children:`event.key === "Escape"`}),`;`]}),`
1458
+ `,(0,c.jsx)(t.li,{children:`target document;`}),`
1459
+ `,(0,c.jsxs)(t.li,{children:[`focus внутри `,(0,c.jsx)(t.code,{children:`containerRef`}),`;`]}),`
1460
+ `,(0,c.jsx)(t.li,{children:`существует ли container.`}),`
1461
+ `]}),`
1462
+ `,(0,c.jsx)(t.h3,{id:`почему-escape-закрыл-несколько-overlay`,children:`Почему Escape закрыл несколько overlay?`}),`
1463
+ `,(0,c.jsxs)(t.p,{children:[`Несколько hooks были active. Модуль не управляет stack и не вызывает `,(0,c.jsx)(t.code,{children:`stopPropagation`}),`. Активируйте только top overlay.`]}),`
1464
+ `,(0,c.jsxs)(t.h3,{id:`можно-ли-отключить-escape-через-documenttarget-null`,children:[`Можно ли отключить Escape через `,(0,c.jsx)(t.code,{children:`documentTarget: null`}),`?`]}),`
1465
+ `,(0,c.jsxs)(t.p,{children:[`Нет при наличии global document: nullish fallback выберет его. Используйте `,(0,c.jsx)(t.code,{children:`active: false`}),` или `,(0,c.jsx)(t.code,{children:`enabled: false`}),`.`]}),`
1466
+ `,(0,c.jsx)(t.h3,{id:`почему-focus-не-установился-на-container`,children:`Почему focus не установился на container?`}),`
1467
+ `,(0,c.jsxs)(t.p,{children:[`Обычному `,(0,c.jsx)(t.code,{children:`div`}),` нужен `,(0,c.jsx)(t.code,{children:`tabIndex={-1}`}),`. Также target должен быть подключён к DOM.`]}),`
1468
+ `,(0,c.jsx)(t.h3,{id:`почему-focus-ушёл-из-trap-программно`,children:`Почему focus ушёл из trap программно?`}),`
1469
+ `,(0,c.jsxs)(t.p,{children:[`Hook зацикливает только keyboard Tab на boundary container. Он не перехватывает произвольный `,(0,c.jsx)(t.code,{children:`.focus()`}),`, pointer focus или другой portal.`]}),`
1470
+ `,(0,c.jsx)(t.h3,{id:`почему-focus-восстановился-до-окончания-анимации`,children:`Почему focus восстановился до окончания анимации?`}),`
1471
+ `,(0,c.jsx)(t.p,{children:`Restore выполняется в effect cleanup при деактивации. Для exit animation отключите встроенный restore и выполните его после фактического удаления overlay.`}),`
1472
+ `,(0,c.jsxs)(t.h3,{id:`почему-useelementheightobserver-сначала-вернул-0`,children:[`Почему `,(0,c.jsx)(t.code,{children:`useElementHeightObserver`}),` сначала вернул `,(0,c.jsx)(t.code,{children:`0`}),`?`]}),`
1473
+ `,(0,c.jsx)(t.p,{children:`Это initial state. Hook ждёт callback ResizeObserver.`}),`
1474
+ `,(0,c.jsx)(t.h3,{id:`какую-высоту-возвращает-observer`,children:`Какую высоту возвращает observer?`}),`
1475
+ `,(0,c.jsxs)(t.p,{children:[`В первую очередь `,(0,c.jsx)(t.code,{children:`ResizeObserverEntry.contentRect.height`}),`, обычно content box. Это не обязательно `,(0,c.jsx)(t.code,{children:`offsetHeight`}),` или визуальный border-box.`]}),`
1476
+ `,(0,c.jsxs)(t.h3,{id:`почему-useintersectionobserver-пересоздаётся`,children:[`Почему `,(0,c.jsx)(t.code,{children:`useIntersectionObserver`}),` пересоздаётся?`]}),`
1477
+ `,(0,c.jsxs)(t.p,{children:[`Вероятно, `,(0,c.jsx)(t.code,{children:`options`}),` создаётся inline и имеет новую ссылку на каждом render. Стабилизируйте object.`]}),`
1478
+ `,(0,c.jsxs)(t.h3,{id:`можно-ли-использовать-isintersecting-как-доказательство-просмотра`,children:[`Можно ли использовать `,(0,c.jsx)(t.code,{children:`isIntersecting`}),` как доказательство просмотра?`]}),`
1479
+ `,(0,c.jsx)(t.p,{children:`Нет. Это layout signal, а не гарантия человеческого внимания или бизнес-событие.`}),`
1480
+ `,(0,c.jsxs)(t.h3,{id:`почему-touch-hook-сначала-возвращает-false`,children:[`Почему touch hook сначала возвращает `,(0,c.jsx)(t.code,{children:`false`}),`?`]}),`
1481
+ `,(0,c.jsxs)(t.p,{children:[`Detection выполняется после mount в effect. Initial state равен `,(0,c.jsx)(t.code,{children:`false`}),`.`]}),`
1482
+ `,(0,c.jsxs)(t.h3,{id:`можно-ли-по-useistouchdevice-определить-телефон`,children:[`Можно ли по `,(0,c.jsx)(t.code,{children:`useIsTouchDevice`}),` определить телефон?`]}),`
1483
+ `,(0,c.jsx)(t.p,{children:`Нет. Результат означает touch/coarse capability heuristic. Он не определяет класс устройства, размер экрана или единственный input method.`}),`
1484
+ `,(0,c.jsxs)(t.h3,{id:`что-выбрать-для-modal-usefocustrap-или-useoverlayfocus`,children:[`Что выбрать для modal: `,(0,c.jsx)(t.code,{children:`useFocusTrap`}),` или `,(0,c.jsx)(t.code,{children:`useOverlayFocus`}),`?`]}),`
1485
+ `,(0,c.jsxs)(t.p,{children:[`Для нового кода обычно `,(0,c.jsx)(t.code,{children:`useOverlayFocus`}),`: у него есть явные options, custom initial/restore target и external ref. `,(0,c.jsx)(t.code,{children:`useFocusTrap`}),` — фиксированный compatibility wrapper.`]}),`
1486
+ `,(0,c.jsxs)(t.h3,{id:`достаточно-ли-useoverlayfocus-trapfocus-true--для-доступного-modal`,children:[`Достаточно ли `,(0,c.jsx)(t.code,{children:`useOverlayFocus({ trapFocus: true })`}),` для доступного modal?`]}),`
1487
+ `,(0,c.jsx)(t.p,{children:`Нет. Нужны semantic role, accessible name, close command, inert/background policy, stack management, scroll policy и проверка keyboard/screen-reader сценария.`})]})}function s(e={}){let{wrapper:t}={...n(),...e.components};return t?(0,c.jsx)(t,{...e,children:(0,c.jsx)(o,{...e})}):o(e)}var c;function l(){return(l=e((()=>{c=t(),r(),a()})))()}l();export{s as default};