@flesler/cursor-canvas-web 1.5.1 → 1.5.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flesler/cursor-canvas-web",
3
3
  "private": false,
4
- "version": "1.5.1",
4
+ "version": "1.5.2",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/flesler/cursor-canvas-web.git"
@@ -32,4 +32,36 @@ describe('useCanvasState', () => {
32
32
 
33
33
  expect(result.current[0]).toBe(true)
34
34
  })
35
+
36
+ it('falls back to __canvasJsonState when localStorage throws', () => {
37
+ const storage = Object.getOwnPropertyDescriptor(globalThis, 'localStorage')
38
+ Object.defineProperty(globalThis, 'localStorage', {
39
+ configurable: true,
40
+ get() {
41
+ throw new DOMException('sandboxed', 'SecurityError')
42
+ },
43
+ })
44
+ ;(globalThis as { __canvasJsonState?: Record<string, unknown> }).__canvasJsonState = {
45
+ label: 'from-sidecar',
46
+ }
47
+
48
+ const { result } = renderHook(() => useCanvasState('label', 'default'))
49
+
50
+ expect(result.current[0]).toBe('from-sidecar')
51
+ delete (globalThis as { __canvasJsonState?: Record<string, unknown> }).__canvasJsonState
52
+ if (storage) {
53
+ Object.defineProperty(globalThis, 'localStorage', storage)
54
+ }
55
+ })
56
+
57
+ it('reads global __canvasJsonState when localStorage has no value', () => {
58
+ ;(globalThis as { __canvasJsonState?: Record<string, unknown> }).__canvasJsonState = {
59
+ label: 'from-sidecar',
60
+ }
61
+
62
+ const { result } = renderHook(() => useCanvasState('label', 'default'))
63
+
64
+ expect(result.current[0]).toBe('from-sidecar')
65
+ delete (globalThis as { __canvasJsonState?: Record<string, unknown> }).__canvasJsonState
66
+ })
35
67
  })
@@ -8,13 +8,15 @@ export function useCanvasState<T>(
8
8
  const storageKey = `canvas:${key}`
9
9
 
10
10
  const [state, setState] = useState<T>(() => {
11
- if (typeof localStorage === 'undefined') return defaultValue
12
11
  try {
13
12
  const raw = localStorage.getItem(storageKey)
14
- return raw === null ? defaultValue : (JSON.parse(raw) as T)
13
+ if (raw !== null) return JSON.parse(raw) as T
15
14
  } catch {
16
- return defaultValue
15
+ // Sandboxed, SSR, or unavailable; fall through to inlined export state.
17
16
  }
17
+ const inline = inlineCanvasState<T>(key)
18
+ if (inline !== undefined) return inline
19
+ return defaultValue
18
20
  })
19
21
 
20
22
  const set = useCallback<SetCanvasState<T>>(
@@ -25,7 +27,7 @@ export function useCanvasState<T>(
25
27
  ? (action as (prev: T) => T)(prev)
26
28
  : action
27
29
  try {
28
- localStorage?.setItem(storageKey, JSON.stringify(next))
30
+ localStorage.setItem(storageKey, JSON.stringify(next))
29
31
  } catch {
30
32
  // Ignore quota / unavailable storage; state still updates in memory.
31
33
  }
@@ -55,3 +57,13 @@ export function mergeStyle(
55
57
  ): CSSProperties {
56
58
  return { ...base, ...override }
57
59
  }
60
+
61
+ function inlineCanvasState<T>(key: string): T | undefined {
62
+ try {
63
+ const bag = (globalThis as { __canvasJsonState?: Record<string, unknown> }).__canvasJsonState
64
+ if (!bag || !Object.hasOwn(bag, key)) return undefined
65
+ return bag[key] as T
66
+ } catch {
67
+ return undefined
68
+ }
69
+ }