ics-ui-kit 0.1.12 → 0.1.14
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.
- package/README.md +36 -36
- package/dist/AlertContext-CxjW1LVL.js.map +1 -1
- package/dist/{AlertIcon-5B6AgZpe.js → AlertIcon-BphnEYvV.js} +3 -3
- package/dist/AlertIcon-BphnEYvV.js.map +1 -0
- package/dist/Button-Dg89xRSi.js.map +1 -1
- package/dist/ButtonBase-e6qyv6mD.js.map +1 -1
- package/dist/{CheckboxField-cX09-eBX.js → CheckboxField-2zNFubvm.js} +2 -2
- package/dist/CheckboxField-2zNFubvm.js.map +1 -0
- package/dist/Combination-DeIugdgY.js +353 -0
- package/dist/Combination-DeIugdgY.js.map +1 -0
- package/dist/{Command-ClIm4U7r.js → Command-D5zgJ7-y.js} +3 -3
- package/dist/Command-D5zgJ7-y.js.map +1 -0
- package/dist/ComponentVariantProvider-brk0g-hm.js.map +1 -1
- package/dist/Description-BTPMlTT-.js.map +1 -1
- package/dist/{Dialog-Bs1B5Iu8.js → Dialog-CdnsGyJD.js} +3 -3
- package/dist/Dialog-CdnsGyJD.js.map +1 -0
- package/dist/Divider-DPW5ZhZ8.js.map +1 -1
- package/dist/EmptyState-DRKjscH-.js.map +1 -1
- package/dist/ErrorState-B26k67-z.js.map +1 -1
- package/dist/FeatureIcon-BGE0Orkc.js.map +1 -1
- package/dist/Field-CP_-li9L.js +90 -0
- package/dist/Field-CP_-li9L.js.map +1 -0
- package/dist/{FieldLabel-QJ4rJFOi.js → FieldLabel-eyKmeWRn.js} +2 -2
- package/dist/FieldLabel-eyKmeWRn.js.map +1 -0
- package/dist/FolderSvg-cw0DxNVy.js.map +1 -1
- package/dist/{FormFooterTemplate-DPB6Dkxn.js → FormFooterTemplate-Cfn5Nmge.js} +3 -3
- package/dist/FormFooterTemplate-Cfn5Nmge.js.map +1 -0
- package/dist/FormHeader--F6b2aRk.js.map +1 -1
- package/dist/{FormStack-DO15vBgd.js → FormStack-Cc_ELdWL.js} +3 -3
- package/dist/FormStack-Cc_ELdWL.js.map +1 -0
- package/dist/HintState-BxrCAWqq.js.map +1 -1
- package/dist/Icon-DH3ev9GK.js.map +1 -1
- package/dist/IconButton-DwkpNgn3.js.map +1 -1
- package/dist/{IconTooltip-CKJKGHbk.js → IconTooltip-RSxvnBgH.js} +2 -2
- package/dist/IconTooltip-RSxvnBgH.js.map +1 -0
- package/dist/Indicator-AqxHWPA9.js.map +1 -1
- package/dist/InputGroup-BvD9Igmt.js.map +1 -1
- package/dist/InputOTP-B2e1nfSO.js.map +1 -1
- package/dist/Loader-BpHiL6W-.js.map +1 -1
- package/dist/MenuItem-d19Ty84F.js.map +1 -1
- package/dist/MenuItemShortcut-D1faeIXp.js.map +1 -1
- package/dist/MenuItemText-CRpaPbvg.js.map +1 -1
- package/dist/Overlay-CVt011mY.js.map +1 -1
- package/dist/{Popover-CY_d3Ggn.js → Popover-CRVD7AtB.js} +20 -19
- package/dist/Popover-CRVD7AtB.js.map +1 -0
- package/dist/RichButton-DuPTenmS.js.map +1 -1
- package/dist/ScrollArea-CuuTeDn3.js.map +1 -1
- package/dist/ScrollShadowContainer-DdrnF8qU.js.map +1 -1
- package/dist/{SearchSelectTag-DFCX9zT1.js → SearchSelectTag-CA7Ie3Nx.js} +2 -2
- package/dist/SearchSelectTag-CA7Ie3Nx.js.map +1 -0
- package/dist/{Select-B2WcoFf5.js → Select-MLPTpIl5.js} +24 -23
- package/dist/Select-MLPTpIl5.js.map +1 -0
- package/dist/{Sheet-zTMuP7C8.js → Sheet-qTIXsqm0.js} +3 -3
- package/dist/Sheet-qTIXsqm0.js.map +1 -0
- package/dist/Shortcut-D8L15_ed.js.map +1 -1
- package/dist/Skeleton-y6I-i9q5.js.map +1 -1
- package/dist/TableFillHandle-C_yvCTSl.js +65 -0
- package/dist/TableFillHandle-C_yvCTSl.js.map +1 -0
- package/dist/Tag-DdHXTyKF.js.map +1 -1
- package/dist/Text-DeBE8AKl.js.map +1 -1
- package/dist/TextInput-Cj50ImXi.js.map +1 -1
- package/dist/{TextOverflowTooltip-DlUgic5y.js → TextOverflowTooltip-B1I5qwAT.js} +2 -2
- package/dist/TextOverflowTooltip-B1I5qwAT.js.map +1 -0
- package/dist/Toggle-v_HT3Gm2.js.map +1 -1
- package/dist/{Tooltip-C4rVuWr4.js → Tooltip-7Duaau0S.js} +3 -3
- package/dist/Tooltip-7Duaau0S.js.map +1 -0
- package/dist/TriggerButton-CfDObWP9.js.map +1 -1
- package/dist/blocks/auth/otp-form.js +1 -1
- package/dist/blocks/auth/otp-form.js.map +1 -1
- package/dist/circle-BbEzypQJ.js +14 -0
- package/dist/circle-BbEzypQJ.js.map +1 -0
- package/dist/cn-dsXlqdJg.js.map +1 -1
- package/dist/components/alert/Alert.d.ts +4 -0
- package/dist/components/alert/Alert.stories.d.ts +1 -0
- package/dist/components/alert-dialog.js +3 -3
- package/dist/components/alert-dialog.js.map +1 -1
- package/dist/components/alert.js +18 -18
- package/dist/components/alert.js.map +1 -1
- package/dist/components/avatar.js.map +1 -1
- package/dist/components/background-pattern.js.map +1 -1
- package/dist/components/badge.js.map +1 -1
- package/dist/components/banner.js.map +1 -1
- package/dist/components/breadcrumb.js.map +1 -1
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/card.js +1 -1
- package/dist/components/card.js.map +1 -1
- package/dist/components/carousel.js.map +1 -1
- package/dist/components/checkbox.js +1 -1
- package/dist/components/collapsible.js.map +1 -1
- package/dist/components/color-tile.js.map +1 -1
- package/dist/components/command/Command.stories.d.ts +1 -0
- package/dist/components/command.js +1 -1
- package/dist/components/container.js.map +1 -1
- package/dist/components/context-menu/ContextMenu.d.ts +27 -0
- package/dist/components/context-menu/ContextMenu.stories.d.ts +10 -0
- package/dist/components/context-menu/index.d.ts +1 -0
- package/dist/components/context-menu.d.ts +2 -0
- package/dist/components/context-menu.js +2675 -0
- package/dist/components/context-menu.js.map +1 -0
- package/dist/components/counter.js.map +1 -1
- package/dist/components/dialog.js +3 -3
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/divider.js.map +1 -1
- package/dist/components/drawer.js +3 -3
- package/dist/components/drawer.js.map +1 -1
- package/dist/components/dropdown.js +26 -25
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/error-state.js.map +1 -1
- package/dist/components/field/Field.d.ts +4 -0
- package/dist/components/field/Field.stories.d.ts +1 -0
- package/dist/components/field.js +1 -1
- package/dist/components/file-upload.js +2 -2
- package/dist/components/file-upload.js.map +1 -1
- package/dist/components/form.js +3 -3
- package/dist/components/form.js.map +1 -1
- package/dist/components/glass-toolbar.js.map +1 -1
- package/dist/components/icon-tooltip.js +1 -1
- package/dist/components/image-placeholder.js.map +1 -1
- package/dist/components/image-preview.js.map +1 -1
- package/dist/components/image-uploader.js.map +1 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/label.js +1 -1
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/overflow-tooltip.js +1 -1
- package/dist/components/page-state.js.map +1 -1
- package/dist/components/popover.js +1 -1
- package/dist/components/portal-container.js +2 -2
- package/dist/components/portal-container.js.map +1 -1
- package/dist/components/progress-circle.js.map +1 -1
- package/dist/components/progress.js.map +1 -1
- package/dist/components/prose.js.map +1 -1
- package/dist/components/radio-group.js +63 -72
- package/dist/components/radio-group.js.map +1 -1
- package/dist/components/resizable.js.map +1 -1
- package/dist/components/scheduler.js +1 -1
- package/dist/components/scheduler.js.map +1 -1
- package/dist/components/search-select.js +4 -4
- package/dist/components/search-select.js.map +1 -1
- package/dist/components/select.js +1 -1
- package/dist/components/sheet.js +1 -1
- package/dist/components/sidebar.js +206 -212
- package/dist/components/sidebar.js.map +1 -1
- package/dist/components/slider.js.map +1 -1
- package/dist/components/stepper.js.map +1 -1
- package/dist/components/switch/Switch.d.ts +1 -1
- package/dist/components/switch.js +5 -2
- package/dist/components/switch.js.map +1 -1
- package/dist/components/system-state.js +1 -1
- package/dist/components/system-state.js.map +1 -1
- package/dist/components/table/Table.d.ts +109 -5
- package/dist/components/table/Table.stories.d.ts +285 -9
- package/dist/components/table/column-reorder/index.d.ts +1 -0
- package/dist/components/table/column-reorder/useColumnReorder.d.ts +82 -0
- package/dist/components/table/column-reorder.d.ts +2 -0
- package/dist/components/table/column-reorder.js +6 -0
- package/dist/components/table/column-reorder.js.map +1 -0
- package/dist/components/table/column-resize/columnAutoFit.d.ts +21 -0
- package/dist/components/table/column-resize/columnResizeSession.d.ts +9 -0
- package/dist/components/table/column-resize/index.d.ts +3 -0
- package/dist/components/table/column-resize/useColumnResize.d.ts +52 -0
- package/dist/components/table/column-resize.d.ts +2 -0
- package/dist/components/table/column-resize.js +7 -0
- package/dist/components/table/column-resize.js.map +1 -0
- package/dist/components/table/editable/TableFillHandle.d.ts +23 -0
- package/dist/components/table/editable/cellRange.d.ts +83 -0
- package/dist/components/table/editable/editableCell.d.ts +90 -0
- package/dist/components/table/editable/index.d.ts +3 -0
- package/dist/components/table/editable.d.ts +2 -0
- package/dist/components/table/editable.js +12 -0
- package/dist/components/table/editable.js.map +1 -0
- package/dist/components/table/index.d.ts +8 -1
- package/dist/components/table/opaqueCell.d.ts +47 -0
- package/dist/components/table/rowTint.d.ts +60 -0
- package/dist/components/table/sticky/index.d.ts +1 -0
- package/dist/components/table/sticky/stickyCell.d.ts +105 -0
- package/dist/components/table/sticky.d.ts +2 -0
- package/dist/components/table/sticky.js +22 -0
- package/dist/components/table/sticky.js.map +1 -0
- package/dist/components/table/tableStoryData.d.ts +84 -0
- package/dist/components/table/tableStoryParts.d.ts +72 -0
- package/dist/components/table/tree/TableTree.d.ts +106 -0
- package/dist/components/table/tree/index.d.ts +3 -0
- package/dist/components/table/tree/treeGeometry.d.ts +86 -0
- package/dist/components/table/tree/treeModel.d.ts +87 -0
- package/dist/components/table/tree.d.ts +2 -0
- package/dist/components/table/tree.js +19 -0
- package/dist/components/table/tree.js.map +1 -0
- package/dist/components/table.js +52 -79
- package/dist/components/table.js.map +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/tag-input.js +2 -2
- package/dist/components/tag-input.js.map +1 -1
- package/dist/components/textarea.js.map +1 -1
- package/dist/components/timer.js.map +1 -1
- package/dist/components/toast.js.map +1 -1
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toolbar.js +1 -2
- package/dist/components/toolbar.js.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/floating-ui.react-dom-BwDfy92G.js +1309 -0
- package/dist/floating-ui.react-dom-BwDfy92G.js.map +1 -0
- package/dist/formatBytes-C4QXq5lC.js.map +1 -1
- package/dist/hooks.js +1 -1
- package/dist/index-0ioNhtNM.js.map +1 -1
- package/dist/index-1SnkxfPR.js +230 -0
- package/dist/index-1SnkxfPR.js.map +1 -0
- package/dist/index-437EL2iq.js.map +1 -1
- package/dist/index-BDrapWDv.js.map +1 -1
- package/dist/index-BGQepRFJ.js.map +1 -1
- package/dist/index-BVqs-uqP.js.map +1 -1
- package/dist/index-BZPx6jYI.js.map +1 -1
- package/dist/index-Bb23HztA.js.map +1 -1
- package/dist/index-BjtNfqNq.js +333 -0
- package/dist/index-BjtNfqNq.js.map +1 -0
- package/dist/index-BpWB3aFK.js.map +1 -1
- package/dist/index-ByaXH_ih.js.map +1 -1
- package/dist/index-CCKe-Mpx.js.map +1 -1
- package/dist/index-CFaU3UXv.js.map +1 -1
- package/dist/index-CLuiUUQ8.js.map +1 -1
- package/dist/{index-CQD-Njgt.js → index-CMxHunQ4.js} +26 -25
- package/dist/index-CMxHunQ4.js.map +1 -0
- package/dist/index-CmBeyp2d.js.map +1 -1
- package/dist/index-DFZozV_h.js.map +1 -1
- package/dist/index-DHsfW5Rj.js.map +1 -1
- package/dist/index-DHyZ_0Ut.js.map +1 -1
- package/dist/index-DLcqcWxM.js.map +1 -1
- package/dist/index-DnnQs07z.js.map +1 -1
- package/dist/index-Uiy0Xwy2.js.map +1 -1
- package/dist/index-_ITz1IyW.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inputBaseStyles-DbzD_jvq.js.map +1 -1
- package/dist/lib/utils/dom.d.ts +72 -0
- package/dist/lib/utils/styles.d.ts +1 -0
- package/dist/lib/utils.js.map +1 -1
- package/dist/opaqueCell-SGaiyL54.js +31 -0
- package/dist/opaqueCell-SGaiyL54.js.map +1 -0
- package/dist/plus-BpxYPnAG.js +15 -0
- package/dist/plus-BpxYPnAG.js.map +1 -0
- package/dist/styles-scoped.css +6684 -6413
- package/dist/styles.css +6423 -6152
- package/dist/theme.css +364 -362
- package/dist/treeModel-BlPWVj4W.js +319 -0
- package/dist/treeModel-BlPWVj4W.js.map +1 -0
- package/dist/types/css.d.ts +4 -4
- package/dist/useBreakpoints-DHvp74UL.js +31 -0
- package/dist/useBreakpoints-DHvp74UL.js.map +1 -0
- package/dist/useColumnReorder-BxVZQ_Y5.js +119 -0
- package/dist/useColumnReorder-BxVZQ_Y5.js.map +1 -0
- package/dist/useColumnResize-wjfEyBBg.js +130 -0
- package/dist/useColumnResize-wjfEyBBg.js.map +1 -0
- package/dist/useDebounce-BRah78CF.js.map +1 -1
- package/dist/useFileUpload-Bnl3P8mP.js.map +1 -1
- package/dist/useItemsKeyboardNavigation-CmeOR30y.js.map +1 -1
- package/dist/useMergeRefs-qoKniYqT.js.map +1 -1
- package/dist/usePortalContainer-BAIp0kSF.js +44 -0
- package/dist/usePortalContainer-BAIp0kSF.js.map +1 -0
- package/dist/vendors/date-fns/index.d.ts +7 -14
- package/dist/vendors/date-fns/locale/en-US/index.d.ts +4 -0
- package/dist/vendors/date-fns/locale/ru/index.d.ts +4 -0
- package/dist/vendors/react-day-picker/index.d.ts +8 -17
- package/dist/vendors/react-hook-form/index.d.ts +8 -15
- package/dist/vendors/tanstack/react-table/index.d.ts +12 -0
- package/dist/vendors/tanstack/react-table.js.map +1 -1
- package/dist/vendors/zod/index.d.ts +8 -9
- package/dist/zod-BMNoK3aM.js.map +1 -1
- package/package.json +207 -205
- package/tailwind.preset.js +452 -448
- package/dist/AlertIcon-5B6AgZpe.js.map +0 -1
- package/dist/CheckboxField-cX09-eBX.js.map +0 -1
- package/dist/Command-ClIm4U7r.js.map +0 -1
- package/dist/Dialog-Bs1B5Iu8.js.map +0 -1
- package/dist/Field-BiIWtq9o.js +0 -86
- package/dist/Field-BiIWtq9o.js.map +0 -1
- package/dist/FieldLabel-QJ4rJFOi.js.map +0 -1
- package/dist/FormFooterTemplate-DPB6Dkxn.js.map +0 -1
- package/dist/FormStack-DO15vBgd.js.map +0 -1
- package/dist/IconTooltip-CKJKGHbk.js.map +0 -1
- package/dist/Popover-CY_d3Ggn.js.map +0 -1
- package/dist/SearchSelectTag-DFCX9zT1.js.map +0 -1
- package/dist/Select-B2WcoFf5.js.map +0 -1
- package/dist/Sheet-zTMuP7C8.js.map +0 -1
- package/dist/TextOverflowTooltip-DlUgic5y.js.map +0 -1
- package/dist/Tooltip-C4rVuWr4.js.map +0 -1
- package/dist/components/data-table/DataTable.stories.d.ts +0 -8
- package/dist/design-tokens.source.json +0 -1
- package/dist/index-BzCriPiK.js +0 -671
- package/dist/index-BzCriPiK.js.map +0 -1
- package/dist/index-CQD-Njgt.js.map +0 -1
- package/dist/index-DRo26PBK.js +0 -1525
- package/dist/index-DRo26PBK.js.map +0 -1
- package/dist/useBreakpoints-D8wKOvil.js +0 -103
- package/dist/useBreakpoints-D8wKOvil.js.map +0 -1
- package/dist/usePortalContainer-ae6EzS08.js +0 -39
- package/dist/usePortalContainer-ae6EzS08.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useColumnReorder-BxVZQ_Y5.js","sources":["../src/components/table/column-reorder/useColumnReorder.ts"],"sourcesContent":["import { type PointerEvent as ReactPointerEvent, useEffect, useMemo, useRef, useState } from \"react\";\nimport { opaqueCellStyles } from \"../opaqueCell\";\n\n/** Атрибут, по которому хук находит в DOM все ячейки одной колонки. */\nexport const COLUMN_KEY_ATTRIBUTE = \"data-column-key\";\n\n/** Сколько миллисекунд колонки разъезжаются и укладываются на место. */\nconst TRANSITION_MS = 200;\n\n/** Порог в пикселях, до которого нажатие считается кликом, а не началом перетаскивания. */\nconst DRAG_THRESHOLD = 3;\n\n/** Инлайновые свойства, которые хук пишет ячейкам на время перетаскивания. */\ntype TouchedStyles = Pick<CSSStyleDeclaration, \"transform\" | \"transition\" | \"position\" | \"zIndex\" | \"boxShadow\">;\n\nexport interface UseColumnReorderOptions<K extends string> {\n\t/** Полный видимый порядок колонок — включая те, что двигать нельзя. */\n\tcolumns: K[];\n\t/** Ширины колонок: из них считаются границы слотов. Обычно это `widths` из `useColumnResize`. */\n\twidths: Record<K, number>;\n\t/** Новый порядок. Хук ничего не хранит — состояние остаётся у вызывающей стороны. */\n\tonReorder: (next: K[]) => void;\n\t/**\n\t * Колонки, которые нельзя ни таскать, ни занимать: например колонка иерархии,\n\t * которая обязана оставаться первой. Свой индекс они сохраняют при любой перестановке.\n\t */\n\tlocked?: K[];\n}\n\nexport interface UseColumnReorderResult<K extends string> {\n\t/** Колонка, которую тащат прямо сейчас. Годится для подсветки грипа. */\n\tdragKey: K | null;\n\t/** Пропы для ручки-грипа. Драг начинается только с неё. */\n\tgetGripProps: (key: K) => {\n\t\tonPointerDown: (event: ReactPointerEvent) => void;\n\t\tstyle: { touchAction: \"none\" };\n\t};\n\t/** Пропы для каждой ячейки колонки — и в шапке, и в теле. Без них колонка не поедет. */\n\tgetColumnProps: (key: K) => Record<string, string>;\n}\n\n/**\n * Перетаскивание колонок таблицы: колонка едет за курсором, соседи разъезжаются, отпускание\n * укладывает её в новый слот.\n *\n * **Почему не библиотека.** Универсальные DnD-библиотеки (dnd-kit и подобные) существуют ради\n * произвольной геометрии: они измеряют узлы, ищут коллизии, держат подписку на каждый\n * перетаскиваемый элемент. Здесь ничего этого не нужно — колонки лежат в один ряд, а их ширины\n * уже известны из `useColumnResize`, так что смещение слота это префиксная сумма, а выбор цели —\n * ближайший центр. Ровно то же самое делает `closestCenter`, только без обхода DOM.\n *\n * Вторая причина — цена на большой таблице. Библиотечный подход требует хука на **каждую**\n * ячейку, включая ячейки тела, иначе колонка не поедет целиком: на таблице 7×34 это больше\n * двухсот подписок и столько же ререндеров на каждое движение мыши. Здесь во время драга\n * React не участвует вовсе: трансформации пишутся прямо в DOM, а состояние меняется один раз,\n * когда кнопку отпустили. Тот же приём и по той же причине используется в `useColumnResize`.\n *\n * **Оговорка про инлайновые стили.** На время драга хук пишет ячейкам `transform`,\n * `transition`, `position`, `z-index` и `box-shadow` — последний рисует левый край, которого\n * у плывущей колонки нет (см. `setColumnSeparator`). Свои значения этих свойств ячейка\n * получает обратно после отпускания: хук снимает их при первом касании и возвращает\n * в конце. Классы при этом не трогаются вовсе, поэтому оформление классами живёт своей\n * жизнью, а тень от хука просто лежит поверх на время перетаскивания.\n *\n * **Оговорка про фон.** Плывущая колонка на время перетаскивания получает классы\n * `opaqueCellStyles`: строка прозрачна, и без непрозрачного фона колонка едет поверх соседей\n * вместе с их текстом, наложенным на свой. Тот же рецепт, что у закреплённой колонки, — и по\n * той же причине. Подсветку строки он сохраняет, поэтому наведение и `selected` под колонкой\n * не пропадают.\n *\n * **Чего нет.** Перетаскивания с клавиатуры: правильный клавиатурный аналог — пункты меню\n * «сдвинуть влево / вправо», а не имитация драга. Автопрокрутки контейнера у края тоже нет —\n * при перетаскивании за пределы видимой области колонка просто упрётся в край.\n *\n * **Как подключается.** `getGripProps` — на ручку, `getColumnProps` — на `TableHead`\n * и на каждую `TableCell` этой колонки. Драг начинается только с ручки: `pointerdown`\n * по всей ячейке конфликтовал бы с ресайзером, который начинается так же.\n *\n * @example\n * const [order, setOrder] = useState([\"tree\", \"client\", \"sum\"]);\n *\n * const { getGripProps, getColumnProps } = useColumnReorder({\n * columns: order,\n * widths, // из useColumnResize\n * onReorder: setOrder,\n * locked: [\"tree\"]\n * });\n *\n * <TableHead {...getColumnProps(key)}>\n * <span {...getGripProps(key)}>⠿</span>\n * </TableHead>\n * <TableCell {...getColumnProps(key)}>…</TableCell>\n */\nexport function useColumnReorder<K extends string>({\n\tcolumns,\n\twidths,\n\tonReorder,\n\tlocked\n}: UseColumnReorderOptions<K>): UseColumnReorderResult<K> {\n\tconst [dragKey, setDragKey] = useState<K | null>(null);\n\n\t/** Всё, что живёт только на время одного перетаскивания. */\n\tconst sessionRef = useRef<{\n\t\tkey: K;\n\t\ttable: HTMLElement;\n\t\tstartX: number;\n\t\t/** Индексы в `columns`, доступные для перестановки. */\n\t\tmovable: number[];\n\t\t/** Левая граница каждой колонки внутри таблицы. */\n\t\toffsets: number[];\n\t\t/**\n\t\t * Цвет разделителя колонок, снятый с живой ячейки, — им рисуется левый край плывущей\n\t\t * колонки. Читается один раз на старте: во время драга `getComputedStyle` на каждую\n\t\t * ячейку — это принудительный пересчёт раскладки в каждом кадре.\n\t\t */\n\t\tborderColor?: string;\n\t\tfrom: number;\n\t\tto: number;\n\t\tstarted: boolean;\n\t} | null>(null);\n\n\tconst settleTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n\t/** Ячейки, которым драг выдал непрозрачный фон, и ровно те классы, которые были выданы. */\n\tconst opaqueCells = useRef(new Map<HTMLElement, string[]>());\n\n\t/**\n\t * Инлайновые стили ячеек в том виде, в каком хук их застал.\n\t *\n\t * Снимаются один раз, при первом касании ячейки, и возвращаются на место после\n\t * перетаскивания. Инлайновый `box-shadow` или `transform` — законный инструмент\n\t * потребителя (рамка выделенного диапазона, своя анимация), и колонка, проехавшая\n\t * поверх такой ячейки, оставляет её ровно такой же, какой взяла.\n\t */\n\tconst touchedCells = useRef(new Map<HTMLElement, TouchedStyles>());\n\n\tconst lockedSet = useMemo(() => new Set(locked ?? []), [locked]);\n\n\t// Актуальные значения нужны обработчикам на `window`, которые подписываются один раз\n\t// за перетаскивание и иначе замкнулись бы на устаревшие пропы.\n\tconst latest = useRef({ columns, widths, onReorder, lockedSet });\n\tlatest.current = { columns, widths, onReorder, lockedSet };\n\n\tuseEffect(() => () => clearTimeout(settleTimer.current), []);\n\n\t/** Все ячейки колонки — заголовок и тело разом. */\n\tconst cellsOf = (table: HTMLElement, key: K) =>\n\t\tArray.from(table.querySelectorAll<HTMLElement>(`[${COLUMN_KEY_ATTRIBUTE}=\"${key}\"]`));\n\n\t/**\n\t * Непрозрачный фон плывущей колонке.\n\t *\n\t * Выданные классы запоминаются поштучно: те, что у ячейки уже были (закреплённая колонка\n\t * несёт тот же набор), не выдаются и не снимаются — иначе после перетаскивания она\n\t * осталась бы без фона.\n\t */\n\tconst markOpaque = (cell: HTMLElement) => {\n\t\tconst classes = opaqueCellStyles({ part: cell.closest(\"thead\") ? \"head\" : \"cell\" })\n\t\t\t.split(\" \")\n\t\t\t.filter((className) => className && !cell.classList.contains(className));\n\t\tcell.classList.add(...classes);\n\t\topaqueCells.current.set(cell, classes);\n\t};\n\n\tconst clearOpaque = () => {\n\t\topaqueCells.current.forEach((classes, cell) => cell.classList.remove(...classes));\n\t\topaqueCells.current.clear();\n\t};\n\n\t/** Снимок инлайновых стилей ячейки до того, как хук в них написал. */\n\tconst rememberCell = (cell: HTMLElement) => {\n\t\tif (touchedCells.current.has(cell)) return;\n\t\tconst { transform, transition, position, zIndex, boxShadow } = cell.style;\n\t\ttouchedCells.current.set(cell, { transform, transition, position, zIndex, boxShadow });\n\t};\n\n\t/**\n\t * Левый край перетаскиваемой колонки.\n\t *\n\t * Правый край едет с колонкой сам: разделители у кита лежат на ячейках (`border-r`),\n\t * а раздельная модель границ (`border-separate` у `Table`) включает границу в бокс ячейки.\n\t * Левый край принадлежит соседу слева и уезжает вместе с ним, поэтому его рисует тень —\n\t * и рисует всегда: у последней колонки своей правой границы нет (`last:border-r-0`),\n\t * и без тени она уплывала бы вовсе без краёв.\n\t *\n\t * По той же причине левый край нужен колонке справа от пустого слота: слева от неё стоит\n\t * не сосед, а дырка, в которую сядет плывущая колонка, и её правый борт рисовать некому.\n\t *\n\t * Тот же приём, что и в референсе на dnd-kit, где разделитель нарисован псевдоэлементом\n\t * `::before` — с той разницей, что здесь ничего не требуется от разметки потребителя.\n\t */\n\tconst setColumnSeparator = (cell: HTMLElement, leftEdge: string | undefined) => {\n\t\tcell.style.boxShadow = leftEdge ? `inset 1px 0 0 0 ${leftEdge}` : \"\";\n\t};\n\n\tconst setColumnTransform = (table: HTMLElement, key: K, shift: number, dragged: boolean, leftEdge?: string) => {\n\t\tconst moving = shift !== 0 || dragged;\n\t\tcellsOf(table, key).forEach((cell) => {\n\t\t\trememberCell(cell);\n\t\t\tcell.style.transform = shift ? `translateX(${shift}px)` : \"\";\n\t\t\t// Перетаскиваемая колонка идёт поверх соседей и следует за курсором без задержки;\n\t\t\t// разъезжающиеся соседи, наоборот, должны двигаться плавно.\n\t\t\tcell.style.transition = dragged ? \"none\" : `transform ${TRANSITION_MS}ms ease`;\n\t\t\tcell.style.position = moving ? \"relative\" : \"\";\n\t\t\tcell.style.zIndex = dragged ? \"20\" : \"\";\n\t\t\t// Край рисуется независимо от того, едет ли колонка: у соседа справа от пустого\n\t\t\t// слота сдвиг нулевой, а борт ему всё равно нужен.\n\t\t\tsetColumnSeparator(cell, leftEdge);\n\t\t});\n\t};\n\n\t/** Возврат ячеек в исходное состояние: ровно те стили и классы, что были до драга. */\n\tconst clearTransforms = () => {\n\t\ttouchedCells.current.forEach((styles, cell) => Object.assign(cell.style, styles));\n\t\ttouchedCells.current.clear();\n\t\tclearOpaque();\n\t};\n\n\t/**\n\t * Куда встанет колонка при текущем сдвиге: ближайший по центру доступный слот.\n\t *\n\t * Именно так выбирает цель и `closestCenter` в dnd-kit — с той разницей, что центры\n\t * слотов здесь известны заранее и обходить DOM не требуется.\n\t */\n\tconst targetIndex = (session: NonNullable<typeof sessionRef.current>, dx: number) => {\n\t\tconst { widths: currentWidths } = latest.current;\n\t\tconst draggedCenter = session.offsets[session.from] + currentWidths[session.key] / 2 + dx;\n\n\t\tlet best = session.from;\n\t\tlet bestDistance = Infinity;\n\t\tsession.movable.forEach((index) => {\n\t\t\tconst center = session.offsets[index] + currentWidths[latest.current.columns[index]] / 2;\n\t\t\tconst distance = Math.abs(center - draggedCenter);\n\t\t\tif (distance < bestDistance) {\n\t\t\t\tbestDistance = distance;\n\t\t\t\tbest = index;\n\t\t\t}\n\t\t});\n\t\treturn best;\n\t};\n\n\t/**\n\t * Сдвиг колонки при перестановке `from → to`.\n\t *\n\t * Двигаются только те, кто оказался между исходным и целевым слотом; закреплённые\n\t * не двигаются никогда, потому что в перестановке не участвуют.\n\t */\n\tconst shiftOf = (session: NonNullable<typeof sessionRef.current>, index: number, to: number) => {\n\t\tconst width = latest.current.widths[session.key];\n\t\tconst { from } = session;\n\t\tif (index === from || !session.movable.includes(index)) return 0;\n\t\tif (from < to && index > from && index <= to) return -width;\n\t\tif (from > to && index >= to && index < from) return width;\n\t\treturn 0;\n\t};\n\n\t/** Порядок колонок после перестановки `from → to`. Закреплённые сохраняют свои индексы. */\n\tconst orderAfter = (session: NonNullable<typeof sessionRef.current>, to: number) => {\n\t\tconst { columns: currentColumns } = latest.current;\n\t\tconst next = [...currentColumns];\n\t\tconst movableKeys = session.movable.map((index) => currentColumns[index]);\n\t\tconst [moved] = movableKeys.splice(session.movable.indexOf(session.from), 1);\n\t\tmovableKeys.splice(session.movable.indexOf(to), 0, moved);\n\t\tsession.movable.forEach((index, slot) => (next[index] = movableKeys[slot]));\n\t\treturn next;\n\t};\n\n\t/**\n\t * Левый край плывущей колонки в раскладке, которая получится после перестановки.\n\t *\n\t * Считается по будущему порядку, а не как `offsets[to]`. При движении вправо соседи между\n\t * слотами уезжают влево, и колонка встаёт не на левый край целевого слота, а на его правый\n\t * край минус собственная ширина; `offsets[to]` промахивается ровно на разницу ширин целевой\n\t * и перетаскиваемой колонок. На колонках равной ширины это ноль, на разных — заметный\n\t * скачок в момент, когда анимация закончилась и трансформации сняты.\n\t */\n\tconst offsetAfter = (session: NonNullable<typeof sessionRef.current>, to: number) => {\n\t\tconst { widths: currentWidths } = latest.current;\n\t\tconst next = orderAfter(session, to);\n\t\treturn next.slice(0, next.indexOf(session.key)).reduce((sum, columnKey) => sum + currentWidths[columnKey], 0);\n\t};\n\n\tconst finish = (commit: boolean) => {\n\t\tconst session = sessionRef.current;\n\t\tsessionRef.current = null;\n\t\tsetDragKey(null);\n\t\tif (!session) return;\n\n\t\tconst { table, from, to } = session;\n\n\t\tif (!commit || from === to) {\n\t\t\tclearTransforms();\n\t\t\treturn;\n\t\t}\n\n\t\t// Колонка доезжает до своего слота, и только потом порядок меняется в state:\n\t\t// иначе она прыгнула бы из-под курсора на новое место рывком.\n\t\tconst settleShift = offsetAfter(session, to) - session.offsets[from];\n\t\tsetColumnTransform(table, session.key, settleShift, false, session.borderColor);\n\n\t\tclearTimeout(settleTimer.current);\n\t\tsettleTimer.current = setTimeout(() => {\n\t\t\tclearTransforms();\n\t\t\tlatest.current.onReorder(orderAfter(session, to));\n\t\t}, TRANSITION_MS);\n\t};\n\n\tconst onPointerDown = (key: K) => (event: ReactPointerEvent) => {\n\t\tif (event.button !== 0 || lockedSet.has(key)) return;\n\t\tconst table = (event.currentTarget as HTMLElement).closest(\"table\");\n\t\tif (!table) return;\n\n\t\tevent.preventDefault();\n\n\t\tconst { columns: currentColumns, widths: currentWidths } = latest.current;\n\t\tconst offsets: number[] = [];\n\t\tcurrentColumns.reduce((sum, columnKey) => {\n\t\t\toffsets.push(sum);\n\t\t\treturn sum + currentWidths[columnKey];\n\t\t}, 0);\n\n\t\t// Цвет разделителя снимается с первой колонки, у которой правая граница есть:\n\t\t// у последней её нет (`last:border-r-0`), а цвет у всех один.\n\t\tconst borderColor = currentColumns\n\t\t\t.map((columnKey) => {\n\t\t\t\tconst head = table.querySelector<HTMLElement>(`thead [${COLUMN_KEY_ATTRIBUTE}=\"${columnKey}\"]`);\n\t\t\t\tif (!head) return undefined;\n\t\t\t\tconst style = window.getComputedStyle(head);\n\t\t\t\treturn parseFloat(style.borderRightWidth) > 0 ? style.borderRightColor : undefined;\n\t\t\t})\n\t\t\t.find(Boolean);\n\n\t\tconst from = currentColumns.indexOf(key);\n\t\tsessionRef.current = {\n\t\t\tborderColor,\n\t\t\tkey,\n\t\t\ttable,\n\t\t\tstartX: event.clientX,\n\t\t\tmovable: currentColumns.map((_, index) => index).filter((index) => !lockedSet.has(currentColumns[index])),\n\t\t\toffsets,\n\t\t\tfrom,\n\t\t\tto: from,\n\t\t\tstarted: false\n\t\t};\n\n\t\tconst onMove = (moveEvent: PointerEvent) => {\n\t\t\tconst session = sessionRef.current;\n\t\t\tif (!session) return;\n\n\t\t\tconst dx = moveEvent.clientX - session.startX;\n\t\t\t// Пока курсор не ушёл дальше порога, это ещё клик: нажатие на ручку не должно\n\t\t\t// дёргать колонку от дрожания руки.\n\t\t\tif (!session.started) {\n\t\t\t\tif (Math.abs(dx) < DRAG_THRESHOLD) return;\n\t\t\t\tsession.started = true;\n\t\t\t\tsetDragKey(session.key);\n\t\t\t\tcellsOf(session.table, session.key).forEach(markOpaque);\n\t\t\t}\n\n\t\t\tconst to = targetIndex(session, dx);\n\t\t\tsession.to = to;\n\n\t\t\t// Колонка, которая окажется справа от пустого слота: ей нужен левый край.\n\t\t\tconst order = orderAfter(session, to);\n\t\t\tconst afterSlot = order[order.indexOf(session.key) + 1];\n\n\t\t\tsetColumnTransform(session.table, session.key, dx, true, session.borderColor);\n\t\t\tlatest.current.columns.forEach((columnKey, index) => {\n\t\t\t\tif (index === session.from) return;\n\t\t\t\tconst shift = shiftOf(session, index, to);\n\t\t\t\tconst leftEdge = columnKey === afterSlot ? session.borderColor : undefined;\n\t\t\t\tsetColumnTransform(session.table, columnKey, shift, false, leftEdge);\n\t\t\t});\n\t\t};\n\n\t\tconst onUp = () => {\n\t\t\twindow.removeEventListener(\"pointermove\", onMove);\n\t\t\twindow.removeEventListener(\"pointerup\", onUp);\n\t\t\twindow.removeEventListener(\"pointercancel\", onCancel);\n\t\t\tfinish(sessionRef.current?.started ?? false);\n\t\t};\n\n\t\tconst onCancel = () => {\n\t\t\twindow.removeEventListener(\"pointermove\", onMove);\n\t\t\twindow.removeEventListener(\"pointerup\", onUp);\n\t\t\twindow.removeEventListener(\"pointercancel\", onCancel);\n\t\t\tfinish(false);\n\t\t};\n\n\t\twindow.addEventListener(\"pointermove\", onMove);\n\t\twindow.addEventListener(\"pointerup\", onUp);\n\t\twindow.addEventListener(\"pointercancel\", onCancel);\n\t};\n\n\treturn {\n\t\tdragKey,\n\t\tgetGripProps: (key: K) => ({\n\t\t\tonPointerDown: onPointerDown(key),\n\t\t\t// Без этого браузер на тач-устройстве начнёт прокрутку вместо перетаскивания.\n\t\t\tstyle: { touchAction: \"none\" as const }\n\t\t}),\n\t\tgetColumnProps: (key: K) => ({ [COLUMN_KEY_ATTRIBUTE]: key })\n\t};\n}\n"],"names":["COLUMN_KEY_ATTRIBUTE","TRANSITION_MS","DRAG_THRESHOLD","useColumnReorder","columns","widths","onReorder","locked","dragKey","setDragKey","useState","sessionRef","useRef","settleTimer","opaqueCells","touchedCells","lockedSet","useMemo","latest","useEffect","cellsOf","table","key","markOpaque","cell","classes","opaqueCellStyles","className","clearOpaque","rememberCell","transform","transition","position","zIndex","boxShadow","setColumnSeparator","leftEdge","setColumnTransform","shift","dragged","moving","clearTransforms","styles","targetIndex","session","dx","currentWidths","draggedCenter","best","bestDistance","index","center","distance","shiftOf","to","width","from","orderAfter","currentColumns","next","movableKeys","moved","slot","offsetAfter","sum","columnKey","finish","commit","settleShift","onPointerDown","event","offsets","borderColor","head","style","_","onMove","moveEvent","order","afterSlot","onUp","onCancel"],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
var H = Object.defineProperty;
|
|
2
|
+
var X = (n, t, e) => t in n ? H(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e;
|
|
3
|
+
var R = (n, t, e) => X(n, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import { useMemo as M, useState as z, useRef as x } from "react";
|
|
5
|
+
class F {
|
|
6
|
+
constructor(t) {
|
|
7
|
+
R(this, "state");
|
|
8
|
+
this.minWidth = t;
|
|
9
|
+
}
|
|
10
|
+
start(t, e, r) {
|
|
11
|
+
this.state = { columnKey: t, startWidth: e, startX: r };
|
|
12
|
+
}
|
|
13
|
+
getWidth(t) {
|
|
14
|
+
if (this.state)
|
|
15
|
+
return Math.max(this.minWidth, this.state.startWidth + t - this.state.startX);
|
|
16
|
+
}
|
|
17
|
+
getColumnKey() {
|
|
18
|
+
return this.state?.columnKey;
|
|
19
|
+
}
|
|
20
|
+
end() {
|
|
21
|
+
this.state = void 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function P(n, t) {
|
|
25
|
+
const e = n.closest("table");
|
|
26
|
+
if (!e || n.cellIndex < 0) return;
|
|
27
|
+
const r = $(n), c = [n, ..._(e, r)], i = document.createElement("div");
|
|
28
|
+
i.setAttribute("aria-hidden", "true"), i.style.position = "absolute", i.style.left = "-10000px", i.style.top = "0", i.style.visibility = "hidden", i.style.width = "max-content";
|
|
29
|
+
const d = e.cloneNode(!1);
|
|
30
|
+
d.removeAttribute("style"), d.style.width = "max-content", d.style.maxWidth = "none", d.style.tableLayout = "auto", d.createTHead().insertRow().appendChild(A(c[0]));
|
|
31
|
+
const h = d.createTBody();
|
|
32
|
+
c.slice(1).forEach((p) => h.insertRow().appendChild(A(p))), i.appendChild(d), document.body.appendChild(i);
|
|
33
|
+
const m = Math.ceil(d.getBoundingClientRect().width);
|
|
34
|
+
return document.body.removeChild(i), Math.max(t.minWidth, Math.min(t.maxWidth, m));
|
|
35
|
+
}
|
|
36
|
+
function _(n, t) {
|
|
37
|
+
const e = [];
|
|
38
|
+
return Array.from(n.tBodies).forEach((r) => {
|
|
39
|
+
Array.from(r.rows).forEach((c) => {
|
|
40
|
+
const i = N(c, t);
|
|
41
|
+
i && i.getClientRects().length && e.push(i);
|
|
42
|
+
});
|
|
43
|
+
}), e;
|
|
44
|
+
}
|
|
45
|
+
function $(n) {
|
|
46
|
+
let t = 0, e = n.previousElementSibling;
|
|
47
|
+
for (; e; )
|
|
48
|
+
t += e.colSpan || 1, e = e.previousElementSibling;
|
|
49
|
+
return t;
|
|
50
|
+
}
|
|
51
|
+
function N(n, t) {
|
|
52
|
+
let e = 0;
|
|
53
|
+
for (const r of Array.from(n.cells)) {
|
|
54
|
+
const c = r.colSpan || 1;
|
|
55
|
+
if (t >= e && t < e + c)
|
|
56
|
+
return c === 1 ? r : void 0;
|
|
57
|
+
e += c;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function A(n) {
|
|
61
|
+
const t = n.cloneNode(!0), e = getComputedStyle(n);
|
|
62
|
+
return [
|
|
63
|
+
"font",
|
|
64
|
+
"letter-spacing",
|
|
65
|
+
"word-spacing",
|
|
66
|
+
"text-transform",
|
|
67
|
+
"white-space",
|
|
68
|
+
"word-break",
|
|
69
|
+
"overflow-wrap",
|
|
70
|
+
"hyphens"
|
|
71
|
+
].forEach((r) => t.style.setProperty(r, e.getPropertyValue(r))), t.style.width = "auto", t.style.minWidth = "0", t.style.maxWidth = "none", t;
|
|
72
|
+
}
|
|
73
|
+
const U = 60, k = 500;
|
|
74
|
+
function q(n, t = {}) {
|
|
75
|
+
const e = t.minWidth ?? U, r = t.maxWidth ?? k, c = M(() => new Map(n.map((o) => [o.key, o])), [n]), i = M(
|
|
76
|
+
() => Object.fromEntries(n.map((o) => [o.key, o.defaultWidth])),
|
|
77
|
+
[n]
|
|
78
|
+
), [d, W] = z(i), h = x({}), m = x(null), p = x(void 0);
|
|
79
|
+
p.current || (p.current = new F(e));
|
|
80
|
+
const b = (o, s) => {
|
|
81
|
+
const l = c.get(o), u = l?.minWidth ?? e, a = l?.maxWidth ?? r;
|
|
82
|
+
return Math.min(a, Math.max(u, s));
|
|
83
|
+
}, L = (o) => (s) => {
|
|
84
|
+
h.current[o] = s ?? void 0;
|
|
85
|
+
}, S = (o) => (s) => {
|
|
86
|
+
s.preventDefault(), s.stopPropagation();
|
|
87
|
+
const l = h.current[o], u = m.current;
|
|
88
|
+
if (!l || !u) return;
|
|
89
|
+
const a = p.current, C = l.getBoundingClientRect().width, B = u.getBoundingClientRect().width;
|
|
90
|
+
a.start(o, C, s.clientX);
|
|
91
|
+
const v = (w) => {
|
|
92
|
+
const y = a.getWidth(w.clientX), f = a.getColumnKey();
|
|
93
|
+
if (y == null || !f) return;
|
|
94
|
+
const g = b(f, y);
|
|
95
|
+
l.style.width = `${g}px`, u.style.width = `${B + g - C}px`;
|
|
96
|
+
}, E = (w) => {
|
|
97
|
+
const y = a.getWidth(w.clientX), f = a.getColumnKey();
|
|
98
|
+
if (a.end(), document.removeEventListener("mousemove", v), document.removeEventListener("mouseup", E), y != null && f) {
|
|
99
|
+
const g = b(f, y);
|
|
100
|
+
W((I) => ({ ...I, [f]: g }));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
document.addEventListener("mousemove", v), document.addEventListener("mouseup", E);
|
|
104
|
+
}, T = (o) => () => {
|
|
105
|
+
const s = h.current[o];
|
|
106
|
+
if (!s) return;
|
|
107
|
+
const l = c.get(o), u = P(s, {
|
|
108
|
+
minWidth: l?.minWidth ?? e,
|
|
109
|
+
maxWidth: l?.maxWidth ?? r
|
|
110
|
+
});
|
|
111
|
+
u != null && W((a) => ({ ...a, [o]: u }));
|
|
112
|
+
}, D = (o) => ({
|
|
113
|
+
onMouseDown: S(o),
|
|
114
|
+
onDoubleClick: T(o)
|
|
115
|
+
}), K = n.reduce((o, s) => o + (d[s.key] ?? s.defaultWidth), 0);
|
|
116
|
+
return { tableRef: m, widths: d, totalWidth: K, getHeaderRef: L, getResizeHandleProps: D, reset: () => {
|
|
117
|
+
let o = 0;
|
|
118
|
+
n.forEach((s) => {
|
|
119
|
+
o += s.defaultWidth;
|
|
120
|
+
const l = h.current[s.key];
|
|
121
|
+
l && (l.style.width = `${s.defaultWidth}px`);
|
|
122
|
+
}), m.current && (m.current.style.width = `${o}px`), W(i);
|
|
123
|
+
} };
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
F as C,
|
|
127
|
+
P as g,
|
|
128
|
+
q as u
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=useColumnResize-wjfEyBBg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useColumnResize-wjfEyBBg.js","sources":["../src/components/table/column-resize/columnResizeSession.ts","../src/components/table/column-resize/columnAutoFit.ts","../src/components/table/column-resize/useColumnResize.ts"],"sourcesContent":["/**\n * Состояние одной drag-сессии ресайза колонки.\n *\n * Чистый DOM/framework-agnostic класс без зависимостей от React — считает новую ширину\n * по формуле `clamp(minWidth, startWidth + dx)`. Используется вместе с прямой мутацией\n * стилей DOM во время `mousemove`, чтобы не гонять React state на каждый пиксель.\n *\n * @example\n * const session = new ColumnResizeSession(60);\n *\n * session.start(\"name\", header.offsetWidth, event.clientX); // mousedown\n * header.style.width = `${session.getWidth(event.clientX)}px`; // mousemove\n * session.end(); // mouseup\n */\ninterface ResizeSessionState {\n\tcolumnKey: string;\n\tstartWidth: number;\n\tstartX: number;\n}\n\nexport class ColumnResizeSession {\n\tprivate state?: ResizeSessionState;\n\n\tconstructor(private minWidth: number) {}\n\n\tstart(columnKey: string, startWidth: number, startX: number) {\n\t\tthis.state = { columnKey, startWidth, startX };\n\t}\n\n\tgetWidth(currentX: number) {\n\t\tif (!this.state) return;\n\t\treturn Math.max(this.minWidth, this.state.startWidth + currentX - this.state.startX);\n\t}\n\n\tgetColumnKey() {\n\t\treturn this.state?.columnKey;\n\t}\n\n\tend() {\n\t\tthis.state = undefined;\n\t}\n}\n","/**\n * Автофит колонки по содержимому (двойной клик по ручке ресайза).\n *\n * Меряет «естественную» ширину контента колонки: клонирует заголовок и видимые ячейки\n * тела в скрытую sandbox-таблицу с `table-layout: auto` / `width: max-content` и берёт\n * итоговую ширину. `cloneCell` переносит на клон вычисленные шрифтовые стили, иначе\n * замер вне исходного каскада получился бы неверным.\n */\nexport interface ColumnAutoFitOptions {\n\tminWidth: number;\n\tmaxWidth: number;\n}\n\n/**\n * Ширина колонки по её содержимому, в пикселях. `undefined` — если замерить не удалось\n * (заголовок вне таблицы). Результат зажат между `minWidth` и `maxWidth`.\n *\n * @example\n * const width = getColumnAutoFitWidth(headerElement, { minWidth: 60, maxWidth: 500 });\n * if (width) setWidths((current) => ({ ...current, name: width }));\n */\nexport function getColumnAutoFitWidth(header: HTMLTableCellElement, options: ColumnAutoFitOptions): number | undefined {\n\tconst table = header.closest(\"table\");\n\tif (!table || header.cellIndex < 0) return;\n\n\tconst columnIndex = getLogicalColumnIndex(header);\n\tconst cells = [header, ...getVisibleCells(table, columnIndex)];\n\n\tconst sandbox = document.createElement(\"div\");\n\tsandbox.setAttribute(\"aria-hidden\", \"true\");\n\tsandbox.style.position = \"absolute\";\n\tsandbox.style.left = \"-10000px\";\n\tsandbox.style.top = \"0\";\n\tsandbox.style.visibility = \"hidden\";\n\tsandbox.style.width = \"max-content\";\n\n\tconst measuredTable = table.cloneNode(false) as HTMLTableElement;\n\tmeasuredTable.removeAttribute(\"style\");\n\tmeasuredTable.style.width = \"max-content\";\n\tmeasuredTable.style.maxWidth = \"none\";\n\tmeasuredTable.style.tableLayout = \"auto\";\n\n\tconst thead = measuredTable.createTHead();\n\tthead.insertRow().appendChild(cloneCell(cells[0]));\n\tconst tbody = measuredTable.createTBody();\n\tcells.slice(1).forEach((cell) => tbody.insertRow().appendChild(cloneCell(cell)));\n\tsandbox.appendChild(measuredTable);\n\tdocument.body.appendChild(sandbox);\n\n\tconst width = Math.ceil(measuredTable.getBoundingClientRect().width);\n\tdocument.body.removeChild(sandbox);\n\treturn Math.max(options.minWidth, Math.min(options.maxWidth, width));\n}\n\nfunction getVisibleCells(table: HTMLTableElement, columnIndex: number) {\n\tconst cells: HTMLTableCellElement[] = [];\n\tArray.from(table.tBodies).forEach((tbody) => {\n\t\tArray.from(tbody.rows).forEach((row) => {\n\t\t\tconst cell = getCellAtColumn(row, columnIndex);\n\t\t\tif (cell && cell.getClientRects().length) cells.push(cell);\n\t\t});\n\t});\n\treturn cells;\n}\n\nfunction getLogicalColumnIndex(cell: HTMLTableCellElement) {\n\tlet index = 0;\n\tlet previous = cell.previousElementSibling as HTMLTableCellElement | null;\n\twhile (previous) {\n\t\tindex += previous.colSpan || 1;\n\t\tprevious = previous.previousElementSibling as HTMLTableCellElement | null;\n\t}\n\treturn index;\n}\n\nfunction getCellAtColumn(row: HTMLTableRowElement, columnIndex: number) {\n\tlet currentIndex = 0;\n\tfor (const cell of Array.from(row.cells)) {\n\t\tconst colSpan = cell.colSpan || 1;\n\t\tif (columnIndex >= currentIndex && columnIndex < currentIndex + colSpan)\n\t\t\treturn colSpan === 1 ? cell : undefined;\n\t\tcurrentIndex += colSpan;\n\t}\n}\n\nfunction cloneCell(cell: HTMLTableCellElement) {\n\tconst clone = cell.cloneNode(true) as HTMLTableCellElement;\n\tconst styles = getComputedStyle(cell);\n\t[\n\t\t\"font\",\n\t\t\"letter-spacing\",\n\t\t\"word-spacing\",\n\t\t\"text-transform\",\n\t\t\"white-space\",\n\t\t\"word-break\",\n\t\t\"overflow-wrap\",\n\t\t\"hyphens\"\n\t].forEach((property) => clone.style.setProperty(property, styles.getPropertyValue(property)));\n\tclone.style.width = \"auto\";\n\tclone.style.minWidth = \"0\";\n\tclone.style.maxWidth = \"none\";\n\treturn clone;\n}\n","import { type MouseEvent as ReactMouseEvent, type RefObject, useMemo, useRef, useState } from \"react\";\nimport { getColumnAutoFitWidth } from \"./columnAutoFit\";\nimport { ColumnResizeSession } from \"./columnResizeSession\";\n\nexport interface ResizableColumn<K extends string = string> {\n\tkey: K;\n\tdefaultWidth: number;\n\tminWidth: number;\n\tmaxWidth?: number;\n}\n\nexport interface UseColumnResizeOptions {\n\t/** Глобальный минимум-подстраховка для драга. */\n\tminWidth?: number;\n\t/** Дефолтный максимум для колонок без своего `maxWidth` (используется в драге и автофите). */\n\tmaxWidth?: number;\n}\n\nexport interface UseColumnResizeResult<K extends string> {\n\t/**\n\t * Ref на саму таблицу — вешается на `<Table ref>`.\n\t *\n\t * Именно `RefObject<HTMLTableElement>`, а не `RefObject<HTMLTableElement | null>`: это то,\n\t * что возвращает `useRef` в типах React 18, и то, что принимает проп `ref` у `forwardRef`.\n\t * Аннотация с `| null` пришла из React 19 и делала результат хука непередаваемым в `ref`.\n\t */\n\ttableRef: RefObject<HTMLTableElement>;\n\twidths: Record<K, number>;\n\ttotalWidth: number;\n\tgetHeaderRef: (key: K) => (el: HTMLTableCellElement | null) => void;\n\tgetResizeHandleProps: (key: K) => {\n\t\tonMouseDown: (event: ReactMouseEvent) => void;\n\t\tonDoubleClick: (event: ReactMouseEvent) => void;\n\t};\n\treset: () => void;\n}\n\nconst DEFAULT_MIN_WIDTH = 60;\nconst DEFAULT_MAX_WIDTH = 500;\n\n/**\n * Ресайз и автофит колонок обычной HTML-таблицы, без table-библиотек.\n *\n * Во время `mousemove` ширина мутируется напрямую в DOM (`th.style.width`, `table.style.width`)\n * через рефы — без React state на каждый пиксель. На `mouseup` — один коммит в state.\n *\n * `columns` должен быть стабильной ссылкой (модульная константа или `useMemo` вызывающей\n * стороны) — из неё один раз считаются дефолтные ширины.\n *\n * @example\n * const COLUMNS = [{ key: \"name\", defaultWidth: 240, minWidth: 120 }];\n *\n * const { tableRef, widths, totalWidth, getHeaderRef, getResizeHandleProps } = useColumnResize(COLUMNS);\n *\n * <Table ref={tableRef} style={{ width: totalWidth, tableLayout: \"fixed\" }}>\n * <TableHead ref={getHeaderRef(\"name\")} className=\"relative\" style={{ width: widths.name }}>\n * Контрагент\n * <TableResizeHandle {...getResizeHandleProps(\"name\")} />\n * </TableHead>\n */\nexport function useColumnResize<K extends string>(\n\tcolumns: ResizableColumn<K>[],\n\toptions: UseColumnResizeOptions = {}\n): UseColumnResizeResult<K> {\n\tconst globalMinWidth = options.minWidth ?? DEFAULT_MIN_WIDTH;\n\tconst globalMaxWidth = options.maxWidth ?? DEFAULT_MAX_WIDTH;\n\n\tconst columnsByKey = useMemo(() => new Map(columns.map((c) => [c.key, c])), [columns]);\n\tconst defaultWidths = useMemo(\n\t\t() => Object.fromEntries(columns.map((c) => [c.key, c.defaultWidth])) as Record<K, number>,\n\t\t[columns]\n\t);\n\n\tconst [widths, setWidths] = useState<Record<K, number>>(defaultWidths);\n\tconst theadRefs = useRef<Partial<Record<K, HTMLTableCellElement>>>({});\n\tconst tableRef = useRef<HTMLTableElement>(null);\n\tconst sessionRef = useRef<ColumnResizeSession | undefined>(undefined);\n\tif (!sessionRef.current) sessionRef.current = new ColumnResizeSession(globalMinWidth);\n\n\tconst clampWidth = (key: K, width: number) => {\n\t\tconst col = columnsByKey.get(key);\n\t\tconst min = col?.minWidth ?? globalMinWidth;\n\t\tconst max = col?.maxWidth ?? globalMaxWidth;\n\t\treturn Math.min(max, Math.max(min, width));\n\t};\n\n\tconst getHeaderRef = (key: K) => (el: HTMLTableCellElement | null) => {\n\t\ttheadRefs.current[key] = el ?? undefined;\n\t};\n\n\tconst startResize = (key: K) => (event: ReactMouseEvent) => {\n\t\tevent.preventDefault();\n\t\tevent.stopPropagation();\n\t\tconst th = theadRefs.current[key];\n\t\tconst table = tableRef.current;\n\t\tif (!th || !table) return;\n\n\t\tconst session = sessionRef.current!;\n\t\tconst startWidth = th.getBoundingClientRect().width;\n\t\tconst startTableWidth = table.getBoundingClientRect().width;\n\t\tsession.start(key, startWidth, event.clientX);\n\n\t\tconst handleMove = (e: MouseEvent) => {\n\t\t\tconst rawWidth = session.getWidth(e.clientX);\n\t\t\tconst columnKey = session.getColumnKey() as K | undefined;\n\t\t\tif (rawWidth == null || !columnKey) return;\n\t\t\tconst width = clampWidth(columnKey, rawWidth);\n\t\t\tth.style.width = `${width}px`;\n\t\t\ttable.style.width = `${startTableWidth + width - startWidth}px`;\n\t\t};\n\n\t\tconst handleUp = (e: MouseEvent) => {\n\t\t\tconst rawWidth = session.getWidth(e.clientX);\n\t\t\tconst columnKey = session.getColumnKey() as K | undefined;\n\t\t\tsession.end();\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMove);\n\t\t\tdocument.removeEventListener(\"mouseup\", handleUp);\n\t\t\tif (rawWidth != null && columnKey) {\n\t\t\t\tconst width = clampWidth(columnKey, rawWidth);\n\t\t\t\tsetWidths((prev) => ({ ...prev, [columnKey]: width }));\n\t\t\t}\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMove);\n\t\tdocument.addEventListener(\"mouseup\", handleUp);\n\t};\n\n\tconst autoFit = (key: K) => () => {\n\t\tconst th = theadRefs.current[key];\n\t\tif (!th) return;\n\t\tconst col = columnsByKey.get(key);\n\t\tconst width = getColumnAutoFitWidth(th, {\n\t\t\tminWidth: col?.minWidth ?? globalMinWidth,\n\t\t\tmaxWidth: col?.maxWidth ?? globalMaxWidth\n\t\t});\n\t\tif (width != null) setWidths((prev) => ({ ...prev, [key]: width }));\n\t};\n\n\tconst getResizeHandleProps = (key: K) => ({\n\t\tonMouseDown: startResize(key),\n\t\tonDoubleClick: autoFit(key)\n\t});\n\n\tconst totalWidth = columns.reduce((sum, col) => sum + (widths[col.key] ?? col.defaultWidth), 0);\n\n\t/**\n\t * Сбрасывает ширины к дефолтным.\n\t *\n\t * Одного `setWidths` мало. Во время драга ширины пишутся прямо в DOM, а React сравнивает\n\t * пропсы с пропсами, а не с DOM. Если закоммиченное в React значение совпало с дефолтным\n\t * (например, драг и сброс попали в один батч, или колонку вернули к исходной ширине),\n\t * React решает, что менять нечего, и в DOM навсегда остаётся значение от драга.\n\t * Поэтому дефолты пишем в DOM явно. Именно пишем, а не снимаем инлайновые стили:\n\t * снятие вернуло бы колонкам авто-раскладку, и при `table-layout: fixed` те из них,\n\t * которые React не тронул, разделили бы ширину поровну вместо своих дефолтных значений.\n\t */\n\tconst reset = () => {\n\t\tlet total = 0;\n\t\tcolumns.forEach((col) => {\n\t\t\ttotal += col.defaultWidth;\n\t\t\tconst th = theadRefs.current[col.key];\n\t\t\tif (th) th.style.width = `${col.defaultWidth}px`;\n\t\t});\n\t\tif (tableRef.current) tableRef.current.style.width = `${total}px`;\n\t\tsetWidths(defaultWidths);\n\t};\n\n\treturn { tableRef, widths, totalWidth, getHeaderRef, getResizeHandleProps, reset };\n}\n"],"names":["ColumnResizeSession","minWidth","__publicField","columnKey","startWidth","startX","currentX","getColumnAutoFitWidth","header","options","table","columnIndex","getLogicalColumnIndex","cells","getVisibleCells","sandbox","measuredTable","cloneCell","tbody","cell","width","row","getCellAtColumn","index","previous","currentIndex","colSpan","clone","styles","property","DEFAULT_MIN_WIDTH","DEFAULT_MAX_WIDTH","useColumnResize","columns","globalMinWidth","globalMaxWidth","columnsByKey","useMemo","c","defaultWidths","widths","setWidths","useState","theadRefs","useRef","tableRef","sessionRef","clampWidth","key","col","min","max","getHeaderRef","el","startResize","event","th","session","startTableWidth","handleMove","e","rawWidth","handleUp","prev","autoFit","getResizeHandleProps","totalWidth","sum","total"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDebounce-BRah78CF.js","sources":["../src/hooks/useDebounce.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\
|
|
1
|
+
{"version":3,"file":"useDebounce-BRah78CF.js","sources":["../src/hooks/useDebounce.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\n/**\n * Хук для дебаунса значения\n * @param value - значение для дебаунса\n * @param delay - задержка в миллисекундах\n * @returns дебаунсированное значение\n */\nexport function useDebounce<T>(value: T, delay: number): T {\n\tconst [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n\tuseEffect(() => {\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetDebouncedValue(value);\n\t\t}, delay);\n\n\t\treturn () => {\n\t\t\tclearTimeout(timer);\n\t\t};\n\t}, [value, delay]);\n\n\treturn debouncedValue;\n}\n"],"names":["useDebounce","value","delay","debouncedValue","setDebouncedValue","useState","useEffect","timer"],"mappings":";AAQgB,SAAAA,EAAeC,GAAUC,GAAkB;AAC1D,QAAM,CAACC,GAAgBC,CAAiB,IAAIC,EAAYJ,CAAK;AAE7D,SAAAK,EAAU,MAAM;AACT,UAAAC,IAAQ,WAAW,MAAM;AAC9B,MAAAH,EAAkBH,CAAK;AAAA,OACrBC,CAAK;AAER,WAAO,MAAM;AACZ,mBAAaK,CAAK;AAAA,IACnB;AAAA,EAAA,GACE,CAACN,GAAOC,CAAK,CAAC,GAEVC;AACR;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFileUpload-Bnl3P8mP.js","sources":["../src/hooks/use-file-upload/errorMessages.ts","../src/hooks/use-file-upload/useFileUpload.ts"],"sourcesContent":["import { formatBytes } from \"@/lib/utils/file/formatBytes\";\r\n\r\n/**\r\n * Интерфейс для кастомизации сообщений об ошибках в useFileUpload\r\n */\r\nexport interface FileUploadErrorMessages {\r\n\t/**\r\n\t * Сообщение когда файл превышает максимальный размер\r\n\t * @param fileName - имя файла\r\n\t * @param maxSizeBytes - максимальный размер в байтах\r\n\t */\r\n\tfileTooLarge?: (fileName: string, maxSizeBytes: number) => string;\r\n\r\n\t/**\r\n\t * Сообщение когда тип файла не разрешен\r\n\t * @param fileName - имя файла\r\n\t */\r\n\tinvalidFileType?: (fileName: string) => string;\r\n\r\n\t/**\r\n\t * Сообщение когда превышено максимальное количество файлов\r\n\t * @param maxFiles - максимальное количество файлов\r\n\t */\r\n\ttooManyFiles?: (maxFiles: number) => string;\r\n\r\n\t/**\r\n\t * Сообщение когда некоторые файлы превышают максимальный размер (для множественной загрузки)\r\n\t * @param maxSizeBytes - максимальный размер в байтах\r\n\t */\r\n\tsomeFilesTooLarge?: (maxSizeBytes: number) => string;\r\n\r\n\t/**\r\n\t * Сообщение когда файл превышает максимальный размер (для одиночной загрузки)\r\n\t * @param maxSizeBytes - максимальный размер в байтах\r\n\t */\r\n\tsingleFileTooLarge?: (maxSizeBytes: number) => string;\r\n}\r\n\r\n/**\r\n * Сообщения об ошибках по умолчанию\r\n */\r\nexport const defaultErrorMessages: Required<FileUploadErrorMessages> = {\r\n\tfileTooLarge: (fileName, maxSizeBytes) =>\r\n\t\t`File \"${fileName}\" exceeds the maximum size of ${formatBytes(maxSizeBytes)}.`,\r\n\tinvalidFileType: (fileName) => `File \"${fileName}\" is not an accepted file type.`,\r\n\ttooManyFiles: (maxFiles) => `You can only upload a maximum of ${maxFiles} files.`,\r\n\tsomeFilesTooLarge: (maxSizeBytes) => `Some files exceed the maximum size of ${formatBytes(maxSizeBytes)}.`,\r\n\tsingleFileTooLarge: (maxSizeBytes) => `File exceeds the maximum size of ${formatBytes(maxSizeBytes)}.`\r\n};\r\n\r\n/**\r\n * Сообщения об ошибках на русском языке\r\n */\r\nexport const defaultErrorMessagesRu: Required<FileUploadErrorMessages> = {\r\n\tfileTooLarge: (fileName, maxSizeBytes) =>\r\n\t\t`Файл \"${fileName}\" превышает максимальный размер ${formatBytes(maxSizeBytes)}`,\r\n\tinvalidFileType: (fileName) => `Файл \"${fileName}\" имеет недопустимый тип`,\r\n\ttooManyFiles: (maxFiles) => `Можно загрузить максимум ${maxFiles} файлов`,\r\n\tsomeFilesTooLarge: (maxSizeBytes) => `Некоторые файлы превышают максимальный размер ${formatBytes(maxSizeBytes)}`,\r\n\tsingleFileTooLarge: (maxSizeBytes) => `Файл превышает максимальный размер ${formatBytes(maxSizeBytes)}`\r\n};\r\n","import {\r\n\tuseCallback,\r\n\tuseMemo,\r\n\tuseRef,\r\n\tuseState,\r\n\ttype ChangeEvent,\r\n\ttype DragEvent,\r\n\ttype InputHTMLAttributes\r\n} from \"react\";\r\nimport { defaultErrorMessages, type FileUploadErrorMessages } from \"./errorMessages\";\r\n\r\nexport type FileData = {\r\n\tname: string;\r\n\tsize: number;\r\n\ttype: string;\r\n\tid?: string;\r\n};\r\n\r\nexport type FileMetadata = FileData & {\r\n\turl: string;\r\n\tid: string;\r\n};\r\n\r\nexport type FileValue = File | FileData;\r\n\r\nexport type FileWithPreview = {\r\n\tfile: FileValue;\r\n\tid: string;\r\n\tpreview?: string;\r\n};\r\n\r\n/**\r\n * Ограничения для загрузки файлов\r\n */\r\nexport type FileUploadConstraints = {\r\n\t/**\r\n\t * Максимальное количество файлов\r\n\t * Используется только когда multiple=true\r\n\t * @default Infinity\r\n\t */\r\n\tmaxFiles?: number;\r\n\r\n\t/**\r\n\t * Максимальный размер файла в байтах\r\n\t * @default Infinity\r\n\t */\r\n\tmaxSize?: number;\r\n\r\n\t/**\r\n\t * Допустимые типы файлов (строка MIME-типов или расширений через запятую)\r\n\t * @example \"image/jpeg,image/png,.pdf,.doc\"\r\n\t * @example \"*.jpg,*.png\"\r\n\t * @example \"image/*\"\r\n\t * @default \"*\"\r\n\t */\r\n\taccept?: string;\r\n\r\n\t/**\r\n\t * Разрешить выбор нескольких файлов\r\n\t * @default false\r\n\t */\r\n\tmultiple?: boolean;\r\n};\r\n\r\nexport type FileUploadOptions = FileUploadConstraints & {\r\n\tinitialFiles?: FileMetadata[];\r\n\tonFilesChange?: (files: FileWithPreview[]) => void;\r\n\tonFilesAdded?: (addedFiles: FileWithPreview[]) => void;\r\n\t/**\r\n\t * Кастомные сообщения об ошибках\r\n\t * Можно переопределить только нужные сообщения\r\n\t */\r\n\terrorMessages?: Partial<FileUploadErrorMessages>;\r\n};\r\n\r\nexport type FileUploadState = {\r\n\tfiles: FileWithPreview[];\r\n\tisDragging: boolean;\r\n\terrors: string[];\r\n};\r\n\r\nexport type FileUploadActions = {\r\n\taddFiles: (files: FileList | File[]) => void;\r\n\tremoveFile: (id: string) => void;\r\n\tclearFiles: () => void;\r\n\tclearErrors: () => void;\r\n\thandleDragEnter: (e: DragEvent<HTMLElement>) => void;\r\n\thandleDragLeave: (e: DragEvent<HTMLElement>) => void;\r\n\thandleDragOver: (e: DragEvent<HTMLElement>) => void;\r\n\thandleDrop: (e: DragEvent<HTMLElement>) => void;\r\n\thandleFileChange: (e: ChangeEvent<HTMLInputElement>) => void;\r\n\topenFileDialog: () => void;\r\n\tgetInputProps: (props?: InputHTMLAttributes<HTMLInputElement>) => InputHTMLAttributes<HTMLInputElement> & {\r\n\t\tref: React.Ref<HTMLInputElement>;\r\n\t};\r\n};\r\n\r\n/** Хук был взять из библиотеки originui.\r\n * Код можно посмотреть по ссылке: https://github.com/origin-space/originui/blob/main/docs/use-file-upload.md\r\n */\r\nexport const useFileUpload = (options: FileUploadOptions = {}): [FileUploadState, FileUploadActions] => {\r\n\tconst {\r\n\t\tmaxFiles = Infinity,\r\n\t\tmaxSize = Infinity,\r\n\t\taccept = \"*\",\r\n\t\tmultiple = false,\r\n\t\tinitialFiles = [],\r\n\t\tonFilesChange,\r\n\t\tonFilesAdded,\r\n\t\terrorMessages: customErrorMessages\r\n\t} = options;\r\n\r\n\t// Объединяем дефолтные и кастомные сообщения\r\n\tconst errorMessages = useMemo(() => ({ ...defaultErrorMessages, ...customErrorMessages }), [customErrorMessages]);\r\n\r\n\tconst [state, setState] = useState<FileUploadState>({\r\n\t\tfiles: initialFiles.map((file) => ({\r\n\t\t\tfile,\r\n\t\t\tid: file.id,\r\n\t\t\tpreview: file.url\r\n\t\t})),\r\n\t\tisDragging: false,\r\n\t\terrors: []\r\n\t});\r\n\r\n\tconst inputRef = useRef<HTMLInputElement>(null);\r\n\r\n\tconst validateFile = useCallback(\r\n\t\t(file: FileValue): string | null => {\r\n\t\t\tconst fileName = file instanceof File ? file.name : file.name;\r\n\r\n\t\t\tif (file.size > maxSize) {\r\n\t\t\t\treturn errorMessages.fileTooLarge(fileName, maxSize);\r\n\t\t\t}\r\n\r\n\t\t\tif (accept !== \"*\") {\r\n\t\t\t\tconst acceptedTypes = accept.split(\",\").map((type) => type.trim());\r\n\t\t\t\tconst fileType = file instanceof File ? file.type || \"\" : file.type;\r\n\t\t\t\tconst fileExtension = `.${fileName.split(\".\").pop()}`;\r\n\r\n\t\t\t\tconst isAccepted = acceptedTypes.some((type) => {\r\n\t\t\t\t\tif (type.startsWith(\".\")) {\r\n\t\t\t\t\t\treturn fileExtension.toLowerCase() === type.toLowerCase();\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (type.endsWith(\"/*\")) {\r\n\t\t\t\t\t\tconst baseType = type.split(\"/\")[0];\r\n\t\t\t\t\t\treturn fileType.startsWith(`${baseType}/`);\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn fileType === type;\r\n\t\t\t\t});\r\n\r\n\t\t\t\tif (!isAccepted) {\r\n\t\t\t\t\treturn errorMessages.invalidFileType(fileName);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn null;\r\n\t\t},\r\n\t\t[accept, maxSize, errorMessages]\r\n\t);\r\n\r\n\tconst createPreview = useCallback((file: File | FileMetadata): string | undefined => {\r\n\t\tif (file instanceof File) {\r\n\t\t\treturn URL.createObjectURL(file);\r\n\t\t}\r\n\t\treturn file.url;\r\n\t}, []);\r\n\r\n\tconst generateUniqueId = useCallback((file: File | FileMetadata): string => {\r\n\t\tif (file instanceof File) {\r\n\t\t\treturn `${file.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\r\n\t\t}\r\n\t\treturn file.id;\r\n\t}, []);\r\n\r\n\tconst clearFiles = useCallback(() => {\r\n\t\tsetState((prev) => {\r\n\t\t\t// Clean up object URLs\r\n\t\t\tprev.files.forEach((file) => {\r\n\t\t\t\tif (file.preview && file.file instanceof File && file.file.type.startsWith(\"image/\")) {\r\n\t\t\t\t\tURL.revokeObjectURL(file.preview);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\tif (inputRef.current) {\r\n\t\t\t\tinputRef.current.value = \"\";\r\n\t\t\t}\r\n\r\n\t\t\tconst newState = {\r\n\t\t\t\t...prev,\r\n\t\t\t\tfiles: [],\r\n\t\t\t\terrors: []\r\n\t\t\t};\r\n\r\n\t\t\tonFilesChange?.(newState.files);\r\n\t\t\treturn newState;\r\n\t\t});\r\n\t}, [onFilesChange]);\r\n\r\n\tconst addFiles = useCallback(\r\n\t\t(newFiles: FileList | File[]) => {\r\n\t\t\tif (!newFiles || newFiles.length === 0) return;\r\n\r\n\t\t\tconst newFilesArray = Array.from(newFiles);\r\n\t\t\tconst errors: string[] = [];\r\n\r\n\t\t\t// Clear existing errors when new files are uploaded\r\n\t\t\tsetState((prev) => ({ ...prev, errors: [] }));\r\n\r\n\t\t\t// In single file mode, clear existing files first\r\n\t\t\tif (!multiple) {\r\n\t\t\t\tclearFiles();\r\n\t\t\t}\r\n\r\n\t\t\t// Check if adding these files would exceed maxFiles (only in multiple mode)\r\n\t\t\tif (multiple && maxFiles !== Infinity && state.files.length + newFilesArray.length > maxFiles) {\r\n\t\t\t\terrors.push(errorMessages.tooManyFiles(maxFiles));\r\n\t\t\t\tsetState((prev) => ({ ...prev, errors }));\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tconst validFiles: FileWithPreview[] = [];\r\n\r\n\t\t\tnewFilesArray.forEach((file) => {\r\n\t\t\t\t// Only check for duplicates if multiple files are allowed\r\n\t\t\t\tif (multiple) {\r\n\t\t\t\t\tconst isDuplicate = state.files.some(\r\n\t\t\t\t\t\t(existingFile) => existingFile.file.name === file.name && existingFile.file.size === file.size\r\n\t\t\t\t\t);\r\n\r\n\t\t\t\t\t// Skip duplicate files silently\r\n\t\t\t\t\tif (isDuplicate) {\r\n\t\t\t\t\t\treturn;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Check file size\r\n\t\t\t\tif (file.size > maxSize) {\r\n\t\t\t\t\terrors.push(\r\n\t\t\t\t\t\tmultiple ? errorMessages.someFilesTooLarge(maxSize) : errorMessages.singleFileTooLarge(maxSize)\r\n\t\t\t\t\t);\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconst error = validateFile(file);\r\n\t\t\t\tif (error) {\r\n\t\t\t\t\terrors.push(error);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tvalidFiles.push({\r\n\t\t\t\t\t\tfile,\r\n\t\t\t\t\t\tid: generateUniqueId(file),\r\n\t\t\t\t\t\tpreview: createPreview(file)\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// Only update state if we have valid files to add\r\n\t\t\tif (validFiles.length > 0) {\r\n\t\t\t\t// Call the onFilesAdded callback with the newly added valid files\r\n\t\t\t\tonFilesAdded?.(validFiles);\r\n\r\n\t\t\t\tsetState((prev) => {\r\n\t\t\t\t\tconst newFiles = !multiple ? validFiles : [...prev.files, ...validFiles];\r\n\t\t\t\t\tonFilesChange?.(newFiles);\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\t...prev,\r\n\t\t\t\t\t\tfiles: newFiles,\r\n\t\t\t\t\t\terrors\r\n\t\t\t\t\t};\r\n\t\t\t\t});\r\n\t\t\t} else if (errors.length > 0) {\r\n\t\t\t\tsetState((prev) => ({\r\n\t\t\t\t\t...prev,\r\n\t\t\t\t\terrors\r\n\t\t\t\t}));\r\n\t\t\t}\r\n\r\n\t\t\t// Reset input value after handling files\r\n\t\t\tif (inputRef.current) {\r\n\t\t\t\tinputRef.current.value = \"\";\r\n\t\t\t}\r\n\t\t},\r\n\t\t[\r\n\t\t\tstate.files,\r\n\t\t\tmaxFiles,\r\n\t\t\tmultiple,\r\n\t\t\tmaxSize,\r\n\t\t\tvalidateFile,\r\n\t\t\tcreatePreview,\r\n\t\t\tgenerateUniqueId,\r\n\t\t\tclearFiles,\r\n\t\t\tonFilesChange,\r\n\t\t\tonFilesAdded,\r\n\t\t\terrorMessages\r\n\t\t]\r\n\t);\r\n\r\n\tconst removeFile = useCallback(\r\n\t\t(id: string) => {\r\n\t\t\tsetState((prev) => {\r\n\t\t\t\tconst fileToRemove = prev.files.find((file) => file.id === id);\r\n\t\t\t\tif (\r\n\t\t\t\t\tfileToRemove &&\r\n\t\t\t\t\tfileToRemove.preview &&\r\n\t\t\t\t\tfileToRemove.file instanceof File &&\r\n\t\t\t\t\tfileToRemove.file.type.startsWith(\"image/\")\r\n\t\t\t\t) {\r\n\t\t\t\t\tURL.revokeObjectURL(fileToRemove.preview);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconst newFiles = prev.files.filter((file) => file.id !== id);\r\n\t\t\t\tonFilesChange?.(newFiles);\r\n\r\n\t\t\t\treturn {\r\n\t\t\t\t\t...prev,\r\n\t\t\t\t\tfiles: newFiles,\r\n\t\t\t\t\terrors: []\r\n\t\t\t\t};\r\n\t\t\t});\r\n\t\t},\r\n\t\t[onFilesChange]\r\n\t);\r\n\r\n\tconst clearErrors = useCallback(() => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\terrors: []\r\n\t\t}));\r\n\t}, []);\r\n\r\n\tconst handleDragEnter = useCallback((e: DragEvent<HTMLElement>) => {\r\n\t\te.preventDefault();\r\n\t\te.stopPropagation();\r\n\t\tsetState((prev) => ({ ...prev, isDragging: true }));\r\n\t}, []);\r\n\r\n\tconst handleDragLeave = useCallback((e: DragEvent<HTMLElement>) => {\r\n\t\te.preventDefault();\r\n\t\te.stopPropagation();\r\n\r\n\t\tif (e.currentTarget.contains(e.relatedTarget as Node)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tsetState((prev) => ({ ...prev, isDragging: false }));\r\n\t}, []);\r\n\r\n\tconst handleDragOver = useCallback((e: DragEvent<HTMLElement>) => {\r\n\t\te.preventDefault();\r\n\t\te.stopPropagation();\r\n\t}, []);\r\n\r\n\tconst handleDrop = useCallback(\r\n\t\t(e: DragEvent<HTMLElement>) => {\r\n\t\t\te.preventDefault();\r\n\t\t\te.stopPropagation();\r\n\t\t\tsetState((prev) => ({ ...prev, isDragging: false }));\r\n\r\n\t\t\t// Don't process files if the input is disabled\r\n\t\t\tif (inputRef.current?.disabled) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\r\n\t\t\t\t// In single file mode, only use the first file\r\n\t\t\t\tif (!multiple) {\r\n\t\t\t\t\tconst file = e.dataTransfer.files[0];\r\n\t\t\t\t\taddFiles([file]);\r\n\t\t\t\t} else {\r\n\t\t\t\t\taddFiles(e.dataTransfer.files);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\t[addFiles, multiple]\r\n\t);\r\n\r\n\tconst handleFileChange = useCallback(\r\n\t\t(e: ChangeEvent<HTMLInputElement>) => {\r\n\t\t\tif (e.target.files && e.target.files.length > 0) {\r\n\t\t\t\taddFiles(e.target.files);\r\n\t\t\t}\r\n\t\t},\r\n\t\t[addFiles]\r\n\t);\r\n\r\n\tconst openFileDialog = useCallback(() => {\r\n\t\tif (inputRef.current) {\r\n\t\t\tinputRef.current.click();\r\n\t\t}\r\n\t}, []);\r\n\r\n\tconst getInputProps = useCallback(\r\n\t\t(props: InputHTMLAttributes<HTMLInputElement> = {}) => {\r\n\t\t\treturn {\r\n\t\t\t\t...props,\r\n\t\t\t\ttype: \"file\" as const,\r\n\t\t\t\tonChange: handleFileChange,\r\n\t\t\t\taccept: props.accept || accept,\r\n\t\t\t\tmultiple: props.multiple !== undefined ? props.multiple : multiple,\r\n\t\t\t\tref: inputRef\r\n\t\t\t};\r\n\t\t},\r\n\t\t[accept, multiple, handleFileChange]\r\n\t);\r\n\r\n\treturn [\r\n\t\tstate,\r\n\t\t{\r\n\t\t\taddFiles,\r\n\t\t\tremoveFile,\r\n\t\t\tclearFiles,\r\n\t\t\tclearErrors,\r\n\t\t\thandleDragEnter,\r\n\t\t\thandleDragLeave,\r\n\t\t\thandleDragOver,\r\n\t\t\thandleDrop,\r\n\t\t\thandleFileChange,\r\n\t\t\topenFileDialog,\r\n\t\t\tgetInputProps\r\n\t\t}\r\n\t];\r\n};\r\n"],"names":["defaultErrorMessages","fileName","maxSizeBytes","formatBytes","maxFiles","defaultErrorMessagesRu","useFileUpload","options","maxSize","accept","multiple","initialFiles","onFilesChange","onFilesAdded","customErrorMessages","errorMessages","useMemo","state","setState","useState","file","inputRef","useRef","validateFile","useCallback","acceptedTypes","type","fileType","fileExtension","baseType","createPreview","generateUniqueId","clearFiles","prev","newState","addFiles","newFiles","newFilesArray","errors","validFiles","existingFile","error","removeFile","id","fileToRemove","clearErrors","handleDragEnter","handleDragLeave","handleDragOver","handleDrop","handleFileChange","openFileDialog","getInputProps","props"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useFileUpload-Bnl3P8mP.js","sources":["../src/hooks/use-file-upload/errorMessages.ts","../src/hooks/use-file-upload/useFileUpload.ts"],"sourcesContent":["import { formatBytes } from \"@/lib/utils/file/formatBytes\";\n\n/**\n * Интерфейс для кастомизации сообщений об ошибках в useFileUpload\n */\nexport interface FileUploadErrorMessages {\n\t/**\n\t * Сообщение когда файл превышает максимальный размер\n\t * @param fileName - имя файла\n\t * @param maxSizeBytes - максимальный размер в байтах\n\t */\n\tfileTooLarge?: (fileName: string, maxSizeBytes: number) => string;\n\n\t/**\n\t * Сообщение когда тип файла не разрешен\n\t * @param fileName - имя файла\n\t */\n\tinvalidFileType?: (fileName: string) => string;\n\n\t/**\n\t * Сообщение когда превышено максимальное количество файлов\n\t * @param maxFiles - максимальное количество файлов\n\t */\n\ttooManyFiles?: (maxFiles: number) => string;\n\n\t/**\n\t * Сообщение когда некоторые файлы превышают максимальный размер (для множественной загрузки)\n\t * @param maxSizeBytes - максимальный размер в байтах\n\t */\n\tsomeFilesTooLarge?: (maxSizeBytes: number) => string;\n\n\t/**\n\t * Сообщение когда файл превышает максимальный размер (для одиночной загрузки)\n\t * @param maxSizeBytes - максимальный размер в байтах\n\t */\n\tsingleFileTooLarge?: (maxSizeBytes: number) => string;\n}\n\n/**\n * Сообщения об ошибках по умолчанию\n */\nexport const defaultErrorMessages: Required<FileUploadErrorMessages> = {\n\tfileTooLarge: (fileName, maxSizeBytes) =>\n\t\t`File \"${fileName}\" exceeds the maximum size of ${formatBytes(maxSizeBytes)}.`,\n\tinvalidFileType: (fileName) => `File \"${fileName}\" is not an accepted file type.`,\n\ttooManyFiles: (maxFiles) => `You can only upload a maximum of ${maxFiles} files.`,\n\tsomeFilesTooLarge: (maxSizeBytes) => `Some files exceed the maximum size of ${formatBytes(maxSizeBytes)}.`,\n\tsingleFileTooLarge: (maxSizeBytes) => `File exceeds the maximum size of ${formatBytes(maxSizeBytes)}.`\n};\n\n/**\n * Сообщения об ошибках на русском языке\n */\nexport const defaultErrorMessagesRu: Required<FileUploadErrorMessages> = {\n\tfileTooLarge: (fileName, maxSizeBytes) =>\n\t\t`Файл \"${fileName}\" превышает максимальный размер ${formatBytes(maxSizeBytes)}`,\n\tinvalidFileType: (fileName) => `Файл \"${fileName}\" имеет недопустимый тип`,\n\ttooManyFiles: (maxFiles) => `Можно загрузить максимум ${maxFiles} файлов`,\n\tsomeFilesTooLarge: (maxSizeBytes) => `Некоторые файлы превышают максимальный размер ${formatBytes(maxSizeBytes)}`,\n\tsingleFileTooLarge: (maxSizeBytes) => `Файл превышает максимальный размер ${formatBytes(maxSizeBytes)}`\n};\n","import {\n\tuseCallback,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n\ttype ChangeEvent,\n\ttype DragEvent,\n\ttype InputHTMLAttributes\n} from \"react\";\nimport { defaultErrorMessages, type FileUploadErrorMessages } from \"./errorMessages\";\n\nexport type FileData = {\n\tname: string;\n\tsize: number;\n\ttype: string;\n\tid?: string;\n};\n\nexport type FileMetadata = FileData & {\n\turl: string;\n\tid: string;\n};\n\nexport type FileValue = File | FileData;\n\nexport type FileWithPreview = {\n\tfile: FileValue;\n\tid: string;\n\tpreview?: string;\n};\n\n/**\n * Ограничения для загрузки файлов\n */\nexport type FileUploadConstraints = {\n\t/**\n\t * Максимальное количество файлов\n\t * Используется только когда multiple=true\n\t * @default Infinity\n\t */\n\tmaxFiles?: number;\n\n\t/**\n\t * Максимальный размер файла в байтах\n\t * @default Infinity\n\t */\n\tmaxSize?: number;\n\n\t/**\n\t * Допустимые типы файлов (строка MIME-типов или расширений через запятую)\n\t * @example \"image/jpeg,image/png,.pdf,.doc\"\n\t * @example \"*.jpg,*.png\"\n\t * @example \"image/*\"\n\t * @default \"*\"\n\t */\n\taccept?: string;\n\n\t/**\n\t * Разрешить выбор нескольких файлов\n\t * @default false\n\t */\n\tmultiple?: boolean;\n};\n\nexport type FileUploadOptions = FileUploadConstraints & {\n\tinitialFiles?: FileMetadata[];\n\tonFilesChange?: (files: FileWithPreview[]) => void;\n\tonFilesAdded?: (addedFiles: FileWithPreview[]) => void;\n\t/**\n\t * Кастомные сообщения об ошибках\n\t * Можно переопределить только нужные сообщения\n\t */\n\terrorMessages?: Partial<FileUploadErrorMessages>;\n};\n\nexport type FileUploadState = {\n\tfiles: FileWithPreview[];\n\tisDragging: boolean;\n\terrors: string[];\n};\n\nexport type FileUploadActions = {\n\taddFiles: (files: FileList | File[]) => void;\n\tremoveFile: (id: string) => void;\n\tclearFiles: () => void;\n\tclearErrors: () => void;\n\thandleDragEnter: (e: DragEvent<HTMLElement>) => void;\n\thandleDragLeave: (e: DragEvent<HTMLElement>) => void;\n\thandleDragOver: (e: DragEvent<HTMLElement>) => void;\n\thandleDrop: (e: DragEvent<HTMLElement>) => void;\n\thandleFileChange: (e: ChangeEvent<HTMLInputElement>) => void;\n\topenFileDialog: () => void;\n\tgetInputProps: (props?: InputHTMLAttributes<HTMLInputElement>) => InputHTMLAttributes<HTMLInputElement> & {\n\t\tref: React.Ref<HTMLInputElement>;\n\t};\n};\n\n/** Хук был взять из библиотеки originui.\n * Код можно посмотреть по ссылке: https://github.com/origin-space/originui/blob/main/docs/use-file-upload.md\n */\nexport const useFileUpload = (options: FileUploadOptions = {}): [FileUploadState, FileUploadActions] => {\n\tconst {\n\t\tmaxFiles = Infinity,\n\t\tmaxSize = Infinity,\n\t\taccept = \"*\",\n\t\tmultiple = false,\n\t\tinitialFiles = [],\n\t\tonFilesChange,\n\t\tonFilesAdded,\n\t\terrorMessages: customErrorMessages\n\t} = options;\n\n\t// Объединяем дефолтные и кастомные сообщения\n\tconst errorMessages = useMemo(() => ({ ...defaultErrorMessages, ...customErrorMessages }), [customErrorMessages]);\n\n\tconst [state, setState] = useState<FileUploadState>({\n\t\tfiles: initialFiles.map((file) => ({\n\t\t\tfile,\n\t\t\tid: file.id,\n\t\t\tpreview: file.url\n\t\t})),\n\t\tisDragging: false,\n\t\terrors: []\n\t});\n\n\tconst inputRef = useRef<HTMLInputElement>(null);\n\n\tconst validateFile = useCallback(\n\t\t(file: FileValue): string | null => {\n\t\t\tconst fileName = file instanceof File ? file.name : file.name;\n\n\t\t\tif (file.size > maxSize) {\n\t\t\t\treturn errorMessages.fileTooLarge(fileName, maxSize);\n\t\t\t}\n\n\t\t\tif (accept !== \"*\") {\n\t\t\t\tconst acceptedTypes = accept.split(\",\").map((type) => type.trim());\n\t\t\t\tconst fileType = file instanceof File ? file.type || \"\" : file.type;\n\t\t\t\tconst fileExtension = `.${fileName.split(\".\").pop()}`;\n\n\t\t\t\tconst isAccepted = acceptedTypes.some((type) => {\n\t\t\t\t\tif (type.startsWith(\".\")) {\n\t\t\t\t\t\treturn fileExtension.toLowerCase() === type.toLowerCase();\n\t\t\t\t\t}\n\t\t\t\t\tif (type.endsWith(\"/*\")) {\n\t\t\t\t\t\tconst baseType = type.split(\"/\")[0];\n\t\t\t\t\t\treturn fileType.startsWith(`${baseType}/`);\n\t\t\t\t\t}\n\t\t\t\t\treturn fileType === type;\n\t\t\t\t});\n\n\t\t\t\tif (!isAccepted) {\n\t\t\t\t\treturn errorMessages.invalidFileType(fileName);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t\t[accept, maxSize, errorMessages]\n\t);\n\n\tconst createPreview = useCallback((file: File | FileMetadata): string | undefined => {\n\t\tif (file instanceof File) {\n\t\t\treturn URL.createObjectURL(file);\n\t\t}\n\t\treturn file.url;\n\t}, []);\n\n\tconst generateUniqueId = useCallback((file: File | FileMetadata): string => {\n\t\tif (file instanceof File) {\n\t\t\treturn `${file.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n\t\t}\n\t\treturn file.id;\n\t}, []);\n\n\tconst clearFiles = useCallback(() => {\n\t\tsetState((prev) => {\n\t\t\t// Clean up object URLs\n\t\t\tprev.files.forEach((file) => {\n\t\t\t\tif (file.preview && file.file instanceof File && file.file.type.startsWith(\"image/\")) {\n\t\t\t\t\tURL.revokeObjectURL(file.preview);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif (inputRef.current) {\n\t\t\t\tinputRef.current.value = \"\";\n\t\t\t}\n\n\t\t\tconst newState = {\n\t\t\t\t...prev,\n\t\t\t\tfiles: [],\n\t\t\t\terrors: []\n\t\t\t};\n\n\t\t\tonFilesChange?.(newState.files);\n\t\t\treturn newState;\n\t\t});\n\t}, [onFilesChange]);\n\n\tconst addFiles = useCallback(\n\t\t(newFiles: FileList | File[]) => {\n\t\t\tif (!newFiles || newFiles.length === 0) return;\n\n\t\t\tconst newFilesArray = Array.from(newFiles);\n\t\t\tconst errors: string[] = [];\n\n\t\t\t// Clear existing errors when new files are uploaded\n\t\t\tsetState((prev) => ({ ...prev, errors: [] }));\n\n\t\t\t// In single file mode, clear existing files first\n\t\t\tif (!multiple) {\n\t\t\t\tclearFiles();\n\t\t\t}\n\n\t\t\t// Check if adding these files would exceed maxFiles (only in multiple mode)\n\t\t\tif (multiple && maxFiles !== Infinity && state.files.length + newFilesArray.length > maxFiles) {\n\t\t\t\terrors.push(errorMessages.tooManyFiles(maxFiles));\n\t\t\t\tsetState((prev) => ({ ...prev, errors }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst validFiles: FileWithPreview[] = [];\n\n\t\t\tnewFilesArray.forEach((file) => {\n\t\t\t\t// Only check for duplicates if multiple files are allowed\n\t\t\t\tif (multiple) {\n\t\t\t\t\tconst isDuplicate = state.files.some(\n\t\t\t\t\t\t(existingFile) => existingFile.file.name === file.name && existingFile.file.size === file.size\n\t\t\t\t\t);\n\n\t\t\t\t\t// Skip duplicate files silently\n\t\t\t\t\tif (isDuplicate) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Check file size\n\t\t\t\tif (file.size > maxSize) {\n\t\t\t\t\terrors.push(\n\t\t\t\t\t\tmultiple ? errorMessages.someFilesTooLarge(maxSize) : errorMessages.singleFileTooLarge(maxSize)\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst error = validateFile(file);\n\t\t\t\tif (error) {\n\t\t\t\t\terrors.push(error);\n\t\t\t\t} else {\n\t\t\t\t\tvalidFiles.push({\n\t\t\t\t\t\tfile,\n\t\t\t\t\t\tid: generateUniqueId(file),\n\t\t\t\t\t\tpreview: createPreview(file)\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Only update state if we have valid files to add\n\t\t\tif (validFiles.length > 0) {\n\t\t\t\t// Call the onFilesAdded callback with the newly added valid files\n\t\t\t\tonFilesAdded?.(validFiles);\n\n\t\t\t\tsetState((prev) => {\n\t\t\t\t\tconst newFiles = !multiple ? validFiles : [...prev.files, ...validFiles];\n\t\t\t\t\tonFilesChange?.(newFiles);\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...prev,\n\t\t\t\t\t\tfiles: newFiles,\n\t\t\t\t\t\terrors\n\t\t\t\t\t};\n\t\t\t\t});\n\t\t\t} else if (errors.length > 0) {\n\t\t\t\tsetState((prev) => ({\n\t\t\t\t\t...prev,\n\t\t\t\t\terrors\n\t\t\t\t}));\n\t\t\t}\n\n\t\t\t// Reset input value after handling files\n\t\t\tif (inputRef.current) {\n\t\t\t\tinputRef.current.value = \"\";\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tstate.files,\n\t\t\tmaxFiles,\n\t\t\tmultiple,\n\t\t\tmaxSize,\n\t\t\tvalidateFile,\n\t\t\tcreatePreview,\n\t\t\tgenerateUniqueId,\n\t\t\tclearFiles,\n\t\t\tonFilesChange,\n\t\t\tonFilesAdded,\n\t\t\terrorMessages\n\t\t]\n\t);\n\n\tconst removeFile = useCallback(\n\t\t(id: string) => {\n\t\t\tsetState((prev) => {\n\t\t\t\tconst fileToRemove = prev.files.find((file) => file.id === id);\n\t\t\t\tif (\n\t\t\t\t\tfileToRemove &&\n\t\t\t\t\tfileToRemove.preview &&\n\t\t\t\t\tfileToRemove.file instanceof File &&\n\t\t\t\t\tfileToRemove.file.type.startsWith(\"image/\")\n\t\t\t\t) {\n\t\t\t\t\tURL.revokeObjectURL(fileToRemove.preview);\n\t\t\t\t}\n\n\t\t\t\tconst newFiles = prev.files.filter((file) => file.id !== id);\n\t\t\t\tonFilesChange?.(newFiles);\n\n\t\t\t\treturn {\n\t\t\t\t\t...prev,\n\t\t\t\t\tfiles: newFiles,\n\t\t\t\t\terrors: []\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\t[onFilesChange]\n\t);\n\n\tconst clearErrors = useCallback(() => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\terrors: []\n\t\t}));\n\t}, []);\n\n\tconst handleDragEnter = useCallback((e: DragEvent<HTMLElement>) => {\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t\tsetState((prev) => ({ ...prev, isDragging: true }));\n\t}, []);\n\n\tconst handleDragLeave = useCallback((e: DragEvent<HTMLElement>) => {\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\n\t\tif (e.currentTarget.contains(e.relatedTarget as Node)) {\n\t\t\treturn;\n\t\t}\n\n\t\tsetState((prev) => ({ ...prev, isDragging: false }));\n\t}, []);\n\n\tconst handleDragOver = useCallback((e: DragEvent<HTMLElement>) => {\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t}, []);\n\n\tconst handleDrop = useCallback(\n\t\t(e: DragEvent<HTMLElement>) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tsetState((prev) => ({ ...prev, isDragging: false }));\n\n\t\t\t// Don't process files if the input is disabled\n\t\t\tif (inputRef.current?.disabled) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n\t\t\t\t// In single file mode, only use the first file\n\t\t\t\tif (!multiple) {\n\t\t\t\t\tconst file = e.dataTransfer.files[0];\n\t\t\t\t\taddFiles([file]);\n\t\t\t\t} else {\n\t\t\t\t\taddFiles(e.dataTransfer.files);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[addFiles, multiple]\n\t);\n\n\tconst handleFileChange = useCallback(\n\t\t(e: ChangeEvent<HTMLInputElement>) => {\n\t\t\tif (e.target.files && e.target.files.length > 0) {\n\t\t\t\taddFiles(e.target.files);\n\t\t\t}\n\t\t},\n\t\t[addFiles]\n\t);\n\n\tconst openFileDialog = useCallback(() => {\n\t\tif (inputRef.current) {\n\t\t\tinputRef.current.click();\n\t\t}\n\t}, []);\n\n\tconst getInputProps = useCallback(\n\t\t(props: InputHTMLAttributes<HTMLInputElement> = {}) => {\n\t\t\treturn {\n\t\t\t\t...props,\n\t\t\t\ttype: \"file\" as const,\n\t\t\t\tonChange: handleFileChange,\n\t\t\t\taccept: props.accept || accept,\n\t\t\t\tmultiple: props.multiple !== undefined ? props.multiple : multiple,\n\t\t\t\tref: inputRef\n\t\t\t};\n\t\t},\n\t\t[accept, multiple, handleFileChange]\n\t);\n\n\treturn [\n\t\tstate,\n\t\t{\n\t\t\taddFiles,\n\t\t\tremoveFile,\n\t\t\tclearFiles,\n\t\t\tclearErrors,\n\t\t\thandleDragEnter,\n\t\t\thandleDragLeave,\n\t\t\thandleDragOver,\n\t\t\thandleDrop,\n\t\t\thandleFileChange,\n\t\t\topenFileDialog,\n\t\t\tgetInputProps\n\t\t}\n\t];\n};\n"],"names":["defaultErrorMessages","fileName","maxSizeBytes","formatBytes","maxFiles","defaultErrorMessagesRu","useFileUpload","options","maxSize","accept","multiple","initialFiles","onFilesChange","onFilesAdded","customErrorMessages","errorMessages","useMemo","state","setState","useState","file","inputRef","useRef","validateFile","useCallback","acceptedTypes","type","fileType","fileExtension","baseType","createPreview","generateUniqueId","clearFiles","prev","newState","addFiles","newFiles","newFilesArray","errors","validFiles","existingFile","error","removeFile","id","fileToRemove","clearErrors","handleDragEnter","handleDragLeave","handleDragOver","handleDrop","handleFileChange","openFileDialog","getInputProps","props"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useItemsKeyboardNavigation-CmeOR30y.js","sources":["../src/hooks/useItemsKeyboardNavigation.ts"],"sourcesContent":["import { useCallback, useState, KeyboardEvent, useEffect } from \"react\";\
|
|
1
|
+
{"version":3,"file":"useItemsKeyboardNavigation-CmeOR30y.js","sources":["../src/hooks/useItemsKeyboardNavigation.ts"],"sourcesContent":["import { useCallback, useState, KeyboardEvent, useEffect } from \"react\";\n\n/**\n * Хук для навигации по элементам с помощью клавиатуры\n *\n * @example\n * ```tsx\n * function TagList({ tags, onTagSelect, onTagDelete }) {\n * const containerRef = useRef(null);\n *\n * const {\n * focusedIndex,\n * handleKeyDown,\n * handleFocus,\n * handleBlur,\n * isFocused\n * } = useItemsKeyboardNavigation({\n * items: tags,\n * onEnter: onTagSelect,\n * onDelete: onTagDelete\n * });\n *\n * return (\n * <div\n * ref={containerRef}\n * tabIndex={0}\n * onKeyDown={handleKeyDown}\n * onFocus={handleFocus}\n * onBlur={handleBlur}\n * >\n * {tags.map((tag, index) => (\n * <div key={tag.id} className={isFocused(index) ? \"focused\" : \"\"}>\n * {tag.label}\n * </div>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useItemsKeyboardNavigation<T>({\n\titems,\n\tonEnter,\n\tonDelete,\n\tcycleNavigation = false\n}: {\n\t/** Массив элементов для навигации */\n\titems: T[];\n\t/** Функция, вызываемая при нажатии Enter на элементе */\n\tonEnter?: (item: T, index: number) => void;\n\t/** Функция, вызываемая при нажатии Delete/Backspace на элементе */\n\tonDelete?: (item: T, index: number) => void;\n\t/** Включает циклическую навигацию (с последнего элемента на первый и наоборот) */\n\tcycleNavigation?: boolean;\n}) {\n\t/** Индекс текущего элемента с фокусом */\n\tconst [focusedIndex, setFocusedIndex] = useState<number | null>(null);\n\n\t/**\n\t * Следим за изменениями в items и корректируем focusedIndex при необходимости\n\t */\n\tuseEffect(() => {\n\t\tif (focusedIndex !== null && items.length > 0) {\n\t\t\t// Если текущий индекс выходит за границы массива\n\t\t\tif (focusedIndex >= items.length) {\n\t\t\t\t// Устанавливаем индекс на последний элемент\n\t\t\t\tsetFocusedIndex(items.length - 1);\n\t\t\t}\n\t\t} else if (items.length === 0) {\n\t\t\t// Если элементов нет, сбрасываем фокус\n\t\t\tsetFocusedIndex(null);\n\t\t} else if (focusedIndex === null && items.length > 0) {\n\t\t\tsetFocusedIndex(0);\n\t\t}\n\t}, [items, focusedIndex]);\n\n\t/**\n\t * Обработчик нажатия клавиш для навигации по элементам\n\t */\n\tconst handleKeyDown = useCallback(\n\t\t(e: KeyboardEvent<HTMLElement>) => {\n\t\t\t// Проверяем, есть ли элементы для навигации\n\t\t\tif (!items.length) return;\n\n\t\t\tswitch (e.key) {\n\t\t\t\tcase \"ArrowLeft\":\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\tsetFocusedIndex((prev) => {\n\t\t\t\t\t\tif (prev === null) return 0;\n\t\t\t\t\t\tif (prev > 0) return prev - 1;\n\t\t\t\t\t\treturn cycleNavigation ? items.length - 1 : prev;\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"ArrowRight\":\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\tsetFocusedIndex((prev) => {\n\t\t\t\t\t\tif (prev === null) return 0;\n\t\t\t\t\t\tif (prev < items.length - 1) return prev + 1;\n\t\t\t\t\t\treturn cycleNavigation ? 0 : prev;\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"Home\":\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\tsetFocusedIndex(0);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"End\":\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\tsetFocusedIndex(items.length - 1);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"Enter\":\n\t\t\t\t\tif (focusedIndex !== null && items[focusedIndex] && onEnter) {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tonEnter(items[focusedIndex], focusedIndex);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"Delete\":\n\t\t\t\tcase \"Backspace\":\n\t\t\t\t\tif (focusedIndex !== null && items[focusedIndex] && onDelete) {\n\t\t\t\t\t\te.preventDefault();\n\n\t\t\t\t\t\t// Сохраняем текущий индекс перед удалением\n\t\t\t\t\t\tconst currentIndex = focusedIndex;\n\n\t\t\t\t\t\tonDelete(items[focusedIndex], focusedIndex);\n\n\t\t\t\t\t\t// Корректируем индекс после удаления\n\t\t\t\t\t\t// Это сработает корректно, если items обновится синхронно\n\t\t\t\t\t\t// В противном случае, корректировку нужно делать в эффекте\n\t\t\t\t\t\tif (currentIndex >= items.length - 1) {\n\t\t\t\t\t\t\tsetFocusedIndex(Math.max(0, items.length - 2));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t[items, focusedIndex, onEnter, onDelete, cycleNavigation]\n\t);\n\n\t/**\n\t * Обработчик получения фокуса контейнером\n\t */\n\tconst handleFocus = useCallback(() => {\n\t\tif (items.length) {\n\t\t\tsetFocusedIndex(0);\n\t\t}\n\t}, [items]);\n\n\t/**\n\t * Обработчик потери фокуса контейнером\n\t */\n\tconst handleBlur = useCallback(() => {\n\t\tsetFocusedIndex(null);\n\t}, []);\n\n\t/**\n\t * Проверяет, находится ли элемент с указанным индексом в фокусе\n\t */\n\tconst isFocused = useCallback((index: number) => focusedIndex === index, [focusedIndex]);\n\n\t/**\n\t * Безопасно устанавливает фокус на элемент по индексу\n\t * @param index Индекс элемента для фокусировки\n\t * @returns Возвращает true, если индекс был установлен успешно\n\t */\n\tconst focusItemAtIndex = useCallback(\n\t\t(index: number) => {\n\t\t\tif (index >= 0 && index < items.length) {\n\t\t\t\tsetFocusedIndex(index);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t\t[items.length]\n\t);\n\n\treturn {\n\t\tfocusedIndex,\n\t\thandleKeyDown,\n\t\thandleFocus,\n\t\thandleBlur,\n\t\tisFocused,\n\t\tfocusItemAtIndex\n\t};\n}\n"],"names":["useItemsKeyboardNavigation","items","onEnter","onDelete","cycleNavigation","focusedIndex","setFocusedIndex","useState","useEffect","handleKeyDown","useCallback","e","prev","currentIndex","handleFocus","handleBlur","isFocused","index","focusItemAtIndex"],"mappings":";AAwCO,SAASA,EAA8B;AAAA,EAC7C,OAAAC;AAAA,EACA,SAAAC;AAAA,EACA,UAAAC;AAAA,EACA,iBAAAC,IAAkB;AACnB,GASG;AAEF,QAAM,CAACC,GAAcC,CAAe,IAAIC,EAAwB,IAAI;AAKpE,EAAAC,EAAU,MAAM;AACf,IAAIH,MAAiB,QAAQJ,EAAM,SAAS,IAEvCI,KAAgBJ,EAAM,UAETK,EAAAL,EAAM,SAAS,CAAC,IAEvBA,EAAM,WAAW,IAE3BK,EAAgB,IAAI,IACVD,MAAiB,QAAQJ,EAAM,SAAS,KAClDK,EAAgB,CAAC;AAAA,EAClB,GACE,CAACL,GAAOI,CAAY,CAAC;AAKxB,QAAMI,IAAgBC;AAAA,IACrB,CAACC,MAAkC;AAE9B,UAACV,EAAM;AAEX,gBAAQU,EAAE,KAAK;AAAA,UACd,KAAK;AACJ,YAAAA,EAAE,eAAe,GACjBL,EAAgB,CAACM,MACZA,MAAS,OAAa,IACtBA,IAAO,IAAUA,IAAO,IACrBR,IAAkBH,EAAM,SAAS,IAAIW,CAC5C;AACD;AAAA,UAED,KAAK;AACJ,YAAAD,EAAE,eAAe,GACjBL,EAAgB,CAACM,MACZA,MAAS,OAAa,IACtBA,IAAOX,EAAM,SAAS,IAAUW,IAAO,IACpCR,IAAkB,IAAIQ,CAC7B;AACD;AAAA,UAED,KAAK;AACJ,YAAAD,EAAE,eAAe,GACjBL,EAAgB,CAAC;AACjB;AAAA,UAED,KAAK;AACJ,YAAAK,EAAE,eAAe,GACDL,EAAAL,EAAM,SAAS,CAAC;AAChC;AAAA,UAED,KAAK;AACJ,YAAII,MAAiB,QAAQJ,EAAMI,CAAY,KAAKH,MACnDS,EAAE,eAAe,GACTT,EAAAD,EAAMI,CAAY,GAAGA,CAAY;AAE1C;AAAA,UAED,KAAK;AAAA,UACL,KAAK;AACJ,gBAAIA,MAAiB,QAAQJ,EAAMI,CAAY,KAAKF,GAAU;AAC7D,cAAAQ,EAAE,eAAe;AAGjB,oBAAME,IAAeR;AAEZ,cAAAF,EAAAF,EAAMI,CAAY,GAAGA,CAAY,GAKtCQ,KAAgBZ,EAAM,SAAS,KAClCK,EAAgB,KAAK,IAAI,GAAGL,EAAM,SAAS,CAAC,CAAC;AAAA,YAC9C;AAED;AAAA,QAGA;AAAA,IAEH;AAAA,IACA,CAACA,GAAOI,GAAcH,GAASC,GAAUC,CAAe;AAAA,EACzD,GAKMU,IAAcJ,EAAY,MAAM;AACrC,IAAIT,EAAM,UACTK,EAAgB,CAAC;AAAA,EAClB,GACE,CAACL,CAAK,CAAC,GAKJc,IAAaL,EAAY,MAAM;AACpC,IAAAJ,EAAgB,IAAI;AAAA,EACrB,GAAG,EAAE,GAKCU,IAAYN,EAAY,CAACO,MAAkBZ,MAAiBY,GAAO,CAACZ,CAAY,CAAC,GAOjFa,IAAmBR;AAAA,IACxB,CAACO,MACIA,KAAS,KAAKA,IAAQhB,EAAM,UAC/BK,EAAgBW,CAAK,GACd,MAED;AAAA,IAER,CAAChB,EAAM,MAAM;AAAA,EACd;AAEO,SAAA;AAAA,IACN,cAAAI;AAAA,IACA,eAAAI;AAAA,IACA,aAAAK;AAAA,IACA,YAAAC;AAAA,IACA,WAAAC;AAAA,IACA,kBAAAE;AAAA,EACD;AACD;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMergeRefs-qoKniYqT.js","sources":["../src/hooks/useMergeRefs.ts"],"sourcesContent":["import { useCallback } from \"react\";\
|
|
1
|
+
{"version":3,"file":"useMergeRefs-qoKniYqT.js","sources":["../src/hooks/useMergeRefs.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\ntype ReactRef<T> = React.RefCallback<T> | React.RefObject<T> | null;\n\nfunction assignRef<T>(ref: ReactRef<T>, value: T | null) {\n\tif (typeof ref === \"function\") {\n\t\tref(value);\n\t} else if (ref !== null && \"current\" in ref) {\n\t\t(ref as React.MutableRefObject<T | null>).current = value;\n\t}\n}\n\nexport function useMergeRefs<T>(...refs: ReactRef<T>[]) {\n\treturn useCallback(\n\t\t(value: T | null) => {\n\t\t\trefs.forEach((ref) => {\n\t\t\t\tassignRef(ref, value);\n\t\t\t});\n\t\t},\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\t[...refs]\n\t);\n}\n"],"names":["assignRef","ref","value","useMergeRefs","refs","useCallback"],"mappings":";AAIA,SAASA,EAAaC,GAAkBC,GAAiB;AACpD,EAAA,OAAOD,KAAQ,aAClBA,EAAIC,CAAK,IACCD,MAAQ,QAAQ,aAAaA,MACtCA,EAAyC,UAAUC;AAEtD;AAEO,SAASC,KAAmBC,GAAqB;AAChD,SAAAC;AAAA,IACN,CAACH,MAAoB;AACf,MAAAE,EAAA,QAAQ,CAACH,MAAQ;AACrB,QAAAD,EAAUC,GAAKC,CAAK;AAAA,MAAA,CACpB;AAAA,IACF;AAAA;AAAA,IAEA,CAAC,GAAGE,CAAI;AAAA,EACT;AACD;"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
var l = Object.defineProperty;
|
|
2
|
+
var u = (t, e, n) => e in t ? l(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n;
|
|
3
|
+
var o = (t, e, n) => u(t, typeof e != "symbol" ? e + "" : e, n);
|
|
4
|
+
import { useState as d, useEffect as c, useCallback as f } from "react";
|
|
5
|
+
class p {
|
|
6
|
+
constructor() {
|
|
7
|
+
o(this, "listeners", /* @__PURE__ */ new Set());
|
|
8
|
+
o(this, "isContainerMounted", !1);
|
|
9
|
+
}
|
|
10
|
+
subscribe(e) {
|
|
11
|
+
return this.listeners.add(e), () => {
|
|
12
|
+
this.listeners.delete(e);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
notify(e = !0) {
|
|
16
|
+
if (e && this.isContainerMounted)
|
|
17
|
+
throw new Error(
|
|
18
|
+
"PortalContainer уже смонтирован. В приложении может быть только один экземпляр PortalContainer."
|
|
19
|
+
);
|
|
20
|
+
this.isContainerMounted = e, this.listeners.forEach((n) => n(e));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const s = new p();
|
|
24
|
+
let r;
|
|
25
|
+
const C = () => {
|
|
26
|
+
if (r !== void 0)
|
|
27
|
+
return r;
|
|
28
|
+
try {
|
|
29
|
+
r = getComputedStyle(document.documentElement).getPropertyValue("--ui-kit-scoped-styles").trim() === '"enabled"';
|
|
30
|
+
} catch {
|
|
31
|
+
return !1;
|
|
32
|
+
}
|
|
33
|
+
return r;
|
|
34
|
+
}, h = (t) => {
|
|
35
|
+
const [e, n] = d(s.isContainerMounted);
|
|
36
|
+
return c(() => s.subscribe((a) => {
|
|
37
|
+
n(a);
|
|
38
|
+
}), []), f(() => t !== void 0 ? t || void 0 : !C() || e === !1 ? void 0 : document.querySelector("[data-portal-container]") || void 0, [t, e])();
|
|
39
|
+
};
|
|
40
|
+
export {
|
|
41
|
+
s as p,
|
|
42
|
+
h as u
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=usePortalContainer-BAIp0kSF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePortalContainer-BAIp0kSF.js","sources":["../src/components/portal-container/portalContainerEvents.ts","../src/lib/utils/styles.ts","../src/components/portal-container/usePortalContainer.ts"],"sourcesContent":["type Listener = (mount: boolean) => void;\n\n/**\n * Простой EventEmitter для синхронизации PortalContainer и usePortalContainer\n */\nclass PortalContainerEvents {\n\tprivate listeners = new Set<Listener>();\n\tisContainerMounted = false;\n\n\tsubscribe(listener: Listener) {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\tnotify(mount: boolean = true) {\n\t\tif (mount && this.isContainerMounted) {\n\t\t\tthrow new Error(\n\t\t\t\t\"PortalContainer уже смонтирован. В приложении может быть только один экземпляр PortalContainer.\"\n\t\t\t);\n\t\t}\n\t\tthis.isContainerMounted = mount;\n\t\tthis.listeners.forEach((listener) => listener(mount));\n\t}\n}\n\nexport const portalContainerEvents = new PortalContainerEvents();\n","let cachedScopedStylesEnabled: boolean | undefined;\n\n/**\n * Проверяет, подключены ли изолированные стили UI Kit.\n *\n * Проверяет наличие CSS-переменной --ui-kit-scoped-styles,\n * которая устанавливается в styles-scoped.css\n * Результат кэшируется на уровне модуля\n *\n * @returns true если изолированные стили подключены, иначе false\n */\nexport const isScopedStylesEnabled = (): boolean => {\n\tif (cachedScopedStylesEnabled !== undefined) {\n\t\treturn cachedScopedStylesEnabled;\n\t}\n\ttry {\n\t\tconst scopedStylesValue = getComputedStyle(document.documentElement)\n\t\t\t.getPropertyValue(\"--ui-kit-scoped-styles\")\n\t\t\t.trim();\n\n\t\tcachedScopedStylesEnabled = scopedStylesValue === '\"enabled\"';\n\t} catch {\n\t\t// Если getComputedStyle недоступен (например, в SSR)\n\t\treturn false;\n\t}\n\n\treturn cachedScopedStylesEnabled;\n};\n","import { useCallback, useEffect, useState } from \"react\";\nimport { isScopedStylesEnabled } from \"@/lib/utils/styles\";\nimport { portalContainerEvents } from \"./portalContainerEvents\";\n\n/**\n * Хук для определения контейнера портала.\n *\n * Если передан customContainer, возвращает его.\n * Иначе проверяет наличие изолированных стилей и ищет элемент с атрибутом 'data-portal-container'.\n *\n * @param customContainer - Кастомный контейнер для портала\n * @returns HTMLElement для портала или undefined\n */\nexport const usePortalContainer = (customContainer?: HTMLElement | null) => {\n\tconst [isMounted, setIsMounted] = useState(portalContainerEvents.isContainerMounted);\n\n\tuseEffect(() => {\n\t\t// Подписываемся на события монтирования/размонтирования PortalContainer\n\t\tconst unsubscribe = portalContainerEvents.subscribe((mount) => {\n\t\t\tsetIsMounted(mount);\n\t\t});\n\t\treturn unsubscribe;\n\t}, []);\n\n\tconst findPortalContainer = useCallback((): HTMLElement | undefined => {\n\t\tif (customContainer !== undefined) {\n\t\t\treturn customContainer || undefined;\n\t\t}\n\t\tif (!isScopedStylesEnabled()) return undefined;\n\t\tif (isMounted === false) return undefined;\n\n\t\tconst portalContainer = document.querySelector(\"[data-portal-container]\") as HTMLElement;\n\t\treturn portalContainer || undefined;\n\t}, [customContainer, isMounted]);\n\n\treturn findPortalContainer();\n};\n"],"names":["PortalContainerEvents","__publicField","listener","mount","portalContainerEvents","cachedScopedStylesEnabled","isScopedStylesEnabled","usePortalContainer","customContainer","isMounted","setIsMounted","useState","useEffect","useCallback"],"mappings":";;;;AAKA,MAAMA,EAAsB;AAAA,EAA5B;AACS,IAAAC,EAAA,uCAAgB,IAAc;AACtC,IAAAA,EAAA,4BAAqB;AAAA;AAAA,EAErB,UAAUC,GAAoB;AACxB,gBAAA,UAAU,IAAIA,CAAQ,GACpB,MAAM;AACP,WAAA,UAAU,OAAOA,CAAQ;AAAA,IAC/B;AAAA,EAAA;AAAA,EAGD,OAAOC,IAAiB,IAAM;AACzB,QAAAA,KAAS,KAAK;AACjB,YAAM,IAAI;AAAA,QACT;AAAA,MACD;AAED,SAAK,qBAAqBA,GAC1B,KAAK,UAAU,QAAQ,CAACD,MAAaA,EAASC,CAAK,CAAC;AAAA,EAAA;AAEtD;AAEa,MAAAC,IAAwB,IAAIJ,EAAsB;AC3B/D,IAAIK;AAWG,MAAMC,IAAwB,MAAe;AACnD,MAAID,MAA8B;AAC1B,WAAAA;AAEJ,MAAA;AAKH,IAAAA,IAJ0B,iBAAiB,SAAS,eAAe,EACjE,iBAAiB,wBAAwB,EACzC,KAAK,MAE2C;AAAA,EAAA,QAC3C;AAEA,WAAA;AAAA,EAAA;AAGD,SAAAA;AACR,GCdaE,IAAqB,CAACC,MAAyC;AAC3E,QAAM,CAACC,GAAWC,CAAY,IAAIC,EAASP,EAAsB,kBAAkB;AAEnF,SAAAQ,EAAU,MAEWR,EAAsB,UAAU,CAACD,MAAU;AAC9D,IAAAO,EAAaP,CAAK;AAAA,EAAA,CAClB,GAEC,EAAE,GAEuBU,EAAY,MACnCL,MAAoB,SAChBA,KAAmB,SAEvB,CAACF,EAAsB,KACvBG,MAAc,KAAc,SAER,SAAS,cAAc,yBAAyB,KAC9C,QACxB,CAACD,GAAiBC,CAAS,CAAC,EAEJ;AAC5B;"}
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Реэкспорт типов из библиотеки date-fns для
|
|
2
|
+
* Реэкспорт типов из библиотеки date-fns для календарей.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* import { ru } from 'ics-ui-kit/vendors/date-fns/locale/ru';
|
|
7
|
-
* import { enUS } from 'ics-ui-kit/vendors/date-fns/locale/en-US';
|
|
8
|
-
* ```
|
|
4
|
+
* @deprecated Реэкспорт будет удалён в следующей мажорной версии. Установите `date-fns`
|
|
5
|
+
* в своём проекте и импортируйте напрямую: `import type { Locale } from "date-fns"`.
|
|
9
6
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* import { ru } from 'ics-ui-kit/vendors/date-fns/locale/ru';
|
|
15
|
-
*
|
|
16
|
-
* <Calendar locale={ru} />
|
|
17
|
-
* ```
|
|
7
|
+
* Почему: кит поставлял чужую библиотеку под своим именем и тем самым замыкал её версию
|
|
8
|
+
* на свою. Потребитель не мог обновить `date-fns` без релиза кита, а сборка получала
|
|
9
|
+
* второй экземпляр библиотеки, если в проекте она уже стояла. Версией внешней зависимости
|
|
10
|
+
* управляет тот, кто её использует, — это дело `dependencies` потребителя, а не кита.
|
|
18
11
|
*/
|
|
19
12
|
export type { Locale } from 'date-fns';
|
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Реэкспорт библиотеки React Day Picker для календарей и выбора
|
|
2
|
+
* Реэкспорт библиотеки React Day Picker для календарей и выбора дат.
|
|
3
3
|
*
|
|
4
|
-
* @
|
|
5
|
-
*
|
|
6
|
-
* import { DayPicker } from
|
|
4
|
+
* @deprecated Реэкспорт будет удалён в следующей мажорной версии. Установите
|
|
5
|
+
* `react-day-picker` в своём проекте и импортируйте напрямую:
|
|
6
|
+
* `import { DayPicker } from "react-day-picker"`.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* <DayPicker
|
|
13
|
-
* mode="single"
|
|
14
|
-
* selected={selected}
|
|
15
|
-
* onSelect={setSelected}
|
|
16
|
-
* className="p-3"
|
|
17
|
-
* />
|
|
18
|
-
* );
|
|
19
|
-
* };
|
|
20
|
-
* ```
|
|
8
|
+
* Почему: кит поставлял чужую библиотеку под своим именем и тем самым замыкал её версию
|
|
9
|
+
* на свою. Потребитель не мог обновить `react-day-picker` без релиза кита, а сборка получала
|
|
10
|
+
* второй экземпляр библиотеки, если в проекте она уже стояла. Версией внешней зависимости
|
|
11
|
+
* управляет тот, кто её использует, — это дело `dependencies` потребителя, а не кита.
|
|
21
12
|
*/
|
|
22
13
|
export * from 'react-day-picker';
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Реэкспорт библиотек для работы с
|
|
3
|
-
* - react-hook-form для управления состоянием форм
|
|
4
|
-
* - @hookform/resolvers для интеграции с валидаторами
|
|
2
|
+
* Реэкспорт библиотек для работы с формами: `react-hook-form` и `@hookform/resolvers`.
|
|
5
3
|
*
|
|
6
|
-
* @
|
|
7
|
-
*
|
|
8
|
-
* import { useForm
|
|
9
|
-
* import { z } from 'ics-ui-kit/vendors/validation';
|
|
4
|
+
* @deprecated Реэкспорт будет удалён в следующей мажорной версии. Установите
|
|
5
|
+
* `react-hook-form` и `@hookform/resolvers` в своём проекте и импортируйте напрямую:
|
|
6
|
+
* `import { useForm } from "react-hook-form"`, `import { zodResolver } from "@hookform/resolvers/zod"`.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* const form = useForm({
|
|
16
|
-
* resolver: zodResolver(schema)
|
|
17
|
-
* });
|
|
18
|
-
* ```
|
|
8
|
+
* Почему: кит поставлял чужие библиотеки под своим именем и тем самым замыкал их версии
|
|
9
|
+
* на свою. Потребитель не мог обновить их без релиза кита, а сборка получала второй
|
|
10
|
+
* экземпляр библиотеки, если в проекте она уже стояла, — для `react-hook-form` это ещё и
|
|
11
|
+
* два разных контекста формы. Версией внешней зависимости управляет тот, кто её использует.
|
|
19
12
|
*/
|
|
20
13
|
export * from 'react-hook-form';
|
|
21
14
|
export * from '@hookform/resolvers/zod';
|