mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-08 04:55:06 +09:00
make flex-direction:*-reverse work & minor fixes
This commit is contained in:
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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user