@dfosco/storyboard-react 4.0.0-beta.35 โ 4.0.0-beta.37
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 +4 -3
- package/src/Icon.jsx +179 -0
- package/src/ViewfinderNew.jsx +1172 -0
- package/src/ViewfinderNew.module.css +1773 -0
- package/src/canvas/CanvasPage.jsx +14 -0
- package/src/canvas/widgets/LinkPreview.jsx +74 -10
- package/src/canvas/widgets/MarkdownBlock.module.css +2 -2
- package/src/canvas/widgets/PrototypeEmbed.jsx +11 -8
- package/src/canvas/widgets/StoryWidget.jsx +47 -283
- package/src/canvas/widgets/StoryWidget.module.css +3 -3
- package/src/index.js +1 -1
- package/src/vite/data-plugin.js +24 -0
- package/src/Viewfinder.jsx +0 -72
- package/src/Viewfinder.module.css +0 -235
- package/src/canvas/widgets/refreshQueue.js +0 -111
- package/src/canvas/widgets/useSnapshotCapture.js +0 -161
- package/src/canvas/widgets/useSnapshotCapture.test.jsx +0 -164
|
@@ -1663,6 +1663,20 @@ export default function CanvasPage({ canvasId: canvasIdProp, name, siblingPages
|
|
|
1663
1663
|
return () => document.removeEventListener('wheel', handleWheel)
|
|
1664
1664
|
}, [])
|
|
1665
1665
|
|
|
1666
|
+
// Receive cmd+wheel events forwarded from prototype/story iframes
|
|
1667
|
+
useEffect(() => {
|
|
1668
|
+
function handleMessage(e) {
|
|
1669
|
+
if (e.data?.type !== 'storyboard:embed:wheel') return
|
|
1670
|
+
zoomAccum.current += -e.data.deltaY
|
|
1671
|
+
const step = Math.trunc(zoomAccum.current)
|
|
1672
|
+
if (step === 0) return
|
|
1673
|
+
zoomAccum.current -= step
|
|
1674
|
+
applyZoom(zoomRef.current + step)
|
|
1675
|
+
}
|
|
1676
|
+
window.addEventListener('message', handleMessage)
|
|
1677
|
+
return () => window.removeEventListener('message', handleMessage)
|
|
1678
|
+
}, [])
|
|
1679
|
+
|
|
1666
1680
|
// Touch pinch-to-zoom for mobile โ two-finger pinch zooms the canvas
|
|
1667
1681
|
const pinchState = useRef({ active: false, startDist: 0, startZoom: 0, centerX: 0, centerY: 0 })
|
|
1668
1682
|
useEffect(() => {
|
|
@@ -228,6 +228,38 @@ export default function LinkPreview({ id, props, onUpdate, resizable }) {
|
|
|
228
228
|
)
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
const ogImage = props?.ogImage || null
|
|
232
|
+
const description = props?.description || ''
|
|
233
|
+
const canEdit = typeof onUpdate === 'function'
|
|
234
|
+
const cardRef = useRef(null)
|
|
235
|
+
const inputRef = useRef(null)
|
|
236
|
+
const [editing, setEditing] = useState(false)
|
|
237
|
+
const [editValue, setEditValue] = useState(title)
|
|
238
|
+
|
|
239
|
+
// Sync editValue when title prop changes externally
|
|
240
|
+
useEffect(() => { setEditValue(title) }, [title])
|
|
241
|
+
|
|
242
|
+
const startEditing = useCallback(() => {
|
|
243
|
+
if (!canEdit) return
|
|
244
|
+
setEditValue(title)
|
|
245
|
+
setEditing(true)
|
|
246
|
+
}, [canEdit, title])
|
|
247
|
+
|
|
248
|
+
const commitEdit = useCallback(() => {
|
|
249
|
+
setEditing(false)
|
|
250
|
+
const trimmed = editValue.trim()
|
|
251
|
+
if (trimmed !== title) {
|
|
252
|
+
onUpdate?.({ title: trimmed })
|
|
253
|
+
}
|
|
254
|
+
}, [editValue, title, onUpdate])
|
|
255
|
+
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (editing && inputRef.current) {
|
|
258
|
+
inputRef.current.focus()
|
|
259
|
+
inputRef.current.select()
|
|
260
|
+
}
|
|
261
|
+
}, [editing])
|
|
262
|
+
|
|
231
263
|
const sizeStyle = (width || height)
|
|
232
264
|
? { ...(width ? { width: `${width}px` } : {}), ...(height ? { minHeight: `${height}px` } : {}) }
|
|
233
265
|
: undefined
|
|
@@ -238,14 +270,46 @@ export default function LinkPreview({ id, props, onUpdate, resizable }) {
|
|
|
238
270
|
const handleResize = (w, h) => onUpdate?.({ width: w, height: h })
|
|
239
271
|
|
|
240
272
|
return (
|
|
241
|
-
<
|
|
242
|
-
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
273
|
+
<div ref={cardRef} className={styles.card} style={sizeStyle}>
|
|
274
|
+
{ogImage && (
|
|
275
|
+
<img
|
|
276
|
+
className={styles.ogImage}
|
|
277
|
+
src={ogImage}
|
|
278
|
+
alt=""
|
|
279
|
+
loading="lazy"
|
|
280
|
+
onError={(e) => { e.target.style.display = 'none' }}
|
|
281
|
+
/>
|
|
282
|
+
)}
|
|
283
|
+
<div className={styles.body}>
|
|
284
|
+
{editing ? (
|
|
285
|
+
<input
|
|
286
|
+
ref={inputRef}
|
|
287
|
+
className={styles.titleInput}
|
|
288
|
+
data-canvas-allow-text-selection
|
|
289
|
+
type="text"
|
|
290
|
+
value={editValue}
|
|
291
|
+
onChange={(e) => setEditValue(e.target.value)}
|
|
292
|
+
onBlur={commitEdit}
|
|
293
|
+
onKeyDown={(e) => {
|
|
294
|
+
if (e.key === 'Enter') commitEdit()
|
|
295
|
+
if (e.key === 'Escape') setEditing(false)
|
|
296
|
+
}}
|
|
297
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
298
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
299
|
+
/>
|
|
300
|
+
) : (
|
|
301
|
+
<p
|
|
302
|
+
className={styles.title}
|
|
303
|
+
data-canvas-allow-text-selection={!canEdit ? '' : undefined}
|
|
304
|
+
onDoubleClick={startEditing}
|
|
305
|
+
role={canEdit ? 'button' : undefined}
|
|
306
|
+
tabIndex={canEdit ? 0 : undefined}
|
|
307
|
+
onKeyDown={canEdit ? (e) => { if (e.key === 'Enter') startEditing() } : undefined}
|
|
308
|
+
>
|
|
309
|
+
{title || hostname || url || 'Untitled'}
|
|
310
|
+
</p>
|
|
311
|
+
)}
|
|
312
|
+
{description && <p className={styles.description}>{description}</p>}
|
|
249
313
|
<a
|
|
250
314
|
href={url || '#'}
|
|
251
315
|
target="_blank"
|
|
@@ -257,7 +321,7 @@ export default function LinkPreview({ id, props, onUpdate, resizable }) {
|
|
|
257
321
|
{hostname || url}
|
|
258
322
|
</a>
|
|
259
323
|
</div>
|
|
260
|
-
{resizable && <ResizeHandle width={width} height={height} onResize={handleResize} />}
|
|
261
|
-
</
|
|
324
|
+
{resizable && <ResizeHandle targetRef={cardRef} width={width} height={height} onResize={handleResize} />}
|
|
325
|
+
</div>
|
|
262
326
|
)
|
|
263
327
|
}
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
border-radius: 4px;
|
|
88
88
|
font-size: 12px;
|
|
89
89
|
font-weight: 400;
|
|
90
|
-
font-family:
|
|
90
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
.preview ul {
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
padding: 0;
|
|
170
170
|
font-size: 12px;
|
|
171
171
|
font-weight: 400;
|
|
172
|
-
font-family:
|
|
172
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
173
173
|
white-space: pre;
|
|
174
174
|
word-break: normal;
|
|
175
175
|
overflow-wrap: normal;
|
|
@@ -133,19 +133,22 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
133
133
|
.filter(Boolean)
|
|
134
134
|
}, [pickerGroups, filter])
|
|
135
135
|
|
|
136
|
-
const
|
|
137
|
-
if (!src) return ''
|
|
136
|
+
const prototypeTitle = useMemo(() => {
|
|
137
|
+
if (!src) return label || 'Prototype'
|
|
138
|
+
const cleanSrc = src.replace(/^\/branch--[^/]+/, '')
|
|
138
139
|
for (const group of pickerGroups) {
|
|
139
140
|
for (const item of group.items) {
|
|
140
141
|
const cleanRoute = item.route.replace(/^\/branch--[^/]+/, '')
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
if (cleanRoute === cleanSrc) {
|
|
143
|
+
// If the flow name matches the group name, just show the name
|
|
144
|
+
if (item.name === group.label) return group.label
|
|
145
|
+
return `${group.label} ยท ${item.name}`
|
|
146
|
+
}
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
|
-
return ''
|
|
146
|
-
}, [src, pickerGroups])
|
|
149
|
+
return label || 'Prototype'
|
|
150
|
+
}, [src, label, pickerGroups])
|
|
147
151
|
|
|
148
|
-
const prototypeTitle = prototypeName || label || 'Prototype'
|
|
149
152
|
const hasPicker = pickerGroups.length > 0
|
|
150
153
|
|
|
151
154
|
useIframeDevLogs({
|
|
@@ -400,7 +403,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
400
403
|
</div>
|
|
401
404
|
)}
|
|
402
405
|
</div>
|
|
403
|
-
{resizable && <ResizeHandle width={width} height={height} onResize={handleResize} />}
|
|
406
|
+
{resizable && <ResizeHandle targetRef={embedRef} width={width} height={height} onResize={handleResize} />}
|
|
404
407
|
</WidgetWrapper>
|
|
405
408
|
{createPortal(
|
|
406
409
|
<div
|