Custom Components
Univer provides multiple ways to integrate custom components, allowing you to extend and customize its functionality. This guide will introduce several common methods.
Univer does not directly pass components as parameters to any rendering functions. You need to register the components with Univer through the Facade API before you can use them in various mounting points.
If you are not familiar with how to obtain an instance of the Facade API, you can refer to Facade API.
Registering Custom Components
univerAPI.registerComponent(componentKey, CustomComponent, options)
Use the univerAPI.registerComponent
method to register custom components. This method accepts three parameters:
componentKey
: A unique identifier for the component, used to reference it within Univer.CustomComponent
: The implementation of the component, which can be a React, Vue, or Web Component.options
: Optional configuration options that can be used to specify the framework the component depends on or other related settings.
React Components
To register a React component, no additional configuration is required; just ensure that the component is a valid React component. Here is a simple example:
function ReactComponent(props: Record<string, any>) {
return <div>Hello Univer!</div>
}
univerAPI.registerComponent(
'MyReactComponent',
ReactComponent,
)
Vue Components
Vue 3.x
If you want to register Vue components, make sure to install and register the UniverVue3AdapterPlugin
from the @univerjs/ui-adapter-vue3
package:
npm install @univerjs/ui-adapter-vue3
import { UniverVue3AdapterPlugin } from '@univerjs/ui-adapter-vue3'
univer.registerPlugin(UniverVue3AdapterPlugin)
When registering Vue components, you need to specify the framework
option as 'vue3'
:
const Vue3Component = defineComponent({
setup(props) {
return () => <div>Hello Univer!</div>
},
})
univerAPI.registerComponent(
'MyVue3Component',
Vue3Component,
{
framework: 'vue3',
},
)
Vue 2.x
Since Vue 2.x is EOL (End of Life) and no longer maintained, Univer does not currently provide a UI adapter plugin for Vue 2.x. However, you can implement a Vue 2.x adapter by creating a custom plugin. Below is an example of how to create a Vue 2.x adapter plugin:
import { DependentOn, Inject, Injector, Plugin } from '@univerjs/core'
import { ComponentManager, UniverUIPlugin } from '@univerjs/ui'
import Vue from 'vue'
/**
* The plugin that allows Univer to use Vue 2 components as UI components.
*/
@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 })
}
Then register it:
import { UniverVue2AdapterPlugin } from './ui-adapter-vue2'
univer.registerPlugin(UniverUIPlugin)
univer.registerPlugin(UniverVue2AdapterPlugin)
When registering Vue 2.x components, you need to specify the framework
option as 'vue2'
:
const Vue2Component = Vue.component('MyVue2Component', {
template: '<div>Hello, Univer!</div>',
})
univerAPI.registerComponent(
'MyVue2Component',
Vue2Component,
{
framework: 'vue2',
},
)
Web Components
If you want to register Web Components, make sure the component adheres to the Web Components standard and install and register the UniverWebComponentAdapterPlugin
from the @univerjs/ui-adapter-web-component
package:
npm install @univerjs/ui-adapter-web-component
import { UniverWebComponentAdapterPlugin } from '@univerjs/ui-adapter-web-component'
univer.registerPlugin(UniverWebComponentAdapterPlugin)
When registering Web Components, you need to specify the framework
option as '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',
},
)
Using Custom Components
Following methods allow you to flexibly integrate various custom components into Univer, enhancing and customizing its functionality.
Caution
- Ensure that Univer has completed rendering before using these methods.
- For components that need to be registered, ensure they are correctly registered before use.
- Use the
dispose()
method to clean up and remove added components to avoid memory leaks.
Adding Custom Menu Items
The top menu bar (Ribbon) and right-click menu (Context Menu) can both include custom components. To add custom components to menu items, you need to create a custom plugin. We have prepared a best practice for adding custom menu items to help you get started quickly.
Replacing Built-in Components
Warning
Replacing built-in components may cause some functionalities to not work properly. Please read the source code and documentation thoroughly before attempting to replace components, and proceed with caution.
When you register a component using the univerAPI.registerComponent
method, if the componentKey
already exists, Univer will replace the existing component with the new one.
For example, to simply replace the built-in ColorPicker component:
// Following code only replaces the UI of the built-in ColorPicker component, not its functionality
univerAPI.registerComponent(
'UI_COLOR_PICKER_COMPONENT',
() => <input type="color" />,
)
Using as Content Components
In the Sidebar
Using the univerAPI.openSidebar
method, you can open a sidebar in the Univer interface that contains a custom component.
// You should register the component at an appropriate time (e.g., after Univer has loaded)
univerAPI.registerComponent(
'MyCustomSidebarComponent',
() => <div>Hello Univer!</div>,
)
const sidebar = univerAPI.openSidebar({
header: { title: 'My Sidebar' },
children: { label: 'MyCustomSidebarComponent' },
onClose: () => {
console.log('close')
},
width: 360,
})
// Later close the sidebar
sidebar.dispose()
Reference: univerAPI.openSidebar
In a Dialog
Using the univerAPI.openDialog
method, you can open a dialog that contains a custom component.
// You should register the component at an appropriate time (e.g., after Univer has loaded)
univerAPI.registerComponent(
'MyCustomDialogComponent',
() => <div>Hello Univer!</div>,
)
const dialog = univerAPI.openDialog({
id: 'unique-dialog-id', // The unique identifier for the dialog
draggable: true,
width: 300,
title: { title: 'My Dialog' },
children: {
label: 'MyCustomDialogComponent',
},
destroyOnClose: true,
preservePositionOnDestroy: true,
onClose: () => {},
})
// Later close the dialog
dialog.dispose()
Reference: univerAPI.openDialog
Attach Popup to Cells
Using the FRange.attachPopup
method, you can attach a custom popup to a specific cell range.
- A popup is a temporary DOM element attached to a cell, typically used to display transient status information and does not support persistent storage.
- You can use the
FRange.attachPopup(popup: IFCanvasPopup)
method to attach a custom popup to a specific cell range. - The popup will attach to the edges of the cell and will automatically adjust its position and orientation if obstructed.
// You should register the component at an appropriate time (e.g., after Univer has loaded)
univerAPI.registerComponent(
'MyCustomPopupComponent',
() => <div>Hello Univer!</div>,
)
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:J10')
// Attach the popup to the first cell of the range
// If `disposable` is null, it indicates that the popup was not added successfully
const disposable = fRange.attachPopup({
componentKey: 'MyCustomPopupComponent',
})
// Remove the popup
disposable.dispose()
Attach Alert Popup
Using the FRange.attachAlertPopup
method, you can attach an alert popup to the starting cell of a specified range.
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const fRange = fWorksheet.getRange('A1:B2')
const disposable = fRange.attachAlertPopup({
key: 'unique-alert-key', // Unique identifier
title: '这是一个警告!',
message: '这是一个警告!',
width: 300,
height: 200,
// 0: Information
// 1: Warning
// 2: Error
type: 0,
})
// Remove the alert later
disposable.dispose()
Add Floating DOM to Worksheet
Using the FWorksheet.addFloatDomToPosition
method, you can add a floating DOM to the worksheet.
- Before using this method, you need to install the
@univerjs/sheets-drawing-ui
plugin or the@univerjs/preset-sheets-drawing
preset. - The floating DOM is a draggable component that hovers over the worksheet and supports persistent storage.
- You should call this method after Univer has finished rendering.
componentKey
: The unique identifier for the component, used to reference it within Univer.- Full parameter definition
// You should register the component at an appropriate time (e.g., after Univer has loaded)
univerAPI.registerComponent(
'MyCustomFloatDOMComponent',
({ data }) => (
<div>
Hello
{data?.label}
!
</div>
),
)
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
// Add a floating DOM to the worksheet
// If `disposable` is null, it indicates that the floating DOM was not added successfully
const disposable = fWorksheet.addFloatDomToPosition({
componentKey: 'MyCustomFloatDOMComponent',
initPosition: {
startX: 100,
endX: 200,
startY: 100,
endY: 200,
},
// The data for the component
data: {
label: 'Univer',
},
})
// Remove the floating DOM
disposable.dispose()