@jackuait/blok 0.4.1-beta.4 → 0.4.1-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -17
- package/codemod/README.md +16 -0
- package/codemod/migrate-editorjs-to-blok.js +868 -92
- package/codemod/test.js +682 -77
- package/dist/blok.mjs +5 -2
- package/dist/chunks/blok-B5qs7C5l.mjs +12838 -0
- package/dist/chunks/i18next-CugVlwWp.mjs +1292 -0
- package/dist/chunks/i18next-loader-CTrK3HzG.mjs +43 -0
- package/dist/{index-XWGz4gev.mjs → chunks/index-DDpzQn-0.mjs} +2 -2
- package/dist/chunks/inline-tool-convert-RBcopmCh.mjs +1988 -0
- package/dist/chunks/messages-2434tVOK.mjs +47 -0
- package/dist/chunks/messages-3DcCwXMF.mjs +47 -0
- package/dist/chunks/messages-4kMwVAKY.mjs +47 -0
- package/dist/chunks/messages-57uL5htT.mjs +47 -0
- package/dist/chunks/messages-76-iJV9Q.mjs +47 -0
- package/dist/chunks/messages-8p86Eyf2.mjs +47 -0
- package/dist/chunks/messages-BBX0p0Pi.mjs +47 -0
- package/dist/chunks/messages-BCm2eudQ.mjs +47 -0
- package/dist/chunks/messages-BFiUomgG.mjs +47 -0
- package/dist/chunks/messages-BIPNHHAV.mjs +47 -0
- package/dist/chunks/messages-BUlwu9mo.mjs +47 -0
- package/dist/chunks/messages-BX-DPa-z.mjs +47 -0
- package/dist/chunks/messages-BextV3Qh.mjs +47 -0
- package/dist/chunks/messages-BiPSFlUG.mjs +47 -0
- package/dist/chunks/messages-BiXe9G-O.mjs +47 -0
- package/dist/chunks/messages-Bl5z_Igo.mjs +47 -0
- package/dist/chunks/messages-BnsE97ku.mjs +47 -0
- package/dist/chunks/messages-BoO8gsVD.mjs +47 -0
- package/dist/chunks/messages-BqWaOGMn.mjs +47 -0
- package/dist/chunks/messages-BqkL2_Ro.mjs +47 -0
- package/dist/chunks/messages-BvCkXKX-.mjs +47 -0
- package/dist/chunks/messages-C6tbPLoj.mjs +47 -0
- package/dist/chunks/messages-CA6T3-gQ.mjs +47 -0
- package/dist/chunks/messages-CFFPFdWP.mjs +47 -0
- package/dist/chunks/messages-CFrKE-TN.mjs +47 -0
- package/dist/chunks/messages-CHz8VlG-.mjs +47 -0
- package/dist/chunks/messages-CLixzySl.mjs +47 -0
- package/dist/chunks/messages-CV7OM_qk.mjs +47 -0
- package/dist/chunks/messages-CXHt3eCC.mjs +47 -0
- package/dist/chunks/messages-CbmsBrB0.mjs +47 -0
- package/dist/chunks/messages-Ceo1KtFx.mjs +47 -0
- package/dist/chunks/messages-Cm0LJLtB.mjs +47 -0
- package/dist/chunks/messages-CmymP_Ar.mjs +47 -0
- package/dist/chunks/messages-D0ohMB5H.mjs +47 -0
- package/dist/chunks/messages-D3GrDwXh.mjs +47 -0
- package/dist/chunks/messages-D3vTzIpL.mjs +47 -0
- package/dist/chunks/messages-D5WeksbV.mjs +47 -0
- package/dist/chunks/messages-DGaab4EP.mjs +47 -0
- package/dist/chunks/messages-DKha57ZU.mjs +47 -0
- package/dist/chunks/messages-DOaujgMW.mjs +47 -0
- package/dist/chunks/messages-DVbPLd_0.mjs +47 -0
- package/dist/chunks/messages-D_FCyfW6.mjs +47 -0
- package/dist/chunks/messages-Dd5iZN3c.mjs +47 -0
- package/dist/chunks/messages-DehM7135.mjs +47 -0
- package/dist/chunks/messages-Dg1OHftD.mjs +47 -0
- package/dist/chunks/messages-Di6Flq-b.mjs +47 -0
- package/dist/chunks/messages-Dqhhex6e.mjs +47 -0
- package/dist/chunks/messages-DueVe0F1.mjs +47 -0
- package/dist/chunks/messages-Dx3eFwI0.mjs +47 -0
- package/dist/chunks/messages-FOtiUoKl.mjs +47 -0
- package/dist/chunks/messages-FTOZNhRD.mjs +47 -0
- package/dist/chunks/messages-IQxGfQIV.mjs +47 -0
- package/dist/chunks/messages-JF2fzCkK.mjs +47 -0
- package/dist/chunks/messages-MOGl7I5v.mjs +47 -0
- package/dist/chunks/messages-QgYhPL-3.mjs +47 -0
- package/dist/chunks/messages-WYWIbQwo.mjs +47 -0
- package/dist/chunks/messages-a6A_LgDv.mjs +47 -0
- package/dist/chunks/messages-bSf31LJi.mjs +47 -0
- package/dist/chunks/messages-diGozhTn.mjs +47 -0
- package/dist/chunks/messages-er-kd-VO.mjs +47 -0
- package/dist/chunks/messages-ez3w5NBn.mjs +47 -0
- package/dist/chunks/messages-f3uXjegd.mjs +47 -0
- package/dist/chunks/messages-ohwI1UGv.mjs +47 -0
- package/dist/chunks/messages-p9BZJaFV.mjs +47 -0
- package/dist/chunks/messages-qIQ4L4rw.mjs +47 -0
- package/dist/chunks/messages-qWkXPggi.mjs +47 -0
- package/dist/chunks/messages-w5foGze_.mjs +47 -0
- package/dist/full.mjs +50 -0
- package/dist/locales.mjs +227 -0
- package/dist/messages-2434tVOK.mjs +47 -0
- package/dist/messages-3DcCwXMF.mjs +47 -0
- package/dist/messages-4kMwVAKY.mjs +47 -0
- package/dist/messages-57uL5htT.mjs +47 -0
- package/dist/messages-76-iJV9Q.mjs +47 -0
- package/dist/messages-8p86Eyf2.mjs +47 -0
- package/dist/messages-BBX0p0Pi.mjs +47 -0
- package/dist/messages-BCm2eudQ.mjs +47 -0
- package/dist/messages-BFiUomgG.mjs +47 -0
- package/dist/messages-BIPNHHAV.mjs +47 -0
- package/dist/messages-BUlwu9mo.mjs +47 -0
- package/dist/messages-BX-DPa-z.mjs +47 -0
- package/dist/messages-BextV3Qh.mjs +47 -0
- package/dist/messages-BiPSFlUG.mjs +47 -0
- package/dist/messages-BiXe9G-O.mjs +47 -0
- package/dist/messages-Bl5z_Igo.mjs +47 -0
- package/dist/messages-BnsE97ku.mjs +47 -0
- package/dist/messages-BoO8gsVD.mjs +47 -0
- package/dist/messages-BqWaOGMn.mjs +47 -0
- package/dist/messages-BqkL2_Ro.mjs +47 -0
- package/dist/messages-BvCkXKX-.mjs +47 -0
- package/dist/messages-C6tbPLoj.mjs +47 -0
- package/dist/messages-CA6T3-gQ.mjs +47 -0
- package/dist/messages-CFFPFdWP.mjs +47 -0
- package/dist/messages-CFrKE-TN.mjs +47 -0
- package/dist/messages-CHz8VlG-.mjs +47 -0
- package/dist/messages-CLixzySl.mjs +47 -0
- package/dist/messages-CV7OM_qk.mjs +47 -0
- package/dist/messages-CXHt3eCC.mjs +47 -0
- package/dist/messages-CbmsBrB0.mjs +47 -0
- package/dist/messages-Ceo1KtFx.mjs +47 -0
- package/dist/messages-Cm0LJLtB.mjs +47 -0
- package/dist/messages-CmymP_Ar.mjs +47 -0
- package/dist/messages-D0ohMB5H.mjs +47 -0
- package/dist/messages-D3GrDwXh.mjs +47 -0
- package/dist/messages-D3vTzIpL.mjs +47 -0
- package/dist/messages-D5WeksbV.mjs +47 -0
- package/dist/messages-DGaab4EP.mjs +47 -0
- package/dist/messages-DKha57ZU.mjs +47 -0
- package/dist/messages-DOaujgMW.mjs +47 -0
- package/dist/messages-DVbPLd_0.mjs +47 -0
- package/dist/messages-D_FCyfW6.mjs +47 -0
- package/dist/messages-Dd5iZN3c.mjs +47 -0
- package/dist/messages-DehM7135.mjs +47 -0
- package/dist/messages-Dg1OHftD.mjs +47 -0
- package/dist/messages-Di6Flq-b.mjs +47 -0
- package/dist/messages-Dqhhex6e.mjs +47 -0
- package/dist/messages-DueVe0F1.mjs +47 -0
- package/dist/messages-Dx3eFwI0.mjs +47 -0
- package/dist/messages-FOtiUoKl.mjs +47 -0
- package/dist/messages-FTOZNhRD.mjs +47 -0
- package/dist/messages-IQxGfQIV.mjs +47 -0
- package/dist/messages-JF2fzCkK.mjs +47 -0
- package/dist/messages-MOGl7I5v.mjs +47 -0
- package/dist/messages-QgYhPL-3.mjs +47 -0
- package/dist/messages-WYWIbQwo.mjs +47 -0
- package/dist/messages-a6A_LgDv.mjs +47 -0
- package/dist/messages-bSf31LJi.mjs +47 -0
- package/dist/messages-diGozhTn.mjs +47 -0
- package/dist/messages-er-kd-VO.mjs +47 -0
- package/dist/messages-ez3w5NBn.mjs +47 -0
- package/dist/messages-f3uXjegd.mjs +47 -0
- package/dist/messages-ohwI1UGv.mjs +47 -0
- package/dist/messages-p9BZJaFV.mjs +47 -0
- package/dist/messages-qIQ4L4rw.mjs +47 -0
- package/dist/messages-qWkXPggi.mjs +47 -0
- package/dist/messages-w5foGze_.mjs +47 -0
- package/dist/tools.mjs +3073 -0
- package/dist/vendor.LICENSE.txt +26 -225
- package/package.json +49 -23
- package/src/blok.ts +267 -0
- package/src/components/__module.ts +139 -0
- package/src/components/block/api.ts +155 -0
- package/src/components/block/index.ts +1427 -0
- package/src/components/block-tunes/block-tune-delete.ts +51 -0
- package/src/components/blocks.ts +338 -0
- package/src/components/constants/data-attributes.ts +342 -0
- package/src/components/constants.ts +76 -0
- package/src/components/core.ts +392 -0
- package/src/components/dom.ts +773 -0
- package/src/components/domIterator.ts +189 -0
- package/src/components/errors/critical.ts +5 -0
- package/src/components/events/BlockChanged.ts +16 -0
- package/src/components/events/BlockHovered.ts +21 -0
- package/src/components/events/BlockSettingsClosed.ts +12 -0
- package/src/components/events/BlockSettingsOpened.ts +12 -0
- package/src/components/events/BlokMobileLayoutToggled.ts +15 -0
- package/src/components/events/FakeCursorAboutToBeToggled.ts +17 -0
- package/src/components/events/FakeCursorHaveBeenSet.ts +17 -0
- package/src/components/events/HistoryStateChanged.ts +19 -0
- package/src/components/events/RedactorDomChanged.ts +14 -0
- package/src/components/events/index.ts +46 -0
- package/src/components/flipper.ts +481 -0
- package/src/components/i18n/i18next-loader.ts +84 -0
- package/src/components/i18n/lightweight-i18n.ts +86 -0
- package/src/components/i18n/locales/TRANSLATION_GUIDELINES.md +113 -0
- package/src/components/i18n/locales/am/messages.json +44 -0
- package/src/components/i18n/locales/ar/messages.json +44 -0
- package/src/components/i18n/locales/az/messages.json +44 -0
- package/src/components/i18n/locales/bg/messages.json +44 -0
- package/src/components/i18n/locales/bn/messages.json +44 -0
- package/src/components/i18n/locales/bs/messages.json +44 -0
- package/src/components/i18n/locales/cs/messages.json +44 -0
- package/src/components/i18n/locales/da/messages.json +44 -0
- package/src/components/i18n/locales/de/messages.json +44 -0
- package/src/components/i18n/locales/dv/messages.json +44 -0
- package/src/components/i18n/locales/el/messages.json +44 -0
- package/src/components/i18n/locales/en/messages.json +44 -0
- package/src/components/i18n/locales/es/messages.json +44 -0
- package/src/components/i18n/locales/et/messages.json +44 -0
- package/src/components/i18n/locales/fa/messages.json +44 -0
- package/src/components/i18n/locales/fi/messages.json +44 -0
- package/src/components/i18n/locales/fil/messages.json +44 -0
- package/src/components/i18n/locales/fr/messages.json +44 -0
- package/src/components/i18n/locales/gu/messages.json +44 -0
- package/src/components/i18n/locales/he/messages.json +44 -0
- package/src/components/i18n/locales/hi/messages.json +44 -0
- package/src/components/i18n/locales/hr/messages.json +44 -0
- package/src/components/i18n/locales/hu/messages.json +44 -0
- package/src/components/i18n/locales/hy/messages.json +44 -0
- package/src/components/i18n/locales/id/messages.json +44 -0
- package/src/components/i18n/locales/index.ts +225 -0
- package/src/components/i18n/locales/it/messages.json +44 -0
- package/src/components/i18n/locales/ja/messages.json +44 -0
- package/src/components/i18n/locales/ka/messages.json +44 -0
- package/src/components/i18n/locales/km/messages.json +44 -0
- package/src/components/i18n/locales/kn/messages.json +44 -0
- package/src/components/i18n/locales/ko/messages.json +44 -0
- package/src/components/i18n/locales/ku/messages.json +44 -0
- package/src/components/i18n/locales/lo/messages.json +44 -0
- package/src/components/i18n/locales/lt/messages.json +44 -0
- package/src/components/i18n/locales/lv/messages.json +44 -0
- package/src/components/i18n/locales/mk/messages.json +44 -0
- package/src/components/i18n/locales/ml/messages.json +44 -0
- package/src/components/i18n/locales/mn/messages.json +44 -0
- package/src/components/i18n/locales/mr/messages.json +44 -0
- package/src/components/i18n/locales/ms/messages.json +44 -0
- package/src/components/i18n/locales/my/messages.json +44 -0
- package/src/components/i18n/locales/ne/messages.json +44 -0
- package/src/components/i18n/locales/nl/messages.json +44 -0
- package/src/components/i18n/locales/no/messages.json +44 -0
- package/src/components/i18n/locales/pa/messages.json +44 -0
- package/src/components/i18n/locales/pl/messages.json +44 -0
- package/src/components/i18n/locales/ps/messages.json +44 -0
- package/src/components/i18n/locales/pt/messages.json +44 -0
- package/src/components/i18n/locales/ro/messages.json +44 -0
- package/src/components/i18n/locales/ru/messages.json +44 -0
- package/src/components/i18n/locales/sd/messages.json +44 -0
- package/src/components/i18n/locales/si/messages.json +44 -0
- package/src/components/i18n/locales/sk/messages.json +44 -0
- package/src/components/i18n/locales/sl/messages.json +44 -0
- package/src/components/i18n/locales/sq/messages.json +44 -0
- package/src/components/i18n/locales/sr/messages.json +44 -0
- package/src/components/i18n/locales/sv/messages.json +44 -0
- package/src/components/i18n/locales/sw/messages.json +44 -0
- package/src/components/i18n/locales/ta/messages.json +44 -0
- package/src/components/i18n/locales/te/messages.json +44 -0
- package/src/components/i18n/locales/th/messages.json +44 -0
- package/src/components/i18n/locales/tr/messages.json +44 -0
- package/src/components/i18n/locales/ug/messages.json +44 -0
- package/src/components/i18n/locales/uk/messages.json +44 -0
- package/src/components/i18n/locales/ur/messages.json +44 -0
- package/src/components/i18n/locales/vi/messages.json +44 -0
- package/src/components/i18n/locales/yi/messages.json +44 -0
- package/src/components/i18n/locales/zh/messages.json +44 -0
- package/src/components/icons/index.ts +242 -0
- package/src/components/inline-tools/inline-tool-bold.ts +2213 -0
- package/src/components/inline-tools/inline-tool-convert.ts +141 -0
- package/src/components/inline-tools/inline-tool-italic.ts +500 -0
- package/src/components/inline-tools/inline-tool-link.ts +539 -0
- package/src/components/modules/api/blocks.ts +363 -0
- package/src/components/modules/api/caret.ts +125 -0
- package/src/components/modules/api/events.ts +51 -0
- package/src/components/modules/api/history.ts +73 -0
- package/src/components/modules/api/i18n.ts +33 -0
- package/src/components/modules/api/index.ts +39 -0
- package/src/components/modules/api/inlineToolbar.ts +33 -0
- package/src/components/modules/api/listeners.ts +56 -0
- package/src/components/modules/api/notifier.ts +46 -0
- package/src/components/modules/api/readonly.ts +39 -0
- package/src/components/modules/api/sanitizer.ts +30 -0
- package/src/components/modules/api/saver.ts +52 -0
- package/src/components/modules/api/selection.ts +48 -0
- package/src/components/modules/api/styles.ts +72 -0
- package/src/components/modules/api/toolbar.ts +79 -0
- package/src/components/modules/api/tools.ts +16 -0
- package/src/components/modules/api/tooltip.ts +67 -0
- package/src/components/modules/api/ui.ts +36 -0
- package/src/components/modules/blockEvents.ts +1375 -0
- package/src/components/modules/blockManager.ts +1348 -0
- package/src/components/modules/blockSelection.ts +708 -0
- package/src/components/modules/caret.ts +853 -0
- package/src/components/modules/crossBlockSelection.ts +329 -0
- package/src/components/modules/dragManager.ts +1141 -0
- package/src/components/modules/history.ts +1098 -0
- package/src/components/modules/i18n.ts +325 -0
- package/src/components/modules/index.ts +139 -0
- package/src/components/modules/modificationsObserver.ts +147 -0
- package/src/components/modules/paste.ts +1092 -0
- package/src/components/modules/readonly.ts +136 -0
- package/src/components/modules/rectangleSelection.ts +668 -0
- package/src/components/modules/renderer.ts +155 -0
- package/src/components/modules/saver.ts +283 -0
- package/src/components/modules/toolbar/blockSettings.ts +776 -0
- package/src/components/modules/toolbar/index.ts +1311 -0
- package/src/components/modules/toolbar/inline.ts +956 -0
- package/src/components/modules/tools.ts +589 -0
- package/src/components/modules/ui.ts +1179 -0
- package/src/components/polyfills.ts +113 -0
- package/src/components/selection.ts +1189 -0
- package/src/components/tools/base.ts +274 -0
- package/src/components/tools/block.ts +291 -0
- package/src/components/tools/collection.ts +67 -0
- package/src/components/tools/factory.ts +85 -0
- package/src/components/tools/inline.ts +71 -0
- package/src/components/tools/tune.ts +33 -0
- package/src/components/ui/toolbox.ts +497 -0
- package/src/components/utils/announcer.ts +205 -0
- package/src/components/utils/api.ts +20 -0
- package/src/components/utils/bem.ts +26 -0
- package/src/components/utils/blocks.ts +284 -0
- package/src/components/utils/caret.ts +1067 -0
- package/src/components/utils/data-model-transform.ts +382 -0
- package/src/components/utils/events.ts +117 -0
- package/src/components/utils/keyboard.ts +60 -0
- package/src/components/utils/listeners.ts +296 -0
- package/src/components/utils/mutations.ts +39 -0
- package/src/components/utils/notifier/draw.ts +190 -0
- package/src/components/utils/notifier/index.ts +66 -0
- package/src/components/utils/notifier/types.ts +1 -0
- package/src/components/utils/notifier.ts +77 -0
- package/src/components/utils/placeholder.ts +140 -0
- package/src/components/utils/popover/components/hint/hint.const.ts +10 -0
- package/src/components/utils/popover/components/hint/hint.ts +46 -0
- package/src/components/utils/popover/components/hint/index.ts +6 -0
- package/src/components/utils/popover/components/popover-header/index.ts +2 -0
- package/src/components/utils/popover/components/popover-header/popover-header.const.ts +8 -0
- package/src/components/utils/popover/components/popover-header/popover-header.ts +80 -0
- package/src/components/utils/popover/components/popover-header/popover-header.types.ts +14 -0
- package/src/components/utils/popover/components/popover-item/index.ts +13 -0
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +50 -0
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +666 -0
- package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.const.ts +14 -0
- package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.ts +136 -0
- package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.const.ts +20 -0
- package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.ts +117 -0
- package/src/components/utils/popover/components/popover-item/popover-item.ts +187 -0
- package/src/components/utils/popover/components/search-input/index.ts +2 -0
- package/src/components/utils/popover/components/search-input/search-input.const.ts +8 -0
- package/src/components/utils/popover/components/search-input/search-input.ts +181 -0
- package/src/components/utils/popover/components/search-input/search-input.types.ts +30 -0
- package/src/components/utils/popover/index.ts +13 -0
- package/src/components/utils/popover/popover-abstract.ts +448 -0
- package/src/components/utils/popover/popover-desktop.ts +643 -0
- package/src/components/utils/popover/popover-inline.ts +338 -0
- package/src/components/utils/popover/popover-mobile.ts +201 -0
- package/src/components/utils/popover/popover.const.ts +81 -0
- package/src/components/utils/popover/utils/popover-states-history.ts +72 -0
- package/src/components/utils/promise-queue.ts +43 -0
- package/src/components/utils/sanitizer.ts +537 -0
- package/src/components/utils/scroll-locker.ts +87 -0
- package/src/components/utils/shortcut.ts +231 -0
- package/src/components/utils/shortcuts.ts +113 -0
- package/src/components/utils/tools.ts +105 -0
- package/src/components/utils/tooltip.ts +642 -0
- package/src/components/utils/tw.ts +241 -0
- package/src/components/utils.ts +1081 -0
- package/src/env.d.ts +13 -0
- package/src/full.ts +69 -0
- package/src/locales.ts +51 -0
- package/src/stories/Block.stories.ts +498 -0
- package/src/stories/EditorModes.stories.ts +505 -0
- package/src/stories/Header.stories.ts +137 -0
- package/src/stories/InlineToolbar.stories.ts +498 -0
- package/src/stories/List.stories.ts +259 -0
- package/src/stories/Notifier.stories.ts +340 -0
- package/src/stories/Paragraph.stories.ts +112 -0
- package/src/stories/Placeholder.stories.ts +319 -0
- package/src/stories/Popover.stories.ts +844 -0
- package/src/stories/Selection.stories.ts +250 -0
- package/src/stories/StubBlock.stories.ts +156 -0
- package/src/stories/Toolbar.stories.ts +223 -0
- package/src/stories/Toolbox.stories.ts +166 -0
- package/src/stories/Tooltip.stories.ts +198 -0
- package/src/stories/helpers.ts +463 -0
- package/src/styles/main.css +123 -0
- package/src/tools/header/index.ts +570 -0
- package/src/tools/index.ts +38 -0
- package/src/tools/list/index.ts +1803 -0
- package/src/tools/paragraph/index.ts +411 -0
- package/src/tools/stub/index.ts +107 -0
- package/src/types-internal/blok-modules.d.ts +87 -0
- package/src/types-internal/html-janitor.d.ts +28 -0
- package/src/types-internal/module-config.d.ts +11 -0
- package/src/variants/all-locales.ts +155 -0
- package/src/variants/blok-maximum.ts +20 -0
- package/src/variants/blok-minimum.ts +243 -0
- package/types/api/blocks.d.ts +1 -1
- package/types/api/i18n.d.ts +5 -3
- package/types/api/selection.d.ts +6 -0
- package/types/api/styles.d.ts +23 -10
- package/types/configs/blok-config.d.ts +29 -0
- package/types/configs/i18n-config.d.ts +52 -2
- package/types/configs/i18n-dictionary.d.ts +16 -90
- package/types/data-attributes.d.ts +169 -0
- package/types/data-formats/output-data.d.ts +15 -0
- package/types/full.d.ts +80 -0
- package/types/index.d.ts +9 -12
- package/types/locales.d.ts +59 -0
- package/types/tools/adapters/inline-tool-adapter.d.ts +10 -0
- package/types/tools/block-tool.d.ts +9 -0
- package/types/tools/header.d.ts +18 -0
- package/types/tools/index.d.ts +1 -0
- package/types/tools/list.d.ts +91 -0
- package/types/tools/paragraph.d.ts +71 -0
- package/types/tools/tool-settings.d.ts +16 -2
- package/types/tools/tool.d.ts +6 -0
- package/types/tools-entry.d.ts +49 -0
- package/types/utils/popover/popover-item.d.ts +6 -5
- package/dist/blok-B870U2fw.mjs +0 -25803
- package/dist/blok.umd.js +0 -181
|
@@ -0,0 +1,1141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class DragManager
|
|
3
|
+
* @classdesc Manages pointer-based (non-native) drag and drop for blocks
|
|
4
|
+
* Uses mousedown/mousemove/mouseup instead of HTML5 drag API
|
|
5
|
+
* This allows full control over the cursor during drag operations
|
|
6
|
+
* @module DragManager
|
|
7
|
+
*/
|
|
8
|
+
import { Module } from '../__module';
|
|
9
|
+
import type { Block } from '../block';
|
|
10
|
+
import { Dom as $ } from '../dom';
|
|
11
|
+
import { hide } from '../utils/tooltip';
|
|
12
|
+
import { DATA_ATTR, createSelector } from '../constants';
|
|
13
|
+
import { twMerge } from '../utils/tw';
|
|
14
|
+
import { announce } from '../utils/announcer';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Styles for the drag preview element
|
|
18
|
+
*/
|
|
19
|
+
const PREVIEW_STYLES = {
|
|
20
|
+
base: 'fixed pointer-events-none z-[10000] opacity-80 transition-none',
|
|
21
|
+
content: 'relative mx-auto max-w-content',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for drag behavior
|
|
26
|
+
*/
|
|
27
|
+
const DRAG_CONFIG = {
|
|
28
|
+
/** Offset from cursor to preview element */
|
|
29
|
+
previewOffsetX: 10,
|
|
30
|
+
previewOffsetY: 0,
|
|
31
|
+
/** Minimum distance to start drag (prevents accidental drags) */
|
|
32
|
+
dragThreshold: 5,
|
|
33
|
+
/** Auto-scroll configuration */
|
|
34
|
+
autoScrollZone: 50,
|
|
35
|
+
autoScrollSpeed: 10,
|
|
36
|
+
/** Throttle delay for drop position announcements (ms) */
|
|
37
|
+
announcementThrottleMs: 300,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* State of the current drag operation
|
|
42
|
+
*/
|
|
43
|
+
interface DragState {
|
|
44
|
+
/** Primary block being dragged (the one with the drag handle) */
|
|
45
|
+
sourceBlock: Block;
|
|
46
|
+
/** All blocks being dragged (single block or multiple selected blocks) */
|
|
47
|
+
sourceBlocks: Block[];
|
|
48
|
+
/** Whether this is a multi-block drag operation */
|
|
49
|
+
isMultiBlockDrag: boolean;
|
|
50
|
+
/** Current drop target block */
|
|
51
|
+
targetBlock: Block | null;
|
|
52
|
+
/** Edge of target block ('top' or 'bottom') */
|
|
53
|
+
targetEdge: 'top' | 'bottom' | null;
|
|
54
|
+
/** Last announced drop position (to avoid duplicate announcements) */
|
|
55
|
+
lastAnnouncedDropIndex: number | null;
|
|
56
|
+
/** Pending drop position to announce (for throttling) */
|
|
57
|
+
pendingAnnouncementIndex: number | null;
|
|
58
|
+
/** Timeout ID for throttled announcement */
|
|
59
|
+
announcementTimeoutId: ReturnType<typeof setTimeout> | null;
|
|
60
|
+
/** Drag preview element */
|
|
61
|
+
previewElement: HTMLElement;
|
|
62
|
+
/** Starting mouse position */
|
|
63
|
+
startX: number;
|
|
64
|
+
startY: number;
|
|
65
|
+
/** Whether drag has actually started (passed threshold) */
|
|
66
|
+
isDragging: boolean;
|
|
67
|
+
/** Auto-scroll interval */
|
|
68
|
+
autoScrollInterval: number | null;
|
|
69
|
+
/** Scrollable container for auto-scroll (null means use window) */
|
|
70
|
+
scrollContainer: HTMLElement | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class DragManager extends Module {
|
|
74
|
+
/**
|
|
75
|
+
* Current drag state
|
|
76
|
+
*/
|
|
77
|
+
private dragState: DragState | null = null;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns true if a drag operation is currently in progress
|
|
81
|
+
*/
|
|
82
|
+
public get isDragging(): boolean {
|
|
83
|
+
return this.dragState?.isDragging ?? false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Cancels any pending drag tracking without performing a drop
|
|
88
|
+
* Call this when something else (like opening a menu) should take precedence
|
|
89
|
+
*/
|
|
90
|
+
public cancelTracking(): void {
|
|
91
|
+
if (this.dragState && !this.dragState.isDragging) {
|
|
92
|
+
this.cleanup();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Bound event handlers for cleanup
|
|
98
|
+
*/
|
|
99
|
+
private boundHandlers: {
|
|
100
|
+
onMouseMove: (e: MouseEvent) => void;
|
|
101
|
+
onMouseUp: (e: MouseEvent) => void;
|
|
102
|
+
onKeyDown: (e: KeyboardEvent) => void;
|
|
103
|
+
onKeyUp: (e: KeyboardEvent) => void;
|
|
104
|
+
} | null = null;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Module preparation
|
|
108
|
+
*/
|
|
109
|
+
public async prepare(): Promise<void> {
|
|
110
|
+
// No preparation needed
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Starts listening for drag on the given drag handle
|
|
115
|
+
* Called by Block when toolbar moves to it
|
|
116
|
+
* @param dragHandle - Element to use as drag handle
|
|
117
|
+
* @param block - Block that will be dragged
|
|
118
|
+
* @returns Cleanup function
|
|
119
|
+
*/
|
|
120
|
+
public setupDragHandle(dragHandle: HTMLElement, block: Block): () => void {
|
|
121
|
+
const onMouseDown = (e: MouseEvent): void => {
|
|
122
|
+
// Only handle left mouse button
|
|
123
|
+
if (e.button !== 0) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.startDragTracking(e, block);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
dragHandle.addEventListener('mousedown', onMouseDown);
|
|
131
|
+
|
|
132
|
+
return (): void => {
|
|
133
|
+
dragHandle.removeEventListener('mousedown', onMouseDown);
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Starts tracking mouse for potential drag
|
|
139
|
+
* @param e - Mouse event
|
|
140
|
+
* @param block - Block to drag
|
|
141
|
+
*/
|
|
142
|
+
private startDragTracking(e: MouseEvent, block: Block): void {
|
|
143
|
+
const contentElement = block.holder.querySelector('[data-blok-element-content]') as HTMLElement | null;
|
|
144
|
+
|
|
145
|
+
if (!contentElement) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Determine if this is a multi-block drag
|
|
150
|
+
const isBlockSelected = block.selected;
|
|
151
|
+
const initialBlocks = isBlockSelected
|
|
152
|
+
? this.Blok.BlockSelection.selectedBlocks
|
|
153
|
+
: [block];
|
|
154
|
+
|
|
155
|
+
// For single-block list item drags, include all descendants
|
|
156
|
+
const descendants = !isBlockSelected
|
|
157
|
+
? this.getListItemDescendants(block)
|
|
158
|
+
: [];
|
|
159
|
+
const blocksToMove = descendants.length > 0
|
|
160
|
+
? [block, ...descendants]
|
|
161
|
+
: initialBlocks;
|
|
162
|
+
|
|
163
|
+
const isMultiBlock = blocksToMove.length > 1;
|
|
164
|
+
|
|
165
|
+
// Create appropriate preview (single or multi-block)
|
|
166
|
+
const preview = isMultiBlock
|
|
167
|
+
? this.createMultiBlockPreview(blocksToMove)
|
|
168
|
+
: this.createPreview(contentElement, block.stretched);
|
|
169
|
+
|
|
170
|
+
preview.style.display = 'none';
|
|
171
|
+
document.body.appendChild(preview);
|
|
172
|
+
|
|
173
|
+
this.dragState = {
|
|
174
|
+
sourceBlock: block,
|
|
175
|
+
sourceBlocks: blocksToMove,
|
|
176
|
+
isMultiBlockDrag: isMultiBlock,
|
|
177
|
+
targetBlock: null,
|
|
178
|
+
targetEdge: null,
|
|
179
|
+
lastAnnouncedDropIndex: null,
|
|
180
|
+
pendingAnnouncementIndex: null,
|
|
181
|
+
announcementTimeoutId: null,
|
|
182
|
+
previewElement: preview,
|
|
183
|
+
startX: e.clientX,
|
|
184
|
+
startY: e.clientY,
|
|
185
|
+
isDragging: false,
|
|
186
|
+
autoScrollInterval: null,
|
|
187
|
+
scrollContainer: this.findScrollableAncestor(this.Blok.UI.nodes.wrapper),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Bind handlers
|
|
191
|
+
this.boundHandlers = {
|
|
192
|
+
onMouseMove: this.onMouseMove.bind(this),
|
|
193
|
+
onMouseUp: this.onMouseUp.bind(this),
|
|
194
|
+
onKeyDown: this.onKeyDown.bind(this),
|
|
195
|
+
onKeyUp: this.onKeyUp.bind(this),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
document.addEventListener('mousemove', this.boundHandlers.onMouseMove);
|
|
199
|
+
document.addEventListener('mouseup', this.boundHandlers.onMouseUp);
|
|
200
|
+
document.addEventListener('keydown', this.boundHandlers.onKeyDown);
|
|
201
|
+
document.addEventListener('keyup', this.boundHandlers.onKeyUp);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Creates the drag preview element
|
|
206
|
+
* @param contentElement - Element to clone for preview
|
|
207
|
+
* @param isStretched - Whether block is stretched
|
|
208
|
+
* @returns Preview element
|
|
209
|
+
*/
|
|
210
|
+
private createPreview(contentElement: HTMLElement, isStretched: boolean): HTMLElement {
|
|
211
|
+
const preview = $.make('div', PREVIEW_STYLES.base);
|
|
212
|
+
const clone = contentElement.cloneNode(true) as HTMLElement;
|
|
213
|
+
|
|
214
|
+
// Reset styles on clone
|
|
215
|
+
clone.className = twMerge(PREVIEW_STYLES.content, isStretched ? 'max-w-none' : '');
|
|
216
|
+
|
|
217
|
+
// Reset margin on the tool's rendered element (first child) to prevent offset
|
|
218
|
+
const toolElement = clone.firstElementChild as HTMLElement | null;
|
|
219
|
+
|
|
220
|
+
if (toolElement) {
|
|
221
|
+
toolElement.className = twMerge(toolElement.className, '!m-0');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
preview.appendChild(clone);
|
|
225
|
+
|
|
226
|
+
return preview;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Creates a stacked preview element for multiple blocks
|
|
231
|
+
* @param blocks - Array of blocks to preview
|
|
232
|
+
* @returns Preview element with stacked blocks and count badge
|
|
233
|
+
*/
|
|
234
|
+
private createMultiBlockPreview(blocks: Block[]): HTMLElement {
|
|
235
|
+
const preview = $.make('div', PREVIEW_STYLES.base);
|
|
236
|
+
|
|
237
|
+
// Get block holder dimensions to capture actual spacing
|
|
238
|
+
const blockInfo = blocks.map((block) => {
|
|
239
|
+
const holderRect = block.holder.getBoundingClientRect();
|
|
240
|
+
const contentElement = block.holder.querySelector('[data-blok-element-content]') as HTMLElement | null;
|
|
241
|
+
|
|
242
|
+
if (!contentElement) {
|
|
243
|
+
return { width: 0, height: 0, element: null, holderHeight: 0 };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const contentRect = contentElement.getBoundingClientRect();
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
width: contentRect.width,
|
|
250
|
+
height: contentRect.height,
|
|
251
|
+
element: contentElement,
|
|
252
|
+
holderHeight: holderRect.height, // Includes margins/padding
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Calculate cumulative top positions using actual block holder heights
|
|
257
|
+
const positions = blockInfo.reduce<number[]>((acc, _, index) => {
|
|
258
|
+
if (index === 0) {
|
|
259
|
+
acc.push(0);
|
|
260
|
+
} else {
|
|
261
|
+
const previousTop = acc[index - 1];
|
|
262
|
+
const previousHolderHeight = blockInfo[index - 1].holderHeight;
|
|
263
|
+
|
|
264
|
+
acc.push(previousTop + previousHolderHeight);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return acc;
|
|
268
|
+
}, []);
|
|
269
|
+
|
|
270
|
+
// Calculate total dimensions
|
|
271
|
+
const maxWidth = Math.max(...blockInfo.map(info => info.width), 0);
|
|
272
|
+
const lastIndex = blockInfo.length - 1;
|
|
273
|
+
const totalHeight = lastIndex >= 0
|
|
274
|
+
? positions[lastIndex] + blockInfo[lastIndex].height
|
|
275
|
+
: 0;
|
|
276
|
+
|
|
277
|
+
// Create stacked blocks
|
|
278
|
+
blocks.forEach((block, index) => {
|
|
279
|
+
const info = blockInfo[index];
|
|
280
|
+
|
|
281
|
+
if (!info.element) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const clone = info.element.cloneNode(true) as HTMLElement;
|
|
286
|
+
|
|
287
|
+
clone.className = twMerge(PREVIEW_STYLES.content, block.stretched ? 'max-w-none' : '');
|
|
288
|
+
|
|
289
|
+
// Reset margin on the tool's rendered element (first child) to prevent offset
|
|
290
|
+
const toolElement = clone.firstElementChild as HTMLElement | null;
|
|
291
|
+
|
|
292
|
+
if (toolElement) {
|
|
293
|
+
toolElement.className = twMerge(toolElement.className, '!m-0');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Position with cumulative offset
|
|
297
|
+
clone.style.position = 'absolute';
|
|
298
|
+
clone.style.top = `${positions[index]}px`;
|
|
299
|
+
clone.style.left = '0';
|
|
300
|
+
clone.style.zIndex = `${blocks.length - index}`;
|
|
301
|
+
|
|
302
|
+
preview.appendChild(clone);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Set explicit dimensions on the preview container
|
|
306
|
+
// This is necessary because absolutely positioned children don't contribute to parent size
|
|
307
|
+
preview.style.width = `${maxWidth}px`;
|
|
308
|
+
preview.style.height = `${totalHeight}px`;
|
|
309
|
+
|
|
310
|
+
return preview;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Handles mouse move during drag
|
|
315
|
+
* @param e - Mouse event
|
|
316
|
+
*/
|
|
317
|
+
private onMouseMove(e: MouseEvent): void {
|
|
318
|
+
if (!this.dragState) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const { startX, startY, isDragging, previewElement } = this.dragState;
|
|
323
|
+
|
|
324
|
+
// Check if we've passed the drag threshold and start actual drag
|
|
325
|
+
const dx = e.clientX - startX;
|
|
326
|
+
const dy = e.clientY - startY;
|
|
327
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
328
|
+
const shouldStartDrag = !isDragging && distance >= DRAG_CONFIG.dragThreshold;
|
|
329
|
+
|
|
330
|
+
if (shouldStartDrag) {
|
|
331
|
+
this.startDrag();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Update preview position
|
|
335
|
+
previewElement.style.left = `${e.clientX + DRAG_CONFIG.previewOffsetX}px`;
|
|
336
|
+
previewElement.style.top = `${e.clientY + DRAG_CONFIG.previewOffsetY}px`;
|
|
337
|
+
|
|
338
|
+
// Find drop target
|
|
339
|
+
this.updateDropTarget(e.clientX, e.clientY);
|
|
340
|
+
|
|
341
|
+
// Handle auto-scroll
|
|
342
|
+
this.handleAutoScroll(e.clientY);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Starts the actual drag operation
|
|
347
|
+
*/
|
|
348
|
+
private startDrag(): void {
|
|
349
|
+
if (!this.dragState) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.dragState.isDragging = true;
|
|
354
|
+
this.dragState.previewElement.style.display = 'block';
|
|
355
|
+
|
|
356
|
+
// Set global dragging state
|
|
357
|
+
const wrapper = this.Blok.UI.nodes.wrapper;
|
|
358
|
+
|
|
359
|
+
wrapper.setAttribute(DATA_ATTR.dragging, 'true');
|
|
360
|
+
|
|
361
|
+
// Add multi-block dragging attribute if applicable
|
|
362
|
+
if (this.dragState.isMultiBlockDrag) {
|
|
363
|
+
wrapper.setAttribute(DATA_ATTR.draggingMulti, 'true');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Clear selection for single-block drags only
|
|
367
|
+
// For multi-block drags, keep selection visible for visual feedback
|
|
368
|
+
if (!this.dragState.isMultiBlockDrag) {
|
|
369
|
+
this.Blok.BlockSelection.clearSelection();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
hide(true);
|
|
373
|
+
this.Blok.Toolbar.close();
|
|
374
|
+
|
|
375
|
+
// Announce drag started to screen readers
|
|
376
|
+
this.announceDragStarted();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Announces that a drag operation has started
|
|
381
|
+
* Note: This is only called from startDrag() which guarantees dragState exists
|
|
382
|
+
*/
|
|
383
|
+
private announceDragStarted(): void {
|
|
384
|
+
const blockCount = this.dragState!.sourceBlocks.length;
|
|
385
|
+
|
|
386
|
+
if (blockCount > 1) {
|
|
387
|
+
const message = this.Blok.I18n.t('a11y.dragStartedMultiple', { count: blockCount });
|
|
388
|
+
|
|
389
|
+
announce(message, { politeness: 'assertive' });
|
|
390
|
+
} else {
|
|
391
|
+
announce(
|
|
392
|
+
this.Blok.I18n.t('a11y.dragStarted'),
|
|
393
|
+
{ politeness: 'assertive' }
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Updates the drop target based on cursor position
|
|
400
|
+
* @param clientX - Cursor X position
|
|
401
|
+
* @param clientY - Cursor Y position
|
|
402
|
+
*/
|
|
403
|
+
private updateDropTarget(clientX: number, clientY: number): void {
|
|
404
|
+
if (!this.dragState) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Clear previous indicator
|
|
409
|
+
if (this.dragState.targetBlock) {
|
|
410
|
+
this.dragState.targetBlock.holder.removeAttribute('data-drop-indicator');
|
|
411
|
+
this.dragState.targetBlock.holder.style.removeProperty('--drop-indicator-depth');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Find element under cursor (temporarily hide preview)
|
|
415
|
+
this.dragState.previewElement.style.display = 'none';
|
|
416
|
+
const elementUnderCursor = document.elementFromPoint(clientX, clientY);
|
|
417
|
+
|
|
418
|
+
this.dragState.previewElement.style.display = 'block';
|
|
419
|
+
|
|
420
|
+
if (!elementUnderCursor) {
|
|
421
|
+
this.dragState.targetBlock = null;
|
|
422
|
+
this.dragState.targetEdge = null;
|
|
423
|
+
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Find block holder
|
|
428
|
+
const blockHolder = elementUnderCursor.closest(createSelector(DATA_ATTR.element)) as HTMLElement | null;
|
|
429
|
+
|
|
430
|
+
if (!blockHolder) {
|
|
431
|
+
this.dragState.targetBlock = null;
|
|
432
|
+
this.dragState.targetEdge = null;
|
|
433
|
+
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Find the block instance
|
|
438
|
+
const targetBlock = this.Blok.BlockManager.blocks.find(b => b.holder === blockHolder);
|
|
439
|
+
|
|
440
|
+
if (!targetBlock || targetBlock === this.dragState.sourceBlock) {
|
|
441
|
+
this.dragState.targetBlock = null;
|
|
442
|
+
this.dragState.targetEdge = null;
|
|
443
|
+
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Prevent dropping into the middle of a multi-block selection
|
|
448
|
+
if (this.dragState.isMultiBlockDrag && this.dragState.sourceBlocks.includes(targetBlock)) {
|
|
449
|
+
this.dragState.targetBlock = null;
|
|
450
|
+
this.dragState.targetEdge = null;
|
|
451
|
+
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Determine edge (top or bottom half of block)
|
|
456
|
+
const rect = blockHolder.getBoundingClientRect();
|
|
457
|
+
const isTopHalf = clientY < rect.top + rect.height / 2;
|
|
458
|
+
const targetIndex = this.Blok.BlockManager.getBlockIndex(targetBlock);
|
|
459
|
+
|
|
460
|
+
// Normalize: convert "top of block N" to "bottom of block N-1" (except for the first block)
|
|
461
|
+
// This ensures we only ever show one indicator per drop position
|
|
462
|
+
const previousBlock = targetIndex > 0
|
|
463
|
+
? this.Blok.BlockManager.getBlockByIndex(targetIndex - 1)
|
|
464
|
+
: null;
|
|
465
|
+
const canUsePreviousBlock = previousBlock && !this.dragState.sourceBlocks.includes(previousBlock);
|
|
466
|
+
|
|
467
|
+
if (isTopHalf && targetIndex > 0 && canUsePreviousBlock) {
|
|
468
|
+
this.dragState.targetBlock = previousBlock;
|
|
469
|
+
this.dragState.targetEdge = 'bottom';
|
|
470
|
+
previousBlock.holder.setAttribute('data-drop-indicator', 'bottom');
|
|
471
|
+
|
|
472
|
+
const targetDepth = this.calculateTargetDepth(previousBlock, 'bottom');
|
|
473
|
+
|
|
474
|
+
previousBlock.holder.style.setProperty('--drop-indicator-depth', String(targetDepth));
|
|
475
|
+
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (isTopHalf && targetIndex > 0) {
|
|
480
|
+
// Previous block is part of selection, can't drop here
|
|
481
|
+
this.dragState.targetBlock = null;
|
|
482
|
+
this.dragState.targetEdge = null;
|
|
483
|
+
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// First block top half, or any block bottom half
|
|
488
|
+
const edge: 'top' | 'bottom' = isTopHalf ? 'top' : 'bottom';
|
|
489
|
+
|
|
490
|
+
this.dragState.targetBlock = targetBlock;
|
|
491
|
+
this.dragState.targetEdge = edge;
|
|
492
|
+
blockHolder.setAttribute('data-drop-indicator', edge);
|
|
493
|
+
|
|
494
|
+
const targetDepth = this.calculateTargetDepth(targetBlock, edge);
|
|
495
|
+
|
|
496
|
+
blockHolder.style.setProperty('--drop-indicator-depth', String(targetDepth));
|
|
497
|
+
|
|
498
|
+
// Announce drop position change to screen readers
|
|
499
|
+
this.announceDropPosition();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Announces the current drop position to screen readers
|
|
504
|
+
* Throttled to avoid overwhelming screen readers with rapid announcements
|
|
505
|
+
* Only announces if the position has changed since the last announcement
|
|
506
|
+
*/
|
|
507
|
+
private announceDropPosition(): void {
|
|
508
|
+
if (!this.dragState?.targetBlock || !this.dragState.targetEdge) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const targetIndex = this.Blok.BlockManager.getBlockIndex(this.dragState.targetBlock);
|
|
513
|
+
const dropIndex = this.dragState.targetEdge === 'top' ? targetIndex : targetIndex + 1;
|
|
514
|
+
|
|
515
|
+
// Don't announce if position hasn't changed
|
|
516
|
+
if (this.dragState.lastAnnouncedDropIndex === dropIndex) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Store the pending announcement
|
|
521
|
+
this.dragState.pendingAnnouncementIndex = dropIndex;
|
|
522
|
+
|
|
523
|
+
// If there's already a pending timeout, let it handle the announcement
|
|
524
|
+
if (this.dragState.announcementTimeoutId !== null) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Schedule the announcement with throttling
|
|
529
|
+
this.dragState.announcementTimeoutId = setTimeout(() => {
|
|
530
|
+
if (!this.dragState || this.dragState.pendingAnnouncementIndex === null) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const pendingIndex = this.dragState.pendingAnnouncementIndex;
|
|
535
|
+
|
|
536
|
+
// Clear the timeout state
|
|
537
|
+
this.dragState.announcementTimeoutId = null;
|
|
538
|
+
this.dragState.pendingAnnouncementIndex = null;
|
|
539
|
+
|
|
540
|
+
// Don't announce if it's the same as what we already announced
|
|
541
|
+
if (this.dragState.lastAnnouncedDropIndex === pendingIndex) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
this.dragState.lastAnnouncedDropIndex = pendingIndex;
|
|
546
|
+
|
|
547
|
+
const total = this.Blok.BlockManager.blocks.length;
|
|
548
|
+
const message = this.Blok.I18n.t('a11y.dropPosition', {
|
|
549
|
+
position: pendingIndex + 1,
|
|
550
|
+
total,
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
announce(message, { politeness: 'polite' });
|
|
554
|
+
}, DRAG_CONFIG.announcementThrottleMs);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Finds the scrollable ancestor of an element
|
|
559
|
+
* @param element - Starting element
|
|
560
|
+
* @returns The scrollable element or null if window should be used
|
|
561
|
+
*/
|
|
562
|
+
private findScrollableAncestor(element: HTMLElement | null): HTMLElement | null {
|
|
563
|
+
if (!element || element === document.body) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const parent = element.parentElement;
|
|
568
|
+
|
|
569
|
+
if (!parent || parent === document.body) {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const style = window.getComputedStyle(parent);
|
|
574
|
+
const overflowY = style.overflowY;
|
|
575
|
+
const isScrollable = overflowY === 'auto' || overflowY === 'scroll';
|
|
576
|
+
const canScroll = parent.scrollHeight > parent.clientHeight;
|
|
577
|
+
|
|
578
|
+
if (isScrollable && canScroll) {
|
|
579
|
+
return parent;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return this.findScrollableAncestor(parent);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Handles auto-scrolling when cursor is near viewport edges
|
|
587
|
+
* @param clientY - Cursor Y position
|
|
588
|
+
*/
|
|
589
|
+
private handleAutoScroll(clientY: number): void {
|
|
590
|
+
if (!this.dragState) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Clear existing auto-scroll
|
|
595
|
+
if (this.dragState.autoScrollInterval !== null) {
|
|
596
|
+
cancelAnimationFrame(this.dragState.autoScrollInterval);
|
|
597
|
+
this.dragState.autoScrollInterval = null;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Determine scroll zones based on viewport
|
|
601
|
+
const viewportHeight = window.innerHeight;
|
|
602
|
+
const scrollUp = clientY < DRAG_CONFIG.autoScrollZone;
|
|
603
|
+
const scrollDown = clientY > viewportHeight - DRAG_CONFIG.autoScrollZone;
|
|
604
|
+
|
|
605
|
+
if (!scrollUp && !scrollDown) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const { scrollContainer } = this.dragState;
|
|
610
|
+
|
|
611
|
+
const scroll = (): void => {
|
|
612
|
+
if (!this.dragState || !this.dragState.isDragging) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const direction = scrollUp ? -1 : 1;
|
|
617
|
+
const scrollAmount = direction * DRAG_CONFIG.autoScrollSpeed;
|
|
618
|
+
|
|
619
|
+
if (scrollContainer) {
|
|
620
|
+
scrollContainer.scrollTop += scrollAmount;
|
|
621
|
+
} else {
|
|
622
|
+
window.scrollBy(0, scrollAmount);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
this.dragState.autoScrollInterval = requestAnimationFrame(scroll);
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
this.dragState.autoScrollInterval = requestAnimationFrame(scroll);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Handles mouse up to complete or cancel drag
|
|
633
|
+
* @param e - Mouse event
|
|
634
|
+
*/
|
|
635
|
+
private onMouseUp(e: MouseEvent): void {
|
|
636
|
+
if (!this.dragState) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const { isDragging, sourceBlock, sourceBlocks, targetBlock, targetEdge } = this.dragState;
|
|
641
|
+
const canDrop = isDragging && targetBlock !== null && targetEdge !== null;
|
|
642
|
+
|
|
643
|
+
if (!canDrop) {
|
|
644
|
+
this.cleanup();
|
|
645
|
+
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Check Alt key state at drop time to determine operation
|
|
650
|
+
if (e.altKey) {
|
|
651
|
+
void this.handleDuplicate(sourceBlocks, targetBlock, targetEdge);
|
|
652
|
+
} else {
|
|
653
|
+
this.handleDrop(sourceBlock, targetBlock, targetEdge);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
this.cleanup();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Handles escape key to cancel drag and Alt key to toggle duplication mode
|
|
661
|
+
* @param e - Keyboard event
|
|
662
|
+
*/
|
|
663
|
+
private onKeyDown(e: KeyboardEvent): void {
|
|
664
|
+
if (e.key === 'Escape') {
|
|
665
|
+
this.cleanup(true);
|
|
666
|
+
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Toggle duplication mode on Alt/Option key press
|
|
671
|
+
if (e.key === 'Alt' && this.dragState?.isDragging) {
|
|
672
|
+
this.setDuplicationMode(true);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Handles Alt key release to toggle off duplication mode
|
|
678
|
+
* @param e - Keyboard event
|
|
679
|
+
*/
|
|
680
|
+
private onKeyUp(e: KeyboardEvent): void {
|
|
681
|
+
if (e.key === 'Alt' && this.dragState?.isDragging) {
|
|
682
|
+
this.setDuplicationMode(false);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Sets duplication mode visual feedback on or off.
|
|
688
|
+
* The actual duplication decision is made by checking e.altKey at drop time,
|
|
689
|
+
* this method only controls the visual indicator for user feedback.
|
|
690
|
+
* @param isDuplicating - Whether duplication mode should be visually indicated
|
|
691
|
+
*/
|
|
692
|
+
private setDuplicationMode(isDuplicating: boolean): void {
|
|
693
|
+
if (!this.dragState) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const wrapper = this.Blok.UI.nodes.wrapper;
|
|
698
|
+
|
|
699
|
+
if (isDuplicating) {
|
|
700
|
+
wrapper.setAttribute(DATA_ATTR.duplicating, 'true');
|
|
701
|
+
} else {
|
|
702
|
+
wrapper.removeAttribute(DATA_ATTR.duplicating);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Handles the actual block drop
|
|
708
|
+
* @param sourceBlock - Block being dragged
|
|
709
|
+
* @param targetBlock - Block dropped onto
|
|
710
|
+
* @param edge - Edge of target ('top' or 'bottom')
|
|
711
|
+
*/
|
|
712
|
+
private handleDrop(sourceBlock: Block, targetBlock: Block, edge: 'top' | 'bottom'): void {
|
|
713
|
+
const { isMultiBlockDrag, sourceBlocks } = this.dragState!;
|
|
714
|
+
|
|
715
|
+
if (isMultiBlockDrag) {
|
|
716
|
+
this.handleMultiBlockDrop(sourceBlocks, targetBlock, edge);
|
|
717
|
+
} else {
|
|
718
|
+
this.handleSingleBlockDrop(sourceBlock, targetBlock, edge);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Announce successful drop to screen readers
|
|
722
|
+
this.announceDropComplete(sourceBlock, sourceBlocks, isMultiBlockDrag);
|
|
723
|
+
|
|
724
|
+
// Re-open toolbar on the dropped block
|
|
725
|
+
this.Blok.Toolbar.skipNextSettingsToggle();
|
|
726
|
+
this.Blok.Toolbar.moveAndOpen(sourceBlock);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Handles block duplication instead of move
|
|
731
|
+
* @param sourceBlocks - Blocks to duplicate
|
|
732
|
+
* @param targetBlock - Block to insert duplicates near
|
|
733
|
+
* @param edge - Edge of target ('top' or 'bottom')
|
|
734
|
+
*/
|
|
735
|
+
private async handleDuplicate(
|
|
736
|
+
sourceBlocks: Block[],
|
|
737
|
+
targetBlock: Block,
|
|
738
|
+
edge: 'top' | 'bottom'
|
|
739
|
+
): Promise<void> {
|
|
740
|
+
const manager = this.Blok.BlockManager;
|
|
741
|
+
|
|
742
|
+
// Sort blocks by current index to preserve order
|
|
743
|
+
const sortedBlocks = [...sourceBlocks].sort((a, b) =>
|
|
744
|
+
manager.getBlockIndex(a) - manager.getBlockIndex(b)
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
// Calculate target insertion point
|
|
748
|
+
const targetIndex = manager.getBlockIndex(targetBlock);
|
|
749
|
+
const baseInsertIndex = edge === 'top' ? targetIndex : targetIndex + 1;
|
|
750
|
+
|
|
751
|
+
// Save all blocks concurrently and filter out failures
|
|
752
|
+
const saveResults = await Promise.all(
|
|
753
|
+
sortedBlocks.map(async (block) => {
|
|
754
|
+
const saved = await block.save();
|
|
755
|
+
|
|
756
|
+
if (!saved) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return {
|
|
761
|
+
saved,
|
|
762
|
+
toolName: block.name,
|
|
763
|
+
};
|
|
764
|
+
})
|
|
765
|
+
);
|
|
766
|
+
|
|
767
|
+
const validResults = saveResults.filter(
|
|
768
|
+
(result): result is NonNullable<typeof result> => result !== null
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
if (validResults.length === 0) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Insert duplicated blocks
|
|
776
|
+
const duplicatedBlocks = validResults.map(({ saved, toolName }, index) =>
|
|
777
|
+
manager.insert({
|
|
778
|
+
tool: toolName,
|
|
779
|
+
data: saved.data,
|
|
780
|
+
tunes: saved.tunes,
|
|
781
|
+
index: baseInsertIndex + index,
|
|
782
|
+
needToFocus: false,
|
|
783
|
+
})
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
// Announce duplication to screen readers
|
|
787
|
+
this.announceDuplicateComplete(duplicatedBlocks);
|
|
788
|
+
|
|
789
|
+
// Select all duplicated blocks
|
|
790
|
+
this.Blok.BlockSelection.clearSelection();
|
|
791
|
+
duplicatedBlocks.forEach(block => {
|
|
792
|
+
this.Blok.BlockSelection.selectBlock(block);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// Re-open toolbar on the first duplicated block
|
|
796
|
+
if (duplicatedBlocks.length > 0) {
|
|
797
|
+
this.Blok.Toolbar.skipNextSettingsToggle();
|
|
798
|
+
this.Blok.Toolbar.moveAndOpen(duplicatedBlocks[0]);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Announces that a duplication operation has completed
|
|
804
|
+
* @param duplicatedBlocks - The blocks that were duplicated
|
|
805
|
+
*/
|
|
806
|
+
private announceDuplicateComplete(duplicatedBlocks: Block[]): void {
|
|
807
|
+
const firstBlock = duplicatedBlocks[0];
|
|
808
|
+
|
|
809
|
+
if (!firstBlock) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const newIndex = this.Blok.BlockManager.getBlockIndex(firstBlock);
|
|
814
|
+
const count = duplicatedBlocks.length;
|
|
815
|
+
|
|
816
|
+
if (count > 1) {
|
|
817
|
+
const message = this.Blok.I18n.t('a11y.blocksDuplicated', {
|
|
818
|
+
count,
|
|
819
|
+
position: newIndex + 1,
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
announce(message, { politeness: 'assertive' });
|
|
823
|
+
} else {
|
|
824
|
+
const total = this.Blok.BlockManager.blocks.length;
|
|
825
|
+
const message = this.Blok.I18n.t('a11y.blockDuplicated', {
|
|
826
|
+
position: newIndex + 1,
|
|
827
|
+
total,
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
announce(message, { politeness: 'assertive' });
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Announces that a drop operation has completed
|
|
836
|
+
* @param sourceBlock - The primary block that was dropped
|
|
837
|
+
* @param sourceBlocks - All blocks that were dropped
|
|
838
|
+
* @param isMultiBlockDrag - Whether this was a multi-block drag
|
|
839
|
+
*/
|
|
840
|
+
private announceDropComplete(sourceBlock: Block, sourceBlocks: Block[], isMultiBlockDrag: boolean): void {
|
|
841
|
+
const newIndex = this.Blok.BlockManager.getBlockIndex(sourceBlock);
|
|
842
|
+
const total = this.Blok.BlockManager.blocks.length;
|
|
843
|
+
|
|
844
|
+
if (isMultiBlockDrag) {
|
|
845
|
+
const message = this.Blok.I18n.t('a11y.blocksMoved', {
|
|
846
|
+
count: sourceBlocks.length,
|
|
847
|
+
position: newIndex + 1,
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
announce(message, { politeness: 'assertive' });
|
|
851
|
+
} else {
|
|
852
|
+
const message = this.Blok.I18n.t('a11y.blockMoved', {
|
|
853
|
+
position: newIndex + 1,
|
|
854
|
+
total,
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
announce(message, { politeness: 'assertive' });
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Handles dropping a single block
|
|
863
|
+
* @param sourceBlock - Block being dragged
|
|
864
|
+
* @param targetBlock - Block dropped onto
|
|
865
|
+
* @param edge - Edge of target ('top' or 'bottom')
|
|
866
|
+
*/
|
|
867
|
+
private handleSingleBlockDrop(sourceBlock: Block, targetBlock: Block, edge: 'top' | 'bottom'): void {
|
|
868
|
+
const fromIndex = this.Blok.BlockManager.getBlockIndex(sourceBlock);
|
|
869
|
+
const targetIndex = this.Blok.BlockManager.getBlockIndex(targetBlock);
|
|
870
|
+
|
|
871
|
+
// Calculate the new index based on drop position
|
|
872
|
+
const baseIndex = edge === 'top' ? targetIndex : targetIndex + 1;
|
|
873
|
+
|
|
874
|
+
// Adjust index if moving from before the target
|
|
875
|
+
const toIndex = fromIndex < baseIndex ? baseIndex - 1 : baseIndex;
|
|
876
|
+
|
|
877
|
+
// Only move if position actually changed
|
|
878
|
+
if (fromIndex === toIndex) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
this.Blok.BlockManager.move(toIndex, fromIndex, false);
|
|
883
|
+
|
|
884
|
+
// Select the moved block to provide visual feedback
|
|
885
|
+
const movedBlock = this.Blok.BlockManager.getBlockByIndex(toIndex);
|
|
886
|
+
|
|
887
|
+
if (!movedBlock) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
this.Blok.BlockSelection.selectBlock(movedBlock);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Handles dropping multiple selected blocks
|
|
896
|
+
* @param sourceBlocks - Array of blocks being dragged
|
|
897
|
+
* @param targetBlock - Block dropped onto
|
|
898
|
+
* @param edge - Edge of target ('top' or 'bottom')
|
|
899
|
+
*/
|
|
900
|
+
private handleMultiBlockDrop(
|
|
901
|
+
sourceBlocks: Block[],
|
|
902
|
+
targetBlock: Block,
|
|
903
|
+
edge: 'top' | 'bottom'
|
|
904
|
+
): void {
|
|
905
|
+
const manager = this.Blok.BlockManager;
|
|
906
|
+
|
|
907
|
+
// Sort blocks by current index
|
|
908
|
+
const sortedBlocks = [...sourceBlocks].sort((a, b) =>
|
|
909
|
+
manager.getBlockIndex(a) - manager.getBlockIndex(b)
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
// Calculate target insertion point
|
|
913
|
+
const targetIndex = manager.getBlockIndex(targetBlock);
|
|
914
|
+
const insertIndex = edge === 'top' ? targetIndex : targetIndex + 1;
|
|
915
|
+
|
|
916
|
+
// Determine if we're moving blocks up or down
|
|
917
|
+
const firstBlockIndex = manager.getBlockIndex(sortedBlocks[0]);
|
|
918
|
+
const movingDown = insertIndex > firstBlockIndex;
|
|
919
|
+
|
|
920
|
+
// Execute the move based on direction
|
|
921
|
+
if (movingDown) {
|
|
922
|
+
this.moveBlocksDown(sortedBlocks, insertIndex);
|
|
923
|
+
} else {
|
|
924
|
+
this.moveBlocksUp(sortedBlocks, insertIndex);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Clear selection first, then re-select all moved blocks
|
|
928
|
+
this.Blok.BlockSelection.clearSelection();
|
|
929
|
+
sortedBlocks.forEach(block => {
|
|
930
|
+
this.Blok.BlockSelection.selectBlock(block);
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Moves blocks down (to a higher index)
|
|
936
|
+
* @param sortedBlocks - Blocks sorted by current index
|
|
937
|
+
* @param insertIndex - Target insertion index
|
|
938
|
+
*/
|
|
939
|
+
private moveBlocksDown(sortedBlocks: Block[], insertIndex: number): void {
|
|
940
|
+
const manager = this.Blok.BlockManager;
|
|
941
|
+
|
|
942
|
+
// When moving down, start with insertIndex - 1 and decrement for each block
|
|
943
|
+
// This ensures blocks maintain their relative order
|
|
944
|
+
const reversedBlocks = [...sortedBlocks].reverse();
|
|
945
|
+
|
|
946
|
+
reversedBlocks.forEach((block, index) => {
|
|
947
|
+
const currentIndex = manager.getBlockIndex(block);
|
|
948
|
+
const targetPosition = insertIndex - 1 - index;
|
|
949
|
+
|
|
950
|
+
manager.move(targetPosition, currentIndex, false);
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Moves blocks up (to a lower index)
|
|
956
|
+
* @param sortedBlocks - Blocks sorted by current index
|
|
957
|
+
* @param baseInsertIndex - Base target insertion index
|
|
958
|
+
*/
|
|
959
|
+
private moveBlocksUp(sortedBlocks: Block[], baseInsertIndex: number): void {
|
|
960
|
+
const manager = this.Blok.BlockManager;
|
|
961
|
+
|
|
962
|
+
// Track how many blocks we've inserted to adjust the target index
|
|
963
|
+
sortedBlocks.forEach((block, index) => {
|
|
964
|
+
const currentIndex = manager.getBlockIndex(block);
|
|
965
|
+
const targetIndex = baseInsertIndex + index;
|
|
966
|
+
|
|
967
|
+
if (currentIndex === targetIndex) {
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
manager.move(targetIndex, currentIndex, false);
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Cleans up drag state and event listeners
|
|
977
|
+
* @param wasCancelled - Whether the drag was cancelled (not dropped)
|
|
978
|
+
*/
|
|
979
|
+
private cleanup(wasCancelled = false): void {
|
|
980
|
+
if (!this.dragState) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Announce cancellation to screen readers if drag was in progress and cancelled
|
|
985
|
+
if (wasCancelled && this.dragState.isDragging) {
|
|
986
|
+
announce(
|
|
987
|
+
this.Blok.I18n.t('a11y.dropCancelled'),
|
|
988
|
+
{ politeness: 'polite' }
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Clear auto-scroll
|
|
993
|
+
if (this.dragState.autoScrollInterval !== null) {
|
|
994
|
+
cancelAnimationFrame(this.dragState.autoScrollInterval);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Clear pending announcement timeout
|
|
998
|
+
if (this.dragState.announcementTimeoutId !== null) {
|
|
999
|
+
clearTimeout(this.dragState.announcementTimeoutId);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Clear drop indicator
|
|
1003
|
+
if (this.dragState.targetBlock) {
|
|
1004
|
+
this.dragState.targetBlock.holder.removeAttribute('data-drop-indicator');
|
|
1005
|
+
this.dragState.targetBlock.holder.style.removeProperty('--drop-indicator-depth');
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Remove preview
|
|
1009
|
+
if (this.dragState.previewElement.parentNode) {
|
|
1010
|
+
this.dragState.previewElement.remove();
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// Remove global dragging state
|
|
1014
|
+
const wrapper = this.Blok.UI.nodes.wrapper;
|
|
1015
|
+
|
|
1016
|
+
wrapper.removeAttribute(DATA_ATTR.dragging);
|
|
1017
|
+
wrapper.removeAttribute(DATA_ATTR.draggingMulti);
|
|
1018
|
+
wrapper.removeAttribute(DATA_ATTR.duplicating);
|
|
1019
|
+
|
|
1020
|
+
// Remove event listeners
|
|
1021
|
+
if (this.boundHandlers) {
|
|
1022
|
+
document.removeEventListener('mousemove', this.boundHandlers.onMouseMove);
|
|
1023
|
+
document.removeEventListener('mouseup', this.boundHandlers.onMouseUp);
|
|
1024
|
+
document.removeEventListener('keydown', this.boundHandlers.onKeyDown);
|
|
1025
|
+
document.removeEventListener('keyup', this.boundHandlers.onKeyUp);
|
|
1026
|
+
this.boundHandlers = null;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
this.dragState = null;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Gets the depth of a list item block from its DOM.
|
|
1034
|
+
* Returns null if the block is not a list item.
|
|
1035
|
+
* @param block - Block to check
|
|
1036
|
+
* @returns Depth number or null if not a list item
|
|
1037
|
+
*/
|
|
1038
|
+
private getListItemDepth(block: Block): number | null {
|
|
1039
|
+
const listWrapper = block.holder.querySelector('[data-list-depth]');
|
|
1040
|
+
|
|
1041
|
+
if (!listWrapper) {
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const depthAttr = listWrapper.getAttribute('data-list-depth');
|
|
1046
|
+
|
|
1047
|
+
return depthAttr ? parseInt(depthAttr, 10) : 0;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Calculates the target depth for a block dropped at the given position.
|
|
1052
|
+
* This determines what nesting level the block will have after being dropped.
|
|
1053
|
+
* @param targetBlock - Block being dropped onto
|
|
1054
|
+
* @param targetEdge - Edge of target ('top' or 'bottom')
|
|
1055
|
+
* @returns The target depth (0 for root level, 1+ for nested)
|
|
1056
|
+
*/
|
|
1057
|
+
private calculateTargetDepth(targetBlock: Block, targetEdge: 'top' | 'bottom'): number {
|
|
1058
|
+
const targetIndex = this.Blok.BlockManager.getBlockIndex(targetBlock);
|
|
1059
|
+
const dropIndex = targetEdge === 'top' ? targetIndex : targetIndex + 1;
|
|
1060
|
+
|
|
1061
|
+
// First position always has depth 0
|
|
1062
|
+
if (dropIndex === 0) {
|
|
1063
|
+
return 0;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Get the block that will be immediately before the drop position
|
|
1067
|
+
const previousBlock = this.Blok.BlockManager.getBlockByIndex(dropIndex - 1);
|
|
1068
|
+
|
|
1069
|
+
if (!previousBlock) {
|
|
1070
|
+
return 0;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const previousDepth = this.getListItemDepth(previousBlock) ?? 0;
|
|
1074
|
+
|
|
1075
|
+
// Get the block that will be immediately after the drop position
|
|
1076
|
+
const nextBlock = this.Blok.BlockManager.getBlockByIndex(dropIndex);
|
|
1077
|
+
const nextDepth = nextBlock ? (this.getListItemDepth(nextBlock) ?? 0) : 0;
|
|
1078
|
+
|
|
1079
|
+
// If next item is nested, match its depth (become sibling)
|
|
1080
|
+
if (nextDepth > 0 && nextDepth <= previousDepth + 1) {
|
|
1081
|
+
return nextDepth;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// If previous item is nested, match its depth
|
|
1085
|
+
if (previousDepth > 0) {
|
|
1086
|
+
return previousDepth;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
return 0;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Gets all descendant list items of a block (direct children and their descendants).
|
|
1094
|
+
* Only includes items that are strictly deeper than the dragged item.
|
|
1095
|
+
* Stops when encountering a sibling (same depth) or parent (shallower depth).
|
|
1096
|
+
* @param block - Parent block to find descendants for
|
|
1097
|
+
* @returns Array of descendant blocks (empty if block is not a list item or has no descendants)
|
|
1098
|
+
*/
|
|
1099
|
+
private getListItemDescendants(block: Block): Block[] {
|
|
1100
|
+
const parentDepth = this.getListItemDepth(block);
|
|
1101
|
+
|
|
1102
|
+
if (parentDepth === null) {
|
|
1103
|
+
return [];
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
const blockIndex = this.Blok.BlockManager.getBlockIndex(block);
|
|
1107
|
+
const totalBlocks = this.Blok.BlockManager.blocks.length;
|
|
1108
|
+
|
|
1109
|
+
const collectDescendants = (index: number, acc: Block[]): Block[] => {
|
|
1110
|
+
if (index >= totalBlocks) {
|
|
1111
|
+
return acc;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
const nextBlock = this.Blok.BlockManager.getBlockByIndex(index);
|
|
1115
|
+
|
|
1116
|
+
if (!nextBlock) {
|
|
1117
|
+
return acc;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const nextDepth = this.getListItemDepth(nextBlock);
|
|
1121
|
+
|
|
1122
|
+
// Stop if not a list item or depth <= parent depth (sibling or shallower level)
|
|
1123
|
+
// A sibling is an item at the same depth - it's not a child of the dragged item
|
|
1124
|
+
if (nextDepth === null || nextDepth <= parentDepth) {
|
|
1125
|
+
return acc;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Only include items strictly deeper than the parent (children, grandchildren, etc.)
|
|
1129
|
+
return collectDescendants(index + 1, [...acc, nextBlock]);
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
return collectDescendants(blockIndex + 1, []);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Module destruction
|
|
1137
|
+
*/
|
|
1138
|
+
public destroy(): void {
|
|
1139
|
+
this.cleanup();
|
|
1140
|
+
}
|
|
1141
|
+
}
|