@dfosco/storyboard-core 3.3.1 → 3.4.0
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/dist/storyboard-ui.css +9 -1
- package/dist/storyboard-ui.js +14701 -11431
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +1 -1
- package/scaffold/toolbar.config.json +2 -2
- package/src/CanvasCreateMenu.svelte +1 -1
- package/src/CanvasZoomControl.svelte +105 -0
- package/src/CommandMenu.svelte +87 -25
- package/src/CoreUIBar.svelte +352 -346
- package/src/CreateMenuButton.svelte +6 -2
- package/src/InspectorPanel.svelte +87 -37
- package/src/SidePanel.svelte +1 -1
- package/src/ThemeMenuButton.svelte +35 -3
- package/src/commandActions.js +14 -0
- package/src/core-ui-colors.css +30 -2
- package/src/devtools.js +7 -1
- package/src/index.js +10 -1
- package/src/inspector/fiberWalker.js +49 -6
- package/src/inspector/highlighter.js +145 -29
- package/src/lib/components/ui/button/button.svelte +1 -1
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/src/lib/components/ui/trigger-button/trigger-button.svelte +31 -3
- package/src/modes.css +8 -0
- package/src/mountStoryboardCore.js +15 -1
- package/src/stores/themeStore.ts +66 -0
- package/src/svelte-plugin-ui/components/Viewfinder.svelte +16 -11
- package/src/toolRegistry.js +226 -0
- package/src/toolStateStore.js +180 -0
- package/src/toolStateStore.test.js +204 -0
- package/src/toolbarConfigStore.js +135 -0
- package/src/tools/handlers/canvasAddWidget.js +11 -0
- package/src/tools/handlers/canvasZoom.js +34 -0
- package/src/tools/handlers/comments.js +16 -0
- package/src/tools/handlers/create.js +39 -0
- package/src/tools/handlers/devtools.js +80 -0
- package/src/tools/handlers/docs.js +11 -0
- package/src/tools/handlers/featureFlags.js +21 -0
- package/src/tools/handlers/flows.js +59 -0
- package/src/tools/handlers/inspector.js +19 -0
- package/src/tools/handlers/theme.js +9 -0
- package/src/tools/registry.js +21 -0
- package/src/tools/surfaces/canvasToolbar.js +10 -0
- package/src/tools/surfaces/commandList.js +10 -0
- package/src/tools/surfaces/mainToolbar.js +11 -0
- package/src/tools/surfaces/registry.js +19 -0
- package/src/vite/server-plugin.js +54 -5
- package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +2 -2
- package/src/workshop/features/createPrototype/server.js +10 -15
- package/toolbar.config.json +107 -48
|
@@ -33,11 +33,15 @@
|
|
|
33
33
|
|
|
34
34
|
interface Props {
|
|
35
35
|
features?: CreateMenuFeature[]
|
|
36
|
+
data?: { features?: CreateMenuFeature[] }
|
|
36
37
|
config?: CreateMenuConfig
|
|
37
38
|
tabindex?: number
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
let { features = [], config = { label: 'Create' }, tabindex }: Props = $props()
|
|
41
|
+
let { features: featuresProp = [], data, config = { label: 'Create' }, tabindex }: Props = $props()
|
|
42
|
+
|
|
43
|
+
// Support both direct `features` prop (legacy) and `data.features` (generic toolbar)
|
|
44
|
+
const features = $derived(featuresProp.length > 0 ? featuresProp : (data?.features || []))
|
|
41
45
|
|
|
42
46
|
const menuWidth = $derived((config as any).menuWidth || null)
|
|
43
47
|
|
|
@@ -110,7 +114,7 @@
|
|
|
110
114
|
<DropdownMenu.Separator />
|
|
111
115
|
{:else if action.type === 'footer'}
|
|
112
116
|
<DropdownMenu.Separator />
|
|
113
|
-
<div class="px-2 py-1.5 text-xs text-muted-foreground">
|
|
117
|
+
<div class="px-2 py-1.5 text-xs text-muted-foreground flex flex-row items-baseline"><span class="inline-flex w-2 h-2 rounded-full mr-1.5" style="background: hsl(137, 66%, 30%)"></span>Only available in dev environment</div>
|
|
114
118
|
{:else if action._feature}
|
|
115
119
|
<DropdownMenu.Item onclick={() => showOverlay(action._feature.overlayId)}>
|
|
116
120
|
{action.label || action._feature.label}
|
|
@@ -109,21 +109,23 @@
|
|
|
109
109
|
return null
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
const _isLocalDev = typeof window !== 'undefined' && window.__SB_LOCAL_DEV__ === true
|
|
113
|
+
|
|
112
114
|
/**
|
|
113
115
|
* Fetch source file content — uses dev middleware in dev, static JSON in prod.
|
|
114
|
-
* Uses runtime detection (not import.meta.env.DEV) so this works in the
|
|
115
|
-
* pre-compiled UI bundle where compile-time env vars are baked in.
|
|
116
116
|
*/
|
|
117
117
|
async function fetchSourceContent(filePath) {
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
// In local dev, use the live middleware (reads from disk)
|
|
119
|
+
if (_isLocalDev) {
|
|
120
|
+
try {
|
|
121
|
+
const res = await fetch(`/_storyboard/docs/source?path=${encodeURIComponent(filePath)}`)
|
|
122
|
+
if (res.ok) {
|
|
123
|
+
const json = await res.json()
|
|
124
|
+
return json?.content || ''
|
|
125
|
+
}
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
// In production (or if dev middleware failed), use static build-time JSON
|
|
127
129
|
const data = await loadStaticData()
|
|
128
130
|
return data?.sources?.[filePath] || ''
|
|
129
131
|
}
|
|
@@ -343,7 +345,7 @@
|
|
|
343
345
|
lang: getLang(path),
|
|
344
346
|
theme: 'github-dark',
|
|
345
347
|
decorations: matchedLine > 0
|
|
346
|
-
? [{ start: { line: matchedLine - 1, character: 0 }, end: { line: matchedLine, character:
|
|
348
|
+
? [{ start: { line: matchedLine - 1, character: 0 }, end: { line: matchedLine - 1, character: Infinity }, properties: { class: 'highlighted-line' } }]
|
|
347
349
|
: [],
|
|
348
350
|
})
|
|
349
351
|
} catch {
|
|
@@ -427,26 +429,28 @@
|
|
|
427
429
|
onDeactivate: handleDeactivate,
|
|
428
430
|
})
|
|
429
431
|
|
|
430
|
-
// Pre-fetch file list and repo info
|
|
431
|
-
//
|
|
432
|
+
// Pre-fetch file list and repo info
|
|
433
|
+
// In local dev, try dev middleware; in production, go straight to static JSON
|
|
432
434
|
let filesLoaded = false
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
435
|
+
if (_isLocalDev) {
|
|
436
|
+
try {
|
|
437
|
+
const [filesRes, repoRes] = await Promise.all([
|
|
438
|
+
fetch('/_storyboard/docs/files'),
|
|
439
|
+
fetch('/_storyboard/docs/repo'),
|
|
440
|
+
])
|
|
441
|
+
if (filesRes.ok) {
|
|
442
|
+
const data = await filesRes.json()
|
|
443
|
+
knownFiles = data.files || []
|
|
444
|
+
filesLoaded = true
|
|
445
|
+
}
|
|
446
|
+
if (repoRes.ok) {
|
|
447
|
+
repoInfo = await repoRes.json()
|
|
448
|
+
}
|
|
449
|
+
} catch {}
|
|
450
|
+
}
|
|
447
451
|
|
|
448
452
|
if (!filesLoaded) {
|
|
449
|
-
//
|
|
453
|
+
// Use static build-time JSON
|
|
450
454
|
const data = await loadStaticData()
|
|
451
455
|
if (data) {
|
|
452
456
|
knownFiles = data.files || []
|
|
@@ -572,9 +576,9 @@
|
|
|
572
576
|
{:else if sourceCode}
|
|
573
577
|
<div class="flex-1 min-h-0 overflow-y-auto source-scroll-container" bind:this={sourceContainer}>
|
|
574
578
|
{#if highlightedHtml}
|
|
575
|
-
<div class="
|
|
579
|
+
<div class="code-wrapper line-numbers">{@html highlightedHtml}</div>
|
|
576
580
|
{:else}
|
|
577
|
-
<pre class="m-0 text-xs leading-relaxed inspector-mono source-pre"
|
|
581
|
+
<pre class="m-0 text-xs leading-relaxed inspector-mono source-pre line-numbers"><code>{#each sourceCode.split('\n') as line, i}<span class="line{matchedLine > 0 && i + 1 === matchedLine ? ' highlighted-line' : ''}">{line}</span>{#if i < sourceCode.split('\n').length - 1}{'\n'}{/if}{/each}</code></pre>
|
|
578
582
|
{/if}
|
|
579
583
|
</div>
|
|
580
584
|
{:else}
|
|
@@ -627,9 +631,54 @@
|
|
|
627
631
|
.source-pre {
|
|
628
632
|
background: transparent;
|
|
629
633
|
tab-size: 2;
|
|
634
|
+
padding: 12px 0;
|
|
635
|
+
color: #c9d1d9;
|
|
636
|
+
overflow-x: auto;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.source-pre code {
|
|
640
|
+
font-family: inherit;
|
|
641
|
+
display: block;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.source-pre .line {
|
|
645
|
+
padding: 0 12px 0 0;
|
|
646
|
+
display: inline-block;
|
|
647
|
+
width: 100%;
|
|
648
|
+
min-height: 1.5em;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.source-pre .line:hover {
|
|
652
|
+
background: rgba(255, 255, 255, 0.04);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.source-pre :global(.highlighted-line) {
|
|
656
|
+
background: color-mix(in srgb, var(--color-purple, #7655a4) 20%, transparent);
|
|
657
|
+
border-left: 2px solid var(--color-purple, #7655a4);
|
|
658
|
+
padding-left: 10px;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/* Line numbers via CSS counters — works for both highlight.js and plain-text */
|
|
662
|
+
.line-numbers :global(code) {
|
|
663
|
+
counter-reset: line;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.line-numbers :global(.line) {
|
|
667
|
+
padding-left: 0 !important;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.line-numbers :global(.line::before) {
|
|
671
|
+
counter-increment: line;
|
|
672
|
+
content: counter(line);
|
|
673
|
+
display: inline-block;
|
|
674
|
+
width: 3.5ch;
|
|
675
|
+
margin-right: 1.5ch;
|
|
676
|
+
text-align: right;
|
|
677
|
+
color: #484f58;
|
|
678
|
+
user-select: none;
|
|
630
679
|
}
|
|
631
680
|
|
|
632
|
-
.
|
|
681
|
+
.code-wrapper :global(pre) {
|
|
633
682
|
margin: 0;
|
|
634
683
|
padding: 12px 0;
|
|
635
684
|
font-size: 12px;
|
|
@@ -640,25 +689,26 @@
|
|
|
640
689
|
overflow-x: auto;
|
|
641
690
|
}
|
|
642
691
|
|
|
643
|
-
.
|
|
692
|
+
.code-wrapper :global(code) {
|
|
644
693
|
font-family: inherit;
|
|
645
694
|
display: block;
|
|
646
695
|
}
|
|
647
696
|
|
|
648
|
-
.
|
|
649
|
-
padding: 0 12px;
|
|
697
|
+
.code-wrapper :global(.line) {
|
|
698
|
+
padding: 0 12px 0 0;
|
|
650
699
|
display: inline-block;
|
|
651
700
|
width: 100%;
|
|
652
701
|
min-height: 1.5em;
|
|
653
702
|
}
|
|
654
703
|
|
|
655
|
-
.
|
|
704
|
+
.code-wrapper :global(.line:hover) {
|
|
656
705
|
background: rgba(255, 255, 255, 0.04);
|
|
657
706
|
}
|
|
658
707
|
|
|
659
|
-
.
|
|
708
|
+
.code-wrapper :global(.highlighted-line) {
|
|
660
709
|
background: color-mix(in srgb, var(--color-purple, #7655a4) 20%, transparent);
|
|
661
710
|
border-left: 2px solid var(--color-purple, #7655a4);
|
|
711
|
+
padding-left: 10px;
|
|
662
712
|
}
|
|
663
713
|
|
|
664
714
|
/* Force dark chrome on the code block — independent of page theme */
|
package/src/SidePanel.svelte
CHANGED
|
@@ -320,7 +320,7 @@
|
|
|
320
320
|
left: 0;
|
|
321
321
|
right: 0;
|
|
322
322
|
height: 3px;
|
|
323
|
-
background: var(--mode-color, var(--borderColor-default, var(--color-border, #d0d7de)));
|
|
323
|
+
/* background: var(--mode-color, var(--borderColor-default, var(--color-border, #d0d7de))); */
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
/* Drag handle — side mode (left edge, vertical) */
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
ThemeMenuButton — toolbar dropdown for switching the app color scheme.
|
|
3
3
|
|
|
4
|
-
Renders a radio group of theme options (System, Light, Dark, etc.)
|
|
5
|
-
and
|
|
4
|
+
Renders a radio group of theme options (System, Light, Dark, etc.),
|
|
5
|
+
followed by a separator and "Theme settings" submenu with sync toggles.
|
|
6
6
|
-->
|
|
7
7
|
|
|
8
8
|
<script lang="ts">
|
|
9
9
|
import { TriggerButton } from './lib/components/ui/trigger-button/index.js'
|
|
10
10
|
import * as DropdownMenu from './lib/components/ui/dropdown-menu/index.js'
|
|
11
11
|
import Icon from './svelte-plugin-ui/components/Icon.svelte'
|
|
12
|
-
import { themeState, setTheme, THEMES, type ThemeValue } from './stores/themeStore.js'
|
|
12
|
+
import { themeState, setTheme, THEMES, type ThemeValue, themeSyncState, setThemeSyncTarget, type ThemeSyncTargets } from './stores/themeStore.js'
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
config?: {
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
setTheme(value)
|
|
31
31
|
menuOpen = false
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
function handleSyncToggle(e: Event, target: keyof ThemeSyncTargets) {
|
|
35
|
+
e.preventDefault()
|
|
36
|
+
setThemeSyncTarget(target, !$themeSyncState[target])
|
|
37
|
+
}
|
|
33
38
|
</script>
|
|
34
39
|
|
|
35
40
|
<DropdownMenu.Root bind:open={menuOpen}>
|
|
@@ -62,5 +67,32 @@
|
|
|
62
67
|
</DropdownMenu.RadioItem>
|
|
63
68
|
{/each}
|
|
64
69
|
</DropdownMenu.RadioGroup>
|
|
70
|
+
|
|
71
|
+
<DropdownMenu.Separator />
|
|
72
|
+
|
|
73
|
+
<DropdownMenu.Sub>
|
|
74
|
+
<DropdownMenu.SubTrigger>Theme settings</DropdownMenu.SubTrigger>
|
|
75
|
+
<DropdownMenu.SubContent class="min-w-[180px]">
|
|
76
|
+
<DropdownMenu.Label>Apply theme to</DropdownMenu.Label>
|
|
77
|
+
<DropdownMenu.CheckboxItem
|
|
78
|
+
checked={$themeSyncState.prototype}
|
|
79
|
+
onSelect={(e) => handleSyncToggle(e, 'prototype')}
|
|
80
|
+
>
|
|
81
|
+
Prototype
|
|
82
|
+
</DropdownMenu.CheckboxItem>
|
|
83
|
+
<DropdownMenu.CheckboxItem
|
|
84
|
+
checked={$themeSyncState.toolbar}
|
|
85
|
+
onSelect={(e) => handleSyncToggle(e, 'toolbar')}
|
|
86
|
+
>
|
|
87
|
+
Toolbar
|
|
88
|
+
</DropdownMenu.CheckboxItem>
|
|
89
|
+
<DropdownMenu.CheckboxItem
|
|
90
|
+
checked={$themeSyncState.codeBoxes}
|
|
91
|
+
onSelect={(e) => handleSyncToggle(e, 'codeBoxes')}
|
|
92
|
+
>
|
|
93
|
+
Code boxes
|
|
94
|
+
</DropdownMenu.CheckboxItem>
|
|
95
|
+
</DropdownMenu.SubContent>
|
|
96
|
+
</DropdownMenu.Sub>
|
|
65
97
|
</DropdownMenu.Content>
|
|
66
98
|
</DropdownMenu.Root>
|
package/src/commandActions.js
CHANGED
|
@@ -192,6 +192,8 @@ export function getActionsForMode(mode) {
|
|
|
192
192
|
label: a.label,
|
|
193
193
|
type: a.type || 'default',
|
|
194
194
|
url: a.url || null,
|
|
195
|
+
toolKey: a.toolKey || null,
|
|
196
|
+
localOnly: a.localOnly || false,
|
|
195
197
|
handler,
|
|
196
198
|
active,
|
|
197
199
|
}
|
|
@@ -224,6 +226,18 @@ export function getActionChildren(id) {
|
|
|
224
226
|
return handler.getChildren()
|
|
225
227
|
}
|
|
226
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Check if a handler provides dynamic children (getChildren).
|
|
231
|
+
* Used by CoreUIBar to distinguish action-menu tools (gate on children count)
|
|
232
|
+
* from custom-component menus (always visible, render their own content).
|
|
233
|
+
* @param {string} id
|
|
234
|
+
* @returns {boolean}
|
|
235
|
+
*/
|
|
236
|
+
export function hasChildrenProvider(id) {
|
|
237
|
+
const handler = _handlers.get(id)
|
|
238
|
+
return !!handler?.getChildren
|
|
239
|
+
}
|
|
240
|
+
|
|
227
241
|
// ---------------------------------------------------------------------------
|
|
228
242
|
// Reactivity
|
|
229
243
|
// ---------------------------------------------------------------------------
|
package/src/core-ui-colors.css
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Core UI Colors —
|
|
2
|
+
* Core UI Colors — toolbar theme tokens.
|
|
3
3
|
*
|
|
4
4
|
* The CoreUIBar and its children (trigger buttons, dropdowns, menus)
|
|
5
|
-
*
|
|
5
|
+
* default to light-mode colors. When the user enables toolbar theme sync,
|
|
6
|
+
* `data-sb-toolbar-theme` switches to the active theme and dark tokens
|
|
7
|
+
* are applied.
|
|
6
8
|
*
|
|
7
9
|
* These override the shadcn/Tailwind design tokens within the
|
|
8
10
|
* [data-core-ui-bar] scope so that CSS custom-property dark-mode
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
* classes) so its scoped CSS has full control over background/color.
|
|
17
19
|
*/
|
|
18
20
|
|
|
21
|
+
/* Default: light tokens */
|
|
19
22
|
[data-core-ui-bar],
|
|
20
23
|
[data-slot="dropdown-menu-content"],
|
|
21
24
|
[data-slot="dropdown-menu-sub-content"] {
|
|
@@ -37,3 +40,28 @@
|
|
|
37
40
|
--color-border: hsl(214.3 31.8% 91.4%);
|
|
38
41
|
--color-input: hsl(214.3 31.8% 91.4%);
|
|
39
42
|
}
|
|
43
|
+
|
|
44
|
+
/* Dark tokens — applied when toolbar syncs with a dark theme.
|
|
45
|
+
* Uses Primer-aligned colors (matching base.css --sb-* dark tokens)
|
|
46
|
+
* instead of generic shadcn dark values.
|
|
47
|
+
*/
|
|
48
|
+
:root[data-sb-toolbar-theme^="dark"] [data-core-ui-bar],
|
|
49
|
+
:root[data-sb-toolbar-theme^="dark"] [data-slot="dropdown-menu-content"],
|
|
50
|
+
:root[data-sb-toolbar-theme^="dark"] [data-slot="dropdown-menu-sub-content"] {
|
|
51
|
+
color-scheme: dark;
|
|
52
|
+
|
|
53
|
+
--color-background: #161b22;
|
|
54
|
+
--color-foreground: #e6edf3;
|
|
55
|
+
--color-popover: #161b22;
|
|
56
|
+
--color-popover-foreground: #e6edf3;
|
|
57
|
+
--color-primary: #e6edf3;
|
|
58
|
+
--color-primary-foreground: #161b22;
|
|
59
|
+
--color-secondary: #21262d;
|
|
60
|
+
--color-secondary-foreground: #e6edf3;
|
|
61
|
+
--color-muted: #21262d;
|
|
62
|
+
--color-muted-foreground: #8b949e;
|
|
63
|
+
--color-accent: #21262d;
|
|
64
|
+
--color-accent-foreground: #e6edf3;
|
|
65
|
+
--color-border: #30363d;
|
|
66
|
+
--color-input: #30363d;
|
|
67
|
+
}
|
package/src/devtools.js
CHANGED
|
@@ -22,6 +22,8 @@ let skipLink = null
|
|
|
22
22
|
* @param {object} [options]
|
|
23
23
|
* @param {HTMLElement} [options.container=document.body] - Where to mount
|
|
24
24
|
* @param {string} [options.basePath='/'] - Base URL path
|
|
25
|
+
* @param {object} [options.toolbarConfig] - Merged toolbar config
|
|
26
|
+
* @param {Record<string, () => Promise<any>>} [options.customHandlers] - Custom tool handlers
|
|
25
27
|
*/
|
|
26
28
|
export async function mountDevTools(options = {}) {
|
|
27
29
|
const container = options.container || document.body
|
|
@@ -108,7 +110,11 @@ export async function mountDevTools(options = {}) {
|
|
|
108
110
|
|
|
109
111
|
instance = mount(CoreUIBar, {
|
|
110
112
|
target: wrapper,
|
|
111
|
-
props: {
|
|
113
|
+
props: {
|
|
114
|
+
basePath,
|
|
115
|
+
toolbarConfig: options.toolbarConfig,
|
|
116
|
+
customHandlers: options.customHandlers,
|
|
117
|
+
},
|
|
112
118
|
})
|
|
113
119
|
}
|
|
114
120
|
|
package/src/index.js
CHANGED
|
@@ -68,7 +68,7 @@ export { resolveSceneRoute, getSceneMeta } from './viewfinder.js'
|
|
|
68
68
|
export { initFeatureFlags, getFlag, setFlag, toggleFlag, getAllFlags, resetFlags, getFlagKeys, syncFlagBodyClasses } from './featureFlags.js'
|
|
69
69
|
|
|
70
70
|
// Command actions (config-driven command menu entries)
|
|
71
|
-
export { initCommandActions, registerCommandAction, unregisterCommandAction, setDynamicActions, clearDynamicActions, getActionsForMode, executeAction, getActionChildren, subscribeToCommandActions, getCommandActionsSnapshot, setRoutingBasePath, isExcludedByRoute } from './commandActions.js'
|
|
71
|
+
export { initCommandActions, registerCommandAction, unregisterCommandAction, setDynamicActions, clearDynamicActions, getActionsForMode, executeAction, getActionChildren, hasChildrenProvider, subscribeToCommandActions, getCommandActionsSnapshot, setRoutingBasePath, isExcludedByRoute } from './commandActions.js'
|
|
72
72
|
|
|
73
73
|
// Plugin configuration
|
|
74
74
|
export { initPlugins, isPluginEnabled, getPluginsConfig } from './plugins.js'
|
|
@@ -76,5 +76,14 @@ export { initPlugins, isPluginEnabled, getPluginsConfig } from './plugins.js'
|
|
|
76
76
|
// UI config (project-level chrome overrides)
|
|
77
77
|
export { initUIConfig, isMenuHidden, getHiddenItems } from './uiConfig.js'
|
|
78
78
|
|
|
79
|
+
// Tool registry (declarative tool system)
|
|
80
|
+
export { initToolRegistry, registerToolModule, setToolComponent, setToolGuardResult, getToolComponent, getToolModule, getToolsForToolbar, getToolConfig, getAllToolConfigs, subscribeToToolRegistry, getToolRegistrySnapshot } from './toolRegistry.js'
|
|
81
|
+
|
|
82
|
+
// Toolbar config store (reactive layered overrides: core → custom → prototype → user)
|
|
83
|
+
export { initToolbarConfig, setPrototypeToolbarConfig, clearPrototypeToolbarConfig, getToolbarConfig, subscribeToToolbarConfig, getToolbarConfigSnapshot } from './toolbarConfigStore.js'
|
|
84
|
+
|
|
85
|
+
// Toolbar tool state management (runtime state for toolbar tools)
|
|
86
|
+
export { TOOL_STATES, initToolbarToolStates, setToolbarToolState, getToolbarToolState, isToolbarToolLocalOnly, subscribeToToolbarToolStates, getToolbarToolStatesSnapshot } from './toolStateStore.js'
|
|
87
|
+
|
|
79
88
|
// Comments system
|
|
80
89
|
export { initCommentsConfig, getCommentsConfig, isCommentsEnabled } from './comments/config.js'
|
|
@@ -50,6 +50,8 @@ function isUserComponent(fiber) {
|
|
|
50
50
|
/**
|
|
51
51
|
* Derive a human-readable name from a fiber's type.
|
|
52
52
|
* Handles plain components, forwardRef, and memo wrappers.
|
|
53
|
+
* Skips minified names (single-char or generic) in favor of 'ForwardRef'/'Memo'
|
|
54
|
+
* so the caller can try walking up the tree for a better name.
|
|
53
55
|
*
|
|
54
56
|
* @param {object} fiber
|
|
55
57
|
* @returns {string}
|
|
@@ -59,20 +61,43 @@ function getComponentName(fiber) {
|
|
|
59
61
|
const t = fiber.type
|
|
60
62
|
// Plain function/class
|
|
61
63
|
if (typeof t === 'function') return t.displayName || t.name || 'Anonymous'
|
|
62
|
-
// forwardRef
|
|
64
|
+
// forwardRef / memo wrapper objects
|
|
63
65
|
if (typeof t === 'object' && t !== null) {
|
|
64
66
|
if (t.displayName) return t.displayName
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
// forwardRef: { render: fn }
|
|
68
|
+
if (typeof t.render === 'function') {
|
|
69
|
+
const name = t.render.displayName || t.render.name
|
|
70
|
+
return isUsableName(name) ? name : 'ForwardRef'
|
|
71
|
+
}
|
|
72
|
+
// memo: { type: ... }
|
|
67
73
|
if (t.type) {
|
|
68
74
|
const inner = t.type
|
|
69
|
-
if (typeof inner === 'function')
|
|
70
|
-
|
|
75
|
+
if (typeof inner === 'function') {
|
|
76
|
+
const name = inner.displayName || inner.name
|
|
77
|
+
return isUsableName(name) ? name : 'Memo'
|
|
78
|
+
}
|
|
79
|
+
// memo(forwardRef)
|
|
80
|
+
if (typeof inner === 'object' && inner.render) {
|
|
81
|
+
if (inner.displayName) return inner.displayName
|
|
82
|
+
const name = inner.render.displayName || inner.render.name
|
|
83
|
+
return isUsableName(name) ? name : 'ForwardRef'
|
|
84
|
+
}
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
return 'Unknown'
|
|
74
88
|
}
|
|
75
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Check if a resolved component name is usable (not minified).
|
|
92
|
+
* Minified names are typically 1-2 chars or all lowercase short strings.
|
|
93
|
+
*/
|
|
94
|
+
function isUsableName(name) {
|
|
95
|
+
if (!name) return false
|
|
96
|
+
// Single-char names (e, t, r, n) are almost certainly minified
|
|
97
|
+
if (name.length <= 2) return false
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
76
101
|
/**
|
|
77
102
|
* Extract debug source info from a fiber (dev builds only).
|
|
78
103
|
*
|
|
@@ -133,10 +158,28 @@ export function getComponentInfo(fiber) {
|
|
|
133
158
|
|
|
134
159
|
if (!current) return null
|
|
135
160
|
|
|
161
|
+
let name = getComponentName(current)
|
|
162
|
+
|
|
163
|
+
// If we got a generic name (ForwardRef, Memo), try walking up
|
|
164
|
+
// to find the nearest ancestor with a real component name
|
|
165
|
+
if (name === 'ForwardRef' || name === 'Memo') {
|
|
166
|
+
let ancestor = current.return
|
|
167
|
+
while (ancestor) {
|
|
168
|
+
if (isUserComponent(ancestor)) {
|
|
169
|
+
const ancestorName = getComponentName(ancestor)
|
|
170
|
+
if (ancestorName !== 'ForwardRef' && ancestorName !== 'Memo' && ancestorName !== 'Unknown') {
|
|
171
|
+
name = ancestorName
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
ancestor = ancestor.return
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
136
179
|
const ownerFiber = current._debugOwner ?? null
|
|
137
180
|
|
|
138
181
|
return {
|
|
139
|
-
name
|
|
182
|
+
name,
|
|
140
183
|
props: current.memoizedProps ?? {},
|
|
141
184
|
source: getDebugSource(current),
|
|
142
185
|
owner: ownerFiber ? getComponentName(ownerFiber) : null,
|