@dfosco/storyboard-react 4.0.0-beta.6 → 4.0.0-beta.7

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.6",
3
+ "version": "4.0.0-beta.7",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@dfosco/storyboard-core": "4.0.0-beta.6",
7
- "@dfosco/tiny-canvas": "4.0.0-beta.6",
6
+ "@dfosco/storyboard-core": "4.0.0-beta.7",
7
+ "@dfosco/tiny-canvas": "4.0.0-beta.7",
8
8
  "@neodrag/react": "^2.3.1",
9
9
  "glob": "^11.0.0",
10
10
  "jsonc-parser": "^3.3.1",
@@ -47,6 +47,35 @@ function resolveCanvasThemeFromStorage() {
47
47
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
48
48
  }
49
49
 
50
+ /**
51
+ * Get the copyable URL for a widget based on its type.
52
+ * Returns the most relevant URL/path for the widget content.
53
+ */
54
+ function getWidgetCopyableUrl(widget) {
55
+ const { type, props = {} } = widget
56
+ const base = (typeof import.meta !== 'undefined' && import.meta.env?.BASE_URL) || '/'
57
+ switch (type) {
58
+ case 'prototype':
59
+ // Prototype src is a path like "/MyPrototype" - make it a full URL
60
+ return props.src ? `${window.location.origin}${base.replace(/\/$/, '')}${props.src}` : ''
61
+ case 'figma-embed':
62
+ return props.url || ''
63
+ case 'link-preview':
64
+ return props.url || ''
65
+ case 'image':
66
+ // Return the served image URL
67
+ return props.src ? `${window.location.origin}${base.replace(/\/$/, '')}/_storyboard/canvas/images/${props.src}` : ''
68
+ case 'sticky-note':
69
+ // Sticky notes have text content, not a URL
70
+ return props.text || ''
71
+ case 'markdown':
72
+ // Markdown has content, not a URL
73
+ return props.content || ''
74
+ default:
75
+ return ''
76
+ }
77
+ }
78
+
50
79
  /**
51
80
  * Debounce helper — returns a function that delays invocation.
52
81
  * Exposes `.cancel()` to abort pending calls (used by undo/redo).
@@ -977,6 +1006,25 @@ export default function CanvasPage({ name }) {
977
1006
  e.preventDefault()
978
1007
  setSelectedWidgetIds(new Set())
979
1008
  }
1009
+ // Copy: cmd+c copies URL, alt+cmd+c copies ID (single widget only)
1010
+ const mod = e.metaKey || e.ctrlKey
1011
+ if (mod && e.key === 'c' && selectedWidgetIds.size === 1) {
1012
+ const widgetId = [...selectedWidgetIds][0]
1013
+ const widget = localWidgets?.find(w => w.id === widgetId)
1014
+ if (widget) {
1015
+ e.preventDefault()
1016
+ if (e.altKey) {
1017
+ // alt+cmd+c → copy widget ID
1018
+ navigator.clipboard.writeText(widgetId).catch(() => {})
1019
+ } else {
1020
+ // cmd+c → copy widget URL/content
1021
+ const url = getWidgetCopyableUrl(widget)
1022
+ if (url) {
1023
+ navigator.clipboard.writeText(url).catch(() => {})
1024
+ }
1025
+ }
1026
+ }
1027
+ }
980
1028
  if (e.key === 'Delete' || e.key === 'Backspace') {
981
1029
  e.preventDefault()
982
1030
  if (selectedWidgetIds.size > 1) {
@@ -1002,7 +1050,7 @@ export default function CanvasPage({ name }) {
1002
1050
  }
1003
1051
  document.addEventListener('keydown', handleKeyDown)
1004
1052
  return () => document.removeEventListener('keydown', handleKeyDown)
1005
- }, [selectedWidgetIds, handleWidgetRemove, undoRedo, name, debouncedSave])
1053
+ }, [selectedWidgetIds, localWidgets, handleWidgetRemove, undoRedo, name, debouncedSave])
1006
1054
 
1007
1055
  // Paste handler — images become image widgets, same-origin URLs become prototypes,
1008
1056
  // other URLs become link previews, text becomes markdown