3 * Backbone View providing the aural view of CKEditor keyboard UX configuration.
6 (function($, Drupal, Backbone, _) {
7 Drupal.ckeditor.KeyboardView = Backbone.View.extend(
8 /** @lends Drupal.ckeditor.KeyboardView# */ {
10 * Backbone View for CKEditor toolbar configuration; keyboard UX.
14 * @augments Backbone.View
17 // Add keyboard arrow support.
20 '.ckeditor-buttons a, .ckeditor-multiple-buttons a',
21 this.onPressButton.bind(this),
25 '[data-drupal-ckeditor-type="group"]',
26 this.onPressGroup.bind(this),
36 * Handles keypresses on a CKEditor configuration button.
38 * @param {jQuery.Event} event
39 * The keypress event triggered.
41 onPressButton(event) {
44 63232, // Safari up arrow.
46 63233, // Safari down arrow.
48 const leftRightKeys = [
50 63234, // Safari left arrow.
52 63235, // Safari right arrow.
55 // Respond to an enter key press. Prevent the bubbling of the enter key
56 // press to the button group parent element.
57 if (event.keyCode === 13) {
58 event.stopPropagation();
61 // Only take action when a direction key is pressed.
62 if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
64 let $target = $(event.currentTarget);
65 let $button = $target.parent();
66 const $container = $button.parent();
67 let $group = $button.closest('.ckeditor-toolbar-group');
69 const containerType = $container.data(
70 'drupal-ckeditor-button-sorting',
72 const $availableButtons = this.$el.find(
73 '[data-drupal-ckeditor-button-sorting="source"]',
75 const $activeButtons = this.$el.find('.ckeditor-toolbar-active');
76 // The current location of the button, just in case it needs to be put
78 const $originalGroup = $group;
81 // Move available buttons between their container and the active
83 if (containerType === 'source') {
84 // Move the button to the active toolbar configuration when the down
85 // or up keys are pressed.
86 if (_.indexOf([40, 63233], event.keyCode) > -1) {
87 // Move the button to the first row, first button group index
90 .find('.ckeditor-toolbar-group-buttons')
94 } else if (containerType === 'target') {
95 // Move buttons between sibling buttons in a group and between groups.
96 if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
98 const $siblings = $container.children();
99 const index = $siblings.index($button);
100 if (_.indexOf([37, 63234], event.keyCode) > -1) {
101 // Move between sibling buttons.
103 $button.insertBefore($container.children().eq(index - 1));
105 // Move between button groups and rows.
107 // Move between button groups.
108 $group = $container.parent().prev();
109 if ($group.length > 0) {
111 .find('.ckeditor-toolbar-group-buttons')
114 // Wrap between rows.
117 .closest('.ckeditor-row')
119 .find('.ckeditor-toolbar-group')
121 .find('.ckeditor-toolbar-group-buttons')
128 else if (_.indexOf([39, 63235], event.keyCode) > -1) {
129 // Move between sibling buttons.
130 if (index < $siblings.length - 1) {
131 $button.insertAfter($container.children().eq(index + 1));
133 // Move between button groups. Moving right at the end of a row
134 // will create a new group.
139 .find('.ckeditor-toolbar-group-buttons')
144 // Move buttons between rows and the available button set.
145 else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
147 _.indexOf([38, 63232], event.keyCode) > -1 ? 'prev' : 'next';
148 $row = $container.closest('.ckeditor-row')[dir]();
149 // Move the button back into the available button set.
150 if (dir === 'prev' && $row.length === 0) {
151 // If this is a divider, just destroy it.
152 if ($button.data('drupal-ckeditor-type') === 'separator') {
153 $button.off().remove();
154 // Focus on the first button in the active toolbar.
156 .find('.ckeditor-toolbar-group-buttons')
163 // Otherwise, move it.
165 $availableButtons.prepend($button);
169 .find('.ckeditor-toolbar-group-buttons')
175 // Move dividers between their container and the active toolbar.
176 else if (containerType === 'dividers') {
177 // Move the button to the active toolbar configuration when the down
178 // or up keys are pressed.
179 if (_.indexOf([40, 63233], event.keyCode) > -1) {
180 // Move the button to the first row, first button group index
182 $button = $button.clone(true);
184 .find('.ckeditor-toolbar-group-buttons')
187 $target = $button.children();
192 // Attempt to move the button to the new toolbar position.
193 Drupal.ckeditor.registerButtonMove(this, $button, result => {
194 // Put the button back if the registration failed.
195 // If the button was in a row, then it was in the active toolbar
196 // configuration. The button was probably placed in a new group, but
197 // that action was canceled.
198 if (!result && $originalGroup) {
199 $originalGroup.find('.ckeditor-buttons').append($button);
201 // Otherwise refresh the sortables to acknowledge the new button
204 view.$el.find('.ui-sortable').sortable('refresh');
206 // Refocus the target button so that the user can continue from a
208 $target.trigger('focus');
211 event.preventDefault();
212 event.stopPropagation();
217 * Handles keypresses on a CKEditor configuration group.
219 * @param {jQuery.Event} event
220 * The keypress event triggered.
222 onPressGroup(event) {
225 63232, // Safari up arrow.
227 63233, // Safari down arrow.
229 const leftRightKeys = [
231 63234, // Safari left arrow.
233 63235, // Safari right arrow.
236 // Respond to an enter key press.
237 if (event.keyCode === 13) {
239 // Open the group renaming dialog in the next evaluation cycle so that
240 // this event can be cancelled and the bubbling wiped out. Otherwise,
241 // Firefox has issues because the page focus is shifted to the dialog
242 // along with the keydown event.
243 window.setTimeout(() => {
244 Drupal.ckeditor.openGroupNameDialog(view, $(event.currentTarget));
246 event.preventDefault();
247 event.stopPropagation();
250 // Respond to direction key presses.
251 if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
252 const $group = $(event.currentTarget);
253 const $container = $group.parent();
254 const $siblings = $container.children();
257 // Move groups between sibling groups.
258 if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
259 index = $siblings.index($group);
260 // Move left between sibling groups.
261 if (_.indexOf([37, 63234], event.keyCode) > -1) {
263 $group.insertBefore($siblings.eq(index - 1));
265 // Wrap between rows. Insert the group before the placeholder group
266 // at the end of the previous row.
268 const $rowChildElement = $container
269 .closest('.ckeditor-row')
271 .find('.ckeditor-toolbar-groups')
274 $group.insertBefore($rowChildElement);
277 // Move right between sibling groups.
278 else if (_.indexOf([39, 63235], event.keyCode) > -1) {
279 // Move to the right if the next group is not a placeholder.
280 if (!$siblings.eq(index + 1).hasClass('placeholder')) {
281 $group.insertAfter($container.children().eq(index + 1));
283 // Wrap group between rows.
286 .closest('.ckeditor-row')
288 .find('.ckeditor-toolbar-groups')
293 // Move groups between rows.
294 else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
295 dir = _.indexOf([38, 63232], event.keyCode) > -1 ? 'prev' : 'next';
297 .closest('.ckeditor-row')
299 .find('.ckeditor-toolbar-groups')
304 Drupal.ckeditor.registerGroupMove(this, $group);
305 $group.trigger('focus');
306 event.preventDefault();
307 event.stopPropagation();
312 })(jQuery, Drupal, Backbone, _);