@wordpress/block-editor
Version:
432 lines (419 loc) • 14.8 kB
JavaScript
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
;