make flex-direction:*-reverse work & minor fixes

This commit is contained in:
Roman Pominov committed 2022-08-03 10:00:38 +03:00
1 parent 595b04c2a6
commit 222bd2d384
6 files changed
+104 -55

No files matched your search

@@ -6,6 +6,10 @@ import {
export type UsableElement = HTMLElement | SVGElement;
// Many DOM APIs produce values of type Element,
// which lacks certain methods that we often need.
// It's also usually safe to assume that the value is in fact HTMLElement or SVGElement.
// We use this helper to convince TypeScript that the value is a UsableElement.
export const toUseableElement = (
element: EventTarget | Element | undefined | null
): UsableElement | undefined => {
@@ -36,9 +40,10 @@ export const getLocalChildrenOrientation = (
const probeRect = probe.getBoundingClientRect();
parent.removeChild(probe);
return probeRect.width === 0 && probeRect.height !== 0
? "horizontal"
: "vertical";
return {
type: probeRect.width === 0 ? "horizontal" : "vertical",
reverse: false,
};
}
return getRectsOrientation(previous, current, next);
@@ -21,8 +21,7 @@ describe("getPlacementBetween", () => {
],
[
// [a]
// -----------
// [a]
// [b]
{ top: 0, left: 600 },
{ top: 200, left: 0 },
@@ -30,10 +29,9 @@ describe("getPlacementBetween", () => {
],
[
// [a] |
// |
// |
// | [b]
// [a]
//
// [b]
{ top: 0, left: 0 },
{ top: 600, left: 200 },
undefined,
@@ -1,6 +1,8 @@
export type Rect = Pick<DOMRect, "top" | "left" | "width" | "height">;
export type ChildrenOrientation = "vertical" | "horizontal" | "mixed";
export type ChildrenOrientation =
| { type: "horizontal" | "vertical"; reverse: boolean }
| { type: "mixed"; reverse?: boolean };
export type Placement = {
x: number;
@@ -75,7 +77,7 @@ export const getPlacementBetween = (
return undefined;
}
if (distanceX < 0 || (distanceY >= 0 && distanceY < distanceX)) {
if (distanceX < 0) {
const minX = Math.min(a.left, b.left);
const maxX = Math.max(a.left + a.width, b.left + b.width);
return {
@@ -99,12 +101,26 @@ export const getPlacementBetween = (
export const getPlacementNextTo = (
parentRect: Rect,
rect: Rect | undefined,
side: "top" | "bottom" | "left" | "right"
childrenOrientation: ChildrenOrientation,
direction: "forward" | "backward"
): Placement | undefined => {
if (rect === undefined) {
return undefined;
}
const isForward = childrenOrientation.reverse
? direction === "backward"
: direction === "forward";
const side =
childrenOrientation.type === "horizontal"
? isForward
? "right"
: "left"
: isForward
? "bottom"
: "top";
const margin = 5;
if (side === "top") {
@@ -154,7 +170,7 @@ export const getPlacementInside = (
): Placement => {
const padding = 5;
if (childrenOrientation === "horizontal") {
if (childrenOrientation.type === "horizontal") {
const safePadding = Math.min(parentRect.width / 2, padding);
return {
y: parentRect.top + safePadding,
@@ -178,12 +194,12 @@ const getSegmentsOrder = (
aEnd: number,
bStart: number,
bEnd: number
): "a-after-b" | "b-after-a" | "overlap" => {
): "b-first" | "a-first" | "overlap" => {
if (aStart >= bEnd) {
return "a-after-b";
return "b-first";
}
if (bStart >= aEnd) {
return "b-after-a";
return "a-first";
}
return "overlap";
};
@@ -205,12 +221,13 @@ const getTwoRectsOrientation = (
second.top + second.height
);
if (xOrder !== "overlap" && yOrder === "overlap") {
return "horizontal";
return { type: "horizontal", reverse: xOrder === "b-first" };
}
if (xOrder === "overlap" && yOrder !== "overlap") {
return "vertical";
return { type: "vertical", reverse: yOrder === "b-first" };
}
return "mixed";
return { type: "mixed" };
};
export const getRectsOrientation = (
@@ -218,25 +235,29 @@ export const getRectsOrientation = (
second: Rect,
third: Rect | undefined
): ChildrenOrientation => {
// @todo: add "vertical-reversed", "horizontal-reversed", "mixed-reversed"
const orientations = [
first && getTwoRectsOrientation(first, second),
third && getTwoRectsOrientation(second, third),
];
const includesVertical = orientations.includes("vertical");
const includesHorizontal = orientations.includes("horizontal");
// @todo: Maybe we should check that at least one is reversed.
// Need to test what works better, but keep an eye on false positives.
const allReverse = orientations.every((x) => !x || x.reverse !== false);
const types = orientations.map((x) => x && x.type);
const includesVertical = types.includes("vertical");
const includesHorizontal = types.includes("horizontal");
if (includesVertical && !includesHorizontal) {
return "vertical";
return { type: "vertical", reverse: allReverse };
}
if (includesHorizontal && !includesVertical) {
return "horizontal";
return { type: "horizontal", reverse: allReverse };
}
return "mixed";
return { type: "mixed" };
};
// Determines whether we should place the item before or after the closest child.
@@ -244,7 +265,7 @@ export const getRectsOrientation = (
export const getIndexAdjustment = (
pointer: { x: number; y: number },
closestChildRect: Rect | undefined,
childrenOrientation: ChildrenOrientation
{ type: orientationType, reverse }: ChildrenOrientation
) => {
if (closestChildRect === undefined) {
return 0;
@@ -252,14 +273,14 @@ export const getIndexAdjustment = (
const { top, left, width, height } = closestChildRect;
if (childrenOrientation === "vertical") {
if (orientationType === "vertical") {
const middleY = top + height / 2;
return pointer.y < middleY ? 0 : 1;
return pointer.y < middleY ? (reverse ? 1 : 0) : reverse ? 0 : 1;
}
if (childrenOrientation === "horizontal") {
if (orientationType === "horizontal") {
const middleX = left + width / 2;
return pointer.x < middleX ? 0 : 1;
return pointer.x < middleX ? (reverse ? 1 : 0) : reverse ? 0 : 1;
}
// For the "mixed" orientation,
@@ -9,12 +9,20 @@ const placementStyle = {
};
export const PlacementIndicator = ({ placement }: { placement: Placement }) => {
const style = {
top: placement.y - (placement.direction === "horizontal" ? 1 : 0),
left: placement.x - (placement.direction === "vertical" ? 1 : 0),
width: placement.direction === "horizontal" ? placement.length : 2,
height: placement.direction === "vertical" ? placement.length : 2,
};
const style =
placement.direction === "horizontal"
? {
top: placement.y - 1,
left: placement.x,
width: placement.length,
height: 2,
}
: {
top: placement.y,
left: placement.x - 1,
width: 2,
height: placement.length,
};
return <Box style={style} css={placementStyle} />;
};
@@ -12,7 +12,7 @@ type ItemData = { id: string; text: string };
const ListItem = styled("li", {
display: "block",
margin: 10,
background: "$mint12",
background: "$mint5",
padding: 10,
userSelect: "none",
});
@@ -39,8 +39,10 @@ const Item = ({
export const SortableList = ({
direction = "vertical",
reversed = false,
}: {
direction?: "horizontal" | "vertical" | "wrap";
reversed?: boolean;
}) => {
const [data, setData] = useState([
{ id: "0", text: "First" },
@@ -132,11 +134,15 @@ export const SortableList = ({
<Box
css={{
height: direction === "horizontal" ? "auto" : 500,
width: direction === "wrap" ? 200 : "auto",
width: direction === "horizontal" ? 500 : 200,
overflow: "auto",
background: "white",
color: "black",
// these are needed to make scroll work with column-reverse/row-reverse below
display: "flex",
flexDirection: direction === "horizontal" ? "row" : "column",
// to make DnD work we have to disable scrolling using touch
touchAction: "none",
}}
@@ -151,8 +157,15 @@ export const SortableList = ({
}}
css={{
li: { cursor: dragItemId === undefined ? "grab" : "default" },
display: direction === "vertical" ? "block" : "flex",
flexDirection: direction === "vertical" ? "none" : "row",
display: "flex",
flexDirection:
direction === "vertical"
? reversed
? "column-reverse"
: "column"
: reversed
? "row-reverse"
: "row",
flexWrap: direction === "wrap" ? "wrap" : "none",
}}
>
@@ -170,10 +183,18 @@ export const SortableList = ({
);
};
export const SortableListHorizontal = () => (
<SortableList direction="horizontal" />
export const SortableListHorizontal = (args: Args) => (
<SortableList direction="horizontal" {...args} />
);
export const SortableListWrap = () => <SortableList direction="wrap" />;
export const SortableListWrap = (args: Args) => (
<SortableList direction="wrap" {...args} />
);
export default {} as ComponentMeta<typeof SortableList>;
type Args = { reversed: boolean };
export default {
args: {
reversed: false,
},
} as ComponentMeta<typeof SortableList>;
@@ -134,15 +134,6 @@ export const useDrop = <Data>(props: UseDropProps<Data>): UseDropHandlers => {
current.indexWithinChildren !== indexWithinChildren ||
!isEqualRect(current.rect, parentRect)
) {
const side =
childrenOrientation === "horizontal"
? indexAdjustment > 0
? "right"
: "left"
: indexAdjustment > 0
? "bottom"
: "top";
const toGlobalCoordinates = (placement: Placement | undefined) =>
placement && {
...placement,
@@ -159,7 +150,12 @@ export const useDrop = <Data>(props: UseDropProps<Data>): UseDropHandlers => {
getPlacementBetween(closestChildRect, neighbourRect)
) ||
toGlobalCoordinates(
getPlacementNextTo(parentRect, closestChildRect, side)
getPlacementNextTo(
parentRect,
closestChildRect,
childrenOrientation,
indexAdjustment > 0 ? "forward" : "backward"
)
) ||
getPlacementInside(parentRect, childrenOrientation);