Skip to content

Commit

Permalink
TINY-9337: Remove focusing after toggling the toolbar (#8272)
Browse files Browse the repository at this point in the history
* TINY-9337: Add skipFocus option to ToggleToolbarDrawer command

* TINY-9337: Update CHANGELOG.md

* TINY-9337: Fix eslint errors

* TINY-9337: Remove Toggling dependance on skipFocus value

* TINY-9337: Add skipFocus: false test

* TINY-9337: Use Singleton to store skipFocus state

* TINY-9337: Remove options for toggleWithoutFocusing api

* TINY-9337: Fix retrieving skipFocus singleton value

* TINY-9337: Update the ToggleToolbarDrawer command to be more readable

* TINY-9337: Simplify SplitFloatingToolbar code

* TINY-9337: Update ToolbarDrawerToggleTest
  • Loading branch information
yacodes committed Jan 10, 2023
1 parent 19daa63 commit 0015110
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 7 deletions.
@@ -1,4 +1,4 @@
import { Fun, Optional } from '@ephox/katamari';
import { Fun, Optional, Singleton } from '@ephox/katamari';

import * as ComponentStructure from '../../alien/ComponentStructure';
import * as AriaControls from '../../aria/AriaControls';
Expand Down Expand Up @@ -26,6 +26,14 @@ import * as Sketcher from './Sketcher';
import { Toolbar } from './Toolbar';
import { CompositeSketchFactory } from './UiSketcher';

const shouldSkipFocus = Singleton.value<boolean>();

const toggleWithoutFocusing = (button: AlloyComponent, externals: Record<string, any>) => {
shouldSkipFocus.set(true);
toggle(button, externals);
shouldSkipFocus.clear();
};

const toggle = (button: AlloyComponent, externals: Record<string, any>) => {
const toolbarSandbox = Coupling.getCoupled(button, 'toolbarSandbox');
if (Sandboxing.isOpen(toolbarSandbox)) {
Expand Down Expand Up @@ -61,17 +69,22 @@ const makeSandbox = (button: AlloyComponent, spec: FloatingToolbarButtonSpec, de
const ariaControls = AriaControls.manager();

const onOpen = (sandbox: AlloyComponent, toolbar: AlloyComponent) => {
const skipFocus = shouldSkipFocus.get().getOr(false);
detail.fetch().get((groups) => {
setGroups(button, toolbar, detail, spec.layouts, groups);
ariaControls.link(button.element);
Keying.focusIn(toolbar);
if (!skipFocus) {
Keying.focusIn(toolbar);
}
});
};

const onClose = () => {
// Toggle and focus the button
Toggling.off(button);
Focusing.focus(button);
if (!shouldSkipFocus.get().getOr(false)) {
Focusing.focus(button);
}
ariaControls.unlink(button.element);
};

Expand Down Expand Up @@ -154,6 +167,9 @@ const factory: CompositeSketchFactory<FloatingToolbarButtonDetail, FloatingToolb
toggle: (button: AlloyComponent) => {
toggle(button, externals);
},
toggleWithoutFocusing: (button: AlloyComponent) => {
toggleWithoutFocusing(button, externals);
},
getToolbar: (button: AlloyComponent) => {
return Sandboxing.getState(Coupling.getCoupled(button, 'toolbarSandbox'));
},
Expand All @@ -178,6 +194,9 @@ const FloatingToolbarButton: FloatingToolbarButtonSketcher = Sketcher.composite<
toggle: (apis, button) => {
apis.toggle(button);
},
toggleWithoutFocusing: (apis, button) => {
apis.toggleWithoutFocusing(button);
},
getToolbar: (apis, button) => apis.getToolbar(button),
isOpen: (apis, button) => apis.isOpen(button)
}
Expand Down
Expand Up @@ -87,6 +87,9 @@ const factory: CompositeSketchFactory<SplitFloatingToolbarDetail, SplitFloatingT
FloatingToolbarButton.toggle(floatingToolbarButton);
});
},
toggleWithoutFocusing: (toolbar: AlloyComponent) => {
memFloatingToolbarButton.getOpt(toolbar).each(FloatingToolbarButton.toggleWithoutFocusing);
},
isOpen: (toolbar: AlloyComponent) =>
memFloatingToolbarButton.getOpt(toolbar).map(FloatingToolbarButton.isOpen).getOr(false),
reposition: (toolbar: AlloyComponent) => {
Expand Down Expand Up @@ -122,6 +125,9 @@ const SplitFloatingToolbar: SplitFloatingToolbarSketcher = Sketcher.composite<Sp
toggle: (apis, toolbar) => {
apis.toggle(toolbar);
},
toggleWithoutFocusing: (apis, toolbar) => {
apis.toggle(toolbar);
},
isOpen: (apis, toolbar) => apis.isOpen(toolbar),
getOverflow: (apis, toolbar) => apis.getOverflow(toolbar)
}
Expand Down
Expand Up @@ -27,6 +27,7 @@ export interface FloatingToolbarButtonApis {
setGroups: (floatingToolbarButton: AlloyComponent, groups: AlloySpec[]) => Optional<AlloyComponent>;
reposition: (floatingToolbarButton: AlloyComponent) => void;
toggle: (floatingToolbarButton: AlloyComponent) => void;
toggleWithoutFocusing: (floatingToolbarButton: AlloyComponent) => void;
getToolbar: (floatingToolbarButton: AlloyComponent) => Optional<AlloyComponent>;
isOpen: (floatingToolbarButton: AlloyComponent) => boolean;
}
Expand Down
Expand Up @@ -19,6 +19,7 @@ export interface SplitToolbarBaseApis {
setGroups: (toolbar: AlloyComponent, groups: SketchSpec[]) => void;
refresh: (toolbar: AlloyComponent) => void;
toggle: (toolbar: AlloyComponent) => void;
toggleWithoutFocusing: (toolbar: AlloyComponent) => void;
isOpen: (toolbar: AlloyComponent) => boolean;
}

Expand Down
1 change: 1 addition & 0 deletions modules/tinymce/CHANGELOG.md
Expand Up @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New `color_default_foreground` and `color_default_background` options to set the initial default color for the `forecolor` and `backcolor` toolbar buttons and menu items. #TINY-9183
- New `getTransparentElements` function added to `tinymce.html.Schema` to return a map object of transparent HTML elements. #TINY-9172
- Added `ToggleToolbarDrawer` event to subscribe to toolbar’s opening and closing. #TINY-9271
- Added `skipFocus` option to the `ToggleToolbarDrawer` command to preserve focus. #TINY-9337

### Changed
- Transparent elements, like anchors, are now allowed in the root of the editor body if they contain blocks. #TINY-9172
Expand Down
8 changes: 6 additions & 2 deletions modules/tinymce/src/themes/silver/main/ts/Render.ts
Expand Up @@ -425,8 +425,12 @@ const setup = (editor: Editor): RenderInfo => {
OuterContainer.focusToolbar(outerContainer);
});

editor.addCommand('ToggleToolbarDrawer', () => {
OuterContainer.toggleToolbarDrawer(outerContainer);
editor.addCommand('ToggleToolbarDrawer', (_ui, options?: { skipFocus: boolean }) => {
if (options?.skipFocus) {
OuterContainer.toggleToolbarDrawerWithoutFocusing(outerContainer);
} else {
OuterContainer.toggleToolbarDrawer(outerContainer);
}
});

editor.addQueryStateHandler('ToggleToolbarDrawer', () => OuterContainer.isToolbarDrawerToggled(outerContainer));
Expand Down
Expand Up @@ -66,6 +66,7 @@ interface OuterContainerApis {
readonly setToolbars: (comp: AlloyComponent, toolbars: ToolbarGroup[][]) => void;
readonly refreshToolbar: (comp: AlloyComponent) => void;
readonly toggleToolbarDrawer: (comp: AlloyComponent) => void;
readonly toggleToolbarDrawerWithoutFocusing: (comp: AlloyComponent) => void;
readonly isToolbarDrawerToggled: (comp: AlloyComponent) => boolean;
readonly getThrobber: (comp: AlloyComponent) => Optional<AlloyComponent>;
readonly focusToolbar: (comp: AlloyComponent) => void;
Expand All @@ -81,7 +82,8 @@ interface OuterContainerApis {
interface ToolbarApis {
readonly setGroups: (toolbar: AlloyComponent, groups: SketchSpec[]) => void;
readonly refresh: (toolbar: AlloyComponent) => void;
readonly toggle?: (toolbar: AlloyComponent) => void;
readonly toggle: (toolbar: AlloyComponent) => void;
readonly toggleWithoutFocusing: (toolbar: AlloyComponent) => void;
readonly isOpen?: (toolbar: AlloyComponent) => boolean;
}

Expand Down Expand Up @@ -134,6 +136,11 @@ const factory: UiSketcher.CompositeSketchFactory<OuterContainerSketchDetail, Out
Optionals.mapFrom(toolbar.getApis<ToolbarApis>().toggle, (toggle) => toggle(toolbar));
});
},
toggleToolbarDrawerWithoutFocusing: (comp) => {
Composite.parts.getPart(comp, detail, 'toolbar').each((toolbar) => {
Optionals.mapFrom(toolbar.getApis<ToolbarApis>().toggleWithoutFocusing, (toggleWithoutFocusing) => toggleWithoutFocusing(toolbar));
});
},
isToolbarDrawerToggled: (comp) => {
// isOpen may not be defined on all toolbars e.g. 'scrolling' and 'wrap'
return Composite.parts.getPart(comp, detail, 'toolbar')
Expand Down Expand Up @@ -424,6 +431,9 @@ export default Sketcher.composite<OuterContainerSketchSpec, OuterContainerSketch
toggleToolbarDrawer: (apis, comp) => {
apis.toggleToolbarDrawer(comp);
},
toggleToolbarDrawerWithoutFocusing: (apis, comp) => {
apis.toggleToolbarDrawerWithoutFocusing(comp);
},
isToolbarDrawerToggled: (apis, comp) => {
return apis.isToolbarDrawerToggled(comp);
},
Expand Down
@@ -1,6 +1,7 @@
import { TestStore, Waiter } from '@ephox/agar';
import { TestStore, Waiter, FocusTools } from '@ephox/agar';
import { context, describe, it } from '@ephox/bedrock-client';
import { Arr } from '@ephox/katamari';
import { SugarDocument } from '@ephox/sugar';
import { McEditor, TinyUiActions } from '@ephox/wrap-mcagar';
import { assert } from 'chai';

Expand Down Expand Up @@ -107,4 +108,52 @@ describe('browser.tinymce.themes.silver.editor.toolbar.ToolbarDrawerToggleTest',
});
});
});

context(`Should preserve focus if skipFocus: true option was passed`, () => {
Arr.each<ToolbarMode>([ 'floating', 'sliding' ], (toolbarMode) => {
it(`TINY-9337: Preserves focus in ${toolbarMode} if skipFocus is true`, async () => {
const editor = await McEditor.pFromSettings<Editor>({
menubar: false,
statusbar: false,
width: 200,
toolbar_mode: toolbarMode,
base_url: '/project/tinymce/js/tinymce'
});
await UiUtils.pWaitForEditorToRender();
editor.focus();

const doc = SugarDocument.getDocument();
FocusTools.isOnSelector('Root element is focused', doc, 'iframe');
editor.execCommand('ToggleToolbarDrawer', false, { skipFocus: true });
await TinyUiActions.pWaitForUi(editor, '.tox-toolbar__overflow');
await FocusTools.pTryOnSelector('Focus should be preserved', doc, 'iframe');
editor.execCommand('ToggleToolbarDrawer', false, { skipFocus: true });
await FocusTools.pTryOnSelector('Focus should be preserved', doc, 'iframe');
McEditor.remove(editor);
});

it(`TINY-9337: Does not preserve focus in ${toolbarMode} if skipFocus is false`, async () => {
const editor = await McEditor.pFromSettings<Editor>({
menubar: false,
statusbar: false,
width: 200,
toolbar_mode: toolbarMode,
base_url: '/project/tinymce/js/tinymce'
});
await UiUtils.pWaitForEditorToRender();
editor.focus();
const initialFocusedElement = document.activeElement;
editor.execCommand('ToggleToolbarDrawer', false, { skipFocus: false });
await TinyUiActions.pWaitForUi(editor, '.tox-toolbar__overflow');
await Waiter.pTryUntil('Wait for toolbar to be completely open', () => {
assert.notEqual(initialFocusedElement, document.activeElement, 'Focus should not be preserved');
});
editor.execCommand('ToggleToolbarDrawer', false, { skipFocus: false });
await Waiter.pTryUntil('Wait for toolbar to be completely closed', () => {
assert.notEqual(initialFocusedElement, document.activeElement, 'Focus should not be preserved');
});
McEditor.remove(editor);
});
});
});
});

0 comments on commit 0015110

Please sign in to comment.