@react-aria/interactions 3.5.1 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { HTMLAttributes, RefObject, ReactElement, ReactNode, FocusEvent, SyntheticEvent } from "react";
2
- import { PressEvents, FocusEvents, HoverEvents, KeyboardEvents, MoveEvents, ScrollEvents } from "@react-types/shared";
2
+ import { PressEvents, FocusEvents, HoverEvents, KeyboardEvents, MoveEvents, ScrollEvents, LongPressEvent } from "@react-types/shared";
3
3
  export interface PressProps extends PressEvents {
4
4
  /** Whether the target is in a controlled press state (e.g. an overlay it triggers is open). */
5
5
  isPressed?: boolean;
@@ -7,6 +7,15 @@ export interface PressProps extends PressEvents {
7
7
  isDisabled?: boolean;
8
8
  /** Whether the target should not receive focus on press. */
9
9
  preventFocusOnPress?: boolean;
10
+ /**
11
+ * Whether press events should be canceled when the pointer leaves the target while pressed.
12
+ * By default, this is `false`, which means if the pointer returns back over the target while
13
+ * still pressed, onPressStart will be fired again. If set to `true`, the press is canceled
14
+ * when the pointer leaves the target and onPressStart will not be fired if the pointer returns.
15
+ */
16
+ shouldCancelOnPointerExit?: boolean;
17
+ /** Whether text selection should be enabled on the pressable element. */
18
+ allowTextSelectionOnPress?: boolean;
10
19
  }
11
20
  export interface PressHookProps extends PressProps {
12
21
  /** A ref to the target element. */
@@ -148,5 +157,40 @@ export interface ScrollWheelProps extends ScrollEvents {
148
157
  isDisabled?: boolean;
149
158
  }
150
159
  export function useScrollWheel(props: ScrollWheelProps, ref: RefObject<HTMLElement>): void;
160
+ interface LongPressProps {
161
+ /** Whether long press events should be disabled. */
162
+ isDisabled?: boolean;
163
+ /** Handler that is called when a long press interaction starts. */
164
+ onLongPressStart?: (e: LongPressEvent) => void;
165
+ /**
166
+ * Handler that is called when a long press interaction ends, either
167
+ * over the target or when the pointer leaves the target.
168
+ */
169
+ onLongPressEnd?: (e: LongPressEvent) => void;
170
+ /**
171
+ * Handler that is called when the threshold time is met while
172
+ * the press is over the target.
173
+ */
174
+ onLongPress?: (e: LongPressEvent) => void;
175
+ /**
176
+ * The amount of time in milliseconds to wait before triggering a long press.
177
+ * @default 500ms
178
+ */
179
+ threshold?: number;
180
+ /**
181
+ * A description for assistive techology users indicating that a long press
182
+ * action is available, e.g. "Long press to open menu".
183
+ */
184
+ accessibilityDescription?: string;
185
+ }
186
+ interface LongPressResult {
187
+ /** Props to spread on the target element. */
188
+ longPressProps: HTMLAttributes<HTMLElement>;
189
+ }
190
+ /**
191
+ * Handles long press interactions across mouse and touch devices. Supports a customizable time threshold,
192
+ * accessibility description, and normalizes behavior across browsers and devices.
193
+ */
194
+ export function useLongPress(props: LongPressProps): LongPressResult;
151
195
 
152
196
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"mappings":"A;A;AGwBA,2BAA4B,SAAQ,WAAW;IAC7C,+FAA+F;IAC/F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED,+BAAgC,SAAQ,UAAU;IAChD,mCAAmC;IACnC,GAAG,CAAC,EAAE,UAAU,WAAW,CAAC,CAAA;CAC7B;AAqBD;IACE,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,UAAU,EAAE,eAAe,WAAW,CAAC,CAAA;CACxC;AAeD;A;A;A;GAIG;AACH,yBAAyB,KAAK,EAAE,cAAc,GAAG,WAAW,CA6gB3D;AC/kBD,wBAAyB,SAAQ,UAAU;IACzC,QAAQ,EAAE,aAAa,eAAe,WAAW,CAAC,EAAE,MAAM,CAAC,CAAA;CAC5D;AAED,OAAO,MAAM,6FAUX,CAAC;ACbH,6BAA8B,SAAQ,UAAU;IAC9C,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,OAAO,MAAM,uGA8BX,CAAC;AC/BH,oBAAqB,SAAQ,WAAW;IACtC,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;IACE,+CAA+C;IAC/C,UAAU,EAAE,eAAe,WAAW,CAAC,CAAA;CACxC;AAED;A;A;GAGG;AACH,yBAAyB,KAAK,EAAE,UAAU,GAAG,WAAW,CAwCvD;ACrDD,gBAAgB,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAGnD,2BAA2B,CAAC,gBAAgB,OAAO,KAAK,IAAI,CAAC;AAC7D;IACE,2CAA2C;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gDAAgD;IAChD,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;IACE,kDAAkD;IAClD,gBAAgB,OAAO,CAAA;CACxB;AA8HD;A;GAEG;AACH,kCAAkC,OAAO,CAExC;AAED,0CAA0C,QAAQ,CAEjD;AAED,uCAAuC,QAAQ,EAAE,QAAQ,QAGxD;AAED;A;GAEG;AACH,0CAA0C,QAAQ,CAgBjD;AAUD;A;GAEG;AACH,gCAAgC,KAAK,GAAE,iBAAsB,GAAG,kBAAkB,CAQjF;AAED;A;GAEG;AACH,wCAAwC,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAC,GAAG,IAAI,CAa/H;ACxND;IACE,0DAA0D;IAC1D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qFAAqF;IACrF,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACxC,qFAAqF;IACrF,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,KAAK,IAAI,CAAA;CACvD;AAED;IACE,+CAA+C;IAC/C,gBAAgB,EAAE,eAAe,WAAW,CAAC,CAAA;CAC9C;AAED;A;GAEG;AACH,+BAA+B,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CA8CzE;AChED,2BAA4B,SAAQ,WAAW;IAC7C,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;IACE,6CAA6C;IAC7C,UAAU,EAAE,eAAe,WAAW,CAAC,CAAC;IACxC,SAAS,EAAE,OAAO,CAAA;CACnB;AAoDD;A;A;GAGG;AACH,yBAAyB,KAAK,EAAE,UAAU,GAAG,WAAW,CAuHvD;ACzLD;IACE,GAAG,EAAE,UAAU,OAAO,CAAC,CAAC;IACxB,iBAAiB,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD,sBAAsB,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,8DAA8D;IAC9D,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;A;A;GAGG;AACH,mCAAmC,KAAK,EAAE,oBAAoB,QA0E7D;AEzFD,8BAA+B,SAAQ,cAAc;IACnD,sDAAsD;IACtD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;IACE,+CAA+C;IAC/C,aAAa,EAAE,eAAe,WAAW,CAAC,CAAA;CAC3C;AAED;A;GAEG;AACH,4BAA4B,KAAK,EAAE,aAAa,GAAG,cAAc,CAOhE;ACnBD;IACE,6CAA6C;IAC7C,SAAS,EAAE,eAAe,WAAW,CAAC,CAAA;CACvC;AAED;A;A;A;GAIG;AACH,wBAAwB,KAAK,EAAE,UAAU,GAAG,UAAU,CA0LrD;ACtMD,iCAAkC,SAAQ,YAAY;IACpD,sDAAsD;IACtD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAGD,+BAA+B,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,WAAW,CAAC,GAAG,IAAI,CA4BzF","sources":["./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/textSelection.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/utils.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/context.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/usePress.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/Pressable.tsx","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/PressResponder.tsx","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useFocus.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useFocusVisible.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useFocusWithin.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useHover.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useInteractOutside.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/createEventHandler.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useKeyboard.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useMove.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useScrollWheel.ts","./packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"names":[],"version":3,"file":"types.d.ts.map"}
1
+ {"mappings":";;AGwBA,2BAA4B,SAAQ,WAAW;IAC7C,+FAA+F;IAC/F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,yEAAyE;IACzE,yBAAyB,CAAC,EAAE,OAAO,CAAA;CACpC;AAED,+BAAgC,SAAQ,UAAU;IAChD,mCAAmC;IACnC,GAAG,CAAC,EAAE,UAAU,WAAW,CAAC,CAAA;CAC7B;AAsBD;IACE,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,UAAU,EAAE,eAAe,WAAW,CAAC,CAAA;CACxC;AAeD;;;;GAIG;AACH,yBAAyB,KAAK,EAAE,cAAc,GAAG,WAAW,CA0jB3D;ACtoBD,wBAAyB,SAAQ,UAAU;IACzC,QAAQ,EAAE,aAAa,eAAe,WAAW,CAAC,EAAE,MAAM,CAAC,CAAA;CAC5D;AAED,OAAO,MAAM,6FAUX,CAAC;ACbH,6BAA8B,SAAQ,UAAU;IAC9C,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,OAAO,MAAM,uGA8BX,CAAC;AC/BH,oBAAqB,SAAQ,WAAW;IACtC,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;IACE,+CAA+C;IAC/C,UAAU,EAAE,eAAe,WAAW,CAAC,CAAA;CACxC;AAED;;;GAGG;AACH,yBAAyB,KAAK,EAAE,UAAU,GAAG,WAAW,CAwCvD;ACrDD,gBAAgB,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAGnD,2BAA2B,CAAC,gBAAgB,OAAO,KAAK,IAAI,CAAC;AAC7D;IACE,2CAA2C;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gDAAgD;IAChD,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;IACE,kDAAkD;IAClD,gBAAgB,OAAO,CAAA;CACxB;AA8HD;;GAEG;AACH,kCAAkC,OAAO,CAExC;AAED,0CAA0C,QAAQ,CAEjD;AAED,uCAAuC,QAAQ,EAAE,QAAQ,QAGxD;AAED;;GAEG;AACH,0CAA0C,QAAQ,CAgBjD;AAUD;;GAEG;AACH,gCAAgC,KAAK,GAAE,iBAAsB,GAAG,kBAAkB,CAQjF;AAED;;GAEG;AACH,wCAAwC,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAC,GAAG,IAAI,CAe/H;AC1ND;IACE,0DAA0D;IAC1D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qFAAqF;IACrF,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACxC,qFAAqF;IACrF,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,KAAK,IAAI,CAAA;CACvD;AAED;IACE,+CAA+C;IAC/C,gBAAgB,EAAE,eAAe,WAAW,CAAC,CAAA;CAC9C;AAED;;GAEG;AACH,+BAA+B,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CA8CzE;AChED,2BAA4B,SAAQ,WAAW;IAC7C,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;IACE,6CAA6C;IAC7C,UAAU,EAAE,eAAe,WAAW,CAAC,CAAC;IACxC,SAAS,EAAE,OAAO,CAAA;CACnB;AAoDD;;;GAGG;AACH,yBAAyB,KAAK,EAAE,UAAU,GAAG,WAAW,CAuHvD;ACzLD;IACE,GAAG,EAAE,UAAU,OAAO,CAAC,CAAC;IACxB,iBAAiB,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD,sBAAsB,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,8DAA8D;IAC9D,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;;;GAGG;AACH,mCAAmC,KAAK,EAAE,oBAAoB,QA0E7D;AEzFD,8BAA+B,SAAQ,cAAc;IACnD,sDAAsD;IACtD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;IACE,+CAA+C;IAC/C,aAAa,EAAE,eAAe,WAAW,CAAC,CAAA;CAC3C;AAED;;GAEG;AACH,4BAA4B,KAAK,EAAE,aAAa,GAAG,cAAc,CAOhE;ACnBD;IACE,6CAA6C;IAC7C,SAAS,EAAE,eAAe,WAAW,CAAC,CAAA;CACvC;AASD;;;;GAIG;AACH,wBAAwB,KAAK,EAAE,UAAU,GAAG,UAAU,CAkMrD;ACpND,iCAAkC,SAAQ,YAAY;IACpD,sDAAsD;IACtD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAGD,+BAA+B,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,WAAW,CAAC,GAAG,IAAI,CAkBzF;ACvBD;IACE,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAC/C;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAC1C;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;CAClC;AAED;IACE,6CAA6C;IAC7C,cAAc,EAAE,eAAe,WAAW,CAAC,CAAA;CAC5C;AAID;;;GAGG;AACH,6BAA6B,KAAK,EAAE,cAAc,GAAG,eAAe,CAwEnE","sources":["packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/textSelection.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/utils.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/context.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/usePress.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/Pressable.tsx","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/PressResponder.tsx","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useFocus.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useFocusVisible.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useFocusWithin.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useHover.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useInteractOutside.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/createEventHandler.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useKeyboard.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useMove.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useScrollWheel.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/useLongPress.ts","packages/@react-aria/interactions/src/packages/@react-aria/interactions/src/index.ts","packages/@react-aria/interactions/src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,"/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nexport * from './Pressable';\nexport * from './PressResponder';\nexport * from './useFocus';\nexport * from './useFocusVisible';\nexport * from './useFocusWithin';\nexport * from './useHover';\nexport * from './useInteractOutside';\nexport * from './useKeyboard';\nexport * from './useMove';\nexport * from './usePress';\nexport * from './useScrollWheel';\nexport * from './useLongPress';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-aria/interactions",
3
- "version": "3.5.1",
3
+ "version": "3.8.1",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -18,8 +18,8 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@babel/runtime": "^7.6.2",
21
- "@react-aria/utils": "^3.8.2",
22
- "@react-types/shared": "^3.8.0"
21
+ "@react-aria/utils": "^3.11.2",
22
+ "@react-types/shared": "^3.11.1"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "react": "^16.8.0 || ^17.0.0-rc.1"
@@ -27,5 +27,5 @@
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "gitHead": "9204e1b8cb61dac767e91087fb16203611dc67c5"
30
+ "gitHead": "404d41859b7d6f56201d7fc01bd9f22ae3512937"
31
31
  }
package/src/index.ts CHANGED
@@ -21,3 +21,4 @@ export * from './useKeyboard';
21
21
  export * from './useMove';
22
22
  export * from './usePress';
23
23
  export * from './useScrollWheel';
24
+ export * from './useLongPress';
@@ -10,7 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {runAfterTransition} from '@react-aria/utils';
13
+ import {isIOS, runAfterTransition} from '@react-aria/utils';
14
14
 
15
15
  // Safari on iOS starts selecting text on long press. The only way to avoid this, it seems,
16
16
  // is to add user-select: none to the entire page. Adding it to the pressable element prevents
@@ -21,44 +21,75 @@ import {runAfterTransition} from '@react-aria/utils';
21
21
  // There are three possible states due to the delay before removing user-select: none after
22
22
  // pointer up. The 'default' state always transitions to the 'disabled' state, which transitions
23
23
  // to 'restoring'. The 'restoring' state can either transition back to 'disabled' or 'default'.
24
+
25
+ // For non-iOS devices, we apply user-select: none to the pressed element instead to avoid possible
26
+ // performance issues that arise from applying and removing user-select: none to the entire page
27
+ // (see https://github.com/adobe/react-spectrum/issues/1609).
24
28
  type State = 'default' | 'disabled' | 'restoring';
25
29
 
30
+ // Note that state only matters here for iOS. Non-iOS gets user-select: none applied to the target element
31
+ // rather than at the document level so we just need to apply/remove user-select: none for each pressed element individually
26
32
  let state: State = 'default';
27
33
  let savedUserSelect = '';
34
+ let modifiedElementMap = new WeakMap<HTMLElement, string>();
28
35
 
29
- export function disableTextSelection() {
30
- if (state === 'default') {
31
- savedUserSelect = document.documentElement.style.webkitUserSelect;
32
- document.documentElement.style.webkitUserSelect = 'none';
33
- }
36
+ export function disableTextSelection(target?: HTMLElement) {
37
+ if (isIOS()) {
38
+ if (state === 'default') {
39
+ savedUserSelect = document.documentElement.style.webkitUserSelect;
40
+ document.documentElement.style.webkitUserSelect = 'none';
41
+ }
34
42
 
35
- state = 'disabled';
43
+ state = 'disabled';
44
+ } else if (target) {
45
+ // If not iOS, store the target's original user-select and change to user-select: none
46
+ // Ignore state since it doesn't apply for non iOS
47
+ modifiedElementMap.set(target, target.style.userSelect);
48
+ target.style.userSelect = 'none';
49
+ }
36
50
  }
37
51
 
38
- export function restoreTextSelection() {
39
- // If the state is already default, there's nothing to do.
40
- // If it is restoring, then there's no need to queue a second restore.
41
- if (state !== 'disabled') {
42
- return;
43
- }
52
+ export function restoreTextSelection(target?: HTMLElement) {
53
+ if (isIOS()) {
54
+ // If the state is already default, there's nothing to do.
55
+ // If it is restoring, then there's no need to queue a second restore.
56
+ if (state !== 'disabled') {
57
+ return;
58
+ }
44
59
 
45
- state = 'restoring';
60
+ state = 'restoring';
46
61
 
47
- // There appears to be a delay on iOS where selection still might occur
48
- // after pointer up, so wait a bit before removing user-select.
49
- setTimeout(() => {
50
- // Wait for any CSS transitions to complete so we don't recompute style
51
- // for the whole page in the middle of the animation and cause jank.
52
- runAfterTransition(() => {
53
- // Avoid race conditions
54
- if (state === 'restoring') {
55
- if (document.documentElement.style.webkitUserSelect === 'none') {
56
- document.documentElement.style.webkitUserSelect = savedUserSelect || '';
62
+ // There appears to be a delay on iOS where selection still might occur
63
+ // after pointer up, so wait a bit before removing user-select.
64
+ setTimeout(() => {
65
+ // Wait for any CSS transitions to complete so we don't recompute style
66
+ // for the whole page in the middle of the animation and cause jank.
67
+ runAfterTransition(() => {
68
+ // Avoid race conditions
69
+ if (state === 'restoring') {
70
+ if (document.documentElement.style.webkitUserSelect === 'none') {
71
+ document.documentElement.style.webkitUserSelect = savedUserSelect || '';
72
+ }
73
+
74
+ savedUserSelect = '';
75
+ state = 'default';
57
76
  }
77
+ });
78
+ }, 300);
79
+ } else {
80
+ // If not iOS, restore the target's original user-select if any
81
+ // Ignore state since it doesn't apply for non iOS
82
+ if (target && modifiedElementMap.has(target)) {
83
+ let targetOldUserSelect = modifiedElementMap.get(target);
84
+
85
+ if (target.style.userSelect === 'none') {
86
+ target.style.userSelect = targetOldUserSelect;
87
+ }
58
88
 
59
- savedUserSelect = '';
60
- state = 'default';
89
+ if (target.getAttribute('style') === '') {
90
+ target.removeAttribute('style');
61
91
  }
62
- });
63
- }, 300);
92
+ modifiedElementMap.delete(target);
93
+ }
94
+ }
64
95
  }
@@ -58,7 +58,7 @@ function triggerChangeHandlers(modality: Modality, e: HandlerEvent) {
58
58
  */
59
59
  function isValidKey(e: KeyboardEvent) {
60
60
  // Control and Shift keys trigger when navigating back to the tab with keyboard.
61
- return !(e.metaKey || (!isMac() && e.altKey) || e.ctrlKey || e.type === 'keyup' && (e.key === 'Control' || e.key === 'Shift'));
61
+ return !(e.metaKey || (!isMac() && e.altKey) || e.ctrlKey || e.key === 'Control' || e.key === 'Shift' || e.key === 'Meta');
62
62
  }
63
63
 
64
64
 
@@ -231,6 +231,8 @@ export function useFocusVisibleListener(fn: FocusVisibleHandler, deps: ReadonlyA
231
231
  fn(isFocusVisible());
232
232
  };
233
233
  changeHandlers.add(handler);
234
- return () => changeHandlers.delete(handler);
234
+ return () => {
235
+ changeHandlers.delete(handler);
236
+ };
235
237
  }, deps);
236
238
  }
package/src/useHover.ts CHANGED
@@ -109,7 +109,7 @@ export function useHover(props: HoverProps): HoverResult {
109
109
  }
110
110
 
111
111
  state.isHovered = true;
112
- let target = event.target;
112
+ let target = event.currentTarget;
113
113
  state.target = target;
114
114
 
115
115
  if (onHoverStart) {
@@ -136,7 +136,7 @@ export function useHover(props: HoverProps): HoverResult {
136
136
  }
137
137
 
138
138
  state.isHovered = false;
139
- let target = event.target;
139
+ let target = event.currentTarget;
140
140
  if (onHoverEnd) {
141
141
  onHoverEnd({
142
142
  type: 'hoverend',
@@ -194,7 +194,7 @@ export function useHover(props: HoverProps): HoverResult {
194
194
  // Call the triggerHoverEnd as soon as isDisabled changes to true
195
195
  // Safe to call triggerHoverEnd, it will early return if we aren't currently hovering
196
196
  if (isDisabled) {
197
- triggerHoverEnd({target: state.target}, state.pointerType);
197
+ triggerHoverEnd({currentTarget: state.target}, state.pointerType);
198
198
  }
199
199
  }, [isDisabled]);
200
200
 
@@ -0,0 +1,128 @@
1
+ /*
2
+ * Copyright 2020 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {HTMLAttributes, useRef} from 'react';
14
+ import {LongPressEvent} from '@react-types/shared';
15
+ import {mergeProps, useDescription, useGlobalListeners} from '@react-aria/utils';
16
+ import {usePress} from './usePress';
17
+
18
+ interface LongPressProps {
19
+ /** Whether long press events should be disabled. */
20
+ isDisabled?: boolean,
21
+ /** Handler that is called when a long press interaction starts. */
22
+ onLongPressStart?: (e: LongPressEvent) => void,
23
+ /**
24
+ * Handler that is called when a long press interaction ends, either
25
+ * over the target or when the pointer leaves the target.
26
+ */
27
+ onLongPressEnd?: (e: LongPressEvent) => void,
28
+ /**
29
+ * Handler that is called when the threshold time is met while
30
+ * the press is over the target.
31
+ */
32
+ onLongPress?: (e: LongPressEvent) => void,
33
+ /**
34
+ * The amount of time in milliseconds to wait before triggering a long press.
35
+ * @default 500ms
36
+ */
37
+ threshold?: number,
38
+ /**
39
+ * A description for assistive techology users indicating that a long press
40
+ * action is available, e.g. "Long press to open menu".
41
+ */
42
+ accessibilityDescription?: string
43
+ }
44
+
45
+ interface LongPressResult {
46
+ /** Props to spread on the target element. */
47
+ longPressProps: HTMLAttributes<HTMLElement>
48
+ }
49
+
50
+ const DEFAULT_THRESHOLD = 500;
51
+
52
+ /**
53
+ * Handles long press interactions across mouse and touch devices. Supports a customizable time threshold,
54
+ * accessibility description, and normalizes behavior across browsers and devices.
55
+ */
56
+ export function useLongPress(props: LongPressProps): LongPressResult {
57
+ let {
58
+ isDisabled,
59
+ onLongPressStart,
60
+ onLongPressEnd,
61
+ onLongPress,
62
+ threshold = DEFAULT_THRESHOLD,
63
+ accessibilityDescription
64
+ } = props;
65
+
66
+ const timeRef = useRef(null);
67
+ let {addGlobalListener, removeGlobalListener} = useGlobalListeners();
68
+
69
+ let {pressProps} = usePress({
70
+ isDisabled,
71
+ onPressStart(e) {
72
+ if (e.pointerType === 'mouse' || e.pointerType === 'touch') {
73
+ if (onLongPressStart) {
74
+ onLongPressStart({
75
+ ...e,
76
+ type: 'longpressstart'
77
+ });
78
+ }
79
+
80
+ timeRef.current = setTimeout(() => {
81
+ // Prevent other usePress handlers from also handling this event.
82
+ e.target.dispatchEvent(new PointerEvent('pointercancel', {bubbles: true}));
83
+ if (onLongPress) {
84
+ onLongPress({
85
+ ...e,
86
+ type: 'longpress'
87
+ });
88
+ }
89
+ timeRef.current = null;
90
+ }, threshold);
91
+
92
+ // Prevent context menu, which may be opened on long press on touch devices
93
+ if (e.pointerType === 'touch') {
94
+ let onContextMenu = e => {
95
+ e.preventDefault();
96
+ };
97
+
98
+ addGlobalListener(e.target, 'contextmenu', onContextMenu, {once: true});
99
+ addGlobalListener(window, 'pointerup', () => {
100
+ // If no contextmenu event is fired quickly after pointerup, remove the handler
101
+ // so future context menu events outside a long press are not prevented.
102
+ setTimeout(() => {
103
+ removeGlobalListener(e.target, 'contextmenu', onContextMenu);
104
+ }, 30);
105
+ }, {once: true});
106
+ }
107
+ }
108
+ },
109
+ onPressEnd(e) {
110
+ if (timeRef.current) {
111
+ clearTimeout(timeRef.current);
112
+ }
113
+
114
+ if (onLongPressEnd && (e.pointerType === 'mouse' || e.pointerType === 'touch')) {
115
+ onLongPressEnd({
116
+ ...e,
117
+ type: 'longpressend'
118
+ });
119
+ }
120
+ }
121
+ });
122
+
123
+ let descriptionProps = useDescription(onLongPress && !isDisabled ? accessibilityDescription : null);
124
+
125
+ return {
126
+ longPressProps: mergeProps(pressProps, descriptionProps)
127
+ };
128
+ }
package/src/useMove.ts CHANGED
@@ -20,6 +20,13 @@ interface MoveResult {
20
20
  moveProps: HTMLAttributes<HTMLElement>
21
21
  }
22
22
 
23
+ interface EventBase {
24
+ shiftKey: boolean,
25
+ ctrlKey: boolean,
26
+ metaKey: boolean,
27
+ altKey: boolean
28
+ }
29
+
23
30
  /**
24
31
  * Handles move interactions across mouse, touch, and keyboard, including dragging with
25
32
  * the mouse or touch, and using the arrow keys. Normalizes behavior across browsers and
@@ -43,7 +50,7 @@ export function useMove(props: MoveEvents): MoveResult {
43
50
  disableTextSelection();
44
51
  state.current.didMove = false;
45
52
  };
46
- let move = (pointerType: PointerType, deltaX: number, deltaY: number) => {
53
+ let move = (originalEvent: EventBase, pointerType: PointerType, deltaX: number, deltaY: number) => {
47
54
  if (deltaX === 0 && deltaY === 0) {
48
55
  return;
49
56
  }
@@ -52,22 +59,34 @@ export function useMove(props: MoveEvents): MoveResult {
52
59
  state.current.didMove = true;
53
60
  onMoveStart?.({
54
61
  type: 'movestart',
55
- pointerType
62
+ pointerType,
63
+ shiftKey: originalEvent.shiftKey,
64
+ metaKey: originalEvent.metaKey,
65
+ ctrlKey: originalEvent.ctrlKey,
66
+ altKey: originalEvent.altKey
56
67
  });
57
68
  }
58
69
  onMove({
59
70
  type: 'move',
60
71
  pointerType,
61
72
  deltaX: deltaX,
62
- deltaY: deltaY
73
+ deltaY: deltaY,
74
+ shiftKey: originalEvent.shiftKey,
75
+ metaKey: originalEvent.metaKey,
76
+ ctrlKey: originalEvent.ctrlKey,
77
+ altKey: originalEvent.altKey
63
78
  });
64
79
  };
65
- let end = (pointerType: PointerType) => {
80
+ let end = (originalEvent: EventBase, pointerType: PointerType) => {
66
81
  restoreTextSelection();
67
82
  if (state.current.didMove) {
68
83
  onMoveEnd?.({
69
84
  type: 'moveend',
70
- pointerType
85
+ pointerType,
86
+ shiftKey: originalEvent.shiftKey,
87
+ metaKey: originalEvent.metaKey,
88
+ ctrlKey: originalEvent.ctrlKey,
89
+ altKey: originalEvent.altKey
71
90
  });
72
91
  }
73
92
  };
@@ -75,13 +94,13 @@ export function useMove(props: MoveEvents): MoveResult {
75
94
  if (typeof PointerEvent === 'undefined') {
76
95
  let onMouseMove = (e: MouseEvent) => {
77
96
  if (e.button === 0) {
78
- move('mouse', e.pageX - state.current.lastPosition.pageX, e.pageY - state.current.lastPosition.pageY);
97
+ move(e, 'mouse', e.pageX - state.current.lastPosition.pageX, e.pageY - state.current.lastPosition.pageY);
79
98
  state.current.lastPosition = {pageX: e.pageX, pageY: e.pageY};
80
99
  }
81
100
  };
82
101
  let onMouseUp = (e: MouseEvent) => {
83
102
  if (e.button === 0) {
84
- end('mouse');
103
+ end(e, 'mouse');
85
104
  removeGlobalListener(window, 'mousemove', onMouseMove, false);
86
105
  removeGlobalListener(window, 'mouseup', onMouseUp, false);
87
106
  }
@@ -98,19 +117,17 @@ export function useMove(props: MoveEvents): MoveResult {
98
117
  };
99
118
 
100
119
  let onTouchMove = (e: TouchEvent) => {
101
- // @ts-ignore
102
120
  let touch = [...e.changedTouches].findIndex(({identifier}) => identifier === state.current.id);
103
121
  if (touch >= 0) {
104
122
  let {pageX, pageY} = e.changedTouches[touch];
105
- move('touch', pageX - state.current.lastPosition.pageX, pageY - state.current.lastPosition.pageY);
123
+ move(e, 'touch', pageX - state.current.lastPosition.pageX, pageY - state.current.lastPosition.pageY);
106
124
  state.current.lastPosition = {pageX, pageY};
107
125
  }
108
126
  };
109
127
  let onTouchEnd = (e: TouchEvent) => {
110
- // @ts-ignore
111
128
  let touch = [...e.changedTouches].findIndex(({identifier}) => identifier === state.current.id);
112
129
  if (touch >= 0) {
113
- end('touch');
130
+ end(e, 'touch');
114
131
  state.current.id = null;
115
132
  removeGlobalListener(window, 'touchmove', onTouchMove);
116
133
  removeGlobalListener(window, 'touchend', onTouchEnd);
@@ -135,22 +152,20 @@ export function useMove(props: MoveEvents): MoveResult {
135
152
  } else {
136
153
  let onPointerMove = (e: PointerEvent) => {
137
154
  if (e.pointerId === state.current.id) {
138
- // @ts-ignore
139
- let pointerType: PointerType = e.pointerType || 'mouse';
155
+ let pointerType = (e.pointerType || 'mouse') as PointerType;
140
156
 
141
157
  // Problems with PointerEvent#movementX/movementY:
142
158
  // 1. it is always 0 on macOS Safari.
143
159
  // 2. On Chrome Android, it's scaled by devicePixelRatio, but not on Chrome macOS
144
- move(pointerType, e.pageX - state.current.lastPosition.pageX, e.pageY - state.current.lastPosition.pageY);
160
+ move(e, pointerType, e.pageX - state.current.lastPosition.pageX, e.pageY - state.current.lastPosition.pageY);
145
161
  state.current.lastPosition = {pageX: e.pageX, pageY: e.pageY};
146
162
  }
147
163
  };
148
164
 
149
165
  let onPointerUp = (e: PointerEvent) => {
150
166
  if (e.pointerId === state.current.id) {
151
- // @ts-ignore
152
- let pointerType: PointerType = e.pointerType || 'mouse';
153
- end(pointerType);
167
+ let pointerType = (e.pointerType || 'mouse') as PointerType;
168
+ end(e, pointerType);
154
169
  state.current.id = null;
155
170
  removeGlobalListener(window, 'pointermove', onPointerMove, false);
156
171
  removeGlobalListener(window, 'pointerup', onPointerUp, false);
@@ -172,10 +187,10 @@ export function useMove(props: MoveEvents): MoveResult {
172
187
  };
173
188
  }
174
189
 
175
- let triggerKeyboardMove = (deltaX: number, deltaY: number) => {
190
+ let triggerKeyboardMove = (e: EventBase, deltaX: number, deltaY: number) => {
176
191
  start();
177
- move('keyboard', deltaX, deltaY);
178
- end('keyboard');
192
+ move(e, 'keyboard', deltaX, deltaY);
193
+ end(e, 'keyboard');
179
194
  };
180
195
 
181
196
  moveProps.onKeyDown = (e) => {
@@ -184,25 +199,25 @@ export function useMove(props: MoveEvents): MoveResult {
184
199
  case 'ArrowLeft':
185
200
  e.preventDefault();
186
201
  e.stopPropagation();
187
- triggerKeyboardMove(-1, 0);
202
+ triggerKeyboardMove(e, -1, 0);
188
203
  break;
189
204
  case 'Right':
190
205
  case 'ArrowRight':
191
206
  e.preventDefault();
192
207
  e.stopPropagation();
193
- triggerKeyboardMove(1, 0);
208
+ triggerKeyboardMove(e, 1, 0);
194
209
  break;
195
210
  case 'Up':
196
211
  case 'ArrowUp':
197
212
  e.preventDefault();
198
213
  e.stopPropagation();
199
- triggerKeyboardMove(0, -1);
214
+ triggerKeyboardMove(e, 0, -1);
200
215
  break;
201
216
  case 'Down':
202
217
  case 'ArrowDown':
203
218
  e.preventDefault();
204
219
  e.stopPropagation();
205
- triggerKeyboardMove(0, 1);
220
+ triggerKeyboardMove(e, 0, 1);
206
221
  break;
207
222
  }
208
223
  };