From 487010943aea12d335d34a0ff364049571003b50 Mon Sep 17 00:00:00 2001 From: Seth Hill <53914-sethhill@users.noreply.drupalcode.org> Date: Sun, 18 Aug 2024 21:02:44 +0000 Subject: [PATCH] Issue #3465076 by woldtwerk, sethhill: use es6 as default and rename to .js --- js/builder-form.es6.js | 29 -- js/builder-form.js | 41 +-- js/builder.es6.js | 609 ------------------------------------- js/builder.js | 586 +++++++++++++++++++++++------------ js/component-form.es6.js | 16 - js/component-form.js | 20 +- js/component-list.es6.js | 20 -- js/component-list.js | 26 +- layout_paragraphs.info.yml | 2 +- 9 files changed, 442 insertions(+), 907 deletions(-) delete mode 100644 js/builder-form.es6.js delete mode 100644 js/builder.es6.js delete mode 100644 js/component-form.es6.js delete mode 100644 js/component-list.es6.js diff --git a/js/builder-form.es6.js b/js/builder-form.es6.js deleted file mode 100644 index 916cf7e..0000000 --- a/js/builder-form.es6.js +++ /dev/null @@ -1,29 +0,0 @@ -(($, Drupal, once) => { - // Updates the "Close" button label when a layout is changed. - Drupal.behaviors.layoutParagraphsBuilderForm = { - attach: function attach(context) { - // Prevent nested frontend editors from being activated at the same time. - $('.lpb-enable__wrapper').removeClass('hidden'); - $('[data-lpb-form-id]').each((i, e) => { - const p = $(e).parents('[data-lpb-id]').toArray().pop(); - const parent = p || e; - $('.lpb-enable__wrapper', parent).addClass('hidden'); - }); - - // Update the "Close" button to say "Cancel" when any changes are made. - const events = [ - 'lpb-component:insert.lpb', - 'lpb-component:update.lpb', - 'lpb-component:move.lpb', - 'lpb-component:drop.lpb', - ].join(' '); - $(once('lpb-builder-form', '[data-lpb-id]', context)) - .on(events, (e) => { - $(e.currentTarget) - .closest('[data-lpb-form-id]') - .find('[data-drupal-selector="edit-close"]') - .val(Drupal.t('Cancel')); - }); - }, - }; -})(jQuery, Drupal, once); diff --git a/js/builder-form.js b/js/builder-form.js index 8d36cbc..916cf7e 100644 --- a/js/builder-form.js +++ b/js/builder-form.js @@ -1,24 +1,29 @@ -/** -* DO NOT EDIT THIS FILE. -* See the following change record for more information, -* https://www.drupal.org/node/2815083 -* @preserve -**/ -"use strict"; - -(function ($, Drupal, once) { +(($, Drupal, once) => { + // Updates the "Close" button label when a layout is changed. Drupal.behaviors.layoutParagraphsBuilderForm = { attach: function attach(context) { + // Prevent nested frontend editors from being activated at the same time. $('.lpb-enable__wrapper').removeClass('hidden'); - $('[data-lpb-form-id]').each(function (i, e) { - var p = $(e).parents('[data-lpb-id]').toArray().pop(); - var parent = p || e; + $('[data-lpb-form-id]').each((i, e) => { + const p = $(e).parents('[data-lpb-id]').toArray().pop(); + const parent = p || e; $('.lpb-enable__wrapper', parent).addClass('hidden'); }); - var events = ['lpb-component:insert.lpb', 'lpb-component:update.lpb', 'lpb-component:move.lpb', 'lpb-component:drop.lpb'].join(' '); - $(once('lpb-builder-form', '[data-lpb-id]', context)).on(events, function (e) { - $(e.currentTarget).closest('[data-lpb-form-id]').find('[data-drupal-selector="edit-close"]').val(Drupal.t('Cancel')); - }); - } + + // Update the "Close" button to say "Cancel" when any changes are made. + const events = [ + 'lpb-component:insert.lpb', + 'lpb-component:update.lpb', + 'lpb-component:move.lpb', + 'lpb-component:drop.lpb', + ].join(' '); + $(once('lpb-builder-form', '[data-lpb-id]', context)) + .on(events, (e) => { + $(e.currentTarget) + .closest('[data-lpb-form-id]') + .find('[data-drupal-selector="edit-close"]') + .val(Drupal.t('Cancel')); + }); + }, }; -})(jQuery, Drupal, once); \ No newline at end of file +})(jQuery, Drupal, once); diff --git a/js/builder.es6.js b/js/builder.es6.js deleted file mode 100644 index be38b63..0000000 --- a/js/builder.es6.js +++ /dev/null @@ -1,609 +0,0 @@ -(($, Drupal, debounce, dragula, once) => { - const idAttr = 'data-lpb-id'; - - /** - * Attaches UI elements to $container. - * @param {jQuery} $container - * The container. - * @param {Object} settings - * The settings object. - */ - function attachUiElements($container, settings) { - const id = $container.attr('data-lpb-ui-id'); - const lpbBuilderSettings = settings.lpBuilder || {}; - const uiElements = lpbBuilderSettings.uiElements || {}; - const containerUiElements = uiElements[id] || []; - Object.values(containerUiElements).forEach((uiElement) => { - const { element, method } = uiElement; - $container[method]($(element).addClass('js-lpb-ui')); - }); - } - - /** - * Repositions open dialogs when their height changes to exceed viewport. - * - * The height of an open dialog will change based on its contents and can - * cause a dialog to grow taller than the current window viewport, making - * it impossible to reach parts of the content (for example, submit buttons). - * Repositioning the dialog fixes the issue. - * @see https://www.drupal.org/project/layout_paragraphs/issues/3252978 - * @see https://stackoverflow.com/questions/5456298/refresh-jquery-ui-dialog-position - * - * @param {Number} intervalId - * The interval id. - */ - function repositionDialog(intervalId) { - const $dialogs = $('.lpb-dialog'); - if ($dialogs.length === 0) { - clearInterval(intervalId); - return; - } - $dialogs.each((i, dialog) => { - const bounding = dialog.getBoundingClientRect(); - const viewPortHeight = - window.innerHeight || document.documentElement.clientHeight; - if (bounding.bottom > viewPortHeight) { - const $dialog = $('.ui-dialog-content', dialog); - const height = viewPortHeight - 200; - $dialog.dialog('option', 'height', height); - $dialog.css('overscroll-behavior', 'contain'); - - if ($dialog.data('lpOriginalHeight') !== height) { - $dialog.data('lpOriginalHeight', height); - const bounding = dialog.getBoundingClientRect(); - const viewPortHeight = window.innerHeight || document.documentElement.clientHeight; - if (bounding.bottom > viewPortHeight) { - const pos = $dialog.dialog('option', 'position'); - $dialog.dialog('option', 'position', pos); - } - } - } - }); - } - - /** - * Makes an ajax request to reorder all items in the layout. - * This function is debounced below and not called directly. - * @param {jQuery} $element The builder element. - */ - function doReorderComponents($element) { - const id = $element.attr(idAttr); - const order = $('.js-lpb-component', $element) - .get() - .map((item) => { - const $item = $(item); - return { - uuid: $item.attr('data-uuid'), - parentUuid: - $item.parents('.js-lpb-component').first().attr('data-uuid') || - null, - region: - $item.parents('.js-lpb-region').first().attr('data-region') || null, - }; - }); - Drupal.ajax({ - url: `${drupalSettings.path.baseUrl}${drupalSettings.path.pathPrefix}layout-paragraphs-builder/${id}/reorder`, - submit: { - components: JSON.stringify(order), - }, - error: () => { - // Fail silently to prevent console errors. - }, - }).execute(); - } - - const reorderComponents = debounce(doReorderComponents); - - /** - * Returns a list of errors for the attempted move, or an empty array if there are no errors. - * @param {Element} settings The builder settings. - * @param {Element} el The element being moved. - * @param {Element} target The destination - * @param {Element} source The source - * @param {Element} sibling The next sibling element - * @return {Array} An array of errors. - */ - function moveErrors(settings, el, target, source, sibling) { - return Drupal._lpbMoveErrors - .map((validator) => - validator.apply(null, [settings, el, target, source, sibling]), - ) - .filter((errors) => errors !== false && errors !== undefined); - } - - /** - * Updates move buttons to reflect current state. - * @param {jQuery} $element The builder element. - */ - function updateMoveButtons($element) { - const lpbBuilderElements = Array.from($element[0].querySelectorAll('.js-lpb-component-list, .js-lpb-region')); - const lpbBuilderComponent = lpbBuilderElements.filter(el => el.querySelector('.js-lpb-component')); - - $element[0].querySelectorAll('.lpb-up, .lpb-down').forEach((el) => { - // Set the tabindex of the up and down arrows to 0. - el.setAttribute('tabindex', '0'); - }); - - lpbBuilderComponent.forEach((el) => { - const components = Array.from(el.children).filter(n => n.classList.contains('js-lpb-component')); - - // Set the tabindex of the first component's up arrow to -1. - components[0].querySelector('.lpb-up')?.setAttribute('tabindex', '-1'); - - // Set the tabindex of the last component's down arrow to -1. - components[components.length - 1].querySelector('.lpb-down')?.setAttribute('tabindex', '-1'); - }); - } - - /** - * Hides the add content button in regions that contain components. - * @param {jQuery} $element The builder element. - */ - function hideEmptyRegionButtons($element) { - $element.find('.js-lpb-region').each((i, e) => { - const $e = $(e); - if ($e.find('.js-lpb-component').length === 0) { - $e.find('.lpb-btn--add.center').css('display', 'block'); - } else { - $e.find('.lpb-btn--add.center').css('display', 'none'); - } - }); - } - - /** - * Updates the UI based on currently state. - * @param {jQuery} $element The builder element. - */ - function updateUi($element) { - reorderComponents($element); - updateMoveButtons($element); - hideEmptyRegionButtons($element); - } - - /** - * Moves a component up or down within a simple list of components. - * @param {jQuery} $moveItem The item to move. - * @param {int} direction 1 (down) or -1 (up). - * @return {void} - */ - function move($moveItem, direction) { - const $sibling = - direction === 1 - ? $moveItem.nextAll('.js-lpb-component').first() - : $moveItem.prevAll('.js-lpb-component').first(); - - const method = direction === 1 ? 'after' : 'before'; - const { scrollY } = window; - const destScroll = scrollY + $sibling.outerHeight() * direction; - - if ($sibling.length === 0) { - return false; - } - - // Determine if the move should be animated horizontally or vertically. - const animateProp = $sibling[0].getBoundingClientRect().top == $moveItem[0].getBoundingClientRect().top - ? 'translateX' - : 'translateY'; - // Determine the dimension property to use for the animation. - const dimmensionProp = animateProp === 'translateX' ? 'offsetWidth' : 'offsetHeight'; - // Determine the distance to move the sibling and the item. - const siblingDest = $moveItem[0][dimmensionProp] * direction * -1; - const itemDest = $sibling[0][dimmensionProp] * direction; - const distance = Math.abs(Math.max(siblingDest, itemDest)); - const duration = distance * .25; - const siblingKeyframes = [ - { transform: `${animateProp}(0)` }, - { transform: `${animateProp}(${siblingDest}px)` }, - ]; - const itemKeyframes = [ - { transform: `${animateProp}(0)` }, - { transform: `${animateProp}(${itemDest}px)` }, - ]; - const timing = { - duration, - iterations: 1 - } - const anim1 = $moveItem[0].animate(itemKeyframes, timing); - anim1.onfinish = () => { - $moveItem.css({ transform: 'none' }); - $sibling.css({ transform: 'none' }); - $sibling[method]($moveItem); - $moveItem - .closest(`[${idAttr}]`) - .trigger('lpb-component:move', [$moveItem.attr('data-uuid')]); - }; - $sibling[0].animate(siblingKeyframes, timing); - if (animateProp === 'translateY') { - window.scrollTo({ - top: destScroll, - behavior: 'smooth', - }); - } - } - - /** - * Moves the focused component up or down the DOM to the next valid position - * when an arrow key is pressed. Unlike move(), nav()can fully navigate - * components to any valid position in an entire layout. - * @param {jQuery} $item The jQuery item to move. - * @param {int} dir The direction to move (1 == down, -1 == up). - * @param {Object} settings The builder ui settings. - */ - function nav($item, dir, settings) { - const $element = $item.closest(`[${idAttr}]`); - $item.addClass('lpb-active-item'); - // Add shims as target elements. - if (dir === -1) { - $( - '.js-lpb-region .lpb-btn--add.center, .lpb-layout:not(.lpb-active-item)', - $element, - ).before('<div class="lpb-shim"></div>'); - } else if (dir === 1) { - $('.js-lpb-region', $element).prepend('<div class="lpb-shim"></div>'); - $('.lpb-layout:not(.lpb-active-item)', $element).after( - '<div class="lpb-shim"></div>', - ); - } - // Build a list of possible targets, or move destinations. - const targets = $('.js-lpb-component, .lpb-shim', $element) - .toArray() - // Remove child components from possible targets. - .filter((i) => !$.contains($item[0], i)) - // Remove layout elements that are not self from possible targets. - .filter( - (i) => i.className.indexOf('lpb-layout') === -1 || i === $item[0], - ); - const currentElement = $item[0]; - let pos = targets.indexOf(currentElement); - // Check to see if the next position is allowed by calling the 'accepts' callback. - while ( - targets[pos + dir] !== undefined && - moveErrors( - settings, - $item[0], - targets[pos + dir].parentNode, - null, - $item.next().length ? $item.next()[0] : null, - ).length > 0 - ) { - pos += dir; - } - if (targets[pos + dir] !== undefined) { - // Move after or before the target based on direction. - $(targets[pos + dir])[dir === 1 ? 'after' : 'before']($item); - } - // Remove the shims and save the order. - $('.lpb-shim', $element).remove(); - $item.removeClass('lpb-active-item').focus(); - $item - .closest(`[${idAttr}]`) - .trigger('lpb-component:move', [$item.attr('data-uuid')]); - } - - function startNav($item) { - const $msg = $( - `<div id="lpb-navigating-msg" class="lpb-tooltiptext lpb-tooltiptext--visible js-lpb-tooltiptext">${Drupal.t( - 'Use arrow keys to move. Press Return or Tab when finished.', - )}</div>`, - ); - $item - .closest('.lp-builder') - .addClass('is-navigating') - .find('.is-navigating') - .removeClass('is-navigating'); - $item - .attr('aria-describedby', 'lpb-navigating-msg') - .addClass('is-navigating') - .prepend($msg); - $item.before('<div class="lpb-navigating-placeholder"></div>'); - } - - function stopNav($item) { - $item - .removeClass('is-navigating') - .attr('aria-describedby', '') - .find('.js-lpb-tooltiptext') - .remove(); - $item - .closest(`[${idAttr}]`) - .removeClass('is-navigating') - .find('.lpb-navigating-placeholder') - .remove(); - } - - function cancelNav($item) { - const $builder = $item.closest(`[${idAttr}]`); - $builder.find('.lpb-navigating-placeholder').replaceWith($item); - updateUi($builder); - stopNav($item); - } - - /** - * Prevents user from navigating away and accidentally loosing changes. - * @param {jQuery} $element The jQuery layout paragraphs builder object. - */ - function preventLostChanges($element) { - // Add class "is_changed" when the builder is edited. - const events = [ - 'lpb-component:insert.lpb', - 'lpb-component:update.lpb', - 'lpb-component:move.lpb', - 'lpb-component:drop.lpb', - ].join(' '); - $element.on(events, (e) => { - $(e.currentTarget).addClass('is_changed'); - }); - window.addEventListener('beforeunload', (e) => { - if ($(`.is_changed[${idAttr}]`).length) { - e.preventDefault(); - e.returnValue = ''; - } - }); - $('.form-actions') - .find('input[type="submit"], a') - .click(() => { - $element.removeClass('is_changed'); - }); - } - - /** - * Attaches event listeners/handlers for builder ui. - * @param {jQuery} $element The layout paragraphs builder object. - * @param {Object} settings The builder settings. - */ - function attachEventListeners($element, settings) { - preventLostChanges($element); - $element.on('click.lp-builder', '.lpb-up', (e) => { - move($(e.target).closest('.js-lpb-component'), -1); - return false; - }); - $element.on('click.lp-builder', '.lpb-down', (e) => { - move($(e.target).closest('.js-lpb-component'), 1); - return false; - }); - $element.on('click.lp-builder', '.js-lpb-component', (e) => { - $(e.currentTarget).focus(); - }); - $element.on('click.lp-builder', '.lpb-drag', (e) => { - const $btn = $(e.currentTarget); - startNav($btn.closest('.js-lpb-component')); - }); - $(document).off('keydown'); - $(document).on('keydown', (e) => { - const $item = $('.js-lpb-component.is-navigating'); - if ($item.length) { - switch (e.code) { - case 'ArrowUp': - case 'ArrowLeft': - nav($item, -1, settings); - break; - case 'ArrowDown': - case 'ArrowRight': - nav($item, 1, settings); - break; - case 'Enter': - case 'Tab': - stopNav($item); - break; - case 'Escape': - cancelNav($item); - break; - default: - break; - } - } - }); - } - - function initDragAndDrop($element, settings) { - const containers = once('is-dragula-enabled', '.js-lpb-component-list, .js-lpb-region', $element[0]); - const drake = dragula( - containers, - { - accepts: (el, target, source, sibling) => - moveErrors(settings, el, target, source, sibling).length === 0, - moves(el, source, handle) { - const $handle = $(handle); - if ($handle.closest('.lpb-drag').length) { - return true; - } - if ($handle.closest('.lpb-controls').length) { - return false; - } - return true; - }, - }, - ); - drake.on('drop', (el) => { - const $el = $(el); - if ($el.prev().is('a')) { - $el.insertBefore($el.prev()); - } - $element.trigger('lpb-component:drop', [$el.attr('data-uuid')]); - }); - drake.on('drag', (el) => { - $element.addClass('is-dragging'); - if (el.className.indexOf('lpb-layout') > -1) { - $element.addClass('is-dragging-layout'); - } else { - $element.addClass('is-dragging-item'); - } - $element.trigger('lpb-component:drag', [$(el).attr('data-uuid')]); - }); - drake.on('dragend', () => { - $element - .removeClass('is-dragging') - .removeClass('is-dragging-layout') - .removeClass('is-dragging-item'); - }); - drake.on('over', (el, container) => { - $(container).addClass('drag-target'); - }); - drake.on('out', (el, container) => { - $(container).removeClass('drag-target'); - }); - return drake; - } - - // An array of move error callback functions. - Drupal._lpbMoveErrors = []; - /** - * Registers a move validation function. - * @param {Function} f The validator function. - */ - Drupal.registerLpbMoveError = (f) => { - Drupal._lpbMoveErrors.push(f); - }; - // Checks nesting depth. - Drupal.registerLpbMoveError((settings, el, target) => { - if ( - el.classList.contains('lpb-layout') && - $(target).parents('.lpb-layout').length > settings.nesting_depth - ) { - return Drupal.t('Exceeds nesting depth of @depth.', { - '@depth': settings.nesting_depth, - }); - } - }); - // If layout is required, prevents component from being placed outside a layout. - Drupal.registerLpbMoveError((settings, el, target) => { - if (settings.require_layouts) { - if ( - el.classList.contains('js-lpb-component') && - !el.classList.contains('lpb-layout') && - !target.classList.contains('js-lpb-region') - ) { - return Drupal.t('Components must be added inside sections.'); - } - } - }); - Drupal.AjaxCommands.prototype.LayoutParagraphsEventCommand = ( - ajax, - response, - ) => { - const { layoutId, componentUuid, eventName } = response; - const $element = $(`[data-lpb-id="${layoutId}"]`); - $element.trigger(`lpb-${eventName}`, [componentUuid]); - }; - - /* - * Moves the main form action buttons into the jQuery modal button pane. - * @param {jQuery} context - * The context to search for dialog buttons. - * @return {void} - */ - function updateDialogButtons(context) { - // Determine if this context is from within dialog content. - const $lpDialog = $(context).closest('.ui-dialog-content'); - - if (!$lpDialog) { - return; - } - - const buttons = []; - const $buttons = $lpDialog.find('.layout-paragraphs-component-form > .form-actions input[type=submit], .layout-paragraphs-component-form > .form-actions a.button'); - - if ($buttons.length === 0) { - return; - } - - $buttons.each((_i, el) => { - const $originalButton = $(el).css({ display: 'none' }); - buttons.push({ - text: $originalButton.html() || $originalButton.attr('value'), - class: $originalButton.attr('class'), - click(e) { - // If the original button is an anchor tag, triggering the "click" - // event will not simulate a click. Use the click method instead. - if ($originalButton.is('a')) { - $originalButton[0].click(); - } else { - $originalButton - .trigger('mousedown') - .trigger('mouseup') - .trigger('click'); - e.preventDefault(); - } - }, - }); - }); - - $lpDialog.dialog('option', 'buttons', buttons); - } - - Drupal.behaviors.layoutParagraphsBuilder = { - attach: function attach(context, settings) { - // Add UI elements to the builder, each component, and each region. - const jsUiElements = once('lpb-ui-elements', '[data-has-js-ui-element]'); - jsUiElements.forEach((el) => { - attachUiElements($(el), settings); - }); - - // Listen to relevant events and update UI. - once('lpb-events', '[data-lpb-id]').forEach((el) => { - $(el).on('lpb-builder:init.lpb lpb-component:insert.lpb lpb-component:update.lpb lpb-component:move.lpb lpb-component:drop.lpb lpb-component:delete.lpb', (e) => { - const $element = $(e.currentTarget); - updateUi($element); - }); - }); - - // Initialize the editor drag and drop ui. - once('lpb-enabled', '[data-lpb-id].has-components').forEach((el) => { - const $element = $(el); - const id = $element.attr(idAttr); - const lpbSettings = settings.lpBuilder[id]; - // Attach event listeners and init dragula just once. - $element.data('drake', initDragAndDrop($element, lpbSettings)); - attachEventListeners($element, lpbSettings); - $element.trigger('lpb-builder:init'); - }); - - // Add new containers to the dragula instance. - once('is-dragula-enabled', '.js-lpb-region').forEach((c) => { - const builderElement = c.closest('[data-lpb-id]'); - const drake = $(builderElement).data('drake'); - drake.containers.push(c); - }); - - // If UI elements have been attached to the DOM, we need to attach behaviors. - if (jsUiElements.length) { - Drupal.attachBehaviors(context, settings); - } - - // Moves dialog buttons into the jQuery modal button pane. - updateDialogButtons(context); - }, - }; - - // Move the main form action buttons into the jQuery modal button pane. - // By default, dialog.ajax.js moves all form action buttons into the button - // pane -- which can have unintended consequences. We suppress that option - // by setting drupalAutoButtons to false, but then manually move _only_ the - // main form action buttons into the jQuery button pane. - // @see https://www.drupal.org/project/layout_paragraphs/issues/3191418 - // @see https://www.drupal.org/project/layout_paragraphs/issues/3216981 - - // Repositions open dialogs. - // @see https://www.drupal.org/project/layout_paragraphs/issues/3252978 - // @see https://stackoverflow.com/questions/5456298/refresh-jquery-ui-dialog-position - - let lpDialogInterval; - - const handleAfterDialogCreate = (event, dialog, $dialog) => { - const $element = $dialog || jQuery(event.target); - if ($element.attr('id').startsWith('lpb-dialog-')) { - updateDialogButtons($element); - clearInterval(lpDialogInterval); - lpDialogInterval = setInterval( - repositionDialog.bind(null, lpDialogInterval), - 500 - ); - } - }; - if (typeof DrupalDialogEvent === 'undefined') { - $(window).on('dialog:aftercreate', handleAfterDialogCreate); - } else { - window.addEventListener('dialog:aftercreate', handleAfterDialogCreate); - } - -})(jQuery, Drupal, Drupal.debounce, dragula, once); diff --git a/js/builder.js b/js/builder.js index ef597fc..be38b63 100644 --- a/js/builder.js +++ b/js/builder.js @@ -1,96 +1,147 @@ -/** -* DO NOT EDIT THIS FILE. -* See the following change record for more information, -* https://www.drupal.org/node/2815083 -* @preserve -**/ -"use strict"; - -(function ($, Drupal, debounce, dragula, once) { - var idAttr = 'data-lpb-id'; +(($, Drupal, debounce, dragula, once) => { + const idAttr = 'data-lpb-id'; + + /** + * Attaches UI elements to $container. + * @param {jQuery} $container + * The container. + * @param {Object} settings + * The settings object. + */ function attachUiElements($container, settings) { - var id = $container.attr('data-lpb-ui-id'); - var lpbBuilderSettings = settings.lpBuilder || {}; - var uiElements = lpbBuilderSettings.uiElements || {}; - var containerUiElements = uiElements[id] || []; - Object.values(containerUiElements).forEach(function (uiElement) { - var element = uiElement.element, - method = uiElement.method; + const id = $container.attr('data-lpb-ui-id'); + const lpbBuilderSettings = settings.lpBuilder || {}; + const uiElements = lpbBuilderSettings.uiElements || {}; + const containerUiElements = uiElements[id] || []; + Object.values(containerUiElements).forEach((uiElement) => { + const { element, method } = uiElement; $container[method]($(element).addClass('js-lpb-ui')); }); } + + /** + * Repositions open dialogs when their height changes to exceed viewport. + * + * The height of an open dialog will change based on its contents and can + * cause a dialog to grow taller than the current window viewport, making + * it impossible to reach parts of the content (for example, submit buttons). + * Repositioning the dialog fixes the issue. + * @see https://www.drupal.org/project/layout_paragraphs/issues/3252978 + * @see https://stackoverflow.com/questions/5456298/refresh-jquery-ui-dialog-position + * + * @param {Number} intervalId + * The interval id. + */ function repositionDialog(intervalId) { - var $dialogs = $('.lpb-dialog'); + const $dialogs = $('.lpb-dialog'); if ($dialogs.length === 0) { clearInterval(intervalId); return; } - $dialogs.each(function (i, dialog) { - var bounding = dialog.getBoundingClientRect(); - var viewPortHeight = window.innerHeight || document.documentElement.clientHeight; + $dialogs.each((i, dialog) => { + const bounding = dialog.getBoundingClientRect(); + const viewPortHeight = + window.innerHeight || document.documentElement.clientHeight; if (bounding.bottom > viewPortHeight) { - var $dialog = $('.ui-dialog-content', dialog); - var height = viewPortHeight - 200; + const $dialog = $('.ui-dialog-content', dialog); + const height = viewPortHeight - 200; $dialog.dialog('option', 'height', height); $dialog.css('overscroll-behavior', 'contain'); + if ($dialog.data('lpOriginalHeight') !== height) { $dialog.data('lpOriginalHeight', height); - var _bounding = dialog.getBoundingClientRect(); - var _viewPortHeight = window.innerHeight || document.documentElement.clientHeight; - if (_bounding.bottom > _viewPortHeight) { - var pos = $dialog.dialog('option', 'position'); + const bounding = dialog.getBoundingClientRect(); + const viewPortHeight = window.innerHeight || document.documentElement.clientHeight; + if (bounding.bottom > viewPortHeight) { + const pos = $dialog.dialog('option', 'position'); $dialog.dialog('option', 'position', pos); } } } }); } + + /** + * Makes an ajax request to reorder all items in the layout. + * This function is debounced below and not called directly. + * @param {jQuery} $element The builder element. + */ function doReorderComponents($element) { - var id = $element.attr(idAttr); - var order = $('.js-lpb-component', $element).get().map(function (item) { - var $item = $(item); - return { - uuid: $item.attr('data-uuid'), - parentUuid: $item.parents('.js-lpb-component').first().attr('data-uuid') || null, - region: $item.parents('.js-lpb-region').first().attr('data-region') || null - }; - }); + const id = $element.attr(idAttr); + const order = $('.js-lpb-component', $element) + .get() + .map((item) => { + const $item = $(item); + return { + uuid: $item.attr('data-uuid'), + parentUuid: + $item.parents('.js-lpb-component').first().attr('data-uuid') || + null, + region: + $item.parents('.js-lpb-region').first().attr('data-region') || null, + }; + }); Drupal.ajax({ - url: "".concat(drupalSettings.path.baseUrl).concat(drupalSettings.path.pathPrefix, "layout-paragraphs-builder/").concat(id, "/reorder"), + url: `${drupalSettings.path.baseUrl}${drupalSettings.path.pathPrefix}layout-paragraphs-builder/${id}/reorder`, submit: { - components: JSON.stringify(order) + components: JSON.stringify(order), + }, + error: () => { + // Fail silently to prevent console errors. }, - error: function error() {} }).execute(); } - var reorderComponents = debounce(doReorderComponents); + + const reorderComponents = debounce(doReorderComponents); + + /** + * Returns a list of errors for the attempted move, or an empty array if there are no errors. + * @param {Element} settings The builder settings. + * @param {Element} el The element being moved. + * @param {Element} target The destination + * @param {Element} source The source + * @param {Element} sibling The next sibling element + * @return {Array} An array of errors. + */ function moveErrors(settings, el, target, source, sibling) { - return Drupal._lpbMoveErrors.map(function (validator) { - return validator.apply(null, [settings, el, target, source, sibling]); - }).filter(function (errors) { - return errors !== false && errors !== undefined; - }); + return Drupal._lpbMoveErrors + .map((validator) => + validator.apply(null, [settings, el, target, source, sibling]), + ) + .filter((errors) => errors !== false && errors !== undefined); } + + /** + * Updates move buttons to reflect current state. + * @param {jQuery} $element The builder element. + */ function updateMoveButtons($element) { - var lpbBuilderElements = Array.from($element[0].querySelectorAll('.js-lpb-component-list, .js-lpb-region')); - var lpbBuilderComponent = lpbBuilderElements.filter(function (el) { - return el.querySelector('.js-lpb-component'); - }); - $element[0].querySelectorAll('.lpb-up, .lpb-down').forEach(function (el) { + const lpbBuilderElements = Array.from($element[0].querySelectorAll('.js-lpb-component-list, .js-lpb-region')); + const lpbBuilderComponent = lpbBuilderElements.filter(el => el.querySelector('.js-lpb-component')); + + $element[0].querySelectorAll('.lpb-up, .lpb-down').forEach((el) => { + // Set the tabindex of the up and down arrows to 0. el.setAttribute('tabindex', '0'); }); - lpbBuilderComponent.forEach(function (el) { - var _components$0$querySe, _components$querySele; - var components = Array.from(el.children).filter(function (n) { - return n.classList.contains('js-lpb-component'); - }); - (_components$0$querySe = components[0].querySelector('.lpb-up')) === null || _components$0$querySe === void 0 || _components$0$querySe.setAttribute('tabindex', '-1'); - (_components$querySele = components[components.length - 1].querySelector('.lpb-down')) === null || _components$querySele === void 0 || _components$querySele.setAttribute('tabindex', '-1'); + + lpbBuilderComponent.forEach((el) => { + const components = Array.from(el.children).filter(n => n.classList.contains('js-lpb-component')); + + // Set the tabindex of the first component's up arrow to -1. + components[0].querySelector('.lpb-up')?.setAttribute('tabindex', '-1'); + + // Set the tabindex of the last component's down arrow to -1. + components[components.length - 1].querySelector('.lpb-down')?.setAttribute('tabindex', '-1'); }); } + + /** + * Hides the add content button in regions that contain components. + * @param {jQuery} $element The builder element. + */ function hideEmptyRegionButtons($element) { - $element.find('.js-lpb-region').each(function (i, e) { - var $e = $(e); + $element.find('.js-lpb-region').each((i, e) => { + const $e = $(e); if ($e.find('.js-lpb-component').length === 0) { $e.find('.lpb-btn--add.center').css('display', 'block'); } else { @@ -98,136 +149,228 @@ } }); } + + /** + * Updates the UI based on currently state. + * @param {jQuery} $element The builder element. + */ function updateUi($element) { reorderComponents($element); updateMoveButtons($element); hideEmptyRegionButtons($element); } + + /** + * Moves a component up or down within a simple list of components. + * @param {jQuery} $moveItem The item to move. + * @param {int} direction 1 (down) or -1 (up). + * @return {void} + */ function move($moveItem, direction) { - var $sibling = direction === 1 ? $moveItem.nextAll('.js-lpb-component').first() : $moveItem.prevAll('.js-lpb-component').first(); - var method = direction === 1 ? 'after' : 'before'; - var _window = window, - scrollY = _window.scrollY; - var destScroll = scrollY + $sibling.outerHeight() * direction; + const $sibling = + direction === 1 + ? $moveItem.nextAll('.js-lpb-component').first() + : $moveItem.prevAll('.js-lpb-component').first(); + + const method = direction === 1 ? 'after' : 'before'; + const { scrollY } = window; + const destScroll = scrollY + $sibling.outerHeight() * direction; + if ($sibling.length === 0) { return false; } - var animateProp = $sibling[0].getBoundingClientRect().top == $moveItem[0].getBoundingClientRect().top ? 'translateX' : 'translateY'; - var dimmensionProp = animateProp === 'translateX' ? 'offsetWidth' : 'offsetHeight'; - var siblingDest = $moveItem[0][dimmensionProp] * direction * -1; - var itemDest = $sibling[0][dimmensionProp] * direction; - var distance = Math.abs(Math.max(siblingDest, itemDest)); - var duration = distance * .25; - var siblingKeyframes = [{ - transform: "".concat(animateProp, "(0)") - }, { - transform: "".concat(animateProp, "(").concat(siblingDest, "px)") - }]; - var itemKeyframes = [{ - transform: "".concat(animateProp, "(0)") - }, { - transform: "".concat(animateProp, "(").concat(itemDest, "px)") - }]; - var timing = { - duration: duration, + + // Determine if the move should be animated horizontally or vertically. + const animateProp = $sibling[0].getBoundingClientRect().top == $moveItem[0].getBoundingClientRect().top + ? 'translateX' + : 'translateY'; + // Determine the dimension property to use for the animation. + const dimmensionProp = animateProp === 'translateX' ? 'offsetWidth' : 'offsetHeight'; + // Determine the distance to move the sibling and the item. + const siblingDest = $moveItem[0][dimmensionProp] * direction * -1; + const itemDest = $sibling[0][dimmensionProp] * direction; + const distance = Math.abs(Math.max(siblingDest, itemDest)); + const duration = distance * .25; + const siblingKeyframes = [ + { transform: `${animateProp}(0)` }, + { transform: `${animateProp}(${siblingDest}px)` }, + ]; + const itemKeyframes = [ + { transform: `${animateProp}(0)` }, + { transform: `${animateProp}(${itemDest}px)` }, + ]; + const timing = { + duration, iterations: 1 - }; - var anim1 = $moveItem[0].animate(itemKeyframes, timing); - anim1.onfinish = function () { - $moveItem.css({ - transform: 'none' - }); - $sibling.css({ - transform: 'none' - }); + } + const anim1 = $moveItem[0].animate(itemKeyframes, timing); + anim1.onfinish = () => { + $moveItem.css({ transform: 'none' }); + $sibling.css({ transform: 'none' }); $sibling[method]($moveItem); - $moveItem.closest("[".concat(idAttr, "]")).trigger('lpb-component:move', [$moveItem.attr('data-uuid')]); + $moveItem + .closest(`[${idAttr}]`) + .trigger('lpb-component:move', [$moveItem.attr('data-uuid')]); }; $sibling[0].animate(siblingKeyframes, timing); if (animateProp === 'translateY') { window.scrollTo({ top: destScroll, - behavior: 'smooth' + behavior: 'smooth', }); } } + + /** + * Moves the focused component up or down the DOM to the next valid position + * when an arrow key is pressed. Unlike move(), nav()can fully navigate + * components to any valid position in an entire layout. + * @param {jQuery} $item The jQuery item to move. + * @param {int} dir The direction to move (1 == down, -1 == up). + * @param {Object} settings The builder ui settings. + */ function nav($item, dir, settings) { - var $element = $item.closest("[".concat(idAttr, "]")); + const $element = $item.closest(`[${idAttr}]`); $item.addClass('lpb-active-item'); + // Add shims as target elements. if (dir === -1) { - $('.js-lpb-region .lpb-btn--add.center, .lpb-layout:not(.lpb-active-item)', $element).before('<div class="lpb-shim"></div>'); + $( + '.js-lpb-region .lpb-btn--add.center, .lpb-layout:not(.lpb-active-item)', + $element, + ).before('<div class="lpb-shim"></div>'); } else if (dir === 1) { $('.js-lpb-region', $element).prepend('<div class="lpb-shim"></div>'); - $('.lpb-layout:not(.lpb-active-item)', $element).after('<div class="lpb-shim"></div>'); + $('.lpb-layout:not(.lpb-active-item)', $element).after( + '<div class="lpb-shim"></div>', + ); } - var targets = $('.js-lpb-component, .lpb-shim', $element).toArray().filter(function (i) { - return !$.contains($item[0], i); - }).filter(function (i) { - return i.className.indexOf('lpb-layout') === -1 || i === $item[0]; - }); - var currentElement = $item[0]; - var pos = targets.indexOf(currentElement); - while (targets[pos + dir] !== undefined && moveErrors(settings, $item[0], targets[pos + dir].parentNode, null, $item.next().length ? $item.next()[0] : null).length > 0) { + // Build a list of possible targets, or move destinations. + const targets = $('.js-lpb-component, .lpb-shim', $element) + .toArray() + // Remove child components from possible targets. + .filter((i) => !$.contains($item[0], i)) + // Remove layout elements that are not self from possible targets. + .filter( + (i) => i.className.indexOf('lpb-layout') === -1 || i === $item[0], + ); + const currentElement = $item[0]; + let pos = targets.indexOf(currentElement); + // Check to see if the next position is allowed by calling the 'accepts' callback. + while ( + targets[pos + dir] !== undefined && + moveErrors( + settings, + $item[0], + targets[pos + dir].parentNode, + null, + $item.next().length ? $item.next()[0] : null, + ).length > 0 + ) { pos += dir; } if (targets[pos + dir] !== undefined) { + // Move after or before the target based on direction. $(targets[pos + dir])[dir === 1 ? 'after' : 'before']($item); } + // Remove the shims and save the order. $('.lpb-shim', $element).remove(); $item.removeClass('lpb-active-item').focus(); - $item.closest("[".concat(idAttr, "]")).trigger('lpb-component:move', [$item.attr('data-uuid')]); + $item + .closest(`[${idAttr}]`) + .trigger('lpb-component:move', [$item.attr('data-uuid')]); } + function startNav($item) { - var $msg = $("<div id=\"lpb-navigating-msg\" class=\"lpb-tooltiptext lpb-tooltiptext--visible js-lpb-tooltiptext\">".concat(Drupal.t('Use arrow keys to move. Press Return or Tab when finished.'), "</div>")); - $item.closest('.lp-builder').addClass('is-navigating').find('.is-navigating').removeClass('is-navigating'); - $item.attr('aria-describedby', 'lpb-navigating-msg').addClass('is-navigating').prepend($msg); + const $msg = $( + `<div id="lpb-navigating-msg" class="lpb-tooltiptext lpb-tooltiptext--visible js-lpb-tooltiptext">${Drupal.t( + 'Use arrow keys to move. Press Return or Tab when finished.', + )}</div>`, + ); + $item + .closest('.lp-builder') + .addClass('is-navigating') + .find('.is-navigating') + .removeClass('is-navigating'); + $item + .attr('aria-describedby', 'lpb-navigating-msg') + .addClass('is-navigating') + .prepend($msg); $item.before('<div class="lpb-navigating-placeholder"></div>'); } + function stopNav($item) { - $item.removeClass('is-navigating').attr('aria-describedby', '').find('.js-lpb-tooltiptext').remove(); - $item.closest("[".concat(idAttr, "]")).removeClass('is-navigating').find('.lpb-navigating-placeholder').remove(); + $item + .removeClass('is-navigating') + .attr('aria-describedby', '') + .find('.js-lpb-tooltiptext') + .remove(); + $item + .closest(`[${idAttr}]`) + .removeClass('is-navigating') + .find('.lpb-navigating-placeholder') + .remove(); } + function cancelNav($item) { - var $builder = $item.closest("[".concat(idAttr, "]")); + const $builder = $item.closest(`[${idAttr}]`); $builder.find('.lpb-navigating-placeholder').replaceWith($item); updateUi($builder); stopNav($item); } + + /** + * Prevents user from navigating away and accidentally loosing changes. + * @param {jQuery} $element The jQuery layout paragraphs builder object. + */ function preventLostChanges($element) { - var events = ['lpb-component:insert.lpb', 'lpb-component:update.lpb', 'lpb-component:move.lpb', 'lpb-component:drop.lpb'].join(' '); - $element.on(events, function (e) { + // Add class "is_changed" when the builder is edited. + const events = [ + 'lpb-component:insert.lpb', + 'lpb-component:update.lpb', + 'lpb-component:move.lpb', + 'lpb-component:drop.lpb', + ].join(' '); + $element.on(events, (e) => { $(e.currentTarget).addClass('is_changed'); }); - window.addEventListener('beforeunload', function (e) { - if ($(".is_changed[".concat(idAttr, "]")).length) { + window.addEventListener('beforeunload', (e) => { + if ($(`.is_changed[${idAttr}]`).length) { e.preventDefault(); e.returnValue = ''; } }); - $('.form-actions').find('input[type="submit"], a').click(function () { - $element.removeClass('is_changed'); - }); + $('.form-actions') + .find('input[type="submit"], a') + .click(() => { + $element.removeClass('is_changed'); + }); } + + /** + * Attaches event listeners/handlers for builder ui. + * @param {jQuery} $element The layout paragraphs builder object. + * @param {Object} settings The builder settings. + */ function attachEventListeners($element, settings) { preventLostChanges($element); - $element.on('click.lp-builder', '.lpb-up', function (e) { + $element.on('click.lp-builder', '.lpb-up', (e) => { move($(e.target).closest('.js-lpb-component'), -1); return false; }); - $element.on('click.lp-builder', '.lpb-down', function (e) { + $element.on('click.lp-builder', '.lpb-down', (e) => { move($(e.target).closest('.js-lpb-component'), 1); return false; }); - $element.on('click.lp-builder', '.js-lpb-component', function (e) { + $element.on('click.lp-builder', '.js-lpb-component', (e) => { $(e.currentTarget).focus(); }); - $element.on('click.lp-builder', '.lpb-drag', function (e) { - var $btn = $(e.currentTarget); + $element.on('click.lp-builder', '.lpb-drag', (e) => { + const $btn = $(e.currentTarget); startNav($btn.closest('.js-lpb-component')); }); $(document).off('keydown'); - $(document).on('keydown', function (e) { - var $item = $('.js-lpb-component.is-navigating'); + $(document).on('keydown', (e) => { + const $item = $('.js-lpb-component.is-navigating'); if ($item.length) { switch (e.code) { case 'ArrowUp': @@ -251,31 +394,34 @@ } }); } + function initDragAndDrop($element, settings) { - var containers = once('is-dragula-enabled', '.js-lpb-component-list, .js-lpb-region', $element[0]); - var drake = dragula(containers, { - accepts: function accepts(el, target, source, sibling) { - return moveErrors(settings, el, target, source, sibling).length === 0; - }, - moves: function moves(el, source, handle) { - var $handle = $(handle); - if ($handle.closest('.lpb-drag').length) { + const containers = once('is-dragula-enabled', '.js-lpb-component-list, .js-lpb-region', $element[0]); + const drake = dragula( + containers, + { + accepts: (el, target, source, sibling) => + moveErrors(settings, el, target, source, sibling).length === 0, + moves(el, source, handle) { + const $handle = $(handle); + if ($handle.closest('.lpb-drag').length) { + return true; + } + if ($handle.closest('.lpb-controls').length) { + return false; + } return true; - } - if ($handle.closest('.lpb-controls').length) { - return false; - } - return true; - } - }); - drake.on('drop', function (el) { - var $el = $(el); + }, + }, + ); + drake.on('drop', (el) => { + const $el = $(el); if ($el.prev().is('a')) { $el.insertBefore($el.prev()); } $element.trigger('lpb-component:drop', [$el.attr('data-uuid')]); }); - drake.on('drag', function (el) { + drake.on('drag', (el) => { $element.addClass('is-dragging'); if (el.className.indexOf('lpb-layout') > -1) { $element.addClass('is-dragging-layout'); @@ -284,109 +430,174 @@ } $element.trigger('lpb-component:drag', [$(el).attr('data-uuid')]); }); - drake.on('dragend', function () { - $element.removeClass('is-dragging').removeClass('is-dragging-layout').removeClass('is-dragging-item'); + drake.on('dragend', () => { + $element + .removeClass('is-dragging') + .removeClass('is-dragging-layout') + .removeClass('is-dragging-item'); }); - drake.on('over', function (el, container) { + drake.on('over', (el, container) => { $(container).addClass('drag-target'); }); - drake.on('out', function (el, container) { + drake.on('out', (el, container) => { $(container).removeClass('drag-target'); }); return drake; } + + // An array of move error callback functions. Drupal._lpbMoveErrors = []; - Drupal.registerLpbMoveError = function (f) { + /** + * Registers a move validation function. + * @param {Function} f The validator function. + */ + Drupal.registerLpbMoveError = (f) => { Drupal._lpbMoveErrors.push(f); }; - Drupal.registerLpbMoveError(function (settings, el, target) { - if (el.classList.contains('lpb-layout') && $(target).parents('.lpb-layout').length > settings.nesting_depth) { + // Checks nesting depth. + Drupal.registerLpbMoveError((settings, el, target) => { + if ( + el.classList.contains('lpb-layout') && + $(target).parents('.lpb-layout').length > settings.nesting_depth + ) { return Drupal.t('Exceeds nesting depth of @depth.', { - '@depth': settings.nesting_depth + '@depth': settings.nesting_depth, }); } }); - Drupal.registerLpbMoveError(function (settings, el, target) { + // If layout is required, prevents component from being placed outside a layout. + Drupal.registerLpbMoveError((settings, el, target) => { if (settings.require_layouts) { - if (el.classList.contains('js-lpb-component') && !el.classList.contains('lpb-layout') && !target.classList.contains('js-lpb-region')) { + if ( + el.classList.contains('js-lpb-component') && + !el.classList.contains('lpb-layout') && + !target.classList.contains('js-lpb-region') + ) { return Drupal.t('Components must be added inside sections.'); } } }); - Drupal.AjaxCommands.prototype.LayoutParagraphsEventCommand = function (ajax, response) { - var layoutId = response.layoutId, - componentUuid = response.componentUuid, - eventName = response.eventName; - var $element = $("[data-lpb-id=\"".concat(layoutId, "\"]")); - $element.trigger("lpb-".concat(eventName), [componentUuid]); + Drupal.AjaxCommands.prototype.LayoutParagraphsEventCommand = ( + ajax, + response, + ) => { + const { layoutId, componentUuid, eventName } = response; + const $element = $(`[data-lpb-id="${layoutId}"]`); + $element.trigger(`lpb-${eventName}`, [componentUuid]); }; + + /* + * Moves the main form action buttons into the jQuery modal button pane. + * @param {jQuery} context + * The context to search for dialog buttons. + * @return {void} + */ function updateDialogButtons(context) { - var $lpDialog = $(context).closest('.ui-dialog-content'); + // Determine if this context is from within dialog content. + const $lpDialog = $(context).closest('.ui-dialog-content'); + if (!$lpDialog) { return; } - var buttons = []; - var $buttons = $lpDialog.find('.layout-paragraphs-component-form > .form-actions input[type=submit], .layout-paragraphs-component-form > .form-actions a.button'); + + const buttons = []; + const $buttons = $lpDialog.find('.layout-paragraphs-component-form > .form-actions input[type=submit], .layout-paragraphs-component-form > .form-actions a.button'); + if ($buttons.length === 0) { return; } - $buttons.each(function (_i, el) { - var $originalButton = $(el).css({ - display: 'none' - }); + + $buttons.each((_i, el) => { + const $originalButton = $(el).css({ display: 'none' }); buttons.push({ text: $originalButton.html() || $originalButton.attr('value'), class: $originalButton.attr('class'), - click: function click(e) { + click(e) { + // If the original button is an anchor tag, triggering the "click" + // event will not simulate a click. Use the click method instead. if ($originalButton.is('a')) { $originalButton[0].click(); } else { - $originalButton.trigger('mousedown').trigger('mouseup').trigger('click'); + $originalButton + .trigger('mousedown') + .trigger('mouseup') + .trigger('click'); e.preventDefault(); } - } + }, }); }); + $lpDialog.dialog('option', 'buttons', buttons); } + Drupal.behaviors.layoutParagraphsBuilder = { attach: function attach(context, settings) { - var jsUiElements = once('lpb-ui-elements', '[data-has-js-ui-element]'); - jsUiElements.forEach(function (el) { + // Add UI elements to the builder, each component, and each region. + const jsUiElements = once('lpb-ui-elements', '[data-has-js-ui-element]'); + jsUiElements.forEach((el) => { attachUiElements($(el), settings); }); - once('lpb-events', '[data-lpb-id]').forEach(function (el) { - $(el).on('lpb-builder:init.lpb lpb-component:insert.lpb lpb-component:update.lpb lpb-component:move.lpb lpb-component:drop.lpb lpb-component:delete.lpb', function (e) { - var $element = $(e.currentTarget); + + // Listen to relevant events and update UI. + once('lpb-events', '[data-lpb-id]').forEach((el) => { + $(el).on('lpb-builder:init.lpb lpb-component:insert.lpb lpb-component:update.lpb lpb-component:move.lpb lpb-component:drop.lpb lpb-component:delete.lpb', (e) => { + const $element = $(e.currentTarget); updateUi($element); }); }); - once('lpb-enabled', '[data-lpb-id].has-components').forEach(function (el) { - var $element = $(el); - var id = $element.attr(idAttr); - var lpbSettings = settings.lpBuilder[id]; + + // Initialize the editor drag and drop ui. + once('lpb-enabled', '[data-lpb-id].has-components').forEach((el) => { + const $element = $(el); + const id = $element.attr(idAttr); + const lpbSettings = settings.lpBuilder[id]; + // Attach event listeners and init dragula just once. $element.data('drake', initDragAndDrop($element, lpbSettings)); attachEventListeners($element, lpbSettings); $element.trigger('lpb-builder:init'); }); - once('is-dragula-enabled', '.js-lpb-region').forEach(function (c) { - var builderElement = c.closest('[data-lpb-id]'); - var drake = $(builderElement).data('drake'); + + // Add new containers to the dragula instance. + once('is-dragula-enabled', '.js-lpb-region').forEach((c) => { + const builderElement = c.closest('[data-lpb-id]'); + const drake = $(builderElement).data('drake'); drake.containers.push(c); }); + + // If UI elements have been attached to the DOM, we need to attach behaviors. if (jsUiElements.length) { Drupal.attachBehaviors(context, settings); } + + // Moves dialog buttons into the jQuery modal button pane. updateDialogButtons(context); - } + }, }; - var lpDialogInterval; - var handleAfterDialogCreate = function handleAfterDialogCreate(event, dialog, $dialog) { - var $element = $dialog || jQuery(event.target); + + // Move the main form action buttons into the jQuery modal button pane. + // By default, dialog.ajax.js moves all form action buttons into the button + // pane -- which can have unintended consequences. We suppress that option + // by setting drupalAutoButtons to false, but then manually move _only_ the + // main form action buttons into the jQuery button pane. + // @see https://www.drupal.org/project/layout_paragraphs/issues/3191418 + // @see https://www.drupal.org/project/layout_paragraphs/issues/3216981 + + // Repositions open dialogs. + // @see https://www.drupal.org/project/layout_paragraphs/issues/3252978 + // @see https://stackoverflow.com/questions/5456298/refresh-jquery-ui-dialog-position + + let lpDialogInterval; + + const handleAfterDialogCreate = (event, dialog, $dialog) => { + const $element = $dialog || jQuery(event.target); if ($element.attr('id').startsWith('lpb-dialog-')) { updateDialogButtons($element); clearInterval(lpDialogInterval); - lpDialogInterval = setInterval(repositionDialog.bind(null, lpDialogInterval), 500); + lpDialogInterval = setInterval( + repositionDialog.bind(null, lpDialogInterval), + 500 + ); } }; if (typeof DrupalDialogEvent === 'undefined') { @@ -394,4 +605,5 @@ } else { window.addEventListener('dialog:aftercreate', handleAfterDialogCreate); } -})(jQuery, Drupal, Drupal.debounce, dragula, once); \ No newline at end of file + +})(jQuery, Drupal, Drupal.debounce, dragula, once); diff --git a/js/component-form.es6.js b/js/component-form.es6.js deleted file mode 100644 index a1a2dac..0000000 --- a/js/component-form.es6.js +++ /dev/null @@ -1,16 +0,0 @@ -(function ($, Drupal) { - Drupal.behaviors.layoutParagraphsComponentForm = { - attach: function attach(context) { - // The layout selection element uses AJAX to load the layout config form. - // We need to disable the save button while waiting for the AJAX request, - // to prevent race UI condition. - // @see https://www.drupal.org/project/layout_paragraphs/issues/3265669 - $('[name="layout_paragraphs[layout]"]').on('change', (e) => { - $('.lpb-btn--save').prop('disabled', e.currentTarget.disabled); - }); - // Re-enable the component form save button when the behavior reattaches, - // which will happen once the AJAX request completes. - $('.lpb-btn--save').prop('disabled', false); - } - } -})(jQuery, Drupal); diff --git a/js/component-form.js b/js/component-form.js index 8dc94e8..a1a2dac 100644 --- a/js/component-form.js +++ b/js/component-form.js @@ -1,18 +1,16 @@ -/** -* DO NOT EDIT THIS FILE. -* See the following change record for more information, -* https://www.drupal.org/node/2815083 -* @preserve -**/ -"use strict"; - (function ($, Drupal) { Drupal.behaviors.layoutParagraphsComponentForm = { attach: function attach(context) { - $('[name="layout_paragraphs[layout]"]').on('change', function (e) { + // The layout selection element uses AJAX to load the layout config form. + // We need to disable the save button while waiting for the AJAX request, + // to prevent race UI condition. + // @see https://www.drupal.org/project/layout_paragraphs/issues/3265669 + $('[name="layout_paragraphs[layout]"]').on('change', (e) => { $('.lpb-btn--save').prop('disabled', e.currentTarget.disabled); }); + // Re-enable the component form save button when the behavior reattaches, + // which will happen once the AJAX request completes. $('.lpb-btn--save').prop('disabled', false); } - }; -})(jQuery, Drupal); \ No newline at end of file + } +})(jQuery, Drupal); diff --git a/js/component-list.es6.js b/js/component-list.es6.js deleted file mode 100644 index c0e2d6f..0000000 --- a/js/component-list.es6.js +++ /dev/null @@ -1,20 +0,0 @@ -(($, Drupal) => { - Drupal.behaviors.layoutParagraphsComponentList = { - attach: function attach(context) { - $('.lpb-component-list-search-input', context).keyup((e) => { - const v = e.currentTarget.value; - const pattern = new RegExp(v, 'i'); - const $list = $(e.currentTarget) - .closest('.lpb-component-list') - .find('.lpb-component-list__item'); - $list.each((i, item) => { - if (pattern.test(item.innerText)) { - item.removeAttribute('style'); - } else { - item.style.display = 'none'; - } - }); - }); - }, - }; -})(jQuery, Drupal); diff --git a/js/component-list.js b/js/component-list.js index f46a48f..c0e2d6f 100644 --- a/js/component-list.js +++ b/js/component-list.js @@ -1,19 +1,13 @@ -/** -* DO NOT EDIT THIS FILE. -* See the following change record for more information, -* https://www.drupal.org/node/2815083 -* @preserve -**/ -"use strict"; - -(function ($, Drupal) { +(($, Drupal) => { Drupal.behaviors.layoutParagraphsComponentList = { attach: function attach(context) { - $('.lpb-component-list-search-input', context).keyup(function (e) { - var v = e.currentTarget.value; - var pattern = new RegExp(v, 'i'); - var $list = $(e.currentTarget).closest('.lpb-component-list').find('.lpb-component-list__item'); - $list.each(function (i, item) { + $('.lpb-component-list-search-input', context).keyup((e) => { + const v = e.currentTarget.value; + const pattern = new RegExp(v, 'i'); + const $list = $(e.currentTarget) + .closest('.lpb-component-list') + .find('.lpb-component-list__item'); + $list.each((i, item) => { if (pattern.test(item.innerText)) { item.removeAttribute('style'); } else { @@ -21,6 +15,6 @@ } }); }); - } + }, }; -})(jQuery, Drupal); \ No newline at end of file +})(jQuery, Drupal); diff --git a/layout_paragraphs.info.yml b/layout_paragraphs.info.yml index 56142e3..9e8754b 100644 --- a/layout_paragraphs.info.yml +++ b/layout_paragraphs.info.yml @@ -1,7 +1,7 @@ name: 'Layout Paragraphs' type: module description: 'Field widget and formatter for using layouts with paragraph fields.' -core_version_requirement: ^9.2 || ^10 || ^11 +core_version_requirement: ^10 || ^11 package: 'Paragraphs' configure: layout_paragraphs.label_settings dependencies: -- GitLab