# 侧边栏面板

> 文档：`vjcad/doc/ui/sidebar-panel.md`
> 示例：
> - `vjcad/example/ui/14sidebarpanel.js` — 自定义面板
> - `vjcad/example/ui/16menucustom.js` — `leftPanels / rightPanels` 配置

## 1. 整体模型

vjcad 在主区域两侧各有一条 **ActivityBar（图标栏）**，点击图标即激活右侧 / 左侧的 **SidebarPanel**。每个 SidebarPanel 是一个 Web Component，由 `SidebarPanelManager` 管理生命周期。

```
[ActivityBar 图标] → 激活 SidebarPanel → 渲染你写的 Web Component
```

侧边栏整体显示由 `MainView({ sidebarStyle })` 控制：

| `sidebarStyle` | 含义 |
|---|---|
| `'right'`（默认） | 仅右侧 |
| `'left'` | 仅左侧 |
| `'both'` | 左右都显示 |
| `'none'` | 不显示侧边栏（仍可由命令打开） |

## 2. 注册自己的面板

```js
import { BasePanelComponent, registerSidebarPanel, activateSidebarPanel,
         IconRegistry, IconCategory, Engine, html, css } from 'vjcad';   // html/css/LitElement 直接从 'vjcad' 导出，没有 'vjcad/lit' 子路径

// 1. 写一个继承 BasePanelComponent 的 Web Component
class MyToolPanel extends BasePanelComponent {
    static styles = css`
        .toolbar { padding: 10px; }
        .btn { display: block; width: 100%; margin: 5px 0; padding: 8px;
               background: var(--theme-color-button-bg, #2d3748); color: var(--theme-color-font, #e8eaed);
               border: 1px solid var(--theme-color-border, #3d4a5c); cursor: pointer; }
        .btn:hover { background: var(--theme-color-button-hover-bg, #3d4a5c); }
    `;

    render() {
        return html`
            <div class="toolbar">
                <button class="btn" @click=${this._drawLine}>画直线</button>
                <button class="btn" @click=${this._drawCircle}>画圆</button>
                <button class="btn" @click=${() => Engine.zoomExtents()}>缩放全图</button>
            </div>`;
    }

    _drawLine()   { Engine.editor.executerWithOp('LINE'); }
    _drawCircle() { Engine.editor.executerWithOp('CIRCLE'); }

    // ⚠️ BasePanelComponent 上的 onActivate / onDeactivate / refresh 只是空钩子，框架从不调用；
    //    侧栏只把"当前面板"渲染进内容区，切换时面板元素被挂上 / 摘下 → 用 DOM 生命周期感知
    connectedCallback()    { super.connectedCallback();    /* 被切到（显示） */ }
    disconnectedCallback() { super.disconnectedCallback(); /* 被切走（隐藏） */ }
}
customElements.define('my-tool-panel', MyToolPanel);

// 2. 图标：icon 不是 URL，而是 images/<分类>/<名>.svg 形式的注册表键；先把 SVG 注册进 Actbar 分类
IconRegistry.registerIcon('my-tools', '<svg viewBox="0 0 24 24">...</svg>', { category: IconCategory.Actbar });

// 3. 注册到 ActivityBar（注册时立即创建面板实例）
registerSidebarPanel({
    name: 'my-tools',                          // 唯一标识（必须）
    label: '我的工具',                          // 面板标题（必须）
    icon: 'images/actbar/my-tools.svg',        // 解析为 Actbar 分类的 'my-tools' 图标
    position: 'left',                          // 可选 'left' | 'right'，默认 'left'
    panelClass: MyToolPanel,                   // LitElement 子类（必须）
    tagName: 'my-tool-panel',                  // 上面已 define 过 → 必须传同一个 tagName
});

// 4. 立即激活
activateSidebarPanel('my-tools');
```

`SidebarPanelConfig` 字段（d.ts 完整定义，就这 7 个）：

| 字段 | 必填 | 说明 |
|---|---|---|
| `name` | √ | 唯一 ID |
| `label` | √ | 显示名 |
| `icon` | √ | **不是 URL**：须写成 `images/<分类>/<名>.svg`（通常 `images/actbar/<名>.svg`），按分类 + 名字去 IconRegistry 查；不会去加载文件，查不到就不显示图标 |
| `position` | | `'left'` / `'right'`，默认 `'left'` |
| `panelClass` | √ | `LitElement` 子类（不一定是 `BasePanelComponent`） |
| `tagName` | | 自定义元素标签（必须含 `-`；缺省自动生成 `sidebar-panel-{name}` 并 `define`）。**同一个类只能 define 到一个标签**：若你已 `customElements.define` 过该类，必须传同一个 tagName，否则自动 define 抛错 → `registerPanel` 返回 false |
| `order` | | ⚠️ 字段存在但**不生效**：排序读的是面板实例上的 `order`（从未赋值），实际按注册顺序排列 |

> ⚠️ `activatedByCommand` / `defaultActive` / `minWidth` / `tooltip` / `data` 等字段**不在** `SidebarPanelConfig` 声明里，
> 运行时 `registerPanel` 也不会消费它们——不要依赖。

## 3. BasePanelComponent 生命周期

```ts
class BasePanelComponent extends LitElement {
    panelId: number;                     // 管理器分配的 id
    name: string;                        // = config.name
    caption: string;                     // = config.label
    label: string;                       // = config.label
    src: string;                         // = config.icon
    isActive: boolean;                   // 是否当前激活（普通字段，由侧栏直接赋值）
    onActivate(): void;                  // ⚠️ 空钩子，框架从不调用
    onDeactivate(): void;                // ⚠️ 空钩子，框架从不调用
    refresh(): void;                     // 默认 requestUpdate()；框架不会自动调，需要时自己调
}
```

> ⚠️ 组件实例上**没有** `.config` 成员（管理器只注入上面几个字段）。
> 想给面板传自定义数据，请在类里写死或通过模块作用域传入。

激活流程（实际行为）：

1. `registerSidebarPanel` 时就 `document.createElement(tagName)` **立即实例化**，并注入 `name / label / caption / src / panelId`；视图已就绪则重建侧边栏
2. 用户点击图标 / `activateSidebarPanel(name)` → 侧边栏只把各面板 `isActive` 置 false、目标面板置 true 并重绘
3. **`onActivate()` / `onDeactivate()` / `refresh()` 从不被框架调用**（基类里只是空方法 / `requestUpdate()`）；侧栏内容区只渲染当前面板，切换时面板元素会被挂上 / 摘下，可用 `connectedCallback / disconnectedCallback`（记得调 `super`）感知；数据变了自己调 `refresh()`
4. 注销（`unregisterSidebarPanel`）：实例被移除（组件自身没有 `dispose` 回调）；插件经 `context.registerSidebarPanel` 注册的面板在插件 `unload` 时移除

## 4. 实用 API

```js
import {
    registerSidebarPanel, unregisterSidebarPanel,
    activateSidebarPanel,
    SidebarPanelManager
} from 'vjcad';
// 注意：没有 deactivateSidebarPanel / getActiveSidebarPanel 导出

registerSidebarPanel(config);
unregisterSidebarPanel('my-tools');
activateSidebarPanel('my-tools');         // = 用户点图标

// 直接拿管理器（高级）——⚠️ d.ts 注释声称的另一入口
// `Engine.sidebarPanelManager` 运行时是 undefined，只能走 getInstance()
const mgr = SidebarPanelManager.getInstance();
mgr.getRegisteredPanels();                // 无参，返回全部已注册 name[]
mgr.getPanelInstance('my-tools');         // 拿到实例（不是 getPanelComponent）
mgr.getLeftPanels(); mgr.getRightPanels();// 分左右列出
mgr.isPanelRegistered('my-tools');
mgr.unregisterPanelsByOwner('my-plugin'); // 插件卸载批量清理
```
```

## 5. 通过 URL 激活

启动时可用 `vcad_panel` query 参数激活指定面板（`MainView.URL_PARAMS.PANEL`）：

```
?vcad_panel=my-tools
```

适合分享 / 嵌入场景。

## 6. MainView 配置内置面板

`leftPanels` / `rightPanels` 是**内置面板名的字符串数组**（不是对象配置）：

```js
new MainView({
    leftPanels:  ['props', 'dsettings'],                       // 左侧只留这两个内置面板
    rightPanels: ['blocks', 'images', 'commands', 'alias', 'history'],
    autoActivatePropertyPanel: true   // 选中实体自动弹属性面板
});
```

> 可用内置面板名：`props`（属性）、`dsettings`（绘图设置）、`blocks`（块）、`images`（图像）、`commands`（命令）、`alias`（别名）、`history`（历史）等，详见 `vjcad/doc/ui/sidebar-panel.md`。
> 自定义面板请走 `registerSidebarPanel`，不能写进这两个数组。

## 7. 常见样式钩子（CSS 变量）

vjcad **没有** `--button-bg` / `--text-color` / `--section-bg` / `--side-panel-bg-color` / `--side-panel-border-color` 这类变量。想让面板跟随主题，在自己的 `static styles` 里直接用真实的全局主题变量（`setThemeColors` 写在 `document.documentElement` 上，可穿透 shadow DOM 继承）：

| 变量 | 用途（`ThemeColors` 键） |
|---|---|
| `--theme-color-sidebarframe-contents` | 侧栏内容底色（`sidebarContentsBg`） |
| `--theme-color-font` / `--theme-color-font-secondary` | 主 / 次文字色（`font` / `fontSecondary`） |
| `--theme-color-border` | 边框（`border`；别名 `--border-color`） |
| `--theme-color-button-bg` / `--theme-color-button-hover-bg` | 按钮底色 / 悬停（`buttonBg` / `buttonHoverBg`） |
| `--theme-color-input-bg` | 输入框底色（`inputBg`） |
| `--theme-color-accent` / `--theme-hover` | 强调色 / 悬停高亮（`accent` / `hover`） |

完整清单见导出常量 `THEME_COLOR_CSS_VARS`。自己起名的变量（如 `--my-btn-bg`）当然也能用，但它们只是你自己的约定，vjcad 不会设置。

## 红旗

- `customElements.define('my-tool-panel', ...)` 的 tagName **必须包含 `-`**，否则浏览器拒绝
- 同一个 `panelClass` 想挂多个 `name`：必须每次都传**同一个已 define 的 `tagName`**（一个类只能对应一个标签名），之后每个 name 各有一个实例；内部状态请存在实例字段，**别**写到模块级全局
- `icon` 不是 URL，必须是 `images/<分类>/<名>.svg` 且该名字已在对应分类注册（自定义图标先 `IconRegistry.registerIcon(name, svg, { category: IconCategory.Actbar })`），否则 ActivityBar 上看不见图标（看不到点不开就没法激活）
- `order` 不生效，面板按注册顺序排列
- `leftPanels`/`rightPanels` 数组里写的是内置面板名（如 `'props'`），不是中文标签，自定义面板也放不进去
- `autoActivatePropertyPanel: true` 配合 `sidebarStyle: 'none'` 时无效（侧边栏被整体关闭了）

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/ui/sidebar-panel.md`
- 示例：
- `vjcad/example/ui/14sidebarpanel.js` — 自定义面板
- `vjcad/example/ui/16menucustom.js` — `leftPanels / rightPanels` 配置
