From f13e923a40a0600f319c026fc7d0c7d83c46762f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= Date: Wed, 23 Jan 2013 20:52:59 -0500 Subject: [PATCH] Issue #1872206 by quicksketch, jessebeach: Toolbar configuration for a WYSIWYG editor is not keyboard accessible; touch suport missing; non-JS version not provided MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: J. ReneĢe Beach --- core/modules/ckeditor/ckeditor.admin.inc | 38 ++++++--- core/modules/ckeditor/css/ckeditor.admin.css | 61 +++++++++----- core/modules/ckeditor/js/ckeditor.admin.js | 116 +++++++++++++++++++++++++- 3 files changed, 179 insertions(+), 36 deletions(-) diff --git a/core/modules/ckeditor/ckeditor.admin.inc b/core/modules/ckeditor/ckeditor.admin.inc index 3342e1b..3168422 100644 --- a/core/modules/ckeditor/ckeditor.admin.inc +++ b/core/modules/ckeditor/ckeditor.admin.inc @@ -66,6 +66,9 @@ function theme_ckeditor_settings_toolbar($variables) { else { $data = '?'; } + // Wrap the button to make it accessible. + $data = '' . $data . ''; + $button_item = array( 'data' => $data, 'data-button-name' => $button_name, @@ -90,6 +93,9 @@ function theme_ckeditor_settings_toolbar($variables) { else { $data = '?'; } + // Wrap the button to make it accessible. + $data = '' . $data . ''; + $button_item = array( 'data' => $data, 'data-button-name' => $button_name, @@ -110,6 +116,9 @@ function theme_ckeditor_settings_toolbar($variables) { else { $data = '?'; } + // Wrap the button to make it accessible. + $data = '' . $data . ''; + $button_item = array( 'data' => $data, 'data-button-name' => $button_name, @@ -123,11 +132,17 @@ function theme_ckeditor_settings_toolbar($variables) { // We don't use theme_item_list() below in case there are no buttons in the // active or disabled list, as theme_item_list() will not print an empty UL. - $output = ''; - $output .= '' . t('Active toolbar') . ''; + $output = '
'; + $output .= '' . t('Button configuration') . ''; + // aria-live region for outputing aural information about the state of the + // configuration. + $output .= '
'; + $output .= '

' . t('Toolbar buttons may be moved by drag and drop or with the keyboard arrow keys. Move a button up into the active toolbar to enable it, or down into the available buttons list to disable it. Dividers are available to create logical button groups.') . '

'; + $output .= ''; $output .= '
'; + // Append buttons to the active toolbar. foreach ($active_buttons as $button_row) { - $output .= '
    '; + $output .= '
      '; foreach ($button_row as $button) { $contents = $button['data']; unset($button['data']); @@ -142,15 +157,13 @@ function theme_ckeditor_settings_toolbar($variables) { } $output .= '
      '; - $output .= '-'; - $output .= '+'; + $output .= '-'; + $output .= '+'; $output .= '
      '; - $output .= '
'; - - $output .= '' . t('Available buttons') . ''; - $output .= '
'; - $output .= '
    '; + $output .= '
    '; + $output .= ''; + $output .= '
      '; foreach ($disabled_buttons as $button) { $contents = $button['data']; unset($button['data']); @@ -158,8 +171,8 @@ function theme_ckeditor_settings_toolbar($variables) { $output .= '' . $contents . ''; } $output .= '
    '; - $output .= '' . t('Dividers') . ': '; - $output .= '
      '; + $output .= ''; + $output .= '
        '; foreach ($multiple_buttons as $button) { $contents = $button['data']; unset($button['data']); @@ -168,6 +181,7 @@ function theme_ckeditor_settings_toolbar($variables) { } $output .= '
      '; $output .= '
    '; + $output .= '
'; return $output; } diff --git a/core/modules/ckeditor/css/ckeditor.admin.css b/core/modules/ckeditor/css/ckeditor.admin.css index 15fad3b..9bb1282 100644 --- a/core/modules/ckeditor/css/ckeditor.admin.css +++ b/core/modules/ckeditor/css/ckeditor.admin.css @@ -29,8 +29,8 @@ ul.ckeditor-buttons { min-width: 26px; list-style: none; - float: left; - clear: left; + float: left; /* RTL */ + clear: left; /* RTL */ padding: 0; margin: 0 6px 5px 0; border: 1px solid #a6a6a6; @@ -40,13 +40,21 @@ ul.ckeditor-buttons { } ul.ckeditor-buttons li { display: inline-block; + padding: 0; + margin: 0; + float: left; /* RTL */ +} +ul.ckeditor-buttons li a { + position: relative; + display: inline-block; height: 18px; padding: 4px 6px; - outline: none; cursor: move; - float: left; border: 0; white-space: nowrap; + text-decoration: none; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + color: #474747; background: #e4e4e4; background-image: -webkit-gradient(linear,left top,left bottom,from(white),to(#e4e4e4)); @@ -57,15 +65,18 @@ ul.ckeditor-buttons li { background-image: linear-gradient(top,white,#e4e4e4); filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffffff',endColorstr='#ffe4e4e4'); } -ul.ckeditor-buttons li:first-child { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; +ul.ckeditor-buttons li a:focus { + z-index: 11; /* Ensure focused buttons show their outline on all sides. */ +} +ul.ckeditor-buttons li:first-child a { + border-top-left-radius: 2px; /* RTL */ + border-bottom-left-radius: 2px; /* RTL */ } -ul.ckeditor-buttons li:last-child { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; +ul.ckeditor-buttons li:last-child a { + border-top-right-radius: 2px; /* RTL */ + border-bottom-right-radius: 2px; /* RTL */ } -ul.ckeditor-buttons li.ckeditor-button-placeholder { +ul.ckeditor-buttons li.ckeditor-button-placeholder a { background: #333; opacity: 0.3; } @@ -73,31 +84,35 @@ ul.ckeditor-multiple-buttons { padding: 2px; margin: 0; list-style: none; - float: left; + float: left; /* RTL */ } ul.ckeditor-multiple-buttons li { - padding: 2px 0; + display: inline-block; + padding: 0; margin: 0; + float: left; /* RTL */ +} +ul.ckeditor-multiple-buttons li a { display: inline-block; + padding: 2px 0; + margin: 0; height: 18px; cursor: move; - float: left; -} -.ckeditor-multiple-label { - float: left; - padding: 4px; } ul.ckeditor-buttons li.ckeditor-group-button-separator, ul.ckeditor-multiple-buttons li.ckeditor-group-button-separator { + margin: -1px -3px -2px; +} +ul.ckeditor-buttons li.ckeditor-group-button-separator a, +ul.ckeditor-multiple-buttons li.ckeditor-group-button-separator a { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAdCAMAAABG4xbVAAAAhFBMVEUAAACmpqampqampqb////l5eX////5+fmmpqatra2urq6vr6+1tbW2tra4uLi6urq8vLzb29ve3t7i4uLl5eXn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w8PDx8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz+/v7qIQO+AAAACHRSTlMATVmAi8XM29MuWToAAABjSURBVBiVrc5BCoAwDETRMKhtRBduev9LKm1xjItWRBBE6Nt9QkIwOTcUzk0Imi8aoMssxbgoTHMtqsFMLta0vPh2N49HyfdelPg6k9uvX/a+Bmggt1qJRNzQFVgjEnkUZDoBmH57VSypjg4AAAAASUVORK5CYII=) no-repeat center center; width: 13px; padding: 0; height: 29px; - margin: -1px -3px -2px; position: relative; z-index: 10; } -ul.ckeditor-buttons li.ckeditor-button-separator { +ul.ckeditor-buttons li.ckeditor-button-separator a { width: 2px; padding: 0 4px; height: 26px; @@ -114,7 +129,7 @@ ul.ckeditor-buttons li.ckeditor-button-separator { background-image: linear-gradient(top, white, #e4e4e4); filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#ffffffff', endColorstr='#ffe4e4e4'); } -ul.ckeditor-multiple-buttons li.ckeditor-button-separator { +ul.ckeditor-multiple-buttons li.ckeditor-button-separator a { width: 2px; padding: 0; height: 26px; @@ -142,10 +157,10 @@ ul.ckeditor-multiple-buttons li.ckeditor-button-separator { } .ckeditor-row-controls { - float: right; + float: right; /* RTL */ font-size: 18px; width: 40px; - text-align: right; + text-align: right; /* RTL */ } .ckeditor-row-controls a { display: inline-block; diff --git a/core/modules/ckeditor/js/ckeditor.admin.js b/core/modules/ckeditor/js/ckeditor.admin.js index d22ac6e..f026be0 100644 --- a/core/modules/ckeditor/js/ckeditor.admin.js +++ b/core/modules/ckeditor/js/ckeditor.admin.js @@ -20,11 +20,29 @@ Drupal.behaviors.ckeditorAdmin = { cursor: 'move', stop: adminToolbarValue }; - $toolbarAdmin.insertAfter($textareaWrapper).find('.ckeditor-buttons').sortable(sortableSettings); + // Add the toolbar to the page. + $toolbarAdmin.insertAfter($textareaWrapper); + + // Then determine if this is RTL or not. + var rtl = $toolbarAdmin.css('direction') === 'rtl' ? -1 : 1; + var $toolbarRows = $toolbarAdmin.find('.ckeditor-buttons'); + + // Add the drag and drop functionality. + $toolbarRows.sortable(sortableSettings); $toolbarAdmin.find('.ckeditor-multiple-buttons li').draggable({ connectToSortable: '.ckeditor-toolbar-active .ckeditor-buttons', helper: 'clone' }); + + // Add keyboard arrow support. + $toolbarAdmin.on('keyup.ckeditorMoveButton', '.ckeditor-buttons a', adminToolbarMoveButton); + $toolbarAdmin.on('keyup.ckeditorMoveSeparator', '.ckeditor-multiple-buttons a', adminToolbarMoveSeparator); + + // Add click for help. + $toolbarAdmin.on('click.ckeditorClickButton', '.ckeditor-buttons a', { type: 'button' }, adminToolbarButtonHelp); + $toolbarAdmin.on('click.ckeditorClickSeparator', '.ckeditor-multiple-buttons a', { type: 'separator' }, adminToolbarButtonHelp); + + // Add/remove row button functionality. $toolbarAdmin.on('click.ckeditorAddRow', 'a.ckeditor-row-add', adminToolbarAddRow); $toolbarAdmin.on('click.ckeditorAddRow', 'a.ckeditor-row-remove', adminToolbarRemoveRow); if ($toolbarAdmin.find('.ckeditor-toolbar-active ul').length > 1) { @@ -32,12 +50,105 @@ Drupal.behaviors.ckeditorAdmin = { } /** + * Event callback for keypress. Move buttons based on arrow keys. + */ + function adminToolbarMoveButton(event) { + var $button = $(this).parent(); + var $currentRow = $button.closest('.ckeditor-buttons'); + var $destinationRow = null; + var destinationPosition = $button.index(); + + switch (event.keyCode) { + case 37: // Left arrow. + case 63234: // Safari left arrow. + $destinationRow = $currentRow; + destinationPosition = destinationPosition - (1 * rtl); + break; + case 38: // Up arrow. + case 63232: // Safari up arrow. + console.log($toolbarRows); + $destinationRow = $($toolbarRows[$toolbarRows.index($currentRow) - 1]); + break; + case 39: // Right arrow. + case 63235: // Safari right arrow. + $destinationRow = $currentRow; + destinationPosition = destinationPosition + (1 * rtl); + break; + case 40: // Down arrow. + case 63233: // Safari down arrow. + $destinationRow = $($toolbarRows[$toolbarRows.index($currentRow) + 1]); + } + + if ($destinationRow && $destinationRow.length) { + // Detach the button from the DOM so its position doesn't interfere. + $button.detach(); + // Move the button before the button whose position it should occupy. + var $targetButton = $destinationRow.children(':eq(' + destinationPosition + ')'); + if ($targetButton.length) { + $targetButton.before($button); + } + else { + $destinationRow.append($button); + } + // Update the toolbar value field. + adminToolbarValue(event, { item: $button }); + } + event.preventDefault(); + } + + /** + * Event callback for keyup. Move a separator into the active toolbar. + */ + function adminToolbarMoveSeparator(event) { + switch (event.keyCode) { + case 38: // Up arrow. + case 63232: // Safari up arrow. + var $button = $(this).parent().clone().appendTo($toolbarRows.eq(-2)); + adminToolbarValue(event, { item: $button }); + event.preventDefault(); + } + } + + /** + * Provide help when a button is clicked on. + */ + function adminToolbarButtonHelp(event) { + var $link = $(this); + var $button = $link.parent(); + var $currentRow = $button.closest('.ckeditor-buttons'); + var enabled = $button.closest('.ckeditor-toolbar-active').length > 0; + var position = $button.index() + 1; // 1-based index for humans. + var rowNumber = $toolbarRows.index($currentRow) + 1; + var $info = $('#ckeditor-button-configuration-aria-live'); + var type = event.data.type; + if (enabled) { + if (type === 'separator') { + $info.text(Drupal.t('Separators are used to visually split individual buttons. This @name is currently enabled, in row @row and position @position.', { '@name': $link.attr('aria-label'), '@row': rowNumber, '@position': position }) + "\n\n" + Drupal.t('Drag and drop the separator or use the keyboard arrow keys to change the position of this separator.')); + } + else { + $info.text(Drupal.t('The @name button is currently enabled, in row @row and position @position.', { '@name': $link.attr('aria-label'), '@row': rowNumber, '@position': position }) + "\n\n" + Drupal.t('Drag and drop the buttons or use the keyboard arrow keys to change the position of this button.')); + } + } + else { + if (type === 'separator') { + $info.text(Drupal.t('Separators are used to visually split individual buttons. This @name is currently disabled.', { '@name': $link.attr('aria-label') }) + "\n\n" + Drupal.t('Drag the button or use the up arrow key to move this separator into the active toolbar. You may add multiple separators to each row.')); + } + else { + $info.text(Drupal.t('The @name button is currently disabled.', { '@name': $link.attr('aria-label') }) + "\n\n" + Drupal.t('Drag the button or use the up arrow key to move this button into the active toolbar.')); + } + } + $link.focus(); + event.preventDefault(); + } + + /** * Add a new row of buttons. */ function adminToolbarAddRow(event) { var $this = $(this); var $rows = $this.closest('.ckeditor-toolbar-active').find('.ckeditor-buttons'); $rows.last().clone().empty().insertAfter($rows.last()).sortable(sortableSettings); + $toolbarRows = $toolbarAdmin.find('.ckeditor-buttons'); $this.siblings('a').show(); redrawToolbarGradient(); event.preventDefault(); @@ -57,6 +168,7 @@ Drupal.behaviors.ckeditorAdmin = { var $disabledButtons = $wrapper.find('.ckeditor-toolbar-disabled .ckeditor-buttons'); $lastRow.children(':not(.ckeditor-multiple-button)').prependTo($disabledButtons); $lastRow.sortable('destroy').remove(); + $toolbarRows = $toolbarAdmin.find('.ckeditor-buttons'); redrawToolbarGradient(); } event.preventDefault(); @@ -78,6 +190,8 @@ Drupal.behaviors.ckeditorAdmin = { function adminToolbarValue(event, ui) { // Update the toolbar config after updating a sortable. var toolbarConfig = []; + var $button = ui.item; + $button.find('a').focus(); $wrapper.find('.ckeditor-toolbar-active ul').each(function() { var $rowButtons = $(this).find('li'); var rowConfig = []; -- 1.7.10.4