@dfosco/storyboard-react 4.0.0-beta.22 → 4.0.0-beta.24

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,10 +1,10 @@
1
1
  {
2
2
  "name": "@dfosco/storyboard-react",
3
- "version": "4.0.0-beta.22",
3
+ "version": "4.0.0-beta.24",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@dfosco/storyboard-core": "4.0.0-beta.22",
7
- "@dfosco/tiny-canvas": "4.0.0-beta.22",
6
+ "@dfosco/storyboard-core": "4.0.0-beta.24",
7
+ "@dfosco/tiny-canvas": "4.0.0-beta.24",
8
8
  "@neodrag/react": "^2.3.1",
9
9
  "glob": "^11.0.0",
10
10
  "jsonc-parser": "^3.3.1",
@@ -1,6 +1,6 @@
1
1
  import { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
- import { buildPrototypeIndex } from '@dfosco/storyboard-core'
3
+ import { buildPrototypeIndex, getFlag } from '@dfosco/storyboard-core'
4
4
  import WidgetWrapper from './WidgetWrapper.jsx'
5
5
  import { readProp, prototypeEmbedSchema } from './widgetProps.js'
6
6
  import { getEmbedChromeVars } from './embedTheme.js'
@@ -9,6 +9,10 @@ import { useIframeQueue } from './useViewportEntry.js'
9
9
  import styles from './PrototypeEmbed.module.css'
10
10
  import overlayStyles from './embedOverlay.module.css'
11
11
 
12
+ function devLog(...args) {
13
+ try { if (getFlag('dev-logs')) console.log('[canvas:prototype-embed]', ...args) } catch { /* */ }
14
+ }
15
+
12
16
  function formatName(name) {
13
17
  return name
14
18
  .replace(/[-_]/g, ' ')
@@ -72,26 +76,40 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
72
76
 
73
77
  // Sequential iframe queue — prevents stampede when many embeds lack snapshots.
74
78
  // Widgets with snapshots skip the queue entirely; others load one at a time.
75
- const { ready: queueReady, releaseSlot } = useIframeQueue(hasSnapshot || isExternal)
79
+ const { ready: queueReady, releaseSlot } = useIframeQueue(hasSnapshot || isExternal, widgetId)
76
80
  const [preloadIframe, setPreloadIframe] = useState(hasSnapshot || isExternal)
77
81
  const [iframeLoaded, setIframeLoaded] = useState(false)
78
82
  const [showIframe, setShowIframe] = useState(hasSnapshot || isExternal)
79
83
  const [showSpinner, setShowSpinner] = useState(false)
80
84
  const capturingRef = useRef(false)
81
85
 
86
+ devLog(widgetId, { hasSnapshot, isExternal, queueReady, preloadIframe, showIframe, iframeLoaded, src })
87
+
82
88
  // Start loading when the queue grants this widget a slot
83
89
  useEffect(() => {
84
90
  if (queueReady && !preloadIframe) {
91
+ devLog(widgetId, 'queue ready → loading iframe')
85
92
  setPreloadIframe(true)
86
93
  setShowIframe(true)
87
94
  }
88
95
  }, [queueReady, preloadIframe])
89
96
 
90
- // Release the queue slot once the iframe has loaded
97
+ // Release the queue slot once the iframe has loaded or user clicked to interact
91
98
  useEffect(() => {
92
- if (iframeLoaded) releaseSlot()
99
+ if (iframeLoaded) {
100
+ devLog(widgetId, 'iframe loaded')
101
+ releaseSlot()
102
+ }
93
103
  }, [iframeLoaded, releaseSlot])
94
104
 
105
+ // Click-to-interact: immediately start iframe and release queue slot for others
106
+ const activateIframe = useCallback(() => {
107
+ devLog(widgetId, 'user activated → jumping queue')
108
+ setShowIframe(true)
109
+ setPreloadIframe(true)
110
+ releaseSlot()
111
+ }, [releaseSlot])
112
+
95
113
  // Show spinner only after 500ms of loading
96
114
  useEffect(() => {
97
115
  if (showIframe && !iframeLoaded && hasSnapshot) {
@@ -553,8 +571,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
553
571
  }}
554
572
  onClick={(e) => {
555
573
  if (e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return
556
- setShowIframe(true)
557
- setPreloadIframe(true)
574
+ activateIframe()
558
575
  enterInteractive()
559
576
  }}
560
577
  role="button"
@@ -563,8 +580,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
563
580
  if (e.key === 'Enter' || e.key === ' ') {
564
581
  e.preventDefault()
565
582
  e.stopPropagation()
566
- setShowIframe(true)
567
- setPreloadIframe(true)
583
+ activateIframe()
568
584
  enterInteractive()
569
585
  }
570
586
  }}
@@ -12,7 +12,7 @@
12
12
  * Props: { storyId, exportName, width, height }
13
13
  */
14
14
  import { forwardRef, useImperativeHandle, useRef, useCallback, useState, useEffect, useMemo } from 'react'
15
- import { getStoryData } from '@dfosco/storyboard-core'
15
+ import { getStoryData, getFlag } from '@dfosco/storyboard-core'
16
16
  import { createInspectorHighlighter } from '@dfosco/storyboard-core/inspector/highlighter'
17
17
  import WidgetWrapper from './WidgetWrapper.jsx'
18
18
  import ResizeHandle from './ResizeHandle.jsx'
@@ -21,6 +21,10 @@ import { useIframeQueue } from './useViewportEntry.js'
21
21
  import styles from './StoryWidget.module.css'
22
22
  import overlayStyles from './embedOverlay.module.css'
23
23
 
24
+ function devLog(...args) {
25
+ try { if (getFlag('dev-logs')) console.log('[canvas:story-widget]', ...args) } catch { /* */ }
26
+ }
27
+
24
28
  function resolveStoryUrl(storyId, exportName) {
25
29
  const story = getStoryData(storyId)
26
30
  if (!story?._route) return null
@@ -114,26 +118,40 @@ export default forwardRef(function StoryWidget({ id: widgetId, props, onUpdate,
114
118
  const hasSnapshot = !!currentSnapshot
115
119
 
116
120
  // Sequential iframe queue — prevents stampede when many embeds lack snapshots.
117
- const { ready: queueReady, releaseSlot } = useIframeQueue(hasSnapshot)
121
+ const { ready: queueReady, releaseSlot } = useIframeQueue(hasSnapshot, widgetId)
118
122
  const [preloadIframe, setPreloadIframe] = useState(hasSnapshot)
119
123
  const [iframeLoaded, setIframeLoaded] = useState(false)
120
124
  const [showIframe, setShowIframe] = useState(hasSnapshot)
121
125
  const [showSpinner, setShowSpinner] = useState(false)
122
126
  const capturingRef = useRef(false)
123
127
 
128
+ devLog(widgetId, { hasSnapshot, queueReady, preloadIframe, showIframe, iframeLoaded, storyId })
129
+
124
130
  // Start loading when the queue grants this widget a slot
125
131
  useEffect(() => {
126
132
  if (queueReady && !preloadIframe) {
133
+ devLog(widgetId, 'queue ready → loading iframe')
127
134
  setPreloadIframe(true)
128
135
  setShowIframe(true)
129
136
  }
130
137
  }, [queueReady, preloadIframe])
131
138
 
132
- // Release the queue slot once the iframe has loaded
139
+ // Release the queue slot once the iframe has loaded or user clicked to interact
133
140
  useEffect(() => {
134
- if (iframeLoaded) releaseSlot()
141
+ if (iframeLoaded) {
142
+ devLog(widgetId, 'iframe loaded')
143
+ releaseSlot()
144
+ }
135
145
  }, [iframeLoaded, releaseSlot])
136
146
 
147
+ // Click-to-interact: immediately start iframe and release queue slot for others
148
+ const activateIframe = useCallback(() => {
149
+ devLog(widgetId, 'user activated → jumping queue')
150
+ setShowIframe(true)
151
+ setPreloadIframe(true)
152
+ releaseSlot()
153
+ }, [releaseSlot, widgetId])
154
+
137
155
  // Show spinner only after 500ms of loading
138
156
  useEffect(() => {
139
157
  if (showIframe && !iframeLoaded && hasSnapshot) {
@@ -452,8 +470,7 @@ export default forwardRef(function StoryWidget({ id: widgetId, props, onUpdate,
452
470
  }}
453
471
  onClick={(e) => {
454
472
  if (e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return
455
- setShowIframe(true)
456
- setPreloadIframe(true)
473
+ activateIframe()
457
474
  enterInteractive()
458
475
  }}
459
476
  role="button"
@@ -462,8 +479,7 @@ export default forwardRef(function StoryWidget({ id: widgetId, props, onUpdate,
462
479
  if (e.key === 'Enter' || e.key === ' ') {
463
480
  e.preventDefault()
464
481
  e.stopPropagation()
465
- setShowIframe(true)
466
- setPreloadIframe(true)
482
+ activateIframe()
467
483
  enterInteractive()
468
484
  }
469
485
  }}
@@ -1,4 +1,9 @@
1
1
  import { useState, useEffect, useCallback, useRef } from 'react'
2
+ import { getFlag } from '@dfosco/storyboard-core'
3
+
4
+ function devLog(...args) {
5
+ try { if (getFlag('dev-logs')) console.log('[canvas:iframe-queue]', ...args) } catch { /* flag system not initialized */ }
6
+ }
2
7
 
3
8
  /**
4
9
  * Sequential iframe loading queue.
@@ -19,13 +24,15 @@ let _active = false
19
24
  function processQueue() {
20
25
  if (_active || _queue.length === 0) return
21
26
  _active = true
22
- const { resolve } = _queue.shift()
27
+ const { resolve, label } = _queue.shift()
28
+ devLog(`slot granted → ${label} (${_queue.length} queued)`)
23
29
 
24
30
  let released = false
25
31
  const release = () => {
26
32
  if (released) return
27
33
  released = true
28
34
  _active = false
35
+ devLog(`slot released ← ${label}`)
29
36
  processQueue()
30
37
  }
31
38
 
@@ -34,9 +41,10 @@ function processQueue() {
34
41
  resolve(release)
35
42
  }
36
43
 
37
- function requestSlot() {
44
+ function requestSlot(label = '?') {
38
45
  return new Promise((resolve) => {
39
- _queue.push({ resolve })
46
+ _queue.push({ resolve, label })
47
+ devLog(`queued ${label} (position ${_queue.length})`)
40
48
  processQueue()
41
49
  })
42
50
  }
@@ -49,9 +57,10 @@ function requestSlot() {
49
57
  * it's this widget's turn.
50
58
  *
51
59
  * @param {boolean} hasUsableSnapshot - whether the widget has a working snapshot
52
- * @returns {{ ready: boolean }}
60
+ * @param {string} [label] - debug label for dev logs
61
+ * @returns {{ ready: boolean, releaseSlot: Function }}
53
62
  */
54
- export function useIframeQueue(hasUsableSnapshot) {
63
+ export function useIframeQueue(hasUsableSnapshot, label = '?') {
55
64
  const [ready, setReady] = useState(hasUsableSnapshot)
56
65
  const releaseRef = useRef(null)
57
66
 
@@ -59,7 +68,7 @@ export function useIframeQueue(hasUsableSnapshot) {
59
68
  if (hasUsableSnapshot || ready) return
60
69
 
61
70
  let cancelled = false
62
- requestSlot().then((release) => {
71
+ requestSlot(label).then((release) => {
63
72
  if (cancelled) {
64
73
  release()
65
74
  return