@react-aria/dnd 3.10.0 → 3.11.0

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.
@@ -182,7 +182,7 @@ class DragSession {
182
182
  this.initialFocused = false;
183
183
  }
184
184
 
185
- setup() {
185
+ setup(): void {
186
186
  document.addEventListener('keydown', this.onKeyDown, true);
187
187
  document.addEventListener('keyup', this.onKeyUp, true);
188
188
  window.addEventListener('focus', this.onFocus, true);
@@ -202,7 +202,7 @@ class DragSession {
202
202
  announce(this.stringFormatter.format(MESSAGES[getDragModality()]));
203
203
  }
204
204
 
205
- teardown() {
205
+ teardown(): void {
206
206
  document.removeEventListener('keydown', this.onKeyDown, true);
207
207
  document.removeEventListener('keyup', this.onKeyUp, true);
208
208
  window.removeEventListener('focus', this.onFocus, true);
@@ -218,7 +218,7 @@ class DragSession {
218
218
  this.restoreAriaHidden?.();
219
219
  }
220
220
 
221
- onKeyDown(e: KeyboardEvent) {
221
+ onKeyDown(e: KeyboardEvent): void {
222
222
  this.cancelEvent(e);
223
223
 
224
224
  if (e.key === 'Escape') {
@@ -239,7 +239,7 @@ class DragSession {
239
239
  }
240
240
  }
241
241
 
242
- onKeyUp(e: KeyboardEvent) {
242
+ onKeyUp(e: KeyboardEvent): void {
243
243
  this.cancelEvent(e);
244
244
 
245
245
  if (e.key === 'Enter') {
@@ -255,7 +255,7 @@ class DragSession {
255
255
  return this.currentDropItem?.activateButtonRef?.current ?? this.currentDropTarget?.activateButtonRef?.current ?? null;
256
256
  }
257
257
 
258
- onFocus(e: FocusEvent) {
258
+ onFocus(e: FocusEvent): void {
259
259
  let activateButton = this.getCurrentActivateButton();
260
260
  if (e.target === activateButton) {
261
261
  // TODO: canceling this breaks the focus ring. Revisit when we support tabbing.
@@ -295,7 +295,7 @@ class DragSession {
295
295
  }
296
296
  }
297
297
 
298
- onBlur(e: FocusEvent) {
298
+ onBlur(e: FocusEvent): void {
299
299
  let activateButton = this.getCurrentActivateButton();
300
300
  if (e.relatedTarget === activateButton) {
301
301
  this.cancelEvent(e);
@@ -317,7 +317,7 @@ class DragSession {
317
317
  }
318
318
  }
319
319
 
320
- onClick(e: MouseEvent) {
320
+ onClick(e: MouseEvent): void {
321
321
  this.cancelEvent(e);
322
322
  if (isVirtualClick(e) || this.isVirtualClick) {
323
323
  let dropElements = dropItems.values();
@@ -341,14 +341,14 @@ class DragSession {
341
341
  }
342
342
  }
343
343
 
344
- onPointerDown(e: PointerEvent) {
344
+ onPointerDown(e: PointerEvent): void {
345
345
  // Android Talkback double tap has e.detail = 1 for onClick. Detect the virtual click in onPointerDown before onClick fires
346
346
  // so we can properly perform cancel and drop operations.
347
347
  this.cancelEvent(e);
348
348
  this.isVirtualClick = isVirtualPointerEvent(e);
349
349
  }
350
350
 
351
- cancelEvent(e: Event) {
351
+ cancelEvent(e: Event): void {
352
352
  // Allow focusin and focusout on the drag target so focus ring works properly.
353
353
  if ((e.type === 'focusin' || e.type === 'focusout') && (e.target === this.dragTarget?.element || e.target === this.getCurrentActivateButton())) {
354
354
  return;
@@ -363,7 +363,7 @@ class DragSession {
363
363
  e.stopImmediatePropagation();
364
364
  }
365
365
 
366
- updateValidDropTargets() {
366
+ updateValidDropTargets(): void {
367
367
  if (!this.mutationObserver) {
368
368
  return;
369
369
  }
@@ -408,12 +408,12 @@ class DragSession {
408
408
  this.dragTarget.element,
409
409
  ...validDropItems.flatMap(item => item.activateButtonRef?.current ? [item.element, item.activateButtonRef?.current] : [item.element]),
410
410
  ...visibleDropTargets.flatMap(target => target.activateButtonRef?.current ? [target.element, target.activateButtonRef?.current] : [target.element])
411
- ]);
411
+ ], {shouldUseInert: true});
412
412
 
413
- this.mutationObserver.observe(document.body, {subtree: true, attributes: true, attributeFilter: ['aria-hidden']});
413
+ this.mutationObserver.observe(document.body, {subtree: true, attributes: true, attributeFilter: ['aria-hidden', 'inert']});
414
414
  }
415
415
 
416
- next() {
416
+ next(): void {
417
417
  // TODO: Allow tabbing to the activate button. Revisit once we fix the focus ring.
418
418
  // For now, the activate button is reachable by screen readers and ArrowLeft/ArrowRight
419
419
  // is usable specifically by Tree. Will need tabbing for other components.
@@ -437,7 +437,7 @@ class DragSession {
437
437
  // If we've reached the end of the valid drop targets, cycle back to the original drag target.
438
438
  // This lets the user cancel the drag in case they don't have an Escape key (e.g. iPad keyboard case).
439
439
  if (index === this.validDropTargets.length - 1) {
440
- if (!this.dragTarget.element.closest('[aria-hidden="true"]')) {
440
+ if (!this.dragTarget.element.closest('[aria-hidden="true"], [inert]')) {
441
441
  this.setCurrentDropTarget(null);
442
442
  this.dragTarget.element.focus();
443
443
  } else {
@@ -448,7 +448,7 @@ class DragSession {
448
448
  }
449
449
  }
450
450
 
451
- previous() {
451
+ previous(): void {
452
452
  // let activateButton = this.getCurrentActivateButton();
453
453
  // if (activateButton && document.activeElement === activateButton) {
454
454
  // let target = this.currentDropItem ?? this.currentDropTarget;
@@ -472,7 +472,7 @@ class DragSession {
472
472
  // If we've reached the start of the valid drop targets, cycle back to the original drag target.
473
473
  // This lets the user cancel the drag in case they don't have an Escape key (e.g. iPad keyboard case).
474
474
  if (index === 0) {
475
- if (!this.dragTarget.element.closest('[aria-hidden="true"]')) {
475
+ if (!this.dragTarget.element.closest('[aria-hidden="true"], [inert]')) {
476
476
  this.setCurrentDropTarget(null);
477
477
  this.dragTarget.element.focus();
478
478
  } else {
@@ -503,7 +503,7 @@ class DragSession {
503
503
  return nearest;
504
504
  }
505
505
 
506
- setCurrentDropTarget(dropTarget: DropTarget | null, item?: DroppableItem) {
506
+ setCurrentDropTarget(dropTarget: DropTarget | null, item?: DroppableItem): void {
507
507
  if (dropTarget !== this.currentDropTarget) {
508
508
  if (this.currentDropTarget && typeof this.currentDropTarget.onDropExit === 'function') {
509
509
  let rect = this.currentDropTarget.element.getBoundingClientRect();
@@ -551,7 +551,7 @@ class DragSession {
551
551
  }
552
552
  }
553
553
 
554
- end() {
554
+ end(): void {
555
555
  this.teardown();
556
556
  endDragging();
557
557
 
@@ -576,17 +576,17 @@ class DragSession {
576
576
  this.setCurrentDropTarget(null);
577
577
  }
578
578
 
579
- cancel() {
579
+ cancel(): void {
580
580
  this.setCurrentDropTarget(null);
581
581
  this.end();
582
- if (!this.dragTarget.element.closest('[aria-hidden="true"]')) {
582
+ if (!this.dragTarget.element.closest('[aria-hidden="true"], [inert]')) {
583
583
  this.dragTarget.element.focus();
584
584
  }
585
585
 
586
586
  announce(this.stringFormatter.format('dropCanceled'));
587
587
  }
588
588
 
589
- drop(item?: DroppableItem) {
589
+ drop(item?: DroppableItem): void {
590
590
  if (!this.currentDropTarget) {
591
591
  this.cancel();
592
592
  return;
@@ -624,7 +624,7 @@ class DragSession {
624
624
  announce(this.stringFormatter.format('dropComplete'));
625
625
  }
626
626
 
627
- activate(dropTarget: DropTarget | null, dropItem: DroppableItem | null | undefined) {
627
+ activate(dropTarget: DropTarget | null, dropItem: DroppableItem | null | undefined): void {
628
628
  if (dropTarget && typeof dropTarget.onDropActivate === 'function') {
629
629
  let target = dropItem?.target ?? null;
630
630
  let rect = dropTarget.element.getBoundingClientRect();
@@ -640,7 +640,7 @@ class DragSession {
640
640
  function findValidDropTargets(options: DragTarget) {
641
641
  let types = getTypes(options.items);
642
642
  return [...dropTargets.values()].filter(target => {
643
- if (target.element.closest('[aria-hidden="true"]')) {
643
+ if (target.element.closest('[aria-hidden="true"], [inert]')) {
644
644
  return false;
645
645
  }
646
646
 
@@ -15,24 +15,45 @@ import {flushSync} from 'react-dom';
15
15
  import React, {ForwardedRef, JSX, useEffect, useImperativeHandle, useRef, useState} from 'react';
16
16
 
17
17
  export interface DragPreviewProps {
18
- children: (items: DragItem[]) => JSX.Element | null
18
+ /**
19
+ * A render function which returns a preview element, or an object containing the element
20
+ * and a custom offset. If an object is returned, the provided `x` and `y` values will be
21
+ * used as the drag preview offset instead of the default calculation.
22
+ */
23
+ children: (items: DragItem[]) => JSX.Element | {element: JSX.Element, x: number, y: number} | null
19
24
  }
20
25
 
21
- export const DragPreview = React.forwardRef(function DragPreview(props: DragPreviewProps, ref: ForwardedRef<DragPreviewRenderer | null>) {
26
+ export const DragPreview:
27
+ React.ForwardRefExoticComponent<DragPreviewProps & React.RefAttributes<DragPreviewRenderer | null>> =
28
+ React.forwardRef(function DragPreview(props: DragPreviewProps, ref: ForwardedRef<DragPreviewRenderer | null>) {
22
29
  let render = props.children;
23
30
  let [children, setChildren] = useState<JSX.Element | null>(null);
24
31
  let domRef = useRef<HTMLDivElement | null>(null);
25
32
  let raf = useRef<ReturnType<typeof requestAnimationFrame> | undefined>(undefined);
26
33
 
27
- useImperativeHandle(ref, () => (items: DragItem[], callback: (node: HTMLElement | null) => void) => {
34
+ useImperativeHandle(ref, () => (items: DragItem[], callback: (node: HTMLElement | null, x?: number, y?: number) => void) => {
28
35
  // This will be called during the onDragStart event by useDrag. We need to render the
29
36
  // preview synchronously before this event returns so we can call event.dataTransfer.setDragImage.
37
+
38
+ let result = render(items);
39
+ let element: JSX.Element | null;
40
+ let offsetX: number | undefined;
41
+ let offsetY: number | undefined;
42
+
43
+ if (result && typeof result === 'object' && 'element' in result) {
44
+ element = result.element;
45
+ offsetX = result.x;
46
+ offsetY = result.y;
47
+ } else {
48
+ element = result as JSX.Element | null;
49
+ }
50
+
30
51
  flushSync(() => {
31
- setChildren(render(items));
52
+ setChildren(element);
32
53
  });
33
54
 
34
55
  // Yield back to useDrag to set the drag image.
35
- callback(domRef.current);
56
+ callback(domRef.current, offsetX, offsetY);
36
57
 
37
58
  // Remove the preview from the DOM after a frame so the browser has time to paint.
38
59
  raf.current = requestAnimationFrame(() => {
package/src/constants.ts CHANGED
@@ -12,6 +12,15 @@
12
12
 
13
13
  import {DropOperation} from '@react-types/shared';
14
14
 
15
+ export interface IDropOperation {
16
+ readonly none: 0,
17
+ readonly cancel: 0,
18
+ readonly move: number,
19
+ readonly copy: number,
20
+ readonly link: number,
21
+ readonly all: number
22
+ }
23
+
15
24
  export enum DROP_OPERATION {
16
25
  none = 0,
17
26
  cancel = 0,
@@ -20,9 +29,15 @@ export enum DROP_OPERATION {
20
29
  link = 1 << 2,
21
30
  all = move | copy | link
22
31
  }
23
-
32
+ interface DropOperationAllowed extends IDropOperation {
33
+ readonly copyMove: number,
34
+ readonly copyLink: number,
35
+ readonly linkMove: number,
36
+ readonly all: number,
37
+ readonly uninitialized: number
38
+ }
24
39
  // See https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed
25
- export const DROP_OPERATION_ALLOWED = {
40
+ export const DROP_OPERATION_ALLOWED: DropOperationAllowed = {
26
41
  ...DROP_OPERATION,
27
42
  copyMove: DROP_OPERATION.copy | DROP_OPERATION.move,
28
43
  copyLink: DROP_OPERATION.copy | DROP_OPERATION.link,
@@ -31,28 +46,40 @@ export const DROP_OPERATION_ALLOWED = {
31
46
  uninitialized: DROP_OPERATION.all
32
47
  };
33
48
 
34
- export const EFFECT_ALLOWED = invert(DROP_OPERATION_ALLOWED);
49
+ interface EffectAllowed {
50
+ 0: 'none' | 'cancel',
51
+ 1: 'move',
52
+ 2: 'copy',
53
+ 3: 'copyMove',
54
+ 4: 'link',
55
+ 5: 'linkMove',
56
+ 6: 'copyLink',
57
+ 7: 'all'
58
+ }
59
+
60
+ export const EFFECT_ALLOWED: EffectAllowed = invert(DROP_OPERATION_ALLOWED) as unknown as EffectAllowed;
35
61
  EFFECT_ALLOWED[DROP_OPERATION.all] = 'all'; // ensure we don't map to 'uninitialized'.
36
62
 
37
- export const DROP_EFFECT = invert(DROP_OPERATION);
38
- export const DROP_EFFECT_TO_DROP_OPERATION: {[name: string]: DropOperation} = {
63
+ type DropEffect = 'none' | 'link' | 'copy' | 'move';
64
+
65
+ export const DROP_EFFECT_TO_DROP_OPERATION: {[K in DropEffect]: DropOperation} = {
39
66
  none: 'cancel',
40
67
  link: 'link',
41
68
  copy: 'copy',
42
69
  move: 'move'
43
70
  };
44
71
 
45
- export const DROP_OPERATION_TO_DROP_EFFECT = invert(DROP_EFFECT_TO_DROP_OPERATION);
72
+ export const DROP_OPERATION_TO_DROP_EFFECT: {[K in DropOperation]: DropEffect} = invert(DROP_EFFECT_TO_DROP_OPERATION);
46
73
 
47
- function invert(object) {
48
- let res = {};
74
+ function invert<T extends string | number, C extends string | number>(object: Record<T, C>): Record<C, T> {
75
+ let res: Record<C, T> = {} as Record<C, T>;
49
76
  for (let key in object) {
50
- res[object[key]] = key;
77
+ res[object[key]] = key as T;
51
78
  }
52
79
 
53
80
  return res;
54
81
  }
55
82
 
56
- export const NATIVE_DRAG_TYPES = new Set(['text/plain', 'text/uri-list', 'text/html']);
83
+ export const NATIVE_DRAG_TYPES: Set<string> = new Set(['text/plain', 'text/uri-list', 'text/html']);
57
84
  export const CUSTOM_DRAG_TYPE = 'application/vnd.react-aria.items+json';
58
85
  export const GENERIC_TYPE = 'application/octet-stream';
package/src/useDrag.ts CHANGED
@@ -12,13 +12,13 @@
12
12
 
13
13
  import {AriaButtonProps} from '@react-types/button';
14
14
  import {DragEndEvent, DragItem, DragMoveEvent, DragPreviewRenderer, DragStartEvent, DropOperation, PressEvent, RefObject} from '@react-types/shared';
15
- import {DragEvent, HTMLAttributes, useRef, useState} from 'react';
15
+ import {DragEvent, HTMLAttributes, version as ReactVersion, useEffect, useRef, useState} from 'react';
16
16
  import * as DragManager from './DragManager';
17
17
  import {DROP_EFFECT_TO_DROP_OPERATION, DROP_OPERATION, EFFECT_ALLOWED} from './constants';
18
18
  import {globalDropEffect, setGlobalAllowedDropOperations, setGlobalDropEffect, useDragModality, writeToDataTransfer} from './utils';
19
19
  // @ts-ignore
20
20
  import intlMessages from '../intl/*.json';
21
- import {isVirtualClick, isVirtualPointerEvent, useDescription, useGlobalListeners, useLayoutEffect} from '@react-aria/utils';
21
+ import {isVirtualClick, isVirtualPointerEvent, useDescription, useGlobalListeners} from '@react-aria/utils';
22
22
  import {useLocalizedStringFormatter} from '@react-aria/i18n';
23
23
 
24
24
  export interface DragOptions {
@@ -82,11 +82,11 @@ export function useDrag(options: DragOptions): DragResult {
82
82
  y: 0
83
83
  }).current;
84
84
  state.options = options;
85
- let isDraggingRef = useRef(false);
85
+ let isDraggingRef = useRef<Element | null>(null);
86
86
  let [isDragging, setDraggingState] = useState(false);
87
- let setDragging = (isDragging) => {
88
- isDraggingRef.current = isDragging;
89
- setDraggingState(isDragging);
87
+ let setDragging = (element: Element | null) => {
88
+ isDraggingRef.current = element;
89
+ setDraggingState(!!element);
90
90
  };
91
91
  let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();
92
92
  let modalityOnPointerDown = useRef<string>(null);
@@ -128,12 +128,13 @@ export function useDrag(options: DragOptions): DragResult {
128
128
  }
129
129
 
130
130
  setGlobalAllowedDropOperations(allowed);
131
- e.dataTransfer.effectAllowed = EFFECT_ALLOWED[allowed] || 'none';
131
+ let effectAllowed = EFFECT_ALLOWED[allowed] || 'none';
132
+ e.dataTransfer.effectAllowed = effectAllowed === 'cancel' ? 'none' : effectAllowed;
132
133
 
133
134
  // If there is a preview option, use it to render a custom preview image that will
134
135
  // appear under the pointer while dragging. If not, the element itself is dragged by the browser.
135
136
  if (typeof options.preview?.current === 'function') {
136
- options.preview.current(items, node => {
137
+ options.preview.current(items, (node, userX, userY) => {
137
138
  if (!node) {
138
139
  return;
139
140
  }
@@ -142,18 +143,35 @@ export function useDrag(options: DragOptions): DragResult {
142
143
  // If the preview is much smaller, then just use the center point of the preview.
143
144
  let size = node.getBoundingClientRect();
144
145
  let rect = e.currentTarget.getBoundingClientRect();
145
- let x = e.clientX - rect.x;
146
- let y = e.clientY - rect.y;
147
- if (x > size.width || y > size.height) {
148
- x = size.width / 2;
149
- y = size.height / 2;
146
+ let defaultX = e.clientX - rect.x;
147
+ let defaultY = e.clientY - rect.y;
148
+ if (defaultX > size.width || defaultY > size.height) {
149
+ defaultX = size.width / 2;
150
+ defaultY = size.height / 2;
150
151
  }
151
152
 
153
+ // Start with default offsets.
154
+ let offsetX = defaultX;
155
+ let offsetY = defaultY;
156
+
157
+ // If the preview renderer supplied explicit offsets, use those.
158
+ if (typeof userX === 'number' && typeof userY === 'number') {
159
+ offsetX = userX;
160
+ offsetY = userY;
161
+ }
162
+
163
+ // Clamp the offset so it stays within the preview bounds. Browsers
164
+ // automatically clamp out-of-range values, but doing it ourselves
165
+ // prevents the visible "snap" that can occur when the browser adjusts
166
+ // them after the first drag update.
167
+ offsetX = Math.max(0, Math.min(offsetX, size.width));
168
+ offsetY = Math.max(0, Math.min(offsetY, size.height));
169
+
152
170
  // Rounding height to an even number prevents blurry preview seen on some screens
153
171
  let height = 2 * Math.round(size.height / 2);
154
172
  node.style.height = `${height}px`;
155
173
 
156
- e.dataTransfer.setDragImage(node, x, y);
174
+ e.dataTransfer.setDragImage(node, offsetX, offsetY);
157
175
  });
158
176
  }
159
177
 
@@ -168,8 +186,9 @@ export function useDrag(options: DragOptions): DragResult {
168
186
 
169
187
  // Wait a frame before we set dragging to true so that the browser has time to
170
188
  // render the preview image before we update the element that has been dragged.
189
+ let target = e.target;
171
190
  requestAnimationFrame(() => {
172
- setDragging(true);
191
+ setDragging(target as Element);
173
192
  });
174
193
  };
175
194
 
@@ -213,7 +232,7 @@ export function useDrag(options: DragOptions): DragResult {
213
232
  options.onDragEnd(event);
214
233
  }
215
234
 
216
- setDragging(false);
235
+ setDragging(null);
217
236
  removeAllGlobalListeners();
218
237
  setGlobalAllowedDropOperations(DROP_OPERATION.none);
219
238
  setGlobalDropEffect(undefined);
@@ -221,10 +240,13 @@ export function useDrag(options: DragOptions): DragResult {
221
240
 
222
241
  // If the dragged element is removed from the DOM via onDrop, onDragEnd won't fire: https://bugzilla.mozilla.org/show_bug.cgi?id=460801
223
242
  // In this case, we need to manually call onDragEnd on cleanup
224
-
225
- useLayoutEffect(() => {
243
+
244
+ useEffect(() => {
226
245
  return () => {
227
- if (isDraggingRef.current) {
246
+ // Check that the dragged element has actually unmounted from the DOM and not a React Strict Mode false positive.
247
+ // https://github.com/facebook/react/issues/29585
248
+ // React 16 ran effect cleanups before removing elements from the DOM but did not have this issue.
249
+ if (isDraggingRef.current && (!isDraggingRef.current.isConnected || parseInt(ReactVersion, 10) < 17)) {
228
250
  if (typeof state.options.onDragEnd === 'function') {
229
251
  let event: DragEndEvent = {
230
252
  type: 'dragend',
@@ -235,7 +257,7 @@ export function useDrag(options: DragOptions): DragResult {
235
257
  state.options.onDragEnd(event);
236
258
  }
237
259
 
238
- setDragging(false);
260
+ setDragging(null);
239
261
  setGlobalAllowedDropOperations(DROP_OPERATION.none);
240
262
  setGlobalDropEffect(undefined);
241
263
  }
@@ -267,14 +289,14 @@ export function useDrag(options: DragOptions): DragResult {
267
289
  ? state.options.getAllowedDropOperations()
268
290
  : ['move', 'copy', 'link'],
269
291
  onDragEnd(e) {
270
- setDragging(false);
292
+ setDragging(null);
271
293
  if (typeof state.options.onDragEnd === 'function') {
272
294
  state.options.onDragEnd(e);
273
295
  }
274
296
  }
275
297
  }, stringFormatter);
276
298
 
277
- setDragging(true);
299
+ setDragging(target);
278
300
  };
279
301
 
280
302
  let modality = useDragModality();
package/src/utils.ts CHANGED
@@ -20,8 +20,8 @@ interface DroppableCollectionMap {
20
20
  ref: RefObject<HTMLElement | null>
21
21
  }
22
22
 
23
- export const droppableCollectionMap = new WeakMap<DroppableCollectionState, DroppableCollectionMap>();
24
- export const DIRECTORY_DRAG_TYPE = Symbol();
23
+ export const droppableCollectionMap: WeakMap<DroppableCollectionState, DroppableCollectionMap> = new WeakMap<DroppableCollectionState, DroppableCollectionMap>();
24
+ export const DIRECTORY_DRAG_TYPE: symbol = Symbol();
25
25
 
26
26
  export function getDroppableCollectionId(state: DroppableCollectionState): string {
27
27
  let {id} = droppableCollectionMap.get(state) || {};
@@ -379,7 +379,7 @@ export function setGlobalDropEffect(dropEffect: DropEffect | undefined): void {
379
379
  globalDropEffect = dropEffect;
380
380
  }
381
381
 
382
- export let globalAllowedDropOperations = DROP_OPERATION.none;
382
+ export let globalAllowedDropOperations: DROP_OPERATION = DROP_OPERATION.none;
383
383
  export function setGlobalAllowedDropOperations(o: DROP_OPERATION): void {
384
384
  globalAllowedDropOperations = o;
385
385
  }