Skip to content

Commit

Permalink
Merge branch 'master' into ck/epic/1944-bookmark
Browse files Browse the repository at this point in the history
  • Loading branch information
godai78 committed Oct 22, 2024
2 parents f6618f4 + 926400f commit c6787e7
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,17 @@
max-height: var(--ck-dialog-max-height);
max-width: var(--ck-dialog-max-width);
border: 1px solid var(--ck-color-base-border);
overscroll-behavior: contain;

& .ck.ck-form__header {
border-bottom: 1px solid var(--ck-color-dialog-form-header-border);
}
}

.ck-dialog-body-scroll-locked {
overflow: hidden;
}

@keyframes ck-dialog-fade-in {
0% {
background: hsla( 0, 0%, 0%, 0 );
Expand Down
31 changes: 31 additions & 0 deletions packages/ckeditor5-ui/src/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ export default class Dialog extends Plugin {
} );
}

/**
* @inheritDoc
*/
public override destroy(): void {
super.destroy();

this._unlockBodyScroll();
}

/**
* Initiates listeners for the `show` and `hide` events emitted by this plugin.
*
Expand Down Expand Up @@ -302,6 +311,10 @@ export default class Dialog extends Plugin {
position = isModal ? DialogViewPosition.SCREEN_CENTER : DialogViewPosition.EDITOR_CENTER;
}

if ( isModal ) {
this._lockBodyScroll();
}

view.set( {
position,
_isVisible: true,
Expand Down Expand Up @@ -349,6 +362,10 @@ export default class Dialog extends Plugin {
const editor = this.editor;
const view = this.view;

if ( view.isModal ) {
this._unlockBodyScroll();
}

// Reset the content view to prevent its children from being destroyed in the standard
// View#destroy() (and collections) chain. If the content children were left in there,
// they would have to be re-created by the feature using the dialog every time the dialog
Expand All @@ -368,6 +385,20 @@ export default class Dialog extends Plugin {
this.isOpen = false;
Dialog._visibleDialogPlugin = null;
}

/**
* Makes the <body> unscrollable (e.g. when the modal shows up).
*/
private _lockBodyScroll(): void {
document.body.classList.add( 'ck-dialog-body-scroll-locked' );
}

/**
* Makes the <body> scrollable again (e.g. once the modal hides).
*/
private _unlockBodyScroll(): void {
document.body.classList.remove( 'ck-dialog-body-scroll-locked' );
}
}

/**
Expand Down
55 changes: 55 additions & 0 deletions packages/ckeditor5-ui/tests/dialog/dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,22 @@ describe( 'Dialog', () => {
} );
} );

describe( 'destroy()', () => {
it( 'should unlock scrolling on the document if modal was displayed', () => {
dialogPlugin._show( {
position: DialogViewPosition.EDITOR_CENTER,
isModal: true,
className: 'foo'
} );

expect( document.body.classList.contains( 'ck-dialog-body-scroll-locked' ) ).to.be.true;

dialogPlugin.destroy();

expect( document.body.classList.contains( 'ck-dialog-body-scroll-locked' ) ).to.be.false;
} );
} );

describe( 'show()', () => {
it( 'should fire the `show` event with id in namespace', () => {
const spy = sinon.spy();
Expand Down Expand Up @@ -430,6 +446,29 @@ describe( 'Dialog', () => {
expect( dialogPlugin._onHide, '`_onHide` should be set' ).to.be.a( 'function' );
expect( Dialog._visibleDialogPlugin, '`_visibleDialogPlugin` instance should be set' ).to.equal( dialogPlugin );
} );

it( 'should lock document scroll if the dialog is a modal', () => {
expect( document.body.classList.contains( 'ck-dialog-body-scroll-locked' ) ).to.be.false;

dialogPlugin._show( {
position: DialogViewPosition.EDITOR_CENTER,
isModal: true,
className: 'foo'
} );

expect( document.body.classList.contains( 'ck-dialog-body-scroll-locked' ) ).to.be.true;
} );

it( 'should not lock document scroll if the dialog is not a modal', () => {
expect( document.body.classList.contains( 'ck-dialog-body-scroll-locked' ) ).to.be.false;

dialogPlugin._show( {
position: DialogViewPosition.EDITOR_CENTER,
className: 'foo'
} );

expect( document.body.classList.contains( 'ck-dialog-body-scroll-locked' ) ).to.be.false;
} );
} );

describe( 'hide()', () => {
Expand Down Expand Up @@ -500,5 +539,21 @@ describe( 'Dialog', () => {
expect( dialogPlugin._onHide, '`_onHide` should be reset' ).to.be.undefined;
expect( Dialog._visibleDialogPlugin, '`_visibleDialogPlugin` instance should be reset' ).to.be.null;
} );

it( 'should unlock document scroll if the dialog is a modal', () => {
expect( document.body.classList.contains( 'ck-dialog-body-scroll-locked' ) ).to.be.false;

dialogPlugin._show( {
position: DialogViewPosition.EDITOR_CENTER,
isModal: true,
className: 'foo'
} );

expect( document.body.classList.contains( 'ck-dialog-body-scroll-locked' ) ).to.be.true;

dialogPlugin._hide();

expect( document.body.classList.contains( 'ck-dialog-body-scroll-locked' ) ).to.be.false;
} );
} );
} );
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<form>
<fieldset>
<legend>Is document scrollable?</legend>
<div>
<input type="radio" id="isScrollable-yes" name="isScrollable" value="yes" />
<label for="isScrollable-yes">yes</label>
</div>
<div>
<input type="radio" id="isScrollable-no" name="isScrollable" value="no" checked />
<label for="isScrollable-no">no</label>
</div>
</fieldset>
<fieldset>
<legend>Has &lt;body&gt; position?</legend>
<div>
<input type="radio" id="hasPosition-yes" name="hasPosition" value="yes" />
<label for="hasPosition-yes">yes</label>
</div>
<div>
<input type="radio" id="hasPosition-no" name="hasPosition" value="no" checked />
<label for="hasPosition-no">no</label>
</div>
</fieldset>
</form>

<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>

<div id="editor">Editor content.</div>

<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>

<style>
.ck.ck-editor {
margin-top: 300px;
}

body {
outline: 2px dashed blue;
outline-offset: -2px;
}

body::before {
content: "<BODY>";
position: absolute;
display: block;
top: 10px;
right: 10px;
background: blue;
color: white;
padding: 2px 4px;
font-size: 8px;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Locking page scroll when the modal is open

1. Click the ">>> Open modal <<<" button.
2. Ensure that the entire page underneath does not scroll in different browsers.
3. Ensure the same applies when various test options are turned on.
4. Play with the global scroll of the webpage before opening the modal.

Known issues:
* In Safari iOS, the page is still scrollable when the modal is visible.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import { Plugin } from '@ckeditor/ckeditor5-core';
import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
import { ButtonView, Dialog, TextareaView } from '../../../src/index.js';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
import { Heading } from '@ckeditor/ckeditor5-heading';
import { Bold, Italic } from '@ckeditor/ckeditor5-basic-styles';

declare global {
interface Window { editor: any }
}

interface FormElements extends HTMLFormControlsCollection {
isScrollable: RadioNodeList;
hasPosition: RadioNodeList;
}

class PluginWithModal extends Plugin {
public static get requires() {
return [ Dialog ] as const;
}

public init(): void {
this.editor.ui.componentFactory.add( 'modal', locale => {
const button = new ButtonView( locale );
const dialog = this.editor.plugins.get( 'Dialog' );
const textareaView = new TextareaView( locale );

textareaView.minRows = 5;
textareaView.maxRows = 10;
textareaView.value =
`Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor
quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean
ultricies mi vitae est. Mauris placerat eleifend leo.`.repeat( 10 );

button.label = '>>> Open modal <<<';
button.withText = true;
button.on( 'execute', () => {
dialog.show( {
id: 'modalWithText',
isModal: true,
title: 'A modal',
content: textareaView,
actionButtons: [
{
label: 'Close',
class: 'ck-button-action',
withText: true,
onExecute: () => dialog.hide()
}
],
onShow() {
textareaView.element!.style.margin = '10px';
textareaView.element!.style.width = '400px';
}
} );
} );

return button;
} );
}
}

ClassicEditor.create( document.querySelector( '#editor' ) as HTMLElement, {
extraPlugins: [ Essentials, Heading, Bold, Italic, PluginWithModal ],
toolbar: [ 'heading', '|', 'bold', 'italic', '|', 'modal' ]
} ).then( editor => {
window.editor = editor;
} );

const form = document.querySelector( 'form' )!;

form.addEventListener( 'change', () => {
const elements = form.elements as FormElements;

document.body.style.height = elements.isScrollable.value === 'yes' ? '3000px' : '';
document.body.style.width = elements.isScrollable.value === 'yes' ? '3000px' : '';

document.body.style.position = elements.hasPosition.value === 'yes' ? 'absolute' : '';
document.body.style.top = elements.hasPosition.value === 'yes' ? '100px' : '';
document.body.style.left = elements.hasPosition.value === 'yes' ? '100px' : '';
} );
2 changes: 1 addition & 1 deletion packages/ckeditor5-ui/tests/manual/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ class MultiRootEditorIntegration extends Plugin {
this.listenTo( view, 'execute', () => {
const root = editor.model.document.selection.getFirstRange()!.root;

editor.detachRoot( root, true );
editor.detachRoot( root.rootName!, true );
} );

return view;
Expand Down

0 comments on commit c6787e7

Please sign in to comment.