@publikit/hooks 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +28 -5
  3. package/dist/index.cjs +113 -67
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +7 -74
  6. package/dist/index.d.ts +7 -74
  7. package/dist/index.js +111 -67
  8. package/dist/index.js.map +1 -1
  9. package/dist/use-infinite-scroll.cjs +53 -0
  10. package/dist/use-infinite-scroll.cjs.map +1 -0
  11. package/dist/use-infinite-scroll.d.cts +28 -0
  12. package/dist/use-infinite-scroll.d.ts +28 -0
  13. package/dist/use-infinite-scroll.js +51 -0
  14. package/dist/use-infinite-scroll.js.map +1 -0
  15. package/dist/use-live-timestamp.cjs +156 -0
  16. package/dist/use-live-timestamp.cjs.map +1 -0
  17. package/dist/use-live-timestamp.d.cts +6 -0
  18. package/dist/use-live-timestamp.d.ts +6 -0
  19. package/dist/use-live-timestamp.js +154 -0
  20. package/dist/use-live-timestamp.js.map +1 -0
  21. package/dist/use-media-query.cjs +24 -0
  22. package/dist/use-media-query.cjs.map +1 -0
  23. package/dist/use-media-query.d.cts +10 -0
  24. package/dist/use-media-query.d.ts +10 -0
  25. package/dist/use-media-query.js +22 -0
  26. package/dist/use-media-query.js.map +1 -0
  27. package/dist/use-mobile.cjs +37 -0
  28. package/dist/use-mobile.cjs.map +1 -0
  29. package/dist/use-mobile.d.cts +10 -0
  30. package/dist/use-mobile.d.ts +10 -0
  31. package/dist/use-mobile.js +34 -0
  32. package/dist/use-mobile.js.map +1 -0
  33. package/dist/use-pull-to-refresh.cjs +103 -0
  34. package/dist/use-pull-to-refresh.cjs.map +1 -0
  35. package/dist/use-pull-to-refresh.d.cts +22 -0
  36. package/dist/use-pull-to-refresh.d.ts +22 -0
  37. package/dist/use-pull-to-refresh.js +101 -0
  38. package/dist/use-pull-to-refresh.js.map +1 -0
  39. package/dist/use-scrollable-container.cjs +149 -0
  40. package/dist/use-scrollable-container.cjs.map +1 -0
  41. package/dist/use-scrollable-container.d.cts +38 -0
  42. package/dist/use-scrollable-container.d.ts +38 -0
  43. package/dist/use-scrollable-container.js +144 -0
  44. package/dist/use-scrollable-container.js.map +1 -0
  45. package/package.json +38 -5
@@ -0,0 +1,28 @@
1
+ import { RefCallback } from 'react';
2
+
3
+ interface UseIntersectionLoadMoreOptions {
4
+ /** Callback when intersection occurs */
5
+ onLoadMore: () => void;
6
+ /** Whether more data can be loaded */
7
+ hasNextPage: boolean;
8
+ /** Whether currently fetching */
9
+ isFetchingNextPage: boolean;
10
+ /** Root margin for intersection observer */
11
+ rootMargin?: string;
12
+ /** Threshold for intersection */
13
+ threshold?: number;
14
+ /** Enable/disable the hook */
15
+ enabled?: boolean;
16
+ /** Custom root element for IntersectionObserver (e.g., ScrollArea viewport) */
17
+ root?: HTMLElement | null;
18
+ }
19
+ interface UseIntersectionLoadMoreReturn {
20
+ /** Callback ref for the sentinel element. */
21
+ sentinelRef: RefCallback<HTMLElement | null>;
22
+ }
23
+ /**
24
+ * Headless infinite-load behaviour using IntersectionObserver.
25
+ */
26
+ declare function useIntersectionLoadMore({ onLoadMore, hasNextPage, isFetchingNextPage, rootMargin, threshold, enabled, root, }: UseIntersectionLoadMoreOptions): UseIntersectionLoadMoreReturn;
27
+
28
+ export { type UseIntersectionLoadMoreOptions, type UseIntersectionLoadMoreReturn, useIntersectionLoadMore };
@@ -0,0 +1,51 @@
1
+ import { useRef, useState, useCallback, useEffect } from 'react';
2
+
3
+ // use-infinite-scroll.tsx
4
+ function useIntersectionLoadMore({
5
+ onLoadMore,
6
+ hasNextPage,
7
+ isFetchingNextPage,
8
+ rootMargin = "200px",
9
+ threshold = 0,
10
+ enabled = true,
11
+ root
12
+ }) {
13
+ const observerRef = useRef(null);
14
+ const [sentinelElement, setSentinelElement] = useState(null);
15
+ const handleIntersection = useCallback(
16
+ (entries) => {
17
+ const entry = entries[0];
18
+ if (!entry?.isIntersecting) return;
19
+ if (hasNextPage && !isFetchingNextPage && enabled) {
20
+ onLoadMore();
21
+ }
22
+ },
23
+ [onLoadMore, hasNextPage, isFetchingNextPage, enabled]
24
+ );
25
+ useEffect(() => {
26
+ if (!enabled) return;
27
+ if (observerRef.current) {
28
+ observerRef.current.disconnect();
29
+ }
30
+ observerRef.current = new IntersectionObserver(handleIntersection, {
31
+ root: root || null,
32
+ rootMargin,
33
+ threshold
34
+ });
35
+ if (sentinelElement) {
36
+ observerRef.current.observe(sentinelElement);
37
+ }
38
+ return () => {
39
+ if (observerRef.current) {
40
+ observerRef.current.disconnect();
41
+ }
42
+ };
43
+ }, [sentinelElement, handleIntersection, rootMargin, threshold, enabled, root]);
44
+ return {
45
+ sentinelRef: setSentinelElement
46
+ };
47
+ }
48
+
49
+ export { useIntersectionLoadMore };
50
+ //# sourceMappingURL=use-infinite-scroll.js.map
51
+ //# sourceMappingURL=use-infinite-scroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../use-infinite-scroll.tsx"],"names":[],"mappings":";;;AA2BO,SAAS,uBAAA,CAAwB;AAAA,EACtC,UAAA;AAAA,EACA,WAAA;AAAA,EACA,kBAAA;AAAA,EACA,UAAA,GAAa,OAAA;AAAA,EACb,SAAA,GAAY,CAAA;AAAA,EACZ,OAAA,GAAU,IAAA;AAAA,EACV;AACF,CAAA,EAAkE;AAChE,EAAA,MAAM,WAAA,GAAc,OAAoC,IAAI,CAAA;AAC5D,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAI,SAA6B,IAAI,CAAA;AAE/E,EAAA,MAAM,kBAAA,GAAqB,WAAA;AAAA,IACzB,CAAC,OAAA,KAAyC;AACxC,MAAA,MAAM,KAAA,GAAQ,QAAQ,CAAC,CAAA;AACvB,MAAA,IAAI,CAAC,OAAO,cAAA,EAAgB;AAC5B,MAAA,IAAI,WAAA,IAAe,CAAC,kBAAA,IAAsB,OAAA,EAAS;AACjD,QAAA,UAAA,EAAW;AAAA,MACb;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAA,EAAY,WAAA,EAAa,kBAAA,EAAoB,OAAO;AAAA,GACvD;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,IAAI,YAAY,OAAA,EAAS;AACvB,MAAA,WAAA,CAAY,QAAQ,UAAA,EAAW;AAAA,IACjC;AAEA,IAAA,WAAA,CAAY,OAAA,GAAU,IAAI,oBAAA,CAAqB,kBAAA,EAAoB;AAAA,MACjE,MAAM,IAAA,IAAQ,IAAA;AAAA,MACd,UAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,WAAA,CAAY,OAAA,CAAQ,QAAQ,eAAe,CAAA;AAAA,IAC7C;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,YAAY,OAAA,EAAS;AACvB,QAAA,WAAA,CAAY,QAAQ,UAAA,EAAW;AAAA,MACjC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,eAAA,EAAiB,kBAAA,EAAoB,YAAY,SAAA,EAAW,OAAA,EAAS,IAAI,CAAC,CAAA;AAE9E,EAAA,OAAO;AAAA,IACL,WAAA,EAAa;AAAA,GACf;AACF","file":"use-infinite-scroll.js","sourcesContent":["import { useEffect, useRef, useCallback, useState, type RefCallback } from 'react';\r\n\r\nexport interface UseIntersectionLoadMoreOptions {\r\n /** Callback when intersection occurs */\r\n onLoadMore: () => void;\r\n /** Whether more data can be loaded */\r\n hasNextPage: boolean;\r\n /** Whether currently fetching */\r\n isFetchingNextPage: boolean;\r\n /** Root margin for intersection observer */\r\n rootMargin?: string;\r\n /** Threshold for intersection */\r\n threshold?: number;\r\n /** Enable/disable the hook */\r\n enabled?: boolean;\r\n /** Custom root element for IntersectionObserver (e.g., ScrollArea viewport) */\r\n root?: HTMLElement | null;\r\n}\r\n\r\nexport interface UseIntersectionLoadMoreReturn {\r\n /** Callback ref for the sentinel element. */\r\n sentinelRef: RefCallback<HTMLElement | null>;\r\n}\r\n\r\n/**\r\n * Headless infinite-load behaviour using IntersectionObserver.\r\n */\r\nexport function useIntersectionLoadMore({\r\n onLoadMore,\r\n hasNextPage,\r\n isFetchingNextPage,\r\n rootMargin = '200px',\r\n threshold = 0,\r\n enabled = true,\r\n root,\r\n}: UseIntersectionLoadMoreOptions): UseIntersectionLoadMoreReturn {\r\n const observerRef = useRef<IntersectionObserver | null>(null);\r\n const [sentinelElement, setSentinelElement] = useState<HTMLElement | null>(null);\r\n\r\n const handleIntersection = useCallback(\r\n (entries: IntersectionObserverEntry[]) => {\r\n const entry = entries[0];\r\n if (!entry?.isIntersecting) return;\r\n if (hasNextPage && !isFetchingNextPage && enabled) {\r\n onLoadMore();\r\n }\r\n },\r\n [onLoadMore, hasNextPage, isFetchingNextPage, enabled]\r\n );\r\n\r\n useEffect(() => {\r\n if (!enabled) return;\r\n\r\n if (observerRef.current) {\r\n observerRef.current.disconnect();\r\n }\r\n\r\n observerRef.current = new IntersectionObserver(handleIntersection, {\r\n root: root || null,\r\n rootMargin,\r\n threshold,\r\n });\r\n\r\n if (sentinelElement) {\r\n observerRef.current.observe(sentinelElement);\r\n }\r\n\r\n return () => {\r\n if (observerRef.current) {\r\n observerRef.current.disconnect();\r\n }\r\n };\r\n }, [sentinelElement, handleIntersection, rootMargin, threshold, enabled, root]);\r\n\r\n return {\r\n sentinelRef: setSentinelElement,\r\n };\r\n}\r\n"]}
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // use-live-timestamp.ts
6
+ var globalNow = /* @__PURE__ */ new Date();
7
+ var subscribers = /* @__PURE__ */ new Map();
8
+ var visibilityMap = /* @__PURE__ */ new Map();
9
+ var rafId = null;
10
+ var intervalId = null;
11
+ var isTabVisible = true;
12
+ var pendingUpdate = false;
13
+ var visibilityListenerAttached = false;
14
+ function getUpdateInterval(date) {
15
+ const ageMs = Date.now() - date.getTime();
16
+ const ageMinutes = ageMs / 6e4;
17
+ if (ageMinutes < 1) return 1e4;
18
+ if (ageMinutes < 60) return 3e4;
19
+ if (ageMinutes < 1440) return 3e5;
20
+ return 36e5;
21
+ }
22
+ function getMinInterval() {
23
+ if (subscribers.size === 0) return 3e4;
24
+ const intervals = Array.from(subscribers.values()).map((sub) => getUpdateInterval(sub.date));
25
+ return Math.max(Math.min(...intervals), 1e4);
26
+ }
27
+ var createObserver = (subscriberId, callback) => {
28
+ return new IntersectionObserver(
29
+ (entries) => {
30
+ entries.forEach((entry) => {
31
+ visibilityMap.set(subscriberId, entry.isIntersecting);
32
+ if (entry.isIntersecting) {
33
+ callback();
34
+ }
35
+ });
36
+ },
37
+ {
38
+ root: null,
39
+ rootMargin: "50px",
40
+ threshold: 0.01
41
+ }
42
+ );
43
+ };
44
+ function updateVisibleSubscribers() {
45
+ if (!isTabVisible || subscribers.size === 0) {
46
+ pendingUpdate = false;
47
+ return;
48
+ }
49
+ globalNow = /* @__PURE__ */ new Date();
50
+ if (!pendingUpdate) {
51
+ pendingUpdate = true;
52
+ rafId = requestAnimationFrame(() => {
53
+ subscribers.forEach((subscriber, subscriberId) => {
54
+ const isVisible = visibilityMap.get(subscriberId) ?? true;
55
+ if (isVisible) {
56
+ subscriber.callback();
57
+ }
58
+ });
59
+ pendingUpdate = false;
60
+ });
61
+ }
62
+ }
63
+ function stopTimer() {
64
+ if (intervalId) {
65
+ clearTimeout(intervalId);
66
+ intervalId = null;
67
+ }
68
+ if (rafId) {
69
+ cancelAnimationFrame(rafId);
70
+ rafId = null;
71
+ }
72
+ pendingUpdate = false;
73
+ }
74
+ function scheduleNextTick() {
75
+ if (intervalId) {
76
+ clearTimeout(intervalId);
77
+ intervalId = null;
78
+ }
79
+ if (subscribers.size === 0 || !isTabVisible) {
80
+ return;
81
+ }
82
+ intervalId = setTimeout(() => {
83
+ updateVisibleSubscribers();
84
+ scheduleNextTick();
85
+ }, getMinInterval());
86
+ }
87
+ function ensureTimerRunning() {
88
+ if (!intervalId && subscribers.size > 0 && isTabVisible) {
89
+ scheduleNextTick();
90
+ }
91
+ }
92
+ function restartTimer() {
93
+ stopTimer();
94
+ ensureTimerRunning();
95
+ }
96
+ function ensureVisibilityListener() {
97
+ if (visibilityListenerAttached) return;
98
+ if (typeof document === "undefined") return;
99
+ document.addEventListener("visibilitychange", () => {
100
+ isTabVisible = !document.hidden;
101
+ if (isTabVisible && subscribers.size > 0) {
102
+ updateVisibleSubscribers();
103
+ ensureTimerRunning();
104
+ } else if (!isTabVisible) {
105
+ stopTimer();
106
+ }
107
+ });
108
+ visibilityListenerAttached = true;
109
+ }
110
+ function useLiveTimestamp(date, element) {
111
+ const [, setTick] = react.useState(0);
112
+ const subscriberIdRef = react.useRef(/* @__PURE__ */ Symbol("live-timestamp-subscriber"));
113
+ react.useEffect(() => {
114
+ ensureVisibilityListener();
115
+ const subscriberId = subscriberIdRef.current;
116
+ const observedElement = element ?? null;
117
+ const callback = () => {
118
+ const isVisible = visibilityMap.get(subscriberId) ?? true;
119
+ if (isVisible) {
120
+ setTick((t) => t + 1);
121
+ }
122
+ };
123
+ let observer = null;
124
+ if (observedElement) {
125
+ observer = createObserver(subscriberId, callback);
126
+ observer.observe(observedElement);
127
+ visibilityMap.set(subscriberId, false);
128
+ } else {
129
+ visibilityMap.set(subscriberId, true);
130
+ }
131
+ subscribers.set(subscriberId, {
132
+ callback,
133
+ date,
134
+ observer
135
+ });
136
+ ensureTimerRunning();
137
+ return () => {
138
+ const subscriber = subscribers.get(subscriberId);
139
+ if (subscriber?.observer) {
140
+ subscriber.observer.disconnect();
141
+ }
142
+ subscribers.delete(subscriberId);
143
+ visibilityMap.delete(subscriberId);
144
+ if (subscribers.size === 0) {
145
+ stopTimer();
146
+ } else {
147
+ restartTimer();
148
+ }
149
+ };
150
+ }, [date.getTime(), element]);
151
+ return globalNow;
152
+ }
153
+
154
+ exports.useLiveTimestamp = useLiveTimestamp;
155
+ //# sourceMappingURL=use-live-timestamp.cjs.map
156
+ //# sourceMappingURL=use-live-timestamp.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../use-live-timestamp.ts"],"names":["useState","useRef","useEffect"],"mappings":"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","file":"use-live-timestamp.cjs","sourcesContent":["import { useState, useEffect, useRef } from 'react';\r\n\r\nlet globalNow = new Date();\r\n\r\ntype SubscriberId = symbol;\r\n\r\ntype Subscriber = {\r\n callback: () => void;\r\n date: Date;\r\n observer: IntersectionObserver | null;\r\n};\r\n\r\nconst subscribers = new Map<SubscriberId, Subscriber>();\r\nconst visibilityMap = new Map<SubscriberId, boolean>();\r\n\r\nlet rafId: number | null = null;\r\nlet intervalId: ReturnType<typeof setTimeout> | null = null;\r\nlet isTabVisible = true;\r\nlet pendingUpdate = false;\r\nlet visibilityListenerAttached = false;\r\n\r\nfunction getUpdateInterval(date: Date): number {\r\n const ageMs = Date.now() - date.getTime();\r\n const ageMinutes = ageMs / 60000;\r\n\r\n if (ageMinutes < 1) return 10000;\r\n if (ageMinutes < 60) return 30000;\r\n if (ageMinutes < 1440) return 300000;\r\n return 3600000;\r\n}\r\n\r\nfunction getMinInterval(): number {\r\n if (subscribers.size === 0) return 30000;\r\n const intervals = Array.from(subscribers.values()).map((sub) => getUpdateInterval(sub.date));\r\n return Math.max(Math.min(...intervals), 10000);\r\n}\r\n\r\nconst createObserver = (\r\n subscriberId: SubscriberId,\r\n callback: () => void\r\n): IntersectionObserver => {\r\n return new IntersectionObserver(\r\n (entries) => {\r\n entries.forEach((entry) => {\r\n visibilityMap.set(subscriberId, entry.isIntersecting);\r\n if (entry.isIntersecting) {\r\n callback();\r\n }\r\n });\r\n },\r\n {\r\n root: null,\r\n rootMargin: '50px',\r\n threshold: 0.01,\r\n }\r\n );\r\n};\r\n\r\nfunction updateVisibleSubscribers() {\r\n if (!isTabVisible || subscribers.size === 0) {\r\n pendingUpdate = false;\r\n return;\r\n }\r\n\r\n globalNow = new Date();\r\n\r\n if (!pendingUpdate) {\r\n pendingUpdate = true;\r\n rafId = requestAnimationFrame(() => {\r\n subscribers.forEach((subscriber, subscriberId) => {\r\n const isVisible = visibilityMap.get(subscriberId) ?? true;\r\n if (isVisible) {\r\n subscriber.callback();\r\n }\r\n });\r\n pendingUpdate = false;\r\n });\r\n }\r\n}\r\n\r\nfunction stopTimer() {\r\n if (intervalId) {\r\n clearTimeout(intervalId);\r\n intervalId = null;\r\n }\r\n if (rafId) {\r\n cancelAnimationFrame(rafId);\r\n rafId = null;\r\n }\r\n pendingUpdate = false;\r\n}\r\n\r\nfunction scheduleNextTick() {\r\n if (intervalId) {\r\n clearTimeout(intervalId);\r\n intervalId = null;\r\n }\r\n\r\n if (subscribers.size === 0 || !isTabVisible) {\r\n return;\r\n }\r\n\r\n intervalId = setTimeout(() => {\r\n updateVisibleSubscribers();\r\n scheduleNextTick();\r\n }, getMinInterval());\r\n}\r\n\r\nfunction ensureTimerRunning() {\r\n if (!intervalId && subscribers.size > 0 && isTabVisible) {\r\n scheduleNextTick();\r\n }\r\n}\r\n\r\nfunction restartTimer() {\r\n stopTimer();\r\n ensureTimerRunning();\r\n}\r\n\r\nfunction ensureVisibilityListener(): void {\r\n if (visibilityListenerAttached) return;\r\n if (typeof document === 'undefined') return;\r\n\r\n document.addEventListener('visibilitychange', () => {\r\n isTabVisible = !document.hidden;\r\n if (isTabVisible && subscribers.size > 0) {\r\n updateVisibleSubscribers();\r\n ensureTimerRunning();\r\n } else if (!isTabVisible) {\r\n stopTimer();\r\n }\r\n });\r\n visibilityListenerAttached = true;\r\n}\r\n\r\n/**\r\n * Live-updating timestamp hook with visibility-aware adaptive intervals.\r\n */\r\nexport function useLiveTimestamp(\r\n date: Date,\r\n element?: Element | null,\r\n): Date {\r\n const [, setTick] = useState(0);\r\n const subscriberIdRef = useRef<SubscriberId>(Symbol('live-timestamp-subscriber'));\r\n\r\n useEffect(() => {\r\n ensureVisibilityListener();\r\n const subscriberId = subscriberIdRef.current;\r\n const observedElement = element ?? null;\r\n\r\n const callback = () => {\r\n const isVisible = visibilityMap.get(subscriberId) ?? true;\r\n if (isVisible) {\r\n setTick((t) => t + 1);\r\n }\r\n };\r\n\r\n let observer: IntersectionObserver | null = null;\r\n if (observedElement) {\r\n observer = createObserver(subscriberId, callback);\r\n observer.observe(observedElement);\r\n visibilityMap.set(subscriberId, false);\r\n } else {\r\n visibilityMap.set(subscriberId, true);\r\n }\r\n\r\n subscribers.set(subscriberId, {\r\n callback,\r\n date,\r\n observer,\r\n });\r\n\r\n ensureTimerRunning();\r\n\r\n return () => {\r\n const subscriber = subscribers.get(subscriberId);\r\n if (subscriber?.observer) {\r\n subscriber.observer.disconnect();\r\n }\r\n subscribers.delete(subscriberId);\r\n visibilityMap.delete(subscriberId);\r\n\r\n if (subscribers.size === 0) {\r\n stopTimer();\r\n } else {\r\n restartTimer();\r\n }\r\n };\r\n // Key on the timestamp value, not the Date identity, so callers passing a\r\n // freshly constructed Date each render don't trigger a re-subscribe.\r\n }, [date.getTime(), element]);\r\n\r\n return globalNow;\r\n}\r\n"]}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Live-updating timestamp hook with visibility-aware adaptive intervals.
3
+ */
4
+ declare function useLiveTimestamp(date: Date, element?: Element | null): Date;
5
+
6
+ export { useLiveTimestamp };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Live-updating timestamp hook with visibility-aware adaptive intervals.
3
+ */
4
+ declare function useLiveTimestamp(date: Date, element?: Element | null): Date;
5
+
6
+ export { useLiveTimestamp };
@@ -0,0 +1,154 @@
1
+ import { useState, useRef, useEffect } from 'react';
2
+
3
+ // use-live-timestamp.ts
4
+ var globalNow = /* @__PURE__ */ new Date();
5
+ var subscribers = /* @__PURE__ */ new Map();
6
+ var visibilityMap = /* @__PURE__ */ new Map();
7
+ var rafId = null;
8
+ var intervalId = null;
9
+ var isTabVisible = true;
10
+ var pendingUpdate = false;
11
+ var visibilityListenerAttached = false;
12
+ function getUpdateInterval(date) {
13
+ const ageMs = Date.now() - date.getTime();
14
+ const ageMinutes = ageMs / 6e4;
15
+ if (ageMinutes < 1) return 1e4;
16
+ if (ageMinutes < 60) return 3e4;
17
+ if (ageMinutes < 1440) return 3e5;
18
+ return 36e5;
19
+ }
20
+ function getMinInterval() {
21
+ if (subscribers.size === 0) return 3e4;
22
+ const intervals = Array.from(subscribers.values()).map((sub) => getUpdateInterval(sub.date));
23
+ return Math.max(Math.min(...intervals), 1e4);
24
+ }
25
+ var createObserver = (subscriberId, callback) => {
26
+ return new IntersectionObserver(
27
+ (entries) => {
28
+ entries.forEach((entry) => {
29
+ visibilityMap.set(subscriberId, entry.isIntersecting);
30
+ if (entry.isIntersecting) {
31
+ callback();
32
+ }
33
+ });
34
+ },
35
+ {
36
+ root: null,
37
+ rootMargin: "50px",
38
+ threshold: 0.01
39
+ }
40
+ );
41
+ };
42
+ function updateVisibleSubscribers() {
43
+ if (!isTabVisible || subscribers.size === 0) {
44
+ pendingUpdate = false;
45
+ return;
46
+ }
47
+ globalNow = /* @__PURE__ */ new Date();
48
+ if (!pendingUpdate) {
49
+ pendingUpdate = true;
50
+ rafId = requestAnimationFrame(() => {
51
+ subscribers.forEach((subscriber, subscriberId) => {
52
+ const isVisible = visibilityMap.get(subscriberId) ?? true;
53
+ if (isVisible) {
54
+ subscriber.callback();
55
+ }
56
+ });
57
+ pendingUpdate = false;
58
+ });
59
+ }
60
+ }
61
+ function stopTimer() {
62
+ if (intervalId) {
63
+ clearTimeout(intervalId);
64
+ intervalId = null;
65
+ }
66
+ if (rafId) {
67
+ cancelAnimationFrame(rafId);
68
+ rafId = null;
69
+ }
70
+ pendingUpdate = false;
71
+ }
72
+ function scheduleNextTick() {
73
+ if (intervalId) {
74
+ clearTimeout(intervalId);
75
+ intervalId = null;
76
+ }
77
+ if (subscribers.size === 0 || !isTabVisible) {
78
+ return;
79
+ }
80
+ intervalId = setTimeout(() => {
81
+ updateVisibleSubscribers();
82
+ scheduleNextTick();
83
+ }, getMinInterval());
84
+ }
85
+ function ensureTimerRunning() {
86
+ if (!intervalId && subscribers.size > 0 && isTabVisible) {
87
+ scheduleNextTick();
88
+ }
89
+ }
90
+ function restartTimer() {
91
+ stopTimer();
92
+ ensureTimerRunning();
93
+ }
94
+ function ensureVisibilityListener() {
95
+ if (visibilityListenerAttached) return;
96
+ if (typeof document === "undefined") return;
97
+ document.addEventListener("visibilitychange", () => {
98
+ isTabVisible = !document.hidden;
99
+ if (isTabVisible && subscribers.size > 0) {
100
+ updateVisibleSubscribers();
101
+ ensureTimerRunning();
102
+ } else if (!isTabVisible) {
103
+ stopTimer();
104
+ }
105
+ });
106
+ visibilityListenerAttached = true;
107
+ }
108
+ function useLiveTimestamp(date, element) {
109
+ const [, setTick] = useState(0);
110
+ const subscriberIdRef = useRef(/* @__PURE__ */ Symbol("live-timestamp-subscriber"));
111
+ useEffect(() => {
112
+ ensureVisibilityListener();
113
+ const subscriberId = subscriberIdRef.current;
114
+ const observedElement = element ?? null;
115
+ const callback = () => {
116
+ const isVisible = visibilityMap.get(subscriberId) ?? true;
117
+ if (isVisible) {
118
+ setTick((t) => t + 1);
119
+ }
120
+ };
121
+ let observer = null;
122
+ if (observedElement) {
123
+ observer = createObserver(subscriberId, callback);
124
+ observer.observe(observedElement);
125
+ visibilityMap.set(subscriberId, false);
126
+ } else {
127
+ visibilityMap.set(subscriberId, true);
128
+ }
129
+ subscribers.set(subscriberId, {
130
+ callback,
131
+ date,
132
+ observer
133
+ });
134
+ ensureTimerRunning();
135
+ return () => {
136
+ const subscriber = subscribers.get(subscriberId);
137
+ if (subscriber?.observer) {
138
+ subscriber.observer.disconnect();
139
+ }
140
+ subscribers.delete(subscriberId);
141
+ visibilityMap.delete(subscriberId);
142
+ if (subscribers.size === 0) {
143
+ stopTimer();
144
+ } else {
145
+ restartTimer();
146
+ }
147
+ };
148
+ }, [date.getTime(), element]);
149
+ return globalNow;
150
+ }
151
+
152
+ export { useLiveTimestamp };
153
+ //# sourceMappingURL=use-live-timestamp.js.map
154
+ //# sourceMappingURL=use-live-timestamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../use-live-timestamp.ts"],"names":[],"mappings":"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","file":"use-live-timestamp.js","sourcesContent":["import { useState, useEffect, useRef } from 'react';\r\n\r\nlet globalNow = new Date();\r\n\r\ntype SubscriberId = symbol;\r\n\r\ntype Subscriber = {\r\n callback: () => void;\r\n date: Date;\r\n observer: IntersectionObserver | null;\r\n};\r\n\r\nconst subscribers = new Map<SubscriberId, Subscriber>();\r\nconst visibilityMap = new Map<SubscriberId, boolean>();\r\n\r\nlet rafId: number | null = null;\r\nlet intervalId: ReturnType<typeof setTimeout> | null = null;\r\nlet isTabVisible = true;\r\nlet pendingUpdate = false;\r\nlet visibilityListenerAttached = false;\r\n\r\nfunction getUpdateInterval(date: Date): number {\r\n const ageMs = Date.now() - date.getTime();\r\n const ageMinutes = ageMs / 60000;\r\n\r\n if (ageMinutes < 1) return 10000;\r\n if (ageMinutes < 60) return 30000;\r\n if (ageMinutes < 1440) return 300000;\r\n return 3600000;\r\n}\r\n\r\nfunction getMinInterval(): number {\r\n if (subscribers.size === 0) return 30000;\r\n const intervals = Array.from(subscribers.values()).map((sub) => getUpdateInterval(sub.date));\r\n return Math.max(Math.min(...intervals), 10000);\r\n}\r\n\r\nconst createObserver = (\r\n subscriberId: SubscriberId,\r\n callback: () => void\r\n): IntersectionObserver => {\r\n return new IntersectionObserver(\r\n (entries) => {\r\n entries.forEach((entry) => {\r\n visibilityMap.set(subscriberId, entry.isIntersecting);\r\n if (entry.isIntersecting) {\r\n callback();\r\n }\r\n });\r\n },\r\n {\r\n root: null,\r\n rootMargin: '50px',\r\n threshold: 0.01,\r\n }\r\n );\r\n};\r\n\r\nfunction updateVisibleSubscribers() {\r\n if (!isTabVisible || subscribers.size === 0) {\r\n pendingUpdate = false;\r\n return;\r\n }\r\n\r\n globalNow = new Date();\r\n\r\n if (!pendingUpdate) {\r\n pendingUpdate = true;\r\n rafId = requestAnimationFrame(() => {\r\n subscribers.forEach((subscriber, subscriberId) => {\r\n const isVisible = visibilityMap.get(subscriberId) ?? true;\r\n if (isVisible) {\r\n subscriber.callback();\r\n }\r\n });\r\n pendingUpdate = false;\r\n });\r\n }\r\n}\r\n\r\nfunction stopTimer() {\r\n if (intervalId) {\r\n clearTimeout(intervalId);\r\n intervalId = null;\r\n }\r\n if (rafId) {\r\n cancelAnimationFrame(rafId);\r\n rafId = null;\r\n }\r\n pendingUpdate = false;\r\n}\r\n\r\nfunction scheduleNextTick() {\r\n if (intervalId) {\r\n clearTimeout(intervalId);\r\n intervalId = null;\r\n }\r\n\r\n if (subscribers.size === 0 || !isTabVisible) {\r\n return;\r\n }\r\n\r\n intervalId = setTimeout(() => {\r\n updateVisibleSubscribers();\r\n scheduleNextTick();\r\n }, getMinInterval());\r\n}\r\n\r\nfunction ensureTimerRunning() {\r\n if (!intervalId && subscribers.size > 0 && isTabVisible) {\r\n scheduleNextTick();\r\n }\r\n}\r\n\r\nfunction restartTimer() {\r\n stopTimer();\r\n ensureTimerRunning();\r\n}\r\n\r\nfunction ensureVisibilityListener(): void {\r\n if (visibilityListenerAttached) return;\r\n if (typeof document === 'undefined') return;\r\n\r\n document.addEventListener('visibilitychange', () => {\r\n isTabVisible = !document.hidden;\r\n if (isTabVisible && subscribers.size > 0) {\r\n updateVisibleSubscribers();\r\n ensureTimerRunning();\r\n } else if (!isTabVisible) {\r\n stopTimer();\r\n }\r\n });\r\n visibilityListenerAttached = true;\r\n}\r\n\r\n/**\r\n * Live-updating timestamp hook with visibility-aware adaptive intervals.\r\n */\r\nexport function useLiveTimestamp(\r\n date: Date,\r\n element?: Element | null,\r\n): Date {\r\n const [, setTick] = useState(0);\r\n const subscriberIdRef = useRef<SubscriberId>(Symbol('live-timestamp-subscriber'));\r\n\r\n useEffect(() => {\r\n ensureVisibilityListener();\r\n const subscriberId = subscriberIdRef.current;\r\n const observedElement = element ?? null;\r\n\r\n const callback = () => {\r\n const isVisible = visibilityMap.get(subscriberId) ?? true;\r\n if (isVisible) {\r\n setTick((t) => t + 1);\r\n }\r\n };\r\n\r\n let observer: IntersectionObserver | null = null;\r\n if (observedElement) {\r\n observer = createObserver(subscriberId, callback);\r\n observer.observe(observedElement);\r\n visibilityMap.set(subscriberId, false);\r\n } else {\r\n visibilityMap.set(subscriberId, true);\r\n }\r\n\r\n subscribers.set(subscriberId, {\r\n callback,\r\n date,\r\n observer,\r\n });\r\n\r\n ensureTimerRunning();\r\n\r\n return () => {\r\n const subscriber = subscribers.get(subscriberId);\r\n if (subscriber?.observer) {\r\n subscriber.observer.disconnect();\r\n }\r\n subscribers.delete(subscriberId);\r\n visibilityMap.delete(subscriberId);\r\n\r\n if (subscribers.size === 0) {\r\n stopTimer();\r\n } else {\r\n restartTimer();\r\n }\r\n };\r\n // Key on the timestamp value, not the Date identity, so callers passing a\r\n // freshly constructed Date each render don't trigger a re-subscribe.\r\n }, [date.getTime(), element]);\r\n\r\n return globalNow;\r\n}\r\n"]}
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // use-media-query.ts
6
+ function getMatches(query, defaultValue) {
7
+ if (typeof window === "undefined") return defaultValue;
8
+ return window.matchMedia(query).matches;
9
+ }
10
+ function useMediaQuery(query, { defaultValue = false } = {}) {
11
+ const [matches, setMatches] = react.useState(() => getMatches(query, defaultValue));
12
+ react.useEffect(() => {
13
+ const mql = window.matchMedia(query);
14
+ const onChange = () => setMatches(mql.matches);
15
+ setMatches(mql.matches);
16
+ mql.addEventListener("change", onChange);
17
+ return () => mql.removeEventListener("change", onChange);
18
+ }, [query]);
19
+ return matches;
20
+ }
21
+
22
+ exports.useMediaQuery = useMediaQuery;
23
+ //# sourceMappingURL=use-media-query.cjs.map
24
+ //# sourceMappingURL=use-media-query.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../use-media-query.ts"],"names":["useState","useEffect"],"mappings":";;;;;AAOA,SAAS,UAAA,CAAW,OAAe,YAAA,EAAgC;AACjE,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,YAAA;AAC1C,EAAA,OAAO,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA,CAAE,OAAA;AAClC;AAKO,SAAS,cACd,KAAA,EACA,EAAE,eAAe,KAAA,EAAM,GAA0B,EAAC,EACzC;AACT,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAIA,eAAkB,MAAM,UAAA,CAAW,KAAA,EAAO,YAAY,CAAC,CAAA;AAErF,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AACnC,IAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AAE7C,IAAA,UAAA,CAAW,IAAI,OAAO,CAAA;AACtB,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AACvC,IAAA,OAAO,MAAM,GAAA,CAAI,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAAA,EACzD,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,OAAA;AACT","file":"use-media-query.cjs","sourcesContent":["import { useEffect, useState } from 'react';\r\n\r\nexport interface UseMediaQueryOptions {\r\n /** Value returned during SSR and before the first browser measurement. */\r\n defaultValue?: boolean;\r\n}\r\n\r\nfunction getMatches(query: string, defaultValue: boolean): boolean {\r\n if (typeof window === 'undefined') return defaultValue;\r\n return window.matchMedia(query).matches;\r\n}\r\n\r\n/**\r\n * Subscribe to a CSS media query.\r\n */\r\nexport function useMediaQuery(\r\n query: string,\r\n { defaultValue = false }: UseMediaQueryOptions = {},\r\n): boolean {\r\n const [matches, setMatches] = useState<boolean>(() => getMatches(query, defaultValue));\r\n\r\n useEffect(() => {\r\n const mql = window.matchMedia(query);\r\n const onChange = () => setMatches(mql.matches);\r\n\r\n setMatches(mql.matches);\r\n mql.addEventListener('change', onChange);\r\n return () => mql.removeEventListener('change', onChange);\r\n }, [query]);\r\n\r\n return matches;\r\n}\r\n"]}
@@ -0,0 +1,10 @@
1
+ interface UseMediaQueryOptions {
2
+ /** Value returned during SSR and before the first browser measurement. */
3
+ defaultValue?: boolean;
4
+ }
5
+ /**
6
+ * Subscribe to a CSS media query.
7
+ */
8
+ declare function useMediaQuery(query: string, { defaultValue }?: UseMediaQueryOptions): boolean;
9
+
10
+ export { type UseMediaQueryOptions, useMediaQuery };
@@ -0,0 +1,10 @@
1
+ interface UseMediaQueryOptions {
2
+ /** Value returned during SSR and before the first browser measurement. */
3
+ defaultValue?: boolean;
4
+ }
5
+ /**
6
+ * Subscribe to a CSS media query.
7
+ */
8
+ declare function useMediaQuery(query: string, { defaultValue }?: UseMediaQueryOptions): boolean;
9
+
10
+ export { type UseMediaQueryOptions, useMediaQuery };
@@ -0,0 +1,22 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ // use-media-query.ts
4
+ function getMatches(query, defaultValue) {
5
+ if (typeof window === "undefined") return defaultValue;
6
+ return window.matchMedia(query).matches;
7
+ }
8
+ function useMediaQuery(query, { defaultValue = false } = {}) {
9
+ const [matches, setMatches] = useState(() => getMatches(query, defaultValue));
10
+ useEffect(() => {
11
+ const mql = window.matchMedia(query);
12
+ const onChange = () => setMatches(mql.matches);
13
+ setMatches(mql.matches);
14
+ mql.addEventListener("change", onChange);
15
+ return () => mql.removeEventListener("change", onChange);
16
+ }, [query]);
17
+ return matches;
18
+ }
19
+
20
+ export { useMediaQuery };
21
+ //# sourceMappingURL=use-media-query.js.map
22
+ //# sourceMappingURL=use-media-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../use-media-query.ts"],"names":[],"mappings":";;;AAOA,SAAS,UAAA,CAAW,OAAe,YAAA,EAAgC;AACjE,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,YAAA;AAC1C,EAAA,OAAO,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA,CAAE,OAAA;AAClC;AAKO,SAAS,cACd,KAAA,EACA,EAAE,eAAe,KAAA,EAAM,GAA0B,EAAC,EACzC;AACT,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAI,SAAkB,MAAM,UAAA,CAAW,KAAA,EAAO,YAAY,CAAC,CAAA;AAErF,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AACnC,IAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AAE7C,IAAA,UAAA,CAAW,IAAI,OAAO,CAAA;AACtB,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AACvC,IAAA,OAAO,MAAM,GAAA,CAAI,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAAA,EACzD,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,OAAA;AACT","file":"use-media-query.js","sourcesContent":["import { useEffect, useState } from 'react';\r\n\r\nexport interface UseMediaQueryOptions {\r\n /** Value returned during SSR and before the first browser measurement. */\r\n defaultValue?: boolean;\r\n}\r\n\r\nfunction getMatches(query: string, defaultValue: boolean): boolean {\r\n if (typeof window === 'undefined') return defaultValue;\r\n return window.matchMedia(query).matches;\r\n}\r\n\r\n/**\r\n * Subscribe to a CSS media query.\r\n */\r\nexport function useMediaQuery(\r\n query: string,\r\n { defaultValue = false }: UseMediaQueryOptions = {},\r\n): boolean {\r\n const [matches, setMatches] = useState<boolean>(() => getMatches(query, defaultValue));\r\n\r\n useEffect(() => {\r\n const mql = window.matchMedia(query);\r\n const onChange = () => setMatches(mql.matches);\r\n\r\n setMatches(mql.matches);\r\n mql.addEventListener('change', onChange);\r\n return () => mql.removeEventListener('change', onChange);\r\n }, [query]);\r\n\r\n return matches;\r\n}\r\n"]}
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // use-media-query.ts
6
+ function getMatches(query, defaultValue) {
7
+ if (typeof window === "undefined") return defaultValue;
8
+ return window.matchMedia(query).matches;
9
+ }
10
+ function useMediaQuery(query, { defaultValue = false } = {}) {
11
+ const [matches, setMatches] = react.useState(() => getMatches(query, defaultValue));
12
+ react.useEffect(() => {
13
+ const mql = window.matchMedia(query);
14
+ const onChange = () => setMatches(mql.matches);
15
+ setMatches(mql.matches);
16
+ mql.addEventListener("change", onChange);
17
+ return () => mql.removeEventListener("change", onChange);
18
+ }, [query]);
19
+ return matches;
20
+ }
21
+
22
+ // use-mobile.ts
23
+ var MOBILE_BREAKPOINT = 768;
24
+ function getMobileMediaQuery(breakpoint) {
25
+ return `(max-width: ${breakpoint - 1}px)`;
26
+ }
27
+ function useIsMobile({
28
+ breakpoint = MOBILE_BREAKPOINT,
29
+ defaultValue = false
30
+ } = {}) {
31
+ return useMediaQuery(getMobileMediaQuery(breakpoint), { defaultValue });
32
+ }
33
+
34
+ exports.MOBILE_BREAKPOINT = MOBILE_BREAKPOINT;
35
+ exports.useIsMobile = useIsMobile;
36
+ //# sourceMappingURL=use-mobile.cjs.map
37
+ //# sourceMappingURL=use-mobile.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../use-media-query.ts","../use-mobile.ts"],"names":["useState","useEffect"],"mappings":";;;;;AAOA,SAAS,UAAA,CAAW,OAAe,YAAA,EAAgC;AACjE,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,YAAA;AAC1C,EAAA,OAAO,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA,CAAE,OAAA;AAClC;AAKO,SAAS,cACd,KAAA,EACA,EAAE,eAAe,KAAA,EAAM,GAA0B,EAAC,EACzC;AACT,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAIA,eAAkB,MAAM,UAAA,CAAW,KAAA,EAAO,YAAY,CAAC,CAAA;AAErF,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AACnC,IAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AAE7C,IAAA,UAAA,CAAW,IAAI,OAAO,CAAA;AACtB,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AACvC,IAAA,OAAO,MAAM,GAAA,CAAI,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAAA,EACzD,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,OAAA;AACT;;;AC7BO,IAAM,iBAAA,GAAoB;AASjC,SAAS,oBAAoB,UAAA,EAA4B;AACvD,EAAA,OAAO,CAAA,YAAA,EAAe,aAAa,CAAC,CAAA,GAAA,CAAA;AACtC;AAEO,SAAS,WAAA,CAAY;AAAA,EAC1B,UAAA,GAAa,iBAAA;AAAA,EACb,YAAA,GAAe;AACjB,CAAA,GAAwB,EAAC,EAAY;AACnC,EAAA,OAAO,cAAc,mBAAA,CAAoB,UAAU,CAAA,EAAG,EAAE,cAAc,CAAA;AACxE","file":"use-mobile.cjs","sourcesContent":["import { useEffect, useState } from 'react';\r\n\r\nexport interface UseMediaQueryOptions {\r\n /** Value returned during SSR and before the first browser measurement. */\r\n defaultValue?: boolean;\r\n}\r\n\r\nfunction getMatches(query: string, defaultValue: boolean): boolean {\r\n if (typeof window === 'undefined') return defaultValue;\r\n return window.matchMedia(query).matches;\r\n}\r\n\r\n/**\r\n * Subscribe to a CSS media query.\r\n */\r\nexport function useMediaQuery(\r\n query: string,\r\n { defaultValue = false }: UseMediaQueryOptions = {},\r\n): boolean {\r\n const [matches, setMatches] = useState<boolean>(() => getMatches(query, defaultValue));\r\n\r\n useEffect(() => {\r\n const mql = window.matchMedia(query);\r\n const onChange = () => setMatches(mql.matches);\r\n\r\n setMatches(mql.matches);\r\n mql.addEventListener('change', onChange);\r\n return () => mql.removeEventListener('change', onChange);\r\n }, [query]);\r\n\r\n return matches;\r\n}\r\n","import { useMediaQuery } from './use-media-query';\r\n\r\nexport const MOBILE_BREAKPOINT = 768;\r\n\r\nexport interface UseIsMobileOptions {\r\n /** Pixel breakpoint below which the viewport is considered mobile. */\r\n breakpoint?: number;\r\n /** Value returned during SSR and before the first browser measurement. */\r\n defaultValue?: boolean;\r\n}\r\n\r\nfunction getMobileMediaQuery(breakpoint: number): string {\r\n return `(max-width: ${breakpoint - 1}px)`;\r\n}\r\n\r\nexport function useIsMobile({\r\n breakpoint = MOBILE_BREAKPOINT,\r\n defaultValue = false,\r\n}: UseIsMobileOptions = {}): boolean {\r\n return useMediaQuery(getMobileMediaQuery(breakpoint), { defaultValue });\r\n}\r\n"]}
@@ -0,0 +1,10 @@
1
+ declare const MOBILE_BREAKPOINT = 768;
2
+ interface UseIsMobileOptions {
3
+ /** Pixel breakpoint below which the viewport is considered mobile. */
4
+ breakpoint?: number;
5
+ /** Value returned during SSR and before the first browser measurement. */
6
+ defaultValue?: boolean;
7
+ }
8
+ declare function useIsMobile({ breakpoint, defaultValue, }?: UseIsMobileOptions): boolean;
9
+
10
+ export { MOBILE_BREAKPOINT, type UseIsMobileOptions, useIsMobile };
@@ -0,0 +1,10 @@
1
+ declare const MOBILE_BREAKPOINT = 768;
2
+ interface UseIsMobileOptions {
3
+ /** Pixel breakpoint below which the viewport is considered mobile. */
4
+ breakpoint?: number;
5
+ /** Value returned during SSR and before the first browser measurement. */
6
+ defaultValue?: boolean;
7
+ }
8
+ declare function useIsMobile({ breakpoint, defaultValue, }?: UseIsMobileOptions): boolean;
9
+
10
+ export { MOBILE_BREAKPOINT, type UseIsMobileOptions, useIsMobile };
@@ -0,0 +1,34 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ // use-media-query.ts
4
+ function getMatches(query, defaultValue) {
5
+ if (typeof window === "undefined") return defaultValue;
6
+ return window.matchMedia(query).matches;
7
+ }
8
+ function useMediaQuery(query, { defaultValue = false } = {}) {
9
+ const [matches, setMatches] = useState(() => getMatches(query, defaultValue));
10
+ useEffect(() => {
11
+ const mql = window.matchMedia(query);
12
+ const onChange = () => setMatches(mql.matches);
13
+ setMatches(mql.matches);
14
+ mql.addEventListener("change", onChange);
15
+ return () => mql.removeEventListener("change", onChange);
16
+ }, [query]);
17
+ return matches;
18
+ }
19
+
20
+ // use-mobile.ts
21
+ var MOBILE_BREAKPOINT = 768;
22
+ function getMobileMediaQuery(breakpoint) {
23
+ return `(max-width: ${breakpoint - 1}px)`;
24
+ }
25
+ function useIsMobile({
26
+ breakpoint = MOBILE_BREAKPOINT,
27
+ defaultValue = false
28
+ } = {}) {
29
+ return useMediaQuery(getMobileMediaQuery(breakpoint), { defaultValue });
30
+ }
31
+
32
+ export { MOBILE_BREAKPOINT, useIsMobile };
33
+ //# sourceMappingURL=use-mobile.js.map
34
+ //# sourceMappingURL=use-mobile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../use-media-query.ts","../use-mobile.ts"],"names":[],"mappings":";;;AAOA,SAAS,UAAA,CAAW,OAAe,YAAA,EAAgC;AACjE,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,YAAA;AAC1C,EAAA,OAAO,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA,CAAE,OAAA;AAClC;AAKO,SAAS,cACd,KAAA,EACA,EAAE,eAAe,KAAA,EAAM,GAA0B,EAAC,EACzC;AACT,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAI,SAAkB,MAAM,UAAA,CAAW,KAAA,EAAO,YAAY,CAAC,CAAA;AAErF,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AACnC,IAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AAE7C,IAAA,UAAA,CAAW,IAAI,OAAO,CAAA;AACtB,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AACvC,IAAA,OAAO,MAAM,GAAA,CAAI,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAAA,EACzD,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,OAAA;AACT;;;AC7BO,IAAM,iBAAA,GAAoB;AASjC,SAAS,oBAAoB,UAAA,EAA4B;AACvD,EAAA,OAAO,CAAA,YAAA,EAAe,aAAa,CAAC,CAAA,GAAA,CAAA;AACtC;AAEO,SAAS,WAAA,CAAY;AAAA,EAC1B,UAAA,GAAa,iBAAA;AAAA,EACb,YAAA,GAAe;AACjB,CAAA,GAAwB,EAAC,EAAY;AACnC,EAAA,OAAO,cAAc,mBAAA,CAAoB,UAAU,CAAA,EAAG,EAAE,cAAc,CAAA;AACxE","file":"use-mobile.js","sourcesContent":["import { useEffect, useState } from 'react';\r\n\r\nexport interface UseMediaQueryOptions {\r\n /** Value returned during SSR and before the first browser measurement. */\r\n defaultValue?: boolean;\r\n}\r\n\r\nfunction getMatches(query: string, defaultValue: boolean): boolean {\r\n if (typeof window === 'undefined') return defaultValue;\r\n return window.matchMedia(query).matches;\r\n}\r\n\r\n/**\r\n * Subscribe to a CSS media query.\r\n */\r\nexport function useMediaQuery(\r\n query: string,\r\n { defaultValue = false }: UseMediaQueryOptions = {},\r\n): boolean {\r\n const [matches, setMatches] = useState<boolean>(() => getMatches(query, defaultValue));\r\n\r\n useEffect(() => {\r\n const mql = window.matchMedia(query);\r\n const onChange = () => setMatches(mql.matches);\r\n\r\n setMatches(mql.matches);\r\n mql.addEventListener('change', onChange);\r\n return () => mql.removeEventListener('change', onChange);\r\n }, [query]);\r\n\r\n return matches;\r\n}\r\n","import { useMediaQuery } from './use-media-query';\r\n\r\nexport const MOBILE_BREAKPOINT = 768;\r\n\r\nexport interface UseIsMobileOptions {\r\n /** Pixel breakpoint below which the viewport is considered mobile. */\r\n breakpoint?: number;\r\n /** Value returned during SSR and before the first browser measurement. */\r\n defaultValue?: boolean;\r\n}\r\n\r\nfunction getMobileMediaQuery(breakpoint: number): string {\r\n return `(max-width: ${breakpoint - 1}px)`;\r\n}\r\n\r\nexport function useIsMobile({\r\n breakpoint = MOBILE_BREAKPOINT,\r\n defaultValue = false,\r\n}: UseIsMobileOptions = {}): boolean {\r\n return useMediaQuery(getMobileMediaQuery(breakpoint), { defaultValue });\r\n}\r\n"]}