Skip to content

Commit 10dfe1c

Browse files
committed
scroll containers or drag
1 parent 5fe8196 commit 10dfe1c

File tree

15 files changed

+323901
-18776
lines changed

15 files changed

+323901
-18776
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"folders": [
3+
{
4+
"name": "Draganddropwidget",
5+
"path": "/Users/ahwelgemoed/Documents/repos/app-services-components/apps/web-widgets/drag-and-drop-widget",
6+
"type": "mainWidget"
7+
},
8+
{ "name": "Mendix Project", "path": "/Users/ahwelgemoed/Documents/MendixApps/dnd-lts-main", "type": "mendix" }
9+
],
10+
"settings": {}
11+
}

apps/web-widgets/drag-and-drop-widget/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "draganddropwidget",
33
"widgetName": "Draganddropwidget",
4-
"version": "3.0.0",
4+
"version": "3.0.1",
55
"description": "My widget description",
66
"copyright": "2022 Mendix Technology BV",
77
"author": "ahwelgemoed",
@@ -43,6 +43,7 @@
4343
"react-dnd-html5-backend": "14.0.2",
4444
"react-dnd-multi-backend": "7.0.0-alpha.4",
4545
"react-dnd-preview": "^6.0.2",
46+
"react-dnd-scrolling": "^1.2.4",
4647
"react-dnd-touch-backend": "^14.1.1",
4748
"react-transition-group": "^4.4.2"
4849
}

apps/web-widgets/drag-and-drop-widget/src/Draganddropwidget.tsx

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import { createElement, Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
1+
import { createElement, Fragment, FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from "react";
22
import { nanoid } from "nanoid";
33

44
import DroppableItem from "./components/DroppableItem";
55
import DroppableArea from "./components/DroppableArea";
6-
7-
import { getClassNames } from "./utils/general";
8-
import { ValueStatus } from "mendix";
9-
10-
import { DraganddropwidgetContainerProps } from "../typings/DraganddropwidgetProps";
11-
import { OnDropTypes, Type_Parsed_Incoming_Data } from "./userTypes";
12-
import DragPreview from "./components/DragPreview";
6+
import ScrollHelper from "./components/ScrollHelper";
137
import MyDragProvider from "./components/MyDragProvider";
8+
import DragPreview from "./components/DragPreview";
149

1510
import "./ui/DndWidget.scss";
1611

17-
const DndWidget = (props: DraganddropwidgetContainerProps) => {
12+
import { ValueStatus } from "mendix";
13+
import type { OnDropTypes, Type_Parsed_Incoming_Data } from "./userTypes";
14+
import type { DraganddropwidgetContainerProps } from "../typings/DraganddropwidgetProps";
15+
16+
const DndWidget: FunctionComponent<DraganddropwidgetContainerProps> = props => {
1817
// Sort Incoming Data
1918
props.incomingData.setSortOrder([[props.sortOn.id, props.sort]]);
2019

@@ -31,12 +30,26 @@ const DndWidget = (props: DraganddropwidgetContainerProps) => {
3130
: props.uuidStringParent?.value;
3231
}, [props.uuidStringParentExpression]);
3332

34-
const htmlElRef = useRef<HTMLDivElement>(null);
33+
const htmlElRef = useRef<HTMLDivElement | null>(null);
34+
const parentContainerName = useRef<HTMLDivElement | null>(null);
3535
const [allData, setAllData] = useState<Type_Parsed_Incoming_Data[]>([]);
36-
const [isLoaded, setIsLoaded] = useState<boolean>(true); // Is used to reset Widget on Keyboard use to prevent fallover ove a11y backend
36+
const [isLoaded, setIsLoaded] = useState<boolean>(true); // Is used to reset Widget on Keyboard use to prevent fallover a11y backend
3737
const [isDragging, setIsDragging] = useState<boolean>(false);
3838
const [isOverIndex, setIsOverIndex] = useState<null | number>(null);
39-
const classNames = useMemo(() => getClassNames(props.uuidStringContainer), [props.uuidStringContainer]);
39+
40+
/**
41+
* We give a Child comp a way to get its parents parent. NOTE: This will only ever work 2n
42+
*/
43+
useEffect(() => {
44+
if (uuidParent) {
45+
const myParent = document.querySelectorAll(`[data-uuid="${uuidParent}"]`);
46+
// @ts-ignore
47+
const containerName = myParent[0] && myParent[0].attributes["data-containing-uuid"].value;
48+
if (containerName) {
49+
parentContainerName.current = document.getElementById(containerName) as HTMLDivElement;
50+
}
51+
}
52+
}, [uuidParent]);
4053

4154
function setUpData() {
4255
if (props.incomingData.status === ValueStatus.Available) {
@@ -58,6 +71,10 @@ const DndWidget = (props: DraganddropwidgetContainerProps) => {
5871
setAllData(allData);
5972
}
6073
}
74+
75+
/**
76+
* To Help with A11y
77+
*/
6178
const ent = () => {
6279
if (htmlElRef && htmlElRef.current) {
6380
const isFocusInDiv = htmlElRef.current.contains(document.activeElement);
@@ -121,24 +138,18 @@ const DndWidget = (props: DraganddropwidgetContainerProps) => {
121138
return <div>Loading..</div>;
122139
}
123140
return (
124-
<div className={`${props.uuidStringContainer}`} style={{ flex: 1 }} ref={htmlElRef} role="region">
125-
<MyDragProvider parent={props.isParent} uuidStringContainer={props.uuidStringContainer}>
126-
<div
127-
style={{
128-
height: "100%",
129-
width: "100%",
130-
display: "flex",
131-
flexDirection: props.isColumn ? "column" : "row"
132-
}}
133-
className={`
134-
${
135-
isDragging
136-
? classNames.dnd_draggable_container_dragging
137-
: classNames.dnd_draggable_container_not_dragging
138-
}
139-
`}
140-
>
141+
<MyDragProvider uuidStringContainer={props.uuidStringContainer}>
142+
<ScrollHelper
143+
parentContainerName={parentContainerName}
144+
isDragging={isDragging}
145+
isColumn={props.isColumn}
146+
acceptedUUids={arrayOfAcceptedUUids}
147+
droppedOnUUID={props.uuidStringContainer}
148+
uuidStringParent={uuidParent as string}
149+
>
150+
<Fragment>
141151
<div
152+
ref={htmlElRef}
142153
style={{
143154
display: "flex",
144155
width: "100%",
@@ -185,10 +196,10 @@ const DndWidget = (props: DraganddropwidgetContainerProps) => {
185196
>
186197
{!allData.length && props.hasNoDataContent}
187198
</DroppableArea>
188-
</div>
189-
<DragPreview displayItem={props.hasDataContent} {...props} />
190-
</MyDragProvider>
191-
</div>
199+
</Fragment>
200+
</ScrollHelper>
201+
<DragPreview displayItem={props.hasDataContent} {...props} />
202+
</MyDragProvider>
192203
);
193204
};
194205

apps/web-widgets/drag-and-drop-widget/src/components/DragPreview.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import { createElement, useMemo } from "react";
1+
import { createElement, FunctionComponent, useMemo } from "react";
22
import { usePreview } from "react-dnd-preview";
3-
4-
import { Type_Card_Props, Type_DragPreview_Props } from "../userTypes";
53
import { getClassNames } from "../utils/general";
64

7-
const DragPreview = (props: Type_DragPreview_Props) => {
5+
import type { Type_Card_Props, Type_DragPreview_Props } from "../userTypes";
6+
7+
const DragPreview: FunctionComponent<Type_DragPreview_Props> = props => {
88
const { display, item, style } = usePreview();
99

10-
const classNames = useMemo(() => getClassNames((item?.item as Type_Card_Props)?.item.uuidContainer), [
11-
(item?.item as Type_Card_Props)?.item.uuidContainer
12-
]);
10+
const classNames = useMemo(
11+
() => getClassNames((item?.item as Type_Card_Props)?.item.uuidContainer),
12+
[(item?.item as Type_Card_Props)?.item.uuidContainer]
13+
);
1314

1415
if (!display) {
1516
return null;

apps/web-widgets/drag-and-drop-widget/src/components/DroppableArea.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { createElement, useMemo } from "react";
1+
import { createElement, FunctionComponent, useMemo } from "react";
2+
import { getClassNames } from "../utils/general";
23
import { useDrop } from "react-dnd";
34

4-
import { getClassNames } from "../utils/general";
5-
import { Custom_DragObject, Type_Content_Area_Props } from "../userTypes";
5+
import type { Custom_DragObject, Type_Content_Area_Props } from "../userTypes";
66

7-
const DroppableArea = (props: Type_Content_Area_Props) => {
7+
const DroppableArea: FunctionComponent<Type_Content_Area_Props> = props => {
88
const classNames = useMemo(() => getClassNames(props.droppedOnUUID), [props.droppedOnUUID]);
99

1010
const [{ isOver }, drop] = useDrop({
@@ -38,7 +38,8 @@ const DroppableArea = (props: Type_Content_Area_Props) => {
3838
isOver ? classNames.dnd_at_end_over : classNames.dnd_at_end_not_over
3939
}`}
4040
style={{
41-
flex: 1
41+
flex: 1,
42+
height: "100%"
4243
}}
4344
>
4445
{props.children}

apps/web-widgets/drag-and-drop-widget/src/components/DroppableItem.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { createElement, useMemo, useRef, useState, CSSProperties } from "react";
1+
import { createElement, useMemo, useRef, useState, CSSProperties, FunctionComponent } from "react";
22
import { DropTargetMonitor, useDrag, useDrop } from "react-dnd";
3-
import { Type_Card_Props } from "../userTypes";
43
import { findOrder, getClassNames } from "../utils/general";
5-
64
import SpaceOnHover from "./SpaceOnHover";
75

8-
const DroppableItem = (props: Type_Card_Props) => {
9-
const ref = useRef<HTMLDivElement>(null);
6+
import type { Type_Card_Props } from "../userTypes";
107

8+
const DroppableItem: FunctionComponent<Type_Card_Props> = props => {
9+
const ref = useRef<HTMLDivElement>(null);
1110
const [mouseOver, setMouseOver] = useState(false);
12-
1311
const classNames = useMemo(() => getClassNames(props.item.uuidContainer), [props.item.uuidContainer]);
1412
const containerFlex = useMemo(() => {
1513
return { display: "flex", flexDirection: props.isColumn ? "column" : "row" };
@@ -97,6 +95,8 @@ const DroppableItem = (props: Type_Card_Props) => {
9795

9896
return (
9997
<div
98+
data-containing-uuid={props.droppedOnUUID}
99+
data-uuid={props.item.uuidCurrent}
100100
ref={ref}
101101
draggable="true"
102102
aria-label={props.item.ariaTitle + " inside " + props.item.ariaOfParent}
Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,28 @@
1-
import { createElement, Fragment } from "react";
1+
import { createElement, forwardRef, Fragment } from "react";
22
import { DndProvider } from "react-dnd-multi-backend";
33
import { DND_OPTIONS } from "../utils/general";
44

55
interface MyDragInterface {
66
children: any;
77
uuidStringContainer: string;
8-
parent: boolean;
98
}
109

11-
const MyDragProvider = ({ children, parent, uuidStringContainer }: MyDragInterface) => {
12-
if (parent) {
13-
return (
14-
<Fragment>
15-
<div
16-
tabIndex={0}
17-
aria-label="This is a Drag and Drop Region - Press Modifier and D To Pick Up Items. Use Up and Down Arrows to Move Items Higher or Lower or To a new List"
18-
></div>
19-
<DndProvider
20-
debugMode={false}
21-
// @ts-ignore
22-
options={DND_OPTIONS(uuidStringContainer)}
23-
>
24-
{children}
25-
</DndProvider>
26-
</Fragment>
27-
);
28-
}
29-
return <Fragment>{children}</Fragment>;
30-
};
10+
const MyDragProvider = forwardRef<HTMLDivElement, MyDragInterface>(props => {
11+
return (
12+
<Fragment>
13+
<div
14+
tabIndex={0}
15+
aria-label="This is a Drag and Drop Region - Press Modifier and D To Pick Up Items. Use Up and Down Arrows to Move Items Higher or Lower or To a new List"
16+
/>
17+
<DndProvider
18+
debugMode={false}
19+
// @ts-ignore
20+
options={DND_OPTIONS(props.uuidStringContainer)}
21+
>
22+
{props.children}
23+
</DndProvider>
24+
</Fragment>
25+
);
26+
});
3127

3228
export default MyDragProvider;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { createElement, useMemo, useRef, FunctionComponent } from "react";
2+
import { useDrop } from "react-dnd";
3+
import { getClassNames } from "../utils/general";
4+
5+
import type { Type_Scroll_Helper } from "../userTypes";
6+
7+
const ScrollHelper: FunctionComponent<Type_Scroll_Helper> = props => {
8+
const ref = useRef<HTMLDivElement>(null);
9+
const classNames = useMemo(() => getClassNames(props.droppedOnUUID), [props.droppedOnUUID]);
10+
11+
const [{ isOver }, drop] = useDrop({
12+
accept: [props.droppedOnUUID, ...props.acceptedUUids],
13+
collect(monitor) {
14+
return {
15+
isOver: monitor.isOver()
16+
};
17+
},
18+
hover(_item, monitor) {
19+
const clientOffset = monitor.getClientOffset();
20+
const hoverBoundingRect = ref.current?.getBoundingClientRect();
21+
const parentBoundingRect = props.parentContainerName.current?.getBoundingClientRect();
22+
if (hoverBoundingRect && clientOffset) {
23+
if (props.isColumn) {
24+
const distanceToBottom = hoverBoundingRect?.bottom - clientOffset.y;
25+
const distanceToTop = clientOffset.y - hoverBoundingRect?.top;
26+
if (parentBoundingRect) {
27+
const distanceToParentRight = parentBoundingRect?.right - clientOffset.x;
28+
const distanceToParentLeft = clientOffset.x - parentBoundingRect?.left;
29+
// TODO -> Maybe we should explore the idea to use ratios % and not -
30+
if (distanceToParentRight < 300) {
31+
props.parentContainerName.current?.scrollBy(50, 0);
32+
}
33+
if (distanceToParentLeft < 300) {
34+
props.parentContainerName.current?.scrollBy(-50, 0);
35+
}
36+
}
37+
if (distanceToBottom < 50) {
38+
ref.current?.scrollBy(0, 10);
39+
}
40+
if (distanceToTop < 50) {
41+
ref.current?.scrollBy(0, -20);
42+
}
43+
} else {
44+
const distanceToRight = hoverBoundingRect?.right - clientOffset.x;
45+
const distanceToLeft = clientOffset.x - hoverBoundingRect?.left;
46+
if (distanceToRight < 100) {
47+
ref.current?.scrollBy(10, 0);
48+
}
49+
if (distanceToLeft < 200) {
50+
ref.current?.scrollBy(-10, 0);
51+
}
52+
}
53+
}
54+
}
55+
});
56+
57+
drop(ref);
58+
59+
return (
60+
<div
61+
ref={ref}
62+
id={`${props.droppedOnUUID}`}
63+
className={`scroll_helper ${props.droppedOnUUID} ${
64+
isOver ? classNames.dnd_at_end_over : classNames.dnd_at_end_not_over
65+
}
66+
${
67+
props.isDragging
68+
? classNames.dnd_draggable_container_dragging
69+
: classNames.dnd_draggable_container_not_dragging
70+
}
71+
`}
72+
style={{
73+
flex: 1,
74+
display: "flex",
75+
width: "100%",
76+
flexDirection: props.isColumn ? "column" : "row"
77+
}}
78+
>
79+
{props.children}
80+
</div>
81+
);
82+
};
83+
84+
export default ScrollHelper;

apps/web-widgets/drag-and-drop-widget/src/components/SpaceOnHover.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { createElement, Fragment } from "react";
1+
import { createElement, Fragment, FunctionComponent } from "react";
22
import { motion } from "framer-motion";
33

44
import { Type_SpaceOnHover_Props } from "../userTypes";
5-
const SpaceOnHover = (props: Type_SpaceOnHover_Props) => {
5+
6+
const SpaceOnHover: FunctionComponent<Type_SpaceOnHover_Props> = props => {
67
return (
78
<Fragment>
89
<motion.div

apps/web-widgets/drag-and-drop-widget/src/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<package xmlns="http://www.mendix.com/package/1.0/">
3-
<clientModule name="Draganddropwidget" version="3.0.0" xmlns="http://www.mendix.com/clientModule/1.0/">
3+
<clientModule name="Draganddropwidget" version="3.0.1" xmlns="http://www.mendix.com/clientModule/1.0/">
44
<widgetFiles>
55
<widgetFile path="Draganddropwidget.xml"/>
66
</widgetFiles>

0 commit comments

Comments
 (0)