Skip to content

Commit

Permalink
remove dnd-kit as deps
Browse files Browse the repository at this point in the history
  • Loading branch information
Sidsector9 committed Aug 19, 2024
1 parent f1a8374 commit 98655eb
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 111 deletions.
161 changes: 161 additions & 0 deletions components/repeater/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { useEffect, useState, useRef } from '@wordpress/element';

export function useSortable(state = []) {

Check failure on line 3 in components/repeater/hooks.js

View workflow job for this annotation

GitHub Actions / Cypress

Parameter 'state' implicitly has an 'any[]' type.
const [items, setItems] = useState(state);
const [elementBeingDragged, setElementBeingDragged] = useState(null);
const [elementBeingDraggedIndex, setElementBeingDraggedIndex] = useState(null);
const [possibleDropTargetIndex, setPossibleDropTargetIndex] = useState(false);
const allDraggables = useRef([]);

function moveElements(arr, fromIndex, toIndex) {

Check failure on line 10 in components/repeater/hooks.js

View workflow job for this annotation

GitHub Actions / Cypress

Parameter 'arr' implicitly has an 'any' type.

Check failure on line 10 in components/repeater/hooks.js

View workflow job for this annotation

GitHub Actions / Cypress

Parameter 'fromIndex' implicitly has an 'any' type.

Check failure on line 10 in components/repeater/hooks.js

View workflow job for this annotation

GitHub Actions / Cypress

Parameter 'toIndex' implicitly has an 'any' type.
arr.splice(toIndex, 0, arr.splice(fromIndex, 1)[0]);
return arr;
}

function resetClasses(from, to) {

Check failure on line 15 in components/repeater/hooks.js

View workflow job for this annotation

GitHub Actions / Cypress

Parameter 'from' implicitly has an 'any' type.

Check failure on line 15 in components/repeater/hooks.js

View workflow job for this annotation

GitHub Actions / Cypress

Parameter 'to' implicitly has an 'any' type.
const __draggables = allDraggables.current.filter(Boolean);

Check failure on line 16 in components/repeater/hooks.js

View workflow job for this annotation

GitHub Actions / Cypress

Variable '__draggables' implicitly has an 'any[]' type.

for (let i = from; i <= to; i++) {
__draggables[i].classList.remove('move-up', 'move-down', 'repeater-item--is-dragging');
}
}

function pushElements(from, to, direction = 'down') {

Check failure on line 23 in components/repeater/hooks.js

View workflow job for this annotation

GitHub Actions / Cypress

Parameter 'from' implicitly has an 'any' type.

Check failure on line 23 in components/repeater/hooks.js

View workflow job for this annotation

GitHub Actions / Cypress

Parameter 'to' implicitly has an 'any' type.
const __draggables = allDraggables.current.filter(Boolean);

for (let i = from; i <= to; i++) {
__draggables[i].classList.add(`move-${direction}`);
}
}

function onDragStart(e, index) {
setElementBeingDragged(e.target);
setElementBeingDraggedIndex(index);
}

function onDragEnd() {
if (elementBeingDragged) {
elementBeingDragged.classList.remove('repeater-item--is-dragging');
}

setElementBeingDragged(null);
setElementBeingDraggedIndex(null);

if (possibleDropTargetIndex !== false) {
let __items = [...items];
__items = moveElements(__items, elementBeingDraggedIndex, possibleDropTargetIndex);
resetClasses(0, items.length - 1);
setItems(__items);
}
}

function onDragOver(e, index) {
e.preventDefault();

const possibleDropTargetEl = e.target;
const possibleDropTargetElBounding = possibleDropTargetEl.getBoundingClientRect();
const __draggables = allDraggables.current.filter(Boolean) || [];

const mid = (possibleDropTargetElBounding.y + possibleDropTargetElBounding.bottom) / 2;

// Being moved from down to up.
if (elementBeingDraggedIndex > index) {
if (e.clientY < mid) {
resetClasses(0, index - 1);
pushElements(index, elementBeingDraggedIndex - 1);
} else {
resetClasses(0, index);
}
}

// Being moved from up to down.
if (elementBeingDraggedIndex < index) {
if (e.clientY > mid) {
resetClasses(index, __draggables.length - 1);
pushElements(elementBeingDraggedIndex + 1, index, 'up');
} else {
resetClasses(index, __draggables.length - 1);
}
}

if (index === elementBeingDraggedIndex) {
return;
}

if (elementBeingDraggedIndex > index) {
if (e.clientY > mid) {
setPossibleDropTargetIndex(index + 1);
} else {
setPossibleDropTargetIndex(index);
}
} else if (e.clientY > mid) {
setPossibleDropTargetIndex(index);
} else {
setPossibleDropTargetIndex(index - 1);
}
}

useEffect(() => {
if (state) {
setItems(state);
}
}, [state]);

useEffect(() => {
if (elementBeingDragged) {
elementBeingDragged.classList.add('repeater-item--is-dragging');
}
}, [elementBeingDragged]);

useEffect(() => {
const parentElement = allDraggables.current.filter(Boolean)[0]?.parentNode;

if (!parentElement) {
return () => null;
}

const handleDragOver = (e) => e.preventDefault();

parentElement.addEventListener('dragover', handleDragOver);

// Cleanup function
return () => parentElement.removeEventListener('dragover', handleDragOver);
}, [allDraggables]);

useEffect(() => {
if (document.getElementById('10up-bc-repeater-styles')) {
return () => null;
}

const style = document.createElement('style');
style.id = '10up-bc-repeater-styles';
style.textContent = `
.repeater-item--is-dragging {
transform: scale(0);
}
.move-down {
position: relative;
transform: translateY( 36px );
}
.move-up {
position: relative;
transform: translateY( -36px );
}
`;

// Append the <style> element to the document head
document.head.appendChild(style);

return () => document.head.removeChild(style);
}, []);

return {
items,
allDraggables,
onDragStart,
onDragOver,
onDragEnd,
};
}
183 changes: 74 additions & 109 deletions components/repeater/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@
import { useBlockEditContext, store as blockEditorStore } from '@wordpress/block-editor';
import { store as blocksStore } from '@wordpress/blocks';
import { useSelect, dispatch } from '@wordpress/data';
import { cloneElement } from '@wordpress/element';
import { cloneElement, forwardRef, useEffect } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { v4 as uuid } from 'uuid';

import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
useSortable,
} from '@dnd-kit/sortable';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import { CSS } from '@dnd-kit/utilities';
import { DragHandle } from '../drag-handle';
import { useSortable } from './hooks';

/**
* The Sortable Item Component.
Expand All @@ -36,37 +20,40 @@ import { DragHandle } from '../drag-handle';
* @param {string} props.id A string identifier for a repeater item.
* @returns {*} React JSX
*/
const SortableItem = ({ children, item = {}, setItem = null, removeItem = null, id = '' }) => {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id,
});

const style = {
transform: CSS.Transform.toString(transform),
transition,
display: 'flex',
zIndex: isDragging ? 999 : 1,
position: 'relative',
};

const repeaterItem = children(item, id, setItem, removeItem);
const clonedRepeaterChild = cloneElement(
repeaterItem,
const SortableItem = forwardRef(
(
{
ref: setNodeRef,
style,
className: isDragging
? `${repeaterItem.props.className} repeater-item--is-dragging`
: repeaterItem.props.className,
children,
item = {},
setItem = null,
removeItem = null,
id = '',
index,
onDragStart,
onDragOver,
onDragEnd,
},
[
<DragHandle className="repeater-item__drag-handle" {...attributes} {...listeners} />,
repeaterItem.props.children,
],
);
ref,
) => {
const repeaterItem = children(item, id, setItem, removeItem);
const clonedRepeaterChild = cloneElement(
repeaterItem,
{
ref,
onDragStart: (e) => onDragStart(e, index),
onDragOver: (e) => onDragOver(e, index),
onDragEnd: () => onDragEnd(),
draggable: true,
style: {
transition: `transform 0.1s`,
},
},
[<DragHandle className="repeater-item__drag-handle" />, repeaterItem.props.children],
);

return clonedRepeaterChild;
};
return clonedRepeaterChild;
},
);

/**
* The Repeater Component.
Expand All @@ -88,28 +75,6 @@ export const AbstractRepeater = ({
value,
defaultValue = [],
}) => {
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);

function handleDragEnd(event) {
const { active, over } = event;

if (active.id !== over.id) {
const moveArray = (items) => {
const oldIndex = items.findIndex((item) => item.id === active.id);
const newIndex = items.findIndex((item) => item.id === over.id);

return arrayMove(items, oldIndex, newIndex);
};

onChange(moveArray(value));
}
}

/**
* Adds a new repeater item.
*/
Expand Down Expand Up @@ -163,50 +128,50 @@ export const AbstractRepeater = ({
onChange(valueCopy);
}

const itemIds = value.map((item) => item.id);
const { items, allDraggables, onDragStart, onDragOver, onDragEnd } = useSortable(value);

useEffect(() => {
onChange(items);
}, [items]);

return (
<>
{allowReordering ? (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={(e) => handleDragEnd(e)}
modifiers={[restrictToVerticalAxis]}
>
<SortableContext items={itemIds} strategy={verticalListSortingStrategy}>
{value.map((item, key) => {
return (
<SortableItem
item={item}
setItem={(val) => setItem(val, key)}
removeItem={() => removeItem(key)}
key={item.id}
id={item.id}
>
{(item, id, setItem, removeItem) => {
return children(
item,
id,
(val) => setItem(val, key),
() => removeItem(key),
);
}}
</SortableItem>
);
})}
</SortableContext>
</DndContext>
) : (
value.map((item, key) => {
return children(
item,
item.id,
(val) => setItem(val, key),
() => removeItem(key),
);
})
)}
{allowReordering
? items.map((item, key) => {
return (
<SortableItem
item={item}
setItem={(val) => setItem(val, key)}
removeItem={() => removeItem(key)}
key={item.id}
id={item.id}
ref={function (el) {
allDraggables.current[key] = el;
}}
index={key}
onDragStart={onDragStart}
onDragOver={onDragOver}
onDragEnd={onDragEnd}
>
{(item, id, setItem, removeItem) => {
return children(
item,
id,
(val) => setItem(val, key),
() => removeItem(key),
);
}}
</SortableItem>
);
})
: value.map((item, key) => {
return children(
item,
item.id,
(val) => setItem(val, key),
() => removeItem(key),
);
})}
{typeof addButton === 'function' ? (
addButton(addItem)
) : (
Expand Down
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 98655eb

Please sign in to comment.