diff --git a/demo.html b/demo.html
index 50557c9..71575dc 100644
--- a/demo.html
+++ b/demo.html
@@ -1,24 +1,20 @@
+
BIM Engine SDK Demo
-
-
+
+
+
\ No newline at end of file
diff --git a/dist/bim-engine-sdk.es.js b/dist/bim-engine-sdk.es.js
index 8f95973..cf7c8aa 100644
--- a/dist/bim-engine-sdk.es.js
+++ b/dist/bim-engine-sdk.es.js
@@ -1,20 +1,210 @@
-class e {
+(function(){"use strict";try{if(typeof document<"u"){var o=document.createElement("style");o.appendChild(document.createTextNode(".bim-engine-wrapper{position:relative;width:100%;height:100%;font-family:sans-serif;color:#333;padding:20px;background-color:#e16969;border-radius:8px;border:1px solid #e0e0e0;box-sizing:border-box}.bim-engine-opt-btn-container{position:absolute;bottom:20px;left:50%;transform:translate(-50%);z-index:100}.toolbar-container{display:flex;align-items:center;max-width:100%;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.toolbar-container::-webkit-scrollbar{display:none}.opt-btn-group{overflow:hidden;display:flex;align-items:center;flex-shrink:0;background-color:#111111e0;border-radius:4px;padding:4px 8px}.has-divider{margin-right:16px}.opt-btn-wrapper{position:relative}.opt-btn{display:flex;flex-direction:column;align-items:center;justify-content:center;width:50px;min-height:50px;padding:4px;cursor:pointer;color:#ccc;transition:all .2s;border-bottom:2px solid transparent}.opt-btn:hover{background-color:#444;color:#fff}.opt-btn.active{background-color:#ffffff26;color:#fff;border-bottom:2px solid #fff}.opt-btn.disabled{opacity:.5;cursor:not-allowed}.opt-btn-icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.opt-btn-icon svg{width:100%;height:100%}.opt-btn-label{font-size:10px;margin-top:2px}.opt-btn-arrow{font-size:8px;position:absolute;top:2px;right:2px;opacity:.6;transition:transform .2s ease}.opt-btn-arrow.rotated{transform:rotate(180deg)}.opt-btn.no-label .opt-btn-arrow{top:2px;right:2px}.opt-btn-dropdown{position:fixed;transform:translate(-50%,-100%);background-color:#111111e0;border-radius:4px;overflow:hidden;box-shadow:0 4px 12px #0000004d;min-width:50px;z-index:9999;display:flex;flex-direction:column}.opt-btn-dropdown-item{display:flex;flex-direction:column;align-items:center;justify-content:center;color:#b3b4b4;cursor:pointer;transition:background .2s;white-space:nowrap;min-width:50px;min-height:50px;padding:4px}.opt-btn-dropdown-item:last-child{border-bottom:none}.opt-btn-dropdown-item:hover{background-color:#444;color:#fff}.opt-btn-dropdown-item .opt-btn-icon.small{width:30px;height:30px;margin-right:0;margin-bottom:4px}.opt-btn-dropdown-item span{font-size:10px}.opt-btn.no-label .opt-btn-icon{width:32px;height:32px}")),document.head.appendChild(o)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})();
+class a {
container;
- constructor(n) {
- const i = typeof n == "string" ? document.getElementById(n) : n;
- if (!i) throw new Error("Container not found");
- this.container = i, this.init();
+ options;
+ // 改用 Array 存储 Group,方便控制顺序
+ groups = [];
+ activeBtnIds = /* @__PURE__ */ new Set();
+ btnRefs = /* @__PURE__ */ new Map();
+ dropdownElement = null;
+ hoverTimeout = null;
+ DEFAULT_ICON = '';
+ constructor(t) {
+ const e = typeof t.container == "string" ? document.getElementById(t.container) : t.container;
+ if (!e) throw new Error("Container not found");
+ this.container = e, this.options = {
+ showLabel: !0,
+ visibility: {},
+ ...t
+ }, this.initContainer();
+ }
+ initContainer() {
+ this.container.innerHTML = "";
+ }
+ /**
+ * 添加按钮组
+ * @param groupId 组ID
+ * @param beforeGroupId 在哪个组之前插入(可选),不传则插入到最后
+ */
+ addGroup(t, e) {
+ if (this.groups.some((n) => n.id === t)) {
+ console.warn("Group " + t + " already exists");
+ return;
+ }
+ const i = { id: t, buttons: [] };
+ if (e) {
+ const n = this.groups.findIndex((s) => s.id === e);
+ n !== -1 ? this.groups.splice(n, 0, i) : (console.warn(`Target group ${e} not found, appending ${t} to end.`), this.groups.push(i));
+ } else
+ this.groups.push(i);
+ }
+ /**
+ * 添加按钮
+ * @param config 按钮配置(必须包含 groupId,可选包含 parentId)
+ */
+ addButton(t) {
+ const { groupId: e, parentId: i } = t;
+ if (!e)
+ throw new Error(`Button ${t.id} config must contain 'groupId'`);
+ const n = this.groups.find((o) => o.id === e);
+ if (!n)
+ throw new Error(`Group ${e} not found. Please call addGroup first.`);
+ const s = {
+ ...t,
+ children: t.children || []
+ };
+ if (i) {
+ const o = this.findButton(n.buttons, i);
+ if (!o)
+ throw new Error(`Parent button ${i} not found in group ${e}`);
+ o.children || (o.children = []), o.children.push(s);
+ } else
+ n.buttons.push(s);
+ }
+ findButton(t, e) {
+ for (const i of t) {
+ if (i.id === e) return i;
+ if (i.children) {
+ const n = this.findButton(i.children, e);
+ if (n) return n;
+ }
+ }
+ }
+ async init() {
+ const { homeButton: t } = await import("./index-CAJWny5G.mjs"), { locationButton: e } = await import("./index-C12x1apF.mjs"), { walkMenuButton: i } = await import("./index-Wpi9Br9A.mjs"), { walkPersonButton: n } = await import("./index-BXbORK0j.mjs"), { walkBirdButton: s } = await import("./index-Djlk5GIH.mjs"), { settingButton: o } = await import("./index-DsRG5l_h.mjs"), { infoButton: r } = await import("./index-DvZ5eiUH.mjs");
+ this.addGroup("group-1"), this.addButton(t), this.addButton(i), this.addButton(n), this.addButton(s), this.addButton(e), this.addGroup("group-2"), this.addButton(o), this.addButton(r), this.render();
+ }
+ render() {
+ this.container.innerHTML = "", this.btnRefs.clear();
+ const t = document.createElement("div");
+ t.className = "toolbar-container", this.groups.forEach((e, i) => {
+ const n = this.renderGroup(e, i, this.groups.length);
+ t.appendChild(n);
+ }), this.container.appendChild(t);
+ }
+ renderGroup(t, e, i) {
+ const n = document.createElement("div");
+ return n.className = "opt-btn-group", e < i - 1 && n.classList.add("has-divider"), t.buttons.forEach((s) => {
+ if (this.isVisible(s.id)) {
+ const o = this.renderButton(s);
+ n.appendChild(o);
+ }
+ }), n;
+ }
+ renderButton(t) {
+ const e = document.createElement("div");
+ e.className = "opt-btn-wrapper";
+ const i = document.createElement("div");
+ i.className = "opt-btn", this.activeBtnIds.has(t.id) && i.classList.add("active"), t.disabled && i.classList.add("disabled"), this.options.showLabel || (i.classList.add("no-label"), t.label && (i.title = t.label));
+ const n = document.createElement("div");
+ if (n.className = "opt-btn-icon", n.innerHTML = this.getIcon(t.icon), i.appendChild(n), this.options.showLabel && t.label) {
+ const s = document.createElement("span");
+ s.className = "opt-btn-label", s.textContent = t.label, i.appendChild(s);
+ }
+ if (t.children && t.children.length > 0) {
+ const s = document.createElement("span");
+ s.className = "opt-btn-arrow", s.textContent = "▼", i.appendChild(s);
+ }
+ return i.addEventListener("click", () => this.handleClick(t)), i.addEventListener("mouseenter", () => this.handleMouseEnter(t, i)), i.addEventListener("mouseleave", () => this.handleMouseLeave()), this.btnRefs.set(t.id, i), e.appendChild(i), e;
+ }
+ handleClick(t) {
+ t.disabled || (!t.children || t.children.length === 0) && (t.keepActive && (this.activeBtnIds.has(t.id) ? this.activeBtnIds.delete(t.id) : this.activeBtnIds.add(t.id), this.updateButtonState(t.id)), this.closeDropdown(), t.onClick && t.onClick(t));
+ }
+ handleSubClick(t) {
+ t.keepActive && (this.activeBtnIds.has(t.id) ? this.activeBtnIds.delete(t.id) : this.activeBtnIds.add(t.id), this.updateButtonState(t.id)), this.closeDropdown(), t.onClick && t.onClick(t);
+ }
+ handleMouseEnter(t, e) {
+ if (this.hoverTimeout && (clearTimeout(this.hoverTimeout), this.hoverTimeout = null), t.children && t.children.length > 0) {
+ this.showDropdown(t, e);
+ const i = e.querySelector(".opt-btn-arrow");
+ i && i.classList.add("rotated");
+ } else
+ this.closeDropdown();
+ }
+ handleMouseLeave() {
+ this.hoverTimeout = window.setTimeout(() => {
+ this.closeDropdown();
+ }, 200);
+ }
+ showDropdown(t, e) {
+ if (this.closeDropdown(), !t.children) return;
+ const i = document.createElement("div");
+ i.className = "opt-btn-dropdown";
+ const n = e.getBoundingClientRect(), s = n.left + n.width / 2;
+ i.style.top = n.top - 8 + "px", i.style.left = s + "px", t.children.forEach((o) => {
+ if (this.isVisible(o.id)) {
+ const r = this.renderDropdownItem(o);
+ i.appendChild(r);
+ }
+ }), i.addEventListener("mouseenter", () => {
+ this.hoverTimeout && (clearTimeout(this.hoverTimeout), this.hoverTimeout = null);
+ }), i.addEventListener("mouseleave", () => this.handleMouseLeave()), document.body.appendChild(i), this.dropdownElement = i;
+ }
+ renderDropdownItem(t) {
+ const e = document.createElement("div");
+ e.className = "opt-btn-dropdown-item";
+ const i = document.createElement("div");
+ if (i.className = "opt-btn-icon small", i.innerHTML = this.getIcon(t.icon), e.appendChild(i), this.options.showLabel) {
+ const n = document.createElement("span");
+ n.textContent = t.label, e.appendChild(n);
+ }
+ return e.addEventListener("click", (n) => {
+ n.stopPropagation(), this.handleSubClick(t);
+ }), e;
+ }
+ closeDropdown() {
+ this.dropdownElement && (this.dropdownElement.remove(), this.dropdownElement = null), this.btnRefs.forEach((t) => {
+ const e = t.querySelector(".opt-btn-arrow");
+ e && e.classList.remove("rotated");
+ });
+ }
+ updateButtonState(t) {
+ const e = this.btnRefs.get(t);
+ e && (this.activeBtnIds.has(t) ? e.classList.add("active") : e.classList.remove("active"));
+ }
+ getIcon(t) {
+ return t || this.DEFAULT_ICON;
+ }
+ isVisible(t) {
+ return this.options.visibility?.[t] !== !1;
+ }
+ destroy() {
+ this.closeDropdown(), this.hoverTimeout && clearTimeout(this.hoverTimeout), this.container.innerHTML = "", this.btnRefs.clear(), this.activeBtnIds.clear(), this.groups = [];
+ }
+}
+class c {
+ container;
+ optBtnGroups = null;
+ constructor(t) {
+ const e = typeof t == "string" ? document.getElementById(t) : t;
+ if (!e) throw new Error("Container not found");
+ this.container = e, this.init();
}
init() {
- this.container.innerHTML = `
-
-
BimEngine
-
这是一个纯 TypeScript 组件入口。
-
- `;
+ this.container.innerHTML = "";
+ const t = document.createElement("div");
+ t.className = "bim-engine-wrapper";
+ const e = document.createElement("h1");
+ e.textContent = "BimEngine", e.className = "bim-engine-title";
+ const i = document.createElement("p");
+ i.textContent = "这是一个使用BIM-ENGINE。", i.className = "bim-engine-desc";
+ const n = document.createElement("div");
+ n.id = "opt-btn-groups", n.className = "bim-engine-opt-btn-container", t.appendChild(e), t.appendChild(i), t.appendChild(n), this.container.appendChild(t), this.initOptBtnGroups(n);
+ }
+ initOptBtnGroups(t) {
+ this.optBtnGroups = new a({
+ container: t,
+ showLabel: !0
+ }), this.optBtnGroups.init().catch((e) => {
+ console.error("Failed to initialize OptBtnGroups:", e);
+ });
+ }
+ destroy() {
+ this.optBtnGroups && (this.optBtnGroups.destroy(), this.optBtnGroups = null), this.container.innerHTML = "";
}
}
export {
- e as BimEngine
+ c as BimEngine,
+ a as OptBtnGroups
};
//# sourceMappingURL=bim-engine-sdk.es.js.map
diff --git a/dist/bim-engine-sdk.es.js.map b/dist/bim-engine-sdk.es.js.map
index db621fb..1f6acae 100644
--- a/dist/bim-engine-sdk.es.js.map
+++ b/dist/bim-engine-sdk.es.js.map
@@ -1 +1 @@
-{"version":3,"file":"bim-engine-sdk.es.js","sources":["../src/BimEngine.ts"],"sourcesContent":["export class BimEngine {\n private container: HTMLElement;\n\n constructor(container: HTMLElement | string) {\n const el = typeof container === 'string' ? document.getElementById(container) : container;\n if (!el) throw new Error('Container not found');\n this.container = el;\n this.init();\n }\n\n private init() {\n this.container.innerHTML = `\n \n
BimEngine
\n
这是一个纯 TypeScript 组件入口。
\n
\n `;\n }\n}"],"names":["BimEngine","container","el"],"mappings":"AAAO,MAAMA,EAAU;AAAA,EACX;AAAA,EAER,YAAYC,GAAiC;AACzC,UAAMC,IAAK,OAAOD,KAAc,WAAW,SAAS,eAAeA,CAAS,IAAIA;AAChF,QAAI,CAACC,EAAI,OAAM,IAAI,MAAM,qBAAqB;AAC9C,SAAK,YAAYA,GACjB,KAAK,KAAA;AAAA,EACT;AAAA,EAEQ,OAAO;AACX,SAAK,UAAU,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/B;AACJ;"}
\ No newline at end of file
+{"version":3,"file":"bim-engine-sdk.es.js","sources":["../src/toolbar/index.ts","../src/bim-engine.ts"],"sourcesContent":["import './index.css';\nimport type {\n OptButton,\n ButtonGroup,\n OptBtnGroupsOptions,\n ButtonConfig\n} from './index.type';\n\nexport class OptBtnGroups {\n private container: HTMLElement;\n private options: OptBtnGroupsOptions;\n // 改用 Array 存储 Group,方便控制顺序\n private groups: ButtonGroup[] = [];\n private activeBtnIds: Set = new Set();\n private btnRefs: Map = new Map();\n private dropdownElement: HTMLElement | null = null;\n private hoverTimeout: number | null = null;\n\n private readonly DEFAULT_ICON = '';\n\n constructor(options: OptBtnGroupsOptions) {\n const el = typeof options.container === 'string'\n ? document.getElementById(options.container)\n : options.container;\n\n if (!el) throw new Error('Container not found');\n\n this.container = el;\n this.options = {\n showLabel: true,\n visibility: {},\n ...options\n };\n\n this.initContainer();\n }\n\n private initContainer(): void {\n this.container.innerHTML = '';\n }\n\n /**\n * 添加按钮组\n * @param groupId 组ID\n * @param beforeGroupId 在哪个组之前插入(可选),不传则插入到最后\n */\n public addGroup(groupId: string, beforeGroupId?: string): void {\n if (this.groups.some(g => g.id === groupId)) {\n console.warn('Group ' + groupId + ' already exists');\n return;\n }\n\n const newGroup: ButtonGroup = { id: groupId, buttons: [] };\n\n if (beforeGroupId) {\n const index = this.groups.findIndex(g => g.id === beforeGroupId);\n if (index !== -1) {\n this.groups.splice(index, 0, newGroup);\n } else {\n console.warn(`Target group ${beforeGroupId} not found, appending ${groupId} to end.`);\n this.groups.push(newGroup);\n }\n } else {\n this.groups.push(newGroup);\n }\n }\n\n /**\n * 添加按钮\n * @param config 按钮配置(必须包含 groupId,可选包含 parentId)\n */\n public addButton(config: ButtonConfig): void {\n const { groupId, parentId } = config;\n\n if (!groupId) {\n throw new Error(`Button ${config.id} config must contain 'groupId'`);\n }\n\n const group = this.groups.find(g => g.id === groupId);\n if (!group) {\n throw new Error(`Group ${groupId} not found. Please call addGroup first.`);\n }\n\n const button: OptButton = {\n ...config,\n children: config.children || []\n };\n\n if (parentId) {\n // Add as sub-button\n const parentBtn = this.findButton(group.buttons, parentId);\n if (!parentBtn) {\n throw new Error(`Parent button ${parentId} not found in group ${groupId}`);\n }\n if (!parentBtn.children) {\n parentBtn.children = [];\n }\n parentBtn.children.push(button);\n } else {\n // Add as main button\n group.buttons.push(button);\n }\n }\n\n private findButton(buttons: OptButton[], id: string): OptButton | undefined {\n for (const btn of buttons) {\n if (btn.id === id) return btn;\n if (btn.children) {\n const found = this.findButton(btn.children, id);\n if (found) return found;\n }\n }\n return undefined;\n }\n\n public async init(): Promise {\n const { homeButton } = await import('./buttons/home');\n const { locationButton } = await import('./buttons/location');\n const { walkMenuButton } = await import('./buttons/walk/walk-menu');\n const { walkPersonButton } = await import('./buttons/walk/walk-person');\n const { walkBirdButton } = await import('./buttons/walk/walk-bird');\n const { settingButton } = await import('./buttons/setting');\n const { infoButton } = await import('./buttons/info');\n\n // 添加组1\n this.addGroup('group-1');\n this.addButton(homeButton);\n this.addButton(walkMenuButton);\n this.addButton(walkPersonButton);\n this.addButton(walkBirdButton);\n this.addButton(locationButton);\n this.addGroup('group-2');\n this.addButton(settingButton);\n this.addButton(infoButton);\n this.render();\n }\n\n public render(): void {\n this.container.innerHTML = '';\n this.btnRefs.clear();\n\n const wrapper = document.createElement('div');\n wrapper.className = 'toolbar-container';\n\n // 直接遍历数组,顺序由 addGroup 控制\n this.groups.forEach((group, index) => {\n const groupElement = this.renderGroup(group, index, this.groups.length);\n wrapper.appendChild(groupElement);\n });\n\n this.container.appendChild(wrapper);\n }\n\n private renderGroup(group: ButtonGroup, index: number, total: number): HTMLElement {\n const groupEl = document.createElement('div');\n groupEl.className = 'opt-btn-group';\n\n if (index < total - 1) {\n groupEl.classList.add('has-divider');\n }\n\n group.buttons.forEach(button => {\n if (this.isVisible(button.id)) {\n const btnWrapper = this.renderButton(button);\n groupEl.appendChild(btnWrapper);\n }\n });\n\n return groupEl;\n }\n\n private renderButton(button: OptButton): HTMLElement {\n const wrapper = document.createElement('div');\n wrapper.className = 'opt-btn-wrapper';\n\n const btnEl = document.createElement('div');\n btnEl.className = 'opt-btn';\n\n if (this.activeBtnIds.has(button.id)) {\n btnEl.classList.add('active');\n }\n\n if (button.disabled) {\n btnEl.classList.add('disabled');\n }\n\n if (!this.options.showLabel) {\n btnEl.classList.add('no-label');\n if (button.label) {\n btnEl.title = button.label;\n }\n }\n\n const icon = document.createElement('div');\n icon.className = 'opt-btn-icon';\n icon.innerHTML = this.getIcon(button.icon);\n btnEl.appendChild(icon);\n\n if (this.options.showLabel && button.label) {\n const label = document.createElement('span');\n label.className = 'opt-btn-label';\n label.textContent = button.label;\n btnEl.appendChild(label);\n }\n\n if (button.children && button.children.length > 0) {\n const arrow = document.createElement('span');\n arrow.className = 'opt-btn-arrow';\n arrow.textContent = '▼';\n btnEl.appendChild(arrow);\n }\n\n btnEl.addEventListener('click', () => this.handleClick(button));\n btnEl.addEventListener('mouseenter', () => this.handleMouseEnter(button, btnEl));\n btnEl.addEventListener('mouseleave', () => this.handleMouseLeave());\n\n this.btnRefs.set(button.id, btnEl);\n\n wrapper.appendChild(btnEl);\n return wrapper;\n }\n\n private handleClick(button: OptButton): void {\n if (button.disabled) return;\n\n if (!button.children || button.children.length === 0) {\n if (button.keepActive) {\n const wasActive = this.activeBtnIds.has(button.id);\n if (wasActive) {\n this.activeBtnIds.delete(button.id);\n } else {\n this.activeBtnIds.add(button.id);\n }\n this.updateButtonState(button.id);\n }\n\n this.closeDropdown();\n\n if (button.onClick) {\n button.onClick(button);\n }\n }\n }\n\n private handleSubClick(button: OptButton): void {\n if (button.keepActive) {\n const wasActive = this.activeBtnIds.has(button.id);\n if (wasActive) {\n this.activeBtnIds.delete(button.id);\n } else {\n this.activeBtnIds.add(button.id);\n }\n this.updateButtonState(button.id);\n }\n\n this.closeDropdown();\n\n if (button.onClick) {\n button.onClick(button);\n }\n }\n\n private handleMouseEnter(button: OptButton, btnEl: HTMLElement): void {\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n this.hoverTimeout = null;\n }\n\n if (button.children && button.children.length > 0) {\n this.showDropdown(button, btnEl);\n\n const arrow = btnEl.querySelector('.opt-btn-arrow');\n if (arrow) {\n arrow.classList.add('rotated');\n }\n } else {\n this.closeDropdown();\n }\n }\n\n private handleMouseLeave(): void {\n this.hoverTimeout = window.setTimeout(() => {\n this.closeDropdown();\n }, 200);\n }\n\n private showDropdown(button: OptButton, btnEl: HTMLElement): void {\n this.closeDropdown();\n\n if (!button.children) return;\n\n const dropdown = document.createElement('div');\n dropdown.className = 'opt-btn-dropdown';\n\n const rect = btnEl.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n\n dropdown.style.top = rect.top - 8 + 'px';\n dropdown.style.left = centerX + 'px';\n\n button.children.forEach(subBtn => {\n if (this.isVisible(subBtn.id)) {\n const item = this.renderDropdownItem(subBtn);\n dropdown.appendChild(item);\n }\n });\n\n dropdown.addEventListener('mouseenter', () => {\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n this.hoverTimeout = null;\n }\n });\n\n dropdown.addEventListener('mouseleave', () => this.handleMouseLeave());\n\n document.body.appendChild(dropdown);\n this.dropdownElement = dropdown;\n }\n\n private renderDropdownItem(button: OptButton): HTMLElement {\n const item = document.createElement('div');\n item.className = 'opt-btn-dropdown-item';\n\n const icon = document.createElement('div');\n icon.className = 'opt-btn-icon small';\n icon.innerHTML = this.getIcon(button.icon);\n item.appendChild(icon);\n\n if (this.options.showLabel) {\n const label = document.createElement('span');\n label.textContent = button.label;\n item.appendChild(label);\n }\n\n item.addEventListener('click', (e) => {\n e.stopPropagation();\n this.handleSubClick(button);\n });\n\n return item;\n }\n\n private closeDropdown(): void {\n if (this.dropdownElement) {\n this.dropdownElement.remove();\n this.dropdownElement = null;\n }\n\n this.btnRefs.forEach(btnEl => {\n const arrow = btnEl.querySelector('.opt-btn-arrow');\n if (arrow) {\n arrow.classList.remove('rotated');\n }\n });\n }\n\n private updateButtonState(buttonId: string): void {\n const btnEl = this.btnRefs.get(buttonId);\n if (btnEl) {\n if (this.activeBtnIds.has(buttonId)) {\n btnEl.classList.add('active');\n } else {\n btnEl.classList.remove('active');\n }\n }\n }\n\n private getIcon(icon?: string): string {\n return icon || this.DEFAULT_ICON;\n }\n\n private isVisible(id: string): boolean {\n return this.options.visibility?.[id] !== false;\n }\n\n public destroy(): void {\n this.closeDropdown();\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n }\n this.container.innerHTML = '';\n this.btnRefs.clear();\n this.activeBtnIds.clear();\n this.groups = []; // 清空数组\n }\n}","import './bim-engine.css';\nimport { OptBtnGroups } from './toolbar';\n\nexport class BimEngine {\n private container: HTMLElement;\n private optBtnGroups: OptBtnGroups | null = null;\n\n constructor(container: HTMLElement | string) {\n const el = typeof container === 'string' ? document.getElementById(container) : container;\n if (!el) throw new Error('Container not found');\n this.container = el;\n this.init();\n }\n\n private init() {\n // 1. 清空容器可能存在的旧内容\n this.container.innerHTML = '';\n\n // 2. 创建外层容器 div\n const wrapper = document.createElement('div');\n wrapper.className = 'bim-engine-wrapper';\n\n // 3. 创建标题 h1\n const title = document.createElement('h1');\n title.textContent = 'BimEngine';\n title.className = 'bim-engine-title';\n\n // 4. 创建段落 p\n const desc = document.createElement('p');\n desc.textContent = '这是一个使用BIM-ENGINE。';\n desc.className = 'bim-engine-desc';\n\n // 6. 创建操作按钮组容器\n const btnGroupContainer = document.createElement('div');\n btnGroupContainer.id = 'opt-btn-groups';\n btnGroupContainer.className = 'bim-engine-opt-btn-container';\n\n // 7. 组装元素\n wrapper.appendChild(title);\n wrapper.appendChild(desc);\n wrapper.appendChild(btnGroupContainer); // 将按钮组放入 wrapper 中\n\n // 8. 挂载到主容器\n this.container.appendChild(wrapper);\n\n // 9. 初始化操作按钮组\n this.initOptBtnGroups(btnGroupContainer);\n }\n\n private initOptBtnGroups(container: HTMLElement) {\n this.optBtnGroups = new OptBtnGroups({\n container,\n showLabel: true\n });\n\n // 初始化并加载默认按钮\n this.optBtnGroups.init().catch(err => {\n console.error('Failed to initialize OptBtnGroups:', err);\n });\n }\n\n public destroy() {\n if (this.optBtnGroups) {\n this.optBtnGroups.destroy();\n this.optBtnGroups = null;\n }\n this.container.innerHTML = '';\n }\n}"],"names":["OptBtnGroups","options","el","groupId","beforeGroupId","g","newGroup","index","config","parentId","group","button","parentBtn","buttons","id","btn","found","homeButton","locationButton","walkMenuButton","walkPersonButton","walkBirdButton","settingButton","infoButton","wrapper","groupElement","total","groupEl","btnWrapper","btnEl","icon","label","arrow","dropdown","rect","centerX","subBtn","item","e","buttonId","BimEngine","container","title","desc","btnGroupContainer","err"],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/bim-engine-sdk.umd.js b/dist/bim-engine-sdk.umd.js
index 5821c3c..a924607 100644
--- a/dist/bim-engine-sdk.umd.js
+++ b/dist/bim-engine-sdk.umd.js
@@ -1,7 +1,3 @@
-(function(e,n){typeof exports=="object"&&typeof module<"u"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(e=typeof globalThis<"u"?globalThis:e||self,n(e.BimEngineSDK={}))})(this,(function(e){"use strict";class n{container;constructor(i){const t=typeof i=="string"?document.getElementById(i):i;if(!t)throw new Error("Container not found");this.container=t,this.init()}init(){this.container.innerHTML=`
-
-
BimEngine
-
这是一个纯 TypeScript 组件入口。
-
- `}}e.BimEngine=n,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}));
+(function(){"use strict";try{if(typeof document<"u"){var o=document.createElement("style");o.appendChild(document.createTextNode(".bim-engine-wrapper{position:relative;width:100%;height:100%;font-family:sans-serif;color:#333;padding:20px;background-color:#e16969;border-radius:8px;border:1px solid #e0e0e0;box-sizing:border-box}.bim-engine-opt-btn-container{position:absolute;bottom:20px;left:50%;transform:translate(-50%);z-index:100}.toolbar-container{display:flex;align-items:center;max-width:100%;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.toolbar-container::-webkit-scrollbar{display:none}.opt-btn-group{overflow:hidden;display:flex;align-items:center;flex-shrink:0;background-color:#111111e0;border-radius:4px;padding:4px 8px}.has-divider{margin-right:16px}.opt-btn-wrapper{position:relative}.opt-btn{display:flex;flex-direction:column;align-items:center;justify-content:center;width:50px;min-height:50px;padding:4px;cursor:pointer;color:#ccc;transition:all .2s;border-bottom:2px solid transparent}.opt-btn:hover{background-color:#444;color:#fff}.opt-btn.active{background-color:#ffffff26;color:#fff;border-bottom:2px solid #fff}.opt-btn.disabled{opacity:.5;cursor:not-allowed}.opt-btn-icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.opt-btn-icon svg{width:100%;height:100%}.opt-btn-label{font-size:10px;margin-top:2px}.opt-btn-arrow{font-size:8px;position:absolute;top:2px;right:2px;opacity:.6;transition:transform .2s ease}.opt-btn-arrow.rotated{transform:rotate(180deg)}.opt-btn.no-label .opt-btn-arrow{top:2px;right:2px}.opt-btn-dropdown{position:fixed;transform:translate(-50%,-100%);background-color:#111111e0;border-radius:4px;overflow:hidden;box-shadow:0 4px 12px #0000004d;min-width:50px;z-index:9999;display:flex;flex-direction:column}.opt-btn-dropdown-item{display:flex;flex-direction:column;align-items:center;justify-content:center;color:#b3b4b4;cursor:pointer;transition:background .2s;white-space:nowrap;min-width:50px;min-height:50px;padding:4px}.opt-btn-dropdown-item:last-child{border-bottom:none}.opt-btn-dropdown-item:hover{background-color:#444;color:#fff}.opt-btn-dropdown-item .opt-btn-icon.small{width:30px;height:30px;margin-right:0;margin-bottom:4px}.opt-btn-dropdown-item span{font-size:10px}.opt-btn.no-label .opt-btn-icon{width:32px;height:32px}")),document.head.appendChild(o)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})();
+(function(l,d){typeof exports=="object"&&typeof module<"u"?d(exports):typeof define=="function"&&define.amd?define(["exports"],d):(l=typeof globalThis<"u"?globalThis:l||self,d(l.LyzBimEngineSDK={}))})(this,(function(l){"use strict";class d{container;options;groups=[];activeBtnIds=new Set;btnRefs=new Map;dropdownElement=null;hoverTimeout=null;DEFAULT_ICON='';constructor(e){const t=typeof e.container=="string"?document.getElementById(e.container):e.container;if(!t)throw new Error("Container not found");this.container=t,this.options={showLabel:!0,visibility:{},...e},this.initContainer()}initContainer(){this.container.innerHTML=""}addGroup(e,t){if(this.groups.some(o=>o.id===e)){console.warn("Group "+e+" already exists");return}const n={id:e,buttons:[]};if(t){const o=this.groups.findIndex(i=>i.id===t);o!==-1?this.groups.splice(o,0,n):(console.warn(`Target group ${t} not found, appending ${e} to end.`),this.groups.push(n))}else this.groups.push(n)}addButton(e){const{groupId:t,parentId:n}=e;if(!t)throw new Error(`Button ${e.id} config must contain 'groupId'`);const o=this.groups.find(r=>r.id===t);if(!o)throw new Error(`Group ${t} not found. Please call addGroup first.`);const i={...e,children:e.children||[]};if(n){const r=this.findButton(o.buttons,n);if(!r)throw new Error(`Parent button ${n} not found in group ${t}`);r.children||(r.children=[]),r.children.push(i)}else o.buttons.push(i)}findButton(e,t){for(const n of e){if(n.id===t)return n;if(n.children){const o=this.findButton(n.children,t);if(o)return o}}}async init(){const{homeButton:e}=await Promise.resolve().then(()=>h),{locationButton:t}=await Promise.resolve().then(()=>p),{walkMenuButton:n}=await Promise.resolve().then(()=>u),{walkPersonButton:o}=await Promise.resolve().then(()=>m),{walkBirdButton:i}=await Promise.resolve().then(()=>v),{settingButton:r}=await Promise.resolve().then(()=>f),{infoButton:a}=await Promise.resolve().then(()=>g);this.addGroup("group-1"),this.addButton(e),this.addButton(n),this.addButton(o),this.addButton(i),this.addButton(t),this.addGroup("group-2"),this.addButton(r),this.addButton(a),this.render()}render(){this.container.innerHTML="",this.btnRefs.clear();const e=document.createElement("div");e.className="toolbar-container",this.groups.forEach((t,n)=>{const o=this.renderGroup(t,n,this.groups.length);e.appendChild(o)}),this.container.appendChild(e)}renderGroup(e,t,n){const o=document.createElement("div");return o.className="opt-btn-group",t{if(this.isVisible(i.id)){const r=this.renderButton(i);o.appendChild(r)}}),o}renderButton(e){const t=document.createElement("div");t.className="opt-btn-wrapper";const n=document.createElement("div");n.className="opt-btn",this.activeBtnIds.has(e.id)&&n.classList.add("active"),e.disabled&&n.classList.add("disabled"),this.options.showLabel||(n.classList.add("no-label"),e.label&&(n.title=e.label));const o=document.createElement("div");if(o.className="opt-btn-icon",o.innerHTML=this.getIcon(e.icon),n.appendChild(o),this.options.showLabel&&e.label){const i=document.createElement("span");i.className="opt-btn-label",i.textContent=e.label,n.appendChild(i)}if(e.children&&e.children.length>0){const i=document.createElement("span");i.className="opt-btn-arrow",i.textContent="▼",n.appendChild(i)}return n.addEventListener("click",()=>this.handleClick(e)),n.addEventListener("mouseenter",()=>this.handleMouseEnter(e,n)),n.addEventListener("mouseleave",()=>this.handleMouseLeave()),this.btnRefs.set(e.id,n),t.appendChild(n),t}handleClick(e){e.disabled||(!e.children||e.children.length===0)&&(e.keepActive&&(this.activeBtnIds.has(e.id)?this.activeBtnIds.delete(e.id):this.activeBtnIds.add(e.id),this.updateButtonState(e.id)),this.closeDropdown(),e.onClick&&e.onClick(e))}handleSubClick(e){e.keepActive&&(this.activeBtnIds.has(e.id)?this.activeBtnIds.delete(e.id):this.activeBtnIds.add(e.id),this.updateButtonState(e.id)),this.closeDropdown(),e.onClick&&e.onClick(e)}handleMouseEnter(e,t){if(this.hoverTimeout&&(clearTimeout(this.hoverTimeout),this.hoverTimeout=null),e.children&&e.children.length>0){this.showDropdown(e,t);const n=t.querySelector(".opt-btn-arrow");n&&n.classList.add("rotated")}else this.closeDropdown()}handleMouseLeave(){this.hoverTimeout=window.setTimeout(()=>{this.closeDropdown()},200)}showDropdown(e,t){if(this.closeDropdown(),!e.children)return;const n=document.createElement("div");n.className="opt-btn-dropdown";const o=t.getBoundingClientRect(),i=o.left+o.width/2;n.style.top=o.top-8+"px",n.style.left=i+"px",e.children.forEach(r=>{if(this.isVisible(r.id)){const a=this.renderDropdownItem(r);n.appendChild(a)}}),n.addEventListener("mouseenter",()=>{this.hoverTimeout&&(clearTimeout(this.hoverTimeout),this.hoverTimeout=null)}),n.addEventListener("mouseleave",()=>this.handleMouseLeave()),document.body.appendChild(n),this.dropdownElement=n}renderDropdownItem(e){const t=document.createElement("div");t.className="opt-btn-dropdown-item";const n=document.createElement("div");if(n.className="opt-btn-icon small",n.innerHTML=this.getIcon(e.icon),t.appendChild(n),this.options.showLabel){const o=document.createElement("span");o.textContent=e.label,t.appendChild(o)}return t.addEventListener("click",o=>{o.stopPropagation(),this.handleSubClick(e)}),t}closeDropdown(){this.dropdownElement&&(this.dropdownElement.remove(),this.dropdownElement=null),this.btnRefs.forEach(e=>{const t=e.querySelector(".opt-btn-arrow");t&&t.classList.remove("rotated")})}updateButtonState(e){const t=this.btnRefs.get(e);t&&(this.activeBtnIds.has(e)?t.classList.add("active"):t.classList.remove("active"))}getIcon(e){return e||this.DEFAULT_ICON}isVisible(e){return this.options.visibility?.[e]!==!1}destroy(){this.closeDropdown(),this.hoverTimeout&&clearTimeout(this.hoverTimeout),this.container.innerHTML="",this.btnRefs.clear(),this.activeBtnIds.clear(),this.groups=[]}}class c{container;optBtnGroups=null;constructor(e){const t=typeof e=="string"?document.getElementById(e):e;if(!t)throw new Error("Container not found");this.container=t,this.init()}init(){this.container.innerHTML="";const e=document.createElement("div");e.className="bim-engine-wrapper";const t=document.createElement("h1");t.textContent="BimEngine",t.className="bim-engine-title";const n=document.createElement("p");n.textContent="这是一个使用BIM-ENGINE。",n.className="bim-engine-desc";const o=document.createElement("div");o.id="opt-btn-groups",o.className="bim-engine-opt-btn-container",e.appendChild(t),e.appendChild(n),e.appendChild(o),this.container.appendChild(e),this.initOptBtnGroups(o)}initOptBtnGroups(e){this.optBtnGroups=new d({container:e,showLabel:!0}),this.optBtnGroups.init().catch(t=>{console.error("Failed to initialize OptBtnGroups:",t)})}destroy(){this.optBtnGroups&&(this.optBtnGroups.destroy(),this.optBtnGroups=null),this.container.innerHTML=""}}const h=Object.freeze(Object.defineProperty({__proto__:null,homeButton:{id:"home",groupId:"group-1",type:"button",label:"首页",icon:'',keepActive:!0,onClick:s=>{console.log("首页按钮被点击:",s.id)}}},Symbol.toStringTag,{value:"Module"})),p=Object.freeze(Object.defineProperty({__proto__:null,locationButton:{id:"location",groupId:"group-1",type:"button",label:"定位",icon:'',keepActive:!1,onClick:s=>{console.log("定位按钮被点击:",s.id)}}},Symbol.toStringTag,{value:"Module"})),u=Object.freeze(Object.defineProperty({__proto__:null,walkMenuButton:{id:"walk",groupId:"group-1",type:"menu",label:"漫游",icon:'',keepActive:!0,onClick:s=>{console.log("漫游按钮被点击:",s.id)}}},Symbol.toStringTag,{value:"Module"})),m=Object.freeze(Object.defineProperty({__proto__:null,walkPersonButton:{id:"walk-person",groupId:"group-1",parentId:"walk",type:"button",label:"人视漫游",icon:'',onClick:s=>{console.log("人视漫游被点击:",s.id)}}},Symbol.toStringTag,{value:"Module"})),v=Object.freeze(Object.defineProperty({__proto__:null,walkBirdButton:{id:"walk-bird",groupId:"group-1",parentId:"walk",type:"button",label:"鸟瞰漫游",icon:'',onClick:s=>{console.log("鸟瞰漫游被点击:",s.id)}}},Symbol.toStringTag,{value:"Module"})),f=Object.freeze(Object.defineProperty({__proto__:null,settingButton:{id:"setting",groupId:"group-2",type:"button",label:"设置",icon:'',keepActive:!1,onClick:s=>{console.log("设置按钮被点击:",s.id)}}},Symbol.toStringTag,{value:"Module"})),g=Object.freeze(Object.defineProperty({__proto__:null,infoButton:{id:"info",groupId:"group-2",type:"button",label:"信息",icon:'',keepActive:!1,onClick:s=>{console.log("信息按钮被点击:",s.id)}}},Symbol.toStringTag,{value:"Module"}));l.BimEngine=c,l.OptBtnGroups=d,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
//# sourceMappingURL=bim-engine-sdk.umd.js.map
diff --git a/dist/bim-engine-sdk.umd.js.map b/dist/bim-engine-sdk.umd.js.map
index cba37e2..2980017 100644
--- a/dist/bim-engine-sdk.umd.js.map
+++ b/dist/bim-engine-sdk.umd.js.map
@@ -1 +1 @@
-{"version":3,"file":"bim-engine-sdk.umd.js","sources":["../src/BimEngine.ts"],"sourcesContent":["export class BimEngine {\n private container: HTMLElement;\n\n constructor(container: HTMLElement | string) {\n const el = typeof container === 'string' ? document.getElementById(container) : container;\n if (!el) throw new Error('Container not found');\n this.container = el;\n this.init();\n }\n\n private init() {\n this.container.innerHTML = `\n \n
BimEngine
\n
这是一个纯 TypeScript 组件入口。
\n
\n `;\n }\n}"],"names":["BimEngine","container","el"],"mappings":"qOAAO,MAAMA,CAAU,CACX,UAER,YAAYC,EAAiC,CACzC,MAAMC,EAAK,OAAOD,GAAc,SAAW,SAAS,eAAeA,CAAS,EAAIA,EAChF,GAAI,CAACC,EAAI,MAAM,IAAI,MAAM,qBAAqB,EAC9C,KAAK,UAAYA,EACjB,KAAK,KAAA,CACT,CAEQ,MAAO,CACX,KAAK,UAAU,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA,SAM/B,CACJ"}
\ No newline at end of file
+{"version":3,"file":"bim-engine-sdk.umd.js","sources":["../src/toolbar/index.ts","../src/bim-engine.ts","../src/toolbar/buttons/home/index.ts","../src/toolbar/buttons/location/index.ts","../src/toolbar/buttons/walk/walk-menu/index.ts","../src/toolbar/buttons/walk/walk-person/index.ts","../src/toolbar/buttons/walk/walk-bird/index.ts","../src/toolbar/buttons/setting/index.ts","../src/toolbar/buttons/info/index.ts"],"sourcesContent":["import './index.css';\nimport type {\n OptButton,\n ButtonGroup,\n OptBtnGroupsOptions,\n ButtonConfig\n} from './index.type';\n\nexport class OptBtnGroups {\n private container: HTMLElement;\n private options: OptBtnGroupsOptions;\n // 改用 Array 存储 Group,方便控制顺序\n private groups: ButtonGroup[] = [];\n private activeBtnIds: Set = new Set();\n private btnRefs: Map = new Map();\n private dropdownElement: HTMLElement | null = null;\n private hoverTimeout: number | null = null;\n\n private readonly DEFAULT_ICON = '';\n\n constructor(options: OptBtnGroupsOptions) {\n const el = typeof options.container === 'string'\n ? document.getElementById(options.container)\n : options.container;\n\n if (!el) throw new Error('Container not found');\n\n this.container = el;\n this.options = {\n showLabel: true,\n visibility: {},\n ...options\n };\n\n this.initContainer();\n }\n\n private initContainer(): void {\n this.container.innerHTML = '';\n }\n\n /**\n * 添加按钮组\n * @param groupId 组ID\n * @param beforeGroupId 在哪个组之前插入(可选),不传则插入到最后\n */\n public addGroup(groupId: string, beforeGroupId?: string): void {\n if (this.groups.some(g => g.id === groupId)) {\n console.warn('Group ' + groupId + ' already exists');\n return;\n }\n\n const newGroup: ButtonGroup = { id: groupId, buttons: [] };\n\n if (beforeGroupId) {\n const index = this.groups.findIndex(g => g.id === beforeGroupId);\n if (index !== -1) {\n this.groups.splice(index, 0, newGroup);\n } else {\n console.warn(`Target group ${beforeGroupId} not found, appending ${groupId} to end.`);\n this.groups.push(newGroup);\n }\n } else {\n this.groups.push(newGroup);\n }\n }\n\n /**\n * 添加按钮\n * @param config 按钮配置(必须包含 groupId,可选包含 parentId)\n */\n public addButton(config: ButtonConfig): void {\n const { groupId, parentId } = config;\n\n if (!groupId) {\n throw new Error(`Button ${config.id} config must contain 'groupId'`);\n }\n\n const group = this.groups.find(g => g.id === groupId);\n if (!group) {\n throw new Error(`Group ${groupId} not found. Please call addGroup first.`);\n }\n\n const button: OptButton = {\n ...config,\n children: config.children || []\n };\n\n if (parentId) {\n // Add as sub-button\n const parentBtn = this.findButton(group.buttons, parentId);\n if (!parentBtn) {\n throw new Error(`Parent button ${parentId} not found in group ${groupId}`);\n }\n if (!parentBtn.children) {\n parentBtn.children = [];\n }\n parentBtn.children.push(button);\n } else {\n // Add as main button\n group.buttons.push(button);\n }\n }\n\n private findButton(buttons: OptButton[], id: string): OptButton | undefined {\n for (const btn of buttons) {\n if (btn.id === id) return btn;\n if (btn.children) {\n const found = this.findButton(btn.children, id);\n if (found) return found;\n }\n }\n return undefined;\n }\n\n public async init(): Promise {\n const { homeButton } = await import('./buttons/home');\n const { locationButton } = await import('./buttons/location');\n const { walkMenuButton } = await import('./buttons/walk/walk-menu');\n const { walkPersonButton } = await import('./buttons/walk/walk-person');\n const { walkBirdButton } = await import('./buttons/walk/walk-bird');\n const { settingButton } = await import('./buttons/setting');\n const { infoButton } = await import('./buttons/info');\n\n // 添加组1\n this.addGroup('group-1');\n this.addButton(homeButton);\n this.addButton(walkMenuButton);\n this.addButton(walkPersonButton);\n this.addButton(walkBirdButton);\n this.addButton(locationButton);\n this.addGroup('group-2');\n this.addButton(settingButton);\n this.addButton(infoButton);\n this.render();\n }\n\n public render(): void {\n this.container.innerHTML = '';\n this.btnRefs.clear();\n\n const wrapper = document.createElement('div');\n wrapper.className = 'toolbar-container';\n\n // 直接遍历数组,顺序由 addGroup 控制\n this.groups.forEach((group, index) => {\n const groupElement = this.renderGroup(group, index, this.groups.length);\n wrapper.appendChild(groupElement);\n });\n\n this.container.appendChild(wrapper);\n }\n\n private renderGroup(group: ButtonGroup, index: number, total: number): HTMLElement {\n const groupEl = document.createElement('div');\n groupEl.className = 'opt-btn-group';\n\n if (index < total - 1) {\n groupEl.classList.add('has-divider');\n }\n\n group.buttons.forEach(button => {\n if (this.isVisible(button.id)) {\n const btnWrapper = this.renderButton(button);\n groupEl.appendChild(btnWrapper);\n }\n });\n\n return groupEl;\n }\n\n private renderButton(button: OptButton): HTMLElement {\n const wrapper = document.createElement('div');\n wrapper.className = 'opt-btn-wrapper';\n\n const btnEl = document.createElement('div');\n btnEl.className = 'opt-btn';\n\n if (this.activeBtnIds.has(button.id)) {\n btnEl.classList.add('active');\n }\n\n if (button.disabled) {\n btnEl.classList.add('disabled');\n }\n\n if (!this.options.showLabel) {\n btnEl.classList.add('no-label');\n if (button.label) {\n btnEl.title = button.label;\n }\n }\n\n const icon = document.createElement('div');\n icon.className = 'opt-btn-icon';\n icon.innerHTML = this.getIcon(button.icon);\n btnEl.appendChild(icon);\n\n if (this.options.showLabel && button.label) {\n const label = document.createElement('span');\n label.className = 'opt-btn-label';\n label.textContent = button.label;\n btnEl.appendChild(label);\n }\n\n if (button.children && button.children.length > 0) {\n const arrow = document.createElement('span');\n arrow.className = 'opt-btn-arrow';\n arrow.textContent = '▼';\n btnEl.appendChild(arrow);\n }\n\n btnEl.addEventListener('click', () => this.handleClick(button));\n btnEl.addEventListener('mouseenter', () => this.handleMouseEnter(button, btnEl));\n btnEl.addEventListener('mouseleave', () => this.handleMouseLeave());\n\n this.btnRefs.set(button.id, btnEl);\n\n wrapper.appendChild(btnEl);\n return wrapper;\n }\n\n private handleClick(button: OptButton): void {\n if (button.disabled) return;\n\n if (!button.children || button.children.length === 0) {\n if (button.keepActive) {\n const wasActive = this.activeBtnIds.has(button.id);\n if (wasActive) {\n this.activeBtnIds.delete(button.id);\n } else {\n this.activeBtnIds.add(button.id);\n }\n this.updateButtonState(button.id);\n }\n\n this.closeDropdown();\n\n if (button.onClick) {\n button.onClick(button);\n }\n }\n }\n\n private handleSubClick(button: OptButton): void {\n if (button.keepActive) {\n const wasActive = this.activeBtnIds.has(button.id);\n if (wasActive) {\n this.activeBtnIds.delete(button.id);\n } else {\n this.activeBtnIds.add(button.id);\n }\n this.updateButtonState(button.id);\n }\n\n this.closeDropdown();\n\n if (button.onClick) {\n button.onClick(button);\n }\n }\n\n private handleMouseEnter(button: OptButton, btnEl: HTMLElement): void {\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n this.hoverTimeout = null;\n }\n\n if (button.children && button.children.length > 0) {\n this.showDropdown(button, btnEl);\n\n const arrow = btnEl.querySelector('.opt-btn-arrow');\n if (arrow) {\n arrow.classList.add('rotated');\n }\n } else {\n this.closeDropdown();\n }\n }\n\n private handleMouseLeave(): void {\n this.hoverTimeout = window.setTimeout(() => {\n this.closeDropdown();\n }, 200);\n }\n\n private showDropdown(button: OptButton, btnEl: HTMLElement): void {\n this.closeDropdown();\n\n if (!button.children) return;\n\n const dropdown = document.createElement('div');\n dropdown.className = 'opt-btn-dropdown';\n\n const rect = btnEl.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n\n dropdown.style.top = rect.top - 8 + 'px';\n dropdown.style.left = centerX + 'px';\n\n button.children.forEach(subBtn => {\n if (this.isVisible(subBtn.id)) {\n const item = this.renderDropdownItem(subBtn);\n dropdown.appendChild(item);\n }\n });\n\n dropdown.addEventListener('mouseenter', () => {\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n this.hoverTimeout = null;\n }\n });\n\n dropdown.addEventListener('mouseleave', () => this.handleMouseLeave());\n\n document.body.appendChild(dropdown);\n this.dropdownElement = dropdown;\n }\n\n private renderDropdownItem(button: OptButton): HTMLElement {\n const item = document.createElement('div');\n item.className = 'opt-btn-dropdown-item';\n\n const icon = document.createElement('div');\n icon.className = 'opt-btn-icon small';\n icon.innerHTML = this.getIcon(button.icon);\n item.appendChild(icon);\n\n if (this.options.showLabel) {\n const label = document.createElement('span');\n label.textContent = button.label;\n item.appendChild(label);\n }\n\n item.addEventListener('click', (e) => {\n e.stopPropagation();\n this.handleSubClick(button);\n });\n\n return item;\n }\n\n private closeDropdown(): void {\n if (this.dropdownElement) {\n this.dropdownElement.remove();\n this.dropdownElement = null;\n }\n\n this.btnRefs.forEach(btnEl => {\n const arrow = btnEl.querySelector('.opt-btn-arrow');\n if (arrow) {\n arrow.classList.remove('rotated');\n }\n });\n }\n\n private updateButtonState(buttonId: string): void {\n const btnEl = this.btnRefs.get(buttonId);\n if (btnEl) {\n if (this.activeBtnIds.has(buttonId)) {\n btnEl.classList.add('active');\n } else {\n btnEl.classList.remove('active');\n }\n }\n }\n\n private getIcon(icon?: string): string {\n return icon || this.DEFAULT_ICON;\n }\n\n private isVisible(id: string): boolean {\n return this.options.visibility?.[id] !== false;\n }\n\n public destroy(): void {\n this.closeDropdown();\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n }\n this.container.innerHTML = '';\n this.btnRefs.clear();\n this.activeBtnIds.clear();\n this.groups = []; // 清空数组\n }\n}","import './bim-engine.css';\nimport { OptBtnGroups } from './toolbar';\n\nexport class BimEngine {\n private container: HTMLElement;\n private optBtnGroups: OptBtnGroups | null = null;\n\n constructor(container: HTMLElement | string) {\n const el = typeof container === 'string' ? document.getElementById(container) : container;\n if (!el) throw new Error('Container not found');\n this.container = el;\n this.init();\n }\n\n private init() {\n // 1. 清空容器可能存在的旧内容\n this.container.innerHTML = '';\n\n // 2. 创建外层容器 div\n const wrapper = document.createElement('div');\n wrapper.className = 'bim-engine-wrapper';\n\n // 3. 创建标题 h1\n const title = document.createElement('h1');\n title.textContent = 'BimEngine';\n title.className = 'bim-engine-title';\n\n // 4. 创建段落 p\n const desc = document.createElement('p');\n desc.textContent = '这是一个使用BIM-ENGINE。';\n desc.className = 'bim-engine-desc';\n\n // 6. 创建操作按钮组容器\n const btnGroupContainer = document.createElement('div');\n btnGroupContainer.id = 'opt-btn-groups';\n btnGroupContainer.className = 'bim-engine-opt-btn-container';\n\n // 7. 组装元素\n wrapper.appendChild(title);\n wrapper.appendChild(desc);\n wrapper.appendChild(btnGroupContainer); // 将按钮组放入 wrapper 中\n\n // 8. 挂载到主容器\n this.container.appendChild(wrapper);\n\n // 9. 初始化操作按钮组\n this.initOptBtnGroups(btnGroupContainer);\n }\n\n private initOptBtnGroups(container: HTMLElement) {\n this.optBtnGroups = new OptBtnGroups({\n container,\n showLabel: true\n });\n\n // 初始化并加载默认按钮\n this.optBtnGroups.init().catch(err => {\n console.error('Failed to initialize OptBtnGroups:', err);\n });\n }\n\n public destroy() {\n if (this.optBtnGroups) {\n this.optBtnGroups.destroy();\n this.optBtnGroups = null;\n }\n this.container.innerHTML = '';\n }\n}","import type { ButtonConfig } from '../../index.type';\n\n/**\n * 首页按钮配置\n */\nexport const homeButton: ButtonConfig = {\n id: 'home',\n groupId: 'group-1',\n type: 'button',\n label: '首页',\n icon: '',\n keepActive: true,\n onClick: (button) => {\n console.log('首页按钮被点击:', button.id);\n }\n};\n","import type { ButtonConfig } from '../../index.type';\n\n/**\n * 定位按钮配置\n */\nexport const locationButton: ButtonConfig = {\n id: 'location',\n groupId: 'group-1',\n type: 'button',\n label: '定位',\n icon: '',\n keepActive: false,\n onClick: (button) => {\n console.log('定位按钮被点击:', button.id);\n }\n};\n","import type { ButtonConfig } from '../../../index.type';\n\n/**\n * 漫游菜单按钮配置\n */\nexport const walkMenuButton: ButtonConfig = {\n id: 'walk',\n groupId: 'group-1',\n type: 'menu',\n label: '漫游',\n icon: '',\n keepActive: true,\n onClick: (button) => {\n console.log('漫游按钮被点击:', button.id);\n }\n};\n","import type { ButtonConfig } from '../../../index.type';\n\nexport const walkPersonButton: ButtonConfig = {\n id: 'walk-person',\n groupId: 'group-1',\n parentId: 'walk',\n type: 'button',\n label: '人视漫游',\n icon: '',\n onClick: (button) => {\n console.log('人视漫游被点击:', button.id);\n }\n};\n","import type { ButtonConfig } from '../../../index.type';\n\nexport const walkBirdButton: ButtonConfig = {\n id: 'walk-bird',\n groupId: 'group-1',\n parentId: 'walk',\n type: 'button',\n label: '鸟瞰漫游',\n icon: '',\n onClick: (button) => {\n console.log('鸟瞰漫游被点击:', button.id);\n }\n};\n","import type { ButtonConfig } from '../../index.type';\n\n/**\n * 定位按钮配置\n */\nexport const settingButton: ButtonConfig = {\n id: 'setting',\n groupId: 'group-2',\n type: 'button',\n label: '设置',\n icon: '',\n keepActive: false,\n onClick: (button) => {\n console.log('设置按钮被点击:', button.id);\n }\n};\n","import type { ButtonConfig } from '../../index.type';\n\n/**\n * 定位按钮配置\n */\nexport const infoButton: ButtonConfig = {\n id: 'info',\n groupId: 'group-2',\n type: 'button',\n label: '信息',\n icon: '',\n keepActive: false,\n onClick: (button) => {\n console.log('信息按钮被点击:', button.id);\n }\n};\n"],"names":["OptBtnGroups","options","el","groupId","beforeGroupId","g","newGroup","index","config","parentId","group","button","parentBtn","buttons","id","btn","found","homeButton","index$6","locationButton","index$5","walkMenuButton","index$4","walkPersonButton","index$3","walkBirdButton","index$2","settingButton","index$1","infoButton","wrapper","groupElement","total","groupEl","btnWrapper","btnEl","icon","label","arrow","dropdown","rect","centerX","subBtn","item","e","buttonId","BimEngine","container","title","desc","btnGroupContainer","err"],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/index.d.ts b/dist/index.d.ts
index 69282a1..60dda3f 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -1,7 +1,101 @@
export declare class BimEngine {
private container;
+ private optBtnGroups;
constructor(container: HTMLElement | string);
private init;
+ private initOptBtnGroups;
+ destroy(): void;
+}
+
+/**
+ * 按钮配置接口(用于外部定义按钮)
+ */
+declare interface ButtonConfig {
+ id: string;
+ type: ButtonType;
+ label: string;
+ icon?: string;
+ keepActive?: boolean;
+ disabled?: boolean;
+ onClick?: (button: OptButton) => void;
+ children?: ButtonConfig[];
+ groupId?: string;
+ parentId?: string;
+}
+
+/**
+ * 按钮组接口
+ */
+export declare interface ButtonGroup {
+ id: string;
+ buttons: OptButton[];
+}
+
+declare type ButtonType = 'button' | 'menu';
+
+/**
+ * 点击事件载荷
+ */
+export declare interface ClickPayload {
+ button: OptButton;
+ action: 'activate' | 'deactivate' | 'trigger';
+ isActive?: boolean;
+}
+
+export declare class OptBtnGroups {
+ private container;
+ private options;
+ private groups;
+ private activeBtnIds;
+ private btnRefs;
+ private dropdownElement;
+ private hoverTimeout;
+ private readonly DEFAULT_ICON;
+ constructor(options: OptBtnGroupsOptions);
+ private initContainer;
+ /**
+ * 添加按钮组
+ * @param groupId 组ID
+ * @param beforeGroupId 在哪个组之前插入(可选),不传则插入到最后
+ */
+ addGroup(groupId: string, beforeGroupId?: string): void;
+ /**
+ * 添加按钮
+ * @param config 按钮配置(必须包含 groupId,可选包含 parentId)
+ */
+ addButton(config: ButtonConfig): void;
+ private findButton;
+ init(): Promise;
+ render(): void;
+ private renderGroup;
+ private renderButton;
+ private handleClick;
+ private handleSubClick;
+ private handleMouseEnter;
+ private handleMouseLeave;
+ private showDropdown;
+ private renderDropdownItem;
+ private closeDropdown;
+ private updateButtonState;
+ private getIcon;
+ private isVisible;
+ destroy(): void;
+}
+
+/**
+ * OptBtnGroups 配置选项
+ */
+export declare interface OptBtnGroupsOptions {
+ container: HTMLElement | string;
+ showLabel?: boolean;
+ visibility?: Record;
+}
+
+/**
+ * 操作按钮接口(内部使用,继承配置)
+ */
+export declare interface OptButton extends ButtonConfig {
+ children?: OptButton[];
}
export { }
diff --git a/docs/OptBtnGroups.md b/docs/OptBtnGroups.md
new file mode 100644
index 0000000..cc6770a
--- /dev/null
+++ b/docs/OptBtnGroups.md
@@ -0,0 +1,160 @@
+# OptBtnGroups 组件使用文档
+
+## 📋 组件简介
+
+`OptBtnGroups` 是一个纯 TypeScript 实现的操作按钮组组件,使用原生 DOM API 开发,无任何框架依赖。
+
+## 🎯 核心特性
+
+- ✅ 纯 TypeScript + 原生 DOM API
+- ✅ 多组分隔,独立背景块
+- ✅ 二级下拉菜单
+- ✅ 多选激活状态
+- ✅ 可控的标签显示
+- ✅ 按钮显隐控制
+- ✅ SVG 图标支持
+- ✅ 响应式横向滚动
+
+## 🚀 快速开始
+
+### 1. 安装/引入
+
+```html
+
+
+
+
+```
+
+```typescript
+// ES Module 方式
+import { OptBtnGroups } from 'bim-engine-sdk';
+```
+
+### 2. 基础使用
+
+```typescript
+const optBtnGroups = new OptBtnGroups({
+ container: 'btn-container', // 容器 ID 或 HTMLElement
+ showLabel: true, // 显示文字标签
+ onClick: (payload) => {
+ console.log('点击了按钮:', payload.button.label);
+ console.log('操作类型:', payload.action);
+ if (payload.isActive !== undefined) {
+ console.log('激活状态:', payload.isActive);
+ }
+ }
+});
+```
+
+### 3. 控制显隐
+
+```typescript
+new OptBtnGroups({
+ container: 'btn-container',
+ visibility: {
+ '1-1': true, // 显示
+ '3-2-1': false // 隐藏
+ },
+ onClick: (payload) => console.log(payload)
+});
+```
+
+## 📝 API 文档
+
+### 构造函数参数
+
+```typescript
+interface OptBtnGroupsOptions {
+ container: HTMLElement | string; // 必需:容器
+ showLabel?: boolean; // 可选:显示标签(默认 true)
+ visibility?: Record; // 可选:显隐控制
+ onClick?: (payload: ClickPayload) => void; // 可选:点击回调
+}
+```
+
+### 点击事件载荷
+
+```typescript
+interface ClickPayload {
+ button: OptButton; // 被点击的按钮
+ action: 'trigger' | 'activate' | 'deactivate'; // 操作类型
+ isActive?: boolean; // 激活状态(仅 keepActive 按钮有值)
+}
+```
+
+- `trigger`: 触发型操作(无状态保持)
+- `activate`: 激活按钮
+- `deactivate`: 取消激活
+
+### 方法
+
+```typescript
+// 销毁组件
+optBtnGroups.destroy();
+```
+
+## 🛠️ 二次开发
+
+### 修改内置按钮
+
+编辑 `src/OptBtnGroups.ts` 中的 `defaultGroups`:
+
+```typescript
+private readonly defaultGroups: ButtonGroup[] = [
+ {
+ id: 'my-group',
+ buttons: [
+ {
+ id: 'home',
+ label: '首页',
+ icon: '',
+ keepActive: true
+ }
+ ]
+ }
+];
+```
+
+### 修改样式
+
+编辑 `src/OptBtnGroups.css`:
+
+```css
+.opt-btn {
+ width: 50px; /* 按钮宽度 */
+ min-height: 50px; /* 最小高度 */
+}
+
+.opt-btn.active {
+ background-color: rgba(255, 255, 255, 0.15); /* 激活背景 */
+ border-bottom: 2px solid #fff; /* 底部指示条 */
+}
+```
+
+## 📦 构建
+
+```bash
+# 安装依赖
+npm install
+
+# 开发模式
+npm run dev
+
+# 构建生产版本
+npm run build
+```
+
+## 🎨 示例
+
+查看 `demo-optbtn.html` 获取完整示例。
+
+## 🤝 贡献
+
+修改组件时请遵循:
+1. 保持中文注释
+2. 使用原生 DOM API(无框架依赖)
+3. TypeScript 严格模式
+4. 测试多种使用场景
diff --git a/src/BimEngine.ts b/src/BimEngine.ts
deleted file mode 100644
index 3d37a26..0000000
--- a/src/BimEngine.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export class BimEngine {
- private container: HTMLElement;
-
- constructor(container: HTMLElement | string) {
- const el = typeof container === 'string' ? document.getElementById(container) : container;
- if (!el) throw new Error('Container not found');
- this.container = el;
- this.init();
- }
-
- private init() {
- this.container.innerHTML = `
-
-
BimEngine
-
这是一个纯 TypeScript 组件入口。
-
- `;
- }
-}
\ No newline at end of file
diff --git a/src/bim-engine.css b/src/bim-engine.css
new file mode 100644
index 0000000..c9400f1
--- /dev/null
+++ b/src/bim-engine.css
@@ -0,0 +1,26 @@
+.bim-engine-wrapper {
+ position: relative;
+ /* 添加相对定位作为参照物 */
+ width: 100%;
+ height: 100%;
+ font-family: sans-serif;
+ color: #333;
+ padding: 20px;
+ background-color: #e16969;
+ border-radius: 8px;
+ border: 1px solid #e0e0e0;
+ box-sizing: border-box;
+ /* 确保 padding 不会撑大容器 */
+}
+
+/* ... (中间代码不变) ... */
+
+/* 操作按钮组容器 */
+.bim-engine-opt-btn-container {
+ position: absolute;
+ /* 改为绝对定位 */
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 100;
+}
\ No newline at end of file
diff --git a/src/bim-engine.ts b/src/bim-engine.ts
new file mode 100644
index 0000000..d0765ef
--- /dev/null
+++ b/src/bim-engine.ts
@@ -0,0 +1,69 @@
+import './bim-engine.css';
+import { OptBtnGroups } from './toolbar';
+
+export class BimEngine {
+ private container: HTMLElement;
+ private optBtnGroups: OptBtnGroups | null = null;
+
+ constructor(container: HTMLElement | string) {
+ const el = typeof container === 'string' ? document.getElementById(container) : container;
+ if (!el) throw new Error('Container not found');
+ this.container = el;
+ this.init();
+ }
+
+ private init() {
+ // 1. 清空容器可能存在的旧内容
+ this.container.innerHTML = '';
+
+ // 2. 创建外层容器 div
+ const wrapper = document.createElement('div');
+ wrapper.className = 'bim-engine-wrapper';
+
+ // 3. 创建标题 h1
+ const title = document.createElement('h1');
+ title.textContent = 'BimEngine';
+ title.className = 'bim-engine-title';
+
+ // 4. 创建段落 p
+ const desc = document.createElement('p');
+ desc.textContent = '这是一个使用BIM-ENGINE。';
+ desc.className = 'bim-engine-desc';
+
+ // 6. 创建操作按钮组容器
+ const btnGroupContainer = document.createElement('div');
+ btnGroupContainer.id = 'opt-btn-groups';
+ btnGroupContainer.className = 'bim-engine-opt-btn-container';
+
+ // 7. 组装元素
+ wrapper.appendChild(title);
+ wrapper.appendChild(desc);
+ wrapper.appendChild(btnGroupContainer); // 将按钮组放入 wrapper 中
+
+ // 8. 挂载到主容器
+ this.container.appendChild(wrapper);
+
+ // 9. 初始化操作按钮组
+ this.initOptBtnGroups(btnGroupContainer);
+ }
+
+ private initOptBtnGroups(container: HTMLElement) {
+ this.optBtnGroups = new OptBtnGroups({
+ container,
+ showLabel: true
+ });
+
+ // 初始化并加载默认按钮
+ this.optBtnGroups.init().catch(err => {
+ console.error('Failed to initialize OptBtnGroups:', err);
+ });
+ }
+
+ public destroy() {
+ if (this.optBtnGroups) {
+ this.optBtnGroups.destroy();
+ this.optBtnGroups = null;
+ }
+ this.container.innerHTML = '';
+ }
+}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index abe2f3a..17ef664 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,4 @@
-import { BimEngine } from './BimEngine';
-
+import { BimEngine } from './bim-engine';
+export { OptBtnGroups } from './toolbar';
+export type { OptButton, ButtonGroup, OptBtnGroupsOptions, ClickPayload } from './toolbar/index.type';
export { BimEngine };
diff --git a/src/toolbar/buttons/home/index.ts b/src/toolbar/buttons/home/index.ts
new file mode 100644
index 0000000..d151aa2
--- /dev/null
+++ b/src/toolbar/buttons/home/index.ts
@@ -0,0 +1,16 @@
+import type { ButtonConfig } from '../../index.type';
+
+/**
+ * 首页按钮配置
+ */
+export const homeButton: ButtonConfig = {
+ id: 'home',
+ groupId: 'group-1',
+ type: 'button',
+ label: '首页',
+ icon: '',
+ keepActive: true,
+ onClick: (button) => {
+ console.log('首页按钮被点击:', button.id);
+ }
+};
diff --git a/src/toolbar/buttons/info/index.ts b/src/toolbar/buttons/info/index.ts
new file mode 100644
index 0000000..ad990c3
--- /dev/null
+++ b/src/toolbar/buttons/info/index.ts
@@ -0,0 +1,16 @@
+import type { ButtonConfig } from '../../index.type';
+
+/**
+ * 定位按钮配置
+ */
+export const infoButton: ButtonConfig = {
+ id: 'info',
+ groupId: 'group-2',
+ type: 'button',
+ label: '信息',
+ icon: '',
+ keepActive: false,
+ onClick: (button) => {
+ console.log('信息按钮被点击:', button.id);
+ }
+};
diff --git a/src/toolbar/buttons/location/index.ts b/src/toolbar/buttons/location/index.ts
new file mode 100644
index 0000000..0099756
--- /dev/null
+++ b/src/toolbar/buttons/location/index.ts
@@ -0,0 +1,16 @@
+import type { ButtonConfig } from '../../index.type';
+
+/**
+ * 定位按钮配置
+ */
+export const locationButton: ButtonConfig = {
+ id: 'location',
+ groupId: 'group-1',
+ type: 'button',
+ label: '定位',
+ icon: '',
+ keepActive: false,
+ onClick: (button) => {
+ console.log('定位按钮被点击:', button.id);
+ }
+};
diff --git a/src/toolbar/buttons/setting/index.ts b/src/toolbar/buttons/setting/index.ts
new file mode 100644
index 0000000..bf3d459
--- /dev/null
+++ b/src/toolbar/buttons/setting/index.ts
@@ -0,0 +1,16 @@
+import type { ButtonConfig } from '../../index.type';
+
+/**
+ * 定位按钮配置
+ */
+export const settingButton: ButtonConfig = {
+ id: 'setting',
+ groupId: 'group-2',
+ type: 'button',
+ label: '设置',
+ icon: '',
+ keepActive: false,
+ onClick: (button) => {
+ console.log('设置按钮被点击:', button.id);
+ }
+};
diff --git a/src/toolbar/buttons/walk/walk-bird/index.ts b/src/toolbar/buttons/walk/walk-bird/index.ts
new file mode 100644
index 0000000..6ead661
--- /dev/null
+++ b/src/toolbar/buttons/walk/walk-bird/index.ts
@@ -0,0 +1,13 @@
+import type { ButtonConfig } from '../../../index.type';
+
+export const walkBirdButton: ButtonConfig = {
+ id: 'walk-bird',
+ groupId: 'group-1',
+ parentId: 'walk',
+ type: 'button',
+ label: '鸟瞰漫游',
+ icon: '',
+ onClick: (button) => {
+ console.log('鸟瞰漫游被点击:', button.id);
+ }
+};
diff --git a/src/toolbar/buttons/walk/walk-menu/index.ts b/src/toolbar/buttons/walk/walk-menu/index.ts
new file mode 100644
index 0000000..5c97cee
--- /dev/null
+++ b/src/toolbar/buttons/walk/walk-menu/index.ts
@@ -0,0 +1,16 @@
+import type { ButtonConfig } from '../../../index.type';
+
+/**
+ * 漫游菜单按钮配置
+ */
+export const walkMenuButton: ButtonConfig = {
+ id: 'walk',
+ groupId: 'group-1',
+ type: 'menu',
+ label: '漫游',
+ icon: '',
+ keepActive: true,
+ onClick: (button) => {
+ console.log('漫游按钮被点击:', button.id);
+ }
+};
diff --git a/src/toolbar/buttons/walk/walk-person/index.ts b/src/toolbar/buttons/walk/walk-person/index.ts
new file mode 100644
index 0000000..65290ab
--- /dev/null
+++ b/src/toolbar/buttons/walk/walk-person/index.ts
@@ -0,0 +1,13 @@
+import type { ButtonConfig } from '../../../index.type';
+
+export const walkPersonButton: ButtonConfig = {
+ id: 'walk-person',
+ groupId: 'group-1',
+ parentId: 'walk',
+ type: 'button',
+ label: '人视漫游',
+ icon: '',
+ onClick: (button) => {
+ console.log('人视漫游被点击:', button.id);
+ }
+};
diff --git a/src/toolbar/index.css b/src/toolbar/index.css
new file mode 100644
index 0000000..1010599
--- /dev/null
+++ b/src/toolbar/index.css
@@ -0,0 +1,181 @@
+/* 容器样式 */
+.toolbar-container {
+ display: flex;
+ align-items: center;
+ /* 容器本身不需要背景 */
+ max-width: 100%;
+ overflow-x: auto;
+ scrollbar-width: none;
+ /* Firefox 隐藏滚动条 */
+ -ms-overflow-style: none;
+ /* IE 10+ 隐藏滚动条 */
+}
+
+.toolbar-container::-webkit-scrollbar {
+ display: none;
+ /* Chrome/Safari 隐藏滚动条 */
+}
+
+/* 按钮组样式 */
+.opt-btn-group {
+ border-radius: 4px;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ background-color: rgba(17, 17, 17, 0.88);
+ /* 每个组独立的背景 */
+ border-radius: 4px;
+ padding: 4px 8px;
+}
+
+.has-divider {
+ margin-right: 16px;
+ /* 增加右边距来分隔组 */
+}
+
+/* 按钮包装器 */
+.opt-btn-wrapper {
+ position: relative;
+}
+
+/* 按钮样式 */
+.opt-btn {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 50px;
+ min-height: 50px;
+ padding: 4px;
+ cursor: pointer;
+ color: #ccc;
+ transition: all 0.2s;
+ border-bottom: 2px solid transparent;
+ /* 默认透明底边 */
+}
+
+.opt-btn:hover {
+ background-color: #444;
+ color: #fff;
+}
+
+.opt-btn.active {
+ background-color: rgba(255, 255, 255, 0.15);
+ /* 白色半透明背景 */
+ color: #fff;
+ border-bottom: 2px solid #fff;
+ /* 纯白色底部横条 */
+}
+
+.opt-btn.disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* 图标样式 */
+.opt-btn-icon {
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ /* 防止图标被压缩 */
+}
+
+.opt-btn-icon svg {
+ width: 100%;
+ height: 100%;
+}
+
+/* 文字标签样式 */
+.opt-btn-label {
+ font-size: 10px;
+ margin-top: 2px;
+}
+
+/* 下拉箭头样式 */
+.opt-btn-arrow {
+ font-size: 8px;
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ opacity: 0.6;
+ transition: transform 0.2s ease;
+}
+
+/* 箭头旋转 (菜单展开时) */
+.opt-btn-arrow.rotated {
+ transform: rotate(180deg);
+}
+
+/* 无标签模式调整 */
+.opt-btn.no-label .opt-btn-arrow {
+ top: 2px;
+ right: 2px;
+}
+
+/* 下拉菜单样式 */
+.opt-btn-dropdown {
+ position: fixed;
+ /* 固定定位 */
+ transform: translate(-50%, -100%);
+ /* 水平居中并向上移动 */
+ background-color: rgba(17, 17, 17, 0.88);
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+ min-width: 50px;
+ z-index: 9999;
+ /* 高层级 */
+ display: flex;
+ flex-direction: column;
+}
+
+/* 下拉菜单项样式 */
+.opt-btn-dropdown-item {
+ display: flex;
+ flex-direction: column;
+ /* 垂直布局 */
+ align-items: center;
+ justify-content: center;
+ color: #b3b4b4;
+ cursor: pointer;
+ transition: background 0.2s;
+ white-space: nowrap;
+ min-width: 50px;
+ min-height: 50px;
+ padding: 4px;
+}
+
+.opt-btn-dropdown-item:last-child {
+ border-bottom: none;
+ /* 移除最后一项的分隔线 */
+}
+
+.opt-btn-dropdown-item:hover {
+ background-color: #444;
+ color: #fff;
+}
+
+.opt-btn-dropdown-item .opt-btn-icon.small {
+ width: 30px;
+ /* 与主按钮图标大小一致 */
+ height: 30px;
+ margin-right: 0;
+ /* 移除右边距 */
+ margin-bottom: 4px;
+ /* 添加下边距 */
+}
+
+.opt-btn-dropdown-item span {
+ font-size: 10px;
+ /* 较小的文字 */
+}
+
+/* 无标签模式调整 */
+.opt-btn.no-label .opt-btn-icon {
+ width: 32px;
+ height: 32px;
+}
\ No newline at end of file
diff --git a/src/toolbar/index.ts b/src/toolbar/index.ts
new file mode 100644
index 0000000..c278ae0
--- /dev/null
+++ b/src/toolbar/index.ts
@@ -0,0 +1,387 @@
+import './index.css';
+import type {
+ OptButton,
+ ButtonGroup,
+ OptBtnGroupsOptions,
+ ButtonConfig
+} from './index.type';
+
+export class OptBtnGroups {
+ private container: HTMLElement;
+ private options: OptBtnGroupsOptions;
+ // 改用 Array 存储 Group,方便控制顺序
+ private groups: ButtonGroup[] = [];
+ private activeBtnIds: Set = new Set();
+ private btnRefs: Map = new Map();
+ private dropdownElement: HTMLElement | null = null;
+ private hoverTimeout: number | null = null;
+
+ private readonly DEFAULT_ICON = '';
+
+ constructor(options: OptBtnGroupsOptions) {
+ const el = typeof options.container === 'string'
+ ? document.getElementById(options.container)
+ : options.container;
+
+ if (!el) throw new Error('Container not found');
+
+ this.container = el;
+ this.options = {
+ showLabel: true,
+ visibility: {},
+ ...options
+ };
+
+ this.initContainer();
+ }
+
+ private initContainer(): void {
+ this.container.innerHTML = '';
+ }
+
+ /**
+ * 添加按钮组
+ * @param groupId 组ID
+ * @param beforeGroupId 在哪个组之前插入(可选),不传则插入到最后
+ */
+ public addGroup(groupId: string, beforeGroupId?: string): void {
+ if (this.groups.some(g => g.id === groupId)) {
+ console.warn('Group ' + groupId + ' already exists');
+ return;
+ }
+
+ const newGroup: ButtonGroup = { id: groupId, buttons: [] };
+
+ if (beforeGroupId) {
+ const index = this.groups.findIndex(g => g.id === beforeGroupId);
+ if (index !== -1) {
+ this.groups.splice(index, 0, newGroup);
+ } else {
+ console.warn(`Target group ${beforeGroupId} not found, appending ${groupId} to end.`);
+ this.groups.push(newGroup);
+ }
+ } else {
+ this.groups.push(newGroup);
+ }
+ }
+
+ /**
+ * 添加按钮
+ * @param config 按钮配置(必须包含 groupId,可选包含 parentId)
+ */
+ public addButton(config: ButtonConfig): void {
+ const { groupId, parentId } = config;
+
+ if (!groupId) {
+ throw new Error(`Button ${config.id} config must contain 'groupId'`);
+ }
+
+ const group = this.groups.find(g => g.id === groupId);
+ if (!group) {
+ throw new Error(`Group ${groupId} not found. Please call addGroup first.`);
+ }
+
+ const button: OptButton = {
+ ...config,
+ children: config.children || []
+ };
+
+ if (parentId) {
+ // Add as sub-button
+ const parentBtn = this.findButton(group.buttons, parentId);
+ if (!parentBtn) {
+ throw new Error(`Parent button ${parentId} not found in group ${groupId}`);
+ }
+ if (!parentBtn.children) {
+ parentBtn.children = [];
+ }
+ parentBtn.children.push(button);
+ } else {
+ // Add as main button
+ group.buttons.push(button);
+ }
+ }
+
+ private findButton(buttons: OptButton[], id: string): OptButton | undefined {
+ for (const btn of buttons) {
+ if (btn.id === id) return btn;
+ if (btn.children) {
+ const found = this.findButton(btn.children, id);
+ if (found) return found;
+ }
+ }
+ return undefined;
+ }
+
+ public async init(): Promise {
+ const { homeButton } = await import('./buttons/home');
+ const { locationButton } = await import('./buttons/location');
+ const { walkMenuButton } = await import('./buttons/walk/walk-menu');
+ const { walkPersonButton } = await import('./buttons/walk/walk-person');
+ const { walkBirdButton } = await import('./buttons/walk/walk-bird');
+ const { settingButton } = await import('./buttons/setting');
+ const { infoButton } = await import('./buttons/info');
+
+ // 添加组1
+ this.addGroup('group-1');
+ this.addButton(homeButton);
+ this.addButton(walkMenuButton);
+ this.addButton(walkPersonButton);
+ this.addButton(walkBirdButton);
+ this.addButton(locationButton);
+ this.addGroup('group-2');
+ this.addButton(settingButton);
+ this.addButton(infoButton);
+ this.render();
+ }
+
+ public render(): void {
+ this.container.innerHTML = '';
+ this.btnRefs.clear();
+
+ const wrapper = document.createElement('div');
+ wrapper.className = 'toolbar-container';
+
+ // 直接遍历数组,顺序由 addGroup 控制
+ this.groups.forEach((group, index) => {
+ const groupElement = this.renderGroup(group, index, this.groups.length);
+ wrapper.appendChild(groupElement);
+ });
+
+ this.container.appendChild(wrapper);
+ }
+
+ private renderGroup(group: ButtonGroup, index: number, total: number): HTMLElement {
+ const groupEl = document.createElement('div');
+ groupEl.className = 'opt-btn-group';
+
+ if (index < total - 1) {
+ groupEl.classList.add('has-divider');
+ }
+
+ group.buttons.forEach(button => {
+ if (this.isVisible(button.id)) {
+ const btnWrapper = this.renderButton(button);
+ groupEl.appendChild(btnWrapper);
+ }
+ });
+
+ return groupEl;
+ }
+
+ private renderButton(button: OptButton): HTMLElement {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'opt-btn-wrapper';
+
+ const btnEl = document.createElement('div');
+ btnEl.className = 'opt-btn';
+
+ if (this.activeBtnIds.has(button.id)) {
+ btnEl.classList.add('active');
+ }
+
+ if (button.disabled) {
+ btnEl.classList.add('disabled');
+ }
+
+ if (!this.options.showLabel) {
+ btnEl.classList.add('no-label');
+ if (button.label) {
+ btnEl.title = button.label;
+ }
+ }
+
+ const icon = document.createElement('div');
+ icon.className = 'opt-btn-icon';
+ icon.innerHTML = this.getIcon(button.icon);
+ btnEl.appendChild(icon);
+
+ if (this.options.showLabel && button.label) {
+ const label = document.createElement('span');
+ label.className = 'opt-btn-label';
+ label.textContent = button.label;
+ btnEl.appendChild(label);
+ }
+
+ if (button.children && button.children.length > 0) {
+ const arrow = document.createElement('span');
+ arrow.className = 'opt-btn-arrow';
+ arrow.textContent = '▼';
+ btnEl.appendChild(arrow);
+ }
+
+ btnEl.addEventListener('click', () => this.handleClick(button));
+ btnEl.addEventListener('mouseenter', () => this.handleMouseEnter(button, btnEl));
+ btnEl.addEventListener('mouseleave', () => this.handleMouseLeave());
+
+ this.btnRefs.set(button.id, btnEl);
+
+ wrapper.appendChild(btnEl);
+ return wrapper;
+ }
+
+ private handleClick(button: OptButton): void {
+ if (button.disabled) return;
+
+ if (!button.children || button.children.length === 0) {
+ if (button.keepActive) {
+ const wasActive = this.activeBtnIds.has(button.id);
+ if (wasActive) {
+ this.activeBtnIds.delete(button.id);
+ } else {
+ this.activeBtnIds.add(button.id);
+ }
+ this.updateButtonState(button.id);
+ }
+
+ this.closeDropdown();
+
+ if (button.onClick) {
+ button.onClick(button);
+ }
+ }
+ }
+
+ private handleSubClick(button: OptButton): void {
+ if (button.keepActive) {
+ const wasActive = this.activeBtnIds.has(button.id);
+ if (wasActive) {
+ this.activeBtnIds.delete(button.id);
+ } else {
+ this.activeBtnIds.add(button.id);
+ }
+ this.updateButtonState(button.id);
+ }
+
+ this.closeDropdown();
+
+ if (button.onClick) {
+ button.onClick(button);
+ }
+ }
+
+ private handleMouseEnter(button: OptButton, btnEl: HTMLElement): void {
+ if (this.hoverTimeout) {
+ clearTimeout(this.hoverTimeout);
+ this.hoverTimeout = null;
+ }
+
+ if (button.children && button.children.length > 0) {
+ this.showDropdown(button, btnEl);
+
+ const arrow = btnEl.querySelector('.opt-btn-arrow');
+ if (arrow) {
+ arrow.classList.add('rotated');
+ }
+ } else {
+ this.closeDropdown();
+ }
+ }
+
+ private handleMouseLeave(): void {
+ this.hoverTimeout = window.setTimeout(() => {
+ this.closeDropdown();
+ }, 200);
+ }
+
+ private showDropdown(button: OptButton, btnEl: HTMLElement): void {
+ this.closeDropdown();
+
+ if (!button.children) return;
+
+ const dropdown = document.createElement('div');
+ dropdown.className = 'opt-btn-dropdown';
+
+ const rect = btnEl.getBoundingClientRect();
+ const centerX = rect.left + rect.width / 2;
+
+ dropdown.style.top = rect.top - 8 + 'px';
+ dropdown.style.left = centerX + 'px';
+
+ button.children.forEach(subBtn => {
+ if (this.isVisible(subBtn.id)) {
+ const item = this.renderDropdownItem(subBtn);
+ dropdown.appendChild(item);
+ }
+ });
+
+ dropdown.addEventListener('mouseenter', () => {
+ if (this.hoverTimeout) {
+ clearTimeout(this.hoverTimeout);
+ this.hoverTimeout = null;
+ }
+ });
+
+ dropdown.addEventListener('mouseleave', () => this.handleMouseLeave());
+
+ document.body.appendChild(dropdown);
+ this.dropdownElement = dropdown;
+ }
+
+ private renderDropdownItem(button: OptButton): HTMLElement {
+ const item = document.createElement('div');
+ item.className = 'opt-btn-dropdown-item';
+
+ const icon = document.createElement('div');
+ icon.className = 'opt-btn-icon small';
+ icon.innerHTML = this.getIcon(button.icon);
+ item.appendChild(icon);
+
+ if (this.options.showLabel) {
+ const label = document.createElement('span');
+ label.textContent = button.label;
+ item.appendChild(label);
+ }
+
+ item.addEventListener('click', (e) => {
+ e.stopPropagation();
+ this.handleSubClick(button);
+ });
+
+ return item;
+ }
+
+ private closeDropdown(): void {
+ if (this.dropdownElement) {
+ this.dropdownElement.remove();
+ this.dropdownElement = null;
+ }
+
+ this.btnRefs.forEach(btnEl => {
+ const arrow = btnEl.querySelector('.opt-btn-arrow');
+ if (arrow) {
+ arrow.classList.remove('rotated');
+ }
+ });
+ }
+
+ private updateButtonState(buttonId: string): void {
+ const btnEl = this.btnRefs.get(buttonId);
+ if (btnEl) {
+ if (this.activeBtnIds.has(buttonId)) {
+ btnEl.classList.add('active');
+ } else {
+ btnEl.classList.remove('active');
+ }
+ }
+ }
+
+ private getIcon(icon?: string): string {
+ return icon || this.DEFAULT_ICON;
+ }
+
+ private isVisible(id: string): boolean {
+ return this.options.visibility?.[id] !== false;
+ }
+
+ public destroy(): void {
+ this.closeDropdown();
+ if (this.hoverTimeout) {
+ clearTimeout(this.hoverTimeout);
+ }
+ this.container.innerHTML = '';
+ this.btnRefs.clear();
+ this.activeBtnIds.clear();
+ this.groups = []; // 清空数组
+ }
+}
\ No newline at end of file
diff --git a/src/toolbar/index.type.ts b/src/toolbar/index.type.ts
new file mode 100644
index 0000000..74352dd
--- /dev/null
+++ b/src/toolbar/index.type.ts
@@ -0,0 +1,51 @@
+export type ButtonType = 'button' | 'menu';
+
+/**
+ * 按钮配置接口(用于外部定义按钮)
+ */
+export interface ButtonConfig {
+ id: string; // 唯一标识
+ type: ButtonType; // 按钮类型
+ label: string; // 按钮文字
+ icon?: string; // SVG 图标(内联 SVG 字符串)
+ keepActive?: boolean; // 是否保持激活状态(默认 false)
+ disabled?: boolean; // 是否禁用
+ onClick?: (button: OptButton) => void; // 点击回调
+ children?: ButtonConfig[]; // 子按钮配置(可选,用于菜单按钮)
+
+ groupId?: string; // 所属组ID
+ parentId?: string; // 父按钮ID(如果是子按钮)
+}
+
+/**
+ * 操作按钮接口(内部使用,继承配置)
+ */
+export interface OptButton extends ButtonConfig {
+ children?: OptButton[]; // 内部使用的子按钮列表
+}
+
+/**
+ * 按钮组接口
+ */
+export interface ButtonGroup {
+ id: string;
+ buttons: OptButton[];
+}
+
+/**
+ * OptBtnGroups 配置选项
+ */
+export interface OptBtnGroupsOptions {
+ container: HTMLElement | string;
+ showLabel?: boolean;
+ visibility?: Record;
+}
+
+/**
+ * 点击事件载荷
+ */
+export interface ClickPayload {
+ button: OptButton;
+ action: 'activate' | 'deactivate' | 'trigger';
+ isActive?: boolean;
+}
diff --git a/vite.config.ts b/vite.config.ts
index 5f28e02..0864cc1 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -16,7 +16,7 @@ export default defineConfig(({ command }) => {
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
- name: 'BimEngineSDK',
+ name: 'LyzBimEngineSDK',
fileName: (format) => `bim-engine-sdk.${format}.js`,
},
rollupOptions: {