UNPKG

solid-awesome-hooks

Version:
293 lines (275 loc) 9.3 kB
'use strict'; var solidJs = require('solid-js'); const useClickOutside = (callback, options) => { const [elementRef, setElementRef] = solidJs.createSignal(); const listenToEvent = () => options?.enabled() ?? true; solidJs.createEffect(() => { const element = elementRef(); if (!listenToEvent() || !element) return; const handleClick = e => { if (!e.composedPath().includes(element)) callback(e); }; document.addEventListener("click", handleClick); solidJs.onCleanup(() => { document.removeEventListener("click", handleClick); }); }); return setElementRef; }; const getDistance = (event1, event2) => Math.hypot(event2.pageX - event1.pageX, event2.pageY - event1.pageY); const DEFAULT_OPTIONS$2 = { preventTouchMoveEvent: true }; const usePinchZoom = ({ onZoomIn, onZoomOut, options = DEFAULT_OPTIONS$2 }) => { const [elementRef, setElementRef] = solidJs.createSignal(); solidJs.createEffect(() => { const element = elementRef(); if (!element) return; let prevDistance = 0; const handleTouchStart = e => { if (e.touches.length === 2) { prevDistance = getDistance(e.touches[1], e.touches[0]); } }; const handleTouchMove = e => { if (e.touches.length === 2) { if (options.preventTouchMoveEvent) e.preventDefault(); // Calculate the distance between the two pointers const currentPointDistance = getDistance(e.touches[1], e.touches[0]); let shouldUpdatePrevDistance = false; const distanceGrowthPX = Math.abs(currentPointDistance - prevDistance); if (currentPointDistance - prevDistance > 0) { onZoomIn?.(distanceGrowthPX); shouldUpdatePrevDistance = true; } if (prevDistance - currentPointDistance > 0) { onZoomOut?.(distanceGrowthPX); shouldUpdatePrevDistance = true; } if (shouldUpdatePrevDistance) { prevDistance = currentPointDistance; } } }; element.addEventListener("touchstart", handleTouchStart); element.addEventListener("touchmove", handleTouchMove); solidJs.onCleanup(() => { element.removeEventListener("touchstart", handleTouchStart); element.removeEventListener("touchmove", handleTouchMove); }); }); return setElementRef; }; /** * Preloads modules imported with `lazy` when the browser is idle one by one * @param lazyModules */ const useModulePreloader = lazyModules => { const [isLoadingStarted, setLoadingStarted] = solidJs.createSignal(false); const [moduleIndex, setModuleIndex] = solidJs.createSignal(0); const deferredModuleIndex = solidJs.createDeferred(moduleIndex); const deferredStarted = solidJs.createDeferred(isLoadingStarted); solidJs.onMount(() => setLoadingStarted(true)); solidJs.createEffect(() => { if (!deferredStarted()) return; solidJs.createEffect(() => { if (deferredModuleIndex() >= lazyModules.length) return; const module = lazyModules[deferredModuleIndex()]; module.preload(); setModuleIndex(index => index + 1); }); }); }; const DEFAULT_OPTIONS$1 = { storage: localStorage, saveWhenIdle: true, defer: true, clearOnEmpty: false }; /** * * @param key - key name in storage * @param data - Reactive accessor to the data * @param options */ const useSaveToStorage = (key, data, options) => { const resolvedOptions = Object.assign({}, DEFAULT_OPTIONS$1, options); const dataToSave = resolvedOptions.saveWhenIdle ? solidJs.createDeferred(data) : data; solidJs.createEffect(solidJs.on(dataToSave, rawData => { if (resolvedOptions.clearOnEmpty && (rawData === null || rawData === undefined)) resolvedOptions.storage.removeItem(key);else resolvedOptions.storage.setItem(key, typeof rawData === "object" ? JSON.stringify(rawData) : String(rawData)); }, { defer: resolvedOptions.defer })); }; /** * Returns AbortController instance * Can be useful inside createResource * If there's no owner scope abort() won't be called */ const useAbortController = ({ reason, fallbackOwner } = {}) => { const controller = new AbortController(); const owner = solidJs.getOwner() ?? fallbackOwner; if (owner) { // register cleanup with owner solidJs.runWithOwner(owner, () => { solidJs.onCleanup(() => { solidJs.runWithOwner(owner, () => controller.abort(reason)); }); }); } return controller; }; const useAsyncAction = () => { const [actionState, setActionState] = solidJs.createSignal("ready"); const [errorMessage, setErrorMessage] = solidJs.createSignal(); const tryAsync = async action => { solidJs.batch(() => { setActionState("pending"); setErrorMessage(undefined); }); try { const data = await action(); setActionState("resolved"); return data; } catch (error) { setActionState("errored"); throw error; } }; const reset = () => solidJs.batch(() => { setActionState("ready"); setErrorMessage(undefined); }); return { setErrorMessage, try: tryAsync, state: actionState, errorMessage, reset }; }; /** * Same as solid's useContext, but it throws an error if there's no context value * @param context * @param errorMessage */ const useContextStrict = (context, errorMessage = `Cannot get ${context} context`) => { const currentContext = solidJs.useContext(context); if (!currentContext) throw new Error(errorMessage); return currentContext; }; const useVisibleState = initialState => { const [isOpen, setOpen] = solidJs.createSignal(Boolean(initialState)); const hide = () => setOpen(false); const reveal = () => setOpen(true); const withAction = (action, callback) => (...args) => { const result = callback?.(...args); if (action === "hide") hide();else reveal(); return result; }; return { isOpen, setOpen, hide, reveal, /** A useful wrapper which adds `reveal` or `hide` action for wrapping function */ withAction }; }; const useScrollTo = ({ scrollTrigger, defer = true, ...scrollOptions }) => { const [scrollableElement, setScrollableElement] = solidJs.createSignal(); solidJs.createEffect(solidJs.on([scrollableElement, scrollTrigger], ([scrollableElementRef]) => { scrollableElementRef?.scrollTo(scrollOptions); }, { defer })); return setScrollableElement; }; let SortState = /*#__PURE__*/function (SortState) { SortState[SortState["ASCENDING"] = 1] = "ASCENDING"; SortState[SortState["DESCENDING"] = -1] = "DESCENDING"; return SortState; }({}); const useSortState = (initialSortState = SortState.ASCENDING) => { const [order, setOrder] = solidJs.createSignal(initialSortState); const toggleOrder = () => setOrder(state => state === SortState.ASCENDING ? SortState.DESCENDING : SortState.ASCENDING); const isAscending = solidJs.createMemo(() => order() === SortState.ASCENDING); const isDescending = solidJs.createMemo(() => order() === SortState.DESCENDING); const resetOrder = () => setOrder(initialSortState); return { order, setOrder, isAscending, isDescending, /** Switches order to another one */ toggleOrder, /** Resets sort order to the initial */ resetOrder }; }; const DEFAULT_OPTIONS = { timeInterval: 3000, enabled: () => true, callLimit: 10 }; /** * * @param readyTrigger Reactive signal that tells that the poll function can now be scheduled * @param poll Function * @param options {UsePollingOptions} */ const usePolling = (readyTrigger, poll, options) => { const { timeInterval, enabled, owner = solidJs.getOwner(), callLimit } = Object.assign({}, DEFAULT_OPTIONS, options); const pollWithOwner = () => solidJs.runWithOwner(owner, () => poll()); let remainingCalls = callLimit; // This effect is triggered when the data signal changes // Thus, we don't rely on slow network solidJs.createEffect(solidJs.on([readyTrigger, enabled], ([_, isEnabled]) => { if (remainingCalls <= 0) return; if (!isEnabled) return; const timer = setTimeout(() => { pollWithOwner(); remainingCalls -= 1; }, timeInterval); solidJs.onCleanup(() => clearTimeout(timer)); })); }; /** * This hook may be used to sync your state (signals or stores) with props. * Basically it's just a shorthand for createComputed(on(source, setter, { defer })) * @param source Reactive signal * @param setter A function which runs immediately when source changes * @param defer A boolean value which is passed to on's defer option. Default - true. */ const useSyncState = (source, setter, defer = true) => solidJs.createComputed(solidJs.on(source, setter, { defer })); exports.useAbortController = useAbortController; exports.useAsyncAction = useAsyncAction; exports.useClickOutside = useClickOutside; exports.useContextStrict = useContextStrict; exports.useModulePreloader = useModulePreloader; exports.usePinchZoom = usePinchZoom; exports.usePolling = usePolling; exports.useSaveToStorage = useSaveToStorage; exports.useScrollTo = useScrollTo; exports.useSortState = useSortState; exports.useSyncState = useSyncState; exports.useVisibleState = useVisibleState; //# sourceMappingURL=index.common.js.map