@cadriciel/ui 0.3.0 → 0.4.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.
@@ -11,7 +11,7 @@ import { PortalModule, ComponentPortal, TemplatePortal, Portal } from '@angular/
11
11
  import * as i2 from '@angular/cdk/a11y';
12
12
  import { A11yModule, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
13
13
  import { Subject, isObservable } from 'rxjs';
14
- import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle, CdkDragPlaceholder, transferArrayItem } from '@angular/cdk/drag-drop';
14
+ import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle, CdkDragPlaceholder, transferArrayItem, CdkDropListGroup } from '@angular/cdk/drag-drop';
15
15
  import { RouterLink, Router, RouterLinkActive } from '@angular/router';
16
16
  import { HttpClient, HttpHeaders, HttpEventType } from '@angular/common/http';
17
17
 
@@ -6004,7 +6004,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
6004
6004
  }], ctorParameters: () => [], propDecorators: { cadIf: [{ type: i0.Input, args: [{ isSignal: true, alias: "cadIf", required: true }] }] } });
6005
6005
 
6006
6006
  const J = { start: 'flex-start', center: 'center', end: 'flex-end', between: 'space-between', around: 'space-around', evenly: 'space-evenly' };
6007
- const A = { stretch: 'stretch', start: 'flex-start', center: 'center', end: 'flex-end', baseline: 'baseline' };
6007
+ const A$1 = { stretch: 'stretch', start: 'flex-start', center: 'center', end: 'flex-end', baseline: 'baseline' };
6008
6008
  /** Pile verticale : `<cad-stack gap="sm" align="stretch">` — les enfants s'empilent avec un gap uniforme (jamais de marges). */
6009
6009
  class CadStackComponent {
6010
6010
  gap = input('md', ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
@@ -6012,7 +6012,7 @@ class CadStackComponent {
6012
6012
  justify = input('start', ...(ngDevMode ? [{ debugName: "justify" }] : /* istanbul ignore next */ []));
6013
6013
  inline = input(false, { ...(ngDevMode ? { debugName: "inline" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
6014
6014
  g = computed(() => cadGap(this.gap()), ...(ngDevMode ? [{ debugName: "g" }] : /* istanbul ignore next */ []));
6015
- a = computed(() => A[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6015
+ a = computed(() => A$1[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6016
6016
  j = computed(() => J[this.justify()], ...(ngDevMode ? [{ debugName: "j" }] : /* istanbul ignore next */ []));
6017
6017
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadStackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6018
6018
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: CadStackComponent, isStandalone: true, selector: "cad-stack", inputs: { gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, justify: { classPropertyName: "justify", publicName: "justify", isSignal: true, isRequired: false, transformFunction: null }, inline: { classPropertyName: "inline", publicName: "inline", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.gap": "g()", "style.align-items": "a()", "style.justify-content": "j()", "class.is-inline": "inline()" }, classAttribute: "cad-stack" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:flex;flex-direction:column;min-width:0}:host(.is-inline){display:inline-flex}:host ::ng-deep>*{min-width:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -6029,7 +6029,7 @@ class CadClusterComponent {
6029
6029
  wrap = input(true, { ...(ngDevMode ? { debugName: "wrap" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
6030
6030
  inline = input(false, { ...(ngDevMode ? { debugName: "inline" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
6031
6031
  g = computed(() => cadGap(this.gap()), ...(ngDevMode ? [{ debugName: "g" }] : /* istanbul ignore next */ []));
6032
- a = computed(() => A[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6032
+ a = computed(() => A$1[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6033
6033
  j = computed(() => J[this.justify()], ...(ngDevMode ? [{ debugName: "j" }] : /* istanbul ignore next */ []));
6034
6034
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadClusterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6035
6035
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: CadClusterComponent, isStandalone: true, selector: "cad-cluster", inputs: { gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, justify: { classPropertyName: "justify", publicName: "justify", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null }, inline: { classPropertyName: "inline", publicName: "inline", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.gap": "g()", "style.align-items": "a()", "style.justify-content": "j()", "style.flex-wrap": "wrap() ? \"wrap\" : \"nowrap\"", "class.is-inline": "inline()" }, classAttribute: "cad-cluster" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:flex;min-width:0}:host(.is-inline){display:inline-flex}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -6054,7 +6054,7 @@ class CadGridComponent {
6054
6054
  tpl = computed(() => this.min() ? `repeat(auto-fit, minmax(min(${this.min()}, 100%), 1fr))` : `repeat(${this.c() ?? 1}, minmax(0, 1fr))`, ...(ngDevMode ? [{ debugName: "tpl" }] : /* istanbul ignore next */ []));
6055
6055
  g = computed(() => cadGap(this.gap()), ...(ngDevMode ? [{ debugName: "g" }] : /* istanbul ignore next */ []));
6056
6056
  rg = computed(() => this.rowGap() ? cadGap(this.rowGap()) : null, ...(ngDevMode ? [{ debugName: "rg" }] : /* istanbul ignore next */ []));
6057
- a = computed(() => A[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6057
+ a = computed(() => A$1[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6058
6058
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6059
6059
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: CadGridComponent, isStandalone: true, selector: "cad-grid", inputs: { cols: { classPropertyName: "cols", publicName: "cols", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, rowGap: { classPropertyName: "rowGap", publicName: "rowGap", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, dense: { classPropertyName: "dense", publicName: "dense", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.grid-template-columns": "tpl()", "style.gap": "g()", "style.row-gap": "rg()", "style.align-items": "a()", "style.grid-auto-flow": "dense() ? \"row dense\" : null" }, classAttribute: "cad-grid" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:grid;min-width:0}:host ::ng-deep>*{min-width:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6060
6060
  }
@@ -6083,7 +6083,7 @@ class CadSplitComponent {
6083
6083
  stackAt = input('sm', ...(ngDevMode ? [{ debugName: "stackAt" }] : /* istanbul ignore next */ []));
6084
6084
  tpl = computed(() => (this.stackAt() && this.bp.down(this.stackAt())) ? '1fr' : this.ratio(), ...(ngDevMode ? [{ debugName: "tpl" }] : /* istanbul ignore next */ []));
6085
6085
  g = computed(() => cadGap(this.gap()), ...(ngDevMode ? [{ debugName: "g" }] : /* istanbul ignore next */ []));
6086
- a = computed(() => A[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6086
+ a = computed(() => A$1[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6087
6087
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadSplitComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6088
6088
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: CadSplitComponent, isStandalone: true, selector: "cad-split", inputs: { ratio: { classPropertyName: "ratio", publicName: "ratio", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, stackAt: { classPropertyName: "stackAt", publicName: "stackAt", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.grid-template-columns": "tpl()", "style.gap": "g()", "style.align-items": "a()" }, classAttribute: "cad-split" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:grid;min-width:0}:host ::ng-deep>*{min-width:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6089
6089
  }
@@ -6114,7 +6114,7 @@ class CadSidebarComponent {
6114
6114
  collapseAt = input('', ...(ngDevMode ? [{ debugName: "collapseAt" }] : /* istanbul ignore next */ []));
6115
6115
  collapsed = computed(() => !!this.collapseAt() && this.bp.down(this.collapseAt()), ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
6116
6116
  g = computed(() => cadGap(this.gap()), ...(ngDevMode ? [{ debugName: "g" }] : /* istanbul ignore next */ []));
6117
- a = computed(() => A[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6117
+ a = computed(() => A$1[this.align()], ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
6118
6118
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadSidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6119
6119
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: CadSidebarComponent, isStandalone: true, selector: "cad-sidebar", inputs: { side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, contentMin: { classPropertyName: "contentMin", publicName: "contentMin", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, collapseAt: { classPropertyName: "collapseAt", publicName: "collapseAt", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.gap": "g()", "class.side-left": "side() === \"left\"", "class.is-collapsed": "collapsed()", "style.--cad-sb-w": "width()", "style.--cad-sb-min": "contentMin()", "style.align-items": "a()" }, classAttribute: "cad-sidebar" }, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:flex;flex-wrap:wrap;min-width:0}:host ::ng-deep>*{flex-basis:0;flex-grow:999;min-inline-size:var(--cad-sb-min, 60%);min-width:0}:host ::ng-deep>.cad-sidebar__panel{flex-basis:var(--cad-sb-w, 280px);flex-grow:1;min-inline-size:0}:host(.side-left) ::ng-deep>.cad-sidebar__panel{order:-1}:host(.is-collapsed) ::ng-deep>*{flex-basis:100%!important;min-inline-size:0!important}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6120
6120
  }
@@ -18096,9 +18096,1453 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
18096
18096
  `, styles: [":host{display:block;font-family:var(--cad-font)}.cad-chart__box{position:relative;width:100%;height:var(--cad-chart-h, 280px)}.cad-chart__box canvas{width:100%!important;height:100%!important}.cad-chart__state{position:absolute;inset:0;display:grid;place-items:center}.cad-chart__table{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}:host(.show-table) .cad-chart__table{position:static;width:100%;height:auto;clip:auto;white-space:normal;margin-top:8px;border-collapse:collapse;font-size:12px}:host(.show-table) .cad-chart__table th,:host(.show-table) .cad-chart__table td{border:1px solid var(--cad-border);padding:4px 8px;text-align:right}:host(.show-table) .cad-chart__table th{background:var(--cad-bg-subtle);text-align:left}\n"] }]
18097
18097
  }], ctorParameters: () => [], propDecorators: { canvas: [{ type: i0.ViewChild, args: ['canvas', { isSignal: true }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], plugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "plugins", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], responsive: [{ type: i0.Input, args: [{ isSignal: true, alias: "responsive", required: false }] }], palette: [{ type: i0.Input, args: [{ isSignal: true, alias: "palette", required: false }] }], themed: [{ type: i0.Input, args: [{ isSignal: true, alias: "themed", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], accessibleTable: [{ type: i0.Input, args: [{ isSignal: true, alias: "accessibleTable", required: false }] }], chartClick: [{ type: i0.Output, args: ["chartClick"] }], chartHover: [{ type: i0.Output, args: ["chartHover"] }], ready: [{ type: i0.Output, args: ["ready"] }], error: [{ type: i0.Output, args: ["error"] }] } });
18098
18098
 
18099
+ /**
18100
+ * Modèle de planification commun aux trois vues (taskboard, scheduler, gantt).
18101
+ *
18102
+ * DOCTRINE — aucun champ n'est obligatoire hors `id`. Chaque vue déclare ses prérequis et
18103
+ * son repli : une tâche qu'une vue ne sait pas placer part dans sa zone « non planifié »,
18104
+ * jamais dans le silence. C'est cette règle, et elle seule, qui rend le document commun.
18105
+ *
18106
+ * Les noms suivent les conventions EXISTANTES plutôt qu'une invention maison :
18107
+ * - RFC 5545 (iCalendar) pour l'événement daté et sa récurrence : rrule, exdates, recurrenceId ;
18108
+ * - MS Project / Primavera P6 pour les liens FS/SS/FF/SF et le décalage (lag).
18109
+ * camelCase partout : c'est la convention du reste de la bibliothèque ET celle de slapi,
18110
+ * ce qui donne une correspondance 1 pour 1 entre le JSON et les colonnes.
18111
+ */
18112
+
18113
+ /**
18114
+ * Durées en unités OUVRÉES : '3d', '4h', '90m', '2w', '1.5d', '-2d'.
18115
+ *
18116
+ * Une durée n'a de sens qu'appuyée sur un calendrier : 'd' vaut une JOURNÉE OUVRÉE, dont la
18117
+ * longueur dépend des horaires déclarés (8 h ouvrées ou 24 h). La conversion en minutes se
18118
+ * fait donc via `CadWorkCalendar.dailyMinutes`, jamais par une constante.
18119
+ *
18120
+ * 'w' vaut une semaine OUVRÉE = nombre de jours ouvrés déclarés (5 par défaut), pas 7 jours.
18121
+ */
18122
+ const RE = /^\s*([+-]?\d+(?:[.,]\d+)?)\s*(m|min|h|d|j|w|s)\s*$/i;
18123
+ /**
18124
+ * Analyse une durée. Accepte les alias français ('j' = jour, 's' = semaine) et la virgule
18125
+ * décimale. Rend null si la forme est invalide — l'appelant en fait une anomalie, pas un plantage.
18126
+ */
18127
+ function cadParseDuration(input) {
18128
+ if (input == null || input === '')
18129
+ return null;
18130
+ if (typeof input === 'number')
18131
+ return Number.isFinite(input) ? { value: input, unit: 'd' } : null;
18132
+ const m = RE.exec(String(input));
18133
+ if (!m)
18134
+ return null;
18135
+ const value = Number(m[1].replace(',', '.'));
18136
+ if (!Number.isFinite(value))
18137
+ return null;
18138
+ const u = m[2].toLowerCase();
18139
+ const unit = u === 'min' ? 'm' : u === 'j' ? 'd' : u === 's' ? 'w' : u;
18140
+ return { value, unit };
18141
+ }
18142
+ /**
18143
+ * Convertit une durée en MINUTES OUVRÉES.
18144
+ * `dailyMinutes` = minutes ouvrées d'une journée type, `weekDays` = jours ouvrés par semaine.
18145
+ */
18146
+ function cadDurationToMinutes(d, dailyMinutes, weekDays) {
18147
+ if (!d)
18148
+ return 0;
18149
+ switch (d.unit) {
18150
+ case 'm': return d.value;
18151
+ case 'h': return d.value * 60;
18152
+ case 'd': return d.value * dailyMinutes;
18153
+ case 'w': return d.value * dailyMinutes * Math.max(1, weekDays);
18154
+ }
18155
+ }
18156
+ /** Rend une durée sous sa forme la plus lisible ('3d', '4h', '90m') depuis des minutes ouvrées. */
18157
+ function cadFormatDuration(minutes, dailyMinutes) {
18158
+ const abs = Math.abs(minutes);
18159
+ const sign = minutes < 0 ? '-' : '';
18160
+ if (dailyMinutes > 0 && abs % dailyMinutes === 0)
18161
+ return sign + abs / dailyMinutes + 'd';
18162
+ if (abs % 60 === 0)
18163
+ return sign + abs / 60 + 'h';
18164
+ return sign + abs + 'm';
18165
+ }
18166
+
18167
+ /**
18168
+ * Rang FRACTIONNAIRE : une chaîne ordonnable dans laquelle on peut toujours insérer.
18169
+ *
18170
+ * POURQUOI — déplacer une carte entre deux autres ne doit pas réécrire toute la colonne en
18171
+ * base. À plusieurs utilisateurs, une renumérotation entière garantit les collisions et coûte
18172
+ * un UPDATE par carte. Ici, insérer entre deux voisins produit une nouvelle chaîne comprise
18173
+ * entre les leurs : UN SEUL enregistrement change.
18174
+ *
18175
+ * C'est un FORMAT STOCKÉ, pas une astuce d'affichage — d'où sa place au socle, avant toute vue.
18176
+ *
18177
+ * INVARIANT — un rang produit ici ne se termine JAMAIS par le caractère plancher ('0'), sans
18178
+ * quoi il existerait des couples sans intervalle (rien ne tient entre '0' et '00'). Tous les
18179
+ * rangs manipulés doivent donc venir de ces fonctions.
18180
+ */
18181
+ const A = '0123456789abcdefghijklmnopqrstuvwxyz';
18182
+ const RADIX = A.length;
18183
+ const CH = (v) => A[Math.max(0, Math.min(RADIX - 1, v))];
18184
+ const IDX = (c) => {
18185
+ const i = A.indexOf(c);
18186
+ return i < 0 ? 0 : i;
18187
+ };
18188
+ /** Vrai si la chaîne est un rang exploitable (alphabet connu, pas de plancher final). */
18189
+ function cadIsRank(s) {
18190
+ return typeof s === 'string' && s.length > 0 && !s.endsWith(A[0]) && [...s].every(c => A.includes(c));
18191
+ }
18192
+ /**
18193
+ * Rang strictement compris entre `prev` et `next`.
18194
+ * `prev` absent = insertion en tête, `next` absent = insertion en queue, les deux = première carte.
18195
+ */
18196
+ function cadRankBetween(prev, next) {
18197
+ const a = cadIsRank(prev) ? prev : '';
18198
+ const b = cadIsRank(next) ? next : '';
18199
+ if (a && b && a >= b)
18200
+ throw new Error('cadRankBetween : ' + a + ' n\'est pas avant ' + b);
18201
+ let out = '';
18202
+ for (let i = 0; i < 64; i++) {
18203
+ const va = i < a.length ? IDX(a[i]) : 0; // a épuisé -> plancher
18204
+ const vb = i < b.length ? IDX(b[i]) : RADIX; // b épuisé -> plafond ouvert
18205
+ if (va + 1 < vb)
18206
+ return out + CH((va + vb) >> 1); // de la place : on coupe au milieu
18207
+ out += i < a.length ? a[i] : A[0]; // pas de place : on descend d'un cran
18208
+ }
18209
+ /* Inatteignable en pratique : 64 caractères = plus d'insertions successives au même point
18210
+ que ce qu'une session humaine produit. Le repli reste ordonné. */
18211
+ return out + CH(RADIX >> 1);
18212
+ }
18213
+ /** Rang d'une carte posée AVANT toutes les autres. */
18214
+ const cadRankFirst = (first) => cadRankBetween(null, first);
18215
+ /** Rang d'une carte posée APRÈS toutes les autres. */
18216
+ const cadRankLast = (last) => cadRankBetween(last, null);
18217
+ /**
18218
+ * `n` rangs croissants bien répartis — pour initialiser une colonne ou rattraper des données
18219
+ * qui n'en portaient pas. La répartition laisse de la place entre chaque voisin.
18220
+ */
18221
+ function cadRankSequence(n) {
18222
+ const count = Math.max(0, Math.floor(n));
18223
+ if (!count)
18224
+ return [];
18225
+ const span = RADIX * RADIX;
18226
+ const out = [];
18227
+ for (let i = 0; i < count; i++) {
18228
+ let v = Math.floor(((i + 1) * span) / (count + 1));
18229
+ if (v % RADIX === 0)
18230
+ v += 1; // jamais de plancher final
18231
+ out.push(CH(Math.floor(v / RADIX)) + CH(v % RADIX));
18232
+ }
18233
+ return out;
18234
+ }
18235
+ /** Comparateur de rangs, utilisable tel quel dans un `sort`. */
18236
+ const cadCompareRank = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
18237
+
18238
+ /**
18239
+ * Calendrier ouvré — le référentiel temps partagé par le scheduler et le gantt.
18240
+ *
18241
+ * Tout se calcule en HEURE MURALE locale (pas en millisecondes écoulées) : « 3 jours ouvrés »
18242
+ * veut dire trois fois les horaires déclarés, que la période traverse ou non un changement
18243
+ * d'heure. C'est la seule sémantique qui corresponde à ce qu'un planificateur attend.
18244
+ *
18245
+ * Les exceptions peuvent viser une ressource précise : un congé n'est pas un jour férié.
18246
+ * La recherche est donc toujours « exception de CETTE ressource, sinon exception globale ».
18247
+ */
18248
+ /** Au-delà, on considère le calendrier impraticable plutôt que de boucler (10 ans de jours). */
18249
+ const MAX_SCAN = 3660;
18250
+ const DAY = 1440;
18251
+ const dayStart = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
18252
+ const minutesOf = (d) => d.getHours() * 60 + d.getMinutes();
18253
+ const atMinutes = (day, min) => new Date(day.getFullYear(), day.getMonth(), day.getDate(), Math.floor(min / 60), Math.round(min % 60));
18254
+ /** 'HH:mm' -> minutes depuis minuit. '24:00' vaut la fin de journée. NaN si illisible. */
18255
+ function parseClock(v) {
18256
+ const m = /^\s*(\d{1,2}):(\d{2})\s*$/.exec(String(v ?? ''));
18257
+ if (!m)
18258
+ return NaN;
18259
+ const min = +m[1] * 60 + +m[2];
18260
+ return min >= 0 && min <= DAY ? min : NaN;
18261
+ }
18262
+ /** Nettoie une liste d'horaires : bornes valides, tri, fusion des recouvrements. */
18263
+ function normalizeHours(hours) {
18264
+ const raw = [];
18265
+ for (const h of hours ?? []) {
18266
+ const a = parseClock(h?.from);
18267
+ const b = parseClock(h?.to);
18268
+ if (Number.isNaN(a) || Number.isNaN(b) || b <= a)
18269
+ continue;
18270
+ raw.push([a, b]);
18271
+ }
18272
+ raw.sort((x, y) => x[0] - y[0]);
18273
+ const out = [];
18274
+ for (const [a, b] of raw) {
18275
+ const last = out[out.length - 1];
18276
+ if (last && a <= last[1])
18277
+ last[1] = Math.max(last[1], b);
18278
+ else
18279
+ out.push([a, b]);
18280
+ }
18281
+ return out;
18282
+ }
18283
+ const FULL_DAY = [[0, DAY]];
18284
+ /**
18285
+ * Jours fériés français d'une année, au format 'yyyy-MM-dd' — les huit dates fixes plus les
18286
+ * trois mobiles adossées à Pâques. Évite d'avoir à saisir la liste à la main chaque année.
18287
+ */
18288
+ function cadFrenchHolidays(year) {
18289
+ // Pâques, algorithme grégorien anonyme.
18290
+ const a = year % 19;
18291
+ const b = Math.floor(year / 100);
18292
+ const c = year % 100;
18293
+ const d = Math.floor(b / 4);
18294
+ const e = b % 4;
18295
+ const f = Math.floor((b + 8) / 25);
18296
+ const g = Math.floor((b - f + 1) / 3);
18297
+ const h = (19 * a + b - d - g + 15) % 30;
18298
+ const i = Math.floor(c / 4);
18299
+ const k = c % 4;
18300
+ const l = (32 + 2 * e + 2 * i - h - k) % 7;
18301
+ const m = Math.floor((a + 11 * h + 22 * l) / 451);
18302
+ const month = Math.floor((h + l - 7 * m + 114) / 31);
18303
+ const day = ((h + l - 7 * m + 114) % 31) + 1;
18304
+ const easter = new Date(year, month - 1, day);
18305
+ const fixed = ['01-01', '05-01', '05-08', '07-14', '08-15', '11-01', '11-11', '12-25'];
18306
+ const mobile = [1, 39, 50].map(n => isoOf(addDays(easter, n)));
18307
+ return [...fixed.map(s => year + '-' + s), ...mobile].sort();
18308
+ }
18309
+ /**
18310
+ * Calendrier ouvré immuable. Toute la logique est pure : elle se teste sans DOM et sans horloge.
18311
+ *
18312
+ * ```ts
18313
+ * const cal = new CadWorkCalendar({ workdays: [1,2,3,4,5], holidays: cadFrenchHolidays(2026) });
18314
+ * cal.addMinutes(new Date(2026, 7, 21, 16, 0), 4 * 60); // vendredi 16 h + 4 h ouvrées -> lundi
18315
+ * ```
18316
+ */
18317
+ class CadWorkCalendar {
18318
+ /** Jours ouvrés, 0 = dimanche. */
18319
+ workdays;
18320
+ /** Minutes ouvrées d'une journée type — sert d'étalon à l'unité 'd'. */
18321
+ dailyMinutes;
18322
+ /** Jours ouvrés par semaine — sert d'étalon à l'unité 'w'. */
18323
+ weekDays;
18324
+ base;
18325
+ holidays;
18326
+ /** Clé 'yyyy-MM-dd' pour une exception globale, 'yyyy-MM-dd|ressource' pour une exception ciblée. */
18327
+ exceptions;
18328
+ constructor(spec) {
18329
+ /* Un défaut ne s'applique qu'à ce qui n'a PAS été déclaré. Déclarer `workdays: []`, c'est
18330
+ dire que rien n'est ouvert : le remplacer par lundi-vendredi reviendrait à contredire
18331
+ l'appelant en silence, et rendrait `empty` inatteignable. */
18332
+ this.workdays = new Set(spec?.workdays === undefined
18333
+ ? [1, 2, 3, 4, 5]
18334
+ : spec.workdays.filter(n => Number.isInteger(n) && n >= 0 && n <= 6));
18335
+ this.base = spec?.hours === undefined ? FULL_DAY : normalizeHours(spec.hours);
18336
+ this.dailyMinutes = this.base.reduce((s, [a, b]) => s + (b - a), 0);
18337
+ this.weekDays = this.workdays.size;
18338
+ this.holidays = new Set((spec?.holidays ?? []).filter(s => typeof s === 'string'));
18339
+ const ex = new Map();
18340
+ for (const e of spec?.exceptions ?? []) {
18341
+ if (!e?.date)
18342
+ continue;
18343
+ const key = e.resource ? e.date + '|' + e.resource : e.date;
18344
+ ex.set(key, e.off ? null : normalizeHours(e.hours ?? []));
18345
+ }
18346
+ this.exceptions = ex;
18347
+ }
18348
+ /** Vrai si aucun temps ouvré n'est déclarable — le normaliseur en fait une anomalie. */
18349
+ get empty() {
18350
+ return this.workdays.size === 0 || this.dailyMinutes === 0;
18351
+ }
18352
+ /** Intervalles ouvrés d'une journée, en minutes depuis minuit. Vide = jour chômé. */
18353
+ intervals(date, resource) {
18354
+ if (!isValidDate(date))
18355
+ return [];
18356
+ const iso = isoOf(date);
18357
+ if (resource) {
18358
+ const scoped = this.exceptions.get(iso + '|' + resource);
18359
+ if (scoped !== undefined)
18360
+ return scoped ?? [];
18361
+ // une exception ciblée peut aussi OUVRIR un jour normalement chômé
18362
+ }
18363
+ const global = this.exceptions.get(iso);
18364
+ if (global !== undefined)
18365
+ return global ?? [];
18366
+ if (this.holidays.has(iso))
18367
+ return [];
18368
+ return this.workdays.has(date.getDay()) ? this.base : [];
18369
+ }
18370
+ /** Vrai si la journée comporte au moins un intervalle ouvré. */
18371
+ isWorkday(date, resource) {
18372
+ return this.intervals(date, resource).length > 0;
18373
+ }
18374
+ /** Vrai si l'instant tombe DANS un intervalle ouvré (borne de fin exclue). */
18375
+ isWorkingTime(date, resource) {
18376
+ if (!isValidDate(date))
18377
+ return false;
18378
+ const m = minutesOf(date);
18379
+ return this.intervals(date, resource).some(([a, b]) => m >= a && m < b);
18380
+ }
18381
+ /** Premier instant ouvré à partir de `date` (`date` lui-même s'il l'est). null si introuvable. */
18382
+ nextWorkingStart(date, resource) {
18383
+ const c = this.scanForward(this.cursorOf(date), resource);
18384
+ return c ? atMinutes(c.day, c.min) : null;
18385
+ }
18386
+ /** Dernier instant ouvré jusqu'à `date` (exclu). null si introuvable. */
18387
+ prevWorkingEnd(date, resource) {
18388
+ const c = this.scanBackward(this.cursorOf(date), resource);
18389
+ return c ? atMinutes(c.day, c.min) : null;
18390
+ }
18391
+ /**
18392
+ * Avance (ou recule, si négatif) de `minutes` OUVRÉES depuis `from`.
18393
+ * Rend null si le calendrier ne permet pas d'y arriver (aucun jour ouvré déclaré).
18394
+ */
18395
+ addMinutes(from, minutes, resource) {
18396
+ if (!isValidDate(from) || !Number.isFinite(minutes))
18397
+ return null;
18398
+ if (minutes === 0)
18399
+ return this.nextWorkingStart(from, resource);
18400
+ return minutes > 0
18401
+ ? this.walkForward(from, minutes, resource)
18402
+ : this.walkBackward(from, -minutes, resource);
18403
+ }
18404
+ /** Minutes OUVRÉES entre deux instants. Négatif si `b` précède `a`. */
18405
+ minutesBetween(a, b, resource) {
18406
+ if (!isValidDate(a) || !isValidDate(b))
18407
+ return 0;
18408
+ if (b.getTime() < a.getTime())
18409
+ return -this.minutesBetween(b, a, resource);
18410
+ const end = this.cursorOf(b);
18411
+ let day = dayStart(a);
18412
+ let from = minutesOf(a);
18413
+ let total = 0;
18414
+ for (let guard = 0; guard < MAX_SCAN; guard++) {
18415
+ const last = day.getTime() >= end.day.getTime();
18416
+ const to = last ? end.min : DAY;
18417
+ for (const [s, e] of this.intervals(day, resource)) {
18418
+ const lo = Math.max(s, from);
18419
+ const hi = Math.min(e, to);
18420
+ if (hi > lo)
18421
+ total += hi - lo;
18422
+ }
18423
+ if (last)
18424
+ return total;
18425
+ day = addDays(day, 1);
18426
+ from = 0;
18427
+ }
18428
+ return total;
18429
+ }
18430
+ /* ---------------- interne : un curseur (jour, minute) évite l'ambiguïté de 24:00 -------- */
18431
+ cursorOf(d) {
18432
+ return { day: dayStart(d), min: minutesOf(d) };
18433
+ }
18434
+ scanForward(c, resource) {
18435
+ if (this.empty)
18436
+ return null;
18437
+ let { day, min } = c;
18438
+ for (let i = 0; i < MAX_SCAN; i++) {
18439
+ for (const [s, e] of this.intervals(day, resource)) {
18440
+ if (e > min)
18441
+ return { day, min: Math.max(s, min) };
18442
+ }
18443
+ day = addDays(day, 1);
18444
+ min = 0;
18445
+ }
18446
+ return null;
18447
+ }
18448
+ scanBackward(c, resource) {
18449
+ if (this.empty)
18450
+ return null;
18451
+ let { day, min } = c;
18452
+ for (let i = 0; i < MAX_SCAN; i++) {
18453
+ const ivs = this.intervals(day, resource);
18454
+ for (let k = ivs.length - 1; k >= 0; k--) {
18455
+ const [s, e] = ivs[k];
18456
+ if (s < min)
18457
+ return { day, min: Math.min(e, min) };
18458
+ }
18459
+ day = addDays(day, -1);
18460
+ min = DAY;
18461
+ }
18462
+ return null;
18463
+ }
18464
+ walkForward(from, minutes, resource) {
18465
+ let c = this.scanForward(this.cursorOf(from), resource);
18466
+ if (!c)
18467
+ return null;
18468
+ let left = minutes;
18469
+ for (let guard = 0; guard < MAX_SCAN; guard++) {
18470
+ for (const [s, e] of this.intervals(c.day, resource)) {
18471
+ if (e <= c.min)
18472
+ continue;
18473
+ const start = Math.max(s, c.min);
18474
+ const avail = e - start;
18475
+ if (avail >= left)
18476
+ return atMinutes(c.day, start + left);
18477
+ left -= avail;
18478
+ }
18479
+ c = { day: addDays(c.day, 1), min: 0 };
18480
+ }
18481
+ return null;
18482
+ }
18483
+ walkBackward(from, minutes, resource) {
18484
+ let c = this.scanBackward(this.cursorOf(from), resource);
18485
+ if (!c)
18486
+ return null;
18487
+ let left = minutes;
18488
+ for (let guard = 0; guard < MAX_SCAN; guard++) {
18489
+ const ivs = this.intervals(c.day, resource);
18490
+ for (let k = ivs.length - 1; k >= 0; k--) {
18491
+ const [s, e] = ivs[k];
18492
+ if (s >= c.min)
18493
+ continue;
18494
+ const end = Math.min(e, c.min);
18495
+ const avail = end - s;
18496
+ if (avail >= left)
18497
+ return atMinutes(c.day, end - left);
18498
+ left -= avail;
18499
+ }
18500
+ c = { day: addDays(c.day, -1), min: DAY };
18501
+ }
18502
+ return null;
18503
+ }
18504
+ }
18505
+
18506
+ /**
18507
+ * Axe de temps : graduations, et conversion pixel <-> date.
18508
+ *
18509
+ * LE POINT CRITIQUE — l'axe n'est PAS une division. Écrire `x = (t - t0) / msParPixel` est
18510
+ * faux dès qu'on dézoome : un jour ne fait pas toujours 24 h (changement d'heure), un mois n'a
18511
+ * pas de largeur fixe, une année pas davantage. Février et mars occuperaient alors des largeurs
18512
+ * différentes alors que l'oeil attend des colonnes égales, et le défaut ne se voit qu'en mars
18513
+ * et en octobre — donc jamais pendant le développement.
18514
+ *
18515
+ * Ici, l'axe est une LISTE DE GRADUATIONS de largeur égale, et la conversion est une recherche
18516
+ * dans cette liste suivie d'une interpolation À L'INTÉRIEUR de la graduation trouvée. Chaque
18517
+ * graduation porte sa vraie durée ; l'arithmétique linéaire ne s'applique que localement.
18518
+ *
18519
+ * Corollaire de rendu : la grille de fond n'a pas à exister en DOM. Des graduations de largeur
18520
+ * égale se peignent en `repeating-linear-gradient`, ce qui évite de virtualiser 1 100 colonnes
18521
+ * pour trois ans au jour. Seuls l'en-tête visible et les barres sont rendus.
18522
+ */
18523
+ /** Échelle du bandeau supérieur d'un en-tête à deux étages. */
18524
+ const PARENT = {
18525
+ hour: 'day',
18526
+ day: 'month',
18527
+ week: 'month',
18528
+ month: 'year',
18529
+ quarter: 'year',
18530
+ year: null,
18531
+ };
18532
+ const MAX_TICKS = 20000;
18533
+ const startOfWeek = (d, firstDay) => {
18534
+ const s = startOfDay(d);
18535
+ const delta = (s.getDay() - firstDay + 7) % 7;
18536
+ return addDays(s, -delta);
18537
+ };
18538
+ const startOfQuarter = (d) => new Date(d.getFullYear(), Math.floor(d.getMonth() / 3) * 3, 1);
18539
+ const startOfHour = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours());
18540
+ /** Aligne un instant sur le début de sa graduation. */
18541
+ function cadScaleStart(date, scale, firstDay = 1) {
18542
+ switch (scale) {
18543
+ case 'hour': return startOfHour(date);
18544
+ case 'day': return startOfDay(date);
18545
+ case 'week': return startOfWeek(date, firstDay);
18546
+ case 'month': return startOfMonth(date);
18547
+ case 'quarter': return startOfQuarter(date);
18548
+ case 'year': return new Date(date.getFullYear(), 0, 1);
18549
+ }
18550
+ }
18551
+ /**
18552
+ * Début de la graduation SUIVANTE. Construit en heure locale, donc juste aux changements d'heure.
18553
+ * L'entrée est alignée au préalable : `cadScaleNext(31 janvier, 'month')` rend le 1er février,
18554
+ * jamais le 28 — sans quoi une date de milieu de mois produirait une graduation décalée.
18555
+ */
18556
+ function cadScaleNext(date, scale, firstDay = 1) {
18557
+ const start = cadScaleStart(date, scale, firstDay);
18558
+ switch (scale) {
18559
+ case 'hour': return new Date(start.getFullYear(), start.getMonth(), start.getDate(), start.getHours() + 1);
18560
+ case 'day': return addDays(start, 1);
18561
+ case 'week': return addDays(start, 7);
18562
+ case 'month': return addMonths(start, 1);
18563
+ case 'quarter': return addMonths(start, 3);
18564
+ case 'year': return addYears(start, 1);
18565
+ }
18566
+ }
18567
+ const intl = (locale, opts) => new Intl.DateTimeFormat(locale, opts);
18568
+ function defaultLabel(d, scale, locale) {
18569
+ switch (scale) {
18570
+ case 'hour': return intl(locale, { hour: '2-digit', minute: '2-digit' }).format(d);
18571
+ case 'day': return String(d.getDate());
18572
+ case 'week': return 'S' + isoWeek(d);
18573
+ case 'month': return intl(locale, { month: 'short' }).format(d);
18574
+ case 'quarter': return 'T' + (Math.floor(d.getMonth() / 3) + 1);
18575
+ case 'year': return String(d.getFullYear());
18576
+ }
18577
+ }
18578
+ /** Libellé du bandeau supérieur — plus explicite, puisqu'il porte le contexte. */
18579
+ function bandLabel(d, scale, locale) {
18580
+ switch (scale) {
18581
+ case 'day': return intl(locale, { weekday: 'short', day: 'numeric', month: 'long' }).format(d);
18582
+ case 'month': return intl(locale, { month: 'long', year: 'numeric' }).format(d);
18583
+ case 'year': return String(d.getFullYear());
18584
+ default: return defaultLabel(d, scale, locale);
18585
+ }
18586
+ }
18587
+ /** Une graduation est « majeure » quand elle ouvre l'unité supérieure. */
18588
+ function isMajor(d, scale, firstDay) {
18589
+ switch (scale) {
18590
+ case 'hour': return d.getHours() === 0;
18591
+ case 'day': return d.getDate() === 1;
18592
+ case 'week': return d.getDate() <= 7;
18593
+ case 'month': return d.getMonth() === 0;
18594
+ case 'quarter': return d.getMonth() === 0;
18595
+ case 'year': return true;
18596
+ }
18597
+ }
18598
+ /**
18599
+ * Graduations couvrant [from, to], alignées sur l'échelle.
18600
+ * La dernière graduation dépasse `to` si nécessaire : un axe ne coupe jamais une unité en deux.
18601
+ */
18602
+ function cadPlanTicks(from, to, scale, options = {}) {
18603
+ if (!isValidDate(from) || !isValidDate(to) || to.getTime() < from.getTime())
18604
+ return [];
18605
+ const locale = options.locale ?? 'fr-FR';
18606
+ const firstDay = options.firstDay ?? 1;
18607
+ const label = options.label ?? defaultLabel;
18608
+ const cal = options.calendar ?? null;
18609
+ const today = options.today === null ? null : (options.today ?? new Date());
18610
+ const max = Math.max(1, options.max ?? MAX_TICKS);
18611
+ const limit = to.getTime();
18612
+ const now = today ? today.getTime() : NaN;
18613
+ const out = [];
18614
+ let start = cadScaleStart(from, scale, firstDay);
18615
+ for (let i = 0; i < max; i++) {
18616
+ const end = cadScaleNext(start, scale, firstDay);
18617
+ out.push({
18618
+ start,
18619
+ end,
18620
+ label: label(start, scale, locale),
18621
+ major: isMajor(start, scale, firstDay),
18622
+ off: cal
18623
+ ? scale === 'hour'
18624
+ ? !cal.isWorkingTime(start)
18625
+ : scale === 'day'
18626
+ ? !cal.isWorkday(start)
18627
+ : false
18628
+ : false,
18629
+ today: today ? now >= start.getTime() && now < end.getTime() : false,
18630
+ });
18631
+ if (end.getTime() > limit)
18632
+ break;
18633
+ start = end;
18634
+ }
18635
+ return out;
18636
+ }
18637
+ function cadPlanHeader(from, to, scale, options = {}) {
18638
+ const lower = cadPlanTicks(from, to, scale, options);
18639
+ const parent = PARENT[scale];
18640
+ if (!parent || !lower.length)
18641
+ return { upper: [], lower };
18642
+ const upper = cadPlanTicks(lower[0].start, lower[lower.length - 1].end, parent, {
18643
+ ...options,
18644
+ calendar: null,
18645
+ label: options.label ?? bandLabel,
18646
+ });
18647
+ return { upper, lower };
18648
+ }
18649
+ /**
18650
+ * Conversion pixel <-> date sur une liste de graduations de largeur égale.
18651
+ *
18652
+ * Hors de l'axe, la position est EXTRAPOLÉE sur la durée de la graduation de bord plutôt que
18653
+ * rabattue sur la borne : une barre qui commence avant la fenêtre garde une position cohérente,
18654
+ * et l'appelant décide seul de la découper ou de la masquer.
18655
+ */
18656
+ class CadTimeAxis {
18657
+ ticks;
18658
+ unitWidth;
18659
+ constructor(ticks,
18660
+ /** Largeur d'une graduation, en pixels. */
18661
+ unitWidth) {
18662
+ this.ticks = ticks;
18663
+ this.unitWidth = unitWidth;
18664
+ }
18665
+ get width() { return this.ticks.length * this.unitWidth; }
18666
+ get start() { return this.ticks.length ? this.ticks[0].start : null; }
18667
+ get end() { return this.ticks.length ? this.ticks[this.ticks.length - 1].end : null; }
18668
+ /** Indice de la graduation contenant `t` (millisecondes), par recherche binaire. */
18669
+ indexOf(t) {
18670
+ let lo = 0;
18671
+ let hi = this.ticks.length - 1;
18672
+ while (lo < hi) {
18673
+ const mid = (lo + hi + 1) >> 1;
18674
+ if (this.ticks[mid].start.getTime() <= t)
18675
+ lo = mid;
18676
+ else
18677
+ hi = mid - 1;
18678
+ }
18679
+ return lo;
18680
+ }
18681
+ /** Abscisse d'une date, en pixels depuis le début de l'axe. */
18682
+ xOf(date) {
18683
+ const n = this.ticks.length;
18684
+ if (!n || !isValidDate(date))
18685
+ return 0;
18686
+ const t = date.getTime();
18687
+ const first = this.ticks[0];
18688
+ const last = this.ticks[n - 1];
18689
+ if (t < first.start.getTime()) {
18690
+ const len = first.end.getTime() - first.start.getTime();
18691
+ return len ? ((t - first.start.getTime()) / len) * this.unitWidth : 0;
18692
+ }
18693
+ if (t >= last.end.getTime()) {
18694
+ const len = last.end.getTime() - last.start.getTime();
18695
+ return this.width + (len ? ((t - last.end.getTime()) / len) * this.unitWidth : 0);
18696
+ }
18697
+ const i = this.indexOf(t);
18698
+ const tk = this.ticks[i];
18699
+ const len = tk.end.getTime() - tk.start.getTime();
18700
+ return (i + (len ? (t - tk.start.getTime()) / len : 0)) * this.unitWidth;
18701
+ }
18702
+ /** Date à une abscisse donnée. */
18703
+ dateAt(x) {
18704
+ const n = this.ticks.length;
18705
+ if (!n || !Number.isFinite(x) || this.unitWidth <= 0)
18706
+ return null;
18707
+ const u = x / this.unitWidth;
18708
+ const i = Math.floor(u);
18709
+ if (i < 0) {
18710
+ const first = this.ticks[0];
18711
+ const len = first.end.getTime() - first.start.getTime();
18712
+ return new Date(first.start.getTime() + u * len);
18713
+ }
18714
+ if (i >= n) {
18715
+ const last = this.ticks[n - 1];
18716
+ const len = last.end.getTime() - last.start.getTime();
18717
+ return new Date(last.end.getTime() + (u - n) * len);
18718
+ }
18719
+ const tk = this.ticks[i];
18720
+ const len = tk.end.getTime() - tk.start.getTime();
18721
+ return new Date(tk.start.getTime() + (u - i) * len);
18722
+ }
18723
+ /** Position et largeur d'une barre. La largeur ne descend jamais sous zéro. */
18724
+ spanOf(start, end) {
18725
+ const x = this.xOf(start);
18726
+ return { x, width: Math.max(0, this.xOf(end) - x) };
18727
+ }
18728
+ }
18729
+
18730
+ /**
18731
+ * Normalisation du document de planification : ce que les trois vues consomment réellement.
18732
+ *
18733
+ * RÈGLE FONDATRICE — hors `id`, aucun champ n'est obligatoire. Une tâche sans dates reste une
18734
+ * tâche : le taskboard la montre, le gantt et le scheduler la rangent dans leur zone « non
18735
+ * planifié ». Rien n'est jamais écarté en silence ; tout écart devient une entrée de `issues`
18736
+ * portant un code stable, que l'appelant affiche ou ignore, mais qu'il peut toujours lire.
18737
+ *
18738
+ * Le normaliseur ne DÉCIDE de rien qu'il ne puisse justifier : il ne replanifie pas, ne
18739
+ * réordonne pas les dates, ne corrige pas les chevauchements. Il résout, indexe et signale.
18740
+ */
18741
+ const LINK_TYPES = ['FS', 'SS', 'FF', 'SF'];
18742
+ const clamp01 = (v) => {
18743
+ const n = typeof v === 'number' ? v : Number(v);
18744
+ return Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : 0;
18745
+ };
18746
+ /**
18747
+ * Résout un document brut : dates coercées, durées converties par le calendrier, hiérarchie
18748
+ * établie, rangs complétés, liens validés.
18749
+ */
18750
+ function cadNormalizePlan(plan, options = {}) {
18751
+ const issues = [];
18752
+ const add = (level, code, message, ref) => {
18753
+ issues.push({ level, code, message, ref });
18754
+ };
18755
+ const calendar = new CadWorkCalendar(plan?.calendar);
18756
+ if (calendar.empty)
18757
+ add('error', 'empty-calendar', 'Aucun temps ouvré déclaré : les durées ne peuvent pas être converties.');
18758
+ const resources = (plan?.resources ?? []).filter(r => r && typeof r.id === 'string');
18759
+ const resourceIds = new Set(resources.map(r => r.id));
18760
+ const buckets = [...(plan?.buckets ?? [])]
18761
+ .filter(b => b && typeof b.id === 'string')
18762
+ .map((b, i) => ({ ...b, order: b.order ?? i }))
18763
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
18764
+ const bucketIds = new Set(buckets.map(b => b.id));
18765
+ /* ---- 1. index des tâches, doublons écartés ---- */
18766
+ const raw = new Map();
18767
+ for (const t of plan?.tasks ?? []) {
18768
+ if (!t || typeof t.id !== 'string' || !t.id)
18769
+ continue;
18770
+ if (raw.has(t.id)) {
18771
+ add('error', 'duplicate-task', 'Identifiant en double : la première occurrence est retenue.', t.id);
18772
+ continue;
18773
+ }
18774
+ raw.set(t.id, t);
18775
+ }
18776
+ /* ---- 2. tâches résolues, hors hiérarchie et hors rollup ---- */
18777
+ const tasks = new Map();
18778
+ for (const [id, t] of raw) {
18779
+ let start = toDate(t.start ?? null);
18780
+ let end = toDate(t.end ?? null);
18781
+ const milestone = !!t.milestone;
18782
+ if (t.start != null && t.start !== '' && !start)
18783
+ add('warning', 'bad-date', 'Date de début illisible.', id);
18784
+ if (t.end != null && t.end !== '' && !end)
18785
+ add('warning', 'bad-date', 'Date de fin illisible.', id);
18786
+ if (milestone) {
18787
+ end = start;
18788
+ }
18789
+ else if (t.duration) {
18790
+ const d = cadParseDuration(t.duration);
18791
+ if (!d)
18792
+ add('warning', 'bad-duration', 'Durée illisible : ' + t.duration, id);
18793
+ else {
18794
+ const minutes = cadDurationToMinutes(d, calendar.dailyMinutes, calendar.weekDays);
18795
+ if (start && !end)
18796
+ end = calendar.addMinutes(start, minutes);
18797
+ else if (!start && end)
18798
+ start = calendar.addMinutes(end, -minutes);
18799
+ }
18800
+ }
18801
+ if (start && end && end.getTime() < start.getTime()) {
18802
+ add('warning', 'end-before-start', 'La fin précède le début : la fin est ramenée au début.', id);
18803
+ end = start;
18804
+ }
18805
+ const assignments = (t.assignments ?? [])
18806
+ .filter(a => a && typeof a.resource === 'string')
18807
+ .map(a => {
18808
+ if (!resourceIds.has(a.resource))
18809
+ add('warning', 'unknown-resource', 'Ressource inconnue : ' + a.resource, id);
18810
+ return { resource: a.resource, units: Number.isFinite(a.units) ? a.units : 1 };
18811
+ });
18812
+ if (t.bucket && !bucketIds.has(t.bucket))
18813
+ add('warning', 'unknown-bucket', 'Colonne inconnue : ' + t.bucket, id);
18814
+ tasks.set(id, {
18815
+ id,
18816
+ label: t.label ?? id,
18817
+ parent: null,
18818
+ start,
18819
+ end,
18820
+ milestone,
18821
+ bucket: t.bucket ?? null,
18822
+ rank: cadIsRank(t.rank) ? t.rank : '',
18823
+ assignments,
18824
+ progress: clamp01(t.progress),
18825
+ tags: [...(t.tags ?? [])],
18826
+ color: t.color ?? null,
18827
+ locked: !!t.locked,
18828
+ rrule: t.rrule ?? null,
18829
+ exdates: [...(t.exdates ?? [])],
18830
+ masterId: t.masterId ?? null,
18831
+ recurrenceId: toDate(t.recurrenceId ?? null),
18832
+ level: 0,
18833
+ children: [],
18834
+ scheduled: false,
18835
+ rolled: false,
18836
+ recurring: !!t.rrule,
18837
+ meta: t.meta ?? {},
18838
+ source: t,
18839
+ });
18840
+ }
18841
+ /* ---- 3. hiérarchie : parent inconnu, auto-parent et cycles deviennent des racines ---- */
18842
+ for (const [id, t] of raw) {
18843
+ const node = tasks.get(id);
18844
+ const parent = t.parent ?? null;
18845
+ if (!parent)
18846
+ continue;
18847
+ if (parent === id) {
18848
+ add('error', 'self-parent', 'Une tâche ne peut pas être son propre parent.', id);
18849
+ continue;
18850
+ }
18851
+ if (!tasks.has(parent)) {
18852
+ add('warning', 'unknown-parent', 'Parent inconnu : ' + parent + ' — la tâche devient racine.', id);
18853
+ continue;
18854
+ }
18855
+ node.parent = parent;
18856
+ }
18857
+ for (const node of tasks.values()) {
18858
+ const seen = new Set([node.id]);
18859
+ let cur = node.parent;
18860
+ while (cur) {
18861
+ if (seen.has(cur)) {
18862
+ add('error', 'parent-cycle', 'Cycle de parenté rompu à cette tâche.', node.id);
18863
+ node.parent = null;
18864
+ break;
18865
+ }
18866
+ seen.add(cur);
18867
+ cur = tasks.get(cur)?.parent ?? null;
18868
+ }
18869
+ }
18870
+ /* ---- 4. fratries et rangs : les rangs existants font l'ordre, les manquants sont complétés ---- */
18871
+ const groups = new Map();
18872
+ for (const id of tasks.keys()) {
18873
+ const key = tasks.get(id).parent;
18874
+ const list = groups.get(key);
18875
+ if (list)
18876
+ list.push(id);
18877
+ else
18878
+ groups.set(key, [id]);
18879
+ }
18880
+ for (const [key, ids] of groups) {
18881
+ const ranked = ids.filter(i => tasks.get(i).rank).sort((a, b) => (tasks.get(a).rank < tasks.get(b).rank ? -1 : 1));
18882
+ const unranked = ids.filter(i => !tasks.get(i).rank);
18883
+ const ordered = [...ranked, ...unranked];
18884
+ for (let i = 0; i < ordered.length; i++) {
18885
+ const node = tasks.get(ordered[i]);
18886
+ if (node.rank)
18887
+ continue;
18888
+ node.rank = cadRankBetween(i > 0 ? tasks.get(ordered[i - 1]).rank : null, null);
18889
+ }
18890
+ groups.set(key, ordered);
18891
+ if (key !== null)
18892
+ tasks.get(key).children = ordered;
18893
+ }
18894
+ /* ---- 5. ordre final : parcours en profondeur, niveaux ---- */
18895
+ const roots = groups.get(null) ?? [];
18896
+ const order = [];
18897
+ const walk = (ids, level) => {
18898
+ for (const id of ids) {
18899
+ const node = tasks.get(id);
18900
+ node.level = level;
18901
+ order.push(node);
18902
+ if (node.children.length)
18903
+ walk(node.children, level + 1);
18904
+ }
18905
+ };
18906
+ walk(roots, 0);
18907
+ /* ---- 6. barres récapitulatives : un parent sans dates prend l'étendue de ses descendants ---- */
18908
+ if (options.rollup !== false) {
18909
+ for (let i = order.length - 1; i >= 0; i--) {
18910
+ const node = order[i];
18911
+ if (!node.children.length || (node.start && node.end))
18912
+ continue;
18913
+ let lo = null;
18914
+ let hi = null;
18915
+ for (const cid of node.children) {
18916
+ const c = tasks.get(cid);
18917
+ if (c.start)
18918
+ lo = lo === null ? c.start.getTime() : Math.min(lo, c.start.getTime());
18919
+ if (c.end)
18920
+ hi = hi === null ? c.end.getTime() : Math.max(hi, c.end.getTime());
18921
+ }
18922
+ if (lo !== null && hi !== null) {
18923
+ node.start = new Date(lo);
18924
+ node.end = new Date(hi);
18925
+ node.rolled = true;
18926
+ }
18927
+ }
18928
+ }
18929
+ for (const node of order)
18930
+ node.scheduled = !!(node.start && node.end);
18931
+ /* ---- 7. liens ---- */
18932
+ const links = [];
18933
+ const seenLink = new Set();
18934
+ for (const l of plan?.links ?? []) {
18935
+ if (!l || typeof l.from !== 'string' || typeof l.to !== 'string')
18936
+ continue;
18937
+ if (l.from === l.to) {
18938
+ add('error', 'self-link', 'Une tâche ne peut pas dépendre d\'elle-même.', l.from);
18939
+ continue;
18940
+ }
18941
+ if (!tasks.has(l.from) || !tasks.has(l.to)) {
18942
+ add('warning', 'unknown-link-end', 'Lien ignoré : ' + l.from + ' -> ' + l.to + ' vise une tâche absente.', l.id);
18943
+ continue;
18944
+ }
18945
+ const key = l.from + '>' + l.to;
18946
+ if (seenLink.has(key))
18947
+ continue;
18948
+ seenLink.add(key);
18949
+ const d = l.lag ? cadParseDuration(l.lag) : null;
18950
+ if (l.lag && !d)
18951
+ add('warning', 'bad-duration', 'Décalage illisible : ' + l.lag, l.id);
18952
+ links.push({
18953
+ id: l.id ?? key,
18954
+ from: l.from,
18955
+ to: l.to,
18956
+ type: LINK_TYPES.includes(l.type) ? l.type : 'FS',
18957
+ lagMinutes: cadDurationToMinutes(d, calendar.dailyMinutes, calendar.weekDays),
18958
+ });
18959
+ }
18960
+ for (const id of cadFindLinkCycle(links)) {
18961
+ add('error', 'link-cycle', 'Cette tâche participe à un cycle de dépendances.', id);
18962
+ }
18963
+ /* ---- 8. étendue ---- */
18964
+ let lo = null;
18965
+ let hi = null;
18966
+ for (const t of order) {
18967
+ if (!t.scheduled)
18968
+ continue;
18969
+ lo = lo === null ? t.start.getTime() : Math.min(lo, t.start.getTime());
18970
+ hi = hi === null ? t.end.getTime() : Math.max(hi, t.end.getTime());
18971
+ }
18972
+ return {
18973
+ tasks: order,
18974
+ byId: tasks,
18975
+ roots,
18976
+ links,
18977
+ resources,
18978
+ buckets,
18979
+ calendar,
18980
+ range: lo !== null && hi !== null ? { start: new Date(lo), end: new Date(hi) } : null,
18981
+ issues,
18982
+ };
18983
+ }
18984
+ /**
18985
+ * Identifiants participant à un cycle de dépendances, par tri topologique (Kahn) :
18986
+ * ce qui ne sort jamais de la file est exactement ce qui est pris dans un cycle.
18987
+ * Nécessaire bien avant le moteur de replanification — un cycle non détecté s'y traduirait
18988
+ * par une boucle infinie plutôt que par un message.
18989
+ */
18990
+ function cadFindLinkCycle(links) {
18991
+ const out = new Map();
18992
+ const indeg = new Map();
18993
+ for (const l of links) {
18994
+ const edges = out.get(l.from);
18995
+ if (edges)
18996
+ edges.push(l.to);
18997
+ else
18998
+ out.set(l.from, [l.to]);
18999
+ if (!indeg.has(l.from))
19000
+ indeg.set(l.from, 0);
19001
+ indeg.set(l.to, (indeg.get(l.to) ?? 0) + 1);
19002
+ }
19003
+ const queue = [...indeg.keys()].filter(k => !indeg.get(k));
19004
+ let seen = 0;
19005
+ while (queue.length) {
19006
+ const id = queue.shift();
19007
+ seen++;
19008
+ for (const next of out.get(id) ?? []) {
19009
+ const n = (indeg.get(next) ?? 0) - 1;
19010
+ indeg.set(next, n);
19011
+ if (n === 0)
19012
+ queue.push(next);
19013
+ }
19014
+ }
19015
+ return seen === indeg.size ? [] : [...indeg.keys()].filter(k => (indeg.get(k) ?? 0) > 0);
19016
+ }
19017
+ /** Tâches qu'aucune vue temporelle ne sait placer — la zone « non planifié », jamais le silence. */
19018
+ const cadUnscheduled = (plan) => plan.tasks.filter(t => !t.scheduled);
19019
+
19020
+ /** Styles de cad-taskboard (présentations cartes et liste). Tokens --cad-* uniquement. */
19021
+ const CAD_TASKBOARD_STYLES = `
19022
+ :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); }
19023
+
19024
+ /* ---------------- présentation CARTES ---------------- */
19025
+ .tb__board { flex: 1; min-height: 0; display: flex; gap: 12px; padding: 12px; overflow-x: auto; overflow-y: hidden; scrollbar-gutter: stable; }
19026
+ .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); }
19027
+ .tb__head { display: flex; align-items: center; gap: 8px; padding: 9px 11px; border-bottom: 1px solid var(--cad-border); }
19028
+ .tb__dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--_c, var(--cad-border-strong)); }
19029
+ .tb__name { font-weight: 500; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
19030
+ .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; }
19031
+ .tb__count.is-full { background: var(--cad-warning-soft); color: var(--cad-warning); }
19032
+ .tb__count.is-over { background: var(--cad-danger-soft); color: var(--cad-danger); font-weight: 500; }
19033
+
19034
+ .tb__list { flex: 1; min-height: 0; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 8px; scrollbar-gutter: stable; }
19035
+ .tb__list.cdk-drop-list-dragging { background: var(--cad-accent-soft); }
19036
+
19037
+ .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; user-select: none; transition: border-color var(--cad-dur) var(--cad-ease), box-shadow var(--cad-dur) var(--cad-ease); }
19038
+ .tb__card:hover { border-color: var(--cad-border-strong); box-shadow: var(--cad-shadow-md); }
19039
+ .tb__card:focus-visible { outline: 2px solid var(--cad-ring); outline-offset: 1px; }
19040
+ .tb__card.is-orphan { border-left: 2px dashed var(--cad-warning); }
19041
+ :host(.is-static) .tb__card, :host(.is-static) .tb__row { cursor: default; }
19042
+
19043
+ .tb__title { font-weight: 500; line-height: 1.32; }
19044
+ .tb__meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
19045
+ .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); }
19046
+ .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; }
19047
+ .tb__due.is-late { color: var(--cad-danger); font-weight: 500; }
19048
+ .tb__sp { flex: 1 1 auto; }
19049
+ .tb__prog { height: 3px; border-radius: var(--cad-radius-pill); background: var(--cad-elevated); overflow: hidden; }
19050
+ .tb__prog i { display: block; height: 100%; border-radius: var(--cad-radius-pill); background: var(--cad-accent); }
19051
+ .tb__prog.is-done i { background: var(--cad-success); }
19052
+
19053
+ .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); }
19054
+
19055
+ /* ---------------- présentation LISTE ---------------- */
19056
+ .tb__rows { flex: 1; min-height: 0; overflow-y: auto; padding-bottom: 12px; scrollbar-gutter: stable; }
19057
+ .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; }
19058
+ .tb__grp:hover { background: var(--cad-hover); }
19059
+ .tb__grp:focus-visible { outline: 2px solid var(--cad-ring); outline-offset: -2px; }
19060
+ .tb__grp cad-icon { color: var(--cad-fg-muted); transition: transform var(--cad-dur) var(--cad-ease); }
19061
+ .tb__grp.is-collapsed cad-icon { transform: rotate(-90deg); }
19062
+ .tb__grp b { font-weight: 500; }
19063
+
19064
+ .tb__group-list { display: block; }
19065
+ .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; user-select: none; background: var(--cad-bg); }
19066
+ .tb__row:hover { background: var(--cad-hover); }
19067
+ .tb__row:focus-visible { outline: 2px solid var(--cad-ring); outline-offset: -2px; }
19068
+ .tb__row.is-orphan { box-shadow: inset 2px 0 0 var(--cad-warning); }
19069
+ .tb__main { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
19070
+ .tb__main .tb__title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
19071
+ .tb__pct { display: flex; align-items: center; gap: 7px; }
19072
+ .tb__pct .tb__prog { flex: 1; }
19073
+ .tb__pct span { font-family: var(--cad-font-mono); font-size: 10px; color: var(--cad-fg-muted); font-variant-numeric: tabular-nums; }
19074
+
19075
+ /* sous le seuil, la liste se resserre : l'échéance rejoint le titre, l'avancement s'efface */
19076
+ :host(.is-compact) .tb__row { grid-template-columns: 1fr 26px; }
19077
+ :host(.is-compact) .tb__cell-due, :host(.is-compact) .tb__pct { display: none; }
19078
+ :host(.is-compact) .tb__mob { display: inline-flex; }
19079
+ .tb__mob { display: none; align-items: center; gap: 7px; }
19080
+
19081
+ /* ---------------- glisser (CDK) ---------------- */
19082
+ .cdk-drag-preview { box-shadow: var(--cad-shadow-lg); border-radius: var(--cad-radius-sm); background: var(--cad-overlay); border: 1px solid var(--cad-border); }
19083
+ .cdk-drag-placeholder { opacity: .34; }
19084
+ .cdk-drag-animating { transition: transform 180ms var(--cad-ease); }
19085
+ .tb__list.cdk-drop-list-dragging .tb__card:not(.cdk-drag-placeholder),
19086
+ .tb__group-list.cdk-drop-list-dragging .tb__row:not(.cdk-drag-placeholder) { transition: transform 180ms var(--cad-ease); }
19087
+
19088
+ /* annonce aux lecteurs d'écran — hors flux, jamais display:none (elle ne serait plus lue) */
19089
+ .tb__live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
19090
+
19091
+ @media (prefers-reduced-motion: reduce) { .cdk-drag-animating, .tb__list.cdk-drop-list-dragging .tb__card, .tb__grp cad-icon { transition: none; } }
19092
+ `;
19093
+
19094
+ /** Contenu d'une carte : `<ng-template cadTaskCard let-task let-bucket="bucket">`. */
19095
+ class CadTaskCardDirective {
19096
+ tpl = inject(TemplateRef);
19097
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadTaskCardDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
19098
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.20", type: CadTaskCardDirective, isStandalone: true, selector: "[cadTaskCard]", ngImport: i0 });
19099
+ }
19100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadTaskCardDirective, decorators: [{
19101
+ type: Directive,
19102
+ args: [{ selector: '[cadTaskCard]', standalone: true }]
19103
+ }] });
19104
+ /** Contenu d'une ligne : `<ng-template cadTaskRow let-task let-group="group">`. */
19105
+ class CadTaskRowDirective {
19106
+ tpl = inject(TemplateRef);
19107
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadTaskRowDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
19108
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.20", type: CadTaskRowDirective, isStandalone: true, selector: "[cadTaskRow]", ngImport: i0 });
19109
+ }
19110
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadTaskRowDirective, decorators: [{
19111
+ type: Directive,
19112
+ args: [{ selector: '[cadTaskRow]', standalone: true }]
19113
+ }] });
19114
+ const EMPTY_PATCH = { src: undefined, map: new Map() };
19115
+ const NO_RESOURCE = ' none';
19116
+ /**
19117
+ * <cad-taskboard [plan]="plan" mode="auto" groupBy="bucket" (taskMove) (taskAssign) (taskOpen) (wipExceeded)>
19118
+ * <ng-template cadTaskCard let-t>…</ng-template>
19119
+ * <ng-template cadTaskRow let-t>…</ng-template>
19120
+ * </cad-taskboard>
19121
+ *
19122
+ * UNE SEULE ENTRÉE sépare les deux présentations : `mode`. On transforme la présentation, jamais
19123
+ * l'architecture de l'information — mêmes tâches, même rang, même geste, même intention. `auto`
19124
+ * bascule en liste sous le seuil mobile, sur le patron de `cad-nav-drawer`.
19125
+ *
19126
+ * LE COMPOSANT N'ÉCRIT JAMAIS. Il émet `taskMove` / `taskAssign` et laisse l'application décider :
19127
+ * c'est ce qui rend la source indifférente (slapi ou document statique). Pour que le geste reste
19128
+ * vivant sans mentir, il conserve un CORRECTIF LOCAL optimiste, abandonné dès qu'un nouveau `plan`
19129
+ * arrive — l'entrée n'est jamais mutée, et l'affichage suit l'application si elle refuse.
19130
+ *
19131
+ * La limite de travail en cours ALERTE mais ne bloque pas : un tableau qui refuse un geste sans
19132
+ * l'expliquer se contourne au lieu d'être lu.
19133
+ *
19134
+ * Clavier : Entrée / Espace ouvrent la tâche ; Ctrl (ou Cmd) + flèches la déplacent — gauche et
19135
+ * droite changent de colonne, haut et bas changent de rang. Chaque déplacement est annoncé.
19136
+ */
19137
+ class CadTaskboardComponent {
19138
+ /** Document de planification brut ; il est normalisé en interne et jamais muté. */
19139
+ plan = input(null, ...(ngDevMode ? [{ debugName: "plan" }] : /* istanbul ignore next */ []));
19140
+ /** 'board' = cartes, 'list' = lignes groupées, 'auto' = liste sous le seuil mobile. */
19141
+ mode = input('auto', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
19142
+ /** Regroupement de la présentation liste. Par personne, le glisser réaffecte au lieu de ranger. */
19143
+ groupBy = input('bucket', ...(ngDevMode ? [{ debugName: "groupBy" }] : /* istanbul ignore next */ []));
19144
+ dragDisabled = input(false, { ...(ngDevMode ? { debugName: "dragDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
19145
+ /** Groupes repliés (présentation liste). */
19146
+ collapsedKeys = model(new Set(), ...(ngDevMode ? [{ debugName: "collapsedKeys" }] : /* istanbul ignore next */ []));
19147
+ emptyText = input('Rien ici', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
19148
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
19149
+ /** Format d'échéance (jetons de `formatDate`). */
19150
+ dateFormat = input('dd/MM', ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
19151
+ /** Instant de référence pour « en retard ». Explicite en test, sinon maintenant. */
19152
+ today = input(null, ...(ngDevMode ? [{ debugName: "today" }] : /* istanbul ignore next */ []));
19153
+ taskMove = output();
19154
+ taskAssign = output();
19155
+ taskOpen = output();
19156
+ wipExceeded = output();
19157
+ cardTpl = contentChild(CadTaskCardDirective, ...(ngDevMode ? [{ debugName: "cardTpl" }] : /* istanbul ignore next */ []));
19158
+ rowTpl = contentChild(CadTaskRowDirective, ...(ngDevMode ? [{ debugName: "rowTpl" }] : /* istanbul ignore next */ []));
19159
+ orphanHint = 'Sans colonne connue — rangée par défaut, jamais escamotée';
19160
+ announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
19161
+ bp = inject(CadBreakpointService);
19162
+ patch = signal(EMPTY_PATCH, ...(ngDevMode ? [{ debugName: "patch" }] : /* istanbul ignore next */ []));
19163
+ /** Document résolu par le socle de planification. */
19164
+ normalized = computed(() => cadNormalizePlan(this.plan()), ...(ngDevMode ? [{ debugName: "normalized" }] : /* istanbul ignore next */ []));
19165
+ /** Présentation effective, une fois `auto` tranché par le seuil. */
19166
+ view = computed(() => {
19167
+ const m = this.mode();
19168
+ return m === 'auto' ? (this.bp.isMobile() ? 'list' : 'board') : m;
19169
+ }, ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
19170
+ compact = computed(() => this.view() === 'list' && this.bp.isMobile(), ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
19171
+ /** Tâches après application du correctif optimiste (abandonné dès que `plan` change). */
19172
+ tasks = computed(() => {
19173
+ const base = this.normalized().tasks;
19174
+ const p = this.patch();
19175
+ if (p.src !== this.plan() || !p.map.size)
19176
+ return base;
19177
+ return base.map(t => {
19178
+ const o = p.map.get(t.id);
19179
+ if (!o)
19180
+ return t;
19181
+ const next = { ...t, rank: o.rank };
19182
+ if (o.bucket !== undefined)
19183
+ next.bucket = o.bucket;
19184
+ if (o.resource !== undefined) {
19185
+ next.assignments = o.resource ? [{ resource: o.resource, units: t.assignments[0]?.units ?? 1 }] : [];
19186
+ }
19187
+ return next;
19188
+ });
19189
+ }, ...(ngDevMode ? [{ debugName: "tasks" }] : /* istanbul ignore next */ []));
19190
+ /** Colonnes déclarées, ou une colonne implicite : un tableau sans colonne reste un tableau. */
19191
+ declared = computed(() => {
19192
+ const b = this.normalized().buckets;
19193
+ return b.length ? b : [{ id: '', label: 'Tâches' }];
19194
+ }, ...(ngDevMode ? [{ debugName: "declared" }] : /* istanbul ignore next */ []));
19195
+ /** Colonnes (cartes, ou liste groupée par colonne) ou personnes (liste groupée par personne). */
19196
+ columns = computed(() => {
19197
+ const tasks = this.tasks();
19198
+ const byRank = (a, b) => (a.rank < b.rank ? -1 : a.rank > b.rank ? 1 : 0);
19199
+ if (this.view() === 'list' && this.groupBy() === 'resource') {
19200
+ const res = this.normalized().resources;
19201
+ const known = new Set(res.map(r => r.id));
19202
+ const groups = res.map(r => ({
19203
+ id: r.id,
19204
+ label: r['label'] ?? r.id,
19205
+ color: r.color ?? null,
19206
+ wipLimit: null,
19207
+ tasks: tasks.filter(t => t.assignments[0]?.resource === r.id).sort(byRank),
19208
+ }));
19209
+ groups.push({
19210
+ id: NO_RESOURCE,
19211
+ label: 'Non affectées',
19212
+ color: null,
19213
+ wipLimit: null,
19214
+ tasks: tasks.filter(t => !t.assignments.length || !known.has(t.assignments[0].resource)).sort(byRank),
19215
+ });
19216
+ return groups;
19217
+ }
19218
+ return this.declared().map((b, i) => ({
19219
+ id: b.id,
19220
+ label: b.label ?? b.id,
19221
+ color: b.color ?? null,
19222
+ wipLimit: b.wipLimit ?? null,
19223
+ tasks: tasks.filter(t => (this.isOrphan(t) ? i === 0 : t.bucket === b.id)).sort(byRank),
19224
+ }));
19225
+ }, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
19226
+ /* ---------------- lecture ---------------- */
19227
+ /** Colonne réelle d'une tâche : la sienne si elle existe, sinon la première. Jamais « nulle part ». */
19228
+ bucketOf(t) {
19229
+ const declared = this.declared();
19230
+ return declared.some(b => b.id === t.bucket) ? t.bucket : declared[0].id;
19231
+ }
19232
+ isOrphan(t) {
19233
+ return !this.declared().some(b => b.id === t.bucket);
19234
+ }
19235
+ countOf(c) {
19236
+ return c.wipLimit == null ? String(c.tasks.length) : c.tasks.length + '/' + c.wipLimit;
19237
+ }
19238
+ wipState(c) {
19239
+ if (c.wipLimit == null)
19240
+ return 'none';
19241
+ return c.tasks.length > c.wipLimit ? 'over' : c.tasks.length === c.wipLimit ? 'full' : 'none';
19242
+ }
19243
+ due(t) {
19244
+ return t.end ? formatDate(t.end, this.dateFormat()) : '';
19245
+ }
19246
+ isLate(t) {
19247
+ return !!t.end && t.progress < 1 && t.end.getTime() < (this.today() ?? new Date()).getTime();
19248
+ }
19249
+ pct(t) { return Math.round(t.progress * 100); }
19250
+ labelOfResource(id) {
19251
+ const r = this.normalized().resources.find(x => x.id === id);
19252
+ return (r?.['label'] ?? id).slice(0, 24);
19253
+ }
19254
+ /** Initiales d'une personne : « S. Zuccatti » donne SZ, « Charles » donne CH. */
19255
+ initialsOfResource(id) {
19256
+ const mots = this.labelOfResource(id).split(/[\s.'-]+/).filter(Boolean);
19257
+ return (mots.length > 1 ? mots[0][0] + mots[1][0] : (mots[0] ?? id).slice(0, 2)).toUpperCase();
19258
+ }
19259
+ colorOfResource(id) {
19260
+ return this.normalized().resources.find(x => x.id === id)?.color ?? null;
19261
+ }
19262
+ isCollapsed(key) { return this.collapsedKeys().has(key); }
19263
+ toggle(key) {
19264
+ const next = new Set(this.collapsedKeys());
19265
+ if (next.has(key))
19266
+ next.delete(key);
19267
+ else
19268
+ next.add(key);
19269
+ this.collapsedKeys.set(next);
19270
+ }
19271
+ open(t, event) {
19272
+ this.taskOpen.emit({ id: t.id, task: t, event });
19273
+ }
19274
+ /* ---------------- déplacement ---------------- */
19275
+ onDrop(e, kind) {
19276
+ const same = e.previousContainer === e.container;
19277
+ this.apply(e.item.data, e.container.data, e.currentIndex, kind, same ? e.previousIndex : -1);
19278
+ }
19279
+ onKey(e, t, colId) {
19280
+ if (e.key === 'Enter' || e.key === ' ') {
19281
+ e.preventDefault();
19282
+ this.open(t, e);
19283
+ return;
19284
+ }
19285
+ if ((!e.ctrlKey && !e.metaKey) || this.dragDisabled() || t.locked)
19286
+ return;
19287
+ const cols = this.columns();
19288
+ const ci = cols.findIndex(c => c.id === colId);
19289
+ if (ci < 0)
19290
+ return;
19291
+ const here = cols[ci].tasks.findIndex(x => x.id === t.id);
19292
+ const kind = this.view() === 'list' ? this.groupBy() : 'bucket';
19293
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
19294
+ const to = here + (e.key === 'ArrowUp' ? -1 : 1);
19295
+ if (to < 0 || to >= cols[ci].tasks.length)
19296
+ return;
19297
+ e.preventDefault();
19298
+ this.apply(t.id, colId, to, kind, here);
19299
+ }
19300
+ else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
19301
+ const target = cols[ci + (e.key === 'ArrowLeft' ? -1 : 1)];
19302
+ if (!target)
19303
+ return;
19304
+ e.preventDefault();
19305
+ this.apply(t.id, target.id, Math.min(here, target.tasks.length), kind, -1);
19306
+ }
19307
+ }
19308
+ /**
19309
+ * Calcule le nouveau rang entre les deux voisins, pose le correctif optimiste et ÉMET.
19310
+ * `fromIndex` >= 0 signale un déplacement dans le même groupe : le voisinage se lit alors sur
19311
+ * la liste privée de la tâche déplacée, exactement comme le fait `moveItemInArray`.
19312
+ */
19313
+ apply(id, target, index, kind, fromIndex) {
19314
+ const task = this.tasks().find(t => t.id === id);
19315
+ const column = this.columns().find(c => c.id === target);
19316
+ if (!task || !column)
19317
+ return;
19318
+ const items = column.tasks.filter(t => t.id !== id);
19319
+ const at = Math.max(0, Math.min(index, items.length));
19320
+ const rank = cadRankBetween(items[at - 1]?.rank ?? null, items[at]?.rank ?? null);
19321
+ const previousRank = task.rank;
19322
+ if (fromIndex === at)
19323
+ return;
19324
+ const current = this.patch();
19325
+ const map = new Map(current.src === this.plan() ? current.map : []);
19326
+ if (kind === 'resource') {
19327
+ const from = task.assignments[0]?.resource ?? null;
19328
+ const to = target === NO_RESOURCE ? null : target;
19329
+ map.set(id, { ...map.get(id), resource: to, rank });
19330
+ this.patch.set({ src: this.plan(), map });
19331
+ this.taskAssign.emit({ id, task, from, to, rank });
19332
+ this.announce(task, target, column.label, at, 'affectée à ');
19333
+ return;
19334
+ }
19335
+ const from = this.bucketOf(task);
19336
+ map.set(id, { ...map.get(id), bucket: target, rank });
19337
+ this.patch.set({ src: this.plan(), map });
19338
+ this.taskMove.emit({ id, task, from, to: target, rank, previousRank, index: at });
19339
+ this.announce(task, target, column.label, at, 'déplacée dans ');
19340
+ const after = this.columns().find(c => c.id === target);
19341
+ if (after && after.wipLimit != null && after.tasks.length > after.wipLimit) {
19342
+ this.wipExceeded.emit({ bucket: target, limit: after.wipLimit, count: after.tasks.length });
19343
+ }
19344
+ }
19345
+ /** L'annonce ne dit pas « déplacé » : elle dit OÙ et À QUELLE PLACE — la seule information utile sans la vue. */
19346
+ announce(task, key, label, at, verb) {
19347
+ const total = this.columns().find(c => c.id === key)?.tasks.length ?? 0;
19348
+ this.announcement.set(task.label + ' — ' + verb + label + ', position ' + (at + 1) + ' sur ' + total + '.');
19349
+ }
19350
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadTaskboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19351
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadTaskboardComponent, isStandalone: true, selector: "cad-taskboard", inputs: { plan: { classPropertyName: "plan", publicName: "plan", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, dragDisabled: { classPropertyName: "dragDisabled", publicName: "dragDisabled", isSignal: true, isRequired: false, transformFunction: null }, collapsedKeys: { classPropertyName: "collapsedKeys", publicName: "collapsedKeys", 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 }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsedKeys: "collapsedKeysChange", taskMove: "taskMove", taskAssign: "taskAssign", taskOpen: "taskOpen", wipExceeded: "wipExceeded" }, host: { properties: { "class.is-compact": "compact()", "class.is-static": "dragDisabled()", "attr.aria-label": "ariaLabel() || null" }, classAttribute: "cad-taskboard" }, queries: [{ propertyName: "cardTpl", first: true, predicate: CadTaskCardDirective, descendants: true, isSignal: true }, { propertyName: "rowTpl", first: true, predicate: CadTaskRowDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
19352
+ <p class="tb__live" role="status" aria-live="polite">{{ announcement() }}</p>
19353
+
19354
+ @if (view() === 'board') {
19355
+ <div class="tb__board" cdkDropListGroup>
19356
+ @for (col of columns(); track col.id) {
19357
+ <section class="tb__col">
19358
+ <header class="tb__head">
19359
+ <span class="tb__dot" [style.--_c]="col.color"></span>
19360
+ <span class="tb__name">{{ col.label }}</span>
19361
+ <span class="tb__count" [class.is-full]="wipState(col) === 'full'" [class.is-over]="wipState(col) === 'over'"
19362
+ [attr.title]="col.wipLimit != null ? 'Limite de travail en cours' : null">{{ countOf(col) }}</span>
19363
+ </header>
19364
+ <div class="tb__list" cdkDropList [cdkDropListData]="col.id" [cdkDropListDisabled]="dragDisabled()"
19365
+ (cdkDropListDropped)="onDrop($event, 'bucket')" [attr.aria-label]="col.label">
19366
+ @for (t of col.tasks; track t.id) {
19367
+ <article class="tb__card" cdkDrag [cdkDragData]="t.id" [cdkDragDisabled]="dragDisabled() || t.locked"
19368
+ tabindex="0" role="button" [attr.data-id]="t.id" [class.is-orphan]="isOrphan(t)"
19369
+ [attr.title]="isOrphan(t) ? orphanHint : null"
19370
+ (click)="open(t, $event)" (keydown)="onKey($event, t, col.id)">
19371
+ @if (cardTpl()) {
19372
+ <ng-container *ngTemplateOutlet="cardTpl()!.tpl; context: { $implicit: t, bucket: col.id }"/>
19373
+ } @else {
19374
+ <p class="tb__title">{{ t.label }}</p>
19375
+ @if (t.progress > 0) {<div class="tb__prog" [class.is-done]="t.progress >= 1"><i [style.width.%]="t.progress * 100"></i></div>}
19376
+ <div class="tb__meta">
19377
+ @for (g of t.tags; track g) {<span class="tb__tag">{{ g }}</span>}
19378
+ <span class="tb__sp"></span>
19379
+ @if (t.end) {<span class="tb__due" [class.is-late]="isLate(t)" title="Échéance">{{ due(t) }}</span>}
19380
+ @for (a of t.assignments; track a.resource) {
19381
+ <cad-avatar size="sm" [text]="initialsOfResource(a.resource)" [color]="colorOfResource(a.resource)" [title]="labelOfResource(a.resource)"/>
19382
+ }
19383
+ </div>
19384
+ }
19385
+ </article>
19386
+ } @empty {
19387
+ <p class="tb__empty">{{ emptyText() }}</p>
19388
+ }
19389
+ </div>
19390
+ </section>
19391
+ }
19392
+ </div>
19393
+ } @else {
19394
+ <div class="tb__rows" cdkDropListGroup>
19395
+ @for (col of columns(); track col.id) {
19396
+ <button type="button" class="tb__grp" [class.is-collapsed]="isCollapsed(col.id)"
19397
+ [attr.aria-expanded]="!isCollapsed(col.id)" (click)="toggle(col.id)">
19398
+ <cad-icon name="chevron-down" [size]="14"/>
19399
+ <span class="tb__dot" [style.--_c]="col.color"></span>
19400
+ <b>{{ col.label }}</b>
19401
+ <span class="tb__sp"></span>
19402
+ <span class="tb__count" [class.is-full]="wipState(col) === 'full'" [class.is-over]="wipState(col) === 'over'">{{ countOf(col) }}</span>
19403
+ </button>
19404
+ @if (!isCollapsed(col.id)) {
19405
+ <div class="tb__group-list" cdkDropList [cdkDropListData]="col.id" [cdkDropListDisabled]="dragDisabled()"
19406
+ (cdkDropListDropped)="onDrop($event, groupBy())" [attr.aria-label]="col.label">
19407
+ @for (t of col.tasks; track t.id) {
19408
+ <div class="tb__row" cdkDrag [cdkDragData]="t.id" [cdkDragDisabled]="dragDisabled() || t.locked"
19409
+ tabindex="0" role="button" [attr.data-id]="t.id" [class.is-orphan]="isOrphan(t)"
19410
+ [attr.title]="isOrphan(t) ? orphanHint : null"
19411
+ (click)="open(t, $event)" (keydown)="onKey($event, t, col.id)">
19412
+ @if (rowTpl()) {
19413
+ <ng-container *ngTemplateOutlet="rowTpl()!.tpl; context: { $implicit: t, group: col.id }"/>
19414
+ } @else {
19415
+ <div class="tb__main">
19416
+ <span class="tb__title">{{ t.label }}</span>
19417
+ <div class="tb__meta">
19418
+ @for (g of t.tags; track g) {<span class="tb__tag">{{ g }}</span>}
19419
+ @if (t.end) {<span class="tb__mob tb__due" [class.is-late]="isLate(t)">{{ due(t) }}</span>}
19420
+ </div>
19421
+ </div>
19422
+ <span class="tb__due tb__cell-due" [class.is-late]="isLate(t)">{{ t.end ? due(t) : '—' }}</span>
19423
+ <div class="tb__pct">
19424
+ <div class="tb__prog" [class.is-done]="t.progress >= 1"><i [style.width.%]="t.progress * 100"></i></div>
19425
+ <span>{{ pct(t) }}%</span>
19426
+ </div>
19427
+ @if (t.assignments.length) {
19428
+ <cad-avatar size="sm" [text]="initialsOfResource(t.assignments[0].resource)"
19429
+ [color]="colorOfResource(t.assignments[0].resource)" [title]="labelOfResource(t.assignments[0].resource)"/>
19430
+ } @else {<span class="tb__due">—</span>}
19431
+ }
19432
+ </div>
19433
+ } @empty {
19434
+ <p class="tb__empty" style="margin: 10px 14px">{{ emptyText() }}</p>
19435
+ }
19436
+ </div>
19437
+ }
19438
+ }
19439
+ </div>
19440
+ }
19441
+ `, isInline: true, 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);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"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "component", type: CadAvatarComponent, selector: "cad-avatar", inputs: ["src", "text", "icon", "size", "shape", "color", "label"] }, { kind: "component", type: CadIconComponent, selector: "cad-icon", inputs: ["name", "size", "strokeWidth", "label", "title", "spin", "filled", "flip", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19442
+ }
19443
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadTaskboardComponent, decorators: [{
19444
+ type: Component,
19445
+ args: [{ selector: 'cad-taskboard', standalone: true, imports: [NgTemplateOutlet, CdkDrag, CdkDropList, CdkDropListGroup, CadAvatarComponent, CadIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
19446
+ class: 'cad-taskboard',
19447
+ '[class.is-compact]': 'compact()',
19448
+ '[class.is-static]': 'dragDisabled()',
19449
+ '[attr.aria-label]': 'ariaLabel() || null',
19450
+ }, template: `
19451
+ <p class="tb__live" role="status" aria-live="polite">{{ announcement() }}</p>
19452
+
19453
+ @if (view() === 'board') {
19454
+ <div class="tb__board" cdkDropListGroup>
19455
+ @for (col of columns(); track col.id) {
19456
+ <section class="tb__col">
19457
+ <header class="tb__head">
19458
+ <span class="tb__dot" [style.--_c]="col.color"></span>
19459
+ <span class="tb__name">{{ col.label }}</span>
19460
+ <span class="tb__count" [class.is-full]="wipState(col) === 'full'" [class.is-over]="wipState(col) === 'over'"
19461
+ [attr.title]="col.wipLimit != null ? 'Limite de travail en cours' : null">{{ countOf(col) }}</span>
19462
+ </header>
19463
+ <div class="tb__list" cdkDropList [cdkDropListData]="col.id" [cdkDropListDisabled]="dragDisabled()"
19464
+ (cdkDropListDropped)="onDrop($event, 'bucket')" [attr.aria-label]="col.label">
19465
+ @for (t of col.tasks; track t.id) {
19466
+ <article class="tb__card" cdkDrag [cdkDragData]="t.id" [cdkDragDisabled]="dragDisabled() || t.locked"
19467
+ tabindex="0" role="button" [attr.data-id]="t.id" [class.is-orphan]="isOrphan(t)"
19468
+ [attr.title]="isOrphan(t) ? orphanHint : null"
19469
+ (click)="open(t, $event)" (keydown)="onKey($event, t, col.id)">
19470
+ @if (cardTpl()) {
19471
+ <ng-container *ngTemplateOutlet="cardTpl()!.tpl; context: { $implicit: t, bucket: col.id }"/>
19472
+ } @else {
19473
+ <p class="tb__title">{{ t.label }}</p>
19474
+ @if (t.progress > 0) {<div class="tb__prog" [class.is-done]="t.progress >= 1"><i [style.width.%]="t.progress * 100"></i></div>}
19475
+ <div class="tb__meta">
19476
+ @for (g of t.tags; track g) {<span class="tb__tag">{{ g }}</span>}
19477
+ <span class="tb__sp"></span>
19478
+ @if (t.end) {<span class="tb__due" [class.is-late]="isLate(t)" title="Échéance">{{ due(t) }}</span>}
19479
+ @for (a of t.assignments; track a.resource) {
19480
+ <cad-avatar size="sm" [text]="initialsOfResource(a.resource)" [color]="colorOfResource(a.resource)" [title]="labelOfResource(a.resource)"/>
19481
+ }
19482
+ </div>
19483
+ }
19484
+ </article>
19485
+ } @empty {
19486
+ <p class="tb__empty">{{ emptyText() }}</p>
19487
+ }
19488
+ </div>
19489
+ </section>
19490
+ }
19491
+ </div>
19492
+ } @else {
19493
+ <div class="tb__rows" cdkDropListGroup>
19494
+ @for (col of columns(); track col.id) {
19495
+ <button type="button" class="tb__grp" [class.is-collapsed]="isCollapsed(col.id)"
19496
+ [attr.aria-expanded]="!isCollapsed(col.id)" (click)="toggle(col.id)">
19497
+ <cad-icon name="chevron-down" [size]="14"/>
19498
+ <span class="tb__dot" [style.--_c]="col.color"></span>
19499
+ <b>{{ col.label }}</b>
19500
+ <span class="tb__sp"></span>
19501
+ <span class="tb__count" [class.is-full]="wipState(col) === 'full'" [class.is-over]="wipState(col) === 'over'">{{ countOf(col) }}</span>
19502
+ </button>
19503
+ @if (!isCollapsed(col.id)) {
19504
+ <div class="tb__group-list" cdkDropList [cdkDropListData]="col.id" [cdkDropListDisabled]="dragDisabled()"
19505
+ (cdkDropListDropped)="onDrop($event, groupBy())" [attr.aria-label]="col.label">
19506
+ @for (t of col.tasks; track t.id) {
19507
+ <div class="tb__row" cdkDrag [cdkDragData]="t.id" [cdkDragDisabled]="dragDisabled() || t.locked"
19508
+ tabindex="0" role="button" [attr.data-id]="t.id" [class.is-orphan]="isOrphan(t)"
19509
+ [attr.title]="isOrphan(t) ? orphanHint : null"
19510
+ (click)="open(t, $event)" (keydown)="onKey($event, t, col.id)">
19511
+ @if (rowTpl()) {
19512
+ <ng-container *ngTemplateOutlet="rowTpl()!.tpl; context: { $implicit: t, group: col.id }"/>
19513
+ } @else {
19514
+ <div class="tb__main">
19515
+ <span class="tb__title">{{ t.label }}</span>
19516
+ <div class="tb__meta">
19517
+ @for (g of t.tags; track g) {<span class="tb__tag">{{ g }}</span>}
19518
+ @if (t.end) {<span class="tb__mob tb__due" [class.is-late]="isLate(t)">{{ due(t) }}</span>}
19519
+ </div>
19520
+ </div>
19521
+ <span class="tb__due tb__cell-due" [class.is-late]="isLate(t)">{{ t.end ? due(t) : '—' }}</span>
19522
+ <div class="tb__pct">
19523
+ <div class="tb__prog" [class.is-done]="t.progress >= 1"><i [style.width.%]="t.progress * 100"></i></div>
19524
+ <span>{{ pct(t) }}%</span>
19525
+ </div>
19526
+ @if (t.assignments.length) {
19527
+ <cad-avatar size="sm" [text]="initialsOfResource(t.assignments[0].resource)"
19528
+ [color]="colorOfResource(t.assignments[0].resource)" [title]="labelOfResource(t.assignments[0].resource)"/>
19529
+ } @else {<span class="tb__due">—</span>}
19530
+ }
19531
+ </div>
19532
+ } @empty {
19533
+ <p class="tb__empty" style="margin: 10px 14px">{{ emptyText() }}</p>
19534
+ }
19535
+ </div>
19536
+ }
19537
+ }
19538
+ </div>
19539
+ }
19540
+ `, 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);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
+ }], 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
+
18099
19543
  /**
18100
19544
  * Generated bundle index. Do not edit.
18101
19545
  */
18102
19546
 
18103
- 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, CadTimelineComponent, CadTimelineContentDirective, CadTimelineMarkerDirective, CadTimelineOppositeDirective, CadToastComponent, CadToastItemDirective, CadToastService, CadToggleButtonComponent, CadTooltipComponent, CadTooltipDirective, CadTreeAdapter, CadTreeComponent, CadTreeNodeDirective, CadTreeselectComponent, CadUploadComponent, CadUploadContentDirective, CadUploadEmptyDirective, CadUploadItemDirective, CadValueAccessor, MAPLIBRE_MIN_CSS, addDays, addMonths, addYears, cadAggregate, cadApplyColumnOrder, cadApplyMask, cadArea, cadAutosizeWidth, cadBasemapThumbnail, cadBoundsOf, cadCategories, cadCentroid, cadColorExpr, cadCompare, cadCurrentOperator, cadDefaultMatchMode, cadDistance, cadDownloadCsv, cadFilterData, cadFilterKindOf, cadFilterOperators, cadFilterRows, cadFmtArea, cadFmtLength, cadFormatCell, cadFormatColor, cadFormatSize, cadGap, cadGeoJSONStats, cadGeometryKinds, cadHeaderMenuItems, cadHighlightJson, cadHslToRgb, cadHsvToRgb, cadId, cadIsGroupKey, cadJsonErrorPos, cadLegendFromStyle, cadLength, cadMapEstVivante, cadMapLayerOf, cadMapLayersOf, cadMatch, cadMoveColumn, cadMoveItems, cadNormalize, cadNormalizeOptions, cadNumberExpr, cadPadBounds, cadParseColor, cadParseResponsive, cadPropsTable, cadResolve, cadResolveResponsive, cadResponsive, cadRgbToHsl, cadRgbToHsv, cadRingArea, cadRowKey, cadRowPredicate, cadSameValue, cadSortData, cadStartResize, cadStyleToExpressions, cadUnmask, 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 };
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 };
18104
19548
  //# sourceMappingURL=cadriciel-ui.mjs.map