diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 60dbf55eae..7f9236fd40 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1226,6 +1226,13 @@ function template_preprocess_splitbutton_item_list(array &$variables) { */ function template_preprocess_splitbutton(array &$variables) { $variables['toggle_attributes'] = new Attribute($variables['toggle_attributes']); + + // If the main element is present, ensure there is an attributes array with a + // class property. This facilitates the addition of classes to the main + // element within templates. + if (!empty($variables['main_element']) && !isset($variables['main_element']['#attributes']['class'])) { + $variables['main_element']['#attributes']['class'] = []; + } } /** diff --git a/core/lib/Drupal/Core/Render/Element/Splitbutton.php b/core/lib/Drupal/Core/Render/Element/Splitbutton.php index 85d3f1d2ca..226344187a 100644 --- a/core/lib/Drupal/Core/Render/Element/Splitbutton.php +++ b/core/lib/Drupal/Core/Render/Element/Splitbutton.php @@ -14,9 +14,9 @@ * button. All other elements will be filtered out. Elements extending this * class can change the items that are filtered by overriding filterItems(). * - #splitbutton_type: A string or an array or strings defining a type of - * dropbutton variant for styling purposes. This adds the class - * `splitbutton--#splitbutton_type` to the splitbutton wrapper and the class - * `button--#splitbutton_type` to the primary and toggle buttons. + * dropbutton variant for styling purposes. This is used in some themes to add + * the class `splitbutton--#splitbutton_type` to the splitbutton wrapper and + * `button--#splitbutton_type` to the main and toggle buttons. * - #title: This changes the default splitbutton behavior of displaying a * primary splitbutton item next a separate toggle button. When this property * is present, there is no primary item, just a toggle. @@ -91,7 +91,6 @@ public static function preRenderSplitbutton(array $element) { if (!isset($element['#title'])) { $first_item = array_shift($items); $element['#main_element'] = $first_item; - static::addMainElementClasses($element, $first_item['#type']); $element['#toggle_attributes']['#attributes']['aria-label'] = t('List additional actions'); } @@ -99,9 +98,11 @@ public static function preRenderSplitbutton(array $element) { if (count($items)) { static::buildItemList($element, $items); $element['#splitbutton_multiple'] = TRUE; + $element['#attributes']['data-drupal-splitbutton-multiple'] = ''; } else { $element['#splitbutton_multiple'] = FALSE; + $element['#attributes']['data-drupal-splitbutton-single'] = ''; } return $element; @@ -112,6 +113,9 @@ public static function preRenderSplitbutton(array $element) { * * @param array $element * The render element. + * + * @return array + * An array of splitbutton list items. */ public static function collectItems(array $element) { $items = $element['#splitbutton_items'] ?? []; @@ -147,7 +151,6 @@ public static function collectItems(array $element) { public static function buildToggleAttributes(array &$element) { $trigger_id = $element['#trigger_id']; $element['#toggle_attributes'] = [ - 'class' => ['button', 'splitbutton__toggle'], 'type' => 'button', 'role' => 'button', 'aria-haspopup' => 'true', @@ -170,9 +173,6 @@ public static function buildItemList(array &$element, array $items) { $trigger_id = $element['#trigger_id']; foreach ($items as &$item) { - // Classes must be added here instead of in templates as the item could - // be one of several different render element types. - $item['#attributes']['class'][] = 'splitbutton__operation-list-item'; $item['#attributes']['role'] = 'menuitem'; $item['#attributes']['tabindex'] = '-1'; $item['#wrapper_attributes']['role'] = 'none'; @@ -186,19 +186,16 @@ public static function buildItemList(array &$element, array $items) { 'role' => 'menu', 'aria-labelledby' => "$trigger_id-trigger", 'id' => "$trigger_id-menu", + 'data-drupal-splitbutton-item-list' => '', ], ]; } /** - * Filters unsupported element types from a splitbutton item list. + * Checks for unsupported element types in a splitbutton item list. * * @param array $items * The splitbutton list items. - * - * @return array - * An array of only immediate children that match specific render element - * types. */ public static function filterItems(array $items) { $allowed_types = ['submit', 'button', 'link']; @@ -209,35 +206,4 @@ public static function filterItems(array $items) { } } - /** - * Adds classes to the main splitbutton input. - * - * By default, the main splitbutton input is styled as a button, regardless of - * its element type. Elements that extend splitbutton may wish to style these - * elements differently, so this is available as a targeted, overridable - * function. - * - * @param array $element - * The splitbutton render array. - * @param null|string $modifier - * A modifier that can be used for adding additional classes. - */ - public static function addMainElementClasses(array &$element, $modifier = NULL) { - // Classes must be added here instead of templates as the main button can be - // be one of several render element types. - $element['#main_element']['#attributes']['class'][] = 'splitbutton__main-button'; - if (!empty($modifier)) { - $element['#main_element']['#attributes']['class'][] = 'splitbutton__main-button--' . $modifier; - } - - // The main splitbutton element will always be styled as a button, - // regardless of its actual type. - $element['#main_element']['#attributes']['class'][] = 'button'; - - // Add variant classes based on #splitbutton_type to the main button. - foreach ($element['#variants'] as $variant) { - $element['#main_element']['#attributes']['class'][] = 'button--' . $variant; - } - } - } diff --git a/core/misc/splitbutton/splitbutton-init.es6.js b/core/misc/splitbutton/splitbutton-init.es6.js index 97ad2cf4b6..60eec711f7 100644 --- a/core/misc/splitbutton/splitbutton-init.es6.js +++ b/core/misc/splitbutton/splitbutton-init.es6.js @@ -15,7 +15,7 @@ Drupal.behaviors.splitButton = { attach(context) { const $splitbuttons = $(context) - .find('.js-splitbutton-multiple') + .find('[data-drupal-splitbutton-multiple]') .once('splitbutton'); if ($splitbuttons.length) { for (let i = 0; i < $splitbuttons.length; i++) { diff --git a/core/misc/splitbutton/splitbutton-init.js b/core/misc/splitbutton/splitbutton-init.js index 5824d774c4..9bbcaff993 100644 --- a/core/misc/splitbutton/splitbutton-init.js +++ b/core/misc/splitbutton/splitbutton-init.js @@ -8,7 +8,7 @@ (function ($, Drupal) { Drupal.behaviors.splitButton = { attach: function attach(context) { - var $splitbuttons = $(context).find('.js-splitbutton-multiple').once('splitbutton'); + var $splitbuttons = $(context).find('[data-drupal-splitbutton-multiple]').once('splitbutton'); if ($splitbuttons.length) { for (var i = 0; i < $splitbuttons.length; i++) { diff --git a/core/misc/splitbutton/splitbutton.css b/core/misc/splitbutton/splitbutton.css index a693cc4499..f12706d860 100644 --- a/core/misc/splitbutton/splitbutton.css +++ b/core/misc/splitbutton/splitbutton.css @@ -1,46 +1,20 @@ -.splitbutton { - box-sizing: border-box; -} - -.splitbutton__operation-list { - margin: 0; - padding: 0; - list-style: none; -} - -.splitbutton__main { +[data-drupal-splitbutton-main] { position: relative; display: inline-flex; - font-size: 0.889rem; } - -.splitbutton__main .button { - margin: 0; -} - -.js .splitbutton__operation-list { +.js [data-drupal-splitbutton-item-list] { display: none; } -.js .splitbutton--enabled.open .splitbutton__operation-list { +[data-drupal-splitbutton-open] [data-drupal-splitbutton-item-list] { z-index: 4; display: block; } -.splitbutton__operation-list-item { - display: block; - padding: 0; - border: none; - background: #fff; -} -.splitbutton__operation-list-item:hover { - text-decoration: none; -} - -.splitbutton__toggle { +[data-drupal-splitbutton-trigger] { position: relative; } -.splitbutton__toggle::after { +[data-drupal-splitbutton-trigger]::after { position: absolute; top: 50%; right: 6px; /* LTR */ @@ -54,24 +28,15 @@ border-bottom-color: transparent; border-left-color: transparent; } -[dir="rtl"] .splitbutton__toggle::after { +[dir="rtl"] [data-drupal-splitbutton-trigger]::after { right: auto; left: 6px; } -.splitbutton.open .splitbutton__toggle::after { +[data-drupal-splitbutton-open] [data-drupal-splitbutton-trigger]::after { top: 48%; transform: translate(50%, -50%) rotate(180deg); } -.splitbutton__toggle--with-title { - position: relative; - padding-right: 15px; /* LTR */ -} -[dir="rtl"] .splitbutton__toggle--with-title { - padding-right: 6px; - padding-left: 15px; -} - -html:not(.js) .splitbutton__toggle { +html:not(.js) [data-drupal-splitbutton-trigger] { display: none; } diff --git a/core/misc/splitbutton/splitbutton.es6.js b/core/misc/splitbutton/splitbutton.es6.js index 80f1cf8c47..1a7aa60912 100644 --- a/core/misc/splitbutton/splitbutton.es6.js +++ b/core/misc/splitbutton/splitbutton.es6.js @@ -6,10 +6,7 @@ ((Drupal, Popper) => { Drupal.SplitButton = class { constructor(splitbutton) { - splitbutton.classList.add( - 'splitbutton--enabled', - 'js-splitbutton-enabled', - ); + splitbutton.setAttribute('data-drupal-splitbutton-enabled', ''); this.keyCode = Object.freeze({ TAB: 9, @@ -33,7 +30,9 @@ '[data-drupal-splitbutton-trigger]', ); this.menu = splitbutton.querySelector('[data-drupal-splitbutton-target]'); - this.triggerContainer = splitbutton.querySelector('.splitbutton__main'); + this.triggerContainer = splitbutton.querySelector( + '[data-drupal-splitbutton-main]', + ); this.splitbutton.addEventListener('mouseenter', () => this.activeIn()); this.splitbutton.addEventListener('focusin', () => this.activeIn()); @@ -56,6 +55,7 @@ .forEach((item, index) => { // Add attribute to each item to identify its focus order. item.setAttribute('data-drupal-splitbutton-item', index); + item.classList.add('splitbutton__operation-list-item'); this.menuItems.push(item); const itemText = @@ -106,7 +106,9 @@ clickToggle(e) { e.preventDefault(); e.stopPropagation(); - const state = this.splitbutton.classList.contains('open') + const state = this.splitbutton.hasAttribute( + 'data-drupal-splitbutton-open', + ) ? 'close' : 'open'; this[state](); @@ -120,9 +122,15 @@ */ toggle(show) { const isBool = typeof show === 'boolean'; - show = isBool ? show : !this.splitbutton.classList.contains('open'); + show = isBool + ? show + : !this.splitbutton.hasAttribute('data-drupal-splitbutton-open'); const expanded = show ? 'true' : 'false'; - this.splitbutton.classList.toggle('open', show); + if (show) { + this.splitbutton.setAttribute('data-drupal-splitbutton-open', ''); + } else { + this.splitbutton.removeAttribute('data-drupal-splitbutton-open'); + } this.trigger.setAttribute('aria-expanded', expanded); } @@ -201,7 +209,7 @@ break; case this.keyCode.UP: - if (this.splitbutton.classList.contains('open')) { + if (this.splitbutton.hasAttribute('data-drupal-splitbutton-open')) { this.focusPrev(e); } else { this.open(); @@ -210,7 +218,7 @@ break; case this.keyCode.DOWN: - if (this.splitbutton.classList.contains('open')) { + if (this.splitbutton.hasAttribute('data-drupal-splitbutton-open')) { this.focusNext(e); } else { this.open(); @@ -238,7 +246,7 @@ if ( char.length === 1 && char.match(/\S/) && - this.splitbutton.classList.contains('open') + this.splitbutton.hasAttribute('data-drupal-splitbutton-open') ) { this.setFocusByFirstCharacter(e, char.toLowerCase()); } diff --git a/core/misc/splitbutton/splitbutton.js b/core/misc/splitbutton/splitbutton.js index cc3e6e68bd..a854337937 100644 --- a/core/misc/splitbutton/splitbutton.js +++ b/core/misc/splitbutton/splitbutton.js @@ -18,7 +18,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d _classCallCheck(this, _class); - splitbutton.classList.add('splitbutton--enabled', 'js-splitbutton-enabled'); + splitbutton.setAttribute('data-drupal-splitbutton-enabled', ''); this.keyCode = Object.freeze({ TAB: 9, RETURN: 13, @@ -38,7 +38,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d this.splitbutton = splitbutton; this.trigger = splitbutton.querySelector('[data-drupal-splitbutton-trigger]'); this.menu = splitbutton.querySelector('[data-drupal-splitbutton-target]'); - this.triggerContainer = splitbutton.querySelector('.splitbutton__main'); + this.triggerContainer = splitbutton.querySelector('[data-drupal-splitbutton-main]'); this.splitbutton.addEventListener('mouseenter', function () { return _this.activeIn(); }); @@ -68,6 +68,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d var itemTags = this.menu.getAttribute('data-drupal-item-tags') || 'a, input, button'; Array.prototype.slice.call(this.menu.querySelectorAll(itemTags)).forEach(function (item, index) { item.setAttribute('data-drupal-splitbutton-item', index); + item.classList.add('splitbutton__operation-list-item'); _this2.menuItems.push(item); @@ -98,16 +99,22 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d value: function clickToggle(e) { e.preventDefault(); e.stopPropagation(); - var state = this.splitbutton.classList.contains('open') ? 'close' : 'open'; + var state = this.splitbutton.hasAttribute('data-drupal-splitbutton-open') ? 'close' : 'open'; this[state](); } }, { key: "toggle", value: function toggle(show) { var isBool = typeof show === 'boolean'; - show = isBool ? show : !this.splitbutton.classList.contains('open'); + show = isBool ? show : !this.splitbutton.hasAttribute('data-drupal-splitbutton-open'); var expanded = show ? 'true' : 'false'; - this.splitbutton.classList.toggle('open', show); + + if (show) { + this.splitbutton.setAttribute('data-drupal-splitbutton-open', ''); + } else { + this.splitbutton.removeAttribute('data-drupal-splitbutton-open'); + } + this.trigger.setAttribute('aria-expanded', expanded); } }, { @@ -175,7 +182,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d break; case this.keyCode.UP: - if (this.splitbutton.classList.contains('open')) { + if (this.splitbutton.hasAttribute('data-drupal-splitbutton-open')) { this.focusPrev(e); } else { this.open(); @@ -185,11 +192,9 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d break; case this.keyCode.DOWN: - if (this.splitbutton.classList.contains('open')) { - console.log('focusnext'); + if (this.splitbutton.hasAttribute('data-drupal-splitbutton-open')) { this.focusNext(e); } else { - console.log('open, then focus first'); this.open(); this.focusFirst(); } @@ -214,7 +219,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d default: preventDefault = false; - if (char.length === 1 && char.match(/\S/) && this.splitbutton.classList.contains('open')) { + if (char.length === 1 && char.match(/\S/) && this.splitbutton.hasAttribute('data-drupal-splitbutton-open')) { this.setFocusByFirstCharacter(e, char.toLowerCase()); } diff --git a/core/modules/system/templates/splitbutton-item-list.html.twig b/core/modules/system/templates/splitbutton-item-list.html.twig index 8c5a367de4..40483e1359 100644 --- a/core/modules/system/templates/splitbutton-item-list.html.twig +++ b/core/modules/system/templates/splitbutton-item-list.html.twig @@ -8,7 +8,6 @@ * - attributes: HTML attributes to be applied to each list item. * - value: The content of the list element. * - list_type: The tag for list element ("ul" or "ol"). - * - wrapper_attributes: HTML attributes to be applied to the list wrapper. * - attributes: HTML attributes to be applied to the list. * * @see template_preprocess_splitbutton_item_list() @@ -17,9 +16,9 @@ */ #} {% if items %} - <{{ list_type }}{{ attributes.addClass('splitbutton__operation-list') }}> - {%- for item in items -%} - {{ item.value }} - {%- endfor -%} + <{{ list_type }}{{ attributes }}> + {%- for item in items -%} + {{ item.value }} + {%- endfor -%} {%- endif %} diff --git a/core/modules/system/templates/splitbutton.html.twig b/core/modules/system/templates/splitbutton.html.twig index 1b38717184..b074c137ab 100644 --- a/core/modules/system/templates/splitbutton.html.twig +++ b/core/modules/system/templates/splitbutton.html.twig @@ -23,29 +23,13 @@ * * @ingroup themeable */ -#}{% - set classes = [ - 'splitbutton', - 'js-splitbutton', - splitbutton_multiple == true ? 'splitbutton--multiple' : 'splitbutton--single', - splitbutton_multiple ? 'js-splitbutton-multiple', - ] -%} -{% - set button_classes = [ - title ? 'splitbutton__toggle--with-title' : 'splitbutton__toggle--no-title', - ] -%} -{% for variant in variants %} - {% set classes = classes|merge(['splitbutton--' ~ variant]) %} - {% set button_classes = button_classes|merge(['button--' ~ variant]) %} -{% endfor %} +#} - -
+
+
{{ main_element }} {% if splitbutton_multiple and exclude_toggle == FALSE %} - {% endif %} diff --git a/core/modules/system/tests/modules/splitbutton_test/src/Element/SplitTextField.php b/core/modules/system/tests/modules/splitbutton_test/src/Element/SplitTextField.php index 876121ee7b..7865d60efd 100644 --- a/core/modules/system/tests/modules/splitbutton_test/src/Element/SplitTextField.php +++ b/core/modules/system/tests/modules/splitbutton_test/src/Element/SplitTextField.php @@ -7,7 +7,7 @@ /** * Used for testing a render element extending Splitbutton. * - * This element isn't particuarly useful, but something like this could be the + * This element isn't particularly useful, but something like this could be the * foundation of an autocomplete widget, as it has a text input as the main * element and the items become visible on input. * @@ -77,11 +77,10 @@ public static function collectItems(array $element) { public static function buildItemList(&$element, $items) { $trigger_id = $element['#trigger_id']; - // The navigable items in Splitbutton default are a, input or button + // The navigable items in Splitbutton default are , or + {% endif %} +
+ {{ splitbutton_item_list }} +
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Render/Element/SplitButtonTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Render/Element/SplitButtonTest.php index 97490b53d2..996ce8faac 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/Render/Element/SplitButtonTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Render/Element/SplitButtonTest.php @@ -48,8 +48,16 @@ protected function setUp(): void { /** * General splitbutton tests. + * + * @dataProvider providerTestSplitbuttons */ - public function testSplitbuttons() { + public function testSplitbuttons($theme_name) { + if (!empty($theme_name)) { + $this->container->get('theme_installer')->install([$theme_name]); + $this->config('system.theme')->set('default', $theme_name)->save(); + $this->drupalGet('/splitbuttons'); + } + $assert_session = $this->assertSession(); $button_types = [ @@ -74,7 +82,7 @@ public function testSplitbuttons() { 'number_items' => 6, ], 'splitbutton_with_title' => [ - 'primary_selector' => '.splitbutton__toggle', + 'primary_selector' => '[data-drupal-splitbutton-trigger]', 'number_links' => 4, 'number_submit' => 2, 'number_items' => 6, @@ -93,43 +101,52 @@ public function testSplitbuttons() { $splitbutton_selector = "[data-splitbutton-test-id=\"$test_key\"]"; $splitbutton = $assert_session->waitForElement('css', $splitbutton_selector); $this->assertNotNull($splitbutton, "Element $splitbutton_selector"); - $toggle = $splitbutton->find('css', '.splitbutton__toggle'); + $toggle = $splitbutton->find('css', '[data-drupal-splitbutton-trigger]'); $this->assertNotNull($toggle); - $this->assertFalse($splitbutton->hasClass('open')); + $this->assertFalse($splitbutton->hasAttribute('data-drupal-splitbutton-open')); $toggle->press(); - $open_splitbutton = $assert_session->waitForElement('css', "$splitbutton_selector.open"); + $open_splitbutton = $assert_session->waitForElement('css', $splitbutton_selector . '[data-drupal-splitbutton-open]'); $this->assertNotNull($open_splitbutton); - $operation_list = $assert_session->waitForElementVisible('css', "$splitbutton_selector .splitbutton__operation-list"); - $this->assertNotNull($operation_list, "$splitbutton_selector .splitbutton__operation-list"); - $primary_button_selector = $scenario_data['primary_selector']; - $assert_session->elementExists('css', "$splitbutton_selector $primary_button_selector"); + $operation_list = $assert_session->waitForElementVisible('css', "$splitbutton_selector [data-drupal-splitbutton-item-list]"); + $this->assertNotNull($operation_list, "$splitbutton_selector [data-drupal-splitbutton-item-list]"); $operation_list_links = $operation_list->findAll('css', 'a'); $operation_list_submits = $operation_list->findAll('css', 'input'); $this->assertCount($scenario_data['number_links'], $operation_list_links); $this->assertCount($scenario_data['number_submit'], $operation_list_submits); $toggle->press(); - $closed_splitbutton = $assert_session->waitForElement('css', "$splitbutton_selector:not(.open)"); + $closed_splitbutton = $assert_session->waitForElement('css', "$splitbutton_selector:not([data-drupal-splitbutton-open])"); $this->assertNotNull($closed_splitbutton); - if ($button_type !== 'default') { - $this->assertTrue($toggle->hasClass("button--$button_type")); - if ($scenario !== 'splitbutton_with_title') { - $assert_session->elementExists('css', "$splitbutton_selector .splitbutton__main-button.button--$button_type"); + if (!empty($theme_name)) { + // Confirm expected classes are added to the primary button. + $primary_button_selector = $scenario_data['primary_selector']; + $assert_session->elementExists('css', "$splitbutton_selector $primary_button_selector"); + + // Confirm splitbutton type classes are added to toggle. + if ($button_type !== 'default') { + $this->assertTrue($toggle->hasClass("button--$button_type")); + if ($scenario !== 'splitbutton_with_title') { + $assert_session->elementExists('css', "$splitbutton_selector .splitbutton__main-button.button--$button_type"); + } } } } } - // Confirm classes were properly added to splitbuttons that have multiple - // types. - $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-primary-small"].splitbutton--primary.splitbutton--small'); - $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-primary-small"] .splitbutton__main-button.button.button--primary.button--small'); - $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-primary-small"] .splitbutton__toggle.button.button--primary.button--small'); - - $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-danger-extrasmall"].splitbutton--danger.splitbutton--extrasmall'); - $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-danger-extrasmall"] .splitbutton__main-button.button.button--danger.button--extrasmall'); - $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-danger-extrasmall"] .splitbutton__toggle.button.button--danger.button--extrasmall'); + // Confirm classes are correctly added to splitbuttons with multiple types. + // The conditional is present because these classes are not added in Stark. + if (!empty($theme_name)) { + // Confirm classes were properly added to splitbuttons that have multiple + // types. + $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-primary-small"].splitbutton--primary.splitbutton--small'); + $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-primary-small"] .splitbutton__main-button.button.button--primary.button--small'); + $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-primary-small"] [data-drupal-splitbutton-trigger].button.button--primary.button--small'); + + $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-danger-extrasmall"].splitbutton--danger.splitbutton--extrasmall'); + $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-danger-extrasmall"] .splitbutton__main-button.button.button--danger.button--extrasmall'); + $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton-danger-extrasmall"] [data-drupal-splitbutton-trigger].button.button--danger.button--extrasmall'); + } // Test single-item splitbuttons. $test_ids = [ @@ -138,13 +155,28 @@ public function testSplitbuttons() { ]; foreach ($test_ids as $test_id) { $splitbutton = $assert_session->waitForElement('css', "[data-splitbutton-test-id=\"$test_id\"]"); - $this->assertFalse($splitbutton->hasClass('splitbutton--multiple')); - $this->assertFalse($splitbutton->hasClass('splitbutton--enabled')); - $this->assertTrue($splitbutton->hasClass('splitbutton--single')); - $this->assertNull($splitbutton->find('css', '.splitbutton__operation-list')); + $this->assertFalse($splitbutton->hasAttribute('data-drupal-splitbutton-multiple')); + $this->assertFalse($splitbutton->hasAttribute('data-drupal-splitbutton-enabled')); + $this->assertTrue($splitbutton->hasAttribute('data-drupal-splitbutton-single')); + $this->assertNull($splitbutton->find('css', '[data-drupal-splitbutton-item-list]')); } } + /** + * Data provider for testSplitbuttons(). + * + * @return string[] + * An array of themes to install for the test. + */ + public function providerTestSplitbuttons() { + return [ + 'stark' => [''], + 'claro' => ['claro'], + 'seven' => ['seven'], + 'bartik' => ['bartik'], + ]; + } + /** * Tests keyboard navigation of splitbutton. * @@ -174,15 +206,15 @@ public function testSplitbuttonKeyboard() { // Find splitbutton and toggle, confirm splitbutton is closed. $splitbutton = $assert_session->elementExists('css', '[data-splitbutton-test-id="splitbutton_link_first-default"]'); - $this->assertFalse($splitbutton->hasClass('open')); - $toggle = $splitbutton->find('css', '.splitbutton__toggle'); + $this->assertFalse($splitbutton->hasAttribute('data-drupal-splitbutton-open')); + $toggle = $splitbutton->find('css', '[data-drupal-splitbutton-trigger]'); $this->assertNotNull($toggle); // Open splitbutton and add newly visible menu items to a variable. $toggle->press(); - $this->assertNotNull($assert_session->waitForElementVisible('css', '[data-splitbutton-test-id="splitbutton_link_first-default"] .splitbutton__operation-list')); - $this->assertTrue($splitbutton->hasClass('open')); - $menu_items = $splitbutton->findAll('css', '.splitbutton__operation-list-item'); + $this->assertNotNull($assert_session->waitForElementVisible('css', '[data-splitbutton-test-id="splitbutton_link_first-default"] [data-drupal-splitbutton-item-list]')); + $this->assertTrue($splitbutton->hasAttribute('data-drupal-splitbutton-open')); + $menu_items = $splitbutton->findAll('css', '[data-drupal-splitbutton-item]'); $this->assertCount(5, $menu_items); // Use down key to select first item in the menu. @@ -243,13 +275,13 @@ public function testSplitbuttonKeyboard() { // toggle. $menu_items[array_search($focused_element_text, $menu_item_values)]->keyDown($this->keys['esc']); $menu_items[array_search($focused_element_text, $menu_item_values)]->keyUp($this->keys['esc']); - $this->assertNotNull($assert_session->waitForElement('css', '[data-splitbutton-test-id="splitbutton_link_first-default"]:not(.open)')); + $this->assertNotNull($assert_session->waitForElement('css', '[data-splitbutton-test-id="splitbutton_link_first-default"]:not([data-drupal-splitbutton-open])')); $this->assertJsCondition('document.querySelector("#splitbutton-trigger") === document.activeElement'); // Reopen the menu. $toggle->press(); - $this->assertNotNull($assert_session->waitForElementVisible('css', '[data-splitbutton-test-id="splitbutton_link_first-default"] .splitbutton__operation-list')); - $this->assertTrue($splitbutton->hasClass('open')); + $this->assertNotNull($assert_session->waitForElementVisible('css', '[data-splitbutton-test-id="splitbutton_link_first-default"] [data-drupal-splitbutton-item-list]')); + $this->assertTrue($splitbutton->hasAttribute('data-drupal-splitbutton-open')); // Navigate into the menu. $toggle->keyDown($this->keys['down']); @@ -261,7 +293,7 @@ public function testSplitbuttonKeyboard() { // toggle. $menu_items[array_search($focused_element_text, $menu_item_values)]->keyDown($this->keys['tab']); $menu_items[array_search($focused_element_text, $menu_item_values)]->keyUp($this->keys['tab']); - $this->assertNotNull($assert_session->waitForElement('css', '[data-splitbutton-test-id="splitbutton_link_first-default"]:not(.open)')); + $this->assertNotNull($assert_session->waitForElement('css', '[data-splitbutton-test-id="splitbutton_link_first-default"]:not([data-drupal-splitbutton-open])')); $this->assertJsCondition('document.querySelector("#splitbutton-trigger") === document.activeElement'); } @@ -290,9 +322,9 @@ public function testElementExtendingSplitbutton() { $focused_element_text = $this->getSession()->evaluateScript('document.activeElement.innerText'); $this->assertEquals('First Item', $focused_element_text); - $splitbutton_items = $custom_splitbutton->findAll('css', 'li.splitbutton__operation-list-item[role="option"]'); + $splitbutton_items = $custom_splitbutton->findAll('css', 'li[role="option"]'); $this->assertCount(3, $splitbutton_items); - $assert_session->elementExists('css', '[data-drupal-selector="edit-text-input-splitbutton"] ul.splitbutton__operation-list[role="listbox"]'); + $assert_session->elementExists('css', '[data-drupal-selector="edit-text-input-splitbutton"] ul[role="listbox"]'); } } diff --git a/core/themes/bartik/css/components/splitbutton.css b/core/themes/bartik/css/components/splitbutton.css index 99c8da8bb2..e22bd776bd 100644 --- a/core/themes/bartik/css/components/splitbutton.css +++ b/core/themes/bartik/css/components/splitbutton.css @@ -7,8 +7,17 @@ box-shadow: 0 0 0 2px #0071b3; } +.splitbutton__main { + font-size: 0.889rem; +} + +.splitbutton__main .button { + margin: 0; +} + .js .splitbutton__toggle { position: relative; + margin: 0; border-radius: 0 20em 20em 0; /* LTR */ background-color: #e8e8e8; background-image: -webkit-linear-gradient(top, #e8e8e8, #d2d2d2); @@ -54,7 +63,7 @@ right: auto; left: 7px; } -.splitbutton.open .splitbutton__toggle::after { +[data-drupal-splitbutton-open] .splitbutton__toggle::after { top: 48%; transform: translate(50%, -50%) rotate(180deg); } @@ -67,7 +76,8 @@ } .js .splitbutton__operation-list { - padding-top: 2px; + padding: 2px 0 0; + list-style: none; } .js .splitbutton__operation-list li:first-child { @@ -78,6 +88,7 @@ } .js .splitbutton__operation-list-item { + display: block; width: 100%; margin: 0; padding: 0.25em 1.063em; diff --git a/core/themes/bartik/templates/splitbutton-item-list.html.twig b/core/themes/bartik/templates/splitbutton-item-list.html.twig new file mode 100644 index 0000000000..83d21f9f8a --- /dev/null +++ b/core/themes/bartik/templates/splitbutton-item-list.html.twig @@ -0,0 +1,22 @@ +{# +/** + * @file + * Theme override for a splitbutton item list. + * + * Available variables: + * - items: A list of items. Each item contains: + * - attributes: HTML attributes to be applied to each list item. + * - value: The content of the list element. + * - list_type: The tag for list element ("ul" or "ol"). + * - attributes: HTML attributes to be applied to the list. + * + * @see template_preprocess_splitbutton_item_list() + */ +#} +{% if items %} + <{{ list_type }}{{ attributes.addClass('splitbutton__operation-list') }}> + {%- for item in items -%} + {{ item.value }} + {%- endfor -%} + +{%- endif %} diff --git a/core/themes/bartik/templates/splitbutton.html.twig b/core/themes/bartik/templates/splitbutton.html.twig new file mode 100644 index 0000000000..c4c39e0239 --- /dev/null +++ b/core/themes/bartik/templates/splitbutton.html.twig @@ -0,0 +1,74 @@ +{# +/** + * @file + * Theme override for a splitbutton. + * + * Available variables: + * - main_element: A render array of either a link, button, or submit element. + * This is the element that is always visible in a splitbutton. + * - variants: An array of strings defined in #splitbutton_type, used for + * adding type-specific classes to elements within the splitbutton. + * - attributes: HTML attributes added to the splitbutton container. + * - toggle_attributes: HTML attributes added to the toggle button. + * - splitbutton_item_list: an array of render elements that will populate the + * list items. + * - title: This is a string that, when present, instructs splitbutton to + * function like a dropdown. This becomes the label of the main element, and + * this main element is what toggles the item list. + * - exclude_toggle: Boolean that defaults to FALSE. When TRUE, this will + * prevent a dedicated toggle button from rendering. This is for elements + * that extend splitbutton that don't necessarily want the toggle button. + * + * @see template_preprocess_splitbutton() + */ +#} +{% + set container_classes = [ + 'splitbutton', + splitbutton_multiple == true ? 'splitbutton--multiple' : 'splitbutton--single', + ] +%} +{% + set toggle_button_classes = [ + 'button', + 'splitbutton__toggle', + title ? 'splitbutton__toggle--with-title' : 'splitbutton__toggle--no-title', + ] +%} +{% + set main_element_classes = [ + 'splitbutton__main-button', + 'button', + ] +%} +{# Add modifier classes based on the splitbutton variant types .#} +{% for variant in variants %} + {% set container_classes = container_classes|merge(['splitbutton--' ~ variant]) %} + {% set toggle_button_classes = toggle_button_classes|merge(['button--' ~ variant]) %} + {% set main_element_classes = main_element_classes|merge(['button--' ~ variant]) %} +{% endfor %} + +{% if main_element %} + {% set main_element_classes = main_element_classes|merge(['splitbutton__main-button--' ~ main_element['#type']]) %} + {# + Since main_element can be any number of different render element types, + splitbutton-specific classes must be added here instead of in their own + template. + #} + {% set main_element = main_element|merge({ + '#attributes': main_element['#attributes']|merge({ + 'class': main_element['#attributes']['class']|merge(main_element_classes), + })}) %} +{% endif %} + + +
+ {{ main_element }} + {% if splitbutton_multiple and exclude_toggle == FALSE %} + + {% endif %} +
+ {{ splitbutton_item_list }} +
diff --git a/core/themes/claro/css/components/splitbutton.css b/core/themes/claro/css/components/splitbutton.css index 7abf149b8a..37ad3b2acc 100644 --- a/core/themes/claro/css/components/splitbutton.css +++ b/core/themes/claro/css/components/splitbutton.css @@ -36,6 +36,9 @@ /** * jQuery.UI dropdown. */ /* Light gray with 0.8 opacity. */ /* Text color with 0.1 opacity. */ + /** + * jQuery.UI dialog. + */ /** * Progress bar. */ @@ -86,7 +89,7 @@ html:not(.js) .splitbutton { box-shadow: none; } -.splitbutton__toggle::before { +.splitbutton__toggle::after { position: absolute; top: 50%; right: calc(1.5rem - 1px); @@ -94,15 +97,16 @@ html:not(.js) .splitbutton { height: 0.5625rem; content: ""; transform: translate(50%, -50%) rotate(0); + border: none; background: url("data:image/svg+xml,%3Csvg width='14' height='9' viewBox='0 0 14 9' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.2384999,1.9384769 1.646703,0.5166019 7.0002189,5.8193359 12.353735,0.5166019 13.761938,1.9384769 7.0002189,8.635742Z' fill='%23222330'/%3E%3C/svg%3E") no-repeat center; background-size: contain; } -.splitbutton.open .splitbutton__toggle::before { +[data-drupal-splitbutton-open] .splitbutton__toggle::after { transform: translate(50%, -50%) rotate(180deg); } -[dir="rtl"] .splitbutton.open .splitbutton__toggle::before { +[dir="rtl"] [data-drupal-splitbutton-open] .splitbutton__toggle::after { transform: translate(50%, -50%) rotate(-180deg); } @@ -110,6 +114,10 @@ html:not(.js) .splitbutton { padding-right: calc(3rem - 2px); } +.js .splitbutton__operation-list { + list-style: none; +} + .js .splitbutton__operation-list li:not(:focus) { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); } @@ -157,8 +165,8 @@ html:not(.js) .splitbutton { /* Variants */ -.no-touchevents .splitbutton--small .splitbutton__toggle::before, -.no-touchevents .splitbutton--extrasmall .splitbutton__toggle::before { +.no-touchevents .splitbutton--small .splitbutton__toggle::after, +.no-touchevents .splitbutton--extrasmall .splitbutton__toggle::after { right: calc(1rem - 1px); width: 0.75rem; } @@ -167,7 +175,7 @@ html:not(.js) .splitbutton { padding-right: 2rem; } -.no-touchevents .splitbutton--extrasmall .splitbutton__toggle::before { +.no-touchevents .splitbutton--extrasmall .splitbutton__toggle::after { right: calc(0.75rem - 1px); } @@ -175,8 +183,8 @@ html:not(.js) .splitbutton { padding-right: calc(1.5rem + 2px); } -.js .splitbutton--primary .splitbutton__toggle::before, -.js .splitbutton--danger .splitbutton__toggle::before { +.js .splitbutton--primary .splitbutton__toggle::after, +.js .splitbutton--danger .splitbutton__toggle::after { background: url("data:image/svg+xml,%3Csvg width='14' height='9' fill='%23FF00FF' viewBox='0 0 14 9' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.2384999,1.9384769 1.646703,0.5166019 7.0002189,5.8193359 12.353735,0.5166019 13.761938,1.9384769 7.0002189,8.635742Z' fill='white'/%3E%3C/svg%3E") no-repeat center; } diff --git a/core/themes/claro/css/components/splitbutton.pcss.css b/core/themes/claro/css/components/splitbutton.pcss.css index 2de60c5051..fefa3cd51e 100644 --- a/core/themes/claro/css/components/splitbutton.pcss.css +++ b/core/themes/claro/css/components/splitbutton.pcss.css @@ -34,7 +34,7 @@ html:not(.js) .splitbutton { position: relative; box-shadow: none; } -.splitbutton__toggle::before { +.splitbutton__toggle::after { position: absolute; top: 50%; right: var(--button--horizontal-padding); @@ -42,13 +42,14 @@ html:not(.js) .splitbutton { height: 0.5625rem; content: ""; transform: translate(50%, -50%) rotate(0); + border: none; background: url("data:image/svg+xml,%3Csvg width='14' height='9' viewBox='0 0 14 9' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.2384999,1.9384769 1.646703,0.5166019 7.0002189,5.8193359 12.353735,0.5166019 13.761938,1.9384769 7.0002189,8.635742Z' fill='%23222330'/%3E%3C/svg%3E") no-repeat center; background-size: contain; } -.splitbutton.open .splitbutton__toggle::before { +[data-drupal-splitbutton-open] .splitbutton__toggle::after { transform: translate(50%, -50%) rotate(180deg); } -[dir="rtl"] .splitbutton.open .splitbutton__toggle::before { +[dir="rtl"] [data-drupal-splitbutton-open] .splitbutton__toggle::after { transform: translate(50%, -50%) rotate(-180deg); } @@ -56,6 +57,10 @@ html:not(.js) .splitbutton { padding-right: calc(var(--button--horizontal-padding) * 2); } +.js .splitbutton__operation-list { + list-style: none; +} + .js .splitbutton__operation-list li:not(:focus) { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); } @@ -98,8 +103,8 @@ html:not(.js) .splitbutton { /* Variants */ -.no-touchevents .splitbutton--small .splitbutton__toggle::before, -.no-touchevents .splitbutton--extrasmall .splitbutton__toggle::before { +.no-touchevents .splitbutton--small .splitbutton__toggle::after, +.no-touchevents .splitbutton--extrasmall .splitbutton__toggle::after { right: var(--button--small--horizontal-padding); width: 0.75rem; } @@ -108,7 +113,7 @@ html:not(.js) .splitbutton { padding-right: calc(var(--button--small--horizontal-padding) * 2 + 2px); } -.no-touchevents .splitbutton--extrasmall .splitbutton__toggle::before { +.no-touchevents .splitbutton--extrasmall .splitbutton__toggle::after { right: var(--button--extrasmall--horizontal-padding); } @@ -116,8 +121,8 @@ html:not(.js) .splitbutton { padding-right: calc(var(--button--extrasmall--horizontal-padding) * 2 + 4px); } -.js .splitbutton--primary .splitbutton__toggle::before, -.js .splitbutton--danger .splitbutton__toggle::before { +.js .splitbutton--primary .splitbutton__toggle::after, +.js .splitbutton--danger .splitbutton__toggle::after { background: url("data:image/svg+xml,%3Csvg width='14' height='9' fill='%23FF00FF' viewBox='0 0 14 9' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.2384999,1.9384769 1.646703,0.5166019 7.0002189,5.8193359 12.353735,0.5166019 13.761938,1.9384769 7.0002189,8.635742Z' fill='white'/%3E%3C/svg%3E") no-repeat center; } diff --git a/core/themes/claro/templates/form/splitbutton-item-list.html.twig b/core/themes/claro/templates/form/splitbutton-item-list.html.twig new file mode 100644 index 0000000000..83d21f9f8a --- /dev/null +++ b/core/themes/claro/templates/form/splitbutton-item-list.html.twig @@ -0,0 +1,22 @@ +{# +/** + * @file + * Theme override for a splitbutton item list. + * + * Available variables: + * - items: A list of items. Each item contains: + * - attributes: HTML attributes to be applied to each list item. + * - value: The content of the list element. + * - list_type: The tag for list element ("ul" or "ol"). + * - attributes: HTML attributes to be applied to the list. + * + * @see template_preprocess_splitbutton_item_list() + */ +#} +{% if items %} + <{{ list_type }}{{ attributes.addClass('splitbutton__operation-list') }}> + {%- for item in items -%} + {{ item.value }} + {%- endfor -%} + +{%- endif %} diff --git a/core/themes/claro/templates/form/splitbutton.html.twig b/core/themes/claro/templates/form/splitbutton.html.twig new file mode 100644 index 0000000000..c4c39e0239 --- /dev/null +++ b/core/themes/claro/templates/form/splitbutton.html.twig @@ -0,0 +1,74 @@ +{# +/** + * @file + * Theme override for a splitbutton. + * + * Available variables: + * - main_element: A render array of either a link, button, or submit element. + * This is the element that is always visible in a splitbutton. + * - variants: An array of strings defined in #splitbutton_type, used for + * adding type-specific classes to elements within the splitbutton. + * - attributes: HTML attributes added to the splitbutton container. + * - toggle_attributes: HTML attributes added to the toggle button. + * - splitbutton_item_list: an array of render elements that will populate the + * list items. + * - title: This is a string that, when present, instructs splitbutton to + * function like a dropdown. This becomes the label of the main element, and + * this main element is what toggles the item list. + * - exclude_toggle: Boolean that defaults to FALSE. When TRUE, this will + * prevent a dedicated toggle button from rendering. This is for elements + * that extend splitbutton that don't necessarily want the toggle button. + * + * @see template_preprocess_splitbutton() + */ +#} +{% + set container_classes = [ + 'splitbutton', + splitbutton_multiple == true ? 'splitbutton--multiple' : 'splitbutton--single', + ] +%} +{% + set toggle_button_classes = [ + 'button', + 'splitbutton__toggle', + title ? 'splitbutton__toggle--with-title' : 'splitbutton__toggle--no-title', + ] +%} +{% + set main_element_classes = [ + 'splitbutton__main-button', + 'button', + ] +%} +{# Add modifier classes based on the splitbutton variant types .#} +{% for variant in variants %} + {% set container_classes = container_classes|merge(['splitbutton--' ~ variant]) %} + {% set toggle_button_classes = toggle_button_classes|merge(['button--' ~ variant]) %} + {% set main_element_classes = main_element_classes|merge(['button--' ~ variant]) %} +{% endfor %} + +{% if main_element %} + {% set main_element_classes = main_element_classes|merge(['splitbutton__main-button--' ~ main_element['#type']]) %} + {# + Since main_element can be any number of different render element types, + splitbutton-specific classes must be added here instead of in their own + template. + #} + {% set main_element = main_element|merge({ + '#attributes': main_element['#attributes']|merge({ + 'class': main_element['#attributes']['class']|merge(main_element_classes), + })}) %} +{% endif %} + + +
+ {{ main_element }} + {% if splitbutton_multiple and exclude_toggle == FALSE %} + + {% endif %} +
+ {{ splitbutton_item_list }} + diff --git a/core/themes/seven/css/components/splitbutton.css b/core/themes/seven/css/components/splitbutton.css index f0298e0e6a..06f3ff3f60 100644 --- a/core/themes/seven/css/components/splitbutton.css +++ b/core/themes/seven/css/components/splitbutton.css @@ -12,6 +12,7 @@ .js .splitbutton__toggle { position: relative; + margin: 0; } .js .splitbutton__toggle--no-title { @@ -52,7 +53,7 @@ left: 6.5px; } -.js .splitbutton.open .splitbutton__toggle::after { +.js [data-drupal-splitbutton-open] .splitbutton__toggle::after { top: 48%; transform: translate(50%, -50%) rotate(180deg); } @@ -69,7 +70,9 @@ .js .splitbutton__operation-list { overflow: hidden; - padding-top: 2px; + margin: 0; + padding: 2px 0 0 0; + list-style: none; } .js .splitbutton__operation-list li:first-child { border-radius: 0.5em 0.5em 0 0; @@ -79,6 +82,7 @@ } .js .splitbutton__operation-list-item { + display: block; width: 100%; margin: 0; padding: 4px 10px; diff --git a/core/themes/seven/templates/splitbutton-item-list.html.twig b/core/themes/seven/templates/splitbutton-item-list.html.twig new file mode 100644 index 0000000000..83d21f9f8a --- /dev/null +++ b/core/themes/seven/templates/splitbutton-item-list.html.twig @@ -0,0 +1,22 @@ +{# +/** + * @file + * Theme override for a splitbutton item list. + * + * Available variables: + * - items: A list of items. Each item contains: + * - attributes: HTML attributes to be applied to each list item. + * - value: The content of the list element. + * - list_type: The tag for list element ("ul" or "ol"). + * - attributes: HTML attributes to be applied to the list. + * + * @see template_preprocess_splitbutton_item_list() + */ +#} +{% if items %} + <{{ list_type }}{{ attributes.addClass('splitbutton__operation-list') }}> + {%- for item in items -%} + {{ item.value }} + {%- endfor -%} + +{%- endif %} diff --git a/core/themes/seven/templates/splitbutton.html.twig b/core/themes/seven/templates/splitbutton.html.twig new file mode 100644 index 0000000000..c4c39e0239 --- /dev/null +++ b/core/themes/seven/templates/splitbutton.html.twig @@ -0,0 +1,74 @@ +{# +/** + * @file + * Theme override for a splitbutton. + * + * Available variables: + * - main_element: A render array of either a link, button, or submit element. + * This is the element that is always visible in a splitbutton. + * - variants: An array of strings defined in #splitbutton_type, used for + * adding type-specific classes to elements within the splitbutton. + * - attributes: HTML attributes added to the splitbutton container. + * - toggle_attributes: HTML attributes added to the toggle button. + * - splitbutton_item_list: an array of render elements that will populate the + * list items. + * - title: This is a string that, when present, instructs splitbutton to + * function like a dropdown. This becomes the label of the main element, and + * this main element is what toggles the item list. + * - exclude_toggle: Boolean that defaults to FALSE. When TRUE, this will + * prevent a dedicated toggle button from rendering. This is for elements + * that extend splitbutton that don't necessarily want the toggle button. + * + * @see template_preprocess_splitbutton() + */ +#} +{% + set container_classes = [ + 'splitbutton', + splitbutton_multiple == true ? 'splitbutton--multiple' : 'splitbutton--single', + ] +%} +{% + set toggle_button_classes = [ + 'button', + 'splitbutton__toggle', + title ? 'splitbutton__toggle--with-title' : 'splitbutton__toggle--no-title', + ] +%} +{% + set main_element_classes = [ + 'splitbutton__main-button', + 'button', + ] +%} +{# Add modifier classes based on the splitbutton variant types .#} +{% for variant in variants %} + {% set container_classes = container_classes|merge(['splitbutton--' ~ variant]) %} + {% set toggle_button_classes = toggle_button_classes|merge(['button--' ~ variant]) %} + {% set main_element_classes = main_element_classes|merge(['button--' ~ variant]) %} +{% endfor %} + +{% if main_element %} + {% set main_element_classes = main_element_classes|merge(['splitbutton__main-button--' ~ main_element['#type']]) %} + {# + Since main_element can be any number of different render element types, + splitbutton-specific classes must be added here instead of in their own + template. + #} + {% set main_element = main_element|merge({ + '#attributes': main_element['#attributes']|merge({ + 'class': main_element['#attributes']['class']|merge(main_element_classes), + })}) %} +{% endif %} + + +
+ {{ main_element }} + {% if splitbutton_multiple and exclude_toggle == FALSE %} + + {% endif %} +
+ {{ splitbutton_item_list }} + diff --git a/core/themes/stable/css/core/splitbutton/splitbutton.css b/core/themes/stable/css/core/splitbutton/splitbutton.css index bb976aa577..f12706d860 100644 --- a/core/themes/stable/css/core/splitbutton/splitbutton.css +++ b/core/themes/stable/css/core/splitbutton/splitbutton.css @@ -1,41 +1,42 @@ -.splitbutton { - box-sizing: border-box; -} - -.splitbutton__operation-list { - margin: 0; - padding: 0; - list-style: none; -} - -.splitbutton__main { +[data-drupal-splitbutton-main] { position: relative; display: inline-flex; - font-size: 0.889rem; -} - -.splitbutton__main .button { - margin: 0; } - -.js .splitbutton__operation-list { +.js [data-drupal-splitbutton-item-list] { display: none; } -.js .splitbutton--enabled.open .splitbutton__operation-list { +[data-drupal-splitbutton-open] [data-drupal-splitbutton-item-list] { z-index: 4; display: block; } -.splitbutton__operation-list-item { - display: block; - padding: 0; - border: none; - background: #fff; +[data-drupal-splitbutton-trigger] { + position: relative; } -.splitbutton__operation-list-item:hover { - text-decoration: none; + +[data-drupal-splitbutton-trigger]::after { + position: absolute; + top: 50%; + right: 6px; /* LTR */ + width: 0; + height: 0; + content: ""; + transform: translate(50%, -50%) rotate(0); + border-width: 0.3333em 0.3333em 0; + border-style: solid; + border-right-color: transparent; + border-bottom-color: transparent; + border-left-color: transparent; +} +[dir="rtl"] [data-drupal-splitbutton-trigger]::after { + right: auto; + left: 6px; +} +[data-drupal-splitbutton-open] [data-drupal-splitbutton-trigger]::after { + top: 48%; + transform: translate(50%, -50%) rotate(180deg); } -html:not(.js) .splitbutton__toggle { +html:not(.js) [data-drupal-splitbutton-trigger] { display: none; } diff --git a/core/themes/stable/templates/misc/splitbutton-item-list.html.twig b/core/themes/stable/templates/misc/splitbutton-item-list.html.twig index 1dbda7d3ee..3f66836428 100644 --- a/core/themes/stable/templates/misc/splitbutton-item-list.html.twig +++ b/core/themes/stable/templates/misc/splitbutton-item-list.html.twig @@ -8,16 +8,15 @@ * - attributes: HTML attributes to be applied to each list item. * - value: The content of the list element. * - list_type: The tag for list element ("ul" or "ol"). - * - wrapper_attributes: HTML attributes to be applied to the list wrapper. * - attributes: HTML attributes to be applied to the list. * * @see template_preprocess_splitbutton_item_list() */ #} {% if items %} - <{{ list_type }}{{ attributes.addClass('splitbutton__operation-list') }}> - {%- for item in items -%} - {{ item.value }} - {%- endfor -%} + <{{ list_type }}{{ attributes }}> + {%- for item in items -%} + {{ item.value }} + {%- endfor -%} {%- endif %} diff --git a/core/themes/stable/templates/misc/splitbutton.html.twig b/core/themes/stable/templates/misc/splitbutton.html.twig index c0fce23578..3b7dcbdab1 100644 --- a/core/themes/stable/templates/misc/splitbutton.html.twig +++ b/core/themes/stable/templates/misc/splitbutton.html.twig @@ -21,29 +21,13 @@ * * @see template_preprocess_splitbutton() */ -#}{% - set classes = [ - 'splitbutton', - 'js-splitbutton', - splitbutton_multiple == true ? 'splitbutton--multiple' : 'splitbutton--single', - splitbutton_multiple ? 'js-splitbutton-multiple', - ] -%} -{% - set button_classes = [ - title ? 'splitbutton__toggle--with-title' : 'splitbutton__toggle--no-title', - ] -%} -{% for variant in variants %} - {% set classes = classes|merge(['splitbutton--' ~ variant]) %} - {% set button_classes = button_classes|merge(['button--' ~ variant]) %} -{% endfor %} +#} - -
+
+
{{ main_element }} {% if splitbutton_multiple and exclude_toggle == FALSE %} - {% endif %} diff --git a/core/themes/stable9/css/core/splitbutton/splitbutton.css b/core/themes/stable9/css/core/splitbutton/splitbutton.css index bb976aa577..f12706d860 100644 --- a/core/themes/stable9/css/core/splitbutton/splitbutton.css +++ b/core/themes/stable9/css/core/splitbutton/splitbutton.css @@ -1,41 +1,42 @@ -.splitbutton { - box-sizing: border-box; -} - -.splitbutton__operation-list { - margin: 0; - padding: 0; - list-style: none; -} - -.splitbutton__main { +[data-drupal-splitbutton-main] { position: relative; display: inline-flex; - font-size: 0.889rem; -} - -.splitbutton__main .button { - margin: 0; } - -.js .splitbutton__operation-list { +.js [data-drupal-splitbutton-item-list] { display: none; } -.js .splitbutton--enabled.open .splitbutton__operation-list { +[data-drupal-splitbutton-open] [data-drupal-splitbutton-item-list] { z-index: 4; display: block; } -.splitbutton__operation-list-item { - display: block; - padding: 0; - border: none; - background: #fff; +[data-drupal-splitbutton-trigger] { + position: relative; } -.splitbutton__operation-list-item:hover { - text-decoration: none; + +[data-drupal-splitbutton-trigger]::after { + position: absolute; + top: 50%; + right: 6px; /* LTR */ + width: 0; + height: 0; + content: ""; + transform: translate(50%, -50%) rotate(0); + border-width: 0.3333em 0.3333em 0; + border-style: solid; + border-right-color: transparent; + border-bottom-color: transparent; + border-left-color: transparent; +} +[dir="rtl"] [data-drupal-splitbutton-trigger]::after { + right: auto; + left: 6px; +} +[data-drupal-splitbutton-open] [data-drupal-splitbutton-trigger]::after { + top: 48%; + transform: translate(50%, -50%) rotate(180deg); } -html:not(.js) .splitbutton__toggle { +html:not(.js) [data-drupal-splitbutton-trigger] { display: none; } diff --git a/core/themes/stable9/templates/misc/splitbutton-item-list.html.twig b/core/themes/stable9/templates/misc/splitbutton-item-list.html.twig index 1dbda7d3ee..3f66836428 100644 --- a/core/themes/stable9/templates/misc/splitbutton-item-list.html.twig +++ b/core/themes/stable9/templates/misc/splitbutton-item-list.html.twig @@ -8,16 +8,15 @@ * - attributes: HTML attributes to be applied to each list item. * - value: The content of the list element. * - list_type: The tag for list element ("ul" or "ol"). - * - wrapper_attributes: HTML attributes to be applied to the list wrapper. * - attributes: HTML attributes to be applied to the list. * * @see template_preprocess_splitbutton_item_list() */ #} {% if items %} - <{{ list_type }}{{ attributes.addClass('splitbutton__operation-list') }}> - {%- for item in items -%} - {{ item.value }} - {%- endfor -%} + <{{ list_type }}{{ attributes }}> + {%- for item in items -%} + {{ item.value }} + {%- endfor -%} {%- endif %} diff --git a/core/themes/stable9/templates/misc/splitbutton.html.twig b/core/themes/stable9/templates/misc/splitbutton.html.twig index c0fce23578..3b7dcbdab1 100644 --- a/core/themes/stable9/templates/misc/splitbutton.html.twig +++ b/core/themes/stable9/templates/misc/splitbutton.html.twig @@ -21,29 +21,13 @@ * * @see template_preprocess_splitbutton() */ -#}{% - set classes = [ - 'splitbutton', - 'js-splitbutton', - splitbutton_multiple == true ? 'splitbutton--multiple' : 'splitbutton--single', - splitbutton_multiple ? 'js-splitbutton-multiple', - ] -%} -{% - set button_classes = [ - title ? 'splitbutton__toggle--with-title' : 'splitbutton__toggle--no-title', - ] -%} -{% for variant in variants %} - {% set classes = classes|merge(['splitbutton--' ~ variant]) %} - {% set button_classes = button_classes|merge(['button--' ~ variant]) %} -{% endfor %} +#} - -
+
+
{{ main_element }} {% if splitbutton_multiple and exclude_toggle == FALSE %} - {% endif %}