@ckeditor/ckeditor5-link
Advanced tools
Comparing version 0.4.0 to 0.5.0
{ | ||
"Unlink": "Toolbar button tooltip for the Unlink feature.", | ||
"Link": "Toolbar button tooltip for the Link feature.", | ||
"Link URL": "Label for the url input in the Link dialog.", | ||
"Cancel": "Label for the cancel button in the Link dialog." | ||
"Link URL": "Label for the url input in the Link dialog." | ||
} |
{ | ||
"name": "@ckeditor/ckeditor5-link", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "Link feature for CKEditor 5.", | ||
@@ -5,0 +5,0 @@ "keywords": [], |
@@ -16,3 +16,2 @@ /** | ||
import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler'; | ||
import escPressHandler from '@ckeditor/ckeditor5-ui/src/bindings/escpresshandler'; | ||
@@ -172,9 +171,18 @@ import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; | ||
// Close on `ESC` press. | ||
escPressHandler( { | ||
emitter: balloonPanelView, | ||
activator: () => balloonPanelView.isVisible, | ||
callback: () => this._hidePanel( true ) | ||
// Focus the form if balloon panel is open and tab key has been pressed. | ||
editor.keystrokes.set( 'Tab', ( data, cancel ) => { | ||
if ( balloonPanelView.isVisible && !this.formView.focusTracker.isFocused ) { | ||
this.formView.focus(); | ||
cancel(); | ||
} | ||
} ); | ||
// Close the panel on esc key press when editable has focus. | ||
editor.keystrokes.set( 'Esc', ( data, cancel ) => { | ||
if ( balloonPanelView.isVisible ) { | ||
this._hidePanel( true ); | ||
cancel(); | ||
} | ||
} ); | ||
// Close on click outside of balloon panel element. | ||
@@ -217,2 +225,8 @@ clickOutsideHandler( { | ||
// Close the panel on esc key press when the form has focus. | ||
formView.keystrokes.set( 'Esc', ( data, cancel ) => { | ||
this._hidePanel( true ); | ||
cancel(); | ||
} ); | ||
// Hide balloon panel after clicking on formView `Cancel` button. | ||
@@ -219,0 +233,0 @@ this.listenTo( formView, 'cancel', () => this._hidePanel( true ) ); |
@@ -12,2 +12,3 @@ /** | ||
import Template from '@ckeditor/ckeditor5-ui/src/template'; | ||
import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection'; | ||
@@ -19,2 +20,5 @@ import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; | ||
import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler'; | ||
import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; | ||
import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler'; | ||
import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; | ||
@@ -38,2 +42,18 @@ /** | ||
/** | ||
* Tracks information about DOM focus in the form. | ||
* | ||
* @readonly | ||
* @member {module:utils/focustracker~FocusTracker} | ||
*/ | ||
this.focusTracker = new FocusTracker(); | ||
/** | ||
* Instance of the {@link module:core/keystrokehandler~KeystrokeHandler}. | ||
* | ||
* @readonly | ||
* @member {module:core/keystrokehandler~KeystrokeHandler} | ||
*/ | ||
this.keystrokes = new KeystrokeHandler(); | ||
/** | ||
* The url input view. | ||
@@ -67,5 +87,31 @@ * | ||
// Register child views. | ||
this.addChildren( [ this.urlInputView, this.saveButtonView, this.cancelButtonView, this.unlinkButtonView ] ); | ||
/** | ||
* A collection of views which can be focused in the form. | ||
* | ||
* @readonly | ||
* @protected | ||
* @member {module:ui/viewcollection~ViewCollection} | ||
*/ | ||
this._focusables = new ViewCollection(); | ||
/** | ||
* Helps cycling over {@link #_focusables} in the form. | ||
* | ||
* @readonly | ||
* @protected | ||
* @member {module:ui/focuscycler~FocusCycler} | ||
*/ | ||
this._focusCycler = new FocusCycler( { | ||
focusables: this._focusables, | ||
focusTracker: this.focusTracker, | ||
keystrokeHandler: this.keystrokes, | ||
actions: { | ||
// Navigate form fields backwards using the shift + tab keystroke. | ||
focusPrevious: 'shift + tab', | ||
// Navigate form fields forwards using the tab key. | ||
focusNext: 'tab' | ||
} | ||
} ); | ||
Template.extend( this.saveButtonView.template, { | ||
@@ -111,5 +157,37 @@ attributes: { | ||
} ); | ||
const childViews = [ | ||
this.urlInputView, | ||
this.saveButtonView, | ||
this.cancelButtonView, | ||
this.unlinkButtonView | ||
]; | ||
childViews.forEach( v => { | ||
// Register the view as focusable. | ||
this._focusables.add( v ); | ||
// Register the view in the focus tracker. | ||
this.focusTracker.add( v.element ); | ||
} ); | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
init() { | ||
// Start listening for the keystrokes coming from #element. | ||
this.keystrokes.listenTo( this.element ); | ||
return super.init(); | ||
} | ||
/** | ||
* Focuses the fist {@link #_focusables} in the form. | ||
*/ | ||
focus() { | ||
this._focusCycler.focusFirst(); | ||
} | ||
/** | ||
* Create labeled input view. | ||
@@ -116,0 +194,0 @@ * |
@@ -186,12 +186,75 @@ /** | ||
it( 'should focus the link form on Tab key press', () => { | ||
const keyEvtData = { | ||
keyCode: keyCodes.tab, | ||
preventDefault: sinon.spy(), | ||
stopPropagation: sinon.spy() | ||
}; | ||
// Mock balloon invisible, form not focused. | ||
balloonPanelView.isVisible = false; | ||
formView.focusTracker.isFocused = false; | ||
const spy = sinon.spy( formView, 'focus' ); | ||
editor.keystrokes.press( keyEvtData ); | ||
sinon.assert.notCalled( keyEvtData.preventDefault ); | ||
sinon.assert.notCalled( keyEvtData.stopPropagation ); | ||
sinon.assert.notCalled( spy ); | ||
// Mock balloon visible, form focused. | ||
balloonPanelView.isVisible = true; | ||
formView.focusTracker.isFocused = true; | ||
editor.keystrokes.press( keyEvtData ); | ||
sinon.assert.notCalled( keyEvtData.preventDefault ); | ||
sinon.assert.notCalled( keyEvtData.stopPropagation ); | ||
sinon.assert.notCalled( spy ); | ||
// Mock balloon visible, form not focused. | ||
balloonPanelView.isVisible = true; | ||
formView.focusTracker.isFocused = false; | ||
editor.keystrokes.press( keyEvtData ); | ||
sinon.assert.calledOnce( keyEvtData.preventDefault ); | ||
sinon.assert.calledOnce( keyEvtData.stopPropagation ); | ||
sinon.assert.calledOnce( spy ); | ||
} ); | ||
describe( 'close listeners', () => { | ||
describe( 'keyboard', () => { | ||
it( 'should close after `ESC` press', () => { | ||
it( 'should close after Esc key press (from editor)', () => { | ||
const keyEvtData = { | ||
keyCode: keyCodes.esc, | ||
preventDefault: sinon.spy(), | ||
stopPropagation: sinon.spy() | ||
}; | ||
balloonPanelView.isVisible = false; | ||
editor.keystrokes.press( keyEvtData ); | ||
sinon.assert.notCalled( hidePanelSpy ); | ||
sinon.assert.notCalled( focusEditableSpy ); | ||
balloonPanelView.isVisible = true; | ||
dispatchKeyboardEvent( document, 'keydown', keyCodes.esc ); | ||
editor.keystrokes.press( keyEvtData ); | ||
expect( hidePanelSpy.calledOnce ).to.true; | ||
expect( focusEditableSpy.calledOnce ).to.true; | ||
sinon.assert.calledOnce( hidePanelSpy ); | ||
sinon.assert.calledOnce( focusEditableSpy ); | ||
} ); | ||
it( 'should close after Esc key press (from the form)', () => { | ||
const keyEvtData = { | ||
keyCode: keyCodes.esc, | ||
preventDefault: sinon.spy(), | ||
stopPropagation: sinon.spy() | ||
}; | ||
formView.keystrokes.press( keyEvtData ); | ||
sinon.assert.calledOnce( hidePanelSpy ); | ||
sinon.assert.calledOnce( focusEditableSpy ); | ||
} ); | ||
} ); | ||
@@ -419,17 +482,1 @@ | ||
} ); | ||
// Creates and dispatches keyboard event with specified keyCode. | ||
// | ||
// @private | ||
// @param {EventTarget} eventTarget | ||
// @param {String} eventName | ||
// @param {Number} keyCode | ||
function dispatchKeyboardEvent( element, eventName, keyCode ) { | ||
const event = document.createEvent( 'Events' ); | ||
event.initEvent( eventName, true, true ); | ||
event.which = keyCode; | ||
event.keyCode = keyCode; | ||
element.dispatchEvent( event ); | ||
} |
@@ -6,5 +6,13 @@ /** | ||
import LinkFormView from '../../src/ui/linkformview'; | ||
import View from '@ckeditor/ckeditor5-ui/src/view'; | ||
import LinkFormView from '../../src/ui/linkformview'; | ||
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; | ||
import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; | ||
import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; | ||
import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler'; | ||
import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection'; | ||
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; | ||
testUtils.createSinonSandbox(); | ||
describe( 'LinkFormView', () => { | ||
@@ -16,3 +24,3 @@ let view; | ||
view.init(); | ||
return view.init(); | ||
} ); | ||
@@ -37,2 +45,38 @@ | ||
it( 'should create #focusTracker instance', () => { | ||
expect( view.focusTracker ).to.be.instanceOf( FocusTracker ); | ||
} ); | ||
it( 'should create #keystrokes instance', () => { | ||
expect( view.keystrokes ).to.be.instanceOf( KeystrokeHandler ); | ||
} ); | ||
it( 'should create #_focusCycler instance', () => { | ||
expect( view._focusCycler ).to.be.instanceOf( FocusCycler ); | ||
} ); | ||
it( 'should create #_focusables view collection', () => { | ||
expect( view._focusables ).to.be.instanceOf( ViewCollection ); | ||
} ); | ||
it( 'should register child views in #_focusables', () => { | ||
expect( view._focusables.map( f => f ) ).to.have.members( [ | ||
view.urlInputView, | ||
view.saveButtonView, | ||
view.cancelButtonView, | ||
view.unlinkButtonView | ||
] ); | ||
} ); | ||
it( 'should register child views\' #element in #focusTracker', () => { | ||
const spy = testUtils.sinon.spy( FocusTracker.prototype, 'add' ); | ||
view = new LinkFormView( { t: () => {} } ); | ||
sinon.assert.calledWithExactly( spy.getCall( 0 ), view.urlInputView.element ); | ||
sinon.assert.calledWithExactly( spy.getCall( 1 ), view.saveButtonView.element ); | ||
sinon.assert.calledWithExactly( spy.getCall( 2 ), view.cancelButtonView.element ); | ||
sinon.assert.calledWithExactly( spy.getCall( 3 ), view.unlinkButtonView.element ); | ||
} ); | ||
it( 'should fire `cancel` event on cancelButtonView#execute', () => { | ||
@@ -77,2 +121,56 @@ const spy = sinon.spy(); | ||
describe( 'init()', () => { | ||
it( 'starts listening for #keystrokes coming from #element', () => { | ||
view = new LinkFormView( { t: () => {} } ); | ||
const spy = sinon.spy( view.keystrokes, 'listenTo' ); | ||
return view.init().then( () => { | ||
sinon.assert.calledOnce( spy ); | ||
sinon.assert.calledWithExactly( spy, view.element ); | ||
} ); | ||
} ); | ||
describe( 'activates keyboard navigation for the toolbar', () => { | ||
it( 'so "tab" the next focusable item', () => { | ||
const keyEvtData = { | ||
keyCode: keyCodes.tab, | ||
preventDefault: sinon.spy(), | ||
stopPropagation: sinon.spy() | ||
}; | ||
// Mock the url input is focused. | ||
view.focusTracker.isFocused = true; | ||
view.focusTracker.focusedElement = view.urlInputView.element; | ||
const spy = sinon.spy( view.saveButtonView, 'focus' ); | ||
view.keystrokes.press( keyEvtData ); | ||
sinon.assert.calledOnce( keyEvtData.preventDefault ); | ||
sinon.assert.calledOnce( keyEvtData.stopPropagation ); | ||
sinon.assert.calledOnce( spy ); | ||
} ); | ||
it( 'so "shift + tab" focuses the previous focusable item', () => { | ||
const keyEvtData = { | ||
keyCode: keyCodes.tab, | ||
shiftKey: true, | ||
preventDefault: sinon.spy(), | ||
stopPropagation: sinon.spy() | ||
}; | ||
// Mock the cancel button is focused. | ||
view.focusTracker.isFocused = true; | ||
view.focusTracker.focusedElement = view.cancelButtonView.element; | ||
const spy = sinon.spy( view.saveButtonView, 'focus' ); | ||
view.keystrokes.press( keyEvtData ); | ||
sinon.assert.calledOnce( keyEvtData.preventDefault ); | ||
sinon.assert.calledOnce( keyEvtData.stopPropagation ); | ||
sinon.assert.calledOnce( spy ); | ||
} ); | ||
} ); | ||
} ); | ||
describe( 'DOM bindings', () => { | ||
@@ -90,2 +188,12 @@ describe( 'submit event', () => { | ||
} ); | ||
describe( 'focus()', () => { | ||
it( 'focuses the #urlInputView', () => { | ||
const spy = sinon.spy( view.urlInputView, 'focus' ); | ||
view.focus(); | ||
sinon.assert.calledOnce( spy ); | ||
} ); | ||
} ); | ||
} ); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
86312
1740
34