# Composants personnalisés

> Créez des composants personnalisés pour PandaSuite avec les librairies PandaBridge (JavaScript & React). Définissez des propriétés, événements, actions, marqueurs, synchronisation et plus encore.

https://docs.pandasuite.com/fr/essentials/components/custom-components/

Les composants personnalisés vous permettent d'étendre PandaSuite avec vos propres éléments interactifs construits avec les technologies web (HTML, CSS, JavaScript). Contrairement aux composants natifs PandaSuite, les composants personnalisés s'exécutent dans une web view, vous donnant accès à tout l'écosystème web : packages npm, React, Vue ou tout autre framework JavaScript.

Grâce aux librairies PandaBridge, votre composant peut communiquer avec le reste de votre projet PandaSuite : recevoir des propriétés, déclencher des événements, répondre à des actions et se synchroniser avec d'autres composants.

> **Info**
>
> Si vous souhaitez simplement intégrer du contenu web externe sans interagir avec d'autres composants PandaSuite, utilisez plutôt le [composant Web](https://docs.pandasuite.com/fr/essentials/components/web/).

## Cas d'utilisation

Les composants personnalisés sont utiles quand vous avez besoin de :

- **Intégrer des services tiers** : se connecter à des APIs comme des processeurs de paiement, des plateformes d'analytics ou des backends personnalisés
- **Ajouter des fonctionnalités spécialisées** : créer des scanners de QR codes, des zones de signature, des graphiques personnalisés ou des visualisations interactives
- **Réutiliser du code existant** : packager votre code JavaScript ou React comme composant PandaSuite
- **Étendre les capacités natives** : accéder aux fonctionnalités de l'appareil ou aux APIs du navigateur non disponibles dans les composants standards

## Ce que les composants personnalisés peuvent faire

Les composants personnalisés se comportent comme les composants natifs PandaSuite. Ils peuvent :

- Afficher des **propriétés** configurables dans le panneau de propriétés du Studio
- Émettre des **événements** (déclencheurs) qui activent des actions sur d'autres composants
- Recevoir des **actions** d'autres composants
- Définir des **marqueurs** (points de données comme des timestamps ou des hotspots)
- **Synchroniser** leur état avec d'autres composants
- Réagir aux **changements de langue** dans les projets multilingues
- Recevoir des **données dynamiques** via le data binding

## Manifeste du composant (pandasuite.json)

Chaque composant personnalisé inclut un fichier `pandasuite.json` qui décrit son interface. Ce manifeste définit les propriétés, événements, actions et autres fonctionnalités qui apparaissent dans PandaSuite Studio.

### Structure de base

```json
{
  "version": "2.0.0",
  "main": "",
  "properties": [],
  "events": [],
  "actions": [],
  "resources": [],
  "markers": [],
  "synchronization": [],
  "queryable": {},
  "system": {}
}
```

| Champ             | Description                                                                    |
| ----------------- | ------------------------------------------------------------------------------ |
| `version`         | Version du manifeste (actuelle : `"2.0.0"`)                                    |
| `main`            | URL du point d'entrée (généralement vide `""` pour les composants PandaBridge) |
| `properties`      | Options de configuration affichées dans le panneau de propriétés               |
| `events`          | Déclencheurs qui peuvent activer des actions sur d'autres composants           |
| `actions`         | Opérations que d'autres composants peuvent appeler                             |
| `resources`       | Emplacements pour les médias (images, vidéos, audio, fichiers zip)             |
| `markers`         | Points de données définis par l'utilisateur                                    |
| `synchronization` | Groupes pour la synchronisation d'état entre composants                        |
| `queryable`       | Données exposées pour le data binding                                          |
| `system`          | Paramètres avancés (localisation, configuration des marqueurs)                 |

### Organiser les données du composant

Lorsque vous concevez un composant, séparez la configuration, les données réutilisables du composant et les charges utiles d'événement. Le manifeste reste ainsi plus lisible, et l'interface générée dans Studio devient plus prévisible.

Utilisez ce tableau pour repérer où chaque type de donnée se déclare, puis ouvrez la section de référence correspondante plus bas :

| Cas d'usage                                                        | Déclarer dans `pandasuite.json`                                                                                               | API runtime                                                                                                                                           | Où il apparaît dans Studio                                              |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Configurer le composant depuis Studio                              | [`properties`](https://docs.pandasuite.com/fr/essentials/components/custom-components/#propriétés)                            | [`PandaBridge.UPDATED`](https://docs.pandasuite.com/fr/essentials/components/custom-components/#mettre-à-jour-les-propriétés-et-les-données-exposées) | Panneau de propriétés à droite                                          |
| Exposer des données vivantes pour le data binding                  | [`queryable` à la racine](https://docs.pandasuite.com/fr/essentials/components/custom-components/#données-exposées-queryable) | [`PandaBridge.UPDATED`](https://docs.pandasuite.com/fr/essentials/components/custom-components/#mettre-à-jour-les-propriétés-et-les-données-exposées) | Fenêtre de data binding sous **Depuis un composant**                    |
| Envoyer des données contextuelles lorsqu'un événement se déclenche | [`events[].queryable`](https://docs.pandasuite.com/fr/essentials/components/custom-components/#événements-déclencheurs)       | [`PandaBridge.send()`](https://docs.pandasuite.com/fr/essentials/components/custom-components/#envoyer-des-événements)                                | Flux d'actions déclenchés par cet événement                             |
| Déclarer des événements découverts à l'exécution                   | — (envoyés par le composant, pas déclarés)                                                                                    | [`PandaBridge.UPDATED`](https://docs.pandasuite.com/fr/essentials/components/custom-components/#déclarer-des-événements-découverts-à-lexécution)      | Panneau **Actions** du composant, arguments sous **Depuis l'événement** |

> **Note**
>
> `pandasuite.json` définit le schéma lu par Studio. Les sections liées ci-dessous expliquent ensuite comment déclarer chaque type de donnée et quelle API PandaBridge utiliser au runtime.

### Propriétés

Les propriétés apparaissent dans le panneau de propriétés du Studio. Chaque propriété a un type qui détermine son interface utilisateur.

```json
{
  "properties": [
    {
      "id": "url",
      "name": "URL",
      "locale_name": { "fr_FR": "URL" },
      "type": "String",
      "value": "https://example.com",
      "bindable": true,
      "required": true
    },
    {
      "id": "autoplay",
      "name": "Autoplay",
      "locale_name": { "fr_FR": "Lecture automatique" },
      "type": "Boolean",
      "value": false
    }
  ]
}
```

#### Types de propriétés

| Type                      | Description                     | Contrôle UI            |
| ------------------------- | ------------------------------- | ---------------------- |
| `String`                  | Valeur texte                    | Champ de texte         |
| `Boolean`                 | Vrai/faux                       | Case à cocher          |
| `Integer`                 | Nombre entier                   | Champ numérique        |
| `Float`                   | Nombre décimal                  | Champ numérique        |
| `Color`                   | Valeur couleur                  | Sélecteur de couleur   |
| `Dropdown`                | Sélection dans une liste        | Menu déroulant         |
| `Image`, `Video`, `Audio` | Ressource média                 | Sélecteur de fichier   |
| `Bind`                    | Expression de data binding      | Éditeur de binding     |
| `JSON`                    | Données structurées             | Éditeur JSON           |
| `Code`                    | Code avec coloration syntaxique | Éditeur de code        |
| `Selection`               | Référence à un autre composant  | Sélecteur de composant |

#### Options des propriétés

| Option        | Type              | Description                                                |
| ------------- | ----------------- | ---------------------------------------------------------- |
| `id`          | string            | Identifiant unique (utilisé dans le code)                  |
| `name`        | string            | Nom affiché dans le Studio                                 |
| `locale_name` | object            | Noms localisés (ex: `{ "fr_FR": "Nom" }`)                  |
| `type`        | string            | Type de propriété                                          |
| `value`       | any               | Valeur par défaut                                          |
| `bindable`    | boolean           | Autoriser le data binding                                  |
| `i18n`        | boolean           | Stocker une valeur différente pour chaque langue du projet |
| `required`    | boolean           | Marquer comme requis                                       |
| `hidden`      | string \| boolean | Expression de visibilité conditionnelle                    |
| `placeholder` | string            | Texte indicatif pour les champs                            |
| `separator`   | boolean           | Afficher un séparateur visuel au-dessus                    |

#### Valeurs de propriétés localisées

Définissez `i18n: true` lorsqu’une propriété doit conserver une valeur différente pour chaque langue du projet. Utilisez `locale_name` pour localiser le libellé de la propriété dans le Studio ; utilisez `i18n` pour localiser la valeur saisie par les éditeurs.

```json
{
  "properties": [
    {
      "id": "title",
      "name": "Title",
      "locale_name": { "fr_FR": "Titre" },
      "type": "String",
      "value": "Welcome",
      "i18n": true
    }
  ]
}
```

#### Visibilité conditionnelle

Utilisez la propriété `hidden` pour afficher/masquer des propriétés selon d'autres valeurs :

```json
{
  "properties": [
    {
      "id": "mode",
      "name": "Mode",
      "type": "Dropdown",
      "items": [
        { "id": "simple", "name": "Simple" },
        { "id": "advanced", "name": "Advanced" }
      ],
      "value": "simple"
    },
    {
      "id": "advancedOption",
      "name": "Advanced Option",
      "type": "String",
      "hidden": "properties.mode.value != 'advanced'"
    }
  ]
}
```

#### Éléments de dropdown

```json
{
  "id": "quality",
  "name": "Quality",
  "type": "Dropdown",
  "value": "medium",
  "items": [
    { "id": "low", "name": "Low", "locale_name": { "fr_FR": "Basse" } },
    { "id": "medium", "name": "Medium", "locale_name": { "fr_FR": "Moyenne" } },
    { "id": "high", "name": "High", "locale_name": { "fr_FR": "Haute" } }
  ]
}
```

#### Contraintes numériques

```json
{
  "id": "speed",
  "name": "Speed",
  "type": "Float",
  "value": 1.0,
  "restrict": {
    "min": 0.1,
    "max": 10.0,
    "step": 0.1
  }
}
```

### Événements (déclencheurs)

Les événements permettent à votre composant de déclencher des actions sur d'autres composants. Définissez des données queryable qui deviennent disponibles lorsque l'événement se déclenche.

```json
{
  "events": [
    {
      "id": "onItemSelected",
      "name": "Item selected",
      "locale_name": { "fr_FR": "Élément sélectionné" },
      "queryable": {
        "itemId": "example-id",
        "__desc_itemId": {
          "name": "Item ID",
          "locale_name": { "fr_FR": "ID de l'élément" }
        },
        "itemName": "Example Name",
        "__desc_itemName": {
          "name": "Item name",
          "locale_name": { "fr_FR": "Nom de l'élément" }
        }
      }
    }
  ]
}
```

L'objet `queryable` définit les données exposées lorsque l'événement se déclenche. Utilisez des clés préfixées par `__desc_` pour fournir des métadonnées pour chaque champ.

#### Permettre à l'auteur de répondre à l'événement

Un événement peut aussi déclarer une **réponse** : les valeurs que l'auteur est censé renvoyer à ce qui l'a déclenché. Ajoutez un objet `response` dont le tableau `params` a la forme des paramètres d'une action.

```json
{
  "id": "searchPages",
  "name": "Search pages",
  "queryable": { "query": "", "__desc_query": { "name": "Query" } },
  "response": {
    "params": [
      {
        "id": "result",
        "name": "Response",
        "locale_name": { "fr_FR": "Réponse" },
        "type": "JSON",
        "bindable": true
      }
    ]
  }
}
```

C'est cette déclaration qui rend l'action [Renvoyer une valeur](https://docs.pandasuite.com/fr/essentials/interactions/interactive-actions/#renvoyer-une-valeur) disponible sur cet événement : l'auteur lie vos paramètres à tout ce que le projet sait produire, et les valeurs vous parviennent comme la réponse à cet appel précis.

Envoyez l'événement avec un callback pour la recevoir. Ce callback demande `pandasuite-bridge` 5.1.0 ou plus récent, qui génère et transporte la clé reliant une réponse à son appel : deux appels lancés au même moment se règlent donc indépendamment.

```javascript
PandaBridge.send('searchPages', [{ query: 'tarifs' }], (outcome) => {
  if (outcome?.status === 'failed') {
    // une action de la liste a échoué ; outcome.error la décrit
    return;
  }
  // outcome.value contient une entrée par paramètre de réponse, indexée par son id
  const answer = outcome?.value?.result;
});
```

Un runtime antérieur à ce callback ne répond jamais, et l'appel reste en attente à dessein, plutôt que d'accuser réception d'une valeur que l'auteur n'a jamais calculée. Donnez à vos paramètres de réponse des identifiants qui vous sont propres : `delay`, `bindings`, `marker`, tout ce qui commence par `__ps_`, ainsi que tout identifiant en collision avec une clé du `queryable` de l'événement sont réservés et ne sont jamais proposés à l'auteur.

### Actions

Les actions sont des opérations que d'autres composants peuvent déclencher sur votre composant. Les actions peuvent avoir des paramètres.

Une action peut aussi déclarer un **résultat** : ce qu'elle remet aux actions placées en dessous d'elle dans la même liste. Ajoutez à l'action un objet `queryable`, de la même forme que celui d'un événement, et l'auteur pourra le lire via [Depuis une action précédente](https://docs.pandasuite.com/fr/essentials/data/data-binding/#depuis-une-action-précédente) dans la fenêtre de data binding. Le lier fait attendre l'action consommatrice jusqu'à ce que la vôtre soit réglée.

```json
{
  "actions": [
    {
      "id": "setVolume",
      "name": "Set volume",
      "locale_name": { "fr_FR": "Définir le volume" },
      "params": [
        {
          "id": "level",
          "name": "Volume level",
          "locale_name": { "fr_FR": "Niveau de volume" },
          "type": "Float",
          "value": 1.0,
          "restrict": { "min": 0, "max": 1 }
        }
      ]
    },
    {
      "id": "reset",
      "name": "Reset",
      "locale_name": { "fr_FR": "Réinitialiser" }
    }
  ]
}
```

### Ressources

Les ressources sont des fichiers médias que les utilisateurs peuvent attacher à votre composant.

```json
{
  "resources": [
    {
      "id": "backgroundImage",
      "name": "Background image",
      "locale_name": { "fr_FR": "Image de fond" },
      "type": "Image",
      "bindable": true,
      "localization": true
    },
    {
      "id": "dataArchive",
      "name": "Data archive",
      "type": "Zip",
      "required": true
    }
  ]
}
```

Types de ressources : `Image`, `Video`, `Audio`, `Zip`, `JSON`

Définissez `localization: true` pour permettre des ressources différentes par langue.

### Synchronisation

Les groupes de synchronisation permettent aux composants de partager leur état (comme la position de lecture ou la position de défilement).

```json
{
  "synchronization": [
    {
      "id": "playbackPosition",
      "name": "Synchronize playback",
      "locale_name": { "fr_FR": "Synchroniser la lecture" }
    }
  ]
}
```

### Données exposées (queryable)

L'objet `queryable` à la racine expose des données auxquelles d'autres composants peuvent accéder via le data binding. Contrairement à `events[].queryable`, ces champs existent indépendamment d'un événement et restent disponibles depuis le composant lui-même dans le sélecteur de data binding.

```json
{
  "queryable": {
    "progress": 0,
    "__desc_progress": {
      "name": "Progress",
      "locale_name": { "fr_FR": "Progression" },
      "type": "number"
    },
    "isPlaying": false,
    "__desc_isPlaying": {
      "name": "Is playing",
      "locale_name": { "fr_FR": "En lecture" }
    }
  }
}
```

#### Mettre à jour les données exposées au runtime

Déclarer `queryable` dans le manifeste définit les champs existants. Pour pousser leurs valeurs courantes après une connexion, une réponse API ou une action utilisateur, envoyez un message `UPDATED` :

```javascript
PandaBridge.send(PandaBridge.UPDATED, {
  queryable: {
    progress: 42,
    isPlaying: true
  }
});
```

Une fois les métadonnées chargées, ces champs apparaissent dans la fenêtre de data binding sous **Depuis un composant**. L'envoi de `UPDATED` actualise leurs valeurs courantes. Si vous ajoutez ou renommez des champs dans `pandasuite.json`, rafraîchissez d'abord les métadonnées dans Studio pour charger le nouveau schéma.

### Configuration système

L'objet `system` contrôle les comportements avancés.

```json
{
  "system": {
    "localization": true,
    "markers": [
      {
        "params": [
          {
            "id": "timestamp",
            "name": "Time",
            "type": "Float"
          }
        ]
      }
    ],
    "actions": [
      {
        "id": "toMarker",
        "name": "Go to marker"
      }
    ],
    "events": [
      {
        "id": "triggerMarker",
        "name": "Marker reached"
      }
    ]
  }
}
```

Définissez `"markers": false` pour désactiver entièrement le système de marqueurs.

## API JavaScript PandaBridge

La librairie JavaScript gère la communication entre votre composant et PandaSuite.

### Installation

```bash
npm install pandasuite-bridge
```

### Initialisation

```javascript
import PandaBridge from 'pandasuite-bridge';

PandaBridge.init(() => {
  console.log('Bridge prêt');
});
```

### Recevoir les propriétés et données

```javascript
// Appelé une fois au chargement du composant
PandaBridge.onLoad(({ properties, markers, resources }) => {
  console.log('Properties:', properties);
  console.log('Markers:', markers);
  console.log('Resources:', resources);

  // Initialisez votre composant avec ces valeurs
  initComponent(properties);
});

// Appelé quand les propriétés changent
PandaBridge.onUpdate(({ properties, markers, resources }) => {
  console.log('Properties mises à jour:', properties);

  // Mettez à jour votre composant
  updateComponent(properties);
});
```

### Mettre à jour les propriétés et les données exposées

Utilisez `PandaBridge.UPDATED` lorsque votre composant doit renvoyer de nouvelles valeurs vers PandaSuite :

```javascript
// Mettre à jour des valeurs déclarées dans le tableau `properties`
PandaBridge.send(PandaBridge.UPDATED, {
  properties: [
    { id: 'url', value: 'https://example.com/player' },
    { id: 'autoplay', value: true }
  ]
});

// Mettre à jour des valeurs déclarées dans l'objet `queryable` racine
PandaBridge.send(PandaBridge.UPDATED, {
  queryable: {
    progress: 42,
    isPlaying: true
  }
});
```

Utilisez `properties` pour les valeurs affichées dans le panneau de droite. Utilisez `queryable` pour les valeurs que vous souhaitez réutiliser ailleurs dans votre projet via le data binding **Depuis un composant**.

#### Déclarer des événements découverts à l'exécution

Certains composants ne connaissent leurs événements qu'à travers un système externe : les Client Tools d'un agent IA, les événements d'une scène 3D, les hooks d'un formulaire. Plutôt que de les lister dans `pandasuite.json`, envoyez-les dans la clé `events` du même message : un seul événement, ou la liste complète. Chaque entrée a la forme d'une entrée du tableau `events` du manifeste (`id`, `name`, `description`, `queryable` pour les arguments, `response` lorsque l'auteur est censé répondre), et Studio la traite exactement comme un événement du manifeste : elle apparaît dans le panneau **Actions** du composant et ses arguments sont proposés sous **Depuis l'événement** dans la fenêtre de data binding. Le composant [Agent D-ID](https://docs.pandasuite.com/fr/essentials/components/d-id-agent/) fonctionne ainsi : il déclare chaque Client Tool de votre agent D-ID comme un événement du composant.

```javascript
PandaBridge.send(PandaBridge.UPDATED, {
  events: [
    {
      id: 'tool_8f3a',                       // identifiant stable, jamais le nom affiché
      name: 'search_pages',
      description: 'Rechercher les pages du projet',
      queryable: { query: '', __desc_query: { name: 'Requête', type: 'String' } }
    }
  ]
});
```

Règles à garder en tête :

- `events` accepte deux formes. Un **tableau** est la liste **complète** des événements que votre composant découvre : un événement absent de la liste est retiré du projet **avec les actions qui lui sont attachées**, exactement comme lorsqu'un événement disparaît du manifeste, et un tableau vide retire tous les événements déclarés. Un **objet** seul ajoute un événement, ou redéfinit en place un événement existant, et laisse les autres intacts : utilisez-le pour déclarer ou actualiser vos événements un par un, sans reconstruire votre liste. N'envoyez une liste qu'une fois celle-ci connue ; pour laisser les événements déclarés intacts, omettez la clé plutôt que d'envoyer une liste partielle ou vide. Les événements de votre manifeste ne sont pas concernés : ne les listez pas, ils restent tels quels.
- Chaque entrée doit avoir un `id`, une chaîne non vide : une liste dont une entrée n'a pas d'`id` est ignorée en entier, pour qu'une entrée mal formée ne retire jamais un événement. Utilisez des identifiants stables (l'identifiant du tool, pas son nom) pour qu'un événement renommé garde ses actions, et ne reprenez pas l'identifiant d'un événement ou d'une action du manifeste.
- À l'exécution, déclenchez un événement déclaré comme n'importe quel autre : `PandaBridge.send('tool_8f3a', [{ query: 'tarifs' }])`. Les valeurs envoyées sont celles proposées sous **Depuis l'événement**. PandaSuite ne renvoie pas les événements déclarés à votre composant : conservez la liste dans l'une de vos propriétés si vous en avez besoin à l'exécution.

### Envoyer des événements

Utilisez `PandaBridge.send()` pour émettre des événements définis dans votre manifeste :

```javascript
// Déclencher un événement
PandaBridge.send('onItemSelected', {
  itemId: 'item-123',
  itemName: 'Selected Item'
});
```

### Écouter les actions

```javascript
// Écouter une action spécifique
PandaBridge.listen('setVolume', ([level]) => {
  setVolume(level);
});

// Écouter toutes les actions
PandaBridge.listen((action, args) => {
  console.log('Action reçue:', action, args);
});
```

### Synchronisation

```javascript
// Envoyer des données de synchronisation
PandaBridge.send(PandaBridge.SYNCHRONIZE, [currentValue, 'syncGroupId']);

// Recevoir des données de synchronisation
PandaBridge.synchronize('syncGroupId', (value) => {
  updateFromSync(value);
});
```

### Marqueurs (snapshots)

```javascript
// Fournir les données de snapshot quand demandé
PandaBridge.getSnapshotData(() => {
  return {
    timestamp: getCurrentTime(),
    customData: getComponentState()
  };
});

// Restaurer depuis les données de snapshot
PandaBridge.setSnapshotData(({ data }) => {
  if (data) {
    setCurrentTime(data.timestamp);
    restoreState(data.customData);
  }
});
```

### Ressources

```javascript
// Résoudre un chemin de ressource
const imagePath = PandaBridge.resolvePath('backgroundImage', '/default.png');

// Obtenir l'objet ressource complet
const resource = PandaBridge.resolveResource('backgroundImage');
if (resource) {
  // Accéder aux variantes responsives
  const largePath = resource.srcsets?.['1280w'] || resource.path;
}
```

### Propriétés utilitaires

```javascript
// Vérifier si on est dans le Studio (vs. app publiée)
if (PandaBridge.isStudio) {
  // Afficher un placeholder ou mode preview
}

// Obtenir la langue actuelle
const language = PandaBridge.currentLanguage;
```

### Exemple complet

```javascript
import PandaBridge from 'pandasuite-bridge';

let state = { count: 0 };

PandaBridge.init(() => {
  // Charger les données initiales
  PandaBridge.onLoad(({ properties }) => {
    state.count = properties.initialCount || 0;
    render();
  });

  // Gérer les mises à jour
  PandaBridge.onUpdate(({ properties }) => {
    if (properties.initialCount !== undefined) {
      state.count = properties.initialCount;
      render();
    }
  });

  // Gérer les actions
  PandaBridge.listen('increment', () => {
    state.count++;
    render();
    PandaBridge.send('onCountChanged', { count: state.count });
  });

  PandaBridge.listen('reset', () => {
    state.count = 0;
    render();
    PandaBridge.send('onCountChanged', { count: state.count });
  });

  // Support des snapshots
  PandaBridge.getSnapshotData(() => ({ count: state.count }));
  PandaBridge.setSnapshotData(({ data }) => {
    if (data) {
      state.count = data.count;
      render();
    }
  });
});

function render() {
  document.getElementById('counter').textContent = state.count;
}
```

## API React PandaBridge

La librairie React fournit des hooks pour une intégration plus facile avec les composants React.

### Installation

```bash
npm install pandasuite-bridge-react
```

### Utilisation de base

```jsx
import { PandaBridgeRoot, usePandaBridge } from 'pandasuite-bridge-react';

function MyComponent() {
  const { properties, setProperty, resources } = usePandaBridge({
    actions: {
      increment: () => console.log('Increment appelé'),
      reset: () => console.log('Reset appelé')
    }
  });

  return (
    <div>
      <p>Compteur : {properties?.count || 0}</p>
      <button onClick={() => setProperty('count', (properties?.count || 0) + 1)}>
        Incrémenter
      </button>
    </div>
  );
}

export default function App() {
  return (
    <PandaBridgeRoot>
      <MyComponent />
    </PandaBridgeRoot>
  );
}
```

### Hook usePandaBridge

```jsx
const {
  properties,      // Valeurs actuelles des propriétés
  markers,         // Données des marqueurs
  resources,       // Ressources résolues (par ID)
  setProperty,     // Mettre à jour une propriété
  setProperties,   // Mettre à jour plusieurs propriétés
  addActions       // Ajouter des gestionnaires d'actions
} = usePandaBridge({
  actions: {
    actionName: (args) => { /* gérer l'action */ }
  },
  synchronization: {
    syncGroupId: (value) => { /* gérer la sync */ }
  },
  markers: {
    getSnapshotDataHook: () => ({ /* données du snapshot */ }),
    setSnapshotDataHook: (data) => { /* restaurer depuis le snapshot */ }
  },
  component: {
    getScreenshotHook: (callback) => { /* capture d'écran personnalisée */ },
    onLanguageChanged: (lang) => { /* gérer le changement de langue */ }
  }
});
```

### Accéder aux ressources

```jsx
function ImageDisplay() {
  const { resources } = usePandaBridge();

  const backgroundImage = resources?.backgroundImage;

  if (!backgroundImage) {
    return <div>Aucune image sélectionnée</div>;
  }

  return <img src={backgroundImage.path} alt="Arrière-plan" />;
}
```

### Composants UI intégrés

La librairie React inclut des composants pré-stylés :

```jsx
import {
  Button,
  Input,
  Dropdown,
  Checkbox,
  Alert,
  Tabs,
  Tab,
  Loader
} from 'pandasuite-bridge-react';

function SettingsPanel() {
  return (
    <div>
      <Input placeholder="Entrez une valeur" />
      <Dropdown>
        <Dropdown.Item value="a">Option A</Dropdown.Item>
        <Dropdown.Item value="b">Option B</Dropdown.Item>
      </Dropdown>
      <Checkbox>Activer la fonctionnalité</Checkbox>
      <Button primary>Enregistrer</Button>
      <Alert type="info">Paramètres enregistrés avec succès</Alert>
    </div>
  );
}
```

## Test et publication

### Configuration de développement

Commencez avec un projet boilerplate :

- **JavaScript** : [js-boilerplate-component](https://github.com/pandasuite/js-boilerplate-component)
- **React** : [react-boilerplate-component](https://github.com/pandasuite/react-boilerplate-component)

### Développement local

```bash
# Installer les dépendances
npm install

# Démarrer le serveur de développement
npm start
```

Cela démarre un serveur local. Dans PandaSuite Studio :

1. Ajoutez un composant **Web**
2. Entrez votre URL locale (ex: `http://localhost:3000`)
3. Dans **Métadonnées**, cliquez/appuyez sur **Rafraîchir** pour charger votre manifeste `pandasuite.json`

### Build pour la production

```bash
# Créer le zip du composant
npm run build
```

Cela crée `pandasuite-component.zip`. Glissez ce fichier sur votre projet PandaSuite pour ajouter le composant.

### Publication sur GitHub

```bash
# Créer une release GitHub
npm run release
```

Cela publie votre composant en tant que release GitHub, le rendant disponible pour d'autres utilisateurs.

## Bonnes pratiques

- **Partez d'un boilerplate** : utilisez les templates officiels [JavaScript](https://github.com/pandasuite/js-boilerplate-component) ou [React](https://github.com/pandasuite/react-boilerplate-component) pour assurer une structure de projet correcte
- **Gardez les composants focalisés** : chaque composant devrait faire une seule chose bien ; divisez les fonctionnalités complexes en plusieurs composants
- **Utilisez des IDs explicites** : les IDs de propriétés, événements et actions doivent être descriptifs et cohérents (ex : `onItemSelected` plutôt que `event1`)
- **Fournissez la localisation** : ajoutez `locale_name` pour toutes les chaînes visibles par l'utilisateur pour supporter les utilisateurs français et anglais
- **Gérez le mode Studio** : utilisez `PandaBridge.isStudio` pour afficher des aperçus ou placeholders appropriés lors de l'édition dans le Studio
- **Testez sur toutes les plateformes** : vérifiez que votre composant fonctionne sur les exports iOS, Android et web

## Limitations

- Les composants personnalisés nécessitent des compétences en développement web (HTML, CSS, JavaScript)
- Les composants s'exécutent dans une web view, ce qui peut limiter l'accès à certaines APIs natives de l'appareil
- Les bundles de composants volumineux peuvent impacter les performances de chargement de l'app

## Exemples de composants

Explorez les composants personnalisés existants comme référence :

- [react-media-player-component](https://github.com/pandasuite/react-media-player-component) : lecteur média par URL
- [react-qr-reader-component](https://github.com/pandasuite/react-qr-reader-component) : scanner de QR code
- [after-effects-component](https://github.com/pandasuite/after-effects-component) : animations Lottie
- [Plus de composants sur GitHub](https://github.com/pandasuite?tab=repositories)

## Voir aussi

- [Composant Web](https://docs.pandasuite.com/fr/essentials/components/web/) : intégrer du contenu web sans PandaBridge
- [Data binding](https://docs.pandasuite.com/fr/essentials/data/data-binding/) : connecter des données aux propriétés des composants
- [Actions interactives](https://docs.pandasuite.com/fr/essentials/interactions/interactive-actions/) : déclencher des comportements entre composants
- [Déclencheurs](https://docs.pandasuite.com/fr/essentials/interactions/triggers/) : événements qui activent des actions
