@ryuzaki13/react-foundation-ui 1.8.11 → 1.8.12
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/dist/chunks/defaultRenderers-u_Vep6Pj.js.map +1 -1
- package/dist/chunks/multi-select-DtmRtX6v.js.map +1 -1
- package/dist/chunks/select-MN6zDPKM.js.map +1 -1
- package/dist/chunks/useODataSelectBase-DR4PrY1j.js.map +1 -1
- package/dist/multi-select/model/useODataMultiSelect.d.ts +1 -1
- package/dist/multi-select/model/useODataMultiSelect.d.ts.map +1 -1
- package/dist/multi-select/ui/MultiSelect.d.ts +1 -1
- package/dist/multi-select/ui/MultiSelect.d.ts.map +1 -1
- package/dist/multi-select/ui/SerializableMultiSelect.d.ts.map +1 -1
- package/dist/multi-select/ui/defaultRenderers.d.ts +1 -1
- package/dist/multi-select/ui/defaultRenderers.d.ts.map +1 -1
- package/dist/select/model/useODataSelect.d.ts +1 -1
- package/dist/select/model/useODataSelect.d.ts.map +1 -1
- package/dist/select/model/useODataSelectBase.d.ts +1 -1
- package/dist/select/model/useODataSelectBase.d.ts.map +1 -1
- package/dist/tree-select/index.js.map +1 -1
- package/dist/tree-select/model/buildODataTreeNodes.d.ts +1 -1
- package/dist/tree-select/model/buildODataTreeNodes.d.ts.map +1 -1
- package/package.json +3 -3
- package/storybook-static/assets/{Badge-BojMuOws.js → Badge-CveIo2LZ.js} +1 -1
- package/storybook-static/assets/{Badge.stories-C9gJRuIO.js → Badge.stories-Bd8M64xJ.js} +1 -1
- package/storybook-static/assets/{BadgeList-dNEWI7-O.js → BadgeList-xT41a0VL.js} +1 -1
- package/storybook-static/assets/{Button-Bbt3JzIY.js → Button-C5bBSb3k.js} +1 -1
- package/storybook-static/assets/{Button-CrTHXWYk.js → Button-DkDgfpHL.js} +1 -1
- package/storybook-static/assets/{Button.stories-BaYXOBeF.js → Button.stories-CeKPeGK5.js} +1 -1
- package/storybook-static/assets/{CalendarView-Dp0zus1o.js → CalendarView-CEqcrblx.js} +1 -1
- package/storybook-static/assets/{CheckBox-BiK9yWNt.js → CheckBox-DKeFkdOt.js} +1 -1
- package/storybook-static/assets/{CheckBox-UzYHaD6S.js → CheckBox-OVvAarW-.js} +1 -1
- package/storybook-static/assets/{CheckBox.stories-C2BOiWZj.js → CheckBox.stories-CfZ0p1yo.js} +1 -1
- package/storybook-static/assets/{Color-6AOWCN3U-C5S4wUlD.js → Color-6AOWCN3U-BWW7zrfs.js} +1 -1
- package/storybook-static/assets/{ContextMenu-BWQln_tb.js → ContextMenu-kia7is1N.js} +1 -1
- package/storybook-static/assets/{ContextMenu-D9EZxb7d.js → ContextMenu-xn2GtiLR.js} +1 -1
- package/storybook-static/assets/{ContextMenu.stories-U38zPh9_.js → ContextMenu.stories-CYuy9uXF.js} +1 -1
- package/storybook-static/assets/{CustomOptionButton-COIOrxQn.js → CustomOptionButton-C4w-APws.js} +1 -1
- package/storybook-static/assets/DateInput-BiUFIihm.js +1 -0
- package/storybook-static/assets/{DateInput-C-entb-r.js → DateInput-DAzE5mCE.js} +1 -1
- package/storybook-static/assets/{DateInput.stories-NmUXQLDU.js → DateInput.stories-BcowGCRD.js} +1 -1
- package/storybook-static/assets/{DateRangePresetSelect-BM1L1KOo.js → DateRangePresetSelect-CQm4dNLR.js} +1 -1
- package/storybook-static/assets/{DateRangePresetSelect-CAJzxaq0.js → DateRangePresetSelect-RIyqNBk9.js} +1 -1
- package/storybook-static/assets/{DateRangePresetSelect.stories-CC47XLnD.js → DateRangePresetSelect.stories-ypGyuGfy.js} +1 -1
- package/storybook-static/assets/{Detail-CDluXtts.js → Detail-CEypL2Nh.js} +1 -1
- package/storybook-static/assets/{Detail.stories-DjniLC4n.js → Detail.stories-DNkT7B-u.js} +1 -1
- package/storybook-static/assets/{Dialog-bzRSgGU7.js → Dialog-BVFFQJDH.js} +1 -1
- package/storybook-static/assets/{Dialog-bw6cHUsz.js → Dialog-DD0grLG3.js} +1 -1
- package/storybook-static/assets/{Dialog.stories-C6Rs6s_p.js → Dialog.stories-DSCypHqR.js} +1 -1
- package/storybook-static/assets/{Disclosure-DelA6FsQ.js → Disclosure-DauUgrev.js} +1 -1
- package/storybook-static/assets/{Disclosure.stories-D7yUinyb.js → Disclosure.stories-B1qB8vQZ.js} +1 -1
- package/storybook-static/assets/{DisclosureGroup-DbK7mU_Q.js → DisclosureGroup-D9oCCeHf.js} +1 -1
- package/storybook-static/assets/{DocsRenderer-JROSPFPF-v3z0zVN1.js → DocsRenderer-JROSPFPF-CmSf6tXp.js} +1 -1
- package/storybook-static/assets/{DropZone-Cyy-ttEO.js → DropZone-D42ueN1w.js} +1 -1
- package/storybook-static/assets/{DropZone-CI3NW4AC.js → DropZone-D8hbPCAv.js} +1 -1
- package/storybook-static/assets/{DropZone.stories-S0HSpGTB.js → DropZone.stories-DO-T_Bh4.js} +1 -1
- package/storybook-static/assets/{DropdownMenu-Ct1bSZsE.js → DropdownMenu-CknJDoji.js} +1 -1
- package/storybook-static/assets/{ExpandableActionPanel-Oj3xlasj.js → ExpandableActionPanel-Dqi_bo3c.js} +1 -1
- package/storybook-static/assets/{ExpandableActionPanel.stories-Ch7CLkpR.js → ExpandableActionPanel.stories-BD9QZGZN.js} +1 -1
- package/storybook-static/assets/{Flex-UiFm8Tkc.js → Flex-CaVn1IOP.js} +1 -1
- package/storybook-static/assets/{Flex.stories-CaDiVlBD.js → Flex.stories-Bp9Y6q6b.js} +1 -1
- package/storybook-static/assets/{FloatingPopover-CTYV9W7T.js → FloatingPopover-U7tx-Jlw.js} +1 -1
- package/storybook-static/assets/{FormattedTableCell-D-ysSR3p.js → FormattedTableCell-pmmhsAbN.js} +1 -1
- package/storybook-static/assets/{Grid-CigBXPe2.js → Grid-CEpMNurH.js} +1 -1
- package/storybook-static/assets/{Grid.stories-ay3z9DQZ.js → Grid.stories-BM-gO5DN.js} +1 -1
- package/storybook-static/assets/{ImageCropper.stories-Dkce_ugb.js → ImageCropper.stories-BWz5XU6U.js} +1 -1
- package/storybook-static/assets/{ImageRenderer-C9_EmGBW.js → ImageRenderer-BTYZEX5t.js} +1 -1
- package/storybook-static/assets/{ImageRenderer.stories-C7bXaNBI.js → ImageRenderer.stories-Dz2STM95.js} +1 -1
- package/storybook-static/assets/{ImageView.stories-D-ZzGlk8.js → ImageView.stories-DBVe0an3.js} +1 -1
- package/storybook-static/assets/{ImageViewer.stories-ItcRfxyy.js → ImageViewer.stories-C4Px97lD.js} +2 -2
- package/storybook-static/assets/{ImageViewerRuntime-eaupID36.js → ImageViewerRuntime-TJtlJVfa.js} +1 -1
- package/storybook-static/assets/{Input-op4j7Beh.js → Input-CIPgijvW.js} +1 -1
- package/storybook-static/assets/{Input-BUDNR96c.js → Input-D8io9jVS.js} +1 -1
- package/storybook-static/assets/{Input.stories-DwulvJIg.js → Input.stories-SUk3aYaC.js} +1 -1
- package/storybook-static/assets/{InputDateTime-B_cxOp6k.js → InputDateTime-DA5pIDuE.js} +1 -1
- package/storybook-static/assets/{InputDateTime.stories-BueLF4HC.js → InputDateTime.stories-DeudzDSU.js} +1 -1
- package/storybook-static/assets/{InputFile-C8z6TPc1.js → InputFile-C5mp5Y5I.js} +1 -1
- package/storybook-static/assets/{InputFile-DSg7sugm.js → InputFile-CGAC7ruP.js} +1 -1
- package/storybook-static/assets/{InputFile.stories-C6XSbIua.js → InputFile.stories-Cx1xQ6PE.js} +1 -1
- package/storybook-static/assets/{InputFiles-DoatLq3q.js → InputFiles-B7OBcPJN.js} +1 -1
- package/storybook-static/assets/{InputFiles-DeZ84Uu_.js → InputFiles-Hzl89Ghf.js} +1 -1
- package/storybook-static/assets/{InputFiles.stories-BMbE8SJU.js → InputFiles.stories-CG6QjDZH.js} +1 -1
- package/storybook-static/assets/{InputFilesList-CsfDDqaJ.js → InputFilesList-B6htWHF1.js} +1 -1
- package/storybook-static/assets/{InputImage-yJSq98EH.js → InputImage-BTa4S4Wp.js} +1 -1
- package/storybook-static/assets/{InputImage-D_YnB2Tw.js → InputImage-CQnoblAE.js} +1 -1
- package/storybook-static/assets/{InputImage.stories-nivUG307.js → InputImage.stories-CEI44Eyz.js} +1 -1
- package/storybook-static/assets/{InteractiveComponents-IPkC_Rkg.js → InteractiveComponents-B0ypWz1x.js} +1 -1
- package/storybook-static/assets/{InteractiveComponents.stories-D4VzVKZJ.js → InteractiveComponents.stories-BadApta9.js} +1 -1
- package/storybook-static/assets/{LayoutPicker-aMzPN5Do.js → LayoutPicker-COB9GlV_.js} +1 -1
- package/storybook-static/assets/{LayoutPicker-ZHkMi-LM.js → LayoutPicker-C_Uz7Tnj.js} +1 -1
- package/storybook-static/assets/{LayoutPicker.stories-DAzpzxn5.js → LayoutPicker.stories-Btp0IcaL.js} +1 -1
- package/storybook-static/assets/{Listbox-CR5nXC9J.js → Listbox-AAaZGSAl.js} +1 -1
- package/storybook-static/assets/{Listbox-BmfQOnrb.js → Listbox-CyfuZw7w.js} +1 -1
- package/storybook-static/assets/{Listbox.stories-CeOpsKxB.js → Listbox.stories-NDKQmjyQ.js} +1 -1
- package/storybook-static/assets/{Modal-BStp52Zo.js → Modal-BJDnJ4zv.js} +1 -1
- package/storybook-static/assets/{Modal.stories-CwGm5GYE.js → Modal.stories-D_X5lTLO.js} +1 -1
- package/storybook-static/assets/{ModalManagerProvider-B5uUXWF9.js → ModalManagerProvider-ShDjozpW.js} +1 -1
- package/storybook-static/assets/{MultiSelect-BDs02MZh.js → MultiSelect-B5CElQ3-.js} +1 -1
- package/storybook-static/assets/{MultiSelect-308npb27.js → MultiSelect-DqXfrjcH.js} +1 -1
- package/storybook-static/assets/{MultiSelect.stories-DRi_iTp4.js → MultiSelect.stories-fB3mcFmx.js} +1 -1
- package/storybook-static/assets/{NoData-ovaQEE9W.js → NoData-DDb0G7T8.js} +1 -1
- package/storybook-static/assets/{NotificationsHost-BpeMWhLY.js → NotificationsHost-CIGL-pKX.js} +1 -1
- package/storybook-static/assets/NotificationsHost.stories-B_MRb3fs.js +29 -0
- package/storybook-static/assets/{ODataMultiSelect-qlft9L9L.js → ODataMultiSelect-DQAitH4N.js} +1 -1
- package/storybook-static/assets/{ODataMultiSelect.stories-CMPIoPiy.js → ODataMultiSelect.stories-DwW221S9.js} +2 -2
- package/storybook-static/assets/{ODataSelect-DKBQArAK.js → ODataSelect-tiZhOIhE.js} +1 -1
- package/storybook-static/assets/{ODataSelect.stories-DxYIhtwe.js → ODataSelect.stories-BhaatPjK.js} +2 -2
- package/storybook-static/assets/{ODataTreeMultiSelect-DAt6THP3.js → ODataTreeMultiSelect-B9WqJ-Un.js} +1 -1
- package/storybook-static/assets/{ODataTreeMultiSelect.stories-CBNJP0mP.js → ODataTreeMultiSelect.stories-CAfKuhfU.js} +1 -1
- package/storybook-static/assets/{ODataTreeSelect-Bzzt4CQC.js → ODataTreeSelect-Cel1phxZ.js} +1 -1
- package/storybook-static/assets/{ODataTreeSelect.stories-CWVvuqOt.js → ODataTreeSelect.stories-BpBCpBA9.js} +1 -1
- package/storybook-static/assets/{OneStepScroller-BY5lz8qi.js → OneStepScroller-BSeGbz02.js} +1 -1
- package/storybook-static/assets/{Option-Rw8xwrPg.js → Option-BBtQ3GdK.js} +1 -1
- package/storybook-static/assets/{OptionButton-Br48w_Zg.js → OptionButton-B9nZuqqW.js} +1 -1
- package/storybook-static/assets/{PeriodSelect-DxSRwKw-.js → PeriodSelect-BGvhR3to.js} +1 -1
- package/storybook-static/assets/{PeriodSelect-8-wpXz6w.js → PeriodSelect-DA_zjcdA.js} +1 -1
- package/storybook-static/assets/{PeriodSelect.stories-B-qgUzlS.js → PeriodSelect.stories-DDP1eXyM.js} +1 -1
- package/storybook-static/assets/{PickerTriggerActions-C5xmysFh.js → PickerTriggerActions-BgvzqRBd.js} +1 -1
- package/storybook-static/assets/{Popover-Ds8PUigp.js → Popover-BQql18MF.js} +1 -1
- package/storybook-static/assets/{Popover-NJwt_vZZ.js → Popover-C0NebUMz.js} +1 -1
- package/storybook-static/assets/{Popover.stories-CUOFA_Xf.js → Popover.stories-CIyDUtlz.js} +1 -1
- package/storybook-static/assets/{PresetRangeDateInput-DyOpbkON.js → PresetRangeDateInput-BsgzPwCV.js} +1 -1
- package/storybook-static/assets/{PresetRangeDateInput-Xk0bOy3S.js → PresetRangeDateInput-htbxc3ni.js} +1 -1
- package/storybook-static/assets/{PresetRangeDateInput.stories-CwjUlXj0.js → PresetRangeDateInput.stories-ClHM5Kah.js} +1 -1
- package/storybook-static/assets/{PresetSelect-Ddmr0niY.js → PresetSelect-Dcx0BOTx.js} +1 -1
- package/storybook-static/assets/README--LCKbep-.js +110 -0
- package/storybook-static/assets/{README-DRnlD7y8.js → README-5M8ajrJp.js} +1 -1
- package/storybook-static/assets/README-5NhzH8VL.js +95 -0
- package/storybook-static/assets/README-Am6Fa6-b.js +69 -0
- package/storybook-static/assets/README-B3bFglqG.js +82 -0
- package/storybook-static/assets/README-B45Cwj2a.js +95 -0
- package/storybook-static/assets/README-B6Nq7NCU.js +416 -0
- package/storybook-static/assets/{README-B4zYJg6u.js → README-B6ltJiw3.js} +1 -1
- package/storybook-static/assets/{README-BdPlV5nA.js → README-BB_JPejH.js} +1 -1
- package/storybook-static/assets/README-BIq-FXcN.js +2124 -0
- package/storybook-static/assets/README-BSh5XUR9.js +110 -0
- package/storybook-static/assets/README-BXJneZAJ.js +147 -0
- package/storybook-static/assets/README-BYSgTTZt.js +296 -0
- package/storybook-static/assets/{README-BzVni2ve.js → README-BmhhWWEk.js} +1 -1
- package/storybook-static/assets/README-Bqnqu1bC.js +118 -0
- package/storybook-static/assets/README-BuMMG4_N.js +192 -0
- package/storybook-static/assets/README-BxjYCoYA.js +186 -0
- package/storybook-static/assets/{README-MzcO5Z_P.js → README-ByALXSEH.js} +1 -1
- package/storybook-static/assets/README-Byo6DxIF.js +101 -0
- package/storybook-static/assets/README-C3UcjOGB.js +208 -0
- package/storybook-static/assets/README-C6BOdXNV.js +322 -0
- package/storybook-static/assets/README-C8E1O7DQ.js +230 -0
- package/storybook-static/assets/README-C9eh0JLs.js +64 -0
- package/storybook-static/assets/README-CAGQnLsv.js +90 -0
- package/storybook-static/assets/README-CC00hlZK.js +527 -0
- package/storybook-static/assets/README-CH_6ZIO2.js +64 -0
- package/storybook-static/assets/README-CHbo4O3Z.js +75 -0
- package/storybook-static/assets/{README-CTIBG4zH.js → README-CIGfHKKE.js} +1 -1
- package/storybook-static/assets/{README-BHw6MVjD.js → README-CM-7E-Jf.js} +1 -1
- package/storybook-static/assets/{README-C0BwTe5N.js → README-CNHemCS2.js} +1 -1
- package/storybook-static/assets/{README-DzTc6XmY.js → README-CWfPiPIB.js} +1 -1
- package/storybook-static/assets/{README-Dx3cpFMr.js → README-CeK4kfFO.js} +1 -1
- package/storybook-static/assets/README-Cj2e6HrF.js +178 -0
- package/storybook-static/assets/README-CsoHGRve.js +1844 -0
- package/storybook-static/assets/README-D9OypaFB.js +208 -0
- package/storybook-static/assets/{README-CkWglKbS.js → README-DDBqoI-T.js} +1 -1
- package/storybook-static/assets/README-DE2wk9-v.js +1282 -0
- package/storybook-static/assets/README-DEj2wSK0.js +264 -0
- package/storybook-static/assets/{README-D-XjRtAE.js → README-DIbfpC-2.js} +1 -1
- package/storybook-static/assets/{README-Dki94Nf2.js → README-DJHVgrpj.js} +1 -1
- package/storybook-static/assets/README-DN9HKtp_.js +124 -0
- package/storybook-static/assets/README-DNricOkt.js +136 -0
- package/storybook-static/assets/README-DV4fCpqN.js +1146 -0
- package/storybook-static/assets/{README-BzVHwvgo.js → README-DjCArsA5.js} +1 -1
- package/storybook-static/assets/README-Dl92E69Z.js +1704 -0
- package/storybook-static/assets/README-DnA-4W65.js +226 -0
- package/storybook-static/assets/README-DoCA4JXf.js +273 -0
- package/storybook-static/assets/{README-DqQDgJN-.js → README-DvAlDsh9.js} +1 -1
- package/storybook-static/assets/README-Ss-3t1nX.js +158 -0
- package/storybook-static/assets/README-eHCINzP3.js +154 -0
- package/storybook-static/assets/README-qHKxXU95.js +98 -0
- package/storybook-static/assets/README-tm9QH9cF.js +103 -0
- package/storybook-static/assets/README-ui-uanyg.js +672 -0
- package/storybook-static/assets/{RadioButton-oSvQ-gQh.js → RadioButton-CWtjclwT.js} +1 -1
- package/storybook-static/assets/{RadioButton-DWbkCY8u.js → RadioButton-YIQXt0wx.js} +1 -1
- package/storybook-static/assets/{RadioButton.stories-GNQ9XJZT.js → RadioButton.stories-Ah-CBpeC.js} +1 -1
- package/storybook-static/assets/{RadioGroup-CvXjGXcV.js → RadioGroup-BnO-GL7A.js} +1 -1
- package/storybook-static/assets/{RadioGroup-eBYggurL.js → RadioGroup-CUS0etBD.js} +1 -1
- package/storybook-static/assets/{RadioGroup.stories-C77j_cPv.js → RadioGroup.stories-DGwCawtU.js} +1 -1
- package/storybook-static/assets/{Scrollable-De6SdhI3.js → Scrollable-D85fA6pO.js} +1 -1
- package/storybook-static/assets/{Select-Bwc0bEpt.js → Select-CGCxJjL9.js} +1 -1
- package/storybook-static/assets/{Select-DQMoVjn4.js → Select-gRnfvfGh.js} +1 -1
- package/storybook-static/assets/{Select.stories-BSIJKJnM.js → Select.stories-CMwt4F9i.js} +1 -1
- package/storybook-static/assets/{SerializableSelect-B73irNFR.js → SerializableSelect-BLXfKJ38.js} +1 -1
- package/storybook-static/assets/{SerializableSelect.stories-Z9q6g5v2.js → SerializableSelect.stories-Bca1a9ZU.js} +1 -1
- package/storybook-static/assets/{SingleDateTimeInput-Dq9LcjnS.js → SingleDateTimeInput-CyeQN0Rd.js} +1 -1
- package/storybook-static/assets/{SingleDateTimeInput-BHl6srqA.js → SingleDateTimeInput-xrmZckzw.js} +1 -1
- package/storybook-static/assets/SingleDateTimeInput.stories-BYFTQM9z.js +48 -0
- package/storybook-static/assets/{Slider-BiyKexKa.js → Slider-1yJ3uVeR.js} +1 -1
- package/storybook-static/assets/{Slider-CvM0N_kc.js → Slider-D8GtXVGI.js} +1 -1
- package/storybook-static/assets/{Slider.stories-CdHAMDmY.js → Slider.stories-CyQ3ns_C.js} +1 -1
- package/storybook-static/assets/{SliderInput-Bx_oHUCV.js → SliderInput-BOzHhEUA.js} +1 -1
- package/storybook-static/assets/{SliderInput-DqHotbhB.js → SliderInput-ISSx9vH_.js} +1 -1
- package/storybook-static/assets/{SliderInput.stories-R5yDhghM.js → SliderInput.stories-7ooTghrS.js} +1 -1
- package/storybook-static/assets/{Sortable-D7pjTHO0.js → Sortable-CCLiMmfZ.js} +1 -1
- package/storybook-static/assets/{Sortable.stories-CVLG6pSH.js → Sortable.stories-B36szI6t.js} +1 -1
- package/storybook-static/assets/{Splitter-B1Du6XI2.js → Splitter-DQGhgRwx.js} +1 -1
- package/storybook-static/assets/{Splitter.stories-D-FRLMCc.js → Splitter.stories-Hsq1wYaq.js} +1 -1
- package/storybook-static/assets/{StateSelect-zl-yZibY.js → StateSelect-C6hldFgO.js} +1 -1
- package/storybook-static/assets/{StateSelect-BJdUEUBc.js → StateSelect-DieNn4VC.js} +1 -1
- package/storybook-static/assets/{StateSelect.stories-DgidR5zR.js → StateSelect.stories-CvAhDMix.js} +1 -1
- package/storybook-static/assets/{StringToggle.stories-IucGrtIi.js → StringToggle.stories-CUT1tvt-.js} +1 -1
- package/storybook-static/assets/{Switch-BJrn2lIN.js → Switch-CX9p_uYU.js} +1 -1
- package/storybook-static/assets/{Switch-5dCshvVo.js → Switch-nEMRtcWe.js} +1 -1
- package/storybook-static/assets/{Switch.stories-Dp_l3_JL.js → Switch.stories-CPsHa2_m.js} +1 -1
- package/storybook-static/assets/{Table-BFAEcUde.js → Table-BhtITVtj.js} +1 -1
- package/storybook-static/assets/{Table.stories-CohejRQv.js → Table.stories-nWNhSydG.js} +2 -2
- package/storybook-static/assets/{Tabs-CI32QiHf.js → Tabs-DMQFh2BJ.js} +1 -1
- package/storybook-static/assets/{Tabs.stories-Cr9d9ipA.js → Tabs.stories-D23_xvom.js} +1 -1
- package/storybook-static/assets/{TabsBox-BlDOUDMF.js → TabsBox-B5BsD_j_.js} +1 -1
- package/storybook-static/assets/{TagInput-YZ7eSv8W.js → TagInput-DO17z100.js} +1 -1
- package/storybook-static/assets/{TagInput.stories-DrOvsiI5.js → TagInput.stories-CMMFnIzb.js} +1 -1
- package/storybook-static/assets/{Text-DK4jN26J.js → Text-ClnbDqRk.js} +1 -1
- package/storybook-static/assets/{Textarea-DxPuWBZj.js → Textarea-C2IO_zsP.js} +1 -1
- package/storybook-static/assets/{Textarea-CFh5f_lC.js → Textarea-DkhohiNc.js} +1 -1
- package/storybook-static/assets/{Textarea.stories-CUeOGKYc.js → Textarea.stories-NL6Aq8Vc.js} +1 -1
- package/storybook-static/assets/{TimePanel-BUhFtwlb.js → TimePanel-DNEPDlSW.js} +1 -1
- package/storybook-static/assets/{TimePanel.stories-CpXpMBDN.js → TimePanel.stories-DlfdPsz1.js} +1 -1
- package/storybook-static/assets/{Toggle-BtApzBNH.js → Toggle-3U5CceQN.js} +1 -1
- package/storybook-static/assets/{Toggle-l2AvDbRn.js → Toggle-Cc6Ca-HI.js} +1 -1
- package/storybook-static/assets/{Toggle.stories-Ca8otmAa.js → Toggle.stories-BR-hVLDp.js} +1 -1
- package/storybook-static/assets/{TreeMultiSelect-nXC4ZB5b.js → TreeMultiSelect-B0oBc-4y.js} +1 -1
- package/storybook-static/assets/{TreeMultiSelect-C9QDV63L.js → TreeMultiSelect-D9hMQN2O.js} +1 -1
- package/storybook-static/assets/{TreeMultiSelect.stories-6STRGJbF.js → TreeMultiSelect.stories-CslpbvPR.js} +1 -1
- package/storybook-static/assets/{TreePickerBase-Cq7fSw3x.js → TreePickerBase-B_dMCe45.js} +1 -1
- package/storybook-static/assets/{TreeSelect-CITef9rp.js → TreeSelect-2lj0Q8gm.js} +1 -1
- package/storybook-static/assets/{TreeSelect-rMNyu_KT.js → TreeSelect-DEKNYF2b.js} +1 -1
- package/storybook-static/assets/{TreeSelect.stories-B-Emz9yW.js → TreeSelect.stories-CVDxn8Vm.js} +1 -1
- package/storybook-static/assets/{TreeTable-CLiP0td3.js → TreeTable-CBIpzYRL.js} +1 -1
- package/storybook-static/assets/{TreeTable.stories-DJb9qt8k.js → TreeTable.stories-BMzrr2Kc.js} +2 -2
- package/storybook-static/assets/{blocks-DndFRxxW.js → blocks-B5YBxaaS.js} +3 -3
- package/storybook-static/assets/{check-CLkTJVXe.js → check-BjtdCRaW.js} +1 -1
- package/storybook-static/assets/{chevron-down-miCejSEn.js → chevron-down-B4u-y8Z1.js} +1 -1
- package/storybook-static/assets/{chevron-left-DhQIPej2.js → chevron-left-BQfWs7Ah.js} +1 -1
- package/storybook-static/assets/{chevron-right-DfBBx-fr.js → chevron-right-ChokKd-f.js} +1 -1
- package/storybook-static/assets/{chunk-W22LQPXL-IdwKKJUw.js → chunk-W22LQPXL-meOkhzk4.js} +1 -1
- package/storybook-static/assets/{circle-alert-DOmVf8am.js → circle-alert-B1AnDRf_.js} +1 -1
- package/storybook-static/assets/{circle-check-e7eSQ2VD.js → circle-check-JYnlcC1F.js} +1 -1
- package/storybook-static/assets/{components-Bm4Cw8Ap.js → components-C_wl1sL5.js} +3 -3
- package/storybook-static/assets/{contextMenu-DpIKeJju.js → contextMenu-B-TUGs_1.js} +1 -1
- package/storybook-static/assets/{defaultRenderers-m21aWJXz.js → defaultRenderers-DfmnIuod.js} +1 -1
- package/storybook-static/assets/{download-eAKjms6q.js → download-eR3X8I-h.js} +1 -1
- package/storybook-static/assets/{floating-ui.react-DmilONq6.js → floating-ui.react-D95VPQ0b.js} +1 -1
- package/storybook-static/assets/formatters-D33Uh3si-CxrG0jsB.js +1 -0
- package/storybook-static/assets/foundationApi-QhHXpYLB.js +59 -0
- package/storybook-static/assets/foundationLib-CEAjnstw.js +30 -0
- package/storybook-static/assets/{hooks-RI5_2AXI.js → hooks-awde7r2r.js} +1 -1
- package/storybook-static/assets/iframe-DzQq_POR.js +1 -0
- package/storybook-static/assets/{iframe-Sr0umHM6.js → iframe-FSp_AjJa.js} +4 -4
- package/storybook-static/assets/{info-DhJTzwcL.js → info-12vmb1sT.js} +1 -1
- package/storybook-static/assets/odata-C0Kpvttz.js +1 -0
- package/storybook-static/assets/odata-service-CWkHASdw-DlmSiioz.js +3 -0
- package/storybook-static/assets/{odataStoryFixtures-lgnWIUp7.js → odataStoryFixtures-BXZnk_C7.js} +1 -1
- package/storybook-static/assets/{react-YFbUKK0m.js → react-BlCqWuXr.js} +1 -1
- package/storybook-static/assets/{refresh-cw-C7WnxX9M.js → refresh-cw-Bf5ufPte.js} +1 -1
- package/storybook-static/assets/{settings-2-0NVvyKQl.js → settings-2-DR-dTXIC.js} +1 -1
- package/storybook-static/assets/{sortable-DDGG-Zfs.js → sortable-B5e5yb_-.js} +1 -1
- package/storybook-static/assets/{syntaxhighlighter-QEQV7K2Y-CxWm8nc8.js → syntaxhighlighter-QEQV7K2Y-EVJARTec.js} +1 -1
- package/storybook-static/assets/{trash-2-BnceCuhg.js → trash-2-QP7TS18H.js} +1 -1
- package/storybook-static/assets/{triangle-alert-MPi3GVgt.js → triangle-alert-qw19mxHr.js} +1 -1
- package/storybook-static/assets/{useODataSelectBase-hmjK6chS.js → useODataSelectBase-Csu_Cct9.js} +1 -1
- package/storybook-static/assets/useODataTreeData-BAv-Twqf.js +1 -0
- package/storybook-static/assets/{usePickerQuery-CtsGhieD.js → usePickerQuery-Da-XM_Y0.js} +1 -1
- package/storybook-static/assets/{usePickerTriggerController-EaI8W2G0.js → usePickerTriggerController-C0TdC2Pz.js} +1 -1
- package/storybook-static/assets/{x-CqPSvWfo.js → x-aCjYTirW.js} +1 -1
- package/storybook-static/iframe.html +2 -2
- package/storybook-static/index.json +1 -1
- package/storybook-static/project.json +1 -1
- package/storybook-static/assets/DateInput-05Iy69_a.js +0 -1
- package/storybook-static/assets/NotificationsHost.stories-CgbFboVD.js +0 -29
- package/storybook-static/assets/SingleDateTimeInput.stories-C2RuBRAF.js +0 -48
- package/storybook-static/assets/async-SjiCjIjO.js +0 -625
- package/storybook-static/assets/formatters-CBgrBeSA-CqB1pZxS.js +0 -1
- package/storybook-static/assets/foundationLib-DRrox3tN.js +0 -25
- package/storybook-static/assets/iframe-DQo-3eW2.js +0 -1
- package/storybook-static/assets/odata-DhISMZkE.js +0 -1
- package/storybook-static/assets/odata-service-B6TxBSLm-Bq8Ya67E.js +0 -3
- package/storybook-static/assets/odataFetchFn-Dv6Xi5CI.js +0 -763
- package/storybook-static/assets/persisted-CUFiw53N.js +0 -600
- package/storybook-static/assets/useODataTreeData-CKuavbWc.js +0 -1
|
@@ -0,0 +1,2124 @@
|
|
|
1
|
+
import{n as e}from"./rolldown-runtime-DkW27tQK.js";import{n as t}from"./iframe-FSp_AjJa.js";import{i as n,r}from"./react-Bl2r1tuC.js";import{l as i,m as a}from"./blocks-B5YBxaaS.js";function o(e){let t={a:`a`,blockquote:`blockquote`,code:`code`,h1:`h1`,h2:`h2`,h3:`h3`,h4:`h4`,li:`li`,ol:`ol`,p:`p`,pre:`pre`,strong:`strong`,table:`table`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,...n(),...e.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(i,{title:`Foundation Lib/Data/Table and Tree Table`}),`
|
|
2
|
+
`,(0,c.jsx)(t.h1,{id:`table-и-tree-table`,children:`Table и Tree Table`}),`
|
|
3
|
+
`,(0,c.jsx)(t.p,{children:`Эта страница описывает два связанных публичных модуля:`}),`
|
|
4
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
5
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/table`}),` — типы и helpers для TanStack Table v9, колонок, layout, выбора строк и ячеек, а также OData metadata;`]}),`
|
|
6
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/tree-table`}),` — построение древовидных строк и преобразование плоских аналитических данных в иерархию.`]}),`
|
|
7
|
+
`]}),`
|
|
8
|
+
`,(0,c.jsx)(t.p,{children:`Документ рассчитан на разработчика без доступа к исходному коду пакета и с минимальным знанием JavaScript, TypeScript, React и TanStack Table. В нём объясняются не только названия функций, но и смысл каждого состояния, правила идентификаторов, результат вызова, ограничения и типичные ошибки.`}),`
|
|
9
|
+
`,(0,c.jsxs)(t.blockquote,{children:[`
|
|
10
|
+
`,(0,c.jsxs)(t.p,{children:[`Документ один, потому что плоская и древовидная таблицы используют общие понятия строк, колонок, идентификаторов и selection. Публичных import path при этом два: API `,(0,c.jsx)(t.code,{children:`tree-table`}),` не реэкспортируется из `,(0,c.jsx)(t.code,{children:`table`}),`, а API `,(0,c.jsx)(t.code,{children:`table`}),` не реэкспортируется из `,(0,c.jsx)(t.code,{children:`tree-table`}),`.`]}),`
|
|
11
|
+
`]}),`
|
|
12
|
+
`,(0,c.jsx)(t.h2,{id:`содержание`,children:`Содержание`}),`
|
|
13
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
14
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BD%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8-%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B`,children:`Назначение и границы`})}),`
|
|
15
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D0%B8-%D0%B8%D0%BC%D0%BF%D0%BE%D1%80%D1%82`,children:`Установка и импорт`})}),`
|
|
16
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%87%D1%82%D0%BE-%D0%BD%D1%83%D0%B6%D0%BD%D0%BE-%D0%B7%D0%BD%D0%B0%D1%82%D1%8C-%D0%BF%D0%B5%D1%80%D0%B5%D0%B4-%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D0%BE%D0%BC`,children:`Что нужно знать перед началом`})}),`
|
|
17
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BA%D0%B0%D0%BA-%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C-%D0%BD%D1%83%D0%B6%D0%BD%D1%83%D1%8E-%D1%87%D0%B0%D1%81%D1%82%D1%8C-api`,children:`Как выбрать нужную часть API`})}),`
|
|
18
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D0%B9-%D1%81%D1%82%D0%B0%D1%80%D1%82-%D0%BF%D0%BB%D0%BE%D1%81%D0%BA%D0%B0%D1%8F-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0`,children:`Быстрый старт: плоская таблица`})}),`
|
|
19
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D0%B9-%D1%81%D1%82%D0%B0%D1%80%D1%82-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE-%D1%81-parentid`,children:[`Быстрый старт: дерево с `,(0,c.jsx)(t.code,{children:`parentId`})]})}),`
|
|
20
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D0%B9-%D1%81%D1%82%D0%B0%D1%80%D1%82-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE-%D0%B8%D0%B7-%D0%B0%D0%BD%D0%B0%D0%BB%D0%B8%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85-%D0%BA%D0%BE%D0%BB%D0%BE%D0%BD%D0%BE%D0%BA`,children:`Быстрый старт: дерево из аналитических колонок`})}),`
|
|
21
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BA%D0%BE%D0%BB%D0%BE%D0%BD%D0%BA%D0%B8-%D0%B8-%D0%B8%D1%85-metadata`,children:`Колонки и их metadata`})}),`
|
|
22
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#foundation-features-tanstack-table`,children:`Foundation features TanStack Table`})}),`
|
|
23
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D0%BA-%D0%B8-layout-%D0%BA%D0%BE%D0%BB%D0%BE%D0%BD%D0%BE%D0%BA`,children:`Порядок и layout колонок`})}),`
|
|
24
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#drag-and-drop-%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D0%BA%D0%BE%D0%BB%D0%BE%D0%BD%D0%BE%D0%BA`,children:`Drag-and-drop перестановка колонок`})}),`
|
|
25
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%8B-%D0%BA%D0%BE%D0%BB%D0%BE%D0%BD%D0%BE%D0%BA`,children:`Ширины колонок`})}),`
|
|
26
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B2%D0%B8%D0%B4%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C-%D0%BA%D0%BE%D0%BB%D0%BE%D0%BD%D0%BE%D0%BA`,children:`Видимость колонок`})}),`
|
|
27
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B2%D1%8B%D0%B1%D0%BE%D1%80-%D1%81%D1%82%D1%80%D0%BE%D0%BA`,children:`Выбор строк`})}),`
|
|
28
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#hook-usetablerowselection`,children:[`Hook `,(0,c.jsx)(t.code,{children:`useTableRowSelection`})]})}),`
|
|
29
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B2%D1%8B%D0%B1%D0%BE%D1%80-%D1%8F%D1%87%D0%B5%D0%B5%D0%BA`,children:`Выбор ячеек`})}),`
|
|
30
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B0%D0%BA%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B5-%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B-%D0%B8-css-%D0%B4%D0%BB%D0%B8%D0%BD%D1%8B`,children:`Интерактивные элементы и CSS-длины`})}),`
|
|
31
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BA%D0%BE%D0%BB%D0%BE%D0%BD%D0%BA%D0%B8-%D0%B8%D0%B7-odata-metadata`,children:`Колонки из OData metadata`})}),`
|
|
32
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0-%D0%B8%D0%B7-id-%D0%B8-parentid`,children:[`Построение дерева из `,(0,c.jsx)(t.code,{children:`id`}),` и `,(0,c.jsx)(t.code,{children:`parentId`})]})}),`
|
|
33
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%82%D1%80%D0%B0%D0%BD%D1%81%D0%BF%D0%BE%D0%BD%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B0%D0%BD%D0%B0%D0%BB%D0%B8%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85-%D1%81%D1%82%D1%80%D0%BE%D0%BA`,children:`Транспонирование аналитических строк`})}),`
|
|
34
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9-pipeline-%D0%B4%D0%BB%D1%8F-%D0%B0%D0%BD%D0%B0%D0%BB%D0%B8%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B0`,children:`Полный pipeline для аналитического дерева`})}),`
|
|
35
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#controlled-state-%D0%B8-%D1%81%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BA`,children:`Controlled state и сохранение настроек`})}),`
|
|
36
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#server-side-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5-%D0%B8-%D0%BF%D0%B0%D0%B3%D0%B8%D0%BD%D0%B0%D1%86%D0%B8%D1%8F`,children:`Server-side данные и пагинация`})}),`
|
|
37
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#react-browser-%D0%B8-ssr`,children:`React, browser и SSR`})}),`
|
|
38
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%BE%D0%B4%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C`,children:`Производительность`})}),`
|
|
39
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-consumer-%D0%BA%D0%BE%D0%B4%D0%B0`,children:`Тестирование consumer-кода`})}),`
|
|
40
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#%D0%BF%D0%BE%D0%BB%D0%BD%D0%B0%D1%8F-%D0%BA%D0%B0%D1%80%D1%82%D0%B0-api-table`,children:[`Полная карта API `,(0,c.jsx)(t.code,{children:`table`})]})}),`
|
|
41
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsxs)(t.a,{href:`#%D0%BF%D0%BE%D0%BB%D0%BD%D0%B0%D1%8F-%D0%BA%D0%B0%D1%80%D1%82%D0%B0-api-tree-table`,children:[`Полная карта API `,(0,c.jsx)(t.code,{children:`tree-table`})]})}),`
|
|
42
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%82%D0%B8%D0%BF%D0%B8%D1%87%D0%BD%D1%8B%D0%B5-%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B8`,children:`Типичные ошибки`})}),`
|
|
43
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%87%D0%B0%D1%81%D1%82%D1%8B%D0%B5-%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D1%8B`,children:`Частые вопросы`})}),`
|
|
44
|
+
`]}),`
|
|
45
|
+
`,(0,c.jsx)(t.h2,{id:`назначение-и-границы`,children:`Назначение и границы`}),`
|
|
46
|
+
`,(0,c.jsxs)(t.h3,{id:`что-делает-table`,children:[`Что делает `,(0,c.jsx)(t.code,{children:`table`})]}),`
|
|
47
|
+
`,(0,c.jsx)(t.p,{children:`Модуль предоставляет общий технический контракт таблиц:`}),`
|
|
48
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`данные + стабильные rowId
|
|
49
|
+
│
|
|
50
|
+
▼
|
|
51
|
+
TanStack Table state
|
|
52
|
+
/ │ \\
|
|
53
|
+
columns selection sizing/order
|
|
54
|
+
\\ │ /
|
|
55
|
+
▼
|
|
56
|
+
UI-компонент host
|
|
57
|
+
`})}),`
|
|
58
|
+
`,(0,c.jsx)(t.p,{children:`Основные возможности:`}),`
|
|
59
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
60
|
+
`,(0,c.jsx)(t.li,{children:`единый набор TanStack Table v9 features;`}),`
|
|
61
|
+
`,(0,c.jsx)(t.li,{children:`типизированное описание колонок;`}),`
|
|
62
|
+
`,(0,c.jsx)(t.li,{children:`metadata колонки: ширина, выравнивание, display-only merge и форматирование;`}),`
|
|
63
|
+
`,(0,c.jsx)(t.li,{children:`восстановление и изменение порядка колонок;`}),`
|
|
64
|
+
`,(0,c.jsx)(t.li,{children:`нормализация ширин колонок;`}),`
|
|
65
|
+
`,(0,c.jsxs)(t.li,{children:[`выбор строк в режимах `,(0,c.jsx)(t.code,{children:`none`}),`, `,(0,c.jsx)(t.code,{children:`single`}),` и `,(0,c.jsx)(t.code,{children:`multi`}),`;`]}),`
|
|
66
|
+
`,(0,c.jsxs)(t.li,{children:[`выбор ячеек по координатам `,(0,c.jsx)(t.code,{children:`rowId + columnId`}),`;`]}),`
|
|
67
|
+
`,(0,c.jsx)(t.li,{children:`React hook для общей модели выбора строк;`}),`
|
|
68
|
+
`,(0,c.jsx)(t.li,{children:`генерация колонок и стартовой видимости по OData metadata;`}),`
|
|
69
|
+
`,(0,c.jsx)(t.li,{children:`browser helper, который отличает клик по кнопке от клика по строке.`}),`
|
|
70
|
+
`]}),`
|
|
71
|
+
`,(0,c.jsxs)(t.h3,{id:`что-делает-tree-table`,children:[`Что делает `,(0,c.jsx)(t.code,{children:`tree-table`})]}),`
|
|
72
|
+
`,(0,c.jsx)(t.p,{children:`Модуль решает две разные задачи:`}),`
|
|
73
|
+
`,(0,c.jsxs)(t.ol,{children:[`
|
|
74
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),` связывает уже существующие строки по `,(0,c.jsx)(t.code,{children:`id`}),` и `,(0,c.jsx)(t.code,{children:`parentId`}),`.`]}),`
|
|
75
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`transposeFlatRowsToTreeTableRows`}),` создаёт уровни дерева из значений колонок, например `,(0,c.jsx)(t.code,{children:`DIVISION`}),` и `,(0,c.jsx)(t.code,{children:`DEPARTMENT`}),`.`]}),`
|
|
76
|
+
`]}),`
|
|
77
|
+
`,(0,c.jsx)(t.p,{children:`Обе функции являются преобразователями данных. Они не рисуют HTML и не хранят React state.`}),`
|
|
78
|
+
`,(0,c.jsx)(t.h3,{id:`чего-эти-модули-не-делают`,children:`Чего эти модули не делают`}),`
|
|
79
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`table`}),` и `,(0,c.jsx)(t.code,{children:`tree-table`}),` не являются готовыми визуальными компонентами:`]}),`
|
|
80
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
81
|
+
`,(0,c.jsxs)(t.li,{children:[`не создают `,(0,c.jsx)(t.code,{children:`<table>`}),`;`]}),`
|
|
82
|
+
`,(0,c.jsx)(t.li,{children:`не применяют CSS;`}),`
|
|
83
|
+
`,(0,c.jsx)(t.li,{children:`не показывают loading, empty и error state;`}),`
|
|
84
|
+
`,(0,c.jsx)(t.li,{children:`не выполняют HTTP- или OData-запросы;`}),`
|
|
85
|
+
`,(0,c.jsxs)(t.li,{children:[`не строят `,(0,c.jsx)(t.code,{children:`$filter`}),`, `,(0,c.jsx)(t.code,{children:`$orderby`}),` и URL;`]}),`
|
|
86
|
+
`,(0,c.jsx)(t.li,{children:`не выполняют server-side sorting, filtering, grouping или paging;`}),`
|
|
87
|
+
`,(0,c.jsx)(t.li,{children:`не агрегируют суммы для синтетических узлов дерева;`}),`
|
|
88
|
+
`,(0,c.jsxs)(t.li,{children:[`не сохраняют настройки в `,(0,c.jsx)(t.code,{children:`localStorage`}),` автоматически;`]}),`
|
|
89
|
+
`,(0,c.jsx)(t.li,{children:`не определяют бизнес-правила выбора строк;`}),`
|
|
90
|
+
`,(0,c.jsx)(t.li,{children:`не делают authorization или validation backend-данных.`}),`
|
|
91
|
+
`]}),`
|
|
92
|
+
`,(0,c.jsx)(t.p,{children:`Используйте правильного владельца:`}),`
|
|
93
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Задача`}),(0,c.jsx)(t.th,{children:`Владелец`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Типы, state helpers, column metadata`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/table`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Построение структуры дерева`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/tree-table`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Готовые визуальные Table/TreeTable primitives`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-ui/table`}),` и `,(0,c.jsx)(t.code,{children:`/tree-table`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`OData metadata, literals, filters и paths`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/odata-service`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`HTTP, SAP SSO, X-CSRF, Query operations`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-api/odata`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Бизнес-колонки, permissions и row actions`}),(0,c.jsx)(t.td,{children:`entity/feature/widget host-приложения`})]})]})]}),`
|
|
94
|
+
`,(0,c.jsx)(t.h3,{id:`важная-граница-server-side-таблицы`,children:`Важная граница server-side таблицы`}),`
|
|
95
|
+
`,(0,c.jsx)(t.p,{children:`Если строки приходят с backend, источник истины для sorting, filtering, grouping, totals и pagination обычно находится на сервере. Helpers этого модуля не дают оснований включать локальную сортировку поверх одной загруженной страницы.`}),`
|
|
96
|
+
`,(0,c.jsx)(t.p,{children:`Правильная схема для большой таблицы:`}),`
|
|
97
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`пользователь меняет sort/filter/page
|
|
98
|
+
│
|
|
99
|
+
▼
|
|
100
|
+
host строит query params и стабильный query key
|
|
101
|
+
│
|
|
102
|
+
▼
|
|
103
|
+
@ryuzaki13/react-foundation-api/odata
|
|
104
|
+
│
|
|
105
|
+
▼
|
|
106
|
+
backend возвращает новый snapshot строк
|
|
107
|
+
│
|
|
108
|
+
▼
|
|
109
|
+
table helpers очищают только локальный UI state
|
|
110
|
+
`})}),`
|
|
111
|
+
`,(0,c.jsx)(t.h2,{id:`установка-и-импорт`,children:`Установка и импорт`}),`
|
|
112
|
+
`,(0,c.jsx)(t.p,{children:`Установите пакет:`}),`
|
|
113
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-bash`,children:`npm install @ryuzaki13/react-foundation-lib
|
|
114
|
+
`})}),`
|
|
115
|
+
`,(0,c.jsxs)(t.p,{children:[`Для `,(0,c.jsx)(t.code,{children:`table`}),` host должен разрешить совместимые peer dependencies React и TanStack Table:`]}),`
|
|
116
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-bash`,children:`npm install react @tanstack/react-table
|
|
117
|
+
`})}),`
|
|
118
|
+
`,(0,c.jsx)(t.p,{children:`Публичные импорты:`}),`
|
|
119
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { buildTableColumnLayout, pruneTableRowSelection, toggleTableRowSelection } from "@ryuzaki13/react-foundation-lib/table";
|
|
120
|
+
|
|
121
|
+
import { buildTreeTableRows, transposeFlatRowsToTreeTableRows } from "@ryuzaki13/react-foundation-lib/tree-table";
|
|
122
|
+
`})}),`
|
|
123
|
+
`,(0,c.jsxs)(t.p,{children:[`Типы предпочтительно импортировать через `,(0,c.jsx)(t.code,{children:`import type`}),`:`]}),`
|
|
124
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import type { TableColumnDef, TableColumnSizingState, TableSelectionMode } from "@ryuzaki13/react-foundation-lib/table";
|
|
125
|
+
|
|
126
|
+
import type { TreeTableBuildResult, TreeTableFlatHierarchy, TreeTableTransposedRow } from "@ryuzaki13/react-foundation-lib/tree-table";
|
|
127
|
+
`})}),`
|
|
128
|
+
`,(0,c.jsx)(t.p,{children:`Неверные импорты:`}),`
|
|
129
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Корневой entrypoint пакета не опубликован.
|
|
130
|
+
import { buildTreeTableRows } from "@ryuzaki13/react-foundation-lib";
|
|
131
|
+
|
|
132
|
+
// Внутреннее расположение файлов не является публичным контрактом.
|
|
133
|
+
import { toggleTableRowSelection } from "@ryuzaki13/react-foundation-lib/dist/table/selection";
|
|
134
|
+
|
|
135
|
+
// tree-table — отдельный опубликованный subpath.
|
|
136
|
+
import { buildTreeTableRows } from "@ryuzaki13/react-foundation-lib/table";
|
|
137
|
+
`})}),`
|
|
138
|
+
`,(0,c.jsxs)(t.p,{children:[`Пакет распространяется как ESM. Используйте `,(0,c.jsx)(t.code,{children:`import`}),`, а не CommonJS `,(0,c.jsx)(t.code,{children:`require`}),`.`]}),`
|
|
139
|
+
`,(0,c.jsx)(t.h3,{id:`совместимость-tanstack-table`,children:`Совместимость TanStack Table`}),`
|
|
140
|
+
`,(0,c.jsxs)(t.p,{children:[`Публичные типы `,(0,c.jsx)(t.code,{children:`TableColumnDef`}),`, `,(0,c.jsx)(t.code,{children:`FoundationTableInstance`}),` и `,(0,c.jsx)(t.code,{children:`foundationTableFeatures`}),` рассчитаны на TanStack Table v9. Host должен использовать совместимую major-версию `,(0,c.jsx)(t.code,{children:`@tanstack/react-table`}),`.`]}),`
|
|
141
|
+
`,(0,c.jsxs)(t.p,{children:[`Не копируйте пример v8 механически: в v9 единый набор features передаётся через свойство `,(0,c.jsx)(t.code,{children:`features`}),`, а основной React hook называется `,(0,c.jsx)(t.code,{children:`useTable`}),`.`]}),`
|
|
142
|
+
`,(0,c.jsx)(t.h2,{id:`что-нужно-знать-перед-началом`,children:`Что нужно знать перед началом`}),`
|
|
143
|
+
`,(0,c.jsx)(t.h3,{id:`строка`,children:`Строка`}),`
|
|
144
|
+
`,(0,c.jsx)(t.p,{children:`Строка — обычный JavaScript-объект:`}),`
|
|
145
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type Employee = {
|
|
146
|
+
id: string;
|
|
147
|
+
name: string;
|
|
148
|
+
departmentId: string | null;
|
|
149
|
+
salary: number;
|
|
150
|
+
};
|
|
151
|
+
`})}),`
|
|
152
|
+
`,(0,c.jsx)(t.p,{children:`Один объект представляет одну запись таблицы.`}),`
|
|
153
|
+
`,(0,c.jsx)(t.h3,{id:`идентификатор-строки`,children:`Идентификатор строки`}),`
|
|
154
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`rowId`}),` — стабильная строка, однозначно указывающая на запись:`]}),`
|
|
155
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const getRowId = (row: Employee) => row.id;
|
|
156
|
+
`})}),`
|
|
157
|
+
`,(0,c.jsx)(t.p,{children:`Хорошие идентификаторы:`}),`
|
|
158
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`employee:000123
|
|
159
|
+
order:4500000123:item:000010
|
|
160
|
+
folder:/reports/2026
|
|
161
|
+
`})}),`
|
|
162
|
+
`,(0,c.jsx)(t.p,{children:`Плохой идентификатор для изменяемого списка:`}),`
|
|
163
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const getRowId = (_row: Employee, index: number) => String(index);
|
|
164
|
+
`})}),`
|
|
165
|
+
`,(0,c.jsx)(t.p,{children:`Индекс меняется после sorting, filtering, вставки или удаления строки. Из-за этого selection может визуально перейти на другую запись.`}),`
|
|
166
|
+
`,(0,c.jsx)(t.h3,{id:`колонка`,children:`Колонка`}),`
|
|
167
|
+
`,(0,c.jsx)(t.p,{children:`Колонка описывает, какое поле читать и как его представлять:`}),`
|
|
168
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import type { TableColumnDef } from "@ryuzaki13/react-foundation-lib/table";
|
|
169
|
+
|
|
170
|
+
const columns: TableColumnDef<Employee>[] = [
|
|
171
|
+
{
|
|
172
|
+
id: "name",
|
|
173
|
+
accessorKey: "name",
|
|
174
|
+
header: "Сотрудник"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "salary",
|
|
178
|
+
accessorKey: "salary",
|
|
179
|
+
header: "Оклад",
|
|
180
|
+
meta: { align: "right" }
|
|
181
|
+
}
|
|
182
|
+
];
|
|
183
|
+
`})}),`
|
|
184
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`id`}),` нужен state-механизмам. `,(0,c.jsx)(t.code,{children:`accessorKey`}),` говорит TanStack Table, какое поле объекта читать.`]}),`
|
|
185
|
+
`,(0,c.jsx)(t.h3,{id:`идентификатор-колонки`,children:`Идентификатор колонки`}),`
|
|
186
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`columnId`}),` связывает несколько независимых состояний:`]}),`
|
|
187
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`columnId "salary"
|
|
188
|
+
├─ порядок колонок
|
|
189
|
+
├─ ширина
|
|
190
|
+
├─ видимость
|
|
191
|
+
├─ pinning
|
|
192
|
+
├─ выбор ячейки
|
|
193
|
+
└─ formatting metadata
|
|
194
|
+
`})}),`
|
|
195
|
+
`,(0,c.jsxs)(t.p,{children:[`После сохранения пользовательских настроек нельзя без миграции менять `,(0,c.jsx)(t.code,{children:`columnId`}),`: старый layout перестанет находить колонку.`]}),`
|
|
196
|
+
`,(0,c.jsx)(t.h3,{id:`state`,children:`State`}),`
|
|
197
|
+
`,(0,c.jsx)(t.p,{children:`State — данные о текущем состоянии UI. Например:`}),`
|
|
198
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const rowSelection = {
|
|
199
|
+
"employee:1": true,
|
|
200
|
+
"employee:2": true
|
|
201
|
+
};
|
|
202
|
+
`})}),`
|
|
203
|
+
`,(0,c.jsxs)(t.p,{children:[`Здесь ключ — `,(0,c.jsx)(t.code,{children:`rowId`}),`, а `,(0,c.jsx)(t.code,{children:`true`}),` означает активный выбор.`]}),`
|
|
204
|
+
`,(0,c.jsx)(t.h3,{id:`иммутабельность`,children:`Иммутабельность`}),`
|
|
205
|
+
`,(0,c.jsx)(t.p,{children:`Helpers возвращают новый массив или объект. Не изменяйте React state напрямую:`}),`
|
|
206
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Неверно: изменяет существующий state.
|
|
207
|
+
rowSelection[rowId] = true;
|
|
208
|
+
|
|
209
|
+
// Верно: helper возвращает следующее состояние.
|
|
210
|
+
const nextSelection = toggleTableRowSelection(rowSelection, rowId, "multi");
|
|
211
|
+
`})}),`
|
|
212
|
+
`,(0,c.jsx)(t.h3,{id:`readonly`,children:(0,c.jsx)(t.code,{children:`readonly`})}),`
|
|
213
|
+
`,(0,c.jsxs)(t.p,{children:[`Тип `,(0,c.jsx)(t.code,{children:`readonly string[]`}),` означает, что функция обещает только читать массив. Обычный `,(0,c.jsx)(t.code,{children:`string[]`}),` можно передать туда без преобразования.`]}),`
|
|
214
|
+
`,(0,c.jsx)(t.h3,{id:`generic-параметр`,children:`Generic-параметр`}),`
|
|
215
|
+
`,(0,c.jsxs)(t.p,{children:[`В записи `,(0,c.jsx)(t.code,{children:`TableColumnDef<Employee>`}),` имя `,(0,c.jsx)(t.code,{children:`Employee`}),` сообщает TypeScript форму одной строки. Это позволяет найти опечатку:`]}),`
|
|
216
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const columns: TableColumnDef<Employee>[] = [
|
|
217
|
+
{
|
|
218
|
+
// TypeScript обнаружит, что поля "fullName" нет в Employee.
|
|
219
|
+
accessorKey: "fullName",
|
|
220
|
+
header: "Имя"
|
|
221
|
+
}
|
|
222
|
+
];
|
|
223
|
+
`})}),`
|
|
224
|
+
`,(0,c.jsx)(t.h3,{id:`map`,children:(0,c.jsx)(t.code,{children:`Map`})}),`
|
|
225
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`Map<string, Employee>`}),` — быстрый индекс строк по идентификатору:`]}),`
|
|
226
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const rowById = new Map(rows.map((row) => [row.id, row]));
|
|
227
|
+
|
|
228
|
+
const employee = rowById.get("employee:1");
|
|
229
|
+
`})}),`
|
|
230
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useTableRowSelection`}),` использует такой индекс, чтобы превратить выбранные `,(0,c.jsx)(t.code,{children:`rowId`}),` обратно в исходные объекты.`]}),`
|
|
231
|
+
`,(0,c.jsx)(t.h2,{id:`как-выбрать-нужную-часть-api`,children:`Как выбрать нужную часть API`}),`
|
|
232
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Нужно`}),(0,c.jsx)(t.th,{children:`API`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Описать типизированные колонки`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TableColumnDef`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Создать TanStack Table v9 вручную`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`foundationTableFeatures`}),` и `,(0,c.jsx)(t.code,{children:`FoundationTable*`}),` types`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Восстановить сохранённый порядок`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resolveTableColumnOrder`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Переставить top-level заголовок`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`reorderTableHeaderColumns`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нормализовать px-ширины`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`normalizeTableColumnSizing`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Выбрать одну или несколько строк`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`toggleTableRowSelection`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Подключить selection к React`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`useTableRowSelection`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Выбрать ячейки`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`toggleTableCellSelection`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Не перехватывать клик по кнопке в строке`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`isTableInteractiveElement`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Создать колонки из OData metadata`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createTableColumnsFromODataMetadata`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[`Связать строки по `,(0,c.jsx)(t.code,{children:`id/parentId`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`buildTreeTableRows`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Получить дерево из колонок уровней`}),(0,c.jsxs)(t.td,{children:[`сначала `,(0,c.jsx)(t.code,{children:`transposeFlatRowsToTreeTableRows`}),`, затем `,(0,c.jsx)(t.code,{children:`buildTreeTableRows`})]})]})]})]}),`
|
|
233
|
+
`,(0,c.jsx)(t.h3,{id:`два-вида-исходных-данных-для-дерева`,children:`Два вида исходных данных для дерева`}),`
|
|
234
|
+
`,(0,c.jsx)(t.p,{children:`Первый вид уже содержит связь родитель–ребёнок:`}),`
|
|
235
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`[
|
|
236
|
+
{ id: "company", parentId: null, title: "Компания" },
|
|
237
|
+
{ id: "sales", parentId: "company", title: "Продажи" }
|
|
238
|
+
];
|
|
239
|
+
`})}),`
|
|
240
|
+
`,(0,c.jsxs)(t.p,{children:[`Для него нужен только `,(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),`.`]}),`
|
|
241
|
+
`,(0,c.jsx)(t.p,{children:`Второй вид содержит факты и повторяющиеся названия уровней:`}),`
|
|
242
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`[
|
|
243
|
+
{ division: "Россия", department: "Продажи", amount: 100 },
|
|
244
|
+
{ division: "Россия", department: "Продажи", amount: 200 },
|
|
245
|
+
{ division: "Россия", department: "Логистика", amount: 50 }
|
|
246
|
+
];
|
|
247
|
+
`})}),`
|
|
248
|
+
`,(0,c.jsxs)(t.p,{children:[`Здесь нет `,(0,c.jsx)(t.code,{children:`id`}),` и `,(0,c.jsx)(t.code,{children:`parentId`}),`. Сначала используйте `,(0,c.jsx)(t.code,{children:`transposeFlatRowsToTreeTableRows`}),`, чтобы создать синтетические group-узлы, потом `,(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),`, чтобы вложить их друг в друга.`]}),`
|
|
249
|
+
`,(0,c.jsx)(t.h2,{id:`быстрый-старт-плоская-таблица`,children:`Быстрый старт: плоская таблица`}),`
|
|
250
|
+
`,(0,c.jsx)(t.h3,{id:`1-опишите-строку`,children:`1. Опишите строку`}),`
|
|
251
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type Order = {
|
|
252
|
+
id: string;
|
|
253
|
+
customer: string;
|
|
254
|
+
amount: number;
|
|
255
|
+
};
|
|
256
|
+
`})}),`
|
|
257
|
+
`,(0,c.jsx)(t.h3,{id:`2-опишите-колонки`,children:`2. Опишите колонки`}),`
|
|
258
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import type { TableColumnDef } from "@ryuzaki13/react-foundation-lib/table";
|
|
259
|
+
|
|
260
|
+
const orderColumns = [
|
|
261
|
+
{
|
|
262
|
+
id: "customer",
|
|
263
|
+
accessorKey: "customer",
|
|
264
|
+
header: "Клиент",
|
|
265
|
+
meta: {
|
|
266
|
+
width: 24,
|
|
267
|
+
align: "left"
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "amount",
|
|
272
|
+
accessorKey: "amount",
|
|
273
|
+
header: "Сумма",
|
|
274
|
+
meta: {
|
|
275
|
+
width: 12,
|
|
276
|
+
align: "right"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
] satisfies TableColumnDef<Order>[];
|
|
280
|
+
`})}),`
|
|
281
|
+
`,(0,c.jsxs)(t.p,{children:[`Число `,(0,c.jsx)(t.code,{children:`24`}),` в `,(0,c.jsx)(t.code,{children:`meta.width`}),` означает `,(0,c.jsx)(t.code,{children:`24em`}),`, а не `,(0,c.jsx)(t.code,{children:`24px`}),`. Runtime px-ширины resize хранятся отдельно в `,(0,c.jsx)(t.code,{children:`TableColumnSizingState`}),`.`]}),`
|
|
282
|
+
`,(0,c.jsx)(t.h3,{id:`3-вычислите-selection`,children:`3. Вычислите selection`}),`
|
|
283
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { toggleTableRowSelection } from "@ryuzaki13/react-foundation-lib/table";
|
|
284
|
+
import type { RowSelectionState } from "@tanstack/react-table";
|
|
285
|
+
|
|
286
|
+
let selection: RowSelectionState = {};
|
|
287
|
+
|
|
288
|
+
selection = toggleTableRowSelection(selection, "order:1", "multi");
|
|
289
|
+
selection = toggleTableRowSelection(selection, "order:2", "multi");
|
|
290
|
+
|
|
291
|
+
console.log(selection);
|
|
292
|
+
// {
|
|
293
|
+
// "order:1": true,
|
|
294
|
+
// "order:2": true
|
|
295
|
+
// }
|
|
296
|
+
`})}),`
|
|
297
|
+
`,(0,c.jsx)(t.h3,{id:`4-передайте-данные-ui-владельцу`,children:`4. Передайте данные UI-владельцу`}),`
|
|
298
|
+
`,(0,c.jsx)(t.p,{children:`Этот lib-модуль не рендерит таблицу. Host может:`}),`
|
|
299
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
300
|
+
`,(0,c.jsxs)(t.li,{children:[`передать `,(0,c.jsx)(t.code,{children:`rows`}),` и `,(0,c.jsx)(t.code,{children:`orderColumns`}),` компоненту из `,(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-ui/table`}),`;`]}),`
|
|
301
|
+
`,(0,c.jsxs)(t.li,{children:[`создать собственную таблицу через `,(0,c.jsx)(t.code,{children:`useTable`}),` и `,(0,c.jsx)(t.code,{children:`foundationTableFeatures`}),`;`]}),`
|
|
302
|
+
`,(0,c.jsx)(t.li,{children:`использовать только pure helpers в другой UI-реализации.`}),`
|
|
303
|
+
`]}),`
|
|
304
|
+
`,(0,c.jsxs)(t.h2,{id:`быстрый-старт-дерево-с-parentid`,children:[`Быстрый старт: дерево с `,(0,c.jsx)(t.code,{children:`parentId`})]}),`
|
|
305
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { buildTreeTableRows } from "@ryuzaki13/react-foundation-lib/tree-table";
|
|
306
|
+
|
|
307
|
+
type Department = {
|
|
308
|
+
id: string;
|
|
309
|
+
parentId: string | null;
|
|
310
|
+
title: string;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const departments: Department[] = [
|
|
314
|
+
{ id: "sales", parentId: "company", title: "Продажи" },
|
|
315
|
+
{ id: "company", parentId: null, title: "Компания" },
|
|
316
|
+
{ id: "retail", parentId: "sales", title: "Розница" }
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
const result = buildTreeTableRows(departments, {
|
|
320
|
+
getRowId: (row) => row.id,
|
|
321
|
+
getParentRowId: (row) => row.parentId
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
console.log(result.rows);
|
|
325
|
+
// [
|
|
326
|
+
// {
|
|
327
|
+
// id: "company",
|
|
328
|
+
// parentId: null,
|
|
329
|
+
// title: "Компания",
|
|
330
|
+
// children: [
|
|
331
|
+
// {
|
|
332
|
+
// id: "sales",
|
|
333
|
+
// parentId: "company",
|
|
334
|
+
// title: "Продажи",
|
|
335
|
+
// children: [
|
|
336
|
+
// { id: "retail", parentId: "sales", title: "Розница" }
|
|
337
|
+
// ]
|
|
338
|
+
// }
|
|
339
|
+
// ]
|
|
340
|
+
// }
|
|
341
|
+
// ]
|
|
342
|
+
`})}),`
|
|
343
|
+
`,(0,c.jsx)(t.p,{children:`Порядок исходных строк не обязан быть «родитель перед ребёнком». Функция сначала строит индексы, а потом дерево.`}),`
|
|
344
|
+
`,(0,c.jsx)(t.h2,{id:`быстрый-старт-дерево-из-аналитических-колонок`,children:`Быстрый старт: дерево из аналитических колонок`}),`
|
|
345
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import {
|
|
346
|
+
buildTreeTableRows,
|
|
347
|
+
transposeFlatRowsToTreeTableRows,
|
|
348
|
+
TREE_TABLE_TRANSPOSED_PARENT_ROW_ID_FIELD,
|
|
349
|
+
TREE_TABLE_TRANSPOSED_ROW_ID_FIELD
|
|
350
|
+
} from "@ryuzaki13/react-foundation-lib/tree-table";
|
|
351
|
+
|
|
352
|
+
type FactRow = {
|
|
353
|
+
DIVISION: string;
|
|
354
|
+
DEPARTMENT: string;
|
|
355
|
+
AMOUNT: number;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const facts: FactRow[] = [
|
|
359
|
+
{ DIVISION: "Россия", DEPARTMENT: "Продажи", AMOUNT: 100 },
|
|
360
|
+
{ DIVISION: "Россия", DEPARTMENT: "Продажи", AMOUNT: 200 },
|
|
361
|
+
{ DIVISION: "Россия", DEPARTMENT: "Логистика", AMOUNT: 50 }
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
const flatHierarchy = transposeFlatRowsToTreeTableRows(facts, {
|
|
365
|
+
hierarchyLevels: [{ columnId: "DIVISION" }, { columnId: "DEPARTMENT" }]
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const tree = buildTreeTableRows(flatHierarchy, {
|
|
369
|
+
getRowId: (row) => row[TREE_TABLE_TRANSPOSED_ROW_ID_FIELD],
|
|
370
|
+
getParentRowId: (row) => row[TREE_TABLE_TRANSPOSED_PARENT_ROW_ID_FIELD]
|
|
371
|
+
});
|
|
372
|
+
`})}),`
|
|
373
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`flatHierarchy`}),` содержит и синтетические group-узлы, и исходные факты-листья. `,(0,c.jsx)(t.code,{children:`tree.rows`}),` содержит те же записи уже с вложенным `,(0,c.jsx)(t.code,{children:`children`}),`.`]}),`
|
|
374
|
+
`,(0,c.jsx)(t.h2,{id:`колонки-и-их-metadata`,children:`Колонки и их metadata`}),`
|
|
375
|
+
`,(0,c.jsx)(t.h3,{id:`tablecolumndeftdata-tvalue`,children:(0,c.jsx)(t.code,{children:`TableColumnDef<TData, TValue>`})}),`
|
|
376
|
+
`,(0,c.jsx)(t.p,{children:`Основной тип описания колонки:`}),`
|
|
377
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TableColumnDef<TData, TValue = unknown> = /* TanStack ColumnDef */ {
|
|
378
|
+
meta?: TableColumnMeta;
|
|
379
|
+
};
|
|
380
|
+
`})}),`
|
|
381
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
382
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`TData`}),` — тип всей строки;`]}),`
|
|
383
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`TValue`}),` — тип значения конкретной колонки;`]}),`
|
|
384
|
+
`,(0,c.jsx)(t.li,{children:`остальные свойства приходят из TanStack Table v9;`}),`
|
|
385
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`meta`}),` добавляет foundation-настройки.`]}),`
|
|
386
|
+
`]}),`
|
|
387
|
+
`,(0,c.jsxs)(t.p,{children:[`Пример с `,(0,c.jsx)(t.code,{children:`accessorFn`}),`:`]}),`
|
|
388
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type Person = {
|
|
389
|
+
firstName: string;
|
|
390
|
+
lastName: string;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const fullNameColumn: TableColumnDef<Person, string> = {
|
|
394
|
+
id: "fullName",
|
|
395
|
+
accessorFn: (row) => \`\${row.lastName} \${row.firstName}\`,
|
|
396
|
+
header: "ФИО"
|
|
397
|
+
};
|
|
398
|
+
`})}),`
|
|
399
|
+
`,(0,c.jsxs)(t.p,{children:[`Для `,(0,c.jsx)(t.code,{children:`accessorFn`}),` задавайте явный `,(0,c.jsx)(t.code,{children:`id`}),`. Иначе OData adapter не сможет устойчиво сопоставить колонку с metadata.`]}),`
|
|
400
|
+
`,(0,c.jsx)(t.h3,{id:`tablecolumnmeta`,children:(0,c.jsx)(t.code,{children:`TableColumnMeta`})}),`
|
|
401
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`interface TableColumnMeta {
|
|
402
|
+
width?: number | "auto" | \`\${number}%\`;
|
|
403
|
+
align?: "left" | "center" | "right";
|
|
404
|
+
mergeDuplicates?: boolean;
|
|
405
|
+
formatting?: TableColumnFormattingMeta;
|
|
406
|
+
}
|
|
407
|
+
`})}),`
|
|
408
|
+
`,(0,c.jsx)(t.h4,{id:`width`,children:(0,c.jsx)(t.code,{children:`width`})}),`
|
|
409
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Значение`}),(0,c.jsx)(t.th,{children:`Смысл`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`12`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`12em`}),` в декларативной metadata колонки`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"auto"`})}),(0,c.jsx)(t.td,{children:`автоматическая CSS-ширина`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"25%"`})}),(0,c.jsx)(t.td,{children:`процентная CSS-ширина`})]})]})]}),`
|
|
410
|
+
`,(0,c.jsxs)(t.p,{children:[`Это не то же самое, что `,(0,c.jsx)(t.code,{children:`TableColumnSizingState`}),`: sizing state хранит число пикселей, полученное от resize runtime.`]}),`
|
|
411
|
+
`,(0,c.jsx)(t.h4,{id:`align`,children:(0,c.jsx)(t.code,{children:`align`})}),`
|
|
412
|
+
`,(0,c.jsx)(t.p,{children:`Допустимые значения:`}),`
|
|
413
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
414
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`left`}),` — обычно текст и dimensions;`]}),`
|
|
415
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`center`}),` — компактный статус или управляющий элемент;`]}),`
|
|
416
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`right`}),` — обычно числа и measures.`]}),`
|
|
417
|
+
`]}),`
|
|
418
|
+
`,(0,c.jsx)(t.p,{children:`Metadata сама не рисует CSS. Визуальный Table primitive должен прочитать значение и применить его.`}),`
|
|
419
|
+
`,(0,c.jsx)(t.h4,{id:`mergeduplicates`,children:(0,c.jsx)(t.code,{children:`mergeDuplicates`})}),`
|
|
420
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`true`}),` включает display-only объединение подряд идущих одинаковых raw-значений в UI, который поддерживает этот контракт.`]}),`
|
|
421
|
+
`,(0,c.jsx)(t.p,{children:`Важно:`}),`
|
|
422
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
423
|
+
`,(0,c.jsx)(t.li,{children:`данные строк не объединяются;`}),`
|
|
424
|
+
`,(0,c.jsx)(t.li,{children:`строки не удаляются;`}),`
|
|
425
|
+
`,(0,c.jsxs)(t.li,{children:[`сравнивается raw-результат accessor или `,(0,c.jsx)(t.code,{children:`cell.getValue()`}),`;`]}),`
|
|
426
|
+
`,(0,c.jsx)(t.li,{children:`одинаковый отформатированный текст не гарантирует merge;`}),`
|
|
427
|
+
`,(0,c.jsx)(t.li,{children:`несоседние одинаковые значения не образуют одну группу;`}),`
|
|
428
|
+
`,(0,c.jsx)(t.li,{children:`tree UI может ограничивать merge границами глубины или ветки.`}),`
|
|
429
|
+
`]}),`
|
|
430
|
+
`,(0,c.jsx)(t.h4,{id:`formatting`,children:(0,c.jsx)(t.code,{children:`formatting`})}),`
|
|
431
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`formatting`}),` передаёт декларативный runtime-контекст модулю форматтеров. Поле экспериментальное: не стройте вокруг его внутренней формы собственный стабильный storage contract без версионирования.`]}),`
|
|
432
|
+
`,(0,c.jsx)(t.p,{children:`Пример:`}),`
|
|
433
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const amountColumn: TableColumnDef<{ amount: number }> = {
|
|
434
|
+
id: "amount",
|
|
435
|
+
accessorKey: "amount",
|
|
436
|
+
header: "Сумма",
|
|
437
|
+
meta: {
|
|
438
|
+
align: "right",
|
|
439
|
+
formatting: {
|
|
440
|
+
role: "measure",
|
|
441
|
+
type: "decimal",
|
|
442
|
+
emptyWhenZero: true,
|
|
443
|
+
overflowTooltip: true
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
`})}),`
|
|
448
|
+
`,(0,c.jsxs)(t.p,{children:[`Полный смысл pipeline, formulas, `,(0,c.jsx)(t.code,{children:`emptyWhenZero`}),` и value state описан в справочнике `,(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/formatters`}),`.`]}),`
|
|
449
|
+
`,(0,c.jsx)(t.h3,{id:`helpers-чтения-metadata`,children:`Helpers чтения metadata`}),`
|
|
450
|
+
`,(0,c.jsx)(t.h4,{id:`gettablecolumnmeta`,children:(0,c.jsx)(t.code,{children:`getTableColumnMeta`})}),`
|
|
451
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const meta = getTableColumnMeta(column);
|
|
452
|
+
`})}),`
|
|
453
|
+
`,(0,c.jsxs)(t.p,{children:[`Возвращает `,(0,c.jsx)(t.code,{children:`column.meta`}),` или `,(0,c.jsx)(t.code,{children:`undefined`}),`. Helper полезен общему UI-коду: consumer не привязывается к внутреннему пересечению TanStack-типа.`]}),`
|
|
454
|
+
`,(0,c.jsx)(t.h4,{id:`gettablecolumnformattingmeta`,children:(0,c.jsx)(t.code,{children:`getTableColumnFormattingMeta`})}),`
|
|
455
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const formatting = getTableColumnFormattingMeta(column);
|
|
456
|
+
`})}),`
|
|
457
|
+
`,(0,c.jsxs)(t.p,{children:[`Возвращает `,(0,c.jsx)(t.code,{children:`column.meta?.formatting`}),` или `,(0,c.jsx)(t.code,{children:`undefined`}),`.`]}),`
|
|
458
|
+
`,(0,c.jsx)(t.p,{children:`Функции ничего не клонируют. Возвращаемый объект — та же ссылка, которая находится в column definition. Не мутируйте её.`}),`
|
|
459
|
+
`,(0,c.jsx)(t.h2,{id:`foundation-features-tanstack-table`,children:`Foundation features TanStack Table`}),`
|
|
460
|
+
`,(0,c.jsx)(t.h3,{id:`зачем-нужен-единый-набор`,children:`Зачем нужен единый набор`}),`
|
|
461
|
+
`,(0,c.jsx)(t.p,{children:`TanStack Table v9 кодирует enabled features в generic-типах. Если UI создаёт table с одним набором features, а тип колонки объявлен с другим, TypeScript может показать несовместимые типы.`}),`
|
|
462
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`foundationTableFeatures`}),` задаёт единый набор для lib- и UI-пакетов.`]}),`
|
|
463
|
+
`,(0,c.jsx)(t.p,{children:`В набор входят:`}),`
|
|
464
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
465
|
+
`,(0,c.jsx)(t.li,{children:`column ordering;`}),`
|
|
466
|
+
`,(0,c.jsx)(t.li,{children:`column pinning;`}),`
|
|
467
|
+
`,(0,c.jsx)(t.li,{children:`column sizing;`}),`
|
|
468
|
+
`,(0,c.jsx)(t.li,{children:`column resizing;`}),`
|
|
469
|
+
`,(0,c.jsx)(t.li,{children:`column visibility;`}),`
|
|
470
|
+
`,(0,c.jsx)(t.li,{children:`row selection;`}),`
|
|
471
|
+
`,(0,c.jsx)(t.li,{children:`row expanding;`}),`
|
|
472
|
+
`,(0,c.jsx)(t.li,{children:`expanded row model.`}),`
|
|
473
|
+
`]}),`
|
|
474
|
+
`,(0,c.jsx)(t.h3,{id:`минимальная-ручная-интеграция`,children:`Минимальная ручная интеграция`}),`
|
|
475
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { useMemo } from "react";
|
|
476
|
+
|
|
477
|
+
import { foundationTableFeatures, type TableColumnDef } from "@ryuzaki13/react-foundation-lib/table";
|
|
478
|
+
import { useTable } from "@tanstack/react-table";
|
|
479
|
+
|
|
480
|
+
type Row = {
|
|
481
|
+
id: string;
|
|
482
|
+
name: string;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
function Example({ data }: { data: readonly Row[] }) {
|
|
486
|
+
const columns = useMemo<TableColumnDef<Row>[]>(
|
|
487
|
+
() => [
|
|
488
|
+
{
|
|
489
|
+
id: "name",
|
|
490
|
+
accessorKey: "name",
|
|
491
|
+
header: "Название"
|
|
492
|
+
}
|
|
493
|
+
],
|
|
494
|
+
[]
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
const table = useTable({
|
|
498
|
+
features: foundationTableFeatures,
|
|
499
|
+
data: data as Row[],
|
|
500
|
+
columns,
|
|
501
|
+
getRowId: (row) => row.id
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
return <div>Строк: {table.getRowModel().rows.length}</div>;
|
|
505
|
+
}
|
|
506
|
+
`})}),`
|
|
507
|
+
`,(0,c.jsxs)(t.p,{children:[`Пример показывает создание instance, а не полноценный доступный UI. Для рендера нужны `,(0,c.jsx)(t.code,{children:`<table>`}),`, headers, cells, keyboard behavior, loading/error states и стили.`]}),`
|
|
508
|
+
`,(0,c.jsx)(t.h3,{id:`публичные-типы-instance`,children:`Публичные типы instance`}),`
|
|
509
|
+
`,(0,c.jsx)(t.p,{children:`Используйте foundation-типы там, где helper или компонент принимает объекты TanStack Table:`}),`
|
|
510
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import type {
|
|
511
|
+
FoundationTableCell,
|
|
512
|
+
FoundationTableColumn,
|
|
513
|
+
FoundationTableHeader,
|
|
514
|
+
FoundationTableInstance,
|
|
515
|
+
FoundationTableRow,
|
|
516
|
+
FoundationTableState
|
|
517
|
+
} from "@ryuzaki13/react-foundation-lib/table";
|
|
518
|
+
`})}),`
|
|
519
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Тип`}),(0,c.jsx)(t.th,{children:`Что представляет`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableInstance<TData>`})}),(0,c.jsx)(t.td,{children:`весь table instance`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableRow<TData>`})}),(0,c.jsx)(t.td,{children:`вычисленную строку TanStack`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableColumn<TData, TValue>`})}),(0,c.jsx)(t.td,{children:`runtime-колонку`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableCell<TData, TValue>`})}),(0,c.jsx)(t.td,{children:`runtime-ячейку`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableHeader<TData, TValue>`})}),(0,c.jsx)(t.td,{children:`runtime-заголовок`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableState`})}),(0,c.jsx)(t.td,{children:`полное состояние enabled features`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableFeatures`})}),(0,c.jsxs)(t.td,{children:[`тип объекта `,(0,c.jsx)(t.code,{children:`foundationTableFeatures`})]})]})]})]}),`
|
|
520
|
+
`,(0,c.jsx)(t.p,{children:`Не создавайте эти объекты вручную. Их создаёт TanStack Table.`}),`
|
|
521
|
+
`,(0,c.jsx)(t.h2,{id:`порядок-и-layout-колонок`,children:`Порядок и layout колонок`}),`
|
|
522
|
+
`,(0,c.jsx)(t.h3,{id:`normalizetablecolumnorder`,children:(0,c.jsx)(t.code,{children:`normalizeTableColumnOrder`})}),`
|
|
523
|
+
`,(0,c.jsx)(t.p,{children:`Очищает массив идентификаторов:`}),`
|
|
524
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { normalizeTableColumnOrder } from "@ryuzaki13/react-foundation-lib/table";
|
|
525
|
+
|
|
526
|
+
normalizeTableColumnOrder(["name", "", "amount", "name"]);
|
|
527
|
+
// ["name", "amount"]
|
|
528
|
+
`})}),`
|
|
529
|
+
`,(0,c.jsx)(t.p,{children:`Правила:`}),`
|
|
530
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
531
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`undefined`}),` превращается в `,(0,c.jsx)(t.code,{children:`[]`}),`;`]}),`
|
|
532
|
+
`,(0,c.jsxs)(t.li,{children:[`строки, у которых `,(0,c.jsx)(t.code,{children:`trim()`}),` пуст, удаляются;`]}),`
|
|
533
|
+
`,(0,c.jsx)(t.li,{children:`повторения удаляются;`}),`
|
|
534
|
+
`,(0,c.jsx)(t.li,{children:`сохраняется первое вхождение;`}),`
|
|
535
|
+
`,(0,c.jsx)(t.li,{children:`непустая строка возвращается как есть и не обрезается.`}),`
|
|
536
|
+
`]}),`
|
|
537
|
+
`,(0,c.jsx)(t.p,{children:`Последний пункт важен:`}),`
|
|
538
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`normalizeTableColumnOrder([" name "]);
|
|
539
|
+
// [" name "], а не ["name"]
|
|
540
|
+
`})}),`
|
|
541
|
+
`,(0,c.jsxs)(t.p,{children:[`Helper проверяет пустоту, но не исправляет идентификаторы. Используйте канонические `,(0,c.jsx)(t.code,{children:`columnId`}),` ещё при создании колонок.`]}),`
|
|
542
|
+
`,(0,c.jsx)(t.h3,{id:`resolvetablecolumnorder`,children:(0,c.jsx)(t.code,{children:`resolveTableColumnOrder`})}),`
|
|
543
|
+
`,(0,c.jsx)(t.p,{children:`Сопоставляет сохранённый порядок с актуальными колонками:`}),`
|
|
544
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { resolveTableColumnOrder } from "@ryuzaki13/react-foundation-lib/table";
|
|
545
|
+
|
|
546
|
+
resolveTableColumnOrder({
|
|
547
|
+
ids: ["name", "status", "amount", "createdAt"],
|
|
548
|
+
order: ["amount", "name", "removed", "amount"]
|
|
549
|
+
});
|
|
550
|
+
// ["amount", "name", "status", "createdAt"]
|
|
551
|
+
`})}),`
|
|
552
|
+
`,(0,c.jsx)(t.p,{children:`Алгоритм:`}),`
|
|
553
|
+
`,(0,c.jsxs)(t.ol,{children:[`
|
|
554
|
+
`,(0,c.jsxs)(t.li,{children:[`Берёт только значения из `,(0,c.jsx)(t.code,{children:`order`}),`, существующие в `,(0,c.jsx)(t.code,{children:`ids`}),`.`]}),`
|
|
555
|
+
`,(0,c.jsx)(t.li,{children:`Удаляет их повторения.`}),`
|
|
556
|
+
`,(0,c.jsx)(t.li,{children:`Сохраняет их относительный порядок.`}),`
|
|
557
|
+
`,(0,c.jsxs)(t.li,{children:[`Добавляет отсутствующие актуальные `,(0,c.jsx)(t.code,{children:`ids`}),` в конец в порядке `,(0,c.jsx)(t.code,{children:`ids`}),`.`]}),`
|
|
558
|
+
`]}),`
|
|
559
|
+
`,(0,c.jsx)(t.p,{children:`Это основной helper восстановления сохранённого layout после добавления или удаления колонок в новой версии приложения.`}),`
|
|
560
|
+
`,(0,c.jsxs)(t.h3,{id:`отличие-normalize-от-resolve`,children:[`Отличие `,(0,c.jsx)(t.code,{children:`normalize`}),` от `,(0,c.jsx)(t.code,{children:`resolve`})]}),`
|
|
561
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`normalizeTableColumnOrder
|
|
562
|
+
знает только сам массив order
|
|
563
|
+
не знает существующие колонки
|
|
564
|
+
|
|
565
|
+
resolveTableColumnOrder
|
|
566
|
+
знает актуальные ids
|
|
567
|
+
удаляет неизвестные и добавляет новые
|
|
568
|
+
`})}),`
|
|
569
|
+
`,(0,c.jsxs)(t.p,{children:[`Для persisted configuration обычно нужен `,(0,c.jsx)(t.code,{children:`resolveTableColumnOrder`}),`.`]}),`
|
|
570
|
+
`,(0,c.jsx)(t.h3,{id:`buildtablecolumnlayout`,children:(0,c.jsx)(t.code,{children:`buildTableColumnLayout`})}),`
|
|
571
|
+
`,(0,c.jsx)(t.p,{children:`Соединяет уже вычисленный порядок с шириной каждой колонки:`}),`
|
|
572
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { buildTableColumnLayout } from "@ryuzaki13/react-foundation-lib/table";
|
|
573
|
+
|
|
574
|
+
const widths = {
|
|
575
|
+
name: 240,
|
|
576
|
+
status: 120,
|
|
577
|
+
amount: 160
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const layout = buildTableColumnLayout({
|
|
581
|
+
ids: ["amount", "name", "status"],
|
|
582
|
+
getWidth: (id) => widths[id]
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// [
|
|
586
|
+
// { id: "amount", width: 160 },
|
|
587
|
+
// { id: "name", width: 240 },
|
|
588
|
+
// { id: "status", width: 120 }
|
|
589
|
+
// ]
|
|
590
|
+
`})}),`
|
|
591
|
+
`,(0,c.jsxs)(t.p,{children:[`Ширина вычисляется по `,(0,c.jsx)(t.code,{children:`id`}),`, а не по визуальному индексу. Поэтому после reorder ширина остаётся у правильной колонки.`]}),`
|
|
592
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`TWidth`}),` generic и может быть не только числом:`]}),`
|
|
593
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const layout = buildTableColumnLayout({
|
|
594
|
+
ids: ["name", "amount"] as const,
|
|
595
|
+
getWidth: (id) => (id === "name" ? "2fr" : "10rem")
|
|
596
|
+
});
|
|
597
|
+
`})}),`
|
|
598
|
+
`,(0,c.jsxs)(t.p,{children:[`Функция вызывает `,(0,c.jsx)(t.code,{children:`getWidth`}),` один раз для каждого `,(0,c.jsx)(t.code,{children:`id`}),` и не проверяет результат.`]}),`
|
|
599
|
+
`,(0,c.jsx)(t.h2,{id:`drag-and-drop-перестановка-колонок`,children:`Drag-and-drop перестановка колонок`}),`
|
|
600
|
+
`,(0,c.jsx)(t.h3,{id:`термины`,children:`Термины`}),`
|
|
601
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
602
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`activeId`}),` — колонка, которую пользователь тащит;`]}),`
|
|
603
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`overId`}),` — колонка, над которой находится dragged item;`]}),`
|
|
604
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`headerIds`}),` — только draggable top-level headers;`]}),`
|
|
605
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`lockedIds`}),` — полностью заблокированные колонки;`]}),`
|
|
606
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`pinnedIds`}),` — закреплённые колонки;`]}),`
|
|
607
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`order`}),` — текущий полный порядок.`]}),`
|
|
608
|
+
`]}),`
|
|
609
|
+
`,(0,c.jsx)(t.h3,{id:`reordertableheadercolumns`,children:(0,c.jsx)(t.code,{children:`reorderTableHeaderColumns`})}),`
|
|
610
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { reorderTableHeaderColumns } from "@ryuzaki13/react-foundation-lib/table";
|
|
611
|
+
|
|
612
|
+
const nextOrder = reorderTableHeaderColumns({
|
|
613
|
+
order: ["select", "name", "status", "amount"],
|
|
614
|
+
headerIds: ["select", "name", "status", "amount"],
|
|
615
|
+
lockedIds: ["select"],
|
|
616
|
+
pinnedIds: ["select"],
|
|
617
|
+
activeId: "amount",
|
|
618
|
+
overId: "name"
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// ["select", "amount", "name", "status"]
|
|
622
|
+
`})}),`
|
|
623
|
+
`,(0,c.jsxs)(t.p,{children:[`Функция возвращает новый массив и не мутирует `,(0,c.jsx)(t.code,{children:`order`}),`.`]}),`
|
|
624
|
+
`,(0,c.jsx)(t.p,{children:`Перестановка блокируется, если:`}),`
|
|
625
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
626
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`activeId`}),` или `,(0,c.jsx)(t.code,{children:`overId`}),` пуст;`]}),`
|
|
627
|
+
`,(0,c.jsx)(t.li,{children:`идентификаторы одинаковы;`}),`
|
|
628
|
+
`,(0,c.jsxs)(t.li,{children:[`хотя бы одного id нет в `,(0,c.jsx)(t.code,{children:`headerIds`}),`;`]}),`
|
|
629
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`activeId`}),` или `,(0,c.jsx)(t.code,{children:`overId`}),` входит в `,(0,c.jsx)(t.code,{children:`lockedIds`}),`;`]}),`
|
|
630
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`activeId`}),` или `,(0,c.jsx)(t.code,{children:`overId`}),` входит в `,(0,c.jsx)(t.code,{children:`pinnedIds`}),`.`]}),`
|
|
631
|
+
`]}),`
|
|
632
|
+
`,(0,c.jsx)(t.p,{children:`Текущее правило pinning строгое: pinned-колонки не переставляются даже внутри pinned-зоны, и обычную колонку нельзя drop-нуть на pinned-колонку.`}),`
|
|
633
|
+
`,(0,c.jsx)(t.h3,{id:`grouped-headers`,children:`Grouped headers`}),`
|
|
634
|
+
`,(0,c.jsxs)(t.p,{children:[`Для grouped columns в `,(0,c.jsx)(t.code,{children:`headerIds`}),` передавайте идентификаторы верхнего уровня:`]}),`
|
|
635
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`CONTACTS AMOUNT
|
|
636
|
+
├─ PHONE
|
|
637
|
+
└─ EMAIL
|
|
638
|
+
`})}),`
|
|
639
|
+
`,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`headerIds`}),` равен `,(0,c.jsx)(t.code,{children:`['CONTACTS', 'AMOUNT']`}),`, группа `,(0,c.jsx)(t.code,{children:`CONTACTS`}),` двигается целиком. `,(0,c.jsx)(t.code,{children:`PHONE`}),` и `,(0,c.jsx)(t.code,{children:`EMAIL`}),` нельзя переставить этим helper отдельно.`]}),`
|
|
640
|
+
`,(0,c.jsxs)(t.h3,{id:`отсутствующий-id-в-order`,children:[`Отсутствующий id в `,(0,c.jsx)(t.code,{children:`order`})]}),`
|
|
641
|
+
`,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`activeId`}),` или `,(0,c.jsx)(t.code,{children:`overId`}),` разрешён через `,(0,c.jsx)(t.code,{children:`headerIds`}),`, но отсутствует в `,(0,c.jsx)(t.code,{children:`order`}),`, helper сначала добавит его в конец и затем выполнит перемещение.`]}),`
|
|
642
|
+
`,(0,c.jsxs)(t.p,{children:[`Обычно перед DnD лучше сначала получить полный порядок через `,(0,c.jsx)(t.code,{children:`resolveTableColumnOrder`}),`.`]}),`
|
|
643
|
+
`,(0,c.jsx)(t.h3,{id:`resolvereorderedtableheadercolumns`,children:(0,c.jsx)(t.code,{children:`resolveReorderedTableHeaderColumns`})}),`
|
|
644
|
+
`,(0,c.jsx)(t.p,{children:`Возвращает:`}),`
|
|
645
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
646
|
+
`,(0,c.jsxs)(t.li,{children:[`новый `,(0,c.jsx)(t.code,{children:`string[]`}),`, если порядок изменился;`]}),`
|
|
647
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`null`}),`, если реального изменения нет.`]}),`
|
|
648
|
+
`]}),`
|
|
649
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const change = resolveReorderedTableHeaderColumns({
|
|
650
|
+
order: ["name", "amount"],
|
|
651
|
+
headerIds: ["name", "amount"],
|
|
652
|
+
activeId: "name",
|
|
653
|
+
overId: "name"
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
console.log(change); // null
|
|
657
|
+
`})}),`
|
|
658
|
+
`,(0,c.jsx)(t.p,{children:`Этот вариант удобен для event handler:`}),`
|
|
659
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const nextOrder = resolveReorderedTableHeaderColumns(args);
|
|
660
|
+
|
|
661
|
+
if (nextOrder) {
|
|
662
|
+
onColumnOrderChange(nextOrder);
|
|
663
|
+
}
|
|
664
|
+
`})}),`
|
|
665
|
+
`,(0,c.jsx)(t.h2,{id:`ширины-колонок`,children:`Ширины колонок`}),`
|
|
666
|
+
`,(0,c.jsx)(t.h3,{id:`два-разных-представления-ширины`,children:`Два разных представления ширины`}),`
|
|
667
|
+
`,(0,c.jsx)(t.p,{children:`Не смешивайте:`}),`
|
|
668
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Представление`}),(0,c.jsx)(t.th,{children:`Пример`}),(0,c.jsx)(t.th,{children:`Единица`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TableColumnMeta.width`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`12`}),`, `,(0,c.jsx)(t.code,{children:`"auto"`}),`, `,(0,c.jsx)(t.code,{children:`"25%"`})]}),(0,c.jsxs)(t.td,{children:[`число означает `,(0,c.jsx)(t.code,{children:`em`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TableColumnSizingState`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`{ amount: 160 }`})}),(0,c.jsx)(t.td,{children:`число означает px`})]})]})]}),`
|
|
669
|
+
`,(0,c.jsx)(t.p,{children:`Helpers этого раздела работают со вторым представлением — px sizing state.`}),`
|
|
670
|
+
`,(0,c.jsx)(t.h3,{id:`default_table_column_min_width`,children:(0,c.jsx)(t.code,{children:`DEFAULT_TABLE_COLUMN_MIN_WIDTH`})}),`
|
|
671
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`DEFAULT_TABLE_COLUMN_MIN_WIDTH === 60;
|
|
672
|
+
`})}),`
|
|
673
|
+
`,(0,c.jsx)(t.p,{children:`Значение используется по умолчанию как минимальная ширина в пикселях.`}),`
|
|
674
|
+
`,(0,c.jsx)(t.h3,{id:`normalizetablecolumnwidth`,children:(0,c.jsx)(t.code,{children:`normalizeTableColumnWidth`})}),`
|
|
675
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`normalizeTableColumnWidth(125.8); // 125
|
|
676
|
+
normalizeTableColumnWidth(12); // 60
|
|
677
|
+
normalizeTableColumnWidth(Number.NaN, 80); // 80
|
|
678
|
+
`})}),`
|
|
679
|
+
`,(0,c.jsx)(t.p,{children:`Алгоритм:`}),`
|
|
680
|
+
`,(0,c.jsxs)(t.ol,{children:[`
|
|
681
|
+
`,(0,c.jsxs)(t.li,{children:[`Проверяет `,(0,c.jsx)(t.code,{children:`minWidth`}),`.`]}),`
|
|
682
|
+
`,(0,c.jsxs)(t.li,{children:[`Если minimum не finite или не больше нуля, использует `,(0,c.jsx)(t.code,{children:`60`}),`.`]}),`
|
|
683
|
+
`,(0,c.jsxs)(t.li,{children:[`Округляет вниз через `,(0,c.jsx)(t.code,{children:`Math.floor`}),`.`]}),`
|
|
684
|
+
`,(0,c.jsx)(t.li,{children:`Невалидную ширину заменяет minimum.`}),`
|
|
685
|
+
`,(0,c.jsx)(t.li,{children:`Возвращает максимум из minimum и ширины.`}),`
|
|
686
|
+
`]}),`
|
|
687
|
+
`,(0,c.jsx)(t.p,{children:`Примеры minimum:`}),`
|
|
688
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`normalizeTableColumnWidth(140.9, 100.9); // 140, minimum станет 100
|
|
689
|
+
normalizeTableColumnWidth(10, -1); // 60
|
|
690
|
+
normalizeTableColumnWidth(10, Number.POSITIVE_INFINITY); // 60
|
|
691
|
+
`})}),`
|
|
692
|
+
`,(0,c.jsx)(t.h3,{id:`normalizetablecolumnsizing`,children:(0,c.jsx)(t.code,{children:`normalizeTableColumnSizing`})}),`
|
|
693
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`normalizeTableColumnSizing({
|
|
694
|
+
name: 240.7,
|
|
695
|
+
amount: 10,
|
|
696
|
+
broken: Number.NaN,
|
|
697
|
+
"": 90
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// {
|
|
701
|
+
// name: 240,
|
|
702
|
+
// amount: 60
|
|
703
|
+
// }
|
|
704
|
+
`})}),`
|
|
705
|
+
`,(0,c.jsx)(t.p,{children:`Функция:`}),`
|
|
706
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
707
|
+
`,(0,c.jsxs)(t.li,{children:[`принимает `,(0,c.jsx)(t.code,{children:`undefined`}),` и возвращает `,(0,c.jsx)(t.code,{children:`{}`}),`;`]}),`
|
|
708
|
+
`,(0,c.jsxs)(t.li,{children:[`удаляет пустой `,(0,c.jsx)(t.code,{children:`columnId`}),`;`]}),`
|
|
709
|
+
`,(0,c.jsx)(t.li,{children:`удаляет не-finite width;`}),`
|
|
710
|
+
`,(0,c.jsx)(t.li,{children:`нормализует каждую оставшуюся ширину;`}),`
|
|
711
|
+
`,(0,c.jsx)(t.li,{children:`возвращает новый объект.`}),`
|
|
712
|
+
`]}),`
|
|
713
|
+
`,(0,c.jsxs)(t.p,{children:[`Непустой `,(0,c.jsx)(t.code,{children:`columnId`}),` не обрезается. Ключ `,(0,c.jsx)(t.code,{children:`" name "`}),` останется `,(0,c.jsx)(t.code,{children:`" name "`}),`.`]}),`
|
|
714
|
+
`,(0,c.jsx)(t.h3,{id:`patchtablecolumnwidth`,children:(0,c.jsx)(t.code,{children:`patchTableColumnWidth`})}),`
|
|
715
|
+
`,(0,c.jsx)(t.p,{children:`Добавляет или заменяет одну ширину, попутно нормализуя весь state:`}),`
|
|
716
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const next = patchTableColumnWidth({ name: 200, broken: Number.NaN }, "amount", 150.8);
|
|
717
|
+
|
|
718
|
+
// { name: 200, amount: 150 }
|
|
719
|
+
`})}),`
|
|
720
|
+
`,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`columnId.trim()`}),` пуст, новая запись не добавляется, но существующий state всё равно нормализуется.`]}),`
|
|
721
|
+
`,(0,c.jsx)(t.h3,{id:`removetablecolumnwidth`,children:(0,c.jsx)(t.code,{children:`removeTableColumnWidth`})}),`
|
|
722
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`removeTableColumnWidth({ name: 200, amount: 150 }, "name");
|
|
723
|
+
// { amount: 150 }
|
|
724
|
+
`})}),`
|
|
725
|
+
`,(0,c.jsxs)(t.p,{children:[`Функция только копирует все ключи, кроме точного `,(0,c.jsx)(t.code,{children:`columnId`}),`. Она не нормализует оставшиеся значения.`]}),`
|
|
726
|
+
`,(0,c.jsx)(t.h2,{id:`видимость-колонок`,children:`Видимость колонок`}),`
|
|
727
|
+
`,(0,c.jsx)(t.h3,{id:`tablecolumnvisibilitystate`,children:(0,c.jsx)(t.code,{children:`TableColumnVisibilityState`})}),`
|
|
728
|
+
`,(0,c.jsx)(t.p,{children:`Это публичное имя состояния TanStack Table v9:`}),`
|
|
729
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TableColumnVisibilityState = Record<string, boolean>;
|
|
730
|
+
`})}),`
|
|
731
|
+
`,(0,c.jsxs)(t.p,{children:[`Ключ — стабильный `,(0,c.jsx)(t.code,{children:`columnId`}),`, значение — видимость:`]}),`
|
|
732
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const visibility: TableColumnVisibilityState = {
|
|
733
|
+
name: true,
|
|
734
|
+
technicalCode: false,
|
|
735
|
+
amount: true
|
|
736
|
+
};
|
|
737
|
+
`})}),`
|
|
738
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`false`}),` скрывает колонку, но не удаляет её definition и данные строки. Поэтому колонку можно снова показать без повторного создания `,(0,c.jsx)(t.code,{children:`columns`}),`.`]}),`
|
|
739
|
+
`,(0,c.jsxs)(t.p,{children:[`Не фильтруйте массив column definitions вручную, если UI поддерживает controlled visibility state: удалённая definition перестаёт участвовать и в настройках, и в поиске по `,(0,c.jsx)(t.code,{children:`columnId`}),`.`]}),`
|
|
740
|
+
`,(0,c.jsxs)(t.p,{children:[`Модуль не содержит общего toggle-helper для visibility. Передавайте state TanStack Table или visual primitive. Стартовую карту по OData metadata создаёт `,(0,c.jsx)(t.code,{children:`createTableColumnVisibilityFromODataMetadata`}),`.`]}),`
|
|
741
|
+
`,(0,c.jsx)(t.h2,{id:`выбор-строк`,children:`Выбор строк`}),`
|
|
742
|
+
`,(0,c.jsx)(t.h3,{id:`tableselectionmode`,children:(0,c.jsx)(t.code,{children:`TableSelectionMode`})}),`
|
|
743
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TableSelectionMode = "none" | "single" | "multi";
|
|
744
|
+
`})}),`
|
|
745
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Режим`}),(0,c.jsx)(t.th,{children:`Поведение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`none`})}),(0,c.jsx)(t.td,{children:`выбор запрещён, state очищается`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`single`})}),(0,c.jsx)(t.td,{children:`может быть выбрана ровно одна строка`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`multi`})}),(0,c.jsx)(t.td,{children:`строки независимо включаются и выключаются`})]})]})]}),`
|
|
746
|
+
`,(0,c.jsx)(t.h3,{id:`rowselectionstate`,children:(0,c.jsx)(t.code,{children:`RowSelectionState`})}),`
|
|
747
|
+
`,(0,c.jsx)(t.p,{children:`Тип принадлежит TanStack Table:`}),`
|
|
748
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import type { RowSelectionState } from "@tanstack/react-table";
|
|
749
|
+
|
|
750
|
+
const selection: RowSelectionState = {
|
|
751
|
+
"row:1": true,
|
|
752
|
+
"row:2": true
|
|
753
|
+
};
|
|
754
|
+
`})}),`
|
|
755
|
+
`,(0,c.jsxs)(t.p,{children:[`В TanStack Table v9 typed state хранит только активные ключи со значением `,(0,c.jsx)(t.code,{children:`true`}),`. Для снятого выбора ключ нужно удалить, а не записывать `,(0,c.jsx)(t.code,{children:`false`}),`.`]}),`
|
|
756
|
+
`,(0,c.jsx)(t.p,{children:`Pure helpers дополнительно проверяют truthiness и поэтому защитно игнорируют false-ключ, если он всё же пришёл из legacy JSON или нетипизированного JavaScript. Но новый TypeScript-код не должен создавать такой shape.`}),`
|
|
757
|
+
`,(0,c.jsx)(t.h3,{id:`normalizetableselectionmode`,children:(0,c.jsx)(t.code,{children:`normalizeTableSelectionMode`})}),`
|
|
758
|
+
`,(0,c.jsx)(t.p,{children:`Нормализует недоверенное значение на config-boundary:`}),`
|
|
759
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`normalizeTableSelectionMode("multi"); // "multi"
|
|
760
|
+
normalizeTableSelectionMode("unknown"); // "none"
|
|
761
|
+
normalizeTableSelectionMode(null, "single"); // "single"
|
|
762
|
+
`})}),`
|
|
763
|
+
`,(0,c.jsxs)(t.p,{children:[`Fallback по умолчанию — `,(0,c.jsx)(t.code,{children:`none`}),`.`]}),`
|
|
764
|
+
`,(0,c.jsx)(t.p,{children:`Используйте helper при чтении JSON, storage или URL config. Внутренний typed state уже должен содержать корректный union.`}),`
|
|
765
|
+
`,(0,c.jsx)(t.h3,{id:`aretablerowselectionsequal`,children:(0,c.jsx)(t.code,{children:`areTableRowSelectionsEqual`})}),`
|
|
766
|
+
`,(0,c.jsxs)(t.p,{children:[`Сравнивает наборы активных `,(0,c.jsx)(t.code,{children:`rowId`}),` без учёта порядка ключей:`]}),`
|
|
767
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`areTableRowSelectionsEqual({ a: true, b: true }, { b: true, a: true }); // true
|
|
768
|
+
`})}),`
|
|
769
|
+
`,(0,c.jsx)(t.p,{children:`Функция не сравнивает ссылки объектов. Если нетипизированный runtime object содержит false-ключи, они не участвуют в сравнении.`}),`
|
|
770
|
+
`,(0,c.jsx)(t.h3,{id:`prunetablerowselection`,children:(0,c.jsx)(t.code,{children:`pruneTableRowSelection`})}),`
|
|
771
|
+
`,(0,c.jsx)(t.p,{children:`Оставляет только выбор, допустимый для текущего набора данных и режима:`}),`
|
|
772
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const next = pruneTableRowSelection({ a: true, b: true, removed: true }, ["a", "b"], "single");
|
|
773
|
+
|
|
774
|
+
// { b: true }
|
|
775
|
+
`})}),`
|
|
776
|
+
`,(0,c.jsx)(t.p,{children:`Правила:`}),`
|
|
777
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
778
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`none`}),` всегда возвращает `,(0,c.jsx)(t.code,{children:`{}`}),`;`]}),`
|
|
779
|
+
`,(0,c.jsxs)(t.li,{children:[`отсутствующие в `,(0,c.jsx)(t.code,{children:`availableRowIds`}),` id удаляются;`]}),`
|
|
780
|
+
`,(0,c.jsx)(t.li,{children:`false-ключи из нетипизированного/legacy input удаляются;`}),`
|
|
781
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`multi`}),` сохраняет все доступные активные id;`]}),`
|
|
782
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`single`}),` сохраняет последний активный id после фильтрации.`]}),`
|
|
783
|
+
`]}),`
|
|
784
|
+
`,(0,c.jsxs)(t.p,{children:[`В `,(0,c.jsx)(t.code,{children:`single`}),` «последний» определяется порядком активных ключей `,(0,c.jsx)(t.code,{children:`Object.keys(selection)`}),`, а не порядком `,(0,c.jsx)(t.code,{children:`availableRowIds`}),`. Не используйте object key order как историю пользовательских кликов. Нормальный источник single state — `,(0,c.jsx)(t.code,{children:`toggleTableRowSelection`}),`, который сам оставляет один ключ.`]}),`
|
|
785
|
+
`,(0,c.jsx)(t.h3,{id:`toggletablerowselection`,children:(0,c.jsx)(t.code,{children:`toggleTableRowSelection`})}),`
|
|
786
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`toggleTableRowSelection({}, "a", "none");
|
|
787
|
+
// {}
|
|
788
|
+
|
|
789
|
+
toggleTableRowSelection({ a: true }, "b", "single");
|
|
790
|
+
// { b: true }
|
|
791
|
+
|
|
792
|
+
toggleTableRowSelection({ a: true }, "a", "single");
|
|
793
|
+
// та же логическая выборка { a: true }
|
|
794
|
+
|
|
795
|
+
toggleTableRowSelection({ a: true }, "b", "multi");
|
|
796
|
+
// { a: true, b: true }
|
|
797
|
+
|
|
798
|
+
toggleTableRowSelection({ a: true, b: true }, "b", "multi");
|
|
799
|
+
// { a: true }
|
|
800
|
+
`})}),`
|
|
801
|
+
`,(0,c.jsx)(t.p,{children:`Ключевое различие:`}),`
|
|
802
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
803
|
+
`,(0,c.jsxs)(t.li,{children:[`в `,(0,c.jsx)(t.code,{children:`single`}),` повторная активация выбранной строки `,(0,c.jsx)(t.strong,{children:`не снимает`}),` выбор;`]}),`
|
|
804
|
+
`,(0,c.jsxs)(t.li,{children:[`в `,(0,c.jsx)(t.code,{children:`multi`}),` повторная активация работает как toggle.`]}),`
|
|
805
|
+
`]}),`
|
|
806
|
+
`,(0,c.jsxs)(t.p,{children:[`Helper не проверяет, существует ли `,(0,c.jsx)(t.code,{children:`rowId`}),`. Если это важно, сначала проверьте membership или используйте `,(0,c.jsx)(t.code,{children:`pruneTableRowSelection`}),` на границе данных.`]}),`
|
|
807
|
+
`,(0,c.jsx)(t.h3,{id:`selection-и-пагинация`,children:`Selection и пагинация`}),`
|
|
808
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`availableRowIds`}),` должен отражать область, внутри которой selection считается допустимым.`]}),`
|
|
809
|
+
`,(0,c.jsx)(t.p,{children:`Если передать только id текущей страницы, selection с других страниц будет удалён. Это правильно для «выбор только среди загруженных строк», но неправильно для cross-page selection.`}),`
|
|
810
|
+
`,(0,c.jsx)(t.p,{children:`Для cross-page сценария нужен отдельный domain contract:`}),`
|
|
811
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
812
|
+
`,(0,c.jsx)(t.li,{children:`полный набор известных id;`}),`
|
|
813
|
+
`,(0,c.jsx)(t.li,{children:`selection-модель «все кроме исключений»;`}),`
|
|
814
|
+
`,(0,c.jsx)(t.li,{children:`server-side selection token;`}),`
|
|
815
|
+
`,(0,c.jsx)(t.li,{children:`или бизнес-store владельца feature.`}),`
|
|
816
|
+
`]}),`
|
|
817
|
+
`,(0,c.jsx)(t.p,{children:`Не пытайтесь представить миллион выбранных server rows обычным объектом TanStack Table.`}),`
|
|
818
|
+
`,(0,c.jsxs)(t.h2,{id:`hook-usetablerowselection`,children:[`Hook `,(0,c.jsx)(t.code,{children:`useTableRowSelection`})]}),`
|
|
819
|
+
`,(0,c.jsx)(t.h3,{id:`назначение`,children:`Назначение`}),`
|
|
820
|
+
`,(0,c.jsx)(t.p,{children:`Hook связывает pure selection helpers с React state:`}),`
|
|
821
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`availableRowIds + rowById + mode
|
|
822
|
+
│
|
|
823
|
+
▼
|
|
824
|
+
useTableRowSelection
|
|
825
|
+
/ │ \\
|
|
826
|
+
rowSelection setter activateRowSelection
|
|
827
|
+
│
|
|
828
|
+
▼
|
|
829
|
+
onRowSelectionChange(rows)
|
|
830
|
+
`})}),`
|
|
831
|
+
`,(0,c.jsx)(t.h3,{id:`параметры`,children:`Параметры`}),`
|
|
832
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`useTableRowSelection<TData>({
|
|
833
|
+
availableRowIds,
|
|
834
|
+
rowById,
|
|
835
|
+
selectionMode,
|
|
836
|
+
selectedRowIds,
|
|
837
|
+
onRowSelectionChange
|
|
838
|
+
});
|
|
839
|
+
`})}),`
|
|
840
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Параметр`}),(0,c.jsx)(t.th,{children:`Тип`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`availableRowIds`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`readonly string[]`})}),(0,c.jsx)(t.td,{children:`id строк, которые сейчас допустимы`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`rowById`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`Map<string, TData>`})}),(0,c.jsx)(t.td,{children:`индекс исходных объектов`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`selectionMode`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TableSelectionMode`})}),(0,c.jsx)(t.td,{children:`cardinality выбора`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`selectedRowIds`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`readonly string[]`}),` или `,(0,c.jsx)(t.code,{children:`undefined`})]}),(0,c.jsx)(t.td,{children:`внешний controlled snapshot`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onRowSelectionChange`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`(rows: TData[]) => void`})}),(0,c.jsx)(t.td,{children:`callback с исходными выбранными строками`})]})]})]}),`
|
|
841
|
+
`,(0,c.jsx)(t.p,{children:`Тип объекта параметров намеренно не экспортируется отдельным именем. Используйте сигнатуру функции, а не private deep import.`}),`
|
|
842
|
+
`,(0,c.jsx)(t.h3,{id:`результат`,children:`Результат`}),`
|
|
843
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const { rowSelection, setRowSelection, activateRowSelection } = useTableRowSelection({
|
|
844
|
+
availableRowIds,
|
|
845
|
+
rowById,
|
|
846
|
+
selectionMode
|
|
847
|
+
});
|
|
848
|
+
`})}),`
|
|
849
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Поле`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`rowSelection`})}),(0,c.jsxs)(t.td,{children:[`текущее TanStack `,(0,c.jsx)(t.code,{children:`RowSelectionState`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`setRowSelection`})}),(0,c.jsx)(t.td,{children:`React setter для низкоуровневой интеграции`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`activateRowSelection(rowId, canSelect)`})}),(0,c.jsx)(t.td,{children:`безопасная пользовательская активация строки`})]})]})]}),`
|
|
850
|
+
`,(0,c.jsx)(t.h3,{id:`полный-пример`,children:`Полный пример`}),`
|
|
851
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { useMemo } from "react";
|
|
852
|
+
|
|
853
|
+
import {
|
|
854
|
+
foundationTableFeatures,
|
|
855
|
+
useTableRowSelection,
|
|
856
|
+
type TableColumnDef,
|
|
857
|
+
type TableSelectionMode
|
|
858
|
+
} from "@ryuzaki13/react-foundation-lib/table";
|
|
859
|
+
import { useTable } from "@tanstack/react-table";
|
|
860
|
+
|
|
861
|
+
type Employee = {
|
|
862
|
+
id: string;
|
|
863
|
+
name: string;
|
|
864
|
+
disabled: boolean;
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
type Props = {
|
|
868
|
+
rows: readonly Employee[];
|
|
869
|
+
columns: TableColumnDef<Employee>[];
|
|
870
|
+
selectionMode: TableSelectionMode;
|
|
871
|
+
onSelectionChange: (rows: Employee[]) => void;
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
function EmployeeTableModel({ rows, columns, selectionMode, onSelectionChange }: Props) {
|
|
875
|
+
const rowById = useMemo(() => new Map(rows.map((row) => [row.id, row])), [rows]);
|
|
876
|
+
const availableRowIds = useMemo(() => Array.from(rowById.keys()), [rowById]);
|
|
877
|
+
|
|
878
|
+
const { rowSelection, activateRowSelection } = useTableRowSelection({
|
|
879
|
+
availableRowIds,
|
|
880
|
+
rowById,
|
|
881
|
+
selectionMode,
|
|
882
|
+
onRowSelectionChange: onSelectionChange
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
const table = useTable({
|
|
886
|
+
features: foundationTableFeatures,
|
|
887
|
+
data: rows as Employee[],
|
|
888
|
+
columns,
|
|
889
|
+
state: { rowSelection },
|
|
890
|
+
getRowId: (row) => row.id,
|
|
891
|
+
enableMultiRowSelection: selectionMode === "multi",
|
|
892
|
+
enableRowSelection: (row) => selectionMode !== "none" && !row.original.disabled
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
return {
|
|
896
|
+
table,
|
|
897
|
+
activateRow: (rowId: string) => {
|
|
898
|
+
const row = rowById.get(rowId);
|
|
899
|
+
activateRowSelection(rowId, Boolean(row && !row.disabled));
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
`})}),`
|
|
904
|
+
`,(0,c.jsx)(t.p,{children:`В реальном компоненте custom hook должен вызываться на верхнем уровне React component или другого custom hook. Не вызывайте его в условии, цикле или обработчике события.`}),`
|
|
905
|
+
`,(0,c.jsx)(t.h3,{id:`canselect`,children:(0,c.jsx)(t.code,{children:`canSelect`})}),`
|
|
906
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`activateRowSelection(rowId, false)`}),` ничего не меняет. Это позволяет держать business permission вне общего helper:`]}),`
|
|
907
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const canSelect = employee.status !== "archived";
|
|
908
|
+
|
|
909
|
+
activateRowSelection(employee.id, canSelect);
|
|
910
|
+
`})}),`
|
|
911
|
+
`,(0,c.jsx)(t.h3,{id:`controlled-selection`,children:`Controlled selection`}),`
|
|
912
|
+
`,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`selectedRowIds`}),` передан, hook синхронизирует внутренний визуальный state с внешним snapshot и применяет:`]}),`
|
|
913
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
914
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`availableRowIds`}),`;`]}),`
|
|
915
|
+
`,(0,c.jsxs)(t.li,{children:[`текущий `,(0,c.jsx)(t.code,{children:`selectionMode`}),`;`]}),`
|
|
916
|
+
`,(0,c.jsx)(t.li,{children:`удаление неизвестных id;`}),`
|
|
917
|
+
`,(0,c.jsx)(t.li,{children:`single/multi cardinality.`}),`
|
|
918
|
+
`]}),`
|
|
919
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
|
920
|
+
|
|
921
|
+
const selection = useTableRowSelection({
|
|
922
|
+
availableRowIds,
|
|
923
|
+
rowById,
|
|
924
|
+
selectionMode: "multi",
|
|
925
|
+
selectedRowIds: selectedIds,
|
|
926
|
+
onRowSelectionChange: (rows) => {
|
|
927
|
+
setSelectedIds(rows.map((row) => row.id));
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
`})}),`
|
|
931
|
+
`,(0,c.jsxs)(t.p,{children:[`Внешний owner должен передавать новый snapshot после принятого пользовательского изменения. Не мутируйте `,(0,c.jsx)(t.code,{children:`selectedIds`}),` на месте.`]}),`
|
|
932
|
+
`,(0,c.jsxs)(t.h3,{id:`почему-нужен-стабильный-rowbyid`,children:[`Почему нужен стабильный `,(0,c.jsx)(t.code,{children:`rowById`})]}),`
|
|
933
|
+
`,(0,c.jsxs)(t.p,{children:[`Hook преобразует selection id в исходные строки для callback. Если id есть в state, но нет в `,(0,c.jsx)(t.code,{children:`rowById`}),`, такая запись не попадёт в callback.`]}),`
|
|
934
|
+
`,(0,c.jsxs)(t.p,{children:[`Создавайте map через `,(0,c.jsx)(t.code,{children:`useMemo`}),`:`]}),`
|
|
935
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const rowById = useMemo(() => new Map(rows.map((row) => [row.id, row])), [rows]);
|
|
936
|
+
`})}),`
|
|
937
|
+
`,(0,c.jsxs)(t.p,{children:[`Не создавайте новую `,(0,c.jsx)(t.code,{children:`Map`}),` и новый `,(0,c.jsx)(t.code,{children:`availableRowIds`}),` без необходимости при каждом render большого компонента.`]}),`
|
|
938
|
+
`,(0,c.jsx)(t.h3,{id:`низкоуровневый-setter`,children:`Низкоуровневый setter`}),`
|
|
939
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`setRowSelection`}),` нужен для интеграции с API TanStack Table. Если установить произвольный state, hook позже очистит его при изменении `,(0,c.jsx)(t.code,{children:`availableRowIds`}),`, `,(0,c.jsx)(t.code,{children:`selectedRowIds`}),` или mode.`]}),`
|
|
940
|
+
`,(0,c.jsxs)(t.p,{children:[`Для обычной активации строки предпочитайте `,(0,c.jsx)(t.code,{children:`activateRowSelection`}),`: он соблюдает `,(0,c.jsx)(t.code,{children:`selectionMode`}),` и `,(0,c.jsx)(t.code,{children:`canSelect`}),`.`]}),`
|
|
941
|
+
`,(0,c.jsx)(t.h2,{id:`выбор-ячеек`,children:`Выбор ячеек`}),`
|
|
942
|
+
`,(0,c.jsx)(t.h3,{id:`координаты`,children:`Координаты`}),`
|
|
943
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TableCellCoordinates = Readonly<{
|
|
944
|
+
rowId: string;
|
|
945
|
+
columnId: string;
|
|
946
|
+
}>;
|
|
947
|
+
`})}),`
|
|
948
|
+
`,(0,c.jsx)(t.p,{children:`Ячейка определяется не DOM-элементом и не индексами, а двумя стабильными id:`}),`
|
|
949
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const cell = {
|
|
950
|
+
rowId: "order:4500000123",
|
|
951
|
+
columnId: "amount"
|
|
952
|
+
};
|
|
953
|
+
`})}),`
|
|
954
|
+
`,(0,c.jsx)(t.p,{children:`Это позволяет сохранять selection после reorder колонок.`}),`
|
|
955
|
+
`,(0,c.jsx)(t.h3,{id:`tablecellselectionstate`,children:(0,c.jsx)(t.code,{children:`TableCellSelectionState`})}),`
|
|
956
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TableCellSelectionState = readonly TableCellCoordinates[];
|
|
957
|
+
`})}),`
|
|
958
|
+
`,(0,c.jsx)(t.p,{children:`Массив сериализуем и не зависит от TanStack runtime objects.`}),`
|
|
959
|
+
`,(0,c.jsx)(t.h3,{id:`tablecellselectionmode`,children:(0,c.jsx)(t.code,{children:`TableCellSelectionMode`})}),`
|
|
960
|
+
`,(0,c.jsxs)(t.p,{children:[`Использует те же значения `,(0,c.jsx)(t.code,{children:`none | single | multi`}),`, но отдельное имя показывает, что настройка относится к ячейкам, а не строкам.`]}),`
|
|
961
|
+
`,(0,c.jsx)(t.h3,{id:`aretablecellselectionsequal`,children:(0,c.jsx)(t.code,{children:`areTableCellSelectionsEqual`})}),`
|
|
962
|
+
`,(0,c.jsx)(t.p,{children:`Сравнивает наборы координат без учёта порядка:`}),`
|
|
963
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const a = { rowId: "1", columnId: "name" };
|
|
964
|
+
const b = { rowId: "2", columnId: "amount" };
|
|
965
|
+
|
|
966
|
+
areTableCellSelectionsEqual([a, b], [b, a]); // true
|
|
967
|
+
`})}),`
|
|
968
|
+
`,(0,c.jsx)(t.p,{children:`Повторы не влияют на множество:`}),`
|
|
969
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`areTableCellSelectionsEqual([a, a], [a]); // true
|
|
970
|
+
`})}),`
|
|
971
|
+
`,(0,c.jsxs)(t.p,{children:[`Внутренний ключ кодирует `,(0,c.jsx)(t.code,{children:`rowId`}),` и `,(0,c.jsx)(t.code,{children:`columnId`}),` через `,(0,c.jsx)(t.code,{children:`encodeURIComponent`}),`, поэтому значения с `,(0,c.jsx)(t.code,{children:`::`}),`, `,(0,c.jsx)(t.code,{children:`/`}),`, пробелами и другими символами не создают простую delimiter-коллизию.`]}),`
|
|
972
|
+
`,(0,c.jsx)(t.h3,{id:`prunetablecellselection`,children:(0,c.jsx)(t.code,{children:`pruneTableCellSelection`})}),`
|
|
973
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const next = pruneTableCellSelection(
|
|
974
|
+
[
|
|
975
|
+
{ rowId: "row-2", columnId: "B" },
|
|
976
|
+
{ rowId: "row-1", columnId: "B" },
|
|
977
|
+
{ rowId: "row-1", columnId: "A" },
|
|
978
|
+
{ rowId: "row-1", columnId: "A" }
|
|
979
|
+
],
|
|
980
|
+
["row-1", "row-2"],
|
|
981
|
+
["A", "B"],
|
|
982
|
+
"multi"
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
// [
|
|
986
|
+
// { rowId: "row-1", columnId: "A" },
|
|
987
|
+
// { rowId: "row-1", columnId: "B" },
|
|
988
|
+
// { rowId: "row-2", columnId: "B" }
|
|
989
|
+
// ]
|
|
990
|
+
`})}),`
|
|
991
|
+
`,(0,c.jsx)(t.p,{children:`Функция:`}),`
|
|
992
|
+
`,(0,c.jsxs)(t.ol,{children:[`
|
|
993
|
+
`,(0,c.jsx)(t.li,{children:`Удаляет координаты неизвестной строки или колонки.`}),`
|
|
994
|
+
`,(0,c.jsx)(t.li,{children:`Удаляет повторы.`}),`
|
|
995
|
+
`,(0,c.jsxs)(t.li,{children:[`Сортирует по порядку `,(0,c.jsx)(t.code,{children:`availableRowIds`}),`.`]}),`
|
|
996
|
+
`,(0,c.jsxs)(t.li,{children:[`Внутри строки сортирует по `,(0,c.jsx)(t.code,{children:`availableColumnIds`}),`.`]}),`
|
|
997
|
+
`,(0,c.jsxs)(t.li,{children:[`Для `,(0,c.jsx)(t.code,{children:`single`}),` оставляет последнюю координату из этого детерминированного порядка.`]}),`
|
|
998
|
+
`,(0,c.jsxs)(t.li,{children:[`Для `,(0,c.jsx)(t.code,{children:`none`}),` возвращает `,(0,c.jsx)(t.code,{children:`[]`}),`.`]}),`
|
|
999
|
+
`]}),`
|
|
1000
|
+
`,(0,c.jsx)(t.p,{children:`Input не мутируется.`}),`
|
|
1001
|
+
`,(0,c.jsx)(t.p,{children:`В отличие от row selection, здесь single-координата определяется актуальным визуальным порядком строк и колонок.`}),`
|
|
1002
|
+
`,(0,c.jsx)(t.h3,{id:`toggletablecellselection`,children:(0,c.jsx)(t.code,{children:`toggleTableCellSelection`})}),`
|
|
1003
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const first = { rowId: "row-1", columnId: "A" };
|
|
1004
|
+
const second = { rowId: "row-1", columnId: "B" };
|
|
1005
|
+
|
|
1006
|
+
toggleTableCellSelection([first], first, "single");
|
|
1007
|
+
// []
|
|
1008
|
+
|
|
1009
|
+
toggleTableCellSelection([first], second, "single");
|
|
1010
|
+
// [second]
|
|
1011
|
+
|
|
1012
|
+
toggleTableCellSelection([first], second, "multi");
|
|
1013
|
+
// [first, second]
|
|
1014
|
+
`})}),`
|
|
1015
|
+
`,(0,c.jsxs)(t.p,{children:[`Важное отличие от выбора строк: повторная активация выбранной ячейки в `,(0,c.jsx)(t.code,{children:`single`}),` `,(0,c.jsx)(t.strong,{children:`снимает`}),` выбор.`]}),`
|
|
1016
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`toggleTableCellSelection`}),` не проверяет доступные строки и колонки. Для очистки stale coordinates используйте `,(0,c.jsx)(t.code,{children:`pruneTableCellSelection`}),`.`]}),`
|
|
1017
|
+
`,(0,c.jsx)(t.h3,{id:`activation-mode`,children:`Activation mode`}),`
|
|
1018
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TableCellSelectionActivationMode = "direct" | "primary-modifier";
|
|
1019
|
+
`})}),`
|
|
1020
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Режим`}),(0,c.jsx)(t.th,{children:`Когда выбирать ячейку`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`direct`})}),(0,c.jsx)(t.td,{children:`при обычной активации`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`primary-modifier`})}),(0,c.jsx)(t.td,{children:`только с Ctrl в Windows/Linux или Command в macOS`})]})]})]}),`
|
|
1021
|
+
`,(0,c.jsx)(t.h3,{id:`normalizetablecellselectionactivationmode`,children:(0,c.jsx)(t.code,{children:`normalizeTableCellSelectionActivationMode`})}),`
|
|
1022
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`normalizeTableCellSelectionActivationMode("primary-modifier");
|
|
1023
|
+
// "primary-modifier"
|
|
1024
|
+
|
|
1025
|
+
normalizeTableCellSelectionActivationMode("meta");
|
|
1026
|
+
// "direct"
|
|
1027
|
+
|
|
1028
|
+
normalizeTableCellSelectionActivationMode(null, "primary-modifier");
|
|
1029
|
+
// "primary-modifier"
|
|
1030
|
+
`})}),`
|
|
1031
|
+
`,(0,c.jsx)(t.h3,{id:`shouldactivatetablecellselection`,children:(0,c.jsx)(t.code,{children:`shouldActivateTableCellSelection`})}),`
|
|
1032
|
+
`,(0,c.jsx)(t.p,{children:`Функция принимает минимальный event-like объект:`}),`
|
|
1033
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TableSelectionModifierEvent = Readonly<{
|
|
1034
|
+
ctrlKey: boolean;
|
|
1035
|
+
metaKey: boolean;
|
|
1036
|
+
shiftKey: boolean;
|
|
1037
|
+
altKey: boolean;
|
|
1038
|
+
}>;
|
|
1039
|
+
`})}),`
|
|
1040
|
+
`,(0,c.jsx)(t.p,{children:`Поэтому можно передать React MouseEvent или KeyboardEvent: у них есть эти поля.`}),`
|
|
1041
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`function handleCellClick(event: React.MouseEvent, cell: TableCellCoordinates) {
|
|
1042
|
+
if (!shouldActivateTableCellSelection(event, "primary-modifier")) {
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
setSelection((current) => toggleTableCellSelection(current, cell, "multi"));
|
|
1047
|
+
}
|
|
1048
|
+
`})}),`
|
|
1049
|
+
`,(0,c.jsxs)(t.p,{children:[`Для `,(0,c.jsx)(t.code,{children:`primary-modifier`}),`:`]}),`
|
|
1050
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1051
|
+
`,(0,c.jsx)(t.li,{children:`Ctrl или Command принимается;`}),`
|
|
1052
|
+
`,(0,c.jsx)(t.li,{children:`Ctrl+Shift не принимается;`}),`
|
|
1053
|
+
`,(0,c.jsx)(t.li,{children:`Command+Alt не принимается;`}),`
|
|
1054
|
+
`,(0,c.jsx)(t.li,{children:`Shift/Alt без primary modifier не принимается.`}),`
|
|
1055
|
+
`]}),`
|
|
1056
|
+
`,(0,c.jsx)(t.p,{children:`Так helper не перехватывает комбинации, обычно занятые browser/OS selection flow.`}),`
|
|
1057
|
+
`,(0,c.jsx)(t.h2,{id:`интерактивные-элементы-и-css-длины`,children:`Интерактивные элементы и CSS-длины`}),`
|
|
1058
|
+
`,(0,c.jsx)(t.h3,{id:`проблема-клика-по-строке`,children:`Проблема клика по строке`}),`
|
|
1059
|
+
`,(0,c.jsx)(t.p,{children:`В строке может быть кнопка:`}),`
|
|
1060
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-html`,children:`<tr data-row-id="1">
|
|
1061
|
+
<td>Заказ</td>
|
|
1062
|
+
<td><button type="button">Удалить</button></td>
|
|
1063
|
+
</tr>
|
|
1064
|
+
`})}),`
|
|
1065
|
+
`,(0,c.jsxs)(t.p,{children:[`Клик по кнопке всплывает до `,(0,c.jsx)(t.code,{children:`<tr>`}),`. Если обработчик строки не проверит target, одновременно с удалением изменится selection.`]}),`
|
|
1066
|
+
`,(0,c.jsx)(t.h3,{id:`istableinteractiveelement`,children:(0,c.jsx)(t.code,{children:`isTableInteractiveElement`})}),`
|
|
1067
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`function handleRowClick(event: React.MouseEvent<HTMLTableRowElement>) {
|
|
1068
|
+
if (isTableInteractiveElement(event.target)) {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
activateRow();
|
|
1073
|
+
}
|
|
1074
|
+
`})}),`
|
|
1075
|
+
`,(0,c.jsx)(t.p,{children:`Helper ищет target или ближайшего ancestor среди:`}),`
|
|
1076
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1077
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`button`}),`, `,(0,c.jsx)(t.code,{children:`a`}),`, `,(0,c.jsx)(t.code,{children:`input`}),`, `,(0,c.jsx)(t.code,{children:`select`}),`, `,(0,c.jsx)(t.code,{children:`textarea`}),`, `,(0,c.jsx)(t.code,{children:`label`}),`, `,(0,c.jsx)(t.code,{children:`summary`}),`;`]}),`
|
|
1078
|
+
`,(0,c.jsx)(t.li,{children:`audio/video с controls;`}),`
|
|
1079
|
+
`,(0,c.jsx)(t.li,{children:`editable element;`}),`
|
|
1080
|
+
`,(0,c.jsxs)(t.li,{children:[`focusable element с `,(0,c.jsx)(t.code,{children:`tabindex`}),`, кроме `,(0,c.jsx)(t.code,{children:`td`}),`, `,(0,c.jsx)(t.code,{children:`th`}),`, `,(0,c.jsx)(t.code,{children:`tr`}),`;`]}),`
|
|
1081
|
+
`,(0,c.jsx)(t.li,{children:`draggable element;`}),`
|
|
1082
|
+
`,(0,c.jsx)(t.li,{children:`распространённых ARIA roles: button, link, checkbox, radio, switch, menu, option, tab, treeitem, slider, textbox, combobox и других.`}),`
|
|
1083
|
+
`]}),`
|
|
1084
|
+
`,(0,c.jsxs)(t.p,{children:[`SVG icon внутри `,(0,c.jsx)(t.code,{children:`<button>`}),` тоже распознаётся, потому что используется `,(0,c.jsx)(t.code,{children:`closest`}),`.`]}),`
|
|
1085
|
+
`,(0,c.jsx)(t.h3,{id:`custom-interactive-selector`,children:`Custom interactive selector`}),`
|
|
1086
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`if (isTableInteractiveElement(event.target, "[data-row-action='true']")) {
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
`})}),`
|
|
1090
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`extraSelector`}),` добавляется к стандартному списку. Передавайте валидный CSS selector: невалидная строка может привести к DOM exception внутри `,(0,c.jsx)(t.code,{children:`Element.closest`}),`.`]}),`
|
|
1091
|
+
`,(0,c.jsx)(t.h3,{id:`ssr-и-тесты`,children:`SSR и тесты`}),`
|
|
1092
|
+
`,(0,c.jsxs)(t.p,{children:[`Если глобального `,(0,c.jsx)(t.code,{children:`Element`}),` нет или target не является `,(0,c.jsx)(t.code,{children:`Element`}),`, функция возвращает `,(0,c.jsx)(t.code,{children:`false`}),`.`]}),`
|
|
1093
|
+
`,(0,c.jsxs)(t.p,{children:[`В unit-тесте с обычным Node environment DOM отсутствует. Для проверки реальных elements нужен `,(0,c.jsx)(t.code,{children:`jsdom`}),` или browser test.`]}),`
|
|
1094
|
+
`,(0,c.jsx)(t.h3,{id:`resolvetablelength`,children:(0,c.jsx)(t.code,{children:`resolveTableLength`})}),`
|
|
1095
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`resolveTableLength(12); // "12em"
|
|
1096
|
+
resolveTableLength("50%"); // "50%"
|
|
1097
|
+
resolveTableLength("auto"); // "auto"
|
|
1098
|
+
`})}),`
|
|
1099
|
+
`,(0,c.jsxs)(t.p,{children:[`Число превращается в `,(0,c.jsx)(t.code,{children:`em`}),`, строка возвращается без изменений.`]}),`
|
|
1100
|
+
`,(0,c.jsx)(t.p,{children:`Функция не валидирует CSS:`}),`
|
|
1101
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`resolveTableLength(Number.NaN); // "NaNem"
|
|
1102
|
+
resolveTableLength("not-a-css-length"); // та же строка
|
|
1103
|
+
`})}),`
|
|
1104
|
+
`,(0,c.jsx)(t.p,{children:`Передавайте finite number или проверенное CSS-значение.`}),`
|
|
1105
|
+
`,(0,c.jsx)(t.h2,{id:`колонки-из-odata-metadata`,children:`Колонки из OData metadata`}),`
|
|
1106
|
+
`,(0,c.jsx)(t.h3,{id:`граница-ответственности`,children:`Граница ответственности`}),`
|
|
1107
|
+
`,(0,c.jsxs)(t.p,{children:[`Adapter принимает уже полученные `,(0,c.jsx)(t.code,{children:`EntityColumnProperty[]`}),`. Он не читает XML и не вызывает backend.`]}),`
|
|
1108
|
+
`,(0,c.jsx)(t.p,{children:`Metadata flow:`}),`
|
|
1109
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`OData XML metadata
|
|
1110
|
+
│
|
|
1111
|
+
▼
|
|
1112
|
+
odata-service parser
|
|
1113
|
+
│
|
|
1114
|
+
▼
|
|
1115
|
+
EntityColumnProperty[]
|
|
1116
|
+
│
|
|
1117
|
+
▼
|
|
1118
|
+
table OData adapter
|
|
1119
|
+
│
|
|
1120
|
+
▼
|
|
1121
|
+
TableColumnDef[] + visibility state
|
|
1122
|
+
`})}),`
|
|
1123
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`EntityColumnProperty`}),` импортируется из `,(0,c.jsx)(t.code,{children:`odata-service`}),`, а не из `,(0,c.jsx)(t.code,{children:`table`}),`:`]}),`
|
|
1124
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import type { EntityColumnProperty } from "@ryuzaki13/react-foundation-lib/odata-service";
|
|
1125
|
+
`})}),`
|
|
1126
|
+
`,(0,c.jsx)(t.h3,{id:`resolvetablecolumnformattingcontextfromodatacolumn`,children:(0,c.jsx)(t.code,{children:`resolveTableColumnFormattingContextFromODataColumn`})}),`
|
|
1127
|
+
`,(0,c.jsxs)(t.p,{children:[`Возвращает `,(0,c.jsx)(t.code,{children:`role`}),` и normalized `,(0,c.jsx)(t.code,{children:`type`}),` для formatting pipeline:`]}),`
|
|
1128
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const context = resolveTableColumnFormattingContextFromODataColumn(column);
|
|
1129
|
+
|
|
1130
|
+
// например:
|
|
1131
|
+
// { role: "measure", type: "decimal" }
|
|
1132
|
+
`})}),`
|
|
1133
|
+
`,(0,c.jsxs)(t.p,{children:[`Числовые типы всегда принудительно считаются `,(0,c.jsx)(t.code,{children:`measure`}),`:`]}),`
|
|
1134
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1135
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`decimal`}),`;`]}),`
|
|
1136
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`float`}),`;`]}),`
|
|
1137
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`double`}),`;`]}),`
|
|
1138
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`int`}),`;`]}),`
|
|
1139
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`byte`}),`.`]}),`
|
|
1140
|
+
`]}),`
|
|
1141
|
+
`,(0,c.jsxs)(t.p,{children:[`Для остальных типов сохраняется `,(0,c.jsx)(t.code,{children:`column.role`}),`.`]}),`
|
|
1142
|
+
`,(0,c.jsx)(t.p,{children:`Это влияет на базовое выравнивание и выбор formatter strategy.`}),`
|
|
1143
|
+
`,(0,c.jsx)(t.h3,{id:`createtablecolumnsfromodatametadata`,children:(0,c.jsx)(t.code,{children:`createTableColumnsFromODataMetadata`})}),`
|
|
1144
|
+
`,(0,c.jsx)(t.p,{children:`Создаёт одну leaf-колонку для каждого metadata property:`}),`
|
|
1145
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { createTableColumnsFromODataMetadata } from "@ryuzaki13/react-foundation-lib/table";
|
|
1146
|
+
import type { EntityColumnProperty } from "@ryuzaki13/react-foundation-lib/odata-service";
|
|
1147
|
+
|
|
1148
|
+
type ResultRow = {
|
|
1149
|
+
AMOUNT: number;
|
|
1150
|
+
NAME: string;
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
const metadataColumns = [
|
|
1154
|
+
{
|
|
1155
|
+
id: "AMOUNT",
|
|
1156
|
+
type: "decimal",
|
|
1157
|
+
originalType: "Edm.Decimal",
|
|
1158
|
+
label: "Сумма",
|
|
1159
|
+
semanticType: "none",
|
|
1160
|
+
sortable: true,
|
|
1161
|
+
filterable: true,
|
|
1162
|
+
role: "measure"
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
id: "NAME",
|
|
1166
|
+
type: "string",
|
|
1167
|
+
originalType: "Edm.String",
|
|
1168
|
+
label: "Наименование",
|
|
1169
|
+
semanticType: "none",
|
|
1170
|
+
sortable: true,
|
|
1171
|
+
filterable: true,
|
|
1172
|
+
role: "dimension"
|
|
1173
|
+
}
|
|
1174
|
+
] satisfies EntityColumnProperty[];
|
|
1175
|
+
|
|
1176
|
+
const columns = createTableColumnsFromODataMetadata<ResultRow>(metadataColumns);
|
|
1177
|
+
`})}),`
|
|
1178
|
+
`,(0,c.jsx)(t.p,{children:`Полученный shape:`}),`
|
|
1179
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`[
|
|
1180
|
+
{
|
|
1181
|
+
id: "AMOUNT",
|
|
1182
|
+
accessorKey: "AMOUNT",
|
|
1183
|
+
header: "Сумма",
|
|
1184
|
+
meta: {
|
|
1185
|
+
align: "right",
|
|
1186
|
+
formatting: {
|
|
1187
|
+
id: "AMOUNT",
|
|
1188
|
+
role: "measure",
|
|
1189
|
+
type: "decimal"
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
id: "NAME",
|
|
1195
|
+
accessorKey: "NAME",
|
|
1196
|
+
header: "Наименование",
|
|
1197
|
+
meta: {
|
|
1198
|
+
align: "left",
|
|
1199
|
+
formatting: {
|
|
1200
|
+
id: "NAME",
|
|
1201
|
+
role: "dimension",
|
|
1202
|
+
type: "string"
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
];
|
|
1207
|
+
`})}),`
|
|
1208
|
+
`,(0,c.jsx)(t.p,{children:`Header выбирается так:`}),`
|
|
1209
|
+
`,(0,c.jsxs)(t.ol,{children:[`
|
|
1210
|
+
`,(0,c.jsxs)(t.li,{children:[`результат `,(0,c.jsx)(t.code,{children:`options.resolveHeader(column)`}),`;`]}),`
|
|
1211
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`column.label`}),`;`]}),`
|
|
1212
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`column.id`}),`.`]}),`
|
|
1213
|
+
`]}),`
|
|
1214
|
+
`,(0,c.jsx)(t.h3,{id:`custom-header-и-formatting`,children:`Custom header и formatting`}),`
|
|
1215
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const columns = createTableColumnsFromODataMetadata<ResultRow>(metadataColumns, {
|
|
1216
|
+
resolveHeader: (column) => (column.id === "AMOUNT" ? "Итого" : (column.label ?? column.id)),
|
|
1217
|
+
resolveFormatting: (column) =>
|
|
1218
|
+
column.id === "AMOUNT"
|
|
1219
|
+
? {
|
|
1220
|
+
emptyWhenZero: true,
|
|
1221
|
+
overflowTooltip: true
|
|
1222
|
+
}
|
|
1223
|
+
: undefined
|
|
1224
|
+
});
|
|
1225
|
+
`})}),`
|
|
1226
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`id`}),`, `,(0,c.jsx)(t.code,{children:`role`}),` и `,(0,c.jsx)(t.code,{children:`type`}),` всегда берутся из metadata после merge custom options. Их нельзя подменить через `,(0,c.jsx)(t.code,{children:`resolveFormatting`}),`.`]}),`
|
|
1227
|
+
`,(0,c.jsxs)(t.p,{children:[`Generated columns не содержат custom `,(0,c.jsx)(t.code,{children:`cell`}),`, business permissions, row action или server sort handler. Добавляйте это на уровне host.`]}),`
|
|
1228
|
+
`,(0,c.jsx)(t.h3,{id:`базовое-выравнивание`,children:`Базовое выравнивание`}),`
|
|
1229
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1230
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`measure`}),` получает `,(0,c.jsx)(t.code,{children:`align: "right"`}),`;`]}),`
|
|
1231
|
+
`,(0,c.jsxs)(t.li,{children:[`остальные role получают `,(0,c.jsx)(t.code,{children:`align: "left"`}),`.`]}),`
|
|
1232
|
+
`]}),`
|
|
1233
|
+
`,(0,c.jsx)(t.p,{children:`Это стартовое значение. Host может создать свои columns вручную или изменить возвращённые immutable copies.`}),`
|
|
1234
|
+
`,(0,c.jsx)(t.h3,{id:`createtablecolumnvisibilityfromodatametadata`,children:(0,c.jsx)(t.code,{children:`createTableColumnVisibilityFromODataMetadata`})}),`
|
|
1235
|
+
`,(0,c.jsx)(t.p,{children:`Строит стартовую карту:`}),`
|
|
1236
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const visibility = createTableColumnVisibilityFromODataMetadata(metadataColumns);
|
|
1237
|
+
|
|
1238
|
+
// {
|
|
1239
|
+
// CUSTOMER: false,
|
|
1240
|
+
// CUSTOMER_Text: true,
|
|
1241
|
+
// AMOUNT: true
|
|
1242
|
+
// }
|
|
1243
|
+
`})}),`
|
|
1244
|
+
`,(0,c.jsx)(t.p,{children:`Default правило:`}),`
|
|
1245
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1246
|
+
`,(0,c.jsx)(t.li,{children:`большинство колонок видимы;`}),`
|
|
1247
|
+
`,(0,c.jsxs)(t.li,{children:[`поле с `,(0,c.jsx)(t.code,{children:`semanticType: "code"`}),` скрывается, если `,(0,c.jsx)(t.code,{children:`linkedColumnId`}),` указывает на существующую metadata-колонку с `,(0,c.jsx)(t.code,{children:`semanticType: "text"`}),`;`]}),`
|
|
1248
|
+
`,(0,c.jsx)(t.li,{children:`code без реальной text-пары остаётся видимым.`}),`
|
|
1249
|
+
`]}),`
|
|
1250
|
+
`,(0,c.jsxs)(t.p,{children:[`Колонки не удаляются из `,(0,c.jsx)(t.code,{children:`TableColumnDef[]`}),`. Visibility — отдельный state, поэтому пользовательский UI может снова показать code field.`]}),`
|
|
1251
|
+
`,(0,c.jsx)(t.h3,{id:`custom-visibility`,children:`Custom visibility`}),`
|
|
1252
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const visibility = createTableColumnVisibilityFromODataMetadata(metadataColumns, {
|
|
1253
|
+
resolveVisible: (column) => column.id !== "TECHNICAL_FIELD"
|
|
1254
|
+
});
|
|
1255
|
+
`})}),`
|
|
1256
|
+
`,(0,c.jsx)(t.p,{children:`Если callback вернул boolean, default code/text правило больше не применяется для этой колонки.`}),`
|
|
1257
|
+
`,(0,c.jsx)(t.h3,{id:`resolvestablecolumnid`,children:(0,c.jsx)(t.code,{children:`resolveStableColumnId`})}),`
|
|
1258
|
+
`,(0,c.jsx)(t.p,{children:`Порядок поиска:`}),`
|
|
1259
|
+
`,(0,c.jsxs)(t.ol,{children:[`
|
|
1260
|
+
`,(0,c.jsxs)(t.li,{children:[`непустой строковый `,(0,c.jsx)(t.code,{children:`column.id`}),`;`]}),`
|
|
1261
|
+
`,(0,c.jsxs)(t.li,{children:[`непустой строковый `,(0,c.jsx)(t.code,{children:`accessorKey`}),`;`]}),`
|
|
1262
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`undefined`}),`.`]}),`
|
|
1263
|
+
`]}),`
|
|
1264
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`resolveStableColumnId({
|
|
1265
|
+
id: "amount",
|
|
1266
|
+
accessorKey: "AMOUNT",
|
|
1267
|
+
header: "Сумма"
|
|
1268
|
+
});
|
|
1269
|
+
// "amount"
|
|
1270
|
+
`})}),`
|
|
1271
|
+
`,(0,c.jsxs)(t.p,{children:[`Явный `,(0,c.jsx)(t.code,{children:`id`}),` имеет приоритет.`]}),`
|
|
1272
|
+
`,(0,c.jsxs)(t.p,{children:[`Для `,(0,c.jsx)(t.code,{children:`accessorFn`}),` без `,(0,c.jsx)(t.code,{children:`id`}),` результат `,(0,c.jsx)(t.code,{children:`undefined`}),`.`]}),`
|
|
1273
|
+
`,(0,c.jsxs)(t.p,{children:[`Функция проверяет `,(0,c.jsx)(t.code,{children:`trim()`}),` только на пустоту, но возвращает оригинальную строку без trimming.`]}),`
|
|
1274
|
+
`,(0,c.jsx)(t.h3,{id:`enrichtablecolumnswithodataformatting`,children:(0,c.jsx)(t.code,{children:`enrichTableColumnsWithODataFormatting`})}),`
|
|
1275
|
+
`,(0,c.jsx)(t.p,{children:`Используйте, когда columns уже созданы вручную, но им не хватает OData role/type:`}),`
|
|
1276
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const manualColumns: TableColumnDef<ResultRow>[] = [
|
|
1277
|
+
{
|
|
1278
|
+
id: "AMOUNT",
|
|
1279
|
+
accessorKey: "AMOUNT",
|
|
1280
|
+
header: "Сумма",
|
|
1281
|
+
meta: {
|
|
1282
|
+
align: "center",
|
|
1283
|
+
formatting: {
|
|
1284
|
+
emptyWhenZero: true
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
];
|
|
1289
|
+
|
|
1290
|
+
const enriched = enrichTableColumnsWithODataFormatting(manualColumns, metadataColumns);
|
|
1291
|
+
`})}),`
|
|
1292
|
+
`,(0,c.jsx)(t.p,{children:`Результат для leaf:`}),`
|
|
1293
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const enrichedMeta = {
|
|
1294
|
+
align: "center",
|
|
1295
|
+
formatting: {
|
|
1296
|
+
emptyWhenZero: true,
|
|
1297
|
+
id: "AMOUNT",
|
|
1298
|
+
role: "measure",
|
|
1299
|
+
type: "decimal"
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
`})}),`
|
|
1303
|
+
`,(0,c.jsxs)(t.p,{children:[`Существующие `,(0,c.jsx)(t.code,{children:`align`}),` и formatting options сохраняются.`]}),`
|
|
1304
|
+
`,(0,c.jsxs)(t.p,{children:[`Если и `,(0,c.jsx)(t.code,{children:`role`}),`, и `,(0,c.jsx)(t.code,{children:`type`}),` уже truthy, adapter не перезаписывает formatting:`]}),`
|
|
1305
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const existingFormatting = {
|
|
1306
|
+
formatting: {
|
|
1307
|
+
role: "dimension",
|
|
1308
|
+
type: "string"
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
`})}),`
|
|
1312
|
+
`,(0,c.jsxs)(t.p,{children:[`Если отсутствует хотя бы одно из двух полей, adapter дозаполняет metadata-контекст и задаёт оба `,(0,c.jsx)(t.code,{children:`role/type`}),` из metadata.`]}),`
|
|
1313
|
+
`,(0,c.jsx)(t.h3,{id:`grouped-columns`,children:`Grouped columns`}),`
|
|
1314
|
+
`,(0,c.jsx)(t.p,{children:`Adapter рекурсивно обходит только leaf columns:`}),`
|
|
1315
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const columns: TableColumnDef<ResultRow>[] = [
|
|
1316
|
+
{
|
|
1317
|
+
id: "financial",
|
|
1318
|
+
header: "Финансы",
|
|
1319
|
+
columns: [
|
|
1320
|
+
{
|
|
1321
|
+
id: "AMOUNT",
|
|
1322
|
+
accessorKey: "AMOUNT",
|
|
1323
|
+
header: "Сумма"
|
|
1324
|
+
}
|
|
1325
|
+
]
|
|
1326
|
+
}
|
|
1327
|
+
];
|
|
1328
|
+
`})}),`
|
|
1329
|
+
`,(0,c.jsxs)(t.p,{children:[`Group `,(0,c.jsx)(t.code,{children:`financial`}),` не сопоставляется с OData property. Leaf `,(0,c.jsx)(t.code,{children:`AMOUNT`}),` сопоставляется.`]}),`
|
|
1330
|
+
`,(0,c.jsx)(t.h3,{id:`custom-metadata-lookup`,children:`Custom metadata lookup`}),`
|
|
1331
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const enriched = enrichTableColumnsWithODataFormatting(columns, metadataColumns, {
|
|
1332
|
+
resolveMetadataColumn: ({ columnId, metadataColumnsById }) => {
|
|
1333
|
+
const sourceId = columnId === "displayAmount" ? "AMOUNT" : columnId;
|
|
1334
|
+
|
|
1335
|
+
return metadataColumnsById.get(sourceId);
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
`})}),`
|
|
1339
|
+
`,(0,c.jsx)(t.p,{children:`Это полезно для view-column, id которой отличается от backend property.`}),`
|
|
1340
|
+
`,(0,c.jsxs)(t.p,{children:[`Callback должен быть pure и не мутировать переданную `,(0,c.jsx)(t.code,{children:`Map`}),` или column definitions.`]}),`
|
|
1341
|
+
`,(0,c.jsxs)(t.h2,{id:`построение-дерева-из-id-и-parentid`,children:[`Построение дерева из `,(0,c.jsx)(t.code,{children:`id`}),` и `,(0,c.jsx)(t.code,{children:`parentId`})]}),`
|
|
1342
|
+
`,(0,c.jsx)(t.h3,{id:`treetableflathierarchytdata`,children:(0,c.jsx)(t.code,{children:`TreeTableFlatHierarchy<TData>`})}),`
|
|
1343
|
+
`,(0,c.jsx)(t.p,{children:`Контракт говорит, как извлечь связь из любой формы строки:`}),`
|
|
1344
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`interface TreeTableFlatHierarchy<TData> {
|
|
1345
|
+
getRowId: (row: TData) => string;
|
|
1346
|
+
getParentRowId: (row: TData) => string | null | undefined;
|
|
1347
|
+
}
|
|
1348
|
+
`})}),`
|
|
1349
|
+
`,(0,c.jsx)(t.p,{children:`Можно использовать любые имена backend-полей:`}),`
|
|
1350
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const hierarchy: TreeTableFlatHierarchy<SapRow> = {
|
|
1351
|
+
getRowId: (row) => row.NODE_KEY,
|
|
1352
|
+
getParentRowId: (row) => row.PARENT_KEY || null
|
|
1353
|
+
};
|
|
1354
|
+
`})}),`
|
|
1355
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`null`}),` и `,(0,c.jsx)(t.code,{children:`undefined`}),` означают root.`]}),`
|
|
1356
|
+
`,(0,c.jsx)(t.h3,{id:`buildtreetablerows`,children:(0,c.jsx)(t.code,{children:`buildTreeTableRows`})}),`
|
|
1357
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = buildTreeTableRows(items, hierarchy);
|
|
1358
|
+
`})}),`
|
|
1359
|
+
`,(0,c.jsx)(t.p,{children:`Функция:`}),`
|
|
1360
|
+
`,(0,c.jsxs)(t.ol,{children:[`
|
|
1361
|
+
`,(0,c.jsx)(t.li,{children:`Один раз проходит по исходным строкам и строит индексы.`}),`
|
|
1362
|
+
`,(0,c.jsx)(t.li,{children:`Определяет обычные roots, orphans и self-references.`}),`
|
|
1363
|
+
`,(0,c.jsx)(t.li,{children:`Рекурсивно создаёт shallow copies узлов.`}),`
|
|
1364
|
+
`,(0,c.jsxs)(t.li,{children:[`Добавляет непустой `,(0,c.jsx)(t.code,{children:`children`}),`.`]}),`
|
|
1365
|
+
`,(0,c.jsx)(t.li,{children:`Не теряет компоненту, если весь набор замкнут в цикл.`}),`
|
|
1366
|
+
`,(0,c.jsx)(t.li,{children:`Возвращает дерево и diagnostics.`}),`
|
|
1367
|
+
`]}),`
|
|
1368
|
+
`,(0,c.jsxs)(t.p,{children:[`Исходный `,(0,c.jsx)(t.code,{children:`items`}),` не мутируется.`]}),`
|
|
1369
|
+
`,(0,c.jsxs)(t.h3,{id:`результат-treetablebuildresult`,children:[`Результат `,(0,c.jsx)(t.code,{children:`TreeTableBuildResult`})]}),`
|
|
1370
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`interface TreeTableBuildResult<TData> {
|
|
1371
|
+
rows: TreeTableRowNode<TData>[];
|
|
1372
|
+
rowById: Map<string, TData>;
|
|
1373
|
+
allRowIds: string[];
|
|
1374
|
+
rootRowIds: string[];
|
|
1375
|
+
orphanRowIds: string[];
|
|
1376
|
+
duplicateRowIds: string[];
|
|
1377
|
+
cyclicRowIds: string[];
|
|
1378
|
+
}
|
|
1379
|
+
`})}),`
|
|
1380
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Поле`}),(0,c.jsx)(t.th,{children:`Содержимое`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`rows`})}),(0,c.jsxs)(t.td,{children:[`корневые shallow-copy узлы с вложенным `,(0,c.jsx)(t.code,{children:`children`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`rowById`})}),(0,c.jsx)(t.td,{children:`первые исходные объекты по id`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`allRowIds`})}),(0,c.jsx)(t.td,{children:`все принятые уникальные id в исходном порядке`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`rootRowIds`})}),(0,c.jsx)(t.td,{children:`id итоговых roots в порядке построения`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`orphanRowIds`})}),(0,c.jsx)(t.td,{children:`строки с отсутствующим parent`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`duplicateRowIds`})}),(0,c.jsx)(t.td,{children:`повторившиеся id`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`cyclicRowIds`})}),(0,c.jsx)(t.td,{children:`точки, в которых алгоритм обнаружил цикл`})]})]})]}),`
|
|
1381
|
+
`,(0,c.jsx)(t.h3,{id:`treetablerownodetdata`,children:(0,c.jsx)(t.code,{children:`TreeTableRowNode<TData>`})}),`
|
|
1382
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TreeTableRowNode<TData> = TData & {
|
|
1383
|
+
children?: TreeTableRowNode<TData>[];
|
|
1384
|
+
};
|
|
1385
|
+
`})}),`
|
|
1386
|
+
`,(0,c.jsxs)(t.p,{children:[`Узел сохраняет все поля исходной строки и при наличии детей получает `,(0,c.jsx)(t.code,{children:`children`}),`.`]}),`
|
|
1387
|
+
`,(0,c.jsx)(t.h3,{id:`shallow-copy`,children:`Shallow copy`}),`
|
|
1388
|
+
`,(0,c.jsx)(t.p,{children:`Копируется только верхний уровень:`}),`
|
|
1389
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const original = {
|
|
1390
|
+
id: "root",
|
|
1391
|
+
parentId: null,
|
|
1392
|
+
settings: { color: "red" }
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
const node = buildTreeTableRows([original], hierarchy).rows[0];
|
|
1396
|
+
|
|
1397
|
+
node !== original; // true
|
|
1398
|
+
node.settings === original.settings; // true
|
|
1399
|
+
`})}),`
|
|
1400
|
+
`,(0,c.jsx)(t.p,{children:`Не мутируйте nested objects через tree node, если они принадлежат query cache или props.`}),`
|
|
1401
|
+
`,(0,c.jsxs)(t.h3,{id:`зарезервированное-поле-children`,children:[`Зарезервированное поле `,(0,c.jsx)(t.code,{children:`children`})]}),`
|
|
1402
|
+
`,(0,c.jsxs)(t.p,{children:[`Функция использует поле `,(0,c.jsx)(t.code,{children:`children`}),`. Если исходный `,(0,c.jsx)(t.code,{children:`TData`}),` уже содержит business-поле с этим именем, возможен конфликт:`]}),`
|
|
1403
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1404
|
+
`,(0,c.jsx)(t.li,{children:`при найденных детях оно будет заменено построенным массивом;`}),`
|
|
1405
|
+
`,(0,c.jsx)(t.li,{children:`при отсутствии найденных детей исходное поле останется в shallow copy.`}),`
|
|
1406
|
+
`]}),`
|
|
1407
|
+
`,(0,c.jsxs)(t.p,{children:[`Предпочитайте input type без собственного `,(0,c.jsx)(t.code,{children:`children`}),` или предварительно переименуйте business-поле на boundary.`]}),`
|
|
1408
|
+
`,(0,c.jsx)(t.h3,{id:`обычные-roots`,children:`Обычные roots`}),`
|
|
1409
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const roots = [
|
|
1410
|
+
{ id: "root-1", parentId: null },
|
|
1411
|
+
{ id: "root-2", parentId: undefined }
|
|
1412
|
+
];
|
|
1413
|
+
`})}),`
|
|
1414
|
+
`,(0,c.jsx)(t.p,{children:`Обе строки считаются корневыми.`}),`
|
|
1415
|
+
`,(0,c.jsx)(t.h3,{id:`orphan`,children:`Orphan`}),`
|
|
1416
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const orphan = { id: "orphan", parentId: "missing" };
|
|
1417
|
+
`})}),`
|
|
1418
|
+
`,(0,c.jsx)(t.p,{children:`Строка не удаляется. Она поднимается в root:`}),`
|
|
1419
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`result.rootRowIds.includes("orphan"); // true
|
|
1420
|
+
result.orphanRowIds.includes("orphan"); // true
|
|
1421
|
+
`})}),`
|
|
1422
|
+
`,(0,c.jsx)(t.p,{children:`Это позволяет UI показать повреждённые данные, а diagnostics — сообщить о проблеме.`}),`
|
|
1423
|
+
`,(0,c.jsx)(t.h3,{id:`self-reference`,children:`Self-reference`}),`
|
|
1424
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const selfReferencedRow = { id: "a", parentId: "a" };
|
|
1425
|
+
`})}),`
|
|
1426
|
+
`,(0,c.jsxs)(t.p,{children:[`Строка становится root и попадает в `,(0,c.jsx)(t.code,{children:`cyclicRowIds`}),`.`]}),`
|
|
1427
|
+
`,(0,c.jsx)(t.h3,{id:`цикл-из-нескольких-строк`,children:`Цикл из нескольких строк`}),`
|
|
1428
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`[
|
|
1429
|
+
{ id: "a", parentId: "b" },
|
|
1430
|
+
{ id: "b", parentId: "a" }
|
|
1431
|
+
];
|
|
1432
|
+
`})}),`
|
|
1433
|
+
`,(0,c.jsx)(t.p,{children:`Явного root нет. Функция начинает с первого непосещённого id, строит доступную цепочку и обрывает обратную ссылку:`}),`
|
|
1434
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`[
|
|
1435
|
+
{
|
|
1436
|
+
id: "a",
|
|
1437
|
+
parentId: "b",
|
|
1438
|
+
children: [{ id: "b", parentId: "a" }]
|
|
1439
|
+
}
|
|
1440
|
+
];
|
|
1441
|
+
`})}),`
|
|
1442
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`cyclicRowIds`}),` — diagnostics точек обнаружения, а не гарантированный полный список всех участников strongly connected component. В примере он может содержать только `,(0,c.jsx)(t.code,{children:`"a"`}),`. Если бизнесу нужен полный анализ графа, выполняйте отдельную validation до построения UI-дерева.`]}),`
|
|
1443
|
+
`,(0,c.jsx)(t.h3,{id:`duplicate-id`,children:`Duplicate id`}),`
|
|
1444
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`[
|
|
1445
|
+
{ id: "root", title: "Первый" },
|
|
1446
|
+
{ id: "root", title: "Повтор" }
|
|
1447
|
+
];
|
|
1448
|
+
`})}),`
|
|
1449
|
+
`,(0,c.jsxs)(t.p,{children:[`Используется первое вхождение. Следующие игнорируются. `,(0,c.jsx)(t.code,{children:`duplicateRowIds`}),` содержит уникальный проблемный id, а не число повторов.`]}),`
|
|
1450
|
+
`,(0,c.jsx)(t.h3,{id:`порядок`,children:`Порядок`}),`
|
|
1451
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1452
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`allRowIds`}),` сохраняет порядок первых уникальных входных строк;`]}),`
|
|
1453
|
+
`,(0,c.jsx)(t.li,{children:`children одного parent идут в порядке входных строк;`}),`
|
|
1454
|
+
`,(0,c.jsx)(t.li,{children:`root candidates идут в порядке входных строк;`}),`
|
|
1455
|
+
`,(0,c.jsx)(t.li,{children:`циклическая компонента без root начинается с первого непосещённого id.`}),`
|
|
1456
|
+
`]}),`
|
|
1457
|
+
`,(0,c.jsx)(t.p,{children:`Функция не сортирует по title, id или business order.`}),`
|
|
1458
|
+
`,(0,c.jsx)(t.h3,{id:`validation-id`,children:`Validation id`}),`
|
|
1459
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),` не обрезает и не запрещает пустой id:`]}),`
|
|
1460
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`getRowId: () => "";
|
|
1461
|
+
`})}),`
|
|
1462
|
+
`,(0,c.jsxs)(t.p,{children:[`Технически такой ключ попадёт в `,(0,c.jsx)(t.code,{children:`Map`}),`. Это почти всегда ошибка данных. Валидируйте uniqueness и non-empty string на external boundary.`]}),`
|
|
1463
|
+
`,(0,c.jsx)(t.h3,{id:`интеграция-с-tanstack-table`,children:`Интеграция с TanStack Table`}),`
|
|
1464
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const treeData = buildTreeTableRows(rows, hierarchy);
|
|
1465
|
+
|
|
1466
|
+
const table = useTable({
|
|
1467
|
+
features: foundationTableFeatures,
|
|
1468
|
+
data: treeData.rows,
|
|
1469
|
+
columns,
|
|
1470
|
+
getRowId: (row) => hierarchy.getRowId(row),
|
|
1471
|
+
getSubRows: (row) => row.children
|
|
1472
|
+
});
|
|
1473
|
+
`})}),`
|
|
1474
|
+
`,(0,c.jsx)(t.p,{children:`Для selection можно передать:`}),`
|
|
1475
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`useTableRowSelection({
|
|
1476
|
+
availableRowIds: treeData.allRowIds,
|
|
1477
|
+
rowById: treeData.rowById,
|
|
1478
|
+
selectionMode: "multi",
|
|
1479
|
+
onRowSelectionChange
|
|
1480
|
+
});
|
|
1481
|
+
`})}),`
|
|
1482
|
+
`,(0,c.jsxs)(t.p,{children:[`Callback получит исходные `,(0,c.jsx)(t.code,{children:`TData`}),` из `,(0,c.jsx)(t.code,{children:`rowById`}),`, а не shallow-copy узлы с `,(0,c.jsx)(t.code,{children:`children`}),`.`]}),`
|
|
1483
|
+
`,(0,c.jsx)(t.h2,{id:`транспонирование-аналитических-строк`,children:`Транспонирование аналитических строк`}),`
|
|
1484
|
+
`,(0,c.jsx)(t.h3,{id:`когда-нужна-функция`,children:`Когда нужна функция`}),`
|
|
1485
|
+
`,(0,c.jsx)(t.p,{children:`Исходные факты часто выглядят так:`}),`
|
|
1486
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`[
|
|
1487
|
+
{ REGION: "Урал", CITY: "Екатеринбург", AMOUNT: 10 },
|
|
1488
|
+
{ REGION: "Урал", CITY: "Екатеринбург", AMOUNT: 20 },
|
|
1489
|
+
{ REGION: "Урал", CITY: "Пермь", AMOUNT: 30 }
|
|
1490
|
+
];
|
|
1491
|
+
`})}),`
|
|
1492
|
+
`,(0,c.jsxs)(t.p,{children:[`Backend не прислал group rows. `,(0,c.jsx)(t.code,{children:`transposeFlatRowsToTreeTableRows`}),` создаст:`]}),`
|
|
1493
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`Урал synthetic level 0
|
|
1494
|
+
├─ Екатеринбург synthetic level 1
|
|
1495
|
+
│ ├─ fact AMOUNT 10 leaf
|
|
1496
|
+
│ └─ fact AMOUNT 20 leaf
|
|
1497
|
+
└─ Пермь synthetic level 1
|
|
1498
|
+
└─ fact AMOUNT 30 leaf
|
|
1499
|
+
`})}),`
|
|
1500
|
+
`,(0,c.jsx)(t.h3,{id:`treetabletransposeflatrowsoptions`,children:(0,c.jsx)(t.code,{children:`TreeTableTransposeFlatRowsOptions`})}),`
|
|
1501
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TreeTableTransposeFlatRowsOptions = {
|
|
1502
|
+
hierarchyLevels: readonly {
|
|
1503
|
+
columnId: string;
|
|
1504
|
+
expandByDefault?: boolean;
|
|
1505
|
+
}[];
|
|
1506
|
+
};
|
|
1507
|
+
`})}),`
|
|
1508
|
+
`,(0,c.jsxs)(t.p,{children:[`Порядок `,(0,c.jsx)(t.code,{children:`hierarchyLevels`}),` задаёт уровни сверху вниз.`]}),`
|
|
1509
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
|
|
1510
|
+
hierarchyLevels: [{ columnId: "REGION" }, { columnId: "CITY" }];
|
|
1511
|
+
}
|
|
1512
|
+
`})}),`
|
|
1513
|
+
`,(0,c.jsx)(t.h3,{id:`нормализация-levels`,children:`Нормализация levels`}),`
|
|
1514
|
+
`,(0,c.jsx)(t.p,{children:`Перед преобразованием:`}),`
|
|
1515
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1516
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`columnId`}),` обрезается через `,(0,c.jsx)(t.code,{children:`trim()`}),`;`]}),`
|
|
1517
|
+
`,(0,c.jsx)(t.li,{children:`пустые id удаляются;`}),`
|
|
1518
|
+
`,(0,c.jsx)(t.li,{children:`повторяющиеся id удаляются;`}),`
|
|
1519
|
+
`,(0,c.jsx)(t.li,{children:`сохраняется первое вхождение.`}),`
|
|
1520
|
+
`]}),`
|
|
1521
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
|
|
1522
|
+
hierarchyLevels: [{ columnId: " REGION " }, { columnId: "" }, { columnId: "REGION" }, { columnId: "CITY" }];
|
|
1523
|
+
}
|
|
1524
|
+
`})}),`
|
|
1525
|
+
`,(0,c.jsxs)(t.p,{children:[`Превращается в levels `,(0,c.jsx)(t.code,{children:`REGION`}),`, `,(0,c.jsx)(t.code,{children:`CITY`}),`.`]}),`
|
|
1526
|
+
`,(0,c.jsxs)(t.p,{children:[`Если после нормализации levels нет, функция возвращает `,(0,c.jsx)(t.code,{children:`[]`}),`, даже если `,(0,c.jsx)(t.code,{children:`rows`}),` непустой.`]}),`
|
|
1527
|
+
`,(0,c.jsx)(t.h3,{id:`expandbydefault`,children:(0,c.jsx)(t.code,{children:`expandByDefault`})}),`
|
|
1528
|
+
`,(0,c.jsxs)(t.p,{children:[`Поле является частью общего публичного описания level, но сама функция `,(0,c.jsx)(t.code,{children:`transposeFlatRowsToTreeTableRows`}),` в текущей реализации его не использует и не записывает в строки.`]}),`
|
|
1529
|
+
`,(0,c.jsxs)(t.p,{children:[`Раскрытие — состояние UI. Передавайте `,(0,c.jsx)(t.code,{children:`expandByDefault`}),` своему downstream runtime или вычисляйте initial expanded ids отдельно. Не ожидайте, что вызов transpose автоматически раскроет узлы.`]}),`
|
|
1530
|
+
`,(0,c.jsx)(t.h3,{id:`служебные-поля`,children:`Служебные поля`}),`
|
|
1531
|
+
`,(0,c.jsx)(t.p,{children:`Каждая выходная строка содержит:`}),`
|
|
1532
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Constant`}),(0,c.jsx)(t.th,{children:`Фактическое имя поля`}),(0,c.jsx)(t.th,{children:`Значение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_ROW_ID_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`__treeTableRowId`})}),(0,c.jsx)(t.td,{children:`сгенерированный уникальный id`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_PARENT_ROW_ID_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`__treeTableParentRowId`})}),(0,c.jsxs)(t.td,{children:[`id parent или `,(0,c.jsx)(t.code,{children:`null`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_LABEL_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`__treeTableLabel`})}),(0,c.jsx)(t.td,{children:`label текущего узла`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_LEVEL_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`__treeTableLevel`})}),(0,c.jsx)(t.td,{children:`индекс уровня/глубина leaf`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_SYNTHETIC_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`__treeTableSynthetic`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`true`}),` для group, `,(0,c.jsx)(t.code,{children:`false`}),` для fact leaf`]})]})]})]}),`
|
|
1533
|
+
`,(0,c.jsx)(t.p,{children:`Не пишите строковые имена вручную. Используйте constants:`}),`
|
|
1534
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const rowId = row[TREE_TABLE_TRANSPOSED_ROW_ID_FIELD];
|
|
1535
|
+
`})}),`
|
|
1536
|
+
`,(0,c.jsx)(t.p,{children:`Так refactor имени поля будет проверяться TypeScript.`}),`
|
|
1537
|
+
`,(0,c.jsx)(t.h3,{id:`зарезервированные-имена`,children:`Зарезервированные имена`}),`
|
|
1538
|
+
`,(0,c.jsx)(t.p,{children:`Input row не должен использовать business-поля:`}),`
|
|
1539
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`__treeTableRowId
|
|
1540
|
+
__treeTableParentRowId
|
|
1541
|
+
__treeTableLabel
|
|
1542
|
+
__treeTableLevel
|
|
1543
|
+
__treeTableSynthetic
|
|
1544
|
+
`})}),`
|
|
1545
|
+
`,(0,c.jsx)(t.p,{children:`Для leaf функция сначала spread-ит исходный объект, затем записывает служебные поля. Совпавшие business-значения будут перезаписаны.`}),`
|
|
1546
|
+
`,(0,c.jsx)(t.h3,{id:`synthetic-row`,children:`Synthetic row`}),`
|
|
1547
|
+
`,(0,c.jsx)(t.p,{children:`Пример group-узла:`}),`
|
|
1548
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const syntheticRow = {
|
|
1549
|
+
__treeTableRowId: '[["REGION","Урал"]]',
|
|
1550
|
+
__treeTableParentRowId: null,
|
|
1551
|
+
__treeTableLabel: "Урал",
|
|
1552
|
+
__treeTableLevel: 0,
|
|
1553
|
+
__treeTableSynthetic: true,
|
|
1554
|
+
REGION: "Урал"
|
|
1555
|
+
};
|
|
1556
|
+
`})}),`
|
|
1557
|
+
`,(0,c.jsx)(t.p,{children:`Synthetic row содержит:`}),`
|
|
1558
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1559
|
+
`,(0,c.jsx)(t.li,{children:`служебные поля;`}),`
|
|
1560
|
+
`,(0,c.jsx)(t.li,{children:`только значение колонки текущего уровня;`}),`
|
|
1561
|
+
`,(0,c.jsxs)(t.li,{children:[`не содержит `,(0,c.jsx)(t.code,{children:`AMOUNT`}),` и остальные business-поля факта;`]}),`
|
|
1562
|
+
`,(0,c.jsx)(t.li,{children:`не содержит агрегированную сумму.`}),`
|
|
1563
|
+
`]}),`
|
|
1564
|
+
`,(0,c.jsxs)(t.p,{children:[`UI должен проверять `,(0,c.jsx)(t.code,{children:`__treeTableSynthetic`}),` прежде чем считать business field обязательным.`]}),`
|
|
1565
|
+
`,(0,c.jsx)(t.h3,{id:`leaf-row`,children:`Leaf row`}),`
|
|
1566
|
+
`,(0,c.jsx)(t.p,{children:`Пример листа:`}),`
|
|
1567
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const leafRow = {
|
|
1568
|
+
REGION: "Урал",
|
|
1569
|
+
CITY: "Екатеринбург",
|
|
1570
|
+
AMOUNT: 10,
|
|
1571
|
+
__treeTableRowId: "...#row:0",
|
|
1572
|
+
__treeTableParentRowId: "...",
|
|
1573
|
+
__treeTableLabel: "Екатеринбург",
|
|
1574
|
+
__treeTableLevel: 2,
|
|
1575
|
+
__treeTableSynthetic: false
|
|
1576
|
+
};
|
|
1577
|
+
`})}),`
|
|
1578
|
+
`,(0,c.jsx)(t.p,{children:`Leaf сохраняет все enumerable-поля исходной строки. Служебные поля добавляются поверх них.`}),`
|
|
1579
|
+
`,(0,c.jsx)(t.h3,{id:`как-создаются-group-id`,children:`Как создаются group id`}),`
|
|
1580
|
+
`,(0,c.jsxs)(t.p,{children:[`ID строится из полного пути пар `,(0,c.jsx)(t.code,{children:`columnId + string label`}),` и сериализуется в JSON:`]}),`
|
|
1581
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`[
|
|
1582
|
+
["REGION", "Урал"],
|
|
1583
|
+
["CITY", "Екатеринбург"]
|
|
1584
|
+
];
|
|
1585
|
+
`})}),`
|
|
1586
|
+
`,(0,c.jsx)(t.p,{children:`Это даёт важные свойства:`}),`
|
|
1587
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1588
|
+
`,(0,c.jsx)(t.li,{children:`одинаковый city под разными region создаёт разные nodes;`}),`
|
|
1589
|
+
`,(0,c.jsx)(t.li,{children:`одинаковый label в разных level columns не смешивается;`}),`
|
|
1590
|
+
`,(0,c.jsx)(t.li,{children:`повторяющийся путь переиспользует один synthetic node;`}),`
|
|
1591
|
+
`,(0,c.jsx)(t.li,{children:`id детерминирован для одинакового нормализованного пути.`}),`
|
|
1592
|
+
`]}),`
|
|
1593
|
+
`,(0,c.jsx)(t.p,{children:`Не парсите формат id ради business-логики. Считайте его opaque string.`}),`
|
|
1594
|
+
`,(0,c.jsx)(t.h3,{id:`как-создаются-leaf-id`,children:`Как создаются leaf id`}),`
|
|
1595
|
+
`,(0,c.jsx)(t.p,{children:`К group path добавляется индекс исходной строки:`}),`
|
|
1596
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`<serialized-path>#row:0
|
|
1597
|
+
<serialized-path>#row:1
|
|
1598
|
+
`})}),`
|
|
1599
|
+
`,(0,c.jsx)(t.p,{children:`Следствие: leaf id стабилен только пока порядок input rows стабилен. После server sorting или insertion индекс может измениться.`}),`
|
|
1600
|
+
`,(0,c.jsx)(t.p,{children:`Если selection листьев должен переживать reorder, текущий helper не принимает custom leaf id. Host должен:`}),`
|
|
1601
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1602
|
+
`,(0,c.jsx)(t.li,{children:`хранить устойчивый business id в исходной строке для восстановления selection;`}),`
|
|
1603
|
+
`,(0,c.jsx)(t.li,{children:`либо использовать другой заранее подготовленный hierarchy contract;`}),`
|
|
1604
|
+
`,(0,c.jsx)(t.li,{children:`либо отдельно мапить business selection, не считая generated leaf id доменным идентификатором.`}),`
|
|
1605
|
+
`]}),`
|
|
1606
|
+
`,(0,c.jsx)(t.h3,{id:`преобразование-значений-level`,children:`Преобразование значений level`}),`
|
|
1607
|
+
`,(0,c.jsx)(t.p,{children:`Правила label:`}),`
|
|
1608
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Исходное значение`}),(0,c.jsx)(t.th,{children:`Результат`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`null`})}),(0,c.jsx)(t.td,{children:`level пропускается`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`undefined`})}),(0,c.jsx)(t.td,{children:`level пропускается`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`""`})}),(0,c.jsx)(t.td,{children:`level пропускается`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`0`})}),(0,c.jsxs)(t.td,{children:[`label `,(0,c.jsx)(t.code,{children:`"0"`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`false`})}),(0,c.jsxs)(t.td,{children:[`label `,(0,c.jsx)(t.code,{children:`"false"`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`123`})}),(0,c.jsxs)(t.td,{children:[`label `,(0,c.jsx)(t.code,{children:`"123"`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`" "`})}),(0,c.jsx)(t.td,{children:`непустой whitespace label, level не пропускается`})]})]})]}),`
|
|
1609
|
+
`,(0,c.jsx)(t.p,{children:`Значение не trim-ится. Если whitespace считается пустым по бизнес-контракту, нормализуйте backend data до вызова.`}),`
|
|
1610
|
+
`,(0,c.jsxs)(t.p,{children:[`Число `,(0,c.jsx)(t.code,{children:`1`}),` и строка `,(0,c.jsx)(t.code,{children:`"1"`}),` становятся одинаковым label и одним group path. Synthetic row сохранит исходное значение первого встретившегося факта в поле level column.`]}),`
|
|
1611
|
+
`,(0,c.jsx)(t.h3,{id:`пустой-промежуточный-level`,children:`Пустой промежуточный level`}),`
|
|
1612
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const rowWithEmptyLevel = {
|
|
1613
|
+
REGION: "Урал",
|
|
1614
|
+
CITY: "",
|
|
1615
|
+
STORE: "Магазин 1"
|
|
1616
|
+
};
|
|
1617
|
+
`})}),`
|
|
1618
|
+
`,(0,c.jsxs)(t.p,{children:[`При levels `,(0,c.jsx)(t.code,{children:`REGION`}),`, `,(0,c.jsx)(t.code,{children:`CITY`}),`, `,(0,c.jsx)(t.code,{children:`STORE`}),` пустой `,(0,c.jsx)(t.code,{children:`CITY`}),` пропускается. `,(0,c.jsx)(t.code,{children:`STORE`}),` становится прямым ребёнком `,(0,c.jsx)(t.code,{children:`REGION`}),`.`]}),`
|
|
1619
|
+
`,(0,c.jsxs)(t.p,{children:[`При этом `,(0,c.jsx)(t.code,{children:`__treeTableLevel`}),` synthetic STORE будет равен исходному индексу level `,(0,c.jsx)(t.code,{children:`2`}),`, а не визуальной глубине `,(0,c.jsx)(t.code,{children:`1`}),`. Leaf получит `,(0,c.jsx)(t.code,{children:`3`}),`.`]}),`
|
|
1620
|
+
`,(0,c.jsxs)(t.p,{children:[`Не используйте служебный level как гарантированную DOM depth при пропущенных значениях. Фактическую глубину после `,(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),` вычисляет tree runtime.`]}),`
|
|
1621
|
+
`,(0,c.jsx)(t.h3,{id:`строка-без-единого-level`,children:`Строка без единого level`}),`
|
|
1622
|
+
`,(0,c.jsxs)(t.p,{children:[`Если у факта все hierarchy values равны `,(0,c.jsx)(t.code,{children:`null`}),`, `,(0,c.jsx)(t.code,{children:`undefined`}),` или `,(0,c.jsx)(t.code,{children:`""`}),`, функция полностью пропускает строку: не создаёт ни group, ни leaf.`]}),`
|
|
1623
|
+
`,(0,c.jsxs)(t.p,{children:[`Если такое поведение неприемлемо, заранее подставьте явный label вроде `,(0,c.jsx)(t.code,{children:`"Без группы"`}),`.`]}),`
|
|
1624
|
+
`,(0,c.jsx)(t.h3,{id:`порядок-output`,children:`Порядок output`}),`
|
|
1625
|
+
`,(0,c.jsx)(t.p,{children:`Функция обходит input rows по порядку. Для каждой строки она:`}),`
|
|
1626
|
+
`,(0,c.jsxs)(t.ol,{children:[`
|
|
1627
|
+
`,(0,c.jsx)(t.li,{children:`Добавляет ещё не созданные synthetic nodes её пути.`}),`
|
|
1628
|
+
`,(0,c.jsx)(t.li,{children:`Добавляет leaf.`}),`
|
|
1629
|
+
`]}),`
|
|
1630
|
+
`,(0,c.jsxs)(t.p,{children:[`Поэтому flat output отражает порядок первого появления групп и листьев. После `,(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),` children каждой группы сохраняют этот порядок.`]}),`
|
|
1631
|
+
`,(0,c.jsx)(t.h3,{id:`никакой-агрегации`,children:`Никакой агрегации`}),`
|
|
1632
|
+
`,(0,c.jsx)(t.p,{children:`Функция не вычисляет:`}),`
|
|
1633
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1634
|
+
`,(0,c.jsx)(t.li,{children:`суммы;`}),`
|
|
1635
|
+
`,(0,c.jsx)(t.li,{children:`counts;`}),`
|
|
1636
|
+
`,(0,c.jsx)(t.li,{children:`min/max;`}),`
|
|
1637
|
+
`,(0,c.jsx)(t.li,{children:`subtotal;`}),`
|
|
1638
|
+
`,(0,c.jsx)(t.li,{children:`состояние mixed selection;`}),`
|
|
1639
|
+
`,(0,c.jsx)(t.li,{children:`наследование permissions.`}),`
|
|
1640
|
+
`]}),`
|
|
1641
|
+
`,(0,c.jsx)(t.p,{children:`Если group row должен показывать total, получите aggregate с backend или вычислите его отдельной domain/presentation функцией с явно заданной политикой.`}),`
|
|
1642
|
+
`,(0,c.jsx)(t.h2,{id:`полный-pipeline-для-аналитического-дерева`,children:`Полный pipeline для аналитического дерева`}),`
|
|
1643
|
+
`,(0,c.jsx)(t.h3,{id:`1-импорты`,children:`1. Импорты`}),`
|
|
1644
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import {
|
|
1645
|
+
buildTreeTableRows,
|
|
1646
|
+
transposeFlatRowsToTreeTableRows,
|
|
1647
|
+
TREE_TABLE_TRANSPOSED_LABEL_FIELD,
|
|
1648
|
+
TREE_TABLE_TRANSPOSED_PARENT_ROW_ID_FIELD,
|
|
1649
|
+
TREE_TABLE_TRANSPOSED_ROW_ID_FIELD,
|
|
1650
|
+
TREE_TABLE_TRANSPOSED_SYNTHETIC_FIELD,
|
|
1651
|
+
type TreeTableTransposedRow
|
|
1652
|
+
} from "@ryuzaki13/react-foundation-lib/tree-table";
|
|
1653
|
+
`})}),`
|
|
1654
|
+
`,(0,c.jsx)(t.h3,{id:`2-исходный-тип`,children:`2. Исходный тип`}),`
|
|
1655
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type SalesFact = {
|
|
1656
|
+
REGION: string;
|
|
1657
|
+
CITY: string;
|
|
1658
|
+
AMOUNT: number;
|
|
1659
|
+
};
|
|
1660
|
+
`})}),`
|
|
1661
|
+
`,(0,c.jsx)(t.h3,{id:`3-flat-hierarchy`,children:`3. Flat hierarchy`}),`
|
|
1662
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const transposed = transposeFlatRowsToTreeTableRows(facts, {
|
|
1663
|
+
hierarchyLevels: [{ columnId: "REGION" }, { columnId: "CITY" }]
|
|
1664
|
+
});
|
|
1665
|
+
`})}),`
|
|
1666
|
+
`,(0,c.jsx)(t.p,{children:`TypeScript выводит:`}),`
|
|
1667
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`TreeTableTransposedRow < SalesFact > [];
|
|
1668
|
+
`})}),`
|
|
1669
|
+
`,(0,c.jsxs)(t.p,{children:[`Фактически synthetic row не имеет всех business fields `,(0,c.jsx)(t.code,{children:`SalesFact`}),`, хотя intersection type предоставляет их compile-time. Поэтому runtime-код обязан проверять флаг:`]}),`
|
|
1670
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function readAmount(row: TreeTableTransposedRow<SalesFact>): number | undefined {
|
|
1671
|
+
if (row[TREE_TABLE_TRANSPOSED_SYNTHETIC_FIELD]) {
|
|
1672
|
+
return undefined;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
return row.AMOUNT;
|
|
1676
|
+
}
|
|
1677
|
+
`})}),`
|
|
1678
|
+
`,(0,c.jsx)(t.h3,{id:`4-nested-hierarchy`,children:`4. Nested hierarchy`}),`
|
|
1679
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const tree = buildTreeTableRows(transposed, {
|
|
1680
|
+
getRowId: (row) => row[TREE_TABLE_TRANSPOSED_ROW_ID_FIELD],
|
|
1681
|
+
getParentRowId: (row) => row[TREE_TABLE_TRANSPOSED_PARENT_ROW_ID_FIELD]
|
|
1682
|
+
});
|
|
1683
|
+
`})}),`
|
|
1684
|
+
`,(0,c.jsx)(t.h3,{id:`5-tree-label-column`,children:`5. Tree label column`}),`
|
|
1685
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import type { TableColumnDef } from "@ryuzaki13/react-foundation-lib/table";
|
|
1686
|
+
|
|
1687
|
+
type RuntimeRow = TreeTableTransposedRow<SalesFact>;
|
|
1688
|
+
|
|
1689
|
+
const columns: TableColumnDef<RuntimeRow>[] = [
|
|
1690
|
+
{
|
|
1691
|
+
id: "treeLabel",
|
|
1692
|
+
accessorFn: (row) => row[TREE_TABLE_TRANSPOSED_LABEL_FIELD],
|
|
1693
|
+
header: "Структура"
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
id: "amount",
|
|
1697
|
+
accessorFn: (row) => (row[TREE_TABLE_TRANSPOSED_SYNTHETIC_FIELD] ? undefined : row.AMOUNT),
|
|
1698
|
+
header: "Сумма",
|
|
1699
|
+
meta: { align: "right" }
|
|
1700
|
+
}
|
|
1701
|
+
];
|
|
1702
|
+
`})}),`
|
|
1703
|
+
`,(0,c.jsx)(t.h3,{id:`6-tanstack-instance`,children:`6. TanStack instance`}),`
|
|
1704
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const table = useTable({
|
|
1705
|
+
features: foundationTableFeatures,
|
|
1706
|
+
data: tree.rows,
|
|
1707
|
+
columns,
|
|
1708
|
+
getRowId: (row) => row[TREE_TABLE_TRANSPOSED_ROW_ID_FIELD],
|
|
1709
|
+
getSubRows: (row) => row.children
|
|
1710
|
+
});
|
|
1711
|
+
`})}),`
|
|
1712
|
+
`,(0,c.jsx)(t.h3,{id:`диагностика-после-pipeline`,children:`Диагностика после pipeline`}),`
|
|
1713
|
+
`,(0,c.jsx)(t.p,{children:`Transposition создаёт собственные уникальные ids, поэтому обычный результат не должен содержать duplicates, orphans или cycles.`}),`
|
|
1714
|
+
`,(0,c.jsx)(t.p,{children:`Проверка полезна для раннего обнаружения неожиданного контракта:`}),`
|
|
1715
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`if (tree.duplicateRowIds.length > 0 || tree.orphanRowIds.length > 0 || tree.cyclicRowIds.length > 0) {
|
|
1716
|
+
console.warn("Некорректная иерархия таблицы", {
|
|
1717
|
+
duplicates: tree.duplicateRowIds,
|
|
1718
|
+
orphans: tree.orphanRowIds,
|
|
1719
|
+
cycles: tree.cyclicRowIds
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
`})}),`
|
|
1723
|
+
`,(0,c.jsx)(t.p,{children:`Не логируйте целые backend rows, если они содержат персональные или чувствительные данные.`}),`
|
|
1724
|
+
`,(0,c.jsx)(t.h2,{id:`controlled-state-и-сохранение-настроек`,children:`Controlled state и сохранение настроек`}),`
|
|
1725
|
+
`,(0,c.jsx)(t.h3,{id:`controlled-и-uncontrolled-простыми-словами`,children:`Controlled и uncontrolled простыми словами`}),`
|
|
1726
|
+
`,(0,c.jsx)(t.p,{children:`Uncontrolled state хранится внутри UI-компонента:`}),`
|
|
1727
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`<Table defaultColumnOrder={["name", "amount"]} />
|
|
1728
|
+
`})}),`
|
|
1729
|
+
`,(0,c.jsx)(t.p,{children:`Controlled state принадлежит host:`}),`
|
|
1730
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`<Table columnOrder={columnOrder} onColumnOrderChange={setColumnOrder} />
|
|
1731
|
+
`})}),`
|
|
1732
|
+
`,(0,c.jsx)(t.p,{children:`Lib helpers подходят обоим вариантам, но сами ничего не сохраняют.`}),`
|
|
1733
|
+
`,(0,c.jsx)(t.h3,{id:`восстановление-сохранённого-order`,children:`Восстановление сохранённого order`}),`
|
|
1734
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const actualColumnIds = columns.map(resolveStableColumnId).filter((id): id is string => id !== undefined);
|
|
1735
|
+
|
|
1736
|
+
const columnOrder = resolveTableColumnOrder({
|
|
1737
|
+
ids: actualColumnIds,
|
|
1738
|
+
order: persistedConfig.columnOrder
|
|
1739
|
+
});
|
|
1740
|
+
`})}),`
|
|
1741
|
+
`,(0,c.jsx)(t.p,{children:`Это внешняя restore-boundary: stale id удаляются, новые columns добавляются.`}),`
|
|
1742
|
+
`,(0,c.jsx)(t.h3,{id:`восстановление-sizing`,children:`Восстановление sizing`}),`
|
|
1743
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const columnSizing = normalizeTableColumnSizing(persistedConfig.columnSizing, 80);
|
|
1744
|
+
`})}),`
|
|
1745
|
+
`,(0,c.jsxs)(t.p,{children:[`После этого полезно удалить ключи несуществующих колонок на стороне config owner, потому что `,(0,c.jsx)(t.code,{children:`normalizeTableColumnSizing`}),` проверяет форму width, но не знает актуальный список колонок.`]}),`
|
|
1746
|
+
`,(0,c.jsx)(t.h3,{id:`сохраняйте-state-по-id`,children:`Сохраняйте state по id`}),`
|
|
1747
|
+
`,(0,c.jsx)(t.p,{children:`Хороший persisted shape:`}),`
|
|
1748
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type TablePreferences = {
|
|
1749
|
+
version: 1;
|
|
1750
|
+
columnOrder: string[];
|
|
1751
|
+
columnSizing: Record<string, number>;
|
|
1752
|
+
columnVisibility: Record<string, boolean>;
|
|
1753
|
+
};
|
|
1754
|
+
`})}),`
|
|
1755
|
+
`,(0,c.jsx)(t.p,{children:`Не сохраняйте:`}),`
|
|
1756
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1757
|
+
`,(0,c.jsx)(t.li,{children:`TanStack table instance;`}),`
|
|
1758
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`Map`}),` без явной сериализации;`]}),`
|
|
1759
|
+
`,(0,c.jsx)(t.li,{children:`functions из column definitions;`}),`
|
|
1760
|
+
`,(0,c.jsx)(t.li,{children:`React elements;`}),`
|
|
1761
|
+
`,(0,c.jsx)(t.li,{children:`DOM nodes;`}),`
|
|
1762
|
+
`,(0,c.jsx)(t.li,{children:`backend secrets;`}),`
|
|
1763
|
+
`,(0,c.jsx)(t.li,{children:`индекс строки как устойчивый id.`}),`
|
|
1764
|
+
`]}),`
|
|
1765
|
+
`,(0,c.jsx)(t.h3,{id:`версионируйте-config`,children:`Версионируйте config`}),`
|
|
1766
|
+
`,(0,c.jsx)(t.p,{children:`Если меняются column ids или семантика width, повышайте версию и выполняйте migration на restore-boundary.`}),`
|
|
1767
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type StoredTablePreferences = { version: 1; order: string[] } | { version: 2; columnOrder: string[] };
|
|
1768
|
+
`})}),`
|
|
1769
|
+
`,(0,c.jsx)(t.p,{children:`Не смешивайте normalization пользовательского draft с migration persisted payload. Draft resize/reorder должен сохранять введённое состояние буквально до явной save-boundary.`}),`
|
|
1770
|
+
`,(0,c.jsx)(t.h2,{id:`server-side-данные-и-пагинация`,children:`Server-side данные и пагинация`}),`
|
|
1771
|
+
`,(0,c.jsx)(t.h3,{id:`table-state-не-является-backend-query`,children:`Table state не является backend query`}),`
|
|
1772
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`columnOrder`}),`, `,(0,c.jsx)(t.code,{children:`columnSizing`}),`, `,(0,c.jsx)(t.code,{children:`rowSelection`}),` и expansion — локальный UI state.`]}),`
|
|
1773
|
+
`,(0,c.jsxs)(t.p,{children:[`Sorting/filter/paging конкретной OData entity принадлежит entity/API owner. Не передавайте `,(0,c.jsx)(t.code,{children:`TableColumnOrderState`}),` как `,(0,c.jsx)(t.code,{children:`$orderby`}),`: визуальный порядок колонок и порядок строк — разные понятия.`]}),`
|
|
1774
|
+
`,(0,c.jsx)(t.h3,{id:`новая-страница-данных`,children:`Новая страница данных`}),`
|
|
1775
|
+
`,(0,c.jsx)(t.p,{children:`После получения нового snapshot:`}),`
|
|
1776
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const availableRowIds = rows.map((row) => row.id);
|
|
1777
|
+
|
|
1778
|
+
const nextSelection = pruneTableRowSelection(selection, availableRowIds, selectionMode);
|
|
1779
|
+
`})}),`
|
|
1780
|
+
`,(0,c.jsx)(t.p,{children:`Это очищает selection удалённых или больше не загруженных строк.`}),`
|
|
1781
|
+
`,(0,c.jsx)(t.h3,{id:`infinite-loading`,children:`Infinite loading`}),`
|
|
1782
|
+
`,(0,c.jsxs)(t.p,{children:[`Если несколько страниц объединены в один массив, `,(0,c.jsx)(t.code,{children:`availableRowIds`}),` должен включать все загруженные уникальные строки.`]}),`
|
|
1783
|
+
`,(0,c.jsx)(t.p,{children:`Не выполняйте network request из cell renderer. Request должен запускаться query/loader/virtualizer boundary.`}),`
|
|
1784
|
+
`,(0,c.jsx)(t.h3,{id:`backend-grouping-и-transposition`,children:`Backend grouping и transposition`}),`
|
|
1785
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`transposeFlatRowsToTreeTableRows`}),` создаёт client-side presentation groups. Она не заменяет server grouping для большого набора:`]}),`
|
|
1786
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1787
|
+
`,(0,c.jsx)(t.li,{children:`helper видит только загруженные rows;`}),`
|
|
1788
|
+
`,(0,c.jsx)(t.li,{children:`total по одной странице не является total всего dataset;`}),`
|
|
1789
|
+
`,(0,c.jsx)(t.li,{children:`одинаковая группа на разных страницах может появляться частями;`}),`
|
|
1790
|
+
`,(0,c.jsx)(t.li,{children:`generated leaf id зависит от индекса объединённого массива.`}),`
|
|
1791
|
+
`]}),`
|
|
1792
|
+
`,(0,c.jsx)(t.p,{children:`Для аналитической таблицы предпочитайте backend totals и устойчивый server contract.`}),`
|
|
1793
|
+
`,(0,c.jsx)(t.h2,{id:`react-browser-и-ssr`,children:`React, browser и SSR`}),`
|
|
1794
|
+
`,(0,c.jsx)(t.h3,{id:`pure-helpers`,children:`Pure helpers`}),`
|
|
1795
|
+
`,(0,c.jsx)(t.p,{children:`Большинство функций можно вызывать в browser, Node.js и unit tests:`}),`
|
|
1796
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1797
|
+
`,(0,c.jsx)(t.li,{children:`layout/order/sizing;`}),`
|
|
1798
|
+
`,(0,c.jsx)(t.li,{children:`row/cell selection;`}),`
|
|
1799
|
+
`,(0,c.jsx)(t.li,{children:`OData column adapter;`}),`
|
|
1800
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),`;`]}),`
|
|
1801
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`transposeFlatRowsToTreeTableRows`}),`;`]}),`
|
|
1802
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`resolveTableLength`}),`.`]}),`
|
|
1803
|
+
`]}),`
|
|
1804
|
+
`,(0,c.jsx)(t.p,{children:`Они не выполняют I/O и не используют global state.`}),`
|
|
1805
|
+
`,(0,c.jsx)(t.h3,{id:`react-hook`,children:`React hook`}),`
|
|
1806
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useTableRowSelection`}),` требует React 19-compatible runtime и вызывается только:`]}),`
|
|
1807
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1808
|
+
`,(0,c.jsx)(t.li,{children:`на верхнем уровне function component;`}),`
|
|
1809
|
+
`,(0,c.jsx)(t.li,{children:`или на верхнем уровне custom hook.`}),`
|
|
1810
|
+
`]}),`
|
|
1811
|
+
`,(0,c.jsx)(t.p,{children:`Нельзя:`}),`
|
|
1812
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`if (enabled) {
|
|
1813
|
+
useTableRowSelection({
|
|
1814
|
+
availableRowIds,
|
|
1815
|
+
rowById,
|
|
1816
|
+
selectionMode: "multi"
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
`})}),`
|
|
1820
|
+
`,(0,c.jsx)(t.p,{children:`Нужно:`}),`
|
|
1821
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const selection = useTableRowSelection({
|
|
1822
|
+
availableRowIds,
|
|
1823
|
+
rowById,
|
|
1824
|
+
selectionMode: enabled ? "multi" : "none"
|
|
1825
|
+
});
|
|
1826
|
+
`})}),`
|
|
1827
|
+
`,(0,c.jsx)(t.h3,{id:`browser-helper`,children:`Browser helper`}),`
|
|
1828
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`isTableInteractiveElement`}),` использует `,(0,c.jsx)(t.code,{children:`Element`}),` и `,(0,c.jsx)(t.code,{children:`closest`}),` только если DOM доступен. При SSR/Node без DOM возвращается `,(0,c.jsx)(t.code,{children:`false`}),`.`]}),`
|
|
1829
|
+
`,(0,c.jsx)(t.p,{children:`Не используйте это поведение как security check. Это только UI-event helper.`}),`
|
|
1830
|
+
`,(0,c.jsx)(t.h3,{id:`hydration-и-стабильные-id`,children:`Hydration и стабильные id`}),`
|
|
1831
|
+
`,(0,c.jsxs)(t.p,{children:[`Если host использует SSR, `,(0,c.jsx)(t.code,{children:`getRowId`}),`, column ids и initial state должны быть детерминированы на server и client. Не используйте `,(0,c.jsx)(t.code,{children:`Math.random()`}),` или `,(0,c.jsx)(t.code,{children:`Date.now()`}),` при render.`]}),`
|
|
1832
|
+
`,(0,c.jsx)(t.p,{children:`Сам пакет не запускает SSR и не создаёт hydration boundary.`}),`
|
|
1833
|
+
`,(0,c.jsx)(t.h3,{id:`side-effects`,children:`Side effects`}),`
|
|
1834
|
+
`,(0,c.jsx)(t.p,{children:`У модулей нет обязательной глобальной инициализации, singleton или cleanup.`}),`
|
|
1835
|
+
`,(0,c.jsx)(t.p,{children:`Side effects появляются только у host:`}),`
|
|
1836
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1837
|
+
`,(0,c.jsx)(t.li,{children:`React state/effects;`}),`
|
|
1838
|
+
`,(0,c.jsx)(t.li,{children:`storage persistence;`}),`
|
|
1839
|
+
`,(0,c.jsx)(t.li,{children:`event listeners;`}),`
|
|
1840
|
+
`,(0,c.jsx)(t.li,{children:`DnD runtime;`}),`
|
|
1841
|
+
`,(0,c.jsx)(t.li,{children:`backend query.`}),`
|
|
1842
|
+
`]}),`
|
|
1843
|
+
`,(0,c.jsx)(t.h2,{id:`производительность`,children:`Производительность`}),`
|
|
1844
|
+
`,(0,c.jsx)(t.h3,{id:`стабильные-columns`,children:`Стабильные columns`}),`
|
|
1845
|
+
`,(0,c.jsx)(t.p,{children:`Не создавайте большой массив columns заново без необходимости:`}),`
|
|
1846
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const columns = useMemo<TableColumnDef<Row>[]>(() => buildColumns(metadata), [metadata]);
|
|
1847
|
+
`})}),`
|
|
1848
|
+
`,(0,c.jsxs)(t.p,{children:[`При этом не добавляйте `,(0,c.jsx)(t.code,{children:`useMemo`}),` механически к маленькой константе вне component.`]}),`
|
|
1849
|
+
`,(0,c.jsx)(t.h3,{id:`buildtreetablerows-1`,children:(0,c.jsx)(t.code,{children:`buildTreeTableRows`})}),`
|
|
1850
|
+
`,(0,c.jsxs)(t.p,{children:[`Ожидаемая сложность построения индексов и дерева близка к `,(0,c.jsx)(t.code,{children:`O(n)`}),` для `,(0,c.jsx)(t.code,{children:`n`}),` строк, потому что используются `,(0,c.jsx)(t.code,{children:`Map`}),` и `,(0,c.jsx)(t.code,{children:`Set`}),`.`]}),`
|
|
1851
|
+
`,(0,c.jsx)(t.p,{children:`Каждый принятый узел получает shallow copy. Для очень глубокого дерева используется рекурсия, поэтому патологическая цепочка в десятки тысяч уровней может переполнить call stack. Валидируйте разумную глубину внешних данных.`}),`
|
|
1852
|
+
`,(0,c.jsx)(t.p,{children:`В React вычисляйте дерево по identity входных данных:`}),`
|
|
1853
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const tree = useMemo(() => buildTreeTableRows(rows, hierarchy), [rows, hierarchy]);
|
|
1854
|
+
`})}),`
|
|
1855
|
+
`,(0,c.jsxs)(t.p,{children:[`Объект `,(0,c.jsx)(t.code,{children:`hierarchy`}),` тоже должен быть стабильным. Вынесите его за component или мемоизируйте.`]}),`
|
|
1856
|
+
`,(0,c.jsx)(t.h3,{id:`transposeflatrowstotreetablerows`,children:(0,c.jsx)(t.code,{children:`transposeFlatRowsToTreeTableRows`})}),`
|
|
1857
|
+
`,(0,c.jsxs)(t.p,{children:[`Сложность примерно `,(0,c.jsx)(t.code,{children:`O(rows × levels)`}),`.`]}),`
|
|
1858
|
+
`,(0,c.jsx)(t.p,{children:`Максимальный output без учёта дедупликации groups может приближаться к:`}),`
|
|
1859
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`rows × (hierarchyLevels + 1)
|
|
1860
|
+
`})}),`
|
|
1861
|
+
`,(0,c.jsx)(t.p,{children:`На практике общие synthetic paths переиспользуются.`}),`
|
|
1862
|
+
`,(0,c.jsx)(t.p,{children:`Не транспонируйте один и тот же большой snapshot в каждом cell render.`}),`
|
|
1863
|
+
`,(0,c.jsx)(t.h3,{id:`selection`,children:`Selection`}),`
|
|
1864
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1865
|
+
`,(0,c.jsxs)(t.li,{children:[`row prune создаёт `,(0,c.jsx)(t.code,{children:`Set`}),` доступных ids;`]}),`
|
|
1866
|
+
`,(0,c.jsx)(t.li,{children:`cell prune создаёт maps порядка и сортирует выбранные координаты;`}),`
|
|
1867
|
+
`,(0,c.jsx)(t.li,{children:`equality helpers строят списки/sets.`}),`
|
|
1868
|
+
`]}),`
|
|
1869
|
+
`,(0,c.jsx)(t.p,{children:`Для обычного пользовательского selection этого достаточно. Для выбора сотен тысяч строк нужна другая server/domain модель.`}),`
|
|
1870
|
+
`,(0,c.jsx)(t.h3,{id:`odata-columns`,children:`OData columns`}),`
|
|
1871
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`createTableColumnVisibilityFromODataMetadata`}),` и enrichment создают `,(0,c.jsx)(t.code,{children:`Map`}),` metadata по id. Вызывайте их при изменении metadata/columns, а не для каждой ячейки.`]}),`
|
|
1872
|
+
`,(0,c.jsx)(t.h2,{id:`тестирование-consumer-кода`,children:`Тестирование consumer-кода`}),`
|
|
1873
|
+
`,(0,c.jsx)(t.h3,{id:`pure-selection-test`,children:`Pure selection test`}),`
|
|
1874
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { describe, expect, it } from "vitest";
|
|
1875
|
+
|
|
1876
|
+
import { pruneTableRowSelection, toggleTableRowSelection } from "@ryuzaki13/react-foundation-lib/table";
|
|
1877
|
+
|
|
1878
|
+
describe("order selection", () => {
|
|
1879
|
+
it("оставляет одну строку в single mode", () => {
|
|
1880
|
+
const selected = toggleTableRowSelection({ old: true }, "new", "single");
|
|
1881
|
+
|
|
1882
|
+
expect(selected).toEqual({ new: true });
|
|
1883
|
+
});
|
|
1884
|
+
|
|
1885
|
+
it("удаляет отсутствующую строку", () => {
|
|
1886
|
+
expect(pruneTableRowSelection({ deleted: true }, ["actual"], "multi")).toEqual({});
|
|
1887
|
+
});
|
|
1888
|
+
});
|
|
1889
|
+
`})}),`
|
|
1890
|
+
`,(0,c.jsx)(t.h3,{id:`tree-diagnostics-test`,children:`Tree diagnostics test`}),`
|
|
1891
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`it("показывает orphan как root", () => {
|
|
1892
|
+
const result = buildTreeTableRows(
|
|
1893
|
+
[
|
|
1894
|
+
{
|
|
1895
|
+
id: "orphan",
|
|
1896
|
+
parentId: "missing",
|
|
1897
|
+
title: "Сирота"
|
|
1898
|
+
}
|
|
1899
|
+
],
|
|
1900
|
+
{
|
|
1901
|
+
getRowId: (row) => row.id,
|
|
1902
|
+
getParentRowId: (row) => row.parentId
|
|
1903
|
+
}
|
|
1904
|
+
);
|
|
1905
|
+
|
|
1906
|
+
expect(result.rootRowIds).toEqual(["orphan"]);
|
|
1907
|
+
expect(result.orphanRowIds).toEqual(["orphan"]);
|
|
1908
|
+
});
|
|
1909
|
+
`})}),`
|
|
1910
|
+
`,(0,c.jsx)(t.h3,{id:`transposition-test`,children:`Transposition test`}),`
|
|
1911
|
+
`,(0,c.jsx)(t.p,{children:`Проверяйте не полный generated JSON id, а отношения через constants:`}),`
|
|
1912
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const rows = transposeFlatRowsToTreeTableRows([{ REGION: "Урал", CITY: "Пермь", AMOUNT: 10 }], {
|
|
1913
|
+
hierarchyLevels: [{ columnId: "REGION" }, { columnId: "CITY" }]
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
const region = rows[0];
|
|
1917
|
+
const city = rows[1];
|
|
1918
|
+
const fact = rows[2];
|
|
1919
|
+
|
|
1920
|
+
expect(city[TREE_TABLE_TRANSPOSED_PARENT_ROW_ID_FIELD]).toBe(region[TREE_TABLE_TRANSPOSED_ROW_ID_FIELD]);
|
|
1921
|
+
|
|
1922
|
+
expect(fact[TREE_TABLE_TRANSPOSED_SYNTHETIC_FIELD]).toBe(false);
|
|
1923
|
+
`})}),`
|
|
1924
|
+
`,(0,c.jsx)(t.h3,{id:`dom-helper-test`,children:`DOM helper test`}),`
|
|
1925
|
+
`,(0,c.jsx)(t.p,{children:`Нужен jsdom:`}),`
|
|
1926
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// @vitest-environment jsdom
|
|
1927
|
+
|
|
1928
|
+
it("не активирует строку по клику на кнопку", () => {
|
|
1929
|
+
const button = document.createElement("button");
|
|
1930
|
+
|
|
1931
|
+
expect(isTableInteractiveElement(button)).toBe(true);
|
|
1932
|
+
});
|
|
1933
|
+
`})}),`
|
|
1934
|
+
`,(0,c.jsx)(t.h3,{id:`react-hook-test`,children:`React hook test`}),`
|
|
1935
|
+
`,(0,c.jsx)(t.p,{children:`Проверяйте пользовательское поведение, а не implementation details:`}),`
|
|
1936
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1937
|
+
`,(0,c.jsxs)(t.li,{children:[`изменение `,(0,c.jsx)(t.code,{children:`selectionMode`}),` очищает/сужает selection;`]}),`
|
|
1938
|
+
`,(0,c.jsxs)(t.li,{children:[`исчезнувший `,(0,c.jsx)(t.code,{children:`availableRowId`}),` удаляется;`]}),`
|
|
1939
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`canSelect: false`}),` блокирует активацию;`]}),`
|
|
1940
|
+
`,(0,c.jsx)(t.li,{children:`callback получает исходные rows;`}),`
|
|
1941
|
+
`,(0,c.jsxs)(t.li,{children:[`controlled `,(0,c.jsx)(t.code,{children:`selectedRowIds`}),` синхронизирует визуальный state.`]}),`
|
|
1942
|
+
`]}),`
|
|
1943
|
+
`,(0,c.jsxs)(t.p,{children:[`Используйте React testing library и `,(0,c.jsx)(t.code,{children:`act`}),`, если test напрямую вызывает state update.`]}),`
|
|
1944
|
+
`,(0,c.jsx)(t.h3,{id:`что-ещё-проверять-в-host`,children:`Что ещё проверять в host`}),`
|
|
1945
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
1946
|
+
`,(0,c.jsx)(t.li,{children:`row/column ids остаются стабильными после sort;`}),`
|
|
1947
|
+
`,(0,c.jsx)(t.li,{children:`DnD не двигает locked/pinned headers;`}),`
|
|
1948
|
+
`,(0,c.jsx)(t.li,{children:`button в ячейке не меняет row selection;`}),`
|
|
1949
|
+
`,(0,c.jsx)(t.li,{children:`keyboard activation доступна;`}),`
|
|
1950
|
+
`,(0,c.jsx)(t.li,{children:`tree orphan/cycle diagnostics наблюдаемы;`}),`
|
|
1951
|
+
`,(0,c.jsx)(t.li,{children:`synthetic rows не читаются как обычные business facts;`}),`
|
|
1952
|
+
`,(0,c.jsx)(t.li,{children:`server paging не выдаётся за полный dataset.`}),`
|
|
1953
|
+
`]}),`
|
|
1954
|
+
`,(0,c.jsxs)(t.h2,{id:`полная-карта-api-table`,children:[`Полная карта API `,(0,c.jsx)(t.code,{children:`table`})]}),`
|
|
1955
|
+
`,(0,c.jsx)(t.p,{children:`Все имена из этого раздела импортируются из:`}),`
|
|
1956
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`"@ryuzaki13/react-foundation-lib/table";
|
|
1957
|
+
`})}),`
|
|
1958
|
+
`,(0,c.jsx)(t.h3,{id:`layout`,children:`Layout`}),`
|
|
1959
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вид`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TableColumnLayoutItem<TColumnId, TWidth>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsxs)(t.td,{children:[`элемент `,(0,c.jsx)(t.code,{children:`{ id, width }`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ResolveTableColumnOrderArgs<TColumnId>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`аргументы восстановления порядка`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`BuildTableColumnLayoutArgs<TColumnId, TWidth>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsxs)(t.td,{children:[`ids и `,(0,c.jsx)(t.code,{children:`getWidth`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resolveTableColumnOrder`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`сохранённый order + новые актуальные ids`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`buildTableColumnLayout`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`строит layout по ids и width resolver`})]})]})]}),`
|
|
1960
|
+
`,(0,c.jsx)(t.h3,{id:`column-order`,children:`Column order`}),`
|
|
1961
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вид`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ReorderTableHeaderColumnsArgs`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`DnD input contract`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TableColumnOrderState`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsxs)(t.td,{children:[`mutable-массив `,(0,c.jsx)(t.code,{children:`string[]`}),` для state`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`normalizeTableColumnOrder`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`удаляет пустые и повторяющиеся id`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`reorderTableHeaderColumns`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`вычисляет следующий top-level order`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resolveReorderedTableHeaderColumns`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsxs)(t.td,{children:[`возвращает change или `,(0,c.jsx)(t.code,{children:`null`})]})]})]})]}),`
|
|
1962
|
+
`,(0,c.jsx)(t.h3,{id:`column-sizing`,children:`Column sizing`}),`
|
|
1963
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вид`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TableColumnSizingState`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`Record<columnId, widthPx>`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`DEFAULT_TABLE_COLUMN_MIN_WIDTH`})}),(0,c.jsx)(t.td,{children:`constant`}),(0,c.jsxs)(t.td,{children:[`default minimum `,(0,c.jsx)(t.code,{children:`60`}),` px`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`normalizeTableColumnWidth`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`finite integer width не меньше minimum`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`normalizeTableColumnSizing`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`очищает весь sizing state`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`patchTableColumnWidth`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`immutable add/update одной ширины`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`removeTableColumnWidth`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`immutable delete одной ширины`})]})]})]}),`
|
|
1964
|
+
`,(0,c.jsx)(t.h3,{id:`row-selection`,children:`Row selection`}),`
|
|
1965
|
+
`,(0,c.jsxs)(t.p,{children:[`| API | Вид | Назначение |
|
|
1966
|
+
| ----------------------------- | ---------- | -------------------------------------- | ------ | ------ |
|
|
1967
|
+
| `,(0,c.jsx)(t.code,{children:`TableSelectionMode`}),` | type | `,(0,c.jsx)(t.code,{children:`none | single | multi`}),` |
|
|
1968
|
+
| `,(0,c.jsx)(t.code,{children:`areTableRowSelectionsEqual`}),` | function | сравнивает активные row ids |
|
|
1969
|
+
| `,(0,c.jsx)(t.code,{children:`normalizeTableSelectionMode`}),` | function | normalizes unknown config value |
|
|
1970
|
+
| `,(0,c.jsx)(t.code,{children:`pruneTableRowSelection`}),` | function | удаляет недоступный/лишний selection |
|
|
1971
|
+
| `,(0,c.jsx)(t.code,{children:`toggleTableRowSelection`}),` | function | вычисляет state после активации |
|
|
1972
|
+
| `,(0,c.jsx)(t.code,{children:`useTableRowSelection`}),` | React hook | state, controlled sync и rows callback |`]}),`
|
|
1973
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`RowSelectionState`}),` принадлежит `,(0,c.jsx)(t.code,{children:`@tanstack/react-table`}),` и не реэкспортируется этим модулем.`]}),`
|
|
1974
|
+
`,(0,c.jsx)(t.h3,{id:`cell-selection`,children:`Cell selection`}),`
|
|
1975
|
+
`,(0,c.jsxs)(t.p,{children:[`| API | Вид | Назначение |
|
|
1976
|
+
| ------------------------------------------- | -------- | ------------------------------------ | ----------------- |
|
|
1977
|
+
| `,(0,c.jsx)(t.code,{children:`TableCellSelectionMode`}),` | type | cardinality выбора ячеек |
|
|
1978
|
+
| `,(0,c.jsx)(t.code,{children:`TableCellSelectionActivationMode`}),` | type | `,(0,c.jsx)(t.code,{children:`direct | primary-modifier`}),` |
|
|
1979
|
+
| `,(0,c.jsx)(t.code,{children:`TableCellCoordinates`}),` | type | `,(0,c.jsx)(t.code,{children:`rowId + columnId`}),` |
|
|
1980
|
+
| `,(0,c.jsx)(t.code,{children:`TableCellSelectionState`}),` | type | readonly-массив координат |
|
|
1981
|
+
| `,(0,c.jsx)(t.code,{children:`TableSelectionModifierEvent`}),` | type | минимальный modifier event contract |
|
|
1982
|
+
| `,(0,c.jsx)(t.code,{children:`areTableCellSelectionsEqual`}),` | function | set-equality координат |
|
|
1983
|
+
| `,(0,c.jsx)(t.code,{children:`pruneTableCellSelection`}),` | function | фильтрует, дедуплицирует и сортирует |
|
|
1984
|
+
| `,(0,c.jsx)(t.code,{children:`toggleTableCellSelection`}),` | function | включает/выключает координату |
|
|
1985
|
+
| `,(0,c.jsx)(t.code,{children:`normalizeTableCellSelectionActivationMode`}),` | function | нормализует activation config |
|
|
1986
|
+
| `,(0,c.jsx)(t.code,{children:`shouldActivateTableCellSelection`}),` | function | проверяет direct/Ctrl/Command flow |`]}),`
|
|
1987
|
+
`,(0,c.jsx)(t.h3,{id:`column-contracts`,children:`Column contracts`}),`
|
|
1988
|
+
`,(0,c.jsxs)(t.p,{children:[`| API | Вид | Назначение |
|
|
1989
|
+
| -------------------------------- | --------- | ------------------------------------------------- | ------ | ------ |
|
|
1990
|
+
| `,(0,c.jsx)(t.code,{children:`TableColumnAlign`}),` | type | `,(0,c.jsx)(t.code,{children:`left | center | right`}),` |
|
|
1991
|
+
| `,(0,c.jsx)(t.code,{children:`TableColumnFormattingMeta`}),` | type | runtime formatting config с optional id/role/type |
|
|
1992
|
+
| `,(0,c.jsx)(t.code,{children:`TableColumnFormattingMetaInput`}),` | type | custom formatting без auto id/role/type |
|
|
1993
|
+
| `,(0,c.jsx)(t.code,{children:`TableColumnMeta`}),` | interface | width, align, mergeDuplicates, formatting |
|
|
1994
|
+
| `,(0,c.jsx)(t.code,{children:`TableColumnDef<TData, TValue>`}),` | type | foundation TanStack column definition |
|
|
1995
|
+
| `,(0,c.jsx)(t.code,{children:`TableColumnVisibilityState`}),` | type | TanStack v9 visibility state |`]}),`
|
|
1996
|
+
`,(0,c.jsx)(t.h3,{id:`tanstack-foundation-types`,children:`TanStack foundation types`}),`
|
|
1997
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вид`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`foundationTableFeatures`})}),(0,c.jsx)(t.td,{children:`constant`}),(0,c.jsx)(t.td,{children:`единый runtime features object`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableFeatures`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`тип features object`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableInstance<TData>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`Table instance`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableRow<TData>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`runtime row`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableColumn<TData, TValue>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`runtime column`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableCell<TData, TValue>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`runtime cell`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableHeader<TData, TValue>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`runtime header`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`FoundationTableState`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`полное состояние features`})]})]})]}),`
|
|
1998
|
+
`,(0,c.jsx)(t.h3,{id:`odata-adapter`,children:`OData adapter`}),`
|
|
1999
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вид`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`CreateTableColumnsFromODataMetadataOptions`})}),(0,c.jsx)(t.td,{children:`interface`}),(0,c.jsx)(t.td,{children:`header/formatting resolvers`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`EnrichTableColumnsWithODataFormattingOptions`})}),(0,c.jsx)(t.td,{children:`interface`}),(0,c.jsx)(t.td,{children:`custom metadata lookup`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`CreateTableColumnVisibilityFromODataMetadataOptions`})}),(0,c.jsx)(t.td,{children:`interface`}),(0,c.jsx)(t.td,{children:`custom visibility resolver`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resolveTableColumnFormattingContextFromODataColumn`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`OData property → role/type`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resolveStableColumnId`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`explicit id или accessorKey`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createTableColumnVisibilityFromODataMetadata`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`стартовый visibility state`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createTableColumnsFromODataMetadata`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`metadata → generated columns`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`enrichTableColumnsWithODataFormatting`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`metadata → existing leaf columns`})]})]})]}),`
|
|
2000
|
+
`,(0,c.jsx)(t.h3,{id:`small-runtime-helpers`,children:`Small runtime helpers`}),`
|
|
2001
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вид`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`getTableColumnMeta`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsxs)(t.td,{children:[`читает `,(0,c.jsx)(t.code,{children:`column.meta`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`getTableColumnFormattingMeta`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsxs)(t.td,{children:[`читает `,(0,c.jsx)(t.code,{children:`column.meta.formatting`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resolveTableLength`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsxs)(t.td,{children:[`число → `,(0,c.jsx)(t.code,{children:`em`}),`, строка без изменений`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`isTableInteractiveElement`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`распознаёт nested interactive target`})]})]})]}),`
|
|
2002
|
+
`,(0,c.jsxs)(t.h2,{id:`полная-карта-api-tree-table`,children:[`Полная карта API `,(0,c.jsx)(t.code,{children:`tree-table`})]}),`
|
|
2003
|
+
`,(0,c.jsx)(t.p,{children:`Все имена из этого раздела импортируются из:`}),`
|
|
2004
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`"@ryuzaki13/react-foundation-lib/tree-table";
|
|
2005
|
+
`})}),`
|
|
2006
|
+
`,(0,c.jsx)(t.h3,{id:`построение-дерева`,children:`Построение дерева`}),`
|
|
2007
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вид`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TreeTableFlatHierarchy<TData>`})}),(0,c.jsx)(t.td,{children:`interface`}),(0,c.jsx)(t.td,{children:`getters row id и parent id`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TreeTableRowNode<TData>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`строка с optional recursive children`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TreeTableBuildResult<TData>`})}),(0,c.jsx)(t.td,{children:`interface`}),(0,c.jsx)(t.td,{children:`tree, indexes и diagnostics`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`buildTreeTableRows`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`flat id/parent rows → nested forest`})]})]})]}),`
|
|
2008
|
+
`,(0,c.jsx)(t.h3,{id:`транспонирование`,children:`Транспонирование`}),`
|
|
2009
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вид`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TreeTableTransposedRowMeta`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`пять служебных полей`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TreeTableTransposedRow<TData>`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`business row + service metadata`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TreeTableTransposeFlatRowsLevel`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`один hierarchy column level`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TreeTableTransposeFlatRowsOptions`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`массив levels`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`transposeFlatRowsToTreeTableRows`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`facts → synthetic groups + leaves`})]})]})]}),`
|
|
2010
|
+
`,(0,c.jsx)(t.h3,{id:`constants-служебных-полей`,children:`Constants служебных полей`}),`
|
|
2011
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Значение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_ROW_ID_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"__treeTableRowId"`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_PARENT_ROW_ID_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"__treeTableParentRowId"`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_LABEL_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"__treeTableLabel"`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_LEVEL_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"__treeTableLevel"`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_SYNTHETIC_FIELD`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"__treeTableSynthetic"`})})]})]})]}),`
|
|
2012
|
+
`,(0,c.jsx)(t.h2,{id:`типичные-ошибки`,children:`Типичные ошибки`}),`
|
|
2013
|
+
`,(0,c.jsx)(t.h3,{id:`импортировать-всё-из-package-root`,children:`Импортировать всё из package root`}),`
|
|
2014
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Неверно.
|
|
2015
|
+
import { TableColumnDef } from "@ryuzaki13/react-foundation-lib";
|
|
2016
|
+
`})}),`
|
|
2017
|
+
`,(0,c.jsxs)(t.p,{children:[`Используйте точный subpath и `,(0,c.jsx)(t.code,{children:`import type`}),` для типа.`]}),`
|
|
2018
|
+
`,(0,c.jsxs)(t.h3,{id:`ожидать-react-component-от-table`,children:[`Ожидать React component от `,(0,c.jsx)(t.code,{children:`table`})]}),`
|
|
2019
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`// Такого компонента в lib entrypoint нет.
|
|
2020
|
+
<Table data={rows} />
|
|
2021
|
+
`})}),`
|
|
2022
|
+
`,(0,c.jsx)(t.p,{children:`Визуальный primitive принадлежит UI-пакету или host.`}),`
|
|
2023
|
+
`,(0,c.jsx)(t.h3,{id:`использовать-индекс-как-row-id`,children:`Использовать индекс как row id`}),`
|
|
2024
|
+
`,(0,c.jsx)(t.p,{children:`После sort selection укажет на другую запись. Используйте business key.`}),`
|
|
2025
|
+
`,(0,c.jsx)(t.h3,{id:`мутировать-state`,children:`Мутировать state`}),`
|
|
2026
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`selection[rowId] = true;
|
|
2027
|
+
`})}),`
|
|
2028
|
+
`,(0,c.jsx)(t.p,{children:`Используйте helper или React setter с новым объектом.`}),`
|
|
2029
|
+
`,(0,c.jsx)(t.h3,{id:`путать-width-в-metadata-и-sizing-state`,children:`Путать width в metadata и sizing state`}),`
|
|
2030
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const column = {
|
|
2031
|
+
meta: {
|
|
2032
|
+
width: 120
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
`})}),`
|
|
2036
|
+
`,(0,c.jsxs)(t.p,{children:[`Здесь `,(0,c.jsx)(t.code,{children:`120em`}),`, а не `,(0,c.jsx)(t.code,{children:`120px`}),`. Resize px храните в `,(0,c.jsx)(t.code,{children:`TableColumnSizingState`}),`.`]}),`
|
|
2037
|
+
`,(0,c.jsxs)(t.h3,{id:`считать-normalizetablecolumnorder-полным-restore`,children:[`Считать `,(0,c.jsx)(t.code,{children:`normalizeTableColumnOrder`}),` полным restore`]}),`
|
|
2038
|
+
`,(0,c.jsxs)(t.p,{children:[`Она не знает актуальные columns и не удаляет stale id. Для restore используйте `,(0,c.jsx)(t.code,{children:`resolveTableColumnOrder`}),`.`]}),`
|
|
2039
|
+
`,(0,c.jsx)(t.h3,{id:`пытаться-переставить-pinned-header`,children:`Пытаться переставить pinned header`}),`
|
|
2040
|
+
`,(0,c.jsx)(t.p,{children:`Текущий helper блокирует любое перемещение, если active или over id pinned.`}),`
|
|
2041
|
+
`,(0,c.jsx)(t.h3,{id:`ожидать-одинаковый-toggle-у-row-и-cell-single-mode`,children:`Ожидать одинаковый toggle у row и cell single mode`}),`
|
|
2042
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
2043
|
+
`,(0,c.jsx)(t.li,{children:`single row повторным кликом не снимается;`}),`
|
|
2044
|
+
`,(0,c.jsx)(t.li,{children:`single cell повторной активацией снимается.`}),`
|
|
2045
|
+
`]}),`
|
|
2046
|
+
`,(0,c.jsx)(t.p,{children:`Это разные публичные семантики.`}),`
|
|
2047
|
+
`,(0,c.jsxs)(t.h3,{id:`передать-child-id-в-headerids`,children:[`Передать child id в `,(0,c.jsx)(t.code,{children:`headerIds`})]}),`
|
|
2048
|
+
`,(0,c.jsx)(t.p,{children:`Helper предназначен для top-level headers. Group child не должен быть самостоятельной DnD целью.`}),`
|
|
2049
|
+
`,(0,c.jsxs)(t.h3,{id:`забыть-rowbyid`,children:[`Забыть `,(0,c.jsx)(t.code,{children:`rowById`})]}),`
|
|
2050
|
+
`,(0,c.jsx)(t.p,{children:`Без актуальной записи hook не сможет вернуть исходный row в callback.`}),`
|
|
2051
|
+
`,(0,c.jsx)(t.h3,{id:`читать-synthetic-row-как-fact`,children:`Читать synthetic row как fact`}),`
|
|
2052
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const amount = row.AMOUNT;
|
|
2053
|
+
`})}),`
|
|
2054
|
+
`,(0,c.jsxs)(t.p,{children:[`Сначала проверьте `,(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_SYNTHETIC_FIELD`}),`.`]}),`
|
|
2055
|
+
`,(0,c.jsx)(t.h3,{id:`ожидать-totals-от-transpose`,children:`Ожидать totals от transpose`}),`
|
|
2056
|
+
`,(0,c.jsx)(t.p,{children:`Synthetic group rows не агрегируют measures.`}),`
|
|
2057
|
+
`,(0,c.jsx)(t.h3,{id:`полагаться-на-generated-leaf-id-после-reorder`,children:`Полагаться на generated leaf id после reorder`}),`
|
|
2058
|
+
`,(0,c.jsx)(t.p,{children:`Он содержит индекс input row и может измениться.`}),`
|
|
2059
|
+
`,(0,c.jsx)(t.h3,{id:`игнорировать-diagnostics-дерева`,children:`Игнорировать diagnostics дерева`}),`
|
|
2060
|
+
`,(0,c.jsx)(t.p,{children:`Orphans, duplicates и cycles не выбрасывают exception. Данные остаются видимыми, а проблема сообщается массивами result.`}),`
|
|
2061
|
+
`,(0,c.jsxs)(t.h3,{id:`использовать-children-как-business-field`,children:[`Использовать `,(0,c.jsx)(t.code,{children:`children`}),` как business field`]}),`
|
|
2062
|
+
`,(0,c.jsx)(t.p,{children:`Поле зарезервировано nested tree shape и может быть заменено.`}),`
|
|
2063
|
+
`,(0,c.jsxs)(t.h3,{id:`считать-expandbydefault-side-effect`,children:[`Считать `,(0,c.jsx)(t.code,{children:`expandByDefault`}),` side effect`]}),`
|
|
2064
|
+
`,(0,c.jsx)(t.p,{children:`Transpose его не применяет. Expansion принадлежит UI state.`}),`
|
|
2065
|
+
`,(0,c.jsx)(t.h3,{id:`транспонировать-уже-готовое-idparent-дерево`,children:`Транспонировать уже готовое id/parent дерево`}),`
|
|
2066
|
+
`,(0,c.jsxs)(t.p,{children:[`Если backend уже прислал отношения, сразу вызывайте `,(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),`. Transpose добавит ненужные synthetic nodes.`]}),`
|
|
2067
|
+
`,(0,c.jsx)(t.h3,{id:`выполнять-client-sorting-одной-server-page`,children:`Выполнять client sorting одной server page`}),`
|
|
2068
|
+
`,(0,c.jsx)(t.p,{children:`Пользователь увидит порядок только внутри загруженного фрагмента. Отправляйте sort backend owner.`}),`
|
|
2069
|
+
`,(0,c.jsx)(t.h2,{id:`частые-вопросы`,children:`Частые вопросы`}),`
|
|
2070
|
+
`,(0,c.jsx)(t.h3,{id:`почему-один-readme-описывает-два-импорта`,children:`Почему один README описывает два импорта?`}),`
|
|
2071
|
+
`,(0,c.jsx)(t.p,{children:`Потому что concepts и интеграция общие, но package boundaries разные. Это уменьшает дублирование объяснений и не создаёт новый barrel.`}),`
|
|
2072
|
+
`,(0,c.jsxs)(t.h3,{id:`можно-ли-импортировать-buildtreetablerows-из-table`,children:[`Можно ли импортировать `,(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),` из `,(0,c.jsx)(t.code,{children:`/table`}),`?`]}),`
|
|
2073
|
+
`,(0,c.jsxs)(t.p,{children:[`Нет. Используйте `,(0,c.jsx)(t.code,{children:`/tree-table`}),`.`]}),`
|
|
2074
|
+
`,(0,c.jsxs)(t.h3,{id:`можно-ли-импортировать-tablecolumndef-из-tree-table`,children:[`Можно ли импортировать `,(0,c.jsx)(t.code,{children:`TableColumnDef`}),` из `,(0,c.jsx)(t.code,{children:`/tree-table`}),`?`]}),`
|
|
2075
|
+
`,(0,c.jsxs)(t.p,{children:[`Нет. Используйте `,(0,c.jsx)(t.code,{children:`/table`}),`.`]}),`
|
|
2076
|
+
`,(0,c.jsx)(t.h3,{id:`нужно-ли-использовать-tanstack-table`,children:`Нужно ли использовать TanStack Table?`}),`
|
|
2077
|
+
`,(0,c.jsxs)(t.p,{children:[`Для `,(0,c.jsx)(t.code,{children:`foundationTableFeatures`}),` и `,(0,c.jsx)(t.code,{children:`FoundationTable*`}),` типов — да. Pure order/sizing/selection/tree helpers можно использовать с другим UI, если их state contract подходит.`]}),`
|
|
2078
|
+
`,(0,c.jsxs)(t.h3,{id:`нужно-ли-использовать-react-для-tree-table`,children:[`Нужно ли использовать React для `,(0,c.jsx)(t.code,{children:`tree-table`}),`?`]}),`
|
|
2079
|
+
`,(0,c.jsxs)(t.p,{children:[`Нет. Обе tree functions pure. React нужен только downstream UI и hook из `,(0,c.jsx)(t.code,{children:`/table`}),`.`]}),`
|
|
2080
|
+
`,(0,c.jsxs)(t.h3,{id:`почему-buildtreetablerows-возвращает-forest-а-не-один-root`,children:[`Почему `,(0,c.jsx)(t.code,{children:`buildTreeTableRows`}),` возвращает forest, а не один root?`]}),`
|
|
2081
|
+
`,(0,c.jsxs)(t.p,{children:[`В реальных данных может быть несколько roots, orphan или циклическая компонента. `,(0,c.jsx)(t.code,{children:`rows`}),` поэтому всегда массив.`]}),`
|
|
2082
|
+
`,(0,c.jsx)(t.h3,{id:`почему-orphan-не-удаляется`,children:`Почему orphan не удаляется?`}),`
|
|
2083
|
+
`,(0,c.jsx)(t.p,{children:`Потеря строки скрыла бы проблему и данные. Helper поднимает её в root и отдельно сообщает id.`}),`
|
|
2084
|
+
`,(0,c.jsx)(t.h3,{id:`почему-duplicate-использует-первое-вхождение`,children:`Почему duplicate использует первое вхождение?`}),`
|
|
2085
|
+
`,(0,c.jsx)(t.p,{children:`Это детерминированное правило, сохраняющее исходный порядок. Но duplicate всё равно означает нарушение data contract.`}),`
|
|
2086
|
+
`,(0,c.jsxs)(t.h3,{id:`можно-ли-изменить-имя-поля-children`,children:[`Можно ли изменить имя поля `,(0,c.jsx)(t.code,{children:`children`}),`?`]}),`
|
|
2087
|
+
`,(0,c.jsx)(t.p,{children:`Нет, параметра для этого нет. Если нужен другой shape, преобразуйте result на host boundary или используйте собственный builder.`}),`
|
|
2088
|
+
`,(0,c.jsxs)(t.h3,{id:`можно-ли-передать-readonlymap-в-usetablerowselection`,children:[`Можно ли передать `,(0,c.jsx)(t.code,{children:`ReadonlyMap`}),` в `,(0,c.jsx)(t.code,{children:`useTableRowSelection`}),`?`]}),`
|
|
2089
|
+
`,(0,c.jsxs)(t.p,{children:[`Сигнатура ожидает `,(0,c.jsx)(t.code,{children:`Map<string, TData>`}),`. Создайте обычный `,(0,c.jsx)(t.code,{children:`Map`}),` и не мутируйте его после передачи в текущем render.`]}),`
|
|
2090
|
+
`,(0,c.jsxs)(t.h3,{id:`почему-prunetablerowselection-удалил-выбор-другой-страницы`,children:[`Почему `,(0,c.jsx)(t.code,{children:`pruneTableRowSelection`}),` удалил выбор другой страницы?`]}),`
|
|
2091
|
+
`,(0,c.jsxs)(t.p,{children:[`Потому что id не было в `,(0,c.jsx)(t.code,{children:`availableRowIds`}),`. Определите корректную область availability для своего сценария.`]}),`
|
|
2092
|
+
`,(0,c.jsxs)(t.h3,{id:`почему-resolvestablecolumnid-вернул-undefined`,children:[`Почему `,(0,c.jsx)(t.code,{children:`resolveStableColumnId`}),` вернул `,(0,c.jsx)(t.code,{children:`undefined`}),`?`]}),`
|
|
2093
|
+
`,(0,c.jsxs)(t.p,{children:[`У колонки нет непустого строкового `,(0,c.jsx)(t.code,{children:`id`}),` и строкового `,(0,c.jsx)(t.code,{children:`accessorKey`}),`. Для `,(0,c.jsx)(t.code,{children:`accessorFn`}),` добавьте explicit id.`]}),`
|
|
2094
|
+
`,(0,c.jsx)(t.h3,{id:`odata-adapter-выполняет-formatting-значения`,children:`OData adapter выполняет formatting значения?`}),`
|
|
2095
|
+
`,(0,c.jsxs)(t.p,{children:[`Нет. Он только добавляет formatting metadata. Значение форматирует downstream runtime из модуля `,(0,c.jsx)(t.code,{children:`formatters`}),`/UI.`]}),`
|
|
2096
|
+
`,(0,c.jsx)(t.h3,{id:`odata-adapter-выполняет-sorting-и-filtering`,children:`OData adapter выполняет sorting и filtering?`}),`
|
|
2097
|
+
`,(0,c.jsx)(t.p,{children:`Нет. Он создаёт column definitions и visibility. Server operations принадлежат API/feature owner.`}),`
|
|
2098
|
+
`,(0,c.jsx)(t.h3,{id:`почему-numeric-odata-property-становится-measure`,children:`Почему numeric OData property становится measure?`}),`
|
|
2099
|
+
`,(0,c.jsx)(t.p,{children:`Adapter принудительно классифицирует поддерживаемые числовые types как measures, чтобы получить ожидаемые formatting и right alignment.`}),`
|
|
2100
|
+
`,(0,c.jsxs)(t.h3,{id:`можно-ли-сохранить-tablecolumndef-в-json`,children:[`Можно ли сохранить `,(0,c.jsx)(t.code,{children:`TableColumnDef[]`}),` в JSON?`]}),`
|
|
2101
|
+
`,(0,c.jsx)(t.p,{children:`Обычно нет: columns могут содержать functions и React renderers. Сохраняйте только serializable state по column id.`}),`
|
|
2102
|
+
`,(0,c.jsx)(t.h3,{id:`можно-ли-использовать-пробелы-в-rowcolumn-id`,children:`Можно ли использовать пробелы в row/column id?`}),`
|
|
2103
|
+
`,(0,c.jsx)(t.p,{children:`Технически да, но helpers не выполняют общий trimming. Канонические id без случайных пробелов безопаснее для persistence и equality.`}),`
|
|
2104
|
+
`,(0,c.jsx)(t.h3,{id:`почему-whitespace-level-создаёт-group`,children:`Почему whitespace level создаёт group?`}),`
|
|
2105
|
+
`,(0,c.jsxs)(t.p,{children:[`Пустым считается только `,(0,c.jsx)(t.code,{children:`null`}),`, `,(0,c.jsx)(t.code,{children:`undefined`}),` и точная пустая строка. Нормализуйте business data до transpose, если пробелы должны считаться пустотой.`]}),`
|
|
2106
|
+
`,(0,c.jsxs)(t.h3,{id:`tree_table_transposed_level_field-равен-глубине`,children:[(0,c.jsx)(t.code,{children:`TREE_TABLE_TRANSPOSED_LEVEL_FIELD`}),` равен глубине?`]}),`
|
|
2107
|
+
`,(0,c.jsx)(t.p,{children:`Только когда все предыдущие levels непустые. При пропущенном промежуточном level он сохраняет исходный индекс level. Для визуального depth используйте tree runtime.`}),`
|
|
2108
|
+
`,(0,c.jsx)(t.h3,{id:`можно-ли-выбирать-synthetic-groups`,children:`Можно ли выбирать synthetic groups?`}),`
|
|
2109
|
+
`,(0,c.jsxs)(t.p,{children:[`Технически да: у них есть row id. Разрешать это или нет — business/UI policy. Проверяйте synthetic flag в `,(0,c.jsx)(t.code,{children:`getRowCanSelect`}),`.`]}),`
|
|
2110
|
+
`,(0,c.jsx)(t.h3,{id:`можно-ли-использовать-diagnostics-как-полную-graph-validation`,children:`Можно ли использовать diagnostics как полную graph validation?`}),`
|
|
2111
|
+
`,(0,c.jsx)(t.p,{children:`Нет. Они предназначены для устойчивого UI-построения. Для строгой проверки всех участников cycles, duplicate occurrences и схемы данных нужен отдельный validator.`}),`
|
|
2112
|
+
`,(0,c.jsx)(t.h3,{id:`есть-ли-глобальная-инициализация-или-cleanup`,children:`Есть ли глобальная инициализация или cleanup?`}),`
|
|
2113
|
+
`,(0,c.jsx)(t.p,{children:`Нет. Pure helpers stateless. React hook живёт в lifecycle component.`}),`
|
|
2114
|
+
`,(0,c.jsx)(t.h2,{id:`связанные-модули`,children:`Связанные модули`}),`
|
|
2115
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
2116
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/array`}),` — массивы, нормализация и reorder primitives;`]}),`
|
|
2117
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/formatters`}),` — форматирование значений и table pipeline;`]}),`
|
|
2118
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/formulas`}),` — client formulas для table rows;`]}),`
|
|
2119
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/odata-service`}),` — OData metadata и query-option builders;`]}),`
|
|
2120
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/virtualizer`}),` — дозагрузка infinite data по virtual range;`]}),`
|
|
2121
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-api/odata`}),` — transport и TanStack Query operations;`]}),`
|
|
2122
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-ui/table`}),` — визуальная плоская таблица;`]}),`
|
|
2123
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-ui/tree-table`}),` — визуальная древовидная таблица.`]}),`
|
|
2124
|
+
`]})]})}function s(e={}){let{wrapper:t}={...n(),...e.components};return t?(0,c.jsx)(t,{...e,children:(0,c.jsx)(o,{...e})}):o(e)}var c;function l(){return(l=e((()=>{c=t(),r(),a()})))()}l();export{s as default};
|