# Composants personnalisés

> Web SDK propose plusieurs méthodes pour intégrer des composants personnalisés afin d’étendre et d’adapter ses fonctionnalités. Ce guide présente plusieurs méthodes courantes.

- Human documentation: [https://docs.univer.ai/fr-FR/guides/sheets/ui/components](https://docs.univer.ai/fr-FR/guides/sheets/ui/components)

- Agent Markdown: [https://docs.univer.ai/fr-FR/guides/sheets/ui/components.md](https://docs.univer.ai/fr-FR/guides/sheets/ui/components.md)

- Requested language: `fr-FR`

- Content language: `fr-FR`

- Documentation version: `1.2.0`

- Source: [sheets/ui/components.fr-FR.mdx](https://github.com/dream-num/documentation/blob/dev/content/guides/sheets/ui/components.fr-FR.mdx)

---

Univer ne transmet pas directement les composants en tant que paramètres aux fonctions de rendu. Vous devez d’abord les enregistrer auprès d’Univer à l’aide de la Facade API avant de pouvoir les utiliser dans les différents points de montage.

Si vous ne savez pas comment obtenir une instance de la Facade API, consultez la page [Facade API](https://docs.univer.ai/fr-FR/guides/sheets/getting-started/facade.md).

## Enregistrer des composants personnalisés

```typescript
// [!code word:componentKey]
// [!code word:CustomComponent]
// [!code word:options]
univerAPI.registerComponent(componentKey, CustomComponent, options)
```

Utilisez la méthode `univerAPI.registerComponent` pour enregistrer des composants personnalisés. Cette méthode accepte trois paramètres :

* `componentKey` : identifiant unique du composant, utilisé pour le référencer dans Univer.
* `CustomComponent` : implémentation du composant, qui peut être un composant React, Vue ou Web Component.
* `options` : options de configuration facultatives permettant notamment d’indiquer le framework dont dépend le composant.

### Composants React

L’enregistrement d’un composant React ne nécessite aucune configuration supplémentaire ; assurez-vous simplement qu’il s’agit d’un composant React valide. Voici un exemple simple :

```tsx
function ReactComponent(props: Record<string, any>) {
  return <div>Hello Univer!</div>
}

univerAPI.registerComponent('MyReactComponent', ReactComponent)
```

### Composants Vue

#### Vue 3.x

Pour enregistrer des composants Vue, installez et enregistrez le plugin `UniverVue3AdapterPlugin` du package `@univerjs/ui-adapter-vue3` :

#### npm

```bash
npm install @univerjs/ui-adapter-vue3
```

#### pnpm

```bash
pnpm add @univerjs/ui-adapter-vue3
```

#### yarn

```bash
yarn add @univerjs/ui-adapter-vue3
```

#### bun

```bash
bun add @univerjs/ui-adapter-vue3
```

```typescript
import { UniverVue3AdapterPlugin } from '@univerjs/ui-adapter-vue3'

univer.registerPlugin(UniverVue3AdapterPlugin)
```

Lors de l’enregistrement de composants Vue, vous devez définir l’option `framework` sur `'vue3'` :

```tsx
const Vue3Component = defineComponent({
  setup(props) {
    return () => <div>Hello Univer!</div>
  },
})

univerAPI.registerComponent('MyVue3Component', Vue3Component, {
  // [!code word:vue3]
  framework: 'vue3',
})
```

#### Vue 2.x

Comme Vue 2.x est arrivé en fin de vie et n’est plus maintenu, Univer ne fournit actuellement aucun plugin d’adaptation d’interface utilisateur pour Vue 2.x. Vous pouvez toutefois créer un plugin personnalisé afin d’implémenter votre propre adaptateur Vue 2.x. Voici un exemple :

```typescript title="ui-adapter-vue2.ts"
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 })
}
```

Enregistrez-le ensuite :

```typescript
import { UniverVue2AdapterPlugin } from './ui-adapter-vue2' // [!code ++]

univer.registerPlugin(UniverUIPlugin)
univer.registerPlugin(UniverVue2AdapterPlugin) // [!code ++]
```

Lors de l’enregistrement de composants Vue 2.x, vous devez définir l’option `framework` sur `'vue2'` :

```tsx
const Vue2Component = Vue.component('MyVue2Component', {
  template: '<div>Hello, Univer!</div>',
})

univerAPI.registerComponent('MyVue2Component', Vue2Component, {
  // [!code word:vue2]
  framework: 'vue2',
})
```

### Web Components

Pour enregistrer des Web Components, assurez-vous qu’ils respectent le standard Web Components, puis installez et enregistrez le plugin `UniverWebComponentAdapterPlugin` du package `@univerjs/ui-adapter-web-component` :

#### npm

```bash
npm install @univerjs/ui-adapter-web-component
```

#### pnpm

```bash
pnpm add @univerjs/ui-adapter-web-component
```

#### yarn

```bash
yarn add @univerjs/ui-adapter-web-component
```

#### bun

```bash
bun add @univerjs/ui-adapter-web-component
```

```typescript
import { UniverWebComponentAdapterPlugin } from '@univerjs/ui-adapter-web-component'

univer.registerPlugin(UniverWebComponentAdapterPlugin)
```

Lors de l’enregistrement de Web Components, vous devez définir l’option `framework` sur `'web-component'` :

```tsx
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, {
  // [!code word:web-component]
  framework: 'web-component',
})
```

### Angular

Convertissez un composant Angular en élément personnalisé avec `createCustomElement` de `@angular/elements`, puis enregistrez-le avec l’adaptateur Web Component de Univer. Il pourra apparaître dans une barre latérale ou un dialogue.

Ajoutez `@angular/elements` à la même version que `@angular/core`, et `@univerjs/ui-adapter-web-component` à la même version que les autres packages Univer.

Appelez cette fonction après l’initialisation de Univer. Dans le composant hôte, obtenez l’injecteur avec `private readonly injector = inject(Injector)`, puis appelez `registerAngularPanel(univer, univerAPI, this.injector)`. Importez `inject` et `Injector` depuis `@angular/core` dans ce composant.

```typescript title="angular-panel.ts"
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',
  })
}
```

Une fois l’éditeur rendu, utilisez la clé enregistrée comme contenu de la barre latérale :

```typescript
const sidebar = univerAPI.openSidebar({
  header: { title: 'Angular' },
  children: { label: 'univer-angular-panel' },
  width: 360,
})
```

Conservez les handles d’enregistrement et de barre latérale, puis appelez leur méthode `dispose()` quand ils ne sont plus nécessaires. Enregistrez l’élément une seule fois dans l’application. Son nom doit contenir un trait d’union et différer du selector Angular.

Angular Elements convertit les entrées en propriétés et les sorties en événements DOM `CustomEvent`. L’adaptateur Univer transmet les propriétés sans s’abonner automatiquement aux sorties Angular : reliez explicitement les événements nécessaires.

## Utiliser des composants personnalisés

Les méthodes suivantes vous permettent d’intégrer avec souplesse différents composants personnalisés dans Univer afin d’enrichir et d’adapter ses fonctionnalités.

> [!WARNING: Attention]
> 1. Avant d’utiliser ces méthodes, assurez-vous qu’Univer a terminé le rendu. 2. Pour les composants qui doivent être
>    enregistrés, vérifiez que leur enregistrement a bien été effectué avant de les utiliser. 3. Utilisez la méthode
>    `dispose()` pour nettoyer et supprimer les composants ajoutés afin d’éviter les fuites de mémoire.

### Ajouter des éléments de menu personnalisés

La barre de menu supérieure (Ribbon) et le menu contextuel (Context Menu) peuvent tous deux contenir des composants personnalisés. Pour ajouter ces composants à des éléments de menu, vous devez créer un plugin personnalisé. Nous avons préparé un [guide de bonnes pratiques pour l’ajout d’éléments de menu personnalisés](/blog/custom-plugin) afin de vous aider à démarrer rapidement.

### Remplacer des composants intégrés

> [!ERROR: Avertissement]
> Le remplacement de composants intégrés peut empêcher certaines fonctionnalités de fonctionner correctement. Lisez
> attentivement le code source et la documentation avant de tenter de remplacer des composants, et procédez avec
> prudence.

`univerAPI.registerComponent` enregistre une substitution par défaut (`override: true`). Elle est prioritaire, quel que soit l’ordre d’enregistrement du composant par défaut du plugin. Une seule substitution peut être active par `componentKey` : libérez la précédente avant d’en enregistrer une autre. La méthode `dispose()` de l’objet retourné rétablit le composant par défaut, s’il est enregistré. Utilisez `{ override: false }` pour enregistrer un composant par défaut.

Enregistrez la substitution avant la première ouverture de la vue. Modifier l’enregistrement ne déclenche pas automatiquement un nouveau rendu des vues déjà montées.

Les entrées publiques des packages Sheets exposent ces clés pour remplacer les composants intégrés correspondants :

| Composant                    | Clé exportée               | Package                                      |
| ---------------------------- | -------------------------- | -------------------------------------------- |
| Noms définis                 | `DEFINED_NAME_CONTAINER`   | `@univerjs/sheets-ui`                        |
| Autres fonctions             | `MORE_FUNCTIONS_COMPONENT` | `@univerjs/sheets-formula-ui`                |
| Validation des données       | `DATA_VALIDATION_PANEL`    | `@univerjs/sheets-data-validation-ui`        |
| Mise en forme conditionnelle | `CF_PANEL_KEY`             | `@univerjs/sheets-conditional-formatting-ui` |

Par exemple, pour remplacer simplement le composant ColorPicker intégré :

```tsx
// Following code only replaces the UI of the built-in ColorPicker component, not its functionality
univerAPI.registerComponent('UI_COLOR_PICKER_COMPONENT', () => <input type="color" />)
```

### Utiliser des composants comme contenu

#### Dans la barre latérale

La méthode `univerAPI.openSidebar` permet d’ouvrir dans l’interface d’Univer une barre latérale contenant un composant personnalisé.

```tsx
// [!code word:MyCustomSidebarComponent]
// 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()
```

Référence : [`univerAPI.openSidebar`](https://docs.univer.ai/fr-FR/reference/facade/univer.md#opensidebar)

#### Dans une boîte de dialogue

La méthode `univerAPI.openDialog` permet d’ouvrir une boîte de dialogue contenant un composant personnalisé.

```tsx
// [!code word:MyCustomDialogComponent]
// 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()
```

Référence : [`univerAPI.openDialog`](https://docs.univer.ai/fr-FR/reference/facade/univer.md#opendialog)

#### Associer une fenêtre contextuelle à des cellules

La méthode `FRange.attachPopup` permet d’associer une fenêtre contextuelle personnalisée à une plage de cellules donnée.

* Une fenêtre contextuelle est un élément DOM temporaire associé à une cellule. Elle sert généralement à afficher des informations d’état provisoires et ne prend pas en charge le stockage persistant.
* La méthode [`FRange.attachPopup(popup: IFCanvasPopup)`](https://docs.univer.ai/fr-FR/reference/facade/range.md#attachpopup) permet d’associer une fenêtre contextuelle personnalisée à une plage de cellules donnée.
* La fenêtre contextuelle se fixe sur les bords de la cellule et ajuste automatiquement sa position et son orientation si elle est masquée.

```tsx
// [!code word:MyCustomPopupComponent]
// 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()
```

#### Associer une fenêtre contextuelle d’alerte

La méthode `FRange.attachAlertPopup` permet d’associer une fenêtre contextuelle d’alerte à la cellule de départ d’une plage donnée.

```typescript
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()
```

#### Ajouter un DOM flottant à une feuille de calcul

La méthode `FWorksheet.addFloatDomToPosition` permet d’ajouter un DOM flottant à une feuille de calcul.

* Avant d’utiliser cette méthode, vous devez installer le plugin `@univerjs/sheets-drawing-ui` ou le preset `@univerjs/preset-sheets-drawing`.
* Le DOM flottant est un composant déplaçable qui s’affiche au-dessus de la feuille de calcul et prend en charge le stockage persistant.
* Vous devez appeler cette méthode une fois le rendu d’Univer terminé.
* `componentKey` : identifiant unique du composant, utilisé pour y faire référence dans Univer.
* [Définition complète des paramètres](https://github.com/dream-num/univer/blob/dev/packages/sheets-drawing-ui/src/facade/f-worksheet.ts#L32)

```tsx
// [!code word:MyCustomFloatDOMComponent]
// 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()
```
