solid-awesome-hooks
Version:
A collection of awesome hooks for solid-js
293 lines (275 loc) • 9.3 kB
JavaScript
;
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