diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/EditorToolbar.vue b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/EditorToolbar.vue index 3749f7640f..4d5290e986 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/EditorToolbar.vue +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/EditorToolbar.vue @@ -34,307 +34,321 @@ - -
- -
- - - - -
- - -
- - - - - - - - - - - - - - - -
- -
- - - - -
- -
- - - - -
+ - -
- - -
- - - - - - - - - - - - - - + + + + -
- + + + + + + import { defineComponent, ref, computed, onMounted, onUnmounted, nextTick } from 'vue'; + import KListWithOverflow from 'kolibri-design-system/lib/KListWithOverflow.vue'; import { useToolbarActions } from '../composables/useToolbarActions'; import { getTipTapEditorStrings } from '../TipTapEditorStrings'; import { useDropdowns } from '../composables/useDropdowns'; @@ -359,6 +374,7 @@ export default defineComponent({ name: 'EditorToolbar', components: { + KListWithOverflow, ToolbarButton, FormatDropdown, PasteDropdown, @@ -370,29 +386,6 @@ const moreDropdown = ref(null); const moreDropdownContainer = ref(null); const isMoreDropdownOpen = ref(false); - const toolbarWidth = ref(0); - - // TODO: Maybe these shouldnt be hardcoded? - const OVERFLOW_BREAKPOINTS = { - insert: 760, - script: 710, - lists: 650, - clearFormat: 560, - align: 530, - clipboard: 500, - textFormat: 400, - }; - - // Categories that can overflow (in order of overflow priority) - const OVERFLOW_CATEGORIES = [ - 'insert', - 'script', - 'lists', - 'clearFormat', - 'align', - 'clipboard', - 'textFormat', - ]; const { handleCopy, @@ -423,48 +416,27 @@ moreButtonText$, } = getTipTapEditorStrings(); - // Compute which categories should be visible vs in overflow - const visibleCategories = computed(() => { - return OVERFLOW_CATEGORIES.filter( - category => toolbarWidth.value >= OVERFLOW_BREAKPOINTS[category], - ); - }); - - const overflowCategories = computed(() => { - return OVERFLOW_CATEGORIES.filter(category => !visibleCategories.value.includes(category)); - }); - - // Handle resize observer - let resizeObserver = null; - - const updateToolbarWidth = () => { - if (toolbarRef.value) { - // Batch layout reads in next frame - requestAnimationFrame(() => { - toolbarWidth.value = toolbarRef.value.offsetWidth; - }); - } - }; - - const handleResize = entries => { - // Use ResizeObserver data directly - no DOM reading - requestAnimationFrame(() => { - for (const entry of entries) { - toolbarWidth.value = entry.contentRect.width; - } - }); - }; - - const handleWindowResize = () => { - requestAnimationFrame(updateToolbarWidth); - }; + // Toolbar groups in visual order (left-to-right). + // KListWithOverflow will collapse items from the end first. + // Note: Don't include dividers here - KListWithOverflow renders + // them automatically via #divider slot. + const toolbarGroups = computed(() => [ + { name: 'textFormat', ariaLabel: textStyleFormatting$() }, + { name: 'clipboard', ariaLabel: copyAndPasteActions$() }, + { name: 'align' }, + { name: 'clearFormat' }, + { name: 'lists', ariaLabel: listFormatting$() }, + { name: 'script', ariaLabel: scriptFormatting$() }, + { name: 'insert', ariaLabel: insertTools$() }, + ]); const onToolClick = (tool, event) => { isMoreDropdownOpen.value = false; let target = event.currentTarget; - // If the tool is in the overflow menu, we center the modal - if (!visibleCategories.value.includes('insert')) target = null; + // If the tool is in the overflow menu (clicked from dropdown), center the modal + const isFromOverflow = moreDropdown.value?.contains(event.target); + if (isFromOverflow) target = null; if (tool.name === 'image') { emit('insert-image', target); @@ -488,7 +460,7 @@ isMoreDropdownOpen.value = false; // Return focus to the more button await nextTick(); - moreButton.value?.$el?.focus(); + moreButton.value?.focus(); } else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { event.preventDefault(); const menuItems = Array.from( @@ -514,32 +486,11 @@ } }; - onMounted(async () => { - await nextTick(); - - // Initial width measurement in next frame - requestAnimationFrame(() => { - updateToolbarWidth(); - }); - - // Set up resize observer - if (toolbarRef.value && window.ResizeObserver) { - resizeObserver = new ResizeObserver(handleResize); - resizeObserver.observe(toolbarRef.value); - } else { - // Fallback to window resize listener with passive flag - window.addEventListener('resize', handleWindowResize, { passive: true }); - } - + onMounted(() => { document.addEventListener('click', handleClickOutside, { passive: true }); }); onUnmounted(() => { - if (resizeObserver) { - resizeObserver.disconnect(); - } else { - window.removeEventListener('resize', handleWindowResize); - } document.removeEventListener('click', handleClickOutside); }); @@ -549,8 +500,7 @@ moreDropdown, moreDropdownContainer, isMoreDropdownOpen, - visibleCategories, - overflowCategories, + toolbarGroups, handleCopy, handleClearFormat, onToolClick, @@ -569,11 +519,6 @@ textFormattingToolbar$, historyActions$, textFormattingOptions$, - textStyleFormatting$, - copyAndPasteActions$, - listFormatting$, - scriptFormatting$, - insertTools$, clearFormatting$, moreButtonText$, }; @@ -606,6 +551,25 @@ align-items: center; } + .overflow-list { + flex: 1; + min-width: 0; + } + + /* Temporary workaround: clip the brief wrap-flicker in KListWithOverflow + during resize recalculation. Remove this block (and stop reaching into + KDS internals via ::v-deep) once learningequality/kolibri-design-system#1246 + is released and the KDS version pinned in package.json is bumped. */ + .overflow-list ::v-deep .list { + overflow: hidden; + } + + .toolbar-group { + display: flex; + gap: 2px; + align-items: center; + } + .more-dropdown-container { position: relative; } diff --git a/package.json b/package.json index d12d284d00..754469a087 100644 --- a/package.json +++ b/package.json @@ -198,6 +198,9 @@ "node-sass", "unrs-resolver", "vue-demi" - ] + ], + "overrides": { + "kolibri-design-system": "github:nucleogenesis/kolibri-design-system-1#studio/5258-rte--dynamic-width/klistwithoverflow-wrap-fix" + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c39cb507c..4bac564d65 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + kolibri-design-system: github:nucleogenesis/kolibri-design-system-1#studio/5258-rte--dynamic-width/klistwithoverflow-wrap-fix + importers: .: @@ -84,8 +87,8 @@ importers: specifier: ^0.2.12 version: 0.2.12 kolibri-design-system: - specifier: 5.6.0 - version: 5.6.0 + specifier: github:nucleogenesis/kolibri-design-system-1#studio/5258-rte--dynamic-width/klistwithoverflow-wrap-fix + version: https://codeload.github.com/nucleogenesis/kolibri-design-system-1/tar.gz/f7e2093f448029ed891c5c8a13170c26d9b06ae4 lodash: specifier: ^4.18.1 version: 4.18.1 @@ -5053,8 +5056,9 @@ packages: kolibri-constants@0.2.12: resolution: {integrity: sha512-ApVc/KLwEaDJohqKhQTdao4UdWmoyq2pQ5lk8ra+1rDpJvsFWsAOGVC4RTv4YEDlAYJzj2/QZlJQ91u5yUURSQ==} - kolibri-design-system@5.6.0: - resolution: {integrity: sha512-lKCQVnnjguUQUIzQ5rKkUyNRWOIhY4cOOtPmuW0H3gz+hv73wPvBjbI2u9kQqA+oFs/C2MsL60roiMMbQDmt4A==} + kolibri-design-system@https://codeload.github.com/nucleogenesis/kolibri-design-system-1/tar.gz/f7e2093f448029ed891c5c8a13170c26d9b06ae4: + resolution: {tarball: https://codeload.github.com/nucleogenesis/kolibri-design-system-1/tar.gz/f7e2093f448029ed891c5c8a13170c26d9b06ae4} + version: 5.6.2 kolibri-format@1.0.1: resolution: {integrity: sha512-yGQpsJkBAzmRueAq6MG1UOuDl9pbhEtMWNxq9ObG5pPVkG8uhWJAS1L71GCuNAeaV1XG2IWo2565Ov4yXnudeA==} @@ -13623,7 +13627,7 @@ snapshots: kolibri-constants@0.2.12: {} - kolibri-design-system@5.6.0: + kolibri-design-system@https://codeload.github.com/nucleogenesis/kolibri-design-system-1/tar.gz/f7e2093f448029ed891c5c8a13170c26d9b06ae4: dependencies: aphrodite: https://codeload.github.com/learningequality/aphrodite/tar.gz/fdc8d7be8912a5cf17f74ff10f124013c52c3e32 autosize: 3.0.21