@jackuait/blok 0.6.0-beta.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blok.mjs +2 -2
- package/dist/chunks/{blok-bzxy6Olq.mjs → blok-BAh1rvUC.mjs} +3410 -2927
- package/dist/chunks/i18next-B47TKgbU.mjs +1303 -0
- package/dist/chunks/{i18next-loader-CzL6YHyQ.mjs → i18next-loader-CHtGO6IK.mjs} +2 -2
- package/dist/chunks/{index-DSSrx_Co.mjs → index-DBWWKrDe.mjs} +12 -12
- package/dist/chunks/{inline-tool-convert-D4SXxjDd.mjs → inline-tool-convert-DduRc0fF.mjs} +1467 -951
- package/dist/chunks/{messages-BogRq8lt.mjs → messages-0AbcLMLm.mjs} +6 -0
- package/dist/chunks/{messages-DJDG55Vq.mjs → messages-0E0AkrNu.mjs} +6 -0
- package/dist/{messages-DnXLrlHh.mjs → chunks/messages-4v4MuVEc.mjs} +6 -0
- package/dist/chunks/{messages-DnIhyAJk.mjs → messages-62v-CLC-.mjs} +6 -0
- package/dist/chunks/{messages-Dzwxv9v1.mjs → messages-8DeO60Oo.mjs} +6 -0
- package/dist/chunks/{messages-B1Aww8q7.mjs → messages-8IPXkrDl.mjs} +6 -0
- package/dist/{messages-uKX8WBaD.mjs → chunks/messages-96kNZDll.mjs} +6 -0
- package/dist/chunks/{messages-BL0tXcDf.mjs → messages-B1FZ8lxU.mjs} +6 -0
- package/dist/{messages-DBn76jVV.mjs → chunks/messages-B217znr-.mjs} +8 -2
- package/dist/{messages-DT4dP5uK.mjs → chunks/messages-B8WNljW3.mjs} +6 -0
- package/dist/chunks/{messages-BdeLo0N9.mjs → messages-BC8IN4Bf.mjs} +6 -0
- package/dist/{messages-CZygwLwM.mjs → chunks/messages-BI43k_BD.mjs} +6 -0
- package/dist/{messages-CzTufCHu.mjs → chunks/messages-BJ6zrz2j.mjs} +6 -0
- package/dist/{messages-BoJc_p1r.mjs → chunks/messages-BUl_Rcnj.mjs} +6 -0
- package/dist/chunks/{messages-CnwibSvh.mjs → messages-BZlmVRwn.mjs} +6 -0
- package/dist/{messages-C2htQ_3F.mjs → chunks/messages-BcpCubnC.mjs} +6 -0
- package/dist/{messages-D5C3J9qr.mjs → chunks/messages-Bm-E4iRC.mjs} +6 -0
- package/dist/chunks/{messages-BELRf6DU.mjs → messages-C4jL-90N.mjs} +6 -0
- package/dist/chunks/{messages-1fC8IMyX.mjs → messages-CDBLbUOQ.mjs} +6 -0
- package/dist/chunks/{messages-7QoX8DkW.mjs → messages-CH4hrauY.mjs} +6 -0
- package/dist/{messages-Dz9L52ol.mjs → chunks/messages-CRJ_mchV.mjs} +6 -0
- package/dist/chunks/{messages-JELdtT6E.mjs → messages-CW4c4cRk.mjs} +6 -0
- package/dist/chunks/{messages-CKI54h6O.mjs → messages-C_4otP7U.mjs} +6 -0
- package/dist/{messages-R3hUSvr3.mjs → chunks/messages-CfiyT2Wi.mjs} +6 -0
- package/dist/{messages-CJdUsQ-c.mjs → chunks/messages-CgTq3QhU.mjs} +6 -0
- package/dist/chunks/{messages-D1Hv8XGo.mjs → messages-Chb7k3Rg.mjs} +6 -0
- package/dist/{messages-Q7AO_FLv.mjs → chunks/messages-Cjjo7yHR.mjs} +6 -0
- package/dist/{messages-C99mq906.mjs → chunks/messages-Cl6ayUaq.mjs} +6 -0
- package/dist/chunks/{messages-Diu6jAaR.mjs → messages-CmR9ftc_.mjs} +6 -0
- package/dist/chunks/{messages-LPVfA-8K.mjs → messages-Cr49Nt3U.mjs} +6 -0
- package/dist/chunks/{messages-DqM1LFg5.mjs → messages-Cr94GzbX.mjs} +6 -0
- package/dist/{messages-BWF-zUpY.mjs → chunks/messages-CrCYPCk3.mjs} +6 -0
- package/dist/{messages-D-ZtY5v0.mjs → chunks/messages-Cs8zmZ3L.mjs} +6 -0
- package/dist/{messages-DprmQg6V.mjs → chunks/messages-CzK0LEhb.mjs} +6 -0
- package/dist/chunks/{messages-BSbjsyHY.mjs → messages-D00x4S8o.mjs} +6 -0
- package/dist/chunks/{messages-Xq8UmkVs.mjs → messages-D1mn7Zd5.mjs} +6 -0
- package/dist/chunks/{messages-BC86qLvI.mjs → messages-D2NOpHn9.mjs} +6 -0
- package/dist/{messages-kep5wtm4.mjs → chunks/messages-D4qqwVgQ.mjs} +6 -0
- package/dist/chunks/{messages-7W4d0DwD.mjs → messages-D5S1Dnpm.mjs} +6 -0
- package/dist/{messages-CY8_RyFE.mjs → chunks/messages-D7u2bmP2.mjs} +6 -0
- package/dist/chunks/{messages-BFG6Wlgy.mjs → messages-D85FqxgY.mjs} +6 -0
- package/dist/{messages-DLfR5bMd.mjs → chunks/messages-D9ndgBnU.mjs} +6 -0
- package/dist/{messages-CVw84KdI.mjs → chunks/messages-DDTQgImT.mjs} +6 -0
- package/dist/{messages-_ErNTNhk.mjs → chunks/messages-DH_jBeED.mjs} +6 -0
- package/dist/chunks/{messages-CMkNSDTo.mjs → messages-DRXWF0PV.mjs} +6 -0
- package/dist/chunks/{messages-BYyy6Wqf.mjs → messages-DVQvl8Qj.mjs} +6 -0
- package/dist/chunks/{messages-CznZadDf.mjs → messages-DXktiao_.mjs} +6 -0
- package/dist/chunks/{messages-DhLKYm2j.mjs → messages-DdK-nFGm.mjs} +6 -0
- package/dist/chunks/{messages-BMXCuEKO.mjs → messages-DlJbPF2T.mjs} +6 -0
- package/dist/chunks/{messages-CvGLfqmV.mjs → messages-DnVlmiNT.mjs} +6 -0
- package/dist/{messages-Z9nEU2xK.mjs → chunks/messages-DviiFSv2.mjs} +6 -0
- package/dist/chunks/{messages-BB5z9Uba.mjs → messages-DzqM3Fel.mjs} +6 -0
- package/dist/{messages-w7v1GNaE.mjs → chunks/messages-Dzzn6XoD.mjs} +6 -0
- package/dist/{messages-CqWJcCbY.mjs → chunks/messages-GSByFygY.mjs} +6 -0
- package/dist/chunks/{messages-_ncGrKHh.mjs → messages-L_kl2Qvh.mjs} +6 -0
- package/dist/chunks/{messages-BrPFGbM-.mjs → messages-Phkd7XmE.mjs} +6 -0
- package/dist/{messages-BU2nlrLK.mjs → chunks/messages-RonBBCnh.mjs} +6 -0
- package/dist/{messages-Bmu_S7GM.mjs → chunks/messages-VDriF5Qy.mjs} +6 -0
- package/dist/{messages-CLhcMlTc.mjs → chunks/messages-ZjUAIWb1.mjs} +6 -0
- package/dist/{messages-9SihnaXQ.mjs → chunks/messages-b1EdvUm0.mjs} +6 -0
- package/dist/{messages-DvFLX36Q.mjs → chunks/messages-begYOTgC.mjs} +6 -0
- package/dist/{messages-BMv4xwIr.mjs → chunks/messages-jrncnb-H.mjs} +6 -0
- package/dist/{messages-D5iv1Kox.mjs → chunks/messages-nefz1S71.mjs} +6 -0
- package/dist/{messages-CQwpzUFp.mjs → chunks/messages-ucTVgS5G.mjs} +6 -0
- package/dist/chunks/{messages-DBRw-7Zc.mjs → messages-v3GipbFl.mjs} +6 -0
- package/dist/{messages-C9eaarcK.mjs → chunks/messages-wmi-iFkH.mjs} +6 -0
- package/dist/chunks/{messages-O5tQus_0.mjs → messages-yHcs38yI.mjs} +6 -0
- package/dist/full.mjs +2 -2
- package/dist/locales.mjs +90 -84
- package/dist/{messages-BogRq8lt.mjs → messages-0AbcLMLm.mjs} +6 -0
- package/dist/{messages-DJDG55Vq.mjs → messages-0E0AkrNu.mjs} +6 -0
- package/dist/{chunks/messages-DnXLrlHh.mjs → messages-4v4MuVEc.mjs} +6 -0
- package/dist/{messages-DnIhyAJk.mjs → messages-62v-CLC-.mjs} +6 -0
- package/dist/{messages-Dzwxv9v1.mjs → messages-8DeO60Oo.mjs} +6 -0
- package/dist/{messages-B1Aww8q7.mjs → messages-8IPXkrDl.mjs} +6 -0
- package/dist/{chunks/messages-uKX8WBaD.mjs → messages-96kNZDll.mjs} +6 -0
- package/dist/{messages-BL0tXcDf.mjs → messages-B1FZ8lxU.mjs} +6 -0
- package/dist/{chunks/messages-DBn76jVV.mjs → messages-B217znr-.mjs} +8 -2
- package/dist/{chunks/messages-DT4dP5uK.mjs → messages-B8WNljW3.mjs} +6 -0
- package/dist/{messages-BdeLo0N9.mjs → messages-BC8IN4Bf.mjs} +6 -0
- package/dist/{chunks/messages-CZygwLwM.mjs → messages-BI43k_BD.mjs} +6 -0
- package/dist/{chunks/messages-CzTufCHu.mjs → messages-BJ6zrz2j.mjs} +6 -0
- package/dist/{chunks/messages-BoJc_p1r.mjs → messages-BUl_Rcnj.mjs} +6 -0
- package/dist/{messages-CnwibSvh.mjs → messages-BZlmVRwn.mjs} +6 -0
- package/dist/{chunks/messages-C2htQ_3F.mjs → messages-BcpCubnC.mjs} +6 -0
- package/dist/{chunks/messages-D5C3J9qr.mjs → messages-Bm-E4iRC.mjs} +6 -0
- package/dist/{messages-BELRf6DU.mjs → messages-C4jL-90N.mjs} +6 -0
- package/dist/{messages-1fC8IMyX.mjs → messages-CDBLbUOQ.mjs} +6 -0
- package/dist/{messages-7QoX8DkW.mjs → messages-CH4hrauY.mjs} +6 -0
- package/dist/{chunks/messages-Dz9L52ol.mjs → messages-CRJ_mchV.mjs} +6 -0
- package/dist/{messages-JELdtT6E.mjs → messages-CW4c4cRk.mjs} +6 -0
- package/dist/{messages-CKI54h6O.mjs → messages-C_4otP7U.mjs} +6 -0
- package/dist/{chunks/messages-R3hUSvr3.mjs → messages-CfiyT2Wi.mjs} +6 -0
- package/dist/{chunks/messages-CJdUsQ-c.mjs → messages-CgTq3QhU.mjs} +6 -0
- package/dist/{messages-D1Hv8XGo.mjs → messages-Chb7k3Rg.mjs} +6 -0
- package/dist/{chunks/messages-Q7AO_FLv.mjs → messages-Cjjo7yHR.mjs} +6 -0
- package/dist/{chunks/messages-C99mq906.mjs → messages-Cl6ayUaq.mjs} +6 -0
- package/dist/{messages-Diu6jAaR.mjs → messages-CmR9ftc_.mjs} +6 -0
- package/dist/{messages-LPVfA-8K.mjs → messages-Cr49Nt3U.mjs} +6 -0
- package/dist/{messages-DqM1LFg5.mjs → messages-Cr94GzbX.mjs} +6 -0
- package/dist/{chunks/messages-BWF-zUpY.mjs → messages-CrCYPCk3.mjs} +6 -0
- package/dist/{chunks/messages-D-ZtY5v0.mjs → messages-Cs8zmZ3L.mjs} +6 -0
- package/dist/{chunks/messages-DprmQg6V.mjs → messages-CzK0LEhb.mjs} +6 -0
- package/dist/{messages-BSbjsyHY.mjs → messages-D00x4S8o.mjs} +6 -0
- package/dist/{messages-Xq8UmkVs.mjs → messages-D1mn7Zd5.mjs} +6 -0
- package/dist/{messages-BC86qLvI.mjs → messages-D2NOpHn9.mjs} +6 -0
- package/dist/{chunks/messages-kep5wtm4.mjs → messages-D4qqwVgQ.mjs} +6 -0
- package/dist/{messages-7W4d0DwD.mjs → messages-D5S1Dnpm.mjs} +6 -0
- package/dist/{chunks/messages-CY8_RyFE.mjs → messages-D7u2bmP2.mjs} +6 -0
- package/dist/{messages-BFG6Wlgy.mjs → messages-D85FqxgY.mjs} +6 -0
- package/dist/{chunks/messages-DLfR5bMd.mjs → messages-D9ndgBnU.mjs} +6 -0
- package/dist/{chunks/messages-CVw84KdI.mjs → messages-DDTQgImT.mjs} +6 -0
- package/dist/{chunks/messages-_ErNTNhk.mjs → messages-DH_jBeED.mjs} +6 -0
- package/dist/{messages-CMkNSDTo.mjs → messages-DRXWF0PV.mjs} +6 -0
- package/dist/{messages-BYyy6Wqf.mjs → messages-DVQvl8Qj.mjs} +6 -0
- package/dist/{messages-CznZadDf.mjs → messages-DXktiao_.mjs} +6 -0
- package/dist/{messages-DhLKYm2j.mjs → messages-DdK-nFGm.mjs} +6 -0
- package/dist/{messages-BMXCuEKO.mjs → messages-DlJbPF2T.mjs} +6 -0
- package/dist/{messages-CvGLfqmV.mjs → messages-DnVlmiNT.mjs} +6 -0
- package/dist/{chunks/messages-Z9nEU2xK.mjs → messages-DviiFSv2.mjs} +6 -0
- package/dist/{messages-BB5z9Uba.mjs → messages-DzqM3Fel.mjs} +6 -0
- package/dist/{chunks/messages-w7v1GNaE.mjs → messages-Dzzn6XoD.mjs} +6 -0
- package/dist/{chunks/messages-CqWJcCbY.mjs → messages-GSByFygY.mjs} +6 -0
- package/dist/{messages-_ncGrKHh.mjs → messages-L_kl2Qvh.mjs} +6 -0
- package/dist/{messages-BrPFGbM-.mjs → messages-Phkd7XmE.mjs} +6 -0
- package/dist/{chunks/messages-BU2nlrLK.mjs → messages-RonBBCnh.mjs} +6 -0
- package/dist/{chunks/messages-Bmu_S7GM.mjs → messages-VDriF5Qy.mjs} +6 -0
- package/dist/{chunks/messages-CLhcMlTc.mjs → messages-ZjUAIWb1.mjs} +6 -0
- package/dist/{chunks/messages-9SihnaXQ.mjs → messages-b1EdvUm0.mjs} +6 -0
- package/dist/{chunks/messages-DvFLX36Q.mjs → messages-begYOTgC.mjs} +6 -0
- package/dist/{chunks/messages-BMv4xwIr.mjs → messages-jrncnb-H.mjs} +6 -0
- package/dist/{chunks/messages-D5iv1Kox.mjs → messages-nefz1S71.mjs} +6 -0
- package/dist/{chunks/messages-CQwpzUFp.mjs → messages-ucTVgS5G.mjs} +6 -0
- package/dist/{messages-DBRw-7Zc.mjs → messages-v3GipbFl.mjs} +6 -0
- package/dist/{chunks/messages-C9eaarcK.mjs → messages-wmi-iFkH.mjs} +6 -0
- package/dist/{messages-O5tQus_0.mjs → messages-yHcs38yI.mjs} +6 -0
- package/dist/tools.mjs +3194 -1690
- package/dist/vendor.LICENSE.txt +109 -109
- package/package.json +43 -57
- package/src/blok.ts +12 -0
- package/src/components/__module.ts +21 -0
- package/src/components/block/api.ts +17 -0
- package/src/components/block/style-manager.ts +6 -2
- package/src/components/block/tool-renderer.ts +33 -30
- package/src/components/blocks.ts +132 -15
- package/src/components/i18n/locales/am/messages.json +6 -0
- package/src/components/i18n/locales/ar/messages.json +6 -0
- package/src/components/i18n/locales/az/messages.json +6 -0
- package/src/components/i18n/locales/bg/messages.json +6 -0
- package/src/components/i18n/locales/bn/messages.json +6 -0
- package/src/components/i18n/locales/bs/messages.json +6 -0
- package/src/components/i18n/locales/cs/messages.json +6 -0
- package/src/components/i18n/locales/da/messages.json +6 -0
- package/src/components/i18n/locales/de/messages.json +6 -0
- package/src/components/i18n/locales/dv/messages.json +6 -0
- package/src/components/i18n/locales/el/messages.json +6 -0
- package/src/components/i18n/locales/en/messages.json +6 -0
- package/src/components/i18n/locales/es/messages.json +6 -0
- package/src/components/i18n/locales/et/messages.json +6 -0
- package/src/components/i18n/locales/fa/messages.json +6 -0
- package/src/components/i18n/locales/fi/messages.json +6 -0
- package/src/components/i18n/locales/fil/messages.json +6 -0
- package/src/components/i18n/locales/fr/messages.json +6 -0
- package/src/components/i18n/locales/gu/messages.json +6 -0
- package/src/components/i18n/locales/he/messages.json +6 -0
- package/src/components/i18n/locales/hi/messages.json +6 -0
- package/src/components/i18n/locales/hr/messages.json +6 -0
- package/src/components/i18n/locales/hu/messages.json +6 -0
- package/src/components/i18n/locales/hy/messages.json +6 -0
- package/src/components/i18n/locales/id/messages.json +6 -0
- package/src/components/i18n/locales/it/messages.json +6 -0
- package/src/components/i18n/locales/ja/messages.json +6 -0
- package/src/components/i18n/locales/ka/messages.json +6 -0
- package/src/components/i18n/locales/km/messages.json +6 -0
- package/src/components/i18n/locales/kn/messages.json +6 -0
- package/src/components/i18n/locales/ko/messages.json +6 -0
- package/src/components/i18n/locales/ku/messages.json +6 -0
- package/src/components/i18n/locales/lo/messages.json +6 -0
- package/src/components/i18n/locales/lt/messages.json +6 -0
- package/src/components/i18n/locales/lv/messages.json +6 -0
- package/src/components/i18n/locales/mk/messages.json +6 -0
- package/src/components/i18n/locales/ml/messages.json +6 -0
- package/src/components/i18n/locales/mn/messages.json +6 -0
- package/src/components/i18n/locales/mr/messages.json +6 -0
- package/src/components/i18n/locales/ms/messages.json +6 -0
- package/src/components/i18n/locales/my/messages.json +6 -0
- package/src/components/i18n/locales/ne/messages.json +6 -0
- package/src/components/i18n/locales/nl/messages.json +6 -0
- package/src/components/i18n/locales/no/messages.json +6 -0
- package/src/components/i18n/locales/pa/messages.json +6 -0
- package/src/components/i18n/locales/pl/messages.json +6 -0
- package/src/components/i18n/locales/ps/messages.json +6 -0
- package/src/components/i18n/locales/pt/messages.json +6 -0
- package/src/components/i18n/locales/ro/messages.json +6 -0
- package/src/components/i18n/locales/ru/messages.json +6 -0
- package/src/components/i18n/locales/sd/messages.json +6 -0
- package/src/components/i18n/locales/si/messages.json +6 -0
- package/src/components/i18n/locales/sk/messages.json +6 -0
- package/src/components/i18n/locales/sl/messages.json +6 -0
- package/src/components/i18n/locales/sq/messages.json +6 -0
- package/src/components/i18n/locales/sr/messages.json +6 -0
- package/src/components/i18n/locales/sv/messages.json +6 -0
- package/src/components/i18n/locales/sw/messages.json +6 -0
- package/src/components/i18n/locales/ta/messages.json +6 -0
- package/src/components/i18n/locales/te/messages.json +6 -0
- package/src/components/i18n/locales/th/messages.json +6 -0
- package/src/components/i18n/locales/tr/messages.json +6 -0
- package/src/components/i18n/locales/ug/messages.json +6 -0
- package/src/components/i18n/locales/uk/messages.json +6 -0
- package/src/components/i18n/locales/ur/messages.json +6 -0
- package/src/components/i18n/locales/vi/messages.json +6 -0
- package/src/components/i18n/locales/yi/messages.json +6 -0
- package/src/components/i18n/locales/zh/messages.json +6 -0
- package/src/components/icons/index.ts +17 -0
- package/src/components/inline-tools/inline-tool-link.ts +1 -1
- package/src/components/inline-tools/inline-tool-marker.ts +737 -0
- package/src/components/inline-tools/utils/formatting-range-utils.ts +6 -3
- package/src/components/inline-tools/utils/marker-dom-utils.ts +17 -0
- package/src/components/modules/api/blocks.ts +34 -9
- package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +75 -29
- package/src/components/modules/blockEvents/index.ts +13 -5
- package/src/components/modules/blockManager/blockManager.ts +81 -2
- package/src/components/modules/blockManager/hierarchy.ts +20 -2
- package/src/components/modules/blockManager/operations.ts +70 -35
- package/src/components/modules/blockManager/repository.ts +22 -0
- package/src/components/modules/blockManager/types.ts +3 -1
- package/src/components/modules/blockManager/yjs-sync.ts +173 -39
- package/src/components/modules/blockSelection.ts +3 -0
- package/src/components/modules/crossBlockSelection.ts +11 -3
- package/src/components/modules/drag/preview/DragPreview.ts +10 -2
- package/src/components/modules/drag/target/DropTargetDetector.ts +100 -11
- package/src/components/modules/drag/utils/drag.constants.ts +1 -1
- package/src/components/modules/normalizeInlineImages.ts +263 -0
- package/src/components/modules/paste/google-docs-preprocessor.ts +197 -0
- package/src/components/modules/paste/handlers/base.ts +43 -2
- package/src/components/modules/paste/handlers/html-handler.ts +1 -1
- package/src/components/modules/paste/handlers/index.ts +1 -0
- package/src/components/modules/paste/handlers/table-cells-handler.ts +104 -0
- package/src/components/modules/paste/index.ts +20 -3
- package/src/components/modules/readonly.ts +8 -2
- package/src/components/modules/rectangleSelection.ts +5 -2
- package/src/components/modules/renderer.ts +35 -0
- package/src/components/modules/saver.ts +52 -2
- package/src/components/modules/toolbar/blockSettings.ts +52 -44
- package/src/components/modules/toolbar/index.ts +124 -17
- package/src/components/modules/toolbar/inline/index.ts +4 -4
- package/src/components/modules/toolbar/plus-button.ts +3 -3
- package/src/components/modules/toolbar/settings-toggler.ts +3 -3
- package/src/components/modules/toolbar/styles.ts +7 -7
- package/src/components/modules/ui.ts +6 -6
- package/src/components/modules/uiControllers/controllers/blockHover.ts +16 -2
- package/src/components/modules/uiControllers/handlers/touch.ts +83 -10
- package/src/components/modules/yjs/block-observer.ts +9 -3
- package/src/components/modules/yjs/document-store.ts +10 -7
- package/src/components/modules/yjs/types.ts +8 -6
- package/src/components/modules/yjs/undo-history.ts +90 -11
- package/src/components/selection/fake-background/shadows.ts +1 -1
- package/src/components/shared/color-picker.ts +211 -0
- package/src/components/shared/color-presets.ts +25 -0
- package/src/components/ui/toolbox.ts +19 -11
- package/src/components/utils/color-mapping.ts +241 -0
- package/src/components/utils/notifier/draw.ts +9 -9
- package/src/components/utils/placeholder.ts +24 -8
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +2 -2
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +14 -12
- package/src/components/utils/popover/components/search-input/search-input.const.ts +2 -2
- package/src/components/utils/popover/popover-abstract.ts +27 -3
- package/src/components/utils/popover/popover-desktop.ts +26 -3
- package/src/components/utils/popover/popover-inline.ts +14 -1
- package/src/components/utils/popover/popover-mobile.ts +4 -4
- package/src/components/utils/popover/popover.const.ts +2 -2
- package/src/components/utils/sanitizer.ts +24 -3
- package/src/components/utils/tw.ts +17 -5
- package/src/stories/Header.stories.ts +106 -0
- package/src/stories/MarkerColors.stories.ts +730 -0
- package/src/stories/Popover.stories.ts +1 -3
- package/src/stories/Table.stories.ts +1662 -0
- package/src/styles/main.css +207 -37
- package/src/tools/header/index.ts +1 -1
- package/src/tools/index.ts +3 -1
- package/src/tools/list/caret-manager.ts +28 -10
- package/src/tools/list/constants.ts +2 -2
- package/src/tools/list/dom-builder.ts +3 -3
- package/src/tools/list/static-configs.ts +0 -1
- package/src/tools/paragraph/index.ts +15 -7
- package/src/tools/table/core/table-commands.ts +99 -0
- package/src/tools/table/core/table-controller.ts +231 -0
- package/src/tools/table/core/table-events.ts +102 -0
- package/src/tools/table/index.ts +1070 -174
- package/src/tools/table/ownership/table-event-broker.ts +74 -0
- package/src/tools/table/ownership/table-ownership-registry.ts +126 -0
- package/src/tools/table/table-add-controls.ts +85 -15
- package/src/tools/table/table-cell-blocks.ts +336 -38
- package/src/tools/table/table-cell-clipboard.ts +415 -0
- package/src/tools/table/table-cell-color-picker.ts +34 -0
- package/src/tools/table/table-cell-selection.ts +264 -15
- package/src/tools/table/table-core.ts +3 -42
- package/src/tools/table/table-heading-toggle.ts +2 -2
- package/src/tools/table/table-model.ts +623 -0
- package/src/tools/table/table-operations.ts +59 -78
- package/src/tools/table/table-resize.ts +15 -11
- package/src/tools/table/table-restrictions.ts +69 -3
- package/src/tools/table/table-row-col-action-handler.ts +22 -7
- package/src/tools/table/table-row-col-controls.ts +129 -12
- package/src/tools/table/table-row-col-drag.ts +14 -0
- package/src/tools/table/table-scroll-haze.ts +152 -0
- package/src/tools/table/types.ts +22 -1
- package/src/tools/table/view/table-cell-blocks-adapter.ts +47 -0
- package/src/variants/blok-minimum.ts +13 -0
- package/types/api/block.d.ts +13 -0
- package/types/api/blocks.d.ts +16 -0
- package/types/tools/table.d.ts +2 -0
- package/dist/chunks/i18next-CugVlwWp.mjs +0 -1292
- package/src/tools/table/data-normalizer.ts +0 -32
package/src/tools/table/index.ts
CHANGED
|
@@ -9,21 +9,31 @@ import type {
|
|
|
9
9
|
import type { ToolSanitizerConfig } from '../../../types/configs/sanitizer-config';
|
|
10
10
|
import { DATA_ATTR } from '../../components/constants';
|
|
11
11
|
import { IconTable } from '../../components/icons';
|
|
12
|
+
import { mapToNearestPresetColor } from '../../components/utils/color-mapping';
|
|
12
13
|
import { twMerge } from '../../components/utils/tw';
|
|
13
14
|
|
|
14
15
|
import { TableAddControls } from './table-add-controls';
|
|
15
|
-
import { TableCellBlocks } from './table-cell-blocks';
|
|
16
|
+
import { TableCellBlocks, CELL_BLOCKS_ATTR } from './table-cell-blocks';
|
|
17
|
+
import {
|
|
18
|
+
serializeCellsToClipboard,
|
|
19
|
+
buildClipboardHtml,
|
|
20
|
+
buildClipboardPlainText,
|
|
21
|
+
parseClipboardHtml,
|
|
22
|
+
parseGenericHtmlTable,
|
|
23
|
+
isDefaultBlack,
|
|
24
|
+
} from './table-cell-clipboard';
|
|
25
|
+
import type { CellColorMode } from './table-cell-color-picker';
|
|
16
26
|
import { TableCellSelection } from './table-cell-selection';
|
|
17
|
-
import { TableGrid } from './table-core';
|
|
27
|
+
import { TableGrid, ROW_ATTR, CELL_ATTR } from './table-core';
|
|
18
28
|
import {
|
|
29
|
+
applyCellColors,
|
|
19
30
|
applyPixelWidths,
|
|
20
31
|
computeHalfAvgWidth,
|
|
21
32
|
computeInitialColWidth,
|
|
22
|
-
deleteColumnWithBlockCleanup,
|
|
23
|
-
deleteRowWithBlockCleanup,
|
|
24
33
|
enableScrollOverflow,
|
|
25
34
|
getBlockIdsInColumn,
|
|
26
35
|
getBlockIdsInRow,
|
|
36
|
+
getCellPosition,
|
|
27
37
|
isColumnEmpty,
|
|
28
38
|
isRowEmpty,
|
|
29
39
|
mountCellBlocksReadOnly,
|
|
@@ -35,12 +45,15 @@ import {
|
|
|
35
45
|
updateHeadingColumnStyles,
|
|
36
46
|
updateHeadingStyles,
|
|
37
47
|
} from './table-operations';
|
|
48
|
+
import { TableModel } from './table-model';
|
|
38
49
|
import { TableResize } from './table-resize';
|
|
39
50
|
import { executeRowColAction } from './table-row-col-action-handler';
|
|
40
51
|
import type { PendingHighlight } from './table-row-col-action-handler';
|
|
41
52
|
import { TableRowColControls } from './table-row-col-controls';
|
|
42
53
|
import type { RowColAction } from './table-row-col-controls';
|
|
43
|
-
import
|
|
54
|
+
import { registerAdditionalRestrictedTools } from './table-restrictions';
|
|
55
|
+
import { TableScrollHaze } from './table-scroll-haze';
|
|
56
|
+
import type { ClipboardBlockData, LegacyCellContent, TableCellsClipboard, TableData, TableConfig } from './types';
|
|
44
57
|
|
|
45
58
|
const DEFAULT_ROWS = 3;
|
|
46
59
|
const DEFAULT_COLS = 3;
|
|
@@ -52,6 +65,12 @@ const WRAPPER_CLASSES = [
|
|
|
52
65
|
|
|
53
66
|
const WRAPPER_EDIT_CLASSES = [
|
|
54
67
|
'relative',
|
|
68
|
+
'after:content-[""]',
|
|
69
|
+
'after:absolute',
|
|
70
|
+
'after:-bottom-10',
|
|
71
|
+
'after:left-0',
|
|
72
|
+
'after:right-0',
|
|
73
|
+
'after:h-10',
|
|
55
74
|
];
|
|
56
75
|
|
|
57
76
|
/**
|
|
@@ -62,25 +81,134 @@ export class Table implements BlockTool {
|
|
|
62
81
|
private api: API;
|
|
63
82
|
private readOnly: boolean;
|
|
64
83
|
private config: TableConfig;
|
|
65
|
-
private
|
|
84
|
+
private initialContent: LegacyCellContent[][] | null = null;
|
|
66
85
|
private grid: TableGrid;
|
|
86
|
+
private model: TableModel;
|
|
67
87
|
private resize: TableResize | null = null;
|
|
68
88
|
private addControls: TableAddControls | null = null;
|
|
69
89
|
private rowColControls: TableRowColControls | null = null;
|
|
70
90
|
private cellBlocks: TableCellBlocks | null = null;
|
|
71
91
|
private cellSelection: TableCellSelection | null = null;
|
|
92
|
+
private scrollHaze: TableScrollHaze | null = null;
|
|
72
93
|
private element: HTMLDivElement | null = null;
|
|
94
|
+
private gridElement: HTMLElement | null = null;
|
|
95
|
+
private scrollContainer: HTMLDivElement | null = null;
|
|
96
|
+
private gripOverlay: HTMLDivElement | null = null;
|
|
73
97
|
private blockId: string | undefined;
|
|
74
98
|
private pendingHighlight: PendingHighlight | null = null;
|
|
75
99
|
private isNewTable = false;
|
|
100
|
+
private unregisterRestrictedTools: (() => void) | null = null;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generation counter for setData calls.
|
|
104
|
+
* Incremented at the start of each setData; checked before expensive operations
|
|
105
|
+
* (DOM rebuild, initializeCells) to bail out if a newer call has started.
|
|
106
|
+
* Prevents orphaned blocks when rapid undo/redo triggers overlapping setData calls.
|
|
107
|
+
*/
|
|
108
|
+
private setDataGeneration = 0;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Depth counter for structural operations (add/delete/move row/col).
|
|
112
|
+
* When > 0, TableCellBlocks defers handleBlockMutation events to prevent
|
|
113
|
+
* event cascade corruption during multi-step structural changes.
|
|
114
|
+
*/
|
|
115
|
+
private structuralOpDepth = 0;
|
|
76
116
|
|
|
77
117
|
constructor({ data, config, api, readOnly, block }: BlockToolConstructorOptions<TableData, TableConfig>) {
|
|
78
118
|
this.api = api;
|
|
79
119
|
this.readOnly = readOnly;
|
|
80
120
|
this.config = config ?? {};
|
|
81
|
-
|
|
121
|
+
const normalized = normalizeTableData(data, this.config);
|
|
122
|
+
|
|
123
|
+
this.initialContent = normalized.content;
|
|
82
124
|
this.grid = new TableGrid({ readOnly });
|
|
125
|
+
this.model = new TableModel(normalized);
|
|
83
126
|
this.blockId = block?.id;
|
|
127
|
+
|
|
128
|
+
if (this.config.restrictedTools !== undefined) {
|
|
129
|
+
this.unregisterRestrictedTools = registerAdditionalRestrictedTools(this.config.restrictedTools);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Execute a function within a structural operation lock.
|
|
135
|
+
* While active, block-changed events are deferred in TableCellBlocks.
|
|
136
|
+
*
|
|
137
|
+
* @param fn - The structural operation to execute
|
|
138
|
+
* @param discard - If true, discard deferred events (for full rebuilds like setData/onPaste).
|
|
139
|
+
* If false (default), replay deferred events after the operation.
|
|
140
|
+
*/
|
|
141
|
+
private runStructuralOp<T>(fn: () => T, discard = false): T {
|
|
142
|
+
this.structuralOpDepth++;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
return fn();
|
|
146
|
+
} finally {
|
|
147
|
+
this.structuralOpDepth--;
|
|
148
|
+
|
|
149
|
+
const shouldFlush = this.structuralOpDepth === 0;
|
|
150
|
+
|
|
151
|
+
if (shouldFlush && discard) {
|
|
152
|
+
this.cellBlocks?.discardDeferredEvents();
|
|
153
|
+
}
|
|
154
|
+
if (shouldFlush && !discard) {
|
|
155
|
+
this.cellBlocks?.flushDeferredEvents();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Execute a structural operation within a Yjs transaction.
|
|
162
|
+
* Combines the structural op lock (event deferral) with Yjs undo grouping.
|
|
163
|
+
* Used for interactive operations that should be a single undo entry.
|
|
164
|
+
*
|
|
165
|
+
* @param fn - The structural operation to execute
|
|
166
|
+
* @param discard - If true, discard deferred events (forwarded to runStructuralOp)
|
|
167
|
+
*/
|
|
168
|
+
private runTransactedStructuralOp<T>(fn: () => T, discard = false): T {
|
|
169
|
+
if (!this.api.blocks.transact) {
|
|
170
|
+
return this.runStructuralOp(fn, discard);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const ref = { current: undefined as T | undefined };
|
|
174
|
+
|
|
175
|
+
this.api.blocks.transact(() => {
|
|
176
|
+
ref.current = this.runStructuralOp(fn, discard);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return ref.current as T;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Tear down all visual subsystems (resize, add-controls, row/col-controls,
|
|
184
|
+
* cell-selection). Called before DOM rebuild in setData/onPaste and during
|
|
185
|
+
* destroy(). Does NOT tear down cellBlocks — that has special Yjs handling.
|
|
186
|
+
*/
|
|
187
|
+
private teardownSubsystems(): void {
|
|
188
|
+
this.resize?.destroy();
|
|
189
|
+
this.resize = null;
|
|
190
|
+
this.addControls?.destroy();
|
|
191
|
+
this.addControls = null;
|
|
192
|
+
this.rowColControls?.destroy();
|
|
193
|
+
this.rowColControls = null;
|
|
194
|
+
this.cellSelection?.destroy();
|
|
195
|
+
this.cellSelection = null;
|
|
196
|
+
this.scrollHaze?.destroy();
|
|
197
|
+
this.scrollHaze = null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Initialize all visual subsystems on a grid element.
|
|
202
|
+
* Shared by rendered(), setData(), and onPaste() to ensure consistent
|
|
203
|
+
* subsystem initialization order.
|
|
204
|
+
*/
|
|
205
|
+
private initSubsystems(gridEl: HTMLElement): void {
|
|
206
|
+
this.initResize(gridEl);
|
|
207
|
+
this.initAddControls(gridEl);
|
|
208
|
+
this.initRowColControls(gridEl);
|
|
209
|
+
this.initCellSelection(gridEl);
|
|
210
|
+
this.initGridPasteListener(gridEl);
|
|
211
|
+
this.initScrollHaze();
|
|
84
212
|
}
|
|
85
213
|
|
|
86
214
|
public static get toolbox(): ToolboxConfig {
|
|
@@ -118,40 +246,95 @@ export class Table implements BlockTool {
|
|
|
118
246
|
};
|
|
119
247
|
}
|
|
120
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Ensure a scroll container exists between the wrapper and the grid.
|
|
251
|
+
* Creates one on demand (e.g. when the first resize converts percent → pixel mode).
|
|
252
|
+
*/
|
|
253
|
+
private ensureScrollContainer(): HTMLDivElement {
|
|
254
|
+
if (this.scrollContainer) {
|
|
255
|
+
return this.scrollContainer;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const sc = document.createElement('div');
|
|
259
|
+
|
|
260
|
+
sc.setAttribute('data-blok-table-scroll', '');
|
|
261
|
+
|
|
262
|
+
const grid = this.gridElement;
|
|
263
|
+
|
|
264
|
+
if (grid && this.element) {
|
|
265
|
+
this.element.insertBefore(sc, grid);
|
|
266
|
+
sc.appendChild(grid);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this.scrollContainer = sc;
|
|
270
|
+
|
|
271
|
+
return sc;
|
|
272
|
+
}
|
|
273
|
+
|
|
121
274
|
public render(): HTMLDivElement {
|
|
122
275
|
const wrapper = document.createElement('div');
|
|
123
276
|
|
|
124
|
-
wrapper.className = twMerge(WRAPPER_CLASSES, !this.readOnly && WRAPPER_EDIT_CLASSES
|
|
277
|
+
wrapper.className = twMerge(WRAPPER_CLASSES, !this.readOnly && WRAPPER_EDIT_CLASSES);
|
|
125
278
|
wrapper.setAttribute(DATA_ATTR.tool, 'table');
|
|
126
279
|
|
|
127
280
|
if (this.readOnly) {
|
|
128
281
|
wrapper.setAttribute('data-blok-table-readonly', '');
|
|
129
282
|
}
|
|
130
283
|
|
|
131
|
-
this.isNewTable = this.
|
|
284
|
+
this.isNewTable = (this.initialContent?.length ?? 0) === 0;
|
|
285
|
+
|
|
286
|
+
const rows = this.initialContent?.length || this.config.rows || DEFAULT_ROWS;
|
|
287
|
+
const cols = this.initialContent?.reduce((max, row) => Math.max(max, row?.length ?? 0), 0) || this.config.cols || DEFAULT_COLS;
|
|
288
|
+
|
|
289
|
+
const gridEl = this.grid.createGrid(rows, cols, this.model.colWidths);
|
|
290
|
+
|
|
291
|
+
if ((this.initialContent?.length ?? 0) > 0) {
|
|
292
|
+
this.grid.fillGrid(gridEl, this.initialContent ?? []);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (this.model.colWidths) {
|
|
296
|
+
applyPixelWidths(gridEl, this.model.colWidths);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
this.gridElement = gridEl;
|
|
132
300
|
|
|
133
|
-
|
|
134
|
-
|
|
301
|
+
if (this.model.colWidths || !this.readOnly) {
|
|
302
|
+
const sc = document.createElement('div');
|
|
135
303
|
|
|
136
|
-
|
|
304
|
+
sc.setAttribute('data-blok-table-scroll', '');
|
|
137
305
|
|
|
138
|
-
|
|
139
|
-
|
|
306
|
+
const overflowClasses = this.model.colWidths ? SCROLL_OVERFLOW_CLASSES : [];
|
|
307
|
+
|
|
308
|
+
sc.classList.add(...overflowClasses);
|
|
309
|
+
|
|
310
|
+
sc.appendChild(gridEl);
|
|
311
|
+
wrapper.appendChild(sc);
|
|
312
|
+
this.scrollContainer = sc;
|
|
313
|
+
} else {
|
|
314
|
+
wrapper.appendChild(gridEl);
|
|
315
|
+
this.scrollContainer = null;
|
|
140
316
|
}
|
|
141
317
|
|
|
142
|
-
if (this.
|
|
143
|
-
|
|
318
|
+
if (!this.readOnly) {
|
|
319
|
+
const overlay = document.createElement('div');
|
|
320
|
+
|
|
321
|
+
overlay.setAttribute('data-blok-table-grip-overlay', '');
|
|
322
|
+
overlay.style.position = 'absolute';
|
|
323
|
+
overlay.style.inset = '0';
|
|
324
|
+
overlay.style.pointerEvents = 'none';
|
|
325
|
+
overlay.style.zIndex = '3';
|
|
326
|
+
wrapper.appendChild(overlay);
|
|
327
|
+
this.gripOverlay = overlay;
|
|
144
328
|
}
|
|
145
329
|
|
|
146
|
-
wrapper.appendChild(gridEl);
|
|
147
330
|
this.element = wrapper;
|
|
148
331
|
|
|
149
|
-
if (this.
|
|
150
|
-
updateHeadingStyles(this.
|
|
332
|
+
if (this.model.withHeadings) {
|
|
333
|
+
updateHeadingStyles(this.gridElement, this.model.withHeadings);
|
|
151
334
|
}
|
|
152
335
|
|
|
153
|
-
if (this.
|
|
154
|
-
updateHeadingColumnStyles(this.
|
|
336
|
+
if (this.model.withHeadingColumn) {
|
|
337
|
+
updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
|
|
155
338
|
}
|
|
156
339
|
|
|
157
340
|
if (!this.readOnly) {
|
|
@@ -163,40 +346,63 @@ export class Table implements BlockTool {
|
|
|
163
346
|
}
|
|
164
347
|
|
|
165
348
|
public rendered(): void {
|
|
166
|
-
if (!this.element) {
|
|
349
|
+
if (!this.element || this.initialContent === null) {
|
|
167
350
|
return;
|
|
168
351
|
}
|
|
169
352
|
|
|
170
|
-
const gridEl = this.
|
|
353
|
+
const gridEl = this.gridElement;
|
|
171
354
|
|
|
172
355
|
if (!gridEl) {
|
|
173
356
|
return;
|
|
174
357
|
}
|
|
175
358
|
|
|
359
|
+
const content = this.initialContent;
|
|
360
|
+
|
|
361
|
+
this.initialContent = null;
|
|
362
|
+
|
|
176
363
|
if (this.readOnly) {
|
|
177
|
-
mountCellBlocksReadOnly(gridEl, this.
|
|
364
|
+
mountCellBlocksReadOnly(gridEl, content, this.api, this.blockId ?? '');
|
|
365
|
+
applyCellColors(gridEl, this.model.snapshot().content);
|
|
366
|
+
this.initScrollHaze();
|
|
178
367
|
|
|
179
368
|
return;
|
|
180
369
|
}
|
|
181
370
|
|
|
182
|
-
this.
|
|
371
|
+
this.runTransactedStructuralOp(() => {
|
|
372
|
+
const initializedContent = this.cellBlocks?.initializeCells(content) ?? content;
|
|
183
373
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
374
|
+
// When a new table is created with empty content, the DOM grid already has
|
|
375
|
+
// the correct dimensions but the model has zero rows. Pre-populate the
|
|
376
|
+
// model with an empty grid so populateNewCells can sync blocks via
|
|
377
|
+
// addBlockToCell (which requires valid row/col bounds).
|
|
378
|
+
const contentForModel = this.isNewTable && initializedContent.length === 0
|
|
379
|
+
? Array.from(gridEl.querySelectorAll(`[${ROW_ATTR}]`), (row) => {
|
|
380
|
+
const cellCount = row.querySelectorAll(`[${CELL_ATTR}]`).length;
|
|
381
|
+
|
|
382
|
+
return Array.from({ length: cellCount }, () => ({ blocks: [] as string[] }));
|
|
383
|
+
})
|
|
384
|
+
: initializedContent;
|
|
385
|
+
|
|
386
|
+
this.model.replaceAll({
|
|
387
|
+
...this.model.snapshot(),
|
|
388
|
+
content: contentForModel,
|
|
389
|
+
});
|
|
187
390
|
|
|
188
|
-
|
|
189
|
-
|
|
391
|
+
if (this.isNewTable) {
|
|
392
|
+
populateNewCells(gridEl, this.cellBlocks);
|
|
393
|
+
}
|
|
394
|
+
}, true);
|
|
190
395
|
|
|
191
|
-
|
|
396
|
+
if (this.model.initialColWidth === undefined) {
|
|
397
|
+
const widths = this.model.colWidths ?? readPixelWidths(gridEl);
|
|
398
|
+
|
|
399
|
+
this.model.setInitialColWidth(widths.length > 0
|
|
192
400
|
? computeInitialColWidth(widths)
|
|
193
|
-
: undefined;
|
|
401
|
+
: undefined);
|
|
194
402
|
}
|
|
195
403
|
|
|
196
|
-
this.
|
|
197
|
-
this.
|
|
198
|
-
this.initRowColControls(gridEl);
|
|
199
|
-
this.initCellSelection(gridEl);
|
|
404
|
+
this.initSubsystems(gridEl);
|
|
405
|
+
applyCellColors(gridEl, this.model.snapshot().content);
|
|
200
406
|
|
|
201
407
|
if (this.isNewTable) {
|
|
202
408
|
const firstEditable = gridEl.querySelector<HTMLElement>('[contenteditable="true"]');
|
|
@@ -205,42 +411,173 @@ export class Table implements BlockTool {
|
|
|
205
411
|
}
|
|
206
412
|
}
|
|
207
413
|
|
|
208
|
-
public save(
|
|
209
|
-
|
|
210
|
-
const colWidths = this.data.colWidths;
|
|
211
|
-
const content = this.readOnly
|
|
212
|
-
? this.data.content
|
|
213
|
-
: this.grid.getData(gridEl);
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
withHeadings: this.data.withHeadings,
|
|
217
|
-
withHeadingColumn: this.data.withHeadingColumn,
|
|
218
|
-
stretched: this.data.stretched,
|
|
219
|
-
content,
|
|
220
|
-
...(colWidths ? { colWidths } : {}),
|
|
221
|
-
...(this.data.initialColWidth !== undefined ? { initialColWidth: this.data.initialColWidth } : {}),
|
|
222
|
-
};
|
|
414
|
+
public save(_blockContent: HTMLElement): TableData {
|
|
415
|
+
return this.model.snapshot();
|
|
223
416
|
}
|
|
224
417
|
|
|
225
418
|
public validate(savedData: TableData): boolean {
|
|
226
419
|
return savedData.content.length > 0;
|
|
227
420
|
}
|
|
228
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Update table with new data in-place (used by undo/redo).
|
|
424
|
+
* Follows the onPaste() pattern: delete old blocks, re-render, reinitialize.
|
|
425
|
+
*/
|
|
426
|
+
public setData(newData: Partial<TableData>): void {
|
|
427
|
+
this.setDataGeneration++;
|
|
428
|
+
const currentGeneration = this.setDataGeneration;
|
|
429
|
+
|
|
430
|
+
const normalized = normalizeTableData(
|
|
431
|
+
{
|
|
432
|
+
...this.model.snapshot(),
|
|
433
|
+
...newData,
|
|
434
|
+
} as TableData,
|
|
435
|
+
this.config
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
this.initialContent = normalized.content;
|
|
439
|
+
this.model.replaceAll(normalized);
|
|
440
|
+
|
|
441
|
+
// Only delete cell blocks during normal updates, not Yjs undo/redo.
|
|
442
|
+
// During Yjs sync, the child cell blocks are managed by Yjs and will be
|
|
443
|
+
// reattached via mountBlocksInCell(). Deleting them here would destroy
|
|
444
|
+
// the block data that Yjs is restoring, causing empty cells after undo.
|
|
445
|
+
if (!this.api.blocks.isSyncingFromYjs) {
|
|
446
|
+
this.runStructuralOp(() => {
|
|
447
|
+
this.cellBlocks?.deleteAllBlocks();
|
|
448
|
+
}, true);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
this.cellBlocks?.destroy();
|
|
452
|
+
|
|
453
|
+
const oldElement = this.element;
|
|
454
|
+
|
|
455
|
+
if (!oldElement?.parentNode) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// If a newer setData call has started, bail out. The newer call will
|
|
460
|
+
// handle the full DOM rebuild and block initialization, so continuing
|
|
461
|
+
// here would create blocks that the newer call immediately orphans.
|
|
462
|
+
if (currentGeneration !== this.setDataGeneration) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const savedSelectionRange = this.cellSelection?.getSelectedRange() ?? null;
|
|
467
|
+
const savedGripIndices = this.rowColControls?.getVisibleGripIndices() ?? null;
|
|
468
|
+
|
|
469
|
+
this.teardownSubsystems();
|
|
470
|
+
|
|
471
|
+
const newElement = this.render();
|
|
472
|
+
|
|
473
|
+
oldElement.parentNode.replaceChild(newElement, oldElement);
|
|
474
|
+
|
|
475
|
+
const gridEl = this.gridElement;
|
|
476
|
+
|
|
477
|
+
if (!gridEl) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (this.readOnly) {
|
|
482
|
+
applyCellColors(gridEl, this.model.snapshot().content);
|
|
483
|
+
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Check generation before initializeCells — if a re-entrant setData
|
|
488
|
+
// was triggered during render() or replaceChild(), bail out.
|
|
489
|
+
if (currentGeneration !== this.setDataGeneration) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
this.runStructuralOp(() => {
|
|
494
|
+
const setDataContent = this.cellBlocks?.initializeCells(this.initialContent ?? []) ?? this.initialContent ?? [];
|
|
495
|
+
|
|
496
|
+
// Check generation after initializeCells — if a re-entrant setData
|
|
497
|
+
// was triggered during block insertion inside initializeCells, bail
|
|
498
|
+
// out to avoid overwriting the newer call's model and controls.
|
|
499
|
+
if (currentGeneration !== this.setDataGeneration) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// When undoing reverts content to empty, the grid has default dimensions
|
|
504
|
+
// but initializeCells([]) mounted zero blocks. Pre-populate the model
|
|
505
|
+
// with empty cell entries so populateNewCells can place blocks correctly.
|
|
506
|
+
if (this.api.blocks.isSyncingFromYjs && setDataContent.length === 0 && gridEl) {
|
|
507
|
+
const emptyGridContent = Array.from(gridEl.querySelectorAll(`[${ROW_ATTR}]`), (row) => {
|
|
508
|
+
const cellCount = row.querySelectorAll(`[${CELL_ATTR}]`).length;
|
|
509
|
+
|
|
510
|
+
return Array.from({ length: cellCount }, () => ({ blocks: [] as string[] }));
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
this.model.replaceAll({
|
|
514
|
+
...this.model.snapshot(),
|
|
515
|
+
content: emptyGridContent,
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
populateNewCells(gridEl, this.cellBlocks);
|
|
519
|
+
} else {
|
|
520
|
+
this.model.replaceAll({
|
|
521
|
+
...this.model.snapshot(),
|
|
522
|
+
content: setDataContent,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
this.initialContent = null;
|
|
527
|
+
}, true);
|
|
528
|
+
|
|
529
|
+
if (currentGeneration !== this.setDataGeneration) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
this.initSubsystems(gridEl);
|
|
534
|
+
|
|
535
|
+
if (savedSelectionRange !== null && this.cellSelection !== null) {
|
|
536
|
+
this.cellSelection.selectRange(savedSelectionRange);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (savedGripIndices !== null && this.rowColControls !== null) {
|
|
540
|
+
this.rowColControls.restoreVisibleGrips(savedGripIndices.col, savedGripIndices.row);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
applyCellColors(gridEl, this.model.snapshot().content);
|
|
544
|
+
}
|
|
545
|
+
|
|
229
546
|
public onPaste(event: HTMLPasteEvent): void {
|
|
230
547
|
const content = event.detail.data;
|
|
231
548
|
const rows = content.querySelectorAll('tr');
|
|
232
549
|
const tableContent: string[][] = [];
|
|
550
|
+
const cellColors: Array<Array<{ color?: string; textColor?: string }>> = [];
|
|
233
551
|
|
|
234
552
|
rows.forEach(row => {
|
|
235
553
|
const cells = row.querySelectorAll('td, th');
|
|
236
554
|
const rowData: string[] = [];
|
|
555
|
+
const rowColors: Array<{ color?: string; textColor?: string }> = [];
|
|
237
556
|
|
|
238
557
|
cells.forEach(cell => {
|
|
239
558
|
rowData.push(cell.innerHTML);
|
|
559
|
+
|
|
560
|
+
const style = cell.getAttribute('style') ?? '';
|
|
561
|
+
const entry: { color?: string; textColor?: string } = {};
|
|
562
|
+
|
|
563
|
+
const bgMatch = /background-color\s*:\s*([^;]+)/i.exec(style);
|
|
564
|
+
|
|
565
|
+
if (bgMatch?.[1]) {
|
|
566
|
+
entry.color = mapToNearestPresetColor(bgMatch[1].trim(), 'bg');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const textMatch = /(?<![a-z-])color\s*:\s*([^;]+)/i.exec(style);
|
|
570
|
+
|
|
571
|
+
if (textMatch?.[1] && !isDefaultBlack(textMatch[1].trim())) {
|
|
572
|
+
entry.textColor = mapToNearestPresetColor(textMatch[1].trim(), 'text');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
rowColors.push(entry);
|
|
240
576
|
});
|
|
241
577
|
|
|
242
578
|
if (rowData.length > 0) {
|
|
243
579
|
tableContent.push(rowData);
|
|
580
|
+
cellColors.push(rowColors);
|
|
244
581
|
}
|
|
245
582
|
});
|
|
246
583
|
|
|
@@ -248,61 +585,109 @@ export class Table implements BlockTool {
|
|
|
248
585
|
const hasThHeadings = rows[0]?.querySelector('th') !== null;
|
|
249
586
|
const withHeadings = hasTheadHeadings || hasThHeadings;
|
|
250
587
|
|
|
251
|
-
this.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
content: tableContent,
|
|
256
|
-
};
|
|
588
|
+
this.initialContent = tableContent;
|
|
589
|
+
this.model.setWithHeadings(withHeadings);
|
|
590
|
+
this.model.setWithHeadingColumn(false);
|
|
591
|
+
this.model.setColWidths(undefined);
|
|
257
592
|
|
|
258
|
-
|
|
593
|
+
this.runStructuralOp(() => {
|
|
594
|
+
this.cellBlocks?.deleteAllBlocks();
|
|
595
|
+
}, true);
|
|
596
|
+
this.cellBlocks?.destroy();
|
|
597
|
+
this.teardownSubsystems();
|
|
598
|
+
|
|
599
|
+
const oldElement = this.element;
|
|
600
|
+
|
|
601
|
+
if (!oldElement?.parentNode) {
|
|
259
602
|
return;
|
|
260
603
|
}
|
|
261
604
|
|
|
262
605
|
const newElement = this.render();
|
|
263
606
|
|
|
264
|
-
|
|
265
|
-
this.element = newElement;
|
|
607
|
+
oldElement.parentNode.replaceChild(newElement, oldElement);
|
|
266
608
|
|
|
267
|
-
const gridEl = this.
|
|
609
|
+
const gridEl = this.gridElement;
|
|
268
610
|
|
|
269
611
|
if (!this.readOnly && gridEl) {
|
|
270
|
-
this.
|
|
271
|
-
|
|
272
|
-
|
|
612
|
+
this.runStructuralOp(() => {
|
|
613
|
+
const pasteContent = this.cellBlocks?.initializeCells(this.initialContent ?? []) ?? this.initialContent ?? [];
|
|
614
|
+
|
|
615
|
+
this.model.replaceAll({
|
|
616
|
+
...this.model.snapshot(),
|
|
617
|
+
content: pasteContent,
|
|
618
|
+
});
|
|
619
|
+
this.initialContent = null;
|
|
620
|
+
|
|
621
|
+
// Apply cell colors extracted from td/th style attributes
|
|
622
|
+
cellColors.forEach((rowColors, r) => {
|
|
623
|
+
rowColors.forEach((colors, c) => {
|
|
624
|
+
if (colors.color !== undefined) {
|
|
625
|
+
this.model.setCellColor(r, c, colors.color);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (colors.textColor !== undefined) {
|
|
629
|
+
this.model.setCellTextColor(r, c, colors.textColor);
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
}, true);
|
|
634
|
+
|
|
635
|
+
this.initSubsystems(gridEl);
|
|
636
|
+
applyCellColors(gridEl, this.model.snapshot().content);
|
|
273
637
|
}
|
|
274
638
|
}
|
|
275
639
|
|
|
276
640
|
public destroy(): void {
|
|
277
|
-
this.
|
|
641
|
+
this.unregisterRestrictedTools?.();
|
|
642
|
+
this.unregisterRestrictedTools = null;
|
|
643
|
+
|
|
644
|
+
// Only delete cell blocks during normal removal, not Yjs undo.
|
|
645
|
+
// When the table is removed via Yjs undo, its child cell blocks are managed
|
|
646
|
+
// by Yjs and will be restored during redo. Deleting them here would make
|
|
647
|
+
// redo create empty paragraphs instead of restoring the original content.
|
|
648
|
+
if (!this.api.blocks.isSyncingFromYjs) {
|
|
649
|
+
this.cellBlocks?.deleteAllBlocks();
|
|
650
|
+
}
|
|
278
651
|
|
|
279
|
-
this.
|
|
280
|
-
this.resize = null;
|
|
281
|
-
this.addControls?.destroy();
|
|
282
|
-
this.addControls = null;
|
|
283
|
-
this.rowColControls?.destroy();
|
|
284
|
-
this.rowColControls = null;
|
|
652
|
+
this.teardownSubsystems();
|
|
285
653
|
this.cellBlocks?.destroy();
|
|
286
654
|
this.cellBlocks = null;
|
|
287
|
-
this.
|
|
288
|
-
this.
|
|
655
|
+
this.gridElement = null;
|
|
656
|
+
this.scrollContainer = null;
|
|
289
657
|
this.element = null;
|
|
290
658
|
}
|
|
291
659
|
|
|
292
660
|
public deleteRowWithCleanup(rowIndex: number): void {
|
|
293
|
-
const gridEl = this.
|
|
661
|
+
const gridEl = this.gridElement;
|
|
294
662
|
|
|
295
|
-
if (gridEl) {
|
|
296
|
-
|
|
663
|
+
if (!gridEl) {
|
|
664
|
+
return;
|
|
297
665
|
}
|
|
666
|
+
|
|
667
|
+
this.runTransactedStructuralOp(() => {
|
|
668
|
+
const { blocksToDelete } = this.model.deleteRow(rowIndex);
|
|
669
|
+
|
|
670
|
+
this.cellBlocks?.deleteBlocks(blocksToDelete);
|
|
671
|
+
this.grid.deleteRow(gridEl, rowIndex);
|
|
672
|
+
});
|
|
298
673
|
}
|
|
299
674
|
|
|
300
675
|
public deleteColumnWithCleanup(colIndex: number): void {
|
|
301
|
-
const gridEl = this.
|
|
676
|
+
const gridEl = this.gridElement;
|
|
302
677
|
|
|
303
|
-
if (gridEl) {
|
|
304
|
-
|
|
678
|
+
if (!gridEl) {
|
|
679
|
+
return;
|
|
305
680
|
}
|
|
681
|
+
|
|
682
|
+
this.runTransactedStructuralOp(() => {
|
|
683
|
+
// model.deleteColumn() already removes the width at colIndex from
|
|
684
|
+
// colWidthsValue internally, so no additional syncColWidthsAfterDeleteColumn
|
|
685
|
+
// call is needed — that would double-delete.
|
|
686
|
+
const { blocksToDelete } = this.model.deleteColumn(colIndex);
|
|
687
|
+
|
|
688
|
+
this.cellBlocks?.deleteBlocks(blocksToDelete);
|
|
689
|
+
this.grid.deleteColumn(gridEl, colIndex);
|
|
690
|
+
});
|
|
306
691
|
}
|
|
307
692
|
|
|
308
693
|
public getBlockIdsInRow(rowIndex: number): string[] {
|
|
@@ -327,34 +712,44 @@ export class Table implements BlockTool {
|
|
|
327
712
|
grid: gridEl,
|
|
328
713
|
i18n: this.api.i18n,
|
|
329
714
|
getNewColumnWidth: () => {
|
|
330
|
-
const colWidths = this.
|
|
715
|
+
const colWidths = this.model.colWidths ?? readPixelWidths(gridEl);
|
|
331
716
|
|
|
332
|
-
return this.
|
|
333
|
-
? Math.round((this.
|
|
717
|
+
return this.model.initialColWidth !== undefined
|
|
718
|
+
? Math.round((this.model.initialColWidth / 2) * 100) / 100
|
|
334
719
|
: computeHalfAvgWidth(colWidths);
|
|
335
720
|
},
|
|
336
721
|
onAddRow: () => {
|
|
337
|
-
this.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
722
|
+
this.runTransactedStructuralOp(() => {
|
|
723
|
+
this.grid.addRow(gridEl);
|
|
724
|
+
this.model.addRow();
|
|
725
|
+
populateNewCells(gridEl, this.cellBlocks);
|
|
726
|
+
updateHeadingStyles(this.gridElement, this.model.withHeadings);
|
|
727
|
+
updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
|
|
728
|
+
this.initResize(gridEl);
|
|
729
|
+
this.addControls?.syncRowButtonWidth();
|
|
730
|
+
this.rowColControls?.refresh();
|
|
731
|
+
});
|
|
344
732
|
},
|
|
345
733
|
onAddColumn: () => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
734
|
+
this.runTransactedStructuralOp(() => {
|
|
735
|
+
const colWidths = this.model.colWidths ?? readPixelWidths(gridEl);
|
|
736
|
+
const halfWidth = this.model.initialColWidth !== undefined
|
|
737
|
+
? Math.round((this.model.initialColWidth / 2) * 100) / 100
|
|
738
|
+
: computeHalfAvgWidth(colWidths);
|
|
739
|
+
|
|
740
|
+
this.grid.addColumn(gridEl, undefined, colWidths, halfWidth);
|
|
741
|
+
this.model.addColumn(undefined, halfWidth);
|
|
742
|
+
this.model.setColWidths([...colWidths, halfWidth]);
|
|
743
|
+
populateNewCells(gridEl, this.cellBlocks);
|
|
744
|
+
updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
|
|
745
|
+
this.initResize(gridEl);
|
|
746
|
+
this.addControls?.syncRowButtonWidth();
|
|
747
|
+
this.rowColControls?.refresh();
|
|
748
|
+
|
|
749
|
+
if (this.scrollContainer) {
|
|
750
|
+
this.scrollContainer.scrollLeft = this.scrollContainer.scrollWidth;
|
|
751
|
+
}
|
|
752
|
+
});
|
|
358
753
|
},
|
|
359
754
|
onDragStart: () => {
|
|
360
755
|
if (this.resize) {
|
|
@@ -364,61 +759,83 @@ export class Table implements BlockTool {
|
|
|
364
759
|
this.rowColControls?.setGripsDisplay(false);
|
|
365
760
|
},
|
|
366
761
|
onDragAddRow: () => {
|
|
367
|
-
this.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
762
|
+
this.runTransactedStructuralOp(() => {
|
|
763
|
+
this.grid.addRow(gridEl);
|
|
764
|
+
this.model.addRow();
|
|
765
|
+
populateNewCells(gridEl, this.cellBlocks);
|
|
766
|
+
updateHeadingStyles(this.gridElement, this.model.withHeadings);
|
|
767
|
+
updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
|
|
768
|
+
});
|
|
371
769
|
},
|
|
372
770
|
onDragRemoveRow: () => {
|
|
373
|
-
|
|
771
|
+
this.runTransactedStructuralOp(() => {
|
|
772
|
+
const rowCount = this.grid.getRowCount(gridEl);
|
|
374
773
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
774
|
+
if (rowCount > 1 && isRowEmpty(gridEl, rowCount - 1)) {
|
|
775
|
+
const { blocksToDelete } = this.model.deleteRow(rowCount - 1);
|
|
776
|
+
|
|
777
|
+
this.cellBlocks?.deleteBlocks(blocksToDelete);
|
|
778
|
+
this.grid.deleteRow(gridEl, rowCount - 1);
|
|
779
|
+
}
|
|
780
|
+
});
|
|
378
781
|
},
|
|
379
782
|
onDragAddCol: () => {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
783
|
+
this.runTransactedStructuralOp(() => {
|
|
784
|
+
const colWidths = this.model.colWidths ?? readPixelWidths(gridEl);
|
|
785
|
+
const halfWidth = this.model.initialColWidth !== undefined
|
|
786
|
+
? Math.round((this.model.initialColWidth / 2) * 100) / 100
|
|
787
|
+
: computeHalfAvgWidth(colWidths);
|
|
788
|
+
|
|
789
|
+
const newWidths = [...colWidths, halfWidth];
|
|
790
|
+
|
|
791
|
+
this.grid.addColumn(gridEl, undefined, colWidths, halfWidth);
|
|
792
|
+
this.model.addColumn(undefined, halfWidth);
|
|
793
|
+
this.model.setColWidths(newWidths);
|
|
794
|
+
applyPixelWidths(gridEl, newWidths);
|
|
795
|
+
populateNewCells(gridEl, this.cellBlocks);
|
|
796
|
+
updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
|
|
797
|
+
this.initResize(gridEl);
|
|
798
|
+
|
|
799
|
+
dragState.addedCols++;
|
|
800
|
+
|
|
801
|
+
if (this.scrollContainer) {
|
|
802
|
+
this.scrollContainer.scrollLeft = this.scrollContainer.scrollWidth;
|
|
803
|
+
}
|
|
804
|
+
});
|
|
397
805
|
},
|
|
398
806
|
onDragRemoveCol: () => {
|
|
399
|
-
|
|
807
|
+
this.runTransactedStructuralOp(() => {
|
|
808
|
+
const colCount = this.grid.getColumnCount(gridEl);
|
|
400
809
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
810
|
+
if (colCount <= 1 || !isColumnEmpty(gridEl, colCount - 1)) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
404
813
|
|
|
405
|
-
|
|
814
|
+
// model.deleteColumn() already removes the width internally,
|
|
815
|
+
// so no additional syncColWidthsAfterDeleteColumn is needed.
|
|
816
|
+
const { blocksToDelete } = this.model.deleteColumn(colCount - 1);
|
|
406
817
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
818
|
+
this.cellBlocks?.deleteBlocks(blocksToDelete);
|
|
819
|
+
this.grid.deleteColumn(gridEl, colCount - 1);
|
|
410
820
|
|
|
411
|
-
|
|
821
|
+
const updatedWidths = this.model.colWidths;
|
|
412
822
|
|
|
413
|
-
|
|
823
|
+
if (updatedWidths) {
|
|
824
|
+
applyPixelWidths(gridEl, updatedWidths);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
this.initResize(gridEl);
|
|
828
|
+
|
|
829
|
+
dragState.addedCols--;
|
|
830
|
+
});
|
|
414
831
|
},
|
|
415
832
|
onDragEnd: () => {
|
|
416
833
|
this.initResize(gridEl);
|
|
417
834
|
this.addControls?.syncRowButtonWidth();
|
|
418
835
|
this.rowColControls?.refresh();
|
|
419
836
|
|
|
420
|
-
if (this.
|
|
421
|
-
this.
|
|
837
|
+
if (this.scrollContainer) {
|
|
838
|
+
this.scrollContainer.scrollLeft = dragState.addedCols > 0 ? this.scrollContainer.scrollWidth : 0;
|
|
422
839
|
}
|
|
423
840
|
|
|
424
841
|
dragState.addedCols = 0;
|
|
@@ -435,10 +852,12 @@ export class Table implements BlockTool {
|
|
|
435
852
|
|
|
436
853
|
this.rowColControls = new TableRowColControls({
|
|
437
854
|
grid: gridEl,
|
|
855
|
+
overlay: this.gripOverlay ?? undefined,
|
|
856
|
+
scrollContainer: this.scrollContainer ?? undefined,
|
|
438
857
|
getColumnCount: () => this.grid.getColumnCount(gridEl),
|
|
439
858
|
getRowCount: () => this.grid.getRowCount(gridEl),
|
|
440
|
-
isHeadingRow: () => this.
|
|
441
|
-
isHeadingColumn: () => this.
|
|
859
|
+
isHeadingRow: () => this.model.withHeadings,
|
|
860
|
+
isHeadingColumn: () => this.model.withHeadingColumn,
|
|
442
861
|
i18n: this.api.i18n,
|
|
443
862
|
onAction: (action: RowColAction) => this.handleRowColAction(gridEl, action),
|
|
444
863
|
onDragStateChange: (isDragging: boolean) => {
|
|
@@ -465,6 +884,10 @@ export class Table implements BlockTool {
|
|
|
465
884
|
|
|
466
885
|
this.pendingHighlight = null;
|
|
467
886
|
|
|
887
|
+
// Lock the grip synchronously so the unlock listener is registered
|
|
888
|
+
// before any external click can race with a deferred RAF
|
|
889
|
+
this.rowColControls?.setActiveGrip(type, index);
|
|
890
|
+
|
|
468
891
|
// Wait for layout so newly inserted cells have dimensions
|
|
469
892
|
requestAnimationFrame(() => {
|
|
470
893
|
if (type === 'row') {
|
|
@@ -472,8 +895,6 @@ export class Table implements BlockTool {
|
|
|
472
895
|
} else {
|
|
473
896
|
this.cellSelection?.selectColumn(index);
|
|
474
897
|
}
|
|
475
|
-
|
|
476
|
-
this.rowColControls?.setActiveGrip(type, index);
|
|
477
898
|
});
|
|
478
899
|
} else {
|
|
479
900
|
this.cellSelection?.clearActiveSelection();
|
|
@@ -483,62 +904,139 @@ export class Table implements BlockTool {
|
|
|
483
904
|
}
|
|
484
905
|
|
|
485
906
|
private handleRowColAction(gridEl: HTMLElement, action: RowColAction): void {
|
|
486
|
-
const
|
|
487
|
-
gridEl,
|
|
488
|
-
action,
|
|
489
|
-
{ grid: this.grid, data: this.data, cellBlocks: this.cellBlocks },
|
|
490
|
-
);
|
|
907
|
+
const generationAtStart = this.setDataGeneration;
|
|
491
908
|
|
|
492
|
-
this.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
909
|
+
this.runTransactedStructuralOp(() => {
|
|
910
|
+
if (generationAtStart !== this.setDataGeneration || this.gridElement !== gridEl) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
496
913
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
914
|
+
// Capture colWidths BEFORE the model mutation so the action handler
|
|
915
|
+
// receives pre-mutation widths. Model methods (addColumn, deleteColumn,
|
|
916
|
+
// moveColumn) update colWidths internally, and the handler functions
|
|
917
|
+
// (syncColWidthsAfterMove, syncColWidthsAfterDeleteColumn, computeInsertColumnWidths)
|
|
918
|
+
// also transform widths — passing post-mutation widths would double-apply
|
|
919
|
+
// the transformation.
|
|
920
|
+
const colWidthsBeforeMutation = this.model.colWidths;
|
|
921
|
+
|
|
922
|
+
// Sync model structural operation before DOM changes
|
|
923
|
+
const { blocksToDelete } = this.syncModelForAction(action);
|
|
924
|
+
|
|
925
|
+
const result = executeRowColAction(
|
|
926
|
+
gridEl,
|
|
927
|
+
action,
|
|
928
|
+
{
|
|
929
|
+
grid: this.grid,
|
|
930
|
+
data: {
|
|
931
|
+
colWidths: colWidthsBeforeMutation,
|
|
932
|
+
withHeadings: this.model.withHeadings,
|
|
933
|
+
withHeadingColumn: this.model.withHeadingColumn,
|
|
934
|
+
initialColWidth: this.model.initialColWidth,
|
|
935
|
+
},
|
|
936
|
+
cellBlocks: this.cellBlocks,
|
|
937
|
+
blocksToDelete,
|
|
938
|
+
},
|
|
939
|
+
);
|
|
940
|
+
|
|
941
|
+
if (generationAtStart !== this.setDataGeneration || this.gridElement !== gridEl) {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
502
944
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
945
|
+
this.model.setColWidths(result.colWidths);
|
|
946
|
+
this.model.setWithHeadings(result.withHeadings);
|
|
947
|
+
this.model.setWithHeadingColumn(result.withHeadingColumn);
|
|
948
|
+
this.pendingHighlight = result.pendingHighlight;
|
|
506
949
|
|
|
507
|
-
|
|
508
|
-
|
|
950
|
+
updateHeadingStyles(this.gridElement, this.model.withHeadings);
|
|
951
|
+
updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
|
|
952
|
+
this.initResize(gridEl);
|
|
953
|
+
this.addControls?.syncRowButtonWidth();
|
|
509
954
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
955
|
+
// Heading toggles don't change grid structure (no rows/columns
|
|
956
|
+
// added/removed/moved), so grips don't need to be recreated.
|
|
957
|
+
// Skipping refresh() keeps the popover's trigger element intact.
|
|
958
|
+
const isHeadingToggle = action.type === 'toggle-heading' || action.type === 'toggle-heading-column';
|
|
959
|
+
|
|
960
|
+
if (!isHeadingToggle) {
|
|
961
|
+
this.rowColControls?.refresh();
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (!result.moveSelection) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// After move operations, select the moved row/column to show where it landed
|
|
969
|
+
const { type: moveType, index: moveIndex } = result.moveSelection;
|
|
970
|
+
|
|
971
|
+
if (moveType === 'row') {
|
|
972
|
+
this.cellSelection?.selectRow(moveIndex);
|
|
973
|
+
} else {
|
|
974
|
+
this.cellSelection?.selectColumn(moveIndex);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
this.rowColControls?.setActiveGrip(moveType, moveIndex);
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
private syncModelForAction(action: RowColAction): { blocksToDelete?: string[] } {
|
|
982
|
+
switch (action.type) {
|
|
983
|
+
case 'insert-row-above':
|
|
984
|
+
this.model.addRow(action.index);
|
|
985
|
+
break;
|
|
986
|
+
case 'insert-row-below':
|
|
987
|
+
this.model.addRow(action.index + 1);
|
|
988
|
+
break;
|
|
989
|
+
case 'insert-col-left':
|
|
990
|
+
this.model.addColumn(action.index);
|
|
991
|
+
break;
|
|
992
|
+
case 'insert-col-right':
|
|
993
|
+
this.model.addColumn(action.index + 1);
|
|
994
|
+
break;
|
|
995
|
+
case 'move-row':
|
|
996
|
+
this.model.moveRow(action.fromIndex, action.toIndex);
|
|
997
|
+
break;
|
|
998
|
+
case 'move-col':
|
|
999
|
+
this.model.moveColumn(action.fromIndex, action.toIndex);
|
|
1000
|
+
break;
|
|
1001
|
+
case 'delete-row':
|
|
1002
|
+
return this.model.deleteRow(action.index);
|
|
1003
|
+
case 'delete-col':
|
|
1004
|
+
return this.model.deleteColumn(action.index);
|
|
1005
|
+
case 'toggle-heading':
|
|
1006
|
+
case 'toggle-heading-column':
|
|
1007
|
+
// Metadata only — handled after executeRowColAction
|
|
1008
|
+
break;
|
|
514
1009
|
}
|
|
515
1010
|
|
|
516
|
-
|
|
1011
|
+
return {};
|
|
517
1012
|
}
|
|
518
1013
|
|
|
519
1014
|
private initResize(gridEl: HTMLElement): void {
|
|
520
1015
|
this.resize?.destroy();
|
|
521
1016
|
|
|
522
|
-
const isPercentMode = this.
|
|
523
|
-
const widths = this.
|
|
1017
|
+
const isPercentMode = this.model.colWidths === undefined;
|
|
1018
|
+
const widths = this.model.colWidths ?? readPixelWidths(gridEl);
|
|
524
1019
|
|
|
525
1020
|
if (!isPercentMode) {
|
|
526
|
-
enableScrollOverflow(this.
|
|
1021
|
+
enableScrollOverflow(this.ensureScrollContainer());
|
|
527
1022
|
}
|
|
528
1023
|
|
|
529
1024
|
this.resize = new TableResize(
|
|
530
1025
|
gridEl,
|
|
531
1026
|
widths,
|
|
532
1027
|
(newWidths: number[]) => {
|
|
533
|
-
this.
|
|
534
|
-
enableScrollOverflow(this.
|
|
1028
|
+
this.model.setColWidths(newWidths);
|
|
1029
|
+
enableScrollOverflow(this.ensureScrollContainer());
|
|
535
1030
|
this.rowColControls?.positionGrips();
|
|
1031
|
+
this.addControls?.syncRowButtonWidth();
|
|
1032
|
+
this.scrollHaze?.update();
|
|
536
1033
|
},
|
|
537
1034
|
() => {
|
|
538
1035
|
this.rowColControls?.hideAllGrips();
|
|
539
1036
|
},
|
|
540
1037
|
() => {
|
|
541
1038
|
this.addControls?.syncRowButtonWidth();
|
|
1039
|
+
this.scrollHaze?.update();
|
|
542
1040
|
},
|
|
543
1041
|
isPercentMode,
|
|
544
1042
|
);
|
|
@@ -549,9 +1047,152 @@ export class Table implements BlockTool {
|
|
|
549
1047
|
api: this.api,
|
|
550
1048
|
gridElement: gridEl,
|
|
551
1049
|
tableBlockId: this.blockId ?? '',
|
|
1050
|
+
model: this.model,
|
|
1051
|
+
isStructuralOpActive: () => this.structuralOpDepth > 0,
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
private handleCellCopy(cells: HTMLElement[], clipboardData: DataTransfer): void {
|
|
1056
|
+
const entries = this.collectCellBlockData(cells);
|
|
1057
|
+
|
|
1058
|
+
if (entries.length === 0) {
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const payload = serializeCellsToClipboard(entries);
|
|
1063
|
+
|
|
1064
|
+
clipboardData.setData('text/html', buildClipboardHtml(payload));
|
|
1065
|
+
clipboardData.setData('text/plain', buildClipboardPlainText(payload));
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
private handleCellCopyViaButton(cells: HTMLElement[]): void {
|
|
1069
|
+
const entries = this.collectCellBlockData(cells);
|
|
1070
|
+
|
|
1071
|
+
if (entries.length === 0) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const payload = serializeCellsToClipboard(entries);
|
|
1076
|
+
const html = buildClipboardHtml(payload);
|
|
1077
|
+
const plainText = buildClipboardPlainText(payload);
|
|
1078
|
+
|
|
1079
|
+
const htmlBlob = new Blob([html], { type: 'text/html' });
|
|
1080
|
+
const textBlob = new Blob([plainText], { type: 'text/plain' });
|
|
1081
|
+
|
|
1082
|
+
void navigator.clipboard.write([
|
|
1083
|
+
new ClipboardItem({
|
|
1084
|
+
'text/html': htmlBlob,
|
|
1085
|
+
'text/plain': textBlob,
|
|
1086
|
+
}),
|
|
1087
|
+
]);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
private handleCellColorChange(cells: HTMLElement[], color: string | null, mode: CellColorMode): void {
|
|
1091
|
+
const gridEl = this.gridElement;
|
|
1092
|
+
|
|
1093
|
+
if (!gridEl) {
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
this.runTransactedStructuralOp(() => {
|
|
1098
|
+
for (const cell of cells) {
|
|
1099
|
+
const coord = getCellPosition(gridEl, cell);
|
|
1100
|
+
|
|
1101
|
+
if (!coord) {
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (mode === 'backgroundColor') {
|
|
1106
|
+
this.model.setCellColor(coord.row, coord.col, color ?? undefined);
|
|
1107
|
+
cell.style.backgroundColor = color ?? '';
|
|
1108
|
+
} else {
|
|
1109
|
+
this.model.setCellTextColor(coord.row, coord.col, color ?? undefined);
|
|
1110
|
+
cell.style.color = color ?? '';
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
552
1113
|
});
|
|
553
1114
|
}
|
|
554
1115
|
|
|
1116
|
+
private collectCellBlockData(
|
|
1117
|
+
cells: HTMLElement[],
|
|
1118
|
+
): Array<{ row: number; col: number; blocks: ClipboardBlockData[]; color?: string; textColor?: string }> {
|
|
1119
|
+
const gridEl = this.gridElement;
|
|
1120
|
+
|
|
1121
|
+
if (!gridEl) {
|
|
1122
|
+
return [];
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
const allRows = Array.from(gridEl.querySelectorAll(`[${ROW_ATTR}]`));
|
|
1126
|
+
|
|
1127
|
+
return cells.map(cell => {
|
|
1128
|
+
const row = cell.closest<HTMLElement>(`[${ROW_ATTR}]`);
|
|
1129
|
+
|
|
1130
|
+
if (!row) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const rowIndex = allRows.indexOf(row);
|
|
1135
|
+
const cellsInRow = Array.from(row.querySelectorAll(`[${CELL_ATTR}]`));
|
|
1136
|
+
const colIndex = cellsInRow.indexOf(cell);
|
|
1137
|
+
|
|
1138
|
+
const container = cell.querySelector(`[${CELL_BLOCKS_ATTR}]`);
|
|
1139
|
+
const blocks: ClipboardBlockData[] = [];
|
|
1140
|
+
|
|
1141
|
+
if (!container) {
|
|
1142
|
+
return { row: rowIndex, col: colIndex, blocks };
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
container.querySelectorAll('[data-blok-id]').forEach(blockEl => {
|
|
1146
|
+
const blockId = blockEl.getAttribute('data-blok-id');
|
|
1147
|
+
|
|
1148
|
+
if (!blockId) {
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const blockIndex = this.api.blocks.getBlockIndex(blockId);
|
|
1153
|
+
|
|
1154
|
+
if (blockIndex === undefined) {
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const block = this.api.blocks.getBlockByIndex(blockIndex);
|
|
1159
|
+
|
|
1160
|
+
if (!block) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
blocks.push({
|
|
1165
|
+
tool: block.name,
|
|
1166
|
+
data: block.preservedData,
|
|
1167
|
+
...(Object.keys(block.preservedTunes).length > 0
|
|
1168
|
+
? { tunes: block.preservedTunes }
|
|
1169
|
+
: {}),
|
|
1170
|
+
});
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
// Read-only legacy cells can render plain text without mounted block holders.
|
|
1174
|
+
const text = blocks.length === 0 ? (container.textContent ?? '').trim() : '';
|
|
1175
|
+
|
|
1176
|
+
if (blocks.length === 0 && text.length > 0) {
|
|
1177
|
+
blocks.push({
|
|
1178
|
+
tool: 'paragraph',
|
|
1179
|
+
data: { text },
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const color = this.model.getCellColor(rowIndex, colIndex);
|
|
1184
|
+
const textColor = this.model.getCellTextColor(rowIndex, colIndex);
|
|
1185
|
+
|
|
1186
|
+
return {
|
|
1187
|
+
row: rowIndex,
|
|
1188
|
+
col: colIndex,
|
|
1189
|
+
blocks,
|
|
1190
|
+
...(color !== undefined ? { color } : {}),
|
|
1191
|
+
...(textColor !== undefined ? { textColor } : {}),
|
|
1192
|
+
};
|
|
1193
|
+
}).filter((entry): entry is NonNullable<typeof entry> => entry !== null);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
555
1196
|
private initCellSelection(gridEl: HTMLElement): void {
|
|
556
1197
|
this.cellSelection?.destroy();
|
|
557
1198
|
|
|
@@ -562,6 +1203,7 @@ export class Table implements BlockTool {
|
|
|
562
1203
|
grid: gridEl,
|
|
563
1204
|
rectangleSelection, // Pass reference
|
|
564
1205
|
i18n: this.api.i18n,
|
|
1206
|
+
isPopoverOpen: () => this.rowColControls?.isPopoverOpen ?? false,
|
|
565
1207
|
onSelectionActiveChange: (hasSelection) => {
|
|
566
1208
|
if (this.resize) {
|
|
567
1209
|
this.resize.enabled = !hasSelection;
|
|
@@ -570,17 +1212,271 @@ export class Table implements BlockTool {
|
|
|
570
1212
|
this.addControls?.setInteractive(!hasSelection);
|
|
571
1213
|
this.rowColControls?.setGripsDisplay(!hasSelection);
|
|
572
1214
|
},
|
|
1215
|
+
onSelectionRangeChange: () => {
|
|
1216
|
+
// Selection finalized — restore grips so hover works normally
|
|
1217
|
+
this.rowColControls?.setGripsDisplay(true);
|
|
1218
|
+
},
|
|
573
1219
|
onClearContent: (cells) => {
|
|
574
|
-
|
|
1220
|
+
const cellBlocks = this.cellBlocks;
|
|
1221
|
+
|
|
1222
|
+
if (!cellBlocks) {
|
|
575
1223
|
return;
|
|
576
1224
|
}
|
|
577
1225
|
|
|
578
|
-
|
|
1226
|
+
this.runTransactedStructuralOp(() => {
|
|
1227
|
+
const blockIds = cellBlocks.getBlockIdsFromCells(cells);
|
|
1228
|
+
|
|
1229
|
+
cellBlocks.deleteBlocks(blockIds);
|
|
1230
|
+
|
|
1231
|
+
const gridEl = this.gridElement;
|
|
1232
|
+
|
|
1233
|
+
if (!gridEl) {
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
for (const cell of cells) {
|
|
1238
|
+
const coord = getCellPosition(gridEl, cell);
|
|
579
1239
|
|
|
580
|
-
|
|
1240
|
+
if (!coord) {
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
this.model.setCellColor(coord.row, coord.col, undefined);
|
|
1245
|
+
this.model.setCellTextColor(coord.row, coord.col, undefined);
|
|
1246
|
+
cell.style.backgroundColor = '';
|
|
1247
|
+
cell.style.color = '';
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
},
|
|
1251
|
+
onCopy: (cells, clipboardData) => {
|
|
1252
|
+
this.handleCellCopy(cells, clipboardData);
|
|
1253
|
+
},
|
|
1254
|
+
onCut: (cells, clipboardData) => {
|
|
1255
|
+
this.handleCellCopy(cells, clipboardData);
|
|
1256
|
+
},
|
|
1257
|
+
onCopyViaButton: (cells) => {
|
|
1258
|
+
this.handleCellCopyViaButton(cells);
|
|
1259
|
+
},
|
|
1260
|
+
onColorChange: (cells, color, mode) => {
|
|
1261
|
+
this.handleCellColorChange(cells, color, mode);
|
|
581
1262
|
},
|
|
582
1263
|
});
|
|
583
1264
|
}
|
|
1265
|
+
|
|
1266
|
+
private initScrollHaze(): void {
|
|
1267
|
+
this.scrollHaze?.destroy();
|
|
1268
|
+
|
|
1269
|
+
if (!this.element || !this.scrollContainer) {
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
this.scrollHaze = new TableScrollHaze();
|
|
1274
|
+
this.scrollHaze.init(this.element, this.scrollContainer);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
private initGridPasteListener(gridEl: HTMLElement): void {
|
|
1278
|
+
gridEl.addEventListener('paste', (e: ClipboardEvent) => {
|
|
1279
|
+
this.handleGridPaste(e, gridEl);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
private handleGridPaste(e: ClipboardEvent, gridEl: HTMLElement): void {
|
|
1284
|
+
if (this.readOnly || !e.clipboardData || e.defaultPrevented) {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const html = e.clipboardData.getData('text/html');
|
|
1289
|
+
const blokPayload = parseClipboardHtml(html);
|
|
1290
|
+
const externalPayload = blokPayload === null ? parseGenericHtmlTable(html) : null;
|
|
1291
|
+
const payload = blokPayload ?? externalPayload;
|
|
1292
|
+
|
|
1293
|
+
if (!payload) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* If the pasted HTML contains multiple tables (e.g. from Google Docs),
|
|
1299
|
+
* don't intercept — let the Paste module handle it as a document-level paste
|
|
1300
|
+
* so each table becomes a separate block without overwriting existing cells.
|
|
1301
|
+
*/
|
|
1302
|
+
if (
|
|
1303
|
+
externalPayload !== null &&
|
|
1304
|
+
new DOMParser().parseFromString(html, 'text/html').querySelectorAll('table').length > 1
|
|
1305
|
+
) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const activeElement = document.activeElement as HTMLElement | null;
|
|
1310
|
+
|
|
1311
|
+
if (!activeElement) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
const targetCell = activeElement.closest<HTMLElement>(`[${CELL_ATTR}]`);
|
|
1316
|
+
|
|
1317
|
+
if (!targetCell || !gridEl.contains(targetCell)) {
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const targetRow = targetCell.closest<HTMLElement>(`[${ROW_ATTR}]`);
|
|
1322
|
+
|
|
1323
|
+
if (!targetRow) {
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
e.preventDefault();
|
|
1328
|
+
e.stopPropagation();
|
|
1329
|
+
|
|
1330
|
+
const rows = Array.from(gridEl.querySelectorAll(`[${ROW_ATTR}]`));
|
|
1331
|
+
const targetRowIndex = rows.indexOf(targetRow);
|
|
1332
|
+
const cellsInRow = Array.from(targetRow.querySelectorAll(`[${CELL_ATTR}]`));
|
|
1333
|
+
const targetColIndex = cellsInRow.indexOf(targetCell);
|
|
1334
|
+
|
|
1335
|
+
this.pastePayloadIntoCells(gridEl, payload, targetRowIndex, targetColIndex);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
private pastePayloadIntoCells(
|
|
1339
|
+
gridEl: HTMLElement,
|
|
1340
|
+
payload: TableCellsClipboard,
|
|
1341
|
+
startRow: number,
|
|
1342
|
+
startCol: number,
|
|
1343
|
+
): void {
|
|
1344
|
+
this.runTransactedStructuralOp(() => {
|
|
1345
|
+
this.expandGridForPaste(gridEl, startRow + payload.rows, startCol + payload.cols);
|
|
1346
|
+
|
|
1347
|
+
// Paste block data into target cells
|
|
1348
|
+
const rows = gridEl.querySelectorAll(`[${ROW_ATTR}]`);
|
|
1349
|
+
|
|
1350
|
+
Array.from({ length: payload.rows }, (_, r) => r).forEach((r) => {
|
|
1351
|
+
const row = rows[startRow + r];
|
|
1352
|
+
|
|
1353
|
+
if (!row) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
const cells = row.querySelectorAll(`[${CELL_ATTR}]`);
|
|
1358
|
+
|
|
1359
|
+
Array.from({ length: payload.cols }, (_, c) => c).forEach((c) => {
|
|
1360
|
+
const cell = cells[startCol + c] as HTMLElement | undefined;
|
|
1361
|
+
|
|
1362
|
+
if (cell) {
|
|
1363
|
+
const cellPayload = payload.cells[r][c];
|
|
1364
|
+
|
|
1365
|
+
this.pasteCellPayload(cell, cellPayload);
|
|
1366
|
+
|
|
1367
|
+
// Sync pasted block IDs to model
|
|
1368
|
+
const blockIds = this.cellBlocks?.getBlockIdsFromCells([cell]) ?? [];
|
|
1369
|
+
|
|
1370
|
+
this.model.setCellBlocks(startRow + r, startCol + c, blockIds);
|
|
1371
|
+
|
|
1372
|
+
// Restore cell colors from clipboard
|
|
1373
|
+
const destRow = startRow + r;
|
|
1374
|
+
const destCol = startCol + c;
|
|
1375
|
+
|
|
1376
|
+
this.model.setCellColor(destRow, destCol, cellPayload.color);
|
|
1377
|
+
cell.style.backgroundColor = cellPayload.color ?? '';
|
|
1378
|
+
|
|
1379
|
+
this.model.setCellTextColor(destRow, destCol, cellPayload.textColor);
|
|
1380
|
+
cell.style.color = cellPayload.textColor ?? '';
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
// Update table state after paste
|
|
1386
|
+
this.initResize(gridEl);
|
|
1387
|
+
this.addControls?.syncRowButtonWidth();
|
|
1388
|
+
this.rowColControls?.refresh();
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
// Caret placement outside the lock (no structural mutation)
|
|
1392
|
+
const updatedRows = gridEl.querySelectorAll(`[${ROW_ATTR}]`);
|
|
1393
|
+
const lastRow = updatedRows[startRow + payload.rows - 1];
|
|
1394
|
+
const lastCell = lastRow?.querySelectorAll(`[${CELL_ATTR}]`)[startCol + payload.cols - 1] as HTMLElement | undefined;
|
|
1395
|
+
|
|
1396
|
+
if (!lastCell || !this.cellBlocks || !this.api.caret) {
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
const blockIds = this.cellBlocks.getBlockIdsFromCells([lastCell]);
|
|
1401
|
+
const lastBlockId = blockIds[blockIds.length - 1];
|
|
1402
|
+
|
|
1403
|
+
if (lastBlockId === undefined) {
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
this.api.caret.setToBlock(lastBlockId, 'end');
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Expand the grid to have at least the required number of rows and columns.
|
|
1412
|
+
*/
|
|
1413
|
+
private expandGridForPaste(gridEl: HTMLElement, neededRows: number, neededCols: number): void {
|
|
1414
|
+
const currentRowCount = this.grid.getRowCount(gridEl);
|
|
1415
|
+
const currentColCount = this.grid.getColumnCount(gridEl);
|
|
1416
|
+
|
|
1417
|
+
// Auto-expand rows
|
|
1418
|
+
Array.from({ length: Math.max(0, neededRows - currentRowCount) }).forEach(() => {
|
|
1419
|
+
this.grid.addRow(gridEl);
|
|
1420
|
+
this.model.addRow();
|
|
1421
|
+
populateNewCells(gridEl, this.cellBlocks);
|
|
1422
|
+
updateHeadingStyles(this.gridElement, this.model.withHeadings);
|
|
1423
|
+
updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
// Auto-expand columns
|
|
1427
|
+
Array.from({ length: Math.max(0, neededCols - currentColCount) }).forEach(() => {
|
|
1428
|
+
const colWidths = this.model.colWidths ?? readPixelWidths(gridEl);
|
|
1429
|
+
const halfWidth = this.model.initialColWidth !== undefined
|
|
1430
|
+
? Math.round((this.model.initialColWidth / 2) * 100) / 100
|
|
1431
|
+
: computeHalfAvgWidth(colWidths);
|
|
1432
|
+
|
|
1433
|
+
this.grid.addColumn(gridEl, undefined, colWidths, halfWidth);
|
|
1434
|
+
this.model.addColumn(undefined, halfWidth);
|
|
1435
|
+
this.model.setColWidths([...colWidths, halfWidth]);
|
|
1436
|
+
populateNewCells(gridEl, this.cellBlocks);
|
|
1437
|
+
updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* Replace the contents of a single cell with data from the clipboard payload.
|
|
1443
|
+
*/
|
|
1444
|
+
private pasteCellPayload(
|
|
1445
|
+
cell: HTMLElement,
|
|
1446
|
+
payloadCell: { blocks: ClipboardBlockData[] },
|
|
1447
|
+
): void {
|
|
1448
|
+
// Clear existing blocks in this cell
|
|
1449
|
+
if (this.cellBlocks) {
|
|
1450
|
+
const existingIds = this.cellBlocks.getBlockIdsFromCells([cell]);
|
|
1451
|
+
|
|
1452
|
+
this.cellBlocks.deleteBlocks(existingIds);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
const container = cell.querySelector<HTMLElement>(`[${CELL_BLOCKS_ATTR}]`);
|
|
1456
|
+
|
|
1457
|
+
if (!container) {
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
if (payloadCell.blocks.length === 0) {
|
|
1462
|
+
this.cellBlocks?.ensureCellHasBlock(cell);
|
|
1463
|
+
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
for (const blockData of payloadCell.blocks) {
|
|
1468
|
+
const block = this.api.blocks.insert(
|
|
1469
|
+
blockData.tool,
|
|
1470
|
+
blockData.data,
|
|
1471
|
+
{},
|
|
1472
|
+
this.api.blocks.getBlocksCount(),
|
|
1473
|
+
false,
|
|
1474
|
+
);
|
|
1475
|
+
|
|
1476
|
+
container.appendChild(block.holder);
|
|
1477
|
+
this.api.blocks.setBlockParent(block.id, this.blockId ?? '');
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
584
1480
|
}
|
|
585
1481
|
|
|
586
1482
|
export { isInsideTableCell, isRestrictedInTableCell, convertToParagraph } from './table-restrictions';
|