Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: RTL support for keyboard commands and pointer events #302

Merged
merged 17 commits into from
May 23, 2024
Merged
6 changes: 6 additions & 0 deletions pages/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { Suspense } from "react";
import { useSearchParams } from "react-router-dom";
import { pagesMap } from "../pages";

export interface PageProps {
pageId: string;
}

export default function Page({ pageId }: PageProps) {
const [searchParams] = useSearchParams();
const direction = searchParams.get("direction") ?? "ltr";
document.documentElement.setAttribute("dir", direction);
georgylobko marked this conversation as resolved.
Show resolved Hide resolved

const Component = pagesMap[pageId];

return (
Expand Down
1 change: 1 addition & 0 deletions pages/dnd/engine-page-template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function EnginePageTemplate({
ariaLabel="Widget settings"
variant="icon"
onItemClick={() => actions.removeItem()}
expandToViewport={true}
/>
}
i18nStrings={boardItemI18nStrings}
Expand Down
3 changes: 3 additions & 0 deletions pages/widget-container/permutations.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default function WidgetContainerPermutations() {
{ id: "two", text: "Two" },
]}
variant="icon"
expandToViewport={true}
/>
}
i18nStrings={i18nStrings.boardItemI18nStrings}
Expand Down Expand Up @@ -91,6 +92,7 @@ export default function WidgetContainerPermutations() {
{ id: "two", text: "Two" },
]}
variant="icon"
expandToViewport={true}
/>
}
i18nStrings={i18nStrings.boardItemI18nStrings}
Expand Down Expand Up @@ -119,6 +121,7 @@ export default function WidgetContainerPermutations() {
{ id: "two", text: "Two" },
]}
variant="icon"
expandToViewport={true}
/>
}
i18nStrings={i18nStrings.boardItemI18nStrings}
Expand Down
1 change: 1 addition & 0 deletions pages/with-app-layout/widgets-board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function WidgetsBoard({ loading, widgets, onWidgetsChange }: WidgetsBoard
ariaLabel={clientI18nStrings.widgetsBoard.widgetSettings}
variant="icon"
onItemClick={() => setDeleteConfirmation(item.id)}
expandToViewport={true}
/>
}
i18nStrings={boardItemI18nStrings}
Expand Down
19 changes: 10 additions & 9 deletions src/board-item/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,30 @@
.header {
display: flex;
justify-items: center;
padding: cs.$space-scaled-s calc(#{cs.$space-container-horizontal} - #{cs.$space-scaled-xs});
padding-block: cs.$space-scaled-s;
padding-inline: calc(#{cs.$space-container-horizontal} - #{cs.$space-scaled-xs});
}

.flexible {
flex: 1 1 min-content;
}

.handle {
margin-top: calc(cs.$space-scaled-xxs + 1px);
margin-block-start: calc(cs.$space-scaled-xxs + 1px);
.refresh > & {
margin-top: calc(cs.$space-static-xxxs + 1px);
margin-block-start: calc(cs.$space-static-xxxs + 1px);
}
}

.header-content {
margin-left: cs.$space-scaled-xxs;
margin-inline-start: cs.$space-scaled-xxs;
}

.settings {
margin-top: calc(cs.$space-scaled-xxxs + 1px);
margin-left: cs.$space-static-xs;
margin-block-start: calc(cs.$space-scaled-xxxs + 1px);
margin-inline-start: cs.$space-static-xs;
.refresh > & {
margin-top: 0px;
margin-block-start: 0px;
}
}

Expand All @@ -50,6 +51,6 @@
.resizer {
position: absolute;
// offset for inner paddings in the handle
bottom: calc(#{cs.$space-static-xs} - #{cs.$space-static-xxxs});
right: calc(#{cs.$space-static-xs} - #{cs.$space-static-xxxs});
inset-block-end: calc(#{cs.$space-static-xs} - #{cs.$space-static-xxxs});
inset-inline-end: calc(#{cs.$space-static-xs} - #{cs.$space-static-xxxs});
}
8 changes: 5 additions & 3 deletions src/board/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

.placeholder {
border-radius: cs.$border-radius-container;
height: 100%;
block-size: 100%;
&--active {
background-color: cs.$color-board-placeholder-active;
}
Expand All @@ -17,8 +17,10 @@

.empty {
box-sizing: border-box;
width: 100%;
padding: cs.$space-scaled-m cs.$space-scaled-l cs.$space-scaled-l;
inline-size: 100%;
padding-block-start: cs.$space-scaled-m;
padding-block-end: cs.$space-scaled-l;
padding-inline: cs.$space-scaled-l;
color: cs.$color-text-empty;
display: flex;
justify-content: center;
Expand Down
12 changes: 8 additions & 4 deletions src/board/transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LayoutEngine } from "../internal/layout-engine/engine";
import { Coordinates } from "../internal/utils/coordinates";
import { getDefaultColumnSpan, getDefaultRowSpan, getMinColumnSpan, getMinRowSpan } from "../internal/utils/layout";
import { Position } from "../internal/utils/position";
import { getLogicalBoundingClientRect } from "../internal/utils/screen";
import { BoardProps, RemoveTransition, Transition, TransitionAnnouncement } from "./interfaces";
import { createOperationAnnouncement } from "./utils/announcements";
import { getHoveredRect } from "./utils/get-hovered-rect";
Expand Down Expand Up @@ -297,11 +298,13 @@ function updateTransitionWithKeyboardEvent<D>(
}
};

const isRtl = document.documentElement.dir === "rtl";
DaemonCahill marked this conversation as resolved.
Show resolved Hide resolved

switch (direction) {
case "left":
return updateManualItemTransition(transition, "left");
return updateManualItemTransition(transition, !isRtl ? "left" : "right");
case "right":
return updateManualItemTransition(transition, "right");
return updateManualItemTransition(transition, !isRtl ? "right" : "left");
case "up":
return updateManualItemTransition(transition, "up");
case "down":
Expand All @@ -321,9 +324,10 @@ function acquireTransitionItem<D>(

const { columns } = transition.itemsLayout;

const layoutRect = layoutElement.getBoundingClientRect();
const layoutRect = getLogicalBoundingClientRect(layoutElement);
const itemRect = transition.draggableRect;
const offset = new Coordinates({ x: itemRect.left - layoutRect.x, y: itemRect.top - layoutRect.y });
const coordinatesX = itemRect.left - layoutRect.left;
const offset = new Coordinates({ x: coordinatesX, y: itemRect.top - layoutRect.top });
const insertionDirection = getInsertionDirection(offset);

// Update original insertion position if the item can't fit into the layout by width.
Expand Down
6 changes: 6 additions & 0 deletions src/internal/global-drag-state-styles/styles.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
@use "../shared.scss" as shared;

.show-grab-cursor * {
cursor: grabbing;
}

.show-resize-cursor * {
cursor: nwse-resize;

@include shared.with-direction('rtl') {
cursor: nesw-resize;
}
}

.disable-selection * {
Expand Down
6 changes: 5 additions & 1 deletion src/internal/grid/grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ export default function Grid({ layout, children: render, columns }: GridProps) {
return colspan * cellWidth + (colspan - 1) * gridGap;
};
const getHeight = (rowspan: number) => rowspan * rowspanHeight + (rowspan - 1) * gridGap;
const getColOffset = (x: number) => getWidth(x) + gridGap;
const getColOffset = (x: number) => {
const offset = getWidth(x) + gridGap;
const isRtl = document.documentElement.dir === "rtl";
DaemonCahill marked this conversation as resolved.
Show resolved Hide resolved
return !isRtl ? offset : -offset;
};
const getRowOffset = (y: number) => getHeight(y) + gridGap;

const gridContext = { getWidth, getHeight, getColOffset, getRowOffset };
Expand Down
3 changes: 2 additions & 1 deletion src/internal/handle/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
appearance: none;
background: transparent;
border: none;
padding: cs.$space-scaled-xxs;
padding-block: cs.$space-scaled-xxs;
padding-inline: cs.$space-scaled-xxs;

color: cs.$color-text-interactive-default;

Expand Down
23 changes: 15 additions & 8 deletions src/internal/item-container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from "../dnd-controller/controller";
import { BoardItemDefinitionBase, Direction, ItemId, Transform } from "../interfaces";
import { Coordinates } from "../utils/coordinates";
import { getNormalizedElementRect } from "../utils/screen";
import { getLogicalBoundingClientRect, getLogicalClientX, getNormalizedElementRect } from "../utils/screen";
import { throttle } from "../utils/throttle";
import { getCollisionRect } from "./get-collision-rect";
import { getNextDroppable } from "./get-next-droppable";
Expand Down Expand Up @@ -308,8 +308,10 @@ function ItemContainerComponent(

function onDragHandlePointerDown(event: ReactPointerEvent) {
// Calculate the offset between item's top-left corner and the pointer landing position.
const rect = itemRef.current!.getBoundingClientRect();
pointerOffsetRef.current = new Coordinates({ x: event.clientX - rect.left, y: event.clientY - rect.top });
const rect = getLogicalBoundingClientRect(itemRef.current!);
const clientX = getLogicalClientX(event);
const clientY = event.clientY;
pointerOffsetRef.current = new Coordinates({ x: clientX - rect.left, y: clientY - rect.top });
originalSizeRef.current = { width: rect.width, height: rect.height };
pointerBoundariesRef.current = null;

Expand All @@ -322,16 +324,18 @@ function ItemContainerComponent(

function onResizeHandlePointerDown(event: ReactPointerEvent) {
// Calculate the offset between item's bottom-right corner and the pointer landing position.
const rect = itemRef.current!.getBoundingClientRect();
pointerOffsetRef.current = new Coordinates({ x: event.clientX - rect.right, y: event.clientY - rect.bottom });
const rect = getLogicalBoundingClientRect(itemRef.current!);
const clientX = getLogicalClientX(event);
const clientY = event.clientY;
pointerOffsetRef.current = new Coordinates({ x: clientX - rect.right, y: clientY - rect.bottom });
originalSizeRef.current = { width: rect.width, height: rect.height };

// Calculate boundaries below which the cursor cannot move.
const minWidth = getItemSize(null).minWidth;
const minHeight = getItemSize(null).minHeight;
pointerBoundariesRef.current = new Coordinates({
x: event.clientX - rect.width + minWidth,
y: event.clientY - rect.height + minHeight,
x: clientX - rect.width + minWidth,
y: clientY - rect.height + minHeight,
});

draggableApi.start("resize", "pointer", Coordinates.fromEvent(event));
Expand All @@ -349,9 +353,12 @@ function ItemContainerComponent(
}

if (transition && transition.interactionType === "pointer") {
const isRtl = document.documentElement.dir === "rtl";
const property = !isRtl ? "left" : "right";
DaemonCahill marked this conversation as resolved.
Show resolved Hide resolved

// Adjust the dragged/resized item to the pointer's location.
itemTransitionClassNames.push(transition.operation === "resize" ? styles.resized : styles.dragged);
itemTransitionStyle.left = transition.positionTransform?.x;
itemTransitionStyle[property] = transition.positionTransform?.x;
itemTransitionStyle.top = transition.positionTransform?.y;
itemTransitionStyle.width = transition.sizeTransform?.width;
itemTransitionStyle.height = transition.sizeTransform?.height;
Expand Down
2 changes: 1 addition & 1 deletion src/internal/item-container/styles.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.root {
touch-action: none;
position: relative;
height: 100%;
block-size: 100%;
}

.inTransition {
Expand Down
4 changes: 4 additions & 0 deletions src/internal/resize-handle/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

.handle {
cursor: nwse-resize;

@include shared.with-direction('rtl') {
cursor: nesw-resize;
}
}

.handle:not(.active):focus-visible {
Expand Down
3 changes: 2 additions & 1 deletion src/internal/screenreader-grid-navigation/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
.screen-reader-navigation-visible {
position: fixed;
background: white;
padding: 8px;
padding-block: 8px;
padding-inline: 8px;
border: 1px solid black;
z-index: 10001;
}
4 changes: 2 additions & 2 deletions src/internal/screenreader-only/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@

.root {
position: absolute !important;
top: -9999px !important;
left: -9999px !important;
inset-block-start: -9999px !important;
inset-inline-start: -9999px !important;
}
14 changes: 10 additions & 4 deletions src/internal/shared.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@
display: block;
position: absolute;
box-sizing: border-box;
left: calc(-1 * #{$gutter});
top: calc(-1 * #{$gutter});
width: calc(100% + 2 * #{$gutter});
height: calc(100% + 2 * #{$gutter});
inset-inline-start: calc(-1 * #{$gutter});
inset-block-start: calc(-1 * #{$gutter});
inline-size: calc(100% + 2 * #{$gutter});
block-size: calc(100% + 2 * #{$gutter});
border-radius: $border-radius;
border: 2px solid cs.$color-border-item-focused;
}
}

@mixin with-direction($direction) {
&:dir(#{$direction}) {
@content;
}
}
5 changes: 4 additions & 1 deletion src/internal/utils/coordinates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { PointerEvent as ReactPointerEvent } from "react";
import { getLogicalClientX } from "./screen";

export class Coordinates {
readonly __type = "Coordinates";
Expand All @@ -11,7 +12,9 @@ export class Coordinates {
readonly scrollY = window.scrollY;

static fromEvent(event: PointerEvent | ReactPointerEvent<unknown>): Coordinates {
return new Coordinates({ x: event.clientX, y: event.clientY });
const clientX = getLogicalClientX(event);
const clientY = event.clientY;
return new Coordinates({ x: clientX, y: clientY });
}

static cursorOffset(current: Coordinates, start: Coordinates): Coordinates {
Expand Down
7 changes: 7 additions & 0 deletions src/internal/utils/rects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ export function getClosestNeighbor(target: Rect, sources: readonly Rect[], direc
const verticalDiff = (r1: Rect, r2: Rect) => Math.abs(r1.top - target.top) - Math.abs(r2.top - target.top);
const horizontalDiff = (r1: Rect, r2: Rect) => Math.abs(r1.left - target.left) - Math.abs(r2.left - target.left);

const isRtl = document.documentElement.dir === "rtl";
if (isRtl && direction === "left") {
direction = "right";
} else if (isRtl && direction === "right") {
direction = "left";
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better solution should be available here. Maybe it is easier to sort the "sources" differently depending on the page direction.


switch (direction) {
case "left":
return getFirst(
Expand Down
Loading
Loading