react-beautiful-dnd#SensorAPI TypeScript Examples
The following examples show how to use
react-beautiful-dnd#SensorAPI.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: dndUtil.ts From clearflask with Apache License 2.0 | 5 votes |
dndDrag = async (api: SensorAPI, draggableId: string, droppableId: string): Promise<boolean> => {
if (windowIso.isSsr) return false;
var preDrag: PreDragActions | null | undefined;
var drag: FluidDragActions | undefined;
try {
preDrag = api.tryGetLock(draggableId);
if (!preDrag) return false;
const draggableEl = dndFindElement('draggable', draggableId);
const droppableEl = dndFindElement('droppable', droppableId);
if (!draggableEl || !droppableEl) {
preDrag.abort();
return false;
}
const draggablePosition = draggableEl.getBoundingClientRect();
const droppablePosition = droppableEl.getBoundingClientRect();
const from = {
x: draggablePosition.x + draggablePosition.width / 2,
y: draggablePosition.y + draggablePosition.height / 2,
}
const to = {
x: droppablePosition.x + droppablePosition.width / 2,
y: droppablePosition.y + droppablePosition.height / 2,
}
drag = preDrag.fluidLift(from);
var step = 0.0;
while (step <= 1) {
step += 0.05;
await new Promise(resolve => setTimeout(resolve, 5));
if (!drag.isActive()) {
drag.cancel();
return false;
}
drag.move({
x: from.x + (to.x - from.x) * step,
y: from.y + (to.y - from.y) * step,
});
}
drag.move({
x: droppablePosition.x + droppablePosition.width / 2 - (draggablePosition.x + draggablePosition.width / 2),
y: droppablePosition.y + droppablePosition.height / 2 - (draggablePosition.y + draggablePosition.height / 2),
});
drag.drop();
return true;
} catch (e) {
if (drag?.isActive()) drag.cancel();
if (preDrag?.isActive()) preDrag.abort();
return false;
}
}
Example #2
Source File: DashboardQuickActions.tsx From clearflask with Apache License 2.0 | 4 votes |
DashboardQuickActions = (props: {
activeProject: Project;
onClickPost: (postId: string) => void;
onUserClick: (userId: string) => void;
searchKey?: string; // When search changes, update to check whether selectedPostId is still draggable
selectedPostId?: string;
feedback?: FeedbackInstance | null;
roadmap?: RoadmapInstance | null;
dragDropSensorApi?: SensorAPI;
draggingPostIdSubscription: Subscription<string | undefined>;
fallbackClickHandler: FallbackClickHandler;
}) => {
const [draggingPostId, setDraggingPostId] = useState(props.draggingPostIdSubscription.getValue());
useEffect(() => props.draggingPostIdSubscription.subscribe(setDraggingPostId), []); // eslint-disable-line react-hooks/exhaustive-deps
const { t } = useTranslation('app');
const classes = useStyles();
const theme = useTheme();
const { enqueueSnackbar } = useSnackbar();
const noticeChangeStatusRef = useRef<FirstTimeNoticeHandle>(null);
const noticeConvertToTaskRef = useRef<FirstTimeNoticeHandle>(null);
const noticeDeleteRef = useRef<FirstTimeNoticeHandle>(null);
const quickActionsPostId = draggingPostId || props.selectedPostId;
const quickActionsPost = useSelector<ReduxState, Admin.Idea | undefined>(state => !quickActionsPostId ? undefined : state.ideas.byId[quickActionsPostId]?.idea, shallowEqual);
// TODO disable in cases if its already merged or linked or something
const enabled = !!quickActionsPostId;
const onClick = (!enabled || !!draggingPostId) ? undefined
: onClickAction(props.dragDropSensorApi, props.fallbackClickHandler, quickActionsPostId);
const statusAccepted = !props.feedback?.statusIdAccepted ? undefined : props.feedback.categoryAndIndex.category.workflow.statuses.find(s => s.statusId === props.feedback?.statusIdAccepted);
const nextStatusIds = new Set<string>(quickActionsPost?.statusId
&& props.feedback?.categoryAndIndex.category.workflow.statuses.find(s => s.statusId === quickActionsPost?.statusId)?.nextStatusIds
|| []);
const feedbackNextStatusActions = props.feedback?.categoryAndIndex.category.workflow.statuses
.filter(status => status.statusId !== props.feedback?.categoryAndIndex.category.workflow.entryStatus
&& status.statusId !== props.feedback?.statusIdAccepted);
const roadmapNextStatusActions = props.roadmap?.categoryAndIndex.category.workflow.statuses
.filter(status => status.statusId !== props.roadmap?.statusIdClosed
&& status.statusId !== props.roadmap?.statusIdCompleted);
return (
<div className={classes.postActionsContainer}>
{feedbackNextStatusActions?.length && (
<>
<FilterControlTitle name='Change status' className={classes.feedbackTitle} help={{
description: helperChangeStatus,
}} />
<div className={classes.postActionGroup}>
{feedbackNextStatusActions.map(status => {
const droppableId = droppableDataSerialize({
type: 'quick-action-feedback-change-status',
dropbox: true,
statusId: status.statusId,
});
return (
<QuickActionArea
key={status.statusId}
isDragging={!!draggingPostId}
droppableId={droppableId}
color={status.color}
enabled={enabled && nextStatusIds.has(status.statusId)}
onClick={async droppableId => {
if (! await noticeChangeStatusRef.current?.invoke()) return;
return await onClick?.(droppableId);
}}
title={status.name}
/>
);
})}
</div>
</>
)}
{roadmapNextStatusActions?.length && (
<Provider store={ServerAdmin.get().getStore()}>
<TourAnchor anchorId='feedback-page-convert-to-task' placement='left'>
{(next, isActive, anchorRef) => (
<>
<FilterControlTitle name='Convert to task' className={classes.feedbackTitle} help={{
description: helperConvertToTask,
}} />
<div className={classes.postActionGroup} ref={anchorRef}>
{roadmapNextStatusActions.map(status => (
<QuickActionArea
key={status.statusId}
isDragging={!!draggingPostId}
droppableId={droppableDataSerialize({
type: 'quick-action-create-task-from-feedback-with-status',
dropbox: true,
statusId: status.statusId,
})}
color={status.color}
enabled={enabled && (!statusAccepted || nextStatusIds.has(statusAccepted.statusId))}
onClick={async droppableId => {
if (isActive) {
enqueueSnackbar('Disabled during tutorial', { variant: 'warning', preventDuplicate: true });
return;
}
if (! await noticeConvertToTaskRef.current?.invoke()) return;
return await onClick?.(droppableId);
}}
title={status.name}
/>
))}
</div>
</>
)}
</TourAnchor>
</Provider>
)}
<FilterControlTitle name={t('delete')} className={classes.feedbackTitle} help={{
description: helperDelete,
}} />
<QuickActionArea
key='delete'
isDragging={!!draggingPostId}
droppableId={droppableDataSerialize({
type: 'quick-action-delete',
dropbox: true,
})}
color={theme.palette.error.dark}
enabled={enabled}
onClick={async droppableId => {
if (! await noticeDeleteRef.current?.invoke()) return;
return await onClick?.(droppableId);
}}
title={t('delete')}
/>
<Provider store={ServerAdmin.get().getStore()}>
<FirstTimeNotice
ref={noticeChangeStatusRef}
id='feedback-change-status'
title='Change status'
description={helperChangeStatus}
confirmButtonTitle='Change'
/>
<FirstTimeNotice
ref={noticeConvertToTaskRef}
id='feedback-convert-to-task'
title='Create task'
description={helperConvertToTask}
confirmButtonTitle='Convert'
/>
<FirstTimeNotice
ref={noticeDeleteRef}
id='feedback-delete'
title='Permanently delete'
description={helperDelete}
confirmButtonTitle='Delete'
confirmButtonRed
/>
</Provider>
</div>
);
}
Example #3
Source File: DashboardQuickActions.tsx From clearflask with Apache License 2.0 | 4 votes |
DroppableQuickActionPostList = React.memo((props: {
server: Server;
title?: {
name: string;
helpDescription?: string;
};
getDroppableId: (post: Admin.Idea) => string | undefined;
selectedPostId?: string;
draggingPostIdSubscription: Subscription<string | undefined>;
dragDropSensorApi?: SensorAPI;
statusColorGivenCategories?: string[];
fallbackClickHandler: (draggableId: string, dstDroppableId: string) => Promise<boolean>;
PostListProps?: Partial<React.ComponentProps<typeof PostList>>;
showSampleItem?: string;
disabledDuringTour?: boolean;
FirstTimeNoticeProps?: React.ComponentPropsWithoutRef<typeof FirstTimeNotice>;
}) => {
const classes = useStyles();
const { enqueueSnackbar } = useSnackbar();
const noticeRef = useRef<FirstTimeNoticeHandle>(null);
const [draggingPostId, setDraggingPostId] = useState(props.draggingPostIdSubscription.getValue());
useEffect(() => props.draggingPostIdSubscription.subscribe(setDraggingPostId), []); // eslint-disable-line react-hooks/exhaustive-deps
const statusIdToColor = useSelector<ReduxState, { [statusId: string]: string } | undefined>(state => {
if (!props.statusColorGivenCategories?.length) return;
const statusIdToColor = {};
state.conf.conf?.content.categories
.forEach(category => {
if (!props.statusColorGivenCategories?.includes(category.categoryId)) return;
category.workflow.statuses.forEach(status => {
if (status.color === undefined) return;
statusIdToColor[status.statusId] = status.color;
});
});
return statusIdToColor;
}, shallowEqual);
const quickActionsPostId = draggingPostId || props.selectedPostId;
const quickActionsPost = useSelector<ReduxState, Admin.Idea | undefined>(state => !quickActionsPostId ? undefined : state.ideas.byId[quickActionsPostId]?.idea, shallowEqual);
const enabled = (!!quickActionsPostId && !quickActionsPost?.mergedToPostId)
const onClick = (!enabled || !!draggingPostId) ? undefined
: onClickAction(props.dragDropSensorApi, props.fallbackClickHandler, quickActionsPostId);
var content = (
<PostList
key={props.server.getProjectId()}
server={props.server}
layout='similar-merge-action'
PanelPostProps={{
overrideTitle: !props.title ? undefined : (
<FilterControlTitle name={props.title.name} className={classes.feedbackTitle} help={{
description: props.title.helpDescription,
}} />
),
renderPost: (idea, ideaIndex) => {
const droppableId = props.getDroppableId(idea);
if (!droppableId) return null;
return (
<QuickActionArea
key={idea.ideaId}
isDragging={!!draggingPostId}
droppableId={droppableId}
enabled={enabled}
onClick={async droppableId => {
if (props.disabledDuringTour) {
enqueueSnackbar('Disabled during tutorial', { variant: 'warning', preventDuplicate: true });
return;
}
if (!!noticeRef.current && ! await noticeRef.current.invoke()) return;
return await onClick?.(droppableId);
}}
color={(!statusIdToColor || !idea.statusId) ? undefined : statusIdToColor[idea.statusId]}
title={truncateWithElipsis(30, idea.title)}
/>
);
},
renderEmpty: !props.showSampleItem ? undefined : () => (
<QuickActionArea
key={`sample-item-${props.showSampleItem}`}
isDragging={false}
droppableId='disabled-for-dropping'
enabled={false}
title={props.showSampleItem}
/>
),
}}
hideIfEmpty={!props.showSampleItem}
{...props.PostListProps}
/>
);
if (props.FirstTimeNoticeProps) {
content = (
<>
{content}
<Provider store={ServerAdmin.get().getStore()}>
<FirstTimeNotice
{...props.FirstTimeNoticeProps}
ref={noticeRef}
/>
</Provider>
</>
);
}
return content;
}, customReactMemoEquals({
nested: new Set(['PostListProps', 'FirstTimeNoticeProps', 'DroppableProvidedProps', 'statusColorGivenCategories', 'title']),
presence: new Set(['fallbackClickHandler', 'getDroppableId']),
}))
Example #4
Source File: useAutoMoveSensor.ts From wikitrivia with MIT License | 4 votes |
export default async function useAutoMoveSensor(
state: GameState,
api: SensorAPI
) {
if (
state.badlyPlaced === null ||
state.badlyPlaced.index === null ||
state.badlyPlaced.rendered === false
) {
return;
}
const preDrag = api.tryGetLock?.(state.played[state.badlyPlaced.index].id);
if (!preDrag) {
return;
}
const itemEl: HTMLElement | null = document.querySelector(
`[data-rbd-draggable-id='${state.played[state.badlyPlaced.index].id}']`
);
const destEl: HTMLElement | null = document.querySelector(
`[data-rbd-draggable-id='${
state.played[state.badlyPlaced.index + state.badlyPlaced.delta].id
}']`
);
const bottomEl: HTMLElement | null = document.getElementById("bottom");
if (itemEl === null || destEl === null || bottomEl === null) {
throw new Error("Can't find element");
}
const bottomElCentreLeft = bottomEl.scrollLeft + bottomEl.clientWidth / 4;
const bottomElCentreRight =
bottomEl.scrollLeft + (bottomEl.clientWidth / 4) * 3 - itemEl.clientWidth;
let scrollDistance = 0;
if (
destEl.offsetLeft < bottomElCentreLeft ||
destEl.offsetLeft > bottomElCentreRight
) {
// Destination is not in middle two quarters of the screen. Calculate
// distance we therefore need to scroll.
scrollDistance =
destEl.offsetLeft < bottomElCentreLeft
? destEl.offsetLeft - bottomElCentreLeft
: destEl.offsetLeft - bottomElCentreRight;
if (bottomEl.scrollLeft + scrollDistance < 0) {
scrollDistance = -bottomEl.scrollLeft;
} else if (
bottomEl.scrollLeft + scrollDistance >
bottomEl.scrollWidth - bottomEl.clientWidth
) {
scrollDistance =
bottomEl.scrollWidth - bottomEl.clientWidth - bottomEl.scrollLeft;
}
}
// Calculate the distance we need to move the item after taking into account
// how far we are scrolling.
const transformDistance =
destEl.offsetLeft - itemEl.offsetLeft - scrollDistance;
const drag = preDrag.fluidLift({ x: 0, y: 0 });
// Create a series of eased transformations and scrolls to animate from the
// current state to the desired state.
const transformPoints = [];
const scrollPoints = [];
const numberOfPoints = 30 + 5 * Math.abs(state.badlyPlaced.delta);
for (let i = 0; i < numberOfPoints; i++) {
transformPoints.push(
tweenFunctions.easeOutCirc(i, 0, transformDistance, numberOfPoints)
);
scrollPoints.push(
tweenFunctions.easeOutCirc(
i,
bottomEl.scrollLeft,
bottomEl.scrollLeft + scrollDistance,
numberOfPoints
)
);
}
moveStepByStep(drag, transformPoints, scrollPoints);
}