---
title: "Constructeur de pages"
description: "Une landing page assemblée par glisser-déposer — des sections depuis une palette qui deviennent l'élément réel au-dessus de la page, des éléments déplacés entre les colonnes, un arbre des calques sur les mêmes données, l'édition sur place, les largeurs d'appareil et l'annulation."
canonical_url: "https://zizigy.github.io/vue-dnd-kit/fr/docs/examples/page-builder"
---

> Index of every page on this site: [llms.txt](https://zizigy.github.io/vue-dnd-kit/llms.txt)

<example height="45rem" hint="Faites glisser une section ou un élément sur la page ; double-cliquez sur un texte pour le modifier" name="page-builder" title="Constructeur de pages">



</example>

Agrandissez le cadre en plein écran : la palette se fixe à gauche, les calques à droite, et la
page prend la place entre les deux. Plus étroit, ils partagent un tiroir qui s'ouvre depuis la
barre du haut.

## Ce qu'il faut regarder

- **Canvas.vue** : la page est une liste triable de sections avec `placeholder: 'indicator'`.
Elle reste immobile pendant qu'une section voyage, et une ligne entre deux sections dit où elle
va : écarter des sections de 500 px à chaque emplacement ferait trembler toute la page.
`dropzone` confie le dépôt à tout le canevas : une section relâchée à côté ou sous la page
atterrit quand même.
- **ColumnList.vue** : chaque colonne de chaque section est une liste du groupe `blocks`. Les
éléments se déplacent entre les colonnes et les sections et se trient en direct : la page montre
le résultat pendant le glissement. Chaque colonne est un composant à part entière, et les
colonnes sont des listes à l'intérieur des items d'une autre liste.
- **Palette.vue** : les modèles de section sont une liste du groupe de la page avec
`pull: 'clone'`, `put: false` et `sort: false` : elle distribue des copies et ne reprend rien.
Les éléments en dessous (**ElementChip.vue**) sont des éléments déplaçables simples, et une
colonne crée le bloc avec `createItem`.
- **Layers.vue** : la même page sous forme d'arbre triable, sur les mêmes données. Une ligne
déplacée là bouge sur la page, et inversement.
- **builder.ts** : la page, la sélection, l'appareil et l'historique. Les dépôts écrivent la page
via les listes ; les boutons de la barre d'outils et les raccourcis la modifient ici. Chaque
changement est une étape d'annulation.

## Une section en main est la vraie section

**SectionGhost.vue** est l'overlay à la fois des sections de la page et des modèles de la
palette. Au-dessus de la page (`to.list` est la liste de la page), il rend la section elle-même, à
la largeur de la page, réduite sous le pointeur. Quand le dépôt est décidé (`status: 'dropping'`),
il revient à l'échelle 1 pendant que la boîte vole jusqu'à la place de la section : il atterrit
exactement dessus. Le dépôt attend cette transition : rien ne saute à la fin.

```vue [SectionGhost.vue]
<script setup lang="ts">
const props = defineProps<{ items: DragItem[]; status: DnDStatus; to: DropDestination | null }>();
const section = computed(() => props.items[0]!.item as Section);
const fromPalette = computed(() => !find(section.value.id));
const onPage = computed(() => props.to?.list === PAGE);

const look = computed(() => {
  if (fromPalette.value && !onPage.value) return 'card'; // still the palette's card
  return props.status === 'dropping' ? 'landing' : 'ghost';
});
</script>

<template>
  <DragPreview v-if="look === 'card'" :id="items[0]!.id" :style="{ width: '100%', height: '100%' }" />
  <div v-else class="replica" :class="look" :style="{ width: `${pageWidth}px`, '--shrink': shrink }">
    <SectionView :section="section" />
  </div>
</template>

<style scoped>
.replica { transform-origin: 0 0; scale: var(--shrink); transition: scale 0.25s ease; }
.replica.landing { scale: 1; }
</style>
```

Loin de la page, un modèle reste ce qu'il était dans la palette : `DragPreview` montre la copie de
sa carte faite par la bibliothèque. La place de la section sur la page est l'emplacement : pendant
le glissement, elle y attend, estompée ; une fois déposée, sa nouvelle place reste vide jusqu'à ce
que l'overlay y ait atterri.

```css [PageSection.vue]
.page-section[data-dnd-placeholder] { opacity: 0; }
html[data-dnd-dragging] .page-section[data-dnd-placeholder] { opacity: 0.35; }
```

Chaque section dessine la ligne d'après son propre `indicator`, et dit ce que fait le dépôt :
*Move Features here*, ou *Add Pricing here* pour un modèle.

## Les éléments : aux couleurs de la section où ils vont

Les blocs se font glisser par tout leur corps ; une section par la poignée de son étiquette, car
son corps est du contenu à cliquer et à faire glisser. **BlockGhost.vue**, l'overlay des blocs et
des pastilles, lit dans `to` la colonne où il irait et prend les couleurs de sa section : un bouton
au-dessus du pied de page sombre devient clair avant que vous ne relâchiez. La boîte a la taille
de l'emplacement du bloc à cet endroit (`overlayFit: 'slot'`, la valeur par défaut), et le bloc s'y
met en page comme il le fera après le dépôt.

```ts [BlockGhost.vue]
// The column it would go into (`to.list` is the column's id): its section's tone.
const look = computed(() => {
  const at = props.to && find(String(props.to.list));
  return at ? { ...TONES[SECTIONS[at.section.kind].tone], ...typeScale(pageWidth.value) } : null;
});
```

Une pastille de la palette est un élément déplaçable simple qui transporte son type. Chaque
colonne en fait un bloc, et le nouveau bloc se place dans la colonne, rendu par le template propre
à la colonne, jusqu'à ce que vous relâchiez ou passiez ailleurs :

```ts [ColumnList.vue]
makeSortableList(list, {
  id: props.column.id,
  items: blocks,
  group: 'blocks',
  overlay: BlockGhost,
  createItem: (active) => {
    const kind = (active.data as ChipData | undefined)?.kind;
    return kind ? createBlock(BLOCKS[kind].spec) : undefined;
  },
});
```

`blocks` est un `computed` modifiable sur `props.column.blocks` plutôt qu'un `toRef` : l'annulation
met de nouveaux objets dans la page, et la colonne doit les suivre.

## Les modèles : des copies sortent, rien n'entre

```ts [Palette.vue]
makeSortableList(list, {
  id: 'templates',
  items: templates,
  group: { name: 'sections', pull: 'clone', put: false },
  sort: false,
  clone: (template) => createSection(template.kind), // new ids all the way down
  overlay: SectionGhost,
});
```

Les modèles sont de vraies sections : leurs cartes les montrent donc (en réduction) et l'overlay
peut les rendre. Un clic sur une carte, ou <kbd value="Enter">



</kbd>

, ajoute la section après celle qui
est sélectionnée : la page peut se construire sans aucun glissement.

## Les calques : la même page sous forme d'arbre

```ts [Layers.vue]
makeSortableTree(tree, {
  id: LAYERS,
  items: sections as Ref<LayerNode[]>,
  // Keys of their own: the canvas's lists already drag these ids.
  getKey: (node) => `layer-${node.id}`,
  getChildren: (node) => (isSection(node) ? node.columns : isColumn(node) ? node.blocks : undefined),
  setChildren: (node, children) => { … },
  // Blocks go into columns, nothing into blocks, sections stay at the top.
  canNest: (node, parent) => levelOf(node) === levelOf(parent) + 1,
  maxDepth: 2,
  placeholder: 'indicator',
  // A tree takes any row at its top level: a block let go there flies back.
  onBeforeDrop: (event) => (event.to.parentKey !== null || event.items.every(isSection) ? 'accept' : 'reject'),
});
```

Les clés sont partagées par tout le provider : les lignes reçoivent donc des clés à elles. Les
colonnes sont aussi des lignes, `disabled` : elles acceptent des blocs et restent où elles sont.
Là où un bloc ne peut pas aller, entre deux sections, sa ligne devient rouge. Survoler une ligne
entoure son nœud sur la page ; sélectionner un nœud sur la page ouvre sa section ici et fait
défiler sa ligne jusqu'à la rendre visible.

## L'édition sur place, et le clavier

Un bloc se fait glisser depuis n'importe quel point : ses textes ne sont donc pas modifiables
tant que vous ne double-cliquez pas dessus ; ils deviennent alors `contenteditable`, et `canDrag`
refuse les glissements pour qu'un appui place le curseur. **EditableText.vue** n'écrit le DOM
depuis le modèle que tant que le texte n'est pas en cours de modification : le curseur ne saute
donc jamais, et l'annulation s'affiche quand même.

Le provider garde <kbd value="Space">



</kbd>

 pour les glissements et laisse <kbd value="Enter">



</kbd>

 à
l'éditeur :

```vue [index.vue]
<DnDProvider
  :keyboard="{ keys: { start: ['Space'], drop: ['Space', 'Enter'], cancel: ['Escape'] } }"
  :announcements="announcements"
  :screen-reader-instructions="instructions"
>
```

Allez sur un bloc avec Tab : <kbd value="Space">



</kbd>

 le saisit et les flèches le déplacent dans sa
colonne et jusque dans les suivantes ; <kbd value="Enter">



</kbd>

 le modifie. Une section se déplace par
sa poignée de la même façon, ou par les flèches de sa barre d'outils. <kbd value="Ctrl">



</kbd>

<kbd value="Z">



</kbd>

 annule, <kbd value="Delete">



</kbd>

 supprime, <kbd value="Ctrl">



</kbd>

 <kbd value="D">



</kbd>


duplique. Les lecteurs d'écran entendent "the Hero section, position 3 of 7 on the page" plutôt
que des ids.

## L'annulation, et les changements sans glissement

Chaque dépôt est une étape de l'historique : un moniteur sauvegarde la page une fois que les listes
l'ont écrite.

```ts [Builder.vue]
useDnDMonitor({ onDragEnd: () => builder.commit() });
```

L'annulation remet un instantané en place. Les listes ont `animateChanges` activé : ce que l'instantané a
déplacé y glisse donc, comme les sections que la barre d'outils monte et descend ; les sections
ajoutées apparaissent en fondu et celles qui sont supprimées disparaissent en fondu.

En savoir plus : [Listes triables](https://zizigy.github.io/docs/guides/sortable-lists#an-insertion-line) ·
[Listes qui partagent des items](https://zizigy.github.io/docs/guides/shared-lists) · [Arbres](https://zizigy.github.io/docs/guides/trees) ·
[L'overlay](https://zizigy.github.io/docs/guides/overlay) · [Animations](https://zizigy.github.io/docs/guides/animations) ·
[Accessibilité](https://zizigy.github.io/docs/guides/accessibility)

## Sitemap

See the full [sitemap](https://zizigy.github.io/vue-dnd-kit/sitemap.md) for all pages.
