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

preserveOffsetOnSource always returns { x: 0, y: 0 } #177

Open
richburdon opened this issue Jan 9, 2025 · 1 comment
Open

preserveOffsetOnSource always returns { x: 0, y: 0 } #177

richburdon opened this issue Jan 9, 2025 · 1 comment

Comments

@richburdon
Copy link

1). Using the pattern below getOffset is called before the preview portal is created, so the container is empty. I've worked around this by passing the element itself as the container.

2). Even so, on drop, the current position is randomly 1 pixel off the native rendered position, so the object seems to jump if it is absolutely positioned. (This doesn't happen if I disable the native drag preview via disableNativeDragPreview).

      onGenerateDragPreview: ({ nativeSetDragImage, location, source }) => {
        setCustomNativeDragPreview({
          render: ({ container }) => {
            setPreview(container);
            return () => setPreview(undefined);
          },
          getOffset: ({ container }) => {
            // container is empty, so pass in source element.
            const f = preserveOffsetOnSource({ element: source.element, input: location.current.input });
            offset.current = f({ container: source.element });
            return offset.current;
          },
          nativeSetDragImage,
        });

  ...

  return (
    <>
      {preview ? createPortal(<DragElement />, preview) : null}
      <DragElement ref={draggingRef} classNames={[dragging && 'opacity-50']} />
    </>
  );
@nmhungdev
Copy link

Hi @richburdon!
My code has no issues like this.

export default function CardNative({ item }: { item: TItem }) {
  const ref = useRef<HTMLDivElement | null>(null);
  const [state, setState] = useState<CardState>(idle);

  useEffect(() => {
    const element = ref.current;
    invariant(element);

    return combine(
      draggable({
        element,
        getInitialData: () => item,
        canDrag: () => item.pinned === false,
        onGenerateDragPreview: ({ source, nativeSetDragImage, location }) => {
          // Using a custom native drag preview
          // so that we get a nicer border radius on
          // the preview 👩‍🍳🤌
          setCustomNativeDragPreview({
            nativeSetDragImage,
            getOffset: preserveOffsetOnSource({
              element: source.element,
              input: location.initial.input,
            }),
            render({ container }) {
              setState({
                type: "preview",
                container,
                rect: element.getBoundingClientRect(),
              });
              return () => setState(isDragging);
            },
          });
        },
        onDragStart: () => setState(isDragging),
        onDrop: () => setState(idle),
      }),
      dropTargetForElements({
        element,
        getData: () => item,
        canDrop: () => item.pinned === false,
        onDragStart: () => setState(isOver),
        onDragEnter: () => setState(isOver),
        onDragLeave: () => setState(idle),
        onDrop: () => setState(idle),
      })
    );
  }, [item]);

  const pinnedStyles = xcss({
    backgroundColor: "color.background.accent.gray.subtlest.pressed",
    borderColor: "color.border.accent.gray",
    color: "color.text.accent.gray",
    cursor: "not-allowed",
  });

  return (
    <Fragment>
      <Box
        ref={ref}
        xcss={[
          cardStyles,
          cardStateStyles[state.type],
          item.pinned && pinnedStyles,
        ]}
        testId={item.id}
      >
        {item.title}
      </Box>
      {state.type === "preview"
        ? createPortal(
            <CardPreview rect={state.rect} item={item} />,
            state.container
          )
        : null}
    </Fragment>
  );
}

function CardPreview({ rect, item }: { rect: DOMRect; item: TItem }) {
  return (
    <Box xcss={cardStyles} style={{ width: rect.width, height: rect.height }}>
      {item.title}
    </Box>
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants