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