import type * as React from 'react'; import type { MediaQueries } from '../../lib/adaptivity'; import { rubberbandIfOutOfBounds } from '../../lib/animation'; import type { ShiftData, SnackbarPlacement } from './types'; export function resolveOffsetYCssStyle( placement: SnackbarPlacement, offsetY?: React.CSSProperties['inset'], ): React.CSSProperties | undefined { if (offsetY === undefined) { return undefined; } switch (placement) { case 'top-start': case 'top': case 'top-end': return { top: offsetY }; case 'bottom-start': case 'bottom': case 'bottom-end': return { bottom: offsetY }; } } export function revertRtlValue(value: number, isRtl: boolean) { return isRtl ? -1 * value : value; } export function getInitialShiftData( width: number, height: number, mediaQueries: MediaQueries, ): ShiftData { return { shifted: false, direction: null, isDesktop: mediaQueries.smallTabletPlus.matches, // eslint-disable-line no-restricted-properties, x: 0, y: 0, width, height, }; } export function getMovedShiftData( placement: SnackbarPlacement, shiftData: ShiftData, nextShift: { x: number; y: number }, isRtl = false, ): ShiftData { /* istanbul ignore else: TODO чтобы протестировать кейс в блоке else, нужно мокать useMediaQueries(), чтобы перебивать mediaQueries.smallTabletPlus.matches */ if (shiftData.isDesktop) { if (placement.endsWith('start')) { shiftData.x = isRtl ? rubberbandIfOutOfBounds(nextShift.x, 0, shiftData.width) : rubberbandIfOutOfBounds(nextShift.x, -shiftData.width, 0); } else if (placement.endsWith('end')) { shiftData.x = isRtl ? rubberbandIfOutOfBounds(nextShift.x, -shiftData.width, 0) : rubberbandIfOutOfBounds(nextShift.x, 0, shiftData.width); } if (placement.startsWith('bottom')) { shiftData.y = rubberbandIfOutOfBounds(nextShift.y, 0, shiftData.height); } } else if (placement.startsWith('bottom')) { shiftData.x = nextShift.x; const movingToLeft = nextShift.x < 0 ? -1 : null; const movingToRight = nextShift.x > 0 ? 1 : null; shiftData.direction = movingToLeft || movingToRight; } if (placement.startsWith('top')) { shiftData.y = rubberbandIfOutOfBounds(nextShift.y, -shiftData.height, 0); } shiftData.shifted = true; return shiftData; } const MINIMUM_PAN_GESTURE_FOR_TRIGGER_CLOSE = 200; export function shouldBeClosedByShiftData( placement: SnackbarPlacement, shiftData: ShiftData, relativeClientRect: DOMRect, velocity: { x: number; y: number }, isRtl = false, ): boolean { if (!shiftData.shifted) { return false; } const shouldBeClosedThreshold = { x: false, y: false }; const shouldBeClosedByVelocity = { x: false, y: false }; /* istanbul ignore else: TODO чтобы протестировать кейс в блоке else, нужно мокать useMediaQueries(), чтобы перебивать mediaQueries.smallTabletPlus.matches */ if (shiftData.isDesktop) { if (placement.endsWith('start')) { shouldBeClosedThreshold.x = revertRtlValue(relativeClientRect.x, isRtl) < -relativeClientRect.width / 2; shouldBeClosedByVelocity.x = revertRtlValue(relativeClientRect.x, isRtl) < 0 ? revertRtlValue(velocity.x, isRtl) < revertRtlValue(-MINIMUM_PAN_GESTURE_FOR_TRIGGER_CLOSE, isRtl) : false; } else if (placement.endsWith('end')) { shouldBeClosedThreshold.x = revertRtlValue(relativeClientRect.x, isRtl) > relativeClientRect.width / 2; shouldBeClosedByVelocity.x = revertRtlValue(relativeClientRect.x, isRtl) > 0 ? revertRtlValue(velocity.x, isRtl) > revertRtlValue(MINIMUM_PAN_GESTURE_FOR_TRIGGER_CLOSE, isRtl) : false; } if (placement.startsWith('bottom')) { shouldBeClosedThreshold.y = relativeClientRect.y > relativeClientRect.height / 2; shouldBeClosedByVelocity.y = relativeClientRect.y > 0 ? velocity.y > MINIMUM_PAN_GESTURE_FOR_TRIGGER_CLOSE : false; } } else if (placement.startsWith('bottom')) { shouldBeClosedThreshold.x = relativeClientRect.x < -relativeClientRect.width / 2 || relativeClientRect.x > relativeClientRect.width / 2; shouldBeClosedByVelocity.x = (relativeClientRect.x < 0 && velocity.x < -MINIMUM_PAN_GESTURE_FOR_TRIGGER_CLOSE) || (relativeClientRect.x > 0 && velocity.x > MINIMUM_PAN_GESTURE_FOR_TRIGGER_CLOSE); } if (placement.startsWith('top')) { shouldBeClosedThreshold.y = relativeClientRect.y < -relativeClientRect.height / 2; shouldBeClosedByVelocity.y = relativeClientRect.y < 0 ? velocity.y < -MINIMUM_PAN_GESTURE_FOR_TRIGGER_CLOSE : false; } return ( shouldBeClosedThreshold.x || shouldBeClosedByVelocity.x || shouldBeClosedThreshold.y || /* istanbul ignore next: подсвечивает жёлтым и пишет "branch not covered" */ shouldBeClosedByVelocity.y ); }