UNPKG

rooks

Version:

Collection of awesome react hooks

562 lines (556 loc) 14.8 kB
// src/hooks/useTemporalAge.ts import { useEffect as useEffect2, useMemo, useSyncExternalStore } from "react"; // src/hooks/useLoadTemporal.ts import { useEffect, useState } from "react"; var cached = null; var loading = null; function getTemporalApi() { const native = globalThis.Temporal; if (native) { return native; } return cached; } function useLoadTemporal() { const [api, setApi] = useState(getTemporalApi); useEffect(() => { if (api) return; if (!loading) { loading = import("@js-temporal/polyfill").then((mod) => { cached = mod.Temporal; }); } loading.then(() => { setApi(getTemporalApi()); }); }, [api]); return api; } // src/hooks/useTemporalAge.ts function getCurrentInstant() { return getTemporalApi().Instant.fromEpochMilliseconds(Date.now()); } function getCurrentTimeZoneId() { return getTemporalApi().Now.timeZoneId(); } function resolveDate(date) { const T = getTemporalApi(); if (date instanceof T.PlainDate) { return date; } return T.PlainDate.from(date); } function computeDayBoundaryDelay(instant, timeZone) { const nextDayStart = instant.toZonedDateTimeISO(timeZone).add({ days: 1 }).startOfDay().toInstant(); return Number(nextDayStart.epochMilliseconds - instant.epochMilliseconds); } var AgeStore = class { constructor(timeZone) { this.timeZone = timeZone; this.snapshot = { instant: getCurrentInstant() }; } snapshot; listeners = /* @__PURE__ */ new Set(); timer = null; subscribe = (listener) => { this.listeners.add(listener); if (this.listeners.size === 1) { this.start(); } return () => { this.listeners.delete(listener); if (this.listeners.size === 0) { this.stop(); } }; }; getSnapshot = () => { return this.snapshot; }; dispose = () => { this.stop(); this.listeners.clear(); }; emit() { this.listeners.forEach((listener) => { listener(); }); } start() { if (this.timer !== null) { return; } const instant = this.snapshot?.instant ?? getCurrentInstant(); const delay = computeDayBoundaryDelay(instant, this.timeZone); this.timer = setTimeout(this.tick, delay); } stop() { if (this.timer !== null) { clearTimeout(this.timer); this.timer = null; } } tick = () => { const now = getCurrentInstant(); this.snapshot = { instant: now }; this.emit(); const delay = computeDayBoundaryDelay(now, this.timeZone); this.timer = setTimeout(this.tick, delay); }; }; function deriveAge(snapshot, birthDate, timeZone) { if (snapshot === null) { return null; } const today = snapshot.instant.toZonedDateTimeISO(timeZone).toPlainDate(); const duration = birthDate.until(today, { largestUnit: "year" }); return { duration, years: duration.years, months: duration.months, days: duration.days }; } function getServerSnapshot() { return null; } var noopSubscribe = () => () => { }; var noopGetSnapshot = () => null; function useTemporalAge(options) { const temporalApi = useLoadTemporal(); const { date, timeZone } = options; const resolvedTimeZone = useMemo( () => temporalApi ? timeZone ?? getCurrentTimeZoneId() : "UTC", [timeZone, temporalApi] ); const birthDate = useMemo( () => temporalApi ? resolveDate(date) : null, [date, temporalApi] ); const store = useMemo(() => { if (!temporalApi) return null; return new AgeStore(resolvedTimeZone); }, [resolvedTimeZone, temporalApi]); useEffect2(() => { return () => { store?.dispose(); }; }, [store]); const snapshot = useSyncExternalStore( store?.subscribe ?? noopSubscribe, store?.getSnapshot ?? noopGetSnapshot, getServerSnapshot ); return useMemo(() => { if (!birthDate) return null; return deriveAge(snapshot, birthDate, resolvedTimeZone); }, [snapshot, birthDate, resolvedTimeZone]); } // src/hooks/useTemporalCountdown.ts import { useEffect as useEffect3, useMemo as useMemo2, useSyncExternalStore as useSyncExternalStore2 } from "react"; function getCurrentInstant2() { return getTemporalApi().Instant.fromEpochMilliseconds(Date.now()); } function resolveTarget(target) { const T = getTemporalApi(); if (target instanceof T.Instant) { return target; } if (typeof target === "number") { return T.Instant.fromEpochMilliseconds(target); } return T.Instant.from(target); } function computeDelay(instant, precision) { switch (precision) { case "second": { const remainder = instant.epochMilliseconds % 1e3; return remainder === 0 ? 1e3 : 1e3 - remainder; } case "minute": { const remainder = instant.epochMilliseconds % 6e4; return remainder === 0 ? 6e4 : 6e4 - remainder; } } } var CountdownStore = class { constructor(targetInstant, precision) { this.targetInstant = targetInstant; this.precision = precision; this.snapshot = { instant: getCurrentInstant2() }; } snapshot; listeners = /* @__PURE__ */ new Set(); timer = null; subscribe = (listener) => { this.listeners.add(listener); if (this.listeners.size === 1) { this.start(); } return () => { this.listeners.delete(listener); if (this.listeners.size === 0) { this.stop(); } }; }; getSnapshot = () => { return this.snapshot; }; dispose = () => { this.stop(); this.listeners.clear(); }; emit() { this.listeners.forEach((listener) => { listener(); }); } start() { if (this.timer !== null) { return; } const T = getTemporalApi(); const now = this.snapshot?.instant ?? getCurrentInstant2(); if (T.Instant.compare(now, this.targetInstant) >= 0) { return; } const delay = computeDelay(now, this.precision); this.timer = setTimeout(this.tick, delay); } stop() { if (this.timer !== null) { clearTimeout(this.timer); this.timer = null; } } tick = () => { const T = getTemporalApi(); const now = getCurrentInstant2(); this.snapshot = { instant: now }; this.emit(); if (T.Instant.compare(now, this.targetInstant) >= 0) { this.stop(); return; } const delay = computeDelay(now, this.precision); this.timer = setTimeout(this.tick, delay); }; }; function deriveResult(snapshot, targetInstant) { if (snapshot === null) { return null; } const T = getTemporalApi(); const comparison = T.Instant.compare(snapshot.instant, targetInstant); if (comparison >= 0) { return { remaining: new T.Duration(), done: true }; } const remaining = snapshot.instant.until(targetInstant); return { remaining, done: false }; } function getServerSnapshot2() { return null; } var noopSubscribe2 = () => () => { }; var noopGetSnapshot2 = () => null; function useTemporalCountdown(options) { const temporalApi = useLoadTemporal(); const { target, precision = "second" } = options; const targetInstant = useMemo2( () => temporalApi ? resolveTarget(target) : null, [target, temporalApi] ); const store = useMemo2(() => { if (!temporalApi || !targetInstant) return null; return new CountdownStore(targetInstant, precision); }, [targetInstant, precision, temporalApi]); useEffect3(() => { return () => { store?.dispose(); }; }, [store]); const snapshot = useSyncExternalStore2( store?.subscribe ?? noopSubscribe2, store?.getSnapshot ?? noopGetSnapshot2, getServerSnapshot2 ); return useMemo2(() => { if (!targetInstant) return null; return deriveResult(snapshot, targetInstant); }, [snapshot, targetInstant]); } // src/hooks/useTemporalElapsed.ts import { useEffect as useEffect4, useMemo as useMemo3, useSyncExternalStore as useSyncExternalStore3 } from "react"; function getCurrentInstant3() { return getTemporalApi().Instant.fromEpochMilliseconds(Date.now()); } function resolveSince(since) { const T = getTemporalApi(); if (since === void 0) { return getCurrentInstant3(); } if (since instanceof T.Instant) { return since; } if (typeof since === "number") { return T.Instant.fromEpochMilliseconds(since); } return T.Instant.from(since); } function computeDelay2(instant, precision) { switch (precision) { case "second": { const remainder = instant.epochMilliseconds % 1e3; return remainder === 0 ? 1e3 : 1e3 - remainder; } case "minute": { const remainder = instant.epochMilliseconds % 6e4; return remainder === 0 ? 6e4 : 6e4 - remainder; } } } var ElapsedStore = class { constructor(precision) { this.precision = precision; this.snapshot = { instant: getCurrentInstant3() }; } snapshot; listeners = /* @__PURE__ */ new Set(); timer = null; subscribe = (listener) => { this.listeners.add(listener); if (this.listeners.size === 1) { this.start(); } return () => { this.listeners.delete(listener); if (this.listeners.size === 0) { this.stop(); } }; }; getSnapshot = () => { return this.snapshot; }; dispose = () => { this.stop(); this.listeners.clear(); }; emit() { this.listeners.forEach((listener) => { listener(); }); } start() { if (this.timer !== null) { return; } const now = this.snapshot?.instant ?? getCurrentInstant3(); const delay = computeDelay2(now, this.precision); this.timer = setTimeout(this.tick, delay); } stop() { if (this.timer !== null) { clearTimeout(this.timer); this.timer = null; } } tick = () => { const now = getCurrentInstant3(); this.snapshot = { instant: now }; this.emit(); const delay = computeDelay2(now, this.precision); this.timer = setTimeout(this.tick, delay); }; }; function deriveElapsed(snapshot, sinceInstant) { if (snapshot === null) { return null; } const T = getTemporalApi(); const comparison = T.Instant.compare(snapshot.instant, sinceInstant); if (comparison <= 0) { return new T.Duration(); } return sinceInstant.until(snapshot.instant); } function getServerSnapshot3() { return null; } var noopSubscribe3 = () => () => { }; var noopGetSnapshot3 = () => null; function useTemporalElapsed(options = {}) { const temporalApi = useLoadTemporal(); const { since, precision = "second" } = options; const sinceInstant = useMemo3( () => temporalApi ? resolveSince(since) : null, [since, temporalApi] ); const store = useMemo3(() => { if (!temporalApi) return null; return new ElapsedStore(precision); }, [precision, temporalApi]); useEffect4(() => { return () => { store?.dispose(); }; }, [store]); const snapshot = useSyncExternalStore3( store?.subscribe ?? noopSubscribe3, store?.getSnapshot ?? noopGetSnapshot3, getServerSnapshot3 ); return useMemo3(() => { if (!sinceInstant) return null; return deriveElapsed(snapshot, sinceInstant); }, [snapshot, sinceInstant]); } // src/hooks/useTemporalNow.ts import { useEffect as useEffect5, useMemo as useMemo4, useSyncExternalStore as useSyncExternalStore4 } from "react"; var ClockStore = class { constructor(precision, timeZone) { this.precision = precision; this.timeZone = timeZone; this.snapshot = { instant: getCurrentInstant4() }; } snapshot; listeners = /* @__PURE__ */ new Set(); timer = null; subscribe = (listener) => { this.listeners.add(listener); if (this.listeners.size === 1) { this.start(); } return () => { this.listeners.delete(listener); if (this.listeners.size === 0) { this.stop(); } }; }; getSnapshot = () => { return this.snapshot; }; dispose = () => { this.stop(); this.listeners.clear(); }; emit() { this.listeners.forEach((listener) => { listener(); }); } start() { if (this.timer !== null) { return; } const instant = this.snapshot?.instant ?? getCurrentInstant4(); const delay = computeDelayFromInstant( instant, this.precision, this.timeZone ); this.timer = setTimeout(this.tick, delay); } stop() { if (this.timer !== null) { clearTimeout(this.timer); this.timer = null; } } tick = () => { const now = getCurrentInstant4(); this.snapshot = { instant: now }; this.emit(); const delay = computeDelayFromInstant(now, this.precision, this.timeZone); this.timer = setTimeout(this.tick, delay); }; }; function getCurrentInstant4() { return getTemporalApi().Instant.fromEpochMilliseconds(Date.now()); } function getCurrentTimeZoneId2() { return getTemporalApi().Now.timeZoneId(); } function computeDelayFromInstant(instant, precision, timeZone) { switch (precision) { case "second": { const remainder = instant.epochMilliseconds % 1e3; return remainder === 0 ? 1e3 : 1e3 - remainder; } case "minute": { const remainder = instant.epochMilliseconds % 6e4; return remainder === 0 ? 6e4 : 6e4 - remainder; } case "day": { const zone = timeZone ?? getCurrentTimeZoneId2(); const nextDayStart = instant.toZonedDateTimeISO(zone).add({ days: 1 }).startOfDay().toInstant(); return Number(nextDayStart.epochMilliseconds - instant.epochMilliseconds); } } } function deriveValue(snapshot, kind, timeZone) { if (snapshot === null) { return null; } if (kind === "instant") { return snapshot.instant; } const zone = timeZone ?? getCurrentTimeZoneId2(); const zonedDateTime = snapshot.instant.toZonedDateTimeISO(zone); switch (kind) { case "zoned": return zonedDateTime; case "plainDateTime": return zonedDateTime.toPlainDateTime(); case "plainDate": return zonedDateTime.toPlainDate(); case "instant": return snapshot.instant; } } function getServerSnapshot4() { return null; } var noopSubscribe4 = () => () => { }; var noopGetSnapshot4 = () => null; function useTemporalNow(options = {}) { const temporalApi = useLoadTemporal(); const { kind = "instant", precision = "second", timeZone } = options; const store = useMemo4(() => { if (!temporalApi) return null; return new ClockStore(precision, timeZone); }, [precision, timeZone, temporalApi]); useEffect5(() => { return () => { store?.dispose(); }; }, [store]); const snapshot = useSyncExternalStore4( store?.subscribe ?? noopSubscribe4, store?.getSnapshot ?? noopGetSnapshot4, getServerSnapshot4 ); return useMemo4(() => { return deriveValue(snapshot, kind, timeZone); }, [snapshot, kind, timeZone]); } export { useTemporalAge, useTemporalCountdown, useTemporalElapsed, useTemporalNow };