@ryuzaki13/react-foundation-ui 1.8.6 → 1.8.8

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 (313) 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-CYbtifH0.js → base-table-CdcVMDGY.js} +2 -2
  4. package/dist/chunks/{base-table-CYbtifH0.js.map → base-table-CdcVMDGY.js.map} +1 -1
  5. package/dist/chunks/{context-menu-L4KM0FRD.js → context-menu-CE_wfxct.js} +2 -2
  6. package/dist/chunks/{context-menu-L4KM0FRD.js.map → context-menu-CE_wfxct.js.map} +1 -1
  7. package/dist/chunks/date-input-g6l1iDmf.js.map +1 -1
  8. package/dist/chunks/{multi-select-CCgoTHWI.js → multi-select-CHZhWoq6.js} +2 -2
  9. package/dist/chunks/{multi-select-CCgoTHWI.js.map → multi-select-CHZhWoq6.js.map} +1 -1
  10. package/dist/chunks/{picker-Cb3D2Hts.js → picker-BcB3rSQt.js} +2 -2
  11. package/dist/chunks/picker-BcB3rSQt.js.map +1 -0
  12. package/dist/chunks/{select-Baek-IUe.js → select-BorNxjrG.js} +2 -2
  13. package/dist/chunks/{select-Baek-IUe.js.map → select-BorNxjrG.js.map} +1 -1
  14. package/dist/chunks/{table-CrQiurtA.js → table-DcABGh_F.js} +2 -2
  15. package/dist/chunks/{table-CrQiurtA.js.map → table-DcABGh_F.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-CV8uR5lW.js → Badge-CxQIV1VM.js} +1 -1
  33. package/storybook-static/assets/{Badge.stories-DqTFDEB6.js → Badge.stories-BJkImUYK.js} +1 -1
  34. package/storybook-static/assets/{BadgeList-CVWvhSS7.js → BadgeList-DCcUez-d.js} +1 -1
  35. package/storybook-static/assets/{Button-C5Z6nJ9s.js → Button-BM_JnsIi.js} +1 -1
  36. package/storybook-static/assets/{Button-DtySOeyD.js → Button-Dh35R_JE.js} +1 -1
  37. package/storybook-static/assets/{Button.stories--Wr67DaY.js → Button.stories-BsgEwN-G.js} +1 -1
  38. package/storybook-static/assets/{CalendarView-ZzqQ8UIe.js → CalendarView-Bu5aaqrw.js} +1 -1
  39. package/storybook-static/assets/{CheckBox-BfNP6vO6.js → CheckBox-B93BCdtW.js} +1 -1
  40. package/storybook-static/assets/{CheckBox-BBNTe4u8.js → CheckBox-DB_H7Jkd.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-B4Sv2TXp.js → CheckBox.stories-ChtE7IN-.js} +1 -1
  43. package/storybook-static/assets/{Color-6AOWCN3U-Ce5w8Y2l.js → Color-6AOWCN3U-CuY1qZ6d.js} +1 -1
  44. package/storybook-static/assets/{ContextMenu-Boi0R7-s.js → ContextMenu-B30yur26.js} +1 -1
  45. package/storybook-static/assets/{ContextMenu-D-lV53Zc.js → ContextMenu-DeoYEX8n.js} +1 -1
  46. package/storybook-static/assets/{ContextMenu.stories-D_rxDbLk.js → ContextMenu.stories-B80uu025.js} +1 -1
  47. package/storybook-static/assets/{CustomOptionButton-VYfjUfhW.js → CustomOptionButton-DQp71AHJ.js} +1 -1
  48. package/storybook-static/assets/{DateInput-f-RcBdOb.js → DateInput-CMhHGS-s.js} +1 -1
  49. package/storybook-static/assets/{DateInput-q2hPsJcH.js → DateInput-wIlcdKHD.js} +1 -1
  50. package/storybook-static/assets/{DateInput.stories-zQzjIsfx.js → DateInput.stories-DS45syC3.js} +1 -1
  51. package/storybook-static/assets/{DateRangePresetSelect-DQBOF8f5.js → DateRangePresetSelect-31S_37_9.js} +1 -1
  52. package/storybook-static/assets/{DateRangePresetSelect-Ciikik1_.js → DateRangePresetSelect-DgpOHDW1.js} +1 -1
  53. package/storybook-static/assets/{DateRangePresetSelect.stories-BF8IP5Ep.js → DateRangePresetSelect.stories-3VlGka98.js} +1 -1
  54. package/storybook-static/assets/{Detail-CEl8URNQ.js → Detail-CxFnUUkK.js} +1 -1
  55. package/storybook-static/assets/{Detail.stories-Mr3ypLAA.js → Detail.stories-DqgQuwn1.js} +1 -1
  56. package/storybook-static/assets/{Dialog-Dj2y3EO1.js → Dialog-B0VCykP7.js} +1 -1
  57. package/storybook-static/assets/{Dialog-BydPFV17.js → Dialog-CdJK__QW.js} +1 -1
  58. package/storybook-static/assets/{Dialog.stories-C1WlZdyp.js → Dialog.stories-C1ABO4hq.js} +1 -1
  59. package/storybook-static/assets/{Disclosure-DH77E5hY.js → Disclosure-g89eWjtw.js} +1 -1
  60. package/storybook-static/assets/{Disclosure.stories-B9ZVczUX.js → Disclosure.stories-B2CCl9Dg.js} +1 -1
  61. package/storybook-static/assets/{DisclosureGroup-CIoFeosY.js → DisclosureGroup-DZ-1k-kK.js} +1 -1
  62. package/storybook-static/assets/{DocsRenderer-JROSPFPF-CbJN3X6f.js → DocsRenderer-JROSPFPF-C54xfLi3.js} +2 -2
  63. package/storybook-static/assets/{DropZone-DTBV-X-J.js → DropZone-CpVX5FJc.js} +1 -1
  64. package/storybook-static/assets/{DropZone-CR7xRn_b.js → DropZone-FboQPtkt.js} +1 -1
  65. package/storybook-static/assets/{DropZone.stories-Cw_og1Cs.js → DropZone.stories-Dtt5tKix.js} +1 -1
  66. package/storybook-static/assets/{DropdownMenu-CVIeHT8N.js → DropdownMenu--_ifmQMt.js} +1 -1
  67. package/storybook-static/assets/{ExpandableActionPanel-D8U6v8X7.js → ExpandableActionPanel-B2g9YRDI.js} +1 -1
  68. package/storybook-static/assets/{ExpandableActionPanel.stories-D42CZyOc.js → ExpandableActionPanel.stories-DnhAdkcl.js} +1 -1
  69. package/storybook-static/assets/{Flex-Bcsl0U5O.js → Flex-B-x1Ohkp.js} +1 -1
  70. package/storybook-static/assets/{Flex.stories-C0vSa3mh.js → Flex.stories-BYEkhW_Q.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-DvU3oc_q.js → FloatingPopover-DOyMIo2-.js} +1 -1
  74. package/storybook-static/assets/FormattedTableCell-Cja58sq0.js +15 -0
  75. package/storybook-static/assets/{Grid-BCzygAqy.js → Grid-CzO0Ae1r.js} +1 -1
  76. package/storybook-static/assets/{Grid-Wl0Y0YAh.js → Grid-DDQ5Q1Zu.js} +1 -1
  77. package/storybook-static/assets/{Grid.stories-G6gMmKCP.js → Grid.stories-Bh-o6XTQ.js} +1 -1
  78. package/storybook-static/assets/{GridContainer-qDguE1HS.js → GridContainer-fKYgDsLl.js} +1 -1
  79. package/storybook-static/assets/{ImageCropper.stories-u6rOvDbd.js → ImageCropper.stories-Dy2RiAcH.js} +3 -3
  80. package/storybook-static/assets/{ImageRenderer-BYIRHN5G.js → ImageRenderer-qllJTZXD.js} +1 -1
  81. package/storybook-static/assets/{ImageRenderer.stories-BMEHEIVH.js → ImageRenderer.stories-DvE7n1QW.js} +1 -1
  82. package/storybook-static/assets/{ImageView.stories-DiEA93Yz.js → ImageView.stories-BmxCb-j1.js} +1 -1
  83. package/storybook-static/assets/{ImageViewer.stories-DyxctGxM.js → ImageViewer.stories-7cW5swP8.js} +2 -2
  84. package/storybook-static/assets/{ImageViewerRuntime--h_3C2s0.js → ImageViewerRuntime-CZu7J7wR.js} +2 -2
  85. package/storybook-static/assets/{Input-B2kOwWOU.js → Input-DWC0iHTm.js} +1 -1
  86. package/storybook-static/assets/{Input-CViRQ_rj.js → Input-JgKfpyoY.js} +1 -1
  87. package/storybook-static/assets/{Input.stories-D9eo9MWc.js → Input.stories-D_3AMqSf.js} +1 -1
  88. package/storybook-static/assets/{InputDateTime-T9_iyG4G.js → InputDateTime-DZOKr9oV.js} +1 -1
  89. package/storybook-static/assets/{InputDateTime.stories-DzegljaN.js → InputDateTime.stories-CFVXMtGS.js} +1 -1
  90. package/storybook-static/assets/{InputFile-CA3YpXnm.js → InputFile-BM_2v8Vr.js} +1 -1
  91. package/storybook-static/assets/{InputFile-Cvdj6RfC.js → InputFile-QkBKG4MF.js} +1 -1
  92. package/storybook-static/assets/{InputFile.stories-C18UaXPS.js → InputFile.stories-y_bqA0ji.js} +1 -1
  93. package/storybook-static/assets/{InputFiles-nS_qsXY7.js → InputFiles-0MQlpVXZ.js} +1 -1
  94. package/storybook-static/assets/{InputFiles-C4OkTG9M.js → InputFiles-CPMaf-0m.js} +1 -1
  95. package/storybook-static/assets/{InputFiles.stories-BYd0Pe0R.js → InputFiles.stories-FvkbnEu9.js} +1 -1
  96. package/storybook-static/assets/{InputFilesList-CXPf7Ui_.js → InputFilesList-LSKKFWI2.js} +1 -1
  97. package/storybook-static/assets/{InputImage-kd50L1Gf.js → InputImage-BtWMRvPj.js} +1 -1
  98. package/storybook-static/assets/{InputImage-jDVIxP7S.js → InputImage-CtHvsmW2.js} +1 -1
  99. package/storybook-static/assets/{InputImage.stories-D-xmS4Qj.js → InputImage.stories-DB4Dpqrp.js} +1 -1
  100. package/storybook-static/assets/{InteractiveComponents-D13nzSK7.js → InteractiveComponents-TycCPNdw.js} +1 -1
  101. package/storybook-static/assets/{InteractiveComponents.stories-yITzDh-Y.js → InteractiveComponents.stories-BPL6ph-t.js} +1 -1
  102. package/storybook-static/assets/{LayoutPicker-BfYXGIKb.js → LayoutPicker-121F1oKo.js} +1 -1
  103. package/storybook-static/assets/{LayoutPicker-9FVo0bF3.js → LayoutPicker-amQe8EyF.js} +1 -1
  104. package/storybook-static/assets/{LayoutPicker.stories-B-UnuGFM.js → LayoutPicker.stories-DpcjEEH4.js} +1 -1
  105. package/storybook-static/assets/{Listbox-9LVlsDzw.js → Listbox-D-Sqd9jb.js} +1 -1
  106. package/storybook-static/assets/{Listbox-C53-i-L6.js → Listbox-P5hFPYDA.js} +1 -1
  107. package/storybook-static/assets/{Listbox.stories-CCN72ABE.js → Listbox.stories-CL85ceLu.js} +1 -1
  108. package/storybook-static/assets/{Modal-CoXpuByh.js → Modal-Cn7uJut0.js} +1 -1
  109. package/storybook-static/assets/{Modal.stories-DAj_oPSt.js → Modal.stories-DgRduIwP.js} +1 -1
  110. package/storybook-static/assets/{ModalManagerProvider-C7pGT0B3.js → ModalManagerProvider-BpTBqDR5.js} +1 -1
  111. package/storybook-static/assets/{MultiSelect-DZ2krz-D.js → MultiSelect-BAHGljvC.js} +1 -1
  112. package/storybook-static/assets/{MultiSelect-DqeGNK15.js → MultiSelect-Bdq-guyW.js} +1 -1
  113. package/storybook-static/assets/{MultiSelect.stories-BoVHXMbn.js → MultiSelect.stories-6C-TQL1s.js} +1 -1
  114. package/storybook-static/assets/{NoData-N6Pbu0LD.js → NoData-mxG4mL4e.js} +1 -1
  115. package/storybook-static/assets/{NotificationsHost--uPz1G3j.js → NotificationsHost-CygepfX-.js} +1 -1
  116. package/storybook-static/assets/{NotificationsHost.stories-B-X-Ljxs.js → NotificationsHost.stories-B7ZmvjI6.js} +1 -1
  117. package/storybook-static/assets/{ODataMultiSelect-7U6nJdqB.js → ODataMultiSelect-BHl4B00F.js} +1 -1
  118. package/storybook-static/assets/{ODataMultiSelect.stories-B-t6MsOr.js → ODataMultiSelect.stories-BHSj5rjf.js} +1 -1
  119. package/storybook-static/assets/{ODataSelect-Dy-fSgE6.js → ODataSelect-foaFaP6d.js} +1 -1
  120. package/storybook-static/assets/{ODataSelect.stories-DhqHtcmD.js → ODataSelect.stories-82q9oBl6.js} +1 -1
  121. package/storybook-static/assets/{ODataTreeMultiSelect-Ol0nuytU.js → ODataTreeMultiSelect-CyFx96Sj.js} +1 -1
  122. package/storybook-static/assets/{ODataTreeMultiSelect.stories-BupVzhbY.js → ODataTreeMultiSelect.stories-zLBWRIRi.js} +1 -1
  123. package/storybook-static/assets/{ODataTreeSelect-EkpDmDyk.js → ODataTreeSelect-DyPqYfQa.js} +1 -1
  124. package/storybook-static/assets/{ODataTreeSelect.stories-CFdbwyEg.js → ODataTreeSelect.stories-BSowZVpd.js} +1 -1
  125. package/storybook-static/assets/{OneStepScroller-rmyPl9f5.js → OneStepScroller-B_7N8BYz.js} +1 -1
  126. package/storybook-static/assets/Option-CVAAv8AB.css +1 -0
  127. package/storybook-static/assets/{Option-72PrSDkj.js → Option-DvXrygAo.js} +1 -1
  128. package/storybook-static/assets/{OptionButton-Br8QoJL7.js → OptionButton-CaiIhG8s.js} +1 -1
  129. package/storybook-static/assets/{PeriodSelect-DEZVGWGR.js → PeriodSelect-CcheHEx9.js} +1 -1
  130. package/storybook-static/assets/{PeriodSelect-C5Xk-g4Y.js → PeriodSelect-DQWcrf3z.js} +1 -1
  131. package/storybook-static/assets/{PeriodSelect.stories-BVBP97HW.js → PeriodSelect.stories-BoZAwgxD.js} +1 -1
  132. package/storybook-static/assets/{PickerTriggerActions-DSmRHX1I.js → PickerTriggerActions-C7iwI6po.js} +1 -1
  133. package/storybook-static/assets/{Popover-BpbeIq6-.js → Popover-BA61XeKS.js} +1 -1
  134. package/storybook-static/assets/{Popover-1KQwBxEJ.js → Popover-DemWjUAR.js} +1 -1
  135. package/storybook-static/assets/{Popover.stories-6RDZ0TND.js → Popover.stories-BZtYXuDy.js} +1 -1
  136. package/storybook-static/assets/{PresetRangeDateInput-BdkKzY69.js → PresetRangeDateInput-BPRlqmwn.js} +1 -1
  137. package/storybook-static/assets/{PresetRangeDateInput-CDhgmyFs.js → PresetRangeDateInput-QZD02nll.js} +1 -1
  138. package/storybook-static/assets/{PresetRangeDateInput.stories-BoE41IUK.js → PresetRangeDateInput.stories-fqhQho_t.js} +1 -1
  139. package/storybook-static/assets/{PresetSelect-CA5AyOOy.js → PresetSelect-604ElKuM.js} +1 -1
  140. package/storybook-static/assets/README-BSfoRcmy.js +648 -0
  141. package/storybook-static/assets/README-B_PcyrCy.js +876 -0
  142. package/storybook-static/assets/README-Bbmv2xJo.js +860 -0
  143. package/storybook-static/assets/README-C1ki7lL7.js +417 -0
  144. package/storybook-static/assets/README-CFCpUZA-.js +1093 -0
  145. package/storybook-static/assets/README-CMoAGNQ5.js +282 -0
  146. package/storybook-static/assets/README-CN_Y4pYZ.js +273 -0
  147. package/storybook-static/assets/README-COR9RMzk.js +1487 -0
  148. package/storybook-static/assets/README-CRfJujEw.js +1309 -0
  149. package/storybook-static/assets/README-CZ49-EeH.js +347 -0
  150. package/storybook-static/assets/README-D01AizPk.js +288 -0
  151. package/storybook-static/assets/README-DL6uUTPo.js +694 -0
  152. package/storybook-static/assets/README-DSHEoxUa.js +334 -0
  153. package/storybook-static/assets/README-Q-znYcgF.js +338 -0
  154. package/storybook-static/assets/README-qNwFrtLd.js +420 -0
  155. package/storybook-static/assets/{RadioButton-IDzSzftl.js → RadioButton-CbD6ZIHI.js} +1 -1
  156. package/storybook-static/assets/{RadioButton-DCoo55ld.js → RadioButton-DndugDg6.js} +1 -1
  157. package/storybook-static/assets/{RadioButton.stories-BEk1uc2x.js → RadioButton.stories-DLBVXRhH.js} +1 -1
  158. package/storybook-static/assets/{RadioGroup-VSiqFUI1.js → RadioGroup-CtB8u1R5.js} +1 -1
  159. package/storybook-static/assets/{RadioGroup-CbBmuFtC.js → RadioGroup-Cvgo5T_8.js} +1 -1
  160. package/storybook-static/assets/{RadioGroup.stories-DR7Vskyy.js → RadioGroup.stories-L1ZkFYiC.js} +1 -1
  161. package/storybook-static/assets/{Scrollable-CDZ18AWy.js → Scrollable-CGvhxTPd.js} +1 -1
  162. package/storybook-static/assets/{Select-IWQpo0LQ.js → Select-ByBilOog.js} +1 -1
  163. package/storybook-static/assets/{Select-YjugVUKX.js → Select-D4MdId1m.js} +1 -1
  164. package/storybook-static/assets/{Select.stories-4JpnXfaB.js → Select.stories-Dt-IM20Z.js} +1 -1
  165. package/storybook-static/assets/{SerializableSelect-oxnbV26a.js → SerializableSelect-nkDX-4xv.js} +1 -1
  166. package/storybook-static/assets/{SerializableSelect.stories-CjVcuU4t.js → SerializableSelect.stories-BWozK3v0.js} +1 -1
  167. package/storybook-static/assets/{SingleDateTimeInput-DFPke7bv.js → SingleDateTimeInput-BM3iEHnL.js} +1 -1
  168. package/storybook-static/assets/{SingleDateTimeInput-CQ1_ABKA.js → SingleDateTimeInput-DwIACH7q.js} +1 -1
  169. package/storybook-static/assets/{SingleDateTimeInput.stories--Nky_yBh.js → SingleDateTimeInput.stories-GluT9Gra.js} +1 -1
  170. package/storybook-static/assets/{Slider-DGVl0dbV.js → Slider-Cijr3rDj.js} +1 -1
  171. package/storybook-static/assets/{Slider-L7pqW7xG.js → Slider-DZF2K2Ew.js} +1 -1
  172. package/storybook-static/assets/{Slider.stories-DkqUavTk.js → Slider.stories-CKkDEud8.js} +1 -1
  173. package/storybook-static/assets/{SliderInput-B7U8M2UN.js → SliderInput-BpmhOQah.js} +1 -1
  174. package/storybook-static/assets/{SliderInput-Dn5-Dt4w.js → SliderInput-D_DduGKi.js} +1 -1
  175. package/storybook-static/assets/{SliderInput.stories-B9WBQOiA.js → SliderInput.stories-vb6qkP3q.js} +1 -1
  176. package/storybook-static/assets/{Sortable-DxdXjG1a.js → Sortable-CbhgYqYI.js} +1 -1
  177. package/storybook-static/assets/{Sortable.stories-D1rp2l9M.js → Sortable.stories-BGYwnEIs.js} +1 -1
  178. package/storybook-static/assets/{Splitter-yZpkev96.js → Splitter-CIrkYu0w.js} +1 -1
  179. package/storybook-static/assets/{Splitter.stories-C3TCKNhu.js → Splitter.stories-BiWcwOKt.js} +1 -1
  180. package/storybook-static/assets/{StateSelect-CaAFL9Jr.js → StateSelect-PvcnwH-_.js} +1 -1
  181. package/storybook-static/assets/{StateSelect-DZjPblfU.js → StateSelect-ZSH1VqJo.js} +1 -1
  182. package/storybook-static/assets/{StateSelect.stories-D7EwT0sO.js → StateSelect.stories-XIHGksRX.js} +1 -1
  183. package/storybook-static/assets/{StringToggle.stories-DuaWy2LK.js → StringToggle.stories-ZIPWDjST.js} +1 -1
  184. package/storybook-static/assets/{Switch-C0GQ2oXw.js → Switch-Bf1TvVDU.js} +1 -1
  185. package/storybook-static/assets/{Switch-DCNQT47U.js → Switch-DHkppwyI.js} +1 -1
  186. package/storybook-static/assets/{Switch.stories-CAIEfQb6.js → Switch.stories-DflM2SkS.js} +1 -1
  187. package/storybook-static/assets/{Table-Be1uta4G.js → Table-CzJkgWa8.js} +1 -1
  188. package/storybook-static/assets/Table.stories-BaalIcC1.js +207 -0
  189. package/storybook-static/assets/{Tabs-Co7Z92UK.js → Tabs-Cys2Volu.js} +1 -1
  190. package/storybook-static/assets/{Tabs.stories-D84U8aJM.js → Tabs.stories-D_fVcYeD.js} +2 -2
  191. package/storybook-static/assets/{TabsBox-CUpOXGdh.js → TabsBox-B9W9qy2m.js} +1 -1
  192. package/storybook-static/assets/{TagInput-BbLK6uxZ.js → TagInput-CnPuhz64.js} +1 -1
  193. package/storybook-static/assets/{TagInput.stories-D34JCfyM.js → TagInput.stories-CU9IW-n_.js} +1 -1
  194. package/storybook-static/assets/{Text-DtJw3wMU.js → Text-CellD6Ua.js} +1 -1
  195. package/storybook-static/assets/{Textarea-C9MWeqLC.js → Textarea-BIne8oBO.js} +1 -1
  196. package/storybook-static/assets/{Textarea-Cz6RWJdh.js → Textarea-CNJiLJKb.js} +1 -1
  197. package/storybook-static/assets/{Textarea.stories-DqK16XGS.js → Textarea.stories-BfgWsqEp.js} +1 -1
  198. package/storybook-static/assets/{TimePanel-DcZ-UUgO.js → TimePanel-BY8XyZeb.js} +1 -1
  199. package/storybook-static/assets/{TimePanel.stories-BafOmX3I.js → TimePanel.stories-BEqFKJd3.js} +1 -1
  200. package/storybook-static/assets/{Toggle-oYTkCbEJ.js → Toggle-CTmUXxr_.js} +1 -1
  201. package/storybook-static/assets/{Toggle-CgU6o7qM.js → Toggle-Dyiyi7DV.js} +1 -1
  202. package/storybook-static/assets/{Toggle.stories-CbmOBWIu.js → Toggle.stories-DysLQdUc.js} +1 -1
  203. package/storybook-static/assets/{TreeMultiSelect-B4WfXc4Z.js → TreeMultiSelect-CDOGzEjr.js} +1 -1
  204. package/storybook-static/assets/{TreeMultiSelect-DN_CYg-F.js → TreeMultiSelect-DUQQUlTr.js} +1 -1
  205. package/storybook-static/assets/{TreeMultiSelect.stories-DYVj6Rm6.js → TreeMultiSelect.stories-Ca1Ezfej.js} +1 -1
  206. package/storybook-static/assets/{TreePickerBase-8gA_hIXn.js → TreePickerBase-CHdoSW2m.js} +1 -1
  207. package/storybook-static/assets/{TreeSelect-2bjYl5ox.js → TreeSelect-DBpqAEoz.js} +1 -1
  208. package/storybook-static/assets/{TreeSelect-CTtmHF-B.js → TreeSelect-DvmeQO0H.js} +1 -1
  209. package/storybook-static/assets/{TreeSelect.stories-BEAY61pJ.js → TreeSelect.stories-zRpBXhMU.js} +1 -1
  210. package/storybook-static/assets/{TreeTable-B4BaHybr.js → TreeTable-Dpr1hsTN.js} +1 -1
  211. package/storybook-static/assets/{TreeTable.stories-lLwH2Gzl.js → TreeTable.stories-D4mBQSif.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-DFALSYBD.js → async-phnLSjcK.js} +1 -1
  215. package/storybook-static/assets/axe-_9eNhXQJ.js +20 -0
  216. package/storybook-static/assets/{blocks-Dr191OF5.js → blocks-Dx5UE11l.js} +6 -6
  217. package/storybook-static/assets/check-Bn8-yPh2.js +1 -0
  218. package/storybook-static/assets/chevron-down-BZ9pvYFr.js +1 -0
  219. package/storybook-static/assets/chevron-left-DWqaY-om.js +1 -0
  220. package/storybook-static/assets/chevron-right-NPmcbCK0.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-BR_vFDlt.js → chunk-W22LQPXL-BYQdtiug.js} +1 -1
  227. package/storybook-static/assets/{circle-alert-Is-jAV5H.js → circle-alert-CJwtzjGy.js} +1 -1
  228. package/storybook-static/assets/circle-check-DwTtGRlB.js +1 -0
  229. package/storybook-static/assets/{components-DqFIc_sf.js → components-Cs2kpiik.js} +12 -12
  230. package/storybook-static/assets/{contextMenu-CWKJO78E.js → contextMenu-B5c0sszu.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-B61X2hm8.js → download-r-5pPKGQ.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-69N16zFe.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-10USAmEi.js → foundationLib-BIHXPLOP.js} +1 -1
  242. package/storybook-static/assets/{hooks-C8GJ9Cvf.js → hooks-DC_oxxhF.js} +2 -2
  243. package/storybook-static/assets/{iframe-Bu7ux79g.js → iframe-C5-IVaAZ.js} +23 -23
  244. package/storybook-static/assets/iframe-CKeBtmwl.js +1 -0
  245. package/storybook-static/assets/{info-CnuHFdaB.js → info-CNW3iwvW.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-CGxTTNvg.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-CAzdQ6ar.js → odataFetchFn-BS9TBmIl.js} +1 -1
  251. package/storybook-static/assets/{odataStoryFixtures-CX6euJaD.js → odataStoryFixtures-C83OxXaj.js} +2 -2
  252. package/storybook-static/assets/{persisted-BKBboERB.js → persisted-BPpV66dG.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-B0_DoKoo.js +9 -0
  256. package/storybook-static/assets/{react-C21x__mS.js → react-BZJXY1be.js} +1 -1
  257. package/storybook-static/assets/{react-qN2cStNd.js → react-Bl2r1tuC.js} +1 -1
  258. package/storybook-static/assets/{react-CvkUhHUq.js → react-GkniIHEK.js} +1 -1
  259. package/storybook-static/assets/{react-dom-2o2phaJG.js → react-dom-BzEl8usk.js} +1 -1
  260. package/storybook-static/assets/{refresh-cw-ByNiehPJ.js → refresh-cw-BIVSqnGe.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-BGG8WBgX.js → settings-2-9-qrKLUO.js} +1 -1
  263. package/storybook-static/assets/{shim-BUMlC1GG.js → shim-DlYORV19.js} +1 -1
  264. package/storybook-static/assets/{sortable-6C6S-hq-.js → sortable-BTkW6ZiT.js} +1 -1
  265. package/storybook-static/assets/syntaxhighlighter-QEQV7K2Y-Ch82sVZk.js +6 -0
  266. package/storybook-static/assets/{theming-mTWhffkp.js → theming-Cvtrnj7n.js} +1 -1
  267. package/storybook-static/assets/{trash-2-EYPGQg68.js → trash-2-D4oKWLBp.js} +1 -1
  268. package/storybook-static/assets/{treeStoryFixtures-Ci7STpiz.js → treeStoryFixtures-DXHh9GL3.js} +1 -1
  269. package/storybook-static/assets/{triangle-alert-qcSLqRkW.js → triangle-alert-CBOrIa31.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-LQU24QnI.js → useODataSelectBase-B6fc-K7G.js} +1 -1
  274. package/storybook-static/assets/{useODataTreeData-0I485gNf.js → useODataTreeData-BEHsAQxw.js} +1 -1
  275. package/storybook-static/assets/usePickerDefaultFilter-5Q1pAV4d.js +1 -0
  276. package/storybook-static/assets/{usePickerQuery-B0a5nSeM.js → usePickerQuery-B-2MddV7.js} +1 -1
  277. package/storybook-static/assets/{usePickerTriggerController-DMRm063p.js → usePickerTriggerController-CyBsILzT.js} +1 -1
  278. package/storybook-static/assets/{utils-DUM89eIj.js → utils-tg3ibbzi.js} +1 -1
  279. package/storybook-static/assets/x-BZ_zKXO5.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-Cb3D2Hts.js.map +0 -1
  289. package/storybook-static/assets/FormattedTableCell-CGREP0wz.js +0 -15
  290. package/storybook-static/assets/Option-CwetjUM8.css +0 -1
  291. package/storybook-static/assets/Table.stories-ByUJbaZl.js +0 -207
  292. package/storybook-static/assets/axe-Bq-2xuso.js +0 -20
  293. package/storybook-static/assets/browserAndReact-BDUP5jYY.js +0 -70
  294. package/storybook-static/assets/check-D56EYchd.js +0 -1
  295. package/storybook-static/assets/chevron-down-CzEzTRkp.js +0 -1
  296. package/storybook-static/assets/chevron-left-Dl69q13G.js +0 -1
  297. package/storybook-static/assets/chevron-right-CJNjsAmt.js +0 -1
  298. package/storybook-static/assets/circle-check-6tyIZ3Kl.js +0 -1
  299. package/storybook-static/assets/filesAndExcel-Bs3kqlsI.js +0 -92
  300. package/storybook-static/assets/floating-ui.react-bhmHzD7p.js +0 -1
  301. package/storybook-static/assets/formatter-L2YTDE3Q-x0raBH34.js +0 -1
  302. package/storybook-static/assets/formatters-NGPd1d-h.js +0 -202
  303. package/storybook-static/assets/formatters-runtime-CCfGWt-f.js +0 -329
  304. package/storybook-static/assets/iframe-BEvUkaBB.js +0 -1
  305. package/storybook-static/assets/odata-BslPtJet.js +0 -1
  306. package/storybook-static/assets/odataAndTables-Cyl3UJOz.js +0 -77
  307. package/storybook-static/assets/pureUtilities-Bj6GraNW.js +0 -30
  308. package/storybook-static/assets/react-18-CgkwBzTo.js +0 -9
  309. package/storybook-static/assets/react-CjYyitxm.js +0 -9
  310. package/storybook-static/assets/runtimeAndState-mq-BaPyx.js +0 -93
  311. package/storybook-static/assets/syntaxhighlighter-T5IQBZDB-B5rzfkHU.js +0 -6
  312. package/storybook-static/assets/usePickerDefaultFilter-Be420WNF.js +0 -1
  313. package/storybook-static/assets/x-FZLq51ZK.js +0 -1
@@ -0,0 +1,1309 @@
1
+ import{n as e}from"./rolldown-runtime-DkW27tQK.js";import{n as t}from"./iframe-C5-IVaAZ.js";import{i as n,r}from"./react-Bl2r1tuC.js";import{l as i,m as a}from"./blocks-Dx5UE11l.js";function o(e){let t={a:`a`,code:`code`,h1:`h1`,h2:`h2`,h3:`h3`,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/File`}),`
2
+ `,(0,c.jsxs)(t.h1,{id:`работа-с-пользовательскими-файлами-через-ryuzaki13react-foundation-libfile`,children:[`Работа с пользовательскими файлами через `,(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/file`})]}),`
3
+ `,(0,c.jsxs)(t.p,{children:[`Модуль `,(0,c.jsx)(t.code,{children:`file`}),` содержит browser helpers для четырёх связанных задач:`]}),`
4
+ `,(0,c.jsxs)(t.ul,{children:[`
5
+ `,(0,c.jsxs)(t.li,{children:[`прочитать выбранный пользователем `,(0,c.jsx)(t.code,{children:`File`}),` как data URL или `,(0,c.jsx)(t.code,{children:`ArrayBuffer`}),`;`]}),`
6
+ `,(0,c.jsx)(t.li,{children:`проверить MIME-тип и размер файла перед чтением;`}),`
7
+ `,(0,c.jsx)(t.li,{children:`прочитать изображение и получить его естественные размеры;`}),`
8
+ `,(0,c.jsxs)(t.li,{children:[`определить MIME по расширению и безопаснее сформировать HTTP-заголовок `,(0,c.jsx)(t.code,{children:`Content-Disposition`}),` с Unicode-именем файла.`]}),`
9
+ `]}),`
10
+ `,(0,c.jsxs)(t.p,{children:[`Модуль является низкоуровневым foundation-слоем. Он не рисует `,(0,c.jsx)(t.code,{children:`<input type="file">`}),`, не отправляет файл на backend, не показывает preview или progress, не проверяет реальное содержимое файла и не заменяет серверную валидацию.`]}),`
11
+ `,(0,c.jsx)(t.p,{children:`README рассчитан на разработчика, который использует опубликованный пакет без доступа к исходному коду. Поэтому ниже отдельно объясняются базовые понятия JavaScript/TypeScript, точный порядок проверок, структура результатов, все публичные функции и типы, фактические классы ошибок, ограничения безопасности и известные особенности текущей реализации.`}),`
12
+ `,(0,c.jsx)(t.h2,{id:`содержание`,children:`Содержание`}),`
13
+ `,(0,c.jsxs)(t.ul,{children:[`
14
+ `,(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:`Назначение и границы модуля`})}),`
15
+ `,(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:`Установка и импорт`})}),`
16
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#runtime-%D0%B8-browser-boundary`,children:`Runtime и browser boundary`})}),`
17
+ `,(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:`Минимальные понятия`})}),`
18
+ `,(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`})}),`
19
+ `,(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:`Быстрый старт`})}),`
20
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#readfile`,children:(0,c.jsx)(t.code,{children:`readFile`})})}),`
21
+ `,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#%D1%80%D0%B5%D0%B6%D0%B8%D0%BC-data-url`,children:[`Режим `,(0,c.jsx)(t.code,{children:`data-url`})]})}),`
22
+ `,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#%D1%80%D0%B5%D0%B6%D0%B8%D0%BC-array-buffer`,children:[`Режим `,(0,c.jsx)(t.code,{children:`array-buffer`})]})}),`
23
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0-%D1%82%D0%B8%D0%BF%D0%B0-%D0%B8-%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%D0%B0`,children:`Проверка типа и размера`})}),`
24
+ `,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B0-%D1%87%D0%B5%D1%80%D0%B5%D0%B7-abortsignal`,children:[`Отмена через `,(0,c.jsx)(t.code,{children:`AbortSignal`})]})}),`
25
+ `,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B8-readfile`,children:[`Ошибки `,(0,c.jsx)(t.code,{children:`readFile`})]})}),`
26
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#readimagefile`,children:(0,c.jsx)(t.code,{children:`readImageFile`})})}),`
27
+ `,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B8-readimagefile`,children:[`Ошибки `,(0,c.jsx)(t.code,{children:`readImageFile`})]})}),`
28
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#resolvemimetypebyextension`,children:(0,c.jsx)(t.code,{children:`resolveMimeTypeByExtension`})})}),`
29
+ `,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#helpers-%D0%B4%D0%BB%D1%8F-content-disposition`,children:[`Helpers для `,(0,c.jsx)(t.code,{children:`Content-Disposition`})]})}),`
30
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BD%D0%B8%D0%B7%D0%BA%D0%BE%D1%83%D1%80%D0%BE%D0%B2%D0%BD%D0%B5%D0%B2%D1%8B%D0%B5-helpers`,children:`Низкоуровневые helpers`})}),`
31
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B3%D0%BE%D1%82%D0%BE%D0%B2%D1%8B%D0%B5-%D1%80%D0%B5%D1%86%D0%B5%D0%BF%D1%82%D1%8B`,children:`Готовые рецепты`})}),`
32
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE%D1%81%D1%82%D1%8C`,children:`Безопасность`})}),`
33
+ `,(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-%D0%B8-%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D1%8C`,children:`Производительность и память`})}),`
34
+ `,(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:`Тестирование`})}),`
35
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B8%D0%B7%D0%B2%D0%B5%D1%81%D1%82%D0%BD%D1%8B%D0%B5-%D0%BE%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D1%8F`,children:`Известные ограничения`})}),`
36
+ `,(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`})}),`
37
+ `,(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:`Вопросы и ответы`})}),`
38
+ `]}),`
39
+ `,(0,c.jsx)(t.h2,{id:`назначение-и-границы-модуля`,children:`Назначение и границы модуля`}),`
40
+ `,(0,c.jsxs)(t.h3,{id:`что-делает-file`,children:[`Что делает `,(0,c.jsx)(t.code,{children:`file`})]}),`
41
+ `,(0,c.jsxs)(t.ul,{children:[`
42
+ `,(0,c.jsxs)(t.li,{children:[`запускает браузерный `,(0,c.jsx)(t.code,{children:`FileReader`}),`;`]}),`
43
+ `,(0,c.jsx)(t.li,{children:`возвращает строго типизированный результат чтения;`}),`
44
+ `,(0,c.jsxs)(t.li,{children:[`сохраняет исходный объект `,(0,c.jsx)(t.code,{children:`File`}),` в результате;`]}),`
45
+ `,(0,c.jsx)(t.li,{children:`создаёт snapshot основных metadata файла;`}),`
46
+ `,(0,c.jsx)(t.li,{children:`ограничивает допустимые MIME и максимальный размер;`}),`
47
+ `,(0,c.jsxs)(t.li,{children:[`связывает `,(0,c.jsx)(t.code,{children:`AbortSignal`}),` с `,(0,c.jsx)(t.code,{children:`FileReader.abort()`}),`;`]}),`
48
+ `,(0,c.jsxs)(t.li,{children:[`проверяет, что файл объявлен как `,(0,c.jsx)(t.code,{children:`image/*`}),`;`]}),`
49
+ `,(0,c.jsxs)(t.li,{children:[`получает `,(0,c.jsx)(t.code,{children:`naturalWidth`}),` и `,(0,c.jsx)(t.code,{children:`naturalHeight`}),` изображения в режиме data URL;`]}),`
50
+ `,(0,c.jsx)(t.li,{children:`сопоставляет ограниченный список расширений с MIME;`}),`
51
+ `,(0,c.jsxs)(t.li,{children:[`формирует `,(0,c.jsx)(t.code,{children:`Content-Disposition`}),` одновременно с ASCII fallback и UTF-8 `,(0,c.jsx)(t.code,{children:`filename*`}),`.`]}),`
52
+ `]}),`
53
+ `,(0,c.jsx)(t.h3,{id:`чего-модуль-не-делает`,children:`Чего модуль не делает`}),`
54
+ `,(0,c.jsxs)(t.ul,{children:[`
55
+ `,(0,c.jsx)(t.li,{children:`не создаёт UI выбора файла;`}),`
56
+ `,(0,c.jsx)(t.li,{children:`не управляет React state;`}),`
57
+ `,(0,c.jsx)(t.li,{children:`не выполняет HTTP/OData-запрос;`}),`
58
+ `,(0,c.jsx)(t.li,{children:`не загружает файл на сервер;`}),`
59
+ `,(0,c.jsxs)(t.li,{children:[`не скачивает `,(0,c.jsx)(t.code,{children:`Blob`}),` в браузере;`]}),`
60
+ `,(0,c.jsxs)(t.li,{children:[`не превращает произвольный base64 в `,(0,c.jsx)(t.code,{children:`Blob`}),`;`]}),`
61
+ `,(0,c.jsx)(t.li,{children:`не читает файл потоково и не сообщает progress;`}),`
62
+ `,(0,c.jsx)(t.li,{children:`не проверяет magic bytes, структуру PDF, ZIP, изображения или документа;`}),`
63
+ `,(0,c.jsx)(t.li,{children:`не обнаруживает вирусы и вредоносное содержимое;`}),`
64
+ `,(0,c.jsxs)(t.li,{children:[`не гарантирует, что `,(0,c.jsx)(t.code,{children:`File.type`}),` соответствует байтам;`]}),`
65
+ `,(0,c.jsx)(t.li,{children:`не сжимает и не изменяет изображение;`}),`
66
+ `,(0,c.jsx)(t.li,{children:`не ограничивает ширину, высоту или количество декодированных пикселей;`}),`
67
+ `,(0,c.jsx)(t.li,{children:`не удаляет потенциально опасное содержимое из SVG;`}),`
68
+ `,(0,c.jsx)(t.li,{children:`не нормализует путь для записи файла на диск;`}),`
69
+ `,(0,c.jsxs)(t.li,{children:[`не создаёт HTTP `,(0,c.jsx)(t.code,{children:`Response`}),` и не устанавливает заголовки автоматически.`]}),`
70
+ `]}),`
71
+ `,(0,c.jsx)(t.h3,{id:`соседние-модули`,children:`Соседние модули`}),`
72
+ `,(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:[`Прочитать выбранный пользователем `,(0,c.jsx)(t.code,{children:`File`})]}),(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:[`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.jsxs)(t.td,{children:[`Скачать готовый `,(0,c.jsx)(t.code,{children:`Blob`}),`, JSON или object URL`]}),(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:`Скопировать обычный текст в clipboard`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/copy`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Прочитать файл через готовый React UI`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-ui/input-file`}),` или `,(0,c.jsx)(t.code,{children:`drop-zone`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Выбрать изображение через готовый React UI`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-ui/input-image`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Проверить формат по реальному содержимому`}),(0,c.jsx)(t.td,{children:`Специализированный parser на доверенной boundary`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Выполнить окончательную проверку перед сохранением`}),(0,c.jsx)(t.td,{children:`Backend`})]})]})]}),`
73
+ `,(0,c.jsxs)(t.p,{children:[`Если нужен только обычный upload через `,(0,c.jsx)(t.code,{children:`multipart/form-data`}),`, браузерный `,(0,c.jsx)(t.code,{children:`File`}),` можно положить в `,(0,c.jsx)(t.code,{children:`FormData`}),` напрямую. Предварительное чтение всего файла через `,(0,c.jsx)(t.code,{children:`readFile`}),` для этого не обязательно.`]}),`
74
+ `,(0,c.jsx)(t.h2,{id:`установка-и-импорт`,children:`Установка и импорт`}),`
75
+ `,(0,c.jsx)(t.p,{children:`Установите пакет:`}),`
76
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-bash`,children:`npm install @ryuzaki13/react-foundation-lib
77
+ `})}),`
78
+ `,(0,c.jsxs)(t.p,{children:[`Импортируйте API только из опубликованного subpath `,(0,c.jsx)(t.code,{children:`/file`}),`:`]}),`
79
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { createContentDispositionHeader, readFile, readImageFile, resolveMimeTypeByExtension } from "@ryuzaki13/react-foundation-lib/file";
80
+
81
+ import type { ImageMime, ReadFileOptions, ReadFileResult, ReadImageResult } from "@ryuzaki13/react-foundation-lib/file";
82
+ `})}),`
83
+ `,(0,c.jsx)(t.p,{children:`Корневой импорт пакета не поддерживается:`}),`
84
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Неправильно.
85
+ import { readFile } from "@ryuzaki13/react-foundation-lib";
86
+
87
+ // Правильно.
88
+ import { readFile } from "@ryuzaki13/react-foundation-lib/file";
89
+ `})}),`
90
+ `,(0,c.jsxs)(t.p,{children:[`Не импортируйте внутренние файлы `,(0,c.jsx)(t.code,{children:`read-file/readFile`}),` или `,(0,c.jsx)(t.code,{children:`image/readImageFile`}),`. Они не являются отдельными опубликованными entrypoints:`]}),`
91
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Неправильно: приватный путь исходников.
92
+ import { readFile } from "@ryuzaki13/react-foundation-lib/src/file/read-file/readFile";
93
+ `})}),`
94
+ `,(0,c.jsx)(t.p,{children:`Модуль поставляется как ESM.`}),`
95
+ `,(0,c.jsx)(t.h3,{id:`нужен-ли-react`,children:`Нужен ли React`}),`
96
+ `,(0,c.jsxs)(t.p,{children:[`Сам entrypoint `,(0,c.jsx)(t.code,{children:`/file`}),` не использует React и подходит обычному browser TypeScript/JavaScript-коду. Однако пакет в целом объявляет React peer dependency. В React-приложении она обычно уже предоставлена host-приложением.`]}),`
97
+ `,(0,c.jsx)(t.h2,{id:`runtime-и-browser-boundary`,children:`Runtime и browser boundary`}),`
98
+ `,(0,c.jsx)(t.p,{children:`Основные функции чтения используют Web APIs:`}),`
99
+ `,(0,c.jsxs)(t.ul,{children:[`
100
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`File`}),`;`]}),`
101
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`FileReader`}),`;`]}),`
102
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`Image`}),`;`]}),`
103
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`AbortController`}),` и `,(0,c.jsx)(t.code,{children:`AbortSignal`}),`;`]}),`
104
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`ArrayBuffer`}),`;`]}),`
105
+ `,(0,c.jsx)(t.li,{children:`data URL.`}),`
106
+ `]}),`
107
+ `,(0,c.jsxs)(t.p,{children:[`Поэтому `,(0,c.jsx)(t.code,{children:`readFile`}),` и `,(0,c.jsx)(t.code,{children:`readImageFile`}),` предназначены прежде всего для браузера. Обычная Node.js-среда, server action, SSR loader или тестовый runner могут не иметь полного `,(0,c.jsx)(t.code,{children:`FileReader`}),`/`,(0,c.jsx)(t.code,{children:`Image`}),` API.`]}),`
108
+ `,(0,c.jsx)(t.p,{children:`Чистые helpers не зависят от DOM:`}),`
109
+ `,(0,c.jsxs)(t.ul,{children:[`
110
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`resolveMimeTypeByExtension`}),`;`]}),`
111
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`sanitizeFileName`}),`;`]}),`
112
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`createAsciiFilenameFallback`}),`;`]}),`
113
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`encodeFilenameRFC5987`}),`;`]}),`
114
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`createContentDispositionHeader`}),`.`]}),`
115
+ `]}),`
116
+ `,(0,c.jsxs)(t.p,{children:[`Их можно вызывать на серверной boundary, если runtime предоставляет стандартные строковые функции и `,(0,c.jsx)(t.code,{children:`encodeURIComponent`}),`.`]}),`
117
+ `,(0,c.jsx)(t.p,{children:`Важно различать:`}),`
118
+ `,(0,c.jsxs)(t.ul,{children:[`
119
+ `,(0,c.jsx)(t.li,{children:`импорт модуля;`}),`
120
+ `,(0,c.jsx)(t.li,{children:`фактический вызов browser-функции.`}),`
121
+ `]}),`
122
+ `,(0,c.jsx)(t.p,{children:`Сам импорт не начинает чтение файла. Browser API используется только при вызове соответствующей функции.`}),`
123
+ `,(0,c.jsx)(t.h2,{id:`минимальные-понятия`,children:`Минимальные понятия`}),`
124
+ `,(0,c.jsxs)(t.h3,{id:`что-такое-file`,children:[`Что такое `,(0,c.jsx)(t.code,{children:`File`})]}),`
125
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`File`}),` — браузерный объект, который представляет выбранный или созданный файл. Он наследуется от `,(0,c.jsx)(t.code,{children:`Blob`}),` и содержит байты плюс metadata:`]}),`
126
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const file = new File(["Привет"], "hello.txt", {
127
+ type: "text/plain",
128
+ lastModified: Date.now()
129
+ });
130
+ `})}),`
131
+ `,(0,c.jsx)(t.p,{children:`Основные поля:`}),`
132
+ `,(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:`file.name`})}),(0,c.jsxs)(t.td,{children:[`Имя, например `,(0,c.jsx)(t.code,{children:`report.pdf`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`file.type`})}),(0,c.jsxs)(t.td,{children:[`Заявленный MIME, например `,(0,c.jsx)(t.code,{children:`application/pdf`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`file.size`})}),(0,c.jsx)(t.td,{children:`Размер в байтах`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`file.lastModified`})}),(0,c.jsx)(t.td,{children:`Время изменения в миллисекундах от Unix epoch`})]})]})]}),`
133
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`File`}),` обычно получают из `,(0,c.jsx)(t.code,{children:`<input type="file">`}),`:`]}),`
134
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const input = document.querySelector<HTMLInputElement>("#document");
135
+
136
+ input?.addEventListener("change", () => {
137
+ const file = input.files?.[0];
138
+
139
+ if (!file) {
140
+ return;
141
+ }
142
+
143
+ console.log(file.name, file.type, file.size);
144
+ });
145
+ `})}),`
146
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`files?.[0]`}),` может быть `,(0,c.jsx)(t.code,{children:`undefined`}),`, если пользователь закрыл диалог или очистил поле. Проверяйте это до вызова `,(0,c.jsx)(t.code,{children:`readFile`}),`.`]}),`
147
+ `,(0,c.jsx)(t.h3,{id:`что-такое-mime-тип`,children:`Что такое MIME-тип`}),`
148
+ `,(0,c.jsx)(t.p,{children:`MIME — строковая metadata о предполагаемом типе содержимого:`}),`
149
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`MIME`}),(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:`application/pdf`})}),(0,c.jsx)(t.td,{children:`PDF`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/png`})}),(0,c.jsx)(t.td,{children:`PNG`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/jpeg`})}),(0,c.jsx)(t.td,{children:`JPEG`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`text/plain`})}),(0,c.jsx)(t.td,{children:`Обычный текст`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/octet-stream`})}),(0,c.jsx)(t.td,{children:`Неизвестные байты`})]})]})]}),`
150
+ `,(0,c.jsxs)(t.p,{children:[`У MIME две части, разделённые `,(0,c.jsx)(t.code,{children:`/`}),`:`]}),`
151
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`image/png
152
+ ^^^^^ ^^^
153
+ type subtype
154
+ `})}),`
155
+ `,(0,c.jsxs)(t.p,{children:[`Значение `,(0,c.jsx)(t.code,{children:`File.type`}),` нельзя считать доказательством формата. Оно может быть пустым, ошибочным или подделанным. Клиентская MIME-проверка улучшает UX, но не является security boundary.`]}),`
156
+ `,(0,c.jsx)(t.h3,{id:`mime-и-расширение--не-одно-и-то-же`,children:`MIME и расширение — не одно и то же`}),`
157
+ `,(0,c.jsxs)(t.p,{children:[`Для файла `,(0,c.jsx)(t.code,{children:`photo.jpeg`}),`:`]}),`
158
+ `,(0,c.jsxs)(t.ul,{children:[`
159
+ `,(0,c.jsxs)(t.li,{children:[`расширение — `,(0,c.jsx)(t.code,{children:`jpeg`}),`;`]}),`
160
+ `,(0,c.jsxs)(t.li,{children:[`MIME — `,(0,c.jsx)(t.code,{children:`image/jpeg`}),`.`]}),`
161
+ `]}),`
162
+ `,(0,c.jsxs)(t.p,{children:[`Переименование `,(0,c.jsx)(t.code,{children:`malware.exe`}),` в `,(0,c.jsx)(t.code,{children:`photo.jpeg`}),` не превращает содержимое в изображение. Окончательное решение должен принимать backend или специализированный parser.`]}),`
163
+ `,(0,c.jsxs)(t.h3,{id:`что-такое-filereader`,children:[`Что такое `,(0,c.jsx)(t.code,{children:`FileReader`})]}),`
164
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`FileReader`}),` — browser API, который асинхронно загружает весь `,(0,c.jsx)(t.code,{children:`Blob`}),`/`,(0,c.jsx)(t.code,{children:`File`}),` в память. Этот модуль скрывает обработчики `,(0,c.jsx)(t.code,{children:`load`}),`, `,(0,c.jsx)(t.code,{children:`error`}),` и `,(0,c.jsx)(t.code,{children:`abort`}),` за обычным `,(0,c.jsx)(t.code,{children:`Promise`}),`.`]}),`
165
+ `,(0,c.jsx)(t.p,{children:`Вместо ручного кода:`}),`
166
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const reader = new FileReader();
167
+
168
+ reader.onload = () => {
169
+ console.log(reader.result);
170
+ };
171
+
172
+ reader.readAsArrayBuffer(file);
173
+ `})}),`
174
+ `,(0,c.jsx)(t.p,{children:`используется:`}),`
175
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file, { mode: "array-buffer" });
176
+ console.log(result.buffer);
177
+ `})}),`
178
+ `,(0,c.jsxs)(t.h3,{id:`что-такое-promise-async-и-await`,children:[`Что такое `,(0,c.jsx)(t.code,{children:`Promise`}),`, `,(0,c.jsx)(t.code,{children:`async`}),` и `,(0,c.jsx)(t.code,{children:`await`})]}),`
179
+ `,(0,c.jsxs)(t.p,{children:[`Чтение не завершается мгновенно. `,(0,c.jsx)(t.code,{children:`readFile`}),` возвращает `,(0,c.jsx)(t.code,{children:`Promise`}),`:`]}),`
180
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const promise = readFile(file);
181
+ `})}),`
182
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`await`}),` ожидает результат внутри `,(0,c.jsx)(t.code,{children:`async`}),`-функции:`]}),`
183
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`async function handleFile(file: File) {
184
+ try {
185
+ const result = await readFile(file);
186
+ console.log(result.dataUrl);
187
+ } catch (error) {
188
+ console.error(error);
189
+ }
190
+ }
191
+ `})}),`
192
+ `,(0,c.jsxs)(t.p,{children:[`Без `,(0,c.jsx)(t.code,{children:`await`}),` переменная содержит не результат, а Promise:`]}),`
193
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Неправильно.
194
+ const result = readFile(file);
195
+ // result.dataUrl недоступен: result — Promise.
196
+ `})}),`
197
+ `,(0,c.jsx)(t.h3,{id:`что-такое-data-url`,children:`Что такое data URL`}),`
198
+ `,(0,c.jsx)(t.p,{children:`Data URL хранит metadata и данные в одной строке:`}),`
199
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`data:image/png;base64,iVBORw0KGgoAAA...
200
+ `})}),`
201
+ `,(0,c.jsxs)(t.p,{children:[`Его удобно передать в `,(0,c.jsx)(t.code,{children:`src`}),` изображения:`]}),`
202
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`imageElement.src = result.dataUrl;
203
+ `})}),`
204
+ `,(0,c.jsxs)(t.p,{children:[`Base64 увеличивает текстовое представление байтов примерно на треть. Для больших файлов data URL расходует заметно больше памяти, чем исходный `,(0,c.jsx)(t.code,{children:`File`}),`.`]}),`
205
+ `,(0,c.jsxs)(t.h3,{id:`что-такое-arraybuffer`,children:[`Что такое `,(0,c.jsx)(t.code,{children:`ArrayBuffer`})]}),`
206
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`ArrayBuffer`}),` — непрерывный участок памяти с байтами. Сам буфер не предоставляет удобного доступа к отдельным байтам; обычно создают typed view:`]}),`
207
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file, { mode: "array-buffer" });
208
+ const bytes = new Uint8Array(result.buffer);
209
+
210
+ console.log(bytes[0]);
211
+ `})}),`
212
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`ArrayBuffer`}),` подходит бинарным parsers, Web APIs и transport-коду, которому нужны именно байты.`]}),`
213
+ `,(0,c.jsx)(t.h3,{id:`что-такое-discriminated-union`,children:`Что такое discriminated union`}),`
214
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`ReadFileResult`}),` может иметь одну из двух форм. Поле `,(0,c.jsx)(t.code,{children:`mode`}),` является discriminator:`]}),`
215
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function consume(result: ReadFileResult) {
216
+ if (result.mode === "data-url") {
217
+ console.log(result.dataUrl);
218
+ return;
219
+ }
220
+
221
+ console.log(result.buffer);
222
+ }
223
+ `})}),`
224
+ `,(0,c.jsxs)(t.p,{children:[`После проверки `,(0,c.jsx)(t.code,{children:`mode`}),` TypeScript понимает, какое поле доступно. Не обращайтесь к `,(0,c.jsx)(t.code,{children:`dataUrl`}),` и `,(0,c.jsx)(t.code,{children:`buffer`}),` одновременно.`]}),`
225
+ `,(0,c.jsx)(t.h3,{id:`размеры-в-байтах`,children:`Размеры в байтах`}),`
226
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`maxBytes`}),` задаётся в байтах:`]}),`
227
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const ONE_MEBIBYTE = 1024 * 1024;
228
+ const FIVE_MEBIBYTES = 5 * ONE_MEBIBYTE;
229
+ `})}),`
230
+ `,(0,c.jsx)(t.p,{children:`Для ограничения около 5 MiB:`}),`
231
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await readFile(file, { maxBytes: 5 * 1024 * 1024 });
232
+ `})}),`
233
+ `,(0,c.jsxs)(t.p,{children:[`Проверка сравнивает `,(0,c.jsx)(t.code,{children:`file.size > maxBytes`}),`. Файл размером ровно `,(0,c.jsx)(t.code,{children:`maxBytes`}),` разрешён.`]}),`
234
+ `,(0,c.jsx)(t.h2,{id:`как-выбрать-api`,children:`Как выбрать API`}),`
235
+ `,(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.jsxs)(t.td,{children:[`Показать маленькое изображение через `,(0,c.jsx)(t.code,{children:`<img src>`})]}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`readImageFile`}),`, режим `,(0,c.jsx)(t.code,{children:`data-url`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Узнать ширину и высоту изображения`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`readImageFile`}),`, режим `,(0,c.jsx)(t.code,{children:`data-url`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Передать байты parser/worker/криптографическому API`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`readFile`}),`, режим `,(0,c.jsx)(t.code,{children:`array-buffer`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Сохранить выбранный файл в `,(0,c.jsx)(t.code,{children:`FormData`})]}),(0,c.jsxs)(t.td,{children:[`Использовать исходный `,(0,c.jsx)(t.code,{children:`File`}),` напрямую`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Ограничить точные MIME`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`allowedMime`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Ограничить размер`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`maxBytes`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Отменить активное чтение`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`AbortController`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Получить MIME по известному расширению`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resolveMimeTypeByExtension`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Сформировать filename в HTTP `,(0,c.jsx)(t.code,{children:`Content-Disposition`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createContentDispositionHeader`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Превратить уже полученный base64 в `,(0,c.jsx)(t.code,{children:`Blob`})]}),(0,c.jsxs)(t.td,{children:[`Соседний модуль `,(0,c.jsx)(t.code,{children:`/binary`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Скачать уже созданный `,(0,c.jsx)(t.code,{children:`Blob`})]}),(0,c.jsxs)(t.td,{children:[`Соседний модуль `,(0,c.jsx)(t.code,{children:`/dom`})]})]})]})]}),`
236
+ `,(0,c.jsx)(t.p,{children:`Практический default:`}),`
237
+ `,(0,c.jsxs)(t.ul,{children:[`
238
+ `,(0,c.jsxs)(t.li,{children:[`для обычных документов предпочитайте `,(0,c.jsx)(t.code,{children:`array-buffer`}),`, если байты действительно нужны;`]}),`
239
+ `,(0,c.jsxs)(t.li,{children:[`для preview изображения используйте `,(0,c.jsx)(t.code,{children:`readImageFile`}),` с небольшим `,(0,c.jsx)(t.code,{children:`maxBytes`}),` и явным `,(0,c.jsx)(t.code,{children:`allowedMime`}),`;`]}),`
240
+ `,(0,c.jsx)(t.li,{children:`для обычного multipart upload вообще не читайте файл заранее;`}),`
241
+ `,(0,c.jsxs)(t.li,{children:[`для проверки типа предпочитайте `,(0,c.jsx)(t.code,{children:`allowedMime`}),`, потому что поведение `,(0,c.jsx)(t.code,{children:`accept`}),` внутри `,(0,c.jsx)(t.code,{children:`readFile`}),` не совпадает с HTML `,(0,c.jsx)(t.code,{children:`accept`}),`.`]}),`
242
+ `]}),`
243
+ `,(0,c.jsx)(t.h2,{id:`быстрый-старт`,children:`Быстрый старт`}),`
244
+ `,(0,c.jsxs)(t.h3,{id:`прочитать-один-pdf-как-arraybuffer`,children:[`Прочитать один PDF как `,(0,c.jsx)(t.code,{children:`ArrayBuffer`})]}),`
245
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { ReadFileError, readFile } from "@ryuzaki13/react-foundation-lib/file";
246
+
247
+ const input = document.querySelector<HTMLInputElement>("#pdf-input");
248
+
249
+ input?.addEventListener("change", async () => {
250
+ const file = input.files?.[0];
251
+
252
+ if (!file) {
253
+ return;
254
+ }
255
+
256
+ try {
257
+ const result = await readFile(file, {
258
+ mode: "array-buffer",
259
+ allowedMime: ["application/pdf"],
260
+ maxBytes: 10 * 1024 * 1024
261
+ });
262
+
263
+ console.log(result.meta.name);
264
+ console.log(result.buffer.byteLength);
265
+ } catch (error) {
266
+ if (error instanceof ReadFileError) {
267
+ console.error(error.code, error.message);
268
+ return;
269
+ }
270
+
271
+ throw error;
272
+ }
273
+ });
274
+ `})}),`
275
+ `,(0,c.jsx)(t.p,{children:`HTML:`}),`
276
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-html`,children:`<input id="pdf-input" type="file" accept="application/pdf,.pdf" />
277
+ `})}),`
278
+ `,(0,c.jsxs)(t.p,{children:[`HTML `,(0,c.jsx)(t.code,{children:`accept`}),` помогает фильтровать диалог выбора. `,(0,c.jsx)(t.code,{children:`allowedMime`}),` повторно проверяет выбранный объект перед чтением. Ни одна из этих проверок не заменяет backend.`]}),`
279
+ `,(0,c.jsx)(t.h3,{id:`прочитать-изображение-для-preview`,children:`Прочитать изображение для preview`}),`
280
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { readImageFile } from "@ryuzaki13/react-foundation-lib/file";
281
+
282
+ async function createPreview(file: File, image: HTMLImageElement) {
283
+ const result = await readImageFile(file, {
284
+ allowedMime: ["image/jpeg", "image/png", "image/webp"],
285
+ maxBytes: 5 * 1024 * 1024
286
+ });
287
+
288
+ image.src = result.dataUrl;
289
+
290
+ if (result.dimensions) {
291
+ console.log(\`\${result.dimensions.width} × \${result.dimensions.height}\`);
292
+ }
293
+ }
294
+ `})}),`
295
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`dimensions`}),` отмечено в публичном типе как optional, поэтому безопасный consumer проверяет его наличие, хотя текущая успешная реализация data URL возвращает объект размеров.`]}),`
296
+ `,(0,c.jsxs)(t.h3,{id:`сформировать-content-disposition`,children:[`Сформировать `,(0,c.jsx)(t.code,{children:`Content-Disposition`})]}),`
297
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { createContentDispositionHeader } from "@ryuzaki13/react-foundation-lib/file";
298
+
299
+ const contentDisposition = createContentDispositionHeader({
300
+ disposition: "attachment",
301
+ filename: "Отчёт за август.pdf",
302
+ asciiFallback: "report-august.pdf"
303
+ });
304
+
305
+ console.log(contentDisposition);
306
+ `})}),`
307
+ `,(0,c.jsx)(t.p,{children:`Результат содержит два параметра:`}),`
308
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`attachment; filename="_____ __ ______.pdf"; filename*=UTF-8''%D0%9E%D1%82...
309
+ `})}),`
310
+ `,(0,c.jsxs)(t.p,{children:[`Точное количество `,(0,c.jsx)(t.code,{children:`_`}),` зависит от Unicode-символов. Важно: `,(0,c.jsx)(t.code,{children:`asciiFallback`}),` не переопределяет ASCII-вариант непустого Unicode-имени. Текущая реализация использует его только тогда, когда имя после первичной обработки пустое либо ASCII-результат оказался пустым.`]}),`
311
+ `,(0,c.jsx)(t.h2,{id:`readfile`,children:(0,c.jsx)(t.code,{children:`readFile`})}),`
312
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`readFile`}),` применяет выбранные ограничения, читает файл через `,(0,c.jsx)(t.code,{children:`FileReader`}),` и возвращает исходный `,(0,c.jsx)(t.code,{children:`File`}),`, metadata и представление данных выбранного режима. Синтаксические assert helpers автоматически не вызываются.`]}),`
313
+ `,(0,c.jsx)(t.h3,{id:`сигнатуры`,children:`Сигнатуры`}),`
314
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function readFile(file: File, opts?: Omit<ReadFileOptions, "mode"> & { mode?: "data-url" }): Promise<ReadFileAsDataUrlResult>;
315
+
316
+ function readFile(file: File, opts: Omit<ReadFileOptions, "mode"> & { mode: "array-buffer" }): Promise<ReadFileAsArrayBufferResult>;
317
+ `})}),`
318
+ `,(0,c.jsxs)(t.p,{children:[`Перегрузки связывают литеральный `,(0,c.jsx)(t.code,{children:`mode`}),` с точным типом результата:`]}),`
319
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const dataResult = await readFile(file);
320
+ dataResult.dataUrl;
321
+
322
+ const bufferResult = await readFile(file, { mode: "array-buffer" });
323
+ bufferResult.buffer;
324
+ `})}),`
325
+ `,(0,c.jsx)(t.h3,{id:`аргументы`,children:`Аргументы`}),`
326
+ `,(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:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`file`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`File`})}),(0,c.jsx)(t.td,{children:`Обязательный файл для проверки и чтения`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`opts`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileOptions`})}),(0,c.jsxs)(t.td,{children:[`Необязательные режим, ограничения и `,(0,c.jsx)(t.code,{children:`AbortSignal`})]})]})]})]}),`
327
+ `,(0,c.jsxs)(t.p,{children:[`Передавайте реальный `,(0,c.jsx)(t.code,{children:`File`}),`. Значение `,(0,c.jsx)(t.code,{children:`undefined`}),` не является поддерживаемым аргументом:`]}),`
328
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const file = input.files?.[0];
329
+
330
+ if (!file) {
331
+ return;
332
+ }
333
+
334
+ await readFile(file);
335
+ `})}),`
336
+ `,(0,c.jsxs)(t.p,{children:[`Несмотря на публичный код ошибки `,(0,c.jsx)(t.code,{children:`NO_FILE`}),`, текущая функция сама не проверяет отсутствующий аргумент. При обходе TypeScript runtime может вернуть обычный `,(0,c.jsx)(t.code,{children:`TypeError`}),` как rejected Promise.`]}),`
337
+ `,(0,c.jsx)(t.h3,{id:`options`,children:`Options`}),`
338
+ `,(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:`Тип`}),(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:`mode`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadMode`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"data-url"`})}),(0,c.jsxs)(t.td,{children:[`Выбирает `,(0,c.jsx)(t.code,{children:`dataUrl`}),` или `,(0,c.jsx)(t.code,{children:`buffer`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`allowedMime`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`readonly string[]`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`undefined`})}),(0,c.jsxs)(t.td,{children:[`Точное case-insensitive сравнение с `,(0,c.jsx)(t.code,{children:`file.type`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`accept`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`readonly string[]`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`undefined`})}),(0,c.jsx)(t.td,{children:`Legacy-проверка по включению суффикса имени; см. предупреждение`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`maxBytes`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`number`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`undefined`})}),(0,c.jsxs)(t.td,{children:[`Ошибка, если `,(0,c.jsx)(t.code,{children:`file.size > maxBytes`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`signal`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`AbortSignal`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`undefined`})}),(0,c.jsxs)(t.td,{children:[`Отменяет `,(0,c.jsx)(t.code,{children:`FileReader`})]})]})]})]}),`
339
+ `,(0,c.jsx)(t.h3,{id:`порядок-выполнения`,children:`Порядок выполнения`}),`
340
+ `,(0,c.jsxs)(t.p,{children:[`При вызове `,(0,c.jsx)(t.code,{children:`readFile`}),` происходит следующее:`]}),`
341
+ `,(0,c.jsxs)(t.ol,{children:[`
342
+ `,(0,c.jsxs)(t.li,{children:[`проверяется, не был ли `,(0,c.jsx)(t.code,{children:`signal`}),` уже отменён;`]}),`
343
+ `,(0,c.jsxs)(t.li,{children:[`если `,(0,c.jsx)(t.code,{children:`allowedMime`}),` непустой, сравнивается `,(0,c.jsx)(t.code,{children:`file.type`}),`;`]}),`
344
+ `,(0,c.jsxs)(t.li,{children:[`иначе, если `,(0,c.jsx)(t.code,{children:`accept`}),` непустой, выполняется legacy-проверка суффикса;`]}),`
345
+ `,(0,c.jsxs)(t.li,{children:[`проверяется `,(0,c.jsx)(t.code,{children:`maxBytes`}),`;`]}),`
346
+ `,(0,c.jsxs)(t.li,{children:[`создаётся snapshot `,(0,c.jsx)(t.code,{children:`meta`}),`;`]}),`
347
+ `,(0,c.jsxs)(t.li,{children:[`создаётся `,(0,c.jsx)(t.code,{children:`FileReader`}),`;`]}),`
348
+ `,(0,c.jsxs)(t.li,{children:[`к нему подключается `,(0,c.jsx)(t.code,{children:`AbortSignal`}),`;`]}),`
349
+ `,(0,c.jsxs)(t.li,{children:[`вызывается `,(0,c.jsx)(t.code,{children:`readAsDataURL`}),` или `,(0,c.jsx)(t.code,{children:`readAsArrayBuffer`}),`;`]}),`
350
+ `,(0,c.jsxs)(t.li,{children:[`listener отмены удаляется после `,(0,c.jsx)(t.code,{children:`load`}),`, `,(0,c.jsx)(t.code,{children:`error`}),` или `,(0,c.jsx)(t.code,{children:`abort`}),`;`]}),`
351
+ `,(0,c.jsx)(t.li,{children:`возвращается типизированный объект либо отклоняется Promise.`}),`
352
+ `]}),`
353
+ `,(0,c.jsxs)(t.p,{children:[`Проверки выполняются до чтения байтов, поэтому неподходящий файл не загружается в память через `,(0,c.jsx)(t.code,{children:`FileReader`}),`.`]}),`
354
+ `,(0,c.jsx)(t.h3,{id:`общие-поля-результата`,children:`Общие поля результата`}),`
355
+ `,(0,c.jsx)(t.p,{children:`Оба режима возвращают:`}),`
356
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type CommonResult = {
357
+ mode: "data-url" | "array-buffer";
358
+ meta: FileMeta;
359
+ file: File;
360
+ };
361
+ `})}),`
362
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`file`}),` — тот же объект, который был передан функции:`]}),`
363
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file);
364
+
365
+ console.log(result.file === file); // true
366
+ `})}),`
367
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`meta`}),` — новый обычный объект со snapshot четырёх полей:`]}),`
368
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type FileMeta = {
369
+ mime: string;
370
+ size: number;
371
+ name: string;
372
+ lastModified: number;
373
+ };
374
+ `})}),`
375
+ `,(0,c.jsx)(t.p,{children:`Соответствие:`}),`
376
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.th,{children:[(0,c.jsx)(t.code,{children:`meta`}),` поле`]}),(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:`meta.mime`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`file.type`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`meta.size`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`file.size`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`meta.name`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`file.name`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`meta.lastModified`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`file.lastModified`})})]})]})]}),`
377
+ `,(0,c.jsx)(t.p,{children:`Metadata не очищается, не нормализуется и не подтверждается по содержимому.`}),`
378
+ `,(0,c.jsxs)(t.h2,{id:`режим-data-url`,children:[`Режим `,(0,c.jsx)(t.code,{children:`data-url`})]}),`
379
+ `,(0,c.jsx)(t.p,{children:`Это default:`}),`
380
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file);
381
+ `})}),`
382
+ `,(0,c.jsx)(t.p,{children:`То же самое явно:`}),`
383
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file, { mode: "data-url" });
384
+ `})}),`
385
+ `,(0,c.jsx)(t.p,{children:`Результат:`}),`
386
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type ReadFileAsDataUrlResult = {
387
+ mode: "data-url";
388
+ meta: FileMeta;
389
+ dataUrl: string;
390
+ file: File;
391
+ };
392
+ `})}),`
393
+ `,(0,c.jsx)(t.h3,{id:`когда-использовать`,children:`Когда использовать`}),`
394
+ `,(0,c.jsxs)(t.ul,{children:[`
395
+ `,(0,c.jsx)(t.li,{children:`preview небольшого изображения;`}),`
396
+ `,(0,c.jsx)(t.li,{children:`API, которому требуется data URL;`}),`
397
+ `,(0,c.jsx)(t.li,{children:`локальное встраивание небольших ресурсов;`}),`
398
+ `,(0,c.jsx)(t.li,{children:`временное отображение содержимого в browser UI.`}),`
399
+ `]}),`
400
+ `,(0,c.jsx)(t.h3,{id:`пример`,children:`Пример`}),`
401
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file, {
402
+ allowedMime: ["image/png"]
403
+ });
404
+
405
+ preview.src = result.dataUrl;
406
+ `})}),`
407
+ `,(0,c.jsx)(t.h3,{id:`когда-не-использовать`,children:`Когда не использовать`}),`
408
+ `,(0,c.jsxs)(t.ul,{children:[`
409
+ `,(0,c.jsx)(t.li,{children:`для большого видео;`}),`
410
+ `,(0,c.jsx)(t.li,{children:`для крупного архива;`}),`
411
+ `,(0,c.jsxs)(t.li,{children:[`если downstream принимает `,(0,c.jsx)(t.code,{children:`File`}),`, `,(0,c.jsx)(t.code,{children:`Blob`}),` или `,(0,c.jsx)(t.code,{children:`ArrayBuffer`}),`;`]}),`
412
+ `,(0,c.jsxs)(t.li,{children:[`только ради обычного `,(0,c.jsx)(t.code,{children:`FormData`}),` upload;`]}),`
413
+ `,(0,c.jsx)(t.li,{children:`для долгого хранения в state/cache без необходимости.`}),`
414
+ `]}),`
415
+ `,(0,c.jsx)(t.p,{children:`Data URL является строкой и обычно содержит base64. Это создаёт дополнительную копию данных и увеличивает размер представления.`}),`
416
+ `,(0,c.jsxs)(t.h2,{id:`режим-array-buffer`,children:[`Режим `,(0,c.jsx)(t.code,{children:`array-buffer`})]}),`
417
+ `,(0,c.jsx)(t.p,{children:`Включается только явным литералом:`}),`
418
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file, {
419
+ mode: "array-buffer"
420
+ });
421
+ `})}),`
422
+ `,(0,c.jsx)(t.p,{children:`Результат:`}),`
423
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type ReadFileAsArrayBufferResult = {
424
+ mode: "array-buffer";
425
+ meta: FileMeta;
426
+ buffer: ArrayBuffer;
427
+ file: File;
428
+ };
429
+ `})}),`
430
+ `,(0,c.jsx)(t.h3,{id:`когда-использовать-1`,children:`Когда использовать`}),`
431
+ `,(0,c.jsxs)(t.ul,{children:[`
432
+ `,(0,c.jsx)(t.li,{children:`бинарный parser;`}),`
433
+ `,(0,c.jsx)(t.li,{children:`вычисление checksum через Web Crypto;`}),`
434
+ `,(0,c.jsxs)(t.li,{children:[`отправка байтов API, которое не принимает `,(0,c.jsx)(t.code,{children:`File`}),`;`]}),`
435
+ `,(0,c.jsx)(t.li,{children:`передача данных Web Worker;`}),`
436
+ `,(0,c.jsx)(t.li,{children:`анализ заголовка файла.`}),`
437
+ `]}),`
438
+ `,(0,c.jsx)(t.h3,{id:`прочитать-первые-байты`,children:`Прочитать первые байты`}),`
439
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file, { mode: "array-buffer" });
440
+ const bytes = new Uint8Array(result.buffer);
441
+
442
+ console.log(bytes.slice(0, 8));
443
+ `})}),`
444
+ `,(0,c.jsx)(t.h3,{id:`переменный-режим`,children:`Переменный режим`}),`
445
+ `,(0,c.jsxs)(t.p,{children:[`Если mode хранится в широкой переменной `,(0,c.jsx)(t.code,{children:`ReadMode`}),`, безопаснее разделить вызовы:`]}),`
446
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import type { ReadFileResult, ReadMode } from "@ryuzaki13/react-foundation-lib/file";
447
+
448
+ async function readByMode(file: File, mode: ReadMode): Promise<ReadFileResult> {
449
+ if (mode === "array-buffer") {
450
+ return readFile(file, { mode: "array-buffer" });
451
+ }
452
+
453
+ return readFile(file);
454
+ }
455
+ `})}),`
456
+ `,(0,c.jsx)(t.p,{children:`Так каждая перегрузка получает литеральное значение, а не union.`}),`
457
+ `,(0,c.jsx)(t.h2,{id:`проверка-типа-и-размера`,children:`Проверка типа и размера`}),`
458
+ `,(0,c.jsxs)(t.h3,{id:`allowedmime--рекомендуемая-проверка-типа`,children:[(0,c.jsx)(t.code,{children:`allowedMime`}),` — рекомендуемая проверка типа`]}),`
459
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`allowedMime`}),` выполняет точное сравнение MIME после `,(0,c.jsx)(t.code,{children:`trim()`}),` и `,(0,c.jsx)(t.code,{children:`toLowerCase()`}),`:`]}),`
460
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await readFile(file, {
461
+ allowedMime: ["application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]
462
+ });
463
+ `})}),`
464
+ `,(0,c.jsx)(t.p,{children:`Примеры:`}),`
465
+ `,(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.code,{children:`file.type`})}),(0,c.jsx)(t.th,{children:(0,c.jsx)(t.code,{children:`allowedMime`})}),(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:`application/pdf`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`['application/pdf']`})}),(0,c.jsx)(t.td,{children:`разрешён`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`APPLICATION/PDF`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`['application/pdf']`})}),(0,c.jsx)(t.td,{children:`разрешён`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:` application/pdf`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`['application/pdf']`})}),(0,c.jsx)(t.td,{children:`разрешён`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/png`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`['image/jpeg']`})}),(0,c.jsx)(t.td,{children:`ошибка`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`пустая строка`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`['application/pdf']`})}),(0,c.jsx)(t.td,{children:`ошибка`})]})]})]}),`
466
+ `,(0,c.jsxs)(t.p,{children:[`Непустой `,(0,c.jsx)(t.code,{children:`allowedMime`}),` имеет приоритет над `,(0,c.jsx)(t.code,{children:`accept`}),`: если переданы оба, `,(0,c.jsx)(t.code,{children:`accept`}),` внутри `,(0,c.jsx)(t.code,{children:`readFile`}),` не проверяется.`]}),`
467
+ `,(0,c.jsx)(t.p,{children:`Пустой массив не ограничивает MIME:`}),`
468
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await readFile(file, { allowedMime: [] });
469
+ `})}),`
470
+ `,(0,c.jsxs)(t.p,{children:[`Если одновременно передан непустой `,(0,c.jsx)(t.code,{children:`accept`}),`, пустой `,(0,c.jsx)(t.code,{children:`allowedMime`}),` не блокирует его применение.`]}),`
471
+ `,(0,c.jsxs)(t.h3,{id:`валидация-конфигурации-allowedmime`,children:[`Валидация конфигурации `,(0,c.jsx)(t.code,{children:`allowedMime`})]}),`
472
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`readFile`}),` не вызывает `,(0,c.jsx)(t.code,{children:`assertValidAllowedMime`}),` автоматически. Если список строится динамически, проверьте его отдельно:`]}),`
473
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { assertValidAllowedMime, readFile } from "@ryuzaki13/react-foundation-lib/file";
474
+
475
+ const allowedMime = ["application/pdf", "image/png"] as const;
476
+
477
+ assertValidAllowedMime(allowedMime);
478
+ const result = await readFile(file, { allowedMime });
479
+ `})}),`
480
+ `,(0,c.jsx)(t.p,{children:`Валидный элемент должен быть полным MIME без wildcard и параметров:`}),`
481
+ `,(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:`application/pdf`})}),(0,c.jsx)(t.td,{children:`да`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/png`})}),(0,c.jsx)(t.td,{children:`да`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/*`})}),(0,c.jsx)(t.td,{children:`нет`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`text/plain; charset=UTF-8`})}),(0,c.jsx)(t.td,{children:`нет`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`.pdf`})}),(0,c.jsx)(t.td,{children:`нет`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:` application/pdf`})}),(0,c.jsx)(t.td,{children:`нет`})]})]})]}),`
482
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`assertValidAllowedMime`}),` не обрезает строку и при ошибке бросает обычный `,(0,c.jsx)(t.code,{children:`Error`}),`, а не `,(0,c.jsx)(t.code,{children:`ReadFileError`}),`.`]}),`
483
+ `,(0,c.jsxs)(t.h3,{id:`accept--важное-отличие-от-html`,children:[(0,c.jsx)(t.code,{children:`accept`}),` — важное отличие от HTML`]}),`
484
+ `,(0,c.jsxs)(t.p,{children:[`В HTML атрибут `,(0,c.jsx)(t.code,{children:`accept`}),` является подсказкой нативному file picker:`]}),`
485
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-html`,children:`<input type="file" accept=".pdf,application/pdf,image/*" />
486
+ `})}),`
487
+ `,(0,c.jsxs)(t.p,{children:[`В `,(0,c.jsx)(t.code,{children:`readFile`}),` option `,(0,c.jsx)(t.code,{children:`accept`}),` имеет текущую legacy-семантику и не повторяет правила HTML:`]}),`
488
+ `,(0,c.jsxs)(t.ol,{children:[`
489
+ `,(0,c.jsxs)(t.li,{children:[`из `,(0,c.jsx)(t.code,{children:`file.name`}),` берётся часть после последней точки;`]}),`
490
+ `,(0,c.jsxs)(t.li,{children:[`если она пустая, берётся subtype из `,(0,c.jsx)(t.code,{children:`file.type`}),`;`]}),`
491
+ `,(0,c.jsx)(t.li,{children:`каждый accept-элемент нормализуется;`}),`
492
+ `,(0,c.jsxs)(t.li,{children:[`файл разрешён, если хотя бы одна строка `,(0,c.jsx)(t.code,{children:`accept`}),` содержит полученный суффикс через `,(0,c.jsx)(t.code,{children:`includes`}),`.`]}),`
493
+ `]}),`
494
+ `,(0,c.jsxs)(t.p,{children:[`Примеры текущего поведения для `,(0,c.jsx)(t.code,{children:`photo.png`}),`:`]}),`
495
+ `,(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.code,{children:`accept`})}),(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:`['.png']`})}),(0,c.jsx)(t.td,{children:`разрешён`}),(0,c.jsxs)(t.td,{children:[`строка `,(0,c.jsx)(t.code,{children:`.png`}),` содержит `,(0,c.jsx)(t.code,{children:`png`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`['image/png']`})}),(0,c.jsx)(t.td,{children:`разрешён`}),(0,c.jsxs)(t.td,{children:[`строка `,(0,c.jsx)(t.code,{children:`image/png`}),` содержит `,(0,c.jsx)(t.code,{children:`png`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`['image/*']`})}),(0,c.jsx)(t.td,{children:`ошибка`}),(0,c.jsxs)(t.td,{children:[`строка `,(0,c.jsx)(t.code,{children:`image/*`}),` не содержит `,(0,c.jsx)(t.code,{children:`png`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`['.jpg']`})}),(0,c.jsx)(t.td,{children:`ошибка`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`.jpg`}),` не содержит `,(0,c.jsx)(t.code,{children:`png`})]})]})]})]}),`
496
+ `,(0,c.jsx)(t.p,{children:`Дополнительные особенности:`}),`
497
+ `,(0,c.jsxs)(t.ul,{children:[`
498
+ `,(0,c.jsxs)(t.li,{children:[`имя без точки, например `,(0,c.jsx)(t.code,{children:`photo`}),`, трактуется как найденный суффикс `,(0,c.jsx)(t.code,{children:`photo`}),`, поэтому MIME fallback обычно не используется;`]}),`
499
+ `,(0,c.jsx)(t.li,{children:`используется substring-проверка, а не строгое сравнение;`}),`
500
+ `,(0,c.jsxs)(t.li,{children:[`wildcard `,(0,c.jsx)(t.code,{children:`image/*`}),` проходит `,(0,c.jsx)(t.code,{children:`assertValidAccept`}),`, но обычно не разрешает файл `,(0,c.jsx)(t.code,{children:`photo.png`}),` внутри `,(0,c.jsx)(t.code,{children:`readFile`}),`;`]}),`
501
+ `,(0,c.jsx)(t.li,{children:`строка с несколькими specifiers через запятую не поддерживается: каждый specifier должен быть отдельным элементом массива;`}),`
502
+ `,(0,c.jsxs)(t.li,{children:[`если непустой `,(0,c.jsx)(t.code,{children:`allowedMime`}),` уже передан, `,(0,c.jsx)(t.code,{children:`accept`}),` игнорируется.`]}),`
503
+ `]}),`
504
+ `,(0,c.jsxs)(t.p,{children:[`Поэтому для runtime-проверки файла используйте `,(0,c.jsx)(t.code,{children:`allowedMime`}),`. `,(0,c.jsx)(t.code,{children:`accept`}),` удобно передавать UI-компоненту или нативному `,(0,c.jsx)(t.code,{children:`<input>`}),`, а затем отдельно проверять MIME.`]}),`
505
+ `,(0,c.jsx)(t.h3,{id:`assertvalidaccept`,children:(0,c.jsx)(t.code,{children:`assertValidAccept`})}),`
506
+ `,(0,c.jsx)(t.p,{children:`Helper проверяет синтаксис массива:`}),`
507
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`assertValidAccept([".pdf", "application/pdf", "image/*"]);
508
+ `})}),`
509
+ `,(0,c.jsx)(t.p,{children:`Допускаются:`}),`
510
+ `,(0,c.jsxs)(t.ul,{children:[`
511
+ `,(0,c.jsxs)(t.li,{children:[`расширение с начальной точкой: `,(0,c.jsx)(t.code,{children:`.pdf`}),`, `,(0,c.jsx)(t.code,{children:`.tar.gz`}),`;`]}),`
512
+ `,(0,c.jsxs)(t.li,{children:[`полный MIME: `,(0,c.jsx)(t.code,{children:`application/pdf`}),`;`]}),`
513
+ `,(0,c.jsxs)(t.li,{children:[`MIME wildcard: `,(0,c.jsx)(t.code,{children:`image/*`}),`.`]}),`
514
+ `]}),`
515
+ `,(0,c.jsx)(t.p,{children:`Не допускаются:`}),`
516
+ `,(0,c.jsxs)(t.ul,{children:[`
517
+ `,(0,c.jsxs)(t.li,{children:[`расширение без точки: `,(0,c.jsx)(t.code,{children:`pdf`}),`;`]}),`
518
+ `,(0,c.jsxs)(t.li,{children:[`запятая внутри одного элемента: `,(0,c.jsx)(t.code,{children:`.pdf,.docx`}),`;`]}),`
519
+ `,(0,c.jsx)(t.li,{children:`пробелы;`}),`
520
+ `,(0,c.jsxs)(t.li,{children:[`параметры MIME: `,(0,c.jsx)(t.code,{children:`text/plain; charset=UTF-8`}),`.`]}),`
521
+ `]}),`
522
+ `,(0,c.jsxs)(t.p,{children:[`Это только синтаксическая проверка. Она не гарантирует, что `,(0,c.jsx)(t.code,{children:`readFile`}),` применит HTML-семантику wildcard.`]}),`
523
+ `,(0,c.jsx)(t.h3,{id:`maxbytes`,children:(0,c.jsx)(t.code,{children:`maxBytes`})}),`
524
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await readFile(file, {
525
+ maxBytes: 5 * 1024 * 1024
526
+ });
527
+ `})}),`
528
+ `,(0,c.jsx)(t.p,{children:`Условие ошибки:`}),`
529
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`file.size > maxBytes;
530
+ `})}),`
531
+ `,(0,c.jsx)(t.p,{children:`Следовательно:`}),`
532
+ `,(0,c.jsxs)(t.ul,{children:[`
533
+ `,(0,c.jsx)(t.li,{children:`ровно допустимый размер проходит;`}),`
534
+ `,(0,c.jsxs)(t.li,{children:[`ограничение применяется до `,(0,c.jsx)(t.code,{children:`FileReader`}),`;`]}),`
535
+ `,(0,c.jsx)(t.li,{children:`значение должно задаваться в байтах;`}),`
536
+ `,(0,c.jsx)(t.li,{children:`функция не проверяет, является ли option положительным конечным числом.`}),`
537
+ `]}),`
538
+ `,(0,c.jsxs)(t.p,{children:[`Передавайте осмысленное положительное значение. Отрицательное число отклонит даже пустой файл, `,(0,c.jsx)(t.code,{children:`NaN`}),` фактически отключит сравнение, а `,(0,c.jsx)(t.code,{children:`Infinity`}),` снимет предел.`]}),`
539
+ `,(0,c.jsx)(t.h3,{id:`почему-нужны-обе-границы`,children:`Почему нужны обе границы`}),`
540
+ `,(0,c.jsx)(t.p,{children:`Типичный UI использует две разные проверки:`}),`
541
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const ACCEPT = ".pdf,application/pdf";
542
+ const ALLOWED_MIME = ["application/pdf"] as const;
543
+ `})}),`
544
+ `,(0,c.jsxs)(t.ul,{children:[`
545
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`accept`}),` в HTML фильтрует диалог и улучшает UX;`]}),`
546
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`allowedMime`}),` в `,(0,c.jsx)(t.code,{children:`readFile`}),` проверяет browser object;`]}),`
547
+ `,(0,c.jsx)(t.li,{children:`backend проверяет реальные байты, политику, права и допустимый размер заново.`}),`
548
+ `]}),`
549
+ `,(0,c.jsxs)(t.h2,{id:`отмена-через-abortsignal`,children:[`Отмена через `,(0,c.jsx)(t.code,{children:`AbortSignal`})]}),`
550
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`AbortController`}),` создаёт signal, который можно отменить:`]}),`
551
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const controller = new AbortController();
552
+
553
+ const promise = readFile(file, {
554
+ mode: "array-buffer",
555
+ signal: controller.signal
556
+ });
557
+
558
+ controller.abort();
559
+
560
+ try {
561
+ await promise;
562
+ } catch (error) {
563
+ console.error(error);
564
+ }
565
+ `})}),`
566
+ `,(0,c.jsxs)(t.p,{children:[`Если signal отменён до вызова, `,(0,c.jsx)(t.code,{children:`readFile`}),` сразу возвращает rejected Promise с `,(0,c.jsx)(t.code,{children:`ReadFileError`}),` и code `,(0,c.jsx)(t.code,{children:`READ_ABORTED`}),`.`]}),`
567
+ `,(0,c.jsxs)(t.p,{children:[`Если отмена происходит во время работы `,(0,c.jsx)(t.code,{children:`FileReader`}),`, helper вызывает `,(0,c.jsx)(t.code,{children:`reader.abort()`}),`. Затем Promise отклоняется с `,(0,c.jsx)(t.code,{children:`READ_ABORTED`}),`.`]}),`
568
+ `,(0,c.jsx)(t.h3,{id:`отменять-предыдущее-чтение-при-новом-выборе`,children:`Отменять предыдущее чтение при новом выборе`}),`
569
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`let activeController: AbortController | undefined;
570
+
571
+ async function selectFile(file: File) {
572
+ activeController?.abort();
573
+
574
+ const controller = new AbortController();
575
+ activeController = controller;
576
+
577
+ try {
578
+ return await readFile(file, {
579
+ mode: "array-buffer",
580
+ signal: controller.signal
581
+ });
582
+ } finally {
583
+ if (activeController === controller) {
584
+ activeController = undefined;
585
+ }
586
+ }
587
+ }
588
+ `})}),`
589
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`AbortSignal`}),` отменяет чтение, но не удаляет выбранный `,(0,c.jsx)(t.code,{children:`File`}),`, не очищает `,(0,c.jsx)(t.code,{children:`<input>`}),` и не отменяет последующий HTTP upload, если он уже запущен другим API.`]}),`
590
+ `,(0,c.jsxs)(t.p,{children:[`Для `,(0,c.jsx)(t.code,{children:`readImageFile`}),` отмена имеет дополнительное ограничение: signal связан с `,(0,c.jsx)(t.code,{children:`FileReader`}),`, но не прерывает ожидание `,(0,c.jsx)(t.code,{children:`Image.onload`}),`/`,(0,c.jsx)(t.code,{children:`Image.onerror`}),` во время определения размеров. Подробнее это описано в разделе об изображениях.`]}),`
591
+ `,(0,c.jsxs)(t.h2,{id:`ошибки-readfile`,children:[`Ошибки `,(0,c.jsx)(t.code,{children:`readFile`})]}),`
592
+ `,(0,c.jsxs)(t.h3,{id:`класс-readfileerror`,children:[`Класс `,(0,c.jsx)(t.code,{children:`ReadFileError`})]}),`
593
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`class ReadFileError extends Error {
594
+ readonly code: ReadFileErrorCode;
595
+ readonly cause?: unknown;
596
+ }
597
+ `})}),`
598
+ `,(0,c.jsxs)(t.p,{children:[`Обрабатывайте `,(0,c.jsx)(t.code,{children:`unknown`}),`, а не делайте небезопасный cast:`]}),`
599
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`try {
600
+ const result = await readFile(file, options);
601
+ return result;
602
+ } catch (error) {
603
+ if (error instanceof ReadFileError) {
604
+ console.error(error.code, error.message, error.cause);
605
+ return;
606
+ }
607
+
608
+ throw error;
609
+ }
610
+ `})}),`
611
+ `,(0,c.jsx)(t.h3,{id:`коды`,children:`Коды`}),`
612
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Code`}),(0,c.jsxs)(t.th,{children:[`Когда фактически возникает в `,(0,c.jsx)(t.code,{children:`readFile`})]})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`MIME_NOT_ALLOWED`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`allowedMime`}),` или legacy `,(0,c.jsx)(t.code,{children:`accept`}),` отклонил файл`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FILE_TOO_LARGE`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`file.size > maxBytes`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`READ_ABORTED`})}),(0,c.jsxs)(t.td,{children:[`signal уже отменён или `,(0,c.jsx)(t.code,{children:`FileReader`}),` получил abort`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`READ_FAILED`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`FileReader`}),` завершился ошибкой, бросил исключение или дал неожиданный result`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`NO_FILE`})}),(0,c.jsxs)(t.td,{children:[`Есть в публичном union, но текущий `,(0,c.jsx)(t.code,{children:`readFile`}),` его не создаёт`]})]})]})]}),`
613
+ `,(0,c.jsx)(t.h3,{id:`cause`,children:(0,c.jsx)(t.code,{children:`cause`})}),`
614
+ `,(0,c.jsxs)(t.p,{children:[`Для некоторых `,(0,c.jsx)(t.code,{children:`READ_FAILED`}),` поле `,(0,c.jsx)(t.code,{children:`cause`}),` содержит исходную ошибку `,(0,c.jsx)(t.code,{children:`FileReader`}),`. Его удобно отправлять в технический logger, но не показывать пользователю без преобразования:`]}),`
615
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`if (error instanceof ReadFileError) {
616
+ logger.error("Ошибка чтения файла", {
617
+ code: error.code,
618
+ cause: error.cause
619
+ });
620
+ }
621
+ `})}),`
622
+ `,(0,c.jsx)(t.h3,{id:`преобразование-в-пользовательский-текст`,children:`Преобразование в пользовательский текст`}),`
623
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function getReadFileMessage(error: ReadFileError): string {
624
+ switch (error.code) {
625
+ case "MIME_NOT_ALLOWED":
626
+ return "Выберите файл поддерживаемого формата.";
627
+ case "FILE_TOO_LARGE":
628
+ return "Размер файла превышает допустимый.";
629
+ case "READ_ABORTED":
630
+ return "Чтение файла отменено.";
631
+ case "READ_FAILED":
632
+ return "Браузеру не удалось прочитать файл.";
633
+ case "NO_FILE":
634
+ return "Файл не выбран.";
635
+ }
636
+ }
637
+ `})}),`
638
+ `,(0,c.jsxs)(t.p,{children:[`Не используйте `,(0,c.jsx)(t.code,{children:`message`}),` как стабильный программный код. Для ветвления предназначено поле `,(0,c.jsx)(t.code,{children:`code`}),`.`]}),`
639
+ `,(0,c.jsxs)(t.h3,{id:`какие-ошибки-не-являются-readfileerror`,children:[`Какие ошибки не являются `,(0,c.jsx)(t.code,{children:`ReadFileError`})]}),`
640
+ `,(0,c.jsxs)(t.ul,{children:[`
641
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`assertValidAllowedMime`}),` и `,(0,c.jsx)(t.code,{children:`assertValidAccept`}),` бросают обычный `,(0,c.jsx)(t.code,{children:`Error`}),`;`]}),`
642
+ `,(0,c.jsxs)(t.li,{children:[`передача `,(0,c.jsx)(t.code,{children:`undefined`}),` вместо `,(0,c.jsx)(t.code,{children:`File`}),` может привести к обычному `,(0,c.jsx)(t.code,{children:`TypeError`}),`;`]}),`
643
+ `,(0,c.jsxs)(t.li,{children:[`ошибочный consumer-код внутри собственного callback тоже не становится `,(0,c.jsx)(t.code,{children:`ReadFileError`}),`.`]}),`
644
+ `]}),`
645
+ `,(0,c.jsx)(t.p,{children:`Поэтому неизвестную ошибку нельзя молча считать ошибкой формата файла.`}),`
646
+ `,(0,c.jsx)(t.h2,{id:`readimagefile`,children:(0,c.jsx)(t.code,{children:`readImageFile`})}),`
647
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`readImageFile`}),` — надстройка над `,(0,c.jsx)(t.code,{children:`readFile`}),` для файлов, чей `,(0,c.jsx)(t.code,{children:`file.type`}),` начинается с точной строки `,(0,c.jsx)(t.code,{children:`image/`}),`.`]}),`
648
+ `,(0,c.jsx)(t.h3,{id:`сигнатуры-1`,children:`Сигнатуры`}),`
649
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function readImageFile(file: File, opts?: Omit<ReadImageOptions, "mode"> & { mode?: "data-url" }): Promise<ReadImageAsDataUrlResult>;
650
+
651
+ function readImageFile(file: File, opts: Omit<ReadImageOptions, "mode"> & { mode: "array-buffer" }): Promise<ReadImageAsArrayBufferResult>;
652
+ `})}),`
653
+ `,(0,c.jsx)(t.h3,{id:`поддерживаемый-typed-список-mime`,children:`Поддерживаемый typed список MIME`}),`
654
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type ImageMime = "image/png" | "image/jpeg" | "image/webp" | "image/gif" | "image/svg+xml" | "image/avif";
655
+ `})}),`
656
+ `,(0,c.jsxs)(t.p,{children:[`Этот union ограничивает `,(0,c.jsx)(t.code,{children:`allowedMime`}),` на уровне TypeScript:`]}),`
657
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await readImageFile(file, {
658
+ allowedMime: ["image/jpeg", "image/png"]
659
+ });
660
+ `})}),`
661
+ `,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`allowedMime`}),` не передан или пуст, первичная runtime-проверка разрешает любой MIME, который начинается с `,(0,c.jsx)(t.code,{children:`image/`}),`, включая MIME вне `,(0,c.jsx)(t.code,{children:`ImageMime`}),`, например `,(0,c.jsx)(t.code,{children:`image/bmp`}),`. Возможность успешного определения размеров затем зависит от браузерного decoder.`]}),`
662
+ `,(0,c.jsxs)(t.h3,{id:`проверка-image`,children:[`Проверка `,(0,c.jsx)(t.code,{children:`image/*`})]}),`
663
+ `,(0,c.jsx)(t.p,{children:`Первичная проверка регистрозависима и не обрезает пробелы:`}),`
664
+ `,(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.code,{children:`file.type`})}),(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:`image/png`})}),(0,c.jsx)(t.td,{children:`проходит`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/svg+xml`})}),(0,c.jsx)(t.td,{children:`проходит`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`IMAGE/PNG`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`NOT_AN_IMAGE`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:` image/png`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`NOT_AN_IMAGE`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`пустая строка`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`NOT_AN_IMAGE`})})]})]})]}),`
665
+ `,(0,c.jsxs)(t.p,{children:[`После неё непустой `,(0,c.jsx)(t.code,{children:`allowedMime`}),` проверяется внутри `,(0,c.jsx)(t.code,{children:`readFile`}),` по точному нормализованному сравнению.`]}),`
666
+ `,(0,c.jsx)(t.h3,{id:`options-1`,children:`Options`}),`
667
+ `,(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:`Тип`}),(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:`mode`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadMode`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"data-url"`})}),(0,c.jsx)(t.td,{children:`Выбирает data URL с dimensions или buffer`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`allowedMime`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`readonly ImageMime[]`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`undefined`})}),(0,c.jsx)(t.td,{children:`Ограничивает точные MIME изображений`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`maxBytes`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`number`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`undefined`})}),(0,c.jsxs)(t.td,{children:[`Передаётся в `,(0,c.jsx)(t.code,{children:`readFile`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`signal`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`AbortSignal`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`undefined`})}),(0,c.jsxs)(t.td,{children:[`Передаётся в `,(0,c.jsx)(t.code,{children:`readFile`}),`; проверяется до/после decode`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`accept`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`readonly string[]`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`undefined`})}),(0,c.jsx)(t.td,{children:`Присутствует в типе, но текущая функция его не передаёт и не применяет`})]})]})]}),`
668
+ `,(0,c.jsxs)(t.p,{children:[`Не рассчитывайте на `,(0,c.jsx)(t.code,{children:`accept`}),` в `,(0,c.jsx)(t.code,{children:`readImageFile`}),`. Для фактического ограничения используйте `,(0,c.jsx)(t.code,{children:`allowedMime`}),`, а для нативного picker установите HTML `,(0,c.jsx)(t.code,{children:`accept`}),` отдельно.`]}),`
669
+ `,(0,c.jsx)(t.h3,{id:`режим-data-url-и-dimensions`,children:`Режим data URL и dimensions`}),`
670
+ `,(0,c.jsx)(t.p,{children:`Default-сценарий:`}),`
671
+ `,(0,c.jsxs)(t.ol,{children:[`
672
+ `,(0,c.jsxs)(t.li,{children:[`проверить `,(0,c.jsx)(t.code,{children:`file.type.startsWith('image/')`}),`;`]}),`
673
+ `,(0,c.jsx)(t.li,{children:`прочитать файл как data URL;`}),`
674
+ `,(0,c.jsxs)(t.li,{children:[`создать browser `,(0,c.jsx)(t.code,{children:`Image`}),`;`]}),`
675
+ `,(0,c.jsxs)(t.li,{children:[`назначить `,(0,c.jsx)(t.code,{children:`img.src = dataUrl`}),`;`]}),`
676
+ `,(0,c.jsxs)(t.li,{children:[`дождаться `,(0,c.jsx)(t.code,{children:`onload`}),`;`]}),`
677
+ `,(0,c.jsxs)(t.li,{children:[`вернуть `,(0,c.jsx)(t.code,{children:`naturalWidth`}),` и `,(0,c.jsx)(t.code,{children:`naturalHeight`}),`.`]}),`
678
+ `]}),`
679
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readImageFile(file, {
680
+ allowedMime: ["image/jpeg", "image/png"],
681
+ maxBytes: 5 * 1024 * 1024
682
+ });
683
+
684
+ console.log(result.mode); // "data-url"
685
+ console.log(result.dataUrl);
686
+
687
+ if (result.dimensions) {
688
+ console.log(result.dimensions.width);
689
+ console.log(result.dimensions.height);
690
+ }
691
+ `})}),`
692
+ `,(0,c.jsx)(t.p,{children:`Тип результата:`}),`
693
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type ReadImageAsDataUrlResult = ReadFileAsDataUrlResult & {
694
+ dimensions?: {
695
+ width: number;
696
+ height: number;
697
+ };
698
+ };
699
+ `})}),`
700
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`width`}),` и `,(0,c.jsx)(t.code,{children:`height`}),` — естественные размеры изображения в пикселях, а не CSS-размер элемента на странице.`]}),`
701
+ `,(0,c.jsxs)(t.h3,{id:`режим-array-buffer-1`,children:[`Режим `,(0,c.jsx)(t.code,{children:`array-buffer`})]}),`
702
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readImageFile(file, {
703
+ mode: "array-buffer",
704
+ allowedMime: ["image/png"]
705
+ });
706
+
707
+ console.log(result.buffer);
708
+ `})}),`
709
+ `,(0,c.jsx)(t.p,{children:`В этом режиме:`}),`
710
+ `,(0,c.jsxs)(t.ul,{children:[`
711
+ `,(0,c.jsxs)(t.li,{children:[`файл проверяется как `,(0,c.jsx)(t.code,{children:`image/*`}),`;`]}),`
712
+ `,(0,c.jsxs)(t.li,{children:[`применяются `,(0,c.jsx)(t.code,{children:`allowedMime`}),` и `,(0,c.jsx)(t.code,{children:`maxBytes`}),`;`]}),`
713
+ `,(0,c.jsxs)(t.li,{children:[`выполняется только `,(0,c.jsx)(t.code,{children:`readFile`}),`;`]}),`
714
+ `,(0,c.jsxs)(t.li,{children:[`browser `,(0,c.jsx)(t.code,{children:`Image`}),` не создаётся;`]}),`
715
+ `,(0,c.jsx)(t.li,{children:`размеры не декодируются и не возвращаются.`}),`
716
+ `]}),`
717
+ `,(0,c.jsx)(t.p,{children:`Если нужны размеры, выбирайте data URL либо специализированный image decoder.`}),`
718
+ `,(0,c.jsx)(t.h3,{id:`ограничения-dimensions`,children:`Ограничения dimensions`}),`
719
+ `,(0,c.jsxs)(t.ul,{children:[`
720
+ `,(0,c.jsx)(t.li,{children:`animated GIF возвращает естественный размер изображения, а не данные каждого кадра;`}),`
721
+ `,(0,c.jsx)(t.li,{children:`SVG зависит от того, может ли браузер определить intrinsic dimensions;`}),`
722
+ `,(0,c.jsx)(t.li,{children:`большой сжатый файл может занимать мало байтов, но требовать огромную память после decode;`}),`
723
+ `,(0,c.jsx)(t.li,{children:`модуль не проверяет максимальные width/height/pixel count;`}),`
724
+ `,(0,c.jsxs)(t.li,{children:[`abort во время ожидания `,(0,c.jsx)(t.code,{children:`Image.onload`}),` не отменяет сам `,(0,c.jsx)(t.code,{children:`Image`}),` и не завершает ожидание немедленно;`]}),`
725
+ `,(0,c.jsx)(t.li,{children:`data URL и decoded bitmap могут одновременно находиться в памяти.`}),`
726
+ `]}),`
727
+ `,(0,c.jsxs)(t.h2,{id:`ошибки-readimagefile`,children:[`Ошибки `,(0,c.jsx)(t.code,{children:`readImageFile`})]}),`
728
+ `,(0,c.jsx)(t.p,{children:`Публично экспортирован отдельный класс:`}),`
729
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`class ReadImageError extends Error {
730
+ readonly code: ReadImageErrorCode;
731
+ readonly cause?: unknown;
732
+ }
733
+ `})}),`
734
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`ReadImageError`}),` не наследуется от `,(0,c.jsx)(t.code,{children:`ReadFileError`}),`.`]}),`
735
+ `,(0,c.jsx)(t.h3,{id:`фактическое-поведение-текущей-реализации`,children:`Фактическое поведение текущей реализации`}),`
736
+ `,(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:`Тип rejected value`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`file.type`}),` не начинается с `,(0,c.jsx)(t.code,{children:`image/`})]}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`ReadImageError`}),` с code `,(0,c.jsx)(t.code,{children:`NOT_AN_IMAGE`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`MIME из `,(0,c.jsx)(t.code,{children:`allowedMime`}),` не подошёл`]}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`ReadFileError`}),` с code `,(0,c.jsx)(t.code,{children:`MIME_NOT_ALLOWED`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`файл превышает `,(0,c.jsx)(t.code,{children:`maxBytes`})]}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`ReadFileError`}),` с code `,(0,c.jsx)(t.code,{children:`FILE_TOO_LARGE`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`чтение отменено`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`ReadFileError`}),` с code `,(0,c.jsx)(t.code,{children:`READ_ABORTED`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`FileReader`}),` завершился ошибкой`]}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`ReadFileError`}),` с code `,(0,c.jsx)(t.code,{children:`READ_FAILED`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`browser `,(0,c.jsx)(t.code,{children:`Image`}),` вызвал `,(0,c.jsx)(t.code,{children:`onerror`})]}),(0,c.jsx)(t.td,{children:`необёрнутое значение browser event`})]})]})]}),`
737
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`ReadImageErrorCode`}),` также включает `,(0,c.jsx)(t.code,{children:`IMAGE_DECODE_FAILED`}),` и коды `,(0,c.jsx)(t.code,{children:`ReadFileErrorCode`}),`, но текущая реализация не оборачивает ими ошибки `,(0,c.jsx)(t.code,{children:`readFile`}),` и decode. Код `,(0,c.jsx)(t.code,{children:`IMAGE_DECODE_FAILED`}),` сейчас самой функцией не создаётся.`]}),`
738
+ `,(0,c.jsx)(t.p,{children:`Поэтому надёжный consumer обрабатывает оба класса и оставляет fallback для неизвестной ошибки:`}),`
739
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { ReadFileError, ReadImageError, readImageFile } from "@ryuzaki13/react-foundation-lib/file";
740
+
741
+ try {
742
+ const result = await readImageFile(file, {
743
+ allowedMime: ["image/jpeg", "image/png"],
744
+ maxBytes: 5 * 1024 * 1024
745
+ });
746
+
747
+ return result;
748
+ } catch (error) {
749
+ if (error instanceof ReadImageError) {
750
+ console.error("Проверка изображения", error.code);
751
+ return;
752
+ }
753
+
754
+ if (error instanceof ReadFileError) {
755
+ console.error("Чтение файла", error.code);
756
+ return;
757
+ }
758
+
759
+ console.error("Браузер не смог декодировать изображение", error);
760
+ }
761
+ `})}),`
762
+ `,(0,c.jsxs)(t.p,{children:[`Не пишите обработчик, который ловит только `,(0,c.jsx)(t.code,{children:`ReadImageError`}),`: он пропустит ограничения MIME/размера и ошибки `,(0,c.jsx)(t.code,{children:`FileReader`}),`.`]}),`
763
+ `,(0,c.jsx)(t.h2,{id:`resolvemimetypebyextension`,children:(0,c.jsx)(t.code,{children:`resolveMimeTypeByExtension`})}),`
764
+ `,(0,c.jsx)(t.p,{children:`Функция принимает только расширение без имени файла и без начальной точки:`}),`
765
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function resolveMimeTypeByExtension(extension: string): string;
766
+ `})}),`
767
+ `,(0,c.jsx)(t.h3,{id:`примеры`,children:`Примеры`}),`
768
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`resolveMimeTypeByExtension("pdf");
769
+ // "application/pdf"
770
+
771
+ resolveMimeTypeByExtension(" JPG ");
772
+ // "image/jpeg"
773
+
774
+ resolveMimeTypeByExtension("unknown");
775
+ // "application/octet-stream"
776
+ `})}),`
777
+ `,(0,c.jsx)(t.p,{children:`Неправильная форма входа:`}),`
778
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`resolveMimeTypeByExtension(".pdf");
779
+ // "application/octet-stream"
780
+
781
+ resolveMimeTypeByExtension("report.pdf");
782
+ // "application/octet-stream"
783
+ `})}),`
784
+ `,(0,c.jsx)(t.p,{children:`Чтобы взять расширение из имени:`}),`
785
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function getExtension(fileName: string): string {
786
+ const dotIndex = fileName.lastIndexOf(".");
787
+
788
+ if (dotIndex < 0 || dotIndex === fileName.length - 1) {
789
+ return "";
790
+ }
791
+
792
+ return fileName.slice(dotIndex + 1);
793
+ }
794
+
795
+ const mime = resolveMimeTypeByExtension(getExtension("report.PDF"));
796
+ `})}),`
797
+ `,(0,c.jsx)(t.h3,{id:`поддерживаемая-таблица`,children:`Поддерживаемая таблица`}),`
798
+ `,(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:`MIME`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`pdf`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/pdf`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`doc`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/msword`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`docx`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/vnd.openxmlformats-officedocument.wordprocessingml.document`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`xls`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/vnd.ms-excel`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`xlsx`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ppt`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/vnd.ms-powerpoint`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`pptx`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/vnd.openxmlformats-officedocument.presentationml.presentation`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`txt`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`text/plain; charset=UTF-8`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`rtf`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/rtf`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`odt`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/vnd.oasis.opendocument.text`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`zip`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/zip`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`rar`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`application/vnd.rar`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`jpg`}),`, `,(0,c.jsx)(t.code,{children:`jpeg`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/jpeg`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`png`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/png`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`gif`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/gif`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`webp`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`image/webp`})})]})]})]}),`
799
+ `,(0,c.jsxs)(t.p,{children:[`Любое другое значение возвращает `,(0,c.jsx)(t.code,{children:`application/octet-stream`}),`. Функция:`]}),`
800
+ `,(0,c.jsxs)(t.ul,{children:[`
801
+ `,(0,c.jsx)(t.li,{children:`обрезает внешние пробелы;`}),`
802
+ `,(0,c.jsx)(t.li,{children:`приводит регистр к lowercase;`}),`
803
+ `,(0,c.jsx)(t.li,{children:`не бросает ошибку для неизвестного расширения;`}),`
804
+ `,(0,c.jsx)(t.li,{children:`не анализирует имя файла;`}),`
805
+ `,(0,c.jsx)(t.li,{children:`не читает байты;`}),`
806
+ `,(0,c.jsxs)(t.li,{children:[`не использует `,(0,c.jsx)(t.code,{children:`File.type`}),`;`]}),`
807
+ `,(0,c.jsx)(t.li,{children:`не доказывает реальный формат.`}),`
808
+ `]}),`
809
+ `,(0,c.jsxs)(t.p,{children:[`Обратите внимание: `,(0,c.jsx)(t.code,{children:`ImageMime`}),` и таблица расширений не идентичны. Например, `,(0,c.jsx)(t.code,{children:`image/avif`}),` и `,(0,c.jsx)(t.code,{children:`image/svg+xml`}),` есть в `,(0,c.jsx)(t.code,{children:`ImageMime`}),`, но расширения `,(0,c.jsx)(t.code,{children:`avif`}),` и `,(0,c.jsx)(t.code,{children:`svg`}),` отсутствуют в resolver и дадут fallback.`]}),`
810
+ `,(0,c.jsxs)(t.h2,{id:`helpers-для-content-disposition`,children:[`Helpers для `,(0,c.jsx)(t.code,{children:`Content-Disposition`})]}),`
811
+ `,(0,c.jsxs)(t.h3,{id:`что-такое-content-disposition`,children:[`Что такое `,(0,c.jsx)(t.code,{children:`Content-Disposition`})]}),`
812
+ `,(0,c.jsx)(t.p,{children:`HTTP-заголовок сообщает клиенту, как обрабатывать response body:`}),`
813
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`Content-Disposition: attachment; filename="report.pdf"
814
+ `})}),`
815
+ `,(0,c.jsx)(t.p,{children:`Основные варианты:`}),`
816
+ `,(0,c.jsxs)(t.ul,{children:[`
817
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`attachment`}),` — предложить скачивание;`]}),`
818
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`inline`}),` — предложить показать содержимое внутри браузера, если он умеет.`]}),`
819
+ `]}),`
820
+ `,(0,c.jsx)(t.p,{children:`Это рекомендация браузеру, а не абсолютная команда. Итоговое поведение зависит от browser, MIME, security policy и response headers.`}),`
821
+ `,(0,c.jsx)(t.h3,{id:`createcontentdispositionheader`,children:(0,c.jsx)(t.code,{children:`createContentDispositionHeader`})}),`
822
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type ContentDispositionKind = "inline" | "attachment";
823
+
824
+ type ContentDispositionOptions = {
825
+ readonly disposition: ContentDispositionKind;
826
+ readonly filename: string;
827
+ readonly asciiFallback?: string;
828
+ };
829
+
830
+ function createContentDispositionHeader(options: ContentDispositionOptions): string;
831
+ `})}),`
832
+ `,(0,c.jsx)(t.p,{children:`Пример для server response:`}),`
833
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const filename = "Отчёт № 42.pdf";
834
+
835
+ const headers = new Headers({
836
+ "Content-Type": "application/pdf",
837
+ "Content-Disposition": createContentDispositionHeader({
838
+ disposition: "attachment",
839
+ filename,
840
+ asciiFallback: "report-42.pdf"
841
+ })
842
+ });
843
+
844
+ return new Response(pdfBytes, { headers });
845
+ `})}),`
846
+ `,(0,c.jsx)(t.p,{children:`Функция создаёт оба параметра:`}),`
847
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`attachment; filename="_____ _ 42.pdf"; filename*=UTF-8''%D0%9E%D1%82%D1%87%D1%91%D1%82%20%E2%84%96%2042.pdf
848
+ `})}),`
849
+ `,(0,c.jsxs)(t.ul,{children:[`
850
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`filename=`}),` нужен старым/ограниченным клиентам и содержит ASCII;`]}),`
851
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`filename*=`}),` содержит UTF-8 имя, percent-encoded по RFC 5987-подобному формату.`]}),`
852
+ `]}),`
853
+ `,(0,c.jsxs)(t.p,{children:[`Default `,(0,c.jsx)(t.code,{children:`asciiFallback`}),` равен `,(0,c.jsx)(t.code,{children:`file.bin`}),`. Это резерв для пустого/неполучившегося имени, а не принудительная замена legacy-параметра для любого Unicode filename.`]}),`
854
+ `,(0,c.jsx)(t.h3,{id:`sanitizefilename`,children:(0,c.jsx)(t.code,{children:`sanitizeFileName`})}),`
855
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function sanitizeFileName(value: string, fallback = "file"): string;
856
+ `})}),`
857
+ `,(0,c.jsx)(t.p,{children:`Функция:`}),`
858
+ `,(0,c.jsxs)(t.ol,{children:[`
859
+ `,(0,c.jsxs)(t.li,{children:[`заменяет `,(0,c.jsx)(t.code,{children:`\\r`}),` и `,(0,c.jsx)(t.code,{children:`\\n`}),` на пробел;`]}),`
860
+ `,(0,c.jsxs)(t.li,{children:[`заменяет двойную кавычку `,(0,c.jsx)(t.code,{children:`"`}),` на одинарную `,(0,c.jsx)(t.code,{children:`'`}),`;`]}),`
861
+ `,(0,c.jsxs)(t.li,{children:[`вызывает `,(0,c.jsx)(t.code,{children:`trim()`}),`;`]}),`
862
+ `,(0,c.jsx)(t.li,{children:`возвращает fallback, если строка стала пустой.`}),`
863
+ `]}),`
864
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`sanitizeFileName(' "report"\\n.pdf ');
865
+ // "'report' .pdf"
866
+
867
+ sanitizeFileName("\\n\\r", "document.bin");
868
+ // "document.bin"
869
+ `})}),`
870
+ `,(0,c.jsx)(t.p,{children:`Это узкий helper для header filename. Он не является полноценным sanitizer пути или имени файла операционной системы:`}),`
871
+ `,(0,c.jsxs)(t.ul,{children:[`
872
+ `,(0,c.jsxs)(t.li,{children:[`не удаляет `,(0,c.jsx)(t.code,{children:`/`}),`;`]}),`
873
+ `,(0,c.jsxs)(t.li,{children:[`не удаляет `,(0,c.jsx)(t.code,{children:`..`}),`;`]}),`
874
+ `,(0,c.jsx)(t.li,{children:`не удаляет все системные запрещённые символы;`}),`
875
+ `,(0,c.jsx)(t.li,{children:`не предотвращает path traversal при записи на диск;`}),`
876
+ `,(0,c.jsx)(t.li,{children:`не гарантирует уникальность;`}),`
877
+ `,(0,c.jsx)(t.li,{children:`не ограничивает длину.`}),`
878
+ `]}),`
879
+ `,(0,c.jsx)(t.p,{children:`Никогда не используйте результат как доверенный server filesystem path. На backend создавайте собственное безопасное имя/ID и храните пользовательское имя только как metadata.`}),`
880
+ `,(0,c.jsx)(t.h3,{id:`createasciifilenamefallback`,children:(0,c.jsx)(t.code,{children:`createAsciiFilenameFallback`})}),`
881
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function createAsciiFilenameFallback(value: string, fallback = "file.bin"): string;
882
+ `})}),`
883
+ `,(0,c.jsx)(t.p,{children:`Функция:`}),`
884
+ `,(0,c.jsxs)(t.ul,{children:[`
885
+ `,(0,c.jsx)(t.li,{children:`заменяет CR/LF пробелом;`}),`
886
+ `,(0,c.jsxs)(t.li,{children:[`заменяет `,(0,c.jsx)(t.code,{children:`"`}),` и `,(0,c.jsx)(t.code,{children:`\\`}),` символом `,(0,c.jsx)(t.code,{children:`_`}),`;`]}),`
887
+ `,(0,c.jsxs)(t.li,{children:[`заменяет каждый символ вне printable ASCII `,(0,c.jsx)(t.code,{children:`0x20–0x7E`}),` на `,(0,c.jsx)(t.code,{children:`_`}),`;`]}),`
888
+ `,(0,c.jsx)(t.li,{children:`схлопывает последовательности whitespace в один пробел;`}),`
889
+ `,(0,c.jsx)(t.li,{children:`обрезает края;`}),`
890
+ `,(0,c.jsx)(t.li,{children:`возвращает fallback для пустого результата.`}),`
891
+ `]}),`
892
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`createAsciiFilenameFallback("Отчёт 42.pdf");
893
+ // "_____ 42.pdf"
894
+
895
+ createAsciiFilenameFallback('report\\\\"42.pdf');
896
+ // "report__42.pdf"
897
+ `})}),`
898
+ `,(0,c.jsxs)(t.p,{children:[`Unicode не транслитерируется. Кириллица не превращается в латиницу, а заменяется `,(0,c.jsx)(t.code,{children:`_`}),`.`]}),`
899
+ `,(0,c.jsxs)(t.p,{children:[`Параметр `,(0,c.jsx)(t.code,{children:`fallback`}),` используется только тогда, когда итоговая строка пуста. Он не заменяет непустой результат из подчёркиваний:`]}),`
900
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`createAsciiFilenameFallback(" ", "contract.pdf");
901
+ // "contract.pdf"
902
+
903
+ createAsciiFilenameFallback("Договор.pdf", "contract.pdf");
904
+ // "_______.pdf", а не "contract.pdf"
905
+ `})}),`
906
+ `,(0,c.jsxs)(t.p,{children:[`Поэтому текущий `,(0,c.jsx)(t.code,{children:`createContentDispositionHeader`}),` не позволяет одновременно принудительно задать отдельное красивое legacy-имя и сохранить другое Unicode-имя в `,(0,c.jsx)(t.code,{children:`filename*`}),`. Для Unicode filename legacy-параметр обычно содержит `,(0,c.jsx)(t.code,{children:`_`}),`, а современный клиент должен использовать корректный `,(0,c.jsx)(t.code,{children:`filename*`}),`.`]}),`
907
+ `,(0,c.jsx)(t.h3,{id:`encodefilenamerfc5987`,children:(0,c.jsx)(t.code,{children:`encodeFilenameRFC5987`})}),`
908
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function encodeFilenameRFC5987(value: string): string;
909
+ `})}),`
910
+ `,(0,c.jsxs)(t.p,{children:[`Helper использует `,(0,c.jsx)(t.code,{children:`encodeURIComponent`}),`, дополнительно percent-encode символы `,(0,c.jsx)(t.code,{children:`'`}),`, `,(0,c.jsx)(t.code,{children:`(`}),`, `,(0,c.jsx)(t.code,{children:`)`}),`, `,(0,c.jsx)(t.code,{children:`*`}),` и оставляет percent hex в uppercase для этих дополнительных замен.`]}),`
911
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`encodeFilenameRFC5987("Отчёт (1).pdf");
912
+ // percent-encoded UTF-8 строка
913
+ `})}),`
914
+ `,(0,c.jsxs)(t.p,{children:[`Функция возвращает только encoded filename, без префикса `,(0,c.jsx)(t.code,{children:`UTF-8''`}),` и без `,(0,c.jsx)(t.code,{children:`filename*=`}),`. Для готового заголовка почти всегда выбирайте `,(0,c.jsx)(t.code,{children:`createContentDispositionHeader`}),`.`]}),`
915
+ `,(0,c.jsxs)(t.p,{children:[`Как и `,(0,c.jsx)(t.code,{children:`encodeURIComponent`}),`, helper может бросить `,(0,c.jsx)(t.code,{children:`URIError`}),` для некорректной Unicode-строки с одиночным surrogate code unit.`]}),`
916
+ `,(0,c.jsx)(t.h3,{id:`порядок-сборки-заголовка`,children:`Порядок сборки заголовка`}),`
917
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`createContentDispositionHeader`}),`:`]}),`
918
+ `,(0,c.jsxs)(t.ol,{children:[`
919
+ `,(0,c.jsxs)(t.li,{children:[`вызывает `,(0,c.jsx)(t.code,{children:`sanitizeFileName(filename, asciiFallback)`}),`;`]}),`
920
+ `,(0,c.jsx)(t.li,{children:`создаёт ASCII-вариант;`}),`
921
+ `,(0,c.jsx)(t.li,{children:`encode исходное очищенное Unicode-имя;`}),`
922
+ `,(0,c.jsxs)(t.li,{children:[`собирает `,(0,c.jsx)(t.code,{children:`disposition`}),`, `,(0,c.jsx)(t.code,{children:`filename`}),` и `,(0,c.jsx)(t.code,{children:`filename*`}),`.`]}),`
923
+ `]}),`
924
+ `,(0,c.jsxs)(t.p,{children:[`Значение `,(0,c.jsx)(t.code,{children:`disposition`}),` ограничено TypeScript union, но runtime-функция отдельно не валидирует вход. Не обходите тип через `,(0,c.jsx)(t.code,{children:`as`}),` и не принимайте это поле напрямую из недоверенного request.`]}),`
925
+ `,(0,c.jsx)(t.h2,{id:`низкоуровневые-helpers`,children:`Низкоуровневые helpers`}),`
926
+ `,(0,c.jsxs)(t.p,{children:[`Большинству приложений достаточно `,(0,c.jsx)(t.code,{children:`readFile`}),` и `,(0,c.jsx)(t.code,{children:`readImageFile`}),`. Следующие функции экспортированы для инфраструктуры, UI primitives и тестов.`]}),`
927
+ `,(0,c.jsx)(t.h3,{id:`buildmeta`,children:(0,c.jsx)(t.code,{children:`buildMeta`})}),`
928
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function buildMeta(file: File): FileMeta;
929
+ `})}),`
930
+ `,(0,c.jsxs)(t.p,{children:[`Создаёт новый объект из четырёх полей `,(0,c.jsx)(t.code,{children:`File`}),`:`]}),`
931
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const meta = buildMeta(file);
932
+
933
+ console.log(meta.name);
934
+ console.log(meta.mime);
935
+ console.log(meta.size);
936
+ console.log(meta.lastModified);
937
+ `})}),`
938
+ `,(0,c.jsx)(t.p,{children:`Функция не читает байты и не валидирует metadata.`}),`
939
+ `,(0,c.jsx)(t.h3,{id:`assertnotaborted`,children:(0,c.jsx)(t.code,{children:`assertNotAborted`})}),`
940
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function assertNotAborted(signal?: AbortSignal): void;
941
+ `})}),`
942
+ `,(0,c.jsxs)(t.ul,{children:[`
943
+ `,(0,c.jsx)(t.li,{children:`ничего не делает без signal;`}),`
944
+ `,(0,c.jsx)(t.li,{children:`ничего не делает для активного signal;`}),`
945
+ `,(0,c.jsxs)(t.li,{children:[`бросает `,(0,c.jsx)(t.code,{children:`ReadFileError('READ_ABORTED')`}),`, если `,(0,c.jsx)(t.code,{children:`signal.aborted === true`}),`.`]}),`
946
+ `]}),`
947
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const controller = new AbortController();
948
+ controller.abort();
949
+
950
+ assertNotAborted(controller.signal);
951
+ // throw ReadFileError
952
+ `})}),`
953
+ `,(0,c.jsx)(t.h3,{id:`attachabort`,children:(0,c.jsx)(t.code,{children:`attachAbort`})}),`
954
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function attachAbort(signal: AbortSignal | undefined, reader: FileReader): (() => void) | undefined;
955
+ `})}),`
956
+ `,(0,c.jsxs)(t.p,{children:[`Функция подписывает signal на `,(0,c.jsx)(t.code,{children:`reader.abort()`}),` и возвращает cleanup:`]}),`
957
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const reader = new FileReader();
958
+ const detach = attachAbort(controller.signal, reader);
959
+
960
+ try {
961
+ reader.readAsArrayBuffer(file);
962
+ } finally {
963
+ detach?.();
964
+ }
965
+ `})}),`
966
+ `,(0,c.jsx)(t.p,{children:`Важные детали:`}),`
967
+ `,(0,c.jsxs)(t.ul,{children:[`
968
+ `,(0,c.jsxs)(t.li,{children:[`без signal возвращается `,(0,c.jsx)(t.code,{children:`undefined`}),`;`]}),`
969
+ `,(0,c.jsxs)(t.li,{children:[`listener использует `,(0,c.jsx)(t.code,{children:`{ once: true }`}),`;`]}),`
970
+ `,(0,c.jsxs)(t.li,{children:[`исключение от `,(0,c.jsx)(t.code,{children:`reader.abort()`}),` игнорируется;`]}),`
971
+ `,(0,c.jsx)(t.li,{children:`helper сам не проверяет уже отменённый signal;`}),`
972
+ `,(0,c.jsxs)(t.li,{children:[`при прямом использовании сначала вызовите `,(0,c.jsx)(t.code,{children:`assertNotAborted(signal)`}),`;`]}),`
973
+ `,(0,c.jsx)(t.li,{children:`вызывающий код отвечает за cleanup после success/error, если использует helper отдельно.`}),`
974
+ `]}),`
975
+ `,(0,c.jsx)(t.h3,{id:`assert-helpers`,children:`Assert helpers`}),`
976
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function assertValidAllowedMime(allowedMime?: readonly string[]): void;
977
+ function assertValidAccept(accept?: readonly string[]): void;
978
+ `})}),`
979
+ `,(0,c.jsxs)(t.p,{children:[`Они проверяют конфигурацию и бросают обычный `,(0,c.jsx)(t.code,{children:`Error`}),`. Они не проверяют `,(0,c.jsx)(t.code,{children:`File`}),`, не читают байты и автоматически не вызываются из `,(0,c.jsx)(t.code,{children:`readFile`}),`.`]}),`
980
+ `,(0,c.jsx)(t.h2,{id:`готовые-рецепты`,children:`Готовые рецепты`}),`
981
+ `,(0,c.jsx)(t.h3,{id:`обычный-multipart-upload-без-предварительного-чтения`,children:`Обычный multipart upload без предварительного чтения`}),`
982
+ `,(0,c.jsxs)(t.p,{children:[`Если backend принимает `,(0,c.jsx)(t.code,{children:`multipart/form-data`}),`, используйте исходный `,(0,c.jsx)(t.code,{children:`File`}),`:`]}),`
983
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`async function uploadDocument(file: File) {
984
+ const body = new FormData();
985
+ body.append("document", file, file.name);
986
+
987
+ const response = await fetch("/api/documents", {
988
+ method: "POST",
989
+ body
990
+ });
991
+
992
+ if (!response.ok) {
993
+ throw new Error(\`Upload failed: \${response.status}\`);
994
+ }
995
+ }
996
+ `})}),`
997
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`readFile`}),` нужен только если перед upload требуется client-side представление, preview или специальный бинарный contract.`]}),`
998
+ `,(0,c.jsxs)(t.h3,{id:`проверить-файл-и-оставить-исходный-file`,children:[`Проверить файл и оставить исходный `,(0,c.jsx)(t.code,{children:`File`})]}),`
999
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file, {
1000
+ allowedMime: ["application/pdf"],
1001
+ maxBytes: 10 * 1024 * 1024,
1002
+ mode: "array-buffer"
1003
+ });
1004
+
1005
+ const body = new FormData();
1006
+ body.append("document", result.file, result.meta.name);
1007
+ `})}),`
1008
+ `,(0,c.jsx)(t.p,{children:`Но помните: чтение буфера уже создало полную дополнительную копию файла в памяти. Не делайте этого без необходимости.`}),`
1009
+ `,(0,c.jsx)(t.h3,{id:`безопасно-разобрать-union-результата`,children:`Безопасно разобрать union результата`}),`
1010
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function describeResult(result: ReadFileResult): string {
1011
+ if (result.mode === "data-url") {
1012
+ return \`Data URL length: \${result.dataUrl.length}\`;
1013
+ }
1014
+
1015
+ return \`Buffer bytes: \${result.buffer.byteLength}\`;
1016
+ }
1017
+ `})}),`
1018
+ `,(0,c.jsx)(t.h3,{id:`preview-изображения-с-проверкой-размеров`,children:`Preview изображения с проверкой размеров`}),`
1019
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`async function validateAvatar(file: File) {
1020
+ const result = await readImageFile(file, {
1021
+ allowedMime: ["image/jpeg", "image/png", "image/webp"],
1022
+ maxBytes: 2 * 1024 * 1024
1023
+ });
1024
+
1025
+ if (!result.dimensions) {
1026
+ throw new Error("Не удалось определить размеры изображения");
1027
+ }
1028
+
1029
+ const { width, height } = result.dimensions;
1030
+
1031
+ if (width < 256 || height < 256) {
1032
+ throw new Error("Изображение должно быть не меньше 256 × 256 пикселей");
1033
+ }
1034
+
1035
+ if (width * height > 16_000_000) {
1036
+ throw new Error("Изображение содержит слишком много пикселей");
1037
+ }
1038
+
1039
+ return result;
1040
+ }
1041
+ `})}),`
1042
+ `,(0,c.jsx)(t.p,{children:`Проверка после decode полезна для UX, но backend всё равно должен проверять файл сам.`}),`
1043
+ `,(0,c.jsx)(t.h3,{id:`последовательная-обработка-нескольких-файлов`,children:`Последовательная обработка нескольких файлов`}),`
1044
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`async function readDocuments(files: readonly File[]) {
1045
+ const results: ReadFileResult[] = [];
1046
+
1047
+ for (const file of files) {
1048
+ try {
1049
+ const result = await readFile(file, {
1050
+ mode: "array-buffer",
1051
+ allowedMime: ["application/pdf"],
1052
+ maxBytes: 10 * 1024 * 1024
1053
+ });
1054
+
1055
+ results.push(result);
1056
+ } catch (error) {
1057
+ console.error(\`Не удалось прочитать \${file.name}\`, error);
1058
+ }
1059
+ }
1060
+
1061
+ return results;
1062
+ }
1063
+ `})}),`
1064
+ `,(0,c.jsxs)(t.p,{children:[`Последовательное чтение ограничивает пик одновременной памяти. `,(0,c.jsx)(t.code,{children:`Promise.all(files.map(...))`}),` запускает все `,(0,c.jsx)(t.code,{children:`FileReader`}),` сразу и может быть тяжёлым для больших файлов.`]}),`
1065
+ `,(0,c.jsxs)(t.h3,{id:`mime-и-content-disposition-для-response`,children:[`MIME и `,(0,c.jsx)(t.code,{children:`Content-Disposition`}),` для response`]}),`
1066
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function createDownloadHeaders(fileName: string): Headers {
1067
+ const dotIndex = fileName.lastIndexOf(".");
1068
+ const extension = dotIndex >= 0 ? fileName.slice(dotIndex + 1) : "";
1069
+
1070
+ return new Headers({
1071
+ "Content-Type": resolveMimeTypeByExtension(extension),
1072
+ "Content-Disposition": createContentDispositionHeader({
1073
+ disposition: "attachment",
1074
+ filename: fileName,
1075
+ asciiFallback: "download.bin"
1076
+ })
1077
+ });
1078
+ }
1079
+ `})}),`
1080
+ `,(0,c.jsx)(t.p,{children:`Это определяет MIME только по имени. Для server response надёжнее использовать подтверждённый MIME из trusted storage/parser, если он доступен.`}),`
1081
+ `,(0,c.jsx)(t.h3,{id:`скачать-прочитанный-файл`,children:`Скачать прочитанный файл`}),`
1082
+ `,(0,c.jsxs)(t.p,{children:[`Модуль `,(0,c.jsx)(t.code,{children:`/file`}),` читает, но не скачивает. Для `,(0,c.jsx)(t.code,{children:`Blob`}),` используйте `,(0,c.jsx)(t.code,{children:`/dom`}),`:`]}),`
1083
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { downloadFileFromBlob } from "@ryuzaki13/react-foundation-lib/dom";
1084
+ import { readFile } from "@ryuzaki13/react-foundation-lib/file";
1085
+
1086
+ const result = await readFile(file, { mode: "array-buffer" });
1087
+ const blob = new Blob([result.buffer], { type: result.meta.mime });
1088
+
1089
+ downloadFileFromBlob(blob, result.meta.name);
1090
+ `})}),`
1091
+ `,(0,c.jsxs)(t.p,{children:[`Если у вас уже есть исходный `,(0,c.jsx)(t.code,{children:`File`}),`, он сам является `,(0,c.jsx)(t.code,{children:`Blob`}),`, поэтому повторное чтение обычно не нужно:`]}),`
1092
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`downloadFileFromBlob(file, file.name);
1093
+ `})}),`
1094
+ `,(0,c.jsx)(t.h2,{id:`безопасность`,children:`Безопасность`}),`
1095
+ `,(0,c.jsx)(t.h3,{id:`client-side-проверка-не-является-защитой-backend`,children:`Client-side проверка не является защитой backend`}),`
1096
+ `,(0,c.jsx)(t.p,{children:`Пользователь контролирует browser request. Он может:`}),`
1097
+ `,(0,c.jsxs)(t.ul,{children:[`
1098
+ `,(0,c.jsx)(t.li,{children:`изменить filename;`}),`
1099
+ `,(0,c.jsx)(t.li,{children:`изменить MIME;`}),`
1100
+ `,(0,c.jsx)(t.li,{children:`обойти UI;`}),`
1101
+ `,(0,c.jsx)(t.li,{children:`отправить request напрямую;`}),`
1102
+ `,(0,c.jsx)(t.li,{children:`поместить недопустимые байты в разрешённое расширение.`}),`
1103
+ `]}),`
1104
+ `,(0,c.jsx)(t.p,{children:`Backend обязан повторно проверить:`}),`
1105
+ `,(0,c.jsxs)(t.ul,{children:[`
1106
+ `,(0,c.jsx)(t.li,{children:`authentication и authorization;`}),`
1107
+ `,(0,c.jsx)(t.li,{children:`размер request/file;`}),`
1108
+ `,(0,c.jsx)(t.li,{children:`реальный формат;`}),`
1109
+ `,(0,c.jsx)(t.li,{children:`допустимый MIME;`}),`
1110
+ `,(0,c.jsx)(t.li,{children:`структуру содержимого;`}),`
1111
+ `,(0,c.jsx)(t.li,{children:`имя и storage key;`}),`
1112
+ `,(0,c.jsx)(t.li,{children:`антивирусную/контентную policy, если она нужна продукту.`}),`
1113
+ `]}),`
1114
+ `,(0,c.jsxs)(t.h3,{id:`filetype-недоверенный`,children:[(0,c.jsx)(t.code,{children:`File.type`}),` недоверенный`]}),`
1115
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`allowedMime`}),` сравнивает только `,(0,c.jsx)(t.code,{children:`file.type`}),`. Модуль не открывает заголовок файла, чтобы подтвердить PNG/PDF/ZIP signature.`]}),`
1116
+ `,(0,c.jsx)(t.h3,{id:`расширение-недоверенное`,children:`Расширение недоверенное`}),`
1117
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`resolveMimeTypeByExtension('pdf')`}),` всегда вернёт `,(0,c.jsx)(t.code,{children:`application/pdf`}),`, даже если реальные байты являются executable. Это mapping, не detector.`]}),`
1118
+ `,(0,c.jsx)(t.h3,{id:`svg-требует-отдельной-policy`,children:`SVG требует отдельной policy`}),`
1119
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`ImageMime`}),` включает `,(0,c.jsx)(t.code,{children:`image/svg+xml`}),`. SVG является активным XML-based форматом и может содержать ссылки и поведение, опасное в некоторых контекстах embedding/rendering. Не считайте разрешённый MIME достаточной sanitization.`]}),`
1120
+ `,(0,c.jsxs)(t.p,{children:[`Если продукту не нужен SVG, не добавляйте его в `,(0,c.jsx)(t.code,{children:`allowedMime`}),`. Если нужен — применяйте отдельную доверенную sanitization и строгую content security policy.`]}),`
1121
+ `,(0,c.jsx)(t.h3,{id:`размер-файла-не-равен-размеру-после-decode`,children:`Размер файла не равен размеру после decode`}),`
1122
+ `,(0,c.jsx)(t.p,{children:`Маленький compressed image может развернуться в огромный bitmap. Например, приблизительная память RGBA:`}),`
1123
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`width × height × 4 bytes
1124
+ `})}),`
1125
+ `,(0,c.jsxs)(t.p,{children:[`Изображение 10 000 × 10 000 может потребовать около 400 MB только для пикселей. Ограничивайте не только `,(0,c.jsx)(t.code,{children:`maxBytes`}),`, но и dimensions/pixel count после decode; backend должен делать собственную проверку до тяжёлой обработки.`]}),`
1126
+ `,(0,c.jsx)(t.h3,{id:`filename-и-header-injection`,children:`Filename и header injection`}),`
1127
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`sanitizeFileName`}),` заменяет CR/LF и кавычки для header-сценария. Это снижает риск сырого переноса строки в создаваемом filename, но не делает значение безопасным filesystem path.`]}),`
1128
+ `,(0,c.jsxs)(t.p,{children:[`Передавайте короткий управляемый `,(0,c.jsx)(t.code,{children:`asciiFallback`}),` на случай пустого имени. Не ожидайте, что он переопределит legacy-вариант непустого Unicode filename. Не разрешайте внешнему input управлять `,(0,c.jsx)(t.code,{children:`disposition`}),` через unsafe cast.`]}),`
1129
+ `,(0,c.jsx)(t.h3,{id:`data-url-может-содержать-чувствительные-данные`,children:`Data URL может содержать чувствительные данные`}),`
1130
+ `,(0,c.jsx)(t.p,{children:`Не отправляйте data URL в analytics, logs или error reports. Он содержит весь файл. Не вставляйте произвольный data URL в опасные execution contexts.`}),`
1131
+ `,(0,c.jsx)(t.h2,{id:`производительность-и-память`,children:`Производительность и память`}),`
1132
+ `,(0,c.jsxs)(t.h3,{id:`filereader-читает-файл-целиком`,children:[(0,c.jsx)(t.code,{children:`FileReader`}),` читает файл целиком`]}),`
1133
+ `,(0,c.jsx)(t.p,{children:`Оба режима создают полное представление данных в памяти:`}),`
1134
+ `,(0,c.jsxs)(t.ul,{children:[`
1135
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`array-buffer`}),` — буфер размером примерно с файл;`]}),`
1136
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`data-url`}),` — большая строка с base64;`]}),`
1137
+ `,(0,c.jsxs)(t.li,{children:[`результат также сохраняет ссылку на исходный `,(0,c.jsx)(t.code,{children:`File`}),`.`]}),`
1138
+ `]}),`
1139
+ `,(0,c.jsx)(t.p,{children:`Для очень больших файлов нужен streaming/chunked API, которого в этом модуле нет.`}),`
1140
+ `,(0,c.jsx)(t.h3,{id:`data-url-дороже`,children:`Data URL дороже`}),`
1141
+ `,(0,c.jsx)(t.p,{children:`Base64 обычно увеличивает payload примерно на 33%, затем JavaScript engine хранит строку в своём внутреннем формате. Не держите несколько больших data URLs в React state, query cache или global store.`}),`
1142
+ `,(0,c.jsx)(t.h3,{id:`изображение-декодируется-дополнительно`,children:`Изображение декодируется дополнительно`}),`
1143
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`readImageFile`}),` в data URL режиме одновременно может удерживать:`]}),`
1144
+ `,(0,c.jsxs)(t.ul,{children:[`
1145
+ `,(0,c.jsxs)(t.li,{children:[`исходный `,(0,c.jsx)(t.code,{children:`File`}),`;`]}),`
1146
+ `,(0,c.jsx)(t.li,{children:`data URL;`}),`
1147
+ `,(0,c.jsx)(t.li,{children:`decoded image bitmap;`}),`
1148
+ `,(0,c.jsx)(t.li,{children:`объект результата.`}),`
1149
+ `]}),`
1150
+ `,(0,c.jsxs)(t.p,{children:[`Устанавливайте небольшой `,(0,c.jsx)(t.code,{children:`maxBytes`}),`, ограничивайте dimensions и освобождайте ненужные ссылки.`]}),`
1151
+ `,(0,c.jsx)(t.h3,{id:`несколько-файлов`,children:`Несколько файлов`}),`
1152
+ `,(0,c.jsxs)(t.p,{children:[`Не запускайте неограниченный `,(0,c.jsx)(t.code,{children:`Promise.all`}),` для большого списка. Используйте последовательную или ограниченно-параллельную обработку.`]}),`
1153
+ `,(0,c.jsx)(t.h3,{id:`upload-без-чтения`,children:`Upload без чтения`}),`
1154
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`fetch`}),`/`,(0,c.jsx)(t.code,{children:`FormData`}),` умеют отправить `,(0,c.jsx)(t.code,{children:`File`}),` напрямую. Это часто быстрее и экономнее, чем `,(0,c.jsx)(t.code,{children:`File → ArrayBuffer → Blob → upload`}),`.`]}),`
1155
+ `,(0,c.jsx)(t.h2,{id:`тестирование`,children:`Тестирование`}),`
1156
+ `,(0,c.jsx)(t.h3,{id:`что-тестировать-у-consumer`,children:`Что тестировать у consumer`}),`
1157
+ `,(0,c.jsxs)(t.ul,{children:[`
1158
+ `,(0,c.jsx)(t.li,{children:`отсутствие выбранного файла;`}),`
1159
+ `,(0,c.jsx)(t.li,{children:`допустимый MIME;`}),`
1160
+ `,(0,c.jsx)(t.li,{children:`запрещённый MIME;`}),`
1161
+ `,(0,c.jsxs)(t.li,{children:[`размер меньше, равный и больше `,(0,c.jsx)(t.code,{children:`maxBytes`}),`;`]}),`
1162
+ `,(0,c.jsx)(t.li,{children:`оба режима результата;`}),`
1163
+ `,(0,c.jsx)(t.li,{children:`abort до начала и во время чтения;`}),`
1164
+ `,(0,c.jsxs)(t.li,{children:[`обработку каждого `,(0,c.jsx)(t.code,{children:`ReadFileError.code`}),`;`]}),`
1165
+ `,(0,c.jsxs)(t.li,{children:[`отдельную обработку `,(0,c.jsx)(t.code,{children:`ReadImageError`}),` и `,(0,c.jsx)(t.code,{children:`ReadFileError`}),`;`]}),`
1166
+ `,(0,c.jsx)(t.li,{children:`decode failure изображения;`}),`
1167
+ `,(0,c.jsxs)(t.li,{children:[`Unicode filename в `,(0,c.jsx)(t.code,{children:`Content-Disposition`}),`;`]}),`
1168
+ `,(0,c.jsx)(t.li,{children:`неизвестное расширение MIME resolver.`}),`
1169
+ `]}),`
1170
+ `,(0,c.jsxs)(t.h3,{id:`создание-file-в-browser-like-тесте`,children:[`Создание `,(0,c.jsx)(t.code,{children:`File`}),` в browser-like тесте`]}),`
1171
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const file = new File([new Uint8Array([1, 2, 3])], "example.bin", {
1172
+ type: "application/octet-stream",
1173
+ lastModified: 1_700_000_000_000
1174
+ });
1175
+ `})}),`
1176
+ `,(0,c.jsxs)(t.h3,{id:`проверка-readfile`,children:[`Проверка `,(0,c.jsx)(t.code,{children:`readFile`})]}),`
1177
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`it("читает файл как ArrayBuffer", async () => {
1178
+ const file = new File([new Uint8Array([1, 2, 3])], "example.bin", {
1179
+ type: "application/octet-stream"
1180
+ });
1181
+
1182
+ const result = await readFile(file, { mode: "array-buffer" });
1183
+
1184
+ expect(result.mode).toBe("array-buffer");
1185
+ expect([...new Uint8Array(result.buffer)]).toEqual([1, 2, 3]);
1186
+ expect(result.file).toBe(file);
1187
+ expect(result.meta.name).toBe("example.bin");
1188
+ });
1189
+ `})}),`
1190
+ `,(0,c.jsx)(t.h3,{id:`проверка-ошибки-размера`,children:`Проверка ошибки размера`}),`
1191
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`it("отклоняет слишком большой файл", async () => {
1192
+ const file = new File([new Uint8Array([1, 2, 3])], "example.bin");
1193
+
1194
+ await expect(readFile(file, { maxBytes: 2 })).rejects.toMatchObject({
1195
+ name: "ReadFileError",
1196
+ code: "FILE_TOO_LARGE"
1197
+ });
1198
+ });
1199
+ `})}),`
1200
+ `,(0,c.jsx)(t.h3,{id:`проверка-pure-helpers`,children:`Проверка pure helpers`}),`
1201
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`expect(resolveMimeTypeByExtension(" PDF ")).toBe("application/pdf");
1202
+ expect(resolveMimeTypeByExtension("csv")).toBe("application/octet-stream");
1203
+
1204
+ const contentDisposition = createContentDispositionHeader({
1205
+ disposition: "attachment",
1206
+ filename: "Отчёт.pdf",
1207
+ asciiFallback: "report.pdf"
1208
+ });
1209
+
1210
+ expect(contentDisposition).toContain('filename="_____.pdf"');
1211
+ expect(contentDisposition).toContain("filename*=UTF-8''%D0%9E");
1212
+ `})}),`
1213
+ `,(0,c.jsx)(t.h3,{id:`особенности-test-environment`,children:`Особенности test environment`}),`
1214
+ `,(0,c.jsxs)(t.ul,{children:[`
1215
+ `,(0,c.jsxs)(t.li,{children:[`в Node environment может не быть `,(0,c.jsx)(t.code,{children:`FileReader`}),`;`]}),`
1216
+ `,(0,c.jsx)(t.li,{children:`jsdom может реализовать не все детали browser decode;`}),`
1217
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`readImageFile`}),` использует глобальный `,(0,c.jsx)(t.code,{children:`Image`}),`, который обычно требуется mock;`]}),`
1218
+ `,(0,c.jsx)(t.li,{children:`не подменяйте только типы: тест должен иметь runtime implementation;`}),`
1219
+ `,(0,c.jsxs)(t.li,{children:[`browser integration test надёжнее проверяет реальный `,(0,c.jsx)(t.code,{children:`Image.onload`}),` и data URL.`]}),`
1220
+ `]}),`
1221
+ `,(0,c.jsxs)(t.p,{children:[`Для unit test `,(0,c.jsx)(t.code,{children:`readImageFile`}),` можно подменить `,(0,c.jsx)(t.code,{children:`globalThis.Image`}),` контролируемым fake, который вызывает `,(0,c.jsx)(t.code,{children:`onload`}),`/`,(0,c.jsx)(t.code,{children:`onerror`}),` и задаёт `,(0,c.jsx)(t.code,{children:`naturalWidth`}),`/`,(0,c.jsx)(t.code,{children:`naturalHeight`}),`.`]}),`
1222
+ `,(0,c.jsx)(t.h2,{id:`известные-ограничения`,children:`Известные ограничения`}),`
1223
+ `,(0,c.jsx)(t.p,{children:`Это не скрытые детали, а часть фактического поведения, которую consumer должен учитывать:`}),`
1224
+ `,(0,c.jsxs)(t.ol,{children:[`
1225
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`accept`}),` внутри `,(0,c.jsx)(t.code,{children:`readFile`}),` использует substring-проверку суффикса и не реализует HTML wildcard semantics.`]}),`
1226
+ `,(0,c.jsxs)(t.li,{children:[`Непустой `,(0,c.jsx)(t.code,{children:`allowedMime`}),` имеет приоритет и отключает проверку `,(0,c.jsx)(t.code,{children:`accept`}),`.`]}),`
1227
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`readImageFile`}),` игнорирует option `,(0,c.jsx)(t.code,{children:`accept`}),`, хотя поле присутствует в унаследованном типе.`]}),`
1228
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`readImageFile`}),` проверяет `,(0,c.jsx)(t.code,{children:`file.type.startsWith('image/')`}),` регистрозависимо и без `,(0,c.jsx)(t.code,{children:`trim()`}),`.`]}),`
1229
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`ReadImageError`}),` создаётся только для `,(0,c.jsx)(t.code,{children:`NOT_AN_IMAGE`}),`; file errors остаются `,(0,c.jsx)(t.code,{children:`ReadFileError`}),`.`]}),`
1230
+ `,(0,c.jsxs)(t.li,{children:[`Browser decode error сейчас отклоняет Promise необёрнутым event, а не `,(0,c.jsx)(t.code,{children:`IMAGE_DECODE_FAILED`}),`.`]}),`
1231
+ `,(0,c.jsxs)(t.li,{children:[`Коды `,(0,c.jsx)(t.code,{children:`NO_FILE`}),` и `,(0,c.jsx)(t.code,{children:`IMAGE_DECODE_FAILED`}),` публичны, но текущие основные функции их не создают.`]}),`
1232
+ `,(0,c.jsxs)(t.li,{children:[`Abort не прерывает ожидание `,(0,c.jsx)(t.code,{children:`Image.onload`}),`/`,(0,c.jsx)(t.code,{children:`Image.onerror`}),` после завершения `,(0,c.jsx)(t.code,{children:`FileReader`}),`.`]}),`
1233
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`assertValidAllowedMime`}),` и `,(0,c.jsx)(t.code,{children:`assertValidAccept`}),` не вызываются автоматически.`]}),`
1234
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`maxBytes`}),` не валидируется как положительное конечное число.`]}),`
1235
+ `,(0,c.jsx)(t.li,{children:`Файл читается целиком; progress и streaming отсутствуют.`}),`
1236
+ `,(0,c.jsx)(t.li,{children:`MIME и extension не сверяются с реальными bytes.`}),`
1237
+ `,(0,c.jsx)(t.li,{children:`Resolver принимает только расширение без точки и поддерживает ограниченную таблицу.`}),`
1238
+ `,(0,c.jsx)(t.li,{children:`Filename helpers предназначены для header, а не для безопасного filesystem path.`}),`
1239
+ `]}),`
1240
+ `,(0,c.jsx)(t.p,{children:`Для нового consumer-кода рекомендуемый безопасный baseline:`}),`
1241
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file, {
1242
+ mode: "array-buffer",
1243
+ allowedMime: ["application/pdf"],
1244
+ maxBytes: 10 * 1024 * 1024,
1245
+ signal
1246
+ });
1247
+ `})}),`
1248
+ `,(0,c.jsx)(t.p,{children:`А для изображения:`}),`
1249
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readImageFile(file, {
1250
+ allowedMime: ["image/jpeg", "image/png", "image/webp"],
1251
+ maxBytes: 5 * 1024 * 1024,
1252
+ signal
1253
+ });
1254
+ `})}),`
1255
+ `,(0,c.jsx)(t.h2,{id:`краткий-справочник-api`,children:`Краткий справочник API`}),`
1256
+ `,(0,c.jsx)(t.h3,{id:`функции-чтения`,children:`Функции чтения`}),`
1257
+ `,(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.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`readFile`})}),(0,c.jsxs)(t.td,{children:[`Проверяет и читает `,(0,c.jsx)(t.code,{children:`File`}),` как data URL или `,(0,c.jsx)(t.code,{children:`ArrayBuffer`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`readImageFile`})}),(0,c.jsxs)(t.td,{children:[`Проверяет `,(0,c.jsx)(t.code,{children:`image/*`}),`, читает файл и в data URL режиме даёт размеры`]})]})]})]}),`
1258
+ `,(0,c.jsx)(t.h3,{id:`проверка-и-инфраструктура-чтения`,children:`Проверка и инфраструктура чтения`}),`
1259
+ `,(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.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`assertValidAllowedMime`})}),(0,c.jsx)(t.td,{children:`Проверяет синтаксис полных MIME без wildcard`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`assertValidAccept`})}),(0,c.jsx)(t.td,{children:`Проверяет синтаксис extension/MIME/wildcard specifiers`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`assertNotAborted`})}),(0,c.jsxs)(t.td,{children:[`Бросает `,(0,c.jsx)(t.code,{children:`READ_ABORTED`}),` для уже отменённого signal`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`attachAbort`})}),(0,c.jsxs)(t.td,{children:[`Связывает signal с `,(0,c.jsx)(t.code,{children:`FileReader.abort()`}),` и возвращает cleanup`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`buildMeta`})}),(0,c.jsxs)(t.td,{children:[`Копирует четыре metadata-поля `,(0,c.jsx)(t.code,{children:`File`}),` в новый объект`]})]})]})]}),`
1260
+ `,(0,c.jsx)(t.h3,{id:`mime-и-http-filename`,children:`MIME и HTTP filename`}),`
1261
+ `,(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.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resolveMimeTypeByExtension`})}),(0,c.jsx)(t.td,{children:`Известное расширение без точки → MIME или octet-stream`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`sanitizeFileName`})}),(0,c.jsx)(t.td,{children:`Узкая очистка CR/LF/кавычек для header filename`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createAsciiFilenameFallback`})}),(0,c.jsxs)(t.td,{children:[`Printable ASCII fallback для legacy `,(0,c.jsx)(t.code,{children:`filename=`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`encodeFilenameRFC5987`})}),(0,c.jsx)(t.td,{children:`Percent-encode Unicode filename`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createContentDispositionHeader`})}),(0,c.jsxs)(t.td,{children:[`Готовая строка `,(0,c.jsx)(t.code,{children:`inline/attachment; filename=...; filename*=...`})]})]})]})]}),`
1262
+ `,(0,c.jsx)(t.h3,{id:`error-classes`,children:`Error classes`}),`
1263
+ `,(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.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileError`})}),(0,c.jsxs)(t.td,{children:[`Typed ошибка MIME, размера, отмены или `,(0,c.jsx)(t.code,{children:`FileReader`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadImageError`})}),(0,c.jsxs)(t.td,{children:[`Отдельная image-ошибка; сейчас основной runtime code — `,(0,c.jsx)(t.code,{children:`NOT_AN_IMAGE`})]})]})]})]}),`
1264
+ `,(0,c.jsx)(t.h3,{id:`публичные-типы-чтения-файла`,children:`Публичные типы чтения файла`}),`
1265
+ `,(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:`ReadMode`})}),(0,c.jsxs)(t.td,{children:[`Литералы `,(0,c.jsx)(t.code,{children:`"data-url"`}),` и `,(0,c.jsx)(t.code,{children:`"array-buffer"`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FileMeta`})}),(0,c.jsx)(t.td,{children:`MIME, size, name, lastModified`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileOptionsBase`})}),(0,c.jsx)(t.td,{children:`Общие accept/MIME/size/signal options`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileAsDataUrlOptions`})}),(0,c.jsx)(t.td,{children:`Options режима data URL`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileAsArrayBufferOptions`})}),(0,c.jsx)(t.td,{children:`Options режима ArrayBuffer`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileOptions`})}),(0,c.jsx)(t.td,{children:`Union обоих вариантов options`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileAsDataUrlResult`})}),(0,c.jsxs)(t.td,{children:[`Результат с `,(0,c.jsx)(t.code,{children:`dataUrl`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileAsArrayBufferResult`})}),(0,c.jsxs)(t.td,{children:[`Результат с `,(0,c.jsx)(t.code,{children:`buffer`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileResult`})}),(0,c.jsx)(t.td,{children:`Union обоих результатов`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadFileErrorCode`})}),(0,c.jsxs)(t.td,{children:[`Union кодов `,(0,c.jsx)(t.code,{children:`ReadFileError`})]})]})]})]}),`
1266
+ `,(0,c.jsx)(t.h3,{id:`публичные-image-типы`,children:`Публичные image-типы`}),`
1267
+ `,(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:`ImageMime`})}),(0,c.jsx)(t.td,{children:`Разрешённый TypeScript union MIME изображений`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ImageDimensions`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`{ width, height }`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadImageOptionsBase`})}),(0,c.jsx)(t.td,{children:`Общие image options`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadImageAsDataUrlOptions`})}),(0,c.jsx)(t.td,{children:`Image options data URL режима`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadImageAsArrayBufferOptions`})}),(0,c.jsx)(t.td,{children:`Image options ArrayBuffer режима`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadImageOptions`})}),(0,c.jsx)(t.td,{children:`Union image options`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadImageAsDataUrlResult`})}),(0,c.jsx)(t.td,{children:`Data URL result с optional dimensions`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadImageAsArrayBufferResult`})}),(0,c.jsx)(t.td,{children:`Alias ArrayBuffer file result`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadImageResult`})}),(0,c.jsx)(t.td,{children:`Union image results`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReadImageErrorCode`})}),(0,c.jsx)(t.td,{children:`File error codes плюс image-specific codes`})]})]})]}),`
1268
+ `,(0,c.jsxs)(t.h3,{id:`публичные-типы-content-disposition`,children:[`Публичные типы `,(0,c.jsx)(t.code,{children:`Content-Disposition`})]}),`
1269
+ `,(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:`ContentDispositionKind`})}),(0,c.jsxs)(t.td,{children:[`Литералы `,(0,c.jsx)(t.code,{children:`"inline"`}),` и `,(0,c.jsx)(t.code,{children:`"attachment"`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ContentDispositionOptions`})}),(0,c.jsx)(t.td,{children:`disposition, Unicode filename, fallback для пустого ASCII-результата`})]})]})]}),`
1270
+ `,(0,c.jsx)(t.h2,{id:`вопросы-и-ответы`,children:`Вопросы и ответы`}),`
1271
+ `,(0,c.jsxs)(t.h3,{id:`почему-readfile-не-видит-выбранный-файл`,children:[`Почему `,(0,c.jsx)(t.code,{children:`readFile`}),` не видит выбранный файл?`]}),`
1272
+ `,(0,c.jsxs)(t.p,{children:[`Сначала получите элемент `,(0,c.jsx)(t.code,{children:`[0]`}),` и проверьте его:`]}),`
1273
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const file = event.currentTarget.files?.[0];
1274
+
1275
+ if (!file) {
1276
+ return;
1277
+ }
1278
+ `})}),`
1279
+ `,(0,c.jsxs)(t.p,{children:[`Не передавайте весь `,(0,c.jsx)(t.code,{children:`FileList`}),` и не используйте non-null assertion без проверки.`]}),`
1280
+ `,(0,c.jsxs)(t.h3,{id:`почему-typescript-не-даёт-прочитать-buffer`,children:[`Почему TypeScript не даёт прочитать `,(0,c.jsx)(t.code,{children:`buffer`}),`?`]}),`
1281
+ `,(0,c.jsx)(t.p,{children:`В default режиме возвращается data URL. Передайте литеральный mode:`}),`
1282
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = await readFile(file, { mode: "array-buffer" });
1283
+ console.log(result.buffer);
1284
+ `})}),`
1285
+ `,(0,c.jsxs)(t.p,{children:[`Для union сначала проверьте `,(0,c.jsx)(t.code,{children:`result.mode`}),`.`]}),`
1286
+ `,(0,c.jsxs)(t.h3,{id:`почему-image-не-работает-в-readfile-accept-`,children:[`Почему `,(0,c.jsx)(t.code,{children:`image/*`}),` не работает в `,(0,c.jsx)(t.code,{children:`readFile({ accept })`}),`?`]}),`
1287
+ `,(0,c.jsxs)(t.p,{children:[`Текущая runtime-проверка `,(0,c.jsx)(t.code,{children:`accept`}),` не повторяет HTML semantics. Для `,(0,c.jsx)(t.code,{children:`photo.png`}),` она ищет строку `,(0,c.jsx)(t.code,{children:`png`}),` внутри каждого specifier, а `,(0,c.jsx)(t.code,{children:`image/*`}),` её не содержит. Используйте HTML `,(0,c.jsx)(t.code,{children:`accept="image/*"`}),` только в picker, а в `,(0,c.jsx)(t.code,{children:`readFile`}),` передайте точный `,(0,c.jsx)(t.code,{children:`allowedMime`}),`.`]}),`
1288
+ `,(0,c.jsxs)(t.h3,{id:`почему-readimagefile-отклонил-imagepng`,children:[`Почему `,(0,c.jsx)(t.code,{children:`readImageFile`}),` отклонил `,(0,c.jsx)(t.code,{children:`IMAGE/PNG`}),`?`]}),`
1289
+ `,(0,c.jsxs)(t.p,{children:[`Первичная проверка регистрозависимо ожидает начало `,(0,c.jsx)(t.code,{children:`image/`}),`. Обычный браузер обычно создаёт lowercase MIME, но вручную созданный `,(0,c.jsx)(t.code,{children:`File`}),` может содержать другое значение.`]}),`
1290
+ `,(0,c.jsxs)(t.h3,{id:`почему-readimagefile-бросил-readfileerror-а-не-readimageerror`,children:[`Почему `,(0,c.jsx)(t.code,{children:`readImageFile`}),` бросил `,(0,c.jsx)(t.code,{children:`ReadFileError`}),`, а не `,(0,c.jsx)(t.code,{children:`ReadImageError`}),`?`]}),`
1291
+ `,(0,c.jsxs)(t.p,{children:[`Потому что функция делегирует MIME, size, abort и чтение в `,(0,c.jsx)(t.code,{children:`readFile`}),` без оборачивания. Обрабатывайте оба error class.`]}),`
1292
+ `,(0,c.jsxs)(t.h3,{id:`почему-decode-failure-не-имеет-code-image_decode_failed`,children:[`Почему decode failure не имеет code `,(0,c.jsx)(t.code,{children:`IMAGE_DECODE_FAILED`}),`?`]}),`
1293
+ `,(0,c.jsxs)(t.p,{children:[`Этот code присутствует в публичном типе, но текущий browser `,(0,c.jsx)(t.code,{children:`Image.onerror`}),` отклоняет Promise исходным event. Оставляйте fallback-ветку для неизвестной ошибки.`]}),`
1294
+ `,(0,c.jsxs)(t.h3,{id:`нужно-ли-читать-file-перед-отправкой-через-formdata`,children:[`Нужно ли читать `,(0,c.jsx)(t.code,{children:`File`}),` перед отправкой через `,(0,c.jsx)(t.code,{children:`FormData`}),`?`]}),`
1295
+ `,(0,c.jsxs)(t.p,{children:[`Нет. `,(0,c.jsx)(t.code,{children:`File`}),` уже является `,(0,c.jsx)(t.code,{children:`Blob`}),` и может быть добавлен напрямую. Читайте файл только для preview, client-side анализа или другого API contract.`]}),`
1296
+ `,(0,c.jsxs)(t.h3,{id:`можно-ли-доверять-filetype`,children:[`Можно ли доверять `,(0,c.jsx)(t.code,{children:`file.type`}),`?`]}),`
1297
+ `,(0,c.jsx)(t.p,{children:`Нет. Это полезная browser metadata для UX, но не security proof. Backend должен проверить реальное содержимое.`}),`
1298
+ `,(0,c.jsxs)(t.h3,{id:`почему-resolvemimetypebyextensionpdf-вернул-octet-stream`,children:[`Почему `,(0,c.jsx)(t.code,{children:`resolveMimeTypeByExtension('.pdf')`}),` вернул octet-stream?`]}),`
1299
+ `,(0,c.jsxs)(t.p,{children:[`Функция ожидает `,(0,c.jsx)(t.code,{children:`pdf`}),`, без точки. Она также не принимает полное имя `,(0,c.jsx)(t.code,{children:`report.pdf`}),`.`]}),`
1300
+ `,(0,c.jsxs)(t.h3,{id:`можно-ли-использовать-sanitizefilename-перед-записью-на-серверный-диск`,children:[`Можно ли использовать `,(0,c.jsx)(t.code,{children:`sanitizeFileName`}),` перед записью на серверный диск?`]}),`
1301
+ `,(0,c.jsx)(t.p,{children:`Нет. Helper предназначен для узкой подготовки header filename. Для storage создавайте server-controlled key и применяйте отдельную filesystem policy.`}),`
1302
+ `,(0,c.jsx)(t.h3,{id:`что-выбрать-для-preview-data-url-или-object-url`,children:`Что выбрать для preview: data URL или object URL?`}),`
1303
+ `,(0,c.jsxs)(t.p,{children:[`Этот модуль возвращает data URL и через него умеет получить dimensions. Для больших файлов object URL обычно экономнее, но его нужно создать через `,(0,c.jsx)(t.code,{children:`URL.createObjectURL(file)`}),` и обязательно освободить через `,(0,c.jsx)(t.code,{children:`URL.revokeObjectURL`}),`. Это отдельный lifecycle, который `,(0,c.jsx)(t.code,{children:`readFile`}),` не управляет.`]}),`
1304
+ `,(0,c.jsx)(t.h3,{id:`можно-ли-читать-очень-большие-файлы`,children:`Можно ли читать очень большие файлы?`}),`
1305
+ `,(0,c.jsxs)(t.p,{children:[`Технически `,(0,c.jsx)(t.code,{children:`FileReader`}),` попробует загрузить файл целиком, но это может привести к зависанию или нехватке памяти. Для больших данных используйте streaming/chunked решение и устанавливайте `,(0,c.jsx)(t.code,{children:`maxBytes`}),` до чтения.`]}),`
1306
+ `,(0,c.jsxs)(t.h3,{id:`нужно-ли-вручную-удалять-listener-abortsignal`,children:[`Нужно ли вручную удалять listener `,(0,c.jsx)(t.code,{children:`AbortSignal`}),`?`]}),`
1307
+ `,(0,c.jsxs)(t.p,{children:[`При обычном использовании `,(0,c.jsx)(t.code,{children:`readFile`}),` — нет: функция снимает listener после `,(0,c.jsx)(t.code,{children:`load`}),`, `,(0,c.jsx)(t.code,{children:`error`}),` или `,(0,c.jsx)(t.code,{children:`abort`}),`. При прямом использовании `,(0,c.jsx)(t.code,{children:`attachAbort`}),` вызывайте возвращённый cleanup самостоятельно.`]}),`
1308
+ `,(0,c.jsxs)(t.h3,{id:`возвращается-копия-file`,children:[`Возвращается копия `,(0,c.jsx)(t.code,{children:`File`}),`?`]}),`
1309
+ `,(0,c.jsxs)(t.p,{children:[`Нет. Поле `,(0,c.jsx)(t.code,{children:`result.file`}),` ссылается на тот же `,(0,c.jsx)(t.code,{children:`File`}),`. Новый объект создаётся только для `,(0,c.jsx)(t.code,{children:`meta`}),` и прочитанного представления данных.`]})]})}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};