Un kit source, pas une nouvelle version npm.

Le kit MIT bcefd29d reste validé avec la dépendance publique @newvexa/flowora@0.1.0-beta.1 ; ses sources et son ZIP sont inchangés. L’intégration du site compile ces sources avec la candidate locale 0.1.0-beta.2, non publiée sur npm : cela ne requalifie pas les résultats beta.1 livrés avec le kit. Les composants Atelier, leur directive et leurs helpers ne sont exportés par aucun de ces paquets : ils appartiennent à l’exemple MIT téléchargeable, à compiler dans votre application hôte. Les nouveaux noms de connexions sont décrits séparément dans le guide beta.2 des liens.

Télécharger les sources réutilisables

Le ZIP contient les sources, les trois fichiers de démonstration Angular, les tests, les configurations TypeScript, un manifeste privé et la licence MIT. Il ne contient ni node_modules, ni les sorties temporaires .build/. Conservez la licence et ses mentions lorsque vous réutilisez le kit.

Télécharger le kit source MIT Lire le README du kit

Préparez d’abord une application conforme au guide Angular, DI & templates et à la configuration AOT/linker de l’installation. Le manifeste private: true du kit sert à sa validation ; il ne désigne pas une bibliothèque npm publiée. Les artefacts partial-Ivy de validation ne remplacent pas la compilation et le linking Angular normaux de l’hôte.

Trois types précis, des données uniquement

Type / composant AngularConfigurationPorts
atelier-note
FloworaNoteNodeComponent
text : texte brut, 2 000 caractères maximum.Aucun.
atelier-http
FloworaHttpNodeComponent
method et url : configuration de requête, URL de 2 048 caractères maximum.Entrée in, sortie out ; type flow.
atelier-decision
FloworaDecisionNodeComponent
expression : chaîne stockée, 2 000 caractères maximum.Entrée in, sorties true / false ; type flow.

Les sélecteurs correspondants sont flowora-note-node, flowora-http-node et flowora-decision-node. Le catalogue ATELIER_NODE_CATALOG fournit exactement ces trois presets. Les entrées de flux acceptent plusieurs connexions ; les sorties gardent la cardinalité définie par leurs ports.

Les cartes sont prévues pour les dimensions stock de 220 × 112. Leur palette ivoire, orange, graphite et sauge conserve une zone libre en haut à droite pour le bouton de propriétés. Un nom long est tronqué visuellement, jamais dans le document.

Configurer n’est pas exécuter.

Le kit n’effectue aucune requête HTTP, aucun eval, aucune compilation d’expression et aucun rendu innerHTML. Une expression qui ressemble à du code reste du texte. Les valeurs de démonstration sont fictives, notamment https://example.invalid/atelier ; elles ne désignent pas un service fonctionnel.

Copier les fichiers, conserver CSS et imports

Dans les exemples suivants, atelier-nodes/ est une simple convention de dossier dans votre application, pas un chemin imposé par npm. Copiez-y le contenu de src/ du ZIP, en conservant toute l’arborescence angular/ et ses CSS. Pour l’exemple hôte ci-dessous, copiez aussi demo/atelier-demo.component.css à côté du composant sous le nom atelier-host.component.css.

Organisation · fichiers de l’application hôte
// Convention de dossier dans votre application, à adapter :
// copiez le CONTENU de src/ du ZIP dans atelier-nodes/.
atelier-nodes/
  catalog.ts
  index.ts
  angular/                 // composants, directive, validation URL et node-card.css
atelier-host.component.ts
atelier-host.component.html
atelier-host.component.css // copie de demo/atelier-demo.component.css

L’entrée Angular est ./atelier-nodes/index.js : composants standalone, directive et helpers. L’entrée portable est ./atelier-nodes/catalog.js : catalogue et helpers sans import Angular, DOM ou API Node. Conservez les suffixes relatifs .js ; TypeScript les résout vers les sources .ts pendant la compilation. Si vous changez le dossier, ajustez les chemins de l’hôte et les références de styles, sans retirer les CSS des composants.

Étendre le catalogue dans chaque instance

Le point de branchement est EditorOptions.catalog. Utilisez combineAtelierCatalog(NODE_CATALOG) pour garder les nœuds standard et ajouter les presets Atelier à la palette. Une instance créée sans ce catalogue peut accepter un type inconnu dans un JSON structurellement valide, mais cela ne rend pas addFromCatalog('atelier-http', …) disponible.

TypeScript · catalogue portable, import et restauration
import { Editor, NODE_CATALOG } from '@newvexa/flowora';
import {
  combineAtelierCatalog, createCustomDemoDocument, createAtelierEditor,
} from './atelier-nodes/catalog.js';

// Ce catalogue conserve les builtins et ajoute les trois presets Atelier.
const catalog = combineAtelierCatalog(NODE_CATALOG);
const editor = new Editor(createCustomDemoDocument(), { catalog });
try {
  editor.addFromCatalog('atelier-http', { x: 280, y: 420 });
  const json = editor.toJSON();

  // Import dans l'instance existante : son catalogue reste en place.
  editor.loadJSON(json);

  // Si votre restauration crée une AUTRE instance, passez encore le catalogue.
  const restoredEditor = new Editor(createCustomDemoDocument(), { catalog });
  try {
    restoredEditor.loadJSON(json);
  } finally {
    restoredEditor.dispose();
  }
} finally {
  editor.dispose();
}

// Variante : cette fabrique fait elle-même combineAtelierCatalog(NODE_CATALOG).
const demoEditor = createAtelierEditor();
demoEditor.dispose(); // Dans un écran, attendez sa destruction pour le libérer.

createCustomDemoDocument() crée le document fictif : une note hors du flux et une chaîne départ → HTTP → décision → deux réponses. createAtelierEditor() crée une instance avec ce document et le catalogue combiné. Dans un écran réel, conservez une instance stable par document et appelez dispose() seulement lorsque son propriétaire est détruit, pas à chaque rendu.

La même règle vaut pour toutes les ouvertures et restaurations. loadJSON() sur une instance déjà configurée conserve son catalogue ; toute fonction d’import, de restauration ou de changement de document qui construit un nouvel Editor doit lui repasser le catalogue étendu, ou employer la fabrique. Le catalogue n’est pas ajouté par la lecture du JSON. Pour les fichiers, le stockage et les erreurs atomiques, reprenez le guide JSON & données locales en conservant ce branchement.

Si l’hôte a d’autres presets, passez son catalogue de base à combineAtelierCatalog(hostBaseCatalog) ou comme troisième argument de createAtelierEditor(document, options, hostBaseCatalog). Ce catalogue de base doit inclure les builtins voulus, sans contenir déjà les trois types Atelier : les collisions sont refusées au lieu d’écraser silencieusement un preset.

Brancher les trois composants et le contexte typé

FloworaNodeTemplateDirective vient du kit, pas du paquet npm. L’attribut floworaNodeTemplate et sa référence exportée donnent accès à face.template. Son garde de contexte permet à strictTemplates de connaître node: EditorNode, selected: boolean et preset: NodePreset | undefined.

Ce composant et le template suivant constituent un exemple hôte contextualisé. Les fichiers demo/atelier-demo.component.ts, .html et .css du ZIP restent la référence complète. Le CSS de démo copié donne une hauteur explicite de 640 px à l’hôte et 100 % au renderer ; prévoyez au moins 420 px réellement disponibles pour le canevas, notamment dans un conteneur flex.

TypeScript · imports locaux et propriétaire de l’éditeur
// atelier-host.component.ts · après copie des sources et du CSS de démo
import { ChangeDetectionStrategy, Component, OnDestroy, signal } from '@angular/core';
import { FloworaEditorComponent } from '@newvexa/flowora/angular';
import type { EditorNode, NodePatch } from '@newvexa/flowora';
import {
  FloworaNoteNodeComponent, FloworaHttpNodeComponent,
  FloworaDecisionNodeComponent, FloworaNodeTemplateDirective,
  builtinConfigPreview, createAtelierEditor,
} from './atelier-nodes/index.js';

@Component({
  selector: 'app-atelier-host', standalone: true,
  imports: [
    FloworaEditorComponent, FloworaNoteNodeComponent, FloworaHttpNodeComponent,
    FloworaDecisionNodeComponent, FloworaNodeTemplateDirective,
  ],
  templateUrl: './atelier-host.component.html',
  styleUrls: ['./atelier-nodes/angular/node-card.css', './atelier-host.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AtelierHostComponent implements OnDestroy {
  readonly editor = createAtelierEditor();
  readonly configPreview = builtinConfigPreview;
  readonly error = signal('');

  updateNode(node: EditorNode, patch: NodePatch): void {
    try {
      this.editor.updateNode(node.id, patch);
      this.error.set('');
    } catch (error: unknown) {
      this.error.set(error instanceof Error ? error.message : 'Modification refusée.');
    }
  }
  ngOnDestroy(): void { this.editor.dispose(); }
}
Angular · dispatch des faces et repli lisible
<!-- atelier-host.component.html · inspector intégré conservé -->
<flowora-editor [editor]="editor" [nodeTemplate]="face.template" />
<ng-template floworaNodeTemplate #face="floworaNodeTemplate"
  let-node let-selected="selected" let-preset="preset">
  @switch (node.type) {
    @case ('atelier-note') {
      <flowora-note-node [node]="node" [selected]="selected"
        (patch)="updateNode(node, $event)" />
    }
    @case ('atelier-http') {
      <flowora-http-node [node]="node" [selected]="selected"
        (patch)="updateNode(node, $event)" />
    }
    @case ('atelier-decision') {
      <flowora-decision-node [node]="node" [selected]="selected"
        (patch)="updateNode(node, $event)" />
    }
    @default {
      <article class="atelier-card fallback" [class.selected]="selected"
        [style.--card-accent]="preset?.color || '#3e7268'">
        <header class="card-header">
          <span class="type-tag">{{ preset?.category || 'Personnalisé' }}</span>
          <strong class="node-name" [title]="node.name">{{ node.name }}</strong>
        </header>
        <span class="fallback-label">{{ preset?.label || node.type }}</span>
        <span class="fallback-preview">{{ configPreview(node) }}</span>
        <footer class="card-footer fallback-footer">
          <span class="fallback-type">{{ node.type }}</span>
          @if (node.outputs.length > 1) {
            <span class="branches">
              @for (port of node.outputs; track port.id) { <span>{{ port.label }}</span> }
            </span>
          }
        </footer>
      </article>
    }
  }
</ng-template>
@if (error()) { <p class="host-error" role="alert">{{ error() }}</p> }

Un nodeTemplate global exige un repli

nodeTemplate remplace toutes les faces non-groupes, pas seulement les trois types Atelier. Flowora beta.1 ne propose pas de retour au template interne depuis votre branche @default. Un repli vide rendrait les nœuds standard vides ; le repli ci-dessus affiche donc le nom, le libellé et la catégorie du preset, le type et les libellés de branches.

builtinConfigPreview(node) choisit quelques champs textuels connus des builtins et produit un aperçu de 80 caractères maximum. Il ne sérialise jamais toute la configuration ; pour un type inconnu ou un champ vide, il affiche « Configuration dans les propriétés ». Nom, libellés et aperçu passent uniquement par l’interpolation Angular. Ce repli est une présentation lisible, pas une reproduction des icônes ou du HTML interne de Flowora.

Le vrai FloworaEditorComponent continue à gérer les ports, les connexions, la sélection, le bouton de propriétés, le déplacement et le double-clic. Les groupes conservent leur renderer spécifique. Ne dupliquez pas les ports et ne recouvrez pas leur zone ou celle des propriétés avec un overlay : les libellés de branches du repli ne sont pas des ports supplémentaires.

Éditer par commandes, pas par mutation

Les trois composants sont standalone, en ChangeDetectionStrategy.OnPush, avec une entrée obligatoire node: EditorNode, une entrée selected et une sortie patch: EventEmitter<NodePatch>. Ils ne possèdent pas le store. L’hôte applique immédiatement chaque patch avec editor.updateNode(node.id, patch) et affiche les erreurs du store ; aucun snapshot n’est muté directement.

La validation et l’émission se font sur change, pas à chaque caractère. Les helpers noteTextPatch, httpConfigPatch et decisionExpressionPatch construisent une configuration complète à partir de { ...node.config, champModifié } : en beta.1, un patch { config } remplace la configuration, sans fusion automatique. Les champs JSON inconnus de l’hôte sont ainsi préservés et une édition acceptée passe par l’historique annulable.

Une validation refusée n’émet pas de patch et n’écrit rien dans le store. Appliquez les patches au nœud actuel ; un patch construit depuis un ancien snapshot n’est pas une fusion concurrente. Si une opération devient asynchrone, reconstruisez la configuration depuis la version courante avant la commande.

Seuls les wrappers des contrôles portent data-flowora-no-drag et arrêtent pointerdown, keydown et dblclick par stopPropagation(). Ils ne bloquent pas les actions par défaut : saisie, sélection de texte par double-clic et Tab restent disponibles, sans ouvrir l’inspector ni déclencher les raccourcis de suppression ou déplacement du canevas. Ne bloquez pas tous les événements de la carte : son fond et ses en-têtes restent déplaçables et le double-clic ouvre les propriétés.

Validation HTTP : deux niveaux, aucun transport

  • Méthodes en liste blanche : GET, POST, PUT, PATCH et DELETE, en majuscules. HttpMethod et isHttpMethod suivent exactement cette liste.
  • Politique portable : normalizeHttpUrlText impose une URL HTTP(S) absolue, au plus 2 048 caractères, sans espace interne, caractère de contrôle, antislash ni identifiants dans l’autorité. Cette vérification textuelle conservative n’est pas un parseur URL complet.
  • Contrôle Angular : validateBrowserHttpUrl, dans angular/http-url.ts, ajoute new URL, vérifie http: / https:, un hostname et l’absence de username/password. Il valide la syntaxe sans réécrire le chemin et sans fetch, DNS ou appel réseau.
  • Erreur accessible : le composant HTTP conserve son brouillon local, affiche un statut lié au champ avec aria-invalid et aria-describedby, et refuse l’émission pour une URL invalide. À la réception d’un nœud restauré, il initialise les valeurs depuis la configuration et signale l’invalidité sans corriger ni écrire le store.

La note et l’expression restent du texte limité à 2 000 caractères. Une URL syntaxiquement valide ne garantit ni l’accessibilité ni la sécurité du service. Ne placez aucun secret, mot de passe, jeton ou clé API dans ces champs ou dans les URL. Une éventuelle exécution ajoutée par l’hôte doit définir séparément sa validation métier, ses règles réseau, son anti-SSRF et une authentification hors document.

Conserver l’inspecteur et contrôler les données importées

Le kit ne fournit aucun inspectorTemplate. Le composant hôte n’en passe pas et conserve donc l’inspecteur intégré, notamment pour les builtins. L’API Angular publiée permet bien cette extension, décrite dans le guide des templates, mais le kit ne remplace pas l’inspecteur par une implémentation globale incomplète.

L’inspecteur intégré édite des valeurs JSON génériques. Ses modifications, comme un import, peuvent contourner les validateurs métier des contrôles Atelier : un JSON structurellement accepté ne garantit pas une configuration HTTP valide. La face HTTP signale une méthode ou une URL restaurée invalide, sans exécution et sans mutation du document. Si votre hôte ajoute ultérieurement un moteur, validez les données à sa frontière avant toute action ; les contraintes du formulaire ne suffisent pas.

Ce que la validation du kit établit

La validation fournie avec le kit rapporte 24 tests Node réussis, avec la vraie dépendance registry @newvexa/flowora@0.1.0-beta.1, Angular 22.2.4, TypeScript 6 et Node 22. Elle comprend la compilation Angular des composants et de la démo avec strictTemplates, la compilation ESM du catalogue portable sans bibliothèque DOM, et les tests du catalogue, des helpers, du store, des patches, de l’historique, de l’import et du round-trip JSON.

Ces résultats appartiennent au kit livré : ce guide ne revendique pas une nouvelle campagne de tests. Les assertions de structure ne sont pas des essais interactifs navigateur. Vérifiez dans votre application le drag, le double-clic, le focus, les raccourcis et le rendu final ; aucun nouveau résultat navigateur, mesure de performance ou test de connectivité n’est annoncé.

Pour explorer les documents et les commandes, ouvrez le playground Flowora. Pour intégrer et conserver vos propres documents, poursuivez avec Angular, DI & templates et JSON & données locales.