@pluralsight/react-utils
Advanced tools
Comparing version 0.7.1-next-f09ded to 0.7.1-next-f24c2e
@@ -112,7 +112,2 @@ import { ChangeEvent } from 'react'; | ||
export declare function useTheme<T extends string>(initialTheme?: CustomThemes<T>): { | ||
theme: CustomThemes<T>; | ||
updateTheme: (theme: CustomThemes<T>) => void; | ||
}; | ||
export { } |
@@ -1,7 +0,35 @@ | ||
import { | ||
createPreloadedImgResource, | ||
createResource, | ||
resourceCache | ||
} from "../chunk-D4SRYCOP.js"; | ||
import "../chunk-MRY5H4GP.js"; | ||
// src/helpers/createResource.ts | ||
import { loadImage, preloadImgResource } from "./loaders.js"; | ||
var resourceCache = /* @__PURE__ */ new Map(); | ||
function createResource(promise) { | ||
let status = "pending"; | ||
let result = promise.then( | ||
(resolved) => { | ||
status = "success"; | ||
result = resolved; | ||
}, | ||
(rejected) => { | ||
status = "error"; | ||
result = rejected; | ||
} | ||
); | ||
return { | ||
read: () => { | ||
switch (status) { | ||
case "error": | ||
throw result; | ||
case "pending": | ||
case "success": | ||
return result; | ||
default: | ||
throw new Error("This should be impossible."); | ||
} | ||
} | ||
}; | ||
} | ||
function createPreloadedImgResource(imgOptions) { | ||
const data = createResource(preloadImgResource(imgOptions)); | ||
const img = createResource(loadImage(imgOptions)); | ||
return { data, img }; | ||
} | ||
export { | ||
@@ -8,0 +36,0 @@ createPreloadedImgResource, |
@@ -1,6 +0,41 @@ | ||
import { | ||
loadImage, | ||
preloadImgResource, | ||
preloadResource | ||
} from "../chunk-MRY5H4GP.js"; | ||
// src/helpers/loaders.ts | ||
function preloadResource(resourceOptions) { | ||
return new Promise((resolve, reject) => { | ||
const link = document.createElement("link"); | ||
link.rel = "preload"; | ||
link.as = resourceOptions.as; | ||
link.media = resourceOptions.media ?? "all"; | ||
link.href = resourceOptions.href; | ||
link.onload = resolve; | ||
link.onerror = reject; | ||
document.body.appendChild(link); | ||
}); | ||
} | ||
function preloadImgResource(imgOptions) { | ||
const { srcSet } = imgOptions; | ||
return new Promise((resolve, reject) => { | ||
const link = document.createElement("link"); | ||
if (srcSet) { | ||
link.imageSrcset = srcSet; | ||
} | ||
link.rel = "preload"; | ||
link.as = "image"; | ||
link.href = imgOptions.src ?? ""; | ||
link.onload = resolve; | ||
link.onerror = reject; | ||
document.body.appendChild(link); | ||
}); | ||
} | ||
function loadImage(imgOptions) { | ||
const { src, srcSet } = imgOptions; | ||
return new Promise((resolve, reject) => { | ||
const img = document.createElement("img"); | ||
if (srcSet) { | ||
img.srcset = srcSet; | ||
} | ||
img.src = src ?? ""; | ||
img.onload = () => resolve(imgOptions); | ||
img.onerror = reject; | ||
}); | ||
} | ||
export { | ||
@@ -7,0 +42,0 @@ loadImage, |
@@ -1,7 +0,30 @@ | ||
import { | ||
THEME_KEY, | ||
getAppliedTheme, | ||
getCachedTheme, | ||
setCachedTheme | ||
} from "../chunk-SO6SAJGK.js"; | ||
// src/helpers/themeHelpers.ts | ||
var THEME_KEY = "pandoTheme"; | ||
var DARK = "dark"; | ||
var LIGHT = "light"; | ||
var SYSTEM = "system"; | ||
function getAppliedTheme(userPreference) { | ||
const choseSystem = userPreference === SYSTEM; | ||
if (userPreference === LIGHT) { | ||
return LIGHT; | ||
} | ||
if (userPreference === DARK) { | ||
return DARK; | ||
} | ||
if (choseSystem && (window == null ? void 0 : window.matchMedia("(prefers-color-scheme: light)").matches)) { | ||
return LIGHT; | ||
} | ||
if (choseSystem && (window == null ? void 0 : window.matchMedia("(prefers-color-scheme: dark)").matches)) { | ||
return DARK; | ||
} | ||
return choseSystem ? DARK : userPreference; | ||
} | ||
function getCachedTheme() { | ||
const cachedTheme = (window == null ? void 0 : window.localStorage.getItem(THEME_KEY)) ?? DARK; | ||
return cachedTheme; | ||
} | ||
function setCachedTheme(theme) { | ||
document == null ? void 0 : document.documentElement.setAttribute("data-theme", getAppliedTheme(theme)); | ||
window == null ? void 0 : window.localStorage.setItem(THEME_KEY, theme); | ||
} | ||
export { | ||
@@ -8,0 +31,0 @@ THEME_KEY, |
"use client"; | ||
// src/hooks/useAutoFormatDate.ts | ||
import { | ||
useAutoFormatDate | ||
} from "../chunk-MJ3R5LPV.js"; | ||
useCallback, | ||
useState, | ||
useMemo | ||
} from "react"; | ||
var DELIMITER = "/"; | ||
function useAutoFormatDate(options) { | ||
const { pattern, value } = defaultAutoFormatOptions(options); | ||
const [formattedDate, setFormattedDate] = useState(value); | ||
const blocks = useMemo(() => getBlocks(pattern), [pattern]); | ||
const template = useMemo( | ||
() => getTemplate(pattern, blocks), | ||
[pattern, blocks] | ||
); | ||
const handleDateChange = useCallback( | ||
(e) => { | ||
return setFormattedDate((prev) => { | ||
const userInput = e.target.value; | ||
const startingCursor = e.target.selectionStart ?? userInput.length; | ||
const { formattedDate: formattedDate2, cursorPosition } = formatDateInput({ | ||
blocks, | ||
cursorPosition: startingCursor, | ||
pattern, | ||
value: userInput, | ||
prevValue: prev | ||
}); | ||
if (startingCursor !== userInput.length) { | ||
setCursorPosition(e.target, cursorPosition); | ||
} | ||
return formattedDate2; | ||
}); | ||
}, | ||
[blocks, pattern] | ||
); | ||
const handleDateBlur = useCallback( | ||
(e) => { | ||
return setFormattedDate( | ||
(prev) => formatDateInput({ | ||
blocks, | ||
cursorPosition: e.target.value.length, | ||
pattern, | ||
prevValue: prev, | ||
value: e.target.value | ||
}).formattedDate | ||
); | ||
}, | ||
[blocks, pattern] | ||
); | ||
return useMemo( | ||
() => ({ | ||
onBlur: handleDateBlur, | ||
onChange: handleDateChange, | ||
placeholder: template, | ||
maxLength: template.length, | ||
value: formattedDate | ||
}), | ||
[handleDateBlur, handleDateChange, template, formattedDate] | ||
); | ||
} | ||
function defaultAutoFormatOptions(options) { | ||
return { | ||
pattern: (options == null ? void 0 : options.pattern) ?? ["m", "d", "Y"], | ||
value: (options == null ? void 0 : options.value) ?? "" | ||
}; | ||
} | ||
function setCursorPosition(element, position) { | ||
requestAnimationFrame(() => element.setSelectionRange(position, position)); | ||
} | ||
function formatDateInput(options) { | ||
const { blocks, cursorPosition, pattern, prevValue, value } = options; | ||
const template = getTemplate(pattern, blocks); | ||
if ((prevValue == null ? void 0 : prevValue.length) > value.length) { | ||
return { | ||
formattedDate: value, | ||
cursorPosition | ||
}; | ||
} | ||
if (value.length > template.length || getCharCount(DELIMITER, value) > pattern.length - 1) { | ||
return { | ||
formattedDate: prevValue, | ||
cursorPosition: cursorPosition - (value.length - (prevValue == null ? void 0 : prevValue.length)) | ||
}; | ||
} | ||
const { formattedDateParts, cursor } = getFormattedDateParts(options); | ||
if (Object.values(formattedDateParts).join(DELIMITER).length === template.length) { | ||
const correctedDateParts = getCorrectedDateParts(formattedDateParts); | ||
return { | ||
formattedDate: combineDateParts(correctedDateParts, pattern), | ||
cursorPosition: cursor | ||
}; | ||
} | ||
return { | ||
formattedDate: combineDateParts(formattedDateParts, pattern), | ||
cursorPosition: cursor | ||
}; | ||
} | ||
function getCharCount(char, str) { | ||
return (str.match(new RegExp(char, "g")) || []).length; | ||
} | ||
function strToInt(value) { | ||
return parseInt(value, 10); | ||
} | ||
function getFormattedDateParts(options) { | ||
const { blocks, cursorPosition, pattern, value } = options; | ||
const chars = value.split(""); | ||
let cursor = cursorPosition; | ||
let currentBlock = pattern[0]; | ||
const dateParts = { | ||
d: "", | ||
m: "", | ||
Y: "" | ||
}; | ||
for (let i = 0; i < chars.length && currentBlock !== null; i += 1) { | ||
if (chars[i] !== DELIMITER) { | ||
dateParts[currentBlock] += chars[i]; | ||
} | ||
const formatted = formatDateBlock( | ||
currentBlock, | ||
dateParts[currentBlock], | ||
chars[i] === DELIMITER && cursorPosition > i | ||
); | ||
cursor += getCursorOffset( | ||
cursorPosition, | ||
i, | ||
formatted.length - dateParts[currentBlock].length | ||
); | ||
dateParts[currentBlock] = formatted; | ||
if (isDatePartComplete( | ||
dateParts[currentBlock], | ||
currentBlock, | ||
blocks, | ||
pattern | ||
) || isBlockEndedByDelimiter( | ||
chars[i], | ||
dateParts[currentBlock], | ||
getNextBlock(currentBlock, pattern) | ||
)) { | ||
currentBlock = getNextBlock(currentBlock, pattern); | ||
} | ||
} | ||
return { | ||
formattedDateParts: dateParts, | ||
cursor | ||
}; | ||
} | ||
function getCursorOffset(cursor, index, value) { | ||
if (cursor > index) { | ||
return value; | ||
} | ||
return 0; | ||
} | ||
function isDatePartComplete(datePart, block, blocks, pattern) { | ||
return datePart.length === blocks[pattern.indexOf(block)]; | ||
} | ||
function isBlockEndedByDelimiter(currentChar, datePart, nextBlock) { | ||
return currentChar === DELIMITER && datePart.length && nextBlock !== null; | ||
} | ||
function getNextBlock(current, pattern) { | ||
return pattern[pattern.indexOf(current) + 1] ?? null; | ||
} | ||
function formatDateBlock(blockId, value, complete) { | ||
const cleanValue = getSanitizedNumericString(value); | ||
if (!cleanValue) { | ||
return cleanValue; | ||
} | ||
switch (blockId) { | ||
case "m": | ||
return formatMonth(cleanValue, complete); | ||
case "d": | ||
return formatDay(cleanValue, complete); | ||
default: | ||
return cleanValue; | ||
} | ||
} | ||
function formatDay(value, complete) { | ||
const numericValue = parseInt(value, 10); | ||
if (value.length === 1) { | ||
if (complete || numericValue * 10 > 31) { | ||
return `0${Math.max(numericValue, 1)}`; | ||
} | ||
} else { | ||
if (numericValue > 31) { | ||
return "31"; | ||
} else if (numericValue < 1) { | ||
return "01"; | ||
} | ||
} | ||
return value; | ||
} | ||
function formatMonth(value, complete) { | ||
const numericValue = parseInt(value, 10); | ||
if (value.length === 1) { | ||
if (complete || numericValue * 10 > 12) { | ||
return `0${Math.max(numericValue, 1)}`; | ||
} | ||
} else { | ||
if (numericValue > 12) { | ||
return "12"; | ||
} else if (numericValue < 1) { | ||
return "01"; | ||
} | ||
} | ||
return value; | ||
} | ||
function getSanitizedNumericString(value) { | ||
return value.replace(/[^0-9]/g, ""); | ||
} | ||
function combineDateParts(dateParts, pattern) { | ||
const blocks = getBlocks(pattern); | ||
return pattern.map((block) => { | ||
const nextBlock = getNextBlock(block, pattern); | ||
const blockIdx = pattern.indexOf(block); | ||
const insertDelimiter = nextBlock && (dateParts[nextBlock] || dateParts[block].length === blocks[blockIdx]); | ||
if (insertDelimiter) { | ||
return dateParts[block] + DELIMITER; | ||
} else { | ||
return dateParts[block]; | ||
} | ||
}).join(""); | ||
} | ||
function getBlocks(pattern) { | ||
const blocks = []; | ||
pattern.forEach(function(value) { | ||
if (value === "Y") { | ||
blocks.push(4); | ||
} else { | ||
blocks.push(2); | ||
} | ||
}); | ||
return blocks; | ||
} | ||
function getTemplate(pattern, blocks) { | ||
return pattern.map((letter, idx) => { | ||
const count = blocks[idx]; | ||
return letter.repeat(count).toUpperCase(); | ||
}).join("/"); | ||
} | ||
function getCorrectedDateParts(dateParts) { | ||
const correctedDay = correctDayForMonth( | ||
strToInt(dateParts.d), | ||
strToInt(dateParts.m), | ||
strToInt(dateParts.Y) | ||
); | ||
return { | ||
d: formatDay(correctedDay.toString(), true), | ||
m: dateParts.m, | ||
Y: dateParts.Y | ||
}; | ||
} | ||
function correctDayForMonth(day, month, year) { | ||
day = Math.min(day, 31); | ||
month = Math.min(month, 12); | ||
year = parseInt(String(year || 0), 10); | ||
if (month < 7 && month % 2 === 0 || month > 8 && month % 2 === 1) { | ||
const leapDay = isLeapYear(year) ? 29 : 28; | ||
return Math.min(day, month === 2 ? leapDay : 30); | ||
} | ||
return day; | ||
} | ||
function isLeapYear(year) { | ||
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; | ||
} | ||
export { | ||
@@ -6,0 +267,0 @@ useAutoFormatDate |
"use client"; | ||
import { | ||
useEscToClose | ||
} from "../chunk-VYA2NVJ4.js"; | ||
// src/hooks/useEscToClose.ts | ||
import { useEffect } from "react"; | ||
function useEscToClose(onClose) { | ||
useEffect(() => { | ||
function handleEscClose(event) { | ||
if (event.key === "Escape") { | ||
event.stopPropagation(); | ||
event.preventDefault(); | ||
onClose(); | ||
} | ||
} | ||
window.addEventListener("keydown", handleEscClose, false); | ||
return () => { | ||
window.removeEventListener("keydown", handleEscClose, false); | ||
}; | ||
}, [onClose]); | ||
return null; | ||
} | ||
export { | ||
@@ -6,0 +22,0 @@ useEscToClose |
"use client"; | ||
// src/hooks/useFocusTrap.ts | ||
import { | ||
useFocusTrap | ||
} from "../chunk-2AC32EVF.js"; | ||
useCallback, | ||
useEffect | ||
} from "react"; | ||
function useFocusTrap(dialogRef) { | ||
const selectorList = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; | ||
const getFocusItems = useCallback(() => { | ||
var _a; | ||
const focusableItems = ((_a = dialogRef == null ? void 0 : dialogRef.current) == null ? void 0 : _a.querySelectorAll(selectorList)) ?? []; | ||
return { | ||
firstItem: focusableItems[0], | ||
lastItem: focusableItems[focusableItems.length - 1] | ||
}; | ||
}, [dialogRef]); | ||
const handleFocus = useCallback( | ||
(event) => { | ||
const { activeElement } = document; | ||
const { firstItem, lastItem } = getFocusItems(); | ||
if (event.key !== "Tab") { | ||
return; | ||
} | ||
if (event.shiftKey) { | ||
if (activeElement === firstItem) { | ||
event.preventDefault(); | ||
lastItem.focus(); | ||
} | ||
} else { | ||
if (activeElement === lastItem) { | ||
event.preventDefault(); | ||
firstItem.focus(); | ||
} | ||
} | ||
}, | ||
[getFocusItems] | ||
); | ||
const handleInitFocusTrap = useCallback(() => { | ||
if ((dialogRef == null ? void 0 : dialogRef.current) != null) { | ||
const { firstItem } = getFocusItems(); | ||
if (document.activeElement !== firstItem) { | ||
firstItem == null ? void 0 : firstItem.focus(); | ||
} | ||
} | ||
}, [dialogRef, getFocusItems]); | ||
useEffect(() => { | ||
handleInitFocusTrap(); | ||
}, [handleInitFocusTrap]); | ||
return { | ||
onKeyDown: handleFocus | ||
}; | ||
} | ||
export { | ||
@@ -6,0 +55,0 @@ useFocusTrap |
"use client"; | ||
import { | ||
useIsIndeterminate | ||
} from "../chunk-HWPTZ2IF.js"; | ||
// src/hooks/useIsIndeterminate.ts | ||
import { useMemo } from "react"; | ||
function useIsIndeterminate(checkboxOptions) { | ||
const checkedItems = Object.keys(checkboxOptions).map( | ||
(optionItem) => checkboxOptions[optionItem] | ||
); | ||
const allChecked = checkedItems.every(Boolean); | ||
return useMemo( | ||
() => (checkedItems.some(Boolean) && !allChecked) ?? false, | ||
[allChecked, checkedItems] | ||
); | ||
} | ||
export { | ||
@@ -6,0 +16,0 @@ useIsIndeterminate |
"use client"; | ||
// src/hooks/usePreloadedImg.ts | ||
import { useEffect, useState, useTransition } from "react"; | ||
import { | ||
usePreloadedImg | ||
} from "../chunk-S6QQHARX.js"; | ||
import "../chunk-D4SRYCOP.js"; | ||
import "../chunk-MRY5H4GP.js"; | ||
resourceCache, | ||
createPreloadedImgResource | ||
} from "../helpers/createResource.js"; | ||
function getPreloadedImgResource(imgOptions) { | ||
const { src } = imgOptions; | ||
let resource = resourceCache.get(src); | ||
if (!resource) { | ||
resource = createPreloadedImgResource(imgOptions); | ||
resourceCache.set(src, resource); | ||
} | ||
return resource; | ||
} | ||
function usePreloadedImg(imgOptions) { | ||
const { src, srcSet } = imgOptions; | ||
const [, startTransition] = useTransition(); | ||
const [resource, setResource] = useState(null); | ||
useEffect(() => { | ||
if (resource) { | ||
return; | ||
} | ||
startTransition(() => { | ||
setResource(getPreloadedImgResource({ src, srcSet })); | ||
}); | ||
}, [resource, src, srcSet, startTransition]); | ||
return resource; | ||
} | ||
export { | ||
@@ -8,0 +33,0 @@ usePreloadedImg |
@@ -1,22 +0,8 @@ | ||
import { | ||
useEscToClose | ||
} from "./chunk-VYA2NVJ4.js"; | ||
import { | ||
useFocusTrap | ||
} from "./chunk-2AC32EVF.js"; | ||
import { | ||
useIsIndeterminate | ||
} from "./chunk-HWPTZ2IF.js"; | ||
import { | ||
usePreloadedImg | ||
} from "./chunk-S6QQHARX.js"; | ||
import "./chunk-D4SRYCOP.js"; | ||
import "./chunk-MRY5H4GP.js"; | ||
import { | ||
getCachedTheme, | ||
setCachedTheme | ||
} from "./chunk-SO6SAJGK.js"; | ||
import { | ||
useAutoFormatDate | ||
} from "./chunk-MJ3R5LPV.js"; | ||
// src/index.ts | ||
import { getCachedTheme, setCachedTheme } from "./helpers/themeHelpers.js"; | ||
import { useAutoFormatDate } from "./hooks/useAutoFormatDate.js"; | ||
import { useEscToClose } from "./hooks/useEscToClose.js"; | ||
import { useFocusTrap } from "./hooks/useFocusTrap.js"; | ||
import { useIsIndeterminate } from "./hooks/useIsIndeterminate.js"; | ||
import { usePreloadedImg } from "./hooks/usePreloadedImg.js"; | ||
export { | ||
@@ -23,0 +9,0 @@ getCachedTheme, |
@@ -112,7 +112,2 @@ import { ChangeEvent } from 'react'; | ||
export declare function useTheme<T extends string>(initialTheme?: CustomThemes<T>): { | ||
theme: CustomThemes<T>; | ||
updateTheme: (theme: CustomThemes<T>) => void; | ||
}; | ||
export { } |
@@ -1,7 +0,35 @@ | ||
import { | ||
createPreloadedImgResource, | ||
createResource, | ||
resourceCache | ||
} from "../chunk-D4SRYCOP.js"; | ||
import "../chunk-MRY5H4GP.js"; | ||
// src/helpers/createResource.ts | ||
import { loadImage, preloadImgResource } from "./loaders.js"; | ||
var resourceCache = /* @__PURE__ */ new Map(); | ||
function createResource(promise) { | ||
let status = "pending"; | ||
let result = promise.then( | ||
(resolved) => { | ||
status = "success"; | ||
result = resolved; | ||
}, | ||
(rejected) => { | ||
status = "error"; | ||
result = rejected; | ||
} | ||
); | ||
return { | ||
read: () => { | ||
switch (status) { | ||
case "error": | ||
throw result; | ||
case "pending": | ||
case "success": | ||
return result; | ||
default: | ||
throw new Error("This should be impossible."); | ||
} | ||
} | ||
}; | ||
} | ||
function createPreloadedImgResource(imgOptions) { | ||
const data = createResource(preloadImgResource(imgOptions)); | ||
const img = createResource(loadImage(imgOptions)); | ||
return { data, img }; | ||
} | ||
export { | ||
@@ -8,0 +36,0 @@ createPreloadedImgResource, |
@@ -1,6 +0,41 @@ | ||
import { | ||
loadImage, | ||
preloadImgResource, | ||
preloadResource | ||
} from "../chunk-MRY5H4GP.js"; | ||
// src/helpers/loaders.ts | ||
function preloadResource(resourceOptions) { | ||
return new Promise((resolve, reject) => { | ||
const link = document.createElement("link"); | ||
link.rel = "preload"; | ||
link.as = resourceOptions.as; | ||
link.media = resourceOptions.media ?? "all"; | ||
link.href = resourceOptions.href; | ||
link.onload = resolve; | ||
link.onerror = reject; | ||
document.body.appendChild(link); | ||
}); | ||
} | ||
function preloadImgResource(imgOptions) { | ||
const { srcSet } = imgOptions; | ||
return new Promise((resolve, reject) => { | ||
const link = document.createElement("link"); | ||
if (srcSet) { | ||
link.imageSrcset = srcSet; | ||
} | ||
link.rel = "preload"; | ||
link.as = "image"; | ||
link.href = imgOptions.src ?? ""; | ||
link.onload = resolve; | ||
link.onerror = reject; | ||
document.body.appendChild(link); | ||
}); | ||
} | ||
function loadImage(imgOptions) { | ||
const { src, srcSet } = imgOptions; | ||
return new Promise((resolve, reject) => { | ||
const img = document.createElement("img"); | ||
if (srcSet) { | ||
img.srcset = srcSet; | ||
} | ||
img.src = src ?? ""; | ||
img.onload = () => resolve(imgOptions); | ||
img.onerror = reject; | ||
}); | ||
} | ||
export { | ||
@@ -7,0 +42,0 @@ loadImage, |
@@ -1,7 +0,30 @@ | ||
import { | ||
THEME_KEY, | ||
getAppliedTheme, | ||
getCachedTheme, | ||
setCachedTheme | ||
} from "../chunk-J6CAGWHA.js"; | ||
// src/helpers/themeHelpers.ts | ||
var THEME_KEY = "pandoTheme"; | ||
var DARK = "dark"; | ||
var LIGHT = "light"; | ||
var SYSTEM = "system"; | ||
function getAppliedTheme(userPreference) { | ||
const choseSystem = userPreference === SYSTEM; | ||
if (userPreference === LIGHT) { | ||
return LIGHT; | ||
} | ||
if (userPreference === DARK) { | ||
return DARK; | ||
} | ||
if (choseSystem && window?.matchMedia("(prefers-color-scheme: light)").matches) { | ||
return LIGHT; | ||
} | ||
if (choseSystem && window?.matchMedia("(prefers-color-scheme: dark)").matches) { | ||
return DARK; | ||
} | ||
return choseSystem ? DARK : userPreference; | ||
} | ||
function getCachedTheme() { | ||
const cachedTheme = window?.localStorage.getItem(THEME_KEY) ?? DARK; | ||
return cachedTheme; | ||
} | ||
function setCachedTheme(theme) { | ||
document?.documentElement.setAttribute("data-theme", getAppliedTheme(theme)); | ||
window?.localStorage.setItem(THEME_KEY, theme); | ||
} | ||
export { | ||
@@ -8,0 +31,0 @@ THEME_KEY, |
"use client"; | ||
// src/hooks/useAutoFormatDate.ts | ||
import { | ||
useAutoFormatDate | ||
} from "../chunk-7E4QK6S2.js"; | ||
useCallback, | ||
useState, | ||
useMemo | ||
} from "react"; | ||
var DELIMITER = "/"; | ||
function useAutoFormatDate(options) { | ||
const { pattern, value } = defaultAutoFormatOptions(options); | ||
const [formattedDate, setFormattedDate] = useState(value); | ||
const blocks = useMemo(() => getBlocks(pattern), [pattern]); | ||
const template = useMemo( | ||
() => getTemplate(pattern, blocks), | ||
[pattern, blocks] | ||
); | ||
const handleDateChange = useCallback( | ||
(e) => { | ||
return setFormattedDate((prev) => { | ||
const userInput = e.target.value; | ||
const startingCursor = e.target.selectionStart ?? userInput.length; | ||
const { formattedDate: formattedDate2, cursorPosition } = formatDateInput({ | ||
blocks, | ||
cursorPosition: startingCursor, | ||
pattern, | ||
value: userInput, | ||
prevValue: prev | ||
}); | ||
if (startingCursor !== userInput.length) { | ||
setCursorPosition(e.target, cursorPosition); | ||
} | ||
return formattedDate2; | ||
}); | ||
}, | ||
[blocks, pattern] | ||
); | ||
const handleDateBlur = useCallback( | ||
(e) => { | ||
return setFormattedDate( | ||
(prev) => formatDateInput({ | ||
blocks, | ||
cursorPosition: e.target.value.length, | ||
pattern, | ||
prevValue: prev, | ||
value: e.target.value | ||
}).formattedDate | ||
); | ||
}, | ||
[blocks, pattern] | ||
); | ||
return useMemo( | ||
() => ({ | ||
onBlur: handleDateBlur, | ||
onChange: handleDateChange, | ||
placeholder: template, | ||
maxLength: template.length, | ||
value: formattedDate | ||
}), | ||
[handleDateBlur, handleDateChange, template, formattedDate] | ||
); | ||
} | ||
function defaultAutoFormatOptions(options) { | ||
return { | ||
pattern: options?.pattern ?? ["m", "d", "Y"], | ||
value: options?.value ?? "" | ||
}; | ||
} | ||
function setCursorPosition(element, position) { | ||
requestAnimationFrame(() => element.setSelectionRange(position, position)); | ||
} | ||
function formatDateInput(options) { | ||
const { blocks, cursorPosition, pattern, prevValue, value } = options; | ||
const template = getTemplate(pattern, blocks); | ||
if (prevValue?.length > value.length) { | ||
return { | ||
formattedDate: value, | ||
cursorPosition | ||
}; | ||
} | ||
if (value.length > template.length || getCharCount(DELIMITER, value) > pattern.length - 1) { | ||
return { | ||
formattedDate: prevValue, | ||
cursorPosition: cursorPosition - (value.length - prevValue?.length) | ||
}; | ||
} | ||
const { formattedDateParts, cursor } = getFormattedDateParts(options); | ||
if (Object.values(formattedDateParts).join(DELIMITER).length === template.length) { | ||
const correctedDateParts = getCorrectedDateParts(formattedDateParts); | ||
return { | ||
formattedDate: combineDateParts(correctedDateParts, pattern), | ||
cursorPosition: cursor | ||
}; | ||
} | ||
return { | ||
formattedDate: combineDateParts(formattedDateParts, pattern), | ||
cursorPosition: cursor | ||
}; | ||
} | ||
function getCharCount(char, str) { | ||
return (str.match(new RegExp(char, "g")) || []).length; | ||
} | ||
function strToInt(value) { | ||
return parseInt(value, 10); | ||
} | ||
function getFormattedDateParts(options) { | ||
const { blocks, cursorPosition, pattern, value } = options; | ||
const chars = value.split(""); | ||
let cursor = cursorPosition; | ||
let currentBlock = pattern[0]; | ||
const dateParts = { | ||
d: "", | ||
m: "", | ||
Y: "" | ||
}; | ||
for (let i = 0; i < chars.length && currentBlock !== null; i += 1) { | ||
if (chars[i] !== DELIMITER) { | ||
dateParts[currentBlock] += chars[i]; | ||
} | ||
const formatted = formatDateBlock( | ||
currentBlock, | ||
dateParts[currentBlock], | ||
chars[i] === DELIMITER && cursorPosition > i | ||
); | ||
cursor += getCursorOffset( | ||
cursorPosition, | ||
i, | ||
formatted.length - dateParts[currentBlock].length | ||
); | ||
dateParts[currentBlock] = formatted; | ||
if (isDatePartComplete( | ||
dateParts[currentBlock], | ||
currentBlock, | ||
blocks, | ||
pattern | ||
) || isBlockEndedByDelimiter( | ||
chars[i], | ||
dateParts[currentBlock], | ||
getNextBlock(currentBlock, pattern) | ||
)) { | ||
currentBlock = getNextBlock(currentBlock, pattern); | ||
} | ||
} | ||
return { | ||
formattedDateParts: dateParts, | ||
cursor | ||
}; | ||
} | ||
function getCursorOffset(cursor, index, value) { | ||
if (cursor > index) { | ||
return value; | ||
} | ||
return 0; | ||
} | ||
function isDatePartComplete(datePart, block, blocks, pattern) { | ||
return datePart.length === blocks[pattern.indexOf(block)]; | ||
} | ||
function isBlockEndedByDelimiter(currentChar, datePart, nextBlock) { | ||
return currentChar === DELIMITER && datePart.length && nextBlock !== null; | ||
} | ||
function getNextBlock(current, pattern) { | ||
return pattern[pattern.indexOf(current) + 1] ?? null; | ||
} | ||
function formatDateBlock(blockId, value, complete) { | ||
const cleanValue = getSanitizedNumericString(value); | ||
if (!cleanValue) { | ||
return cleanValue; | ||
} | ||
switch (blockId) { | ||
case "m": | ||
return formatMonth(cleanValue, complete); | ||
case "d": | ||
return formatDay(cleanValue, complete); | ||
default: | ||
return cleanValue; | ||
} | ||
} | ||
function formatDay(value, complete) { | ||
const numericValue = parseInt(value, 10); | ||
if (value.length === 1) { | ||
if (complete || numericValue * 10 > 31) { | ||
return `0${Math.max(numericValue, 1)}`; | ||
} | ||
} else { | ||
if (numericValue > 31) { | ||
return "31"; | ||
} else if (numericValue < 1) { | ||
return "01"; | ||
} | ||
} | ||
return value; | ||
} | ||
function formatMonth(value, complete) { | ||
const numericValue = parseInt(value, 10); | ||
if (value.length === 1) { | ||
if (complete || numericValue * 10 > 12) { | ||
return `0${Math.max(numericValue, 1)}`; | ||
} | ||
} else { | ||
if (numericValue > 12) { | ||
return "12"; | ||
} else if (numericValue < 1) { | ||
return "01"; | ||
} | ||
} | ||
return value; | ||
} | ||
function getSanitizedNumericString(value) { | ||
return value.replace(/[^0-9]/g, ""); | ||
} | ||
function combineDateParts(dateParts, pattern) { | ||
const blocks = getBlocks(pattern); | ||
return pattern.map((block) => { | ||
const nextBlock = getNextBlock(block, pattern); | ||
const blockIdx = pattern.indexOf(block); | ||
const insertDelimiter = nextBlock && (dateParts[nextBlock] || dateParts[block].length === blocks[blockIdx]); | ||
if (insertDelimiter) { | ||
return dateParts[block] + DELIMITER; | ||
} else { | ||
return dateParts[block]; | ||
} | ||
}).join(""); | ||
} | ||
function getBlocks(pattern) { | ||
const blocks = []; | ||
pattern.forEach(function(value) { | ||
if (value === "Y") { | ||
blocks.push(4); | ||
} else { | ||
blocks.push(2); | ||
} | ||
}); | ||
return blocks; | ||
} | ||
function getTemplate(pattern, blocks) { | ||
return pattern.map((letter, idx) => { | ||
const count = blocks[idx]; | ||
return letter.repeat(count).toUpperCase(); | ||
}).join("/"); | ||
} | ||
function getCorrectedDateParts(dateParts) { | ||
const correctedDay = correctDayForMonth( | ||
strToInt(dateParts.d), | ||
strToInt(dateParts.m), | ||
strToInt(dateParts.Y) | ||
); | ||
return { | ||
d: formatDay(correctedDay.toString(), true), | ||
m: dateParts.m, | ||
Y: dateParts.Y | ||
}; | ||
} | ||
function correctDayForMonth(day, month, year) { | ||
day = Math.min(day, 31); | ||
month = Math.min(month, 12); | ||
year = parseInt(String(year || 0), 10); | ||
if (month < 7 && month % 2 === 0 || month > 8 && month % 2 === 1) { | ||
const leapDay = isLeapYear(year) ? 29 : 28; | ||
return Math.min(day, month === 2 ? leapDay : 30); | ||
} | ||
return day; | ||
} | ||
function isLeapYear(year) { | ||
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; | ||
} | ||
export { | ||
@@ -6,0 +267,0 @@ useAutoFormatDate |
"use client"; | ||
import { | ||
useEscToClose | ||
} from "../chunk-VYA2NVJ4.js"; | ||
// src/hooks/useEscToClose.ts | ||
import { useEffect } from "react"; | ||
function useEscToClose(onClose) { | ||
useEffect(() => { | ||
function handleEscClose(event) { | ||
if (event.key === "Escape") { | ||
event.stopPropagation(); | ||
event.preventDefault(); | ||
onClose(); | ||
} | ||
} | ||
window.addEventListener("keydown", handleEscClose, false); | ||
return () => { | ||
window.removeEventListener("keydown", handleEscClose, false); | ||
}; | ||
}, [onClose]); | ||
return null; | ||
} | ||
export { | ||
@@ -6,0 +22,0 @@ useEscToClose |
"use client"; | ||
// src/hooks/useFocusTrap.ts | ||
import { | ||
useFocusTrap | ||
} from "../chunk-4DMM3XQ3.js"; | ||
useCallback, | ||
useEffect | ||
} from "react"; | ||
function useFocusTrap(dialogRef) { | ||
const selectorList = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; | ||
const getFocusItems = useCallback(() => { | ||
const focusableItems = dialogRef?.current?.querySelectorAll(selectorList) ?? []; | ||
return { | ||
firstItem: focusableItems[0], | ||
lastItem: focusableItems[focusableItems.length - 1] | ||
}; | ||
}, [dialogRef]); | ||
const handleFocus = useCallback( | ||
(event) => { | ||
const { activeElement } = document; | ||
const { firstItem, lastItem } = getFocusItems(); | ||
if (event.key !== "Tab") { | ||
return; | ||
} | ||
if (event.shiftKey) { | ||
if (activeElement === firstItem) { | ||
event.preventDefault(); | ||
lastItem.focus(); | ||
} | ||
} else { | ||
if (activeElement === lastItem) { | ||
event.preventDefault(); | ||
firstItem.focus(); | ||
} | ||
} | ||
}, | ||
[getFocusItems] | ||
); | ||
const handleInitFocusTrap = useCallback(() => { | ||
if (dialogRef?.current != null) { | ||
const { firstItem } = getFocusItems(); | ||
if (document.activeElement !== firstItem) { | ||
firstItem?.focus(); | ||
} | ||
} | ||
}, [dialogRef, getFocusItems]); | ||
useEffect(() => { | ||
handleInitFocusTrap(); | ||
}, [handleInitFocusTrap]); | ||
return { | ||
onKeyDown: handleFocus | ||
}; | ||
} | ||
export { | ||
@@ -6,0 +54,0 @@ useFocusTrap |
"use client"; | ||
import { | ||
useIsIndeterminate | ||
} from "../chunk-HWPTZ2IF.js"; | ||
// src/hooks/useIsIndeterminate.ts | ||
import { useMemo } from "react"; | ||
function useIsIndeterminate(checkboxOptions) { | ||
const checkedItems = Object.keys(checkboxOptions).map( | ||
(optionItem) => checkboxOptions[optionItem] | ||
); | ||
const allChecked = checkedItems.every(Boolean); | ||
return useMemo( | ||
() => (checkedItems.some(Boolean) && !allChecked) ?? false, | ||
[allChecked, checkedItems] | ||
); | ||
} | ||
export { | ||
@@ -6,0 +16,0 @@ useIsIndeterminate |
"use client"; | ||
// src/hooks/usePreloadedImg.ts | ||
import { useEffect, useState, useTransition } from "react"; | ||
import { | ||
usePreloadedImg | ||
} from "../chunk-S6QQHARX.js"; | ||
import "../chunk-D4SRYCOP.js"; | ||
import "../chunk-MRY5H4GP.js"; | ||
resourceCache, | ||
createPreloadedImgResource | ||
} from "../helpers/createResource.js"; | ||
function getPreloadedImgResource(imgOptions) { | ||
const { src } = imgOptions; | ||
let resource = resourceCache.get(src); | ||
if (!resource) { | ||
resource = createPreloadedImgResource(imgOptions); | ||
resourceCache.set(src, resource); | ||
} | ||
return resource; | ||
} | ||
function usePreloadedImg(imgOptions) { | ||
const { src, srcSet } = imgOptions; | ||
const [, startTransition] = useTransition(); | ||
const [resource, setResource] = useState(null); | ||
useEffect(() => { | ||
if (resource) { | ||
return; | ||
} | ||
startTransition(() => { | ||
setResource(getPreloadedImgResource({ src, srcSet })); | ||
}); | ||
}, [resource, src, srcSet, startTransition]); | ||
return resource; | ||
} | ||
export { | ||
@@ -8,0 +33,0 @@ usePreloadedImg |
@@ -1,22 +0,8 @@ | ||
import { | ||
useEscToClose | ||
} from "./chunk-VYA2NVJ4.js"; | ||
import { | ||
useFocusTrap | ||
} from "./chunk-4DMM3XQ3.js"; | ||
import { | ||
useIsIndeterminate | ||
} from "./chunk-HWPTZ2IF.js"; | ||
import { | ||
usePreloadedImg | ||
} from "./chunk-S6QQHARX.js"; | ||
import "./chunk-D4SRYCOP.js"; | ||
import "./chunk-MRY5H4GP.js"; | ||
import { | ||
getCachedTheme, | ||
setCachedTheme | ||
} from "./chunk-J6CAGWHA.js"; | ||
import { | ||
useAutoFormatDate | ||
} from "./chunk-7E4QK6S2.js"; | ||
// src/index.ts | ||
import { getCachedTheme, setCachedTheme } from "./helpers/themeHelpers.js"; | ||
import { useAutoFormatDate } from "./hooks/useAutoFormatDate.js"; | ||
import { useEscToClose } from "./hooks/useEscToClose.js"; | ||
import { useFocusTrap } from "./hooks/useFocusTrap.js"; | ||
import { useIsIndeterminate } from "./hooks/useIsIndeterminate.js"; | ||
import { usePreloadedImg } from "./hooks/usePreloadedImg.js"; | ||
export { | ||
@@ -23,0 +9,0 @@ getCachedTheme, |
{ | ||
"name": "@pluralsight/react-utils", | ||
"version": "0.7.1-next-f09ded", | ||
"version": "0.7.1-next-f24c2e", | ||
"description": "A set of React custom hooks for Pando.", | ||
@@ -60,3 +60,3 @@ "browserslist": "> 0.25%, not dead", | ||
"license": "Apache 2.0", | ||
"packageManager": "bun@1.0.14", | ||
"packageManager": "pnpm@8.15.1", | ||
"scripts": { | ||
@@ -63,0 +63,0 @@ "build": "tsup --experimental-dts", |
@@ -30,7 +30,7 @@ # React utils | ||
This project uses bun so there are no setup commands needed. If you get any errors, you may need to run an initial `bun install` or ensure you are using Node >= 18. | ||
This project uses pnPm so there are no setup commands needed. If you get any errors, you may need to run an initial `pnpm install` or ensure you are using Node >= 18. | ||
## Development | ||
There is no development sandbox for this specific project, but most of the time, we import the hooks into the [Headless Styles sandbox](https://github.com/pluralsight/pando/tree/main/packages/headless-styles/sandbox) which is [just as easy](https://github.com/pluralsight/pando/blob/main/packages/headless-styles/sandbox/src/components/Tabs.jsx#L2-L8). | ||
There is no development sandbox for this specific project, but most of the time, we import the hooks into the [Headless Styles sandbox](https://github.com/ps-dev/pando/tree/main/packages/headless-styles/sandbox) which is [just as easy](https://github.com/ps-dev/pando/blob/main/packages/headless-styles/sandbox/src/components/Tabs.jsx#L2-L8). | ||
@@ -40,3 +40,3 @@ From the **root directory of the project**, run: | ||
```bash | ||
bun run start:sandbox | ||
pnpm run start:sandbox | ||
``` | ||
@@ -49,3 +49,3 @@ | ||
```bash | ||
bun test packages/react-utils/tests | ||
pnpm test packages/react-utils/tests | ||
``` | ||
@@ -55,2 +55,2 @@ | ||
If you plan on contributing to this project, please take time to read our [CONTRIBUTING.md](https://github.com/pluralsight/pando/blob/main/CONTRIBUTING.md). Pull requests that do not adhere to the requirements in this doc will automatically be flagged and closed. | ||
If you plan on contributing to this project, please take time to read our [CONTRIBUTING.md](https://github.com/ps-dev/pando/blob/main/CONTRIBUTING.md). Pull requests that do not adhere to the requirements in this doc will automatically be flagged and closed. |
@@ -1,2 +0,2 @@ | ||
import { loadImage, preloadImgResource } from './loaders.ts' | ||
import { loadImage, preloadImgResource } from './loaders' | ||
import type { ImgProps } from './types' | ||
@@ -24,6 +24,6 @@ | ||
switch (status) { | ||
case 'pending': | ||
case 'error': | ||
throw result | ||
case 'pending': | ||
case 'success': | ||
@@ -30,0 +30,0 @@ return result |
@@ -7,3 +7,3 @@ 'use client' | ||
createPreloadedImgResource, | ||
} from '../helpers/createResource.ts' | ||
} from '../helpers/createResource' | ||
import type { ImgProps, ImgResource } from '../helpers/types' | ||
@@ -10,0 +10,0 @@ |
@@ -1,9 +0,9 @@ | ||
export { getCachedTheme, setCachedTheme } from './helpers/themeHelpers.ts' | ||
export { getCachedTheme, setCachedTheme } from './helpers/themeHelpers' | ||
// hooks | ||
export { useAutoFormatDate } from './hooks/useAutoFormatDate.ts' | ||
export { useEscToClose } from './hooks/useEscToClose.ts' | ||
export { useFocusTrap } from './hooks/useFocusTrap.ts' | ||
export { useIsIndeterminate } from './hooks/useIsIndeterminate.ts' | ||
export { usePreloadedImg } from './hooks/usePreloadedImg.ts' | ||
export { useAutoFormatDate } from './hooks/useAutoFormatDate' | ||
export { useEscToClose } from './hooks/useEscToClose' | ||
export { useFocusTrap } from './hooks/useFocusTrap' | ||
export { useIsIndeterminate } from './hooks/useIsIndeterminate' | ||
export { usePreloadedImg } from './hooks/usePreloadedImg' |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
0
247863
163
3435