@cadriciel/ui 0.4.1 → 0.6.0

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.
@@ -4090,6 +4090,19 @@ class CadDialogContainerComponent {
4090
4090
  const vertical = c.drawerPosition === 'top' || c.drawerPosition === 'bottom';
4091
4091
  return vertical ? '100%' : (CadDialogContainerComponent.DRAWER_SIZES[s] ?? s);
4092
4092
  }
4093
+ // EN PLEIN ÉCRAN MOBILE, LA TAILLE DEMANDÉE N'A PLUS DE SENS : c'est l'écran qui décide.
4094
+ // Sans ça, `size="md"` continuait de valoir 480px et, plafonné par le `max-width: 92%`
4095
+ // ci-dessous, rendait un dialogue de 352px avec des marges — alors qu'un `size="sm"`
4096
+ // (360px) tenait, lui, dans les 390px et paraissait plein écran. Deux dialogues déclarés
4097
+ // dans le même mode s'affichaient donc différemment (signalé par SZU le 22/08/2026).
4098
+ // On corrige à la SOURCE : le CSS tentait de rattraper ça en aval avec des `!important`,
4099
+ // qui ne peuvent pas gagner contre une largeur en ligne dans la chaîne de dimensionnement.
4100
+ // « 100% » ne suffit PAS ici : le panneau CDK qui nous porte a une largeur AUTOMATIQUE,
4101
+ // donc un pourcentage se résout circulairement sur la largeur du contenu — mesuré à 352px
4102
+ // au lieu de 390. On vise donc le viewport. Pas de risque de gouttière d'ascenseur : ce
4103
+ // mode n'existe que sous le seuil mobile, où il n'y a pas d'ascenseur classique.
4104
+ if (this.mobileMode() === 'full')
4105
+ return '100dvw';
4093
4106
  return c.width ?? CadDialogContainerComponent.SIZES[c.size ?? 'md'] ?? '480px';
4094
4107
  }, ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
4095
4108
  height = computed(() => {
@@ -4103,7 +4116,10 @@ class CadDialogContainerComponent {
4103
4116
  }
4104
4117
  return c.height ?? null;
4105
4118
  }, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
4106
- maxWidth = computed(() => this.config().kind === 'drawer' ? '100%' : (this.config().maxWidth ?? (this.config().size === 'full' ? '100%' : '92%')), ...(ngDevMode ? [{ debugName: "maxWidth" }] : /* istanbul ignore next */ []));
4119
+ /** Le plafond à 92% laisse respirer un dialogue centré ; en plein écran mobile il n'a plus lieu d'être. */
4120
+ maxWidth = computed(() => this.config().kind === 'drawer' || this.mobileMode() === 'full'
4121
+ ? '100%'
4122
+ : (this.config().maxWidth ?? (this.config().size === 'full' ? '100%' : '92%')), ...(ngDevMode ? [{ debugName: "maxWidth" }] : /* istanbul ignore next */ []));
4107
4123
  drag = signal(null, ...(ngDevMode ? [{ debugName: "drag" }] : /* istanbul ignore next */ []));
4108
4124
  dragTransform = computed(() => { const d = this.drag(); return d ? `translate(${d.x}px, ${d.y}px)` : null; }, ...(ngDevMode ? [{ debugName: "dragTransform" }] : /* istanbul ignore next */ []));
4109
4125
  toggleMaximize() { this.maximized.set(!this.maximized()); this.drag.set(null); this.maximizeChange.emit(this.maximized()); }
@@ -4148,7 +4164,7 @@ class CadDialogContainerComponent {
4148
4164
  <footer class="cad-dialog__footer"><ng-container *ngTemplateOutlet="config().footer!" /></footer>
4149
4165
  }
4150
4166
  </div>
4151
- `, isInline: true, styles: [".cdk-overlay-container{position:fixed;z-index:var(--cad-z-overlay, 100002);pointer-events:none;top:0;left:0;width:100%;height:100%}.cdk-overlay-container:empty{display:none}.cdk-global-overlay-wrapper{display:flex;position:absolute;z-index:1000;width:100%;height:100%}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1000;display:flex;max-width:100%;max-height:100%}.cdk-overlay-backdrop{position:absolute;inset:0;z-index:1000;pointer-events:auto;-webkit-tap-highlight-color:transparent;opacity:0;transition:opacity var(--cad-dur, .14s) var(--cad-ease, ease)}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:1}.cdk-overlay-transparent-backdrop{transition:visibility 1ms linear,opacity 1ms linear;visibility:hidden;opacity:1}.cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing{visibility:visible;opacity:0}.cdk-overlay-connected-position-bounding-box{position:absolute;z-index:1000;display:flex;flex-direction:column;min-width:1px;min-height:1px}.cdk-global-scrollblock{position:fixed;width:100%;overflow-y:scroll}.cad-overlay-backdrop{background:var(--cad-backdrop, rgba(2, 6, 23, .45))}.cad-overlay-backdrop.is-hidden{display:none}.cad-overlay-pane{pointer-events:auto}.cad-overlay-pane.is-drawer-pane{height:100%}.cad-dialog-container{display:flex;outline:none;font-family:var(--cad-font);font-size:var(--cad-font-size);color:var(--cad-fg);max-height:100%;max-width:100%}.cad-dialog-container.is-hidden{display:none}.cad-dialog{display:flex;flex-direction:column;background:var(--cad-surface);border:1px solid var(--cad-border);border-radius:var(--cad-radius-lg);box-shadow:var(--cad-shadow-lg);max-height:92vh;min-width:0;outline:none;box-sizing:border-box;opacity:0;transform:translateY(6px) scale(.98);transition:opacity var(--cad-dur) var(--cad-ease),transform var(--cad-dur) var(--cad-ease)}.cad-dialog-container.is-open .cad-dialog{opacity:1;transform:none}.cad-dialog-container.is-maximized .cad-dialog{width:100%!important;max-width:100%!important;height:100vh!important;max-height:100vh;border-radius:0}.cad-dialog__header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 16px 10px;flex:none}.cad-dialog__header.is-draggable{cursor:move;-webkit-user-select:none;user-select:none}.cad-dialog__title{margin:0;font-size:14.5px;font-weight:600;color:var(--cad-fg);min-width:0}.cad-dialog__actions{display:inline-flex;gap:2px;margin-left:auto;flex:none}.cad-dialog__icon-btn{display:grid;place-items:center;width:26px;height:26px;border:0;border-radius:var(--cad-radius-sm);background:transparent;color:var(--cad-fg-muted);cursor:pointer;padding:0;transition:background var(--cad-dur),color var(--cad-dur)}.cad-dialog__icon-btn:hover{background:var(--cad-hover);color:var(--cad-fg)}.cad-dialog__icon-btn:focus-visible{outline:2px solid var(--cad-ring);outline-offset:1px}.cad-dialog__body{padding:4px 16px 14px;color:var(--cad-fg-secondary);font-size:var(--cad-font-size);overflow:auto;flex:1;min-height:0}.cad-dialog__body:first-child{padding-top:16px}.cad-dialog__footer{display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:12px 16px;border-top:1px solid var(--cad-border);background:var(--cad-bg-subtle);border-radius:0 0 var(--cad-radius-lg) var(--cad-radius-lg);flex:none}.cad-dialog-container.is-drawer{height:100%}.cad-dialog-container.is-drawer .cad-dialog{max-height:none;height:100%;border-radius:0;opacity:1;transform:none;transition:transform .18s var(--cad-ease)}.cad-dialog-container.is-drawer .cad-dialog__footer{border-radius:0}.cad-dialog-container.is-drawer.pos-right .cad-dialog{transform:translate(100%);border-width:0 0 0 1px}.cad-dialog-container.is-drawer.pos-left .cad-dialog{transform:translate(-100%);border-width:0 1px 0 0}.cad-dialog-container.is-drawer.pos-top .cad-dialog{transform:translateY(-100%);border-width:0 0 1px 0}.cad-dialog-container.is-drawer.pos-bottom .cad-dialog{transform:translateY(100%);border-width:1px 0 0 0}.cad-dialog-container.is-drawer.is-open .cad-dialog{transform:none}.cad-dialog-container.is-drawer.pos-top,.cad-dialog-container.is-drawer.pos-bottom{width:100%}.cad-dialog-container.is-drawer.pos-top .cad-dialog,.cad-dialog-container.is-drawer.pos-bottom .cad-dialog{width:100%!important;max-width:100%!important}.cad-dialog-container.is-mobile-full{width:100%;height:100dvh;max-height:100dvh}.cad-dialog-container.is-mobile-full .cad-dialog{width:100%!important;max-width:100%!important;height:100dvh!important;max-height:100dvh;border-radius:0;border:0;box-shadow:none;padding-bottom:env(safe-area-inset-bottom)}.cad-dialog-container.is-mobile-full .cad-dialog__header{position:sticky;top:0;background:var(--cad-surface);z-index:1;padding-top:calc(14px + env(safe-area-inset-top))}.cad-dialog-container.is-mobile-full .cad-dialog__footer{border-radius:0}.cad-overlay-pane.is-mobile-sheet-pane{width:100%;max-width:100%}.cad-dialog-container.is-mobile-sheet{width:100%;align-items:flex-end}.cad-dialog-container.is-mobile-sheet .cad-dialog{position:relative;width:100%!important;max-width:100%!important;height:92dvh!important;max-height:92dvh;border-radius:16px 16px 0 0;border-bottom:0;box-shadow:0 -8px 32px #0000002e;padding-bottom:env(safe-area-inset-bottom);opacity:1;transform:translateY(100%);transition:transform .2s var(--cad-ease)}.cad-dialog-container.is-mobile-sheet.sheet-h-auto .cad-dialog{height:auto!important}.cad-dialog-container.is-mobile-sheet .cad-dialog:before{content:\"\";position:absolute;top:8px;left:50%;width:36px;height:4px;margin-left:-18px;border-radius:2px;background:var(--cad-border-strong)}.cad-dialog-container.is-mobile-sheet .cad-dialog__header{padding-top:20px}.cad-dialog-container.is-mobile-sheet .cad-dialog__body:first-child{padding-top:22px}.cad-dialog-container.is-mobile-sheet.is-open .cad-dialog{transform:none}.cad-dialog-container.is-mobile-sheet .cad-dialog__footer{border-radius:0}.cad-confirm-dialog.is-mobile-sheet .cad-dialog__footer{flex-direction:column-reverse;align-items:stretch;gap:8px}.cad-confirm-dialog.is-mobile-sheet .cad-dialog__footer>*{width:100%;height:44px}@media(prefers-reduced-motion:reduce){.cad-dialog,.cad-dialog-container.is-drawer .cad-dialog,.cad-dialog-container.is-mobile-sheet .cad-dialog,.cdk-overlay-backdrop{transition:none!important}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i1$1.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: CadIconComponent, selector: "cad-icon", inputs: ["name", "size", "strokeWidth", "label", "title", "spin", "filled", "flip", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4167
+ `, isInline: true, styles: [".cdk-overlay-container{position:fixed;z-index:var(--cad-z-overlay, 100002);pointer-events:none;top:0;left:0;width:100%;height:100%}.cdk-overlay-container:empty{display:none}.cdk-global-overlay-wrapper{display:flex;position:absolute;z-index:1000;width:100%;height:100%}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1000;display:flex;max-width:100%;max-height:100%}.cdk-overlay-backdrop{position:absolute;inset:0;z-index:1000;pointer-events:auto;-webkit-tap-highlight-color:transparent;opacity:0;transition:opacity var(--cad-dur, .14s) var(--cad-ease, ease)}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:1}.cdk-overlay-transparent-backdrop{transition:visibility 1ms linear,opacity 1ms linear;visibility:hidden;opacity:1}.cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing{visibility:visible;opacity:0}.cdk-overlay-connected-position-bounding-box{position:absolute;z-index:1000;display:flex;flex-direction:column;min-width:1px;min-height:1px}.cdk-global-scrollblock{position:fixed;width:100%;overflow-y:scroll}.cad-overlay-backdrop{background:var(--cad-backdrop, rgba(2, 6, 23, .45))}.cad-overlay-backdrop.is-hidden{display:none}.cad-overlay-pane{pointer-events:auto}.cad-overlay-pane.is-drawer-pane{height:100%}.cad-dialog-container{display:flex;outline:none;font-family:var(--cad-font);font-size:var(--cad-font-size);color:var(--cad-fg);max-height:100%;max-width:100%}.cad-dialog-container.is-hidden{display:none}.cad-dialog{display:flex;flex-direction:column;background:var(--cad-surface);border:1px solid var(--cad-border);border-radius:var(--cad-radius-lg);box-shadow:var(--cad-shadow-lg);max-height:92vh;min-width:0;outline:none;box-sizing:border-box;opacity:0;transform:translateY(6px) scale(.98);transition:opacity var(--cad-dur) var(--cad-ease),transform var(--cad-dur) var(--cad-ease)}.cad-dialog-container.is-open .cad-dialog{opacity:1;transform:none}.cad-dialog-container.is-maximized .cad-dialog{width:100%!important;max-width:100%!important;height:100vh!important;max-height:100vh;border-radius:0}.cad-dialog__header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 16px 10px;flex:none}.cad-dialog__header.is-draggable{cursor:move;-webkit-user-select:none;user-select:none}.cad-dialog__title{margin:0;font-size:14.5px;font-weight:600;color:var(--cad-fg);min-width:0}.cad-dialog__actions{display:inline-flex;gap:2px;margin-left:auto;flex:none}.cad-dialog__icon-btn{display:grid;place-items:center;width:26px;height:26px;border:0;border-radius:var(--cad-radius-sm);background:transparent;color:var(--cad-fg-muted);cursor:pointer;padding:0;transition:background var(--cad-dur),color var(--cad-dur)}.cad-dialog__icon-btn:hover{background:var(--cad-hover);color:var(--cad-fg)}.cad-dialog__icon-btn:focus-visible{outline:2px solid var(--cad-ring);outline-offset:1px}.cad-dialog__body{padding:4px 16px 14px;color:var(--cad-fg-secondary);font-size:var(--cad-font-size);overflow:auto;flex:1;min-height:0}.cad-dialog__body:first-child{padding-top:16px}.cad-dialog__footer>[cadDialogFooter]{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;width:100%}.cad-dialog__footer{display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:12px 16px;border-top:1px solid var(--cad-border);background:var(--cad-bg-subtle);border-radius:0 0 var(--cad-radius-lg) var(--cad-radius-lg);flex:none}.cad-dialog-container.is-drawer{height:100%}.cad-dialog-container.is-drawer .cad-dialog{max-height:none;height:100%;border-radius:0;opacity:1;transform:none;transition:transform .18s var(--cad-ease)}.cad-dialog-container.is-drawer .cad-dialog__footer{border-radius:0}.cad-dialog-container.is-drawer.pos-right .cad-dialog{transform:translate(100%);border-width:0 0 0 1px}.cad-dialog-container.is-drawer.pos-left .cad-dialog{transform:translate(-100%);border-width:0 1px 0 0}.cad-dialog-container.is-drawer.pos-top .cad-dialog{transform:translateY(-100%);border-width:0 0 1px 0}.cad-dialog-container.is-drawer.pos-bottom .cad-dialog{transform:translateY(100%);border-width:1px 0 0 0}.cad-dialog-container.is-drawer.is-open .cad-dialog{transform:none}.cad-dialog-container.is-drawer.pos-top,.cad-dialog-container.is-drawer.pos-bottom{width:100%}.cad-dialog-container.is-drawer.pos-top .cad-dialog,.cad-dialog-container.is-drawer.pos-bottom .cad-dialog{width:100%!important;max-width:100%!important}.cad-dialog-container.is-mobile-full{width:100dvw;height:100dvh;max-height:100dvh;align-items:flex-start}.cad-dialog-container.is-mobile-full .cad-dialog{width:100dvw!important;max-width:100dvw!important;height:auto!important;max-height:100dvh;border-radius:0 0 var(--cad-radius-lg) var(--cad-radius-lg);border:0;box-shadow:none;padding-bottom:env(safe-area-inset-bottom)}.cad-dialog-container.is-mobile-full .cad-dialog__header{position:sticky;top:0;background:var(--cad-surface);z-index:1;padding-top:calc(14px + env(safe-area-inset-top))}.cad-dialog-container.is-mobile-full .cad-dialog__footer{border-radius:0}.cad-overlay-pane.is-mobile-sheet-pane{width:100%;max-width:100%}.cad-dialog-container.is-mobile-sheet{width:100%;align-items:flex-end}.cad-dialog-container.is-mobile-sheet .cad-dialog{position:relative;width:100%!important;max-width:100%!important;height:92dvh!important;max-height:92dvh;border-radius:16px 16px 0 0;border-bottom:0;box-shadow:0 -8px 32px #0000002e;padding-bottom:env(safe-area-inset-bottom);opacity:1;transform:translateY(100%);transition:transform .2s var(--cad-ease)}.cad-dialog-container.is-mobile-sheet.sheet-h-auto .cad-dialog{height:auto!important}.cad-dialog-container.is-mobile-sheet .cad-dialog:before{content:\"\";position:absolute;top:8px;left:50%;width:36px;height:4px;margin-left:-18px;border-radius:2px;background:var(--cad-border-strong)}.cad-dialog-container.is-mobile-sheet .cad-dialog__header{padding-top:20px}.cad-dialog-container.is-mobile-sheet .cad-dialog__body:first-child{padding-top:22px}.cad-dialog-container.is-mobile-sheet.is-open .cad-dialog{transform:none}.cad-dialog-container.is-mobile-sheet .cad-dialog__footer{border-radius:0}.cad-confirm-dialog.is-mobile-sheet .cad-dialog__footer{flex-direction:column-reverse;align-items:stretch;gap:8px}.cad-confirm-dialog.is-mobile-sheet .cad-dialog__footer>*{width:100%;height:44px}@media(prefers-reduced-motion:reduce){.cad-dialog,.cad-dialog-container.is-drawer .cad-dialog,.cad-dialog-container.is-mobile-sheet .cad-dialog,.cdk-overlay-backdrop{transition:none!important}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i1$1.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: CadIconComponent, selector: "cad-icon", inputs: ["name", "size", "strokeWidth", "label", "title", "spin", "filled", "flip", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4152
4168
  }
4153
4169
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadDialogContainerComponent, decorators: [{
4154
4170
  type: Component,
@@ -4194,7 +4210,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
4194
4210
  <footer class="cad-dialog__footer"><ng-container *ngTemplateOutlet="config().footer!" /></footer>
4195
4211
  }
4196
4212
  </div>
4197
- `, styles: [".cdk-overlay-container{position:fixed;z-index:var(--cad-z-overlay, 100002);pointer-events:none;top:0;left:0;width:100%;height:100%}.cdk-overlay-container:empty{display:none}.cdk-global-overlay-wrapper{display:flex;position:absolute;z-index:1000;width:100%;height:100%}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1000;display:flex;max-width:100%;max-height:100%}.cdk-overlay-backdrop{position:absolute;inset:0;z-index:1000;pointer-events:auto;-webkit-tap-highlight-color:transparent;opacity:0;transition:opacity var(--cad-dur, .14s) var(--cad-ease, ease)}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:1}.cdk-overlay-transparent-backdrop{transition:visibility 1ms linear,opacity 1ms linear;visibility:hidden;opacity:1}.cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing{visibility:visible;opacity:0}.cdk-overlay-connected-position-bounding-box{position:absolute;z-index:1000;display:flex;flex-direction:column;min-width:1px;min-height:1px}.cdk-global-scrollblock{position:fixed;width:100%;overflow-y:scroll}.cad-overlay-backdrop{background:var(--cad-backdrop, rgba(2, 6, 23, .45))}.cad-overlay-backdrop.is-hidden{display:none}.cad-overlay-pane{pointer-events:auto}.cad-overlay-pane.is-drawer-pane{height:100%}.cad-dialog-container{display:flex;outline:none;font-family:var(--cad-font);font-size:var(--cad-font-size);color:var(--cad-fg);max-height:100%;max-width:100%}.cad-dialog-container.is-hidden{display:none}.cad-dialog{display:flex;flex-direction:column;background:var(--cad-surface);border:1px solid var(--cad-border);border-radius:var(--cad-radius-lg);box-shadow:var(--cad-shadow-lg);max-height:92vh;min-width:0;outline:none;box-sizing:border-box;opacity:0;transform:translateY(6px) scale(.98);transition:opacity var(--cad-dur) var(--cad-ease),transform var(--cad-dur) var(--cad-ease)}.cad-dialog-container.is-open .cad-dialog{opacity:1;transform:none}.cad-dialog-container.is-maximized .cad-dialog{width:100%!important;max-width:100%!important;height:100vh!important;max-height:100vh;border-radius:0}.cad-dialog__header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 16px 10px;flex:none}.cad-dialog__header.is-draggable{cursor:move;-webkit-user-select:none;user-select:none}.cad-dialog__title{margin:0;font-size:14.5px;font-weight:600;color:var(--cad-fg);min-width:0}.cad-dialog__actions{display:inline-flex;gap:2px;margin-left:auto;flex:none}.cad-dialog__icon-btn{display:grid;place-items:center;width:26px;height:26px;border:0;border-radius:var(--cad-radius-sm);background:transparent;color:var(--cad-fg-muted);cursor:pointer;padding:0;transition:background var(--cad-dur),color var(--cad-dur)}.cad-dialog__icon-btn:hover{background:var(--cad-hover);color:var(--cad-fg)}.cad-dialog__icon-btn:focus-visible{outline:2px solid var(--cad-ring);outline-offset:1px}.cad-dialog__body{padding:4px 16px 14px;color:var(--cad-fg-secondary);font-size:var(--cad-font-size);overflow:auto;flex:1;min-height:0}.cad-dialog__body:first-child{padding-top:16px}.cad-dialog__footer{display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:12px 16px;border-top:1px solid var(--cad-border);background:var(--cad-bg-subtle);border-radius:0 0 var(--cad-radius-lg) var(--cad-radius-lg);flex:none}.cad-dialog-container.is-drawer{height:100%}.cad-dialog-container.is-drawer .cad-dialog{max-height:none;height:100%;border-radius:0;opacity:1;transform:none;transition:transform .18s var(--cad-ease)}.cad-dialog-container.is-drawer .cad-dialog__footer{border-radius:0}.cad-dialog-container.is-drawer.pos-right .cad-dialog{transform:translate(100%);border-width:0 0 0 1px}.cad-dialog-container.is-drawer.pos-left .cad-dialog{transform:translate(-100%);border-width:0 1px 0 0}.cad-dialog-container.is-drawer.pos-top .cad-dialog{transform:translateY(-100%);border-width:0 0 1px 0}.cad-dialog-container.is-drawer.pos-bottom .cad-dialog{transform:translateY(100%);border-width:1px 0 0 0}.cad-dialog-container.is-drawer.is-open .cad-dialog{transform:none}.cad-dialog-container.is-drawer.pos-top,.cad-dialog-container.is-drawer.pos-bottom{width:100%}.cad-dialog-container.is-drawer.pos-top .cad-dialog,.cad-dialog-container.is-drawer.pos-bottom .cad-dialog{width:100%!important;max-width:100%!important}.cad-dialog-container.is-mobile-full{width:100%;height:100dvh;max-height:100dvh}.cad-dialog-container.is-mobile-full .cad-dialog{width:100%!important;max-width:100%!important;height:100dvh!important;max-height:100dvh;border-radius:0;border:0;box-shadow:none;padding-bottom:env(safe-area-inset-bottom)}.cad-dialog-container.is-mobile-full .cad-dialog__header{position:sticky;top:0;background:var(--cad-surface);z-index:1;padding-top:calc(14px + env(safe-area-inset-top))}.cad-dialog-container.is-mobile-full .cad-dialog__footer{border-radius:0}.cad-overlay-pane.is-mobile-sheet-pane{width:100%;max-width:100%}.cad-dialog-container.is-mobile-sheet{width:100%;align-items:flex-end}.cad-dialog-container.is-mobile-sheet .cad-dialog{position:relative;width:100%!important;max-width:100%!important;height:92dvh!important;max-height:92dvh;border-radius:16px 16px 0 0;border-bottom:0;box-shadow:0 -8px 32px #0000002e;padding-bottom:env(safe-area-inset-bottom);opacity:1;transform:translateY(100%);transition:transform .2s var(--cad-ease)}.cad-dialog-container.is-mobile-sheet.sheet-h-auto .cad-dialog{height:auto!important}.cad-dialog-container.is-mobile-sheet .cad-dialog:before{content:\"\";position:absolute;top:8px;left:50%;width:36px;height:4px;margin-left:-18px;border-radius:2px;background:var(--cad-border-strong)}.cad-dialog-container.is-mobile-sheet .cad-dialog__header{padding-top:20px}.cad-dialog-container.is-mobile-sheet .cad-dialog__body:first-child{padding-top:22px}.cad-dialog-container.is-mobile-sheet.is-open .cad-dialog{transform:none}.cad-dialog-container.is-mobile-sheet .cad-dialog__footer{border-radius:0}.cad-confirm-dialog.is-mobile-sheet .cad-dialog__footer{flex-direction:column-reverse;align-items:stretch;gap:8px}.cad-confirm-dialog.is-mobile-sheet .cad-dialog__footer>*{width:100%;height:44px}@media(prefers-reduced-motion:reduce){.cad-dialog,.cad-dialog-container.is-drawer .cad-dialog,.cad-dialog-container.is-mobile-sheet .cad-dialog,.cdk-overlay-backdrop{transition:none!important}}\n"] }]
4213
+ `, styles: [".cdk-overlay-container{position:fixed;z-index:var(--cad-z-overlay, 100002);pointer-events:none;top:0;left:0;width:100%;height:100%}.cdk-overlay-container:empty{display:none}.cdk-global-overlay-wrapper{display:flex;position:absolute;z-index:1000;width:100%;height:100%}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1000;display:flex;max-width:100%;max-height:100%}.cdk-overlay-backdrop{position:absolute;inset:0;z-index:1000;pointer-events:auto;-webkit-tap-highlight-color:transparent;opacity:0;transition:opacity var(--cad-dur, .14s) var(--cad-ease, ease)}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:1}.cdk-overlay-transparent-backdrop{transition:visibility 1ms linear,opacity 1ms linear;visibility:hidden;opacity:1}.cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing{visibility:visible;opacity:0}.cdk-overlay-connected-position-bounding-box{position:absolute;z-index:1000;display:flex;flex-direction:column;min-width:1px;min-height:1px}.cdk-global-scrollblock{position:fixed;width:100%;overflow-y:scroll}.cad-overlay-backdrop{background:var(--cad-backdrop, rgba(2, 6, 23, .45))}.cad-overlay-backdrop.is-hidden{display:none}.cad-overlay-pane{pointer-events:auto}.cad-overlay-pane.is-drawer-pane{height:100%}.cad-dialog-container{display:flex;outline:none;font-family:var(--cad-font);font-size:var(--cad-font-size);color:var(--cad-fg);max-height:100%;max-width:100%}.cad-dialog-container.is-hidden{display:none}.cad-dialog{display:flex;flex-direction:column;background:var(--cad-surface);border:1px solid var(--cad-border);border-radius:var(--cad-radius-lg);box-shadow:var(--cad-shadow-lg);max-height:92vh;min-width:0;outline:none;box-sizing:border-box;opacity:0;transform:translateY(6px) scale(.98);transition:opacity var(--cad-dur) var(--cad-ease),transform var(--cad-dur) var(--cad-ease)}.cad-dialog-container.is-open .cad-dialog{opacity:1;transform:none}.cad-dialog-container.is-maximized .cad-dialog{width:100%!important;max-width:100%!important;height:100vh!important;max-height:100vh;border-radius:0}.cad-dialog__header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 16px 10px;flex:none}.cad-dialog__header.is-draggable{cursor:move;-webkit-user-select:none;user-select:none}.cad-dialog__title{margin:0;font-size:14.5px;font-weight:600;color:var(--cad-fg);min-width:0}.cad-dialog__actions{display:inline-flex;gap:2px;margin-left:auto;flex:none}.cad-dialog__icon-btn{display:grid;place-items:center;width:26px;height:26px;border:0;border-radius:var(--cad-radius-sm);background:transparent;color:var(--cad-fg-muted);cursor:pointer;padding:0;transition:background var(--cad-dur),color var(--cad-dur)}.cad-dialog__icon-btn:hover{background:var(--cad-hover);color:var(--cad-fg)}.cad-dialog__icon-btn:focus-visible{outline:2px solid var(--cad-ring);outline-offset:1px}.cad-dialog__body{padding:4px 16px 14px;color:var(--cad-fg-secondary);font-size:var(--cad-font-size);overflow:auto;flex:1;min-height:0}.cad-dialog__body:first-child{padding-top:16px}.cad-dialog__footer>[cadDialogFooter]{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;width:100%}.cad-dialog__footer{display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:12px 16px;border-top:1px solid var(--cad-border);background:var(--cad-bg-subtle);border-radius:0 0 var(--cad-radius-lg) var(--cad-radius-lg);flex:none}.cad-dialog-container.is-drawer{height:100%}.cad-dialog-container.is-drawer .cad-dialog{max-height:none;height:100%;border-radius:0;opacity:1;transform:none;transition:transform .18s var(--cad-ease)}.cad-dialog-container.is-drawer .cad-dialog__footer{border-radius:0}.cad-dialog-container.is-drawer.pos-right .cad-dialog{transform:translate(100%);border-width:0 0 0 1px}.cad-dialog-container.is-drawer.pos-left .cad-dialog{transform:translate(-100%);border-width:0 1px 0 0}.cad-dialog-container.is-drawer.pos-top .cad-dialog{transform:translateY(-100%);border-width:0 0 1px 0}.cad-dialog-container.is-drawer.pos-bottom .cad-dialog{transform:translateY(100%);border-width:1px 0 0 0}.cad-dialog-container.is-drawer.is-open .cad-dialog{transform:none}.cad-dialog-container.is-drawer.pos-top,.cad-dialog-container.is-drawer.pos-bottom{width:100%}.cad-dialog-container.is-drawer.pos-top .cad-dialog,.cad-dialog-container.is-drawer.pos-bottom .cad-dialog{width:100%!important;max-width:100%!important}.cad-dialog-container.is-mobile-full{width:100dvw;height:100dvh;max-height:100dvh;align-items:flex-start}.cad-dialog-container.is-mobile-full .cad-dialog{width:100dvw!important;max-width:100dvw!important;height:auto!important;max-height:100dvh;border-radius:0 0 var(--cad-radius-lg) var(--cad-radius-lg);border:0;box-shadow:none;padding-bottom:env(safe-area-inset-bottom)}.cad-dialog-container.is-mobile-full .cad-dialog__header{position:sticky;top:0;background:var(--cad-surface);z-index:1;padding-top:calc(14px + env(safe-area-inset-top))}.cad-dialog-container.is-mobile-full .cad-dialog__footer{border-radius:0}.cad-overlay-pane.is-mobile-sheet-pane{width:100%;max-width:100%}.cad-dialog-container.is-mobile-sheet{width:100%;align-items:flex-end}.cad-dialog-container.is-mobile-sheet .cad-dialog{position:relative;width:100%!important;max-width:100%!important;height:92dvh!important;max-height:92dvh;border-radius:16px 16px 0 0;border-bottom:0;box-shadow:0 -8px 32px #0000002e;padding-bottom:env(safe-area-inset-bottom);opacity:1;transform:translateY(100%);transition:transform .2s var(--cad-ease)}.cad-dialog-container.is-mobile-sheet.sheet-h-auto .cad-dialog{height:auto!important}.cad-dialog-container.is-mobile-sheet .cad-dialog:before{content:\"\";position:absolute;top:8px;left:50%;width:36px;height:4px;margin-left:-18px;border-radius:2px;background:var(--cad-border-strong)}.cad-dialog-container.is-mobile-sheet .cad-dialog__header{padding-top:20px}.cad-dialog-container.is-mobile-sheet .cad-dialog__body:first-child{padding-top:22px}.cad-dialog-container.is-mobile-sheet.is-open .cad-dialog{transform:none}.cad-dialog-container.is-mobile-sheet .cad-dialog__footer{border-radius:0}.cad-confirm-dialog.is-mobile-sheet .cad-dialog__footer{flex-direction:column-reverse;align-items:stretch;gap:8px}.cad-confirm-dialog.is-mobile-sheet .cad-dialog__footer>*{width:100%;height:44px}@media(prefers-reduced-motion:reduce){.cad-dialog,.cad-dialog-container.is-drawer .cad-dialog,.cad-dialog-container.is-mobile-sheet .cad-dialog,.cdk-overlay-backdrop{transition:none!important}}\n"] }]
4198
4214
  }], propDecorators: { closeRequest: [{ type: i0.Output, args: ["closeRequest"] }], attached: [{ type: i0.Output, args: ["attached"] }], maximizeChange: [{ type: i0.Output, args: ["maximizeChange"] }] } });
4199
4215
 
4200
4216
  const ANIM_MS = 180;
@@ -6314,6 +6330,44 @@ const CAD_SHELL = new InjectionToken('CAD_SHELL');
6314
6330
  * <cad-bottom-nav cadShellBottom [items]="dest" [(value)]="page"/>
6315
6331
  * </cad-app-shell>
6316
6332
  * ```
6333
+ *
6334
+ * ⚠️ N'ENVELOPPEZ JAMAIS UN EMPLACEMENT DANS UN `@if` (ni `@for`, ni `<ng-template>`).
6335
+ *
6336
+ * La projection de contenu est STATIQUE : Angular apparie les nœuds aux sélecteurs de
6337
+ * `ng-content` sur la structure déclarée. Un `<header cadShellBar>` placé dans un bloc de
6338
+ * flux de contrôle n'est jamais apparié — il tombe dans l'emplacement PAR DÉFAUT, donc dans
6339
+ * la zone de CONTENU. Rien ne le signale : ça compile, ça s'affiche, et la barre se met à
6340
+ * défiler avec le contenu — exactement le défaut que cette coquille existe pour empêcher.
6341
+ * Constaté au navigateur le 22/08/2026 sur le showcase (`.cad-shell__bar` mesurait 0×0).
6342
+ *
6343
+ * ```html
6344
+ * <!-- FAUX : la barre atterrit dans le contenu -->
6345
+ * @if (connecte()) { <header cadShellBar>…</header> }
6346
+ *
6347
+ * <!-- JUSTE : l'emplacement est toujours rendu, c'est son CONTENU qui varie -->
6348
+ * <header cadShellBar [class.est-masque]="!connecte()">…</header>
6349
+ * ```
6350
+ *
6351
+ * Une rangée ou une colonne `auto` dont l'unique enfant est en `display: none` se replie à
6352
+ * zéro : masquer ainsi ne laisse aucune trace dans la mise en page.
6353
+ *
6354
+ * ⚠️ ZONES SÛRES — DEUX CONDITIONS, ET LA PREMIÈRE EST DANS `index.html`.
6355
+ *
6356
+ * 1. **`<meta name="viewport" content="…, viewport-fit=cover">`**. Sans lui, iOS encadre la
6357
+ * page : elle ne s'étend jamais sous l'encoche, la bande du haut est peinte par le SYSTÈME,
6358
+ * et **tous les `env(safe-area-inset-*)` valent 0**. Toute la mécanique de zone sûre de la
6359
+ * bibliothèque — celle de `cad-bottom-nav`, celle du tiroir en mode temporaire — est alors
6360
+ * inerte sans que rien ne le signale.
6361
+ * 2. **La barre peint sa propre zone sûre haute** : `padding-top: env(safe-area-inset-top)`
6362
+ * sur l'élément projeté, PAS sur la coquille. Le rembourrage est intérieur au fond, donc la
6363
+ * couleur de la barre monte jusqu'au bord de l'écran. Le poser côté coquille laisserait la
6364
+ * bande sous l'encoche au fond de la COQUILLE : la cassure resterait, d'une autre couleur.
6365
+ * Avec `box-sizing: border-box`, penser au `min-height: calc(52px + env(safe-area-inset-top))`
6366
+ * — sinon l'encoche mange la hauteur du contenu.
6367
+ *
6368
+ * En application autonome (ajoutée à l'écran d'accueil), la page ne passe pas sous la bande
6369
+ * d'état : `viewport-fit=cover` n'y suffit pas et c'est `<meta name="theme-color">`, tenu à
6370
+ * jour avec le thème, qui donne la continuité.
6317
6371
  */
6318
6372
  class CadAppShellComponent {
6319
6373
  bp = inject(CadBreakpointService);
@@ -9398,6 +9452,15 @@ class CadNavDrawerComponent {
9398
9452
  open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
9399
9453
  /** Entrée courante, comparée à `item.value`. */
9400
9454
  value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
9455
+ /**
9456
+ * Clés des intertitres repliés (`item.value`, à défaut `item.label`).
9457
+ *
9458
+ * Un TABLEAU et non un `Set` : c'est ce qui se lie en deux sens, se compare, et surtout se
9459
+ * sérialise tel quel — l'hôte qui veut persister l'état le pose dans `localStorage` sans
9460
+ * conversion. Le composant ne persiste rien lui-même : il ne saurait pas sous quelle clé,
9461
+ * ni s'il a le droit.
9462
+ */
9463
+ collapsed = model([], ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
9401
9464
  mode = input('auto', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
9402
9465
  /** Seuil de bascule permanent → tiroir, en mode `auto`. */
9403
9466
  breakpoint = input('md', ...(ngDevMode ? [{ debugName: "breakpoint" }] : /* istanbul ignore next */ []));
@@ -9426,6 +9489,42 @@ class CadNavDrawerComponent {
9426
9489
  }, ...(ngDevMode ? [{ debugName: "effective" }] : /* istanbul ignore next */ []));
9427
9490
  temporary = computed(() => this.effective() === 'temporary', ...(ngDevMode ? [{ debugName: "temporary" }] : /* istanbul ignore next */ []));
9428
9491
  rail = computed(() => this.effective() === 'rail', ...(ngDevMode ? [{ debugName: "rail" }] : /* istanbul ignore next */ []));
9492
+ /** Clé d'un intertitre. `value` d'abord : un libellé se renomme, une clé non. */
9493
+ cle(it) { return it.value ?? it.label; }
9494
+ /**
9495
+ * Les lignes à rendre : les intertitres toujours, les entrées seulement si leur intertitre
9496
+ * n'est pas replié. Une entrée qui précède tout intertitre n'appartient à aucun groupe et
9497
+ * ne se replie donc jamais — sans quoi elle serait masquée sans bouton pour la rouvrir.
9498
+ */
9499
+ rows = computed(() => {
9500
+ const replies = new Set(this.collapsed());
9501
+ const rail = this.rail();
9502
+ const out = [];
9503
+ let courante = null;
9504
+ for (const item of this.items()) {
9505
+ if (item.section) {
9506
+ // En rail l'intertitre devient un filet : plus de bouton, donc rien ne se replie.
9507
+ const collapsible = !rail && !!item.collapsible;
9508
+ const key = this.cle(item);
9509
+ courante = { item, key, collapsible, collapsed: collapsible && replies.has(key), count: 0 };
9510
+ out.push(courante);
9511
+ continue;
9512
+ }
9513
+ // Le compte est celui du GROUPE, pas de ce qui est visible : il doit rester le même
9514
+ // une fois replié, sinon il annoncerait zéro au moment où il sert le plus.
9515
+ if (courante)
9516
+ courante.count++;
9517
+ if (courante?.collapsed)
9518
+ continue;
9519
+ out.push({ item, key: '', collapsible: false, collapsed: false, count: 0 });
9520
+ }
9521
+ return out;
9522
+ }, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
9523
+ toggleSection(r) {
9524
+ const s = new Set(this.collapsed());
9525
+ s.has(r.key) ? s.delete(r.key) : s.add(r.key);
9526
+ this.collapsed.set([...s]);
9527
+ }
9429
9528
  wasOpen = signal(false, ...(ngDevMode ? [{ debugName: "wasOpen" }] : /* istanbul ignore next */ []));
9430
9529
  constructor() {
9431
9530
  effect(() => {
@@ -9471,7 +9570,7 @@ class CadNavDrawerComponent {
9471
9570
  this.open.set(false);
9472
9571
  }
9473
9572
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadNavDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9474
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadNavDrawerComponent, isStandalone: true, selector: "cad-nav-drawer", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, breakpoint: { classPropertyName: "breakpoint", publicName: "breakpoint", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, closeOnNavigate: { classPropertyName: "closeOnNavigate", publicName: "closeOnNavigate", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, dismissableScrim: { classPropertyName: "dismissableScrim", publicName: "dismissableScrim", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", value: "valueChange", opened: "opened", closed: "closed", itemClick: "itemClick", changed: "changed" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.is-temporary": "temporary()", "class.is-permanent": "!temporary()", "class.is-rail": "rail()", "class.is-open": "open()", "class.is-right": "position() === \"right\"" }, classAttribute: "cad-nav-drawer" }, queries: [{ propertyName: "headerSlot", first: true, predicate: CadNavDrawerHeaderDirective, descendants: true, isSignal: true }, { propertyName: "footerSlot", first: true, predicate: CadNavDrawerFooterDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
9573
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadNavDrawerComponent, isStandalone: true, selector: "cad-nav-drawer", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, breakpoint: { classPropertyName: "breakpoint", publicName: "breakpoint", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, closeOnNavigate: { classPropertyName: "closeOnNavigate", publicName: "closeOnNavigate", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, dismissableScrim: { classPropertyName: "dismissableScrim", publicName: "dismissableScrim", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", value: "valueChange", collapsed: "collapsedChange", opened: "opened", closed: "closed", itemClick: "itemClick", changed: "changed" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.is-temporary": "temporary()", "class.is-permanent": "!temporary()", "class.is-rail": "rail()", "class.is-open": "open()", "class.is-right": "position() === \"right\"" }, classAttribute: "cad-nav-drawer" }, queries: [{ propertyName: "headerSlot", first: true, predicate: CadNavDrawerHeaderDirective, descendants: true, isSignal: true }, { propertyName: "footerSlot", first: true, predicate: CadNavDrawerFooterDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
9475
9574
  <!-- Le voile n'existe qu'en mode tiroir : en permanent il n'y a rien à recouvrir. -->
9476
9575
  @if (temporary() && open()) {
9477
9576
  <div class="cad-nav-drawer__scrim" (click)="dismiss()" aria-hidden="true"></div>
@@ -9492,11 +9591,28 @@ class CadNavDrawerComponent {
9492
9591
  </div>
9493
9592
 
9494
9593
  <ul class="cad-nav-drawer__list" role="list">
9495
- @for (it of items(); track $index) {
9594
+ @for (r of rows(); track $index) {
9595
+ @let it = r.item;
9496
9596
  @if (it.section) {
9497
9597
  <!-- En rail, un intertitre n'a plus de place : un filet le remplace. -->
9498
9598
  @if (rail()) {
9499
9599
  <li class="cad-nav-drawer__rule" role="separator"></li>
9600
+ } @else if (r.collapsible) {
9601
+ <li>
9602
+ <button
9603
+ type="button"
9604
+ class="cad-nav-drawer__section is-collapsible"
9605
+ [class.is-collapsed]="r.collapsed"
9606
+ [attr.aria-expanded]="!r.collapsed"
9607
+ (click)="toggleSection(r)"
9608
+ >
9609
+ <cad-icon name="chevron-down" [size]="12" class="cad-nav-drawer__chev"/>
9610
+ <span class="cad-nav-drawer__label">{{ it.label }}</span>
9611
+ <!-- Un compte DÉCLARÉ l'emporte sur le compte calculé : une valeur posée
9612
+ par l'hôte est une affirmation, pas un oubli à combler. -->
9613
+ <span class="cad-nav-drawer__badge">{{ it.badge ?? r.count }}</span>
9614
+ </button>
9615
+ </li>
9500
9616
  } @else {
9501
9617
  <li class="cad-nav-drawer__section">{{ it.label }}</li>
9502
9618
  }
@@ -9547,7 +9663,7 @@ class CadNavDrawerComponent {
9547
9663
  <span class="cad-nav-drawer__dot" [attr.title]="it.badge"></span>
9548
9664
  }
9549
9665
  </ng-template>
9550
- `, isInline: true, styles: [":host{display:block}:host(.is-permanent){flex:none}.cad-nav-drawer__panel{display:flex;flex-direction:column;min-height:0;height:100%;width:var(--cad-nav-drawer-w, 240px);background:var(--cad-bg-subtle);border-right:1px solid var(--cad-border);transition:width var(--cad-dur) var(--cad-ease)}:host(.is-rail) .cad-nav-drawer__panel{width:var(--cad-nav-drawer-rail-w, 56px)}:host(.is-right) .cad-nav-drawer__panel{border-right:0;border-left:1px solid var(--cad-border)}:host(.is-temporary){position:fixed;inset:0 auto 0 0;z-index:var(--cad-z-overlay, 100002);pointer-events:none}:host(.is-temporary.is-right){inset:0 0 0 auto}:host(.is-temporary) .cad-nav-drawer__panel{pointer-events:auto;height:100%;box-shadow:var(--cad-shadow-lg, 0 12px 24px rgba(15,23,42,.18));transform:translate(-100%);transition:transform var(--cad-dur) var(--cad-ease)}:host(.is-temporary.is-right) .cad-nav-drawer__panel{transform:translate(100%)}:host(.is-temporary.is-open) .cad-nav-drawer__panel{transform:translate(0)}.cad-nav-drawer__scrim{position:fixed;inset:0;background:var(--cad-backdrop, rgba(2,6,23,.45));pointer-events:auto;z-index:-1}.cad-nav-drawer__head{display:flex;align-items:center;gap:9px;flex:none;padding:14px 14px 12px;border-bottom:1px solid var(--cad-border);min-height:48px;color:var(--cad-fg);font-size:13px}:host(.is-rail) .cad-nav-drawer__head{justify-content:center;padding:14px 0 12px}.cad-nav-drawer__head cad-icon{color:var(--cad-accent);flex:none}.cad-nav-drawer__list{list-style:none;margin:0;padding:8px 0;flex:1;min-height:0;overflow-y:auto}.cad-nav-drawer__section{padding:12px 14px 5px;font-size:10px;font-weight:500;letter-spacing:.07em;text-transform:uppercase;color:var(--cad-fg-muted)}.cad-nav-drawer__rule{height:1px;margin:8px 12px;background:var(--cad-border)}.cad-nav-drawer__item{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:10px;width:100%;cursor:pointer;padding:8px 14px;min-height:38px;color:var(--cad-fg-secondary);font:12.5px var(--cad-font);text-decoration:none}:host(.is-rail) .cad-nav-drawer__item{justify-content:center;padding:10px 0}.cad-nav-drawer__item:hover{background:var(--cad-hover);color:var(--cad-fg)}.cad-nav-drawer__item:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.cad-nav-drawer__item.is-active{background:var(--cad-accent-soft);color:var(--cad-accent);font-weight:500;box-shadow:inset 3px 0 0 var(--cad-accent)}:host(.is-right) .cad-nav-drawer__item.is-active{box-shadow:inset -3px 0 0 var(--cad-accent)}.cad-nav-drawer__item.is-disabled,.cad-nav-drawer__item:disabled{opacity:.45;cursor:not-allowed}.cad-nav-drawer__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cad-nav-drawer__badge{flex:none;font-size:10.5px;font-family:var(--cad-font-mono);border-radius:var(--cad-radius-pill);padding:1px 7px;background:var(--cad-elevated, var(--cad-border));color:var(--cad-fg-secondary)}.cad-nav-drawer__item.is-active .cad-nav-drawer__badge{background:var(--cad-accent);color:var(--cad-fg-on-accent)}.cad-nav-drawer__dot{position:absolute;margin:-14px 0 0 14px;width:7px;height:7px;border-radius:50%;background:var(--cad-danger, #f85149)}.cad-nav-drawer__foot{flex:none;border-top:1px solid var(--cad-border);padding:10px 14px}.cad-nav-drawer__foot.is-empty{display:none}:host(.is-rail) .cad-nav-drawer__foot{padding:10px 0;display:flex;justify-content:center}:host-context(.cad-touch) .cad-nav-drawer__item{min-height:44px}@media(prefers-reduced-motion:reduce){.cad-nav-drawer__panel{transition:none}}\n"], dependencies: [{ kind: "component", type: CadIconComponent, selector: "cad-icon", inputs: ["name", "size", "strokeWidth", "label", "title", "spin", "filled", "flip", "rotate"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9666
+ `, isInline: true, styles: [":host{display:block}:host(.is-permanent){flex:none}.cad-nav-drawer__panel{display:flex;flex-direction:column;min-height:0;height:100%;width:var(--cad-nav-drawer-w, 240px);background:var(--cad-bg-subtle);border-right:1px solid var(--cad-border);transition:width var(--cad-dur) var(--cad-ease)}:host(.is-rail) .cad-nav-drawer__panel{width:var(--cad-nav-drawer-rail-w, 56px)}:host(.is-right) .cad-nav-drawer__panel{border-right:0;border-left:1px solid var(--cad-border)}:host(.is-temporary){position:fixed;inset:0 auto 0 0;z-index:var(--cad-z-overlay, 100002);pointer-events:none}:host(.is-temporary.is-right){inset:0 0 0 auto}:host(.is-temporary) .cad-nav-drawer__panel{pointer-events:auto;height:100%;box-shadow:var(--cad-shadow-lg, 0 12px 24px rgba(15,23,42,.18));transform:translate(-100%);transition:transform var(--cad-dur) var(--cad-ease);padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}:host(.is-temporary.is-right) .cad-nav-drawer__panel{transform:translate(100%)}:host(.is-temporary.is-open) .cad-nav-drawer__panel{transform:translate(0)}.cad-nav-drawer__scrim{position:fixed;inset:0;background:var(--cad-backdrop, rgba(2,6,23,.45));pointer-events:auto;z-index:-1}.cad-nav-drawer__head{display:flex;align-items:center;gap:9px;flex:none;padding:14px 14px 12px;border-bottom:1px solid var(--cad-border);min-height:48px;color:var(--cad-fg);font-size:13px}:host(.is-rail) .cad-nav-drawer__head{justify-content:center;padding:14px 0 12px}.cad-nav-drawer__head cad-icon{color:var(--cad-accent);flex:none}.cad-nav-drawer__list{list-style:none;margin:0;padding:8px 0;flex:1;min-height:0;overflow-y:auto}.cad-nav-drawer__section{padding:12px 14px 5px;font-size:10px;font-weight:500;letter-spacing:.07em;text-transform:uppercase;color:var(--cad-fg-muted)}.cad-nav-drawer__rule{height:1px;margin:8px 12px;background:var(--cad-border)}.cad-nav-drawer__section.is-collapsible{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:8px;width:100%;cursor:pointer;padding:12px 14px 5px;font:500 10px var(--cad-font);letter-spacing:.07em;text-transform:uppercase;color:var(--cad-fg-muted)}.cad-nav-drawer__section.is-collapsible:hover{color:var(--cad-fg-secondary)}.cad-nav-drawer__section.is-collapsible:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.cad-nav-drawer__chev{flex:none;transition:transform var(--cad-dur) var(--cad-ease)}.cad-nav-drawer__section.is-collapsed .cad-nav-drawer__chev{transform:rotate(-90deg)}@media(prefers-reduced-motion:reduce){.cad-nav-drawer__chev{transition:none}}.cad-nav-drawer__item{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:10px;width:100%;cursor:pointer;padding:8px 14px;min-height:38px;color:var(--cad-fg-secondary);font:12.5px var(--cad-font);text-decoration:none}:host(.is-rail) .cad-nav-drawer__item{justify-content:center;padding:10px 0}.cad-nav-drawer__item:hover{background:var(--cad-hover);color:var(--cad-fg)}.cad-nav-drawer__item:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.cad-nav-drawer__item.is-active{background:var(--cad-accent-soft);color:var(--cad-accent);font-weight:500;box-shadow:inset 3px 0 0 var(--cad-accent)}:host(.is-right) .cad-nav-drawer__item.is-active{box-shadow:inset -3px 0 0 var(--cad-accent)}.cad-nav-drawer__item.is-disabled,.cad-nav-drawer__item:disabled{opacity:.45;cursor:not-allowed}.cad-nav-drawer__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cad-nav-drawer__badge{flex:none;font-size:10.5px;font-family:var(--cad-font-mono);border-radius:var(--cad-radius-pill);padding:1px 7px;background:var(--cad-elevated, var(--cad-border));color:var(--cad-fg-secondary)}.cad-nav-drawer__item.is-active .cad-nav-drawer__badge{background:var(--cad-accent);color:var(--cad-fg-on-accent)}.cad-nav-drawer__dot{position:absolute;margin:-14px 0 0 14px;width:7px;height:7px;border-radius:50%;background:var(--cad-danger, #f85149)}.cad-nav-drawer__foot{flex:none;border-top:1px solid var(--cad-border);padding:10px 14px}.cad-nav-drawer__foot.is-empty{display:none}:host(.is-rail) .cad-nav-drawer__foot{padding:10px 0;display:flex;justify-content:center}:host-context(.cad-touch) .cad-nav-drawer__item{min-height:44px}@media(prefers-reduced-motion:reduce){.cad-nav-drawer__panel{transition:none}}\n"], dependencies: [{ kind: "component", type: CadIconComponent, selector: "cad-icon", inputs: ["name", "size", "strokeWidth", "label", "title", "spin", "filled", "flip", "rotate"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9551
9667
  }
9552
9668
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadNavDrawerComponent, decorators: [{
9553
9669
  type: Component,
@@ -9579,11 +9695,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
9579
9695
  </div>
9580
9696
 
9581
9697
  <ul class="cad-nav-drawer__list" role="list">
9582
- @for (it of items(); track $index) {
9698
+ @for (r of rows(); track $index) {
9699
+ @let it = r.item;
9583
9700
  @if (it.section) {
9584
9701
  <!-- En rail, un intertitre n'a plus de place : un filet le remplace. -->
9585
9702
  @if (rail()) {
9586
9703
  <li class="cad-nav-drawer__rule" role="separator"></li>
9704
+ } @else if (r.collapsible) {
9705
+ <li>
9706
+ <button
9707
+ type="button"
9708
+ class="cad-nav-drawer__section is-collapsible"
9709
+ [class.is-collapsed]="r.collapsed"
9710
+ [attr.aria-expanded]="!r.collapsed"
9711
+ (click)="toggleSection(r)"
9712
+ >
9713
+ <cad-icon name="chevron-down" [size]="12" class="cad-nav-drawer__chev"/>
9714
+ <span class="cad-nav-drawer__label">{{ it.label }}</span>
9715
+ <!-- Un compte DÉCLARÉ l'emporte sur le compte calculé : une valeur posée
9716
+ par l'hôte est une affirmation, pas un oubli à combler. -->
9717
+ <span class="cad-nav-drawer__badge">{{ it.badge ?? r.count }}</span>
9718
+ </button>
9719
+ </li>
9587
9720
  } @else {
9588
9721
  <li class="cad-nav-drawer__section">{{ it.label }}</li>
9589
9722
  }
@@ -9634,8 +9767,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
9634
9767
  <span class="cad-nav-drawer__dot" [attr.title]="it.badge"></span>
9635
9768
  }
9636
9769
  </ng-template>
9637
- `, styles: [":host{display:block}:host(.is-permanent){flex:none}.cad-nav-drawer__panel{display:flex;flex-direction:column;min-height:0;height:100%;width:var(--cad-nav-drawer-w, 240px);background:var(--cad-bg-subtle);border-right:1px solid var(--cad-border);transition:width var(--cad-dur) var(--cad-ease)}:host(.is-rail) .cad-nav-drawer__panel{width:var(--cad-nav-drawer-rail-w, 56px)}:host(.is-right) .cad-nav-drawer__panel{border-right:0;border-left:1px solid var(--cad-border)}:host(.is-temporary){position:fixed;inset:0 auto 0 0;z-index:var(--cad-z-overlay, 100002);pointer-events:none}:host(.is-temporary.is-right){inset:0 0 0 auto}:host(.is-temporary) .cad-nav-drawer__panel{pointer-events:auto;height:100%;box-shadow:var(--cad-shadow-lg, 0 12px 24px rgba(15,23,42,.18));transform:translate(-100%);transition:transform var(--cad-dur) var(--cad-ease)}:host(.is-temporary.is-right) .cad-nav-drawer__panel{transform:translate(100%)}:host(.is-temporary.is-open) .cad-nav-drawer__panel{transform:translate(0)}.cad-nav-drawer__scrim{position:fixed;inset:0;background:var(--cad-backdrop, rgba(2,6,23,.45));pointer-events:auto;z-index:-1}.cad-nav-drawer__head{display:flex;align-items:center;gap:9px;flex:none;padding:14px 14px 12px;border-bottom:1px solid var(--cad-border);min-height:48px;color:var(--cad-fg);font-size:13px}:host(.is-rail) .cad-nav-drawer__head{justify-content:center;padding:14px 0 12px}.cad-nav-drawer__head cad-icon{color:var(--cad-accent);flex:none}.cad-nav-drawer__list{list-style:none;margin:0;padding:8px 0;flex:1;min-height:0;overflow-y:auto}.cad-nav-drawer__section{padding:12px 14px 5px;font-size:10px;font-weight:500;letter-spacing:.07em;text-transform:uppercase;color:var(--cad-fg-muted)}.cad-nav-drawer__rule{height:1px;margin:8px 12px;background:var(--cad-border)}.cad-nav-drawer__item{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:10px;width:100%;cursor:pointer;padding:8px 14px;min-height:38px;color:var(--cad-fg-secondary);font:12.5px var(--cad-font);text-decoration:none}:host(.is-rail) .cad-nav-drawer__item{justify-content:center;padding:10px 0}.cad-nav-drawer__item:hover{background:var(--cad-hover);color:var(--cad-fg)}.cad-nav-drawer__item:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.cad-nav-drawer__item.is-active{background:var(--cad-accent-soft);color:var(--cad-accent);font-weight:500;box-shadow:inset 3px 0 0 var(--cad-accent)}:host(.is-right) .cad-nav-drawer__item.is-active{box-shadow:inset -3px 0 0 var(--cad-accent)}.cad-nav-drawer__item.is-disabled,.cad-nav-drawer__item:disabled{opacity:.45;cursor:not-allowed}.cad-nav-drawer__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cad-nav-drawer__badge{flex:none;font-size:10.5px;font-family:var(--cad-font-mono);border-radius:var(--cad-radius-pill);padding:1px 7px;background:var(--cad-elevated, var(--cad-border));color:var(--cad-fg-secondary)}.cad-nav-drawer__item.is-active .cad-nav-drawer__badge{background:var(--cad-accent);color:var(--cad-fg-on-accent)}.cad-nav-drawer__dot{position:absolute;margin:-14px 0 0 14px;width:7px;height:7px;border-radius:50%;background:var(--cad-danger, #f85149)}.cad-nav-drawer__foot{flex:none;border-top:1px solid var(--cad-border);padding:10px 14px}.cad-nav-drawer__foot.is-empty{display:none}:host(.is-rail) .cad-nav-drawer__foot{padding:10px 0;display:flex;justify-content:center}:host-context(.cad-touch) .cad-nav-drawer__item{min-height:44px}@media(prefers-reduced-motion:reduce){.cad-nav-drawer__panel{transition:none}}\n"] }]
9638
- }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], breakpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "breakpoint", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], closeOnNavigate: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnNavigate", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], dismissableScrim: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissableScrim", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], changed: [{ type: i0.Output, args: ["changed"] }], headerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadNavDrawerHeaderDirective), { isSignal: true }] }], footerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadNavDrawerFooterDirective), { isSignal: true }] }], onEscape: [{
9770
+ `, styles: [":host{display:block}:host(.is-permanent){flex:none}.cad-nav-drawer__panel{display:flex;flex-direction:column;min-height:0;height:100%;width:var(--cad-nav-drawer-w, 240px);background:var(--cad-bg-subtle);border-right:1px solid var(--cad-border);transition:width var(--cad-dur) var(--cad-ease)}:host(.is-rail) .cad-nav-drawer__panel{width:var(--cad-nav-drawer-rail-w, 56px)}:host(.is-right) .cad-nav-drawer__panel{border-right:0;border-left:1px solid var(--cad-border)}:host(.is-temporary){position:fixed;inset:0 auto 0 0;z-index:var(--cad-z-overlay, 100002);pointer-events:none}:host(.is-temporary.is-right){inset:0 0 0 auto}:host(.is-temporary) .cad-nav-drawer__panel{pointer-events:auto;height:100%;box-shadow:var(--cad-shadow-lg, 0 12px 24px rgba(15,23,42,.18));transform:translate(-100%);transition:transform var(--cad-dur) var(--cad-ease);padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}:host(.is-temporary.is-right) .cad-nav-drawer__panel{transform:translate(100%)}:host(.is-temporary.is-open) .cad-nav-drawer__panel{transform:translate(0)}.cad-nav-drawer__scrim{position:fixed;inset:0;background:var(--cad-backdrop, rgba(2,6,23,.45));pointer-events:auto;z-index:-1}.cad-nav-drawer__head{display:flex;align-items:center;gap:9px;flex:none;padding:14px 14px 12px;border-bottom:1px solid var(--cad-border);min-height:48px;color:var(--cad-fg);font-size:13px}:host(.is-rail) .cad-nav-drawer__head{justify-content:center;padding:14px 0 12px}.cad-nav-drawer__head cad-icon{color:var(--cad-accent);flex:none}.cad-nav-drawer__list{list-style:none;margin:0;padding:8px 0;flex:1;min-height:0;overflow-y:auto}.cad-nav-drawer__section{padding:12px 14px 5px;font-size:10px;font-weight:500;letter-spacing:.07em;text-transform:uppercase;color:var(--cad-fg-muted)}.cad-nav-drawer__rule{height:1px;margin:8px 12px;background:var(--cad-border)}.cad-nav-drawer__section.is-collapsible{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:8px;width:100%;cursor:pointer;padding:12px 14px 5px;font:500 10px var(--cad-font);letter-spacing:.07em;text-transform:uppercase;color:var(--cad-fg-muted)}.cad-nav-drawer__section.is-collapsible:hover{color:var(--cad-fg-secondary)}.cad-nav-drawer__section.is-collapsible:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.cad-nav-drawer__chev{flex:none;transition:transform var(--cad-dur) var(--cad-ease)}.cad-nav-drawer__section.is-collapsed .cad-nav-drawer__chev{transform:rotate(-90deg)}@media(prefers-reduced-motion:reduce){.cad-nav-drawer__chev{transition:none}}.cad-nav-drawer__item{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:10px;width:100%;cursor:pointer;padding:8px 14px;min-height:38px;color:var(--cad-fg-secondary);font:12.5px var(--cad-font);text-decoration:none}:host(.is-rail) .cad-nav-drawer__item{justify-content:center;padding:10px 0}.cad-nav-drawer__item:hover{background:var(--cad-hover);color:var(--cad-fg)}.cad-nav-drawer__item:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.cad-nav-drawer__item.is-active{background:var(--cad-accent-soft);color:var(--cad-accent);font-weight:500;box-shadow:inset 3px 0 0 var(--cad-accent)}:host(.is-right) .cad-nav-drawer__item.is-active{box-shadow:inset -3px 0 0 var(--cad-accent)}.cad-nav-drawer__item.is-disabled,.cad-nav-drawer__item:disabled{opacity:.45;cursor:not-allowed}.cad-nav-drawer__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cad-nav-drawer__badge{flex:none;font-size:10.5px;font-family:var(--cad-font-mono);border-radius:var(--cad-radius-pill);padding:1px 7px;background:var(--cad-elevated, var(--cad-border));color:var(--cad-fg-secondary)}.cad-nav-drawer__item.is-active .cad-nav-drawer__badge{background:var(--cad-accent);color:var(--cad-fg-on-accent)}.cad-nav-drawer__dot{position:absolute;margin:-14px 0 0 14px;width:7px;height:7px;border-radius:50%;background:var(--cad-danger, #f85149)}.cad-nav-drawer__foot{flex:none;border-top:1px solid var(--cad-border);padding:10px 14px}.cad-nav-drawer__foot.is-empty{display:none}:host(.is-rail) .cad-nav-drawer__foot{padding:10px 0;display:flex;justify-content:center}:host-context(.cad-touch) .cad-nav-drawer__item{min-height:44px}@media(prefers-reduced-motion:reduce){.cad-nav-drawer__panel{transition:none}}\n"] }]
9771
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], breakpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "breakpoint", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], closeOnNavigate: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnNavigate", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], dismissableScrim: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissableScrim", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], changed: [{ type: i0.Output, args: ["changed"] }], headerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadNavDrawerHeaderDirective), { isSignal: true }] }], footerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadNavDrawerFooterDirective), { isSignal: true }] }], onEscape: [{
9639
9772
  type: HostListener,
9640
9773
  args: ['document:keydown.escape']
9641
9774
  }] } });
@@ -12376,7 +12509,7 @@ class CadToastComponent {
12376
12509
  }
12377
12510
  </div>
12378
12511
  }
12379
- `, isInline: true, styles: [":host{position:fixed;display:flex;flex-direction:column;gap:8px;width:340px;max-width:calc(100% - 32px);pointer-events:none;font-family:var(--cad-font)}:host(.pos-top-right){top:16px;right:16px}:host(.pos-top-left){top:16px;left:16px}:host(.pos-bottom-right){bottom:16px;right:16px;flex-direction:column-reverse}:host(.pos-bottom-left){bottom:16px;left:16px;flex-direction:column-reverse}:host(.pos-top-center){top:16px;left:50%;transform:translate(-50%)}:host(.pos-bottom-center){bottom:16px;left:50%;transform:translate(-50%);flex-direction:column-reverse}.cad-toast__item{--_c: var(--cad-accent);position:relative;overflow:hidden;pointer-events:auto;display:flex;gap:10px;align-items:flex-start;padding:10px 12px;border-radius:10px;background:var(--cad-overlay);--cad-hover: var(--cad-overlay-hover);border:1px solid var(--cad-border);border-left:3px solid var(--_c);box-shadow:var(--cad-shadow-lg);color:var(--cad-fg);animation:cad-toast-in .18s var(--cad-ease)}.cad-toast__item.is-out{animation:cad-toast-out .16s var(--cad-ease) forwards}:host(.pos-top-left) .cad-toast__item,:host(.pos-bottom-left) .cad-toast__item{animation-name:cad-toast-in-l}.cad-toast__item.sev-success{--_c: var(--cad-success)}.cad-toast__item.sev-info{--_c: var(--cad-info)}.cad-toast__item.sev-warning{--_c: var(--cad-warning)}.cad-toast__item.sev-danger{--_c: var(--cad-danger)}.cad-toast__item.sev-secondary{--_c: var(--cad-fg-muted)}.cad-toast__item.sev-contrast{--_c: var(--cad-fg);background:var(--cad-fg);color:var(--cad-bg)}.cad-toast__icon{color:var(--_c);margin-top:1px;flex:none}.cad-toast__text{display:grid;gap:2px;flex:1;min-width:0}.cad-toast__text b{font-weight:600;font-size:13px}.cad-toast__text span{color:var(--cad-fg-secondary);font-size:12.5px;line-height:1.4}.sev-contrast .cad-toast__text span{color:inherit;opacity:.8}.cad-toast__actions{display:flex;gap:4px;margin-top:4px;margin-left:-8px}.cad-toast__close{border:0;background:transparent;color:var(--cad-fg-muted);cursor:pointer;padding:2px;border-radius:4px;display:grid;place-items:center;flex:none;margin:-2px -4px 0 0}.cad-toast__close:hover{color:var(--cad-fg);background:var(--cad-hover)}.cad-toast__progress{position:absolute;left:0;bottom:0;height:2px;width:100%;background:var(--_c);opacity:.5;transform-origin:left;animation:cad-toast-life linear forwards}@keyframes cad-toast-in{0%{opacity:0;transform:translate(24px)}to{opacity:1;transform:none}}@keyframes cad-toast-in-l{0%{opacity:0;transform:translate(-24px)}to{opacity:1;transform:none}}@keyframes cad-toast-out{to{opacity:0;transform:scale(.96)}}@keyframes cad-toast-life{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@media(prefers-reduced-motion:reduce){.cad-toast__item,.cad-toast__item.is-out{animation-duration:1ms}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: CadIconComponent, selector: "cad-icon", inputs: ["name", "size", "strokeWidth", "label", "title", "spin", "filled", "flip", "rotate"] }, { kind: "component", type: CadButtonComponent, selector: "button[cadButton], a[cadButton]", inputs: ["severity", "variant", "size", "loading", "disabled", "icon", "block", "value", "pressed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12512
+ `, isInline: true, styles: [":host{position:fixed;display:flex;flex-direction:column;gap:8px;width:340px;max-width:calc(100% - 32px);pointer-events:none;font-family:var(--cad-font)}:host{--_haut: calc(16px + max(var(--cad-shell-top, 0px), env(safe-area-inset-top, 0px))) ;--_bas: calc(16px + max(var(--cad-shell-bottom, 0px), env(safe-area-inset-bottom, 0px))) }:host(.pos-top-right){top:var(--_haut);right:16px}:host(.pos-top-left){top:var(--_haut);left:16px}:host(.pos-bottom-right){bottom:var(--_bas);right:16px;flex-direction:column-reverse}:host(.pos-bottom-left){bottom:var(--_bas);left:16px;flex-direction:column-reverse}:host(.pos-top-center){top:var(--_haut);left:50%;transform:translate(-50%)}:host(.pos-bottom-center){bottom:var(--_bas);left:50%;transform:translate(-50%);flex-direction:column-reverse}.cad-toast__item{--_c: var(--cad-accent);position:relative;overflow:hidden;pointer-events:auto;display:flex;gap:10px;align-items:flex-start;padding:10px 12px;border-radius:10px;background:var(--cad-overlay);--cad-hover: var(--cad-overlay-hover);border:1px solid var(--cad-border);border-left:3px solid var(--_c);box-shadow:var(--cad-shadow-lg);color:var(--cad-fg);animation:cad-toast-in .18s var(--cad-ease)}.cad-toast__item.is-out{animation:cad-toast-out .16s var(--cad-ease) forwards}:host(.pos-top-left) .cad-toast__item,:host(.pos-bottom-left) .cad-toast__item{animation-name:cad-toast-in-l}.cad-toast__item.sev-success{--_c: var(--cad-success)}.cad-toast__item.sev-info{--_c: var(--cad-info)}.cad-toast__item.sev-warning{--_c: var(--cad-warning)}.cad-toast__item.sev-danger{--_c: var(--cad-danger)}.cad-toast__item.sev-secondary{--_c: var(--cad-fg-muted)}.cad-toast__item.sev-contrast{--_c: var(--cad-fg);background:var(--cad-fg);color:var(--cad-bg)}.cad-toast__icon{color:var(--_c);margin-top:1px;flex:none}.cad-toast__text{display:grid;gap:2px;flex:1;min-width:0}.cad-toast__text b{font-weight:600;font-size:13px}.cad-toast__text span{color:var(--cad-fg-secondary);font-size:12.5px;line-height:1.4}.sev-contrast .cad-toast__text span{color:inherit;opacity:.8}.cad-toast__actions{display:flex;gap:4px;margin-top:4px;margin-left:-8px}.cad-toast__close{border:0;background:transparent;color:var(--cad-fg-muted);cursor:pointer;padding:2px;border-radius:4px;display:grid;place-items:center;flex:none;margin:-2px -4px 0 0}.cad-toast__close:hover{color:var(--cad-fg);background:var(--cad-hover)}.cad-toast__progress{position:absolute;left:0;bottom:0;height:2px;width:100%;background:var(--_c);opacity:.5;transform-origin:left;animation:cad-toast-life linear forwards}@keyframes cad-toast-in{0%{opacity:0;transform:translate(24px)}to{opacity:1;transform:none}}@keyframes cad-toast-in-l{0%{opacity:0;transform:translate(-24px)}to{opacity:1;transform:none}}@keyframes cad-toast-out{to{opacity:0;transform:scale(.96)}}@keyframes cad-toast-life{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@media(prefers-reduced-motion:reduce){.cad-toast__item,.cad-toast__item.is-out{animation-duration:1ms}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: CadIconComponent, selector: "cad-icon", inputs: ["name", "size", "strokeWidth", "label", "title", "spin", "filled", "flip", "rotate"] }, { kind: "component", type: CadButtonComponent, selector: "button[cadButton], a[cadButton]", inputs: ["severity", "variant", "size", "loading", "disabled", "icon", "block", "value", "pressed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12380
12513
  }
12381
12514
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadToastComponent, decorators: [{
12382
12515
  type: Component,
@@ -12409,7 +12542,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
12409
12542
  }
12410
12543
  </div>
12411
12544
  }
12412
- `, styles: [":host{position:fixed;display:flex;flex-direction:column;gap:8px;width:340px;max-width:calc(100% - 32px);pointer-events:none;font-family:var(--cad-font)}:host(.pos-top-right){top:16px;right:16px}:host(.pos-top-left){top:16px;left:16px}:host(.pos-bottom-right){bottom:16px;right:16px;flex-direction:column-reverse}:host(.pos-bottom-left){bottom:16px;left:16px;flex-direction:column-reverse}:host(.pos-top-center){top:16px;left:50%;transform:translate(-50%)}:host(.pos-bottom-center){bottom:16px;left:50%;transform:translate(-50%);flex-direction:column-reverse}.cad-toast__item{--_c: var(--cad-accent);position:relative;overflow:hidden;pointer-events:auto;display:flex;gap:10px;align-items:flex-start;padding:10px 12px;border-radius:10px;background:var(--cad-overlay);--cad-hover: var(--cad-overlay-hover);border:1px solid var(--cad-border);border-left:3px solid var(--_c);box-shadow:var(--cad-shadow-lg);color:var(--cad-fg);animation:cad-toast-in .18s var(--cad-ease)}.cad-toast__item.is-out{animation:cad-toast-out .16s var(--cad-ease) forwards}:host(.pos-top-left) .cad-toast__item,:host(.pos-bottom-left) .cad-toast__item{animation-name:cad-toast-in-l}.cad-toast__item.sev-success{--_c: var(--cad-success)}.cad-toast__item.sev-info{--_c: var(--cad-info)}.cad-toast__item.sev-warning{--_c: var(--cad-warning)}.cad-toast__item.sev-danger{--_c: var(--cad-danger)}.cad-toast__item.sev-secondary{--_c: var(--cad-fg-muted)}.cad-toast__item.sev-contrast{--_c: var(--cad-fg);background:var(--cad-fg);color:var(--cad-bg)}.cad-toast__icon{color:var(--_c);margin-top:1px;flex:none}.cad-toast__text{display:grid;gap:2px;flex:1;min-width:0}.cad-toast__text b{font-weight:600;font-size:13px}.cad-toast__text span{color:var(--cad-fg-secondary);font-size:12.5px;line-height:1.4}.sev-contrast .cad-toast__text span{color:inherit;opacity:.8}.cad-toast__actions{display:flex;gap:4px;margin-top:4px;margin-left:-8px}.cad-toast__close{border:0;background:transparent;color:var(--cad-fg-muted);cursor:pointer;padding:2px;border-radius:4px;display:grid;place-items:center;flex:none;margin:-2px -4px 0 0}.cad-toast__close:hover{color:var(--cad-fg);background:var(--cad-hover)}.cad-toast__progress{position:absolute;left:0;bottom:0;height:2px;width:100%;background:var(--_c);opacity:.5;transform-origin:left;animation:cad-toast-life linear forwards}@keyframes cad-toast-in{0%{opacity:0;transform:translate(24px)}to{opacity:1;transform:none}}@keyframes cad-toast-in-l{0%{opacity:0;transform:translate(-24px)}to{opacity:1;transform:none}}@keyframes cad-toast-out{to{opacity:0;transform:scale(.96)}}@keyframes cad-toast-life{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@media(prefers-reduced-motion:reduce){.cad-toast__item,.cad-toast__item.is-out{animation-duration:1ms}}\n"] }]
12545
+ `, styles: [":host{position:fixed;display:flex;flex-direction:column;gap:8px;width:340px;max-width:calc(100% - 32px);pointer-events:none;font-family:var(--cad-font)}:host{--_haut: calc(16px + max(var(--cad-shell-top, 0px), env(safe-area-inset-top, 0px))) ;--_bas: calc(16px + max(var(--cad-shell-bottom, 0px), env(safe-area-inset-bottom, 0px))) }:host(.pos-top-right){top:var(--_haut);right:16px}:host(.pos-top-left){top:var(--_haut);left:16px}:host(.pos-bottom-right){bottom:var(--_bas);right:16px;flex-direction:column-reverse}:host(.pos-bottom-left){bottom:var(--_bas);left:16px;flex-direction:column-reverse}:host(.pos-top-center){top:var(--_haut);left:50%;transform:translate(-50%)}:host(.pos-bottom-center){bottom:var(--_bas);left:50%;transform:translate(-50%);flex-direction:column-reverse}.cad-toast__item{--_c: var(--cad-accent);position:relative;overflow:hidden;pointer-events:auto;display:flex;gap:10px;align-items:flex-start;padding:10px 12px;border-radius:10px;background:var(--cad-overlay);--cad-hover: var(--cad-overlay-hover);border:1px solid var(--cad-border);border-left:3px solid var(--_c);box-shadow:var(--cad-shadow-lg);color:var(--cad-fg);animation:cad-toast-in .18s var(--cad-ease)}.cad-toast__item.is-out{animation:cad-toast-out .16s var(--cad-ease) forwards}:host(.pos-top-left) .cad-toast__item,:host(.pos-bottom-left) .cad-toast__item{animation-name:cad-toast-in-l}.cad-toast__item.sev-success{--_c: var(--cad-success)}.cad-toast__item.sev-info{--_c: var(--cad-info)}.cad-toast__item.sev-warning{--_c: var(--cad-warning)}.cad-toast__item.sev-danger{--_c: var(--cad-danger)}.cad-toast__item.sev-secondary{--_c: var(--cad-fg-muted)}.cad-toast__item.sev-contrast{--_c: var(--cad-fg);background:var(--cad-fg);color:var(--cad-bg)}.cad-toast__icon{color:var(--_c);margin-top:1px;flex:none}.cad-toast__text{display:grid;gap:2px;flex:1;min-width:0}.cad-toast__text b{font-weight:600;font-size:13px}.cad-toast__text span{color:var(--cad-fg-secondary);font-size:12.5px;line-height:1.4}.sev-contrast .cad-toast__text span{color:inherit;opacity:.8}.cad-toast__actions{display:flex;gap:4px;margin-top:4px;margin-left:-8px}.cad-toast__close{border:0;background:transparent;color:var(--cad-fg-muted);cursor:pointer;padding:2px;border-radius:4px;display:grid;place-items:center;flex:none;margin:-2px -4px 0 0}.cad-toast__close:hover{color:var(--cad-fg);background:var(--cad-hover)}.cad-toast__progress{position:absolute;left:0;bottom:0;height:2px;width:100%;background:var(--_c);opacity:.5;transform-origin:left;animation:cad-toast-life linear forwards}@keyframes cad-toast-in{0%{opacity:0;transform:translate(24px)}to{opacity:1;transform:none}}@keyframes cad-toast-in-l{0%{opacity:0;transform:translate(-24px)}to{opacity:1;transform:none}}@keyframes cad-toast-out{to{opacity:0;transform:scale(.96)}}@keyframes cad-toast-life{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@media(prefers-reduced-motion:reduce){.cad-toast__item,.cad-toast__item.is-out{animation-duration:1ms}}\n"] }]
12413
12546
  }], ctorParameters: () => [], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: false }] }], life: [{ type: i0.Input, args: [{ isSignal: true, alias: "life", required: false }] }], maxVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisible", required: false }] }], preventDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "preventDuplicates", required: false }] }], showProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "showProgress", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], clicked: [{ type: i0.Output, args: ["clicked"] }], itemTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadToastItemDirective), { isSignal: true }] }] } });
12414
12547
 
12415
12548
  /**
@@ -18818,6 +18951,7 @@ function cadNormalizePlan(plan, options = {}) {
18818
18951
  start,
18819
18952
  end,
18820
18953
  milestone,
18954
+ allDay: !!t.allDay,
18821
18955
  bucket: t.bucket ?? null,
18822
18956
  rank: cadIsRank(t.rank) ? t.rank : '',
18823
18957
  assignments,
@@ -19017,6 +19151,552 @@ function cadFindLinkCycle(links) {
19017
19151
  /** Tâches qu'aucune vue temporelle ne sait placer — la zone « non planifié », jamais le silence. */
19018
19152
  const cadUnscheduled = (plan) => plan.tasks.filter(t => !t.scheduled);
19019
19153
 
19154
+ /**
19155
+ * Dépliage des séries récurrentes — EN LECTURE SEULE.
19156
+ *
19157
+ * CE QUE CE FICHIER FAIT, ET CE QU'IL NE FAIT PAS. Il projette une règle en occurrences pour
19158
+ * qu'une vue puisse les afficher. Il n'écrit rien, ne modifie aucune série, et ne connaît pas
19159
+ * la sémantique d'édition à trois branches (« cette occurrence / celle-ci et les suivantes /
19160
+ * toutes »), qui reste entière dans son propre lot.
19161
+ *
19162
+ * C'est un choix, pas un raccourci : une récurrence à moitié faite PERD DES MODIFICATIONS SANS
19163
+ * LE DIRE, ce qui est pire que pas de récurrence. Déplier pour afficher n'écrit rien, donc ne
19164
+ * peut rien perdre. On livre donc la moitié qui est sûre.
19165
+ *
19166
+ * SOUS-ENSEMBLE RRULE ASSUMÉ (RFC 5545) : `FREQ` (DAILY, WEEKLY, MONTHLY, YEARLY), `INTERVAL`,
19167
+ * `BYDAY` (en hebdomadaire), `BYMONTHDAY` (en mensuel), `COUNT`, `UNTIL`.
19168
+ * EXCLUS ET DOCUMENTÉS : `BYSETPOS` composé, `BYYEARDAY`, `BYWEEKNO`, `VTIMEZONE`, et les
19169
+ * préfixes ordinaux de `BYDAY` (`2MO` = deuxième lundi). Une règle qui en contient est rendue
19170
+ * telle qu'elle peut l'être, et l'anomalie est signalée — jamais silencieusement approximée.
19171
+ */
19172
+ /** Jours iCalendar, dans l'ordre de `Date.getDay()` : 0 = dimanche. */
19173
+ const JOURS_ICAL = { SU: 0, MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6 };
19174
+ /** Parties que ce moteur sait lire. Tout le reste atterrit dans `ignore`. */
19175
+ const CONNUES = new Set(['FREQ', 'INTERVAL', 'BYDAY', 'BYMONTHDAY', 'COUNT', 'UNTIL', 'WKST']);
19176
+ /**
19177
+ * Analyse une chaîne `RRULE`. Rend `null` si elle est inexploitable — jamais une règle
19178
+ * approximative : mieux vaut un événement unique qu'une série fausse.
19179
+ */
19180
+ function cadParseRRule(entree) {
19181
+ if (!entree)
19182
+ return null;
19183
+ const brut = entree.replace(/^RRULE:/i, '').trim();
19184
+ if (!brut)
19185
+ return null;
19186
+ const parts = new Map();
19187
+ const ignore = [];
19188
+ for (const morceau of brut.split(';')) {
19189
+ const i = morceau.indexOf('=');
19190
+ if (i < 0)
19191
+ continue;
19192
+ const cle = morceau.slice(0, i).trim().toUpperCase();
19193
+ const val = morceau.slice(i + 1).trim();
19194
+ if (!CONNUES.has(cle)) {
19195
+ ignore.push(cle);
19196
+ continue;
19197
+ }
19198
+ parts.set(cle, val);
19199
+ }
19200
+ const freq = (parts.get('FREQ') || '').toUpperCase();
19201
+ if (freq !== 'DAILY' && freq !== 'WEEKLY' && freq !== 'MONTHLY' && freq !== 'YEARLY')
19202
+ return null;
19203
+ const interval = Math.max(1, Number(parts.get('INTERVAL') || 1) || 1);
19204
+ let byday = null;
19205
+ const bydayBrut = parts.get('BYDAY');
19206
+ if (bydayBrut) {
19207
+ const jours = [];
19208
+ for (const j of bydayBrut.split(',')) {
19209
+ const net = j.trim().toUpperCase();
19210
+ // « 2MO » (deuxième lundi) n'est pas géré : on le signale plutôt que de le lire « MO ».
19211
+ if (/^[+-]?\d/.test(net)) {
19212
+ ignore.push('BYDAY:' + net);
19213
+ continue;
19214
+ }
19215
+ const n = JOURS_ICAL[net];
19216
+ if (n !== undefined)
19217
+ jours.push(n);
19218
+ }
19219
+ byday = jours.length ? jours.sort((a, b) => a - b) : null;
19220
+ }
19221
+ let bymonthday = null;
19222
+ const bmdBrut = parts.get('BYMONTHDAY');
19223
+ if (bmdBrut) {
19224
+ const q = bmdBrut.split(',').map((x) => Number(x.trim())).filter((x) => Number.isInteger(x) && x >= 1 && x <= 31);
19225
+ bymonthday = q.length ? q : null;
19226
+ }
19227
+ const count = parts.has('COUNT') ? Math.max(0, Number(parts.get('COUNT')) || 0) : null;
19228
+ const until = parts.has('UNTIL') ? cadParseUntil(parts.get('UNTIL')) : null;
19229
+ return { freq, interval, byday, bymonthday, count, until, ignore };
19230
+ }
19231
+ /** `UNTIL` en forme iCalendar (`20260930T235959Z`) ou en ISO. */
19232
+ function cadParseUntil(v) {
19233
+ const m = /^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2})Z?)?$/.exec(v.trim());
19234
+ if (m) {
19235
+ const d = new Date(+m[1], +m[2] - 1, +m[3], +(m[4] ?? 23), +(m[5] ?? 59), +(m[6] ?? 59));
19236
+ return Number.isNaN(d.getTime()) ? null : d;
19237
+ }
19238
+ const d = new Date(v);
19239
+ return Number.isNaN(d.getTime()) ? null : d;
19240
+ }
19241
+ /** Garde-fou : une règle mal formée ne doit pas produire une boucle infinie mais un message. */
19242
+ const PLAFOND = 750;
19243
+ const JOUR_MS = 86_400_000;
19244
+ function memeJour(a, b) {
19245
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
19246
+ }
19247
+ function cle(d) {
19248
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
19249
+ }
19250
+ function horodatage(d, allDay) {
19251
+ return allDay ? cle(d) : `${cle(d)}T${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
19252
+ }
19253
+ function avance(d, jours) {
19254
+ const n = new Date(d);
19255
+ n.setDate(n.getDate() + jours);
19256
+ return n;
19257
+ }
19258
+ /**
19259
+ * Déplie les tâches sur la fenêtre `[du, au]`.
19260
+ *
19261
+ * TROIS RÈGLES, dans cet ordre :
19262
+ * ① une tâche sans règle donne une occurrence unique, si elle croise la fenêtre ;
19263
+ * ② une tâche avec règle est projetée, `exdates` retirées ;
19264
+ * ③ une occurrence MODIFIÉE (`masterId` + `recurrenceId`) REMPLACE celle que la règle aurait
19265
+ * produite à cette date — convention iCalendar. Sans ce remplacement on afficherait les
19266
+ * deux, et l'utilisateur verrait sa modification en double au lieu de la voir appliquée.
19267
+ */
19268
+ function cadExpandOccurrences(taches, du, au) {
19269
+ const occurrences = [];
19270
+ const issues = [];
19271
+ // ③ d'abord : on répertorie les exceptions pour savoir quoi remplacer.
19272
+ const exceptions = new Map();
19273
+ for (const t of taches) {
19274
+ if (t.masterId && t.recurrenceId)
19275
+ exceptions.set(`${t.masterId}@${cle(t.recurrenceId)}`, t);
19276
+ }
19277
+ for (const t of taches) {
19278
+ // Une exception est rendue par la série qu'elle corrige, pas deux fois.
19279
+ if (t.masterId && t.recurrenceId)
19280
+ continue;
19281
+ if (!t.start) {
19282
+ if (t.rrule)
19283
+ issues.push({ taskId: t.id, code: 'sans-date' });
19284
+ continue;
19285
+ }
19286
+ const duree = t.end ? Math.max(0, t.end.getTime() - t.start.getTime()) : 0;
19287
+ if (!t.rrule) {
19288
+ if (croise(t.start, t.end ?? t.start, du, au)) {
19289
+ occurrences.push(fabrique(t, t.start, duree, false, false));
19290
+ }
19291
+ continue;
19292
+ }
19293
+ const regle = cadParseRRule(t.rrule);
19294
+ if (!regle) {
19295
+ issues.push({ taskId: t.id, code: 'rrule-illisible', detail: t.rrule });
19296
+ // Une règle illisible ne fait pas disparaître l'événement : il reste, seul.
19297
+ if (croise(t.start, t.end ?? t.start, du, au))
19298
+ occurrences.push(fabrique(t, t.start, duree, false, false));
19299
+ continue;
19300
+ }
19301
+ if (regle.ignore.length) {
19302
+ issues.push({ taskId: t.id, code: 'rrule-partielle', detail: regle.ignore.join(', ') });
19303
+ }
19304
+ const exclues = new Set(t.exdates.map((x) => cle(new Date(x))));
19305
+ let rendues = 0;
19306
+ let tours = 0;
19307
+ let curseur = new Date(t.start);
19308
+ while (tours++ < PLAFOND) {
19309
+ if (regle.count !== null && rendues >= regle.count)
19310
+ break;
19311
+ if (curseur.getTime() > au.getTime())
19312
+ break;
19313
+ if (regle.until && curseur.getTime() > regle.until.getTime())
19314
+ break;
19315
+ for (const debut of datesDuTour(curseur, regle, t.start)) {
19316
+ if (regle.count !== null && rendues >= regle.count)
19317
+ break;
19318
+ if (debut.getTime() < t.start.getTime())
19319
+ continue;
19320
+ if (regle.until && debut.getTime() > regle.until.getTime())
19321
+ break;
19322
+ rendues++;
19323
+ if (exclues.has(cle(debut)))
19324
+ continue;
19325
+ if (debut.getTime() > au.getTime())
19326
+ continue;
19327
+ const remplacement = exceptions.get(`${t.id}@${cle(debut)}`);
19328
+ if (remplacement) {
19329
+ if (remplacement.start && croise(remplacement.start, remplacement.end ?? remplacement.start, du, au)) {
19330
+ const d2 = remplacement.end ? remplacement.end.getTime() - remplacement.start.getTime() : duree;
19331
+ occurrences.push(fabrique(remplacement, remplacement.start, d2, true, true));
19332
+ }
19333
+ continue;
19334
+ }
19335
+ const fin = new Date(debut.getTime() + duree);
19336
+ if (croise(debut, fin, du, au))
19337
+ occurrences.push(fabrique(t, debut, duree, true, false));
19338
+ }
19339
+ curseur = tourSuivant(curseur, regle);
19340
+ }
19341
+ if (tours >= PLAFOND)
19342
+ issues.push({ taskId: t.id, code: 'plafond-atteint', detail: String(PLAFOND) });
19343
+ }
19344
+ occurrences.sort((a, b) => a.start.getTime() - b.start.getTime() || a.id.localeCompare(b.id));
19345
+ return { occurrences, issues };
19346
+ }
19347
+ function croise(debut, fin, du, au) {
19348
+ return fin.getTime() >= du.getTime() && debut.getTime() <= au.getTime();
19349
+ }
19350
+ function fabrique(t, debut, duree, recurring, overridden) {
19351
+ const start = new Date(debut);
19352
+ const end = new Date(debut.getTime() + duree);
19353
+ return {
19354
+ id: `${t.masterId ?? t.id}@${horodatage(start, t.allDay)}`,
19355
+ taskId: t.id,
19356
+ start,
19357
+ end,
19358
+ allDay: t.allDay,
19359
+ recurring,
19360
+ overridden,
19361
+ task: t,
19362
+ };
19363
+ }
19364
+ /** Les dates produites par UN tour de la règle (une semaine, un mois, un jour…). */
19365
+ function datesDuTour(curseur, r, ancre) {
19366
+ const h = ancre.getHours(), m = ancre.getMinutes();
19367
+ const pose = (d) => { const n = new Date(d); n.setHours(h, m, 0, 0); return n; };
19368
+ if (r.freq === 'WEEKLY' && r.byday) {
19369
+ // Le curseur est posé sur le début de semaine du tour ; on émet les jours visés.
19370
+ const lundi = debutSemaine(curseur);
19371
+ return r.byday.map((j) => pose(avance(lundi, (j + 6) % 7)));
19372
+ }
19373
+ if (r.freq === 'MONTHLY' && r.bymonthday) {
19374
+ return r.bymonthday
19375
+ .map((q) => {
19376
+ const d = new Date(curseur.getFullYear(), curseur.getMonth(), q);
19377
+ // Le 31 d'un mois de 30 jours n'existe pas : on ne le décale pas en douce, on le saute.
19378
+ return d.getMonth() === curseur.getMonth() ? pose(d) : null;
19379
+ })
19380
+ .filter((d) => d !== null)
19381
+ .sort((a, b) => a.getTime() - b.getTime());
19382
+ }
19383
+ return [pose(curseur)];
19384
+ }
19385
+ function tourSuivant(curseur, r) {
19386
+ const n = new Date(curseur);
19387
+ if (r.freq === 'DAILY')
19388
+ n.setDate(n.getDate() + r.interval);
19389
+ else if (r.freq === 'WEEKLY')
19390
+ n.setDate(n.getDate() + 7 * r.interval);
19391
+ else if (r.freq === 'MONTHLY') {
19392
+ // On repart du 1er : sans ça, un 31 janvier + 1 mois donne le 3 mars et la série dérive
19393
+ // de mois en mois. Le quantième visé est réappliqué par datesDuTour.
19394
+ const j = r.bymonthday ? 1 : n.getDate();
19395
+ n.setDate(1);
19396
+ n.setMonth(n.getMonth() + r.interval);
19397
+ if (!r.bymonthday)
19398
+ n.setDate(Math.min(j, joursDansMois(n.getFullYear(), n.getMonth())));
19399
+ }
19400
+ else {
19401
+ n.setFullYear(n.getFullYear() + r.interval);
19402
+ }
19403
+ return n;
19404
+ }
19405
+ function joursDansMois(annee, mois) {
19406
+ return new Date(annee, mois + 1, 0).getDate();
19407
+ }
19408
+ /** Lundi de la semaine de `d`. */
19409
+ function debutSemaine(d) {
19410
+ const n = new Date(d);
19411
+ n.setHours(0, 0, 0, 0);
19412
+ n.setDate(n.getDate() - ((n.getDay() + 6) % 7));
19413
+ return n;
19414
+ }
19415
+ const CAD_RECURRENCE_INTERNE = { debutSemaine, joursDansMois, memeJour, JOUR_MS };
19416
+
19417
+ /**
19418
+ * Mise en page d'un calendrier — logique PURE, sans DOM, donc testable seule.
19419
+ *
19420
+ * Trois calculs, et ce sont les trois endroits où un calendrier se trompe :
19421
+ * ① la grille du mois (combien de rangées, quels jours débordent) ;
19422
+ * ② les BANDEAUX multi-jours, continus à travers une rangée de semaine ;
19423
+ * ③ le placement côte à côte des événements horodatés d'une journée.
19424
+ *
19425
+ * BORNE DE FIN : dans cette bibliothèque, `end` est INCLUSIVE pour un événement journée
19426
+ * entière — « congés du 21 au 25 » couvre le 25. La RFC 5545 la veut exclusive (DTEND au 26).
19427
+ * On diverge SCIEMMENT : c'est ce qu'un humain saisit, c'est ce que font déjà le gantt et le
19428
+ * scheduler pour une tâche qui « finit le 25 », et mélanger les deux conventions en silence
19429
+ * est la première cause d'erreur d'un jour dans un calendrier. Un import iCalendar devra donc
19430
+ * retrancher un jour à DTEND — c'est le travail de l'import, pas celui de la vue.
19431
+ */
19432
+ /** Numéro de jour absolu, insensible au fuseau et à l'heure d'été. */
19433
+ function cadNumeroJour(d) {
19434
+ return Math.round(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / 86_400_000);
19435
+ }
19436
+ /** Minuit local du jour de `d`. */
19437
+ function cadMinuit(d) {
19438
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
19439
+ }
19440
+ /** Début de la semaine contenant `d`. `premierJour` : 1 = lundi (défaut), 0 = dimanche. */
19441
+ function cadDebutSemaine(d, premierJour = 1) {
19442
+ const n = cadMinuit(d);
19443
+ const decalage = (n.getDay() - premierJour + 7) % 7;
19444
+ n.setDate(n.getDate() - decalage);
19445
+ return n;
19446
+ }
19447
+ /**
19448
+ * Grille du mois contenant `ancre`.
19449
+ *
19450
+ * On rend 5 OU 6 rangées, selon ce que le mois demande — jamais un nombre fixe. Forcer 6
19451
+ * rangées laisse une semaine entière vide neuf mois sur douze ; en forcer 5 tronque les mois
19452
+ * de 31 jours qui commencent un samedi. La hauteur de la vue s'adapte, pas la vérité.
19453
+ */
19454
+ function cadMonthGrid(ancre, premierJour = 1) {
19455
+ const annee = ancre.getFullYear();
19456
+ const mois = ancre.getMonth();
19457
+ const premier = new Date(annee, mois, 1);
19458
+ const debut = cadDebutSemaine(premier, premierJour);
19459
+ const dernier = new Date(annee, mois + 1, 0);
19460
+ const finGrille = cadDebutSemaine(dernier, premierJour);
19461
+ const rangees = Math.round((cadNumeroJour(finGrille) - cadNumeroJour(debut)) / 7) + 1;
19462
+ const semaines = [];
19463
+ for (let r = 0; r < rangees; r++) {
19464
+ const semaine = [];
19465
+ for (let j = 0; j < 7; j++) {
19466
+ const d = new Date(debut);
19467
+ d.setDate(d.getDate() + r * 7 + j);
19468
+ semaine.push(d);
19469
+ }
19470
+ semaines.push(semaine);
19471
+ }
19472
+ return { debut, semaines, mois, annee };
19473
+ }
19474
+ /** Une occurrence va au bandeau si elle est journée entière OU si elle franchit un minuit. */
19475
+ function cadEstBandeau(o) {
19476
+ return o.allDay || cadNumeroJour(o.start) !== cadNumeroJour(o.end);
19477
+ }
19478
+ /**
19479
+ * Range les bandeaux d'une rangée de semaine en voies.
19480
+ *
19481
+ * Le tri place les plus LONGS en haut : sans lui, un événement d'un jour posé en voie 0 coupe
19482
+ * en deux la place d'un événement de cinq jours, qui doit alors descendre — et la rangée
19483
+ * devient un escalier illisible.
19484
+ */
19485
+ function cadWeekBands(occurrences, jours) {
19486
+ if (!jours.length)
19487
+ return { bandes: [], voies: 0 };
19488
+ const j0 = cadNumeroJour(jours[0]);
19489
+ const j1 = cadNumeroJour(jours[jours.length - 1]);
19490
+ const candidats = occurrences
19491
+ .filter(cadEstBandeau)
19492
+ .map((o) => {
19493
+ const d = cadNumeroJour(o.start);
19494
+ const f = cadNumeroJour(o.end);
19495
+ return { o, d, f };
19496
+ })
19497
+ .filter(({ d, f }) => f >= j0 && d <= j1)
19498
+ .sort((a, b) => (b.f - b.d) - (a.f - a.d) || a.d - b.d || a.o.id.localeCompare(b.o.id));
19499
+ const occupe = [];
19500
+ const bandes = candidats.map(({ o, d, f }) => {
19501
+ const colDebut = Math.max(0, d - j0);
19502
+ const colFin = Math.min(jours.length - 1, f - j0);
19503
+ let voie = 0;
19504
+ while (occupe[voie]?.some(([a, b]) => !(b < colDebut || a > colFin)))
19505
+ voie++;
19506
+ (occupe[voie] ??= []).push([colDebut, colFin]);
19507
+ return { occurrence: o, colDebut, colFin, voie, continueAvant: d < j0, continueApres: f > j1 };
19508
+ });
19509
+ return { bandes, voies: occupe.length };
19510
+ }
19511
+ /**
19512
+ * Place les événements horodatés d'une journée, côte à côte quand ils se chevauchent.
19513
+ *
19514
+ * Le nombre de voies est celui du GROUPE de chevauchement, pas de la journée : deux réunions
19515
+ * qui se chevauchent le matin ne doivent pas rétrécir de moitié une réunion isolée de
19516
+ * l'après-midi. C'est l'erreur la plus courante des calendriers faits à la main.
19517
+ *
19518
+ * `plageDebut` / `plageFin` en minutes depuis minuit. Un événement hors plage est CONSERVÉ et
19519
+ * ramené au bord : le faire disparaître serait le mensonge le plus grave qu'une vue puisse
19520
+ * commettre.
19521
+ */
19522
+ function cadPackDay(occurrences, plageDebut = 7 * 60, plageFin = 20 * 60) {
19523
+ const plage = Math.max(1, plageFin - plageDebut);
19524
+ const items = occurrences
19525
+ .filter((o) => !cadEstBandeau(o))
19526
+ .map((o) => {
19527
+ const d = o.start.getHours() * 60 + o.start.getMinutes();
19528
+ const f = Math.max(d + 15, o.end.getHours() * 60 + o.end.getMinutes());
19529
+ return { o, d, f };
19530
+ })
19531
+ .sort((a, b) => a.d - b.d || b.f - a.f);
19532
+ // ① voies par balayage, première voie libre.
19533
+ const finVoie = [];
19534
+ const places = items.map((it) => {
19535
+ let voie = finVoie.findIndex((fin) => fin <= it.d);
19536
+ if (voie === -1) {
19537
+ voie = finVoie.length;
19538
+ finVoie.push(it.f);
19539
+ }
19540
+ else
19541
+ finVoie[voie] = it.f;
19542
+ return { ...it, voie };
19543
+ });
19544
+ // ② groupes de chevauchement : on ne partage la largeur qu'entre voisins réels.
19545
+ const slots = [];
19546
+ let i = 0;
19547
+ while (i < places.length) {
19548
+ let j = i + 1;
19549
+ let finGroupe = places[i].f;
19550
+ while (j < places.length && places[j].d < finGroupe) {
19551
+ finGroupe = Math.max(finGroupe, places[j].f);
19552
+ j++;
19553
+ }
19554
+ const groupe = places.slice(i, j);
19555
+ const voies = Math.max(...groupe.map((g) => g.voie)) + 1;
19556
+ for (const g of groupe) {
19557
+ const haut = (Math.max(g.d, plageDebut) - plageDebut) / plage;
19558
+ const bas = (Math.min(g.f, plageFin) - plageDebut) / plage;
19559
+ slots.push({
19560
+ occurrence: g.o,
19561
+ haut: Math.max(0, Math.min(1, haut)),
19562
+ hauteur: Math.max(0.01, Math.min(1, bas) - Math.max(0, haut)),
19563
+ voie: g.voie,
19564
+ voies,
19565
+ });
19566
+ }
19567
+ i = j;
19568
+ }
19569
+ return slots;
19570
+ }
19571
+
19572
+ /**
19573
+ * Projection d'un geste d'édition — logique PURE, sans DOM, donc testable seule.
19574
+ *
19575
+ * Un geste ne produit jamais une écriture : il produit une PROPOSITION de bornes, que le
19576
+ * composant émet en intention. C'est ce fichier qui répond à « l'utilisateur a bougé de 43
19577
+ * pixels vers le bas et de 1,2 colonne : qu'est-ce que ça veut dire en dates ? ».
19578
+ *
19579
+ * L'AIMANTAGE EST OBLIGATOIRE, pas cosmétique. Sans lui on crée des réunions de 9 h 07 à
19580
+ * 10 h 23 : personne n'a voulu ça, et la donnée devient impossible à relire.
19581
+ */
19582
+ const MIN = 60_000;
19583
+ function aimante(minutes, pas) {
19584
+ return Math.round(minutes / Math.max(1, pas)) * Math.max(1, pas);
19585
+ }
19586
+ function minutesDansJour(d) {
19587
+ return d.getHours() * 60 + d.getMinutes();
19588
+ }
19589
+ function poseMinutes(jour, minutes) {
19590
+ const n = new Date(jour.getFullYear(), jour.getMonth(), jour.getDate());
19591
+ n.setMinutes(minutes);
19592
+ return n;
19593
+ }
19594
+ function decaleJours(d, jours) {
19595
+ const n = new Date(d);
19596
+ n.setDate(n.getDate() + jours);
19597
+ return n;
19598
+ }
19599
+ /**
19600
+ * Projette un geste en nouvelles bornes.
19601
+ *
19602
+ * `dx` / `dy` sont les déplacements du pointeur en pixels depuis le début du geste.
19603
+ *
19604
+ * TROIS INVARIANTS, et chacun corrige une bêtise classique :
19605
+ * ① un déplacement CONSERVE la durée — sinon déplacer une réunion la rallonge en douce ;
19606
+ * ② un redimensionnement ne franchit JAMAIS l'autre bord : il s'arrête à un pas ;
19607
+ * ③ un événement journée entière ne bouge qu'en JOURS — lui donner une heure en le glissant
19608
+ * le ferait sortir du bandeau, ce que personne n'a demandé.
19609
+ */
19610
+ function cadProjeterEdition(kind, base, dx, dy, g) {
19611
+ const jours = g.largeurJour > 0 ? Math.round(dx / g.largeurJour) : 0;
19612
+ const minutesBrutes = g.hauteurHeure > 0 ? (dy / g.hauteurHeure) * 60 : 0;
19613
+ const dMin = aimante(minutesBrutes, g.pas);
19614
+ // ③ journée entière : seuls les jours comptent.
19615
+ if (base.allDay) {
19616
+ if (kind === 'move')
19617
+ return { start: decaleJours(base.start, jours), end: decaleJours(base.end, jours) };
19618
+ if (kind === 'resize-start') {
19619
+ const s = decaleJours(base.start, jours);
19620
+ return { start: s > base.end ? base.end : s, end: base.end };
19621
+ }
19622
+ if (kind === 'resize-end') {
19623
+ const e = decaleJours(base.end, jours);
19624
+ return { start: base.start, end: e < base.start ? base.start : e };
19625
+ }
19626
+ return { start: base.start, end: base.end };
19627
+ }
19628
+ const duree = Math.max(g.pas, Math.round((base.end.getTime() - base.start.getTime()) / MIN));
19629
+ if (kind === 'move' || kind === 'create') {
19630
+ // ① la durée est conservée. On borne le DÉBUT à la plage affichée, jamais la durée :
19631
+ // rogner la fin en douce ferait raccourcir un rendez-vous qu'on voulait seulement déplacer.
19632
+ const vise = aimante(minutesDansJour(base.start) + dMin, g.pas);
19633
+ const debut = Math.max(g.plageDebut, Math.min(vise, g.plageFin - g.pas));
19634
+ const jour = decaleJours(base.start, jours);
19635
+ const start = poseMinutes(jour, debut);
19636
+ return { start, end: new Date(start.getTime() + duree * MIN) };
19637
+ }
19638
+ if (kind === 'resize-start') {
19639
+ const finMin = minutesDansJour(base.end);
19640
+ const vise = aimante(minutesDansJour(base.start) + dMin, g.pas);
19641
+ // ② on s'arrête un pas avant la fin, jamais au-delà.
19642
+ const debut = Math.max(g.plageDebut, Math.min(vise, finMin - g.pas));
19643
+ return { start: poseMinutes(base.start, debut), end: base.end };
19644
+ }
19645
+ const debutMin = minutesDansJour(base.start);
19646
+ const vise = aimante(minutesDansJour(base.end) + dMin, g.pas);
19647
+ const fin = Math.min(g.plageFin, Math.max(vise, debutMin + g.pas));
19648
+ return { start: base.start, end: poseMinutes(base.end, fin) };
19649
+ }
19650
+ /**
19651
+ * Bornes d'une création par glisser sur une plage vide.
19652
+ *
19653
+ * `y0` / `y1` sont les positions verticales dans la colonne, en pixels. On aimante les DEUX
19654
+ * bords, et on garantit au moins un pas : un clic sec ne doit pas produire un événement de
19655
+ * durée nulle, invisible et impossible à rattraper.
19656
+ */
19657
+ function cadProjeterCreation(jour, y0, y1, g) {
19658
+ const enMinutes = (y) => g.plageDebut + (y / Math.max(1, g.hauteurHeure)) * 60;
19659
+ const a = aimante(enMinutes(Math.min(y0, y1)), g.pas);
19660
+ let b = aimante(enMinutes(Math.max(y0, y1)), g.pas);
19661
+ if (b - a < g.pas)
19662
+ b = a + g.pas;
19663
+ const debut = Math.max(g.plageDebut, Math.min(a, g.plageFin - g.pas));
19664
+ const fin = Math.min(g.plageFin, Math.max(b, debut + g.pas));
19665
+ return { start: poseMinutes(jour, debut), end: poseMinutes(jour, fin) };
19666
+ }
19667
+ /**
19668
+ * Projection d'un geste dans une GRILLE DE MOIS — et ce n'est pas la même que dans une grille
19669
+ * horaire, contrairement à ce que j'avais écrit.
19670
+ *
19671
+ * Dans un mois, l'axe vertical n'est PAS le temps de la journée : c'est la semaine. Descendre
19672
+ * d'une rangée avance de SEPT JOURS, et l'heure ne bouge pas. Utiliser la projection horaire
19673
+ * ici donnait « +3 h » au lieu de « +7 jours » (mesuré au navigateur le 22/08/2026 : glisser
19674
+ * d'une rangée déplaçait un rendez-vous de 10 h 30 à 13 h 30 sans changer de date).
19675
+ *
19676
+ * Une seule règle à retenir : dans le mois, un geste ne déplace que des JOURS.
19677
+ */
19678
+ function cadProjeterMois(kind, base, dx, dy, g) {
19679
+ const colonnes = g.largeurJour > 0 ? Math.round(dx / g.largeurJour) : 0;
19680
+ const rangees = g.hauteurRangee > 0 ? Math.round(dy / g.hauteurRangee) : 0;
19681
+ const jours = colonnes + 7 * rangees;
19682
+ if (kind === 'resize-start') {
19683
+ const s = decaleJours(base.start, jours);
19684
+ return { start: s > base.end ? base.end : s, end: base.end };
19685
+ }
19686
+ if (kind === 'resize-end') {
19687
+ const e = decaleJours(base.end, jours);
19688
+ return { start: base.start, end: e < base.start ? base.start : e };
19689
+ }
19690
+ // L'heure est conservée telle quelle : decaleJours ne touche qu'au quantième.
19691
+ return { start: decaleJours(base.start, jours), end: decaleJours(base.end, jours) };
19692
+ }
19693
+ /** Déplacement au CLAVIER, exprimé dans la même unité qu'un geste : jours et pas. */
19694
+ function cadProjeterClavier(kind, base, jours, pas, g) {
19695
+ // On repasse par la MÊME projection que le pointeur : une seule logique de placement à
19696
+ // éprouver, et le clavier n'est pas une reprise ultérieure qui dérive.
19697
+ return cadProjeterEdition(kind, base, jours * g.largeurJour, (pas * g.pas / 60) * g.hauteurHeure, g);
19698
+ }
19699
+
19020
19700
  /** Styles de cad-taskboard (présentations cartes et liste). Tokens --cad-* uniquement. */
19021
19701
  const CAD_TASKBOARD_STYLES = `
19022
19702
  :host { display: flex; flex-direction: column; min-height: 0; height: 100%; font: var(--cad-font-size)/1.35 var(--cad-font); color: var(--cad-fg); background: var(--cad-bg); }
@@ -19540,9 +20220,1016 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
19540
20220
  `, styles: [":host{display:flex;flex-direction:column;min-height:0;height:100%;font:var(--cad-font-size)/1.35 var(--cad-font);color:var(--cad-fg);background:var(--cad-bg)}.tb__board{flex:1;min-height:0;display:flex;gap:12px;padding:12px;overflow-x:auto;overflow-y:hidden;scrollbar-gutter:stable}.tb__col{flex:0 0 var(--cad-taskboard-col, 258px);display:flex;flex-direction:column;min-height:0;background:var(--cad-bg-subtle);border:1px solid var(--cad-border);border-radius:var(--cad-radius)}.tb__head{display:flex;align-items:center;gap:8px;padding:9px 11px;border-bottom:1px solid var(--cad-border)}.tb__dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--_c, var(--cad-border-strong))}.tb__name{font-weight:500;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tb__count{font-family:var(--cad-font-mono);font-size:var(--cad-font-size-sm);padding:2px 7px;border-radius:var(--cad-radius-pill);background:var(--cad-elevated);color:var(--cad-fg-muted);font-variant-numeric:tabular-nums;white-space:nowrap}.tb__count.is-full{background:var(--cad-warning-soft);color:var(--cad-warning)}.tb__count.is-over{background:var(--cad-danger-soft);color:var(--cad-danger);font-weight:500}.tb__list{flex:1;min-height:0;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:8px;scrollbar-gutter:stable}.tb__list.cdk-drop-list-dragging{background:var(--cad-accent-soft)}.tb__card{background:var(--cad-surface);border:1px solid var(--cad-border);border-radius:var(--cad-radius-sm);padding:9px 10px;box-shadow:var(--cad-shadow-sm);display:flex;flex-direction:column;gap:7px;cursor:grab;-webkit-user-select:none;user-select:none;transition:border-color var(--cad-dur) var(--cad-ease),box-shadow var(--cad-dur) var(--cad-ease)}.tb__card:hover{border-color:var(--cad-border-strong);box-shadow:var(--cad-shadow-md)}.tb__card:focus-visible{outline:2px solid var(--cad-ring);outline-offset:1px}.tb__card.is-orphan{border-left:2px dashed var(--cad-warning)}:host(.is-static) .tb__card,:host(.is-static) .tb__row{cursor:default}.tb__title{font-weight:500;line-height:1.32}.tb__meta{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.tb__tag{font-family:var(--cad-font-mono);font-size:10px;padding:2px 6px;border-radius:var(--cad-radius-sm);background:var(--cad-elevated);color:var(--cad-fg-secondary)}.tb__due{font-family:var(--cad-font-mono);font-size:var(--cad-font-size-sm);color:var(--cad-fg-muted);font-variant-numeric:tabular-nums}.tb__due.is-late{color:var(--cad-danger);font-weight:500}.tb__sp{flex:1 1 auto}.tb__prog{height:3px;border-radius:var(--cad-radius-pill);background:var(--cad-elevated);overflow:hidden}.tb__prog i{display:block;height:100%;border-radius:var(--cad-radius-pill);background:var(--cad-accent)}.tb__prog.is-done i{background:var(--cad-success)}.tb__empty{padding:14px 10px;text-align:center;color:var(--cad-fg-muted);font-size:var(--cad-font-size-sm);border:1px dashed var(--cad-border-strong);border-radius:var(--cad-radius-sm)}.tb__rows{flex:1;min-height:0;overflow-y:auto;padding-bottom:12px;scrollbar-gutter:stable}.tb__grp{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;background:var(--cad-bg-subtle);border-top:1px solid var(--cad-border);border-bottom:1px solid var(--cad-border);position:sticky;top:0;z-index:1;cursor:pointer;text-align:left;font:inherit;color:inherit}.tb__grp:hover{background:var(--cad-hover)}.tb__grp:focus-visible{outline:2px solid var(--cad-ring);outline-offset:-2px}.tb__grp cad-icon{color:var(--cad-fg-muted);transition:transform var(--cad-dur) var(--cad-ease)}.tb__grp.is-collapsed cad-icon{transform:rotate(-90deg)}.tb__grp b{font-weight:500}.tb__group-list{display:block}.tb__row{display:grid;grid-template-columns:1fr 92px 88px 26px;align-items:center;gap:12px;padding:8px 14px;border-bottom:1px solid var(--cad-border);cursor:grab;-webkit-user-select:none;user-select:none;background:var(--cad-bg)}.tb__row:hover{background:var(--cad-hover)}.tb__row:focus-visible{outline:2px solid var(--cad-ring);outline-offset:-2px}.tb__row.is-orphan{box-shadow:inset 2px 0 0 var(--cad-warning)}.tb__main{min-width:0;display:flex;flex-direction:column;gap:4px}.tb__main .tb__title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tb__pct{display:flex;align-items:center;gap:7px}.tb__pct .tb__prog{flex:1}.tb__pct span{font-family:var(--cad-font-mono);font-size:10px;color:var(--cad-fg-muted);font-variant-numeric:tabular-nums}:host(.is-compact) .tb__row{grid-template-columns:1fr 26px}:host(.is-compact) .tb__cell-due,:host(.is-compact) .tb__pct{display:none}:host(.is-compact) .tb__mob{display:inline-flex}.tb__mob{display:none;align-items:center;gap:7px}.cdk-drag-preview{box-shadow:var(--cad-shadow-lg);border-radius:var(--cad-radius-sm);background:var(--cad-overlay);--cad-hover: var(--cad-overlay-hover);border:1px solid var(--cad-border)}.cdk-drag-placeholder{opacity:.34}.cdk-drag-animating{transition:transform .18s var(--cad-ease)}.tb__list.cdk-drop-list-dragging .tb__card:not(.cdk-drag-placeholder),.tb__group-list.cdk-drop-list-dragging .tb__row:not(.cdk-drag-placeholder){transition:transform .18s var(--cad-ease)}.tb__live{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.cdk-drag-animating,.tb__list.cdk-drop-list-dragging .tb__card,.tb__grp cad-icon{transition:none}}\n"] }]
19541
20221
  }], propDecorators: { plan: [{ type: i0.Input, args: [{ isSignal: true, alias: "plan", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], dragDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragDisabled", required: false }] }], collapsedKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedKeys", required: false }] }, { type: i0.Output, args: ["collapsedKeysChange"] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], taskMove: [{ type: i0.Output, args: ["taskMove"] }], taskAssign: [{ type: i0.Output, args: ["taskAssign"] }], taskOpen: [{ type: i0.Output, args: ["taskOpen"] }], wipExceeded: [{ type: i0.Output, args: ["wipExceeded"] }], cardTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadTaskCardDirective), { isSignal: true }] }], rowTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadTaskRowDirective), { isSignal: true }] }] } });
19542
20222
 
20223
+ /**
20224
+ * Styles de cad-calendar. Tokens --cad-* uniquement.
20225
+ *
20226
+ * ATTENTION : ce fichier n'est PAS couvert par tools/check-styles.mjs, qui ne scanne que les
20227
+ * blocs « styles: [ » en ligne. Aucun accent grave dans les commentaires ci-dessous : un seul
20228
+ * refermerait le litteral gabarit et Angular echouerait sur « Failed to resolve styles ».
20229
+ */
20230
+ const CAD_CALENDAR_STYLES = `
20231
+ :host { display: flex; flex-direction: column; min-height: 0; height: 100%;
20232
+ font: var(--cad-font-size)/1.4 var(--cad-font); color: var(--cad-fg); background: var(--cad-bg); }
20233
+
20234
+ .cal__corps { flex: 1; min-height: 0; display: flex; }
20235
+ .cal__vue { flex: 1; min-width: 0; min-height: 0; overflow: auto; }
20236
+
20237
+ /* ---------------- MOIS ---------------- */
20238
+ .mois { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
20239
+ grid-template-rows: auto; grid-auto-rows: minmax(84px, 1fr); min-height: 100%; }
20240
+ /* grid-template-rows: auto pour la premiere rangee : sans elle, grid-auto-rows s'applique
20241
+ aussi aux en-tetes de jour, qui heritent de la hauteur des cases et laissent une bande vide. */
20242
+ .mois__jsem { padding: 6px 8px; text-align: right; border-bottom: 1px solid var(--cad-border-strong);
20243
+ background: var(--cad-bg-subtle); position: sticky; top: 0; z-index: 4;
20244
+ font: 600 10.5px var(--cad-font-mono); letter-spacing: .06em; text-transform: uppercase;
20245
+ color: var(--cad-fg-muted); }
20246
+
20247
+ /* La rangee de semaine est un CONTENEUR relatif : les bandeaux multi-jours sont un calque
20248
+ par-dessus les cases, seule facon de les rendre continus d'un jour a l'autre. */
20249
+ .mois__sem { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
20250
+ position: relative; border-bottom: 1px solid var(--cad-border); min-height: 84px; }
20251
+ .mois__jour { border-right: 1px solid var(--cad-border); padding: 3px; min-width: 0;
20252
+ display: flex; flex-direction: column; gap: 2px; }
20253
+ .mois__jour:last-child { border-right: 0; }
20254
+ .mois__jour.is-hors { background: var(--cad-bg-subtle); }
20255
+ .mois__jour.is-ferme { background: var(--cad-bg-subtle); }
20256
+ .mois__n { font: 11px var(--cad-font-mono); color: var(--cad-fg-secondary); text-align: right;
20257
+ padding: 1px 3px; font-variant-numeric: tabular-nums; flex: none; }
20258
+ .mois__jour.is-auj .mois__n { background: var(--cad-accent); color: var(--cad-fg-on-accent);
20259
+ border-radius: var(--cad-radius-sm); font-weight: 600; }
20260
+ .mois__reserve { flex: none; }
20261
+
20262
+ .calque { position: absolute; left: 0; right: 0; pointer-events: none; z-index: 2; }
20263
+ .bandeau { position: absolute; height: 17px; border-radius: var(--cad-radius-sm); padding: 0 7px;
20264
+ font-size: 10.5px; line-height: 17px; white-space: nowrap; overflow: hidden;
20265
+ text-overflow: ellipsis; pointer-events: auto; cursor: pointer;
20266
+ background: color-mix(in srgb, var(--_c) 22%, transparent); color: var(--_c);
20267
+ border: 1px solid color-mix(in srgb, var(--_c) 42%, transparent); }
20268
+ .bandeau.is-avant { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left-style: dashed; }
20269
+ .bandeau.is-apres { border-top-right-radius: 0; border-bottom-right-radius: 0; border-right-style: dashed; }
20270
+
20271
+ .puce { display: flex; align-items: center; gap: 5px; font-size: 10.5px; padding: 1px 5px;
20272
+ border-radius: var(--cad-radius-sm); white-space: nowrap; overflow: hidden;
20273
+ text-overflow: ellipsis; cursor: pointer; border-left: 2px solid var(--_c);
20274
+ background: color-mix(in srgb, var(--_c) 13%, transparent); color: var(--_c); }
20275
+ .puce:hover { background: color-mix(in srgb, var(--_c) 22%, transparent); }
20276
+ .puce__h { font: 9.5px var(--cad-font-mono); opacity: .85; flex: none; font-variant-numeric: tabular-nums; }
20277
+ .puce__t { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
20278
+ .plus { all: unset; box-sizing: border-box; font-size: 10px; color: var(--cad-fg-muted);
20279
+ padding: 1px 5px; cursor: pointer; border-radius: var(--cad-radius-sm); }
20280
+ .plus:hover { color: var(--cad-accent); background: var(--cad-hover); }
20281
+
20282
+ /* ---------------- SEMAINE / JOUR ---------------- */
20283
+ .grille { min-width: 0; display: flex; flex-direction: column; height: 100%; }
20284
+ .grille__hd { display: grid; position: sticky; top: 0; z-index: 5; background: var(--cad-bg-subtle);
20285
+ border-bottom: 1px solid var(--cad-border-strong); flex: none; }
20286
+ .grille__hd > div { padding: 5px 6px; text-align: center; border-right: 1px solid var(--cad-border); min-width: 0; }
20287
+ .grille__hd > div:last-child { border-right: 0; }
20288
+ .grille__d { font: 10px var(--cad-font-mono); color: var(--cad-fg-muted);
20289
+ text-transform: uppercase; letter-spacing: .06em; }
20290
+ .grille__n { font-size: 16px; font-weight: 600; letter-spacing: -.02em; }
20291
+ .is-auj .grille__n { color: var(--cad-accent); }
20292
+
20293
+ .bande { display: grid; border-bottom: 1px solid var(--cad-border-strong);
20294
+ background: var(--cad-bg-subtle); flex: none; }
20295
+ .bande__lbl { font: 9px var(--cad-font-mono); color: var(--cad-fg-muted); padding: 4px 6px;
20296
+ text-align: right; border-right: 1px solid var(--cad-border); }
20297
+ .bande__zone { grid-column: 2 / -1; position: relative; padding: 2px 0; }
20298
+
20299
+ .heures { display: grid; position: relative; flex: 1; min-height: 0; overflow: auto; }
20300
+ .heures__lbl { border-right: 1px solid var(--cad-border); }
20301
+ .heures__lbl > div { font: 9.5px var(--cad-font-mono); color: var(--cad-fg-muted);
20302
+ text-align: right; padding-right: 6px; transform: translateY(-6px); }
20303
+ .heures__col { border-right: 1px solid var(--cad-border); position: relative; min-width: 0; }
20304
+ .heures__col:last-child { border-right: 0; }
20305
+ .heures__col.is-ferme { background: var(--cad-bg-subtle); }
20306
+ .trait { position: absolute; left: 0; right: 0; border-top: 1px solid var(--cad-border); pointer-events: none; }
20307
+ .trait.is-demi { border-top-style: dotted; opacity: .55; }
20308
+
20309
+ .evt { position: absolute; border-radius: var(--cad-radius-sm); padding: 1px 5px; overflow: hidden;
20310
+ cursor: pointer; border-left: 3px solid var(--_c); font-size: 10.5px; line-height: 1.25;
20311
+ background: color-mix(in srgb, var(--_c) 15%, transparent); color: var(--_c); }
20312
+ .evt:hover { background: color-mix(in srgb, var(--_c) 26%, transparent); }
20313
+ .evt__t { display: block; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
20314
+ .evt__h { font: 9px var(--cad-font-mono); opacity: .85; }
20315
+ .maintenant { position: absolute; left: 0; right: 0; height: 2px; background: var(--cad-danger); z-index: 4; pointer-events: none; }
20316
+ .maintenant::before { content: ''; position: absolute; left: -3px; top: -3px; width: 8px; height: 8px;
20317
+ border-radius: 50%; background: var(--cad-danger); }
20318
+
20319
+ /* ---------------- AGENDA ---------------- */
20320
+ .agenda { display: flex; flex-direction: column; }
20321
+ .ag__jour { display: grid; grid-template-columns: 72px minmax(0, 1fr); border-bottom: 1px solid var(--cad-border); }
20322
+ .ag__d { padding: 9px 11px; border-right: 1px solid var(--cad-border); background: var(--cad-bg-subtle); }
20323
+ .ag__d b { display: block; font-size: 18px; font-weight: 600; letter-spacing: -.02em; }
20324
+ .ag__d span { display: block; font: 9.5px var(--cad-font-mono); color: var(--cad-fg-muted); text-transform: uppercase; }
20325
+ .ag__d.is-auj b { color: var(--cad-accent); }
20326
+ .ag__l { padding: 5px 0; min-width: 0; }
20327
+ .ag__e { all: unset; box-sizing: border-box; display: flex; align-items: center; gap: 9px;
20328
+ padding: 6px 11px; cursor: pointer; width: 100%; min-height: 34px; }
20329
+ .ag__e:hover { background: var(--cad-hover); }
20330
+ .ag__e:focus-visible { outline: 2px solid var(--cad-accent); outline-offset: -2px; }
20331
+ .ag__p { width: 8px; height: 8px; border-radius: 50%; background: var(--_c); flex: none; }
20332
+ .ag__h { font: 11px var(--cad-font-mono); color: var(--cad-fg-secondary); flex: none;
20333
+ width: 92px; font-variant-numeric: tabular-nums; }
20334
+ .ag__t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
20335
+ .ag__c { margin-left: auto; font-size: 10.5px; color: var(--cad-fg-muted); flex: none; }
20336
+
20337
+ /* ---------------- ANNEE ---------------- */
20338
+ .annee { display: grid; grid-template-columns: repeat(auto-fill, minmax(178px, 1fr)); gap: 14px; padding: 14px; }
20339
+ .mm { border: 1px solid var(--cad-border); border-radius: var(--cad-radius); padding: 8px; background: var(--cad-surface); }
20340
+ .mm__t { font-size: 12px; font-weight: 600; margin-bottom: 5px; text-transform: capitalize; }
20341
+ .mm__g { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
20342
+ .mm__g > span, .mm__g > button { all: unset; box-sizing: border-box; text-align: center;
20343
+ font: 9.5px var(--cad-font-mono); padding: 2px 0; border-radius: 3px;
20344
+ color: var(--cad-fg-secondary); font-variant-numeric: tabular-nums; }
20345
+ .mm__g > span { color: var(--cad-fg-muted); font-weight: 500; }
20346
+ .mm__g > button { cursor: pointer; position: relative; }
20347
+ .mm__g > button:hover { background: var(--cad-hover); }
20348
+ .mm__g > button.is-hors { opacity: .35; }
20349
+ .mm__g > button.is-auj { background: var(--cad-accent); color: var(--cad-fg-on-accent); font-weight: 600; }
20350
+ .mm__pt { position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
20351
+ width: 3px; height: 3px; border-radius: 50%; background: var(--cad-accent); }
20352
+ .mm__g > button.is-auj .mm__pt { background: var(--cad-fg-on-accent); }
20353
+
20354
+ /* ---------------- EDITION ---------------- */
20355
+ /* Les poignees n'existent que si l'edition est active ET l'evenement manipulable : une
20356
+ poignee qui ne repond pas est pire que pas de poignee. */
20357
+ .evt.is-editable, .bandeau.is-editable, .puce.is-editable { cursor: grab; }
20358
+ .evt.is-editable:active, .bandeau.is-editable:active, .puce.is-editable:active { cursor: grabbing; }
20359
+ .poignee { position: absolute; z-index: 2; }
20360
+ .evt .poignee { left: 0; right: 0; height: 6px; cursor: ns-resize; }
20361
+ .evt .poignee.is-haut { top: 0; }
20362
+ .evt .poignee.is-bas { bottom: 0; }
20363
+ .bandeau .poignee { top: 0; bottom: 0; width: 6px; cursor: ew-resize; }
20364
+ .bandeau .poignee.is-haut { left: 0; }
20365
+ .bandeau .poignee.is-bas { right: 0; }
20366
+ .poignee::after { content: ''; position: absolute; inset: 0; }
20367
+
20368
+ /* Une occurrence de SERIE n'est pas manipulable tant que l'edition de recurrence n'existe
20369
+ pas : bouger une occurrence sans savoir si l'on modifie l'occurrence, la suite ou toute la
20370
+ serie perdrait la modification en silence. On le MONTRE au lieu de laisser essayer. */
20371
+ .evt.is-verrouille, .bandeau.is-verrouille, .puce.is-verrouille { cursor: not-allowed; position: relative; }
20372
+ .evt.is-verrouille::after, .bandeau.is-verrouille::after, .puce.is-verrouille::after {
20373
+ content: ''; position: absolute; inset: 0; pointer-events: none;
20374
+ background: repeating-linear-gradient(-45deg, currentColor 0 1px, transparent 1px 5px); opacity: .18; }
20375
+
20376
+ /* Fantome du geste en cours : il montre ou l'on va tomber, apres aimantage. */
20377
+ .fantome { position: absolute; z-index: 6; pointer-events: none; border-radius: var(--cad-radius-sm);
20378
+ background: var(--cad-accent-soft); border: 1px dashed var(--cad-accent);
20379
+ color: var(--cad-accent); font: 10.5px var(--cad-font-mono); padding: 1px 5px;
20380
+ overflow: hidden; white-space: nowrap; }
20381
+
20382
+ /* ---------------- vide ---------------- */
20383
+ .vide { padding: 28px 16px; color: var(--cad-fg-muted); text-align: center; }
20384
+
20385
+ @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
20386
+ `;
20387
+
20388
+ /**
20389
+ * `<cad-calendar [plan]="plan" [sources]="agendas" view="auto"/>`
20390
+ *
20391
+ * LA GRILLE EST LE TEMPS — c'est ce qui le distingue de `cad-scheduler`, dont les lignes sont
20392
+ * des ressources. On répond ici à « qu'est-ce qui se passe le 17 ? », là-bas à « qui est libre
20393
+ * jeudi ? ». Deux questions, deux composants, un seul document JSON.
20394
+ *
20395
+ * L'AGENDA D'UNE TÂCHE EST SON `bucket`. Pas un champ de plus : le taskboard rend les buckets
20396
+ * en colonnes, le calendrier les rend en agendas superposés. C'est le pari « une donnée,
20397
+ * plusieurs vues » qui paie, et ça évite d'inventer un `calendarId` que les autres vues
20398
+ * ignoreraient.
20399
+ *
20400
+ * LA RÉCURRENCE EST EN LECTURE SEULE (voir `planning.recurrence.ts`). On déplie une règle pour
20401
+ * l'afficher ; on n'édite pas de série. Une récurrence à moitié faite perd des modifications
20402
+ * sans le dire — déplier n'écrit rien, donc ne peut rien perdre.
20403
+ *
20404
+ * LE COMPOSANT N'ÉCRIT JAMAIS : il émet `eventClick`, `dateClick`, `rangeChange`. Masquer un
20405
+ * agenda est en revanche un état de PRÉSENTATION, donc un `model()` qu'il porte lui-même.
20406
+ */
20407
+ class CadCalendarComponent {
20408
+ Math = Math;
20409
+ bp = inject(CadBreakpointService);
20410
+ hote = inject((ElementRef));
20411
+ /**
20412
+ * Une colonne de jour, pour mesurer la largeur d'un pas horizontal.
20413
+ *
20414
+ * On MESURE au lieu de calculer : les colonnes sont en `minmax(0, 1fr)`, donc leur largeur
20415
+ * dépend du conteneur, de la barre de défilement et du zoom. Un calcul serait faux dès que
20416
+ * l'un des trois change, et le glisser dériverait d'un jour sans que rien ne le signale.
20417
+ */
20418
+ largeurColonne() {
20419
+ const el = this.hote.nativeElement;
20420
+ return el.querySelector('.heures__col') ?? el.querySelector('.mois__jour');
20421
+ }
20422
+ plan = input(null, ...(ngDevMode ? [{ debugName: "plan" }] : /* istanbul ignore next */ []));
20423
+ /** Agendas superposables. Un `bucket` sans source déclarée prend l'accent et son propre nom. */
20424
+ sources = input([], ...(ngDevMode ? [{ debugName: "sources" }] : /* istanbul ignore next */ []));
20425
+ /** Agendas décochés. État de PRÉSENTATION, donc porté ici et lié en deux sens. */
20426
+ hidden = model(new Set(), ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
20427
+ view = model('auto', ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
20428
+ /** Date d'ancrage : le mois, la semaine ou le jour affiché. */
20429
+ date = model(new Date(), ...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
20430
+ /** 1 = lundi (défaut), 0 = dimanche. Touche le mois, la semaine ET le mini-calendrier. */
20431
+ firstDay = input(1, ...(ngDevMode ? [{ debugName: "firstDay" }] : /* istanbul ignore next */ []));
20432
+ /** Plage horaire affichée, en heures. Réglable : 7 → 20 par défaut. */
20433
+ dayStart = input(7, ...(ngDevMode ? [{ debugName: "dayStart" }] : /* istanbul ignore next */ []));
20434
+ dayEnd = input(20, ...(ngDevMode ? [{ debugName: "dayEnd" }] : /* istanbul ignore next */ []));
20435
+ hourHeight = input(34, ...(ngDevMode ? [{ debugName: "hourHeight" }] : /* istanbul ignore next */ []));
20436
+ /** Événements montrés dans une case de mois avant le « +N ». */
20437
+ maxPerDay = input(3, ...(ngDevMode ? [{ debugName: "maxPerDay" }] : /* istanbul ignore next */ []));
20438
+ workCalendar = input(null, ...(ngDevMode ? [{ debugName: "workCalendar" }] : /* istanbul ignore next */ []));
20439
+ today = input(null, ...(ngDevMode ? [{ debugName: "today" }] : /* istanbul ignore next */ []));
20440
+ locale = input('fr-FR', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
20441
+ emptyText = input('Aucun événement sur la période', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
20442
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
20443
+ showNow = input(true, { ...(ngDevMode ? { debugName: "showNow" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
20444
+ eventClick = output();
20445
+ dateClick = output();
20446
+ /** La fenêtre affichée a changé : l'hôte peut aller chercher les données qui manquent. */
20447
+ rangeChange = output();
20448
+ /** Gestes d'édition. `false` = vue en lecture, sans poignée ni curseur trompeur. */
20449
+ editable = input(false, { ...(ngDevMode ? { debugName: "editable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
20450
+ /** Aimantage, en minutes. Sans lui on crée des réunions de 9 h 07 que personne n'a voulues. */
20451
+ snap = input(15, ...(ngDevMode ? [{ debugName: "snap" }] : /* istanbul ignore next */ []));
20452
+ eventMove = output();
20453
+ eventResize = output();
20454
+ eventCreate = output();
20455
+ /** `auto` : agenda sous le seuil. Une grille de mois à 55 px par jour ne montre plus rien. */
20456
+ vueEffective = computed(() => {
20457
+ const v = this.view();
20458
+ if (v !== 'auto')
20459
+ return v;
20460
+ return this.bp.isMobile() ? 'agenda' : 'month';
20461
+ }, ...(ngDevMode ? [{ debugName: "vueEffective" }] : /* istanbul ignore next */ []));
20462
+ normalise = computed(() => cadNormalizePlan(this.plan()), ...(ngDevMode ? [{ debugName: "normalise" }] : /* istanbul ignore next */ []));
20463
+ ajd = computed(() => cadMinuit(this.today() ?? new Date()), ...(ngDevMode ? [{ debugName: "ajd" }] : /* istanbul ignore next */ []));
20464
+ hauteurHeure = computed(() => this.hourHeight(), ...(ngDevMode ? [{ debugName: "hauteurHeure" }] : /* istanbul ignore next */ []));
20465
+ hauteurTotale = computed(() => (this.dayEnd() - this.dayStart()) * this.hourHeight(), ...(ngDevMode ? [{ debugName: "hauteurTotale" }] : /* istanbul ignore next */ []));
20466
+ heuresAffichees = computed(() => {
20467
+ const out = [];
20468
+ for (let h = this.dayStart(); h <= this.dayEnd(); h++)
20469
+ out.push(h);
20470
+ return out;
20471
+ }, ...(ngDevMode ? [{ debugName: "heuresAffichees" }] : /* istanbul ignore next */ []));
20472
+ /** Fenêtre couverte par la vue courante. Elle borne le dépliage : on ne projette pas l'infini. */
20473
+ fenetre = computed(() => {
20474
+ const d = this.date();
20475
+ const v = this.vueEffective();
20476
+ if (v === 'day') {
20477
+ const s = cadMinuit(d);
20478
+ return { start: s, end: new Date(s.getFullYear(), s.getMonth(), s.getDate(), 23, 59, 59) };
20479
+ }
20480
+ if (v === 'week') {
20481
+ const s = cadDebutSemaine(d, this.firstDay());
20482
+ const e = new Date(s);
20483
+ e.setDate(e.getDate() + 6);
20484
+ e.setHours(23, 59, 59);
20485
+ return { start: s, end: e };
20486
+ }
20487
+ if (v === 'year') {
20488
+ return { start: new Date(d.getFullYear(), 0, 1), end: new Date(d.getFullYear(), 11, 31, 23, 59, 59) };
20489
+ }
20490
+ const g = cadMonthGrid(d, this.firstDay());
20491
+ const dernier = g.semaines[g.semaines.length - 1][6];
20492
+ return { start: g.debut, end: new Date(dernier.getFullYear(), dernier.getMonth(), dernier.getDate(), 23, 59, 59) };
20493
+ }, ...(ngDevMode ? [{ debugName: "fenetre" }] : /* istanbul ignore next */ []));
20494
+ /**
20495
+ * CORRECTIF OPTIMISTE. Le composant n'écrit jamais dans le document — mais sans état local
20496
+ * le geste serait mort à l'écran : on relâche, et la barre revient à sa place en attendant
20497
+ * que l'hôte réponde. On garde donc les bornes proposées, ATTACHÉES au `plan` d'origine, et
20498
+ * on les abandonne dès qu'un nouveau `plan` arrive. Même patron que `cad-taskboard`.
20499
+ */
20500
+ patch = signal({ src: null, map: new Map() }, ...(ngDevMode ? [{ debugName: "patch" }] : /* istanbul ignore next */ []));
20501
+ occurrences = computed(() => {
20502
+ const f = this.fenetre();
20503
+ const caches = this.hidden();
20504
+ const { occurrences } = cadExpandOccurrences(this.normalise().tasks, f.start, f.end);
20505
+ const base = occurrences.filter((o) => !caches.has(o.task.bucket ?? ''));
20506
+ const p = this.patch();
20507
+ if (p.src !== this.plan() || !p.map.size)
20508
+ return base;
20509
+ return base.map((o) => {
20510
+ const n = p.map.get(o.id);
20511
+ return n ? { ...o, start: n.start, end: n.end } : o;
20512
+ });
20513
+ }, ...(ngDevMode ? [{ debugName: "occurrences" }] : /* istanbul ignore next */ []));
20514
+ /** Fantôme du geste en cours : il montre où l'on va tomber, APRÈS aimantage. */
20515
+ fantome = signal(null, ...(ngDevMode ? [{ debugName: "fantome" }] : /* istanbul ignore next */ []));
20516
+ /** Anomalies de dépliage — jamais tues : l'hôte peut les montrer. */
20517
+ issues = computed(() => cadExpandOccurrences(this.normalise().tasks, this.fenetre().start, this.fenetre().end).issues, ...(ngDevMode ? [{ debugName: "issues" }] : /* istanbul ignore next */ []));
20518
+ parJour = computed(() => {
20519
+ const carte = new Map();
20520
+ for (const o of this.occurrences()) {
20521
+ const d0 = cadNumeroJour(o.start), d1 = cadNumeroJour(o.end);
20522
+ for (let n = d0; n <= d1; n++)
20523
+ (carte.get(n) ?? carte.set(n, []).get(n)).push(o);
20524
+ }
20525
+ return carte;
20526
+ }, ...(ngDevMode ? [{ debugName: "parJour" }] : /* istanbul ignore next */ []));
20527
+ nomsJours = computed(() => {
20528
+ const base = ['lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim'];
20529
+ return this.firstDay() === 0 ? ['dim', ...base.slice(0, 6)] : base;
20530
+ }, ...(ngDevMode ? [{ debugName: "nomsJours" }] : /* istanbul ignore next */ []));
20531
+ semaines = computed(() => {
20532
+ const g = cadMonthGrid(this.date(), this.firstDay());
20533
+ const occ = this.occurrences();
20534
+ const wc = this.workCalendar();
20535
+ const ajd = cadNumeroJour(this.ajd());
20536
+ const max = this.maxPerDay();
20537
+ return g.semaines.map((jours) => {
20538
+ const { bandes, voies } = cadWeekBands(occ, jours);
20539
+ return {
20540
+ voies,
20541
+ bandes,
20542
+ jours: jours.map((date) => {
20543
+ const n = cadNumeroJour(date);
20544
+ const duJour = (this.parJour().get(n) ?? []).filter((o) => !cadEstBandeau(o));
20545
+ return {
20546
+ date, cle: n, n: date.getDate(),
20547
+ hors: date.getMonth() !== g.mois,
20548
+ auj: n === ajd,
20549
+ ferme: wc ? !wc.isWorkday(date) : false,
20550
+ puces: duJour.slice(0, max),
20551
+ reste: Math.max(0, duJour.length - max),
20552
+ };
20553
+ }),
20554
+ };
20555
+ });
20556
+ }, ...(ngDevMode ? [{ debugName: "semaines" }] : /* istanbul ignore next */ []));
20557
+ joursGrille = computed(() => {
20558
+ const v = this.vueEffective();
20559
+ const jours = v === 'day'
20560
+ ? [cadMinuit(this.date())]
20561
+ : Array.from({ length: 7 }, (_, k) => { const d = cadDebutSemaine(this.date(), this.firstDay()); d.setDate(d.getDate() + k); return d; });
20562
+ const wc = this.workCalendar();
20563
+ const ajd = cadNumeroJour(this.ajd());
20564
+ return jours.map((date) => {
20565
+ const n = cadNumeroJour(date);
20566
+ return {
20567
+ date, cle: n, n: date.getDate(), jsem: this.nomsJours()[(date.getDay() + 6) % 7],
20568
+ auj: n === ajd,
20569
+ ferme: wc ? !wc.isWorkday(date) : false,
20570
+ slots: cadPackDay(this.parJour().get(n) ?? [], this.dayStart() * 60, this.dayEnd() * 60),
20571
+ };
20572
+ });
20573
+ }, ...(ngDevMode ? [{ debugName: "joursGrille" }] : /* istanbul ignore next */ []));
20574
+ bandeGrille = computed(() => cadWeekBands(this.occurrences(), this.joursGrille().map((j) => j.date)), ...(ngDevMode ? [{ debugName: "bandeGrille" }] : /* istanbul ignore next */ []));
20575
+ colonnes = computed(() => `54px repeat(${this.joursGrille().length}, minmax(0, 1fr))`, ...(ngDevMode ? [{ debugName: "colonnes" }] : /* istanbul ignore next */ []));
20576
+ maintenant = computed(() => {
20577
+ if (!this.showNow())
20578
+ return null;
20579
+ const n = this.today() ?? new Date();
20580
+ const m = n.getHours() * 60 + n.getMinutes();
20581
+ if (m < this.dayStart() * 60 || m > this.dayEnd() * 60)
20582
+ return null;
20583
+ return (m - this.dayStart() * 60) / 60 * this.hourHeight();
20584
+ }, ...(ngDevMode ? [{ debugName: "maintenant" }] : /* istanbul ignore next */ []));
20585
+ groupes = computed(() => {
20586
+ const carte = new Map();
20587
+ for (const [n, liste] of this.parJour())
20588
+ carte.set(n, liste);
20589
+ const ajd = cadNumeroJour(this.ajd());
20590
+ return [...carte.entries()]
20591
+ .sort((a, b) => a[0] - b[0])
20592
+ .map(([n, evts]) => {
20593
+ const date = evts[0].start;
20594
+ const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());
20595
+ // Le jour du groupe est celui de la CASE, pas du premier événement : un multi-jours
20596
+ // apparaît dans chaque jour qu'il couvre, avec le bon quantième.
20597
+ const vrai = new Date(d);
20598
+ vrai.setDate(vrai.getDate() + (n - cadNumeroJour(d)));
20599
+ return {
20600
+ cle: n, n: vrai.getDate(), auj: n === ajd,
20601
+ jsem: this.nomsJours()[(vrai.getDay() + 6) % 7],
20602
+ evts: [...evts].sort((a, b) => Number(b.allDay) - Number(a.allDay) || a.start.getTime() - b.start.getTime()),
20603
+ };
20604
+ });
20605
+ }, ...(ngDevMode ? [{ debugName: "groupes" }] : /* istanbul ignore next */ []));
20606
+ miniMois = computed(() => {
20607
+ const an = this.date().getFullYear();
20608
+ const ajd = cadNumeroJour(this.ajd());
20609
+ const charge = this.parJour();
20610
+ return Array.from({ length: 12 }, (_, m) => {
20611
+ const g = cadMonthGrid(new Date(an, m, 1), this.firstDay());
20612
+ return {
20613
+ mois: m,
20614
+ titre: new Date(an, m, 1).toLocaleDateString(this.locale(), { month: 'long' }),
20615
+ jours: g.semaines.flat().map((date) => {
20616
+ const n = cadNumeroJour(date);
20617
+ return { date, cle: n, n: date.getDate(), hors: date.getMonth() !== m, auj: n === ajd, charge: (charge.get(n) ?? []).length };
20618
+ }),
20619
+ };
20620
+ });
20621
+ }, ...(ngDevMode ? [{ debugName: "miniMois" }] : /* istanbul ignore next */ []));
20622
+ couleur(o) {
20623
+ const b = o.task.bucket ?? '';
20624
+ const s = this.sources().find((x) => x.id === b);
20625
+ return o.task.color || s?.color || 'var(--cad-accent)';
20626
+ }
20627
+ nomSource(o) {
20628
+ const b = o.task.bucket ?? '';
20629
+ return this.sources().find((x) => x.id === b)?.label ?? b;
20630
+ }
20631
+ heure(d) {
20632
+ return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
20633
+ }
20634
+ titre(o) {
20635
+ const q = o.allDay ? 'journée entière' : `${this.heure(o.start)} – ${this.heure(o.end)}`;
20636
+ const s = this.nomSource(o);
20637
+ return `${o.task.label} · ${q}${s ? ' · ' + s : ''}${o.recurring ? ' · récurrent' : ''}`;
20638
+ }
20639
+ /**
20640
+ * Une occurrence de SÉRIE n'est pas manipulable tant que l'édition de récurrence n'existe
20641
+ * pas. Bouger l'une d'elles sans savoir si l'on modifie l'occurrence, la suite ou toute la
20642
+ * série perdrait la modification EN SILENCE. On le montre (hachures, curseur interdit,
20643
+ * motif au survol) plutôt que de laisser essayer et rater.
20644
+ */
20645
+ manipulable(o) {
20646
+ return this.editable() && !o.recurring && !o.task.locked;
20647
+ }
20648
+ motifVerrou(o) {
20649
+ if (!this.editable())
20650
+ return '';
20651
+ if (o.task.locked)
20652
+ return 'Verrouillé';
20653
+ if (o.recurring)
20654
+ return 'Série : édition au lot récurrence';
20655
+ return '';
20656
+ }
20657
+ geometrie(largeurJour) {
20658
+ return {
20659
+ hauteurHeure: this.hourHeight(),
20660
+ largeurJour,
20661
+ pas: Math.max(1, this.snap()),
20662
+ plageDebut: this.dayStart() * 60,
20663
+ plageFin: this.dayEnd() * 60,
20664
+ };
20665
+ }
20666
+ /**
20667
+ * Un glisser ne doit PAS produire aussi un clic.
20668
+ *
20669
+ * Le navigateur synthétise un `click` après tout `pointerup` sur un bouton : sans cette
20670
+ * garde, déplacer un événement émettait `eventMove` PUIS `eventClick`, et un hôte qui ouvre
20671
+ * une fiche au clic en ouvrirait une à chaque déplacement. Constaté au navigateur le
20672
+ * 22/08/2026 — un test unitaire ne l'aurait pas vu, le clic synthétique n'existe pas en jsdom.
20673
+ */
20674
+ finGeste = 0;
20675
+ poser(o, bornes) {
20676
+ const p = this.patch();
20677
+ const map = p.src === this.plan() ? new Map(p.map) : new Map();
20678
+ map.set(o.id, bornes);
20679
+ this.patch.set({ src: this.plan(), map });
20680
+ }
20681
+ /**
20682
+ * Un geste au pointeur. Le patron vient de `cadStartResize` de `cad-table` : on écoute sur
20683
+ * le DOCUMENT, pas sur l'élément — sinon le geste s'interrompt dès que le pointeur sort de
20684
+ * la barre, ce qui arrive tout le temps quand on déplace vite.
20685
+ */
20686
+ geste(kind, o, ev, colonne) {
20687
+ if (!this.manipulable(o))
20688
+ return;
20689
+ ev.preventDefault();
20690
+ ev.stopPropagation();
20691
+ const g = this.geometrie(colonne?.getBoundingClientRect().width ?? 0);
20692
+ const base = { start: o.start, end: o.end, allDay: o.allDay };
20693
+ const x0 = ev.clientX, y0 = ev.clientY;
20694
+ let bouge = false;
20695
+ const projette = (e) => cadProjeterEdition(kind, base, e.clientX - x0, e.clientY - y0, g);
20696
+ const move = (e) => { bouge = true; this.poser(o, projette(e)); };
20697
+ const up = (e) => {
20698
+ document.removeEventListener('pointermove', move);
20699
+ document.removeEventListener('pointerup', up);
20700
+ if (!bouge)
20701
+ return;
20702
+ this.finGeste = Date.now();
20703
+ const b = projette(e);
20704
+ if (b.start.getTime() === base.start.getTime() && b.end.getTime() === base.end.getTime())
20705
+ return;
20706
+ this.poser(o, b);
20707
+ const charge = { occurrence: o, start: b.start, end: b.end };
20708
+ (kind === 'move' ? this.eventMove : this.eventResize).emit(charge);
20709
+ };
20710
+ document.addEventListener('pointermove', move);
20711
+ document.addEventListener('pointerup', up);
20712
+ }
20713
+ /**
20714
+ * Geste dans la GRILLE DE MOIS — projection différente, et c'est la correction du 22/08/2026.
20715
+ *
20716
+ * Ici l'axe vertical n'est pas l'heure : c'est la semaine. Une rangée vaut SEPT JOURS et
20717
+ * l'heure ne bouge pas. En passant par la projection horaire, glisser d'une rangée déplaçait
20718
+ * un rendez-vous de 10 h 30 à 13 h 30 sans changer de date — mesuré au navigateur, signalé
20719
+ * par SZU. On MESURE la rangée plutôt que de la calculer : sa hauteur dépend du nombre de
20720
+ * voies de bandeaux, donc elle change d'une semaine à l'autre.
20721
+ */
20722
+ gesteMois(kind, o, ev) {
20723
+ if (!this.manipulable(o))
20724
+ return;
20725
+ ev.preventDefault();
20726
+ ev.stopPropagation();
20727
+ const el = this.hote.nativeElement;
20728
+ const rangee = ev.target.closest('.mois__sem') ?? el.querySelector('.mois__sem');
20729
+ const g = {
20730
+ largeurJour: el.querySelector('.mois__jour')?.getBoundingClientRect().width ?? 0,
20731
+ hauteurRangee: rangee?.getBoundingClientRect().height ?? 0,
20732
+ };
20733
+ const base = { start: o.start, end: o.end, allDay: o.allDay };
20734
+ const x0 = ev.clientX, y0 = ev.clientY;
20735
+ let bouge = false;
20736
+ const projette = (e) => cadProjeterMois(kind, base, e.clientX - x0, e.clientY - y0, g);
20737
+ const move = (e) => { bouge = true; this.poser(o, projette(e)); };
20738
+ const up = (e) => {
20739
+ document.removeEventListener('pointermove', move);
20740
+ document.removeEventListener('pointerup', up);
20741
+ if (!bouge)
20742
+ return;
20743
+ this.finGeste = Date.now();
20744
+ const b = projette(e);
20745
+ if (b.start.getTime() === base.start.getTime() && b.end.getTime() === base.end.getTime())
20746
+ return;
20747
+ this.poser(o, b);
20748
+ const charge = { occurrence: o, start: b.start, end: b.end };
20749
+ (kind === 'move' ? this.eventMove : this.eventResize).emit(charge);
20750
+ };
20751
+ document.addEventListener('pointermove', move);
20752
+ document.addEventListener('pointerup', up);
20753
+ }
20754
+ /** Création par glisser sur une plage libre. Un clic sec produit un pas, jamais rien. */
20755
+ creer(jour, ev, colonne) {
20756
+ if (!this.editable())
20757
+ return;
20758
+ if (ev.target.closest('.evt'))
20759
+ return;
20760
+ ev.preventDefault();
20761
+ const rect = colonne.getBoundingClientRect();
20762
+ const g = this.geometrie(rect.width);
20763
+ const y0 = ev.clientY - rect.top;
20764
+ const cle = cadNumeroJour(jour);
20765
+ const dessine = (y1) => {
20766
+ const b = cadProjeterCreation(jour, y0, y1, g);
20767
+ const h = (o) => (o.getHours() * 60 + o.getMinutes() - g.plageDebut) / 60 * g.hauteurHeure;
20768
+ this.fantome.set({ jour: cle, haut: h(b.start), hauteur: Math.max(12, h(b.end) - h(b.start)),
20769
+ texte: this.heure(b.start) + ' – ' + this.heure(b.end) });
20770
+ return b;
20771
+ };
20772
+ dessine(y0);
20773
+ const move = (e) => dessine(e.clientY - rect.top);
20774
+ const up = (e) => {
20775
+ document.removeEventListener('pointermove', move);
20776
+ document.removeEventListener('pointerup', up);
20777
+ const b = cadProjeterCreation(jour, y0, e.clientY - rect.top, g);
20778
+ this.fantome.set(null);
20779
+ this.finGeste = Date.now();
20780
+ this.eventCreate.emit({ start: b.start, end: b.end, allDay: false });
20781
+ };
20782
+ document.addEventListener('pointermove', move);
20783
+ document.addEventListener('pointerup', up);
20784
+ }
20785
+ /**
20786
+ * Le CLAVIER passe par la MÊME projection que le pointeur — une seule logique de placement
20787
+ * à éprouver, et l'accessibilité n'est pas une reprise ultérieure qui dérive.
20788
+ * Ctrl/Cmd + flèches déplacent, Maj + flèches verticales redimensionnent par le bas.
20789
+ */
20790
+ clavier(o, e) {
20791
+ if (!this.manipulable(o))
20792
+ return;
20793
+ const fleches = {
20794
+ ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1],
20795
+ };
20796
+ const d = fleches[e.key];
20797
+ if (!d)
20798
+ return;
20799
+ const deplace = e.ctrlKey || e.metaKey;
20800
+ const taille = e.shiftKey;
20801
+ if (!deplace && !taille)
20802
+ return;
20803
+ e.preventDefault();
20804
+ const g = this.geometrie(1);
20805
+ const base = { start: o.start, end: o.end, allDay: o.allDay };
20806
+ const kind = taille ? 'resize-end' : 'move';
20807
+ const b = cadProjeterEdition(kind, base, d[0] * g.largeurJour, (d[1] * g.pas / 60) * g.hauteurHeure, g);
20808
+ this.poser(o, b);
20809
+ (kind === 'move' ? this.eventMove : this.eventResize).emit({ occurrence: o, start: b.start, end: b.end });
20810
+ }
20811
+ clicEvenement(occurrence, originalEvent) {
20812
+ // Le clic qui SUIT un glisser est celui du navigateur, pas celui de l'utilisateur.
20813
+ if (Date.now() - this.finGeste < 300)
20814
+ return;
20815
+ this.eventClick.emit({ occurrence, originalEvent });
20816
+ }
20817
+ /** Le « +N » et un jour de la vue année mènent au JOUR : c'est ce qu'on cherchait. */
20818
+ voirJour(date, originalEvent) {
20819
+ this.dateClick.emit({ date, originalEvent });
20820
+ this.date.set(date);
20821
+ if (this.view() !== 'auto')
20822
+ this.view.set('day');
20823
+ }
20824
+ constructor() {
20825
+ // La fenêtre est annoncée : un hôte qui charge à la demande sait quoi aller chercher.
20826
+ let derniere = '';
20827
+ setTimeout(() => {
20828
+ const f = this.fenetre();
20829
+ const k = f.start.toISOString() + f.end.toISOString();
20830
+ if (k !== derniere) {
20831
+ derniere = k;
20832
+ this.rangeChange.emit(f);
20833
+ }
20834
+ });
20835
+ }
20836
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20837
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadCalendarComponent, isStandalone: true, selector: "cad-calendar", inputs: { plan: { classPropertyName: "plan", publicName: "plan", isSignal: true, isRequired: false, transformFunction: null }, sources: { classPropertyName: "sources", publicName: "sources", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, firstDay: { classPropertyName: "firstDay", publicName: "firstDay", isSignal: true, isRequired: false, transformFunction: null }, dayStart: { classPropertyName: "dayStart", publicName: "dayStart", isSignal: true, isRequired: false, transformFunction: null }, dayEnd: { classPropertyName: "dayEnd", publicName: "dayEnd", isSignal: true, isRequired: false, transformFunction: null }, hourHeight: { classPropertyName: "hourHeight", publicName: "hourHeight", isSignal: true, isRequired: false, transformFunction: null }, maxPerDay: { classPropertyName: "maxPerDay", publicName: "maxPerDay", isSignal: true, isRequired: false, transformFunction: null }, workCalendar: { classPropertyName: "workCalendar", publicName: "workCalendar", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, showNow: { classPropertyName: "showNow", publicName: "showNow", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hidden: "hiddenChange", view: "viewChange", date: "dateChange", eventClick: "eventClick", dateClick: "dateClick", rangeChange: "rangeChange", eventMove: "eventMove", eventResize: "eventResize", eventCreate: "eventCreate" }, host: { attributes: { "role": "region" }, properties: { "attr.aria-label": "ariaLabel() || null" }, classAttribute: "cad-calendar" }, ngImport: i0, template: `
20838
+ <div class="cal__corps">
20839
+ <div class="cal__vue">
20840
+ @switch (vueEffective()) {
20841
+ @case ('month') {
20842
+ <div class="mois">
20843
+ @for (n of nomsJours(); track $index) { <div class="mois__jsem">{{ n }}</div> }
20844
+ @for (sem of semaines(); track $index) {
20845
+ <div class="mois__sem">
20846
+ @for (j of sem.jours; track j.cle) {
20847
+ <div class="mois__jour"
20848
+ [class.is-hors]="j.hors" [class.is-auj]="j.auj" [class.is-ferme]="j.ferme">
20849
+ <div class="mois__n">{{ j.n }}</div>
20850
+ <!-- La place des bandeaux est RÉSERVÉE dans chaque case : le calque étant
20851
+ en position absolue, sans cette réserve il recouvrirait les puces. -->
20852
+ <div class="mois__reserve" [style.height.px]="sem.voies * 19"></div>
20853
+ @for (o of j.puces; track o.id) {
20854
+ <button type="button" class="puce"
20855
+ [class.is-editable]="manipulable(o)"
20856
+ [class.is-verrouille]="editable() && !manipulable(o)"
20857
+ [style.--_c]="couleur(o)"
20858
+ [title]="titre(o) + (motifVerrou(o) ? ' · ' + motifVerrou(o) : '')"
20859
+ (pointerdown)="gesteMois('move', o, $event)"
20860
+ (keydown)="clavier(o, $event)"
20861
+ (click)="clicEvenement(o, $event)">
20862
+ @if (!o.allDay) { <span class="puce__h">{{ heure(o.start) }}</span> }
20863
+ <span class="puce__t">{{ o.task.label }}</span>
20864
+ @if (o.recurring) { <span aria-hidden="true">↻</span> }
20865
+ </button>
20866
+ }
20867
+ @if (j.reste > 0) {
20868
+ <button type="button" class="plus" (click)="voirJour(j.date, $event)">
20869
+ +{{ j.reste }} autre{{ j.reste > 1 ? 's' : '' }}
20870
+ </button>
20871
+ }
20872
+ </div>
20873
+ }
20874
+ <!-- CALQUE DES BANDEAUX : un multi-jours doit être CONTINU d'un jour à
20875
+ l'autre. Le dessiner dans chaque case le répéterait ; il vit donc
20876
+ au-dessus de la rangée, en pourcentages de sa largeur. -->
20877
+ <div class="calque" [style.top.px]="20">
20878
+ @for (b of sem.bandes; track b.occurrence.id) {
20879
+ <button type="button" class="bandeau"
20880
+ [class.is-avant]="b.continueAvant" [class.is-apres]="b.continueApres"
20881
+ [class.is-editable]="manipulable(b.occurrence)"
20882
+ [class.is-verrouille]="editable() && !manipulable(b.occurrence)"
20883
+ [style.--_c]="couleur(b.occurrence)"
20884
+ [style.left.%]="b.colDebut / 7 * 100"
20885
+ [style.width.%]="(b.colFin - b.colDebut + 1) / 7 * 100"
20886
+ [style.top.px]="b.voie * 19"
20887
+ [title]="titre(b.occurrence) + (motifVerrou(b.occurrence) ? ' · ' + motifVerrou(b.occurrence) : '')"
20888
+ (pointerdown)="gesteMois('move', b.occurrence, $event)"
20889
+ (keydown)="clavier(b.occurrence, $event)"
20890
+ (click)="clicEvenement(b.occurrence, $event)">
20891
+ {{ b.continueAvant ? '◀ ' : '' }}{{ b.occurrence.task.label }}{{ b.continueApres ? ' ▶' : '' }}
20892
+ @if (manipulable(b.occurrence)) {
20893
+ <span class="poignee is-haut" (pointerdown)="gesteMois('resize-start', b.occurrence, $event)"></span>
20894
+ <span class="poignee is-bas" (pointerdown)="gesteMois('resize-end', b.occurrence, $event)"></span>
20895
+ }
20896
+ </button>
20897
+ }
20898
+ </div>
20899
+ </div>
20900
+ }
20901
+ </div>
20902
+ }
20903
+
20904
+ @case ('agenda') {
20905
+ <div class="agenda">
20906
+ @for (g of groupes(); track g.cle) {
20907
+ <div class="ag__jour">
20908
+ <div class="ag__d" [class.is-auj]="g.auj"><b>{{ g.n }}</b><span>{{ g.jsem }}</span></div>
20909
+ <div class="ag__l">
20910
+ @for (o of g.evts; track o.id) {
20911
+ <button type="button" class="ag__e" [style.--_c]="couleur(o)" (click)="clicEvenement(o, $event)">
20912
+ <span class="ag__p"></span>
20913
+ <span class="ag__h">{{ o.allDay ? 'journée' : heure(o.start) + ' – ' + heure(o.end) }}</span>
20914
+ <span class="ag__t">{{ o.task.label }}@if (o.recurring) { <span aria-hidden="true"> ↻</span> }</span>
20915
+ <span class="ag__c">{{ nomSource(o) }}</span>
20916
+ </button>
20917
+ }
20918
+ </div>
20919
+ </div>
20920
+ } @empty { <div class="vide">{{ emptyText() }}</div> }
20921
+ </div>
20922
+ }
20923
+
20924
+ @case ('year') {
20925
+ <div class="annee">
20926
+ @for (m of miniMois(); track m.mois) {
20927
+ <div class="mm">
20928
+ <div class="mm__t">{{ m.titre }}</div>
20929
+ <div class="mm__g">
20930
+ @for (n of nomsJours(); track $index) { <span>{{ n.charAt(0) }}</span> }
20931
+ @for (j of m.jours; track j.cle) {
20932
+ <button type="button" [class.is-hors]="j.hors" [class.is-auj]="j.auj"
20933
+ [title]="j.charge ? j.charge + ' événement(s)' : ''"
20934
+ (click)="voirJour(j.date, $event)">
20935
+ {{ j.n }}@if (j.charge) { <span class="mm__pt"></span> }
20936
+ </button>
20937
+ }
20938
+ </div>
20939
+ </div>
20940
+ }
20941
+ </div>
20942
+ }
20943
+
20944
+ @default {
20945
+ <div class="grille">
20946
+ <div class="grille__hd" [style.grid-template-columns]="colonnes()">
20947
+ <div></div>
20948
+ @for (j of joursGrille(); track j.cle) {
20949
+ <div [class.is-auj]="j.auj"><div class="grille__d">{{ j.jsem }}</div><div class="grille__n">{{ j.n }}</div></div>
20950
+ }
20951
+ </div>
20952
+
20953
+ <div class="bande" [style.grid-template-columns]="colonnes()">
20954
+ <div class="bande__lbl">jour</div>
20955
+ <div class="bande__zone" [style.height.px]="Math.max(1, bandeGrille().voies) * 19 + 2">
20956
+ @for (b of bandeGrille().bandes; track b.occurrence.id) {
20957
+ <button type="button" class="bandeau" #bd
20958
+ [class.is-avant]="b.continueAvant" [class.is-apres]="b.continueApres"
20959
+ [class.is-editable]="manipulable(b.occurrence)"
20960
+ [class.is-verrouille]="editable() && !manipulable(b.occurrence)"
20961
+ [style.--_c]="couleur(b.occurrence)"
20962
+ [style.left.%]="b.colDebut / joursGrille().length * 100"
20963
+ [style.width.%]="(b.colFin - b.colDebut + 1) / joursGrille().length * 100"
20964
+ [style.top.px]="b.voie * 19"
20965
+ [title]="titre(b.occurrence) + (motifVerrou(b.occurrence) ? ' · ' + motifVerrou(b.occurrence) : '')"
20966
+ (pointerdown)="geste('move', b.occurrence, $event, largeurColonne())"
20967
+ (keydown)="clavier(b.occurrence, $event)"
20968
+ (click)="clicEvenement(b.occurrence, $event)">
20969
+ {{ b.continueAvant ? '◀ ' : '' }}{{ b.occurrence.task.label }}{{ b.continueApres ? ' ▶' : '' }}
20970
+ @if (manipulable(b.occurrence)) {
20971
+ <span class="poignee is-haut" (pointerdown)="geste('resize-start', b.occurrence, $event, largeurColonne())"></span>
20972
+ <span class="poignee is-bas" (pointerdown)="geste('resize-end', b.occurrence, $event, largeurColonne())"></span>
20973
+ }
20974
+ </button>
20975
+ }
20976
+ </div>
20977
+ </div>
20978
+
20979
+ <div class="heures" [style.grid-template-columns]="colonnes()">
20980
+ <div class="heures__lbl">
20981
+ @for (h of heuresAffichees(); track h) { <div [style.height.px]="hauteurHeure()">{{ h }}h</div> }
20982
+ </div>
20983
+ @for (j of joursGrille(); track j.cle) {
20984
+ <div class="heures__col" #col [class.is-ferme]="j.ferme"
20985
+ (pointerdown)="creer(j.date, $event, col)">
20986
+ @for (h of heuresAffichees(); track h) {
20987
+ <div class="trait" [style.top.px]="(h - dayStart()) * hauteurHeure()"></div>
20988
+ <div class="trait is-demi" [style.top.px]="(h - dayStart()) * hauteurHeure() + hauteurHeure() / 2"></div>
20989
+ }
20990
+ @for (s of j.slots; track s.occurrence.id) {
20991
+ <button type="button" class="evt"
20992
+ [class.is-editable]="manipulable(s.occurrence)"
20993
+ [class.is-verrouille]="editable() && !manipulable(s.occurrence)"
20994
+ [style.--_c]="couleur(s.occurrence)"
20995
+ [style.top.px]="s.haut * hauteurTotale()"
20996
+ [style.height.px]="Math.max(16, s.hauteur * hauteurTotale() - 2)"
20997
+ [style.left]="'calc(' + (s.voie / s.voies * 100) + '% + 1px)'"
20998
+ [style.width]="'calc(' + (100 / s.voies) + '% - 3px)'"
20999
+ [title]="titre(s.occurrence) + (motifVerrou(s.occurrence) ? ' · ' + motifVerrou(s.occurrence) : '')"
21000
+ (pointerdown)="geste('move', s.occurrence, $event, col)"
21001
+ (keydown)="clavier(s.occurrence, $event)"
21002
+ (click)="clicEvenement(s.occurrence, $event)">
21003
+ <span class="evt__t">{{ s.occurrence.task.label }}@if (s.occurrence.recurring) { <span aria-hidden="true"> ↻</span> }</span>
21004
+ @if (s.hauteur * hauteurTotale() > 30) {
21005
+ <span class="evt__h">{{ heure(s.occurrence.start) }} – {{ heure(s.occurrence.end) }}</span>
21006
+ }
21007
+ @if (manipulable(s.occurrence)) {
21008
+ <span class="poignee is-haut" (pointerdown)="geste('resize-start', s.occurrence, $event, col)"></span>
21009
+ <span class="poignee is-bas" (pointerdown)="geste('resize-end', s.occurrence, $event, col)"></span>
21010
+ }
21011
+ </button>
21012
+ }
21013
+ @if (fantome(); as f) {
21014
+ @if (f.jour === j.cle) {
21015
+ <div class="fantome" [style.top.px]="f.haut" [style.height.px]="f.hauteur"
21016
+ [style.left.px]="2" [style.right.px]="2">{{ f.texte }}</div>
21017
+ }
21018
+ }
21019
+ @if (j.auj && maintenant() !== null) {
21020
+ <div class="maintenant" [style.top.px]="maintenant()"></div>
21021
+ }
21022
+ </div>
21023
+ }
21024
+ </div>
21025
+ </div>
21026
+ }
21027
+ }
21028
+ </div>
21029
+ </div>
21030
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;min-height:0;height:100%;font:var(--cad-font-size)/1.4 var(--cad-font);color:var(--cad-fg);background:var(--cad-bg)}.cal__corps{flex:1;min-height:0;display:flex}.cal__vue{flex:1;min-width:0;min-height:0;overflow:auto}.mois{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));grid-template-rows:auto;grid-auto-rows:minmax(84px,1fr);min-height:100%}.mois__jsem{padding:6px 8px;text-align:right;border-bottom:1px solid var(--cad-border-strong);background:var(--cad-bg-subtle);position:sticky;top:0;z-index:4;font:600 10.5px var(--cad-font-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--cad-fg-muted)}.mois__sem{grid-column:1 / -1;display:grid;grid-template-columns:repeat(7,minmax(0,1fr));position:relative;border-bottom:1px solid var(--cad-border);min-height:84px}.mois__jour{border-right:1px solid var(--cad-border);padding:3px;min-width:0;display:flex;flex-direction:column;gap:2px}.mois__jour:last-child{border-right:0}.mois__jour.is-hors,.mois__jour.is-ferme{background:var(--cad-bg-subtle)}.mois__n{font:11px var(--cad-font-mono);color:var(--cad-fg-secondary);text-align:right;padding:1px 3px;font-variant-numeric:tabular-nums;flex:none}.mois__jour.is-auj .mois__n{background:var(--cad-accent);color:var(--cad-fg-on-accent);border-radius:var(--cad-radius-sm);font-weight:600}.mois__reserve{flex:none}.calque{position:absolute;left:0;right:0;pointer-events:none;z-index:2}.bandeau{position:absolute;height:17px;border-radius:var(--cad-radius-sm);padding:0 7px;font-size:10.5px;line-height:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:auto;cursor:pointer;background:color-mix(in srgb,var(--_c) 22%,transparent);color:var(--_c);border:1px solid color-mix(in srgb,var(--_c) 42%,transparent)}.bandeau.is-avant{border-top-left-radius:0;border-bottom-left-radius:0;border-left-style:dashed}.bandeau.is-apres{border-top-right-radius:0;border-bottom-right-radius:0;border-right-style:dashed}.puce{display:flex;align-items:center;gap:5px;font-size:10.5px;padding:1px 5px;border-radius:var(--cad-radius-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;border-left:2px solid var(--_c);background:color-mix(in srgb,var(--_c) 13%,transparent);color:var(--_c)}.puce:hover{background:color-mix(in srgb,var(--_c) 22%,transparent)}.puce__h{font:9.5px var(--cad-font-mono);opacity:.85;flex:none;font-variant-numeric:tabular-nums}.puce__t{min-width:0;overflow:hidden;text-overflow:ellipsis}.plus{all:unset;box-sizing:border-box;font-size:10px;color:var(--cad-fg-muted);padding:1px 5px;cursor:pointer;border-radius:var(--cad-radius-sm)}.plus:hover{color:var(--cad-accent);background:var(--cad-hover)}.grille{min-width:0;display:flex;flex-direction:column;height:100%}.grille__hd{display:grid;position:sticky;top:0;z-index:5;background:var(--cad-bg-subtle);border-bottom:1px solid var(--cad-border-strong);flex:none}.grille__hd>div{padding:5px 6px;text-align:center;border-right:1px solid var(--cad-border);min-width:0}.grille__hd>div:last-child{border-right:0}.grille__d{font:10px var(--cad-font-mono);color:var(--cad-fg-muted);text-transform:uppercase;letter-spacing:.06em}.grille__n{font-size:16px;font-weight:600;letter-spacing:-.02em}.is-auj .grille__n{color:var(--cad-accent)}.bande{display:grid;border-bottom:1px solid var(--cad-border-strong);background:var(--cad-bg-subtle);flex:none}.bande__lbl{font:9px var(--cad-font-mono);color:var(--cad-fg-muted);padding:4px 6px;text-align:right;border-right:1px solid var(--cad-border)}.bande__zone{grid-column:2 / -1;position:relative;padding:2px 0}.heures{display:grid;position:relative;flex:1;min-height:0;overflow:auto}.heures__lbl{border-right:1px solid var(--cad-border)}.heures__lbl>div{font:9.5px var(--cad-font-mono);color:var(--cad-fg-muted);text-align:right;padding-right:6px;transform:translateY(-6px)}.heures__col{border-right:1px solid var(--cad-border);position:relative;min-width:0}.heures__col:last-child{border-right:0}.heures__col.is-ferme{background:var(--cad-bg-subtle)}.trait{position:absolute;left:0;right:0;border-top:1px solid var(--cad-border);pointer-events:none}.trait.is-demi{border-top-style:dotted;opacity:.55}.evt{position:absolute;border-radius:var(--cad-radius-sm);padding:1px 5px;overflow:hidden;cursor:pointer;border-left:3px solid var(--_c);font-size:10.5px;line-height:1.25;background:color-mix(in srgb,var(--_c) 15%,transparent);color:var(--_c)}.evt:hover{background:color-mix(in srgb,var(--_c) 26%,transparent)}.evt__t{display:block;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.evt__h{font:9px var(--cad-font-mono);opacity:.85}.maintenant{position:absolute;left:0;right:0;height:2px;background:var(--cad-danger);z-index:4;pointer-events:none}.maintenant:before{content:\"\";position:absolute;left:-3px;top:-3px;width:8px;height:8px;border-radius:50%;background:var(--cad-danger)}.agenda{display:flex;flex-direction:column}.ag__jour{display:grid;grid-template-columns:72px minmax(0,1fr);border-bottom:1px solid var(--cad-border)}.ag__d{padding:9px 11px;border-right:1px solid var(--cad-border);background:var(--cad-bg-subtle)}.ag__d b{display:block;font-size:18px;font-weight:600;letter-spacing:-.02em}.ag__d span{display:block;font:9.5px var(--cad-font-mono);color:var(--cad-fg-muted);text-transform:uppercase}.ag__d.is-auj b{color:var(--cad-accent)}.ag__l{padding:5px 0;min-width:0}.ag__e{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:9px;padding:6px 11px;cursor:pointer;width:100%;min-height:34px}.ag__e:hover{background:var(--cad-hover)}.ag__e:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.ag__p{width:8px;height:8px;border-radius:50%;background:var(--_c);flex:none}.ag__h{font:11px var(--cad-font-mono);color:var(--cad-fg-secondary);flex:none;width:92px;font-variant-numeric:tabular-nums}.ag__t{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag__c{margin-left:auto;font-size:10.5px;color:var(--cad-fg-muted);flex:none}.annee{display:grid;grid-template-columns:repeat(auto-fill,minmax(178px,1fr));gap:14px;padding:14px}.mm{border:1px solid var(--cad-border);border-radius:var(--cad-radius);padding:8px;background:var(--cad-surface)}.mm__t{font-size:12px;font-weight:600;margin-bottom:5px;text-transform:capitalize}.mm__g{display:grid;grid-template-columns:repeat(7,1fr);gap:1px}.mm__g>span,.mm__g>button{all:unset;box-sizing:border-box;text-align:center;font:9.5px var(--cad-font-mono);padding:2px 0;border-radius:3px;color:var(--cad-fg-secondary);font-variant-numeric:tabular-nums}.mm__g>span{color:var(--cad-fg-muted);font-weight:500}.mm__g>button{cursor:pointer;position:relative}.mm__g>button:hover{background:var(--cad-hover)}.mm__g>button.is-hors{opacity:.35}.mm__g>button.is-auj{background:var(--cad-accent);color:var(--cad-fg-on-accent);font-weight:600}.mm__pt{position:absolute;left:50%;bottom:0;transform:translate(-50%);width:3px;height:3px;border-radius:50%;background:var(--cad-accent)}.mm__g>button.is-auj .mm__pt{background:var(--cad-fg-on-accent)}.evt.is-editable,.bandeau.is-editable,.puce.is-editable{cursor:grab}.evt.is-editable:active,.bandeau.is-editable:active,.puce.is-editable:active{cursor:grabbing}.poignee{position:absolute;z-index:2}.evt .poignee{left:0;right:0;height:6px;cursor:ns-resize}.evt .poignee.is-haut{top:0}.evt .poignee.is-bas{bottom:0}.bandeau .poignee{top:0;bottom:0;width:6px;cursor:ew-resize}.bandeau .poignee.is-haut{left:0}.bandeau .poignee.is-bas{right:0}.poignee:after{content:\"\";position:absolute;inset:0}.evt.is-verrouille,.bandeau.is-verrouille,.puce.is-verrouille{cursor:not-allowed;position:relative}.evt.is-verrouille:after,.bandeau.is-verrouille:after,.puce.is-verrouille:after{content:\"\";position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(-45deg,currentColor 0 1px,transparent 1px 5px);opacity:.18}.fantome{position:absolute;z-index:6;pointer-events:none;border-radius:var(--cad-radius-sm);background:var(--cad-accent-soft);border:1px dashed var(--cad-accent);color:var(--cad-accent);font:10.5px var(--cad-font-mono);padding:1px 5px;overflow:hidden;white-space:nowrap}.vide{padding:28px 16px;color:var(--cad-fg-muted);text-align:center}@media(prefers-reduced-motion:reduce){*{transition:none!important}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
21031
+ }
21032
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadCalendarComponent, decorators: [{
21033
+ type: Component,
21034
+ args: [{ selector: 'cad-calendar', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cad-calendar', '[attr.aria-label]': 'ariaLabel() || null', role: 'region' }, template: `
21035
+ <div class="cal__corps">
21036
+ <div class="cal__vue">
21037
+ @switch (vueEffective()) {
21038
+ @case ('month') {
21039
+ <div class="mois">
21040
+ @for (n of nomsJours(); track $index) { <div class="mois__jsem">{{ n }}</div> }
21041
+ @for (sem of semaines(); track $index) {
21042
+ <div class="mois__sem">
21043
+ @for (j of sem.jours; track j.cle) {
21044
+ <div class="mois__jour"
21045
+ [class.is-hors]="j.hors" [class.is-auj]="j.auj" [class.is-ferme]="j.ferme">
21046
+ <div class="mois__n">{{ j.n }}</div>
21047
+ <!-- La place des bandeaux est RÉSERVÉE dans chaque case : le calque étant
21048
+ en position absolue, sans cette réserve il recouvrirait les puces. -->
21049
+ <div class="mois__reserve" [style.height.px]="sem.voies * 19"></div>
21050
+ @for (o of j.puces; track o.id) {
21051
+ <button type="button" class="puce"
21052
+ [class.is-editable]="manipulable(o)"
21053
+ [class.is-verrouille]="editable() && !manipulable(o)"
21054
+ [style.--_c]="couleur(o)"
21055
+ [title]="titre(o) + (motifVerrou(o) ? ' · ' + motifVerrou(o) : '')"
21056
+ (pointerdown)="gesteMois('move', o, $event)"
21057
+ (keydown)="clavier(o, $event)"
21058
+ (click)="clicEvenement(o, $event)">
21059
+ @if (!o.allDay) { <span class="puce__h">{{ heure(o.start) }}</span> }
21060
+ <span class="puce__t">{{ o.task.label }}</span>
21061
+ @if (o.recurring) { <span aria-hidden="true">↻</span> }
21062
+ </button>
21063
+ }
21064
+ @if (j.reste > 0) {
21065
+ <button type="button" class="plus" (click)="voirJour(j.date, $event)">
21066
+ +{{ j.reste }} autre{{ j.reste > 1 ? 's' : '' }}
21067
+ </button>
21068
+ }
21069
+ </div>
21070
+ }
21071
+ <!-- CALQUE DES BANDEAUX : un multi-jours doit être CONTINU d'un jour à
21072
+ l'autre. Le dessiner dans chaque case le répéterait ; il vit donc
21073
+ au-dessus de la rangée, en pourcentages de sa largeur. -->
21074
+ <div class="calque" [style.top.px]="20">
21075
+ @for (b of sem.bandes; track b.occurrence.id) {
21076
+ <button type="button" class="bandeau"
21077
+ [class.is-avant]="b.continueAvant" [class.is-apres]="b.continueApres"
21078
+ [class.is-editable]="manipulable(b.occurrence)"
21079
+ [class.is-verrouille]="editable() && !manipulable(b.occurrence)"
21080
+ [style.--_c]="couleur(b.occurrence)"
21081
+ [style.left.%]="b.colDebut / 7 * 100"
21082
+ [style.width.%]="(b.colFin - b.colDebut + 1) / 7 * 100"
21083
+ [style.top.px]="b.voie * 19"
21084
+ [title]="titre(b.occurrence) + (motifVerrou(b.occurrence) ? ' · ' + motifVerrou(b.occurrence) : '')"
21085
+ (pointerdown)="gesteMois('move', b.occurrence, $event)"
21086
+ (keydown)="clavier(b.occurrence, $event)"
21087
+ (click)="clicEvenement(b.occurrence, $event)">
21088
+ {{ b.continueAvant ? '◀ ' : '' }}{{ b.occurrence.task.label }}{{ b.continueApres ? ' ▶' : '' }}
21089
+ @if (manipulable(b.occurrence)) {
21090
+ <span class="poignee is-haut" (pointerdown)="gesteMois('resize-start', b.occurrence, $event)"></span>
21091
+ <span class="poignee is-bas" (pointerdown)="gesteMois('resize-end', b.occurrence, $event)"></span>
21092
+ }
21093
+ </button>
21094
+ }
21095
+ </div>
21096
+ </div>
21097
+ }
21098
+ </div>
21099
+ }
21100
+
21101
+ @case ('agenda') {
21102
+ <div class="agenda">
21103
+ @for (g of groupes(); track g.cle) {
21104
+ <div class="ag__jour">
21105
+ <div class="ag__d" [class.is-auj]="g.auj"><b>{{ g.n }}</b><span>{{ g.jsem }}</span></div>
21106
+ <div class="ag__l">
21107
+ @for (o of g.evts; track o.id) {
21108
+ <button type="button" class="ag__e" [style.--_c]="couleur(o)" (click)="clicEvenement(o, $event)">
21109
+ <span class="ag__p"></span>
21110
+ <span class="ag__h">{{ o.allDay ? 'journée' : heure(o.start) + ' – ' + heure(o.end) }}</span>
21111
+ <span class="ag__t">{{ o.task.label }}@if (o.recurring) { <span aria-hidden="true"> ↻</span> }</span>
21112
+ <span class="ag__c">{{ nomSource(o) }}</span>
21113
+ </button>
21114
+ }
21115
+ </div>
21116
+ </div>
21117
+ } @empty { <div class="vide">{{ emptyText() }}</div> }
21118
+ </div>
21119
+ }
21120
+
21121
+ @case ('year') {
21122
+ <div class="annee">
21123
+ @for (m of miniMois(); track m.mois) {
21124
+ <div class="mm">
21125
+ <div class="mm__t">{{ m.titre }}</div>
21126
+ <div class="mm__g">
21127
+ @for (n of nomsJours(); track $index) { <span>{{ n.charAt(0) }}</span> }
21128
+ @for (j of m.jours; track j.cle) {
21129
+ <button type="button" [class.is-hors]="j.hors" [class.is-auj]="j.auj"
21130
+ [title]="j.charge ? j.charge + ' événement(s)' : ''"
21131
+ (click)="voirJour(j.date, $event)">
21132
+ {{ j.n }}@if (j.charge) { <span class="mm__pt"></span> }
21133
+ </button>
21134
+ }
21135
+ </div>
21136
+ </div>
21137
+ }
21138
+ </div>
21139
+ }
21140
+
21141
+ @default {
21142
+ <div class="grille">
21143
+ <div class="grille__hd" [style.grid-template-columns]="colonnes()">
21144
+ <div></div>
21145
+ @for (j of joursGrille(); track j.cle) {
21146
+ <div [class.is-auj]="j.auj"><div class="grille__d">{{ j.jsem }}</div><div class="grille__n">{{ j.n }}</div></div>
21147
+ }
21148
+ </div>
21149
+
21150
+ <div class="bande" [style.grid-template-columns]="colonnes()">
21151
+ <div class="bande__lbl">jour</div>
21152
+ <div class="bande__zone" [style.height.px]="Math.max(1, bandeGrille().voies) * 19 + 2">
21153
+ @for (b of bandeGrille().bandes; track b.occurrence.id) {
21154
+ <button type="button" class="bandeau" #bd
21155
+ [class.is-avant]="b.continueAvant" [class.is-apres]="b.continueApres"
21156
+ [class.is-editable]="manipulable(b.occurrence)"
21157
+ [class.is-verrouille]="editable() && !manipulable(b.occurrence)"
21158
+ [style.--_c]="couleur(b.occurrence)"
21159
+ [style.left.%]="b.colDebut / joursGrille().length * 100"
21160
+ [style.width.%]="(b.colFin - b.colDebut + 1) / joursGrille().length * 100"
21161
+ [style.top.px]="b.voie * 19"
21162
+ [title]="titre(b.occurrence) + (motifVerrou(b.occurrence) ? ' · ' + motifVerrou(b.occurrence) : '')"
21163
+ (pointerdown)="geste('move', b.occurrence, $event, largeurColonne())"
21164
+ (keydown)="clavier(b.occurrence, $event)"
21165
+ (click)="clicEvenement(b.occurrence, $event)">
21166
+ {{ b.continueAvant ? '◀ ' : '' }}{{ b.occurrence.task.label }}{{ b.continueApres ? ' ▶' : '' }}
21167
+ @if (manipulable(b.occurrence)) {
21168
+ <span class="poignee is-haut" (pointerdown)="geste('resize-start', b.occurrence, $event, largeurColonne())"></span>
21169
+ <span class="poignee is-bas" (pointerdown)="geste('resize-end', b.occurrence, $event, largeurColonne())"></span>
21170
+ }
21171
+ </button>
21172
+ }
21173
+ </div>
21174
+ </div>
21175
+
21176
+ <div class="heures" [style.grid-template-columns]="colonnes()">
21177
+ <div class="heures__lbl">
21178
+ @for (h of heuresAffichees(); track h) { <div [style.height.px]="hauteurHeure()">{{ h }}h</div> }
21179
+ </div>
21180
+ @for (j of joursGrille(); track j.cle) {
21181
+ <div class="heures__col" #col [class.is-ferme]="j.ferme"
21182
+ (pointerdown)="creer(j.date, $event, col)">
21183
+ @for (h of heuresAffichees(); track h) {
21184
+ <div class="trait" [style.top.px]="(h - dayStart()) * hauteurHeure()"></div>
21185
+ <div class="trait is-demi" [style.top.px]="(h - dayStart()) * hauteurHeure() + hauteurHeure() / 2"></div>
21186
+ }
21187
+ @for (s of j.slots; track s.occurrence.id) {
21188
+ <button type="button" class="evt"
21189
+ [class.is-editable]="manipulable(s.occurrence)"
21190
+ [class.is-verrouille]="editable() && !manipulable(s.occurrence)"
21191
+ [style.--_c]="couleur(s.occurrence)"
21192
+ [style.top.px]="s.haut * hauteurTotale()"
21193
+ [style.height.px]="Math.max(16, s.hauteur * hauteurTotale() - 2)"
21194
+ [style.left]="'calc(' + (s.voie / s.voies * 100) + '% + 1px)'"
21195
+ [style.width]="'calc(' + (100 / s.voies) + '% - 3px)'"
21196
+ [title]="titre(s.occurrence) + (motifVerrou(s.occurrence) ? ' · ' + motifVerrou(s.occurrence) : '')"
21197
+ (pointerdown)="geste('move', s.occurrence, $event, col)"
21198
+ (keydown)="clavier(s.occurrence, $event)"
21199
+ (click)="clicEvenement(s.occurrence, $event)">
21200
+ <span class="evt__t">{{ s.occurrence.task.label }}@if (s.occurrence.recurring) { <span aria-hidden="true"> ↻</span> }</span>
21201
+ @if (s.hauteur * hauteurTotale() > 30) {
21202
+ <span class="evt__h">{{ heure(s.occurrence.start) }} – {{ heure(s.occurrence.end) }}</span>
21203
+ }
21204
+ @if (manipulable(s.occurrence)) {
21205
+ <span class="poignee is-haut" (pointerdown)="geste('resize-start', s.occurrence, $event, col)"></span>
21206
+ <span class="poignee is-bas" (pointerdown)="geste('resize-end', s.occurrence, $event, col)"></span>
21207
+ }
21208
+ </button>
21209
+ }
21210
+ @if (fantome(); as f) {
21211
+ @if (f.jour === j.cle) {
21212
+ <div class="fantome" [style.top.px]="f.haut" [style.height.px]="f.hauteur"
21213
+ [style.left.px]="2" [style.right.px]="2">{{ f.texte }}</div>
21214
+ }
21215
+ }
21216
+ @if (j.auj && maintenant() !== null) {
21217
+ <div class="maintenant" [style.top.px]="maintenant()"></div>
21218
+ }
21219
+ </div>
21220
+ }
21221
+ </div>
21222
+ </div>
21223
+ }
21224
+ }
21225
+ </div>
21226
+ </div>
21227
+ `, styles: [":host{display:flex;flex-direction:column;min-height:0;height:100%;font:var(--cad-font-size)/1.4 var(--cad-font);color:var(--cad-fg);background:var(--cad-bg)}.cal__corps{flex:1;min-height:0;display:flex}.cal__vue{flex:1;min-width:0;min-height:0;overflow:auto}.mois{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));grid-template-rows:auto;grid-auto-rows:minmax(84px,1fr);min-height:100%}.mois__jsem{padding:6px 8px;text-align:right;border-bottom:1px solid var(--cad-border-strong);background:var(--cad-bg-subtle);position:sticky;top:0;z-index:4;font:600 10.5px var(--cad-font-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--cad-fg-muted)}.mois__sem{grid-column:1 / -1;display:grid;grid-template-columns:repeat(7,minmax(0,1fr));position:relative;border-bottom:1px solid var(--cad-border);min-height:84px}.mois__jour{border-right:1px solid var(--cad-border);padding:3px;min-width:0;display:flex;flex-direction:column;gap:2px}.mois__jour:last-child{border-right:0}.mois__jour.is-hors,.mois__jour.is-ferme{background:var(--cad-bg-subtle)}.mois__n{font:11px var(--cad-font-mono);color:var(--cad-fg-secondary);text-align:right;padding:1px 3px;font-variant-numeric:tabular-nums;flex:none}.mois__jour.is-auj .mois__n{background:var(--cad-accent);color:var(--cad-fg-on-accent);border-radius:var(--cad-radius-sm);font-weight:600}.mois__reserve{flex:none}.calque{position:absolute;left:0;right:0;pointer-events:none;z-index:2}.bandeau{position:absolute;height:17px;border-radius:var(--cad-radius-sm);padding:0 7px;font-size:10.5px;line-height:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:auto;cursor:pointer;background:color-mix(in srgb,var(--_c) 22%,transparent);color:var(--_c);border:1px solid color-mix(in srgb,var(--_c) 42%,transparent)}.bandeau.is-avant{border-top-left-radius:0;border-bottom-left-radius:0;border-left-style:dashed}.bandeau.is-apres{border-top-right-radius:0;border-bottom-right-radius:0;border-right-style:dashed}.puce{display:flex;align-items:center;gap:5px;font-size:10.5px;padding:1px 5px;border-radius:var(--cad-radius-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;border-left:2px solid var(--_c);background:color-mix(in srgb,var(--_c) 13%,transparent);color:var(--_c)}.puce:hover{background:color-mix(in srgb,var(--_c) 22%,transparent)}.puce__h{font:9.5px var(--cad-font-mono);opacity:.85;flex:none;font-variant-numeric:tabular-nums}.puce__t{min-width:0;overflow:hidden;text-overflow:ellipsis}.plus{all:unset;box-sizing:border-box;font-size:10px;color:var(--cad-fg-muted);padding:1px 5px;cursor:pointer;border-radius:var(--cad-radius-sm)}.plus:hover{color:var(--cad-accent);background:var(--cad-hover)}.grille{min-width:0;display:flex;flex-direction:column;height:100%}.grille__hd{display:grid;position:sticky;top:0;z-index:5;background:var(--cad-bg-subtle);border-bottom:1px solid var(--cad-border-strong);flex:none}.grille__hd>div{padding:5px 6px;text-align:center;border-right:1px solid var(--cad-border);min-width:0}.grille__hd>div:last-child{border-right:0}.grille__d{font:10px var(--cad-font-mono);color:var(--cad-fg-muted);text-transform:uppercase;letter-spacing:.06em}.grille__n{font-size:16px;font-weight:600;letter-spacing:-.02em}.is-auj .grille__n{color:var(--cad-accent)}.bande{display:grid;border-bottom:1px solid var(--cad-border-strong);background:var(--cad-bg-subtle);flex:none}.bande__lbl{font:9px var(--cad-font-mono);color:var(--cad-fg-muted);padding:4px 6px;text-align:right;border-right:1px solid var(--cad-border)}.bande__zone{grid-column:2 / -1;position:relative;padding:2px 0}.heures{display:grid;position:relative;flex:1;min-height:0;overflow:auto}.heures__lbl{border-right:1px solid var(--cad-border)}.heures__lbl>div{font:9.5px var(--cad-font-mono);color:var(--cad-fg-muted);text-align:right;padding-right:6px;transform:translateY(-6px)}.heures__col{border-right:1px solid var(--cad-border);position:relative;min-width:0}.heures__col:last-child{border-right:0}.heures__col.is-ferme{background:var(--cad-bg-subtle)}.trait{position:absolute;left:0;right:0;border-top:1px solid var(--cad-border);pointer-events:none}.trait.is-demi{border-top-style:dotted;opacity:.55}.evt{position:absolute;border-radius:var(--cad-radius-sm);padding:1px 5px;overflow:hidden;cursor:pointer;border-left:3px solid var(--_c);font-size:10.5px;line-height:1.25;background:color-mix(in srgb,var(--_c) 15%,transparent);color:var(--_c)}.evt:hover{background:color-mix(in srgb,var(--_c) 26%,transparent)}.evt__t{display:block;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.evt__h{font:9px var(--cad-font-mono);opacity:.85}.maintenant{position:absolute;left:0;right:0;height:2px;background:var(--cad-danger);z-index:4;pointer-events:none}.maintenant:before{content:\"\";position:absolute;left:-3px;top:-3px;width:8px;height:8px;border-radius:50%;background:var(--cad-danger)}.agenda{display:flex;flex-direction:column}.ag__jour{display:grid;grid-template-columns:72px minmax(0,1fr);border-bottom:1px solid var(--cad-border)}.ag__d{padding:9px 11px;border-right:1px solid var(--cad-border);background:var(--cad-bg-subtle)}.ag__d b{display:block;font-size:18px;font-weight:600;letter-spacing:-.02em}.ag__d span{display:block;font:9.5px var(--cad-font-mono);color:var(--cad-fg-muted);text-transform:uppercase}.ag__d.is-auj b{color:var(--cad-accent)}.ag__l{padding:5px 0;min-width:0}.ag__e{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:9px;padding:6px 11px;cursor:pointer;width:100%;min-height:34px}.ag__e:hover{background:var(--cad-hover)}.ag__e:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.ag__p{width:8px;height:8px;border-radius:50%;background:var(--_c);flex:none}.ag__h{font:11px var(--cad-font-mono);color:var(--cad-fg-secondary);flex:none;width:92px;font-variant-numeric:tabular-nums}.ag__t{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag__c{margin-left:auto;font-size:10.5px;color:var(--cad-fg-muted);flex:none}.annee{display:grid;grid-template-columns:repeat(auto-fill,minmax(178px,1fr));gap:14px;padding:14px}.mm{border:1px solid var(--cad-border);border-radius:var(--cad-radius);padding:8px;background:var(--cad-surface)}.mm__t{font-size:12px;font-weight:600;margin-bottom:5px;text-transform:capitalize}.mm__g{display:grid;grid-template-columns:repeat(7,1fr);gap:1px}.mm__g>span,.mm__g>button{all:unset;box-sizing:border-box;text-align:center;font:9.5px var(--cad-font-mono);padding:2px 0;border-radius:3px;color:var(--cad-fg-secondary);font-variant-numeric:tabular-nums}.mm__g>span{color:var(--cad-fg-muted);font-weight:500}.mm__g>button{cursor:pointer;position:relative}.mm__g>button:hover{background:var(--cad-hover)}.mm__g>button.is-hors{opacity:.35}.mm__g>button.is-auj{background:var(--cad-accent);color:var(--cad-fg-on-accent);font-weight:600}.mm__pt{position:absolute;left:50%;bottom:0;transform:translate(-50%);width:3px;height:3px;border-radius:50%;background:var(--cad-accent)}.mm__g>button.is-auj .mm__pt{background:var(--cad-fg-on-accent)}.evt.is-editable,.bandeau.is-editable,.puce.is-editable{cursor:grab}.evt.is-editable:active,.bandeau.is-editable:active,.puce.is-editable:active{cursor:grabbing}.poignee{position:absolute;z-index:2}.evt .poignee{left:0;right:0;height:6px;cursor:ns-resize}.evt .poignee.is-haut{top:0}.evt .poignee.is-bas{bottom:0}.bandeau .poignee{top:0;bottom:0;width:6px;cursor:ew-resize}.bandeau .poignee.is-haut{left:0}.bandeau .poignee.is-bas{right:0}.poignee:after{content:\"\";position:absolute;inset:0}.evt.is-verrouille,.bandeau.is-verrouille,.puce.is-verrouille{cursor:not-allowed;position:relative}.evt.is-verrouille:after,.bandeau.is-verrouille:after,.puce.is-verrouille:after{content:\"\";position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(-45deg,currentColor 0 1px,transparent 1px 5px);opacity:.18}.fantome{position:absolute;z-index:6;pointer-events:none;border-radius:var(--cad-radius-sm);background:var(--cad-accent-soft);border:1px dashed var(--cad-accent);color:var(--cad-accent);font:10.5px var(--cad-font-mono);padding:1px 5px;overflow:hidden;white-space:nowrap}.vide{padding:28px 16px;color:var(--cad-fg-muted);text-align:center}@media(prefers-reduced-motion:reduce){*{transition:none!important}}\n"] }]
21228
+ }], ctorParameters: () => [], propDecorators: { plan: [{ type: i0.Input, args: [{ isSignal: true, alias: "plan", required: false }] }], sources: [{ type: i0.Input, args: [{ isSignal: true, alias: "sources", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }, { type: i0.Output, args: ["hiddenChange"] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }, { type: i0.Output, args: ["dateChange"] }], firstDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDay", required: false }] }], dayStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayStart", required: false }] }], dayEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayEnd", required: false }] }], hourHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "hourHeight", required: false }] }], maxPerDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPerDay", required: false }] }], workCalendar: [{ type: i0.Input, args: [{ isSignal: true, alias: "workCalendar", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], showNow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNow", required: false }] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }], dateClick: [{ type: i0.Output, args: ["dateClick"] }], rangeChange: [{ type: i0.Output, args: ["rangeChange"] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], snap: [{ type: i0.Input, args: [{ isSignal: true, alias: "snap", required: false }] }], eventMove: [{ type: i0.Output, args: ["eventMove"] }], eventResize: [{ type: i0.Output, args: ["eventResize"] }], eventCreate: [{ type: i0.Output, args: ["eventCreate"] }] } });
21229
+
19543
21230
  /**
19544
21231
  * Generated bundle index. Do not edit.
19545
21232
  */
19546
21233
 
19547
- export { 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_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, 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, 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, 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, addDays, addMonths, addYears, cadAggregate, cadApplyColumnOrder, cadApplyMask, cadArea, cadAutosizeWidth, cadBasemapThumbnail, cadBoundsOf, cadCategories, cadCentroid, cadColorExpr, cadCompare, cadCompareRank, cadCurrentOperator, cadDefaultMatchMode, cadDistance, cadDownloadCsv, cadDurationToMinutes, cadFilterData, cadFilterKindOf, cadFilterOperators, cadFilterRows, cadFindLinkCycle, cadFmtArea, cadFmtLength, cadFormatCell, cadFormatColor, cadFormatDuration, cadFormatSize, cadFrenchHolidays, cadGap, cadGeoJSONStats, cadGeometryKinds, cadHeaderMenuItems, cadHighlightJson, cadHslToRgb, cadHsvToRgb, cadId, cadIsGroupKey, cadIsRank, cadJsonErrorPos, cadLegendFromStyle, cadLength, cadMapEstVivante, cadMapLayerOf, cadMapLayersOf, cadMatch, cadMoveColumn, cadMoveItems, cadNormalize, cadNormalizeOptions, cadNormalizePlan, cadNumberExpr, cadPadBounds, cadParseColor, cadParseDuration, cadParseResponsive, cadPlanHeader, cadPlanTicks, cadPropsTable, cadRankBetween, cadRankFirst, cadRankLast, cadRankSequence, cadResolve, cadResolveResponsive, cadResponsive, cadRgbToHsl, cadRgbToHsv, cadRingArea, cadRowKey, cadRowPredicate, cadSameValue, cadScaleNext, cadScaleStart, cadSortData, cadStartResize, cadStyleToExpressions, cadUnmask, cadUnscheduled, cadValidateGeoJSON, cadValueForOperator, cadWriteField, compareDay, daysInMonth, endOfMonth, ensureMaplibreCss, formatDate, isBetweenDay, isSameDay, isSameMonth, isValidDate, isoOf, isoWeek, loadChartJs, loadMaplibre, monthNames, pad2, parseIso, parseWithFormat, provideCadIcons, startOfDay, startOfMonth, toDate, weekdayNames };
21234
+ export { 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, 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, 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, addDays, addMonths, addYears, cadAggregate, cadApplyColumnOrder, cadApplyMask, cadArea, cadAutosizeWidth, cadBasemapThumbnail, cadBoundsOf, cadCategories, cadCentroid, cadColorExpr, cadCompare, cadCompareRank, cadCurrentOperator, cadDebutSemaine, cadDefaultMatchMode, cadDistance, cadDownloadCsv, cadDurationToMinutes, cadEstBandeau, cadExpandOccurrences, cadFilterData, cadFilterKindOf, cadFilterOperators, cadFilterRows, cadFindLinkCycle, cadFmtArea, cadFmtLength, cadFormatCell, cadFormatColor, cadFormatDuration, cadFormatSize, cadFrenchHolidays, 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, cadPadBounds, cadParseColor, cadParseDuration, cadParseRRule, cadParseResponsive, cadPlanHeader, cadPlanTicks, cadProjeterClavier, cadProjeterCreation, cadProjeterEdition, cadProjeterMois, cadPropsTable, cadRankBetween, cadRankFirst, cadRankLast, cadRankSequence, cadResolve, cadResolveResponsive, cadResponsive, cadRgbToHsl, cadRgbToHsv, cadRingArea, cadRowKey, cadRowPredicate, cadSameValue, cadScaleNext, cadScaleStart, cadSortData, cadStartResize, cadStyleToExpressions, cadUnmask, cadUnscheduled, cadValidateGeoJSON, cadValueForOperator, cadWeekBands, cadWriteField, compareDay, daysInMonth, endOfMonth, ensureMaplibreCss, formatDate, isBetweenDay, isSameDay, isSameMonth, isValidDate, isoOf, isoWeek, loadChartJs, loadMaplibre, monthNames, pad2, parseIso, parseWithFormat, provideCadIcons, startOfDay, startOfMonth, toDate, weekdayNames };
19548
21235
  //# sourceMappingURL=cadriciel-ui.mjs.map