"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.BlockDraggableWrapper = void 0;
var _reactNative = require("react-native");
var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
var _components = require("@wordpress/components");
var _data = require("@wordpress/data");
var _element = require("@wordpress/element");
var _blocks = require("@wordpress/blocks");
var _reactNativeBridge = require("@wordpress/react-native-bridge");
var _reactNativeAztec = _interopRequireDefault(require("@wordpress/react-native-aztec"));
var _useScrollWhenDragging = _interopRequireDefault(require("./use-scroll-when-dragging"));
var _draggableChip = _interopRequireDefault(require("./draggable-chip"));
var _store = require("../../store");
var _droppingInsertionPoint = _interopRequireDefault(require("./dropping-insertion-point"));
var _useBlockDropZone = _interopRequireDefault(require("../use-block-drop-zone"));
var _style = _interopRequireDefault(require("./style.scss"));
var _jsxRuntime = require("react/jsx-runtime");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
 * External dependencies
 */

/**
 * WordPress dependencies
 */

/**
 * Internal dependencies
 */

const CHIP_OFFSET_TO_TOUCH_POSITION = 32;
const BLOCK_OPACITY_ANIMATION_CONFIG = {
  duration: 350
};
const BLOCK_OPACITY_ANIMATION_DELAY = 250;
const DEFAULT_LONG_PRESS_MIN_DURATION = 500;
const DEFAULT_IOS_LONG_PRESS_MIN_DURATION = DEFAULT_LONG_PRESS_MIN_DURATION - 50;

/**
 * Block draggable wrapper component
 *
 * This component handles all the interactions for dragging blocks.
 * It relies on the block list and its context for dragging, hence it
 * should be rendered between the `BlockListProvider` component and the
 * block list rendering. It also requires listening to scroll events,
 * therefore for this purpose, it returns the `onScroll` event handler
 * that should be attached to the list that renders the blocks.
 *
 *
 * @param {Object}      props          Component props.
 * @param {JSX.Element} props.children Children to be rendered.
 * @param {boolean}     props.isRTL    Check if current locale is RTL.
 *
 * @return {Function} Render function that passes `onScroll` event handler.
 */
const BlockDraggableWrapper = ({
  children,
  isRTL
}) => {
  const [draggedBlockIcon, setDraggedBlockIcon] = (0, _element.useState)();
  const {
    selectBlock,
    startDraggingBlocks,
    stopDraggingBlocks
  } = (0, _data.useDispatch)(_store.store);
  const {
    left,
    right
  } = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
  const {
    width
  } = (0, _reactNativeSafeAreaContext.useSafeAreaFrame)();
  const safeAreaOffset = left + right;
  const contentWidth = width - safeAreaOffset;
  const scroll = {
    offsetY: (0, _reactNativeReanimated.useSharedValue)(0)
  };
  const chip = {
    x: (0, _reactNativeReanimated.useSharedValue)(0),
    y: (0, _reactNativeReanimated.useSharedValue)(0),
    width: (0, _reactNativeReanimated.useSharedValue)(0),
    height: (0, _reactNativeReanimated.useSharedValue)(0)
  };
  const currentYPosition = (0, _reactNativeReanimated.useSharedValue)(0);
  const isDragging = (0, _reactNativeReanimated.useSharedValue)(false);
  const [startScrolling, scrollOnDragOver, stopScrolling, draggingScrollHandler] = (0, _useScrollWhenDragging.default)();
  const scrollHandler = event => {
    'worklet';

    const {
      contentOffset
    } = event;
    scroll.offsetY.value = contentOffset.y;
    draggingScrollHandler(event);
  };
  const {
    onBlockDragOverWorklet,
    onBlockDragEnd,
    onBlockDrop,
    targetBlockIndex
  } = (0, _useBlockDropZone.default)();

  // Stop dragging blocks if the block draggable is unmounted.
  (0, _element.useEffect)(() => {
    return () => {
      if (isDragging.value) {
        stopDraggingBlocks();
      }
    };
  }, []);
  const setDraggedBlockIconByClientId = clientId => {
    const blockName = (0, _data.select)(_store.store).getBlockName(clientId);
    const blockIcon = (0, _blocks.getBlockType)(blockName)?.icon;
    if (blockIcon) {
      setDraggedBlockIcon(blockIcon);
    }
  };
  const onStartDragging = ({
    clientId,
    position
  }) => {
    if (clientId) {
      startDraggingBlocks([clientId]);
      setDraggedBlockIconByClientId(clientId);
      (0, _reactNativeReanimated.runOnUI)(startScrolling)(position.y);
      (0, _reactNativeBridge.generateHapticFeedback)();
    } else {
      // We stop dragging if no block is found.
      (0, _reactNativeReanimated.runOnUI)(stopDragging)();
    }
  };
  const onStopDragging = ({
    clientId
  }) => {
    if (clientId) {
      onBlockDrop({
        // Dropping is only allowed at root level
        srcRootClientId: '',
        srcClientIds: [clientId],
        type: 'block'
      });
      selectBlock(clientId);
      setDraggedBlockIcon(undefined);
    }
    onBlockDragEnd();
    stopDraggingBlocks();
  };
  const onChipLayout = ({
    nativeEvent: {
      layout
    }
  }) => {
    if (layout.width > 0) {
      chip.width.value = layout.width;
    }
    if (layout.height > 0) {
      chip.height.value = layout.height;
    }
  };
  const startDragging = ({
    x,
    y,
    id
  }) => {
    'worklet';

    const dragPosition = {
      x,
      y
    };
    chip.x.value = dragPosition.x;
    chip.y.value = dragPosition.y;
    currentYPosition.value = dragPosition.y;
    isDragging.value = true;
    (0, _reactNativeReanimated.runOnJS)(onStartDragging)({
      clientId: id,
      position: dragPosition
    });
  };
  const updateDragging = ({
    x,
    y
  }) => {
    'worklet';

    const dragPosition = {
      x,
      y
    };
    chip.x.value = dragPosition.x;
    chip.y.value = dragPosition.y;
    currentYPosition.value = dragPosition.y;
    onBlockDragOverWorklet({
      x,
      y: y + scroll.offsetY.value
    });

    // Update scrolling velocity
    scrollOnDragOver(dragPosition.y);
  };
  const stopDragging = ({
    id
  }) => {
    'worklet';

    isDragging.value = false;
    stopScrolling();
    (0, _reactNativeReanimated.runOnJS)(onStopDragging)({
      clientId: id
    });
  };
  const chipDynamicStyles = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
    const chipOffset = chip.width.value / 2;
    const translateX = !isRTL ? chip.x.value - chipOffset : -(contentWidth - (chip.x.value + chipOffset));
    return {
      transform: [{
        translateX
      }, {
        translateY: chip.y.value - chip.height.value - CHIP_OFFSET_TO_TOUCH_POSITION
      }]
    };
  });
  const chipStyles = [chipDynamicStyles, _style.default['draggable-chip__wrapper']];
  const exitingAnimation = ({
    currentHeight,
    currentWidth
  }) => {
    'worklet';

    const translateX = !isRTL ? 0 : currentWidth * -1;
    const duration = 150;
    const animations = {
      transform: [{
        translateY: (0, _reactNativeReanimated.withTiming)(currentHeight, {
          duration
        })
      }, {
        translateX: (0, _reactNativeReanimated.withTiming)(translateX, {
          duration
        })
      }, {
        scale: (0, _reactNativeReanimated.withTiming)(0, {
          duration
        })
      }]
    };
    const initialValues = {
      transform: [{
        translateY: 0
      }, {
        translateX
      }, {
        scale: 1
      }]
    };
    return {
      initialValues,
      animations
    };
  };
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
    children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_droppingInsertionPoint.default, {
      scroll: scroll,
      currentYPosition: currentYPosition,
      isDragging: isDragging,
      targetBlockIndex: targetBlockIndex
    }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Draggable, {
      onDragStart: startDragging,
      onDragOver: updateDragging,
      onDragEnd: stopDragging,
      testID: "block-draggable-wrapper",
      children: children({
        onScroll: scrollHandler
      })
    }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
      onLayout: onChipLayout,
      style: chipStyles,
      pointerEvents: "none",
      children: draggedBlockIcon && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
        entering: _reactNativeReanimated.ZoomInEasyDown.duration(200),
        exiting: exitingAnimation,
        children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_draggableChip.default, {
          icon: draggedBlockIcon
        })
      })
    })]
  });
};
exports.BlockDraggableWrapper = BlockDraggableWrapper;
function useIsScreenReaderEnabled() {
  const [isScreenReaderEnabled, setIsScreenReaderEnabled] = (0, _element.useState)(false);
  (0, _element.useEffect)(() => {
    let mounted = true;
    const changeListener = _reactNative.AccessibilityInfo.addEventListener('screenReaderChanged', enabled => setIsScreenReaderEnabled(enabled));
    _reactNative.AccessibilityInfo.isScreenReaderEnabled().then(screenReaderEnabled => {
      if (mounted && screenReaderEnabled) {
        setIsScreenReaderEnabled(screenReaderEnabled);
      }
    });
    return () => {
      mounted = false;
      changeListener.remove();
    };
  }, []);
  return isScreenReaderEnabled;
}
function useIsEditingText() {
  const [isEditingText, setIsEditingText] = (0, _element.useState)(() => _reactNativeAztec.default.InputState.isFocused());
  (0, _element.useEffect)(() => {
    const onFocusChangeAztec = ({
      isFocused
    }) => {
      setIsEditingText(isFocused);
    };
    _reactNativeAztec.default.InputState.addFocusChangeListener(onFocusChangeAztec);
    return () => {
      _reactNativeAztec.default.InputState.removeFocusChangeListener(onFocusChangeAztec);
    };
  }, []);
  return isEditingText;
}

/**
 * Block draggable component
 *
 * This component serves for animating the block when it is being dragged.
 * Hence, it should be wrapped around the rendering of a block.
 *
 * @param {Object}      props                    Component props.
 * @param {JSX.Element} props.children           Children to be rendered.
 * @param {string}      props.clientId           Client id of the block.
 * @param {string}      [props.draggingClientId] Client id to use for dragging. If not defined, the value from `clientId` will be used.
 * @param {boolean}     [props.enabled]          Enables the draggable trigger.
 * @param {string}      [props.testID]           Id used for querying the long-press gesture handler in tests.
 *
 * @return {Function} Render function which includes the parameter `isDraggable` to determine if the block can be dragged.
 */
const BlockDraggable = ({
  clientId,
  children,
  draggingClientId,
  enabled = true,
  testID
}) => {
  const wasBeingDragged = (0, _element.useRef)(false);
  const isEditingText = useIsEditingText();
  const isScreenReaderEnabled = useIsScreenReaderEnabled();
  const draggingAnimation = {
    opacity: (0, _reactNativeReanimated.useSharedValue)(1)
  };
  const startDraggingBlock = () => {
    draggingAnimation.opacity.value = (0, _reactNativeReanimated.withTiming)(0.4, BLOCK_OPACITY_ANIMATION_CONFIG);
  };
  const stopDraggingBlock = () => {
    draggingAnimation.opacity.value = (0, _reactNativeReanimated.withDelay)(BLOCK_OPACITY_ANIMATION_DELAY, (0, _reactNativeReanimated.withTiming)(1, BLOCK_OPACITY_ANIMATION_CONFIG));
  };
  const {
    isDraggable,
    isBeingDragged,
    isBlockSelected
  } = (0, _data.useSelect)(_select => {
    const {
      getBlockRootClientId,
      getTemplateLock,
      isBlockBeingDragged,
      getSelectedBlockClientId
    } = _select(_store.store);
    const rootClientId = getBlockRootClientId(clientId);
    const templateLock = rootClientId ? getTemplateLock(rootClientId) : null;
    const selectedBlockClientId = getSelectedBlockClientId();
    return {
      isBeingDragged: isBlockBeingDragged(clientId),
      isDraggable: 'all' !== templateLock,
      isBlockSelected: selectedBlockClientId && selectedBlockClientId === clientId
    };
  }, [clientId]);
  (0, _element.useEffect)(() => {
    if (isBeingDragged !== wasBeingDragged.current) {
      if (isBeingDragged) {
        startDraggingBlock();
      } else {
        stopDraggingBlock();
      }
    }
    wasBeingDragged.current = isBeingDragged;
  }, [isBeingDragged]);
  const onLongPressDraggable = (0, _element.useCallback)(() => {
    // Ensure that no text input is focused when starting the dragging gesture in order to prevent conflicts with text editing.
    _reactNativeAztec.default.InputState.blurCurrentFocusedElement();
  }, []);
  const animatedWrapperStyles = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
    return {
      opacity: draggingAnimation.opacity.value
    };
  });
  const wrapperStyles = [animatedWrapperStyles, _style.default['draggable-wrapper__container']];
  const canDragBlock = enabled && !isScreenReaderEnabled && (!isBlockSelected || !isEditingText);
  if (!isDraggable) {
    return children({
      isDraggable: false
    });
  }
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.DraggableTrigger, {
    id: draggingClientId || clientId,
    enabled: enabled && canDragBlock,
    minDuration: _element.Platform.select({
      // On iOS, using a lower min duration than the default
      // value prevents the long-press gesture from being
      // triggered in underneath elements. This is required to
      // prevent enabling text editing when dragging is available.
      ios: canDragBlock ? DEFAULT_IOS_LONG_PRESS_MIN_DURATION : DEFAULT_LONG_PRESS_MIN_DURATION,
      android: DEFAULT_LONG_PRESS_MIN_DURATION
    }),
    onLongPress: onLongPressDraggable,
    testID: testID,
    children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
      style: wrapperStyles,
      children: children({
        isDraggable: true
      })
    })
  });
};
var _default = exports.default = BlockDraggable;
//# sourceMappingURL=index.native.js.map