@dfosco/storyboard-core 3.3.2 → 3.5.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 +1 -1
- package/dist/storyboard-ui.js +14899 -11508
- 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 +350 -347
- package/src/CreateMenuButton.svelte +6 -2
- package/src/InspectorPanel.svelte +123 -59
- 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 +257 -33
- 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/sidepanel.css +2 -2
- 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 +62 -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 +36 -6
- package/toolbar.config.json +101 -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}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import Icon from './svelte-plugin-ui/components/Icon.svelte'
|
|
10
10
|
import { inspectElement, inspectElementChain } from './inspector/fiberWalker.js'
|
|
11
11
|
import { createMouseMode } from './inspector/mouseMode.js'
|
|
12
|
+
import { getColors } from './inspector/highlighter.js'
|
|
12
13
|
|
|
13
14
|
/** @type {{ name: string, props: object, source: { fileName: string, lineNumber: number, columnNumber?: number } | null, owner: string | null } | null} */
|
|
14
15
|
let componentInfo = $state(null)
|
|
@@ -109,21 +110,23 @@
|
|
|
109
110
|
return null
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
const _isLocalDev = typeof window !== 'undefined' && window.__SB_LOCAL_DEV__ === true
|
|
114
|
+
|
|
112
115
|
/**
|
|
113
116
|
* 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
117
|
*/
|
|
117
118
|
async function fetchSourceContent(filePath) {
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
// In local dev, use the live middleware (reads from disk)
|
|
120
|
+
if (_isLocalDev) {
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetch(`/_storyboard/docs/source?path=${encodeURIComponent(filePath)}`)
|
|
123
|
+
if (res.ok) {
|
|
124
|
+
const json = await res.json()
|
|
125
|
+
return json?.content || ''
|
|
126
|
+
}
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
// In production (or if dev middleware failed), use static build-time JSON
|
|
127
130
|
const data = await loadStaticData()
|
|
128
131
|
return data?.sources?.[filePath] || ''
|
|
129
132
|
}
|
|
@@ -244,6 +247,9 @@
|
|
|
244
247
|
|
|
245
248
|
let highlightedHtml = $state('')
|
|
246
249
|
|
|
250
|
+
// Code theme colors — refreshed on theme change events
|
|
251
|
+
let codeTheme = $state(getColors())
|
|
252
|
+
|
|
247
253
|
/** @type {any} */
|
|
248
254
|
let highlighter = null
|
|
249
255
|
|
|
@@ -254,6 +260,22 @@
|
|
|
254
260
|
return highlighter
|
|
255
261
|
}
|
|
256
262
|
|
|
263
|
+
/** Re-highlight source code with current theme (called on theme change). */
|
|
264
|
+
async function rehighlight() {
|
|
265
|
+
codeTheme = getColors()
|
|
266
|
+
if (!sourceCode || !sourcePath) return
|
|
267
|
+
try {
|
|
268
|
+
const hl = await getHighlighter()
|
|
269
|
+
highlightedHtml = hl.codeToHtml(sourceCode, {
|
|
270
|
+
lang: getLang(sourcePath),
|
|
271
|
+
theme: 'github-dark',
|
|
272
|
+
decorations: matchedLine > 0
|
|
273
|
+
? [{ start: { line: matchedLine - 1, character: 0 }, end: { line: matchedLine - 1, character: Infinity }, properties: { class: 'highlighted-line' } }]
|
|
274
|
+
: [],
|
|
275
|
+
})
|
|
276
|
+
} catch { /* ignore */ }
|
|
277
|
+
}
|
|
278
|
+
|
|
257
279
|
/**
|
|
258
280
|
* Find the line number of a JSX component in source code by matching
|
|
259
281
|
* the component name and its props against the source lines.
|
|
@@ -343,7 +365,7 @@
|
|
|
343
365
|
lang: getLang(path),
|
|
344
366
|
theme: 'github-dark',
|
|
345
367
|
decorations: matchedLine > 0
|
|
346
|
-
? [{ start: { line: matchedLine - 1, character: 0 }, end: { line: matchedLine, character:
|
|
368
|
+
? [{ start: { line: matchedLine - 1, character: 0 }, end: { line: matchedLine - 1, character: Infinity }, properties: { class: 'highlighted-line' } }]
|
|
347
369
|
: [],
|
|
348
370
|
})
|
|
349
371
|
} catch {
|
|
@@ -427,26 +449,31 @@
|
|
|
427
449
|
onDeactivate: handleDeactivate,
|
|
428
450
|
})
|
|
429
451
|
|
|
430
|
-
//
|
|
431
|
-
|
|
452
|
+
// Re-highlight code when theme changes
|
|
453
|
+
document.addEventListener('storyboard:theme:changed', rehighlight)
|
|
454
|
+
|
|
455
|
+
// Pre-fetch file list and repo info
|
|
456
|
+
// In local dev, try dev middleware; in production, go straight to static JSON
|
|
432
457
|
let filesLoaded = false
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
458
|
+
if (_isLocalDev) {
|
|
459
|
+
try {
|
|
460
|
+
const [filesRes, repoRes] = await Promise.all([
|
|
461
|
+
fetch('/_storyboard/docs/files'),
|
|
462
|
+
fetch('/_storyboard/docs/repo'),
|
|
463
|
+
])
|
|
464
|
+
if (filesRes.ok) {
|
|
465
|
+
const data = await filesRes.json()
|
|
466
|
+
knownFiles = data.files || []
|
|
467
|
+
filesLoaded = true
|
|
468
|
+
}
|
|
469
|
+
if (repoRes.ok) {
|
|
470
|
+
repoInfo = await repoRes.json()
|
|
471
|
+
}
|
|
472
|
+
} catch {}
|
|
473
|
+
}
|
|
447
474
|
|
|
448
475
|
if (!filesLoaded) {
|
|
449
|
-
//
|
|
476
|
+
// Use static build-time JSON
|
|
450
477
|
const data = await loadStaticData()
|
|
451
478
|
if (data) {
|
|
452
479
|
knownFiles = data.files || []
|
|
@@ -483,6 +510,7 @@
|
|
|
483
510
|
mouseMode?.deactivate()
|
|
484
511
|
mouseMode?.hideHighlight()
|
|
485
512
|
setInspectParam(null)
|
|
513
|
+
document.removeEventListener('storyboard:theme:changed', rehighlight)
|
|
486
514
|
})
|
|
487
515
|
</script>
|
|
488
516
|
|
|
@@ -543,9 +571,12 @@
|
|
|
543
571
|
|
|
544
572
|
<!-- Source code -->
|
|
545
573
|
{#if sourcePath}
|
|
546
|
-
<div class="border rounded-md overflow-hidden flex-1 min-h-0 flex flex-col
|
|
574
|
+
<div class="border rounded-md overflow-hidden flex-1 min-h-0 flex flex-col" style:background={codeTheme.bg} style:border-color={codeTheme.border}>
|
|
547
575
|
<div
|
|
548
|
-
class="flex items-center justify-between w-full px-3 py-1.5 text-xs font-semibold shrink-0
|
|
576
|
+
class="flex items-center justify-between w-full px-3 py-1.5 text-xs font-semibold shrink-0"
|
|
577
|
+
style:background={codeTheme.headerBg}
|
|
578
|
+
style:color={codeTheme.headerFg}
|
|
579
|
+
style:border-bottom="1px solid {codeTheme.border}"
|
|
549
580
|
>
|
|
550
581
|
<span class="flex items-center gap-1.5 min-w-0">
|
|
551
582
|
<Icon name="primer/file-code" size={12} />
|
|
@@ -557,6 +588,7 @@
|
|
|
557
588
|
target="_blank"
|
|
558
589
|
rel="noopener noreferrer"
|
|
559
590
|
class="flex items-center gap-1 shrink-0 text-xs no-underline hover:underline inspector-mono inspector-code-link"
|
|
591
|
+
style:color={codeTheme.headerFg}
|
|
560
592
|
>
|
|
561
593
|
<Icon name="primer/mark-github" size={14} />
|
|
562
594
|
<span>GitHub</span>
|
|
@@ -564,21 +596,21 @@
|
|
|
564
596
|
{/if}
|
|
565
597
|
</div>
|
|
566
598
|
|
|
567
|
-
<div class="border-t flex-1 min-h-0 flex flex-col" style:border-color=
|
|
599
|
+
<div class="border-t flex-1 min-h-0 flex flex-col" style:border-color={codeTheme.border}>
|
|
568
600
|
{#if sourceLoading}
|
|
569
|
-
<div class="px-3 py-4 text-xs text-center" style:color=
|
|
601
|
+
<div class="px-3 py-4 text-xs text-center" style:color={codeTheme.headerFg}>
|
|
570
602
|
Loading source…
|
|
571
603
|
</div>
|
|
572
604
|
{:else if sourceCode}
|
|
573
|
-
<div class="flex-1 min-h-0 overflow-y-auto source-scroll-container" bind:this={sourceContainer}>
|
|
605
|
+
<div class="flex-1 min-h-0 overflow-y-auto source-scroll-container" bind:this={sourceContainer} style:--inspector-line-num-color={codeTheme.comment} style:--inspector-line-hover={codeTheme.lineHighlight}>
|
|
574
606
|
{#if highlightedHtml}
|
|
575
|
-
<div class="
|
|
607
|
+
<div class="code-wrapper line-numbers">{@html highlightedHtml}</div>
|
|
576
608
|
{:else}
|
|
577
|
-
<pre class="m-0 text-xs leading-relaxed inspector-mono source-pre" style:color=
|
|
609
|
+
<pre class="m-0 text-xs leading-relaxed inspector-mono source-pre line-numbers" style:background={codeTheme.bg} style:color={codeTheme.fg}><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
610
|
{/if}
|
|
579
611
|
</div>
|
|
580
612
|
{:else}
|
|
581
|
-
<div class="px-3 py-4 text-xs text-center" style:color=
|
|
613
|
+
<div class="px-3 py-4 text-xs text-center" style:color={codeTheme.headerFg}>
|
|
582
614
|
Unable to load source
|
|
583
615
|
</div>
|
|
584
616
|
{/if}
|
|
@@ -627,9 +659,54 @@
|
|
|
627
659
|
.source-pre {
|
|
628
660
|
background: transparent;
|
|
629
661
|
tab-size: 2;
|
|
662
|
+
padding: 12px 0;
|
|
663
|
+
color: #c9d1d9;
|
|
664
|
+
overflow-x: auto;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.source-pre code {
|
|
668
|
+
font-family: inherit;
|
|
669
|
+
display: block;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.source-pre .line {
|
|
673
|
+
padding: 0 12px 0 0;
|
|
674
|
+
display: inline-block;
|
|
675
|
+
width: 100%;
|
|
676
|
+
min-height: 1.5em;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.source-pre .line:hover {
|
|
680
|
+
background: var(--inspector-line-hover, rgba(255, 255, 255, 0.04));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.source-pre :global(.highlighted-line) {
|
|
684
|
+
background: color-mix(in srgb, var(--color-purple, #7655a4) 20%, transparent);
|
|
685
|
+
border-left: 2px solid var(--color-purple, #7655a4);
|
|
686
|
+
padding-left: 10px;
|
|
630
687
|
}
|
|
631
688
|
|
|
632
|
-
.
|
|
689
|
+
/* Line numbers via CSS counters — works for both highlight.js and plain-text */
|
|
690
|
+
.line-numbers :global(code) {
|
|
691
|
+
counter-reset: line;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.line-numbers :global(.line) {
|
|
695
|
+
padding-left: 0 !important;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.line-numbers :global(.line::before) {
|
|
699
|
+
counter-increment: line;
|
|
700
|
+
content: counter(line);
|
|
701
|
+
display: inline-block;
|
|
702
|
+
width: 3.5ch;
|
|
703
|
+
margin-right: 1.5ch;
|
|
704
|
+
text-align: right;
|
|
705
|
+
color: var(--inspector-line-num-color, #484f58);
|
|
706
|
+
user-select: none;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.code-wrapper :global(pre) {
|
|
633
710
|
margin: 0;
|
|
634
711
|
padding: 12px 0;
|
|
635
712
|
font-size: 12px;
|
|
@@ -640,43 +717,30 @@
|
|
|
640
717
|
overflow-x: auto;
|
|
641
718
|
}
|
|
642
719
|
|
|
643
|
-
.
|
|
720
|
+
.code-wrapper :global(code) {
|
|
644
721
|
font-family: inherit;
|
|
645
722
|
display: block;
|
|
646
723
|
}
|
|
647
724
|
|
|
648
|
-
.
|
|
649
|
-
padding: 0 12px;
|
|
725
|
+
.code-wrapper :global(.line) {
|
|
726
|
+
padding: 0 12px 0 0;
|
|
650
727
|
display: inline-block;
|
|
651
728
|
width: 100%;
|
|
652
729
|
min-height: 1.5em;
|
|
653
730
|
}
|
|
654
731
|
|
|
655
|
-
.
|
|
656
|
-
background: rgba(255, 255, 255, 0.04);
|
|
732
|
+
.code-wrapper :global(.line:hover) {
|
|
733
|
+
background: var(--inspector-line-hover, rgba(255, 255, 255, 0.04));
|
|
657
734
|
}
|
|
658
735
|
|
|
659
|
-
.
|
|
736
|
+
.code-wrapper :global(.highlighted-line) {
|
|
660
737
|
background: color-mix(in srgb, var(--color-purple, #7655a4) 20%, transparent);
|
|
661
738
|
border-left: 2px solid var(--color-purple, #7655a4);
|
|
739
|
+
padding-left: 10px;
|
|
662
740
|
}
|
|
663
741
|
|
|
664
742
|
/* Force dark chrome on the code block — independent of page theme */
|
|
665
|
-
.inspector-code-block {
|
|
666
|
-
background: #0d1117;
|
|
667
|
-
border-color: #30363d !important;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
.inspector-code-header {
|
|
671
|
-
background: #161b22;
|
|
672
|
-
color: #8b949e;
|
|
673
|
-
border-bottom: 1px solid #30363d;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
.inspector-code-link {
|
|
677
|
-
color: #8b949e;
|
|
678
|
-
}
|
|
679
743
|
.inspector-code-link:hover {
|
|
680
|
-
|
|
744
|
+
text-decoration: underline;
|
|
681
745
|
}
|
|
682
746
|
</style>
|
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,
|