UNPKG

12.9 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Draggable = void 0;
4const tslib_1 = require("tslib");
5const React = tslib_1.__importStar(require("react"));
6const react_styles_1 = require("@patternfly/react-styles");
7const drag_drop_1 = tslib_1.__importDefault(require("@patternfly/react-styles/css/components/DragDrop/drag-drop"));
8const DroppableContext_1 = require("./DroppableContext");
9const DragDrop_1 = require("./DragDrop");
10// Browsers really like being different from each other.
11function getDefaultBackground() {
12 const div = document.createElement('div');
13 document.head.appendChild(div);
14 const bg = window.getComputedStyle(div).backgroundColor;
15 document.head.removeChild(div);
16 return bg;
17}
18function getInheritedBackgroundColor(el) {
19 const defaultStyle = getDefaultBackground();
20 const backgroundColor = window.getComputedStyle(el).backgroundColor;
21 if (backgroundColor !== defaultStyle) {
22 return backgroundColor;
23 }
24 else if (!el.parentElement) {
25 return defaultStyle;
26 }
27 return getInheritedBackgroundColor(el.parentElement);
28}
29function removeBlankDiv(node) {
30 if (node.getAttribute('blankDiv') === 'true') {
31 // eslint-disable-next-line @typescript-eslint/prefer-for-of
32 for (let i = 0; i < node.children.length; i++) {
33 const child = node.children[i];
34 if (child.getAttribute('blankDiv') === 'true') {
35 node.removeChild(child);
36 node.setAttribute('blankDiv', 'false');
37 break;
38 }
39 }
40 }
41}
42// Reset per-element state
43function resetDroppableItem(droppableItem) {
44 removeBlankDiv(droppableItem.node);
45 droppableItem.node.classList.remove(drag_drop_1.default.modifiers.dragging);
46 droppableItem.node.classList.remove(drag_drop_1.default.modifiers.dragOutside);
47 droppableItem.draggableNodes.forEach((n, i) => {
48 n.style.transform = '';
49 n.style.transition = '';
50 droppableItem.draggableNodesRects[i] = n.getBoundingClientRect();
51 });
52}
53function overlaps(ev, rect) {
54 return (ev.clientX > rect.x && ev.clientX < rect.x + rect.width && ev.clientY > rect.y && ev.clientY < rect.y + rect.height);
55}
56const Draggable = (_a) => {
57 var { className, children, style: styleProp = {}, hasNoWrapper = false } = _a, props = tslib_1.__rest(_a, ["className", "children", "style", "hasNoWrapper"]);
58 /* eslint-disable prefer-const */
59 let [style, setStyle] = React.useState(styleProp);
60 /* eslint-enable prefer-const */
61 const [isDragging, setIsDragging] = React.useState(false);
62 const [isValidDrag, setIsValidDrag] = React.useState(true);
63 const { zone, droppableId } = React.useContext(DroppableContext_1.DroppableContext);
64 const { onDrag, onDragMove, onDrop } = React.useContext(DragDrop_1.DragDropContext);
65 // Some state is better just to leave as vars passed around between various callbacks
66 // You can only drag around one item at a time anyways...
67 let startX = 0;
68 let startY = 0;
69 let index = null; // Index of this draggable
70 let hoveringDroppable;
71 let hoveringIndex = null;
72 let mouseMoveListener;
73 let mouseUpListener;
74 // Makes it so dragging the _bottom_ of the item over the halfway of another moves it
75 let startYOffset = 0;
76 // After item returning to where it started animation completes
77 const onTransitionEnd = (_ev) => {
78 if (isDragging) {
79 setIsDragging(false);
80 setStyle(styleProp);
81 }
82 };
83 function getSourceAndDest() {
84 const hoveringDroppableId = hoveringDroppable ? hoveringDroppable.getAttribute('data-pf-droppableid') : null;
85 const source = {
86 droppableId,
87 index
88 };
89 const dest = hoveringDroppableId !== null && hoveringIndex !== null
90 ? {
91 droppableId: hoveringDroppableId,
92 index: hoveringIndex
93 }
94 : undefined;
95 return { source, dest, hoveringDroppableId };
96 }
97 const onMouseUpWhileDragging = (droppableItems) => {
98 droppableItems.forEach(resetDroppableItem);
99 document.removeEventListener('mousemove', mouseMoveListener);
100 document.removeEventListener('mouseup', mouseUpListener);
101 document.removeEventListener('contextmenu', mouseUpListener);
102 const { source, dest, hoveringDroppableId } = getSourceAndDest();
103 const consumerReordered = onDrop(source, dest);
104 if (consumerReordered && droppableId === hoveringDroppableId) {
105 setIsDragging(false);
106 setStyle(styleProp);
107 }
108 else if (!consumerReordered) {
109 // Animate item returning to where it started
110 setStyle(Object.assign(Object.assign({}, style), { transition: 'transform 0.5s cubic-bezier(0.2, 1, 0.1, 1) 0s', transform: '', background: styleProp.background, boxShadow: styleProp.boxShadow }));
111 }
112 };
113 // This is where the magic happens
114 const onMouseMoveWhileDragging = (ev, droppableItems, blankDivRect) => {
115 // Compute each time what droppable node we are hovering over
116 hoveringDroppable = null;
117 droppableItems.forEach(droppableItem => {
118 const { node, rect, isDraggingHost, draggableNodes, draggableNodesRects } = droppableItem;
119 if (overlaps(ev, rect)) {
120 // Add valid dropzone style
121 node.classList.remove(drag_drop_1.default.modifiers.dragOutside);
122 hoveringDroppable = node;
123 // Check if we need to add a blank div row
124 if (node.getAttribute('blankDiv') !== 'true' && !isDraggingHost) {
125 const blankDiv = document.createElement('div');
126 blankDiv.setAttribute('blankDiv', 'true'); // Makes removing easier
127 let blankDivPos = -1;
128 for (let i = 0; i < draggableNodes.length; i++) {
129 const childRect = draggableNodesRects[i];
130 const isLast = i === draggableNodes.length - 1;
131 const startOverlaps = childRect.y >= startY - startYOffset;
132 if ((startOverlaps || isLast) && blankDivPos === -1) {
133 if (isLast && !startOverlaps) {
134 draggableNodes[i].after(blankDiv);
135 }
136 else {
137 draggableNodes[i].before(blankDiv);
138 }
139 blankDiv.style.height = `${blankDivRect.height}px`;
140 blankDiv.style.width = `${blankDivRect.width}px`;
141 node.setAttribute('blankDiv', 'true'); // Makes removing easier
142 blankDivPos = i;
143 }
144 if (blankDivPos !== -1) {
145 childRect.y += blankDivRect.height;
146 }
147 }
148 // Insert so drag + drop behavior matches single-list case
149 draggableNodes.splice(blankDivPos, 0, blankDiv);
150 draggableNodesRects.splice(blankDivPos, 0, blankDivRect);
151 // Extend hitbox of droppable zone
152 rect.height += blankDivRect.height;
153 }
154 }
155 else {
156 resetDroppableItem(droppableItem);
157 node.classList.add(drag_drop_1.default.modifiers.dragging);
158 node.classList.add(drag_drop_1.default.modifiers.dragOutside);
159 }
160 });
161 // Move hovering draggable and style it based on cursor position
162 setStyle(Object.assign(Object.assign({}, style), { transform: `translate(${ev.pageX - startX}px, ${ev.pageY - startY}px)` }));
163 setIsValidDrag(Boolean(hoveringDroppable));
164 // Iterate through sibling draggable nodes to reposition them and store correct hoveringIndex for onDrop
165 hoveringIndex = null;
166 if (hoveringDroppable) {
167 const { draggableNodes, draggableNodesRects } = droppableItems.find(item => item.node === hoveringDroppable);
168 let lastTranslate = 0;
169 draggableNodes.forEach((n, i) => {
170 n.style.transition = 'transform 0.5s cubic-bezier(0.2, 1, 0.1, 1) 0s';
171 const rect = draggableNodesRects[i];
172 const halfway = rect.y + rect.height / 2;
173 let translateY = 0;
174 // Use offset for more interactive translations
175 if (startY < halfway && ev.pageY + (blankDivRect.height - startYOffset) > halfway) {
176 translateY -= blankDivRect.height;
177 }
178 else if (startY >= halfway && ev.pageY - startYOffset <= halfway) {
179 translateY += blankDivRect.height;
180 }
181 // Clever way to find item currently hovering over
182 if ((translateY <= lastTranslate && translateY < 0) || (translateY > lastTranslate && translateY > 0)) {
183 hoveringIndex = i;
184 }
185 n.style.transform = `translate(0, ${translateY}px`;
186 lastTranslate = translateY;
187 });
188 }
189 const { source, dest } = getSourceAndDest();
190 onDragMove(source, dest);
191 };
192 const onDragStart = (ev) => {
193 // Default HTML drag and drop doesn't allow us to change what the thing
194 // being dragged looks like. Because of this we'll use prevent the default
195 // and use `mouseMove` and `mouseUp` instead
196 ev.preventDefault();
197 if (isDragging) {
198 // still in animation
199 return;
200 }
201 // Cache droppable and draggable nodes and their bounding rects
202 const dragging = ev.target;
203 const rect = dragging.getBoundingClientRect();
204 const droppableNodes = Array.from(document.querySelectorAll(`[data-pf-droppable="${zone}"]`));
205 const droppableItems = droppableNodes.reduce((acc, cur) => {
206 cur.classList.add(drag_drop_1.default.modifiers.dragging);
207 const draggableNodes = Array.from(cur.querySelectorAll(`[data-pf-draggable-zone="${zone}"]`));
208 const isDraggingHost = cur.contains(dragging);
209 if (isDraggingHost) {
210 index = draggableNodes.indexOf(dragging);
211 }
212 const droppableItem = {
213 node: cur,
214 rect: cur.getBoundingClientRect(),
215 isDraggingHost,
216 // We don't want styles to apply to the left behind div in onMouseMoveWhileDragging
217 draggableNodes: draggableNodes.map(node => (node === dragging ? node.cloneNode(false) : node)),
218 draggableNodesRects: draggableNodes.map(node => node.getBoundingClientRect())
219 };
220 acc.push(droppableItem);
221 return acc;
222 }, []);
223 if (!onDrag({ droppableId, index })) {
224 // Consumer disallowed drag
225 return;
226 }
227 // Set initial style so future style mods take effect
228 style = Object.assign(Object.assign({}, style), { top: rect.y, left: rect.x, width: rect.width, height: rect.height, '--pf-c-draggable--m-dragging--BackgroundColor': getInheritedBackgroundColor(dragging), position: 'fixed', zIndex: 5000 });
229 setStyle(style);
230 // Store event details
231 startX = ev.pageX;
232 startY = ev.pageY;
233 startYOffset = startY - rect.y;
234 setIsDragging(true);
235 mouseMoveListener = ev => onMouseMoveWhileDragging(ev, droppableItems, rect);
236 mouseUpListener = () => onMouseUpWhileDragging(droppableItems);
237 document.addEventListener('mousemove', mouseMoveListener);
238 document.addEventListener('mouseup', mouseUpListener);
239 // Comment out this line to debug while dragging by right clicking
240 // document.addEventListener('contextmenu', mouseUpListener);
241 };
242 const childProps = Object.assign({ 'data-pf-draggable-zone': isDragging ? null : zone, draggable: true, className: react_styles_1.css(drag_drop_1.default.draggable, isDragging && drag_drop_1.default.modifiers.dragging, !isValidDrag && drag_drop_1.default.modifiers.dragOutside, className), onDragStart,
243 onTransitionEnd,
244 style }, props);
245 return (React.createElement(React.Fragment, null,
246 isDragging && (React.createElement("div", Object.assign({ draggable: true }, props, { style: Object.assign(Object.assign({}, styleProp), { visibility: 'hidden' }) }), children)),
247 hasNoWrapper ? (React.cloneElement(children, childProps)) : (React.createElement("div", Object.assign({}, childProps), children))));
248};
249exports.Draggable = Draggable;
250exports.Draggable.displayName = 'Draggable';
251//# sourceMappingURL=Draggable.js.map
\No newline at end of file