@cadriciel/ui 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2136,14 +2136,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
2136
2136
  * <cad-radio value="session" label="Session" [disabled]="true" />
2137
2137
  * </cad-radio-group>
2138
2138
  * CVA<T> sur le GROUPE. Clavier : ←/→/↑/↓ déplacent la sélection (pattern WAI-ARIA radiogroup).
2139
- * @atelier icone=circle-dot largeur=2
2139
+ * @atelier icone=circle-dot largeur=2 conteneur=direct hauteur=4
2140
2140
  */
2141
2141
  class CadRadioGroupComponent extends CadValueAccessor {
2142
2142
  orientation = input('vertical', ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
2143
2143
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
2144
2144
  name = input(cadId('cad-radio'), ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
2145
2145
  changed = output();
2146
- radios = contentChildren(forwardRef(() => CadRadioComponent), { ...(ngDevMode ? { debugName: "radios" } : /* istanbul ignore next */ {}), descendants: true });
2146
+ radiosProjetes = contentChildren(forwardRef(() => CadRadioComponent), { ...(ngDevMode ? { debugName: "radiosProjetes" } : /* istanbul ignore next */ {}), descendants: true });
2147
+ /** Radios POSÉS hors gabarit, annoncés par `enregistrerEnfants`. */
2148
+ radiosPoses = signal([], ...(ngDevMode ? [{ debugName: "radiosPoses" }] : /* istanbul ignore next */ []));
2149
+ radios = computed(() => [...this.radiosProjetes(), ...this.radiosPoses()], ...(ngDevMode ? [{ debugName: "radios" }] : /* istanbul ignore next */ []));
2150
+ /** Reçoit la LISTE COMPLÈTE des radios posés, et se RATTACHE à chacun. */
2151
+ enregistrerEnfants(instances) {
2152
+ const pieces = instances.filter((i) => i instanceof CadRadioComponent);
2153
+ // Le générique du groupe et celui du radio sont indépendants au montage ; la
2154
+ // relation est structurelle, pas typée. On la pose explicitement plutôt que de
2155
+ // faire remonter un paramètre de type jusqu'au poseur, qui ne connaît ni l'un ni
2156
+ // l'autre.
2157
+ for (const p of pieces)
2158
+ p.rattacher(this);
2159
+ this.radiosPoses.set(pieces);
2160
+ }
2147
2161
  select(v) { if (this.isDisabled())
2148
2162
  return; this.commit(v); this.changed.emit(v); this.markTouched(); }
2149
2163
  onKey(e) {
@@ -2161,32 +2175,69 @@ class CadRadioGroupComponent extends CadValueAccessor {
2161
2175
  next.focus();
2162
2176
  }
2163
2177
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadRadioGroupComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2164
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.20", type: CadRadioGroupComponent, isStandalone: true, selector: "cad-radio-group", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, host: { attributes: { "role": "radiogroup" }, listeners: { "keydown": "onKey($event)" }, properties: { "class.is-horizontal": "orientation()===\"horizontal\"", "class.is-disabled": "isDisabled()", "attr.aria-label": "ariaLabel() || null" }, classAttribute: "cad-radio-group" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CadRadioGroupComponent), multi: true }], queries: [{ propertyName: "radios", predicate: i0.forwardRef(() => CadRadioComponent), descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:inline-flex;flex-direction:column;gap:8px}:host(.is-horizontal){flex-direction:row;flex-wrap:wrap;gap:16px}:host(.is-disabled){opacity:.55}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2178
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.20", type: CadRadioGroupComponent, isStandalone: true, selector: "cad-radio-group", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, host: { attributes: { "role": "radiogroup" }, listeners: { "keydown": "onKey($event)" }, properties: { "class.is-horizontal": "orientation()===\"horizontal\"", "class.is-disabled": "isDisabled()", "attr.aria-label": "ariaLabel() || null" }, classAttribute: "cad-radio-group" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CadRadioGroupComponent), multi: true }], queries: [{ propertyName: "radiosProjetes", predicate: i0.forwardRef(() => CadRadioComponent), descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:inline-flex;flex-direction:column;gap:8px}:host(.is-horizontal){flex-direction:row;flex-wrap:wrap;gap:16px}:host(.is-disabled){opacity:.55}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2165
2179
  }
2166
2180
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadRadioGroupComponent, decorators: [{
2167
2181
  type: Component,
2168
2182
  args: [{ selector: 'cad-radio-group', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CadRadioGroupComponent), multi: true }], host: { class: 'cad-radio-group', role: 'radiogroup', '[class.is-horizontal]': 'orientation()==="horizontal"', '[class.is-disabled]': 'isDisabled()', '[attr.aria-label]': 'ariaLabel() || null', '(keydown)': 'onKey($event)' }, template: `<ng-content />`, styles: [":host{display:inline-flex;flex-direction:column;gap:8px}:host(.is-horizontal){flex-direction:row;flex-wrap:wrap;gap:16px}:host(.is-disabled){opacity:.55}\n"] }]
2169
- }], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }], radios: [{ type: i0.ContentChildren, args: [forwardRef(() => CadRadioComponent), { ...{ descendants: true }, isSignal: true }] }] } });
2183
+ }], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }], radiosProjetes: [{ type: i0.ContentChildren, args: [forwardRef(() => CadRadioComponent), { ...{ descendants: true }, isSignal: true }] }] } });
2184
+ /**
2185
+ * Un choix du groupe : `<cad-radio [value]="'a'" label="Option A">`.
2186
+ * @atelier icone=circle-dot conteneur=flux dans=cad-radio-group pose-label=Option
2187
+ */
2170
2188
  class CadRadioComponent {
2171
- group = inject(CadRadioGroupComponent);
2172
- value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
2189
+ /**
2190
+ * Le groupe d'accueil INJECTÉ dans un gabarit, POUSSÉ par l'hôte à la pose.
2191
+ *
2192
+ * ⚠️ OPTIONNEL depuis le 27/08, même correctif que `cad-accordion-panel` : l'injection
2193
+ * obligatoire levait `NG0201` dès qu'on créait le bouton à la main, et `gen-catalog`
2194
+ * classait `cad-radio` « contextuel » — il n'était pas posable. `cad-tab`, la pièce
2195
+ * jumelle des onglets, n'injecte rien : elle est PASSIVE et son hôte la pilote. La
2196
+ * divergence était d'écriture, pas de nature (SZU 27/08).
2197
+ */
2198
+ groupInjecte = inject(forwardRef(() => CadRadioGroupComponent), { optional: true });
2199
+ groupPose = signal(null, ...(ngDevMode ? [{ debugName: "groupPose" }] : /* istanbul ignore next */ []));
2200
+ group = computed(() => this.groupInjecte ?? this.groupPose(), ...(ngDevMode ? [{ debugName: "group" }] : /* istanbul ignore next */ []));
2201
+ /** Le groupe s'annonce au bouton qu'il accueille. Cf. `atelier/atelier.pieces.ts`. */
2202
+ rattacher(g) {
2203
+ this.groupPose.set(g);
2204
+ }
2205
+ /** Sélectionne, si un groupe existe. Un radio solitaire ne prétend pas gérer un choix. */
2206
+ choisir() {
2207
+ this.group()?.select(this.value());
2208
+ }
2209
+ /**
2210
+ * La valeur que ce choix représente.
2211
+ *
2212
+ * DÉFAUT UNIQUE plutôt que `input.required` (27/08) — même précédent que
2213
+ * `cad-accordion-panel`, qui se donne `cadId('cad-panel')`. Une entrée requise LÈVE
2214
+ * dès qu'on instancie le composant sans la fournir : posé dans l'atelier, le radio
2215
+ * mourait à la construction et le poseur l'écartait EN SILENCE (groupe vide, aucune
2216
+ * erreur visible). Un défaut unique par instance rend un radio posé immédiatement
2217
+ * cohérent — deux radios ne se retrouvent jamais cochés ensemble — et n'enlève rien à
2218
+ * l'usage écrit, où l'on passe toujours une valeur.
2219
+ */
2220
+ value = input(cadId('cad-radio-v'), ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
2173
2221
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
2174
2222
  hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
2175
2223
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2176
2224
  id = cadId('cad-radio');
2177
- checked = computed(() => this.group.value() === this.value(), ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
2225
+ checked = computed(() => this.group()?.value() === this.value(), ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
2178
2226
  /** Roving tabindex : seul l'élément coché (ou le 1er) est tabulable. */
2179
2227
  tabIndex = computed(() => {
2180
2228
  if (this.checked())
2181
2229
  return 0;
2182
- const first = this.group.radios().find(r => !r.disabled());
2183
- return this.group.value() == null && first === this ? 0 : -1;
2230
+ const g = this.group();
2231
+ if (!g)
2232
+ return 0;
2233
+ const first = g.radios().find(r => !r.disabled());
2234
+ return g.value() == null && first === this ? 0 : -1;
2184
2235
  }, ...(ngDevMode ? [{ debugName: "tabIndex" }] : /* istanbul ignore next */ []));
2185
2236
  focus() { document.getElementById(this.id)?.focus(); }
2186
2237
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadRadioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2187
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadRadioComponent, isStandalone: true, selector: "cad-radio", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-on": "checked()", "class.is-disabled": "disabled() || group.isDisabled()" }, classAttribute: "cad-radio" }, ngImport: i0, template: `
2188
- <button type="button" role="radio" [id]="id" [attr.aria-checked]="checked()" [tabindex]="tabIndex()" [disabled]="disabled() || group.isDisabled()"
2189
- [attr.aria-labelledby]="label() ? id + '-l' : null" (click)="group.select(value())">
2238
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadRadioComponent, isStandalone: true, selector: "cad-radio", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-on": "checked()", "class.is-disabled": "disabled() || !!group()?.isDisabled()" }, classAttribute: "cad-radio" }, ngImport: i0, template: `
2239
+ <button type="button" role="radio" [id]="id" [attr.aria-checked]="checked()" [tabindex]="tabIndex()" [disabled]="disabled() || !!group()?.isDisabled()"
2240
+ [attr.aria-labelledby]="label() ? id + '-l' : null" (click)="choisir()">
2190
2241
  <span class="cad-radio__dot"></span>
2191
2242
  </button>
2192
2243
  @if (label()) {<label [id]="id + '-l'" [attr.for]="id" class="cad-radio__label">{{ label() }}@if (hint()) {<small>{{ hint() }}</small>}</label>}
@@ -2195,15 +2246,15 @@ class CadRadioComponent {
2195
2246
  }
2196
2247
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadRadioComponent, decorators: [{
2197
2248
  type: Component,
2198
- args: [{ selector: 'cad-radio', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cad-radio', '[class.is-on]': 'checked()', '[class.is-disabled]': 'disabled() || group.isDisabled()' }, template: `
2199
- <button type="button" role="radio" [id]="id" [attr.aria-checked]="checked()" [tabindex]="tabIndex()" [disabled]="disabled() || group.isDisabled()"
2200
- [attr.aria-labelledby]="label() ? id + '-l' : null" (click)="group.select(value())">
2249
+ args: [{ selector: 'cad-radio', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cad-radio', '[class.is-on]': 'checked()', '[class.is-disabled]': 'disabled() || !!group()?.isDisabled()' }, template: `
2250
+ <button type="button" role="radio" [id]="id" [attr.aria-checked]="checked()" [tabindex]="tabIndex()" [disabled]="disabled() || !!group()?.isDisabled()"
2251
+ [attr.aria-labelledby]="label() ? id + '-l' : null" (click)="choisir()">
2201
2252
  <span class="cad-radio__dot"></span>
2202
2253
  </button>
2203
2254
  @if (label()) {<label [id]="id + '-l'" [attr.for]="id" class="cad-radio__label">{{ label() }}@if (hint()) {<small>{{ hint() }}</small>}</label>}
2204
2255
  <ng-content />
2205
2256
  `, styles: [":host{display:inline-flex;align-items:flex-start;gap:8px;font-family:var(--cad-font);font-size:var(--cad-font-size);color:var(--cad-fg)}button{all:unset;display:inline-flex;cursor:pointer;border-radius:50%;margin-top:1px}:host-context(.cad-touch) button{min-width:44px;min-height:44px;justify-content:center;align-items:center;margin:-12px;padding:12px;box-sizing:border-box}button:focus-visible{outline:2px solid var(--cad-ring);outline-offset:2px}button[disabled]{cursor:not-allowed}:host(.is-disabled){opacity:.55}.cad-radio__dot{width:16px;height:16px;box-sizing:border-box;border:1.5px solid var(--cad-border-strong);border-radius:50%;background:var(--cad-input-bg);display:grid;place-items:center;transition:all var(--cad-dur)}.cad-radio__dot:after{content:\"\";width:6px;height:6px;border-radius:50%;background:var(--cad-fg-on-accent);transform:scale(0);transition:transform var(--cad-dur)}:host(.is-on) .cad-radio__dot{background:var(--cad-accent);border-color:var(--cad-accent)}:host(.is-on) .cad-radio__dot:after{transform:scale(1)}:host(:hover:not(.is-disabled)) .cad-radio__dot{border-color:var(--cad-accent)}.cad-radio__label{cursor:pointer;-webkit-user-select:none;user-select:none;display:grid}.cad-radio__label small{color:var(--cad-fg-muted);font-size:11.5px}\n"] }]
2206
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
2257
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
2207
2258
 
2208
2259
  /**
2209
2260
  * <cad-segmented [(value)]="view" [options]="[{label:'Tableau',value:'t'},{label:'SQL',value:'s',icon:'code'}]" />
@@ -6105,7 +6156,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
6105
6156
  * Grille responsive : `<cad-grid cols="1 sm:2 lg:4" gap="md">` (breakpoints FENÊTRE, mobile-first)
6106
6157
  * ou `<cad-grid min="220px">` (auto-fit : autant de colonnes que le CONTENEUR en accepte — préférer pour les composants).
6107
6158
  * Enfant : `<div cadCol="2">` / `cadCol="1 md:2"` = span, `cadCol="full"` = toute la largeur.
6108
- * @atelier icone=grid
6159
+ *
6160
+ * CONTENEUR D'ATELIER en régime `direct`, et non `flux` (27/08) : en flux, l'atelier
6161
+ * envelopperait les enfants dans SA propre grille 12 colonnes, la grille posée n'aurait
6162
+ * plus qu'un seul enfant et son `cols` ne servirait plus à rien — un conteneur qui a
6163
+ * l'air de marcher et ne fait rien. En direct, les enfants sont projetés nus et c'est
6164
+ * bien `cols` / `min` qui les place, ce qu'on vient chercher en posant une grille.
6165
+ * Aucune trappe d'accueil n'est nécessaire ici : `cad-grid` ne fait ni requête de
6166
+ * contenu ni injection depuis ses enfants, il ne les REGARDE pas, il les dispose.
6167
+ * @atelier icone=grid conteneur=direct largeur=12 hauteur=4
6109
6168
  */
6110
6169
  class CadGridComponent {
6111
6170
  cols = input('1', ...(ngDevMode ? [{ debugName: "cols" }] : /* istanbul ignore next */ []));
@@ -6224,7 +6283,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
6224
6283
  }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }] } });
6225
6284
  /**
6226
6285
  * Espace flexible (pousse ce qui suit) ou fixe : `<cad-spacer/>` · `<cad-spacer size="lg"/>`
6227
- * @atelier icone=move-horizontal
6286
+ *
6287
+ * `surface=aucune` : il occupe sa place et ne dessine RIEN. Posé dans une maquette, il
6288
+ * fait donc exactement son travail en restant invisible — indistinguable d'un composant
6289
+ * qui n'aurait pas été posé (SZU 27/08). L'atelier s'en sert pour le MATÉRIALISER le
6290
+ * temps de la conception ; le rendu, lui, n'en sait rien.
6291
+ * @atelier icone=move-horizontal surface=aucune
6228
6292
  */
6229
6293
  class CadSpacerComponent {
6230
6294
  size = input('', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
@@ -6237,7 +6301,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
6237
6301
  args: [{ selector: 'cad-spacer', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cad-spacer', '[style.flex]': 'size() ? "none" : "1 1 auto"', '[style.width]': 'size() ? cadGap(size()) : null', '[style.height]': 'size() ? cadGap(size()) : null' }, template: ``, styles: [":host{display:block}\n"] }]
6238
6302
  }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
6239
6303
 
6240
- /** Panneau d'un splitter : `<cad-splitter-panel [size]="30" [minSize]="10">…</cad-splitter-panel>` (size en %). */
6304
+ /**
6305
+ * Panneau d'un splitter : `<cad-splitter-panel [size]="30" [minSize]="10">…</cad-splitter-panel>` (size en %).
6306
+ * @atelier icone=square conteneur=flux dans=cad-splitter
6307
+ */
6241
6308
  class CadSplitterPanelComponent {
6242
6309
  size = input(null, { ...(ngDevMode ? { debugName: "size" } : /* istanbul ignore next */ {}), transform: v => (v == null || v === '' ? null : Number(v)) });
6243
6310
  minSize = input(0, { ...(ngDevMode ? { debugName: "minSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
@@ -6253,7 +6320,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
6253
6320
  * <cad-splitter-panel>…</cad-splitter-panel><cad-splitter-panel>…</cad-splitter-panel>
6254
6321
  * </cad-splitter>
6255
6322
  * Gouttière glissable (pointer), double-clic = tailles initiales, clavier ←→/↑↓ (±2 %), `role=separator`, persistance localStorage via `stateKey`, imbriquable.
6256
- * @atelier icone=columns
6323
+ * @atelier icone=columns conteneur=direct largeur=12 hauteur=8
6257
6324
  */
6258
6325
  class CadSplitterComponent {
6259
6326
  host = inject(ElementRef);
@@ -6264,7 +6331,24 @@ class CadSplitterComponent {
6264
6331
  stateKey = input('', ...(ngDevMode ? [{ debugName: "stateKey" }] : /* istanbul ignore next */ []));
6265
6332
  resizeStart = output();
6266
6333
  resizeEnd = output();
6267
- panels = contentChildren(CadSplitterPanelComponent, ...(ngDevMode ? [{ debugName: "panels" }] : /* istanbul ignore next */ []));
6334
+ /** Panneaux déclarés dans un GABARIT la voie normale. */
6335
+ panelsProjetes = contentChildren(CadSplitterPanelComponent, ...(ngDevMode ? [{ debugName: "panelsProjetes" }] : /* istanbul ignore next */ []));
6336
+ /**
6337
+ * Panneaux POSÉS, créés hors gabarit et annoncés par `enregistrerEnfants`
6338
+ * (cf. `atelier/atelier.pieces.ts`).
6339
+ *
6340
+ * Sans eux, `panels()` reste vide pour un splitter posé : aucune gouttière, aucune
6341
+ * taille calculée, `grid-template-columns` vide — mesuré à `gouttieres=0` le 27/08.
6342
+ * Les panneaux s'affichaient quand même (ils sont projetés), ce qui donnait un
6343
+ * splitter d'apparence correcte mais sans mise en page ni poignée.
6344
+ */
6345
+ panelsPoses = signal([], ...(ngDevMode ? [{ debugName: "panelsPoses" }] : /* istanbul ignore next */ []));
6346
+ /** Tous les panneaux : projetés d'abord, posés ensuite — l'ordre fait les colonnes. */
6347
+ panels = computed(() => [...this.panelsProjetes(), ...this.panelsPoses()], ...(ngDevMode ? [{ debugName: "panels" }] : /* istanbul ignore next */ []));
6348
+ /** Reçoit la LISTE COMPLÈTE des panneaux posés. L'hôte filtre ce qui n'en est pas un. */
6349
+ enregistrerEnfants(instances) {
6350
+ this.panelsPoses.set(instances.filter((i) => i instanceof CadSplitterPanelComponent));
6351
+ }
6268
6352
  sizes = signal([], ...(ngDevMode ? [{ debugName: "sizes" }] : /* istanbul ignore next */ []));
6269
6353
  gutters = computed(() => Array.from({ length: Math.max(0, this.panels().length - 1) }, (_, i) => i), ...(ngDevMode ? [{ debugName: "gutters" }] : /* istanbul ignore next */ []));
6270
6354
  mins = computed(() => this.panels().map((p, i) => this.minSizes()[i] ?? p.minSize() ?? 0), ...(ngDevMode ? [{ debugName: "mins" }] : /* istanbul ignore next */ []));
@@ -6340,7 +6424,7 @@ class CadSplitterComponent {
6340
6424
  this.reset();
6341
6425
  }
6342
6426
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadSplitterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6343
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadSplitterComponent, isStandalone: true, selector: "cad-splitter", inputs: { layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, panelSizes: { classPropertyName: "panelSizes", publicName: "panelSizes", isSignal: true, isRequired: false, transformFunction: null }, minSizes: { classPropertyName: "minSizes", publicName: "minSizes", isSignal: true, isRequired: false, transformFunction: null }, gutterSize: { classPropertyName: "gutterSize", publicName: "gutterSize", isSignal: true, isRequired: false, transformFunction: null }, stateKey: { classPropertyName: "stateKey", publicName: "stateKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { resizeStart: "resizeStart", resizeEnd: "resizeEnd" }, host: { properties: { "class.is-vertical": "layout() === \"vertical\"", "style.--_g.px": "gutterSize()", "style.grid-template-columns": "layout() === \"horizontal\" ? tpl() : null", "style.grid-template-rows": "layout() === \"vertical\" ? tpl() : null" }, classAttribute: "cad-splitter" }, queries: [{ propertyName: "panels", predicate: CadSplitterPanelComponent, isSignal: true }], ngImport: i0, template: `
6427
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadSplitterComponent, isStandalone: true, selector: "cad-splitter", inputs: { layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, panelSizes: { classPropertyName: "panelSizes", publicName: "panelSizes", isSignal: true, isRequired: false, transformFunction: null }, minSizes: { classPropertyName: "minSizes", publicName: "minSizes", isSignal: true, isRequired: false, transformFunction: null }, gutterSize: { classPropertyName: "gutterSize", publicName: "gutterSize", isSignal: true, isRequired: false, transformFunction: null }, stateKey: { classPropertyName: "stateKey", publicName: "stateKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { resizeStart: "resizeStart", resizeEnd: "resizeEnd" }, host: { properties: { "class.is-vertical": "layout() === \"vertical\"", "style.--_g.px": "gutterSize()", "style.grid-template-columns": "layout() === \"horizontal\" ? tpl() : null", "style.grid-template-rows": "layout() === \"vertical\" ? tpl() : null" }, classAttribute: "cad-splitter" }, queries: [{ propertyName: "panelsProjetes", predicate: CadSplitterPanelComponent, isSignal: true }], ngImport: i0, template: `
6344
6428
  <ng-content/>
6345
6429
  @for (g of gutters(); track g) {
6346
6430
  <div class="sp__gutter" role="separator" tabindex="0" [attr.aria-orientation]="layout() === 'horizontal' ? 'vertical' : 'horizontal'" [attr.aria-valuenow]="round(sizes()[g])" [attr.aria-valuemin]="mins()[g]" [attr.aria-valuemax]="100" [style.grid-column]="layout() === 'horizontal' ? g * 2 + 2 : null" [style.grid-row]="layout() === 'vertical' ? g * 2 + 2 : null"
@@ -6357,7 +6441,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
6357
6441
  (pointerdown)="onDown($event, g)" (dblclick)="reset()" (keydown)="onKey($event, g)"><i></i></div>
6358
6442
  }
6359
6443
  `, styles: [":host{display:grid;width:100%;height:100%;min-height:0;min-width:0;--_g: 6px}:host ::ng-deep>cad-splitter-panel{grid-row:1}:host(.is-vertical) ::ng-deep>cad-splitter-panel{grid-row:auto;grid-column:1}:host(:not(.is-vertical)) ::ng-deep>cad-splitter-panel:nth-child(1){grid-column:1}:host(:not(.is-vertical)) ::ng-deep>cad-splitter-panel:nth-child(2){grid-column:3}:host(:not(.is-vertical)) ::ng-deep>cad-splitter-panel:nth-child(3){grid-column:5}:host(:not(.is-vertical)) ::ng-deep>cad-splitter-panel:nth-child(4){grid-column:7}:host(:not(.is-vertical)) ::ng-deep>cad-splitter-panel:nth-child(5){grid-column:9}:host(.is-vertical) ::ng-deep>cad-splitter-panel:nth-child(1){grid-row:1}:host(.is-vertical) ::ng-deep>cad-splitter-panel:nth-child(2){grid-row:3}:host(.is-vertical) ::ng-deep>cad-splitter-panel:nth-child(3){grid-row:5}:host(.is-vertical) ::ng-deep>cad-splitter-panel:nth-child(4){grid-row:7}:host(.is-vertical) ::ng-deep>cad-splitter-panel:nth-child(5){grid-row:9}.sp__gutter{grid-row:1;background:var(--cad-border);display:grid;place-items:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none;transition:background var(--cad-dur)}:host(.is-vertical) .sp__gutter{grid-column:1;cursor:row-resize}.sp__gutter i{width:2px;height:22px;border-radius:2px;background:var(--cad-border-strong)}:host(.is-vertical) .sp__gutter i{width:22px;height:2px}.sp__gutter:hover,.sp__gutter.is-active{background:var(--cad-accent-soft)}.sp__gutter:hover i,.sp__gutter.is-active i{background:var(--cad-accent)}.sp__gutter:focus-visible{outline:2px solid var(--cad-ring);outline-offset:-2px}\n"] }]
6360
- }], ctorParameters: () => [], propDecorators: { layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], panelSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelSizes", required: false }] }], minSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSizes", required: false }] }], gutterSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "gutterSize", required: false }] }], stateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateKey", required: false }] }], resizeStart: [{ type: i0.Output, args: ["resizeStart"] }], resizeEnd: [{ type: i0.Output, args: ["resizeEnd"] }], panels: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CadSplitterPanelComponent), { isSignal: true }] }] } });
6444
+ }], ctorParameters: () => [], propDecorators: { layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], panelSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelSizes", required: false }] }], minSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSizes", required: false }] }], gutterSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "gutterSize", required: false }] }], stateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateKey", required: false }] }], resizeStart: [{ type: i0.Output, args: ["resizeStart"] }], resizeEnd: [{ type: i0.Output, args: ["resizeEnd"] }], panelsProjetes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CadSplitterPanelComponent), { isSignal: true }] }] } });
6361
6445
 
6362
6446
  const CAD_SHELL = new InjectionToken('CAD_SHELL');
6363
6447
  /**
@@ -8031,6 +8115,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
8031
8115
  * contenu (ou <ng-template cadTabContent> pour un rendu vraiment paresseux)
8032
8116
  * <ng-template cadTabLabel>label custom</ng-template>
8033
8117
  * </cad-tab>
8118
+ * @atelier icone=panel-top conteneur=flux dans=cad-tabs pose-label=Onglet
8034
8119
  */
8035
8120
  class CadTabComponent {
8036
8121
  value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
@@ -8064,7 +8149,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
8064
8149
  /**
8065
8150
  * <cad-tabs [(value)]="tab" [(activeIndex)]="i" variant="line|pill|enclosed" size="sm|md" [stretch] [scrollable] [lazy]="true" [keepAlive]="true" (changed) (tabChange)>
8066
8151
  * `value` prime sur `activeIndex` (les deux sont two-way et synchronisés). Clavier ←/→/Home/End sur la liste.
8067
- * @atelier icone=folder
8152
+ * @atelier icone=folder conteneur=direct largeur=12 hauteur=6
8068
8153
  */
8069
8154
  class CadTabsComponent {
8070
8155
  value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
@@ -8080,7 +8165,39 @@ class CadTabsComponent {
8080
8165
  changed = output();
8081
8166
  /** Alias PrimeNG-like. */
8082
8167
  tabChange = output();
8083
- tabs = contentChildren(CadTabComponent, ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
8168
+ /** Onglets déclarés dans un GABARIT la voie normale, celle de tous les usages écrits. */
8169
+ tabsProjetes = contentChildren(CadTabComponent, ...(ngDevMode ? [{ debugName: "tabsProjetes" }] : /* istanbul ignore next */ []));
8170
+ /**
8171
+ * Onglets POSÉS : créés hors gabarit par `createComponent`, et annoncés par
8172
+ * `enregistrerEnfants` — le contrat d'accueil décrit dans `atelier/atelier.pieces.ts`.
8173
+ *
8174
+ * ⚠️ CE N'EST PAS UN DOUBLON DE LA REQUÊTE, C'EST LA SEULE VOIE POSSIBLE POUR EUX.
8175
+ * Mesuré le 27/08 : un `cad-tab` créé impérativement et passé en `projectableNodes`
8176
+ * arrive bien dans le DOM, mais `contentChildren` NE LE VOIT PAS — une requête de
8177
+ * contenu se résout sur la structure déclarée d'un gabarit, et un composant instancié
8178
+ * à la main vit dans une autre vue. Résultat avant correction : `onglets=0`, une barre
8179
+ * vide et aucun panneau, sans la moindre erreur. C'est ce qui rendait `cad-tabs`
8180
+ * inutilisable dans l'atelier et dans les pages visuelles de frontless.
8181
+ *
8182
+ * Même trappe que `cad-button-group`, qui la porte depuis le 24/08 pour la même
8183
+ * raison (il était SOURD aux clics de ses boutons posés) : les deux sources
8184
+ * FUSIONNENT, la projection reste prioritaire dans l'ordre, et rien ne change pour
8185
+ * qui écrit du gabarit.
8186
+ */
8187
+ tabsPoses = signal([], ...(ngDevMode ? [{ debugName: "tabsPoses" }] : /* istanbul ignore next */ []));
8188
+ /** Tous les onglets : projetés d'abord, posés ensuite. L'ordre fait la barre. */
8189
+ tabs = computed(() => [...this.tabsProjetes(), ...this.tabsPoses()], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
8190
+ /**
8191
+ * Reçoit les instances des onglets posés — la LISTE COMPLÈTE, pas un ajout.
8192
+ *
8193
+ * Le poseur reconstruit ses enfants d'un bloc à chaque changement de structure : lui
8194
+ * faire tenir un différentiel inviterait le fantôme (un onglet détruit qui reste dans
8195
+ * la barre). L'hôte filtre lui-même ce qui n'est pas un onglet, pour qu'un appelant
8196
+ * n'ait pas à connaître nos types.
8197
+ */
8198
+ enregistrerEnfants(instances) {
8199
+ this.tabsPoses.set(instances.filter((i) => i instanceof CadTabComponent));
8200
+ }
8084
8201
  list = viewChild('list', ...(ngDevMode ? [{ debugName: "list" }] : /* istanbul ignore next */ []));
8085
8202
  btns = viewChildren('tabBtn', ...(ngDevMode ? [{ debugName: "btns" }] : /* istanbul ignore next */ []));
8086
8203
  overflow = signal(false, ...(ngDevMode ? [{ debugName: "overflow" }] : /* istanbul ignore next */ []));
@@ -8138,7 +8255,7 @@ class CadTabsComponent {
8138
8255
  el.scrollBy({ left: dir * el.clientWidth * .6 }); }
8139
8256
  checkOverflow() { const el = this.list()?.nativeElement; this.overflow.set(!!el && el.scrollWidth > el.clientWidth + 1); }
8140
8257
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8141
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadTabsComponent, isStandalone: true, selector: "cad-tabs", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, stretch: { classPropertyName: "stretch", publicName: "stretch", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, keepAlive: { classPropertyName: "keepAlive", publicName: "keepAlive", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", activeIndex: "activeIndexChange", changed: "changed", tabChange: "tabChange" }, host: { properties: { "class": "\"cad-tabs var-\" + variant()", "class.size-sm": "size()===\"sm\"", "class.is-stretch": "stretch()" }, classAttribute: "cad-tabs" }, queries: [{ propertyName: "tabs", predicate: CadTabComponent, isSignal: true }], viewQueries: [{ propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "btns", predicate: ["tabBtn"], descendants: true, isSignal: true }], ngImport: i0, template: `
8258
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadTabsComponent, isStandalone: true, selector: "cad-tabs", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, stretch: { classPropertyName: "stretch", publicName: "stretch", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, keepAlive: { classPropertyName: "keepAlive", publicName: "keepAlive", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", activeIndex: "activeIndexChange", changed: "changed", tabChange: "tabChange" }, host: { properties: { "class": "\"cad-tabs var-\" + variant()", "class.size-sm": "size()===\"sm\"", "class.is-stretch": "stretch()" }, classAttribute: "cad-tabs" }, queries: [{ propertyName: "tabsProjetes", predicate: CadTabComponent, isSignal: true }], viewQueries: [{ propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "btns", predicate: ["tabBtn"], descendants: true, isSignal: true }], ngImport: i0, template: `
8142
8259
  <div class="cad-tabs__bar">
8143
8260
  @if (scrollable() && overflow()) {<button type="button" class="cad-tabs__arrow" aria-label="Défiler à gauche" (click)="scroll(-1)"><cad-icon name="chevron-left" [size]="14"/></button>}
8144
8261
  <div #list class="cad-tabs__list" role="tablist" [class.is-scrollable]="scrollable()" (keydown)="onKey($event)" (scroll)="checkOverflow()">
@@ -8197,7 +8314,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
8197
8314
  }
8198
8315
  }
8199
8316
  `, styles: [":host{display:block;font-family:var(--cad-font);font-size:var(--cad-font-size)}.cad-tabs__bar{display:flex;align-items:stretch;border-bottom:1px solid var(--cad-border)}.cad-tabs__list{display:flex;gap:2px;flex:1;min-width:0}.cad-tabs__list.is-scrollable{overflow-x:auto;scrollbar-width:none;scroll-behavior:smooth}.cad-tabs__list.is-scrollable::-webkit-scrollbar{display:none}:host(.is-stretch) [role=tab]{flex:1;justify-content:center}[role=tab]{position:relative;height:34px;padding:0 12px;border:0;background:transparent;color:var(--cad-fg-secondary);font:500 var(--cad-font-size) var(--cad-font);cursor:pointer;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;border-radius:6px 6px 0 0;transition:color var(--cad-dur),background var(--cad-dur)}:host(.size-sm) [role=tab]{height:28px;padding:0 9px;font-size:var(--cad-font-size-sm)}[role=tab]:hover:not([disabled]){color:var(--cad-fg)}[role=tab]:focus-visible{outline:2px solid var(--cad-ring);outline-offset:-2px}[role=tab][disabled]{opacity:.5;cursor:not-allowed}[role=tab].is-active{color:var(--cad-accent)}[role=tab].is-active:after{content:\"\";position:absolute;left:8px;right:8px;bottom:-1px;height:2px;border-radius:2px;background:var(--cad-accent)}.cad-tabs__count{font:500 10.5px var(--cad-font-mono);padding:1px 6px;border-radius:999px;background:var(--cad-elevated);color:var(--cad-fg-secondary)}[role=tab].is-active .cad-tabs__count{background:var(--cad-accent-soft);color:var(--cad-accent)}.cad-tabs__close{display:inline-grid;place-items:center;width:16px;height:16px;border-radius:4px;color:var(--cad-fg-muted);margin-left:2px}.cad-tabs__close:hover{background:var(--cad-hover);color:var(--cad-fg)}.cad-tabs__arrow{border:0;background:transparent;color:var(--cad-fg-muted);cursor:pointer;padding:0 4px;display:grid;place-items:center}.cad-tabs__arrow:hover{color:var(--cad-fg)}.cad-tabs__panel{padding-top:12px;outline:none}:host(.var-pill) .cad-tabs__bar{border-bottom:0}:host(.var-pill) .cad-tabs__list{gap:4px;padding:3px;background:var(--cad-bg-subtle);border:1px solid var(--cad-border);border-radius:var(--cad-radius);flex:0 1 auto}:host(.var-pill) [role=tab]{height:26px;border-radius:6px;padding:0 10px}:host(.var-pill) [role=tab].is-active{background:var(--cad-surface);color:var(--cad-fg);box-shadow:0 1px 2px #0000001f}:host(.var-pill) [role=tab].is-active:after{display:none}:host(.var-pill) [role=tab].is-active .cad-tabs__count{background:var(--cad-accent-soft);color:var(--cad-accent)}:host(.var-enclosed) [role=tab]{border:1px solid transparent;border-bottom:0;margin-bottom:-1px}:host(.var-enclosed) [role=tab].is-active{background:var(--cad-surface);border-color:var(--cad-border);color:var(--cad-fg)}:host(.var-enclosed) [role=tab].is-active:after{display:none}\n"] }]
8200
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], activeIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIndex", required: false }] }, { type: i0.Output, args: ["activeIndexChange"] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], stretch: [{ type: i0.Input, args: [{ isSignal: true, alias: "stretch", required: false }] }], scrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollable", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], keepAlive: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepAlive", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }], tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CadTabComponent), { isSignal: true }] }], list: [{ type: i0.ViewChild, args: ['list', { isSignal: true }] }], btns: [{ type: i0.ViewChildren, args: ['tabBtn', { isSignal: true }] }] } });
8317
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], activeIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIndex", required: false }] }, { type: i0.Output, args: ["activeIndexChange"] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], stretch: [{ type: i0.Input, args: [{ isSignal: true, alias: "stretch", required: false }] }], scrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollable", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], keepAlive: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepAlive", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }], tabsProjetes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CadTabComponent), { isSignal: true }] }], list: [{ type: i0.ViewChild, args: ['list', { isSignal: true }] }], btns: [{ type: i0.ViewChildren, args: ['tabBtn', { isSignal: true }] }] } });
8201
8318
 
8202
8319
  /** <cad-step label="Domaine" description="…" icon [optional] [disabled] [completed] [state]="'error'"> contenu optionnel </cad-step> */
8203
8320
  class CadStepComponent {
@@ -8370,9 +8487,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
8370
8487
  * </cad-accordion-panel>
8371
8488
  * </cad-accordion>
8372
8489
  * `value` = valeur ouverte (string) ou tableau si `multiple`. Clavier : Entrée/Espace, ↑/↓/Home/End entre en-têtes.
8490
+ * @atelier icone=panel-top conteneur=flux dans=cad-accordion pose-header=Section
8373
8491
  */
8374
8492
  class CadAccordionPanelComponent {
8375
- acc = inject(forwardRef(() => CadAccordionComponent));
8493
+ /**
8494
+ * L'accordéon d'accueil — INJECTÉ dans un gabarit, POUSSÉ par l'hôte quand la pièce
8495
+ * est posée hors gabarit.
8496
+ *
8497
+ * ⚠️ L'INJECTION EST OPTIONNELLE, ET C'EST LE CŒUR DU CORRECTIF (SZU 27/08 : « pourquoi
8498
+ * cad-accordion serait différent d'un cad-tabs ? »). Elle était obligatoire, donc :
8499
+ * `NG0201` dès qu'on créait le panneau à la main, et `gen-catalog` le classait
8500
+ * « contextuel » — il n'était même pas POSABLE. Or `cad-tab`, lui, n'injecte rien :
8501
+ * c'est une pièce PASSIVE que son hôte pilote. La différence n'était pas une contrainte
8502
+ * du composant, c'était une divergence d'écriture entre deux familles jumelles.
8503
+ *
8504
+ * Renversement : la pièce ne va plus CHERCHER son hôte, c'est l'hôte qui s'annonce en
8505
+ * l'accueillant (`rattacher`). Un panneau posé seul reste sensé — il gère alors son
8506
+ * propre `expanded` au lieu de lever.
8507
+ */
8508
+ accInjecte = inject(forwardRef(() => CadAccordionComponent), { optional: true });
8509
+ accPose = signal(null, ...(ngDevMode ? [{ debugName: "accPose" }] : /* istanbul ignore next */ []));
8510
+ acc = computed(() => this.accInjecte ?? this.accPose(), ...(ngDevMode ? [{ debugName: "acc" }] : /* istanbul ignore next */ []));
8511
+ /** L'accordéon s'annonce à la pièce qu'il accueille. Cf. `atelier/atelier.pieces.ts`. */
8512
+ rattacher(acc) {
8513
+ this.accPose.set(acc);
8514
+ }
8376
8515
  value = input(cadId('cad-panel'), ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
8377
8516
  header = input('', ...(ngDevMode ? [{ debugName: "header" }] : /* istanbul ignore next */ []));
8378
8517
  icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
@@ -8385,7 +8524,8 @@ class CadAccordionPanelComponent {
8385
8524
  contentTpl = contentChild(CadPanelContentDirective, ...(ngDevMode ? [{ debugName: "contentTpl" }] : /* istanbul ignore next */ []));
8386
8525
  id = cadId('cad-acc');
8387
8526
  rendered = signal(false, ...(ngDevMode ? [{ debugName: "rendered" }] : /* istanbul ignore next */ []));
8388
- isOpen = computed(() => this.acc.isOpen(this.value()), ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
8527
+ /** Sans hôte, le panneau fait autorité sur lui-même : posé seul, il reste utilisable. */
8528
+ isOpen = computed(() => this.acc()?.isOpen(this.value()) ?? this.expanded(), ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
8389
8529
  constructor() {
8390
8530
  effect(() => { if (this.isOpen())
8391
8531
  this.rendered.set(true); });
@@ -8409,18 +8549,25 @@ class CadAccordionPanelComponent {
8409
8549
  if (e === precedent)
8410
8550
  return;
8411
8551
  precedent = e;
8412
- untracked(() => { if (e !== this.acc.isOpen(this.value()))
8413
- this.acc.setOpen(this.value(), e, this); });
8552
+ untracked(() => { const a = this.acc(); if (a && e !== a.isOpen(this.value()))
8553
+ a.setOpen(this.value(), e, this); });
8414
8554
  });
8415
8555
  }
8416
- toggle() { if (!this.disabled())
8417
- this.acc.setOpen(this.value(), !this.isOpen(), this); }
8556
+ toggle() {
8557
+ if (this.disabled())
8558
+ return;
8559
+ const a = this.acc();
8560
+ if (a)
8561
+ a.setOpen(this.value(), !this.isOpen(), this);
8562
+ else
8563
+ this.expanded.set(!this.expanded()); // panneau solitaire : il se gère lui-même
8564
+ }
8418
8565
  focus() { document.getElementById(this.id + '-h')?.focus(); }
8419
8566
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadAccordionPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8420
8567
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadAccordionPanelComponent, isStandalone: true, selector: "cad-accordion-panel", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange", opened: "opened", closed: "closed" }, host: { properties: { "class.is-open": "isOpen()", "class.is-disabled": "disabled()" }, classAttribute: "cad-accordion-panel" }, queries: [{ propertyName: "headerTpl", first: true, predicate: CadPanelHeaderDirective, descendants: true, isSignal: true }, { propertyName: "contentTpl", first: true, predicate: CadPanelContentDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
8421
8568
  <h3 class="cad-acc__h">
8422
- <button type="button" class="cad-acc__hd" [id]="id + '-h'" [attr.aria-expanded]="isOpen()" [attr.aria-controls]="id + '-c'" [disabled]="disabled()" (click)="toggle()" (keydown)="acc.onKey($event, this)">
8423
- <cad-icon class="cad-acc__chev" [name]="acc.expandIcon()" [size]="13"/>
8569
+ <button type="button" class="cad-acc__hd" [id]="id + '-h'" [attr.aria-expanded]="isOpen()" [attr.aria-controls]="id + '-c'" [disabled]="disabled()" (click)="toggle()" (keydown)="acc()?.onKey($event, this)">
8570
+ <cad-icon class="cad-acc__chev" [name]="acc()?.expandIcon() ?? 'chevron-right'" [size]="13"/>
8424
8571
  @if (icon()) {<cad-icon [name]="icon()" [size]="14"/>}
8425
8572
  @if (headerTpl()) { <ng-container [ngTemplateOutlet]="headerTpl()!.tpl" /> } @else { <span class="cad-acc__title">{{ header() }}</span> }
8426
8573
  <span class="cad-acc__extra"><ng-content select="[cadPanelExtra]" /></span>
@@ -8440,8 +8587,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
8440
8587
  type: Component,
8441
8588
  args: [{ selector: 'cad-accordion-panel', standalone: true, imports: [NgTemplateOutlet, CadIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cad-accordion-panel', '[class.is-open]': 'isOpen()', '[class.is-disabled]': 'disabled()' }, template: `
8442
8589
  <h3 class="cad-acc__h">
8443
- <button type="button" class="cad-acc__hd" [id]="id + '-h'" [attr.aria-expanded]="isOpen()" [attr.aria-controls]="id + '-c'" [disabled]="disabled()" (click)="toggle()" (keydown)="acc.onKey($event, this)">
8444
- <cad-icon class="cad-acc__chev" [name]="acc.expandIcon()" [size]="13"/>
8590
+ <button type="button" class="cad-acc__hd" [id]="id + '-h'" [attr.aria-expanded]="isOpen()" [attr.aria-controls]="id + '-c'" [disabled]="disabled()" (click)="toggle()" (keydown)="acc()?.onKey($event, this)">
8591
+ <cad-icon class="cad-acc__chev" [name]="acc()?.expandIcon() ?? 'chevron-right'" [size]="13"/>
8445
8592
  @if (icon()) {<cad-icon [name]="icon()" [size]="14"/>}
8446
8593
  @if (headerTpl()) { <ng-container [ngTemplateOutlet]="headerTpl()!.tpl" /> } @else { <span class="cad-acc__title">{{ header() }}</span> }
8447
8594
  <span class="cad-acc__extra"><ng-content select="[cadPanelExtra]" /></span>
@@ -8457,7 +8604,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
8457
8604
  </div>
8458
8605
  `, styles: [":host{display:block;border-bottom:1px solid var(--cad-border);font-family:var(--cad-font);font-size:var(--cad-font-size)}:host(:last-child){border-bottom:0}.cad-acc__h{margin:0;font:inherit}.cad-acc__hd{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:8px;width:100%;padding:9px 12px;font-weight:500;cursor:pointer;background:var(--cad-bg-subtle);color:var(--cad-fg);transition:background var(--cad-dur)}.cad-acc__hd:hover:not([disabled]){background:var(--cad-hover)}.cad-acc__hd:focus-visible{outline:2px solid var(--cad-ring);outline-offset:-2px}.cad-acc__hd[disabled]{cursor:not-allowed;opacity:.55}.cad-acc__chev{color:var(--cad-fg-muted);transition:transform .16s var(--cad-ease)}:host(.is-open) .cad-acc__chev{transform:rotate(90deg)}.cad-acc__title{flex:1}.cad-acc__extra{margin-left:auto;display:inline-flex;gap:6px}.cad-acc__wrap{display:grid;grid-template-rows:0fr;transition:grid-template-rows .16s var(--cad-ease)}:host(.is-open) .cad-acc__wrap{grid-template-rows:1fr}.cad-acc__inner{overflow:hidden;min-height:0}.cad-acc__bd{padding:10px 12px 12px;font-size:12.5px;color:var(--cad-fg-secondary)}@media(prefers-reduced-motion:reduce){.cad-acc__wrap,.cad-acc__chev{transition:none}}\n"] }]
8459
8606
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], headerTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadPanelHeaderDirective), { isSignal: true }] }], contentTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadPanelContentDirective), { isSignal: true }] }] } });
8460
- /** @atelier icone=chevron-down */
8607
+ /** @atelier icone=chevron-down conteneur=direct largeur=12 hauteur=8 */
8461
8608
  class CadAccordionComponent {
8462
8609
  multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
8463
8610
  /** Ouvert(s) : string (single) ou string[] (multiple). */
@@ -8466,7 +8613,20 @@ class CadAccordionComponent {
8466
8613
  /** Sans bordure extérieure. */
8467
8614
  flush = input(false, { ...(ngDevMode ? { debugName: "flush" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
8468
8615
  changed = output();
8469
- panels = contentChildren(CadAccordionPanelComponent, ...(ngDevMode ? [{ debugName: "panels" }] : /* istanbul ignore next */ []));
8616
+ panelsProjetes = contentChildren(CadAccordionPanelComponent, ...(ngDevMode ? [{ debugName: "panelsProjetes" }] : /* istanbul ignore next */ []));
8617
+ /** Panneaux POSÉS hors gabarit, annoncés par `enregistrerEnfants`. */
8618
+ panelsPoses = signal([], ...(ngDevMode ? [{ debugName: "panelsPoses" }] : /* istanbul ignore next */ []));
8619
+ panels = computed(() => [...this.panelsProjetes(), ...this.panelsPoses()], ...(ngDevMode ? [{ debugName: "panels" }] : /* istanbul ignore next */ []));
8620
+ /**
8621
+ * Reçoit la LISTE COMPLÈTE des panneaux posés, et se RATTACHE à chacun : la pièce
8622
+ * n'ayant plus d'injection obligatoire, c'est ce geste qui rétablit le lien.
8623
+ */
8624
+ enregistrerEnfants(instances) {
8625
+ const pieces = instances.filter((i) => i instanceof CadAccordionPanelComponent);
8626
+ for (const p of pieces)
8627
+ p.rattacher(this);
8628
+ this.panelsPoses.set(pieces);
8629
+ }
8470
8630
  openSet = computed(() => { const v = this.value(); return new Set(v == null ? [] : Array.isArray(v) ? v : [v]); }, ...(ngDevMode ? [{ debugName: "openSet" }] : /* istanbul ignore next */ []));
8471
8631
  isOpen(v) { return this.openSet().has(v); }
8472
8632
  setOpen(v, open, panel) {
@@ -8504,12 +8664,12 @@ class CadAccordionComponent {
8504
8664
  t?.focus();
8505
8665
  }
8506
8666
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadAccordionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8507
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.20", type: CadAccordionComponent, isStandalone: true, selector: "cad-accordion", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, expandIcon: { classPropertyName: "expandIcon", publicName: "expandIcon", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", changed: "changed" }, host: { properties: { "class.is-flush": "flush()" }, classAttribute: "cad-accordion" }, queries: [{ propertyName: "panels", predicate: CadAccordionPanelComponent, isSignal: true }], ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:block;border:1px solid var(--cad-border);border-radius:var(--cad-radius);overflow:hidden;background:var(--cad-surface)}:host(.is-flush){border:0;border-radius:0;background:transparent}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8667
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.20", type: CadAccordionComponent, isStandalone: true, selector: "cad-accordion", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, expandIcon: { classPropertyName: "expandIcon", publicName: "expandIcon", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", changed: "changed" }, host: { properties: { "class.is-flush": "flush()" }, classAttribute: "cad-accordion" }, queries: [{ propertyName: "panelsProjetes", predicate: CadAccordionPanelComponent, isSignal: true }], ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:block;border:1px solid var(--cad-border);border-radius:var(--cad-radius);overflow:hidden;background:var(--cad-surface)}:host(.is-flush){border:0;border-radius:0;background:transparent}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8508
8668
  }
8509
8669
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadAccordionComponent, decorators: [{
8510
8670
  type: Component,
8511
8671
  args: [{ selector: 'cad-accordion', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cad-accordion', '[class.is-flush]': 'flush()' }, template: `<ng-content />`, styles: [":host{display:block;border:1px solid var(--cad-border);border-radius:var(--cad-radius);overflow:hidden;background:var(--cad-surface)}:host(.is-flush){border:0;border-radius:0;background:transparent}\n"] }]
8512
- }], propDecorators: { multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], expandIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandIcon", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }], panels: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CadAccordionPanelComponent), { isSignal: true }] }] } });
8672
+ }], propDecorators: { multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], expandIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandIcon", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }], panelsProjetes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CadAccordionPanelComponent), { isSignal: true }] }] } });
8513
8673
 
8514
8674
  class CadBreadcrumbItemDirective {
8515
8675
  tpl;
@@ -12708,6 +12868,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
12708
12868
  * @atelier ajustement=naturel icone=user-round largeur=1 hauteur=1 pose-text=AB
12709
12869
  */
12710
12870
  class CadAvatarComponent {
12871
+ /**
12872
+ * Son élément, exposé comme `cad-button` le fait déjà : le groupe marque le
12873
+ * débordement (`.is-overflow`) sur les éléments, et une requête de contenu ne les
12874
+ * rapporte pas pour un avatar POSÉ hors gabarit.
12875
+ */
12876
+ el = inject(ElementRef);
12711
12877
  src = input(null, ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
12712
12878
  text = input('', ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
12713
12879
  icon = input('user', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
@@ -13200,26 +13366,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
13200
13366
  /**
13201
13367
  * <cad-avatar-group [max]="3" size="md"><cad-avatar text="SZ"/><cad-avatar text="PA"/>…</cad-avatar-group>
13202
13368
  * Avatars empilés (chevauchement, bordure surface) ; au-delà de `max` un avatar « +N » (title = noms masqués).
13203
- * @atelier ajustement=naturel icone=users largeur=2 hauteur=1
13369
+ * @atelier ajustement=naturel icone=users largeur=2 hauteur=1 conteneur=direct accepte=cad-avatar
13204
13370
  */
13205
13371
  class CadAvatarGroupComponent {
13206
13372
  max = input(null, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), transform: v => (v == null || v === '' ? null : Number(v)) });
13207
13373
  size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
13208
13374
  moreColor = input('#94a3b8', ...(ngDevMode ? [{ debugName: "moreColor" }] : /* istanbul ignore next */ []));
13209
- avatars = contentChildren(CadAvatarComponent, ...(ngDevMode ? [{ debugName: "avatars" }] : /* istanbul ignore next */ []));
13210
- avatarEls = contentChildren(CadAvatarComponent, { ...(ngDevMode ? { debugName: "avatarEls" } : /* istanbul ignore next */ {}), read: ElementRef });
13375
+ avatarsProjetes = contentChildren(CadAvatarComponent, ...(ngDevMode ? [{ debugName: "avatarsProjetes" }] : /* istanbul ignore next */ []));
13376
+ /**
13377
+ * Avatars POSÉS hors gabarit, annoncés par `enregistrerEnfants`
13378
+ * (cf. `atelier/atelier.pieces.ts`).
13379
+ *
13380
+ * Le groupe RENDAIT correctement sans eux — le chevauchement et les bordures sont du
13381
+ * CSS sur les enfants directs. Seul `max` était inerte : le « +N » ne comptait rien et
13382
+ * rien ne passait en débordement. Une panne discrète, donc pire qu'une panne franche.
13383
+ */
13384
+ avatarsPoses = signal([], ...(ngDevMode ? [{ debugName: "avatarsPoses" }] : /* istanbul ignore next */ []));
13385
+ avatars = computed(() => [...this.avatarsProjetes(), ...this.avatarsPoses()], ...(ngDevMode ? [{ debugName: "avatars" }] : /* istanbul ignore next */ []));
13386
+ /** Reçoit la LISTE COMPLÈTE des avatars posés. Le groupe filtre ce qui n'en est pas un. */
13387
+ enregistrerEnfants(instances) {
13388
+ this.avatarsPoses.set(instances.filter((i) => i instanceof CadAvatarComponent));
13389
+ }
13211
13390
  hidden = computed(() => { const m = this.max(); const n = this.avatars().length; return m != null && n > m ? n - m : 0; }, ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
13212
13391
  hiddenTitle = computed(() => { const m = this.max() ?? Infinity; return this.avatars().slice(m).map(a => a.label() || a.text()).filter(Boolean).join(', '); }, ...(ngDevMode ? [{ debugName: "hiddenTitle" }] : /* istanbul ignore next */ []));
13213
13392
  constructor() {
13214
- effect(() => { const m = this.max() ?? Infinity; this.avatarEls().forEach((r, i) => r.nativeElement.classList.toggle('is-overflow', i >= m)); });
13393
+ // Le marquage du débordement passe par l'ÉLÉMENT de chaque avatar (`el`), et non par
13394
+ // une seconde requête de contenu : celle-ci ne rapporterait rien pour un avatar posé.
13395
+ effect(() => { const m = this.max() ?? Infinity; this.avatars().forEach((a, i) => a.el.nativeElement.classList.toggle('is-overflow', i >= m)); });
13215
13396
  }
13216
13397
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadAvatarGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13217
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadAvatarGroupComponent, isStandalone: true, selector: "cad-avatar-group", inputs: { max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, moreColor: { classPropertyName: "moreColor", publicName: "moreColor", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "class.size-sm": "size() === \"sm\"", "class.size-lg": "size() === \"lg\"" }, classAttribute: "cad-avatar-group" }, queries: [{ propertyName: "avatars", predicate: CadAvatarComponent, isSignal: true }, { propertyName: "avatarEls", predicate: CadAvatarComponent, read: ElementRef, isSignal: true }], ngImport: i0, template: `<ng-content/>@if (hidden() > 0) {<cad-avatar class="more" [text]="'+' + hidden()" [size]="size()" [color]="moreColor()" [attr.title]="hiddenTitle()" [label]="hiddenTitle()"/>}`, isInline: true, styles: [":host{display:inline-flex;align-items:center;padding-left:8px}:host ::ng-deep>cad-avatar,.more{margin-left:-8px;box-shadow:0 0 0 2px var(--cad-surface);border-radius:50%}:host(.size-sm) ::ng-deep>cad-avatar,:host(.size-sm) .more{margin-left:-6px}:host(.size-lg) ::ng-deep>cad-avatar,:host(.size-lg) .more{margin-left:-10px}:host ::ng-deep>cad-avatar.is-overflow{display:none}:host ::ng-deep>cad-avatar:hover{z-index:1;position:relative}\n"], dependencies: [{ kind: "component", type: CadAvatarComponent, selector: "cad-avatar", inputs: ["src", "text", "icon", "size", "shape", "color", "label"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13398
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadAvatarGroupComponent, isStandalone: true, selector: "cad-avatar-group", inputs: { max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, moreColor: { classPropertyName: "moreColor", publicName: "moreColor", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "class.size-sm": "size() === \"sm\"", "class.size-lg": "size() === \"lg\"" }, classAttribute: "cad-avatar-group" }, queries: [{ propertyName: "avatarsProjetes", predicate: CadAvatarComponent, isSignal: true }], ngImport: i0, template: `<ng-content/>@if (hidden() > 0) {<cad-avatar class="more" [text]="'+' + hidden()" [size]="size()" [color]="moreColor()" [attr.title]="hiddenTitle()" [label]="hiddenTitle()"/>}`, isInline: true, styles: [":host{display:inline-flex;align-items:center;padding-left:8px}:host ::ng-deep>cad-avatar,.more{margin-left:-8px;box-shadow:0 0 0 2px var(--cad-surface);border-radius:50%}:host(.size-sm) ::ng-deep>cad-avatar,:host(.size-sm) .more{margin-left:-6px}:host(.size-lg) ::ng-deep>cad-avatar,:host(.size-lg) .more{margin-left:-10px}:host ::ng-deep>cad-avatar.is-overflow{display:none}:host ::ng-deep>cad-avatar:hover{z-index:1;position:relative}\n"], dependencies: [{ kind: "component", type: CadAvatarComponent, selector: "cad-avatar", inputs: ["src", "text", "icon", "size", "shape", "color", "label"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13218
13399
  }
13219
13400
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadAvatarGroupComponent, decorators: [{
13220
13401
  type: Component,
13221
13402
  args: [{ selector: 'cad-avatar-group', standalone: true, imports: [CadAvatarComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cad-avatar-group', '[class.size-sm]': 'size() === "sm"', '[class.size-lg]': 'size() === "lg"', role: 'group' }, template: `<ng-content/>@if (hidden() > 0) {<cad-avatar class="more" [text]="'+' + hidden()" [size]="size()" [color]="moreColor()" [attr.title]="hiddenTitle()" [label]="hiddenTitle()"/>}`, styles: [":host{display:inline-flex;align-items:center;padding-left:8px}:host ::ng-deep>cad-avatar,.more{margin-left:-8px;box-shadow:0 0 0 2px var(--cad-surface);border-radius:50%}:host(.size-sm) ::ng-deep>cad-avatar,:host(.size-sm) .more{margin-left:-6px}:host(.size-lg) ::ng-deep>cad-avatar,:host(.size-lg) .more{margin-left:-10px}:host ::ng-deep>cad-avatar.is-overflow{display:none}:host ::ng-deep>cad-avatar:hover{z-index:1;position:relative}\n"] }]
13222
- }], ctorParameters: () => [], propDecorators: { max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], moreColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "moreColor", required: false }] }], avatars: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CadAvatarComponent), { isSignal: true }] }], avatarEls: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CadAvatarComponent), { ...{ read: ElementRef }, isSignal: true }] }] } });
13403
+ }], ctorParameters: () => [], propDecorators: { max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], moreColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "moreColor", required: false }] }], avatarsProjetes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CadAvatarComponent), { isSignal: true }] }] } });
13223
13404
 
13224
13405
  /** `<ng-template cadMeterLabel let-v let-pct="percent">` */
13225
13406
  class CadMeterLabelDirective {
@@ -14204,6 +14385,30 @@ class CadMapComponent {
14204
14385
  }
14205
14386
  catch { /* ignore */ }
14206
14387
  } }
14388
+ /**
14389
+ * Contrat d'accueil des pièces POSÉES (cf. `atelier/atelier.pieces.ts`).
14390
+ *
14391
+ * ⚠️ CORRIGE UN MENSONGE LIVRÉ LE 24/08. `cad-map` était annoté conteneur et ses onze
14392
+ * pièces déclaraient `dans=cad-map`, mais AUCUNE couche posée ne s'est jamais
14393
+ * enregistrée : la carte n'exposait que `registerChild`, que les poseurs n'appellent
14394
+ * pas, et `cad-map-layer` s'injecte la carte en `optional` — donc `null` sans chaîne
14395
+ * d'injecteurs, donc pas d'auto-enregistrement, et pas la moindre erreur pour le dire.
14396
+ * Deux vocabulaires pour un besoin : c'est la divergence qu'on paie ici.
14397
+ *
14398
+ * Liste COMPLÈTE à chaque appel : le poseur reconstruit ses enfants d'un bloc, et un
14399
+ * contrat incrémental laisserait des couches fantômes sur la carte.
14400
+ */
14401
+ enregistrerEnfants(instances) {
14402
+ const pieces = instances.filter((i) => {
14403
+ const k = i;
14404
+ return !!k && typeof k.attach === 'function' && typeof k.ownedIds === 'function';
14405
+ });
14406
+ for (const k of this.extraChildren())
14407
+ if (!pieces.includes(k))
14408
+ this.unregisterChild(k);
14409
+ for (const k of pieces)
14410
+ this.registerChild(k);
14411
+ }
14207
14412
  /** Événements du chrome intégré (dessin, import). */
14208
14413
  drawChange = output();
14209
14414
  layerImport = output();
@@ -23174,367 +23379,73 @@ const ATELIER_BORNES = {
23174
23379
  propsMax: 30,
23175
23380
  /** Enfants directs d'un conteneur. Le plafond GLOBAL reste `itemsMax`, enfants compris. */
23176
23381
  enfantsMax: 30,
23382
+ /**
23383
+ * Profondeur d'imbrication, en niveaux SOUS le plan.
23384
+ *
23385
+ * Un item du plan est à la profondeur 0, son enfant à 1, et ainsi de suite jusqu'à
23386
+ * `profondeurMax` INCLUS — soit cinq étages en tout. Ce n'est pas une préférence
23387
+ * d'ergonomie : `atelierNormaliser` et l'assainisseur backend descendent une
23388
+ * structure VENUE DE L'EXTÉRIEUR (lien partagé, fichier collé), et une récursion
23389
+ * non bornée sur du JSON hostile fait sauter la pile. Le plafond de COMPTE
23390
+ * (`itemsMax`) ne borne pas la profondeur : deux cents items peuvent former une
23391
+ * chaîne de deux cents étages.
23392
+ *
23393
+ * Cinq étages couvrent le besoin réel mesuré le 27/08 — le pire cas de la
23394
+ * bibliothèque est `cad-splitter > cad-splitter-panel > cad-tabs > cad-tab >
23395
+ * contenu`, soit une profondeur de 4 pour le contenu.
23396
+ */
23397
+ profondeurMax: 4,
23177
23398
  };
23178
23399
  /** Un plan vide, jamais `null` : un canevas absent et un canevas vide se traitent pareil. */
23179
23400
  const atelierLayoutVide = () => ({ version: 1, items: [] });
23180
23401
 
23181
23402
  /**
23182
- * Les opérations du plan, en logique PURE aucun DOM, aucun Angular.
23403
+ * Le contrat d'accueil entre un HÔTE de la bibliothèque et les pièces qu'on lui POSE.
23183
23404
  *
23184
- * Même parti que `lib/planning/` : ce qui décide se teste sans rendu. Les défauts du
23185
- * calendrier ont tous été trouvés là, et deux fois c'est mon ATTENTE de test qui était
23186
- * fausse, pas le code — c'est plus facile à voir sur une fonction pure.
23405
+ * ── POURQUOI CE FICHIER EXISTE ──────────────────────────────────────────────────
23187
23406
  *
23188
- * Toutes rendent un NOUVEAU plan : la maquette est de la donnée, on ne la mute pas sous
23189
- * les pieds de qui l'affiche.
23190
- */
23191
- /** Compteur de la session : les identifiants n'ont pas à être uniques au monde, seulement dans un plan. */
23192
- let compteur = 0;
23193
- /** Identifiant conforme à l'assainisseur backend : `/^[A-Za-z0-9_-]{1,24}$/`. */
23194
- function atelierNouvelId() {
23195
- compteur += 1;
23196
- return `i${compteur.toString(36)}`;
23197
- }
23198
- /** Remet le compteur à zéro. Réservé aux tests, pour qu'ils ne dépendent pas de leur ordre. */
23199
- function atelierReinitialiserIds() {
23200
- compteur = 0;
23201
- }
23202
- const borne = (v, min, max) => Math.max(min, Math.min(max, Math.trunc(Number.isFinite(v) ? v : min)));
23203
- /**
23204
- * La carte d'occupation du plan, ligne par ligne.
23407
+ * L'atelier du studio et le rendu frontless du shell instancient les composants
23408
+ * IMPÉRATIVEMENT (`createComponent` + `projectableNodes`) : c'est ce qui permet de poser
23409
+ * le VRAI composant, vivant, plutôt qu'une image de lui. Mais deux mécanismes d'Angular
23410
+ * ne traversent PAS cette frontière, et c'est mesuré, pas supposé (27/08) :
23205
23411
  *
23206
- * On ne garde pas de largeur fixe : une ligne absente est une ligne vide. Un plan de
23207
- * trois éléments ne fabrique pas 500 tableaux.
23208
- */
23209
- function occupation(items, sauf) {
23210
- const lignes = new Map();
23211
- for (const it of items) {
23212
- if (sauf && it.id === sauf)
23213
- continue;
23214
- for (let y = it.y; y < it.y + it.h; y++) {
23215
- let ligne = lignes.get(y);
23216
- if (!ligne) {
23217
- ligne = new Array(ATELIER_COLONNES).fill(false);
23218
- lignes.set(y, ligne);
23219
- }
23220
- for (let x = it.x; x < it.x + it.w && x < ATELIER_COLONNES; x++)
23221
- ligne[x] = true;
23222
- }
23223
- }
23224
- return lignes;
23225
- }
23226
- /**
23227
- * La première position libre pour un bloc de `w × h`, en balayant de haut en bas puis de
23228
- * gauche à droite.
23412
+ * `contentChildren()` une requête de contenu se résout sur la structure DÉCLARÉE
23413
+ * d'un gabarit. Un composant instancié à la main vit dans une autre vue : il arrive
23414
+ * bien dans le DOM, la requête ne le voit jamais. `cad-tabs` rendait `onglets=0`,
23415
+ * `cad-splitter` `gouttieres=0` — une barre vide, aucune erreur.
23416
+ * ② `inject(Hôte)` depuis la pièce — sans chaîne d'injecteurs de nœud, la recherche
23417
+ * remonte à l'injecteur d'environnement et échoue : `NG0201` pour
23418
+ * `cad-accordion-panel` et `cad-radio` (que `gen-catalog` classe d'ailleurs en
23419
+ * « contextuels », donc non posables), ou `null` silencieux quand l'injection est
23420
+ * `optional`.
23229
23421
  *
23230
- * C'est ce qui rend le CLIC utilisable comme geste principal : on choisit dans la palette
23231
- * et ça se pose à un endroit sensé, sans viser. Le glisser sert ensuite à réarranger,
23232
- * il est réellement meilleur.
23233
- */
23234
- function atelierPremierePlaceLibre(items, w, h) {
23235
- const large = borne(w, 1, ATELIER_COLONNES);
23236
- const haut = borne(h, ATELIER_BORNES.hMin, ATELIER_BORNES.hMax);
23237
- const carte = occupation(items);
23238
- for (let y = 0; y <= ATELIER_BORNES.yMax; y++) {
23239
- for (let x = 0; x + large <= ATELIER_COLONNES; x++) {
23240
- let libre = true;
23241
- for (let dy = 0; dy < haut && libre; dy++) {
23242
- const ligne = carte.get(y + dy);
23243
- if (!ligne)
23244
- continue; // ligne entièrement vide
23245
- for (let dx = 0; dx < large; dx++) {
23246
- if (ligne[x + dx]) {
23247
- libre = false;
23248
- break;
23249
- }
23250
- }
23251
- }
23252
- if (libre)
23253
- return { x, y };
23254
- }
23255
- }
23256
- // Plan saturé : on pose en bas plutôt que de refuser. Un outil de maquette ne dit pas non.
23257
- return { x: 0, y: ATELIER_BORNES.yMax };
23258
- }
23259
- /** Nombre TOTAL de composants, enfants compris — c'est lui que borne `itemsMax`. */
23260
- function atelierCompter(layout) {
23261
- return layout.items.reduce((n, it) => n + 1 + (it.children?.length ?? 0), 0);
23262
- }
23263
- /**
23264
- * Retrouve un composant OÙ QU'IL VIVE — au premier niveau ou dans un conteneur.
23265
- * `parentId` dit d'où il vient : c'est ce qui permet à l'inspecteur de remonter.
23266
- */
23267
- function atelierTrouver(layout, id) {
23268
- for (const it of layout.items) {
23269
- if (it.id === id)
23270
- return { item: it, parentId: null };
23271
- for (const enfant of it.children ?? []) {
23272
- if (enfant.id === id)
23273
- return { item: enfant, parentId: it.id };
23274
- }
23275
- }
23276
- return null;
23277
- }
23278
- /**
23279
- * Pose un composant — sur le plan, ou DANS un conteneur (`dansId`).
23422
+ * `<ng-content>` est donc NÉCESSAIRE mais PAS SUFFISANT pour qu'une famille soit un
23423
+ * conteneur d'atelier. Le raccourci « ça projette, donc ça marche » a coûté une matinée :
23424
+ * il faut aussi que l'hôte sache ACCUEILLIR une pièce qu'aucune requête ne lui rapportera.
23280
23425
  *
23281
- * Dans un conteneur, l'enfant vit en FLUX : ajouté en fin, x/y/w/h normalisés (0/0/12/1),
23282
- * l'ordre fait loi. Refus VISIBLE (id vide) si le conteneur n'existe pas, est lui-même un
23283
- * enfant (un seul niveau en v1), ou si un plafond est atteint jamais de troncature
23284
- * silencieuse, même doctrine que le plafond global.
23285
- */
23286
- function atelierAjouter(layout, type, gabarit = {}, dansId) {
23287
- const base = layout ?? atelierLayoutVide();
23288
- if (atelierCompter(base) >= ATELIER_BORNES.itemsMax) {
23289
- // Plafond de l'assainisseur : au-delà, le backend tronquerait EN SILENCE et
23290
- // l'utilisateur perdrait des composants sans le savoir. On refuse visiblement.
23291
- return { layout: base, id: '' };
23292
- }
23293
- if (dansId) {
23294
- const cible = atelierTrouver(base, dansId);
23295
- if (!cible || cible.parentId !== null)
23296
- return { layout: base, id: '' };
23297
- if ((cible.item.children?.length ?? 0) >= ATELIER_BORNES.enfantsMax)
23298
- return { layout: base, id: '' };
23299
- const id = atelierNouvelId();
23300
- // `w` d'un enfant = LARGEUR EN COLONNES du conteneur (grille de flux). Il naît
23301
- // pleine ligne — prévisible — et se réduit ensuite au curseur, comme un bloc.
23302
- const enfant = { id, type, x: 0, y: 0, w: ATELIER_COLONNES, h: 1 };
23303
- if (gabarit.props && Object.keys(gabarit.props).length)
23304
- enfant.props = { ...gabarit.props };
23305
- return {
23306
- layout: {
23307
- version: 1,
23308
- items: base.items.map((it) => it.id === dansId ? { ...it, children: [...(it.children ?? []), enfant] } : it),
23309
- },
23310
- id,
23311
- };
23312
- }
23313
- const w = borne(gabarit.w ?? 4, 1, ATELIER_COLONNES);
23314
- const h = borne(gabarit.h ?? 2, ATELIER_BORNES.hMin, ATELIER_BORNES.hMax);
23315
- const { x, y } = atelierPremierePlaceLibre(base.items, w, h);
23316
- const id = atelierNouvelId();
23317
- const item = { id, type, x, y, w, h };
23318
- if (gabarit.props && Object.keys(gabarit.props).length)
23319
- item.props = { ...gabarit.props };
23320
- return { layout: { version: 1, items: [...base.items, item] }, id };
23321
- }
23322
- /** Retire un composant, où qu'il vive. Retirer un conteneur emporte ses enfants. */
23323
- function atelierRetirer(layout, id) {
23324
- return {
23325
- version: 1,
23326
- items: layout.items
23327
- .filter((it) => it.id !== id)
23328
- .map((it) => {
23329
- if (!it.children?.some((e) => e.id === id))
23330
- return it;
23331
- const enfants = it.children.filter((e) => e.id !== id);
23332
- const suite = { ...it };
23333
- if (enfants.length)
23334
- suite.children = enfants;
23335
- else
23336
- delete suite.children;
23337
- return suite;
23338
- }),
23339
- };
23340
- }
23341
- /** Change une propriété — où que vive le composant. `undefined` la supprime. */
23342
- function atelierDefinirProp(layout, id, nom, valeur) {
23343
- const poserProp = (it) => {
23344
- const props = { ...(it.props ?? {}) };
23345
- if (valeur === undefined)
23346
- delete props[nom];
23347
- else
23348
- props[nom] = valeur;
23349
- const suite = { ...it };
23350
- if (Object.keys(props).length)
23351
- suite.props = props;
23352
- else
23353
- delete suite.props;
23354
- return suite;
23355
- };
23356
- return {
23357
- version: 1,
23358
- items: layout.items.map((it) => {
23359
- if (it.id === id)
23360
- return poserProp(it);
23361
- if (!it.children?.some((e) => e.id === id))
23362
- return it;
23363
- return { ...it, children: it.children.map((e) => (e.id === id ? poserProp(e) : e)) };
23364
- }),
23365
- };
23366
- }
23367
- /**
23368
- * Déplace un ENFANT d'un cran dans son conteneur (le flux : l'ordre fait loi).
23369
- * Même chemin pour la souris et le clavier, comme le reste du modèle.
23426
+ * ── LE CONTRAT ──────────────────────────────────────────────────────────────────
23427
+ *
23428
+ * Il n'est pas neuf : `cad-button-group` le porte depuis le 24/08, posé le jour l'on
23429
+ * a constaté qu'un groupe était SOURD aux clics de ses boutons posés. Ce fichier ne
23430
+ * l'invente pas, il l'ÉCRIT — jusqu'ici il vivait duck-typé dans le seul poseur du
23431
+ * studio, invisible depuis la bibliothèque, ce qui a permis à `cad-map` de partir avec
23432
+ * un nom à lui (`registerChild`) que personne n'appelle. Deux vocabulaires pour un
23433
+ * besoin, c'est une famille cassée en silence.
23370
23434
  */
23371
- function atelierReordonnerEnfant(layout, parentId, id, sens) {
23372
- return {
23373
- version: 1,
23374
- items: layout.items.map((it) => {
23375
- if (it.id !== parentId || !it.children)
23376
- return it;
23377
- const i = it.children.findIndex((e) => e.id === id);
23378
- const j = i + sens;
23379
- if (i < 0 || j < 0 || j >= it.children.length)
23380
- return it;
23381
- const enfants = [...it.children];
23382
- [enfants[i], enfants[j]] = [enfants[j], enfants[i]];
23383
- return { ...it, children: enfants };
23384
- }),
23385
- };
23386
- }
23387
23435
  /**
23388
- * Change la largeur, en gardant le bloc DANS la grille.
23436
+ * L'hôte sait-il accueillir des pièces posées ?
23389
23437
  *
23390
- * On décale x quand la largeur déborde, plutôt que de rogner w : l'utilisateur a demandé
23391
- * une largeur, pas une place. Rogner silencieusement donnerait un bloc plus étroit que
23392
- * ce qu'il a réglé, sans rien dire.
23438
+ * Reconnaissance par CAPACITÉ et non par liste de types : un poseur qui énumérerait les
23439
+ * familles concernées serait à mettre à jour à chaque nouvelle et c'est toujours la
23440
+ * mise à jour qu'on oublie. Une famille qui gagne la trappe devient utilisable dans
23441
+ * l'atelier sans qu'aucun poseur ne bouge.
23393
23442
  */
23394
- function atelierRedimensionner(layout, id, w) {
23395
- const large = borne(w, 1, ATELIER_COLONNES);
23396
- return {
23397
- version: 1,
23398
- items: layout.items.map((it) => {
23399
- if (it.id === id)
23400
- return { ...it, w: large, x: Math.min(it.x, ATELIER_COLONNES - large) };
23401
- // Un ENFANT se redimensionne aussi (SZU 24/08 : « on ne peut plus les
23402
- // redimensionner, très limitatif ») — son `w` est un span de la grille de flux
23403
- // du conteneur, x reste 0 : pas de placement absolu à l'intérieur.
23404
- if (!it.children?.some((e) => e.id === id))
23405
- return it;
23406
- return {
23407
- ...it,
23408
- children: it.children.map((e) => (e.id === id ? { ...e, w: large } : e)),
23409
- };
23410
- }),
23411
- };
23443
+ function estHoteAPieces(hote) {
23444
+ return !!hote && typeof hote.enregistrerEnfants === 'function';
23412
23445
  }
23446
+
23413
23447
  /**
23414
- * Déplace un bloc d'un cran dans l'ordre de lecture (haut/bas), puis recompacte.
23415
- *
23416
- * C'est le CHEMIN CLAVIER, et il passe par les mêmes fonctions que le glisser — la règle
23417
- * déjà tenue par `cad-taskboard` et `cad-calendar`. Deux chemins séparés divergent
23418
- * toujours, et c'est celui du clavier qui pourrit, parce que personne ne le regarde.
23419
- */
23420
- function atelierDeplacer(layout, id, sens) {
23421
- const ordonne = [...layout.items].sort((a, b) => a.y - b.y || a.x - b.x);
23422
- const i = ordonne.findIndex((it) => it.id === id);
23423
- if (i < 0)
23424
- return layout;
23425
- const j = i + sens;
23426
- if (j < 0 || j >= ordonne.length)
23427
- return layout;
23428
- [ordonne[i], ordonne[j]] = [ordonne[j], ordonne[i]];
23429
- // `atelierPoser` et NON `atelierRecompacter` : ce dernier re-trie par (y, x), ce qui
23430
- // annulerait l'échange qu'on vient de faire — les `y` n'ont pas encore bougé. Défaut
23431
- // trouvé par le test du clavier, qui rendait le déplacement parfaitement inopérant.
23432
- return atelierPoser(ordonne);
23433
- }
23434
- /**
23435
- * Repose tous les blocs dans l'ordre de lecture, en supprimant les trous verticaux.
23436
- *
23437
- * Le plan garde des positions ABSOLUES (c'est le contrat de frontless), mais une maquette
23438
- * criblée de lignes vides est illisible. On recompacte donc à chaque réarrangement, en
23439
- * respectant l'ordre voulu et les largeurs.
23440
- */
23441
- function atelierRecompacter(layout) {
23442
- return atelierPoser([...layout.items].sort((a, b) => a.y - b.y || a.x - b.x));
23443
- }
23444
- /**
23445
- * Repose les blocs DANS L'ORDRE DU TABLEAU REÇU, sans le retrier.
23446
- *
23447
- * La distinction avec `atelierRecompacter` n'est pas cosmétique : après un déplacement au
23448
- * clavier, l'ordre voulu est celui du tableau, et les `y` portent encore les anciennes
23449
- * positions. Retrier à ce moment-là annule l'échange — le déplacement devenait
23450
- * silencieusement inopérant, et c'est le test du clavier qui l'a montré.
23451
- */
23452
- function atelierPoser(items) {
23453
- const poses = [];
23454
- for (const it of items) {
23455
- const { x, y } = atelierPremierePlaceLibre(poses, it.w, it.h);
23456
- poses.push({ ...it, x, y });
23457
- }
23458
- return { version: 1, items: poses };
23459
- }
23460
- /**
23461
- * Ramène un plan venu de l'extérieur dans les clous — un lien partagé, un fichier collé.
23462
- *
23463
- * Écarte ce qui est irrécupérable plutôt que de le réparer au jugé : un bloc dont on a
23464
- * deviné le type serait pire qu'un bloc absent. Même doctrine que l'assainisseur backend,
23465
- * dont ce n'est PAS un doublon : celui-ci protège l'affichage, celui-là protège la base.
23466
- */
23467
- function atelierNormaliser(brut) {
23468
- const entree = brut;
23469
- if (!entree || !Array.isArray(entree.items))
23470
- return atelierLayoutVide();
23471
- let total = 0;
23472
- /**
23473
- * Un item ramené dans les clous, ou `null` s'il est irrécupérable.
23474
- * `enfant` change les règles : positions forcées au FLUX (0/0/12/1) et JAMAIS de
23475
- * petits-enfants — un seul niveau en v1, le surplus est écarté, pas réparé.
23476
- */
23477
- const normaliserItem = (it, enfant) => {
23478
- if (!it || typeof it !== 'object')
23479
- return null;
23480
- const brutItem = it;
23481
- const type = String(brutItem.type ?? '');
23482
- if (!/^(?:app:)?[a-z][a-z0-9-]{0,40}$/.test(type))
23483
- return null;
23484
- // `w` d'un enfant = span de la grille de flux, GARDÉ (borné 1–12) ; x/y/h forcés.
23485
- const w = borne(brutItem.w, 1, ATELIER_COLONNES);
23486
- const x = enfant ? 0 : borne(brutItem.x, 0, ATELIER_COLONNES - w);
23487
- const h = enfant ? 1 : borne(brutItem.h, ATELIER_BORNES.hMin, ATELIER_BORNES.hMax);
23488
- const y = enfant ? 0 : borne(brutItem.y, 0, ATELIER_BORNES.yMax);
23489
- const propre = { id: brutItem.id || atelierNouvelId(), type, x, y, w, h };
23490
- const props = brutItem.props;
23491
- if (props && typeof props === 'object' && !Array.isArray(props)) {
23492
- const gardees = {};
23493
- for (const [k, v] of Object.entries(props).slice(0, ATELIER_BORNES.propsMax)) {
23494
- if (!/^[A-Za-z_][A-Za-z0-9_]{0,30}$/.test(k))
23495
- continue;
23496
- if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')
23497
- gardees[k] = v;
23498
- else if (Array.isArray(v))
23499
- gardees[k] = v.slice(0, 100).map((s) => String(s));
23500
- }
23501
- if (Object.keys(gardees).length)
23502
- propre.props = gardees;
23503
- }
23504
- if (brutItem.hideMobile === true)
23505
- propre.hideMobile = true;
23506
- if (!enfant && Array.isArray(brutItem.children)) {
23507
- const enfants = [];
23508
- for (const e of brutItem.children.slice(0, ATELIER_BORNES.enfantsMax)) {
23509
- if (total >= ATELIER_BORNES.itemsMax)
23510
- break;
23511
- const propreEnfant = normaliserItem(e, true);
23512
- if (propreEnfant) {
23513
- enfants.push(propreEnfant);
23514
- total += 1;
23515
- }
23516
- }
23517
- if (enfants.length)
23518
- propre.children = enfants;
23519
- }
23520
- return propre;
23521
- };
23522
- const items = [];
23523
- for (const it of entree.items) {
23524
- if (total >= ATELIER_BORNES.itemsMax)
23525
- break;
23526
- total += 1; // l'item lui-même, compté AVANT ses enfants pour que le plafond soit global
23527
- const propre = normaliserItem(it, false);
23528
- if (propre)
23529
- items.push(propre);
23530
- else
23531
- total -= 1;
23532
- }
23533
- return { version: 1, items };
23534
- }
23535
-
23536
- /**
23537
- * Le catalogue de la bibliothèque — GÉNÉRÉ, ne pas modifier à la main.
23448
+ * Le catalogue de la bibliothèque GÉNÉRÉ, ne pas modifier à la main.
23538
23449
  *
23539
23450
  * Source : tools/gen-catalog.mjs. Jumeau TypeScript de projects/ui/catalog.json, pour
23540
23451
  * les modules de la bibliothèque : ng-packagr ne sait pas résoudre un import JSON hors
@@ -25827,7 +25738,9 @@ const CATALOGUE = {
25827
25738
  "ajustement": "naturel",
25828
25739
  "icone": "users",
25829
25740
  "largeur": "2",
25830
- "hauteur": "1"
25741
+ "hauteur": "1",
25742
+ "conteneur": "direct",
25743
+ "accepte": "cad-avatar"
25831
25744
  },
25832
25745
  "inputs": [
25833
25746
  {
@@ -28402,7 +28315,10 @@ const CATALOGUE = {
28402
28315
  "kind": "component",
28403
28316
  "role": null,
28404
28317
  "atelier": {
28405
- "icone": "grid"
28318
+ "icone": "grid",
28319
+ "conteneur": "direct",
28320
+ "largeur": "12",
28321
+ "hauteur": "4"
28406
28322
  },
28407
28323
  "inputs": [
28408
28324
  {
@@ -28620,7 +28536,8 @@ const CATALOGUE = {
28620
28536
  "kind": "component",
28621
28537
  "role": null,
28622
28538
  "atelier": {
28623
- "icone": "move-horizontal"
28539
+ "icone": "move-horizontal",
28540
+ "surface": "aucune"
28624
28541
  },
28625
28542
  "inputs": [
28626
28543
  {
@@ -28643,6 +28560,11 @@ const CATALOGUE = {
28643
28560
  "selector": "cad-splitter-panel",
28644
28561
  "kind": "component",
28645
28562
  "role": null,
28563
+ "atelier": {
28564
+ "icone": "square",
28565
+ "conteneur": "flux",
28566
+ "dans": "cad-splitter"
28567
+ },
28646
28568
  "inputs": [
28647
28569
  {
28648
28570
  "name": "size",
@@ -28664,7 +28586,10 @@ const CATALOGUE = {
28664
28586
  "kind": "component",
28665
28587
  "role": null,
28666
28588
  "atelier": {
28667
- "icone": "columns"
28589
+ "icone": "columns",
28590
+ "conteneur": "direct",
28591
+ "largeur": "12",
28592
+ "hauteur": "8"
28668
28593
  },
28669
28594
  "inputs": [
28670
28595
  {
@@ -30500,6 +30425,12 @@ const CATALOGUE = {
30500
30425
  "selector": "cad-accordion-panel",
30501
30426
  "kind": "component",
30502
30427
  "role": null,
30428
+ "atelier": {
30429
+ "icone": "panel-top",
30430
+ "conteneur": "flux",
30431
+ "dans": "cad-accordion",
30432
+ "pose-header": "Section"
30433
+ },
30503
30434
  "inputs": [
30504
30435
  {
30505
30436
  "name": "value",
@@ -30548,7 +30479,10 @@ const CATALOGUE = {
30548
30479
  "kind": "component",
30549
30480
  "role": null,
30550
30481
  "atelier": {
30551
- "icone": "chevron-down"
30482
+ "icone": "chevron-down",
30483
+ "conteneur": "direct",
30484
+ "largeur": "12",
30485
+ "hauteur": "8"
30552
30486
  },
30553
30487
  "inputs": [
30554
30488
  {
@@ -31417,6 +31351,12 @@ const CATALOGUE = {
31417
31351
  "selector": "cad-tab",
31418
31352
  "kind": "component",
31419
31353
  "role": null,
31354
+ "atelier": {
31355
+ "icone": "panel-top",
31356
+ "conteneur": "flux",
31357
+ "dans": "cad-tabs",
31358
+ "pose-label": "Onglet"
31359
+ },
31420
31360
  "inputs": [
31421
31361
  {
31422
31362
  "name": "value",
@@ -31466,7 +31406,10 @@ const CATALOGUE = {
31466
31406
  "kind": "component",
31467
31407
  "role": null,
31468
31408
  "atelier": {
31469
- "icone": "folder"
31409
+ "icone": "folder",
31410
+ "conteneur": "direct",
31411
+ "largeur": "12",
31412
+ "hauteur": "6"
31470
31413
  },
31471
31414
  "inputs": [
31472
31415
  {
@@ -33886,7 +33829,9 @@ const CATALOGUE = {
33886
33829
  "role": null,
33887
33830
  "atelier": {
33888
33831
  "icone": "circle-dot",
33889
- "largeur": "2"
33832
+ "largeur": "2",
33833
+ "conteneur": "direct",
33834
+ "hauteur": "4"
33890
33835
  },
33891
33836
  "inputs": [
33892
33837
  {
@@ -33930,10 +33875,16 @@ const CATALOGUE = {
33930
33875
  "selector": "cad-radio",
33931
33876
  "kind": "component",
33932
33877
  "role": null,
33878
+ "atelier": {
33879
+ "icone": "circle-dot",
33880
+ "conteneur": "flux",
33881
+ "dans": "cad-radio-group",
33882
+ "pose-label": "Option"
33883
+ },
33933
33884
  "inputs": [
33934
33885
  {
33935
33886
  "name": "value",
33936
- "required": true,
33887
+ "required": false,
33937
33888
  "type": "T"
33938
33889
  },
33939
33890
  {
@@ -34244,7 +34195,7 @@ const CATALOGUE = {
34244
34195
  * Les 61 directives de la bibliothèque n'y figurent pas : une directive ne s'instancie
34245
34196
  * pas seule, elle se pose sur un hôte existant. Elles restent visibles dans catalog.json.
34246
34197
  */
34247
- /** Symbole -> de quoi le construire. 104 composants posables. */
34198
+ /** Symbole -> de quoi le construire. 106 composants posables. */
34248
34199
  const REGISTRE = {
34249
34200
  CadButtonGroupComponent: { classe: CadButtonGroupComponent, selecteur: "cad-button-group", hote: "cad-button-group" },
34250
34201
  CadButtonComponent: { classe: CadButtonComponent, selecteur: "button[cadButton], a[cadButton]", hote: "button" },
@@ -34316,6 +34267,7 @@ const REGISTRE = {
34316
34267
  CadMapPanelComponent: { classe: CadMapPanelComponent, selecteur: "cad-map-panel", hote: "cad-map-panel" },
34317
34268
  CadMapSectionComponent: { classe: CadMapSectionComponent, selecteur: "cad-map-section", hote: "cad-map-section" },
34318
34269
  CadMapComponent: { classe: CadMapComponent, selecteur: "cad-map", hote: "cad-map" },
34270
+ CadAccordionPanelComponent: { classe: CadAccordionPanelComponent, selecteur: "cad-accordion-panel", hote: "cad-accordion-panel" },
34319
34271
  CadAccordionComponent: { classe: CadAccordionComponent, selecteur: "cad-accordion", hote: "cad-accordion" },
34320
34272
  CadBottomNavComponent: { classe: CadBottomNavComponent, selecteur: "cad-bottom-nav", hote: "cad-bottom-nav" },
34321
34273
  CadBreadcrumbComponent: { classe: CadBreadcrumbComponent, selecteur: "cad-breadcrumb", hote: "cad-breadcrumb" },
@@ -34347,6 +34299,7 @@ const REGISTRE = {
34347
34299
  CadTreeselectComponent: { classe: CadTreeselectComponent, selecteur: "cad-treeselect", hote: "cad-treeselect" },
34348
34300
  CadCheckboxComponent: { classe: CadCheckboxComponent, selecteur: "cad-checkbox", hote: "cad-checkbox" },
34349
34301
  CadRadioGroupComponent: { classe: CadRadioGroupComponent, selecteur: "cad-radio-group", hote: "cad-radio-group" },
34302
+ CadRadioComponent: { classe: CadRadioComponent, selecteur: "cad-radio", hote: "cad-radio" },
34350
34303
  CadSegmentedComponent: { classe: CadSegmentedComponent, selecteur: "cad-segmented", hote: "cad-segmented" },
34351
34304
  CadSwitchComponent: { classe: CadSwitchComponent, selecteur: "cad-switch", hote: "cad-switch" },
34352
34305
  CadToggleButtonComponent: { classe: CadToggleButtonComponent, selecteur: "cad-toggle-button", hote: "cad-toggle-button" },
@@ -34515,9 +34468,11 @@ function construire() {
34515
34468
  largeur: entierAnnote(meta['largeur'], 1, 12) ?? largeur,
34516
34469
  hauteur: entierAnnote(meta['hauteur'], 1, 24) ?? hauteur,
34517
34470
  ajustement: meta['ajustement'] === 'naturel' ? 'naturel' : 'remplir',
34471
+ surface: meta['surface'] === 'aucune' ? 'aucune' : 'propre',
34518
34472
  rang: entierAnnote(meta['rang'], 1, 99) ?? 99,
34519
34473
  exemples: s.exemples ?? {},
34520
34474
  dans: meta['dans'] || null,
34475
+ accepte: meta['accepte'] ? meta['accepte'].split('|').filter(Boolean) : null,
34521
34476
  icone: meta['icone'] && CAD_ICONS[meta['icone']] ? meta['icone'] : null,
34522
34477
  contenu: meta['contenu'] || null,
34523
34478
  conteneur,
@@ -34545,6 +34500,16 @@ function construire() {
34545
34500
  }
34546
34501
  let _palette = null;
34547
34502
  let _parCle = null;
34503
+ /**
34504
+ * Les hôtes qui ont AU MOINS UNE pièce, c'est-à-dire les clés citées par un `dans=`.
34505
+ *
34506
+ * C'est la moitié qui manquait. `dans=` était lu dans un seul sens — « cette pièce ne
34507
+ * s'affiche que chez son hôte » — et rien ne disait l'inverse. Un `cad-spacer` entrait
34508
+ * donc dans un `cad-tabs` (constaté au clic par SZU le 27/08) : il partait en base,
34509
+ * s'affichait dans l'inspecteur, et ne rendait rien, puisque l'hôte filtre ses pièces
34510
+ * par type. Le pire des conteneurs : celui qui a l'air d'accepter.
34511
+ */
34512
+ let _hotesAPieces = null;
34548
34513
  /**
34549
34514
  * Tous les composants posables — calculés à la PREMIÈRE DEMANDE, jamais au chargement.
34550
34515
  *
@@ -34565,6 +34530,7 @@ function atelierPalette() {
34565
34530
  if (!_palette) {
34566
34531
  _palette = construire();
34567
34532
  _parCle = new Map(_palette.map((c) => [c.cle, c]));
34533
+ _hotesAPieces = new Set(_palette.map((c) => c.dans).filter((d) => !!d));
34568
34534
  }
34569
34535
  return _palette;
34570
34536
  }
@@ -34574,13 +34540,57 @@ function atelierComposant(cle) {
34574
34540
  return _parCle.get(cle) ?? null;
34575
34541
  }
34576
34542
  /**
34577
- * Recherche par nom, clé ou sélecteur. Sans terme, rend tout SAUF les composants
34578
- * `dans=`, réservés à leur hôte : ils n'apparaissent que quand `hote` est le leur
34579
- * (la cible de pose courante). `cad-map-panel` posé seul ne montre rien de sensé
34580
- * signalé par SZU le 24/08.
34543
+ * Cet hôte accepte-t-il ce composant ? `hote = null` désigne le PLAN.
34544
+ *
34545
+ * ⚠️ `dans=` SE LIT DANS LES DEUX SENS, et c'est toute la règle (arbitrage SZU 27/08) :
34546
+ *
34547
+ * · SENS DIRECT — une pièce ne se pose QUE chez son hôte. `cad-map-panel` posé seul
34548
+ * ne montre rien de sensé (signalé le 24/08), `cad-tab` hors d'un `cad-tabs` non plus.
34549
+ * · SENS INVERSE — un hôte QUI A des pièces n'accepte QUE ses pièces. `cad-tabs`
34550
+ * n'accueille que des `cad-tab`, `cad-map` que ses onze pièces de carte.
34551
+ *
34552
+ * La troisième règle en découle sans être écrite : un conteneur SANS pièce déclarée
34553
+ * (`cad-tab`, `cad-panel`, `cad-grid`…) accepte tout, sauf ce qui appartient à un autre
34554
+ * hôte. Rien à annoter : la relation existe déjà, on la lisait à moitié.
34555
+ */
34556
+ function atelierAccepte(hote, cle) {
34557
+ const c = atelierComposant(cle);
34558
+ if (!c)
34559
+ return false;
34560
+ if (c.dans)
34561
+ return c.dans === hote; // une pièce ne va QUE chez son hôte, jamais au plan
34562
+ if (!hote)
34563
+ return true;
34564
+ const h = atelierComposant(hote);
34565
+ if (h?.accepte)
34566
+ return h.accepte.includes(cle); // strictesse déclarée par l'hôte
34567
+ return !_hotesAPieces.has(hote); // strictesse DÉDUITE : cet hôte a des pièces à lui
34568
+ }
34569
+ /**
34570
+ * Les clés des pièces d'un hôte, dans l'ordre de la palette. Vide si l'hôte n'est pas
34571
+ * strict — c'est le test « ce conteneur a-t-il des pièces à lui ? ».
34572
+ */
34573
+ function atelierPiecesDe(hote) {
34574
+ if (!hote)
34575
+ return [];
34576
+ const h = atelierComposant(hote);
34577
+ if (h?.accepte)
34578
+ return [...h.accepte];
34579
+ return atelierPalette().filter((c) => c.dans === hote).map((c) => c.cle);
34580
+ }
34581
+ /**
34582
+ * Recherche par nom, clé ou sélecteur. Rend TOUT ce qui correspond au terme.
34583
+ *
34584
+ * ⚠️ NE FILTRE PLUS SUR LA CIBLE, et c'est délibéré (SZU 27/08, règle déjà posée le
34585
+ * 26/08 pour le builder) : ce qu'un conteneur refuse doit apparaître VERROUILLÉ, pas
34586
+ * disparaître. Masquer fait deux dégâts — ça dérobe l'information (l'utilisateur ne sait
34587
+ * pas que le composant existe, ni pourquoi il n'est pas là) et ça fait SAUTER la palette
34588
+ * à chaque changement de sélection, si bien qu'on ne retrouve plus rien où on l'avait vu.
34589
+ *
34590
+ * L'appelant interroge `atelierAccepte` pour savoir quoi désactiver, et dit pourquoi.
34581
34591
  */
34582
- function atelierChercher(terme, hote = null) {
34583
- const visibles = atelierPalette().filter((c) => !c.dans || c.dans === hote);
34592
+ function atelierChercher(terme) {
34593
+ const visibles = [...atelierPalette()];
34584
34594
  const q = terme.trim().toLowerCase();
34585
34595
  if (!q)
34586
34596
  return visibles;
@@ -34589,10 +34599,15 @@ function atelierChercher(terme, hote = null) {
34589
34599
  c.symbole.toLowerCase().includes(q) ||
34590
34600
  (c.role ?? '').toLowerCase().includes(q));
34591
34601
  }
34592
- /** Les composants groupés, dans l'ordre d'affichage de la palette. */
34593
- function atelierGroupes(terme = '', hote = null) {
34602
+ /**
34603
+ * Les composants groupés, dans l'ordre d'affichage de la palette.
34604
+ *
34605
+ * Les groupes ne dépendent PAS de la cible : la palette garde la même forme quoi qu'on
34606
+ * sélectionne, ce qui est la moitié de l'intérêt du verrouillage-expliqué.
34607
+ */
34608
+ function atelierGroupes(terme = '') {
34594
34609
  const par = new Map();
34595
- for (const c of atelierChercher(terme, hote)) {
34610
+ for (const c of atelierChercher(terme)) {
34596
34611
  if (!par.has(c.groupe))
34597
34612
  par.set(c.groupe, []);
34598
34613
  par.get(c.groupe).push(c);
@@ -34600,6 +34615,521 @@ function atelierGroupes(terme = '', hote = null) {
34600
34615
  return [...par.entries()].map(([groupe, composants]) => ({ groupe, composants }));
34601
34616
  }
34602
34617
 
34618
+ /**
34619
+ * Les opérations du plan, en logique PURE — aucun DOM, aucun Angular.
34620
+ *
34621
+ * Même parti que `lib/planning/` : ce qui décide se teste sans rendu. Les défauts du
34622
+ * calendrier ont tous été trouvés là, et deux fois c'est mon ATTENTE de test qui était
34623
+ * fausse, pas le code — c'est plus facile à voir sur une fonction pure.
34624
+ *
34625
+ * Toutes rendent un NOUVEAU plan : la maquette est de la donnée, on ne la mute pas sous
34626
+ * les pieds de qui l'affiche.
34627
+ */
34628
+ /** Compteur de la session : les identifiants n'ont pas à être uniques au monde, seulement dans un plan. */
34629
+ let compteur = 0;
34630
+ /** Identifiant conforme à l'assainisseur backend : `/^[A-Za-z0-9_-]{1,24}$/`. */
34631
+ function atelierNouvelId() {
34632
+ compteur += 1;
34633
+ return `i${compteur.toString(36)}`;
34634
+ }
34635
+ /** Remet le compteur à zéro. Réservé aux tests, pour qu'ils ne dépendent pas de leur ordre. */
34636
+ function atelierReinitialiserIds() {
34637
+ compteur = 0;
34638
+ }
34639
+ /** Fait remonter le compteur au-dessus d'un id existant. Silencieux sur une autre forme. */
34640
+ function adopterId(id) {
34641
+ const m = /^i([0-9a-z]+)$/.exec(id ?? '');
34642
+ if (!m)
34643
+ return;
34644
+ const n = parseInt(m[1], 36);
34645
+ if (Number.isSafeInteger(n) && n > compteur)
34646
+ compteur = n;
34647
+ }
34648
+ /**
34649
+ * Empêche les prochains ids de VOLER l'identité d'items déjà présents.
34650
+ *
34651
+ * ⚠️ À APPELER À CHAQUE CHARGEMENT d'un plan venu d'ailleurs (base, fichier collé). Le
34652
+ * compteur est propre à la SESSION et repart de zéro : une maquette rechargée porte déjà
34653
+ * `i1`, `i2`… et la première pose redonnait `i1`. Deux items partagent alors une clé, et
34654
+ * tout ce qui vise par id se trompe de cible — `atelierTrouver` rend le premier homonyme,
34655
+ * `transformerArbre` édite les deux, et le `@for … track` d'Angular en FUSIONNE deux à
34656
+ * l'écran : un bloc disparaît du canevas sans qu'aucune erreur ne soit levée (NG0955 en
34657
+ * avertissement, c'est tout).
34658
+ *
34659
+ * C'EST LE MÊME DÉFAUT QUE `eb87d3f7` (26/08), qui l'avait corrigé dans le builder
34660
+ * frontless — « le compteur valait items.length et non le plus grand id existant ». Le
34661
+ * format a DEUX producteurs ; le correctif n'en avait couvert qu'un. Il vit désormais
34662
+ * dans la bibliothèque, là où les deux le partagent.
34663
+ */
34664
+ function atelierAdopterIds(layout) {
34665
+ const parcourir = (items) => {
34666
+ for (const it of items) {
34667
+ adopterId(it.id);
34668
+ parcourir(it.children ?? []);
34669
+ }
34670
+ };
34671
+ parcourir(layout?.items ?? []);
34672
+ }
34673
+ const borne = (v, min, max) => Math.max(min, Math.min(max, Math.trunc(Number.isFinite(v) ? v : min)));
34674
+ /**
34675
+ * La carte d'occupation du plan, ligne par ligne.
34676
+ *
34677
+ * On ne garde pas de largeur fixe : une ligne absente est une ligne vide. Un plan de
34678
+ * trois éléments ne fabrique pas 500 tableaux.
34679
+ */
34680
+ function occupation(items, sauf) {
34681
+ const lignes = new Map();
34682
+ for (const it of items) {
34683
+ if (sauf && it.id === sauf)
34684
+ continue;
34685
+ for (let y = it.y; y < it.y + it.h; y++) {
34686
+ let ligne = lignes.get(y);
34687
+ if (!ligne) {
34688
+ ligne = new Array(ATELIER_COLONNES).fill(false);
34689
+ lignes.set(y, ligne);
34690
+ }
34691
+ for (let x = it.x; x < it.x + it.w && x < ATELIER_COLONNES; x++)
34692
+ ligne[x] = true;
34693
+ }
34694
+ }
34695
+ return lignes;
34696
+ }
34697
+ /**
34698
+ * La première position libre pour un bloc de `w × h`, en balayant de haut en bas puis de
34699
+ * gauche à droite.
34700
+ *
34701
+ * C'est ce qui rend le CLIC utilisable comme geste principal : on choisit dans la palette
34702
+ * et ça se pose à un endroit sensé, sans viser. Le glisser sert ensuite à réarranger,
34703
+ * là où il est réellement meilleur.
34704
+ */
34705
+ function atelierPremierePlaceLibre(items, w, h) {
34706
+ const large = borne(w, 1, ATELIER_COLONNES);
34707
+ const haut = borne(h, ATELIER_BORNES.hMin, ATELIER_BORNES.hMax);
34708
+ const carte = occupation(items);
34709
+ for (let y = 0; y <= ATELIER_BORNES.yMax; y++) {
34710
+ for (let x = 0; x + large <= ATELIER_COLONNES; x++) {
34711
+ let libre = true;
34712
+ for (let dy = 0; dy < haut && libre; dy++) {
34713
+ const ligne = carte.get(y + dy);
34714
+ if (!ligne)
34715
+ continue; // ligne entièrement vide
34716
+ for (let dx = 0; dx < large; dx++) {
34717
+ if (ligne[x + dx]) {
34718
+ libre = false;
34719
+ break;
34720
+ }
34721
+ }
34722
+ }
34723
+ if (libre)
34724
+ return { x, y };
34725
+ }
34726
+ }
34727
+ // Plan saturé : on pose en bas plutôt que de refuser. Un outil de maquette ne dit pas non.
34728
+ return { x: 0, y: ATELIER_BORNES.yMax };
34729
+ }
34730
+ /**
34731
+ * Applique une transformation à CHAQUE item de l'arbre, enfants compris.
34732
+ *
34733
+ * Toutes les opérations qui touchent « un composant, où qu'il vive » passent par ici :
34734
+ * retirer, éditer une prop, redimensionner, réordonner. Écrire la descente une seule
34735
+ * fois est ce qui a permis de lever le plafond d'un niveau sans réécrire six fonctions —
34736
+ * et surtout sans en oublier une, ce qui aurait donné un modèle qui pose en profondeur
34737
+ * mais refuse d'y éditer.
34738
+ *
34739
+ * `fn` rend l'item transformé, ou `null` pour l'écarter (ses enfants partent avec lui).
34740
+ * La profondeur est passée parce qu'elle CHANGE les règles : au premier niveau le
34741
+ * placement est absolu, en dessous c'est un flux.
34742
+ */
34743
+ function transformerArbre(items, fn, profondeur = 0) {
34744
+ const sortie = [];
34745
+ for (const it of items) {
34746
+ const transforme = fn(it, profondeur);
34747
+ if (!transforme)
34748
+ continue;
34749
+ if (!transforme.children) {
34750
+ sortie.push(transforme);
34751
+ continue;
34752
+ }
34753
+ const enfants = transformerArbre(transforme.children, fn, profondeur + 1);
34754
+ const suite = { ...transforme };
34755
+ if (enfants.length)
34756
+ suite.children = enfants;
34757
+ else
34758
+ delete suite.children;
34759
+ sortie.push(suite);
34760
+ }
34761
+ return sortie;
34762
+ }
34763
+ /** Nombre TOTAL de composants, à toutes les profondeurs — c'est lui que borne `itemsMax`. */
34764
+ function atelierCompter(layout) {
34765
+ const compter = (items) => items.reduce((n, it) => n + 1 + compter(it.children ?? []), 0);
34766
+ return compter(layout.items);
34767
+ }
34768
+ /**
34769
+ * Retrouve un composant OÙ QU'IL VIVE, à n'importe quelle profondeur.
34770
+ *
34771
+ * `parentId` dit d'où il vient — c'est ce qui permet à l'inspecteur de remonter, d'un
34772
+ * cran à la fois. `profondeur` dit à quel étage il se trouve (0 = sur le plan) : la
34773
+ * pose s'en sert pour refuser AVANT d'agir, plutôt que de tronquer après.
34774
+ */
34775
+ function atelierTrouver(layout, id) {
34776
+ const chercher = (items, parentId, profondeur) => {
34777
+ for (const it of items) {
34778
+ if (it.id === id)
34779
+ return { item: it, parentId, profondeur };
34780
+ const dedans = chercher(it.children ?? [], it.id, profondeur + 1);
34781
+ if (dedans)
34782
+ return dedans;
34783
+ }
34784
+ return null;
34785
+ };
34786
+ return chercher(layout.items, null, 0);
34787
+ }
34788
+ /**
34789
+ * Pose un composant — sur le plan, ou DANS un conteneur (`dansId`), à n'importe quelle
34790
+ * profondeur jusqu'à `profondeurMax`.
34791
+ *
34792
+ * Dans un conteneur, l'enfant vit en FLUX : ajouté en fin, x/y/w/h normalisés (0/0/12/1),
34793
+ * l'ordre fait loi. Refus VISIBLE (id vide) si le conteneur n'existe pas, s'il est déjà
34794
+ * au dernier étage autorisé, ou si un plafond est atteint — jamais de troncature
34795
+ * silencieuse, même doctrine que le plafond global.
34796
+ */
34797
+ function atelierAjouter(layout, type, gabarit = {}, dansId) {
34798
+ const base = layout ?? atelierLayoutVide();
34799
+ if (atelierCompter(base) >= ATELIER_BORNES.itemsMax) {
34800
+ // Plafond de l'assainisseur : au-delà, le backend tronquerait EN SILENCE et
34801
+ // l'utilisateur perdrait des composants sans le savoir. On refuse visiblement.
34802
+ return { layout: base, id: '' };
34803
+ }
34804
+ if (dansId) {
34805
+ const cible = atelierTrouver(base, dansId);
34806
+ if (!cible)
34807
+ return { layout: base, id: '' };
34808
+ // UN HÔTE N'ACCEPTE PAS TOUT (SZU 27/08) : `cad-tabs` n'accueille que des `cad-tab`,
34809
+ // `cad-map` que ses pièces de carte. La palette le montre déjà en verrouillant, mais
34810
+ // elle est de l'affichage ; le modèle est la vérité, et rien d'autre ne protège les
34811
+ // chemins qui ne passent pas par elle (collage, import, appel programmatique). Un
34812
+ // `cad-spacer` glissé dans un `cad-tabs` partait en base et ne rendait jamais rien.
34813
+ if (!atelierAccepte(cible.item.type, type))
34814
+ return { layout: base, id: '' };
34815
+ // L'ENFANT naîtrait un cran plus bas que sa cible : c'est SA profondeur qu'on borne.
34816
+ if (cible.profondeur + 1 > ATELIER_BORNES.profondeurMax)
34817
+ return { layout: base, id: '' };
34818
+ if ((cible.item.children?.length ?? 0) >= ATELIER_BORNES.enfantsMax)
34819
+ return { layout: base, id: '' };
34820
+ const id = atelierNouvelId();
34821
+ // `w` d'un enfant = LARGEUR EN COLONNES du conteneur (grille de flux). Les deux
34822
+ // poseurs en passent une utile (palette 2/12, builder def.w×2) : l'honorer, sinon
34823
+ // l'aperçu et le modèle divergent. Sans gabarit, il naît pleine ligne — prévisible.
34824
+ const enfant = {
34825
+ id, type, x: 0, y: 0,
34826
+ w: borne(gabarit.w ?? ATELIER_COLONNES, 1, ATELIER_COLONNES),
34827
+ h: 1,
34828
+ };
34829
+ if (gabarit.props && Object.keys(gabarit.props).length)
34830
+ enfant.props = { ...gabarit.props };
34831
+ return {
34832
+ layout: {
34833
+ version: 1,
34834
+ items: transformerArbre(base.items, (it) => it.id === dansId ? { ...it, children: [...(it.children ?? []), enfant] } : it),
34835
+ },
34836
+ id,
34837
+ };
34838
+ }
34839
+ const w = borne(gabarit.w ?? 4, 1, ATELIER_COLONNES);
34840
+ const h = borne(gabarit.h ?? 2, ATELIER_BORNES.hMin, ATELIER_BORNES.hMax);
34841
+ const { x, y } = atelierPremierePlaceLibre(base.items, w, h);
34842
+ const id = atelierNouvelId();
34843
+ const item = { id, type, x, y, w, h };
34844
+ if (gabarit.props && Object.keys(gabarit.props).length)
34845
+ item.props = { ...gabarit.props };
34846
+ return { layout: { version: 1, items: [...base.items, item] }, id };
34847
+ }
34848
+ /**
34849
+ * Retire un composant, où qu'il vive et à quelque profondeur que ce soit.
34850
+ * Retirer un conteneur emporte toute sa descendance — jamais d'orphelin invisible.
34851
+ */
34852
+ function atelierRetirer(layout, id) {
34853
+ return {
34854
+ version: 1,
34855
+ items: transformerArbre(layout.items, (it) => (it.id === id ? null : it)),
34856
+ };
34857
+ }
34858
+ /** Change une propriété — où que vive le composant. `undefined` la supprime. */
34859
+ function atelierDefinirProp(layout, id, nom, valeur) {
34860
+ const poserProp = (it) => {
34861
+ const props = { ...(it.props ?? {}) };
34862
+ if (valeur === undefined)
34863
+ delete props[nom];
34864
+ else
34865
+ props[nom] = valeur;
34866
+ const suite = { ...it };
34867
+ if (Object.keys(props).length)
34868
+ suite.props = props;
34869
+ else
34870
+ delete suite.props;
34871
+ return suite;
34872
+ };
34873
+ return {
34874
+ version: 1,
34875
+ items: transformerArbre(layout.items, (it) => (it.id === id ? poserProp(it) : it)),
34876
+ };
34877
+ }
34878
+ /**
34879
+ * Déplace un ENFANT d'un cran dans son conteneur (le flux : l'ordre fait loi).
34880
+ * Même chemin pour la souris et le clavier, comme le reste du modèle.
34881
+ */
34882
+ function atelierReordonnerEnfant(layout, parentId, id, sens) {
34883
+ return {
34884
+ version: 1,
34885
+ items: transformerArbre(layout.items, (it) => {
34886
+ if (it.id !== parentId || !it.children)
34887
+ return it;
34888
+ const i = it.children.findIndex((e) => e.id === id);
34889
+ const j = i + sens;
34890
+ if (i < 0 || j < 0 || j >= it.children.length)
34891
+ return it;
34892
+ const enfants = [...it.children];
34893
+ [enfants[i], enfants[j]] = [enfants[j], enfants[i]];
34894
+ return { ...it, children: enfants };
34895
+ }),
34896
+ };
34897
+ }
34898
+ /**
34899
+ * Change la largeur, en gardant le bloc DANS la grille.
34900
+ *
34901
+ * On décale x quand la largeur déborde, plutôt que de rogner w : l'utilisateur a demandé
34902
+ * une largeur, pas une place. Rogner silencieusement donnerait un bloc plus étroit que
34903
+ * ce qu'il a réglé, sans rien dire.
34904
+ */
34905
+ function atelierRedimensionner(layout, id, w) {
34906
+ const large = borne(w, 1, ATELIER_COLONNES);
34907
+ return {
34908
+ version: 1,
34909
+ items: transformerArbre(layout.items, (it, profondeur) => {
34910
+ if (it.id !== id)
34911
+ return it;
34912
+ // Sur le PLAN, la largeur peut déborder de la grille : on décale x plutôt que de
34913
+ // rogner w. Un ENFANT, lui, se redimensionne aussi (SZU 24/08 : « on ne peut plus
34914
+ // les redimensionner, très limitatif ») mais son `w` est un span de la grille de
34915
+ // flux de son conteneur, x reste 0 : pas de placement absolu à l'intérieur.
34916
+ return profondeur === 0
34917
+ ? { ...it, w: large, x: Math.min(it.x, ATELIER_COLONNES - large) }
34918
+ : { ...it, w: large };
34919
+ }),
34920
+ };
34921
+ }
34922
+ /**
34923
+ * Déplace un bloc d'un cran dans l'ordre de lecture (haut/bas), puis recompacte.
34924
+ *
34925
+ * C'est le CHEMIN CLAVIER, et il passe par les mêmes fonctions que le glisser — la règle
34926
+ * déjà tenue par `cad-taskboard` et `cad-calendar`. Deux chemins séparés divergent
34927
+ * toujours, et c'est celui du clavier qui pourrit, parce que personne ne le regarde.
34928
+ */
34929
+ function atelierDeplacer(layout, id, sens) {
34930
+ const ordonne = [...layout.items].sort((a, b) => a.y - b.y || a.x - b.x);
34931
+ const i = ordonne.findIndex((it) => it.id === id);
34932
+ if (i < 0)
34933
+ return layout;
34934
+ const j = i + sens;
34935
+ if (j < 0 || j >= ordonne.length)
34936
+ return layout;
34937
+ [ordonne[i], ordonne[j]] = [ordonne[j], ordonne[i]];
34938
+ // `atelierPoser` et NON `atelierRecompacter` : ce dernier re-trie par (y, x), ce qui
34939
+ // annulerait l'échange qu'on vient de faire — les `y` n'ont pas encore bougé. Défaut
34940
+ // trouvé par le test du clavier, qui rendait le déplacement parfaitement inopérant.
34941
+ return atelierPoser(ordonne);
34942
+ }
34943
+ /**
34944
+ * Repose tous les blocs dans l'ordre de lecture, en supprimant les trous verticaux.
34945
+ *
34946
+ * Le plan garde des positions ABSOLUES (c'est le contrat de frontless), mais une maquette
34947
+ * criblée de lignes vides est illisible. On recompacte donc à chaque réarrangement, en
34948
+ * respectant l'ordre voulu et les largeurs.
34949
+ */
34950
+ function atelierRecompacter(layout) {
34951
+ return atelierPoser([...layout.items].sort((a, b) => a.y - b.y || a.x - b.x));
34952
+ }
34953
+ /**
34954
+ * Repose les blocs DANS L'ORDRE DU TABLEAU REÇU, sans le retrier.
34955
+ *
34956
+ * La distinction avec `atelierRecompacter` n'est pas cosmétique : après un déplacement au
34957
+ * clavier, l'ordre voulu est celui du tableau, et les `y` portent encore les anciennes
34958
+ * positions. Retrier à ce moment-là annule l'échange — le déplacement devenait
34959
+ * silencieusement inopérant, et c'est le test du clavier qui l'a montré.
34960
+ */
34961
+ function atelierPoser(items) {
34962
+ const poses = [];
34963
+ for (const it of items) {
34964
+ const { x, y } = atelierPremierePlaceLibre(poses, it.w, it.h);
34965
+ poses.push({ ...it, x, y });
34966
+ }
34967
+ return { version: 1, items: poses };
34968
+ }
34969
+ /**
34970
+ * Ramène un plan venu de l'extérieur dans les clous — un lien partagé, un fichier collé.
34971
+ *
34972
+ * Écarte ce qui est irrécupérable plutôt que de le réparer au jugé : un bloc dont on a
34973
+ * deviné le type serait pire qu'un bloc absent. Même doctrine que l'assainisseur backend,
34974
+ * dont ce n'est PAS un doublon : celui-ci protège l'affichage, celui-là protège la base.
34975
+ */
34976
+ function atelierNormaliser(brut) {
34977
+ const entree = brut;
34978
+ if (!entree || !Array.isArray(entree.items))
34979
+ return atelierLayoutVide();
34980
+ let total = 0;
34981
+ /**
34982
+ * Un item ramené dans les clous, ou `null` s'il est irrécupérable.
34983
+ *
34984
+ * La PROFONDEUR change les règles : à 0 le placement est absolu, en dessous il est
34985
+ * forcé au FLUX (x/y/h normalisés, seul `w` — le span — survit). Au-delà de
34986
+ * `profondeurMax` la descendance est écartée, pas réparée : borner ici n'est pas de
34987
+ * l'ergonomie, c'est ce qui empêche un JSON hostile de faire sauter la pile.
34988
+ */
34989
+ const normaliserItem = (it, profondeur) => {
34990
+ const enfant = profondeur > 0;
34991
+ if (!it || typeof it !== 'object')
34992
+ return null;
34993
+ const brutItem = it;
34994
+ const type = String(brutItem.type ?? '');
34995
+ if (!/^(?:app:)?[a-z][a-z0-9-]{0,40}$/.test(type))
34996
+ return null;
34997
+ // `w` d'un enfant = span de la grille de flux, GARDÉ (borné 1–12) ; x/y/h forcés.
34998
+ const w = borne(brutItem.w, 1, ATELIER_COLONNES);
34999
+ const x = enfant ? 0 : borne(brutItem.x, 0, ATELIER_COLONNES - w);
35000
+ const h = enfant ? 1 : borne(brutItem.h, ATELIER_BORNES.hMin, ATELIER_BORNES.hMax);
35001
+ const y = enfant ? 0 : borne(brutItem.y, 0, ATELIER_BORNES.yMax);
35002
+ adopterId(brutItem.id); // avant de fabriquer quoi que ce soit : pas d'homonyme
35003
+ const propre = { id: brutItem.id || atelierNouvelId(), type, x, y, w, h };
35004
+ const props = brutItem.props;
35005
+ if (props && typeof props === 'object' && !Array.isArray(props)) {
35006
+ const gardees = {};
35007
+ for (const [k, v] of Object.entries(props).slice(0, ATELIER_BORNES.propsMax)) {
35008
+ if (!/^[A-Za-z_][A-Za-z0-9_]{0,30}$/.test(k))
35009
+ continue;
35010
+ if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')
35011
+ gardees[k] = v;
35012
+ else if (Array.isArray(v))
35013
+ gardees[k] = v.slice(0, 100).map((s) => String(s));
35014
+ }
35015
+ if (Object.keys(gardees).length)
35016
+ propre.props = gardees;
35017
+ }
35018
+ if (brutItem.hideMobile === true)
35019
+ propre.hideMobile = true;
35020
+ if (profondeur < ATELIER_BORNES.profondeurMax && Array.isArray(brutItem.children)) {
35021
+ const enfants = [];
35022
+ for (const e of brutItem.children.slice(0, ATELIER_BORNES.enfantsMax)) {
35023
+ if (total >= ATELIER_BORNES.itemsMax)
35024
+ break;
35025
+ total += 1; // compté AVANT sa descendance, pour que le plafond reste global
35026
+ const propreEnfant = normaliserItem(e, profondeur + 1);
35027
+ if (propreEnfant)
35028
+ enfants.push(propreEnfant);
35029
+ else
35030
+ total -= 1;
35031
+ }
35032
+ if (enfants.length)
35033
+ propre.children = enfants;
35034
+ }
35035
+ return propre;
35036
+ };
35037
+ const items = [];
35038
+ for (const it of entree.items) {
35039
+ if (total >= ATELIER_BORNES.itemsMax)
35040
+ break;
35041
+ total += 1; // l'item lui-même, compté AVANT ses enfants pour que le plafond soit global
35042
+ const propre = normaliserItem(it, 0);
35043
+ if (propre)
35044
+ items.push(propre);
35045
+ else
35046
+ total -= 1;
35047
+ }
35048
+ return { version: 1, items };
35049
+ }
35050
+
35051
+ /** Le libellé d'une pièce, pour le chemin : son `label`/`header`, sinon son type. */
35052
+ function libelle(props, type) {
35053
+ for (const k of ['label', 'header', 'title']) {
35054
+ const v = props?.[k];
35055
+ if (typeof v === 'string' && v.trim())
35056
+ return v.trim();
35057
+ }
35058
+ return type;
35059
+ }
35060
+ /**
35061
+ * Résout la destination d'un clic de palette.
35062
+ *
35063
+ * `selectionId` est le composant actuellement sélectionné — un conteneur accueille, un
35064
+ * composant simple fait poser à côté de lui, chez son parent.
35065
+ */
35066
+ function atelierCibleDePose(layout, selectionId, cle) {
35067
+ const auPlan = {
35068
+ dansId: null,
35069
+ pieceACreer: null,
35070
+ chemin: [],
35071
+ possible: atelierAccepte(null, cle),
35072
+ };
35073
+ if (!selectionId)
35074
+ return auPlan;
35075
+ const sel = atelierTrouver(layout, selectionId);
35076
+ if (!sel)
35077
+ return auPlan;
35078
+ // Un CONTENEUR sélectionné accueille ; un composant simple fait poser chez son parent,
35079
+ // donc à côté de lui — le geste que l'on attend quand on vient d'en poser un.
35080
+ const estConteneur = !!atelierComposant(sel.item.type)?.conteneur;
35081
+ const hote = estConteneur ? sel.item : sel.parentId ? atelierTrouver(layout, sel.parentId)?.item : null;
35082
+ if (!hote)
35083
+ return auPlan;
35084
+ if (atelierAccepte(hote.type, cle)) {
35085
+ return { dansId: hote.id, pieceACreer: null, chemin: [hote.type], possible: true };
35086
+ }
35087
+ // L'hôte refuse. Peut-on DESCENDRE dans sa pièce ? Seulement s'il n'a qu'un type de
35088
+ // pièce et que cette pièce accueille elle-même quelque chose.
35089
+ const pieces = atelierPiecesDe(hote.type);
35090
+ const piece = pieces.length === 1 ? pieces[0] : null;
35091
+ if (!piece || !atelierComposant(piece)?.conteneur || !atelierAccepte(piece, cle)) {
35092
+ return { dansId: hote.id, pieceACreer: null, chemin: [hote.type], possible: false };
35093
+ }
35094
+ const posees = (hote.children ?? []).filter((e) => e.type === piece);
35095
+ if (!posees.length) {
35096
+ // Aucune pièce : on en crée une au passage. Elle sera VISIBLE (un onglet apparaît),
35097
+ // ce qui la distingue d'une structure fabriquée en douce.
35098
+ return { dansId: hote.id, pieceACreer: piece, chemin: [hote.type, piece], possible: true };
35099
+ }
35100
+ // La pièce COURANTE, c'est-à-dire celle qu'on voit : en mode design le canevas est
35101
+ // inerte (les onglets ne se cliquent pas), donc l'affichage suit `activeIndex`, que
35102
+ // l'inspecteur expose. Sans prop exploitable, la première — comme à l'écran.
35103
+ const brut = Number(hote.props?.['activeIndex']);
35104
+ const i = Number.isInteger(brut) && brut >= 0 && brut < posees.length ? brut : 0;
35105
+ const courante = posees[i];
35106
+ return {
35107
+ dansId: courante.id,
35108
+ pieceACreer: null,
35109
+ chemin: [hote.type, libelle(courante.props, courante.type)],
35110
+ possible: true,
35111
+ };
35112
+ }
35113
+ /**
35114
+ * Les valeurs de DÉPART d'un composant qu'on pose : le texte projeté (`contenu=`) et
35115
+ * les entrées (`pose-*`) du catalogue.
35116
+ *
35117
+ * Extrait ici parce que DEUX poseurs doivent l'appliquer, et à DEUX endroits chacun :
35118
+ * au composant cliqué, et à la pièce d'hôte que le routage crée au passage. Cette
35119
+ * seconde application manquait — un onglet né du routage arrivait sans libellé, donc
35120
+ * invisible dans la barre (mesuré au clic dans le builder le 27/08). Un composant posé
35121
+ * sans ses valeurs de départ est une coquille, c'était déjà la leçon du 24/08.
35122
+ */
35123
+ function atelierProposDePose(cle) {
35124
+ const c = atelierComposant(cle);
35125
+ if (!c)
35126
+ return {};
35127
+ return {
35128
+ ...c.proposDeDepart,
35129
+ ...(c.contenu ? { __contenu: c.contenu } : {}),
35130
+ };
35131
+ }
35132
+
34603
35133
  /**
34604
35134
  * Le code Angular d'une maquette — la sortie qui distingue cet atelier d'un Figma.
34605
35135
  *
@@ -34668,9 +35198,15 @@ function ouvrante(item) {
34668
35198
  }
34669
35199
  return { balise: c.balise, attribut: c.attribut ? ` ${c.attribut}` : '' };
34670
35200
  }
34671
- /** Tous les items, enfants compris — les balayages (imports, manques) sont récursifs. */
35201
+ /**
35202
+ * Tous les items, à TOUTES les profondeurs — les balayages (imports, manques) doivent
35203
+ * descendre jusqu'au bout. Une descente d'un seul cran oubliait les composants posés
35204
+ * dans un onglet : le code généré les écrivait sans les importer, donc il ne compilait
35205
+ * pas chez qui le collait.
35206
+ */
34672
35207
  function tousLesItems(layout) {
34673
- return layout.items.flatMap((it) => [it, ...(it.children ?? [])]);
35208
+ const descendre = (items) => items.flatMap((it) => [it, ...descendre(it.children ?? [])]);
35209
+ return descendre(layout.items);
34674
35210
  }
34675
35211
  /** Les entrées requises qu'aucune valeur ne remplit — nommées, jamais inventées. */
34676
35212
  function atelierManques(layout) {
@@ -34817,5 +35353,5 @@ function atelierCode(layout, titre = 'Maquette') {
34817
35353
  * Generated bundle index. Do not edit.
34818
35354
  */
34819
35355
 
34820
- export { ATELIER_BORNES, ATELIER_COLONNES, ATELIER_STYLES_PLAN, BanGeocoder, CAD_BREAKPOINTS, CAD_CHART_PALETTE, CAD_COLOR_PRESETS, CAD_DEFAULT_BASEMAPS, CAD_DEFAULT_PRIORITY_BREAKPOINTS, CAD_DIALOG_DATA, CAD_FILTER_OPERATORS, CAD_GEOCODER, CAD_ICONS, CAD_ICONS_CEREMA, CAD_ICONS_CORE, CAD_ICONS_DATA, CAD_ICONS_FILES, CAD_ICONS_GEO, CAD_ICONS_SUBSET, CAD_ICONS_TOKEN, CAD_ICONS_UI, CAD_ICON_ALIASES, CAD_ICON_CATEGORIES, CAD_MAPLIBRE_CSS_URL, CAD_MAP_CHILD, CAD_MAP_DEFAULTS, CAD_MAP_GLOBAL_STYLES, CAD_MAP_GLYPHS, CAD_MAP_PALETTE, CAD_MASK_PRESETS, CAD_MOBILE_BREAKPOINT, CAD_RECURRENCE_INTERNE, CAD_SELECT_POSITIONS, CAD_SELECT_TRIGGER_STYLES, CAD_SHEET_DATA, CAD_SHELL, CAD_TOUCH_DENSITY, CadAccordionComponent, CadAccordionPanelComponent, CadAppShellComponent, CadAvatarComponent, CadAvatarGroupComponent, CadBadgeComponent, CadBadgeDirective, CadBlockDirective, CadBlockOverlayComponent, CadBlockUiComponent, CadBottomNavComponent, CadBreadcrumbComponent, CadBreadcrumbItemDirective, CadBreakpointService, CadButtonComponent, CadButtonGroupComponent, CadCalendarComponent, CadCaptionDirective, CadCardComponent, CadCardDirective, CadCardHeaderDirective, CadCarouselComponent, CadCarouselItemDirective, CadCascadeSelectComponent, CadCellTemplateDirective, CadCenterComponent, CadChartComponent, CadCheckboxComponent, CadChipComponent, CadClusterComponent, CadColDirective, CadColorpickerComponent, CadColumnComponent, CadComboComponent, CadConfirmContentComponent, CadConfirmDialogComponent, CadConfirmService, CadContainerComponent, CadDataviewComponent, CadDataviewGridDirective, CadDataviewListDirective, CadDatepickerComponent, CadDialogComponent, CadDialogContainerComponent, CadDialogFooterDirective, CadDialogHeaderDirective, CadDialogRef, CadDialogService, CadDividerComponent, CadDrawerComponent, CadEditFocusDirective, CadEditorTemplateDirective, CadEmptyDirective, CadFieldComponent, CadFieldsetComponent, CadFieldsetLegendDirective, CadFilterChipComponent, CadFilterTemplateDirective, CadGanttComponent, CadGeocodingService, CadGridComponent, CadGroupFooterDirective, CadGroupHeaderDirective, CadHeaderTemplateDirective, CadIconComponent, CadIconRegistry, CadIfDirective, CadImageComponent, CadInplaceComponent, CadInplaceContentDirective, CadInplaceDisplayDirective, CadInputDirective, CadJsonEditorComponent, CadKeyFilterDirective, CadListEmptyDirective, CadListFooterDirective, CadListHeaderDirective, CadListItemDirective, CadListboxComponent, CadMapBasemapRegistry, CadMapBasemapsComponent, CadMapCardComponent, CadMapChromeComponent, CadMapComponent, CadMapDrawEngine, CadMapLayerComponent, CadMapLayerGroupComponent, CadMapLayerToggleComponent, CadMapLegendComponent, CadMapMenuComponent, CadMapPanelComponent, CadMapPanels, CadMapRailComponent, CadMapRailItemComponent, CadMapSearchComponent, CadMapSectionComponent, CadMaskDirective, CadMenuComponent, CadMenubarComponent, CadMessageComponent, CadMeterComponent, CadMeterLabelDirective, CadMultiselectComponent, CadNavDrawerComponent, CadNavDrawerFooterDirective, CadNavDrawerHeaderDirective, CadNumberComponent, CadOptionListComponent, CadOrderlistComponent, CadOrgNodeDirective, CadOrgchartComponent, CadOverlayBase, CadPaginatorComponent, CadPanelComponent, CadPanelContentDirective, CadPanelHeaderDirective, CadPanelHeadingDirective, CadPasswordComponent, CadPicklistComponent, CadPopoverComponent, CadProgressComponent, CadRadioComponent, CadRadioGroupComponent, CadRatingComponent, CadRowActionsDirective, CadRowCountDirective, CadRowExpansionDirective, CadSchedulerComponent, CadScrollTopComponent, CadSegmentedComponent, CadSelectBase, CadSelectComponent, CadSelectEmptyDirective, CadSelectFooterDirective, CadSelectHeaderDirective, CadSelectItemDirective, CadSelectSelectedDirective, CadSheetContainerComponent, CadSheetRef, CadSheetService, CadSidebarComponent, CadSidebarPanelDirective, CadSkeletonComponent, CadSliderComponent, CadSpacerComponent, CadSpeedDialComponent, CadSpinnerComponent, CadSplitButtonComponent, CadSplitComponent, CadSplitterComponent, CadSplitterPanelComponent, CadStackComponent, CadStepComponent, CadStepperComponent, CadSwitchComponent, CadTabComponent, CadTabContentDirective, CadTabLabelDirective, CadTableBase, CadTableComponent, CadTableEdit, CadTableEditUi, CadTableFooterDirective, CadTablePanelComponent, CadTableResponsive, CadTableSelection, CadTableSticky, CadTableVirtual, CadTabsComponent, CadTagComponent, CadTaskCardDirective, CadTaskRowDirective, CadTaskboardComponent, CadTimeAxis, CadTimelineComponent, CadTimelineContentDirective, CadTimelineMarkerDirective, CadTimelineOppositeDirective, CadToastComponent, CadToastItemDirective, CadToastService, CadToggleButtonComponent, CadTooltipComponent, CadTooltipDirective, CadTreeAdapter, CadTreeComponent, CadTreeNodeDirective, CadTreeselectComponent, CadUploadComponent, CadUploadContentDirective, CadUploadEmptyDirective, CadUploadItemDirective, CadValueAccessor, CadWorkCalendar, MAPLIBRE_MIN_CSS, REGISTRE, addDays, addMonths, addYears, atelierAjouter, atelierBalise, atelierChercher, atelierCleDeSymbole, atelierCode, atelierComposant, atelierCompter, atelierControle, atelierDefinirProp, atelierDeplacer, atelierGabarit, atelierGroupes, atelierImports, atelierLayoutVide, atelierManques, atelierNomDeClasse, atelierNormaliser, atelierNouvelId, atelierPalette, atelierPoser, atelierPremierePlaceLibre, atelierRecompacter, atelierRedimensionner, atelierReinitialiserIds, atelierReordonnerEnfant, atelierRetirer, atelierSelecteurDeClasse, atelierTrouver, cadAggregate, cadApplyColumnOrder, cadApplyMask, cadArea, cadAutosizeWidth, cadBasemapThumbnail, cadBoundsOf, cadCategories, cadCentroid, cadClipFenetre, cadColorExpr, cadCompare, cadCompareRank, cadCurrentOperator, cadDebutSemaine, cadDefaultMatchMode, cadDistance, cadDownloadCsv, cadDurationToMinutes, cadEstBandeau, cadExpandOccurrences, cadFilterData, cadFilterKindOf, cadFilterOperators, cadFilterRows, cadFindLinkCycle, cadFmtArea, cadFmtLength, cadFormatCell, cadFormatColor, cadFormatDuration, cadFormatSize, cadFrenchHolidays, cadGanttRows, cadGap, cadGeoJSONStats, cadGeometryKinds, cadHeaderMenuItems, cadHighlightJson, cadHslToRgb, cadHsvToRgb, cadId, cadIsGroupKey, cadIsRank, cadJsonErrorPos, cadLegendFromStyle, cadLength, cadMapEstVivante, cadMapLayerOf, cadMapLayersOf, cadMatch, cadMinuit, cadMonthGrid, cadMoveColumn, cadMoveItems, cadNormalize, cadNormalizeOptions, cadNormalizePlan, cadNumberExpr, cadNumeroJour, cadPackDay, cadPackLanes, cadPadBounds, cadParseColor, cadParseDuration, cadParseRRule, cadParseResponsive, cadPlanHeader, cadPlanTicks, cadPointeFleche, cadProjeterClavier, cadProjeterCreation, cadProjeterEdition, cadProjeterMois, cadPropsTable, cadRankBetween, cadRankFirst, cadRankLast, cadRankSequence, cadResolve, cadResolveResponsive, cadResponsive, cadRgbToHsl, cadRgbToHsv, cadRingArea, cadRouterLien, cadRowKey, cadRowPredicate, cadSameValue, cadScaleNext, cadScaleStart, cadSchedulerPartition, cadSortData, cadStartResize, cadStyleToExpressions, cadUnmask, cadUnscheduled, cadValidateGeoJSON, cadValueForOperator, cadWeekBands, cadWriteField, classeDe, compareDay, daysInMonth, endOfMonth, ensureMaplibreCss, formatDate, isBetweenDay, isSameDay, isSameMonth, isValidDate, isoOf, isoWeek, loadChartJs, loadMaplibre, monthNames, pad2, parseIso, parseWithFormat, provideCadIcons, startOfDay, startOfMonth, toDate, weekdayNames };
35356
+ export { ATELIER_BORNES, ATELIER_COLONNES, ATELIER_STYLES_PLAN, BanGeocoder, CAD_BREAKPOINTS, CAD_CHART_PALETTE, CAD_COLOR_PRESETS, CAD_DEFAULT_BASEMAPS, CAD_DEFAULT_PRIORITY_BREAKPOINTS, CAD_DIALOG_DATA, CAD_FILTER_OPERATORS, CAD_GEOCODER, CAD_ICONS, CAD_ICONS_CEREMA, CAD_ICONS_CORE, CAD_ICONS_DATA, CAD_ICONS_FILES, CAD_ICONS_GEO, CAD_ICONS_SUBSET, CAD_ICONS_TOKEN, CAD_ICONS_UI, CAD_ICON_ALIASES, CAD_ICON_CATEGORIES, CAD_MAPLIBRE_CSS_URL, CAD_MAP_CHILD, CAD_MAP_DEFAULTS, CAD_MAP_GLOBAL_STYLES, CAD_MAP_GLYPHS, CAD_MAP_PALETTE, CAD_MASK_PRESETS, CAD_MOBILE_BREAKPOINT, CAD_RECURRENCE_INTERNE, CAD_SELECT_POSITIONS, CAD_SELECT_TRIGGER_STYLES, CAD_SHEET_DATA, CAD_SHELL, CAD_TOUCH_DENSITY, CadAccordionComponent, CadAccordionPanelComponent, CadAppShellComponent, CadAvatarComponent, CadAvatarGroupComponent, CadBadgeComponent, CadBadgeDirective, CadBlockDirective, CadBlockOverlayComponent, CadBlockUiComponent, CadBottomNavComponent, CadBreadcrumbComponent, CadBreadcrumbItemDirective, CadBreakpointService, CadButtonComponent, CadButtonGroupComponent, CadCalendarComponent, CadCaptionDirective, CadCardComponent, CadCardDirective, CadCardHeaderDirective, CadCarouselComponent, CadCarouselItemDirective, CadCascadeSelectComponent, CadCellTemplateDirective, CadCenterComponent, CadChartComponent, CadCheckboxComponent, CadChipComponent, CadClusterComponent, CadColDirective, CadColorpickerComponent, CadColumnComponent, CadComboComponent, CadConfirmContentComponent, CadConfirmDialogComponent, CadConfirmService, CadContainerComponent, CadDataviewComponent, CadDataviewGridDirective, CadDataviewListDirective, CadDatepickerComponent, CadDialogComponent, CadDialogContainerComponent, CadDialogFooterDirective, CadDialogHeaderDirective, CadDialogRef, CadDialogService, CadDividerComponent, CadDrawerComponent, CadEditFocusDirective, CadEditorTemplateDirective, CadEmptyDirective, CadFieldComponent, CadFieldsetComponent, CadFieldsetLegendDirective, CadFilterChipComponent, CadFilterTemplateDirective, CadGanttComponent, CadGeocodingService, CadGridComponent, CadGroupFooterDirective, CadGroupHeaderDirective, CadHeaderTemplateDirective, CadIconComponent, CadIconRegistry, CadIfDirective, CadImageComponent, CadInplaceComponent, CadInplaceContentDirective, CadInplaceDisplayDirective, CadInputDirective, CadJsonEditorComponent, CadKeyFilterDirective, CadListEmptyDirective, CadListFooterDirective, CadListHeaderDirective, CadListItemDirective, CadListboxComponent, CadMapBasemapRegistry, CadMapBasemapsComponent, CadMapCardComponent, CadMapChromeComponent, CadMapComponent, CadMapDrawEngine, CadMapLayerComponent, CadMapLayerGroupComponent, CadMapLayerToggleComponent, CadMapLegendComponent, CadMapMenuComponent, CadMapPanelComponent, CadMapPanels, CadMapRailComponent, CadMapRailItemComponent, CadMapSearchComponent, CadMapSectionComponent, CadMaskDirective, CadMenuComponent, CadMenubarComponent, CadMessageComponent, CadMeterComponent, CadMeterLabelDirective, CadMultiselectComponent, CadNavDrawerComponent, CadNavDrawerFooterDirective, CadNavDrawerHeaderDirective, CadNumberComponent, CadOptionListComponent, CadOrderlistComponent, CadOrgNodeDirective, CadOrgchartComponent, CadOverlayBase, CadPaginatorComponent, CadPanelComponent, CadPanelContentDirective, CadPanelHeaderDirective, CadPanelHeadingDirective, CadPasswordComponent, CadPicklistComponent, CadPopoverComponent, CadProgressComponent, CadRadioComponent, CadRadioGroupComponent, CadRatingComponent, CadRowActionsDirective, CadRowCountDirective, CadRowExpansionDirective, CadSchedulerComponent, CadScrollTopComponent, CadSegmentedComponent, CadSelectBase, CadSelectComponent, CadSelectEmptyDirective, CadSelectFooterDirective, CadSelectHeaderDirective, CadSelectItemDirective, CadSelectSelectedDirective, CadSheetContainerComponent, CadSheetRef, CadSheetService, CadSidebarComponent, CadSidebarPanelDirective, CadSkeletonComponent, CadSliderComponent, CadSpacerComponent, CadSpeedDialComponent, CadSpinnerComponent, CadSplitButtonComponent, CadSplitComponent, CadSplitterComponent, CadSplitterPanelComponent, CadStackComponent, CadStepComponent, CadStepperComponent, CadSwitchComponent, CadTabComponent, CadTabContentDirective, CadTabLabelDirective, CadTableBase, CadTableComponent, CadTableEdit, CadTableEditUi, CadTableFooterDirective, CadTablePanelComponent, CadTableResponsive, CadTableSelection, CadTableSticky, CadTableVirtual, CadTabsComponent, CadTagComponent, CadTaskCardDirective, CadTaskRowDirective, CadTaskboardComponent, CadTimeAxis, CadTimelineComponent, CadTimelineContentDirective, CadTimelineMarkerDirective, CadTimelineOppositeDirective, CadToastComponent, CadToastItemDirective, CadToastService, CadToggleButtonComponent, CadTooltipComponent, CadTooltipDirective, CadTreeAdapter, CadTreeComponent, CadTreeNodeDirective, CadTreeselectComponent, CadUploadComponent, CadUploadContentDirective, CadUploadEmptyDirective, CadUploadItemDirective, CadValueAccessor, CadWorkCalendar, MAPLIBRE_MIN_CSS, REGISTRE, addDays, addMonths, addYears, atelierAccepte, atelierAdopterIds, atelierAjouter, atelierBalise, atelierChercher, atelierCibleDePose, atelierCleDeSymbole, atelierCode, atelierComposant, atelierCompter, atelierControle, atelierDefinirProp, atelierDeplacer, atelierGabarit, atelierGroupes, atelierImports, atelierLayoutVide, atelierManques, atelierNomDeClasse, atelierNormaliser, atelierNouvelId, atelierPalette, atelierPiecesDe, atelierPoser, atelierPremierePlaceLibre, atelierProposDePose, atelierRecompacter, atelierRedimensionner, atelierReinitialiserIds, atelierReordonnerEnfant, atelierRetirer, atelierSelecteurDeClasse, atelierTrouver, cadAggregate, cadApplyColumnOrder, cadApplyMask, cadArea, cadAutosizeWidth, cadBasemapThumbnail, cadBoundsOf, cadCategories, cadCentroid, cadClipFenetre, cadColorExpr, cadCompare, cadCompareRank, cadCurrentOperator, cadDebutSemaine, cadDefaultMatchMode, cadDistance, cadDownloadCsv, cadDurationToMinutes, cadEstBandeau, cadExpandOccurrences, cadFilterData, cadFilterKindOf, cadFilterOperators, cadFilterRows, cadFindLinkCycle, cadFmtArea, cadFmtLength, cadFormatCell, cadFormatColor, cadFormatDuration, cadFormatSize, cadFrenchHolidays, cadGanttRows, cadGap, cadGeoJSONStats, cadGeometryKinds, cadHeaderMenuItems, cadHighlightJson, cadHslToRgb, cadHsvToRgb, cadId, cadIsGroupKey, cadIsRank, cadJsonErrorPos, cadLegendFromStyle, cadLength, cadMapEstVivante, cadMapLayerOf, cadMapLayersOf, cadMatch, cadMinuit, cadMonthGrid, cadMoveColumn, cadMoveItems, cadNormalize, cadNormalizeOptions, cadNormalizePlan, cadNumberExpr, cadNumeroJour, cadPackDay, cadPackLanes, cadPadBounds, cadParseColor, cadParseDuration, cadParseRRule, cadParseResponsive, cadPlanHeader, cadPlanTicks, cadPointeFleche, cadProjeterClavier, cadProjeterCreation, cadProjeterEdition, cadProjeterMois, cadPropsTable, cadRankBetween, cadRankFirst, cadRankLast, cadRankSequence, cadResolve, cadResolveResponsive, cadResponsive, cadRgbToHsl, cadRgbToHsv, cadRingArea, cadRouterLien, cadRowKey, cadRowPredicate, cadSameValue, cadScaleNext, cadScaleStart, cadSchedulerPartition, cadSortData, cadStartResize, cadStyleToExpressions, cadUnmask, cadUnscheduled, cadValidateGeoJSON, cadValueForOperator, cadWeekBands, cadWriteField, classeDe, compareDay, daysInMonth, endOfMonth, ensureMaplibreCss, estHoteAPieces, formatDate, isBetweenDay, isSameDay, isSameMonth, isValidDate, isoOf, isoWeek, loadChartJs, loadMaplibre, monthNames, pad2, parseIso, parseWithFormat, provideCadIcons, startOfDay, startOfMonth, toDate, weekdayNames };
34821
35357
  //# sourceMappingURL=cadriciel-ui.mjs.map