@checkstack/ui 1.3.4 → 1.3.5

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @checkstack/ui
2
2
 
3
+ ## 1.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 286491a: Added automatic FPS detection that enables "Low Power Mode" once for devices running below 50 FPS, ensuring smooth performance even for users unaware of the manual toggle.
8
+
3
9
  ## 1.3.4
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/ui",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "dependencies": {
@@ -1,9 +1,10 @@
1
1
  import React, { createContext, useContext, useEffect, useState } from "react";
2
+ import { useToast } from "./ToastProvider";
2
3
 
3
4
  const STORAGE_KEY = "checkstack-low-power";
4
5
 
5
6
  interface PerformanceContextValue {
6
- /**
7
+ /**
7
8
  * Whether the application should run in low-power mode.
8
9
  * Derived from (manualLowPower || prefersReducedMotion).
9
10
  */
@@ -38,9 +39,13 @@ interface PerformanceProviderProps {
38
39
  * Supports manual override (persisted to localStorage) and respects OS-level
39
40
  * Reduced Motion preferences.
40
41
  */
41
- export const PerformanceProvider: React.FC<PerformanceProviderProps> = ({ children }) => {
42
+ export const PerformanceProvider: React.FC<PerformanceProviderProps> = ({
43
+ children,
44
+ }) => {
45
+ const toast = useToast();
42
46
  const [manualLowPower, setManualLowPower] = useState<boolean>(false);
43
- const [prefersReducedMotion, setPrefersReducedMotion] = useState<boolean>(false);
47
+ const [prefersReducedMotion, setPrefersReducedMotion] =
48
+ useState<boolean>(false);
44
49
  const [isLoaded, setIsLoaded] = useState<boolean>(false);
45
50
 
46
51
  useEffect(() => {
@@ -51,11 +56,14 @@ export const PerformanceProvider: React.FC<PerformanceProviderProps> = ({ childr
51
56
  }
52
57
 
53
58
  // 2. Initialize Reduced Motion Detection
54
- const mediaQuery = globalThis.matchMedia("(prefers-reduced-motion: reduce)");
59
+ const mediaQuery = globalThis.matchMedia(
60
+ "(prefers-reduced-motion: reduce)",
61
+ );
55
62
  setPrefersReducedMotion(mediaQuery.matches);
56
63
 
57
64
  // 3. Listen for changes in OS-level settings
58
- const listener = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches);
65
+ const listener = (e: MediaQueryListEvent) =>
66
+ setPrefersReducedMotion(e.matches);
59
67
  mediaQuery.addEventListener("change", listener);
60
68
 
61
69
  setIsLoaded(true);
@@ -63,6 +71,61 @@ export const PerformanceProvider: React.FC<PerformanceProviderProps> = ({ childr
63
71
  return () => mediaQuery.removeEventListener("change", listener);
64
72
  }, []);
65
73
 
74
+ useEffect(() => {
75
+ // 4. Automatic FPS Detection (Runs once per device)
76
+ const stored = globalThis.localStorage?.getItem(STORAGE_KEY);
77
+ if (stored !== null) return; // User already has a saved preference, don't auto-detect
78
+
79
+ let rAFId: number;
80
+ let isCancelled = false;
81
+
82
+ // Wait 1 second for initial render and hydration to settle
83
+ const warmupTimer = setTimeout(() => {
84
+ if (isCancelled) return;
85
+
86
+ let frameCount = 0;
87
+ let startTime = performance.now();
88
+
89
+ const measure = (time: DOMHighResTimeStamp) => {
90
+ if (isCancelled) return;
91
+ if (frameCount === 0) startTime = time;
92
+ frameCount++;
93
+ const elapsed = time - startTime;
94
+
95
+ if (elapsed >= 1000) {
96
+ const fps = (frameCount * 1000) / elapsed;
97
+ if (fps < 50) {
98
+ setManualLowPower(true);
99
+ globalThis.localStorage?.setItem(STORAGE_KEY, "true");
100
+ toast.info(
101
+ "Low power mode was automatically activated to improve performance. Hardware acceleration appears to be disabled.",
102
+ 8000
103
+ );
104
+ console.warn(
105
+ `[PerformanceProvider] Auto-enabled Low Power Mode. Detected FPS: ${fps.toFixed(1)}`,
106
+ );
107
+ } else {
108
+ // Save 'false' to prevent future auto-detection checks
109
+ globalThis.localStorage?.setItem(STORAGE_KEY, "false");
110
+ console.log(
111
+ `[PerformanceProvider] Performance OK. Detected FPS: ${fps.toFixed(1)}`,
112
+ );
113
+ }
114
+ } else {
115
+ rAFId = globalThis.requestAnimationFrame(measure);
116
+ }
117
+ };
118
+
119
+ rAFId = globalThis.requestAnimationFrame(measure);
120
+ }, 1000);
121
+
122
+ return () => {
123
+ isCancelled = true;
124
+ clearTimeout(warmupTimer);
125
+ if (rAFId) globalThis.cancelAnimationFrame(rAFId);
126
+ };
127
+ }, [toast]);
128
+
66
129
  const toggleManualLowPower = () => {
67
130
  setManualLowPower((prev) => {
68
131
  const next = !prev;