symbiote-ui 0.3.0-alpha.4
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/CHANGELOG.md +46 -0
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/canvas/AutoLayout.js +731 -0
- package/canvas/Breadcrumb/Breadcrumb.css.js +75 -0
- package/canvas/Breadcrumb/Breadcrumb.js +96 -0
- package/canvas/Breadcrumb/Breadcrumb.tpl.js +7 -0
- package/canvas/CanvasConnectionRenderer.js +971 -0
- package/canvas/CanvasGraph/CanvasGraph.css.js +29 -0
- package/canvas/CanvasGraph/CanvasGraph.js +1697 -0
- package/canvas/CanvasGraph/CanvasGraphDrawState.js +280 -0
- package/canvas/CanvasGraph/CanvasGraphGeometry.js +194 -0
- package/canvas/CanvasViewport.js +550 -0
- package/canvas/ConnectionRenderer.js +1283 -0
- package/canvas/FlowSimulator.js +326 -0
- package/canvas/ForceLayout.js +226 -0
- package/canvas/ForceWorker.js +1303 -0
- package/canvas/FrameManager.js +223 -0
- package/canvas/GraphExplorerShell/GraphExplorerShell.css.js +136 -0
- package/canvas/GraphExplorerShell/GraphExplorerShell.js +129 -0
- package/canvas/GraphExplorerShell/GraphExplorerShell.tpl.js +12 -0
- package/canvas/GraphTabs/GraphTabs.css.js +101 -0
- package/canvas/GraphTabs/GraphTabs.js +189 -0
- package/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
- package/canvas/LODManager.js +88 -0
- package/canvas/Minimap/Minimap.css.js +73 -0
- package/canvas/Minimap/Minimap.js +210 -0
- package/canvas/Minimap/Minimap.tpl.js +7 -0
- package/canvas/NodeCanvas/NodeCanvas.css.js +398 -0
- package/canvas/NodeCanvas/NodeCanvas.js +1499 -0
- package/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
- package/canvas/NodeSearch/NodeSearch.css.js +97 -0
- package/canvas/NodeSearch/NodeSearch.js +140 -0
- package/canvas/NodeSearch/NodeSearch.tpl.js +25 -0
- package/canvas/NodeViewManager.js +748 -0
- package/canvas/PcbRouteDiagnostics.js +463 -0
- package/canvas/PcbRouter.js +1127 -0
- package/canvas/PinExpansion.js +134 -0
- package/canvas/PseudoConnection.js +84 -0
- package/canvas/SelectionSync.js +163 -0
- package/canvas/SubgraphManager.js +203 -0
- package/canvas/SubgraphRouter.js +452 -0
- package/canvas/ViewportActions.js +473 -0
- package/canvas/graph-explorer.js +339 -0
- package/canvas/graph-layout.js +148 -0
- package/canvas/graph-model.js +68 -0
- package/canvas/html-in-canvas.js +202 -0
- package/canvas/project-graph-builder.js +440 -0
- package/canvas/project-graph-model.js +183 -0
- package/chat/ChatComposer/ChatComposer.css.js +652 -0
- package/chat/ChatComposer/ChatComposer.js +304 -0
- package/chat/ChatList/ChatList.css.js +102 -0
- package/chat/ChatList/ChatList.js +99 -0
- package/chat/ChatList/ChatList.tpl.js +20 -0
- package/chat/ChatListItem/ChatListItem.css.js +117 -0
- package/chat/ChatListItem/ChatListItem.js +32 -0
- package/chat/ChatListItem/ChatListItem.tpl.js +17 -0
- package/chat/ChatMessageItem/ChatMessageItem.css.js +628 -0
- package/chat/ChatMessageItem/ChatMessageItem.js +156 -0
- package/chat/ChatSidebar/ChatSidebar.css.js +150 -0
- package/chat/ChatSidebar/ChatSidebar.js +230 -0
- package/chat/ChatSidebar/ChatSidebar.tpl.js +18 -0
- package/chat/ChatSidebar/constants.js +11 -0
- package/chat/ChatSidebarItem/ChatSidebarItem.css.js +445 -0
- package/chat/ChatSidebarItem/ChatSidebarItem.js +304 -0
- package/chat/ChatTranscript/ChatTranscript.css.js +90 -0
- package/chat/ChatTranscript/ChatTranscript.js +244 -0
- package/chat/chat-context.js +123 -0
- package/chat/message-model.js +156 -0
- package/cli.js +20 -0
- package/control/Button/Button.css.js +93 -0
- package/control/Button/Button.js +78 -0
- package/control/Button/Button.tpl.js +3 -0
- package/control/Field/Field.css.js +91 -0
- package/control/Field/Field.js +17 -0
- package/control/Field/Field.tpl.js +3 -0
- package/core/Connection.js +47 -0
- package/core/Editor.js +449 -0
- package/core/Frame.js +33 -0
- package/core/GraphMermaid.js +348 -0
- package/core/GraphText.js +228 -0
- package/core/Node.js +145 -0
- package/core/Portal.js +106 -0
- package/core/Socket.js +187 -0
- package/core/SubgraphNode.js +121 -0
- package/core/base-path.js +55 -0
- package/core/dom-utils.js +14 -0
- package/core/index.js +18 -0
- package/core/local-cache.js +26 -0
- package/core/state-sync.js +227 -0
- package/custom-elements.json +6380 -0
- package/discover.js +240 -0
- package/display/Badge/Badge.css.js +44 -0
- package/display/Badge/Badge.js +17 -0
- package/display/Badge/Badge.tpl.js +3 -0
- package/display/Banner/Banner.css.js +61 -0
- package/display/Banner/Banner.js +17 -0
- package/display/Banner/Banner.tpl.js +3 -0
- package/display/CodeBlock/CodeBlock.css.js +194 -0
- package/display/CodeBlock/CodeBlock.js +220 -0
- package/display/CodeBlock/CodeBlock.tpl.js +11 -0
- package/display/DataTable/DataTable.css.js +101 -0
- package/display/DataTable/DataTable.js +136 -0
- package/display/DataTable/DataTable.tpl.js +13 -0
- package/display/EmptyState/EmptyState.css.js +33 -0
- package/display/EmptyState/EmptyState.js +17 -0
- package/display/EmptyState/EmptyState.tpl.js +3 -0
- package/display/EventFeed/EventFeed.css.js +145 -0
- package/display/EventFeed/EventFeed.js +64 -0
- package/display/EventFeed/EventFeed.tpl.js +14 -0
- package/display/EventFeed/EventFeedItem.js +116 -0
- package/display/EventFeed/EventFeedItem.tpl.js +22 -0
- package/display/LoadingOverlay/LoadingOverlay.css.js +91 -0
- package/display/LoadingOverlay/LoadingOverlay.js +48 -0
- package/display/LoadingOverlay/LoadingOverlay.tpl.js +12 -0
- package/display/Metric/Metric.css.js +60 -0
- package/display/Metric/Metric.js +17 -0
- package/display/Metric/Metric.tpl.js +6 -0
- package/display/OutputGraphPreview/OutputGraphPreview.css.js +122 -0
- package/display/OutputGraphPreview/OutputGraphPreview.js +89 -0
- package/display/OutputGraphPreview/OutputGraphPreview.tpl.js +13 -0
- package/display/OutputListPreview/OutputListPreview.css.js +109 -0
- package/display/OutputListPreview/OutputListPreview.js +77 -0
- package/display/OutputListPreview/OutputListPreview.tpl.js +13 -0
- package/display/SourceEditor/SourceEditor.css.js +39 -0
- package/display/SourceEditor/SourceEditor.js +129 -0
- package/display/SourceEditor/SourceEditor.tpl.js +10 -0
- package/display/SourceViewer/SourceViewer.css.js +80 -0
- package/display/SourceViewer/SourceViewer.js +418 -0
- package/display/SourceViewer/SourceViewer.tpl.js +17 -0
- package/display/StatusRibbon/StatusRibbon.css.js +73 -0
- package/display/StatusRibbon/StatusRibbon.js +87 -0
- package/display/StatusRibbon/StatusRibbon.tpl.js +7 -0
- package/display/event-feed-adapter.js +72 -0
- package/display/format-utils.js +29 -0
- package/display/highlight.js +659 -0
- package/display/icons.js +37 -0
- package/display/markdown-formatter.js +60 -0
- package/display/network-approval-page.js +487 -0
- package/display/output-preview.js +261 -0
- package/effects/CellBg/CellBg.css.js +33 -0
- package/effects/CellBg/CellBg.js +410 -0
- package/effects/CellBg/CellBg.tpl.js +5 -0
- package/graph/canvas-adapter.js +223 -0
- package/graph/graph-algorithms.js +31 -0
- package/graph/index.js +46 -0
- package/graph/model.js +176 -0
- package/graph/project-graph-build.js +66 -0
- package/graph/project-graph-metadata.js +253 -0
- package/graph/project-package.js +128 -0
- package/graph/project-runtime.js +116 -0
- package/graph/project-transaction.js +284 -0
- package/graph/skeleton-utils.js +84 -0
- package/graph/theme-contract.js +36 -0
- package/graph/transaction-parser.js +56 -0
- package/icons/MaterialSymbols.js +69 -0
- package/icons/material-symbols-outlined-400.ttf +0 -0
- package/icons/material-symbols.css +24 -0
- package/index.js +95 -0
- package/inspector/InspectorPanel/InspectorPanel.css.js +375 -0
- package/inspector/InspectorPanel/InspectorPanel.js +368 -0
- package/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
- package/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
- package/inspector/TemplatePreview/TemplatePreview.js +145 -0
- package/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
- package/interactions/ConnectFlow.js +304 -0
- package/interactions/Drag.js +104 -0
- package/interactions/Selector.js +133 -0
- package/interactions/SnapGrid.js +66 -0
- package/interactions/Zoom.js +139 -0
- package/layout/ActionZone/ActionZone.css.js +88 -0
- package/layout/ActionZone/ActionZone.js +261 -0
- package/layout/ActionZone/ActionZone.tpl.js +11 -0
- package/layout/CrossLayoutPortalBridge/CrossLayoutPortalBridge.js +255 -0
- package/layout/Layout/Layout.css.js +91 -0
- package/layout/Layout/Layout.js +637 -0
- package/layout/Layout/Layout.tpl.js +27 -0
- package/layout/LayoutNode/LayoutNode.css.js +302 -0
- package/layout/LayoutNode/LayoutNode.js +509 -0
- package/layout/LayoutNode/LayoutNode.tpl.js +39 -0
- package/layout/LayoutPreview/LayoutPreview.css.js +46 -0
- package/layout/LayoutPreview/LayoutPreview.js +102 -0
- package/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
- package/layout/LayoutRouter/LayoutRouter.js +274 -0
- package/layout/LayoutRouter/SectionRegistry.js +135 -0
- package/layout/LayoutRouter/routerSync.js +250 -0
- package/layout/LayoutSidebar/LayoutSidebar.css.js +411 -0
- package/layout/LayoutSidebar/LayoutSidebar.js +368 -0
- package/layout/LayoutSidebar/LayoutSidebar.tpl.js +26 -0
- package/layout/LayoutSidebar/SidebarSection.css.js +20 -0
- package/layout/LayoutSidebar/SidebarSection.js +184 -0
- package/layout/LayoutSidebar/SidebarSection.tpl.js +22 -0
- package/layout/LayoutTree.js +373 -0
- package/layout/PanelMenu/PanelMenu.css.js +43 -0
- package/layout/PanelMenu/PanelMenu.js +95 -0
- package/layout/PanelMenu/PanelMenu.tpl.js +17 -0
- package/layout/ProjectTabs/ProjectTabs.css.js +188 -0
- package/layout/ProjectTabs/ProjectTabs.js +77 -0
- package/layout/ProjectTabs/ProjectTabs.tpl.js +15 -0
- package/layout/index.js +40 -0
- package/list/ListDetailShell/ListDetailShell.css.js +128 -0
- package/list/ListDetailShell/ListDetailShell.js +72 -0
- package/list/ListDetailShell/ListDetailShell.tpl.js +36 -0
- package/list/ListItem/ListItem.css.js +111 -0
- package/list/ListItem/ListItem.js +66 -0
- package/list/ListItem/ListItem.tpl.js +18 -0
- package/locale/index.js +503 -0
- package/manifest/component-registry.js +2446 -0
- package/manifest/graph-schema.js +285 -0
- package/manifest/index.js +6 -0
- package/manifest/project-schema-catalog.js +246 -0
- package/manifest/rule-catalog.js +201 -0
- package/manifest/theme-catalog.js +2149 -0
- package/manifest/ui-schema-catalog.js +334 -0
- package/menu/ContextMenu/ContextMenu.css.js +61 -0
- package/menu/ContextMenu/ContextMenu.js +82 -0
- package/menu/ContextMenu/ContextMenu.tpl.js +19 -0
- package/navigation/QuickOpen/QuickOpen.css.js +92 -0
- package/navigation/QuickOpen/QuickOpen.js +185 -0
- package/navigation/QuickOpen/QuickOpen.tpl.js +15 -0
- package/navigation/quick-open-utils.js +101 -0
- package/node/CtrlItem/CtrlItem.css.js +41 -0
- package/node/CtrlItem/CtrlItem.js +24 -0
- package/node/CtrlItem/CtrlItem.tpl.js +17 -0
- package/node/GraphFrame/GraphFrame.css.js +66 -0
- package/node/GraphFrame/GraphFrame.js +32 -0
- package/node/GraphFrame/GraphFrame.tpl.js +13 -0
- package/node/GraphNode/GraphNode.css.js +815 -0
- package/node/GraphNode/GraphNode.js +173 -0
- package/node/GraphNode/GraphNode.tpl.js +33 -0
- package/node/NodeCallout/NodeCallout.css.js +91 -0
- package/node/NodeCallout/NodeCallout.js +281 -0
- package/node/NodeCallout/NodeCallout.tpl.js +8 -0
- package/node/NodeSocket/NodeSocket.css.js +68 -0
- package/node/NodeSocket/NodeSocket.js +26 -0
- package/node/NodeSocket/NodeSocket.tpl.js +7 -0
- package/node/PortItem/PortItem.css.js +93 -0
- package/node/PortItem/PortItem.js +87 -0
- package/node/PortItem/PortItem.tpl.js +10 -0
- package/package.json +165 -0
- package/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
- package/palette/PaletteBrowser/PaletteBrowser.js +152 -0
- package/palette/PaletteBrowser/PaletteBrowser.tpl.js +23 -0
- package/plugins/History.js +408 -0
- package/plugins/Readonly.js +60 -0
- package/rules/symbiote-3x.json +170 -0
- package/schemas/component-descriptor-v1.json +91 -0
- package/schemas/component-descriptor-v2.json +145 -0
- package/schemas/graph-model-v1.json +179 -0
- package/schemas/graph-v1.json +91 -0
- package/schemas/project-package-v1.json +102 -0
- package/schemas/project-transaction-v1.json +114 -0
- package/schemas/runtime-ui-v1.json +80 -0
- package/schemas/theme-rule-block-v1.json +73 -0
- package/shapes/CircleShape.js +79 -0
- package/shapes/CommentShape.js +35 -0
- package/shapes/DiamondShape.js +130 -0
- package/shapes/NodeShape.js +79 -0
- package/shapes/PillShape.js +91 -0
- package/shapes/RectShape.js +84 -0
- package/shapes/SVGShape.js +525 -0
- package/shapes/index.js +63 -0
- package/surface/Card/Card.css.js +57 -0
- package/surface/Card/Card.js +17 -0
- package/surface/Card/Card.tpl.js +3 -0
- package/themes/Palette.js +30 -0
- package/themes/Skin.js +113 -0
- package/themes/Theme.js +82 -0
- package/themes/carbon.js +135 -0
- package/themes/dark.js +140 -0
- package/themes/default-dark.js +714 -0
- package/themes/default-provider.css +635 -0
- package/themes/default-provider.js +718 -0
- package/themes/ebook.js +136 -0
- package/themes/grey.js +137 -0
- package/themes/light.js +139 -0
- package/themes/neon.js +138 -0
- package/themes/pcb.js +273 -0
- package/themes/synthwave.js +138 -0
- package/tokens/base.json +29 -0
- package/tokens/themes/carbon.json +11 -0
- package/tokens/themes/dark.json +12 -0
- package/tokens/themes/default-dark.json +1543 -0
- package/tokens/themes/default-provider.json +1543 -0
- package/tokens/themes/ebook.json +11 -0
- package/tokens/themes/grey.json +11 -0
- package/tokens/themes/light.json +12 -0
- package/tokens/themes/neon.json +11 -0
- package/tokens/themes/pcb.json +11 -0
- package/tokens/themes/synthwave.json +11 -0
- package/toolbar/QuickToolbar/QuickToolbar.css.js +152 -0
- package/toolbar/QuickToolbar/QuickToolbar.js +529 -0
- package/toolbar/QuickToolbar/QuickToolbar.tpl.js +34 -0
- package/tree/TreePanel/TreePanel.css.js +112 -0
- package/tree/TreePanel/TreePanel.js +147 -0
- package/tree/TreePanel/TreePanel.tpl.js +18 -0
- package/tree/TreeView/TreeView.css.js +122 -0
- package/tree/TreeView/TreeView.js +365 -0
- package/tree/TreeView/TreeView.tpl.js +10 -0
- package/ui/dialogs.js +221 -0
- package/ui/host-adapters.js +114 -0
- package/ui/index.js +660 -0
- package/ui/locale.js +50 -0
- package/ui/overlay-stack.js +89 -0
- package/ui/shared-styles.js +26 -0
- package/webmcp.js +37 -0
- package/xr/deep-graph.js +646 -0
- package/xr/emulation.js +198 -0
- package/xr/gesture.js +228 -0
- package/xr/html-canvas-renderer.js +472 -0
- package/xr/index.js +15 -0
- package/xr/layout-projection.js +1046 -0
- package/xr/panel-frame.js +128 -0
- package/xr/panel-host.js +267 -0
- package/xr/pointer.js +258 -0
- package/xr/scene-controller.js +242 -0
- package/xr/spatial-scene.js +212 -0
- package/xr/theme-bridge.js +105 -0
- package/xr/three-webxr-adapter.js +3439 -0
- package/xr/webgl-layer-renderer.js +419 -0
- package/xr/webxr.js +679 -0
- package/xr/workbench.js +516 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LayoutRouter — universal hash-based router for layout system
|
|
3
|
+
*
|
|
4
|
+
* Uses Symbiote PubSub named data context (ROUTER) to provide
|
|
5
|
+
* reactive routing across the application.
|
|
6
|
+
*
|
|
7
|
+
* URL format: #panel/subpath?param1=value¶m2=value
|
|
8
|
+
*
|
|
9
|
+
* Two levels of query params:
|
|
10
|
+
* - **Global** (registered via registerGlobalParam): persist across section switches
|
|
11
|
+
* - **Section** (everything else): reset when navigating to a new section
|
|
12
|
+
*
|
|
13
|
+
* Usage in templates: {{ROUTER/panel}}, {{ROUTER/subpath}}, {{ROUTER/query}}
|
|
14
|
+
* Usage in code: this.$['ROUTER/panel'], this.sub('ROUTER/panel', cb)
|
|
15
|
+
* Global params: this.sub('ROUTER/globalParams', cb)
|
|
16
|
+
*
|
|
17
|
+
* @module symbiote-node/layout/LayoutRouter
|
|
18
|
+
*/
|
|
19
|
+
import { PubSub } from '@symbiotejs/symbiote/core/PubSub.js';
|
|
20
|
+
|
|
21
|
+
const CTX = 'ROUTER';
|
|
22
|
+
|
|
23
|
+
/** @type {Set<string>} Keys that persist across section switches */
|
|
24
|
+
const _globalKeys = new Set();
|
|
25
|
+
|
|
26
|
+
const routerCtx = PubSub.registerCtx(
|
|
27
|
+
{
|
|
28
|
+
panel: 'default',
|
|
29
|
+
subpath: '',
|
|
30
|
+
query: '',
|
|
31
|
+
globalParams: {},
|
|
32
|
+
},
|
|
33
|
+
CTX
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse query string into object
|
|
38
|
+
* @param {string} str - Query string (without leading ?)
|
|
39
|
+
* @returns {Object<string, string>}
|
|
40
|
+
*/
|
|
41
|
+
export function parseQuery(str) {
|
|
42
|
+
if (!str) return {};
|
|
43
|
+
const result = {};
|
|
44
|
+
for (const pair of str.split('&')) {
|
|
45
|
+
const eqIdx = pair.indexOf('=');
|
|
46
|
+
if (eqIdx >= 0) {
|
|
47
|
+
result[decodeURIComponent(pair.substring(0, eqIdx))] = decodeURIComponent(
|
|
48
|
+
pair.substring(eqIdx + 1)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build query string from key-value object
|
|
57
|
+
* @param {Object<string, string>} params
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
export function buildQuery(params) {
|
|
61
|
+
const entries = Object.entries(params).filter(([, v]) => v !== '' && v != null);
|
|
62
|
+
if (entries.length === 0) return '';
|
|
63
|
+
return entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build full hash string from parts
|
|
68
|
+
* @param {string} panel
|
|
69
|
+
* @param {string} [subpath]
|
|
70
|
+
* @param {Object} [params]
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
export function buildHash(panel, subpath, params) {
|
|
74
|
+
let hash = panel;
|
|
75
|
+
if (subpath) hash += '/' + subpath;
|
|
76
|
+
const q = params ? buildQuery(params) : '';
|
|
77
|
+
if (q) hash += '?' + q;
|
|
78
|
+
return hash;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Navigate to a new route — updates URL and PubSub context.
|
|
83
|
+
* Global params (registered via registerGlobalParam) are automatically
|
|
84
|
+
* carried over unless explicitly overridden or set to null.
|
|
85
|
+
* @param {string} panel - Master panel section ID
|
|
86
|
+
* @param {string} [subpath] - Sub-path (entity ID, etc.)
|
|
87
|
+
* @param {Object} [params] - Query parameters (overrides globals if specified)
|
|
88
|
+
*/
|
|
89
|
+
export function navigate(panel, subpath = '', params = {}) {
|
|
90
|
+
if (typeof location === 'undefined') return;
|
|
91
|
+
|
|
92
|
+
const currentQuery = parseQuery(routerCtx.read('query'));
|
|
93
|
+
const merged = {};
|
|
94
|
+
for (const key of _globalKeys) {
|
|
95
|
+
if (currentQuery[key] && params[key] === undefined) {
|
|
96
|
+
merged[key] = currentQuery[key];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const [k, v] of Object.entries(params)) {
|
|
101
|
+
if (v != null && v !== '') {
|
|
102
|
+
merged[k] = v;
|
|
103
|
+
} else {
|
|
104
|
+
delete merged[k];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const hash = buildHash(panel, subpath, merged);
|
|
108
|
+
|
|
109
|
+
history.pushState(null, '', location.pathname + '#' + hash);
|
|
110
|
+
syncFromHash();
|
|
111
|
+
if (typeof window !== 'undefined') {
|
|
112
|
+
window.dispatchEvent(new Event('hashchange'));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Update only query params of current route (keeps panel/subpath)
|
|
118
|
+
* Uses replaceState to avoid cluttering browser history
|
|
119
|
+
* @param {Object} params - Params to merge
|
|
120
|
+
*/
|
|
121
|
+
export function updateParams(params) {
|
|
122
|
+
if (typeof location === 'undefined') return;
|
|
123
|
+
const currentQuery = parseQuery(routerCtx.read('query'));
|
|
124
|
+
const merged = { ...currentQuery };
|
|
125
|
+
for (const [k, v] of Object.entries(params)) {
|
|
126
|
+
if (v === '' || v == null) {
|
|
127
|
+
delete merged[k];
|
|
128
|
+
} else {
|
|
129
|
+
merged[k] = v;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const query = buildQuery(merged);
|
|
133
|
+
const hash = buildHash(routerCtx.read('panel'), routerCtx.read('subpath'), merged);
|
|
134
|
+
history.replaceState(null, '', '#' + hash);
|
|
135
|
+
routerCtx.pub('query', query);
|
|
136
|
+
if (typeof window !== 'undefined') {
|
|
137
|
+
window.dispatchEvent(new Event('hashchange'));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Sync PubSub context from current URL hash
|
|
143
|
+
*/
|
|
144
|
+
function syncFromHash() {
|
|
145
|
+
const raw = location.hash.replace(/^#/, '') || 'default';
|
|
146
|
+
|
|
147
|
+
const qIdx = raw.indexOf('?');
|
|
148
|
+
const pathPart = qIdx >= 0 ? raw.substring(0, qIdx) : raw;
|
|
149
|
+
const queryPart = qIdx >= 0 ? raw.substring(qIdx + 1) : '';
|
|
150
|
+
|
|
151
|
+
const slashIdx = pathPart.indexOf('/');
|
|
152
|
+
const panel = slashIdx >= 0 ? pathPart.substring(0, slashIdx) : pathPart;
|
|
153
|
+
const subpath = slashIdx >= 0 ? pathPart.substring(slashIdx + 1) : '';
|
|
154
|
+
|
|
155
|
+
routerCtx.pub('panel', panel);
|
|
156
|
+
routerCtx.pub('subpath', subpath);
|
|
157
|
+
routerCtx.pub('query', queryPart);
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if (_globalKeys.size > 0) {
|
|
161
|
+
const allParams = parseQuery(queryPart);
|
|
162
|
+
const globals = {};
|
|
163
|
+
for (const key of _globalKeys) {
|
|
164
|
+
if (allParams[key]) globals[key] = allParams[key];
|
|
165
|
+
}
|
|
166
|
+
routerCtx.pub('globalParams', globals);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get current route state
|
|
172
|
+
* @returns {{ panel: string, subpath: string, query: string }}
|
|
173
|
+
*/
|
|
174
|
+
export function getRoute() {
|
|
175
|
+
return {
|
|
176
|
+
panel: routerCtx.read('panel'),
|
|
177
|
+
subpath: routerCtx.read('subpath'),
|
|
178
|
+
query: routerCtx.read('query'),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Set default panel (first section to show if hash is empty)
|
|
184
|
+
* @param {string} panel
|
|
185
|
+
*/
|
|
186
|
+
export function setDefaultPanel(panel) {
|
|
187
|
+
if (typeof location === 'undefined') return;
|
|
188
|
+
if (!location.hash || location.hash === '#') {
|
|
189
|
+
navigate(panel);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Register one or more param keys as global (persistent across section switches).
|
|
195
|
+
* Global params are automatically carried in navigate() and published
|
|
196
|
+
* via ROUTER/globalParams PubSub context.
|
|
197
|
+
* @param {...string} keys - Param names to register as global
|
|
198
|
+
*/
|
|
199
|
+
export function registerGlobalParam(...keys) {
|
|
200
|
+
keys.forEach((k) => _globalKeys.add(k));
|
|
201
|
+
|
|
202
|
+
if (typeof location !== 'undefined') {
|
|
203
|
+
const allParams = parseQuery(routerCtx.read('query'));
|
|
204
|
+
const globals = {};
|
|
205
|
+
for (const key of _globalKeys) {
|
|
206
|
+
if (allParams[key]) globals[key] = allParams[key];
|
|
207
|
+
}
|
|
208
|
+
routerCtx.pub('globalParams', globals);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Set a single global param value.
|
|
214
|
+
* Shorthand for registerGlobalParam + updateParams.
|
|
215
|
+
* @param {string} key
|
|
216
|
+
* @param {string|null} value - null removes the param
|
|
217
|
+
*/
|
|
218
|
+
export function setGlobalParam(key, value) {
|
|
219
|
+
_globalKeys.add(key);
|
|
220
|
+
updateParams({ [key]: value });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
if (typeof location !== 'undefined' && typeof window !== 'undefined') {
|
|
225
|
+
syncFromHash();
|
|
226
|
+
window.addEventListener('hashchange', syncFromHash);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Generic search parameters builder
|
|
231
|
+
*/
|
|
232
|
+
export function getGraphSearchString(locationObj = typeof window !== 'undefined' ? window.location : {}) {
|
|
233
|
+
if (!locationObj || !locationObj.hash) return ''
|
|
234
|
+
const params = new URLSearchParams(locationObj.search || '')
|
|
235
|
+
const hashQuery = locationObj.hash.includes('?') ? locationObj.hash.split('?')[1] : ''
|
|
236
|
+
const hashParams = new URLSearchParams(hashQuery)
|
|
237
|
+
for (let [key, value] of hashParams) {
|
|
238
|
+
params.set(key, value)
|
|
239
|
+
}
|
|
240
|
+
return params.toString()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function getGraphUrlParams(locationObj = typeof window !== 'undefined' ? window.location : {}) {
|
|
244
|
+
return new URLSearchParams(getGraphSearchString(locationObj))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function parseGraphHash(hash = typeof window !== 'undefined' ? window.location.hash : '') {
|
|
248
|
+
if (!hash) return { path: '', params: new URLSearchParams() }
|
|
249
|
+
const [hashBase, queryStr] = hash.replace('#', '').split('?')
|
|
250
|
+
const hashParams = hashBase.split('/')
|
|
251
|
+
if (hashParams[0] === 'graph') hashParams.shift()
|
|
252
|
+
return {
|
|
253
|
+
path: hashParams.join('/'),
|
|
254
|
+
params: new URLSearchParams(queryStr || ''),
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function updateHashParam(key, value, locationObj = typeof window !== 'undefined' ? window.location : {}, historyObj = typeof history !== 'undefined' ? history : {}) {
|
|
259
|
+
if (!locationObj || !locationObj.hash) return
|
|
260
|
+
const [basePath, queryStr] = locationObj.hash.split('?')
|
|
261
|
+
const params = new URLSearchParams(queryStr || '')
|
|
262
|
+
if (value === null || value === undefined) {
|
|
263
|
+
params.delete(key)
|
|
264
|
+
} else {
|
|
265
|
+
params.set(key, value)
|
|
266
|
+
}
|
|
267
|
+
const newQuery = params.toString()
|
|
268
|
+
const newHash = newQuery ? `${basePath}?${newQuery}` : basePath
|
|
269
|
+
if (locationObj.hash === newHash) return
|
|
270
|
+
if (historyObj && typeof historyObj.replaceState === 'function') {
|
|
271
|
+
historyObj.replaceState(null, '', newHash)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as LayoutTree from '../LayoutTree.js';
|
|
2
|
+
|
|
3
|
+
export const SECTION_SCOPES = Object.freeze({
|
|
4
|
+
HOME: 'home',
|
|
5
|
+
PROJECT: 'project',
|
|
6
|
+
BOTH: 'both',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export class SectionRegistry {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.sections = new Map();
|
|
12
|
+
this.layouts = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
registerSection(id, { icon, label, order = 100, scope = SECTION_SCOPES.BOTH, layout } = {}) {
|
|
16
|
+
if (!id) throw new TypeError('Section id is required');
|
|
17
|
+
let normalizedScope = normalizeSectionScope(scope);
|
|
18
|
+
this.sections.set(id, { id, icon, label, order, scope: normalizedScope });
|
|
19
|
+
if (layout) this.layouts.set(id, layout);
|
|
20
|
+
return this.getSection(id);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getSection(id) {
|
|
24
|
+
return this.sections.get(id) || null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getSections(filter = {}) {
|
|
28
|
+
let result = [...this.sections.values()];
|
|
29
|
+
if (filter.scope) {
|
|
30
|
+
result = result.filter((section) => sectionMatchesScope(section, filter.scope));
|
|
31
|
+
}
|
|
32
|
+
return result.sort((a, b) => a.order - b.order);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getHomeSections() {
|
|
36
|
+
return this.getSections({ scope: SECTION_SCOPES.HOME });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getProjectSections() {
|
|
40
|
+
return this.getSections({ scope: SECTION_SCOPES.PROJECT });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getSectionsForScope(projectIdOrScope) {
|
|
44
|
+
let scope = projectIdOrScope === SECTION_SCOPES.HOME || projectIdOrScope === SECTION_SCOPES.PROJECT
|
|
45
|
+
? projectIdOrScope
|
|
46
|
+
: projectIdOrScope
|
|
47
|
+
? SECTION_SCOPES.PROJECT
|
|
48
|
+
: SECTION_SCOPES.HOME;
|
|
49
|
+
return this.getSections({ scope });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getLayout(id) {
|
|
53
|
+
let layout = this.layouts.get(id);
|
|
54
|
+
return typeof layout === 'function' ? layout() : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
hasSection(id) {
|
|
58
|
+
return this.sections.has(id);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
clear() {
|
|
62
|
+
this.sections.clear();
|
|
63
|
+
this.layouts.clear();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createSectionRegistry() {
|
|
68
|
+
return new SectionRegistry();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function normalizeSectionScope(scope = SECTION_SCOPES.BOTH) {
|
|
72
|
+
if (scope === SECTION_SCOPES.HOME || scope === SECTION_SCOPES.PROJECT || scope === SECTION_SCOPES.BOTH) {
|
|
73
|
+
return scope;
|
|
74
|
+
}
|
|
75
|
+
return SECTION_SCOPES.BOTH;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function sectionMatchesScope(section, scope) {
|
|
79
|
+
let normalized = normalizeSectionScope(scope);
|
|
80
|
+
return section?.scope === normalized || section?.scope === SECTION_SCOPES.BOTH;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function withGlobalPanel(layoutFn, panelType, {
|
|
84
|
+
direction = 'horizontal',
|
|
85
|
+
ratio = 0.65,
|
|
86
|
+
collapsed = true,
|
|
87
|
+
global = true,
|
|
88
|
+
panelState = {},
|
|
89
|
+
} = {}) {
|
|
90
|
+
return () => {
|
|
91
|
+
let main = layoutFn();
|
|
92
|
+
let panel = LayoutTree.createPanel(panelType, panelState);
|
|
93
|
+
panel.global = global;
|
|
94
|
+
panel.collapsed = collapsed;
|
|
95
|
+
return LayoutTree.createSplit(direction, main, panel, ratio);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const defaultRegistry = createSectionRegistry();
|
|
100
|
+
|
|
101
|
+
export function registerSection(id, options) {
|
|
102
|
+
return defaultRegistry.registerSection(id, options);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getSection(id) {
|
|
106
|
+
return defaultRegistry.getSection(id);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getSections(filter) {
|
|
110
|
+
return defaultRegistry.getSections(filter);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getHomeSections() {
|
|
114
|
+
return defaultRegistry.getHomeSections();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getProjectSections() {
|
|
118
|
+
return defaultRegistry.getProjectSections();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getSectionsForScope(projectIdOrScope) {
|
|
122
|
+
return defaultRegistry.getSectionsForScope(projectIdOrScope);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function getLayout(id) {
|
|
126
|
+
return defaultRegistry.getLayout(id);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function hasSection(id) {
|
|
130
|
+
return defaultRegistry.hasSection(id);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function clearSections() {
|
|
134
|
+
defaultRegistry.clear();
|
|
135
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* routerSync — bidirectional URL ↔ component state sync
|
|
3
|
+
*
|
|
4
|
+
* Maps URL query params to component init$ properties and vice versa.
|
|
5
|
+
* Only syncs when the component's panel is active.
|
|
6
|
+
*
|
|
7
|
+
* Supports two mapping formats:
|
|
8
|
+
*
|
|
9
|
+
* Simple: { componentProp: 'urlParam' }
|
|
10
|
+
* Extended: { componentProp: { param: 'urlParam', default: 'all', type: 'number' } }
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Simple format:
|
|
14
|
+
* syncWithRouter(this, 'jobs', {
|
|
15
|
+
* filterStatus: 'status',
|
|
16
|
+
* filterRegion: 'region',
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Extended format:
|
|
20
|
+
* syncWithRouter(this, 'jobs', {
|
|
21
|
+
* filterStatus: { param: 'status', default: 'all' },
|
|
22
|
+
* currentPage: { param: 'page', default: 1, type: 'number' },
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* @module symbiote-node/layout/LayoutRouter/routerSync
|
|
26
|
+
*/
|
|
27
|
+
import { parseQuery, updateParams } from './LayoutRouter.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Normalize mapping entry to { param, defaultVal, type }
|
|
31
|
+
* @param {string | { param: string, default?: *, type?: string }} entry
|
|
32
|
+
* @returns {{ param: string, defaultVal: *, type: string }}
|
|
33
|
+
*/
|
|
34
|
+
function normalizeMapping(entry) {
|
|
35
|
+
if (typeof entry === 'string') {
|
|
36
|
+
return { param: entry, defaultVal: undefined, type: 'string' };
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
param: entry.param,
|
|
40
|
+
defaultVal: entry.default,
|
|
41
|
+
type: entry.type ?? 'string',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Cast value to the target type
|
|
47
|
+
* @param {string} value
|
|
48
|
+
* @param {string} type
|
|
49
|
+
* @returns {*}
|
|
50
|
+
*/
|
|
51
|
+
function castValue(value, type) {
|
|
52
|
+
if (type === 'number') return Number(value);
|
|
53
|
+
if (type === 'boolean') return value === 'true';
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Sync component state with router URL params
|
|
59
|
+
*
|
|
60
|
+
* @param {import('@symbiotejs/symbiote').default} component - Symbiote component
|
|
61
|
+
* @param {string} panelName - Panel this component belongs to
|
|
62
|
+
* @param {Object<string, string | { param: string, default?: *, type?: string }>} mapping
|
|
63
|
+
*/
|
|
64
|
+
export function syncWithRouter(component, panelName, mapping) {
|
|
65
|
+
let syncing = false;
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
let normalizedMap = {};
|
|
69
|
+
for (const [prop, entry] of Object.entries(mapping)) {
|
|
70
|
+
normalizedMap[prop] = normalizeMapping(entry);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read URL params into component state
|
|
75
|
+
*/
|
|
76
|
+
function readFromURL() {
|
|
77
|
+
if (syncing) return;
|
|
78
|
+
syncing = true;
|
|
79
|
+
let query = parseQuery(component.$['ROUTER/query']);
|
|
80
|
+
for (const [prop, { param, defaultVal, type }] of Object.entries(normalizedMap)) {
|
|
81
|
+
let rawValue = query[param];
|
|
82
|
+
if (rawValue !== undefined) {
|
|
83
|
+
let val = castValue(rawValue, type);
|
|
84
|
+
if (component.$[prop] !== val) {
|
|
85
|
+
component.$[prop] = val;
|
|
86
|
+
}
|
|
87
|
+
} else if (defaultVal !== undefined) {
|
|
88
|
+
|
|
89
|
+
if (component.$[prop] !== defaultVal) {
|
|
90
|
+
component.$[prop] = defaultVal;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
syncing = false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Write component state to URL params
|
|
99
|
+
* @param {string} prop - Changed property name
|
|
100
|
+
*/
|
|
101
|
+
function writeToURL(prop) {
|
|
102
|
+
if (syncing) return;
|
|
103
|
+
if (component.$['ROUTER/panel'] !== panelName) return;
|
|
104
|
+
syncing = true;
|
|
105
|
+
let { param, defaultVal } = normalizedMap[prop];
|
|
106
|
+
let value = component.$[prop];
|
|
107
|
+
|
|
108
|
+
if (value === defaultVal) {
|
|
109
|
+
updateParams({ [param]: '' });
|
|
110
|
+
} else {
|
|
111
|
+
updateParams({ [param]: String(value) });
|
|
112
|
+
}
|
|
113
|
+
syncing = false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
component.sub('ROUTER/panel', (panel) => {
|
|
118
|
+
if (panel === panelName) {
|
|
119
|
+
readFromURL();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
component.sub('ROUTER/query', () => {
|
|
125
|
+
if (component.$['ROUTER/panel'] !== panelName) return;
|
|
126
|
+
readFromURL();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
for (const prop of Object.keys(normalizedMap)) {
|
|
131
|
+
component.sub(prop, () => {
|
|
132
|
+
if (component.$['ROUTER/panel'] === panelName) {
|
|
133
|
+
writeToURL(prop);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if (component.$['ROUTER/panel'] === panelName) {
|
|
140
|
+
readFromURL();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* setupPanelRouting — high-level panel routing setup
|
|
146
|
+
*
|
|
147
|
+
* Centralizes all routing logic for a panel:
|
|
148
|
+
* - Panel activation (onActivate callback)
|
|
149
|
+
* - List/detail switching via ROUTER/subpath
|
|
150
|
+
* - Tab sync via ?tab= query param
|
|
151
|
+
*
|
|
152
|
+
* Convention:
|
|
153
|
+
* #panel → list view, default tab
|
|
154
|
+
* #panel?tab=groups → list view, groups tab
|
|
155
|
+
* #panel/{id} → detail view
|
|
156
|
+
*
|
|
157
|
+
* Component requirements:
|
|
158
|
+
* - ref="listWrap" → container for list view (hidden when detail)
|
|
159
|
+
* - <detail-component> → detail view element (hidden when list)
|
|
160
|
+
* - $.activeTab → tab state property (if tabs configured)
|
|
161
|
+
*
|
|
162
|
+
* @param {import('@symbiotejs/symbiote').default} component
|
|
163
|
+
* @param {string} panelName - Panel section ID (e.g. 'users')
|
|
164
|
+
* @param {Object} config
|
|
165
|
+
* @param {string[]} [config.tabs] - Tab names, first is default
|
|
166
|
+
* @param {{ component: string, loadMethod: string }} [config.detail] - Detail view config
|
|
167
|
+
* @param {Function} [config.onActivate] - Called when panel becomes active (list mode)
|
|
168
|
+
* @param {Object} [config.syncParams] - Additional params to sync via syncWithRouter
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* renderCallback() {
|
|
172
|
+
* setupPanelRouting(this, 'users', {
|
|
173
|
+
* tabs: ['users', 'groups'],
|
|
174
|
+
* detail: { component: 'user-detail-view', loadMethod: 'loadUser' },
|
|
175
|
+
* onActivate: () => this.#loadData(),
|
|
176
|
+
* });
|
|
177
|
+
* }
|
|
178
|
+
*/
|
|
179
|
+
export function setupPanelRouting(component, panelName, config = {}) {
|
|
180
|
+
let { tabs, detail, onActivate, syncParams } = config;
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if (tabs && tabs.length > 0) {
|
|
184
|
+
let defaultTab = tabs[0];
|
|
185
|
+
syncWithRouter(component, panelName, {
|
|
186
|
+
activeTab: { param: 'tab', default: defaultTab },
|
|
187
|
+
...(syncParams || {}),
|
|
188
|
+
});
|
|
189
|
+
} else if (syncParams) {
|
|
190
|
+
syncWithRouter(component, panelName, syncParams);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Check and apply list/detail mode based on ROUTER/subpath
|
|
195
|
+
*/
|
|
196
|
+
function checkDetailMode() {
|
|
197
|
+
if (component.$['ROUTER/panel'] !== panelName) return;
|
|
198
|
+
|
|
199
|
+
let subpath = component.$['ROUTER/subpath'];
|
|
200
|
+
let listWrap = component.ref?.listWrap;
|
|
201
|
+
let isDetail = !!(detail && subpath);
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
component.toggleAttribute('data-detail', isDetail);
|
|
205
|
+
|
|
206
|
+
if (isDetail) {
|
|
207
|
+
|
|
208
|
+
if (listWrap) listWrap.hidden = true;
|
|
209
|
+
|
|
210
|
+
let detailEl = component.querySelector(detail.component);
|
|
211
|
+
if (detailEl) {
|
|
212
|
+
detailEl.hidden = false;
|
|
213
|
+
if (typeof detailEl[detail.loadMethod] === 'function') {
|
|
214
|
+
detailEl[detail.loadMethod](subpath);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
|
|
219
|
+
if (listWrap) listWrap.hidden = false;
|
|
220
|
+
|
|
221
|
+
if (detail) {
|
|
222
|
+
let detailEl = component.querySelector(detail.component);
|
|
223
|
+
if (detailEl) detailEl.hidden = true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (onActivate) onActivate();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
component.sub('ROUTER/panel', (panel) => {
|
|
232
|
+
if (panel === panelName) {
|
|
233
|
+
checkDetailMode();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if (detail) {
|
|
239
|
+
component.sub('ROUTER/subpath', () => {
|
|
240
|
+
if (component.$['ROUTER/panel'] === panelName) {
|
|
241
|
+
checkDetailMode();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if (component.$['ROUTER/panel'] === panelName) {
|
|
248
|
+
checkDetailMode();
|
|
249
|
+
}
|
|
250
|
+
}
|