yet-another-react-lightbox
Advanced tools
Comparing version 1.9.6 to 1.10.0
export * from "./useContainerRect.js"; | ||
export * from "./useEnhancedEffect.js"; | ||
export * from "./useLayoutEffect.js"; | ||
export * from "./useLatest.js"; | ||
@@ -4,0 +4,0 @@ export * from "./useMotionPreference.js"; |
export * from "./useContainerRect.js"; | ||
export * from "./useEnhancedEffect.js"; | ||
export * from "./useLayoutEffect.js"; | ||
export * from "./useLatest.js"; | ||
@@ -4,0 +4,0 @@ export * from "./useMotionPreference.js"; |
import * as React from "react"; | ||
import { useEnhancedEffect } from "./useEnhancedEffect.js"; | ||
import { useLayoutEffect } from "./useLayoutEffect.js"; | ||
export const useMotionPreference = () => { | ||
const [reduceMotion, setReduceMotion] = React.useState(false); | ||
useEnhancedEffect(() => { | ||
useLayoutEffect(() => { | ||
var _a; | ||
const mediaQuery = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(prefers-reduced-motion: reduce)"); | ||
mediaQuery === null || mediaQuery === void 0 ? void 0 : mediaQuery.addEventListener("change", () => setReduceMotion(mediaQuery.matches)); | ||
setReduceMotion(mediaQuery === null || mediaQuery === void 0 ? void 0 : mediaQuery.matches); | ||
const listener = () => setReduceMotion(mediaQuery.matches); | ||
mediaQuery === null || mediaQuery === void 0 ? void 0 : mediaQuery.addEventListener("change", listener); | ||
return () => mediaQuery === null || mediaQuery === void 0 ? void 0 : mediaQuery.removeEventListener("change", listener); | ||
}, []); | ||
return reduceMotion; | ||
}; |
import * as React from "react"; | ||
import { useEnhancedEffect } from "./useEnhancedEffect.js"; | ||
import { useLayoutEffect } from "./useLayoutEffect.js"; | ||
export const useRTL = () => { | ||
const [isRTL, setIsRTL] = React.useState(false); | ||
useEnhancedEffect(() => { | ||
useLayoutEffect(() => { | ||
setIsRTL(window.getComputedStyle(window.document.documentElement).direction === "rtl"); | ||
@@ -7,0 +7,0 @@ }, []); |
@@ -5,3 +5,3 @@ import * as React from "react"; | ||
import { createModule } from "../config.js"; | ||
import { useContainerRect, useEnhancedEffect, useLatest, useRTL, useSensors, } from "../hooks/index.js"; | ||
import { useContainerRect, useLatest, useLayoutEffect, useRTL, useSensors, } from "../hooks/index.js"; | ||
import { useEvents, useTimeouts } from "../contexts/index.js"; | ||
@@ -33,3 +33,3 @@ const SWIPE_OFFSET_THRESHOLD = 30; | ||
refs.current.props = props; | ||
useEnhancedEffect(() => { | ||
useLayoutEffect(() => { | ||
const preventDefault = (event) => { | ||
@@ -70,3 +70,3 @@ if (Math.abs(event.deltaX) > Math.abs(event.deltaY) || event.ctrlKey) { | ||
}, [containerRef]); | ||
useEnhancedEffect(() => { | ||
useLayoutEffect(() => { | ||
updateSwipeOffset(); | ||
@@ -304,4 +304,4 @@ }); | ||
: null), | ||
}, role: "presentation", "aria-live": "polite", tabIndex: -1, ...registerSensors }, containerRect && (React.createElement(ControllerContext.Provider, { value: context }, children)))); | ||
}, ...(props.controller.aria ? { role: "presentation", "aria-live": "polite" } : null), tabIndex: -1, ...registerSensors }, containerRect && (React.createElement(ControllerContext.Provider, { value: context }, children)))); | ||
}; | ||
export const ControllerModule = createModule("controller", Controller); |
import * as React from "react"; | ||
import { createModule } from "../config.js"; | ||
import { cssClass, cssVar } from "../utils.js"; | ||
import { cssClass } from "../utils.js"; | ||
import { useLayoutEffect, useRTL } from "../hooks/index.js"; | ||
const noScroll = cssClass("no_scroll"); | ||
const padScrollbar = cssClass("pad_scrollbar"); | ||
const scrollbarPadding = cssVar("scrollbar_padding"); | ||
const noScrollPadding = cssClass("no_scroll_padding"); | ||
const isHTMLElement = (element) => "style" in element; | ||
const padScrollbar = (element, padding, rtl) => { | ||
const styles = window.getComputedStyle(element); | ||
const property = rtl ? "padding-left" : "padding-right"; | ||
const computedValue = rtl ? styles.paddingLeft : styles.paddingRight; | ||
const originalValue = element.style.getPropertyValue(property); | ||
element.style.setProperty(property, `${(parseInt(computedValue, 10) || 0) + padding}px`); | ||
return () => { | ||
if (originalValue) { | ||
element.style.setProperty(property, originalValue); | ||
} | ||
else { | ||
element.style.removeProperty(property); | ||
} | ||
}; | ||
}; | ||
export const NoScroll = ({ children }) => { | ||
React.useEffect(() => { | ||
const scrollbarWidth = Math.round(window.innerWidth - document.documentElement.clientWidth); | ||
if (scrollbarWidth > 1) { | ||
document.body.style.setProperty(scrollbarPadding, `${scrollbarWidth}px`); | ||
document.body.classList.add(padScrollbar); | ||
const rtl = useRTL(); | ||
useLayoutEffect(() => { | ||
const cleanup = []; | ||
const { body, documentElement } = document; | ||
const scrollbar = Math.round(window.innerWidth - documentElement.clientWidth); | ||
if (scrollbar > 0) { | ||
cleanup.push(padScrollbar(body, scrollbar, rtl)); | ||
const elements = body.getElementsByTagName("*"); | ||
for (let i = 0; i < elements.length; i += 1) { | ||
const element = elements[i]; | ||
if (isHTMLElement(element) && | ||
window.getComputedStyle(element).getPropertyValue("position") === "fixed" && | ||
!element.classList.contains(noScrollPadding)) { | ||
cleanup.push(padScrollbar(element, scrollbar, rtl)); | ||
} | ||
} | ||
} | ||
document.body.classList.add(noScroll); | ||
body.classList.add(noScroll); | ||
return () => { | ||
document.body.style.removeProperty(scrollbarPadding); | ||
document.body.classList.remove(noScroll, padScrollbar); | ||
body.classList.remove(noScroll); | ||
cleanup.forEach((clean) => clean()); | ||
}; | ||
}); | ||
}, [rtl]); | ||
return React.createElement(React.Fragment, null, children); | ||
}; | ||
export const NoScrollModule = createModule("no-scroll", NoScroll); |
@@ -5,23 +5,35 @@ import * as React from "react"; | ||
import { createModule } from "../config.js"; | ||
import { cssClass, cssVar } from "../utils.js"; | ||
import { useLatest } from "../hooks/useLatest.js"; | ||
import { clsx, cssClass, cssVar } from "../utils.js"; | ||
import { useLatest, useMotionPreference } from "../hooks/index.js"; | ||
import { useEvents, useTimeouts } from "../contexts/index.js"; | ||
const setAttribute = (element, attribute, value) => { | ||
const previousValue = element.getAttribute(attribute); | ||
element.setAttribute(attribute, value); | ||
return () => { | ||
if (previousValue) { | ||
element.setAttribute(attribute, previousValue); | ||
} | ||
else { | ||
element.removeAttribute(attribute); | ||
} | ||
}; | ||
}; | ||
export const Portal = ({ children, ...props }) => { | ||
const [mounted, setMounted] = React.useState(false); | ||
const [visible, setVisible] = React.useState(false); | ||
const cleanup = React.useRef([]); | ||
const latestProps = useLatest(props); | ||
const latestAnimationDuration = useLatest(!useMotionPreference() ? props.animation.fade : 0); | ||
const { setTimeout } = useTimeouts(); | ||
const { subscribe } = useEvents(); | ||
const [portal] = React.useState(() => { | ||
const div = document.createElement("div"); | ||
div.className = cssClass("portal"); | ||
const fadeAnimation = latestProps.current.animation.fade; | ||
if (fadeAnimation !== LightboxDefaultProps.animation.fade) { | ||
div.style.setProperty(cssVar("fade_animation_duration"), `${Math.round(fadeAnimation)}ms`); | ||
} | ||
return div; | ||
}); | ||
React.useEffect(() => { | ||
setMounted(true); | ||
return () => { | ||
setMounted(false); | ||
}; | ||
}, []); | ||
React.useEffect(() => subscribe("close", () => { | ||
var _a, _b; | ||
setVisible(false); | ||
(_b = (_a = latestProps.current.on).exiting) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
portal.classList.remove(cssClass("portal_open")); | ||
setTimeout(() => { | ||
@@ -31,23 +43,38 @@ var _a, _b; | ||
latestProps.current.close(); | ||
}, latestProps.current.animation.fade); | ||
}), [portal, setTimeout, subscribe, latestProps]); | ||
React.useEffect(() => { | ||
var _a, _b; | ||
document.body.appendChild(portal); | ||
setMounted(true); | ||
(_b = (_a = latestProps.current.on).entering) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
setTimeout(() => { | ||
portal.classList.add(cssClass("portal_open")); | ||
}, 0); | ||
setTimeout(() => { | ||
var _a, _b; | ||
(_b = (_a = latestProps.current.on).entered) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
}, latestProps.current.animation.fade); | ||
return () => { | ||
document.body.removeChild(portal); | ||
setMounted(false); | ||
}; | ||
}, [portal, setTimeout, latestProps]); | ||
return ReactDOM.createPortal(mounted ? children : null, portal); | ||
}, latestAnimationDuration.current); | ||
}), [setTimeout, subscribe, latestProps, latestAnimationDuration]); | ||
const handlePortalRef = React.useCallback((node) => { | ||
var _a, _b, _c, _d; | ||
if (node) { | ||
node.getBoundingClientRect(); | ||
setVisible(true); | ||
(_b = (_a = latestProps.current.on).entering) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
const elements = (_d = (_c = node.parentNode) === null || _c === void 0 ? void 0 : _c.children) !== null && _d !== void 0 ? _d : []; | ||
for (let i = 0; i < elements.length; i += 1) { | ||
const element = elements[i]; | ||
if (["TEMPLATE", "SCRIPT", "STYLE"].indexOf(element.tagName) === -1 && element !== node) { | ||
cleanup.current.push(setAttribute(element, "inert", "true")); | ||
cleanup.current.push(setAttribute(element, "aria-hidden", "true")); | ||
} | ||
} | ||
setTimeout(() => { | ||
var _a, _b; | ||
(_b = (_a = latestProps.current.on).entered) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
}, latestAnimationDuration.current); | ||
} | ||
else { | ||
cleanup.current.forEach((clean) => clean()); | ||
cleanup.current = []; | ||
} | ||
}, [setTimeout, latestProps, latestAnimationDuration]); | ||
return mounted | ||
? ReactDOM.createPortal(React.createElement("div", { ref: handlePortalRef, className: clsx(cssClass("portal"), cssClass("no_scroll_padding"), visible && cssClass("portal_open")), role: "presentation", "aria-live": "polite", ...(props.animation.fade !== LightboxDefaultProps.animation.fade | ||
? { | ||
style: { | ||
[cssVar("fade_animation_duration")]: `${Math.round(props.animation.fade)}ms`, | ||
}, | ||
} | ||
: null) }, children), document.body) | ||
: null; | ||
}; | ||
export const PortalModule = createModule("portal", Portal); |
@@ -6,3 +6,3 @@ import * as React from "react"; | ||
export const Inline = ({ augment, replace, remove }) => { | ||
augment(({ toolbar: { buttons, ...restToolbar }, open, close, controller: { focus, touchAction, ...restController }, ...restProps }) => ({ | ||
augment(({ toolbar: { buttons, ...restToolbar }, open, close, controller: { focus, aria, touchAction, ...restController }, ...restProps }) => ({ | ||
open: true, | ||
@@ -15,3 +15,3 @@ close: () => { }, | ||
inline: { style: { width: "100%", height: "100%" } }, | ||
controller: { focus: false, touchAction: "pan-y", ...restController }, | ||
controller: { focus: false, aria: true, touchAction: "pan-y", ...restController }, | ||
...restProps, | ||
@@ -18,0 +18,0 @@ })); |
import * as React from "react"; | ||
import { clsx, createIcon, createModule, cssClass, cssVar, ImageSlide, useEnhancedEffect, useEvents, useLatest, useMotionPreference, useRTL, } from "../core/index.js"; | ||
import { clsx, createIcon, createModule, cssClass, cssVar, ImageSlide, useLayoutEffect, useEvents, useLatest, useMotionPreference, useRTL, } from "../core/index.js"; | ||
const defaultThumbnailsProps = { | ||
@@ -98,3 +98,3 @@ position: "bottom", | ||
}), [container, subscribe]); | ||
useEnhancedEffect(() => { | ||
useLayoutEffect(() => { | ||
var _a, _b, _c, _d; | ||
@@ -101,0 +101,0 @@ if (track.current && state.offset) { |
import * as React from "react"; | ||
import { cleanup, clsx, createIcon, createModule, cssClass, IconButton, ImageSlide, label, makeUseContext, round, useContainerRect, useController, useEnhancedEffect, useEvents, useMotionPreference, } from "../core/index.js"; | ||
import { cleanup, clsx, createIcon, createModule, cssClass, IconButton, ImageSlide, label, makeUseContext, round, useContainerRect, useController, useLayoutEffect, useEvents, useMotionPreference, } from "../core/index.js"; | ||
const defaultZoomProps = { | ||
@@ -20,13 +20,13 @@ maxZoomPixelRatio: 1, | ||
const ZoomContextProvider = ({ children }) => { | ||
const [minZoom, setMinZoom] = React.useState(false); | ||
const [maxZoom, setMaxZoom] = React.useState(false); | ||
const [zoomSupported, setZoomSupported] = React.useState(false); | ||
const [isMinZoom, setIsMinZoom] = React.useState(false); | ||
const [isMaxZoom, setIsMaxZoom] = React.useState(false); | ||
const [isZoomSupported, setIsZoomSupported] = React.useState(false); | ||
const context = React.useMemo(() => ({ | ||
minZoom, | ||
maxZoom, | ||
zoomSupported, | ||
setMinZoom, | ||
setMaxZoom, | ||
setZoomSupported, | ||
}), [minZoom, maxZoom, zoomSupported]); | ||
isMinZoom, | ||
isMaxZoom, | ||
isZoomSupported, | ||
setIsMinZoom, | ||
setIsMaxZoom, | ||
setIsZoomSupported, | ||
}), [isMinZoom, isMaxZoom, isZoomSupported]); | ||
return React.createElement(ZoomContext.Provider, { value: context }, children); | ||
@@ -37,5 +37,5 @@ }; | ||
const wasFocused = React.useRef(false); | ||
const { zoomSupported, minZoom, maxZoom } = useZoom(); | ||
const { isMinZoom, isMaxZoom, isZoomSupported } = useZoom(); | ||
const { publish } = useEvents(); | ||
const disabled = !zoomSupported || (zoomIn ? maxZoom : minZoom); | ||
const disabled = !isZoomSupported || (zoomIn ? isMaxZoom : isMinZoom); | ||
const onClick = () => publish(zoomIn ? "zoom-in" : "zoom-out"); | ||
@@ -131,3 +131,3 @@ const onFocus = React.useCallback(() => { | ||
const zoomProps = { ...defaultZoomProps, ...originalZoomProps }; | ||
const { setMinZoom: currentSetMinZoom, setMaxZoom: currentSetMaxZoom } = useZoom(); | ||
const { isMinZoom, isMaxZoom, setIsMinZoom, setIsMaxZoom } = useZoom(); | ||
const { setContainerRef, containerRef: currentContainerRef, containerRect: currentContainerRect, } = useContainerRect(); | ||
@@ -151,4 +151,2 @@ const { subscribeSensors, containerRef: currentControllerRef, containerRect: currentControllerRect, } = useController(); | ||
reduceMotion: currentReduceMotion, | ||
setMinZoom: currentSetMinZoom, | ||
setMaxZoom: currentSetMaxZoom, | ||
activePointers: [], | ||
@@ -166,4 +164,2 @@ lastPointerDown: 0, | ||
refs.current.reduceMotion = currentReduceMotion; | ||
refs.current.setMinZoom = currentSetMinZoom; | ||
refs.current.setMaxZoom = currentSetMaxZoom; | ||
refs.current.zoomAnimationDuration = animation.zoom; | ||
@@ -184,3 +180,3 @@ refs.current.zoomProps = zoomProps; | ||
}, []); | ||
useEnhancedEffect(() => { | ||
useLayoutEffect(() => { | ||
if (refs.current.state.zoom > 1) { | ||
@@ -190,3 +186,3 @@ changeOffsets(); | ||
}, [currentControllerRect.width, currentControllerRect.height, changeOffsets]); | ||
useEnhancedEffect(() => { | ||
useLayoutEffect(() => { | ||
var _a, _b; | ||
@@ -214,9 +210,8 @@ const { current } = refs; | ||
}, [state.zoom, state.offsetX, state.offsetY]); | ||
useEnhancedEffect(() => { | ||
useLayoutEffect(() => { | ||
if (offset === 0) { | ||
const { setMinZoom, setMaxZoom } = refs.current; | ||
const resetZoom = () => { | ||
setState({ zoom: 1, offsetX: 0, offsetY: 0 }); | ||
setMinZoom(true); | ||
setMaxZoom(false); | ||
setIsMinZoom(true); | ||
setIsMaxZoom(false); | ||
}; | ||
@@ -229,10 +224,15 @@ resetZoom(); | ||
return () => { }; | ||
}, [offset]); | ||
useEnhancedEffect(() => { | ||
}, [offset, setIsMinZoom, setIsMaxZoom]); | ||
useLayoutEffect(() => { | ||
if (offset === 0) { | ||
const { setMinZoom, setMaxZoom } = refs.current; | ||
setMinZoom(state.zoom <= 1); | ||
setMaxZoom(state.zoom >= currentMaxZoom); | ||
const newMinZoom = state.zoom <= 1; | ||
if (newMinZoom !== isMinZoom) { | ||
setIsMinZoom(newMinZoom); | ||
} | ||
const newMaxZoom = state.zoom >= currentMaxZoom; | ||
if (newMaxZoom !== isMaxZoom) { | ||
setIsMaxZoom(newMaxZoom); | ||
} | ||
} | ||
}, [offset, state.zoom, currentMaxZoom]); | ||
}, [offset, state.zoom, currentMaxZoom, isMinZoom, isMaxZoom, setIsMinZoom, setIsMaxZoom]); | ||
const changeZoom = React.useCallback((value, rapid, dx, dy) => { | ||
@@ -393,10 +393,10 @@ const { current } = refs; | ||
var _a; | ||
const { setZoomSupported } = useZoom(); | ||
const { setIsZoomSupported, isZoomSupported } = useZoom(); | ||
const imageSlide = !("type" in slide); | ||
const zoomSupported = imageSlide && ("srcSet" in slide || ("width" in slide && "height" in slide)); | ||
React.useEffect(() => { | ||
if (offset === 0) { | ||
setZoomSupported(zoomSupported); | ||
if (offset === 0 && zoomSupported !== isZoomSupported) { | ||
setIsZoomSupported(zoomSupported); | ||
} | ||
}, [offset, zoomSupported, setZoomSupported]); | ||
}, [offset, zoomSupported, isZoomSupported, setIsZoomSupported]); | ||
if (zoomSupported) { | ||
@@ -403,0 +403,0 @@ return (React.createElement(ZoomContainer, { slide: slide, offset: offset, rect: rect, render: render, carousel: carousel, animation: animation, zoom: zoom })); |
@@ -62,2 +62,4 @@ import * as React from "react"; | ||
touchAction: "none" | "pan-y"; | ||
/** if `true`, set ARIA attributes on the controller div */ | ||
aria: boolean; | ||
} | ||
@@ -64,0 +66,0 @@ /** Custom render functions. */ |
@@ -23,2 +23,3 @@ export const LightboxDefaultProps = { | ||
focus: true, | ||
aria: false, | ||
touchAction: "none", | ||
@@ -25,0 +26,0 @@ }, |
{ | ||
"name": "yet-another-react-lightbox", | ||
"version": "1.9.6", | ||
"version": "1.10.0", | ||
"description": "Modern React lightbox component", | ||
@@ -99,10 +99,10 @@ "author": "Igor Danchenko", | ||
"@testing-library/react": "^13.3.0", | ||
"@testing-library/user-event": "^14.2.1", | ||
"@types/jest": "^28.1.3", | ||
"@types/react": "^18.0.14", | ||
"@types/react-dom": "^18.0.5", | ||
"@typescript-eslint/eslint-plugin": "^5.30.0", | ||
"@typescript-eslint/parser": "^5.30.0", | ||
"@testing-library/user-event": "^14.2.3", | ||
"@types/jest": "^28.1.5", | ||
"@types/react": "^18.0.15", | ||
"@types/react-dom": "^18.0.6", | ||
"@typescript-eslint/eslint-plugin": "^5.30.6", | ||
"@typescript-eslint/parser": "^5.30.6", | ||
"autoprefixer": "^10.4.7", | ||
"eslint": "^8.18.0", | ||
"eslint": "^8.19.0", | ||
"eslint-config-airbnb": "^19.0.4", | ||
@@ -117,4 +117,4 @@ "eslint-config-airbnb-typescript": "^17.0.0", | ||
"husky": "^8.0.1", | ||
"jest": "^28.1.2", | ||
"jest-environment-jsdom": "^28.1.2", | ||
"jest": "^28.1.3", | ||
"jest-environment-jsdom": "^28.1.3", | ||
"lint-staged": "^13.0.3", | ||
@@ -129,3 +129,3 @@ "npm-run-all": "^4.1.5", | ||
"sass": "^1.53.0", | ||
"ts-jest": "^28.0.5", | ||
"ts-jest": "^28.0.6", | ||
"typescript": "^4.7.4" | ||
@@ -132,0 +132,0 @@ }, |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
157017
3238