@flanksource/clicky-ui 0.3.44 → 0.3.45
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/README.md +2 -0
- package/dist/ai.cjs +4 -2
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.js +3 -1
- package/dist/components/MdxEditorField.cjs +40 -22
- package/dist/components/MdxEditorField.cjs.map +1 -1
- package/dist/components/MdxEditorField.d.ts.map +1 -1
- package/dist/components/MdxEditorField.js +41 -23
- package/dist/components/MdxEditorField.js.map +1 -1
- package/dist/components/json-schema-form-item-summary.cjs +6 -1
- package/dist/components/json-schema-form-item-summary.cjs.map +1 -1
- package/dist/components/json-schema-form-item-summary.d.ts.map +1 -1
- package/dist/components/json-schema-form-item-summary.js +6 -1
- package/dist/components/json-schema-form-item-summary.js.map +1 -1
- package/dist/data/CodeBlock.cjs +3 -2
- package/dist/data/CodeBlock.cjs.map +1 -1
- package/dist/data/CodeBlock.d.ts +3 -1
- package/dist/data/CodeBlock.d.ts.map +1 -1
- package/dist/data/CodeBlock.js +3 -2
- package/dist/data/CodeBlock.js.map +1 -1
- package/dist/data/GraphDiagram.cjs +18 -6
- package/dist/data/GraphDiagram.cjs.map +1 -1
- package/dist/data/GraphDiagram.d.ts +9 -1
- package/dist/data/GraphDiagram.d.ts.map +1 -1
- package/dist/data/GraphDiagram.js +18 -6
- package/dist/data/GraphDiagram.js.map +1 -1
- package/dist/data/GraphDiagramNodes.cjs +38 -29
- package/dist/data/GraphDiagramNodes.cjs.map +1 -1
- package/dist/data/GraphDiagramNodes.d.ts +8 -2
- package/dist/data/GraphDiagramNodes.d.ts.map +1 -1
- package/dist/data/GraphDiagramNodes.js +38 -29
- package/dist/data/GraphDiagramNodes.js.map +1 -1
- package/dist/data/JsonView.cjs +118 -49
- package/dist/data/JsonView.cjs.map +1 -1
- package/dist/data/JsonView.d.ts +13 -10
- package/dist/data/JsonView.d.ts.map +1 -1
- package/dist/data/JsonView.js +118 -49
- package/dist/data/JsonView.js.map +1 -1
- package/dist/data/MarkdownEditor.cjs +2 -2
- package/dist/data/MarkdownEditor.cjs.map +1 -1
- package/dist/data/MarkdownEditor.js +2 -2
- package/dist/data/MarkdownEditor.js.map +1 -1
- package/dist/data/ai/AdvancedChatConfig.cjs +90 -249
- package/dist/data/ai/AdvancedChatConfig.cjs.map +1 -1
- package/dist/data/ai/AdvancedChatConfig.d.ts +2 -5
- package/dist/data/ai/AdvancedChatConfig.d.ts.map +1 -1
- package/dist/data/ai/AdvancedChatConfig.js +91 -250
- package/dist/data/ai/AdvancedChatConfig.js.map +1 -1
- package/dist/data/ai/ChatButton.cjs +2 -0
- package/dist/data/ai/ChatButton.cjs.map +1 -1
- package/dist/data/ai/ChatButton.d.ts +1 -1
- package/dist/data/ai/ChatButton.d.ts.map +1 -1
- package/dist/data/ai/ChatButton.js +2 -0
- package/dist/data/ai/ChatButton.js.map +1 -1
- package/dist/data/ai/ChatFab.cjs +5 -1
- package/dist/data/ai/ChatFab.cjs.map +1 -1
- package/dist/data/ai/ChatFab.d.ts +5 -1
- package/dist/data/ai/ChatFab.d.ts.map +1 -1
- package/dist/data/ai/ChatFab.js +5 -1
- package/dist/data/ai/ChatFab.js.map +1 -1
- package/dist/data/ai/ChatWindow.cjs +2 -8
- package/dist/data/ai/ChatWindow.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.js +2 -8
- package/dist/data/ai/ChatWindow.js.map +1 -1
- package/dist/data/ai/ChatWindow.runtime.cjs +0 -5
- package/dist/data/ai/ChatWindow.runtime.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.runtime.d.ts +0 -2
- package/dist/data/ai/ChatWindow.runtime.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.runtime.js +0 -5
- package/dist/data/ai/ChatWindow.runtime.js.map +1 -1
- package/dist/data/ai/PromptRunEditor/runtimeActions.cjs +2 -2
- package/dist/data/ai/PromptRunEditor/runtimeActions.cjs.map +1 -1
- package/dist/data/ai/PromptRunEditor/runtimeActions.d.ts.map +1 -1
- package/dist/data/ai/PromptRunEditor/runtimeActions.js +2 -2
- package/dist/data/ai/PromptRunEditor/runtimeActions.js.map +1 -1
- package/dist/data/ai/SessionInspector.approvals.cjs +3 -6
- package/dist/data/ai/SessionInspector.approvals.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.approvals.js +3 -6
- package/dist/data/ai/SessionInspector.approvals.js.map +1 -1
- package/dist/data/ai/SessionViewer.cjs +13 -4
- package/dist/data/ai/SessionViewer.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.d.ts +5 -1
- package/dist/data/ai/SessionViewer.d.ts.map +1 -1
- package/dist/data/ai/SessionViewer.header.cjs +1 -1
- package/dist/data/ai/SessionViewer.header.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.header.js +1 -1
- package/dist/data/ai/SessionViewer.header.js.map +1 -1
- package/dist/data/ai/SessionViewer.js +13 -4
- package/dist/data/ai/SessionViewer.js.map +1 -1
- package/dist/data/ai/SessionViewer.model.cjs +2 -0
- package/dist/data/ai/SessionViewer.model.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.model.d.ts +1 -0
- package/dist/data/ai/SessionViewer.model.d.ts.map +1 -1
- package/dist/data/ai/SessionViewer.model.js +2 -0
- package/dist/data/ai/SessionViewer.model.js.map +1 -1
- package/dist/data/ai/SessionViewer.rows.cjs +41 -27
- package/dist/data/ai/SessionViewer.rows.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.rows.d.ts +4 -2
- package/dist/data/ai/SessionViewer.rows.d.ts.map +1 -1
- package/dist/data/ai/SessionViewer.rows.js +41 -27
- package/dist/data/ai/SessionViewer.rows.js.map +1 -1
- package/dist/data/ai/SessionViewer.tokens.cjs +35 -0
- package/dist/data/ai/SessionViewer.tokens.cjs.map +1 -0
- package/dist/data/ai/SessionViewer.tokens.d.ts +5 -0
- package/dist/data/ai/SessionViewer.tokens.d.ts.map +1 -0
- package/dist/data/ai/SessionViewer.tokens.js +35 -0
- package/dist/data/ai/SessionViewer.tokens.js.map +1 -0
- package/dist/data/ai/SessionViewer.tool-cost.cjs +128 -0
- package/dist/data/ai/SessionViewer.tool-cost.cjs.map +1 -0
- package/dist/data/ai/SessionViewer.tool-cost.d.ts +6 -0
- package/dist/data/ai/SessionViewer.tool-cost.d.ts.map +1 -0
- package/dist/data/ai/SessionViewer.tool-cost.js +128 -0
- package/dist/data/ai/SessionViewer.tool-cost.js.map +1 -0
- package/dist/data/ai/SessionViewer.tool-details.cjs +3 -1
- package/dist/data/ai/SessionViewer.tool-details.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.tool-details.d.ts.map +1 -1
- package/dist/data/ai/SessionViewer.tool-details.js +3 -1
- package/dist/data/ai/SessionViewer.tool-details.js.map +1 -1
- package/dist/data/ai/SessionViewer.tool-row.cjs +55 -35
- package/dist/data/ai/SessionViewer.tool-row.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.tool-row.d.ts +2 -1
- package/dist/data/ai/SessionViewer.tool-row.d.ts.map +1 -1
- package/dist/data/ai/SessionViewer.tool-row.js +55 -35
- package/dist/data/ai/SessionViewer.tool-row.js.map +1 -1
- package/dist/data/ai/SessionViewer.unified.d.ts +4 -0
- package/dist/data/ai/SessionViewer.unified.d.ts.map +1 -1
- package/dist/data/ai/SessionViewerMenu.cjs +18 -1
- package/dist/data/ai/SessionViewerMenu.cjs.map +1 -1
- package/dist/data/ai/SessionViewerMenu.d.ts +6 -1
- package/dist/data/ai/SessionViewerMenu.d.ts.map +1 -1
- package/dist/data/ai/SessionViewerMenu.js +18 -1
- package/dist/data/ai/SessionViewerMenu.js.map +1 -1
- package/dist/data/ai/ToolPreferences.cjs +149 -143
- package/dist/data/ai/ToolPreferences.cjs.map +1 -1
- package/dist/data/ai/ToolPreferences.d.ts +20 -6
- package/dist/data/ai/ToolPreferences.d.ts.map +1 -1
- package/dist/data/ai/ToolPreferences.js +151 -146
- package/dist/data/ai/ToolPreferences.js.map +1 -1
- package/dist/data/ai/ToolPreferences.model.cjs +14 -10
- package/dist/data/ai/ToolPreferences.model.cjs.map +1 -1
- package/dist/data/ai/ToolPreferences.model.d.ts +1 -0
- package/dist/data/ai/ToolPreferences.model.d.ts.map +1 -1
- package/dist/data/ai/ToolPreferences.model.js +15 -11
- package/dist/data/ai/ToolPreferences.model.js.map +1 -1
- package/dist/data/ai/ToolPreferencesList.cjs +1 -0
- package/dist/data/ai/ToolPreferencesList.cjs.map +1 -1
- package/dist/data/ai/ToolPreferencesList.d.ts +4 -1
- package/dist/data/ai/ToolPreferencesList.d.ts.map +1 -1
- package/dist/data/ai/ToolPreferencesList.js +2 -1
- package/dist/data/ai/ToolPreferencesList.js.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.cjs +52 -53
- package/dist/data/ai/ToolSchemaBrowser.cjs.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.d.ts +6 -3
- package/dist/data/ai/ToolSchemaBrowser.d.ts.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.directory-header.cjs +9 -13
- package/dist/data/ai/ToolSchemaBrowser.directory-header.cjs.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.directory-header.d.ts +5 -2
- package/dist/data/ai/ToolSchemaBrowser.directory-header.d.ts.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.directory-header.js +9 -13
- package/dist/data/ai/ToolSchemaBrowser.directory-header.js.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.js +53 -54
- package/dist/data/ai/ToolSchemaBrowser.js.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.model.cjs +19 -27
- package/dist/data/ai/ToolSchemaBrowser.model.cjs.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.model.d.ts +9 -6
- package/dist/data/ai/ToolSchemaBrowser.model.d.ts.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.model.js +20 -28
- package/dist/data/ai/ToolSchemaBrowser.model.js.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.sidebar.cjs +12 -213
- package/dist/data/ai/ToolSchemaBrowser.sidebar.cjs.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.sidebar.d.ts +1 -3
- package/dist/data/ai/ToolSchemaBrowser.sidebar.d.ts.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.sidebar.js +13 -214
- package/dist/data/ai/ToolSchemaBrowser.sidebar.js.map +1 -1
- package/dist/data/ai/ToolSchemaBrowser.strategies.cjs +54 -0
- package/dist/data/ai/ToolSchemaBrowser.strategies.cjs.map +1 -0
- package/dist/data/ai/ToolSchemaBrowser.strategies.d.ts +9 -0
- package/dist/data/ai/ToolSchemaBrowser.strategies.d.ts.map +1 -0
- package/dist/data/ai/ToolSchemaBrowser.strategies.js +54 -0
- package/dist/data/ai/ToolSchemaBrowser.strategies.js.map +1 -0
- package/dist/data/ai/ToolSchemaBrowser.tree.cjs +179 -0
- package/dist/data/ai/ToolSchemaBrowser.tree.cjs.map +1 -0
- package/dist/data/ai/ToolSchemaBrowser.tree.d.ts +20 -0
- package/dist/data/ai/ToolSchemaBrowser.tree.d.ts.map +1 -0
- package/dist/data/ai/ToolSchemaBrowser.tree.js +179 -0
- package/dist/data/ai/ToolSchemaBrowser.tree.js.map +1 -0
- package/dist/data/ai/index.d.ts +2 -2
- package/dist/data/ai/index.d.ts.map +1 -1
- package/dist/data/ai/runtime-profiles/PermissionStrategiesEditor.cjs +8 -28
- package/dist/data/ai/runtime-profiles/PermissionStrategiesEditor.cjs.map +1 -1
- package/dist/data/ai/runtime-profiles/PermissionStrategiesEditor.d.ts.map +1 -1
- package/dist/data/ai/runtime-profiles/PermissionStrategiesEditor.js +9 -29
- package/dist/data/ai/runtime-profiles/PermissionStrategiesEditor.js.map +1 -1
- package/dist/data/ai/session-token-sizing.d.ts +32 -0
- package/dist/data/ai/session-token-sizing.d.ts.map +1 -0
- package/dist/data/ai/use-session-token-sizing.cjs +73 -0
- package/dist/data/ai/use-session-token-sizing.cjs.map +1 -0
- package/dist/data/ai/use-session-token-sizing.d.ts +21 -0
- package/dist/data/ai/use-session-token-sizing.d.ts.map +1 -0
- package/dist/data/ai/use-session-token-sizing.js +73 -0
- package/dist/data/ai/use-session-token-sizing.js.map +1 -0
- package/dist/data/call-graph/CallGraph.cjs +250 -0
- package/dist/data/call-graph/CallGraph.cjs.map +1 -0
- package/dist/data/call-graph/CallGraph.d.ts +69 -0
- package/dist/data/call-graph/CallGraph.d.ts.map +1 -0
- package/dist/data/call-graph/CallGraph.js +250 -0
- package/dist/data/call-graph/CallGraph.js.map +1 -0
- package/dist/data/call-graph/CallGraphGlyphs.cjs +39 -0
- package/dist/data/call-graph/CallGraphGlyphs.cjs.map +1 -0
- package/dist/data/call-graph/CallGraphGlyphs.d.ts +19 -0
- package/dist/data/call-graph/CallGraphGlyphs.d.ts.map +1 -0
- package/dist/data/call-graph/CallGraphGlyphs.js +39 -0
- package/dist/data/call-graph/CallGraphGlyphs.js.map +1 -0
- package/dist/data/call-graph/CallGraphMembers.cjs +50 -0
- package/dist/data/call-graph/CallGraphMembers.cjs.map +1 -0
- package/dist/data/call-graph/CallGraphMembers.d.ts +22 -0
- package/dist/data/call-graph/CallGraphMembers.d.ts.map +1 -0
- package/dist/data/call-graph/CallGraphMembers.js +50 -0
- package/dist/data/call-graph/CallGraphMembers.js.map +1 -0
- package/dist/data/call-graph/CallGraphParts.cjs +204 -0
- package/dist/data/call-graph/CallGraphParts.cjs.map +1 -0
- package/dist/data/call-graph/CallGraphParts.d.ts +44 -0
- package/dist/data/call-graph/CallGraphParts.d.ts.map +1 -0
- package/dist/data/call-graph/CallGraphParts.js +204 -0
- package/dist/data/call-graph/CallGraphParts.js.map +1 -0
- package/dist/data/call-graph/CallGraphToolbar.cjs +186 -0
- package/dist/data/call-graph/CallGraphToolbar.cjs.map +1 -0
- package/dist/data/call-graph/CallGraphToolbar.d.ts +45 -0
- package/dist/data/call-graph/CallGraphToolbar.d.ts.map +1 -0
- package/dist/data/call-graph/CallGraphToolbar.js +186 -0
- package/dist/data/call-graph/CallGraphToolbar.js.map +1 -0
- package/dist/data/call-graph/call-graph-exclude.cjs +75 -0
- package/dist/data/call-graph/call-graph-exclude.cjs.map +1 -0
- package/dist/data/call-graph/call-graph-exclude.d.ts +31 -0
- package/dist/data/call-graph/call-graph-exclude.d.ts.map +1 -0
- package/dist/data/call-graph/call-graph-exclude.js +75 -0
- package/dist/data/call-graph/call-graph-exclude.js.map +1 -0
- package/dist/data/call-graph/call-graph-labels.cjs +96 -0
- package/dist/data/call-graph/call-graph-labels.cjs.map +1 -0
- package/dist/data/call-graph/call-graph-labels.d.ts +53 -0
- package/dist/data/call-graph/call-graph-labels.d.ts.map +1 -0
- package/dist/data/call-graph/call-graph-labels.js +96 -0
- package/dist/data/call-graph/call-graph-labels.js.map +1 -0
- package/dist/data/call-graph/call-graph-model.cjs +161 -0
- package/dist/data/call-graph/call-graph-model.cjs.map +1 -0
- package/dist/data/call-graph/call-graph-model.d.ts +102 -0
- package/dist/data/call-graph/call-graph-model.d.ts.map +1 -0
- package/dist/data/call-graph/call-graph-model.js +161 -0
- package/dist/data/call-graph/call-graph-model.js.map +1 -0
- package/dist/data/call-graph/call-graph-vocabulary.cjs +177 -0
- package/dist/data/call-graph/call-graph-vocabulary.cjs.map +1 -0
- package/dist/data/call-graph/call-graph-vocabulary.d.ts +72 -0
- package/dist/data/call-graph/call-graph-vocabulary.d.ts.map +1 -0
- package/dist/data/call-graph/call-graph-vocabulary.js +177 -0
- package/dist/data/call-graph/call-graph-vocabulary.js.map +1 -0
- package/dist/data/call-graph/call-graph.fixtures.d.ts +63 -0
- package/dist/data/call-graph/call-graph.fixtures.d.ts.map +1 -0
- package/dist/data/call-graph/index.d.ts +7 -0
- package/dist/data/call-graph/index.d.ts.map +1 -0
- package/dist/data/call-graph/types.cjs +5 -0
- package/dist/data/call-graph/types.cjs.map +1 -0
- package/dist/data/call-graph/types.d.ts +135 -0
- package/dist/data/call-graph/types.d.ts.map +1 -0
- package/dist/data/call-graph/types.js +5 -0
- package/dist/data/call-graph/types.js.map +1 -0
- package/dist/data/call-graph/use-call-graph.cjs +93 -0
- package/dist/data/call-graph/use-call-graph.cjs.map +1 -0
- package/dist/data/call-graph/use-call-graph.d.ts +24 -0
- package/dist/data/call-graph/use-call-graph.d.ts.map +1 -0
- package/dist/data/call-graph/use-call-graph.js +93 -0
- package/dist/data/call-graph/use-call-graph.js.map +1 -0
- package/dist/data/chat/Chat.cjs +39 -15
- package/dist/data/chat/Chat.cjs.map +1 -1
- package/dist/data/chat/Chat.d.ts.map +1 -1
- package/dist/data/chat/Chat.fixtures.cjs +2 -2
- package/dist/data/chat/Chat.fixtures.cjs.map +1 -1
- package/dist/data/chat/Chat.fixtures.js +2 -2
- package/dist/data/chat/Chat.fixtures.js.map +1 -1
- package/dist/data/chat/Chat.js +40 -16
- package/dist/data/chat/Chat.js.map +1 -1
- package/dist/data/chat/ChatRuntimeToolbar.cjs +3 -6
- package/dist/data/chat/ChatRuntimeToolbar.cjs.map +1 -1
- package/dist/data/chat/ChatRuntimeToolbar.d.ts.map +1 -1
- package/dist/data/chat/ChatRuntimeToolbar.js +3 -6
- package/dist/data/chat/ChatRuntimeToolbar.js.map +1 -1
- package/dist/data/chat/ContextMeter.bar.cjs +5 -8
- package/dist/data/chat/ContextMeter.bar.cjs.map +1 -1
- package/dist/data/chat/ContextMeter.bar.d.ts +1 -1
- package/dist/data/chat/ContextMeter.bar.d.ts.map +1 -1
- package/dist/data/chat/ContextMeter.bar.js +5 -8
- package/dist/data/chat/ContextMeter.bar.js.map +1 -1
- package/dist/data/chat/ContextMeter.cjs +18 -19
- package/dist/data/chat/ContextMeter.cjs.map +1 -1
- package/dist/data/chat/ContextMeter.d.ts +1 -1
- package/dist/data/chat/ContextMeter.d.ts.map +1 -1
- package/dist/data/chat/ContextMeter.js +18 -19
- package/dist/data/chat/ContextMeter.js.map +1 -1
- package/dist/data/chat/Conversation.cjs +2 -1
- package/dist/data/chat/Conversation.cjs.map +1 -1
- package/dist/data/chat/Conversation.d.ts +4 -1
- package/dist/data/chat/Conversation.d.ts.map +1 -1
- package/dist/data/chat/Conversation.js +2 -1
- package/dist/data/chat/Conversation.js.map +1 -1
- package/dist/data/chat/Message.cjs +39 -12
- package/dist/data/chat/Message.cjs.map +1 -1
- package/dist/data/chat/Message.d.ts +1 -1
- package/dist/data/chat/Message.d.ts.map +1 -1
- package/dist/data/chat/Message.js +40 -13
- package/dist/data/chat/Message.js.map +1 -1
- package/dist/data/chat/ToolCall.cjs +60 -5
- package/dist/data/chat/ToolCall.cjs.map +1 -1
- package/dist/data/chat/ToolCall.d.ts +7 -2
- package/dist/data/chat/ToolCall.d.ts.map +1 -1
- package/dist/data/chat/ToolCall.js +60 -5
- package/dist/data/chat/ToolCall.js.map +1 -1
- package/dist/data/chat/approval.cjs.map +1 -1
- package/dist/data/chat/approval.d.ts +2 -2
- package/dist/data/chat/approval.d.ts.map +1 -1
- package/dist/data/chat/approval.js.map +1 -1
- package/dist/data/chat/tool-policy.cjs +59 -2
- package/dist/data/chat/tool-policy.cjs.map +1 -1
- package/dist/data/chat/tool-policy.d.ts +9 -0
- package/dist/data/chat/tool-policy.d.ts.map +1 -1
- package/dist/data/chat/tool-policy.js +59 -2
- package/dist/data/chat/tool-policy.js.map +1 -1
- package/dist/data/chat/types.cjs +6 -0
- package/dist/data/chat/types.cjs.map +1 -1
- package/dist/data/chat/types.d.ts +13 -4
- package/dist/data/chat/types.d.ts.map +1 -1
- package/dist/data/chat/types.js +7 -1
- package/dist/data/chat/types.js.map +1 -1
- package/dist/data/chat/usage-snapshot.cjs +11 -6
- package/dist/data/chat/usage-snapshot.cjs.map +1 -1
- package/dist/data/chat/usage-snapshot.d.ts +0 -1
- package/dist/data/chat/usage-snapshot.d.ts.map +1 -1
- package/dist/data/chat/usage-snapshot.js +11 -6
- package/dist/data/chat/usage-snapshot.js.map +1 -1
- package/dist/data/diagnostics/StackFrameRow.cjs +3 -3
- package/dist/data/diagnostics/StackFrameRow.cjs.map +1 -1
- package/dist/data/diagnostics/StackFrameRow.js +3 -3
- package/dist/data/diagnostics/StackFrameRow.js.map +1 -1
- package/dist/data/graph-columns-layout.cjs +26 -10
- package/dist/data/graph-columns-layout.cjs.map +1 -1
- package/dist/data/graph-columns-layout.d.ts +8 -0
- package/dist/data/graph-columns-layout.d.ts.map +1 -1
- package/dist/data/graph-columns-layout.js +26 -10
- package/dist/data/graph-columns-layout.js.map +1 -1
- package/dist/data/graph-diagram-model.cjs.map +1 -1
- package/dist/data/graph-diagram-model.d.ts +5 -0
- package/dist/data/graph-diagram-model.d.ts.map +1 -1
- package/dist/data/graph-diagram-model.js.map +1 -1
- package/dist/data/graph-edge-routing.cjs +9 -3
- package/dist/data/graph-edge-routing.cjs.map +1 -1
- package/dist/data/graph-edge-routing.d.ts +2 -0
- package/dist/data/graph-edge-routing.d.ts.map +1 -1
- package/dist/data/graph-edge-routing.js +9 -3
- package/dist/data/graph-edge-routing.js.map +1 -1
- package/dist/data/json-view/JsonSource.cjs +84 -0
- package/dist/data/json-view/JsonSource.cjs.map +1 -0
- package/dist/data/json-view/JsonSource.d.ts +7 -0
- package/dist/data/json-view/JsonSource.d.ts.map +1 -0
- package/dist/data/json-view/JsonSource.js +84 -0
- package/dist/data/json-view/JsonSource.js.map +1 -0
- package/dist/data/json-view/parse-prefix.cjs +132 -0
- package/dist/data/json-view/parse-prefix.cjs.map +1 -0
- package/dist/data/json-view/parse-prefix.d.ts +11 -0
- package/dist/data/json-view/parse-prefix.d.ts.map +1 -0
- package/dist/data/json-view/parse-prefix.js +132 -0
- package/dist/data/json-view/parse-prefix.js.map +1 -0
- package/dist/data/json-view/parse-source.cjs +62 -0
- package/dist/data/json-view/parse-source.cjs.map +1 -0
- package/dist/data/json-view/parse-source.d.ts +16 -0
- package/dist/data/json-view/parse-source.d.ts.map +1 -0
- package/dist/data/json-view/parse-source.js +62 -0
- package/dist/data/json-view/parse-source.js.map +1 -0
- package/dist/data/json-view/story-fixtures.d.ts +6 -0
- package/dist/data/json-view/story-fixtures.d.ts.map +1 -0
- package/dist/data/json-view/types.d.ts +14 -0
- package/dist/data/json-view/types.d.ts.map +1 -0
- package/dist/data/runtime/RuntimeBar.cjs +2 -5
- package/dist/data/runtime/RuntimeBar.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBar.d.ts +2 -4
- package/dist/data/runtime/RuntimeBar.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBar.fields.cjs +2 -2
- package/dist/data/runtime/RuntimeBar.fields.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBar.fields.js +2 -2
- package/dist/data/runtime/RuntimeBar.fields.js.map +1 -1
- package/dist/data/runtime/RuntimeBar.js +2 -5
- package/dist/data/runtime/RuntimeBar.js.map +1 -1
- package/dist/data/runtime/RuntimeBar.selection.cjs +4 -1
- package/dist/data/runtime/RuntimeBar.selection.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBar.selection.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBar.selection.js +5 -2
- package/dist/data/runtime/RuntimeBar.selection.js.map +1 -1
- package/dist/data/runtime/RuntimeBarActions.cjs +1 -5
- package/dist/data/runtime/RuntimeBarActions.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBarActions.d.ts +1 -1
- package/dist/data/runtime/RuntimeBarActions.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBarActions.js +1 -5
- package/dist/data/runtime/RuntimeBarActions.js.map +1 -1
- package/dist/data/runtime/RuntimeBarIdentity.cjs +6 -14
- package/dist/data/runtime/RuntimeBarIdentity.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBarIdentity.d.ts +0 -1
- package/dist/data/runtime/RuntimeBarIdentity.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBarIdentity.js +6 -14
- package/dist/data/runtime/RuntimeBarIdentity.js.map +1 -1
- package/dist/data/runtime/RuntimeBarLayout.cjs +8 -27
- package/dist/data/runtime/RuntimeBarLayout.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBarLayout.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBarLayout.js +8 -27
- package/dist/data/runtime/RuntimeBarLayout.js.map +1 -1
- package/dist/data/runtime/RuntimeBarSegment.cjs +1 -5
- package/dist/data/runtime/RuntimeBarSegment.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBarSegment.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBarSegment.js +1 -5
- package/dist/data/runtime/RuntimeBarSegment.js.map +1 -1
- package/dist/data/runtime/use-runtime-bar-overflow.cjs +4 -10
- package/dist/data/runtime/use-runtime-bar-overflow.cjs.map +1 -1
- package/dist/data/runtime/use-runtime-bar-overflow.d.ts +1 -2
- package/dist/data/runtime/use-runtime-bar-overflow.d.ts.map +1 -1
- package/dist/data/runtime/use-runtime-bar-overflow.js +4 -10
- package/dist/data/runtime/use-runtime-bar-overflow.js.map +1 -1
- package/dist/data/test-runner/TestRunner.fixtures.d.ts +0 -20
- package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -1
- package/dist/data/test-runner/TestRunner.large-fixtures.d.ts +22 -0
- package/dist/data/test-runner/TestRunner.large-fixtures.d.ts.map +1 -0
- package/dist/data/use-pan-zoom.cjs +23 -10
- package/dist/data/use-pan-zoom.cjs.map +1 -1
- package/dist/data/use-pan-zoom.d.ts +16 -2
- package/dist/data/use-pan-zoom.d.ts.map +1 -1
- package/dist/data/use-pan-zoom.js +23 -10
- package/dist/data/use-pan-zoom.js.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +26 -2
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +1 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +24 -0
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +28 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/layout/Workspace.cjs +13 -4
- package/dist/layout/Workspace.cjs.map +1 -1
- package/dist/layout/Workspace.d.ts +7 -2
- package/dist/layout/Workspace.d.ts.map +1 -1
- package/dist/layout/Workspace.js +13 -4
- package/dist/layout/Workspace.js.map +1 -1
- package/dist/lib/permission-strategies.cjs +71 -11
- package/dist/lib/permission-strategies.cjs.map +1 -1
- package/dist/lib/permission-strategies.d.ts +3 -0
- package/dist/lib/permission-strategies.d.ts.map +1 -1
- package/dist/lib/permission-strategies.js +71 -11
- package/dist/lib/permission-strategies.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +5 -3
- package/dist/data/ai/ToolSchemaBrowser.selection-toolbar.cjs +0 -57
- package/dist/data/ai/ToolSchemaBrowser.selection-toolbar.cjs.map +0 -1
- package/dist/data/ai/ToolSchemaBrowser.selection-toolbar.d.ts +0 -7
- package/dist/data/ai/ToolSchemaBrowser.selection-toolbar.d.ts.map +0 -1
- package/dist/data/ai/ToolSchemaBrowser.selection-toolbar.js +0 -57
- package/dist/data/ai/ToolSchemaBrowser.selection-toolbar.js.map +0 -1
- package/dist/data/runtime/RuntimeBar.context.cjs +0 -8
- package/dist/data/runtime/RuntimeBar.context.cjs.map +0 -1
- package/dist/data/runtime/RuntimeBar.context.d.ts +0 -2
- package/dist/data/runtime/RuntimeBar.context.d.ts.map +0 -1
- package/dist/data/runtime/RuntimeBar.context.js +0 -8
- package/dist/data/runtime/RuntimeBar.context.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlock.cjs","sources":["../../src/data/CodeBlock.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useResolvedTheme, type ResolvedTheme } from \"../hooks/use-theme\";\nimport { CodeBlockActions } from \"./code-block-actions\";\nimport { CodeDiff } from \"./CodeDiff\";\nimport { JsonView } from \"./JsonView\";\nimport { highlightCode } from \"./code-highlight\";\nimport { sanitizeHtml } from \"./html-utils\";\n\nexport type CodeBlockProps = {\n /** Language label and highlighter hint, e.g. `json`, `tsx`, or `bash`. */\n language?: string | undefined;\n /** Raw source code to render or highlight. */\n source?: string | undefined;\n /** Trusted server-rendered highlighted HTML, sanitized before rendering. */\n highlightedHtml?: string | undefined;\n /** Classes applied to the code block shell. */\n className?: string | undefined;\n /** Default expansion depth when rendering JSON as a tree. */\n jsonDefaultOpenDepth?: number | undefined;\n /** Render only the code — no border, background, or language header. */\n bare?: boolean | undefined;\n /** Show a copy-to-clipboard button in the header (ignored when `bare`). */\n copyable?: boolean | undefined;\n /** Show a download button in the header (ignored when `bare`). */\n downloadable?: boolean | undefined;\n /** Name of the downloaded file. Defaults to `snippet.<extension of language>`. */\n downloadFilename?: string | undefined;\n /** Show a per-block light/dark theme toggle in the header (ignored when `bare`). */\n themeToggle?: boolean | undefined;\n /** Render a language-aware diff instead of highlighting `source`. */\n diff?: { original: string; modified: string } | { unified: string } | undefined;\n /** Diff layout when `diff` is set. Defaults to `unified`. */\n diffView?: \"unified\" | \"split\" | undefined;\n /**\n * Render as a single truncated line for a table cell: no border, header, or\n * wrap, whitespace collapsed to one line, syntax-colored when highlighting\n * is available, with the collapsed source (up to 2,000 chars) in a title\n * tooltip. Only the leading 500 chars are rendered and highlighted — the\n * cell clips long before that. The full block is what a row-detail dialog\n * should show instead.\n */\n inline?: boolean | undefined;\n};\n\n// A captured SQL statement can run to tens of thousands of characters on one\n// line; highlighting all of it for a one-line cell cost ~220 ms a statement.\nconst INLINE_RENDER_CHARS = 500;\nconst INLINE_TITLE_CHARS = 2000;\n\nexport function CodeBlock({\n language: languageProp,\n source = \"\",\n highlightedHtml,\n className,\n jsonDefaultOpenDepth = 2,\n bare = false,\n copyable = false,\n downloadable = false,\n downloadFilename,\n themeToggle = false,\n diff,\n diffView,\n inline = false,\n}: CodeBlockProps) {\n const language = (languageProp ?? \"\").toLowerCase().replace(/^\\.+/, \"\");\n const chromaHtml = highlightedHtml ? sanitizeHtml(highlightedHtml) : \"\";\n const parsedJson = useMemo(\n () => (language === \"json\" ? tryParseJson(source) : JSON_PARSE_FAILED),\n [language, source],\n );\n // The block follows the global app theme by default; a per-block override\n // (set by the header toggle) wins over it and re-highlights just this block.\n const globalTheme = useResolvedTheme();\n const [themeOverride, setThemeOverride] = useState<ResolvedTheme | null>(null);\n const effectiveTheme = themeOverride ?? globalTheme;\n const [shikiHtml, setShikiHtml] = useState<string | null>(null);\n const wantsClientHighlight =\n parsedJson === JSON_PARSE_FAILED && !chromaHtml && !!language && !!source;\n const singleLine = useMemo(\n () => (inline ? source.replace(/\\s+/g, \" \").trim() : \"\"),\n [inline, source],\n );\n const highlightSource = inline ? singleLine.slice(0, INLINE_RENDER_CHARS) : source;\n\n useEffect(() => {\n if (!wantsClientHighlight) {\n setShikiHtml(null);\n return;\n }\n\n let cancelled = false;\n const theme = effectiveTheme === \"dark\" ? \"github-dark\" : \"github-light\";\n highlightCode(highlightSource, { lang: language, theme }).then((out) => {\n if (!cancelled) setShikiHtml(out);\n });\n\n return () => {\n cancelled = true;\n };\n }, [wantsClientHighlight, language, highlightSource, effectiveTheme]);\n\n const headerActions = (\n <CodeBlockActions\n source={source}\n language={languageProp}\n copyable={copyable}\n downloadable={downloadable}\n downloadFilename={downloadFilename}\n theme={effectiveTheme}\n {...(themeToggle\n ? { onToggleTheme: () => setThemeOverride(effectiveTheme === \"dark\" ? \"light\" : \"dark\") }\n : {})}\n />\n );\n // Re-scope theme tokens (shell background/border/label) to the override so an\n // overridden block stays visually coherent with its re-highlighted code.\n const overrideAttrs = themeOverride ? { \"data-theme\": themeOverride } : {};\n\n if (inline) {\n const html = chromaHtml || shikiHtml;\n const title =\n singleLine.length > INLINE_TITLE_CHARS\n ? `${singleLine.slice(0, INLINE_TITLE_CHARS)}…`\n : singleLine;\n return (\n <code\n className={cn(\n \"block max-w-full truncate font-mono text-xs text-foreground\",\n className,\n html && \"[&_pre]:inline [&_pre]:whitespace-pre [&_.chroma]:bg-transparent\",\n )}\n title={title}\n >\n {html ? <span dangerouslySetInnerHTML={{ __html: html }} /> : highlightSource}\n </code>\n );\n }\n\n if (diff) {\n return (\n <CodeDiff\n language={languageProp}\n view={diffView}\n bare={bare}\n className={className}\n {...diff}\n />\n );\n }\n\n if (parsedJson !== JSON_PARSE_FAILED) {\n const tree = <JsonView data={parsedJson} defaultOpenDepth={jsonDefaultOpenDepth} />;\n if (bare) return <div className={cn(\"overflow-auto text-xs\", className)}>{tree}</div>;\n return (\n <div\n className={cn(\"overflow-hidden rounded-md border border-border bg-muted/40\", className)}\n {...overrideAttrs}\n >\n <CodeBlockHeader label=\"json\" actions={headerActions} />\n <div className=\"overflow-auto p-3 text-xs\">{tree}</div>\n </div>\n );\n }\n\n const xmlClasses =\n language === \"xml\" || language === \"xslt\" || language === \"html\"\n ? \" [&_.chroma_.nt]:text-pink-700 [&_.chroma_.nt]:dark:text-pink-300 [&_.chroma_.nt]:font-semibold\" +\n \" [&_.chroma_.na]:text-amber-700 [&_.chroma_.na]:dark:text-amber-300\" +\n \" [&_.chroma_.s]:text-emerald-700 [&_.chroma_.s]:dark:text-emerald-300\" +\n \" [&_.chroma_.c]:text-slate-500 [&_.chroma_.c]:italic\" +\n \" [&_.chroma_.cp]:text-slate-500 [&_.chroma_.cp]:italic\" +\n \" [&_pre]:leading-relaxed\"\n : \"\";\n\n const pad = bare ? undefined : \"p-3\";\n const body = chromaHtml ? (\n <div\n className={cn(\n \"overflow-auto text-xs font-mono [&_.chroma]:bg-transparent [&_pre]:m-0 [&_pre]:whitespace-pre-wrap [&_pre]:bg-transparent\" +\n xmlClasses,\n pad,\n bare && className,\n )}\n dangerouslySetInnerHTML={{ __html: chromaHtml }}\n />\n ) : shikiHtml ? (\n <div\n className={cn(\n \"overflow-auto text-xs font-mono [&_pre]:m-0 [&_pre.shiki]:!bg-transparent [&_pre]:whitespace-pre-wrap\",\n pad,\n bare && className,\n )}\n dangerouslySetInnerHTML={{ __html: shikiHtml }}\n />\n ) : (\n <pre\n className={cn(\n \"overflow-auto whitespace-pre-wrap break-words text-xs font-mono text-foreground\",\n pad,\n bare && className,\n )}\n >\n {source}\n </pre>\n );\n\n if (bare) return body;\n return (\n <div\n className={cn(\"overflow-hidden rounded-md border border-border bg-muted/40\", className)}\n {...overrideAttrs}\n >\n <CodeBlockHeader label={languageProp || \"text\"} actions={headerActions} />\n {body}\n </div>\n );\n}\n\nfunction CodeBlockHeader({ label, actions }: { label: string; actions: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-2 border-b border-border px-3 py-1.5\">\n <span className=\"text-[11px] uppercase tracking-wide text-muted-foreground\">{label}</span>\n {actions}\n </div>\n );\n}\n\nconst JSON_PARSE_FAILED: unique symbol = Symbol(\"json-parse-failed\");\n\nfunction tryParseJson(source: string): unknown | typeof JSON_PARSE_FAILED {\n const trimmed = source.trim();\n if (!trimmed) return JSON_PARSE_FAILED;\n try {\n return JSON.parse(trimmed);\n } catch {\n return JSON_PARSE_FAILED;\n }\n}\n"],"names":["sanitizeHtml","useMemo","useResolvedTheme","useState","useEffect","highlightCode","jsx","CodeBlockActions","cn","CodeDiff","JsonView","jsxs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CodeBlock.cjs","sources":["../../src/data/CodeBlock.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useResolvedTheme, type ResolvedTheme } from \"../hooks/use-theme\";\nimport { CodeBlockActions } from \"./code-block-actions\";\nimport { CodeDiff } from \"./CodeDiff\";\nimport { JsonView } from \"./JsonView\";\nimport { highlightCode } from \"./code-highlight\";\nimport { sanitizeHtml } from \"./html-utils\";\n\nexport type CodeBlockProps = {\n /** Language label and highlighter hint, e.g. `json`, `tsx`, or `bash`. */\n language?: string | undefined;\n /** Raw source code to render or highlight. */\n source?: string | undefined;\n /** Trusted server-rendered highlighted HTML, sanitized before rendering. */\n highlightedHtml?: string | undefined;\n /** Classes applied to the code block shell. */\n className?: string | undefined;\n /** Default expansion depth when rendering JSON as a tree. */\n jsonDefaultOpenDepth?: number | undefined;\n /** Display format for parsed JSON values. */\n jsonFormat?: \"json\" | \"yaml\" | undefined;\n /** Render only the code — no border, background, or language header. */\n bare?: boolean | undefined;\n /** Show a copy-to-clipboard button in the header (ignored when `bare`). */\n copyable?: boolean | undefined;\n /** Show a download button in the header (ignored when `bare`). */\n downloadable?: boolean | undefined;\n /** Name of the downloaded file. Defaults to `snippet.<extension of language>`. */\n downloadFilename?: string | undefined;\n /** Show a per-block light/dark theme toggle in the header (ignored when `bare`). */\n themeToggle?: boolean | undefined;\n /** Render a language-aware diff instead of highlighting `source`. */\n diff?: { original: string; modified: string } | { unified: string } | undefined;\n /** Diff layout when `diff` is set. Defaults to `unified`. */\n diffView?: \"unified\" | \"split\" | undefined;\n /**\n * Render as a single truncated line for a table cell: no border, header, or\n * wrap, whitespace collapsed to one line, syntax-colored when highlighting\n * is available, with the collapsed source (up to 2,000 chars) in a title\n * tooltip. Only the leading 500 chars are rendered and highlighted — the\n * cell clips long before that. The full block is what a row-detail dialog\n * should show instead.\n */\n inline?: boolean | undefined;\n};\n\n// A captured SQL statement can run to tens of thousands of characters on one\n// line; highlighting all of it for a one-line cell cost ~220 ms a statement.\nconst INLINE_RENDER_CHARS = 500;\nconst INLINE_TITLE_CHARS = 2000;\n\nexport function CodeBlock({\n language: languageProp,\n source = \"\",\n highlightedHtml,\n className,\n jsonDefaultOpenDepth = 2,\n jsonFormat = \"json\",\n bare = false,\n copyable = false,\n downloadable = false,\n downloadFilename,\n themeToggle = false,\n diff,\n diffView,\n inline = false,\n}: CodeBlockProps) {\n const language = (languageProp ?? \"\").toLowerCase().replace(/^\\.+/, \"\");\n const chromaHtml = highlightedHtml ? sanitizeHtml(highlightedHtml) : \"\";\n const parsedJson = useMemo(\n () => (language === \"json\" ? tryParseJson(source) : JSON_PARSE_FAILED),\n [language, source],\n );\n // The block follows the global app theme by default; a per-block override\n // (set by the header toggle) wins over it and re-highlights just this block.\n const globalTheme = useResolvedTheme();\n const [themeOverride, setThemeOverride] = useState<ResolvedTheme | null>(null);\n const effectiveTheme = themeOverride ?? globalTheme;\n const [shikiHtml, setShikiHtml] = useState<string | null>(null);\n const wantsClientHighlight =\n parsedJson === JSON_PARSE_FAILED && !chromaHtml && !!language && !!source;\n const singleLine = useMemo(\n () => (inline ? source.replace(/\\s+/g, \" \").trim() : \"\"),\n [inline, source],\n );\n const highlightSource = inline ? singleLine.slice(0, INLINE_RENDER_CHARS) : source;\n\n useEffect(() => {\n if (!wantsClientHighlight) {\n setShikiHtml(null);\n return;\n }\n\n let cancelled = false;\n const theme = effectiveTheme === \"dark\" ? \"github-dark\" : \"github-light\";\n highlightCode(highlightSource, { lang: language, theme }).then((out) => {\n if (!cancelled) setShikiHtml(out);\n });\n\n return () => {\n cancelled = true;\n };\n }, [wantsClientHighlight, language, highlightSource, effectiveTheme]);\n\n const headerActions = (\n <CodeBlockActions\n source={source}\n language={languageProp}\n copyable={copyable}\n downloadable={downloadable}\n downloadFilename={downloadFilename}\n theme={effectiveTheme}\n {...(themeToggle\n ? { onToggleTheme: () => setThemeOverride(effectiveTheme === \"dark\" ? \"light\" : \"dark\") }\n : {})}\n />\n );\n // Re-scope theme tokens (shell background/border/label) to the override so an\n // overridden block stays visually coherent with its re-highlighted code.\n const overrideAttrs = themeOverride ? { \"data-theme\": themeOverride } : {};\n\n if (inline) {\n const html = chromaHtml || shikiHtml;\n const title =\n singleLine.length > INLINE_TITLE_CHARS\n ? `${singleLine.slice(0, INLINE_TITLE_CHARS)}…`\n : singleLine;\n return (\n <code\n className={cn(\n \"block max-w-full truncate font-mono text-xs text-foreground\",\n className,\n html && \"[&_pre]:inline [&_pre]:whitespace-pre [&_.chroma]:bg-transparent\",\n )}\n title={title}\n >\n {html ? <span dangerouslySetInnerHTML={{ __html: html }} /> : highlightSource}\n </code>\n );\n }\n\n if (diff) {\n return (\n <CodeDiff\n language={languageProp}\n view={diffView}\n bare={bare}\n className={className}\n {...diff}\n />\n );\n }\n\n if (parsedJson !== JSON_PARSE_FAILED) {\n const tree = <JsonView data={parsedJson} format={jsonFormat} defaultOpenDepth={jsonDefaultOpenDepth} />;\n if (bare) return <div className={cn(\"overflow-auto text-xs\", className)}>{tree}</div>;\n return (\n <div\n className={cn(\"overflow-hidden rounded-md border border-border bg-muted/40\", className)}\n {...overrideAttrs}\n >\n <CodeBlockHeader label={jsonFormat} actions={headerActions} />\n <div className=\"overflow-auto p-3 text-xs\">{tree}</div>\n </div>\n );\n }\n\n const xmlClasses =\n language === \"xml\" || language === \"xslt\" || language === \"html\"\n ? \" [&_.chroma_.nt]:text-pink-700 [&_.chroma_.nt]:dark:text-pink-300 [&_.chroma_.nt]:font-semibold\" +\n \" [&_.chroma_.na]:text-amber-700 [&_.chroma_.na]:dark:text-amber-300\" +\n \" [&_.chroma_.s]:text-emerald-700 [&_.chroma_.s]:dark:text-emerald-300\" +\n \" [&_.chroma_.c]:text-slate-500 [&_.chroma_.c]:italic\" +\n \" [&_.chroma_.cp]:text-slate-500 [&_.chroma_.cp]:italic\" +\n \" [&_pre]:leading-relaxed\"\n : \"\";\n\n const pad = bare ? undefined : \"p-3\";\n const body = chromaHtml ? (\n <div\n className={cn(\n \"overflow-auto text-xs font-mono [&_.chroma]:bg-transparent [&_pre]:m-0 [&_pre]:whitespace-pre-wrap [&_pre]:bg-transparent\" +\n xmlClasses,\n pad,\n bare && className,\n )}\n dangerouslySetInnerHTML={{ __html: chromaHtml }}\n />\n ) : shikiHtml ? (\n <div\n className={cn(\n \"overflow-auto text-xs font-mono [&_pre]:m-0 [&_pre.shiki]:!bg-transparent [&_pre]:whitespace-pre-wrap\",\n pad,\n bare && className,\n )}\n dangerouslySetInnerHTML={{ __html: shikiHtml }}\n />\n ) : (\n <pre\n className={cn(\n \"overflow-auto whitespace-pre-wrap break-words text-xs font-mono text-foreground\",\n pad,\n bare && className,\n )}\n >\n {source}\n </pre>\n );\n\n if (bare) return body;\n return (\n <div\n className={cn(\"overflow-hidden rounded-md border border-border bg-muted/40\", className)}\n {...overrideAttrs}\n >\n <CodeBlockHeader label={languageProp || \"text\"} actions={headerActions} />\n {body}\n </div>\n );\n}\n\nfunction CodeBlockHeader({ label, actions }: { label: string; actions: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-2 border-b border-border px-3 py-1.5\">\n <span className=\"text-[11px] uppercase tracking-wide text-muted-foreground\">{label}</span>\n {actions}\n </div>\n );\n}\n\nconst JSON_PARSE_FAILED: unique symbol = Symbol(\"json-parse-failed\");\n\nfunction tryParseJson(source: string): unknown | typeof JSON_PARSE_FAILED {\n const trimmed = source.trim();\n if (!trimmed) return JSON_PARSE_FAILED;\n try {\n return JSON.parse(trimmed);\n } catch {\n return JSON_PARSE_FAILED;\n }\n}\n"],"names":["sanitizeHtml","useMemo","useResolvedTheme","useState","useEffect","highlightCode","jsx","CodeBlockActions","cn","CodeDiff","JsonView","jsxs"],"mappings":";;;;;;;;;;;AAiDA,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAEpB,SAAS,UAAU;AAAA,EACxB,UAAU;AAAA,EACV,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA,uBAAuB;AAAA,EACvB,aAAa;AAAA,EACb,OAAO;AAAA,EACP,WAAW;AAAA,EACX,eAAe;AAAA,EACf;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,SAAS;AACX,GAAmB;AACjB,QAAM,YAAY,gBAAgB,IAAI,cAAc,QAAQ,QAAQ,EAAE;AACtE,QAAM,aAAa,kBAAkBA,uBAAa,eAAe,IAAI;AACrE,QAAM,aAAaC,MAAAA;AAAAA,IACjB,MAAO,aAAa,SAAS,aAAa,MAAM,IAAI;AAAA,IACpD,CAAC,UAAU,MAAM;AAAA,EAAA;AAInB,QAAM,cAAcC,SAAAA,iBAAA;AACpB,QAAM,CAAC,eAAe,gBAAgB,IAAIC,MAAAA,SAA+B,IAAI;AAC7E,QAAM,iBAAiB,iBAAiB;AACxC,QAAM,CAAC,WAAW,YAAY,IAAIA,MAAAA,SAAwB,IAAI;AAC9D,QAAM,uBACJ,eAAe,qBAAqB,CAAC,cAAc,CAAC,CAAC,YAAY,CAAC,CAAC;AACrE,QAAM,aAAaF,MAAAA;AAAAA,IACjB,MAAO,SAAS,OAAO,QAAQ,QAAQ,GAAG,EAAE,SAAS;AAAA,IACrD,CAAC,QAAQ,MAAM;AAAA,EAAA;AAEjB,QAAM,kBAAkB,SAAS,WAAW,MAAM,GAAG,mBAAmB,IAAI;AAE5EG,QAAAA,UAAU,MAAM;AACd,QAAI,CAAC,sBAAsB;AACzB,mBAAa,IAAI;AACjB;AAAA,IACF;AAEA,QAAI,YAAY;AAChB,UAAM,QAAQ,mBAAmB,SAAS,gBAAgB;AAC1DC,gCAAc,iBAAiB,EAAE,MAAM,UAAU,OAAO,EAAE,KAAK,CAAC,QAAQ;AACtE,UAAI,CAAC,UAAW,cAAa,GAAG;AAAA,IAClC,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,sBAAsB,UAAU,iBAAiB,cAAc,CAAC;AAEpE,QAAM,gBACJC,2BAAAA;AAAAA,IAACC,iBAAAA;AAAAA,IAAA;AAAA,MACC;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACN,GAAI,cACD,EAAE,eAAe,MAAM,iBAAiB,mBAAmB,SAAS,UAAU,MAAM,MACpF,CAAA;AAAA,IAAC;AAAA,EAAA;AAKT,QAAM,gBAAgB,gBAAgB,EAAE,cAAc,cAAA,IAAkB,CAAA;AAExE,MAAI,QAAQ;AACV,UAAM,OAAO,cAAc;AAC3B,UAAM,QACJ,WAAW,SAAS,qBAChB,GAAG,WAAW,MAAM,GAAG,kBAAkB,CAAC,MAC1C;AACN,WACED,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAWE,MAAAA;AAAAA,UACT;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,QAAA;AAAA,QAEV;AAAA,QAEC,UAAA,sCAAQ,QAAA,EAAK,yBAAyB,EAAE,QAAQ,KAAA,GAAQ,IAAK;AAAA,MAAA;AAAA,IAAA;AAAA,EAGpE;AAEA,MAAI,MAAM;AACR,WACEF,2BAAAA;AAAAA,MAACG,SAAAA;AAAAA,MAAA;AAAA,QACC,UAAU;AAAA,QACV,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACC,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AAEA,MAAI,eAAe,mBAAmB;AACpC,UAAM,sCAAQC,SAAAA,UAAA,EAAS,MAAM,YAAY,QAAQ,YAAY,kBAAkB,sBAAsB;AACrG,QAAI,aAAaJ,+BAAC,OAAA,EAAI,WAAWE,SAAG,yBAAyB,SAAS,GAAI,UAAA,KAAA,CAAK;AAC/E,WACEG,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAWH,MAAAA,GAAG,+DAA+D,SAAS;AAAA,QACrF,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAAF,2BAAAA,IAAC,iBAAA,EAAgB,OAAO,YAAY,SAAS,eAAe;AAAA,UAC5DA,2BAAAA,IAAC,OAAA,EAAI,WAAU,6BAA6B,UAAA,KAAA,CAAK;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGvD;AAEA,QAAM,aACJ,aAAa,SAAS,aAAa,UAAU,aAAa,SACtD,8WAMA;AAEN,QAAM,MAAM,OAAO,SAAY;AAC/B,QAAM,OAAO,aACXA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWE,MAAAA;AAAAA,QACT,8HACE;AAAA,QACF;AAAA,QACA,QAAQ;AAAA,MAAA;AAAA,MAEV,yBAAyB,EAAE,QAAQ,WAAA;AAAA,IAAW;AAAA,EAAA,IAE9C,YACFF,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWE,MAAAA;AAAAA,QACT;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MAAA;AAAA,MAEV,yBAAyB,EAAE,QAAQ,UAAA;AAAA,IAAU;AAAA,EAAA,IAG/CF,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWE,MAAAA;AAAAA,QACT;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MAAA;AAAA,MAGT,UAAA;AAAA,IAAA;AAAA,EAAA;AAIL,MAAI,KAAM,QAAO;AACjB,SACEG,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWH,MAAAA,GAAG,+DAA+D,SAAS;AAAA,MACrF,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAAF,2BAAAA,IAAC,iBAAA,EAAgB,OAAO,gBAAgB,QAAQ,SAAS,eAAe;AAAA,QACvE;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGP;AAEA,SAAS,gBAAgB,EAAE,OAAO,WAAkD;AAClF,SACEK,2BAAAA,KAAC,OAAA,EAAI,WAAU,8EACb,UAAA;AAAA,IAAAL,2BAAAA,IAAC,QAAA,EAAK,WAAU,6DAA6D,UAAA,OAAM;AAAA,IAClF;AAAA,EAAA,GACH;AAEJ;AAEA,MAAM,oBAAmC,OAAO,mBAAmB;AAEnE,SAAS,aAAa,QAAoD;AACxE,QAAM,UAAU,OAAO,KAAA;AACvB,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;"}
|
package/dist/data/CodeBlock.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type CodeBlockProps = {
|
|
|
9
9
|
className?: string | undefined;
|
|
10
10
|
/** Default expansion depth when rendering JSON as a tree. */
|
|
11
11
|
jsonDefaultOpenDepth?: number | undefined;
|
|
12
|
+
/** Display format for parsed JSON values. */
|
|
13
|
+
jsonFormat?: "json" | "yaml" | undefined;
|
|
12
14
|
/** Render only the code — no border, background, or language header. */
|
|
13
15
|
bare?: boolean | undefined;
|
|
14
16
|
/** Show a copy-to-clipboard button in the header (ignored when `bare`). */
|
|
@@ -38,5 +40,5 @@ export type CodeBlockProps = {
|
|
|
38
40
|
*/
|
|
39
41
|
inline?: boolean | undefined;
|
|
40
42
|
};
|
|
41
|
-
export declare function CodeBlock({ language: languageProp, source, highlightedHtml, className, jsonDefaultOpenDepth, bare, copyable, downloadable, downloadFilename, themeToggle, diff, diffView, inline, }: CodeBlockProps): import("react").JSX.Element;
|
|
43
|
+
export declare function CodeBlock({ language: languageProp, source, highlightedHtml, className, jsonDefaultOpenDepth, jsonFormat, bare, copyable, downloadable, downloadFilename, themeToggle, diff, diffView, inline, }: CodeBlockProps): import("react").JSX.Element;
|
|
42
44
|
//# sourceMappingURL=CodeBlock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlock.d.ts","sourceRoot":"","sources":["../../src/data/CodeBlock.tsx"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,6DAA6D;IAC7D,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,wEAAwE;IACxE,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,oFAAoF;IACpF,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC,qEAAqE;IACrE,IAAI,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAChF,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3C;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B,CAAC;AAOF,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EAAE,YAAY,EACtB,MAAW,EACX,eAAe,EACf,SAAS,EACT,oBAAwB,EACxB,IAAY,EACZ,QAAgB,EAChB,YAAoB,EACpB,gBAAgB,EAChB,WAAmB,EACnB,IAAI,EACJ,QAAQ,EACR,MAAc,GACf,EAAE,cAAc,+BAyJhB"}
|
|
1
|
+
{"version":3,"file":"CodeBlock.d.ts","sourceRoot":"","sources":["../../src/data/CodeBlock.tsx"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,6DAA6D;IAC7D,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACzC,wEAAwE;IACxE,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,oFAAoF;IACpF,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC,qEAAqE;IACrE,IAAI,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAChF,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3C;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B,CAAC;AAOF,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EAAE,YAAY,EACtB,MAAW,EACX,eAAe,EACf,SAAS,EACT,oBAAwB,EACxB,UAAmB,EACnB,IAAY,EACZ,QAAgB,EAChB,YAAoB,EACpB,gBAAgB,EAChB,WAAmB,EACnB,IAAI,EACJ,QAAQ,EACR,MAAc,GACf,EAAE,cAAc,+BAyJhB"}
|
package/dist/data/CodeBlock.js
CHANGED
|
@@ -15,6 +15,7 @@ function CodeBlock({
|
|
|
15
15
|
highlightedHtml,
|
|
16
16
|
className,
|
|
17
17
|
jsonDefaultOpenDepth = 2,
|
|
18
|
+
jsonFormat = "json",
|
|
18
19
|
bare = false,
|
|
19
20
|
copyable = false,
|
|
20
21
|
downloadable = false,
|
|
@@ -96,7 +97,7 @@ function CodeBlock({
|
|
|
96
97
|
);
|
|
97
98
|
}
|
|
98
99
|
if (parsedJson !== JSON_PARSE_FAILED) {
|
|
99
|
-
const tree = /* @__PURE__ */ jsx(JsonView, { data: parsedJson, defaultOpenDepth: jsonDefaultOpenDepth });
|
|
100
|
+
const tree = /* @__PURE__ */ jsx(JsonView, { data: parsedJson, format: jsonFormat, defaultOpenDepth: jsonDefaultOpenDepth });
|
|
100
101
|
if (bare) return /* @__PURE__ */ jsx("div", { className: cn("overflow-auto text-xs", className), children: tree });
|
|
101
102
|
return /* @__PURE__ */ jsxs(
|
|
102
103
|
"div",
|
|
@@ -104,7 +105,7 @@ function CodeBlock({
|
|
|
104
105
|
className: cn("overflow-hidden rounded-md border border-border bg-muted/40", className),
|
|
105
106
|
...overrideAttrs,
|
|
106
107
|
children: [
|
|
107
|
-
/* @__PURE__ */ jsx(CodeBlockHeader, { label:
|
|
108
|
+
/* @__PURE__ */ jsx(CodeBlockHeader, { label: jsonFormat, actions: headerActions }),
|
|
108
109
|
/* @__PURE__ */ jsx("div", { className: "overflow-auto p-3 text-xs", children: tree })
|
|
109
110
|
]
|
|
110
111
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlock.js","sources":["../../src/data/CodeBlock.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useResolvedTheme, type ResolvedTheme } from \"../hooks/use-theme\";\nimport { CodeBlockActions } from \"./code-block-actions\";\nimport { CodeDiff } from \"./CodeDiff\";\nimport { JsonView } from \"./JsonView\";\nimport { highlightCode } from \"./code-highlight\";\nimport { sanitizeHtml } from \"./html-utils\";\n\nexport type CodeBlockProps = {\n /** Language label and highlighter hint, e.g. `json`, `tsx`, or `bash`. */\n language?: string | undefined;\n /** Raw source code to render or highlight. */\n source?: string | undefined;\n /** Trusted server-rendered highlighted HTML, sanitized before rendering. */\n highlightedHtml?: string | undefined;\n /** Classes applied to the code block shell. */\n className?: string | undefined;\n /** Default expansion depth when rendering JSON as a tree. */\n jsonDefaultOpenDepth?: number | undefined;\n /** Render only the code — no border, background, or language header. */\n bare?: boolean | undefined;\n /** Show a copy-to-clipboard button in the header (ignored when `bare`). */\n copyable?: boolean | undefined;\n /** Show a download button in the header (ignored when `bare`). */\n downloadable?: boolean | undefined;\n /** Name of the downloaded file. Defaults to `snippet.<extension of language>`. */\n downloadFilename?: string | undefined;\n /** Show a per-block light/dark theme toggle in the header (ignored when `bare`). */\n themeToggle?: boolean | undefined;\n /** Render a language-aware diff instead of highlighting `source`. */\n diff?: { original: string; modified: string } | { unified: string } | undefined;\n /** Diff layout when `diff` is set. Defaults to `unified`. */\n diffView?: \"unified\" | \"split\" | undefined;\n /**\n * Render as a single truncated line for a table cell: no border, header, or\n * wrap, whitespace collapsed to one line, syntax-colored when highlighting\n * is available, with the collapsed source (up to 2,000 chars) in a title\n * tooltip. Only the leading 500 chars are rendered and highlighted — the\n * cell clips long before that. The full block is what a row-detail dialog\n * should show instead.\n */\n inline?: boolean | undefined;\n};\n\n// A captured SQL statement can run to tens of thousands of characters on one\n// line; highlighting all of it for a one-line cell cost ~220 ms a statement.\nconst INLINE_RENDER_CHARS = 500;\nconst INLINE_TITLE_CHARS = 2000;\n\nexport function CodeBlock({\n language: languageProp,\n source = \"\",\n highlightedHtml,\n className,\n jsonDefaultOpenDepth = 2,\n bare = false,\n copyable = false,\n downloadable = false,\n downloadFilename,\n themeToggle = false,\n diff,\n diffView,\n inline = false,\n}: CodeBlockProps) {\n const language = (languageProp ?? \"\").toLowerCase().replace(/^\\.+/, \"\");\n const chromaHtml = highlightedHtml ? sanitizeHtml(highlightedHtml) : \"\";\n const parsedJson = useMemo(\n () => (language === \"json\" ? tryParseJson(source) : JSON_PARSE_FAILED),\n [language, source],\n );\n // The block follows the global app theme by default; a per-block override\n // (set by the header toggle) wins over it and re-highlights just this block.\n const globalTheme = useResolvedTheme();\n const [themeOverride, setThemeOverride] = useState<ResolvedTheme | null>(null);\n const effectiveTheme = themeOverride ?? globalTheme;\n const [shikiHtml, setShikiHtml] = useState<string | null>(null);\n const wantsClientHighlight =\n parsedJson === JSON_PARSE_FAILED && !chromaHtml && !!language && !!source;\n const singleLine = useMemo(\n () => (inline ? source.replace(/\\s+/g, \" \").trim() : \"\"),\n [inline, source],\n );\n const highlightSource = inline ? singleLine.slice(0, INLINE_RENDER_CHARS) : source;\n\n useEffect(() => {\n if (!wantsClientHighlight) {\n setShikiHtml(null);\n return;\n }\n\n let cancelled = false;\n const theme = effectiveTheme === \"dark\" ? \"github-dark\" : \"github-light\";\n highlightCode(highlightSource, { lang: language, theme }).then((out) => {\n if (!cancelled) setShikiHtml(out);\n });\n\n return () => {\n cancelled = true;\n };\n }, [wantsClientHighlight, language, highlightSource, effectiveTheme]);\n\n const headerActions = (\n <CodeBlockActions\n source={source}\n language={languageProp}\n copyable={copyable}\n downloadable={downloadable}\n downloadFilename={downloadFilename}\n theme={effectiveTheme}\n {...(themeToggle\n ? { onToggleTheme: () => setThemeOverride(effectiveTheme === \"dark\" ? \"light\" : \"dark\") }\n : {})}\n />\n );\n // Re-scope theme tokens (shell background/border/label) to the override so an\n // overridden block stays visually coherent with its re-highlighted code.\n const overrideAttrs = themeOverride ? { \"data-theme\": themeOverride } : {};\n\n if (inline) {\n const html = chromaHtml || shikiHtml;\n const title =\n singleLine.length > INLINE_TITLE_CHARS\n ? `${singleLine.slice(0, INLINE_TITLE_CHARS)}…`\n : singleLine;\n return (\n <code\n className={cn(\n \"block max-w-full truncate font-mono text-xs text-foreground\",\n className,\n html && \"[&_pre]:inline [&_pre]:whitespace-pre [&_.chroma]:bg-transparent\",\n )}\n title={title}\n >\n {html ? <span dangerouslySetInnerHTML={{ __html: html }} /> : highlightSource}\n </code>\n );\n }\n\n if (diff) {\n return (\n <CodeDiff\n language={languageProp}\n view={diffView}\n bare={bare}\n className={className}\n {...diff}\n />\n );\n }\n\n if (parsedJson !== JSON_PARSE_FAILED) {\n const tree = <JsonView data={parsedJson} defaultOpenDepth={jsonDefaultOpenDepth} />;\n if (bare) return <div className={cn(\"overflow-auto text-xs\", className)}>{tree}</div>;\n return (\n <div\n className={cn(\"overflow-hidden rounded-md border border-border bg-muted/40\", className)}\n {...overrideAttrs}\n >\n <CodeBlockHeader label=\"json\" actions={headerActions} />\n <div className=\"overflow-auto p-3 text-xs\">{tree}</div>\n </div>\n );\n }\n\n const xmlClasses =\n language === \"xml\" || language === \"xslt\" || language === \"html\"\n ? \" [&_.chroma_.nt]:text-pink-700 [&_.chroma_.nt]:dark:text-pink-300 [&_.chroma_.nt]:font-semibold\" +\n \" [&_.chroma_.na]:text-amber-700 [&_.chroma_.na]:dark:text-amber-300\" +\n \" [&_.chroma_.s]:text-emerald-700 [&_.chroma_.s]:dark:text-emerald-300\" +\n \" [&_.chroma_.c]:text-slate-500 [&_.chroma_.c]:italic\" +\n \" [&_.chroma_.cp]:text-slate-500 [&_.chroma_.cp]:italic\" +\n \" [&_pre]:leading-relaxed\"\n : \"\";\n\n const pad = bare ? undefined : \"p-3\";\n const body = chromaHtml ? (\n <div\n className={cn(\n \"overflow-auto text-xs font-mono [&_.chroma]:bg-transparent [&_pre]:m-0 [&_pre]:whitespace-pre-wrap [&_pre]:bg-transparent\" +\n xmlClasses,\n pad,\n bare && className,\n )}\n dangerouslySetInnerHTML={{ __html: chromaHtml }}\n />\n ) : shikiHtml ? (\n <div\n className={cn(\n \"overflow-auto text-xs font-mono [&_pre]:m-0 [&_pre.shiki]:!bg-transparent [&_pre]:whitespace-pre-wrap\",\n pad,\n bare && className,\n )}\n dangerouslySetInnerHTML={{ __html: shikiHtml }}\n />\n ) : (\n <pre\n className={cn(\n \"overflow-auto whitespace-pre-wrap break-words text-xs font-mono text-foreground\",\n pad,\n bare && className,\n )}\n >\n {source}\n </pre>\n );\n\n if (bare) return body;\n return (\n <div\n className={cn(\"overflow-hidden rounded-md border border-border bg-muted/40\", className)}\n {...overrideAttrs}\n >\n <CodeBlockHeader label={languageProp || \"text\"} actions={headerActions} />\n {body}\n </div>\n );\n}\n\nfunction CodeBlockHeader({ label, actions }: { label: string; actions: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-2 border-b border-border px-3 py-1.5\">\n <span className=\"text-[11px] uppercase tracking-wide text-muted-foreground\">{label}</span>\n {actions}\n </div>\n );\n}\n\nconst JSON_PARSE_FAILED: unique symbol = Symbol(\"json-parse-failed\");\n\nfunction tryParseJson(source: string): unknown | typeof JSON_PARSE_FAILED {\n const trimmed = source.trim();\n if (!trimmed) return JSON_PARSE_FAILED;\n try {\n return JSON.parse(trimmed);\n } catch {\n return JSON_PARSE_FAILED;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CodeBlock.js","sources":["../../src/data/CodeBlock.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useResolvedTheme, type ResolvedTheme } from \"../hooks/use-theme\";\nimport { CodeBlockActions } from \"./code-block-actions\";\nimport { CodeDiff } from \"./CodeDiff\";\nimport { JsonView } from \"./JsonView\";\nimport { highlightCode } from \"./code-highlight\";\nimport { sanitizeHtml } from \"./html-utils\";\n\nexport type CodeBlockProps = {\n /** Language label and highlighter hint, e.g. `json`, `tsx`, or `bash`. */\n language?: string | undefined;\n /** Raw source code to render or highlight. */\n source?: string | undefined;\n /** Trusted server-rendered highlighted HTML, sanitized before rendering. */\n highlightedHtml?: string | undefined;\n /** Classes applied to the code block shell. */\n className?: string | undefined;\n /** Default expansion depth when rendering JSON as a tree. */\n jsonDefaultOpenDepth?: number | undefined;\n /** Display format for parsed JSON values. */\n jsonFormat?: \"json\" | \"yaml\" | undefined;\n /** Render only the code — no border, background, or language header. */\n bare?: boolean | undefined;\n /** Show a copy-to-clipboard button in the header (ignored when `bare`). */\n copyable?: boolean | undefined;\n /** Show a download button in the header (ignored when `bare`). */\n downloadable?: boolean | undefined;\n /** Name of the downloaded file. Defaults to `snippet.<extension of language>`. */\n downloadFilename?: string | undefined;\n /** Show a per-block light/dark theme toggle in the header (ignored when `bare`). */\n themeToggle?: boolean | undefined;\n /** Render a language-aware diff instead of highlighting `source`. */\n diff?: { original: string; modified: string } | { unified: string } | undefined;\n /** Diff layout when `diff` is set. Defaults to `unified`. */\n diffView?: \"unified\" | \"split\" | undefined;\n /**\n * Render as a single truncated line for a table cell: no border, header, or\n * wrap, whitespace collapsed to one line, syntax-colored when highlighting\n * is available, with the collapsed source (up to 2,000 chars) in a title\n * tooltip. Only the leading 500 chars are rendered and highlighted — the\n * cell clips long before that. The full block is what a row-detail dialog\n * should show instead.\n */\n inline?: boolean | undefined;\n};\n\n// A captured SQL statement can run to tens of thousands of characters on one\n// line; highlighting all of it for a one-line cell cost ~220 ms a statement.\nconst INLINE_RENDER_CHARS = 500;\nconst INLINE_TITLE_CHARS = 2000;\n\nexport function CodeBlock({\n language: languageProp,\n source = \"\",\n highlightedHtml,\n className,\n jsonDefaultOpenDepth = 2,\n jsonFormat = \"json\",\n bare = false,\n copyable = false,\n downloadable = false,\n downloadFilename,\n themeToggle = false,\n diff,\n diffView,\n inline = false,\n}: CodeBlockProps) {\n const language = (languageProp ?? \"\").toLowerCase().replace(/^\\.+/, \"\");\n const chromaHtml = highlightedHtml ? sanitizeHtml(highlightedHtml) : \"\";\n const parsedJson = useMemo(\n () => (language === \"json\" ? tryParseJson(source) : JSON_PARSE_FAILED),\n [language, source],\n );\n // The block follows the global app theme by default; a per-block override\n // (set by the header toggle) wins over it and re-highlights just this block.\n const globalTheme = useResolvedTheme();\n const [themeOverride, setThemeOverride] = useState<ResolvedTheme | null>(null);\n const effectiveTheme = themeOverride ?? globalTheme;\n const [shikiHtml, setShikiHtml] = useState<string | null>(null);\n const wantsClientHighlight =\n parsedJson === JSON_PARSE_FAILED && !chromaHtml && !!language && !!source;\n const singleLine = useMemo(\n () => (inline ? source.replace(/\\s+/g, \" \").trim() : \"\"),\n [inline, source],\n );\n const highlightSource = inline ? singleLine.slice(0, INLINE_RENDER_CHARS) : source;\n\n useEffect(() => {\n if (!wantsClientHighlight) {\n setShikiHtml(null);\n return;\n }\n\n let cancelled = false;\n const theme = effectiveTheme === \"dark\" ? \"github-dark\" : \"github-light\";\n highlightCode(highlightSource, { lang: language, theme }).then((out) => {\n if (!cancelled) setShikiHtml(out);\n });\n\n return () => {\n cancelled = true;\n };\n }, [wantsClientHighlight, language, highlightSource, effectiveTheme]);\n\n const headerActions = (\n <CodeBlockActions\n source={source}\n language={languageProp}\n copyable={copyable}\n downloadable={downloadable}\n downloadFilename={downloadFilename}\n theme={effectiveTheme}\n {...(themeToggle\n ? { onToggleTheme: () => setThemeOverride(effectiveTheme === \"dark\" ? \"light\" : \"dark\") }\n : {})}\n />\n );\n // Re-scope theme tokens (shell background/border/label) to the override so an\n // overridden block stays visually coherent with its re-highlighted code.\n const overrideAttrs = themeOverride ? { \"data-theme\": themeOverride } : {};\n\n if (inline) {\n const html = chromaHtml || shikiHtml;\n const title =\n singleLine.length > INLINE_TITLE_CHARS\n ? `${singleLine.slice(0, INLINE_TITLE_CHARS)}…`\n : singleLine;\n return (\n <code\n className={cn(\n \"block max-w-full truncate font-mono text-xs text-foreground\",\n className,\n html && \"[&_pre]:inline [&_pre]:whitespace-pre [&_.chroma]:bg-transparent\",\n )}\n title={title}\n >\n {html ? <span dangerouslySetInnerHTML={{ __html: html }} /> : highlightSource}\n </code>\n );\n }\n\n if (diff) {\n return (\n <CodeDiff\n language={languageProp}\n view={diffView}\n bare={bare}\n className={className}\n {...diff}\n />\n );\n }\n\n if (parsedJson !== JSON_PARSE_FAILED) {\n const tree = <JsonView data={parsedJson} format={jsonFormat} defaultOpenDepth={jsonDefaultOpenDepth} />;\n if (bare) return <div className={cn(\"overflow-auto text-xs\", className)}>{tree}</div>;\n return (\n <div\n className={cn(\"overflow-hidden rounded-md border border-border bg-muted/40\", className)}\n {...overrideAttrs}\n >\n <CodeBlockHeader label={jsonFormat} actions={headerActions} />\n <div className=\"overflow-auto p-3 text-xs\">{tree}</div>\n </div>\n );\n }\n\n const xmlClasses =\n language === \"xml\" || language === \"xslt\" || language === \"html\"\n ? \" [&_.chroma_.nt]:text-pink-700 [&_.chroma_.nt]:dark:text-pink-300 [&_.chroma_.nt]:font-semibold\" +\n \" [&_.chroma_.na]:text-amber-700 [&_.chroma_.na]:dark:text-amber-300\" +\n \" [&_.chroma_.s]:text-emerald-700 [&_.chroma_.s]:dark:text-emerald-300\" +\n \" [&_.chroma_.c]:text-slate-500 [&_.chroma_.c]:italic\" +\n \" [&_.chroma_.cp]:text-slate-500 [&_.chroma_.cp]:italic\" +\n \" [&_pre]:leading-relaxed\"\n : \"\";\n\n const pad = bare ? undefined : \"p-3\";\n const body = chromaHtml ? (\n <div\n className={cn(\n \"overflow-auto text-xs font-mono [&_.chroma]:bg-transparent [&_pre]:m-0 [&_pre]:whitespace-pre-wrap [&_pre]:bg-transparent\" +\n xmlClasses,\n pad,\n bare && className,\n )}\n dangerouslySetInnerHTML={{ __html: chromaHtml }}\n />\n ) : shikiHtml ? (\n <div\n className={cn(\n \"overflow-auto text-xs font-mono [&_pre]:m-0 [&_pre.shiki]:!bg-transparent [&_pre]:whitespace-pre-wrap\",\n pad,\n bare && className,\n )}\n dangerouslySetInnerHTML={{ __html: shikiHtml }}\n />\n ) : (\n <pre\n className={cn(\n \"overflow-auto whitespace-pre-wrap break-words text-xs font-mono text-foreground\",\n pad,\n bare && className,\n )}\n >\n {source}\n </pre>\n );\n\n if (bare) return body;\n return (\n <div\n className={cn(\"overflow-hidden rounded-md border border-border bg-muted/40\", className)}\n {...overrideAttrs}\n >\n <CodeBlockHeader label={languageProp || \"text\"} actions={headerActions} />\n {body}\n </div>\n );\n}\n\nfunction CodeBlockHeader({ label, actions }: { label: string; actions: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-2 border-b border-border px-3 py-1.5\">\n <span className=\"text-[11px] uppercase tracking-wide text-muted-foreground\">{label}</span>\n {actions}\n </div>\n );\n}\n\nconst JSON_PARSE_FAILED: unique symbol = Symbol(\"json-parse-failed\");\n\nfunction tryParseJson(source: string): unknown | typeof JSON_PARSE_FAILED {\n const trimmed = source.trim();\n if (!trimmed) return JSON_PARSE_FAILED;\n try {\n return JSON.parse(trimmed);\n } catch {\n return JSON_PARSE_FAILED;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -18,6 +18,11 @@ const UiFullscreen = require("../icons/components/UiFullscreen.cjs");
|
|
|
18
18
|
const UiHome = require("../icons/components/UiHome.cjs");
|
|
19
19
|
const DEFAULT_NODE_WIDTH = 168;
|
|
20
20
|
const DEFAULT_NODE_HEIGHT = 60;
|
|
21
|
+
const DEFAULT_COMPACT_NODE_HEIGHT = 24;
|
|
22
|
+
function compactHeights(nodes, layout, compactNodeHeight) {
|
|
23
|
+
if (layout !== "columns") return {};
|
|
24
|
+
return Object.fromEntries(nodes.filter((node) => node.size === "compact").map((node) => [node.id, compactNodeHeight]));
|
|
25
|
+
}
|
|
21
26
|
function assertEdgesReferenceKnownNodes(nodes, edges) {
|
|
22
27
|
const ids = new Set(nodes.map((node) => node.id));
|
|
23
28
|
for (const edge of edges) {
|
|
@@ -35,7 +40,7 @@ function requireLevel(node) {
|
|
|
35
40
|
}
|
|
36
41
|
return node.level;
|
|
37
42
|
}
|
|
38
|
-
function layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, columnGap, rowGap }) {
|
|
43
|
+
function layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap }) {
|
|
39
44
|
switch (layout) {
|
|
40
45
|
case "ring":
|
|
41
46
|
return { ...graphLayout.ringLayout(nodes, { nodeWidth, nodeHeight }), groups: [] };
|
|
@@ -44,12 +49,14 @@ function layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, columnGap, r
|
|
|
44
49
|
nodes.map((node) => ({
|
|
45
50
|
id: node.id,
|
|
46
51
|
level: requireLevel(node),
|
|
47
|
-
...node.group !== void 0 ? { group: node.group } : {}
|
|
52
|
+
...node.group !== void 0 ? { group: node.group } : {},
|
|
53
|
+
...node.size === "compact" ? { compact: true } : {}
|
|
48
54
|
})),
|
|
49
55
|
edges,
|
|
50
56
|
{
|
|
51
57
|
nodeWidth,
|
|
52
58
|
nodeHeight,
|
|
59
|
+
compactNodeHeight,
|
|
53
60
|
...columnGap !== void 0 ? { columnGap } : {},
|
|
54
61
|
...rowGap !== void 0 ? { rowGap } : {}
|
|
55
62
|
}
|
|
@@ -94,9 +101,11 @@ function GraphDiagram({
|
|
|
94
101
|
groups,
|
|
95
102
|
nodeWidth = DEFAULT_NODE_WIDTH,
|
|
96
103
|
nodeHeight = DEFAULT_NODE_HEIGHT,
|
|
104
|
+
compactNodeHeight = DEFAULT_COMPACT_NODE_HEIGHT,
|
|
97
105
|
columnGap,
|
|
98
106
|
rowGap,
|
|
99
107
|
zoomable = false,
|
|
108
|
+
wheelZoom = "modifier",
|
|
100
109
|
fitMinScale,
|
|
101
110
|
focusId,
|
|
102
111
|
maxHeight,
|
|
@@ -106,15 +115,17 @@ function GraphDiagram({
|
|
|
106
115
|
assertEdgesReferenceKnownNodes(nodes, edges);
|
|
107
116
|
const markerPrefix = `graph-diagram-${react.useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
108
117
|
const { width, height, positions, groups: groupBoxes } = react.useMemo(
|
|
109
|
-
() => layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, columnGap, rowGap }),
|
|
110
|
-
[layout, nodes, edges, nodeWidth, nodeHeight, columnGap, rowGap]
|
|
118
|
+
() => layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap }),
|
|
119
|
+
[layout, nodes, edges, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap]
|
|
111
120
|
);
|
|
121
|
+
const nodeHeights = react.useMemo(() => compactHeights(nodes, layout, compactNodeHeight), [nodes, layout, compactNodeHeight]);
|
|
112
122
|
const routes = react.useMemo(
|
|
113
|
-
() => graphEdgeRouting.routeEdges(edges, positions, { nodeWidth, nodeHeight }, { anchor: layout === "columns" ? "side" : "center" }),
|
|
114
|
-
[edges, positions, nodeWidth, nodeHeight, layout]
|
|
123
|
+
() => graphEdgeRouting.routeEdges(edges, positions, { nodeWidth, nodeHeight, nodeHeights }, { anchor: layout === "columns" ? "side" : "center" }),
|
|
124
|
+
[edges, positions, nodeWidth, nodeHeight, nodeHeights, layout]
|
|
115
125
|
);
|
|
116
126
|
const panZoom = usePanZoom.usePanZoom({
|
|
117
127
|
enabled: zoomable,
|
|
128
|
+
wheel: wheelZoom,
|
|
118
129
|
contentWidth: width,
|
|
119
130
|
contentHeight: height,
|
|
120
131
|
opening: openingFit(positions, fitMinScale, focusId)
|
|
@@ -169,6 +180,7 @@ function GraphDiagram({
|
|
|
169
180
|
place,
|
|
170
181
|
nodeWidth,
|
|
171
182
|
nodeHeight,
|
|
183
|
+
nodeHeights,
|
|
172
184
|
selectedId,
|
|
173
185
|
onNodeSelect,
|
|
174
186
|
onNodeExpand,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphDiagram.cjs","sources":["../../src/data/GraphDiagram.tsx"],"sourcesContent":["import { useId, useMemo, type CSSProperties } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { IconButton } from \"../components/IconButton\";\nimport { UiFullscreen, UiHome, UiZoomIn, UiZoomOut } from \"../icons\";\nimport { columnsLayout, type ColumnsLayoutResult } from \"./graph-columns-layout\";\nimport { ringLayout, routeEdges, type GraphLayoutPosition } from \"./graph-layout\";\nimport { GraphDiagramEdgeLabels, GraphDiagramEdgePaths } from \"./GraphDiagramEdges\";\nimport { GraphDiagramGroupBoxes, GraphDiagramNodes } from \"./GraphDiagramNodes\";\nimport {\n requireEntry,\n type GraphDiagramEdge,\n type GraphDiagramEdgeFocus,\n type GraphDiagramGroup,\n type GraphDiagramLayout,\n type GraphDiagramNode,\n type GraphPlacer,\n} from \"./graph-diagram-model\";\nimport { useEdgeFocus } from \"./use-edge-focus\";\nimport { usePanZoom, type FitOptions, type PanZoom } from \"./use-pan-zoom\";\n\nexport type {\n GraphDiagramEdge,\n GraphDiagramEdgeFocus,\n GraphDiagramGroup,\n GraphDiagramLayout,\n GraphDiagramNode,\n} from \"./graph-diagram-model\";\n\n// GraphDiagram renders a generic, type-agnostic node/edge diagram. It knows\n// nothing about the domain: a producer maps its data into\n// GraphDiagramNode/GraphDiagramEdge and gets layout, edge routing and\n// selection behaviour.\n//\n// \"ring\" suits a small cycle of resources and processes — relationships with\n// back edges or reciprocal pairs. \"columns\" suits a directed graph with a\n// natural depth (a call graph, a dependency chain): nodes sit in the column of\n// their `level`, grouped and boxed by `group`.\n//\n// The \"columns\" layout, and any zoomable diagram, is drawn at its natural\n// pixel size so node boxes, edges and group boxes share one coordinate system\n// at every container width and zoom level. A non-zoomable \"ring\" keeps scaling\n// its node positions to the container width.\n\nexport interface GraphDiagramProps {\n nodes: GraphDiagramNode[];\n edges: GraphDiagramEdge[];\n /** Defaults to \"ring\". \"columns\" needs a `level` on every node. */\n layout?: GraphDiagramLayout;\n selectedId?: string;\n onNodeSelect?: (id: string) => void;\n /** Called by a node's `+N` control (see `expandCount`); does not select the node. */\n onNodeExpand?: (id: string) => void;\n selectedEdgeId?: string;\n /** Makes edges, and their label pills, selectable. */\n onEdgeSelect?: (id: string) => void;\n /**\n * Whether an edge shows its pill only while in focus, and fades while another is. Defaults to\n * \"auto\": a diagram with more than 32 edges is focused, a smaller one shows everything.\n */\n edgeFocus?: GraphDiagramEdgeFocus;\n /** \"columns\" layout: captions for the group rectangles. A group without an entry shows its id. */\n groups?: GraphDiagramGroup[];\n nodeWidth?: number;\n nodeHeight?: number;\n /** \"columns\" layout: horizontal gap between columns in px. Leave room for edge label pills. */\n columnGap?: number;\n /** \"columns\" layout: vertical gap between stacked nodes in px. */\n rowGap?: number;\n /** Ctrl/meta + wheel (or pinch) zooms, dragging the background pans, and zoom controls appear. */\n zoomable?: boolean;\n /**\n * Zoomable: the smallest scale the diagram opens at, so its text stays readable. A diagram too\n * large for it opens overflowing, centred on `focusId`, and is panned to reach the rest. `Fit to\n * view` still shows everything, and `Reset view` returns here. Unset, the diagram opens whole.\n */\n fitMinScale?: number;\n /** Zoomable: the node an overflowing diagram opens centred on. Defaults to the diagram's centre. */\n focusId?: string;\n maxHeight?: number | string;\n ariaLabel: string;\n className?: string;\n}\n\nconst DEFAULT_NODE_WIDTH = 168;\nconst DEFAULT_NODE_HEIGHT = 60;\n\ninterface LayoutOptions {\n layout: GraphDiagramLayout;\n nodeWidth: number;\n nodeHeight: number;\n columnGap: number | undefined;\n rowGap: number | undefined;\n}\n\nfunction assertEdgesReferenceKnownNodes(nodes: GraphDiagramNode[], edges: GraphDiagramEdge[]): void {\n const ids = new Set(nodes.map((node) => node.id));\n for (const edge of edges) {\n if (!ids.has(edge.from)) {\n throw new Error(`GraphDiagram: edge \"${edge.id}\" references unknown node \"${edge.from}\"`);\n }\n if (!ids.has(edge.to)) {\n throw new Error(`GraphDiagram: edge \"${edge.id}\" references unknown node \"${edge.to}\"`);\n }\n }\n}\n\nfunction requireLevel(node: GraphDiagramNode): number {\n if (node.level === undefined || !Number.isFinite(node.level)) {\n throw new Error(`GraphDiagram: node \"${node.id}\" needs a finite level for the \"columns\" layout`);\n }\n return node.level;\n}\n\nfunction layoutNodes(\n nodes: GraphDiagramNode[],\n edges: GraphDiagramEdge[],\n { layout, nodeWidth, nodeHeight, columnGap, rowGap }: LayoutOptions,\n): ColumnsLayoutResult {\n switch (layout) {\n case \"ring\":\n return { ...ringLayout(nodes, { nodeWidth, nodeHeight }), groups: [] };\n case \"columns\":\n return columnsLayout(\n nodes.map((node) => ({\n id: node.id,\n level: requireLevel(node),\n ...(node.group !== undefined ? { group: node.group } : {}),\n })),\n edges,\n {\n nodeWidth,\n nodeHeight,\n ...(columnGap !== undefined ? { columnGap } : {}),\n ...(rowGap !== undefined ? { rowGap } : {}),\n },\n );\n default:\n throw new Error(`GraphDiagram: unknown layout \"${layout satisfies never}\"`);\n }\n}\n\nfunction toPercent(value: number, extent: number): string {\n return `${(value / extent) * 100}%`;\n}\n\nfunction stageStyle(size: { width: number; height: number }, natural: boolean, panZoom: PanZoom | undefined): CSSProperties {\n if (!natural) return { aspectRatio: `${size.width} / ${size.height}` };\n if (!panZoom) return size;\n const { x, y, scale } = panZoom.transform;\n return { ...size, transform: `translate(${x}px, ${y}px) scale(${scale})`, transformOrigin: \"0 0\" };\n}\n\n/** `resettable`: the diagram opens at another view than the whole of it, so there is one to return to. */\nfunction ZoomControls({ panZoom, resettable }: { panZoom: PanZoom; resettable: boolean }) {\n return (\n <div className=\"absolute right-2 top-2 flex cursor-default gap-0.5 rounded-md border border-border bg-background p-0.5 shadow-sm\">\n <IconButton icon={UiZoomOut} label=\"Zoom out\" className=\"size-6\" onClick={panZoom.zoomOut} />\n <IconButton icon={UiZoomIn} label=\"Zoom in\" className=\"size-6\" onClick={panZoom.zoomIn} />\n <IconButton icon={UiFullscreen} label=\"Fit to view\" className=\"size-6\" onClick={panZoom.fit} />\n {resettable && <IconButton icon={UiHome} label=\"Reset view\" className=\"size-6\" onClick={panZoom.reset} />}\n </div>\n );\n}\n\n/** How a zoomable diagram opens: no smaller than `fitMinScale`, centred on the `focusId` node. */\nfunction openingFit(\n positions: Record<string, GraphLayoutPosition>,\n fitMinScale: number | undefined,\n focusId: string | undefined,\n): FitOptions {\n return {\n ...(fitMinScale !== undefined ? { minScale: fitMinScale } : {}),\n ...(focusId !== undefined ? { focus: requireEntry(positions, focusId, \"layout position\") } : {}),\n };\n}\n\nexport function GraphDiagram({\n nodes,\n edges,\n layout = \"ring\",\n selectedId,\n onNodeSelect,\n onNodeExpand,\n selectedEdgeId,\n onEdgeSelect,\n edgeFocus = \"auto\",\n groups,\n nodeWidth = DEFAULT_NODE_WIDTH,\n nodeHeight = DEFAULT_NODE_HEIGHT,\n columnGap,\n rowGap,\n zoomable = false,\n fitMinScale,\n focusId,\n maxHeight,\n ariaLabel,\n className,\n}: GraphDiagramProps) {\n assertEdgesReferenceKnownNodes(nodes, edges);\n const markerPrefix = `graph-diagram-${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n\n const { width, height, positions, groups: groupBoxes } = useMemo(\n () => layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, columnGap, rowGap }),\n [layout, nodes, edges, nodeWidth, nodeHeight, columnGap, rowGap],\n );\n const routes = useMemo(\n () =>\n routeEdges(edges, positions, { nodeWidth, nodeHeight }, { anchor: layout === \"columns\" ? \"side\" : \"center\" }),\n [edges, positions, nodeWidth, nodeHeight, layout],\n );\n\n const panZoom = usePanZoom({\n enabled: zoomable,\n contentWidth: width,\n contentHeight: height,\n opening: openingFit(positions, fitMinScale, focusId),\n });\n const natural = layout === \"columns\" || zoomable;\n const place: GraphPlacer = natural\n ? ({ x, y }) => ({ left: x, top: y })\n : ({ x, y }) => ({ left: toPercent(x, width), top: toPercent(y, height) });\n const focus = useEdgeFocus({ mode: edgeFocus, edges, selectedId, selectedEdgeId });\n const edgeSelection = { selectedEdgeId, onEdgeSelect, focus: focus.state };\n\n return (\n <div\n ref={panZoom.viewportRef}\n className={cn(\n \"w-full\",\n zoomable ? \"relative touch-none overflow-hidden\" : \"overflow-auto\",\n zoomable && (panZoom.panning ? \"cursor-grabbing select-none\" : \"cursor-grab\"),\n className,\n )}\n style={maxHeight != null ? { maxHeight } : undefined}\n {...panZoom.handlers}\n >\n <div\n ref={panZoom.contentRef}\n data-graph-stage=\"\"\n className={cn(\"relative\", natural ? !zoomable && \"mx-auto\" : \"w-full\")}\n style={stageStyle({ width, height }, natural, zoomable ? panZoom : undefined)}\n >\n <GraphDiagramGroupBoxes boxes={groupBoxes} groups={groups} />\n <GraphDiagramEdgePaths\n edges={edges}\n nodes={nodes}\n routes={routes}\n width={width}\n height={height}\n ariaLabel={ariaLabel}\n markerPrefix={markerPrefix}\n onEdgeHover={focus.onEdgeHover}\n {...edgeSelection}\n />\n <div className=\"pointer-events-none absolute inset-0\">\n <GraphDiagramEdgeLabels edges={edges} routes={routes} place={place} spread={natural} {...edgeSelection} />\n <GraphDiagramNodes\n nodes={nodes}\n positions={positions}\n place={place}\n nodeWidth={nodeWidth}\n nodeHeight={nodeHeight}\n selectedId={selectedId}\n onNodeSelect={onNodeSelect}\n onNodeExpand={onNodeExpand}\n onNodeHover={focus.onNodeHover}\n />\n </div>\n </div>\n {zoomable && <ZoomControls panZoom={panZoom} resettable={fitMinScale !== undefined} />}\n </div>\n );\n}\n"],"names":["ringLayout","columnsLayout","jsxs","jsx","IconButton","UiZoomOut","UiZoomIn","UiFullscreen","UiHome","requireEntry","useId","useMemo","routeEdges","usePanZoom","useEdgeFocus","cn","GraphDiagramGroupBoxes","GraphDiagramEdgePaths","GraphDiagramEdgeLabels","GraphDiagramNodes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GraphDiagram.cjs","sources":["../../src/data/GraphDiagram.tsx"],"sourcesContent":["import { useId, useMemo, type CSSProperties } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { IconButton } from \"../components/IconButton\";\nimport { UiFullscreen, UiHome, UiZoomIn, UiZoomOut } from \"../icons\";\nimport { columnsLayout, type ColumnsLayoutResult } from \"./graph-columns-layout\";\nimport { ringLayout, routeEdges, type GraphLayoutPosition } from \"./graph-layout\";\nimport { GraphDiagramEdgeLabels, GraphDiagramEdgePaths } from \"./GraphDiagramEdges\";\nimport { GraphDiagramGroupBoxes, GraphDiagramNodes } from \"./GraphDiagramNodes\";\nimport {\n requireEntry,\n type GraphDiagramEdge,\n type GraphDiagramEdgeFocus,\n type GraphDiagramGroup,\n type GraphDiagramLayout,\n type GraphDiagramNode,\n type GraphPlacer,\n} from \"./graph-diagram-model\";\nimport { useEdgeFocus } from \"./use-edge-focus\";\nimport { usePanZoom, type FitOptions, type PanZoom, type WheelMode } from \"./use-pan-zoom\";\n\nexport type {\n GraphDiagramEdge,\n GraphDiagramEdgeFocus,\n GraphDiagramGroup,\n GraphDiagramLayout,\n GraphDiagramNode,\n} from \"./graph-diagram-model\";\n\n// GraphDiagram renders a generic, type-agnostic node/edge diagram. It knows\n// nothing about the domain: a producer maps its data into\n// GraphDiagramNode/GraphDiagramEdge and gets layout, edge routing and\n// selection behaviour.\n//\n// \"ring\" suits a small cycle of resources and processes — relationships with\n// back edges or reciprocal pairs. \"columns\" suits a directed graph with a\n// natural depth (a call graph, a dependency chain): nodes sit in the column of\n// their `level`, grouped and boxed by `group`.\n//\n// The \"columns\" layout, and any zoomable diagram, is drawn at its natural\n// pixel size so node boxes, edges and group boxes share one coordinate system\n// at every container width and zoom level. A non-zoomable \"ring\" keeps scaling\n// its node positions to the container width.\n\nexport interface GraphDiagramProps {\n nodes: GraphDiagramNode[];\n edges: GraphDiagramEdge[];\n /** Defaults to \"ring\". \"columns\" needs a `level` on every node. */\n layout?: GraphDiagramLayout;\n selectedId?: string;\n onNodeSelect?: (id: string) => void;\n /** Called by a node's `+N` control (see `expandCount`); does not select the node. */\n onNodeExpand?: (id: string) => void;\n selectedEdgeId?: string;\n /** Makes edges, and their label pills, selectable. */\n onEdgeSelect?: (id: string) => void;\n /**\n * Whether an edge shows its pill only while in focus, and fades while another is. Defaults to\n * \"auto\": a diagram with more than 32 edges is focused, a smaller one shows everything.\n */\n edgeFocus?: GraphDiagramEdgeFocus;\n /** \"columns\" layout: captions for the group rectangles. A group without an entry shows its id. */\n groups?: GraphDiagramGroup[];\n nodeWidth?: number;\n nodeHeight?: number;\n /**\n * \"columns\" layout: the height of a node with `size: \"compact\"`, drawn on one line. A group of only\n * compact nodes stacks them closer and pads them tighter. Defaults to 24.\n */\n compactNodeHeight?: number;\n /** \"columns\" layout: horizontal gap between columns in px. Leave room for edge label pills. */\n columnGap?: number;\n /** \"columns\" layout: vertical gap between stacked nodes in px. */\n rowGap?: number;\n /** Ctrl/meta + wheel (or pinch) zooms, dragging the background pans, and zoom controls appear. */\n zoomable?: boolean;\n /** Zoomable: \"zoom\" makes a plain mouse wheel zoom too, for a diagram that fills its pane. Defaults to \"modifier\". */\n wheelZoom?: WheelMode;\n /**\n * Zoomable: the smallest scale the diagram opens at, so its text stays readable. A diagram too\n * large for it opens overflowing, centred on `focusId`, and is panned to reach the rest. `Fit to\n * view` still shows everything, and `Reset view` returns here. Unset, the diagram opens whole.\n */\n fitMinScale?: number;\n /** Zoomable: the node an overflowing diagram opens centred on. Defaults to the diagram's centre. */\n focusId?: string;\n maxHeight?: number | string;\n ariaLabel: string;\n className?: string;\n}\n\nconst DEFAULT_NODE_WIDTH = 168;\nconst DEFAULT_NODE_HEIGHT = 60;\nconst DEFAULT_COMPACT_NODE_HEIGHT = 24;\n\ninterface LayoutOptions {\n layout: GraphDiagramLayout;\n nodeWidth: number;\n nodeHeight: number;\n compactNodeHeight: number;\n columnGap: number | undefined;\n rowGap: number | undefined;\n}\n\n/** The heights of the nodes not drawn at the node height: the compact ones, in the \"columns\" layout. */\nfunction compactHeights(nodes: GraphDiagramNode[], layout: GraphDiagramLayout, compactNodeHeight: number): Record<string, number> {\n if (layout !== \"columns\") return {};\n return Object.fromEntries(nodes.filter((node) => node.size === \"compact\").map((node) => [node.id, compactNodeHeight]));\n}\n\nfunction assertEdgesReferenceKnownNodes(nodes: GraphDiagramNode[], edges: GraphDiagramEdge[]): void {\n const ids = new Set(nodes.map((node) => node.id));\n for (const edge of edges) {\n if (!ids.has(edge.from)) {\n throw new Error(`GraphDiagram: edge \"${edge.id}\" references unknown node \"${edge.from}\"`);\n }\n if (!ids.has(edge.to)) {\n throw new Error(`GraphDiagram: edge \"${edge.id}\" references unknown node \"${edge.to}\"`);\n }\n }\n}\n\nfunction requireLevel(node: GraphDiagramNode): number {\n if (node.level === undefined || !Number.isFinite(node.level)) {\n throw new Error(`GraphDiagram: node \"${node.id}\" needs a finite level for the \"columns\" layout`);\n }\n return node.level;\n}\n\nfunction layoutNodes(\n nodes: GraphDiagramNode[],\n edges: GraphDiagramEdge[],\n { layout, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap }: LayoutOptions,\n): ColumnsLayoutResult {\n switch (layout) {\n case \"ring\":\n return { ...ringLayout(nodes, { nodeWidth, nodeHeight }), groups: [] };\n case \"columns\":\n return columnsLayout(\n nodes.map((node) => ({\n id: node.id,\n level: requireLevel(node),\n ...(node.group !== undefined ? { group: node.group } : {}),\n ...(node.size === \"compact\" ? { compact: true } : {}),\n })),\n edges,\n {\n nodeWidth,\n nodeHeight,\n compactNodeHeight,\n ...(columnGap !== undefined ? { columnGap } : {}),\n ...(rowGap !== undefined ? { rowGap } : {}),\n },\n );\n default:\n throw new Error(`GraphDiagram: unknown layout \"${layout satisfies never}\"`);\n }\n}\n\nfunction toPercent(value: number, extent: number): string {\n return `${(value / extent) * 100}%`;\n}\n\nfunction stageStyle(size: { width: number; height: number }, natural: boolean, panZoom: PanZoom | undefined): CSSProperties {\n if (!natural) return { aspectRatio: `${size.width} / ${size.height}` };\n if (!panZoom) return size;\n const { x, y, scale } = panZoom.transform;\n return { ...size, transform: `translate(${x}px, ${y}px) scale(${scale})`, transformOrigin: \"0 0\" };\n}\n\n/** `resettable`: the diagram opens at another view than the whole of it, so there is one to return to. */\nfunction ZoomControls({ panZoom, resettable }: { panZoom: PanZoom; resettable: boolean }) {\n return (\n <div className=\"absolute right-2 top-2 flex cursor-default gap-0.5 rounded-md border border-border bg-background p-0.5 shadow-sm\">\n <IconButton icon={UiZoomOut} label=\"Zoom out\" className=\"size-6\" onClick={panZoom.zoomOut} />\n <IconButton icon={UiZoomIn} label=\"Zoom in\" className=\"size-6\" onClick={panZoom.zoomIn} />\n <IconButton icon={UiFullscreen} label=\"Fit to view\" className=\"size-6\" onClick={panZoom.fit} />\n {resettable && <IconButton icon={UiHome} label=\"Reset view\" className=\"size-6\" onClick={panZoom.reset} />}\n </div>\n );\n}\n\n/** How a zoomable diagram opens: no smaller than `fitMinScale`, centred on the `focusId` node. */\nfunction openingFit(\n positions: Record<string, GraphLayoutPosition>,\n fitMinScale: number | undefined,\n focusId: string | undefined,\n): FitOptions {\n return {\n ...(fitMinScale !== undefined ? { minScale: fitMinScale } : {}),\n ...(focusId !== undefined ? { focus: requireEntry(positions, focusId, \"layout position\") } : {}),\n };\n}\n\nexport function GraphDiagram({\n nodes,\n edges,\n layout = \"ring\",\n selectedId,\n onNodeSelect,\n onNodeExpand,\n selectedEdgeId,\n onEdgeSelect,\n edgeFocus = \"auto\",\n groups,\n nodeWidth = DEFAULT_NODE_WIDTH,\n nodeHeight = DEFAULT_NODE_HEIGHT,\n compactNodeHeight = DEFAULT_COMPACT_NODE_HEIGHT,\n columnGap,\n rowGap,\n zoomable = false,\n wheelZoom = \"modifier\",\n fitMinScale,\n focusId,\n maxHeight,\n ariaLabel,\n className,\n}: GraphDiagramProps) {\n assertEdgesReferenceKnownNodes(nodes, edges);\n const markerPrefix = `graph-diagram-${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n\n const { width, height, positions, groups: groupBoxes } = useMemo(\n () => layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap }),\n [layout, nodes, edges, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap],\n );\n const nodeHeights = useMemo(() => compactHeights(nodes, layout, compactNodeHeight), [nodes, layout, compactNodeHeight]);\n const routes = useMemo(\n () =>\n routeEdges(edges, positions, { nodeWidth, nodeHeight, nodeHeights }, { anchor: layout === \"columns\" ? \"side\" : \"center\" }),\n [edges, positions, nodeWidth, nodeHeight, nodeHeights, layout],\n );\n\n const panZoom = usePanZoom({\n enabled: zoomable,\n wheel: wheelZoom,\n contentWidth: width,\n contentHeight: height,\n opening: openingFit(positions, fitMinScale, focusId),\n });\n const natural = layout === \"columns\" || zoomable;\n const place: GraphPlacer = natural\n ? ({ x, y }) => ({ left: x, top: y })\n : ({ x, y }) => ({ left: toPercent(x, width), top: toPercent(y, height) });\n const focus = useEdgeFocus({ mode: edgeFocus, edges, selectedId, selectedEdgeId });\n const edgeSelection = { selectedEdgeId, onEdgeSelect, focus: focus.state };\n\n return (\n <div\n ref={panZoom.viewportRef}\n className={cn(\n \"w-full\",\n zoomable ? \"relative touch-none overflow-hidden\" : \"overflow-auto\",\n zoomable && (panZoom.panning ? \"cursor-grabbing select-none\" : \"cursor-grab\"),\n className,\n )}\n style={maxHeight != null ? { maxHeight } : undefined}\n {...panZoom.handlers}\n >\n <div\n ref={panZoom.contentRef}\n data-graph-stage=\"\"\n className={cn(\"relative\", natural ? !zoomable && \"mx-auto\" : \"w-full\")}\n style={stageStyle({ width, height }, natural, zoomable ? panZoom : undefined)}\n >\n <GraphDiagramGroupBoxes boxes={groupBoxes} groups={groups} />\n <GraphDiagramEdgePaths\n edges={edges}\n nodes={nodes}\n routes={routes}\n width={width}\n height={height}\n ariaLabel={ariaLabel}\n markerPrefix={markerPrefix}\n onEdgeHover={focus.onEdgeHover}\n {...edgeSelection}\n />\n <div className=\"pointer-events-none absolute inset-0\">\n <GraphDiagramEdgeLabels edges={edges} routes={routes} place={place} spread={natural} {...edgeSelection} />\n <GraphDiagramNodes\n nodes={nodes}\n positions={positions}\n place={place}\n nodeWidth={nodeWidth}\n nodeHeight={nodeHeight}\n nodeHeights={nodeHeights}\n selectedId={selectedId}\n onNodeSelect={onNodeSelect}\n onNodeExpand={onNodeExpand}\n onNodeHover={focus.onNodeHover}\n />\n </div>\n </div>\n {zoomable && <ZoomControls panZoom={panZoom} resettable={fitMinScale !== undefined} />}\n </div>\n );\n}\n"],"names":["ringLayout","columnsLayout","jsxs","jsx","IconButton","UiZoomOut","UiZoomIn","UiFullscreen","UiHome","requireEntry","useId","useMemo","routeEdges","usePanZoom","useEdgeFocus","cn","GraphDiagramGroupBoxes","GraphDiagramEdgePaths","GraphDiagramEdgeLabels","GraphDiagramNodes"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GraphDiagramEdge, GraphDiagramEdgeFocus, GraphDiagramGroup, GraphDiagramLayout, GraphDiagramNode } from './graph-diagram-model.js';
|
|
2
|
+
import { WheelMode } from './use-pan-zoom.js';
|
|
2
3
|
export type { GraphDiagramEdge, GraphDiagramEdgeFocus, GraphDiagramGroup, GraphDiagramLayout, GraphDiagramNode, } from './graph-diagram-model.js';
|
|
3
4
|
export interface GraphDiagramProps {
|
|
4
5
|
nodes: GraphDiagramNode[];
|
|
@@ -21,12 +22,19 @@ export interface GraphDiagramProps {
|
|
|
21
22
|
groups?: GraphDiagramGroup[];
|
|
22
23
|
nodeWidth?: number;
|
|
23
24
|
nodeHeight?: number;
|
|
25
|
+
/**
|
|
26
|
+
* "columns" layout: the height of a node with `size: "compact"`, drawn on one line. A group of only
|
|
27
|
+
* compact nodes stacks them closer and pads them tighter. Defaults to 24.
|
|
28
|
+
*/
|
|
29
|
+
compactNodeHeight?: number;
|
|
24
30
|
/** "columns" layout: horizontal gap between columns in px. Leave room for edge label pills. */
|
|
25
31
|
columnGap?: number;
|
|
26
32
|
/** "columns" layout: vertical gap between stacked nodes in px. */
|
|
27
33
|
rowGap?: number;
|
|
28
34
|
/** Ctrl/meta + wheel (or pinch) zooms, dragging the background pans, and zoom controls appear. */
|
|
29
35
|
zoomable?: boolean;
|
|
36
|
+
/** Zoomable: "zoom" makes a plain mouse wheel zoom too, for a diagram that fills its pane. Defaults to "modifier". */
|
|
37
|
+
wheelZoom?: WheelMode;
|
|
30
38
|
/**
|
|
31
39
|
* Zoomable: the smallest scale the diagram opens at, so its text stays readable. A diagram too
|
|
32
40
|
* large for it opens overflowing, centred on `focusId`, and is panned to reach the rest. `Fit to
|
|
@@ -39,5 +47,5 @@ export interface GraphDiagramProps {
|
|
|
39
47
|
ariaLabel: string;
|
|
40
48
|
className?: string;
|
|
41
49
|
}
|
|
42
|
-
export declare function GraphDiagram({ nodes, edges, layout, selectedId, onNodeSelect, onNodeExpand, selectedEdgeId, onEdgeSelect, edgeFocus, groups, nodeWidth, nodeHeight, columnGap, rowGap, zoomable, fitMinScale, focusId, maxHeight, ariaLabel, className, }: GraphDiagramProps): import("react").JSX.Element;
|
|
50
|
+
export declare function GraphDiagram({ nodes, edges, layout, selectedId, onNodeSelect, onNodeExpand, selectedEdgeId, onEdgeSelect, edgeFocus, groups, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap, zoomable, wheelZoom, fitMinScale, focusId, maxHeight, ariaLabel, className, }: GraphDiagramProps): import("react").JSX.Element;
|
|
43
51
|
//# sourceMappingURL=GraphDiagram.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphDiagram.d.ts","sourceRoot":"","sources":["../../src/data/GraphDiagram.tsx"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EAEtB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"GraphDiagram.d.ts","sourceRoot":"","sources":["../../src/data/GraphDiagram.tsx"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EAEtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAA6C,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3F,YAAY,EACV,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAiB/B,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,mEAAmE;IACnE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,qFAAqF;IACrF,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,kGAAkG;IAClG,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sHAAsH;IACtH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAyGD,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,KAAK,EACL,MAAe,EACf,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,SAAkB,EAClB,MAAM,EACN,SAA8B,EAC9B,UAAgC,EAChC,iBAA+C,EAC/C,SAAS,EACT,MAAM,EACN,QAAgB,EAChB,SAAsB,EACtB,WAAW,EACX,OAAO,EACP,SAAS,EACT,SAAS,EACT,SAAS,GACV,EAAE,iBAAiB,+BA8EnB"}
|
|
@@ -16,6 +16,11 @@ import { UiFullscreen } from "../icons/components/UiFullscreen.js";
|
|
|
16
16
|
import { UiHome } from "../icons/components/UiHome.js";
|
|
17
17
|
const DEFAULT_NODE_WIDTH = 168;
|
|
18
18
|
const DEFAULT_NODE_HEIGHT = 60;
|
|
19
|
+
const DEFAULT_COMPACT_NODE_HEIGHT = 24;
|
|
20
|
+
function compactHeights(nodes, layout, compactNodeHeight) {
|
|
21
|
+
if (layout !== "columns") return {};
|
|
22
|
+
return Object.fromEntries(nodes.filter((node) => node.size === "compact").map((node) => [node.id, compactNodeHeight]));
|
|
23
|
+
}
|
|
19
24
|
function assertEdgesReferenceKnownNodes(nodes, edges) {
|
|
20
25
|
const ids = new Set(nodes.map((node) => node.id));
|
|
21
26
|
for (const edge of edges) {
|
|
@@ -33,7 +38,7 @@ function requireLevel(node) {
|
|
|
33
38
|
}
|
|
34
39
|
return node.level;
|
|
35
40
|
}
|
|
36
|
-
function layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, columnGap, rowGap }) {
|
|
41
|
+
function layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap }) {
|
|
37
42
|
switch (layout) {
|
|
38
43
|
case "ring":
|
|
39
44
|
return { ...ringLayout(nodes, { nodeWidth, nodeHeight }), groups: [] };
|
|
@@ -42,12 +47,14 @@ function layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, columnGap, r
|
|
|
42
47
|
nodes.map((node) => ({
|
|
43
48
|
id: node.id,
|
|
44
49
|
level: requireLevel(node),
|
|
45
|
-
...node.group !== void 0 ? { group: node.group } : {}
|
|
50
|
+
...node.group !== void 0 ? { group: node.group } : {},
|
|
51
|
+
...node.size === "compact" ? { compact: true } : {}
|
|
46
52
|
})),
|
|
47
53
|
edges,
|
|
48
54
|
{
|
|
49
55
|
nodeWidth,
|
|
50
56
|
nodeHeight,
|
|
57
|
+
compactNodeHeight,
|
|
51
58
|
...columnGap !== void 0 ? { columnGap } : {},
|
|
52
59
|
...rowGap !== void 0 ? { rowGap } : {}
|
|
53
60
|
}
|
|
@@ -92,9 +99,11 @@ function GraphDiagram({
|
|
|
92
99
|
groups,
|
|
93
100
|
nodeWidth = DEFAULT_NODE_WIDTH,
|
|
94
101
|
nodeHeight = DEFAULT_NODE_HEIGHT,
|
|
102
|
+
compactNodeHeight = DEFAULT_COMPACT_NODE_HEIGHT,
|
|
95
103
|
columnGap,
|
|
96
104
|
rowGap,
|
|
97
105
|
zoomable = false,
|
|
106
|
+
wheelZoom = "modifier",
|
|
98
107
|
fitMinScale,
|
|
99
108
|
focusId,
|
|
100
109
|
maxHeight,
|
|
@@ -104,15 +113,17 @@ function GraphDiagram({
|
|
|
104
113
|
assertEdgesReferenceKnownNodes(nodes, edges);
|
|
105
114
|
const markerPrefix = `graph-diagram-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
106
115
|
const { width, height, positions, groups: groupBoxes } = useMemo(
|
|
107
|
-
() => layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, columnGap, rowGap }),
|
|
108
|
-
[layout, nodes, edges, nodeWidth, nodeHeight, columnGap, rowGap]
|
|
116
|
+
() => layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap }),
|
|
117
|
+
[layout, nodes, edges, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap]
|
|
109
118
|
);
|
|
119
|
+
const nodeHeights = useMemo(() => compactHeights(nodes, layout, compactNodeHeight), [nodes, layout, compactNodeHeight]);
|
|
110
120
|
const routes = useMemo(
|
|
111
|
-
() => routeEdges(edges, positions, { nodeWidth, nodeHeight }, { anchor: layout === "columns" ? "side" : "center" }),
|
|
112
|
-
[edges, positions, nodeWidth, nodeHeight, layout]
|
|
121
|
+
() => routeEdges(edges, positions, { nodeWidth, nodeHeight, nodeHeights }, { anchor: layout === "columns" ? "side" : "center" }),
|
|
122
|
+
[edges, positions, nodeWidth, nodeHeight, nodeHeights, layout]
|
|
113
123
|
);
|
|
114
124
|
const panZoom = usePanZoom({
|
|
115
125
|
enabled: zoomable,
|
|
126
|
+
wheel: wheelZoom,
|
|
116
127
|
contentWidth: width,
|
|
117
128
|
contentHeight: height,
|
|
118
129
|
opening: openingFit(positions, fitMinScale, focusId)
|
|
@@ -167,6 +178,7 @@ function GraphDiagram({
|
|
|
167
178
|
place,
|
|
168
179
|
nodeWidth,
|
|
169
180
|
nodeHeight,
|
|
181
|
+
nodeHeights,
|
|
170
182
|
selectedId,
|
|
171
183
|
onNodeSelect,
|
|
172
184
|
onNodeExpand,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphDiagram.js","sources":["../../src/data/GraphDiagram.tsx"],"sourcesContent":["import { useId, useMemo, type CSSProperties } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { IconButton } from \"../components/IconButton\";\nimport { UiFullscreen, UiHome, UiZoomIn, UiZoomOut } from \"../icons\";\nimport { columnsLayout, type ColumnsLayoutResult } from \"./graph-columns-layout\";\nimport { ringLayout, routeEdges, type GraphLayoutPosition } from \"./graph-layout\";\nimport { GraphDiagramEdgeLabels, GraphDiagramEdgePaths } from \"./GraphDiagramEdges\";\nimport { GraphDiagramGroupBoxes, GraphDiagramNodes } from \"./GraphDiagramNodes\";\nimport {\n requireEntry,\n type GraphDiagramEdge,\n type GraphDiagramEdgeFocus,\n type GraphDiagramGroup,\n type GraphDiagramLayout,\n type GraphDiagramNode,\n type GraphPlacer,\n} from \"./graph-diagram-model\";\nimport { useEdgeFocus } from \"./use-edge-focus\";\nimport { usePanZoom, type FitOptions, type PanZoom } from \"./use-pan-zoom\";\n\nexport type {\n GraphDiagramEdge,\n GraphDiagramEdgeFocus,\n GraphDiagramGroup,\n GraphDiagramLayout,\n GraphDiagramNode,\n} from \"./graph-diagram-model\";\n\n// GraphDiagram renders a generic, type-agnostic node/edge diagram. It knows\n// nothing about the domain: a producer maps its data into\n// GraphDiagramNode/GraphDiagramEdge and gets layout, edge routing and\n// selection behaviour.\n//\n// \"ring\" suits a small cycle of resources and processes — relationships with\n// back edges or reciprocal pairs. \"columns\" suits a directed graph with a\n// natural depth (a call graph, a dependency chain): nodes sit in the column of\n// their `level`, grouped and boxed by `group`.\n//\n// The \"columns\" layout, and any zoomable diagram, is drawn at its natural\n// pixel size so node boxes, edges and group boxes share one coordinate system\n// at every container width and zoom level. A non-zoomable \"ring\" keeps scaling\n// its node positions to the container width.\n\nexport interface GraphDiagramProps {\n nodes: GraphDiagramNode[];\n edges: GraphDiagramEdge[];\n /** Defaults to \"ring\". \"columns\" needs a `level` on every node. */\n layout?: GraphDiagramLayout;\n selectedId?: string;\n onNodeSelect?: (id: string) => void;\n /** Called by a node's `+N` control (see `expandCount`); does not select the node. */\n onNodeExpand?: (id: string) => void;\n selectedEdgeId?: string;\n /** Makes edges, and their label pills, selectable. */\n onEdgeSelect?: (id: string) => void;\n /**\n * Whether an edge shows its pill only while in focus, and fades while another is. Defaults to\n * \"auto\": a diagram with more than 32 edges is focused, a smaller one shows everything.\n */\n edgeFocus?: GraphDiagramEdgeFocus;\n /** \"columns\" layout: captions for the group rectangles. A group without an entry shows its id. */\n groups?: GraphDiagramGroup[];\n nodeWidth?: number;\n nodeHeight?: number;\n /** \"columns\" layout: horizontal gap between columns in px. Leave room for edge label pills. */\n columnGap?: number;\n /** \"columns\" layout: vertical gap between stacked nodes in px. */\n rowGap?: number;\n /** Ctrl/meta + wheel (or pinch) zooms, dragging the background pans, and zoom controls appear. */\n zoomable?: boolean;\n /**\n * Zoomable: the smallest scale the diagram opens at, so its text stays readable. A diagram too\n * large for it opens overflowing, centred on `focusId`, and is panned to reach the rest. `Fit to\n * view` still shows everything, and `Reset view` returns here. Unset, the diagram opens whole.\n */\n fitMinScale?: number;\n /** Zoomable: the node an overflowing diagram opens centred on. Defaults to the diagram's centre. */\n focusId?: string;\n maxHeight?: number | string;\n ariaLabel: string;\n className?: string;\n}\n\nconst DEFAULT_NODE_WIDTH = 168;\nconst DEFAULT_NODE_HEIGHT = 60;\n\ninterface LayoutOptions {\n layout: GraphDiagramLayout;\n nodeWidth: number;\n nodeHeight: number;\n columnGap: number | undefined;\n rowGap: number | undefined;\n}\n\nfunction assertEdgesReferenceKnownNodes(nodes: GraphDiagramNode[], edges: GraphDiagramEdge[]): void {\n const ids = new Set(nodes.map((node) => node.id));\n for (const edge of edges) {\n if (!ids.has(edge.from)) {\n throw new Error(`GraphDiagram: edge \"${edge.id}\" references unknown node \"${edge.from}\"`);\n }\n if (!ids.has(edge.to)) {\n throw new Error(`GraphDiagram: edge \"${edge.id}\" references unknown node \"${edge.to}\"`);\n }\n }\n}\n\nfunction requireLevel(node: GraphDiagramNode): number {\n if (node.level === undefined || !Number.isFinite(node.level)) {\n throw new Error(`GraphDiagram: node \"${node.id}\" needs a finite level for the \"columns\" layout`);\n }\n return node.level;\n}\n\nfunction layoutNodes(\n nodes: GraphDiagramNode[],\n edges: GraphDiagramEdge[],\n { layout, nodeWidth, nodeHeight, columnGap, rowGap }: LayoutOptions,\n): ColumnsLayoutResult {\n switch (layout) {\n case \"ring\":\n return { ...ringLayout(nodes, { nodeWidth, nodeHeight }), groups: [] };\n case \"columns\":\n return columnsLayout(\n nodes.map((node) => ({\n id: node.id,\n level: requireLevel(node),\n ...(node.group !== undefined ? { group: node.group } : {}),\n })),\n edges,\n {\n nodeWidth,\n nodeHeight,\n ...(columnGap !== undefined ? { columnGap } : {}),\n ...(rowGap !== undefined ? { rowGap } : {}),\n },\n );\n default:\n throw new Error(`GraphDiagram: unknown layout \"${layout satisfies never}\"`);\n }\n}\n\nfunction toPercent(value: number, extent: number): string {\n return `${(value / extent) * 100}%`;\n}\n\nfunction stageStyle(size: { width: number; height: number }, natural: boolean, panZoom: PanZoom | undefined): CSSProperties {\n if (!natural) return { aspectRatio: `${size.width} / ${size.height}` };\n if (!panZoom) return size;\n const { x, y, scale } = panZoom.transform;\n return { ...size, transform: `translate(${x}px, ${y}px) scale(${scale})`, transformOrigin: \"0 0\" };\n}\n\n/** `resettable`: the diagram opens at another view than the whole of it, so there is one to return to. */\nfunction ZoomControls({ panZoom, resettable }: { panZoom: PanZoom; resettable: boolean }) {\n return (\n <div className=\"absolute right-2 top-2 flex cursor-default gap-0.5 rounded-md border border-border bg-background p-0.5 shadow-sm\">\n <IconButton icon={UiZoomOut} label=\"Zoom out\" className=\"size-6\" onClick={panZoom.zoomOut} />\n <IconButton icon={UiZoomIn} label=\"Zoom in\" className=\"size-6\" onClick={panZoom.zoomIn} />\n <IconButton icon={UiFullscreen} label=\"Fit to view\" className=\"size-6\" onClick={panZoom.fit} />\n {resettable && <IconButton icon={UiHome} label=\"Reset view\" className=\"size-6\" onClick={panZoom.reset} />}\n </div>\n );\n}\n\n/** How a zoomable diagram opens: no smaller than `fitMinScale`, centred on the `focusId` node. */\nfunction openingFit(\n positions: Record<string, GraphLayoutPosition>,\n fitMinScale: number | undefined,\n focusId: string | undefined,\n): FitOptions {\n return {\n ...(fitMinScale !== undefined ? { minScale: fitMinScale } : {}),\n ...(focusId !== undefined ? { focus: requireEntry(positions, focusId, \"layout position\") } : {}),\n };\n}\n\nexport function GraphDiagram({\n nodes,\n edges,\n layout = \"ring\",\n selectedId,\n onNodeSelect,\n onNodeExpand,\n selectedEdgeId,\n onEdgeSelect,\n edgeFocus = \"auto\",\n groups,\n nodeWidth = DEFAULT_NODE_WIDTH,\n nodeHeight = DEFAULT_NODE_HEIGHT,\n columnGap,\n rowGap,\n zoomable = false,\n fitMinScale,\n focusId,\n maxHeight,\n ariaLabel,\n className,\n}: GraphDiagramProps) {\n assertEdgesReferenceKnownNodes(nodes, edges);\n const markerPrefix = `graph-diagram-${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n\n const { width, height, positions, groups: groupBoxes } = useMemo(\n () => layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, columnGap, rowGap }),\n [layout, nodes, edges, nodeWidth, nodeHeight, columnGap, rowGap],\n );\n const routes = useMemo(\n () =>\n routeEdges(edges, positions, { nodeWidth, nodeHeight }, { anchor: layout === \"columns\" ? \"side\" : \"center\" }),\n [edges, positions, nodeWidth, nodeHeight, layout],\n );\n\n const panZoom = usePanZoom({\n enabled: zoomable,\n contentWidth: width,\n contentHeight: height,\n opening: openingFit(positions, fitMinScale, focusId),\n });\n const natural = layout === \"columns\" || zoomable;\n const place: GraphPlacer = natural\n ? ({ x, y }) => ({ left: x, top: y })\n : ({ x, y }) => ({ left: toPercent(x, width), top: toPercent(y, height) });\n const focus = useEdgeFocus({ mode: edgeFocus, edges, selectedId, selectedEdgeId });\n const edgeSelection = { selectedEdgeId, onEdgeSelect, focus: focus.state };\n\n return (\n <div\n ref={panZoom.viewportRef}\n className={cn(\n \"w-full\",\n zoomable ? \"relative touch-none overflow-hidden\" : \"overflow-auto\",\n zoomable && (panZoom.panning ? \"cursor-grabbing select-none\" : \"cursor-grab\"),\n className,\n )}\n style={maxHeight != null ? { maxHeight } : undefined}\n {...panZoom.handlers}\n >\n <div\n ref={panZoom.contentRef}\n data-graph-stage=\"\"\n className={cn(\"relative\", natural ? !zoomable && \"mx-auto\" : \"w-full\")}\n style={stageStyle({ width, height }, natural, zoomable ? panZoom : undefined)}\n >\n <GraphDiagramGroupBoxes boxes={groupBoxes} groups={groups} />\n <GraphDiagramEdgePaths\n edges={edges}\n nodes={nodes}\n routes={routes}\n width={width}\n height={height}\n ariaLabel={ariaLabel}\n markerPrefix={markerPrefix}\n onEdgeHover={focus.onEdgeHover}\n {...edgeSelection}\n />\n <div className=\"pointer-events-none absolute inset-0\">\n <GraphDiagramEdgeLabels edges={edges} routes={routes} place={place} spread={natural} {...edgeSelection} />\n <GraphDiagramNodes\n nodes={nodes}\n positions={positions}\n place={place}\n nodeWidth={nodeWidth}\n nodeHeight={nodeHeight}\n selectedId={selectedId}\n onNodeSelect={onNodeSelect}\n onNodeExpand={onNodeExpand}\n onNodeHover={focus.onNodeHover}\n />\n </div>\n </div>\n {zoomable && <ZoomControls panZoom={panZoom} resettable={fitMinScale !== undefined} />}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GraphDiagram.js","sources":["../../src/data/GraphDiagram.tsx"],"sourcesContent":["import { useId, useMemo, type CSSProperties } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { IconButton } from \"../components/IconButton\";\nimport { UiFullscreen, UiHome, UiZoomIn, UiZoomOut } from \"../icons\";\nimport { columnsLayout, type ColumnsLayoutResult } from \"./graph-columns-layout\";\nimport { ringLayout, routeEdges, type GraphLayoutPosition } from \"./graph-layout\";\nimport { GraphDiagramEdgeLabels, GraphDiagramEdgePaths } from \"./GraphDiagramEdges\";\nimport { GraphDiagramGroupBoxes, GraphDiagramNodes } from \"./GraphDiagramNodes\";\nimport {\n requireEntry,\n type GraphDiagramEdge,\n type GraphDiagramEdgeFocus,\n type GraphDiagramGroup,\n type GraphDiagramLayout,\n type GraphDiagramNode,\n type GraphPlacer,\n} from \"./graph-diagram-model\";\nimport { useEdgeFocus } from \"./use-edge-focus\";\nimport { usePanZoom, type FitOptions, type PanZoom, type WheelMode } from \"./use-pan-zoom\";\n\nexport type {\n GraphDiagramEdge,\n GraphDiagramEdgeFocus,\n GraphDiagramGroup,\n GraphDiagramLayout,\n GraphDiagramNode,\n} from \"./graph-diagram-model\";\n\n// GraphDiagram renders a generic, type-agnostic node/edge diagram. It knows\n// nothing about the domain: a producer maps its data into\n// GraphDiagramNode/GraphDiagramEdge and gets layout, edge routing and\n// selection behaviour.\n//\n// \"ring\" suits a small cycle of resources and processes — relationships with\n// back edges or reciprocal pairs. \"columns\" suits a directed graph with a\n// natural depth (a call graph, a dependency chain): nodes sit in the column of\n// their `level`, grouped and boxed by `group`.\n//\n// The \"columns\" layout, and any zoomable diagram, is drawn at its natural\n// pixel size so node boxes, edges and group boxes share one coordinate system\n// at every container width and zoom level. A non-zoomable \"ring\" keeps scaling\n// its node positions to the container width.\n\nexport interface GraphDiagramProps {\n nodes: GraphDiagramNode[];\n edges: GraphDiagramEdge[];\n /** Defaults to \"ring\". \"columns\" needs a `level` on every node. */\n layout?: GraphDiagramLayout;\n selectedId?: string;\n onNodeSelect?: (id: string) => void;\n /** Called by a node's `+N` control (see `expandCount`); does not select the node. */\n onNodeExpand?: (id: string) => void;\n selectedEdgeId?: string;\n /** Makes edges, and their label pills, selectable. */\n onEdgeSelect?: (id: string) => void;\n /**\n * Whether an edge shows its pill only while in focus, and fades while another is. Defaults to\n * \"auto\": a diagram with more than 32 edges is focused, a smaller one shows everything.\n */\n edgeFocus?: GraphDiagramEdgeFocus;\n /** \"columns\" layout: captions for the group rectangles. A group without an entry shows its id. */\n groups?: GraphDiagramGroup[];\n nodeWidth?: number;\n nodeHeight?: number;\n /**\n * \"columns\" layout: the height of a node with `size: \"compact\"`, drawn on one line. A group of only\n * compact nodes stacks them closer and pads them tighter. Defaults to 24.\n */\n compactNodeHeight?: number;\n /** \"columns\" layout: horizontal gap between columns in px. Leave room for edge label pills. */\n columnGap?: number;\n /** \"columns\" layout: vertical gap between stacked nodes in px. */\n rowGap?: number;\n /** Ctrl/meta + wheel (or pinch) zooms, dragging the background pans, and zoom controls appear. */\n zoomable?: boolean;\n /** Zoomable: \"zoom\" makes a plain mouse wheel zoom too, for a diagram that fills its pane. Defaults to \"modifier\". */\n wheelZoom?: WheelMode;\n /**\n * Zoomable: the smallest scale the diagram opens at, so its text stays readable. A diagram too\n * large for it opens overflowing, centred on `focusId`, and is panned to reach the rest. `Fit to\n * view` still shows everything, and `Reset view` returns here. Unset, the diagram opens whole.\n */\n fitMinScale?: number;\n /** Zoomable: the node an overflowing diagram opens centred on. Defaults to the diagram's centre. */\n focusId?: string;\n maxHeight?: number | string;\n ariaLabel: string;\n className?: string;\n}\n\nconst DEFAULT_NODE_WIDTH = 168;\nconst DEFAULT_NODE_HEIGHT = 60;\nconst DEFAULT_COMPACT_NODE_HEIGHT = 24;\n\ninterface LayoutOptions {\n layout: GraphDiagramLayout;\n nodeWidth: number;\n nodeHeight: number;\n compactNodeHeight: number;\n columnGap: number | undefined;\n rowGap: number | undefined;\n}\n\n/** The heights of the nodes not drawn at the node height: the compact ones, in the \"columns\" layout. */\nfunction compactHeights(nodes: GraphDiagramNode[], layout: GraphDiagramLayout, compactNodeHeight: number): Record<string, number> {\n if (layout !== \"columns\") return {};\n return Object.fromEntries(nodes.filter((node) => node.size === \"compact\").map((node) => [node.id, compactNodeHeight]));\n}\n\nfunction assertEdgesReferenceKnownNodes(nodes: GraphDiagramNode[], edges: GraphDiagramEdge[]): void {\n const ids = new Set(nodes.map((node) => node.id));\n for (const edge of edges) {\n if (!ids.has(edge.from)) {\n throw new Error(`GraphDiagram: edge \"${edge.id}\" references unknown node \"${edge.from}\"`);\n }\n if (!ids.has(edge.to)) {\n throw new Error(`GraphDiagram: edge \"${edge.id}\" references unknown node \"${edge.to}\"`);\n }\n }\n}\n\nfunction requireLevel(node: GraphDiagramNode): number {\n if (node.level === undefined || !Number.isFinite(node.level)) {\n throw new Error(`GraphDiagram: node \"${node.id}\" needs a finite level for the \"columns\" layout`);\n }\n return node.level;\n}\n\nfunction layoutNodes(\n nodes: GraphDiagramNode[],\n edges: GraphDiagramEdge[],\n { layout, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap }: LayoutOptions,\n): ColumnsLayoutResult {\n switch (layout) {\n case \"ring\":\n return { ...ringLayout(nodes, { nodeWidth, nodeHeight }), groups: [] };\n case \"columns\":\n return columnsLayout(\n nodes.map((node) => ({\n id: node.id,\n level: requireLevel(node),\n ...(node.group !== undefined ? { group: node.group } : {}),\n ...(node.size === \"compact\" ? { compact: true } : {}),\n })),\n edges,\n {\n nodeWidth,\n nodeHeight,\n compactNodeHeight,\n ...(columnGap !== undefined ? { columnGap } : {}),\n ...(rowGap !== undefined ? { rowGap } : {}),\n },\n );\n default:\n throw new Error(`GraphDiagram: unknown layout \"${layout satisfies never}\"`);\n }\n}\n\nfunction toPercent(value: number, extent: number): string {\n return `${(value / extent) * 100}%`;\n}\n\nfunction stageStyle(size: { width: number; height: number }, natural: boolean, panZoom: PanZoom | undefined): CSSProperties {\n if (!natural) return { aspectRatio: `${size.width} / ${size.height}` };\n if (!panZoom) return size;\n const { x, y, scale } = panZoom.transform;\n return { ...size, transform: `translate(${x}px, ${y}px) scale(${scale})`, transformOrigin: \"0 0\" };\n}\n\n/** `resettable`: the diagram opens at another view than the whole of it, so there is one to return to. */\nfunction ZoomControls({ panZoom, resettable }: { panZoom: PanZoom; resettable: boolean }) {\n return (\n <div className=\"absolute right-2 top-2 flex cursor-default gap-0.5 rounded-md border border-border bg-background p-0.5 shadow-sm\">\n <IconButton icon={UiZoomOut} label=\"Zoom out\" className=\"size-6\" onClick={panZoom.zoomOut} />\n <IconButton icon={UiZoomIn} label=\"Zoom in\" className=\"size-6\" onClick={panZoom.zoomIn} />\n <IconButton icon={UiFullscreen} label=\"Fit to view\" className=\"size-6\" onClick={panZoom.fit} />\n {resettable && <IconButton icon={UiHome} label=\"Reset view\" className=\"size-6\" onClick={panZoom.reset} />}\n </div>\n );\n}\n\n/** How a zoomable diagram opens: no smaller than `fitMinScale`, centred on the `focusId` node. */\nfunction openingFit(\n positions: Record<string, GraphLayoutPosition>,\n fitMinScale: number | undefined,\n focusId: string | undefined,\n): FitOptions {\n return {\n ...(fitMinScale !== undefined ? { minScale: fitMinScale } : {}),\n ...(focusId !== undefined ? { focus: requireEntry(positions, focusId, \"layout position\") } : {}),\n };\n}\n\nexport function GraphDiagram({\n nodes,\n edges,\n layout = \"ring\",\n selectedId,\n onNodeSelect,\n onNodeExpand,\n selectedEdgeId,\n onEdgeSelect,\n edgeFocus = \"auto\",\n groups,\n nodeWidth = DEFAULT_NODE_WIDTH,\n nodeHeight = DEFAULT_NODE_HEIGHT,\n compactNodeHeight = DEFAULT_COMPACT_NODE_HEIGHT,\n columnGap,\n rowGap,\n zoomable = false,\n wheelZoom = \"modifier\",\n fitMinScale,\n focusId,\n maxHeight,\n ariaLabel,\n className,\n}: GraphDiagramProps) {\n assertEdgesReferenceKnownNodes(nodes, edges);\n const markerPrefix = `graph-diagram-${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n\n const { width, height, positions, groups: groupBoxes } = useMemo(\n () => layoutNodes(nodes, edges, { layout, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap }),\n [layout, nodes, edges, nodeWidth, nodeHeight, compactNodeHeight, columnGap, rowGap],\n );\n const nodeHeights = useMemo(() => compactHeights(nodes, layout, compactNodeHeight), [nodes, layout, compactNodeHeight]);\n const routes = useMemo(\n () =>\n routeEdges(edges, positions, { nodeWidth, nodeHeight, nodeHeights }, { anchor: layout === \"columns\" ? \"side\" : \"center\" }),\n [edges, positions, nodeWidth, nodeHeight, nodeHeights, layout],\n );\n\n const panZoom = usePanZoom({\n enabled: zoomable,\n wheel: wheelZoom,\n contentWidth: width,\n contentHeight: height,\n opening: openingFit(positions, fitMinScale, focusId),\n });\n const natural = layout === \"columns\" || zoomable;\n const place: GraphPlacer = natural\n ? ({ x, y }) => ({ left: x, top: y })\n : ({ x, y }) => ({ left: toPercent(x, width), top: toPercent(y, height) });\n const focus = useEdgeFocus({ mode: edgeFocus, edges, selectedId, selectedEdgeId });\n const edgeSelection = { selectedEdgeId, onEdgeSelect, focus: focus.state };\n\n return (\n <div\n ref={panZoom.viewportRef}\n className={cn(\n \"w-full\",\n zoomable ? \"relative touch-none overflow-hidden\" : \"overflow-auto\",\n zoomable && (panZoom.panning ? \"cursor-grabbing select-none\" : \"cursor-grab\"),\n className,\n )}\n style={maxHeight != null ? { maxHeight } : undefined}\n {...panZoom.handlers}\n >\n <div\n ref={panZoom.contentRef}\n data-graph-stage=\"\"\n className={cn(\"relative\", natural ? !zoomable && \"mx-auto\" : \"w-full\")}\n style={stageStyle({ width, height }, natural, zoomable ? panZoom : undefined)}\n >\n <GraphDiagramGroupBoxes boxes={groupBoxes} groups={groups} />\n <GraphDiagramEdgePaths\n edges={edges}\n nodes={nodes}\n routes={routes}\n width={width}\n height={height}\n ariaLabel={ariaLabel}\n markerPrefix={markerPrefix}\n onEdgeHover={focus.onEdgeHover}\n {...edgeSelection}\n />\n <div className=\"pointer-events-none absolute inset-0\">\n <GraphDiagramEdgeLabels edges={edges} routes={routes} place={place} spread={natural} {...edgeSelection} />\n <GraphDiagramNodes\n nodes={nodes}\n positions={positions}\n place={place}\n nodeWidth={nodeWidth}\n nodeHeight={nodeHeight}\n nodeHeights={nodeHeights}\n selectedId={selectedId}\n onNodeSelect={onNodeSelect}\n onNodeExpand={onNodeExpand}\n onNodeHover={focus.onNodeHover}\n />\n </div>\n </div>\n {zoomable && <ZoomControls panZoom={panZoom} resettable={fitMinScale !== undefined} />}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|