diff --git a/packages/super-editor/src/components/toolbar/super-toolbar.js b/packages/super-editor/src/components/toolbar/super-toolbar.js index 25a1a75e43..52f79c887a 100644 --- a/packages/super-editor/src/components/toolbar/super-toolbar.js +++ b/packages/super-editor/src/components/toolbar/super-toolbar.js @@ -828,12 +828,52 @@ export class SuperToolbar extends EventEmitter { highlightItem.nestedOptions.value = [option]; } + /** + * Sync document mode dropdown UI with the current mode. + * @private + * @returns {void} + */ + #syncDocumentModeUi() { + const documentModeItem = this.getToolbarItemByName('documentMode'); + if (!documentModeItem) return; + + const mode = (this.documentMode || 'editing').toLowerCase(); + const texts = this.config.texts || {}; + const icons = this.config.icons || {}; + const map = { + editing: { + label: texts.documentEditingMode || 'Editing', + icon: icons.documentEditingMode || icons.documentMode, + }, + suggesting: { + label: texts.documentSuggestingMode || 'Suggesting', + icon: icons.documentSuggestingMode || icons.documentMode, + }, + viewing: { + label: texts.documentViewingMode || 'Viewing', + icon: icons.documentViewingMode || icons.documentMode, + }, + }; + + const next = map[mode] || map.editing; + if (documentModeItem.label?.value !== undefined) { + documentModeItem.label.value = next.label; + } + if (documentModeItem.defaultLabel?.value !== undefined) { + documentModeItem.defaultLabel.value = next.label; + } + if (documentModeItem.icon?.value !== undefined && next.icon) { + documentModeItem.icon.value = next.icon; + } + } + /** * Update the toolbar state based on the current editor state * Updates active/inactive state of all toolbar items * @returns {void} */ updateToolbarState() { + this.#syncDocumentModeUi(); this.#updateToolbarHistory(); this.#initDefaultFonts(); this.#updateHighlightColors(); diff --git a/packages/super-editor/src/tests/toolbar/updateToolbarState.test.js b/packages/super-editor/src/tests/toolbar/updateToolbarState.test.js index 92dc5a4c98..3735abe2e0 100644 --- a/packages/super-editor/src/tests/toolbar/updateToolbarState.test.js +++ b/packages/super-editor/src/tests/toolbar/updateToolbarState.test.js @@ -195,6 +195,168 @@ describe('updateToolbarState', () => { toolbar.documentMode = 'editing'; }); + describe('document mode dropdown sync', () => { + let documentModeItem; + + beforeEach(() => { + documentModeItem = { + name: { value: 'documentMode' }, + label: { value: 'Editing' }, + defaultLabel: { value: 'Editing' }, + icon: { value: null }, + allowWithoutEditor: { value: true }, + setDisabled: vi.fn(), + }; + toolbar.toolbarItems = [documentModeItem]; + toolbar.activeEditor = null; + }); + + it('should sync to suggesting mode', () => { + toolbar.documentMode = 'suggesting'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Suggesting'); + expect(documentModeItem.defaultLabel.value).toBe('Suggesting'); + expect(documentModeItem.icon.value).toBe(toolbar.config.icons.documentSuggestingMode); + }); + + it('should sync to editing mode', () => { + toolbar.documentMode = 'editing'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Editing'); + expect(documentModeItem.defaultLabel.value).toBe('Editing'); + expect(documentModeItem.icon.value).toBe(toolbar.config.icons.documentEditingMode); + }); + + it('should sync to viewing mode', () => { + toolbar.documentMode = 'viewing'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Viewing'); + expect(documentModeItem.defaultLabel.value).toBe('Viewing'); + expect(documentModeItem.icon.value).toBe(toolbar.config.icons.documentViewingMode); + }); + + it('should default to editing when documentMode is null', () => { + toolbar.documentMode = null; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Editing'); + expect(documentModeItem.defaultLabel.value).toBe('Editing'); + }); + + it('should default to editing when documentMode is undefined', () => { + toolbar.documentMode = undefined; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Editing'); + expect(documentModeItem.defaultLabel.value).toBe('Editing'); + }); + + it('should default to editing when documentMode is an unknown value', () => { + toolbar.documentMode = 'unknown-mode'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Editing'); + expect(documentModeItem.defaultLabel.value).toBe('Editing'); + }); + + it('should handle uppercase mode values via toLowerCase', () => { + toolbar.documentMode = 'SUGGESTING'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Suggesting'); + expect(documentModeItem.defaultLabel.value).toBe('Suggesting'); + }); + + it('should handle mixed case mode values', () => { + toolbar.documentMode = 'Viewing'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Viewing'); + expect(documentModeItem.defaultLabel.value).toBe('Viewing'); + }); + + it('should use custom config.texts labels when provided', () => { + toolbar.config.texts.documentSuggestingMode = 'Custom Suggesting Label'; + toolbar.documentMode = 'suggesting'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Custom Suggesting Label'); + expect(documentModeItem.defaultLabel.value).toBe('Custom Suggesting Label'); + }); + + it('should not update icon when mode-specific icon is undefined', () => { + const originalIcon = { type: 'original-icon' }; + documentModeItem.icon.value = originalIcon; + toolbar.config.icons.documentSuggestingMode = undefined; + toolbar.config.icons.documentMode = undefined; + toolbar.documentMode = 'suggesting'; + + toolbar.updateToolbarState(); + + // Icon should remain unchanged when next.icon is falsy + expect(documentModeItem.icon.value).toBe(originalIcon); + }); + + it('should fall back to documentMode icon when mode-specific icon is missing', () => { + const fallbackIcon = { type: 'fallback-icon' }; + toolbar.config.icons.documentEditingMode = undefined; + toolbar.config.icons.documentMode = fallbackIcon; + toolbar.documentMode = 'editing'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.icon.value).toBe(fallbackIcon); + }); + + it('should not throw when documentModeItem is missing from toolbar', () => { + toolbar.toolbarItems = []; + toolbar.documentMode = 'suggesting'; + + expect(() => toolbar.updateToolbarState()).not.toThrow(); + }); + + it('should not update label when label.value is undefined', () => { + documentModeItem.label = {}; + toolbar.documentMode = 'suggesting'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBeUndefined(); + expect(documentModeItem.defaultLabel.value).toBe('Suggesting'); + }); + + it('should not update defaultLabel when defaultLabel.value is undefined', () => { + documentModeItem.defaultLabel = {}; + toolbar.documentMode = 'suggesting'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.label.value).toBe('Suggesting'); + expect(documentModeItem.defaultLabel.value).toBeUndefined(); + }); + + it('should not update icon when icon.value is undefined', () => { + documentModeItem.icon = {}; + toolbar.documentMode = 'suggesting'; + + toolbar.updateToolbarState(); + + expect(documentModeItem.icon.value).toBeUndefined(); + }); + }); + it('should update toolbar state with active formatting marks', () => { mockGetActiveFormatting.mockReturnValue([ { name: 'bold', attrs: {} },