File size: 3,591 Bytes
8a37e0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import type { GetOffsetFn } from '@atlaskit/pragmatic-drag-and-drop/dist/types/public-utils/element/custom-native-drag-preview/types';
import type { Input } from '@atlaskit/pragmatic-drag-and-drop/types';
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { noop } from 'lodash-es';
import type { CSSProperties } from 'react';

/**
 * The size of the image drag preview in theme units.
 */
export const DND_IMAGE_DRAG_PREVIEW_SIZE = 32 satisfies SystemStyleObject['w'];

/**
 * A drag preview offset function that works like the provided `preserveOffsetOnSource`, except when either the X or Y
 * offset is outside the container, in which case it centers the preview in the container.
 */
export function preserveOffsetOnSourceFallbackCentered({
  element,
  input,
}: {
  element: HTMLElement;
  input: Input;
}): GetOffsetFn {
  return ({ container }) => {
    const sourceRect = element.getBoundingClientRect();
    const containerRect = container.getBoundingClientRect();

    let offsetX = input.clientX - sourceRect.x;
    let offsetY = input.clientY - sourceRect.y;

    if (offsetY > containerRect.height || offsetX > containerRect.width) {
      offsetX = containerRect.width / 2;
      offsetY = containerRect.height / 2;
    }

    return { x: offsetX, y: offsetY };
  };
}

// Based on https://github.com/atlassian/pragmatic-drag-and-drop/blob/main/packages/flourish/src/trigger-post-move-flash.tsx
// That package has a lot of extra deps so we just copied the function here
export function triggerPostMoveFlash(element: HTMLElement, backgroundColor: CSSProperties['backgroundColor']) {
  element.animate([{ backgroundColor }, {}], {
    duration: 700,
    easing: 'cubic-bezier(0.25, 0.1, 0.25, 1.0)',
    iterations: 1,
  });
}

/**
 * Firefox has a bug where input or textarea elements with draggable parents do not allow selection of their text.
 *
 * This helper function implements a workaround by setting the draggable attribute to false when the mouse is over a
 * input or textarea child of the draggable. It reverts the attribute on mouse out.
 *
 * The fix is only applied for Firefox, and should be used in every `pragmatic-drag-and-drop` `draggable`.
 *
 * See:
 * - https://github.com/atlassian/pragmatic-drag-and-drop/issues/111
 * - https://bugzilla.mozilla.org/show_bug.cgi?id=1853069
 *
 * @example
 * ```tsx
 * useEffect(() => {
 *   const element = ref.current;
 *   if (!element) {
 *     return;
 *   }
 *   return combine(
 *     firefoxDndFix(element),
 *     // The rest of the draggable setup is the same
 *     draggable({
 *       element,
 *       // ...
 *     }),
 *   );
 *```
 * @param element The draggable element
 * @returns A cleanup function that removes the event listeners
 */
export const firefoxDndFix = (element: HTMLElement): (() => void) => {
  if (!navigator.userAgent.includes('Firefox')) {
    return noop;
  }

  const abortController = new AbortController();

  element.addEventListener(
    'mouseover',
    (event) => {
      if (event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLInputElement) {
        element.setAttribute('draggable', 'false');
      }
    },
    { signal: abortController.signal }
  );

  element.addEventListener(
    'mouseout',
    (event) => {
      if (event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLInputElement) {
        element.setAttribute('draggable', 'true');
      }
    },
    { signal: abortController.signal }
  );

  return () => {
    element.setAttribute('draggable', 'true');
    abortController.abort();
  };
};