From 14ac91aa6e7160791b0c2fd8159f0a868dfa39b2 Mon Sep 17 00:00:00 2001 From: yuding <1023798085@qq.com> Date: Wed, 3 Dec 2025 15:46:18 +0800 Subject: [PATCH] Refactor: modularize toolbar buttons, integrate config, and fix layout positioning --- demo.html | 15 +- dist/bim-engine-sdk.es.js | 214 +++++++++- dist/bim-engine-sdk.es.js.map | 2 +- dist/bim-engine-sdk.umd.js | 8 +- dist/bim-engine-sdk.umd.js.map | 2 +- dist/index.d.ts | 94 +++++ docs/OptBtnGroups.md | 160 ++++++++ src/BimEngine.ts | 19 - src/bim-engine.css | 26 ++ src/bim-engine.ts | 69 ++++ src/index.ts | 5 +- src/toolbar/buttons/home/index.ts | 16 + src/toolbar/buttons/info/index.ts | 16 + src/toolbar/buttons/location/index.ts | 16 + src/toolbar/buttons/setting/index.ts | 16 + src/toolbar/buttons/walk/walk-bird/index.ts | 13 + src/toolbar/buttons/walk/walk-menu/index.ts | 16 + src/toolbar/buttons/walk/walk-person/index.ts | 13 + src/toolbar/index.css | 181 ++++++++ src/toolbar/index.ts | 387 ++++++++++++++++++ src/toolbar/index.type.ts | 51 +++ vite.config.ts | 2 +- 22 files changed, 1290 insertions(+), 51 deletions(-) create mode 100644 docs/OptBtnGroups.md delete mode 100644 src/BimEngine.ts create mode 100644 src/bim-engine.css create mode 100644 src/bim-engine.ts create mode 100644 src/toolbar/buttons/home/index.ts create mode 100644 src/toolbar/buttons/info/index.ts create mode 100644 src/toolbar/buttons/location/index.ts create mode 100644 src/toolbar/buttons/setting/index.ts create mode 100644 src/toolbar/buttons/walk/walk-bird/index.ts create mode 100644 src/toolbar/buttons/walk/walk-menu/index.ts create mode 100644 src/toolbar/buttons/walk/walk-person/index.ts create mode 100644 src/toolbar/index.css create mode 100644 src/toolbar/index.ts create mode 100644 src/toolbar/index.type.ts 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: {