# Dépendances et liens entre événements

> Tracez des liens fin-début et d’autres types entre événements depuis vos données, stylez-les, réagissez aux clics et vérifiez les dépendances dans votre code.

Source: https://superscheduler.org/fr/docs/links-dependencies/
Reviewed: 2026-10-07

Passez les liens sous forme de tableau d’objets { id, from, to, type }, où from et to sont des id d’événements ; FinishToStart est le type par défaut et les lignes courbes sont le tracé par défaut. Stylez les liens avec color, width, style et cssClass ou avec les tokens --super-scheduler-link, et traitez les clics avec onLinkClick. La bibliothèque dessine les liens mais ne les fait pas respecter : vérifiez les dépendances dans onEventMoving et onEventMove. La création de liens par glisser d’un événement à l’autre est réservée et n’est pas implémentée dans la 0.1.0.

Étapes de production, tournages et campagnes dépendent les uns des autres : la découpe commence quand la conception se termine, deux équipes démarrent ensemble. SuperScheduler dessine ces dépendances sous forme de liens entre événements et les garde attachés pendant que les utilisateurs font défiler, zooment et déplacent. Ce qu’une dépendance signifie pour la planification (un déplacement est-il permis, les successeurs se décalent-ils) reste dans votre code, là où vivent vos règles.

Les liens nécessitent SuperScheduler Pro.

## Tracer des liens à partir des données
Un lien est un objet simple dans la prop `links`. `from` et `to` sont des id d’événements (l’`id` de `EventData`) ; `id` identifie le lien lui-même.

```ts
// src/links.ts
import type { SuperScheduler } from 'super-scheduler'

// `from` and `to` are event ids. Links between ids that are not loaded are not drawn.
export const LINKS: SuperScheduler.LinkData[] = [
  // Cutting can start once design has finished (the default type).
  { id: 'design-cut', from: 'design', to: 'cutting', type: 'FinishToStart' },
  // Painting and drying start together.
  { id: 'paint-dry', from: 'painting', to: 'drying', type: 'StartToStart', style: 'dashed' },
  // Inspection and packing finish together; drawn below the events, in a fixed color.
  {
    id: 'qa-pack',
    from: 'inspection',
    to: 'packing',
    type: 'FinishToFinish',
    color: '#7c3aed',
    layer: 'Below',
  },
  // A heavier line, styled further by CSS through its class.
  { id: 'ship', from: 'packing', to: 'shipping', width: 3, cssClass: 'link--critical' },
]

export const LINK_OPTIONS = {
  linkShape: 'Curved', // default; 'RightAngled' draws segments
  linkBottomMargin: 12, // clearance for links that run below events (8 px minimum for curves)
  linkLayer: 'Above', // default layer of links without their own `layer`
  links: LINKS,
} satisfies SuperScheduler.SchedulerConfig
```
Vous devriez voir quatre lignes : une courbe pleine de la conception à la découpe, une en pointillés entre peinture et séchage, une violette sous les événements d’inspection et d’emballage, et une plus épaisse vers l’expédition.

Les liens proviennent de votre modèle de données : une colonne `dependsOn`, une table de dépendances, ou une règle comme « même commande, étape suivante ». Dérivez le tableau avec `useMemo` pour que son identité ne change que lorsque les données changent. Depuis le code, `control.links` propose `list`, `add(link)`, `update(link)`, `remove(link or id)`, `find(id)`, `findByFromTo(from, to)` et `findAllByFromTo(from, to)` ; avec un état React, préférez mettre à jour votre tableau.

## Types de liens
| `type` | Tracé depuis | Vers | Sens habituel |
|---|---|---|---|
| `'FinishToStart'` (défaut) | la fin de `from` | le début de `to` | `to` commence après la fin de `from` |
| `'StartToStart'` | le début de `from` | le début de `to` | les deux commencent ensemble, ou `to` après le début de `from` |
| `'FinishToFinish'` | la fin de `from` | la fin de `to` | les deux se terminent ensemble |
| `'StartToFinish'` | le début de `from` | la fin de `to` | `to` se termine quand `from` commence |

Le type change l’endroit où la ligne s’attache et la façon dont elle est tracée. Il ne change pas ce que les utilisateurs ont le droit de faire ; voir [faire respecter les dépendances](#validation).

## Forme et tracé
| Option | Défaut | Effet |
|---|---|---|
| `linkShape` | `'Curved'` | Courbes douces avec un petit anneau à l’origine et une pointe de flèche. `'RightAngled'` trace des segments droits. |
| `linkBottomMargin` | `17` | Dégagement pour les liens qui passent sous les événements (au moins 8 px pour les courbes) |
| `linkLayer` | `'Above'` | Couche par défaut des liens sans `layer` propre : au-dessus ou en dessous des événements |
| `linkWidth` | `'Auto'` | Épaisseur de trait par défaut en pixels (les courbes font 2 px) |

Les liens entre deux extrémités du même côté s’arrondissent vers l’extérieur avec une amplitude limitée, si bien que les liens vers des lignes éloignées ne forment pas de boucles démesurées. Les liens vers l’arrière empruntent l’espace entre les lignes, et passent sous les deux événements quand ces lignes se chevauchent. Les tracés évitent les deux événements liés, pas tous les événements intermédiaires : ce n’est pas un routeur d’évitement d’obstacles.

## Styler les liens
Lien par lien, `color`, `width`, `style` (`'solid'`, `'dashed'`, `'dotted'`), `layer` et `cssClass` modifient un seul lien ; un `width` explicite l’emporte sur le token CSS d’épaisseur. Pour le reste, utilisez des tokens sur n’importe quel ancêtre ou sur une classe de lien :

```css
.planning {
  --super-scheduler-link: #8b5cf6;
  --super-scheduler-link-hover: #6d28d9;
  --super-scheduler-link-width: 2;
}
.dark .planning {
  --super-scheduler-link: #b89afa;
  --super-scheduler-link-hover: #d8c6ff;
}
/* A class from your data or from onBeforeLinkRender */
.link--late {
  --super-scheduler-link: var(--super-scheduler-danger);
}
```

Les liens sont des éléments SVG portant la classe `super-scheduler__link` et `data-super-scheduler-part="link"` ; la flèche et l’anneau d’origine prennent la couleur du lien.

Pour styler en fonction de l’état des événements liés, utilisez `onBeforeLinkRender`. Il reçoit une copie du lien dans `args.data`, ainsi que les deux événements dans `args.from` et `args.to`. Modifiez `color`, `width`, `style`, `cssClass`, `layer` ou `type` sur la copie ; les extrémités ne peuvent pas changer. Le hook s’exécute une fois par lien tracé, puis à nouveau seulement quand l’apparence de l’un de ses événements change.

## Quand les liens sont tracés
Un lien est tracé quand les deux événements sont chargés et disposés dans la même section de la grille (les lignes qui défilent, ou les mêmes lignes figées en haut ou en bas). Il n’est pas tracé quand :

- l’un des id n’est pas chargé, par exemple hors de la plage que vous avez récupérée ;
- une extrémité est masquée par un filtre ou se trouve dans une ligne d’arborescence repliée ;
- le niveau de zoom dessine une extrémité comme une fine barre de vue d’ensemble ;
- il se trouve entièrement hors de la zone rendue (il réapparaît quand il revient dans la vue).

Pendant qu’un événement est déplacé, ses liens suivent l’aperçu avec le même tracé.

## Clic, survol et menus
- `onLinkClick` s’exécute au clic avec `args.link` (un `SuperScheduler.Link` : `data`, `id()`, `from()`, `to()`, `type()`) et `args.originalEvent` ; `args.preventDefault()` empêche l’appel de `onLinkClicked`, qui s’exécute après.
- `linkBubble` affiche le `bubbleHtml` d’un lien au survol.
- `contextMenuLink`, ou le `contextMenu` propre à un lien, s’ouvre au clic droit.

Les liens ne réagissent au pointeur que si l’une de ces options est configurée ; par défaut, ils ne gênent donc jamais le déplacement des événements. Leur zone de clic invisible est plus large que la ligne visible.

## Faire respecter les dépendances dans votre application
Les liens sont des dessins. Rien n’empêche un utilisateur de déplacer la découpe avant la fin de la conception, sauf si votre code le vérifie. Écrivez la règle une fois, sous forme de fonction simple :

```ts
// src/finishToStart.ts
import { SuperScheduler } from 'super-scheduler'

export interface Bounds {
  /** Ticks of the civil wall clock, as SuperScheduler.Date.getTime() returns them. */
  readonly start: number
  /** Exclusive end. */
  readonly end: number
}

export const boundsOf = (event: SuperScheduler.EventData): Bounds => ({
  start: new SuperScheduler.Date(event.start).getTime(),
  end: new SuperScheduler.Date(event.end).getTime(),
})

/**
 * Finish-to-start rule: a task may not start before each of its predecessors ends.
 * Returns the first link that `proposed` times for `taskId` would break, or null.
 * Other link types need rules of their own; links only draw what the data says.
 */
export function brokenFinishToStart(
  taskId: SuperScheduler.EventId,
  proposed: Bounds,
  links: readonly SuperScheduler.LinkData[],
  find: (id: SuperScheduler.EventId) => Bounds | null,
): SuperScheduler.LinkData | null {
  for (const link of links) {
    if ((link.type ?? 'FinishToStart') !== 'FinishToStart') continue
    if (link.to === taskId) {
      const predecessor = find(link.from)
      if (predecessor !== null && proposed.start < predecessor.end) return link
    } else if (link.from === taskId) {
      const successor = find(link.to)
      if (successor !== null && successor.start < proposed.end) return link
    }
  }
  return null
}

/** Ids of the links the current data already breaks, to draw them differently. */
export function lateLinks(
  links: readonly SuperScheduler.LinkData[],
  events: readonly SuperScheduler.EventData[],
): ReadonlySet<SuperScheduler.LinkData> {
  const byId = new Map(events.map((event) => [event.id, boundsOf(event)]))
  const late = new Set<SuperScheduler.LinkData>()
  for (const link of links) {
    const from = byId.get(link.from)
    const to = byId.get(link.to)
    if ((link.type ?? 'FinishToStart') === 'FinishToStart' && from && to && to.start < from.end)
      late.add(link)
  }
  return late
}
```
Appelez-la ensuite depuis les hooks qui s’exécutent pendant et avant une modification :

```tsx
// src/ProductionPlan.tsx
import { useCallback, useMemo, useState } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerEventsChangeArgs, SchedulerProps, SuperScheduler } from 'super-scheduler'
import { boundsOf, brokenFinishToStart, lateLinks } from './finish-to-start'
import type { Bounds } from './finish-to-start'

export function ProductionPlan(props: {
  resources: SuperScheduler.ResourceData[]
  initialTasks: SuperScheduler.EventData[]
  /** Dependencies come from your data: the library draws them, it does not invent them. */
  initialLinks: SuperScheduler.LinkData[]
}) {
  const [tasks, setTasks] = useState(props.initialTasks)
  const [links, setLinks] = useState(props.initialLinks)
  const owned = useMemo(() => tasks.slice(), [tasks])

  // Links the data already breaks get a class; the color comes from CSS.
  const drawnLinks = useMemo(() => {
    const late = lateLinks(links, tasks)
    return links.map((link) => (late.has(link) ? { ...link, cssClass: 'link--late' } : link))
  }, [links, tasks])

  const onEventsChange = useCallback(
    (args: SchedulerEventsChangeArgs) => setTasks([...args.events]),
    [],
  )

  const config = useMemo<SchedulerProps>(() => {
    // The other tasks as the control holds them right now.
    const finder =
      (control: SuperScheduler.SchedulerApi) =>
      (id: SuperScheduler.EventId): Bounds | null => {
        const event = control.events.find(id)
        return event === null ? null : boundsOf(event.data)
      }
    return {
      linkShape: 'Curved',
      // While dragging: refuse and explain, frame by frame.
      onEventMoving(args) {
        const proposed = { start: args.start.getTime(), end: args.end.getTime() }
        const broken = brokenFinishToStart(args.e.id(), proposed, links, finder(this))
        if (broken !== null) {
          args.allowed = false
          args.message = 'This breaks a finish-to-start dependency'
        }
      },
      onEventResizing(args) {
        const proposed = { start: args.start.getTime(), end: args.end.getTime() }
        if (brokenFinishToStart(args.e.id(), proposed, links, finder(this)) !== null)
          args.allowed = false
      },
      // Before the commit: also covers keyboard moves.
      onEventMove(args) {
        const proposed = { start: args.newStart.getTime(), end: args.newEnd.getTime() }
        if (brokenFinishToStart(args.e.id(), proposed, links, finder(this)) !== null)
          args.preventDefault()
      },
      // Clicking a link offers to remove the dependency.
      onLinkClick(args) {
        const link = args.link.data
        void confirmWithUser('Remove this dependency?').then((ok) => {
          if (ok) setLinks((current) => current.filter((item) => item.id !== link.id))
        })
      },
    }
  }, [links])

  return (
    <SuperSchedulerComponent
      {...config}
      events={owned}
      links={drawnLinks}
      onEventsChange={onEventsChange}
      resources={props.resources}
      startDate="2026-10-05"
      days={14}
      scale="Day"
    />
  )
}
```
Vous devriez voir :

- quand vous déplacez une étape avant la fin de son prédécesseur, la carte de glissement indique « Not allowed · This breaks a finish-to-start dependency » et le dépôt est refusé ;
- les liens que vos données enfreignent déjà, tracés dans la couleur de danger ;
- un clic sur un lien qui demande s’il faut supprimer cette dépendance.

`onEventMoving` et `onEventResizing` s’exécutent à chaque image d’un glissement : gardez donc la vérification légère en ne consultant que les liens de l’événement déplacé, comme le fait `brokenFinishToStart`. `onEventMove` s’exécute une seule fois avant la validation et couvre aussi les déplacements au clavier et les événements déposés depuis l’extérieur.

> **Tip:**
> La replanification automatique (repousser les successeurs quand un prédécesseur s’allonge) relève aussi de la logique applicative. Dans `onEventMoved` ou `onEventResized`, calculez les nouveaux horaires des successeurs, mettez à jour votre état et regroupez toute la modification dans un seul [lot d’historique](https://superscheduler.org/fr/docs/undo-redo/#commands) pour qu’une seule annulation la défasse. Chemins critiques, décalages et calendriers de dépendances ne font pas partie de la bibliothèque.

## Réservé : création interactive de liens
> **Limitation:**
> La création de liens en glissant d’un événement à un autre n’est pas implémentée dans la 0.1.0. `linkCreateHandling`, `onLinkCreate`, `onLinkCreated`, `linkDotSize` et `linkPointSize` sont typés pour la compatibilité, émettent un avertissement unique en développement et ne font rien ; `linksLoadMethod` et `control.links.load()` ne chargent rien depuis une URL. Les champs `text` et `textAlignment` d’un lien sont acceptés, mais aucun libellé n’est tracé.

Pour permettre dès aujourd’hui aux utilisateurs de créer des dépendances, ajoutez-les depuis votre propre interface (par exemple « Ajouter une dépendance » dans le panneau de détail ou le menu contextuel d’un événement, en choisissant l’autre événement dans une liste ou dans la sélection courante) et ajoutez le lien à votre tableau.

## Voir aussi
→ https://superscheduler.org/fr/examples/manufacturing-orders/
→ https://superscheduler.org/fr/examples/video-production/
→ https://superscheduler.org/fr/examples/port-berths/
- [Glisser, redimensionner et règles métier](https://superscheduler.org/fr/docs/drag-resize-rules/) pour `onEventMoving`, `onEventMove` et les messages de refus.
- [Annuler, rétablir et historique](https://superscheduler.org/fr/docs/undo-redo/) pour rendre la replanification annulable.
