diff --git a/css/paragraphs.modal.css b/css/paragraphs.modal.css
index 22a368d..52b2269 100644
--- a/css/paragraphs.modal.css
+++ b/css/paragraphs.modal.css
@@ -21,3 +21,26 @@ ul.paragraphs-add-dialog-list input.field-add-more-submit {
width: 100%;
min-width: 200px;
}
+
+.js .field--widget-paragraphs .add-in-between-row {
+ border: none;
+}
+
+.js .field--widget-paragraphs .add-in-between-row > td {
+ padding: 0;
+ text-align: center;
+}
+
+.js .field--widget-paragraphs .add-in-between-row .paragraph-type-add-modal {
+ display: block;
+ height: 0;
+ margin: -12px 0 0 0;
+ padding: 0;
+}
+
+@media screen and (max-width: 600px) {
+ .js .field--widget-paragraphs .add-in-between-row .paragraph-type-add-modal input {
+ padding-bottom: 2px;
+ width: auto;
+ }
+}
diff --git a/css/paragraphs.modal.scss b/css/paragraphs.modal.scss
index 0da0fe1..c6c847d 100644
--- a/css/paragraphs.modal.scss
+++ b/css/paragraphs.modal.scss
@@ -21,3 +21,26 @@ ul.paragraphs-add-dialog-list {
min-width: 200px;
}
}
+
+.js .field--widget-paragraphs .add-in-between-row {
+ border: none;
+
+ & > td {
+ padding: 0;
+ text-align: center;
+ }
+
+ .paragraph-type-add-modal {
+ display: block;
+ height: 0;
+ margin: -12px 0 0 0;
+ padding: 0;
+
+ @media screen and (max-width: 600px) {
+ & input {
+ padding-bottom: 2px;
+ width: auto;
+ }
+ }
+ }
+}
diff --git a/js/paragraphs.modal.js b/js/paragraphs.modal.js
index 0295525..9987359 100644
--- a/js/paragraphs.modal.js
+++ b/js/paragraphs.modal.js
@@ -19,6 +19,16 @@
.on('click', function (event) {
var $button = $(this);
var $add_more_wrapper = $button.parent().siblings('.paragraphs-add-dialog');
+ var delta = '';
+
+ // If it's not initial button, then it can be add in between button.
+ if ($add_more_wrapper.length === 0) {
+ $add_more_wrapper = $button.closest('table').siblings('.clearfix').find('.paragraphs-add-dialog');
+ delta = $button.closest('tr').index() / 2;
+ }
+
+ // Set delta before dialog is created.
+ Drupal.paragraphsAddModal.setDelta($add_more_wrapper, delta);
Drupal.paragraphsAddModal.openDialog($add_more_wrapper, $button.val());
// Stop default execution of click event.
@@ -71,4 +81,142 @@
});
};
+ Drupal.paragraphsAddModal.setDelta = function($add_more_wrapper, delta) {
+ var $delta = $add_more_wrapper.closest('.clearfix').find('.paragraph-type-add-modal-delta');
+
+ $delta.val(delta);
+ };
+
+ /**
+ * Namespace for in between button handling methods.
+ *
+ * @type {Object}
+ */
+ Drupal.paragraphsAddModal.addInBetween = {};
+
+ /**
+ * Add single in between button row.
+ *
+ * @param index
+ * @param rowElement
+ */
+ Drupal.paragraphsAddModal.addInBetween.addButton = function (index, rowElement) {
+ // Create row with add in between button.
+ var str = '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' | ' +
+ '
';
+ var $buttonRow = $.parseHTML(str);
+
+ $($buttonRow).insertBefore(rowElement);
+ };
+
+
+ /**
+ * Init in between buttons for paragraphs table.
+ *
+ * @type {Object}
+ */
+ Drupal.behaviors.paragraphsInitAddInBetween = {
+ attach: function () {
+ var $tables = $('.paragraphs-tabs-wrapper .field-multiple-table')
+ .once('init-in-between-buttons');
+
+ $tables.each(function (index, table) {
+ var $table = $(table);
+
+ // Ensure that paragraph list uses modal dialog.
+ if ($table.siblings('.clearfix').find('.paragraph-type-add-modal-button').length === 0) {
+ return;
+ }
+
+ // Add buttons and adjust drag-drop functionality.
+ $table.find('> tbody > tr')
+ .each(Drupal.paragraphsAddModal.addInBetween.addButton);
+
+ // Trigger attaching of behaviours for added buttons.
+ Drupal.behaviors.paragraphsModalAdd.attach($table);
+ });
+ }
+ };
+
+ /**
+ * Adjust drag-drop functionality for paragraphs with "add in between"
+ * buttons.
+ *
+ * @param tableId
+ */
+ Drupal.paragraphsAddModal.addInBetween.adjustDragDrop = function (tableId) {
+ // Ensure that function changes are executed only once.
+ if (!Drupal.tableDrag[tableId] || Drupal.tableDrag[tableId].paragraphsDragDrop) {
+ return;
+ }
+ Drupal.tableDrag[tableId].paragraphsDragDrop = true;
+
+ // Helper function to create sequence execution of two bool functions.
+ var sequenceBoolFunctions = function (originalFn, newFn) {
+ return function () {
+ var result = originalFn.apply(this, arguments);
+
+ if (result) {
+ result = newFn.apply(this, arguments);
+ }
+
+ return result;
+ };
+ };
+
+ // Allow row swap if it's not in between button.
+ var paragraphsIsValidSwap = function (row) {
+ return !$(row).hasClass('add-in-between-row');
+ };
+
+ // Sequence default .isValidSwap() function with custom paragraphs function.
+ var rowObject = Drupal.tableDrag[tableId].row;
+ rowObject.prototype.isValidSwap = sequenceBoolFunctions(rowObject.prototype.isValidSwap, paragraphsIsValidSwap);
+
+ // provide custom .onSwap() handler to reorder "Add" buttons.
+ rowObject.prototype.onSwap = function (row) {
+ var $table = $(row).closest('table');
+ var allDrags = $table.find('tbody > tr.draggable');
+ var allAdds = $table.find('tbody > tr.add-in-between-row');
+
+ allDrags.each(function (index, dragElem) {
+ var $paragraphRow = $(dragElem);
+ if ($paragraphRow.prev('tr').hasClass('draggable')) {
+ Drupal.detachBehaviors(allAdds[index], drupalSettings, 'move');
+ $(dragElem).before(allAdds[index]);
+ Drupal.attachBehaviors(allAdds[index], drupalSettings);
+ }
+ });
+ };
+ };
+
+ /**
+ * Init in between buttons for paragraphs table.
+ *
+ * @type {Object}
+ */
+ Drupal.behaviors.paragraphsAddInBetweenTableDragDrop = {
+ attach: function () {
+ for (var tableId in drupalSettings.tableDrag) {
+ if (drupalSettings.tableDrag.hasOwnProperty(tableId)) {
+ Drupal.paragraphsAddModal.addInBetween.adjustDragDrop(tableId);
+
+ jQuery('#' + tableId)
+ .once('in-between-buttons-columnschange')
+ .on('columnschange', function () {
+ var tableId = $(this).prop('id');
+
+ Drupal.paragraphsAddModal.addInBetween.adjustDragDrop(tableId);
+ });
+ }
+ }
+ }
+ };
+
}(jQuery, Drupal, drupalSettings));
diff --git a/src/Plugin/Field/FieldWidget/ParagraphsWidget.php b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
index 84b47ea..2f28063 100644
--- a/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
+++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
@@ -818,6 +818,15 @@ class ParagraphsWidget extends WidgetBase {
],
];
+ $element['add_modal_form_area']['add_more_delta'] = [
+ '#type' => 'hidden',
+ '#attributes' => [
+ 'class' => [
+ 'paragraph-type-add-modal-delta',
+ ],
+ ],
+ ];
+
$element['#attached']['library'][] = 'paragraphs/drupal.paragraphs.modal';
}
@@ -1563,6 +1572,22 @@ class ParagraphsWidget extends WidgetBase {
$submit['widget_state']['selected_bundle'] = $submit['element']['add_more']['add_more_select']['#value'];
}
+ // Get insert position for new paragraph.
+ $submit['widget_state']['selected_position'] = $form_state->getValue(
+ array_merge(
+ $submit['parents'],
+ [
+ $submit['field_name'],
+ 'add_more',
+ 'add_modal_form_area',
+ 'add_more_delta',
+ ]
+ )
+ );
+ if (is_numeric($submit['widget_state']['selected_position'])) {
+ static::createEntityPosition($form, $form_state, $submit['widget_state']['selected_position']);
+ }
+
$submit['widget_state'] = static::autocollapse($submit['widget_state']);
static::setWidgetState($submit['parents'], $submit['field_name'], $form_state, $submit['widget_state']);
@@ -1571,6 +1596,54 @@ class ParagraphsWidget extends WidgetBase {
}
/**
+ * Creates place in user input for new entity.
+ *
+ * @param array $form
+ * Form array.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * Form state.
+ * @param int $position
+ * Position where new entity should be added.
+ */
+ public static function createEntityPosition(array $form, FormStateInterface $form_state, $position) {
+ $button = $form_state->getTriggeringElement();
+ $widget_path = array_slice($button['#array_parents'], 0, -2);
+ $filed_path = array_slice($button['#parents'], 0, -2);
+
+ // Go one level up in the form, to the widgets container.
+ $element = NestedArray::getValue($form, $widget_path);
+ $field_name = $element['#field_name'];
+ $parents = $element['#field_parents'];
+
+ // Inserting new element in the array.
+ $widget_state = static::getWidgetState($parents, $field_name, $form_state);
+
+ $widget_state['items_count']++;
+ $widget_state['real_item_count']++;
+
+ $current_position = intval($position);
+
+ // Initialize the new original delta map with the new entry.
+ $new_original_deltas = [
+ $current_position => count($widget_state['original_deltas']),
+ ];
+
+ $user_input = NestedArray::getValue($form_state->getUserInput(), $filed_path);
+ $user_input[count($widget_state['original_deltas'])]['_weight'] = $current_position;
+
+ // Increase all original deltas bigger than the delta of the duplicated
+ // element by one.
+ foreach ($widget_state['original_deltas'] as $current_delta => $original_delta) {
+ $new_delta = $current_delta >= $current_position ? $current_delta + 1 : $current_delta;
+ $new_original_deltas[$new_delta] = $original_delta;
+ $user_input[$original_delta]['_weight'] = $new_delta;
+ }
+ $widget_state['original_deltas'] = $new_original_deltas;
+
+ NestedArray::setValue($form_state->getUserInput(), $filed_path, $user_input);
+ }
+
+ /**
* Creates a duplicate of the paragraph entity.
*/
public static function duplicateSubmit(array $form, FormStateInterface $form_state) {