diff --git a/css/layout-paragraphs-widget.css b/css/layout-paragraphs-widget.css index 36c820d61a2929f8724dce15ec7899334148765e..a2941fe8b2c8ecb1310e50e14917ee38f2d175b9 100644 --- a/css/layout-paragraphs-widget.css +++ b/css/layout-paragraphs-widget.css @@ -71,6 +71,7 @@ fieldset.layout-paragraphs-field > legend { text-align: center; } .layout-paragraphs-field .layout-paragraphs-item, +.layout-paragraphs-field.is-moving .layout-paragraphs-item:hover, .layout-paragraphs-field .layout-paragraphs-disabled-items .layout-paragraphs-item { border: 1px solid #ffffff; box-sizing: border-box; @@ -90,8 +91,10 @@ fieldset.layout-paragraphs-field > legend { outline: none; } .layout-paragraphs-field .layout-paragraphs-item:hover, +.layout-paragraphs-field.is-moving .layout-paragraphs-item.is-moving:hover, .layout-paragraphs-field .layout-paragraphs-item:focus, -.layout-paragraphs-field .layout-paragraphs-item:focus-within { +.layout-paragraphs-field .layout-paragraphs-item:focus-within, +.layout-paragraphs-field .layout-paragraphs-item.is-moving { border-color: #0074bd; outline: none; } @@ -124,13 +127,22 @@ fieldset.layout-paragraphs-field > legend { background: none; } .layout-paragraphs-field .layout-controls { - height: 30px; - left: 0; + height: 28px; + left: 2px; position: absolute; - top: 0; + top: 2px; width: 90px; opacity: 0; } +.layout-paragraphs-field .layout-paragraphs-moving-message { + position: absolute; + padding: 3px 0; + width: 100%; + text-align: center; + background: #efefef; + top: 0px; + left: 0; +} .layout-paragraphs-field .layout-paragraphs-item:hover > .layout-controls, .layout-paragraphs-field .layout-paragraphs-item:focus-within > .layout-controls { @@ -147,14 +159,14 @@ fieldset.layout-paragraphs-field > legend { .layout-up { cursor: pointer; display: block; - height: 30px; + height: 28px; left: 0; overflow: hidden; opacity: .75; position: absolute; top: 0; text-indent: 100%; - width: 30px; + width: 28px; margin: 0; padding: 0; border: none; @@ -168,6 +180,7 @@ fieldset.layout-paragraphs-field > legend { opacity: 1; cursor: pointer; } +.layout-handle:focus, .layout-down:focus, .layout-up:focus { opacity: 1; @@ -200,12 +213,8 @@ fieldset.layout-paragraphs-field > legend { .reversed .layout-up { background-image: url(../img/icon-up--reversed.png); } -.layout-paragraphs-item:first-child > .layout-controls .layout-up { - pointer-events: none; - opacity: .15; -} -.layout-paragraphs-layout .layout-paragraphs-item:nth-last-child(2) > .layout-controls .layout-down, -.active-items > .layout-paragraphs-item:last-child > .layout-controls .layout-down { +.layout-down:disabled, +.layout-up:disabled { pointer-events: none; opacity: .15; } @@ -218,8 +227,8 @@ fieldset.layout-paragraphs-field > legend { .layout-paragraphs-field .layout-paragraphs-actions, .gu-mirror .layout-paragraphs-actions { position: absolute; - right: 0; - top: 0; + right: 2px; + top: 2px; padding: 0; margin: 0; visibility: hidden; @@ -231,8 +240,8 @@ fieldset.layout-paragraphs-field > legend { .layout-paragraphs-field .layout-paragraphs-actions input.layout-paragraphs-edit, .layout-paragraphs-field .layout-paragraphs-actions input.layout-paragraphs-remove { cursor: pointer; - height: 30px; - width: 30px; + height: 28px; + width: 28px; margin: 0; text-indent: 100%; overflow: hidden; @@ -281,8 +290,8 @@ fieldset.layout-paragraphs-field > legend { .layout-paragraphs-add-content__toggle { position: absolute; left: 50%; - margin-left: -11px; - margin-top: -11px; + margin-left: -15px; + margin-top: -15px; border: none; color: #fff; font-weight: bold; @@ -291,8 +300,8 @@ fieldset.layout-paragraphs-field > legend { padding: 0; opacity: .75; font-size: large; - width: 22px; - height: 22px; + width: 30px; + height: 30px; text-indent: 100%; overflow: hidden; background: url(../img/icon-add.png) 0 0 no-repeat; diff --git a/js/layout-paragraphs-widget.js b/js/layout-paragraphs-widget.js index 12f6cfa3b718a2ceec8248d2adb1092d6b5130d3..488ff0ba4e5b5be86c44dbfb8952ba02ceda3b8f 100644 --- a/js/layout-paragraphs-widget.js +++ b/js/layout-paragraphs-widget.js @@ -100,86 +100,17 @@ } } /** - * Moves an layout-paragraphs item up. - * @param {event} e DOM Event (i.e. click). - * @return {bool} Returns false if state is still loading. + * Disables layout controls based on their position. + * @param {jQuery} $container The jQuery field container object. */ - function moveUp(e) { - const $btn = $(e.currentTarget); - const $item = $btn.parents(".layout-paragraphs-item:first"); - const $container = $item.parent(); - - if (isLoading($item)) { - return false; - } - - // We're first, jump up to next available region. - if ($item.prev(".layout-paragraphs-item").length === 0) { - // Previous region, same layout. - if ($container.prev(".layout-paragraphs-layout-region").length) { - $container.prev(".layout-paragraphs-layout-region").append($item); - } - // Otherwise jump to last region in previous layout. - else if ( - $container - .closest(".layout-paragraphs-layout") - .prev() - .find(".layout-paragraphs-layout-region:last-child").length - ) { - $container - .closest(".layout-paragraphs-layout") - .prev() - .find( - ".layout-paragraphs-layout-region:last-child .layout-paragraphs-add-content__container" - ) - .before($item); - } - } else { - $item.after($item.prev()); - } - updateFields($container.closest(".layout-paragraphs-field")); - return false; - } - /** - * Moves an layout-paragraphs item down. - * @param {event} e DOM Event (i.e. click). - * @return {bool} Returns false if state is still loading. - */ - function moveDown(e) { - const $btn = $(e.currentTarget); - const $item = $btn.parents(".layout-paragraphs-item:first"); - const $container = $item.parent(); - - if (isLoading($item)) { - return false; - } - - // We're first, jump down to next available region. - if ($item.next(".layout-paragraphs-item").length === 0) { - // Next region, same layout. - if ($container.next(".layout-paragraphs-layout-region").length) { - $container.next(".layout-paragraphs-layout-region").prepend($item); - } - // Otherwise jump to first region in next layout. - else if ( - $container - .closest(".layout-paragraphs-layout") - .next() - .find(".layout-paragraphs-layout-region:first-child").length - ) { - $container - .closest(".layout-paragraphs-layout") - .next() - .find( - ".layout-paragraphs-layout-region:first-child .layout-paragraphs-add-content__container" - ) - .before($item); - } - } else { - $item.before($item.next()); - } - updateFields($container.closest(".layout-paragraphs-field")); - return false; + function updateLayoutControls($container) { + $(".layout-up, .layout-down", $container).prop("disabled", false); + $( + ".layout-paragraphs-item:first-child > .layout-controls > .layout-up, .layout-paragraphs-item:last-child > .layout-controls > .layout-down", + $container + ) + .blur() + .prop("disabled", true); } /** * Closes the "add paragraph item" menu. @@ -222,7 +153,8 @@ const btnHeight = $btn.height(); const menuHeight = $menu.outerHeight(); // Account for rotation with slight padding. - const left = btnWidth / 4 + Math.floor(btnOffset.left + btnWidth / 2 - menuWidth / 2); + const left = + btnWidth / 4 + Math.floor(btnOffset.left + btnWidth / 2 - menuWidth / 2); // Default to positioning the menu beneath the button. let orientation = "beneath"; @@ -288,13 +220,6 @@ .find(".layout-paragraphs-add-more-menu__search") .removeClass("hidden"); } - if ( - !$menu.find(".layout-paragraphs-add-more-menu__search").hasClass("hidden") - ) { - $menu - .find('.layout-paragraphs-add-more-menu__search input[type="text"]') - .focus(); - } $menu.data("activeButton", $btn); // Make other buttons inactive. $widget @@ -317,6 +242,20 @@ setTimeout(() => { positionMenu($menu); + if ( + !$menu + .find(".layout-paragraphs-add-more-menu__search") + .hasClass("hidden") + ) { + $menu + .find('.layout-paragraphs-add-more-menu__search input[type="text"]') + .focus(); + } else { + $menu + .find("a") + .first() + .focus(); + } }, 100); window.addEventListener("click", handleClickOutsideMenu); } @@ -379,7 +318,7 @@ } /** * Removes all toggle buttons. - * @param {*} $container + * @param {jQuery} $container The jQuery widget object. */ function removeToggleButtons($container) { $(".layout-paragraphs-add-content__toggle", $container).remove(); @@ -389,7 +328,8 @@ * @param {jQuery} $container The jQuery layout-paragraphs Field container. * @param {Object} widgetSettings The widget settings object. */ - function toggleButtons($container, widgetSettings) { + function toggleButtons($container) { + const widgetSettings = $container.data("widgetSettings"); $(".layout-paragraphs-add-content__toggle", $container).remove(); if (widgetSettings.isTranslating) { return; @@ -466,6 +406,229 @@ ); } } + /** + * Runs all necessary updates to widget. + * @param {jQuery} $widget The jQuery widget item. + */ + function updateWidget($widget) { + toggleButtons($widget); + updateFields($widget); + updateDisabled($widget); + updateLayoutControls($widget); + } + function move(e) { + const { $layoutItem } = e.data; + const $widget = $layoutItem.closest(".layout-paragraphs-field"); + const widgetSettings = $widget.data("widgetSettings"); + const maxDepth = widgetSettings.maxDepth; + let dir; + + switch (e.keyCode) { + case 37: + case 38: + dir = "up"; + break; + case 39: + case 40: + dir = "down"; + break; + default: + dir = "stop"; + break; + } + const $next = dir === "up" ? $layoutItem.prev() : $layoutItem.next(); + + switch (dir) { + case "up": + if ($next.length) { + if ($next.hasClass("layout-paragraphs-layout")) { + $( + $next + .find(".layout-paragraphs-layout-region") + .last() + .append($layoutItem) + ); + } else if ($next.hasClass("layout-paragraphs-item")) { + $next.before($layoutItem); + } + } else if ( + $layoutItem + .parent() + .prev() + .hasClass("layout-paragraphs-layout-region") + ) { + $layoutItem + .parent() + .prev() + .append($layoutItem); + } else if ( + $layoutItem.parent().hasClass("layout-paragraphs-layout-region") + ) { + $layoutItem + .parents(".layout-paragraphs-item") + .last() + .before($layoutItem); + } else if ( + $layoutItem + .parent() + .hasClass("layout-paragraphs-disabled-items__items") + ) { + $widget.find(".active-items").append($layoutItem); + } + break; + case "down": + if ($next.length) { + if ($next.hasClass("layout-paragraphs-layout")) { + $( + $next + .find(".layout-paragraphs-layout-region") + .first() + .prepend($layoutItem) + ); + } else if ($next.hasClass("layout-paragraphs-item")) { + $next.after($layoutItem); + } + } else if ( + $layoutItem + .parent() + .next() + .hasClass("layout-paragraphs-layout-region") + ) { + $layoutItem + .parent() + .next() + .prepend($layoutItem); + } else if ( + $layoutItem.parent().hasClass("layout-paragraphs-layout-region") + ) { + $layoutItem + .parents(".layout-paragraphs-item") + .first() + .after($layoutItem); + } else if ($layoutItem.parent().hasClass("active-items")) { + $widget + .find(".layout-paragraphs-disabled-items__items") + .prepend($layoutItem); + } + break; + case "stop": + $(document).unbind("keydown", move); + $widget + .removeClass("is-moving") + .find(".is-moving") + .removeClass("is-moving"); + $widget.find(".layout-controls, .layout-paragraphs-actions").show(); + updateWidget($widget); + break; + default: + break; + } + $widget.find(".layout-paragraphs-moving-message").remove(); + return false; + } + function startMove(e) { + const $item = $(e.currentTarget).closest(".layout-paragraphs-item"); + const $widget = $item.closest(".layout-paragraphs-field"); + $(e.currentTarget) + .parent(".layout-controls") + .after( + $( + `<div class="layout-paragraphs-moving-message">${Drupal.t( + "Use arrow keys to move, any other key to stop." + )}</div>` + ) + ); + removeToggleButtons($widget); + $widget.find(".layout-controls, .layout-paragraphs-actions").hide(); + $item.addClass("is-moving"); + $widget.addClass("is-moving"); + $(document).bind("keydown", { $layoutItem: $item }, move); + return false; + } + /** + * Moves an layout-paragraphs item up. + * @param {event} e DOM Event (i.e. click). + * @return {bool} Returns false if state is still loading. + */ + function moveUp(e) { + const $btn = $(e.currentTarget); + const $item = $btn.parents(".layout-paragraphs-item:first"); + const $container = $item.parent(); + const $widget = $btn.closest(".layout-paragraphs-field"); + + if (isLoading($item)) { + return false; + } + + // We're first, jump up to next available region. + if ($item.prev(".layout-paragraphs-item").length === 0) { + // Previous region, same layout. + if ($container.prev(".layout-paragraphs-layout-region").length) { + $container.prev(".layout-paragraphs-layout-region").append($item); + } + // Otherwise jump to last region in previous layout. + else if ( + $container + .closest(".layout-paragraphs-layout") + .prev() + .find(".layout-paragraphs-layout-region:last-child").length + ) { + $container + .closest(".layout-paragraphs-layout") + .prev() + .find( + ".layout-paragraphs-layout-region:last-child .layout-paragraphs-add-content__container" + ) + .before($item); + } + } else { + $item.after($item.prev()); + } + updateWidget($widget); + return false; + } + /** + * Moves an layout-paragraphs item down. + * @param {event} e DOM Event (i.e. click). + * @return {bool} Returns false if state is still loading. + */ + function moveDown(e) { + const $btn = $(e.currentTarget); + const $item = $btn.parents(".layout-paragraphs-item:first"); + const $container = $item.parent(); + const $widget = $btn.closest(".layout-paragraphs-field"); + + if (isLoading($item)) { + return false; + } + + // We're first, jump down to next available region. + if ($item.next(".layout-paragraphs-item").length === 0) { + // Next region, same layout. + if ($container.next(".layout-paragraphs-layout-region").length) { + $container.next(".layout-paragraphs-layout-region").prepend($item); + } + // Otherwise jump to first region in next layout. + else if ( + $container + .closest(".layout-paragraphs-layout") + .next() + .find(".layout-paragraphs-layout-region:first-child").length + ) { + $container + .closest(".layout-paragraphs-layout") + .next() + .find( + ".layout-paragraphs-layout-region:first-child .layout-paragraphs-add-content__container" + ) + .before($item); + } + } else { + $item.before($item.next()); + } + updateWidget($widget); + return false; + } /** * Initiates dragula drag/drop functionality. * @param {object} $widget ERL field item to attach drag/drop behavior to. @@ -526,8 +689,7 @@ } }); drake.on("drop", el => { - updateFields($(el).closest(".layout-paragraphs-field")); - updateDisabled($(el).closest(".layout-paragraphs-field")); + updateWidget($(el).closest(".layout-paragraphs-field")); }); drake.on("drag", el => { removeToggleButtons($(el).closest(".layout-paragraphs-field")); @@ -609,6 +771,7 @@ } }; Drupal.layoutParagraphsWidget = ($widget, widgetSettings) => { + $widget.data("widgetSettings", widgetSettings); /** * Hide all "add paragraph item" buttons if we have reached cardinality. */ @@ -712,7 +875,7 @@ .once("layout-paragraphs-controls") .each((layoutParagraphsItemIndex, layoutParagraphsItem) => { $('<div class="layout-controls">') - .append($('<div class="layout-handle">')) + .append($('<button class="layout-handle">').click(startMove)) .append( $( `<button class="layout-up">${Drupal.t("Move up")}</button>` @@ -746,9 +909,7 @@ * Update weights, regions, and disabled area on load. * Runs every time DOM is updated. */ - toggleButtons($widget, widgetSettings); - updateFields($widget); - updateDisabled($widget); + updateWidget($widget); /** * Dialog close buttons should trigger the "Cancel" action. */ diff --git a/src/Plugin/Field/FieldWidget/LayoutParagraphsWidget.php b/src/Plugin/Field/FieldWidget/LayoutParagraphsWidget.php index 3a53fd8a6c7f8e6f9d5a0856c3ad7d4cb7c58f71..6345b71decdfcb4590fe9f16ee743750a91e0101 100644 --- a/src/Plugin/Field/FieldWidget/LayoutParagraphsWidget.php +++ b/src/Plugin/Field/FieldWidget/LayoutParagraphsWidget.php @@ -750,6 +750,9 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi '#attributes' => ['class' => ['layout-paragraphs-disabled-items']], '#weight' => 999, '#title' => $this->t('Disabled Items'), + 'description' => [ + '#markup' => '<div class="layout-paragraphs-disabled-items__description">' . $this->t('Drop items here that you want to keep disabled / hidden, without removing them permanently.') . '</div>', + ], 'items' => [ '#type' => 'container', '#attributes' => [ @@ -757,9 +760,6 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi 'layout-paragraphs-disabled-items__items', ], ], - 'description' => [ - '#markup' => '<div class="layout-paragraphs-disabled-items__description">' . $this->t('Drop items here that you want to keep disabled / hidden, without removing them permanently.') . '</div>', - ], ], ]; @@ -1337,7 +1337,10 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi $radio_element = $element[$key]; $radio_with_icon_element = [ '#type' => 'container', - '#attributes' => ['class' => ['layout-select--list-item']], + '#attributes' => [ + 'class' => ['layout-select--list-item'], + 'tabindex' => 0, + ], 'icon' => [ '#type' => 'inline_template', '#template' => '<div class="layout-icon-wrapper">{{ img }}</div>',