自訂元件
Web SDK 提供了多種方式來整合自訂元件,使你能夠擴充並客製化 Web SDK 的功能。本指南將介紹幾種常用的方法。
Univer 並不會直接將元件作為參數傳遞給任何渲染函數,你需要透過 Facade API 將元件註冊到 Univer 中之後,才能在各個掛載點使用它們。
如果你還不瞭解如何獲取 Facade API 的實例,可以參考 Facade API。
註冊自訂元件
univerAPI.registerComponent(componentKey, CustomComponent, options)使用 univerAPI.registerComponent 方法來註冊自訂元件。此方法接受三個參數:
componentKey: 元件的唯一識別符,用於在 Univer 中引用該元件。CustomComponent: 元件的實現,可以是 React、Vue 或 Web Components。options: 可選的配置選項,用於指定元件所依賴的框架或其他相關設定。
React 元件
註冊 React 元件無需額外的配置,只需確保元件是一個有效的 React 元件即可。以下是一個簡單的範例:
function ReactComponent(props: Record<string, any>) { return <div>Hello Univer!</div>}univerAPI.registerComponent('MyReactComponent', ReactComponent)Vue 元件
Vue 3.x
註冊 Vue 元件時,需要確保安裝並註冊 @univerjs/ui-adapter-vue3 的 UniverVue3AdapterPlugin 插件:
pnpm add @univerjs/ui-adapter-vue3npm install @univerjs/ui-adapter-vue3yarn add @univerjs/ui-adapter-vue3bun add @univerjs/ui-adapter-vue3import { UniverVue3AdapterPlugin } from '@univerjs/ui-adapter-vue3'univer.registerPlugin(UniverVue3AdapterPlugin)註冊 Vue 元件時,需要指定 framework 選項為 'vue3'
const Vue3Component = defineComponent({ setup(props) { return () => <div>Hello Univer!</div> },})univerAPI.registerComponent('MyVue3Component', Vue3Component, { framework: 'vue3',})Vue 2.x
由於 Vue 2.x 已經不再維護,Univer 暫時沒有提供 Vue 2.x 的 UI 適配器插件的計劃,你可以參考如下程式碼通過自訂插件來實現一個 Vue 2.x 的適配器:
import { DependentOn, Inject, Injector, Plugin } from '@univerjs/core'import { ComponentManager, UniverUIPlugin } from '@univerjs/ui'import Vue from 'vue'/** * 允許 Univer 使用 Vue 2 元件作為 UI 元件的插件。 */@DependentOn(UniverUIPlugin)export class UniverVue2AdapterPlugin extends Plugin { static override pluginName = 'UNIVER_UI_VUE2_ADAPTER_PLUGIN' constructor( private readonly _config = {}, @Inject(Injector) protected readonly _injector: Injector, @Inject(ComponentManager) protected readonly _componentManager: ComponentManager, ) { super() } override onStarting(): void { const { createElement, useEffect, useRef } = this._componentManager.reactUtils this._componentManager.setHandler('vue2', (component: any) => { return (props: Record<string, any>) => createElement(VueComponentWrapper, { component, props: Object.keys(props).reduce<Record<string, any>>((acc, key) => { if (key !== 'key') { acc[key] = props[key] } return acc }, {}), reactUtils: { createElement, useEffect, useRef }, }) }) }}export function VueComponentWrapper(options: { component: any props: Record<string, any> reactUtils: typeof ComponentManager.prototype.reactUtils}) { const { component, props, reactUtils } = options const { createElement, useEffect, useRef } = reactUtils const domRef = useRef<HTMLDivElement>(null) useEffect(() => { if (!domRef.current) return const Constructor = Vue.extend(component) const instance = new Constructor({ data: props, }) instance.$mount() domRef.current.appendChild(instance.$el) return () => { instance.$destroy() } }, [props]) return createElement('div', { ref: domRef })}然後將其註冊:
import { UniverVue2AdapterPlugin } from './ui-adapter-vue2'univer.registerPlugin(UniverUIPlugin)univer.registerPlugin(UniverVue2AdapterPlugin)使用時需要指定 framework 選項為 'vue2'
const Vue2Component = Vue.component('MyVue2Component', { template: '<div>Hello, Univer!</div>',})univerAPI.registerComponent('MyVue2Component', Vue2Component, { framework: 'vue2',})Web Components
註冊 Web Components 時,需要確保元件符合 Web Components 標準,並安裝和註冊 @univerjs/ui-adapter-web-component 的 UniverWebComponentAdapterPlugin 插件:
pnpm add @univerjs/ui-adapter-web-componentnpm install @univerjs/ui-adapter-web-componentyarn add @univerjs/ui-adapter-web-componentbun add @univerjs/ui-adapter-web-componentimport { UniverWebComponentAdapterPlugin } from '@univerjs/ui-adapter-web-component'univer.registerPlugin(UniverWebComponentAdapterPlugin)註冊 Web Components 時,需要指定 framework 選項為 'web-component'
class WebComponent extends HTMLElement { constructor() { super() const shadow = this.attachShadow({ mode: 'open' }) const div = document.createElement('div') div.textContent = 'Hello Univer!' shadow.appendChild(div) }}univerAPI.registerComponent('my-web-component', WebComponent, { framework: 'web-component',})Angular
透過 @angular/elements 的 createCustomElement 將 Angular 元件轉換成自訂元素,再使用 Univer 的 Web Component 適配器註冊,即可將 Angular 元件用於 Univer 側邊欄或對話方塊。
加入與 @angular/core 版本一致的 @angular/elements,以及與其他 Univer 套件版本一致的 @univerjs/ui-adapter-web-component。
初始化 Univer 後呼叫以下函式。傳入宿主元件的 Angular 注入器:使用 private readonly injector = inject(Injector) 取得注入器,再呼叫 registerAngularPanel(univer, univerAPI, this.injector)。宿主元件中的 inject 與 Injector 從 @angular/core 匯入。
import type { Injector } from '@angular/core'import type { FUniver, Univer } from '@univerjs/presets'import { Component } from '@angular/core'import { createCustomElement } from '@angular/elements'import { UniverWebComponentAdapterPlugin } from '@univerjs/ui-adapter-web-component'@Component({ selector: 'app-angular-panel', standalone: true, template: '<p>Hello from Angular!</p>',})export class AngularPanelComponent {}export function registerAngularPanel(univer: Univer, univerAPI: FUniver, injector: Injector) { const element = createCustomElement(AngularPanelComponent, { injector }) univer.registerPlugin(UniverWebComponentAdapterPlugin) return univerAPI.registerComponent('univer-angular-panel', element, { framework: 'web-component', })}編輯器渲染完成後,使用註冊的元件識別碼作為側邊欄內容:
const sidebar = univerAPI.openSidebar({ header: { title: 'Angular' }, children: { label: 'univer-angular-panel' }, width: 360,})保存傳回的註冊與側邊欄控制物件,不再使用時呼叫各自的 dispose()。自訂元素在應用程式中註冊一次即可:名稱必須包含連字號,並與 Angular 元件的 selector 不同。
Angular Elements 將輸入映射為元素屬性,將輸出轉換成 DOM CustomEvent 事件。Univer 適配器會傳遞屬性,但不會自動訂閱 Angular 輸出;需要互動時須明確連接事件。
使用自訂元件
透過以下方法,你可以靈活地在 Univer 中整合各種自訂元件,從而增強和定制 Univer 的功能。
注意事項
- 在使用這些方法時,請確保 Univer 已經完成渲染。 2. 對於需要註冊的元件,請確保在使用前已正確註冊。 3. 使用
dispose()方法來清理和移除添加的元件,以避免記憶體洩漏。
添加自訂選單項目
頂部選單列(Ribbon)和右鍵選單(Context Menu)都可以添加自訂元件。為選單項目添加自訂元件需要透過建立自訂插件來實現,我們準備了一份新增自訂選單項目的最佳實踐來幫助你快速上手。
替換內建元件
警告
替換內建元件可能會導致一些功能無法正常工作,請在充分閱讀原始碼和文件後自行尋找可替換的元件並謹慎操作。
univerAPI.registerComponent 預設註冊覆寫元件(override: true)。無論外掛在呼叫前或呼叫後註冊內建預設元件,覆寫元件都優先生效。每個 componentKey 同時只能有一個覆寫元件,再次註冊前須先釋放上一個註冊。呼叫回傳值的 dispose() 後,會恢復已註冊的內建預設元件。傳入 { override: false } 則註冊預設元件。
請在對應檢視首次開啟前註冊覆寫元件;變更註冊不會自動重新呈現已掛載的檢視。
Sheets 從套件的公開入口匯出以下識別碼,可用來覆寫對應的內建元件:
| 元件 | 匯出的識別碼 | 套件 |
|---|---|---|
| 定義名稱 | DEFINED_NAME_CONTAINER | @univerjs/sheets-ui |
| 更多函數 | MORE_FUNCTIONS_COMPONENT | @univerjs/sheets-formula-ui |
| 資料驗證 | DATA_VALIDATION_PANEL | @univerjs/sheets-data-validation-ui |
| 條件式格式 | CF_PANEL_KEY | @univerjs/sheets-conditional-formatting-ui |
例如簡單地替換內建的 ColorPicker 元件:
// 此程式碼僅可在 UI 層面替換內建 ColorPicker 元件,無法替代其功能實現univerAPI.registerComponent('UI_COLOR_PICKER_COMPONENT', () => <input type="color" />)作為內容元件添加到……
在側邊欄中使用
使用 univerAPI.openSidebar 方法可以在 Univer 介面中打開一個包含自訂元件的側邊欄。
// 你應該在合適的時機(比如渲染完成)註冊元件univerAPI.registerComponent('MyCustomSidebarComponent', () => <div>Hello Univer!</div>)const sidebar = univerAPI.openSidebar({ header: { title: 'My Sidebar' }, children: { label: 'MyCustomSidebarComponent' }, onClose: () => { console.log('close') }, width: 360,})// 稍後關閉側邊欄sidebar.dispose()在對話框中使用
使用 univerAPI.openDialog 方法可以打開一個包含自訂元件的對話框。
// 你應該在合適的時機(比如渲染完成)註冊元件univerAPI.registerComponent('MyCustomDialogComponent', () => <div>Hello Univer!</div>)const dialog = univerAPI.openDialog({ id: 'unique-dialog-id', // 對話框的唯一識別符 draggable: true, width: 300, title: { title: 'My Dialog' }, children: { label: 'MyCustomDialogComponent', }, destroyOnClose: true, preservePositionOnDestroy: true, onClose: () => {},})// 稍後關閉對話框dialog.dispose()將彈出框附加到儲存格
使用 FRange.attachPopup 方法可以將自訂彈出框附加到特定的儲存格範圍。
- Popup 是一個依附於儲存格之上的臨時 DOM,一般用於顯示一些臨時的狀態資訊,不支援持久化存儲。
- 使用
FRange.attachPopup(popup: IFCanvasPopup)方法可以將自訂彈出框附加到特定的儲存格範圍。 - Popup 會吸附在儲存格的四邊,如果被遮擋,會自動調整位置和方向。
// 你應該在合適的時機(例如渲染完成)註冊元件univerAPI.registerComponent( 'MyCustomPopupComponent', () => <div>Hello Univer!</div>,)const fWorkbook = univerAPI.getActiveWorkbook()const fWorksheet = fWorkbook.getActiveSheet()const fRange = fWorksheet.getRange('A1:J10')// 將彈出窗口附加到範圍的第一個儲存格// 如果 `disposable` 為 null,則表示 popup 添加失敗const disposable = fRange.attachPopup({ componentKey: 'MyCustomPopupComponent',})// 移除彈出框disposable.dispose()附加警告彈出框
使用 FRange.attachAlertPopup 方法可以在指定範圍的起始儲存格附加一個警告彈出框。
const fWorkbook = univerAPI.getActiveWorkbook()const fWorksheet = fWorkbook.getActiveSheet()const fRange = fWorksheet.getRange('A1:B2')const disposable = fRange.attachAlertPopup({ key: 'unique-alert-key', // 唯一識別符 title: '這是一個警告!', message: '這是一個警告!', width: 300, height: 200, // 0: 資訊 // 1: 警告 // 2: 錯誤 type: 0,})// 稍後移除警告disposable.dispose()添加浮動 DOM 到工作表
透過 FWorksheet.addFloatDomToPosition 方法可以在工作表上添加一個浮動 DOM。
- 使用該方法前需要安裝
@univerjs/sheets-drawing-ui外掛或@univerjs/preset-sheets-drawing預設。 - 浮動 DOM 是懸浮於工作表上的可拖動元件,同時支援持久化存儲。
- 需要在 Univer 渲染完成之後呼叫。
componentKey: 元件的唯一識別符,用於在 Univer 中引用該元件。- 完整的參數定義
// 你應該在合適的時機(例如渲染完成)註冊元件univerAPI.registerComponent( 'MyCustomFloatingDOMComponent', ({ data }) => ( <div> Hello {data?.label} ! </div> ),)const fWorkbook = univerAPI.getActiveWorkbook()const fWorksheet = fWorkbook.getActiveSheet()// 添加一個浮動 DOM// 如果 `disposable` 為 null,則表示浮動 DOM 添加失敗const disposable = fWorksheet.addFloatDomToPosition({ componentKey: 'MyCustomFloatingDOMComponent', initPosition: { startX: 100, endX: 200, startY: 100, endY: 200, }, // 這是元件的資料 data: { label: 'Univer', },})// 移除浮動 DOMdisposable.dispose()你覺得這篇文檔如何?