3 * Backbone View providing the aural view of CKEditor keyboard UX configuration.
6 (function ($, Drupal, Backbone, _) {
7 Drupal.ckeditor.KeyboardView = Backbone.View.extend(/** @lends Drupal.ckeditor.KeyboardView# */{
10 * Backbone View for CKEditor toolbar configuration; keyboard UX.
14 * @augments Backbone.View
17 // Add keyboard arrow support.
18 this.$el.on('keydown.ckeditor', '.ckeditor-buttons a, .ckeditor-multiple-buttons a', this.onPressButton.bind(this));
19 this.$el.on('keydown.ckeditor', '[data-drupal-ckeditor-type="group"]', this.onPressGroup.bind(this));
29 * Handles keypresses on a CKEditor configuration button.
31 * @param {jQuery.Event} event
32 * The keypress event triggered.
34 onPressButton(event) {
37 63232, // Safari up arrow.
39 63233, // Safari down arrow.
41 const leftRightKeys = [
43 63234, // Safari left arrow.
45 63235, // Safari right arrow.
48 // Respond to an enter key press. Prevent the bubbling of the enter key
49 // press to the button group parent element.
50 if (event.keyCode === 13) {
51 event.stopPropagation();
54 // Only take action when a direction key is pressed.
55 if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
57 let $target = $(event.currentTarget);
58 let $button = $target.parent();
59 const $container = $button.parent();
60 let $group = $button.closest('.ckeditor-toolbar-group');
62 const containerType = $container.data('drupal-ckeditor-button-sorting');
63 const $availableButtons = this.$el.find('[data-drupal-ckeditor-button-sorting="source"]');
64 const $activeButtons = this.$el.find('.ckeditor-toolbar-active');
65 // The current location of the button, just in case it needs to be put
67 const $originalGroup = $group;
70 // Move available buttons between their container and the active
72 if (containerType === 'source') {
73 // Move the button to the active toolbar configuration when the down
74 // or up keys are pressed.
75 if (_.indexOf([40, 63233], event.keyCode) > -1) {
76 // Move the button to the first row, first button group index
78 $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
81 else if (containerType === 'target') {
82 // Move buttons between sibling buttons in a group and between groups.
83 if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
85 const $siblings = $container.children();
86 const index = $siblings.index($button);
87 if (_.indexOf([37, 63234], event.keyCode) > -1) {
88 // Move between sibling buttons.
90 $button.insertBefore($container.children().eq(index - 1));
92 // Move between button groups and rows.
94 // Move between button groups.
95 $group = $container.parent().prev();
96 if ($group.length > 0) {
97 $group.find('.ckeditor-toolbar-group-buttons').append($button);
101 $container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-group').not('.placeholder').find('.ckeditor-toolbar-group-buttons').eq(-1).append($button);
106 else if (_.indexOf([39, 63235], event.keyCode) > -1) {
107 // Move between sibling buttons.
108 if (index < ($siblings.length - 1)) {
109 $button.insertAfter($container.children().eq(index + 1));
111 // Move between button groups. Moving right at the end of a row
112 // will create a new group.
114 $container.parent().next().find('.ckeditor-toolbar-group-buttons').prepend($button);
118 // Move buttons between rows and the available button set.
119 else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
120 dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
121 $row = $container.closest('.ckeditor-row')[dir]();
122 // Move the button back into the available button set.
123 if (dir === 'prev' && $row.length === 0) {
124 // If this is a divider, just destroy it.
125 if ($button.data('drupal-ckeditor-type') === 'separator') {
129 // Focus on the first button in the active toolbar.
130 $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).children().eq(0).children().trigger('focus');
132 // Otherwise, move it.
134 $availableButtons.prepend($button);
138 $row.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
142 // Move dividers between their container and the active toolbar.
143 else if (containerType === 'dividers') {
144 // Move the button to the active toolbar configuration when the down
145 // or up keys are pressed.
146 if (_.indexOf([40, 63233], event.keyCode) > -1) {
147 // Move the button to the first row, first button group index
149 $button = $button.clone(true);
150 $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
151 $target = $button.children();
156 // Attempt to move the button to the new toolbar position.
157 Drupal.ckeditor.registerButtonMove(this, $button, (result) => {
158 // Put the button back if the registration failed.
159 // If the button was in a row, then it was in the active toolbar
160 // configuration. The button was probably placed in a new group, but
161 // that action was canceled.
162 if (!result && $originalGroup) {
163 $originalGroup.find('.ckeditor-buttons').append($button);
165 // Otherwise refresh the sortables to acknowledge the new button
168 view.$el.find('.ui-sortable').sortable('refresh');
170 // Refocus the target button so that the user can continue from a
172 $target.trigger('focus');
175 event.preventDefault();
176 event.stopPropagation();
181 * Handles keypresses on a CKEditor configuration group.
183 * @param {jQuery.Event} event
184 * The keypress event triggered.
186 onPressGroup(event) {
189 63232, // Safari up arrow.
191 63233, // Safari down arrow.
193 const leftRightKeys = [
195 63234, // Safari left arrow.
197 63235, // Safari right arrow.
200 // Respond to an enter key press.
201 if (event.keyCode === 13) {
203 // Open the group renaming dialog in the next evaluation cycle so that
204 // this event can be cancelled and the bubbling wiped out. Otherwise,
205 // Firefox has issues because the page focus is shifted to the dialog
206 // along with the keydown event.
207 window.setTimeout(() => {
208 Drupal.ckeditor.openGroupNameDialog(view, $(event.currentTarget));
210 event.preventDefault();
211 event.stopPropagation();
214 // Respond to direction key presses.
215 if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
216 const $group = $(event.currentTarget);
217 const $container = $group.parent();
218 const $siblings = $container.children();
221 // Move groups between sibling groups.
222 if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
223 index = $siblings.index($group);
224 // Move left between sibling groups.
225 if ((_.indexOf([37, 63234], event.keyCode) > -1)) {
227 $group.insertBefore($siblings.eq(index - 1));
229 // Wrap between rows. Insert the group before the placeholder group
230 // at the end of the previous row.
232 $group.insertBefore($container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-groups').children().eq(-1));
235 // Move right between sibling groups.
236 else if (_.indexOf([39, 63235], event.keyCode) > -1) {
237 // Move to the right if the next group is not a placeholder.
238 if (!$siblings.eq(index + 1).hasClass('placeholder')) {
239 $group.insertAfter($container.children().eq(index + 1));
241 // Wrap group between rows.
243 $container.closest('.ckeditor-row').next().find('.ckeditor-toolbar-groups').prepend($group);
247 // Move groups between rows.
248 else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
249 dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
250 $group.closest('.ckeditor-row')[dir]().find('.ckeditor-toolbar-groups').eq(0).prepend($group);
253 Drupal.ckeditor.registerGroupMove(this, $group);
254 $group.trigger('focus');
255 event.preventDefault();
256 event.stopPropagation();
260 }(jQuery, Drupal, Backbone, _));