Skip to content

Commit

Permalink
Merge pull request #14893 from ckeditor/cf/5436
Browse files Browse the repository at this point in the history
Feature (ui): Implemented new UI components: `ListItemGroupView`, `TextareaView`, `SpinnerView`, `SearchView` and `AutocompleteView`.

Feature (ui): Introduced `HighlightedTextView` component for better search results presentation.

Other (ui): Made the `ButtonView` label logic open for extension.
  • Loading branch information
scofalik authored Sep 25, 2023
2 parents ba1bcba + 0e4742b commit 550f73c
Show file tree
Hide file tree
Showing 85 changed files with 6,256 additions and 577 deletions.
2 changes: 1 addition & 1 deletion docs/features/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Additionally, CKEditor 5 offers the {@link features/restricted-editing rest

{@img assets/img/features-collaboration.png 800 CKEditor 5 collaboration features.}

You can also easily track the progress and changes done in the content with the {@link features/revision-history revision history feature} {@icon @ckeditor/ckeditor5-revision-history/theme/icons/revision-history.svg Revision history}. This modern and robust document versioning tool lets you create named versions, compare changes, and restore previous document versions at ease, tracking all progress — also when multiple editors work together.
You can also easily track the progress and changes done in the content with the {@link features/revision-history revision history feature} {@icon @ckeditor/ckeditor5-core/theme/icons/history.svg Revision history}. This modern and robust document versioning tool lets you create named versions, compare changes, and restore previous document versions at ease, tracking all progress — also when multiple editors work together.

{@img assets/img/features-revision-history.png 800 CKEditor 5 document versioning feature.}

Expand Down
4 changes: 4 additions & 0 deletions packages/ckeditor5-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ import caption from './../theme/icons/caption.svg';
import check from './../theme/icons/check.svg';
import cog from './../theme/icons/cog.svg';
import eraser from './../theme/icons/eraser.svg';
import history from './../theme/icons/history.svg';
import lowVision from './../theme/icons/low-vision.svg';
import loupe from './../theme/icons/loupe.svg';
import image from './../theme/icons/image.svg';

import alignBottom from './../theme/icons/align-bottom.svg';
Expand Down Expand Up @@ -81,8 +83,10 @@ export const icons = {
check,
cog,
eraser,
history,
image,
lowVision,
loupe,
importExport,
paragraph,
plus,
Expand Down
1 change: 1 addition & 0 deletions packages/ckeditor5-core/theme/icons/history.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/ckeditor5-core/theme/icons/loupe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions packages/ckeditor5-mention/src/ui/domwrapperview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,11 @@ export default class DomWrapperView extends View {

this.element = this.domElement;
}

/**
* Focuses the DOM element.
*/
public focus(): void {
this.domElement.focus();
}
}
70 changes: 70 additions & 0 deletions packages/ckeditor5-mention/tests/ui/domwrapperview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* global document, Event */

import { Locale } from '@ckeditor/ckeditor5-utils';
import DomWrapperView from '../../src/ui/domwrapperview';

describe( 'DomWrapperView', () => {
let domElement, view;

beforeEach( () => {
domElement = document.createElement( 'div' );
view = new DomWrapperView( new Locale(), domElement );
} );

afterEach( () => {
view.destroy();
} );

describe( 'constructor()', () => {
it( 'should add CSS class to the element', () => {
expect( domElement.classList.contains( 'ck-button' ) ).to.be.true;
} );

it( 'should set #isOn observable property with a CSS class binding', () => {
expect( view.isOn ).to.be.false;

// TODO: This is actually a bug because the initial state is not set correctly.
expect( domElement.classList.contains( 'ck-on' ) ).to.be.false;
expect( domElement.classList.contains( 'ck-off' ) ).to.be.false;

view.isOn = true;
expect( domElement.classList.contains( 'ck-on' ) ).to.be.true;
expect( domElement.classList.contains( 'ck-off' ) ).to.be.false;

view.isOn = false;
expect( domElement.classList.contains( 'ck-on' ) ).to.be.false;
expect( domElement.classList.contains( 'ck-off' ) ).to.be.true;
} );

it( 'should fire #execute on DOM element click', () => {
const spy = sinon.spy();
view.on( 'execute', spy );

domElement.dispatchEvent( new Event( 'click' ) );

sinon.assert.calledOnce( spy );
} );
} );

describe( 'render()', () => {
it( 'should assign passed element to #element', () => {
view.render();
expect( view.element ).to.equal( domElement );
} );
} );

describe( 'focus()', () => {
it( 'focuses the #domElement', () => {
const spy = sinon.spy( domElement, 'focus' );

view.focus();

sinon.assert.calledOnce( spy );
} );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import {
ViewCollection,
type FocusableView,
type NormalizedColorOption,
type ColorPickerConfig
type ColorPickerConfig,
type FocusCyclerBackwardCycleEvent,
type FocusCyclerForwardCycleEvent
} from 'ckeditor5/src/ui';
import {
KeystrokeHandler,
Expand Down Expand Up @@ -390,13 +392,24 @@ export default class TableCellPropertiesView extends View {
view: this
} );

// Maintain continuous focus cycling over views that have focusable children and focus cyclers themselves.
[ this.borderColorInput, this.backgroundInput ].forEach( view => {
view.fieldView.focusCycler.on<FocusCyclerForwardCycleEvent>( 'forwardCycle', evt => {
this._focusCycler.focusNext();
evt.stop();
} );

view.fieldView.focusCycler.on<FocusCyclerBackwardCycleEvent>( 'backwardCycle', evt => {
this._focusCycler.focusPrevious();
evt.stop();
} );
} );

[
this.borderStyleDropdown,
this.borderColorInput,
this.borderColorInput.fieldView.dropdownView.buttonView,
this.borderWidthInput,
this.backgroundInput,
this.backgroundInput.fieldView.dropdownView.buttonView,
this.widthInput,
this.heightInput,
this.paddingInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import {
type DropdownView,
type InputTextView,
type NormalizedColorOption,
type ColorPickerConfig
type ColorPickerConfig,
type FocusCyclerForwardCycleEvent,
type FocusCyclerBackwardCycleEvent
} from 'ckeditor5/src/ui';
import { FocusTracker, KeystrokeHandler, type ObservableChangeEvent, type Locale } from 'ckeditor5/src/utils';
import { icons } from 'ckeditor5/src/core';
Expand Down Expand Up @@ -358,13 +360,24 @@ export default class TablePropertiesView extends View {
view: this
} );

// Maintain continuous focus cycling over views that have focusable children and focus cyclers themselves.
[ this.borderColorInput, this.backgroundInput ].forEach( view => {
view.fieldView.focusCycler.on<FocusCyclerForwardCycleEvent>( 'forwardCycle', evt => {
this._focusCycler.focusNext();
evt.stop();
} );

view.fieldView.focusCycler.on<FocusCyclerBackwardCycleEvent>( 'backwardCycle', evt => {
this._focusCycler.focusPrevious();
evt.stop();
} );
} );

[
this.borderStyleDropdown,
this.borderColorInput,
this.borderColorInput!.fieldView.dropdownView.buttonView,
this.borderWidthInput,
this.backgroundInput,
this.backgroundInput!.fieldView.dropdownView.buttonView,
this.widthInput,
this.heightInput,
this.alignmentToolbar,
Expand Down
39 changes: 22 additions & 17 deletions packages/ckeditor5-table/src/ui/colorinputview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
type DropdownView,
type ColorPickerConfig,
type ColorSelectorExecuteEvent,
type ColorSelectorColorPickerCancelEvent
type ColorSelectorColorPickerCancelEvent,
type FocusableView
} from 'ckeditor5/src/ui';

import { FocusTracker, KeystrokeHandler, type Locale } from 'ckeditor5/src/utils';
Expand All @@ -38,7 +39,7 @@ export type ColorInputViewOptions = {
*
* @internal
*/
export default class ColorInputView extends View {
export default class ColorInputView extends View implements FocusableView {
/**
* The value of the input.
*
Expand Down Expand Up @@ -87,6 +88,11 @@ export default class ColorInputView extends View {
*/
public readonly focusTracker: FocusTracker;

/**
* Helps cycling over focusable children in the input view.
*/
public readonly focusCycler: FocusCycler;

/**
* A collection of views that can be focused in the view.
*/
Expand Down Expand Up @@ -114,11 +120,6 @@ export default class ColorInputView extends View {
*/
protected _stillTyping: boolean;

/**
* Helps cycling over focusable items in the view.
*/
protected readonly _focusCycler: FocusCycler;

/**
* Creates an instance of the color input view.
*
Expand All @@ -145,7 +146,7 @@ export default class ColorInputView extends View {
this.keystrokes = new KeystrokeHandler();
this._stillTyping = false;

this._focusCycler = new FocusCycler( {
this.focusCycler = new FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
Expand Down Expand Up @@ -181,15 +182,23 @@ export default class ColorInputView extends View {
public override render(): void {
super.render();

// Start listening for the keystrokes coming from the dropdown panel view.
this.keystrokes.listenTo( this.dropdownView.panelView.element! );
[ this.inputView, this.dropdownView.buttonView ].forEach( view => {
this.focusTracker.add( view.element! );
this._focusables.add( view );
} );

this.keystrokes.listenTo( this.element! );
}

/**
* Focuses the input.
* Focuses the view.
*/
public focus(): void {
this.inputView.focus();
public focus( direction: 1 | -1 ): void {
if ( direction === -1 ) {
this.focusCycler.focusLast();
} else {
this.focusCycler.focusFirst();
}
}

/**
Expand Down Expand Up @@ -250,10 +259,6 @@ export default class ColorInputView extends View {
dropdown.panelView.children.add( colorSelector );
dropdown.bind( 'isEnabled' ).to( this, 'isReadOnly', value => !value );

this._focusables.add( colorSelector );

this.focusTracker.add( colorSelector.element! );

dropdown.on( 'change:isOpen', ( evt, name, isVisible ) => {
if ( isVisible ) {
colorSelector.updateSelectedColors();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -695,10 +695,8 @@ describe( 'table cell properties', () => {
expect( view._focusables.map( f => f ) ).to.have.members( [
view.borderStyleDropdown,
view.borderColorInput,
view.borderColorInput.fieldView.dropdownView.buttonView,
view.borderWidthInput,
view.backgroundInput,
view.backgroundInput.fieldView.dropdownView.buttonView,
view.widthInput,
view.heightInput,
view.paddingInput,
Expand Down Expand Up @@ -775,6 +773,49 @@ describe( 'table cell properties', () => {
sinon.assert.calledOnce( keyEvtData.stopPropagation );
sinon.assert.calledOnce( spy );
} );

it( 'providing seamless forward navigation over child views with their own focusable children and focus cyclers', () => {
const keyEvtData = {
keyCode: keyCodes.tab,
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};

// Mock the border color dropdown button button is focused.
view.focusTracker.isFocused = view.borderColorInput.fieldView.focusTracker.isFocused = true;
view.focusTracker.focusedElement = view.borderColorInput.element;
view.borderColorInput.fieldView.focusTracker.focusedElement =
view.borderColorInput.fieldView.dropdownView.buttonView.element;

const spy = sinon.spy( view.borderWidthInput, 'focus' );

view.borderColorInput.fieldView.keystrokes.press( keyEvtData );
sinon.assert.calledOnce( keyEvtData.preventDefault );
sinon.assert.calledOnce( keyEvtData.stopPropagation );
sinon.assert.calledOnce( spy );
} );

it( 'providing seamless backward navigation over child views with their own focusable children and focus cyclers', () => {
const keyEvtData = {
keyCode: keyCodes.tab,
shiftKey: true,
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};

// Mock the border color dropdown input is focused.
view.focusTracker.isFocused = view.borderColorInput.fieldView.focusTracker.isFocused = true;
view.focusTracker.focusedElement = view.borderColorInput.element;
view.borderColorInput.fieldView.focusTracker.focusedElement =
view.borderColorInput.fieldView.inputView.element;

const spy = sinon.spy( view.borderStyleDropdown, 'focus' );

view.borderColorInput.fieldView.keystrokes.press( keyEvtData );
sinon.assert.calledOnce( keyEvtData.preventDefault );
sinon.assert.calledOnce( keyEvtData.stopPropagation );
sinon.assert.calledOnce( spy );
} );
} );
} );

Expand Down
Loading

0 comments on commit 550f73c

Please sign in to comment.