@cadriciel/ui 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/cadriciel-ui.mjs +1555 -1
- package/fesm2022/cadriciel-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/types/cadriciel-ui.d.ts +475 -2
|
@@ -18951,6 +18951,7 @@ function cadNormalizePlan(plan, options = {}) {
|
|
|
18951
18951
|
start,
|
|
18952
18952
|
end,
|
|
18953
18953
|
milestone,
|
|
18954
|
+
allDay: !!t.allDay,
|
|
18954
18955
|
bucket: t.bucket ?? null,
|
|
18955
18956
|
rank: cadIsRank(t.rank) ? t.rank : '',
|
|
18956
18957
|
assignments,
|
|
@@ -19150,6 +19151,552 @@ function cadFindLinkCycle(links) {
|
|
|
19150
19151
|
/** Tâches qu'aucune vue temporelle ne sait placer — la zone « non planifié », jamais le silence. */
|
|
19151
19152
|
const cadUnscheduled = (plan) => plan.tasks.filter(t => !t.scheduled);
|
|
19152
19153
|
|
|
19154
|
+
/**
|
|
19155
|
+
* Dépliage des séries récurrentes — EN LECTURE SEULE.
|
|
19156
|
+
*
|
|
19157
|
+
* CE QUE CE FICHIER FAIT, ET CE QU'IL NE FAIT PAS. Il projette une règle en occurrences pour
|
|
19158
|
+
* qu'une vue puisse les afficher. Il n'écrit rien, ne modifie aucune série, et ne connaît pas
|
|
19159
|
+
* la sémantique d'édition à trois branches (« cette occurrence / celle-ci et les suivantes /
|
|
19160
|
+
* toutes »), qui reste entière dans son propre lot.
|
|
19161
|
+
*
|
|
19162
|
+
* C'est un choix, pas un raccourci : une récurrence à moitié faite PERD DES MODIFICATIONS SANS
|
|
19163
|
+
* LE DIRE, ce qui est pire que pas de récurrence. Déplier pour afficher n'écrit rien, donc ne
|
|
19164
|
+
* peut rien perdre. On livre donc la moitié qui est sûre.
|
|
19165
|
+
*
|
|
19166
|
+
* SOUS-ENSEMBLE RRULE ASSUMÉ (RFC 5545) : `FREQ` (DAILY, WEEKLY, MONTHLY, YEARLY), `INTERVAL`,
|
|
19167
|
+
* `BYDAY` (en hebdomadaire), `BYMONTHDAY` (en mensuel), `COUNT`, `UNTIL`.
|
|
19168
|
+
* EXCLUS ET DOCUMENTÉS : `BYSETPOS` composé, `BYYEARDAY`, `BYWEEKNO`, `VTIMEZONE`, et les
|
|
19169
|
+
* préfixes ordinaux de `BYDAY` (`2MO` = deuxième lundi). Une règle qui en contient est rendue
|
|
19170
|
+
* telle qu'elle peut l'être, et l'anomalie est signalée — jamais silencieusement approximée.
|
|
19171
|
+
*/
|
|
19172
|
+
/** Jours iCalendar, dans l'ordre de `Date.getDay()` : 0 = dimanche. */
|
|
19173
|
+
const JOURS_ICAL = { SU: 0, MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6 };
|
|
19174
|
+
/** Parties que ce moteur sait lire. Tout le reste atterrit dans `ignore`. */
|
|
19175
|
+
const CONNUES = new Set(['FREQ', 'INTERVAL', 'BYDAY', 'BYMONTHDAY', 'COUNT', 'UNTIL', 'WKST']);
|
|
19176
|
+
/**
|
|
19177
|
+
* Analyse une chaîne `RRULE`. Rend `null` si elle est inexploitable — jamais une règle
|
|
19178
|
+
* approximative : mieux vaut un événement unique qu'une série fausse.
|
|
19179
|
+
*/
|
|
19180
|
+
function cadParseRRule(entree) {
|
|
19181
|
+
if (!entree)
|
|
19182
|
+
return null;
|
|
19183
|
+
const brut = entree.replace(/^RRULE:/i, '').trim();
|
|
19184
|
+
if (!brut)
|
|
19185
|
+
return null;
|
|
19186
|
+
const parts = new Map();
|
|
19187
|
+
const ignore = [];
|
|
19188
|
+
for (const morceau of brut.split(';')) {
|
|
19189
|
+
const i = morceau.indexOf('=');
|
|
19190
|
+
if (i < 0)
|
|
19191
|
+
continue;
|
|
19192
|
+
const cle = morceau.slice(0, i).trim().toUpperCase();
|
|
19193
|
+
const val = morceau.slice(i + 1).trim();
|
|
19194
|
+
if (!CONNUES.has(cle)) {
|
|
19195
|
+
ignore.push(cle);
|
|
19196
|
+
continue;
|
|
19197
|
+
}
|
|
19198
|
+
parts.set(cle, val);
|
|
19199
|
+
}
|
|
19200
|
+
const freq = (parts.get('FREQ') || '').toUpperCase();
|
|
19201
|
+
if (freq !== 'DAILY' && freq !== 'WEEKLY' && freq !== 'MONTHLY' && freq !== 'YEARLY')
|
|
19202
|
+
return null;
|
|
19203
|
+
const interval = Math.max(1, Number(parts.get('INTERVAL') || 1) || 1);
|
|
19204
|
+
let byday = null;
|
|
19205
|
+
const bydayBrut = parts.get('BYDAY');
|
|
19206
|
+
if (bydayBrut) {
|
|
19207
|
+
const jours = [];
|
|
19208
|
+
for (const j of bydayBrut.split(',')) {
|
|
19209
|
+
const net = j.trim().toUpperCase();
|
|
19210
|
+
// « 2MO » (deuxième lundi) n'est pas géré : on le signale plutôt que de le lire « MO ».
|
|
19211
|
+
if (/^[+-]?\d/.test(net)) {
|
|
19212
|
+
ignore.push('BYDAY:' + net);
|
|
19213
|
+
continue;
|
|
19214
|
+
}
|
|
19215
|
+
const n = JOURS_ICAL[net];
|
|
19216
|
+
if (n !== undefined)
|
|
19217
|
+
jours.push(n);
|
|
19218
|
+
}
|
|
19219
|
+
byday = jours.length ? jours.sort((a, b) => a - b) : null;
|
|
19220
|
+
}
|
|
19221
|
+
let bymonthday = null;
|
|
19222
|
+
const bmdBrut = parts.get('BYMONTHDAY');
|
|
19223
|
+
if (bmdBrut) {
|
|
19224
|
+
const q = bmdBrut.split(',').map((x) => Number(x.trim())).filter((x) => Number.isInteger(x) && x >= 1 && x <= 31);
|
|
19225
|
+
bymonthday = q.length ? q : null;
|
|
19226
|
+
}
|
|
19227
|
+
const count = parts.has('COUNT') ? Math.max(0, Number(parts.get('COUNT')) || 0) : null;
|
|
19228
|
+
const until = parts.has('UNTIL') ? cadParseUntil(parts.get('UNTIL')) : null;
|
|
19229
|
+
return { freq, interval, byday, bymonthday, count, until, ignore };
|
|
19230
|
+
}
|
|
19231
|
+
/** `UNTIL` en forme iCalendar (`20260930T235959Z`) ou en ISO. */
|
|
19232
|
+
function cadParseUntil(v) {
|
|
19233
|
+
const m = /^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2})Z?)?$/.exec(v.trim());
|
|
19234
|
+
if (m) {
|
|
19235
|
+
const d = new Date(+m[1], +m[2] - 1, +m[3], +(m[4] ?? 23), +(m[5] ?? 59), +(m[6] ?? 59));
|
|
19236
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
19237
|
+
}
|
|
19238
|
+
const d = new Date(v);
|
|
19239
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
19240
|
+
}
|
|
19241
|
+
/** Garde-fou : une règle mal formée ne doit pas produire une boucle infinie mais un message. */
|
|
19242
|
+
const PLAFOND = 750;
|
|
19243
|
+
const JOUR_MS = 86_400_000;
|
|
19244
|
+
function memeJour(a, b) {
|
|
19245
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
19246
|
+
}
|
|
19247
|
+
function cle(d) {
|
|
19248
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
19249
|
+
}
|
|
19250
|
+
function horodatage(d, allDay) {
|
|
19251
|
+
return allDay ? cle(d) : `${cle(d)}T${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
19252
|
+
}
|
|
19253
|
+
function avance(d, jours) {
|
|
19254
|
+
const n = new Date(d);
|
|
19255
|
+
n.setDate(n.getDate() + jours);
|
|
19256
|
+
return n;
|
|
19257
|
+
}
|
|
19258
|
+
/**
|
|
19259
|
+
* Déplie les tâches sur la fenêtre `[du, au]`.
|
|
19260
|
+
*
|
|
19261
|
+
* TROIS RÈGLES, dans cet ordre :
|
|
19262
|
+
* ① une tâche sans règle donne une occurrence unique, si elle croise la fenêtre ;
|
|
19263
|
+
* ② une tâche avec règle est projetée, `exdates` retirées ;
|
|
19264
|
+
* ③ une occurrence MODIFIÉE (`masterId` + `recurrenceId`) REMPLACE celle que la règle aurait
|
|
19265
|
+
* produite à cette date — convention iCalendar. Sans ce remplacement on afficherait les
|
|
19266
|
+
* deux, et l'utilisateur verrait sa modification en double au lieu de la voir appliquée.
|
|
19267
|
+
*/
|
|
19268
|
+
function cadExpandOccurrences(taches, du, au) {
|
|
19269
|
+
const occurrences = [];
|
|
19270
|
+
const issues = [];
|
|
19271
|
+
// ③ d'abord : on répertorie les exceptions pour savoir quoi remplacer.
|
|
19272
|
+
const exceptions = new Map();
|
|
19273
|
+
for (const t of taches) {
|
|
19274
|
+
if (t.masterId && t.recurrenceId)
|
|
19275
|
+
exceptions.set(`${t.masterId}@${cle(t.recurrenceId)}`, t);
|
|
19276
|
+
}
|
|
19277
|
+
for (const t of taches) {
|
|
19278
|
+
// Une exception est rendue par la série qu'elle corrige, pas deux fois.
|
|
19279
|
+
if (t.masterId && t.recurrenceId)
|
|
19280
|
+
continue;
|
|
19281
|
+
if (!t.start) {
|
|
19282
|
+
if (t.rrule)
|
|
19283
|
+
issues.push({ taskId: t.id, code: 'sans-date' });
|
|
19284
|
+
continue;
|
|
19285
|
+
}
|
|
19286
|
+
const duree = t.end ? Math.max(0, t.end.getTime() - t.start.getTime()) : 0;
|
|
19287
|
+
if (!t.rrule) {
|
|
19288
|
+
if (croise(t.start, t.end ?? t.start, du, au)) {
|
|
19289
|
+
occurrences.push(fabrique(t, t.start, duree, false, false));
|
|
19290
|
+
}
|
|
19291
|
+
continue;
|
|
19292
|
+
}
|
|
19293
|
+
const regle = cadParseRRule(t.rrule);
|
|
19294
|
+
if (!regle) {
|
|
19295
|
+
issues.push({ taskId: t.id, code: 'rrule-illisible', detail: t.rrule });
|
|
19296
|
+
// Une règle illisible ne fait pas disparaître l'événement : il reste, seul.
|
|
19297
|
+
if (croise(t.start, t.end ?? t.start, du, au))
|
|
19298
|
+
occurrences.push(fabrique(t, t.start, duree, false, false));
|
|
19299
|
+
continue;
|
|
19300
|
+
}
|
|
19301
|
+
if (regle.ignore.length) {
|
|
19302
|
+
issues.push({ taskId: t.id, code: 'rrule-partielle', detail: regle.ignore.join(', ') });
|
|
19303
|
+
}
|
|
19304
|
+
const exclues = new Set(t.exdates.map((x) => cle(new Date(x))));
|
|
19305
|
+
let rendues = 0;
|
|
19306
|
+
let tours = 0;
|
|
19307
|
+
let curseur = new Date(t.start);
|
|
19308
|
+
while (tours++ < PLAFOND) {
|
|
19309
|
+
if (regle.count !== null && rendues >= regle.count)
|
|
19310
|
+
break;
|
|
19311
|
+
if (curseur.getTime() > au.getTime())
|
|
19312
|
+
break;
|
|
19313
|
+
if (regle.until && curseur.getTime() > regle.until.getTime())
|
|
19314
|
+
break;
|
|
19315
|
+
for (const debut of datesDuTour(curseur, regle, t.start)) {
|
|
19316
|
+
if (regle.count !== null && rendues >= regle.count)
|
|
19317
|
+
break;
|
|
19318
|
+
if (debut.getTime() < t.start.getTime())
|
|
19319
|
+
continue;
|
|
19320
|
+
if (regle.until && debut.getTime() > regle.until.getTime())
|
|
19321
|
+
break;
|
|
19322
|
+
rendues++;
|
|
19323
|
+
if (exclues.has(cle(debut)))
|
|
19324
|
+
continue;
|
|
19325
|
+
if (debut.getTime() > au.getTime())
|
|
19326
|
+
continue;
|
|
19327
|
+
const remplacement = exceptions.get(`${t.id}@${cle(debut)}`);
|
|
19328
|
+
if (remplacement) {
|
|
19329
|
+
if (remplacement.start && croise(remplacement.start, remplacement.end ?? remplacement.start, du, au)) {
|
|
19330
|
+
const d2 = remplacement.end ? remplacement.end.getTime() - remplacement.start.getTime() : duree;
|
|
19331
|
+
occurrences.push(fabrique(remplacement, remplacement.start, d2, true, true));
|
|
19332
|
+
}
|
|
19333
|
+
continue;
|
|
19334
|
+
}
|
|
19335
|
+
const fin = new Date(debut.getTime() + duree);
|
|
19336
|
+
if (croise(debut, fin, du, au))
|
|
19337
|
+
occurrences.push(fabrique(t, debut, duree, true, false));
|
|
19338
|
+
}
|
|
19339
|
+
curseur = tourSuivant(curseur, regle);
|
|
19340
|
+
}
|
|
19341
|
+
if (tours >= PLAFOND)
|
|
19342
|
+
issues.push({ taskId: t.id, code: 'plafond-atteint', detail: String(PLAFOND) });
|
|
19343
|
+
}
|
|
19344
|
+
occurrences.sort((a, b) => a.start.getTime() - b.start.getTime() || a.id.localeCompare(b.id));
|
|
19345
|
+
return { occurrences, issues };
|
|
19346
|
+
}
|
|
19347
|
+
function croise(debut, fin, du, au) {
|
|
19348
|
+
return fin.getTime() >= du.getTime() && debut.getTime() <= au.getTime();
|
|
19349
|
+
}
|
|
19350
|
+
function fabrique(t, debut, duree, recurring, overridden) {
|
|
19351
|
+
const start = new Date(debut);
|
|
19352
|
+
const end = new Date(debut.getTime() + duree);
|
|
19353
|
+
return {
|
|
19354
|
+
id: `${t.masterId ?? t.id}@${horodatage(start, t.allDay)}`,
|
|
19355
|
+
taskId: t.id,
|
|
19356
|
+
start,
|
|
19357
|
+
end,
|
|
19358
|
+
allDay: t.allDay,
|
|
19359
|
+
recurring,
|
|
19360
|
+
overridden,
|
|
19361
|
+
task: t,
|
|
19362
|
+
};
|
|
19363
|
+
}
|
|
19364
|
+
/** Les dates produites par UN tour de la règle (une semaine, un mois, un jour…). */
|
|
19365
|
+
function datesDuTour(curseur, r, ancre) {
|
|
19366
|
+
const h = ancre.getHours(), m = ancre.getMinutes();
|
|
19367
|
+
const pose = (d) => { const n = new Date(d); n.setHours(h, m, 0, 0); return n; };
|
|
19368
|
+
if (r.freq === 'WEEKLY' && r.byday) {
|
|
19369
|
+
// Le curseur est posé sur le début de semaine du tour ; on émet les jours visés.
|
|
19370
|
+
const lundi = debutSemaine(curseur);
|
|
19371
|
+
return r.byday.map((j) => pose(avance(lundi, (j + 6) % 7)));
|
|
19372
|
+
}
|
|
19373
|
+
if (r.freq === 'MONTHLY' && r.bymonthday) {
|
|
19374
|
+
return r.bymonthday
|
|
19375
|
+
.map((q) => {
|
|
19376
|
+
const d = new Date(curseur.getFullYear(), curseur.getMonth(), q);
|
|
19377
|
+
// Le 31 d'un mois de 30 jours n'existe pas : on ne le décale pas en douce, on le saute.
|
|
19378
|
+
return d.getMonth() === curseur.getMonth() ? pose(d) : null;
|
|
19379
|
+
})
|
|
19380
|
+
.filter((d) => d !== null)
|
|
19381
|
+
.sort((a, b) => a.getTime() - b.getTime());
|
|
19382
|
+
}
|
|
19383
|
+
return [pose(curseur)];
|
|
19384
|
+
}
|
|
19385
|
+
function tourSuivant(curseur, r) {
|
|
19386
|
+
const n = new Date(curseur);
|
|
19387
|
+
if (r.freq === 'DAILY')
|
|
19388
|
+
n.setDate(n.getDate() + r.interval);
|
|
19389
|
+
else if (r.freq === 'WEEKLY')
|
|
19390
|
+
n.setDate(n.getDate() + 7 * r.interval);
|
|
19391
|
+
else if (r.freq === 'MONTHLY') {
|
|
19392
|
+
// On repart du 1er : sans ça, un 31 janvier + 1 mois donne le 3 mars et la série dérive
|
|
19393
|
+
// de mois en mois. Le quantième visé est réappliqué par datesDuTour.
|
|
19394
|
+
const j = r.bymonthday ? 1 : n.getDate();
|
|
19395
|
+
n.setDate(1);
|
|
19396
|
+
n.setMonth(n.getMonth() + r.interval);
|
|
19397
|
+
if (!r.bymonthday)
|
|
19398
|
+
n.setDate(Math.min(j, joursDansMois(n.getFullYear(), n.getMonth())));
|
|
19399
|
+
}
|
|
19400
|
+
else {
|
|
19401
|
+
n.setFullYear(n.getFullYear() + r.interval);
|
|
19402
|
+
}
|
|
19403
|
+
return n;
|
|
19404
|
+
}
|
|
19405
|
+
function joursDansMois(annee, mois) {
|
|
19406
|
+
return new Date(annee, mois + 1, 0).getDate();
|
|
19407
|
+
}
|
|
19408
|
+
/** Lundi de la semaine de `d`. */
|
|
19409
|
+
function debutSemaine(d) {
|
|
19410
|
+
const n = new Date(d);
|
|
19411
|
+
n.setHours(0, 0, 0, 0);
|
|
19412
|
+
n.setDate(n.getDate() - ((n.getDay() + 6) % 7));
|
|
19413
|
+
return n;
|
|
19414
|
+
}
|
|
19415
|
+
const CAD_RECURRENCE_INTERNE = { debutSemaine, joursDansMois, memeJour, JOUR_MS };
|
|
19416
|
+
|
|
19417
|
+
/**
|
|
19418
|
+
* Mise en page d'un calendrier — logique PURE, sans DOM, donc testable seule.
|
|
19419
|
+
*
|
|
19420
|
+
* Trois calculs, et ce sont les trois endroits où un calendrier se trompe :
|
|
19421
|
+
* ① la grille du mois (combien de rangées, quels jours débordent) ;
|
|
19422
|
+
* ② les BANDEAUX multi-jours, continus à travers une rangée de semaine ;
|
|
19423
|
+
* ③ le placement côte à côte des événements horodatés d'une journée.
|
|
19424
|
+
*
|
|
19425
|
+
* BORNE DE FIN : dans cette bibliothèque, `end` est INCLUSIVE pour un événement journée
|
|
19426
|
+
* entière — « congés du 21 au 25 » couvre le 25. La RFC 5545 la veut exclusive (DTEND au 26).
|
|
19427
|
+
* On diverge SCIEMMENT : c'est ce qu'un humain saisit, c'est ce que font déjà le gantt et le
|
|
19428
|
+
* scheduler pour une tâche qui « finit le 25 », et mélanger les deux conventions en silence
|
|
19429
|
+
* est la première cause d'erreur d'un jour dans un calendrier. Un import iCalendar devra donc
|
|
19430
|
+
* retrancher un jour à DTEND — c'est le travail de l'import, pas celui de la vue.
|
|
19431
|
+
*/
|
|
19432
|
+
/** Numéro de jour absolu, insensible au fuseau et à l'heure d'été. */
|
|
19433
|
+
function cadNumeroJour(d) {
|
|
19434
|
+
return Math.round(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / 86_400_000);
|
|
19435
|
+
}
|
|
19436
|
+
/** Minuit local du jour de `d`. */
|
|
19437
|
+
function cadMinuit(d) {
|
|
19438
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
19439
|
+
}
|
|
19440
|
+
/** Début de la semaine contenant `d`. `premierJour` : 1 = lundi (défaut), 0 = dimanche. */
|
|
19441
|
+
function cadDebutSemaine(d, premierJour = 1) {
|
|
19442
|
+
const n = cadMinuit(d);
|
|
19443
|
+
const decalage = (n.getDay() - premierJour + 7) % 7;
|
|
19444
|
+
n.setDate(n.getDate() - decalage);
|
|
19445
|
+
return n;
|
|
19446
|
+
}
|
|
19447
|
+
/**
|
|
19448
|
+
* Grille du mois contenant `ancre`.
|
|
19449
|
+
*
|
|
19450
|
+
* On rend 5 OU 6 rangées, selon ce que le mois demande — jamais un nombre fixe. Forcer 6
|
|
19451
|
+
* rangées laisse une semaine entière vide neuf mois sur douze ; en forcer 5 tronque les mois
|
|
19452
|
+
* de 31 jours qui commencent un samedi. La hauteur de la vue s'adapte, pas la vérité.
|
|
19453
|
+
*/
|
|
19454
|
+
function cadMonthGrid(ancre, premierJour = 1) {
|
|
19455
|
+
const annee = ancre.getFullYear();
|
|
19456
|
+
const mois = ancre.getMonth();
|
|
19457
|
+
const premier = new Date(annee, mois, 1);
|
|
19458
|
+
const debut = cadDebutSemaine(premier, premierJour);
|
|
19459
|
+
const dernier = new Date(annee, mois + 1, 0);
|
|
19460
|
+
const finGrille = cadDebutSemaine(dernier, premierJour);
|
|
19461
|
+
const rangees = Math.round((cadNumeroJour(finGrille) - cadNumeroJour(debut)) / 7) + 1;
|
|
19462
|
+
const semaines = [];
|
|
19463
|
+
for (let r = 0; r < rangees; r++) {
|
|
19464
|
+
const semaine = [];
|
|
19465
|
+
for (let j = 0; j < 7; j++) {
|
|
19466
|
+
const d = new Date(debut);
|
|
19467
|
+
d.setDate(d.getDate() + r * 7 + j);
|
|
19468
|
+
semaine.push(d);
|
|
19469
|
+
}
|
|
19470
|
+
semaines.push(semaine);
|
|
19471
|
+
}
|
|
19472
|
+
return { debut, semaines, mois, annee };
|
|
19473
|
+
}
|
|
19474
|
+
/** Une occurrence va au bandeau si elle est journée entière OU si elle franchit un minuit. */
|
|
19475
|
+
function cadEstBandeau(o) {
|
|
19476
|
+
return o.allDay || cadNumeroJour(o.start) !== cadNumeroJour(o.end);
|
|
19477
|
+
}
|
|
19478
|
+
/**
|
|
19479
|
+
* Range les bandeaux d'une rangée de semaine en voies.
|
|
19480
|
+
*
|
|
19481
|
+
* Le tri place les plus LONGS en haut : sans lui, un événement d'un jour posé en voie 0 coupe
|
|
19482
|
+
* en deux la place d'un événement de cinq jours, qui doit alors descendre — et la rangée
|
|
19483
|
+
* devient un escalier illisible.
|
|
19484
|
+
*/
|
|
19485
|
+
function cadWeekBands(occurrences, jours) {
|
|
19486
|
+
if (!jours.length)
|
|
19487
|
+
return { bandes: [], voies: 0 };
|
|
19488
|
+
const j0 = cadNumeroJour(jours[0]);
|
|
19489
|
+
const j1 = cadNumeroJour(jours[jours.length - 1]);
|
|
19490
|
+
const candidats = occurrences
|
|
19491
|
+
.filter(cadEstBandeau)
|
|
19492
|
+
.map((o) => {
|
|
19493
|
+
const d = cadNumeroJour(o.start);
|
|
19494
|
+
const f = cadNumeroJour(o.end);
|
|
19495
|
+
return { o, d, f };
|
|
19496
|
+
})
|
|
19497
|
+
.filter(({ d, f }) => f >= j0 && d <= j1)
|
|
19498
|
+
.sort((a, b) => (b.f - b.d) - (a.f - a.d) || a.d - b.d || a.o.id.localeCompare(b.o.id));
|
|
19499
|
+
const occupe = [];
|
|
19500
|
+
const bandes = candidats.map(({ o, d, f }) => {
|
|
19501
|
+
const colDebut = Math.max(0, d - j0);
|
|
19502
|
+
const colFin = Math.min(jours.length - 1, f - j0);
|
|
19503
|
+
let voie = 0;
|
|
19504
|
+
while (occupe[voie]?.some(([a, b]) => !(b < colDebut || a > colFin)))
|
|
19505
|
+
voie++;
|
|
19506
|
+
(occupe[voie] ??= []).push([colDebut, colFin]);
|
|
19507
|
+
return { occurrence: o, colDebut, colFin, voie, continueAvant: d < j0, continueApres: f > j1 };
|
|
19508
|
+
});
|
|
19509
|
+
return { bandes, voies: occupe.length };
|
|
19510
|
+
}
|
|
19511
|
+
/**
|
|
19512
|
+
* Place les événements horodatés d'une journée, côte à côte quand ils se chevauchent.
|
|
19513
|
+
*
|
|
19514
|
+
* Le nombre de voies est celui du GROUPE de chevauchement, pas de la journée : deux réunions
|
|
19515
|
+
* qui se chevauchent le matin ne doivent pas rétrécir de moitié une réunion isolée de
|
|
19516
|
+
* l'après-midi. C'est l'erreur la plus courante des calendriers faits à la main.
|
|
19517
|
+
*
|
|
19518
|
+
* `plageDebut` / `plageFin` en minutes depuis minuit. Un événement hors plage est CONSERVÉ et
|
|
19519
|
+
* ramené au bord : le faire disparaître serait le mensonge le plus grave qu'une vue puisse
|
|
19520
|
+
* commettre.
|
|
19521
|
+
*/
|
|
19522
|
+
function cadPackDay(occurrences, plageDebut = 7 * 60, plageFin = 20 * 60) {
|
|
19523
|
+
const plage = Math.max(1, plageFin - plageDebut);
|
|
19524
|
+
const items = occurrences
|
|
19525
|
+
.filter((o) => !cadEstBandeau(o))
|
|
19526
|
+
.map((o) => {
|
|
19527
|
+
const d = o.start.getHours() * 60 + o.start.getMinutes();
|
|
19528
|
+
const f = Math.max(d + 15, o.end.getHours() * 60 + o.end.getMinutes());
|
|
19529
|
+
return { o, d, f };
|
|
19530
|
+
})
|
|
19531
|
+
.sort((a, b) => a.d - b.d || b.f - a.f);
|
|
19532
|
+
// ① voies par balayage, première voie libre.
|
|
19533
|
+
const finVoie = [];
|
|
19534
|
+
const places = items.map((it) => {
|
|
19535
|
+
let voie = finVoie.findIndex((fin) => fin <= it.d);
|
|
19536
|
+
if (voie === -1) {
|
|
19537
|
+
voie = finVoie.length;
|
|
19538
|
+
finVoie.push(it.f);
|
|
19539
|
+
}
|
|
19540
|
+
else
|
|
19541
|
+
finVoie[voie] = it.f;
|
|
19542
|
+
return { ...it, voie };
|
|
19543
|
+
});
|
|
19544
|
+
// ② groupes de chevauchement : on ne partage la largeur qu'entre voisins réels.
|
|
19545
|
+
const slots = [];
|
|
19546
|
+
let i = 0;
|
|
19547
|
+
while (i < places.length) {
|
|
19548
|
+
let j = i + 1;
|
|
19549
|
+
let finGroupe = places[i].f;
|
|
19550
|
+
while (j < places.length && places[j].d < finGroupe) {
|
|
19551
|
+
finGroupe = Math.max(finGroupe, places[j].f);
|
|
19552
|
+
j++;
|
|
19553
|
+
}
|
|
19554
|
+
const groupe = places.slice(i, j);
|
|
19555
|
+
const voies = Math.max(...groupe.map((g) => g.voie)) + 1;
|
|
19556
|
+
for (const g of groupe) {
|
|
19557
|
+
const haut = (Math.max(g.d, plageDebut) - plageDebut) / plage;
|
|
19558
|
+
const bas = (Math.min(g.f, plageFin) - plageDebut) / plage;
|
|
19559
|
+
slots.push({
|
|
19560
|
+
occurrence: g.o,
|
|
19561
|
+
haut: Math.max(0, Math.min(1, haut)),
|
|
19562
|
+
hauteur: Math.max(0.01, Math.min(1, bas) - Math.max(0, haut)),
|
|
19563
|
+
voie: g.voie,
|
|
19564
|
+
voies,
|
|
19565
|
+
});
|
|
19566
|
+
}
|
|
19567
|
+
i = j;
|
|
19568
|
+
}
|
|
19569
|
+
return slots;
|
|
19570
|
+
}
|
|
19571
|
+
|
|
19572
|
+
/**
|
|
19573
|
+
* Projection d'un geste d'édition — logique PURE, sans DOM, donc testable seule.
|
|
19574
|
+
*
|
|
19575
|
+
* Un geste ne produit jamais une écriture : il produit une PROPOSITION de bornes, que le
|
|
19576
|
+
* composant émet en intention. C'est ce fichier qui répond à « l'utilisateur a bougé de 43
|
|
19577
|
+
* pixels vers le bas et de 1,2 colonne : qu'est-ce que ça veut dire en dates ? ».
|
|
19578
|
+
*
|
|
19579
|
+
* L'AIMANTAGE EST OBLIGATOIRE, pas cosmétique. Sans lui on crée des réunions de 9 h 07 à
|
|
19580
|
+
* 10 h 23 : personne n'a voulu ça, et la donnée devient impossible à relire.
|
|
19581
|
+
*/
|
|
19582
|
+
const MIN = 60_000;
|
|
19583
|
+
function aimante(minutes, pas) {
|
|
19584
|
+
return Math.round(minutes / Math.max(1, pas)) * Math.max(1, pas);
|
|
19585
|
+
}
|
|
19586
|
+
function minutesDansJour(d) {
|
|
19587
|
+
return d.getHours() * 60 + d.getMinutes();
|
|
19588
|
+
}
|
|
19589
|
+
function poseMinutes(jour, minutes) {
|
|
19590
|
+
const n = new Date(jour.getFullYear(), jour.getMonth(), jour.getDate());
|
|
19591
|
+
n.setMinutes(minutes);
|
|
19592
|
+
return n;
|
|
19593
|
+
}
|
|
19594
|
+
function decaleJours(d, jours) {
|
|
19595
|
+
const n = new Date(d);
|
|
19596
|
+
n.setDate(n.getDate() + jours);
|
|
19597
|
+
return n;
|
|
19598
|
+
}
|
|
19599
|
+
/**
|
|
19600
|
+
* Projette un geste en nouvelles bornes.
|
|
19601
|
+
*
|
|
19602
|
+
* `dx` / `dy` sont les déplacements du pointeur en pixels depuis le début du geste.
|
|
19603
|
+
*
|
|
19604
|
+
* TROIS INVARIANTS, et chacun corrige une bêtise classique :
|
|
19605
|
+
* ① un déplacement CONSERVE la durée — sinon déplacer une réunion la rallonge en douce ;
|
|
19606
|
+
* ② un redimensionnement ne franchit JAMAIS l'autre bord : il s'arrête à un pas ;
|
|
19607
|
+
* ③ un événement journée entière ne bouge qu'en JOURS — lui donner une heure en le glissant
|
|
19608
|
+
* le ferait sortir du bandeau, ce que personne n'a demandé.
|
|
19609
|
+
*/
|
|
19610
|
+
function cadProjeterEdition(kind, base, dx, dy, g) {
|
|
19611
|
+
const jours = g.largeurJour > 0 ? Math.round(dx / g.largeurJour) : 0;
|
|
19612
|
+
const minutesBrutes = g.hauteurHeure > 0 ? (dy / g.hauteurHeure) * 60 : 0;
|
|
19613
|
+
const dMin = aimante(minutesBrutes, g.pas);
|
|
19614
|
+
// ③ journée entière : seuls les jours comptent.
|
|
19615
|
+
if (base.allDay) {
|
|
19616
|
+
if (kind === 'move')
|
|
19617
|
+
return { start: decaleJours(base.start, jours), end: decaleJours(base.end, jours) };
|
|
19618
|
+
if (kind === 'resize-start') {
|
|
19619
|
+
const s = decaleJours(base.start, jours);
|
|
19620
|
+
return { start: s > base.end ? base.end : s, end: base.end };
|
|
19621
|
+
}
|
|
19622
|
+
if (kind === 'resize-end') {
|
|
19623
|
+
const e = decaleJours(base.end, jours);
|
|
19624
|
+
return { start: base.start, end: e < base.start ? base.start : e };
|
|
19625
|
+
}
|
|
19626
|
+
return { start: base.start, end: base.end };
|
|
19627
|
+
}
|
|
19628
|
+
const duree = Math.max(g.pas, Math.round((base.end.getTime() - base.start.getTime()) / MIN));
|
|
19629
|
+
if (kind === 'move' || kind === 'create') {
|
|
19630
|
+
// ① la durée est conservée. On borne le DÉBUT à la plage affichée, jamais la durée :
|
|
19631
|
+
// rogner la fin en douce ferait raccourcir un rendez-vous qu'on voulait seulement déplacer.
|
|
19632
|
+
const vise = aimante(minutesDansJour(base.start) + dMin, g.pas);
|
|
19633
|
+
const debut = Math.max(g.plageDebut, Math.min(vise, g.plageFin - g.pas));
|
|
19634
|
+
const jour = decaleJours(base.start, jours);
|
|
19635
|
+
const start = poseMinutes(jour, debut);
|
|
19636
|
+
return { start, end: new Date(start.getTime() + duree * MIN) };
|
|
19637
|
+
}
|
|
19638
|
+
if (kind === 'resize-start') {
|
|
19639
|
+
const finMin = minutesDansJour(base.end);
|
|
19640
|
+
const vise = aimante(minutesDansJour(base.start) + dMin, g.pas);
|
|
19641
|
+
// ② on s'arrête un pas avant la fin, jamais au-delà.
|
|
19642
|
+
const debut = Math.max(g.plageDebut, Math.min(vise, finMin - g.pas));
|
|
19643
|
+
return { start: poseMinutes(base.start, debut), end: base.end };
|
|
19644
|
+
}
|
|
19645
|
+
const debutMin = minutesDansJour(base.start);
|
|
19646
|
+
const vise = aimante(minutesDansJour(base.end) + dMin, g.pas);
|
|
19647
|
+
const fin = Math.min(g.plageFin, Math.max(vise, debutMin + g.pas));
|
|
19648
|
+
return { start: base.start, end: poseMinutes(base.end, fin) };
|
|
19649
|
+
}
|
|
19650
|
+
/**
|
|
19651
|
+
* Bornes d'une création par glisser sur une plage vide.
|
|
19652
|
+
*
|
|
19653
|
+
* `y0` / `y1` sont les positions verticales dans la colonne, en pixels. On aimante les DEUX
|
|
19654
|
+
* bords, et on garantit au moins un pas : un clic sec ne doit pas produire un événement de
|
|
19655
|
+
* durée nulle, invisible et impossible à rattraper.
|
|
19656
|
+
*/
|
|
19657
|
+
function cadProjeterCreation(jour, y0, y1, g) {
|
|
19658
|
+
const enMinutes = (y) => g.plageDebut + (y / Math.max(1, g.hauteurHeure)) * 60;
|
|
19659
|
+
const a = aimante(enMinutes(Math.min(y0, y1)), g.pas);
|
|
19660
|
+
let b = aimante(enMinutes(Math.max(y0, y1)), g.pas);
|
|
19661
|
+
if (b - a < g.pas)
|
|
19662
|
+
b = a + g.pas;
|
|
19663
|
+
const debut = Math.max(g.plageDebut, Math.min(a, g.plageFin - g.pas));
|
|
19664
|
+
const fin = Math.min(g.plageFin, Math.max(b, debut + g.pas));
|
|
19665
|
+
return { start: poseMinutes(jour, debut), end: poseMinutes(jour, fin) };
|
|
19666
|
+
}
|
|
19667
|
+
/**
|
|
19668
|
+
* Projection d'un geste dans une GRILLE DE MOIS — et ce n'est pas la même que dans une grille
|
|
19669
|
+
* horaire, contrairement à ce que j'avais écrit.
|
|
19670
|
+
*
|
|
19671
|
+
* Dans un mois, l'axe vertical n'est PAS le temps de la journée : c'est la semaine. Descendre
|
|
19672
|
+
* d'une rangée avance de SEPT JOURS, et l'heure ne bouge pas. Utiliser la projection horaire
|
|
19673
|
+
* ici donnait « +3 h » au lieu de « +7 jours » (mesuré au navigateur le 22/08/2026 : glisser
|
|
19674
|
+
* d'une rangée déplaçait un rendez-vous de 10 h 30 à 13 h 30 sans changer de date).
|
|
19675
|
+
*
|
|
19676
|
+
* Une seule règle à retenir : dans le mois, un geste ne déplace que des JOURS.
|
|
19677
|
+
*/
|
|
19678
|
+
function cadProjeterMois(kind, base, dx, dy, g) {
|
|
19679
|
+
const colonnes = g.largeurJour > 0 ? Math.round(dx / g.largeurJour) : 0;
|
|
19680
|
+
const rangees = g.hauteurRangee > 0 ? Math.round(dy / g.hauteurRangee) : 0;
|
|
19681
|
+
const jours = colonnes + 7 * rangees;
|
|
19682
|
+
if (kind === 'resize-start') {
|
|
19683
|
+
const s = decaleJours(base.start, jours);
|
|
19684
|
+
return { start: s > base.end ? base.end : s, end: base.end };
|
|
19685
|
+
}
|
|
19686
|
+
if (kind === 'resize-end') {
|
|
19687
|
+
const e = decaleJours(base.end, jours);
|
|
19688
|
+
return { start: base.start, end: e < base.start ? base.start : e };
|
|
19689
|
+
}
|
|
19690
|
+
// L'heure est conservée telle quelle : decaleJours ne touche qu'au quantième.
|
|
19691
|
+
return { start: decaleJours(base.start, jours), end: decaleJours(base.end, jours) };
|
|
19692
|
+
}
|
|
19693
|
+
/** Déplacement au CLAVIER, exprimé dans la même unité qu'un geste : jours et pas. */
|
|
19694
|
+
function cadProjeterClavier(kind, base, jours, pas, g) {
|
|
19695
|
+
// On repasse par la MÊME projection que le pointeur : une seule logique de placement à
|
|
19696
|
+
// éprouver, et le clavier n'est pas une reprise ultérieure qui dérive.
|
|
19697
|
+
return cadProjeterEdition(kind, base, jours * g.largeurJour, (pas * g.pas / 60) * g.hauteurHeure, g);
|
|
19698
|
+
}
|
|
19699
|
+
|
|
19153
19700
|
/** Styles de cad-taskboard (présentations cartes et liste). Tokens --cad-* uniquement. */
|
|
19154
19701
|
const CAD_TASKBOARD_STYLES = `
|
|
19155
19702
|
:host { display: flex; flex-direction: column; min-height: 0; height: 100%; font: var(--cad-font-size)/1.35 var(--cad-font); color: var(--cad-fg); background: var(--cad-bg); }
|
|
@@ -19673,9 +20220,1016 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
19673
20220
|
`, styles: [":host{display:flex;flex-direction:column;min-height:0;height:100%;font:var(--cad-font-size)/1.35 var(--cad-font);color:var(--cad-fg);background:var(--cad-bg)}.tb__board{flex:1;min-height:0;display:flex;gap:12px;padding:12px;overflow-x:auto;overflow-y:hidden;scrollbar-gutter:stable}.tb__col{flex:0 0 var(--cad-taskboard-col, 258px);display:flex;flex-direction:column;min-height:0;background:var(--cad-bg-subtle);border:1px solid var(--cad-border);border-radius:var(--cad-radius)}.tb__head{display:flex;align-items:center;gap:8px;padding:9px 11px;border-bottom:1px solid var(--cad-border)}.tb__dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--_c, var(--cad-border-strong))}.tb__name{font-weight:500;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tb__count{font-family:var(--cad-font-mono);font-size:var(--cad-font-size-sm);padding:2px 7px;border-radius:var(--cad-radius-pill);background:var(--cad-elevated);color:var(--cad-fg-muted);font-variant-numeric:tabular-nums;white-space:nowrap}.tb__count.is-full{background:var(--cad-warning-soft);color:var(--cad-warning)}.tb__count.is-over{background:var(--cad-danger-soft);color:var(--cad-danger);font-weight:500}.tb__list{flex:1;min-height:0;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:8px;scrollbar-gutter:stable}.tb__list.cdk-drop-list-dragging{background:var(--cad-accent-soft)}.tb__card{background:var(--cad-surface);border:1px solid var(--cad-border);border-radius:var(--cad-radius-sm);padding:9px 10px;box-shadow:var(--cad-shadow-sm);display:flex;flex-direction:column;gap:7px;cursor:grab;-webkit-user-select:none;user-select:none;transition:border-color var(--cad-dur) var(--cad-ease),box-shadow var(--cad-dur) var(--cad-ease)}.tb__card:hover{border-color:var(--cad-border-strong);box-shadow:var(--cad-shadow-md)}.tb__card:focus-visible{outline:2px solid var(--cad-ring);outline-offset:1px}.tb__card.is-orphan{border-left:2px dashed var(--cad-warning)}:host(.is-static) .tb__card,:host(.is-static) .tb__row{cursor:default}.tb__title{font-weight:500;line-height:1.32}.tb__meta{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.tb__tag{font-family:var(--cad-font-mono);font-size:10px;padding:2px 6px;border-radius:var(--cad-radius-sm);background:var(--cad-elevated);color:var(--cad-fg-secondary)}.tb__due{font-family:var(--cad-font-mono);font-size:var(--cad-font-size-sm);color:var(--cad-fg-muted);font-variant-numeric:tabular-nums}.tb__due.is-late{color:var(--cad-danger);font-weight:500}.tb__sp{flex:1 1 auto}.tb__prog{height:3px;border-radius:var(--cad-radius-pill);background:var(--cad-elevated);overflow:hidden}.tb__prog i{display:block;height:100%;border-radius:var(--cad-radius-pill);background:var(--cad-accent)}.tb__prog.is-done i{background:var(--cad-success)}.tb__empty{padding:14px 10px;text-align:center;color:var(--cad-fg-muted);font-size:var(--cad-font-size-sm);border:1px dashed var(--cad-border-strong);border-radius:var(--cad-radius-sm)}.tb__rows{flex:1;min-height:0;overflow-y:auto;padding-bottom:12px;scrollbar-gutter:stable}.tb__grp{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;background:var(--cad-bg-subtle);border-top:1px solid var(--cad-border);border-bottom:1px solid var(--cad-border);position:sticky;top:0;z-index:1;cursor:pointer;text-align:left;font:inherit;color:inherit}.tb__grp:hover{background:var(--cad-hover)}.tb__grp:focus-visible{outline:2px solid var(--cad-ring);outline-offset:-2px}.tb__grp cad-icon{color:var(--cad-fg-muted);transition:transform var(--cad-dur) var(--cad-ease)}.tb__grp.is-collapsed cad-icon{transform:rotate(-90deg)}.tb__grp b{font-weight:500}.tb__group-list{display:block}.tb__row{display:grid;grid-template-columns:1fr 92px 88px 26px;align-items:center;gap:12px;padding:8px 14px;border-bottom:1px solid var(--cad-border);cursor:grab;-webkit-user-select:none;user-select:none;background:var(--cad-bg)}.tb__row:hover{background:var(--cad-hover)}.tb__row:focus-visible{outline:2px solid var(--cad-ring);outline-offset:-2px}.tb__row.is-orphan{box-shadow:inset 2px 0 0 var(--cad-warning)}.tb__main{min-width:0;display:flex;flex-direction:column;gap:4px}.tb__main .tb__title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tb__pct{display:flex;align-items:center;gap:7px}.tb__pct .tb__prog{flex:1}.tb__pct span{font-family:var(--cad-font-mono);font-size:10px;color:var(--cad-fg-muted);font-variant-numeric:tabular-nums}:host(.is-compact) .tb__row{grid-template-columns:1fr 26px}:host(.is-compact) .tb__cell-due,:host(.is-compact) .tb__pct{display:none}:host(.is-compact) .tb__mob{display:inline-flex}.tb__mob{display:none;align-items:center;gap:7px}.cdk-drag-preview{box-shadow:var(--cad-shadow-lg);border-radius:var(--cad-radius-sm);background:var(--cad-overlay);--cad-hover: var(--cad-overlay-hover);border:1px solid var(--cad-border)}.cdk-drag-placeholder{opacity:.34}.cdk-drag-animating{transition:transform .18s var(--cad-ease)}.tb__list.cdk-drop-list-dragging .tb__card:not(.cdk-drag-placeholder),.tb__group-list.cdk-drop-list-dragging .tb__row:not(.cdk-drag-placeholder){transition:transform .18s var(--cad-ease)}.tb__live{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.cdk-drag-animating,.tb__list.cdk-drop-list-dragging .tb__card,.tb__grp cad-icon{transition:none}}\n"] }]
|
|
19674
20221
|
}], propDecorators: { plan: [{ type: i0.Input, args: [{ isSignal: true, alias: "plan", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], dragDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragDisabled", required: false }] }], collapsedKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedKeys", required: false }] }, { type: i0.Output, args: ["collapsedKeysChange"] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], taskMove: [{ type: i0.Output, args: ["taskMove"] }], taskAssign: [{ type: i0.Output, args: ["taskAssign"] }], taskOpen: [{ type: i0.Output, args: ["taskOpen"] }], wipExceeded: [{ type: i0.Output, args: ["wipExceeded"] }], cardTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadTaskCardDirective), { isSignal: true }] }], rowTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CadTaskRowDirective), { isSignal: true }] }] } });
|
|
19675
20222
|
|
|
20223
|
+
/**
|
|
20224
|
+
* Styles de cad-calendar. Tokens --cad-* uniquement.
|
|
20225
|
+
*
|
|
20226
|
+
* ATTENTION : ce fichier n'est PAS couvert par tools/check-styles.mjs, qui ne scanne que les
|
|
20227
|
+
* blocs « styles: [ » en ligne. Aucun accent grave dans les commentaires ci-dessous : un seul
|
|
20228
|
+
* refermerait le litteral gabarit et Angular echouerait sur « Failed to resolve styles ».
|
|
20229
|
+
*/
|
|
20230
|
+
const CAD_CALENDAR_STYLES = `
|
|
20231
|
+
:host { display: flex; flex-direction: column; min-height: 0; height: 100%;
|
|
20232
|
+
font: var(--cad-font-size)/1.4 var(--cad-font); color: var(--cad-fg); background: var(--cad-bg); }
|
|
20233
|
+
|
|
20234
|
+
.cal__corps { flex: 1; min-height: 0; display: flex; }
|
|
20235
|
+
.cal__vue { flex: 1; min-width: 0; min-height: 0; overflow: auto; }
|
|
20236
|
+
|
|
20237
|
+
/* ---------------- MOIS ---------------- */
|
|
20238
|
+
.mois { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
20239
|
+
grid-template-rows: auto; grid-auto-rows: minmax(84px, 1fr); min-height: 100%; }
|
|
20240
|
+
/* grid-template-rows: auto pour la premiere rangee : sans elle, grid-auto-rows s'applique
|
|
20241
|
+
aussi aux en-tetes de jour, qui heritent de la hauteur des cases et laissent une bande vide. */
|
|
20242
|
+
.mois__jsem { padding: 6px 8px; text-align: right; border-bottom: 1px solid var(--cad-border-strong);
|
|
20243
|
+
background: var(--cad-bg-subtle); position: sticky; top: 0; z-index: 4;
|
|
20244
|
+
font: 600 10.5px var(--cad-font-mono); letter-spacing: .06em; text-transform: uppercase;
|
|
20245
|
+
color: var(--cad-fg-muted); }
|
|
20246
|
+
|
|
20247
|
+
/* La rangee de semaine est un CONTENEUR relatif : les bandeaux multi-jours sont un calque
|
|
20248
|
+
par-dessus les cases, seule facon de les rendre continus d'un jour a l'autre. */
|
|
20249
|
+
.mois__sem { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
20250
|
+
position: relative; border-bottom: 1px solid var(--cad-border); min-height: 84px; }
|
|
20251
|
+
.mois__jour { border-right: 1px solid var(--cad-border); padding: 3px; min-width: 0;
|
|
20252
|
+
display: flex; flex-direction: column; gap: 2px; }
|
|
20253
|
+
.mois__jour:last-child { border-right: 0; }
|
|
20254
|
+
.mois__jour.is-hors { background: var(--cad-bg-subtle); }
|
|
20255
|
+
.mois__jour.is-ferme { background: var(--cad-bg-subtle); }
|
|
20256
|
+
.mois__n { font: 11px var(--cad-font-mono); color: var(--cad-fg-secondary); text-align: right;
|
|
20257
|
+
padding: 1px 3px; font-variant-numeric: tabular-nums; flex: none; }
|
|
20258
|
+
.mois__jour.is-auj .mois__n { background: var(--cad-accent); color: var(--cad-fg-on-accent);
|
|
20259
|
+
border-radius: var(--cad-radius-sm); font-weight: 600; }
|
|
20260
|
+
.mois__reserve { flex: none; }
|
|
20261
|
+
|
|
20262
|
+
.calque { position: absolute; left: 0; right: 0; pointer-events: none; z-index: 2; }
|
|
20263
|
+
.bandeau { position: absolute; height: 17px; border-radius: var(--cad-radius-sm); padding: 0 7px;
|
|
20264
|
+
font-size: 10.5px; line-height: 17px; white-space: nowrap; overflow: hidden;
|
|
20265
|
+
text-overflow: ellipsis; pointer-events: auto; cursor: pointer;
|
|
20266
|
+
background: color-mix(in srgb, var(--_c) 22%, transparent); color: var(--_c);
|
|
20267
|
+
border: 1px solid color-mix(in srgb, var(--_c) 42%, transparent); }
|
|
20268
|
+
.bandeau.is-avant { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left-style: dashed; }
|
|
20269
|
+
.bandeau.is-apres { border-top-right-radius: 0; border-bottom-right-radius: 0; border-right-style: dashed; }
|
|
20270
|
+
|
|
20271
|
+
.puce { display: flex; align-items: center; gap: 5px; font-size: 10.5px; padding: 1px 5px;
|
|
20272
|
+
border-radius: var(--cad-radius-sm); white-space: nowrap; overflow: hidden;
|
|
20273
|
+
text-overflow: ellipsis; cursor: pointer; border-left: 2px solid var(--_c);
|
|
20274
|
+
background: color-mix(in srgb, var(--_c) 13%, transparent); color: var(--_c); }
|
|
20275
|
+
.puce:hover { background: color-mix(in srgb, var(--_c) 22%, transparent); }
|
|
20276
|
+
.puce__h { font: 9.5px var(--cad-font-mono); opacity: .85; flex: none; font-variant-numeric: tabular-nums; }
|
|
20277
|
+
.puce__t { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
20278
|
+
.plus { all: unset; box-sizing: border-box; font-size: 10px; color: var(--cad-fg-muted);
|
|
20279
|
+
padding: 1px 5px; cursor: pointer; border-radius: var(--cad-radius-sm); }
|
|
20280
|
+
.plus:hover { color: var(--cad-accent); background: var(--cad-hover); }
|
|
20281
|
+
|
|
20282
|
+
/* ---------------- SEMAINE / JOUR ---------------- */
|
|
20283
|
+
.grille { min-width: 0; display: flex; flex-direction: column; height: 100%; }
|
|
20284
|
+
.grille__hd { display: grid; position: sticky; top: 0; z-index: 5; background: var(--cad-bg-subtle);
|
|
20285
|
+
border-bottom: 1px solid var(--cad-border-strong); flex: none; }
|
|
20286
|
+
.grille__hd > div { padding: 5px 6px; text-align: center; border-right: 1px solid var(--cad-border); min-width: 0; }
|
|
20287
|
+
.grille__hd > div:last-child { border-right: 0; }
|
|
20288
|
+
.grille__d { font: 10px var(--cad-font-mono); color: var(--cad-fg-muted);
|
|
20289
|
+
text-transform: uppercase; letter-spacing: .06em; }
|
|
20290
|
+
.grille__n { font-size: 16px; font-weight: 600; letter-spacing: -.02em; }
|
|
20291
|
+
.is-auj .grille__n { color: var(--cad-accent); }
|
|
20292
|
+
|
|
20293
|
+
.bande { display: grid; border-bottom: 1px solid var(--cad-border-strong);
|
|
20294
|
+
background: var(--cad-bg-subtle); flex: none; }
|
|
20295
|
+
.bande__lbl { font: 9px var(--cad-font-mono); color: var(--cad-fg-muted); padding: 4px 6px;
|
|
20296
|
+
text-align: right; border-right: 1px solid var(--cad-border); }
|
|
20297
|
+
.bande__zone { grid-column: 2 / -1; position: relative; padding: 2px 0; }
|
|
20298
|
+
|
|
20299
|
+
.heures { display: grid; position: relative; flex: 1; min-height: 0; overflow: auto; }
|
|
20300
|
+
.heures__lbl { border-right: 1px solid var(--cad-border); }
|
|
20301
|
+
.heures__lbl > div { font: 9.5px var(--cad-font-mono); color: var(--cad-fg-muted);
|
|
20302
|
+
text-align: right; padding-right: 6px; transform: translateY(-6px); }
|
|
20303
|
+
.heures__col { border-right: 1px solid var(--cad-border); position: relative; min-width: 0; }
|
|
20304
|
+
.heures__col:last-child { border-right: 0; }
|
|
20305
|
+
.heures__col.is-ferme { background: var(--cad-bg-subtle); }
|
|
20306
|
+
.trait { position: absolute; left: 0; right: 0; border-top: 1px solid var(--cad-border); pointer-events: none; }
|
|
20307
|
+
.trait.is-demi { border-top-style: dotted; opacity: .55; }
|
|
20308
|
+
|
|
20309
|
+
.evt { position: absolute; border-radius: var(--cad-radius-sm); padding: 1px 5px; overflow: hidden;
|
|
20310
|
+
cursor: pointer; border-left: 3px solid var(--_c); font-size: 10.5px; line-height: 1.25;
|
|
20311
|
+
background: color-mix(in srgb, var(--_c) 15%, transparent); color: var(--_c); }
|
|
20312
|
+
.evt:hover { background: color-mix(in srgb, var(--_c) 26%, transparent); }
|
|
20313
|
+
.evt__t { display: block; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
20314
|
+
.evt__h { font: 9px var(--cad-font-mono); opacity: .85; }
|
|
20315
|
+
.maintenant { position: absolute; left: 0; right: 0; height: 2px; background: var(--cad-danger); z-index: 4; pointer-events: none; }
|
|
20316
|
+
.maintenant::before { content: ''; position: absolute; left: -3px; top: -3px; width: 8px; height: 8px;
|
|
20317
|
+
border-radius: 50%; background: var(--cad-danger); }
|
|
20318
|
+
|
|
20319
|
+
/* ---------------- AGENDA ---------------- */
|
|
20320
|
+
.agenda { display: flex; flex-direction: column; }
|
|
20321
|
+
.ag__jour { display: grid; grid-template-columns: 72px minmax(0, 1fr); border-bottom: 1px solid var(--cad-border); }
|
|
20322
|
+
.ag__d { padding: 9px 11px; border-right: 1px solid var(--cad-border); background: var(--cad-bg-subtle); }
|
|
20323
|
+
.ag__d b { display: block; font-size: 18px; font-weight: 600; letter-spacing: -.02em; }
|
|
20324
|
+
.ag__d span { display: block; font: 9.5px var(--cad-font-mono); color: var(--cad-fg-muted); text-transform: uppercase; }
|
|
20325
|
+
.ag__d.is-auj b { color: var(--cad-accent); }
|
|
20326
|
+
.ag__l { padding: 5px 0; min-width: 0; }
|
|
20327
|
+
.ag__e { all: unset; box-sizing: border-box; display: flex; align-items: center; gap: 9px;
|
|
20328
|
+
padding: 6px 11px; cursor: pointer; width: 100%; min-height: 34px; }
|
|
20329
|
+
.ag__e:hover { background: var(--cad-hover); }
|
|
20330
|
+
.ag__e:focus-visible { outline: 2px solid var(--cad-accent); outline-offset: -2px; }
|
|
20331
|
+
.ag__p { width: 8px; height: 8px; border-radius: 50%; background: var(--_c); flex: none; }
|
|
20332
|
+
.ag__h { font: 11px var(--cad-font-mono); color: var(--cad-fg-secondary); flex: none;
|
|
20333
|
+
width: 92px; font-variant-numeric: tabular-nums; }
|
|
20334
|
+
.ag__t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
20335
|
+
.ag__c { margin-left: auto; font-size: 10.5px; color: var(--cad-fg-muted); flex: none; }
|
|
20336
|
+
|
|
20337
|
+
/* ---------------- ANNEE ---------------- */
|
|
20338
|
+
.annee { display: grid; grid-template-columns: repeat(auto-fill, minmax(178px, 1fr)); gap: 14px; padding: 14px; }
|
|
20339
|
+
.mm { border: 1px solid var(--cad-border); border-radius: var(--cad-radius); padding: 8px; background: var(--cad-surface); }
|
|
20340
|
+
.mm__t { font-size: 12px; font-weight: 600; margin-bottom: 5px; text-transform: capitalize; }
|
|
20341
|
+
.mm__g { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
|
|
20342
|
+
.mm__g > span, .mm__g > button { all: unset; box-sizing: border-box; text-align: center;
|
|
20343
|
+
font: 9.5px var(--cad-font-mono); padding: 2px 0; border-radius: 3px;
|
|
20344
|
+
color: var(--cad-fg-secondary); font-variant-numeric: tabular-nums; }
|
|
20345
|
+
.mm__g > span { color: var(--cad-fg-muted); font-weight: 500; }
|
|
20346
|
+
.mm__g > button { cursor: pointer; position: relative; }
|
|
20347
|
+
.mm__g > button:hover { background: var(--cad-hover); }
|
|
20348
|
+
.mm__g > button.is-hors { opacity: .35; }
|
|
20349
|
+
.mm__g > button.is-auj { background: var(--cad-accent); color: var(--cad-fg-on-accent); font-weight: 600; }
|
|
20350
|
+
.mm__pt { position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
|
|
20351
|
+
width: 3px; height: 3px; border-radius: 50%; background: var(--cad-accent); }
|
|
20352
|
+
.mm__g > button.is-auj .mm__pt { background: var(--cad-fg-on-accent); }
|
|
20353
|
+
|
|
20354
|
+
/* ---------------- EDITION ---------------- */
|
|
20355
|
+
/* Les poignees n'existent que si l'edition est active ET l'evenement manipulable : une
|
|
20356
|
+
poignee qui ne repond pas est pire que pas de poignee. */
|
|
20357
|
+
.evt.is-editable, .bandeau.is-editable, .puce.is-editable { cursor: grab; }
|
|
20358
|
+
.evt.is-editable:active, .bandeau.is-editable:active, .puce.is-editable:active { cursor: grabbing; }
|
|
20359
|
+
.poignee { position: absolute; z-index: 2; }
|
|
20360
|
+
.evt .poignee { left: 0; right: 0; height: 6px; cursor: ns-resize; }
|
|
20361
|
+
.evt .poignee.is-haut { top: 0; }
|
|
20362
|
+
.evt .poignee.is-bas { bottom: 0; }
|
|
20363
|
+
.bandeau .poignee { top: 0; bottom: 0; width: 6px; cursor: ew-resize; }
|
|
20364
|
+
.bandeau .poignee.is-haut { left: 0; }
|
|
20365
|
+
.bandeau .poignee.is-bas { right: 0; }
|
|
20366
|
+
.poignee::after { content: ''; position: absolute; inset: 0; }
|
|
20367
|
+
|
|
20368
|
+
/* Une occurrence de SERIE n'est pas manipulable tant que l'edition de recurrence n'existe
|
|
20369
|
+
pas : bouger une occurrence sans savoir si l'on modifie l'occurrence, la suite ou toute la
|
|
20370
|
+
serie perdrait la modification en silence. On le MONTRE au lieu de laisser essayer. */
|
|
20371
|
+
.evt.is-verrouille, .bandeau.is-verrouille, .puce.is-verrouille { cursor: not-allowed; position: relative; }
|
|
20372
|
+
.evt.is-verrouille::after, .bandeau.is-verrouille::after, .puce.is-verrouille::after {
|
|
20373
|
+
content: ''; position: absolute; inset: 0; pointer-events: none;
|
|
20374
|
+
background: repeating-linear-gradient(-45deg, currentColor 0 1px, transparent 1px 5px); opacity: .18; }
|
|
20375
|
+
|
|
20376
|
+
/* Fantome du geste en cours : il montre ou l'on va tomber, apres aimantage. */
|
|
20377
|
+
.fantome { position: absolute; z-index: 6; pointer-events: none; border-radius: var(--cad-radius-sm);
|
|
20378
|
+
background: var(--cad-accent-soft); border: 1px dashed var(--cad-accent);
|
|
20379
|
+
color: var(--cad-accent); font: 10.5px var(--cad-font-mono); padding: 1px 5px;
|
|
20380
|
+
overflow: hidden; white-space: nowrap; }
|
|
20381
|
+
|
|
20382
|
+
/* ---------------- vide ---------------- */
|
|
20383
|
+
.vide { padding: 28px 16px; color: var(--cad-fg-muted); text-align: center; }
|
|
20384
|
+
|
|
20385
|
+
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
|
|
20386
|
+
`;
|
|
20387
|
+
|
|
20388
|
+
/**
|
|
20389
|
+
* `<cad-calendar [plan]="plan" [sources]="agendas" view="auto"/>`
|
|
20390
|
+
*
|
|
20391
|
+
* LA GRILLE EST LE TEMPS — c'est ce qui le distingue de `cad-scheduler`, dont les lignes sont
|
|
20392
|
+
* des ressources. On répond ici à « qu'est-ce qui se passe le 17 ? », là-bas à « qui est libre
|
|
20393
|
+
* jeudi ? ». Deux questions, deux composants, un seul document JSON.
|
|
20394
|
+
*
|
|
20395
|
+
* L'AGENDA D'UNE TÂCHE EST SON `bucket`. Pas un champ de plus : le taskboard rend les buckets
|
|
20396
|
+
* en colonnes, le calendrier les rend en agendas superposés. C'est le pari « une donnée,
|
|
20397
|
+
* plusieurs vues » qui paie, et ça évite d'inventer un `calendarId` que les autres vues
|
|
20398
|
+
* ignoreraient.
|
|
20399
|
+
*
|
|
20400
|
+
* LA RÉCURRENCE EST EN LECTURE SEULE (voir `planning.recurrence.ts`). On déplie une règle pour
|
|
20401
|
+
* l'afficher ; on n'édite pas de série. Une récurrence à moitié faite perd des modifications
|
|
20402
|
+
* sans le dire — déplier n'écrit rien, donc ne peut rien perdre.
|
|
20403
|
+
*
|
|
20404
|
+
* LE COMPOSANT N'ÉCRIT JAMAIS : il émet `eventClick`, `dateClick`, `rangeChange`. Masquer un
|
|
20405
|
+
* agenda est en revanche un état de PRÉSENTATION, donc un `model()` qu'il porte lui-même.
|
|
20406
|
+
*/
|
|
20407
|
+
class CadCalendarComponent {
|
|
20408
|
+
Math = Math;
|
|
20409
|
+
bp = inject(CadBreakpointService);
|
|
20410
|
+
hote = inject((ElementRef));
|
|
20411
|
+
/**
|
|
20412
|
+
* Une colonne de jour, pour mesurer la largeur d'un pas horizontal.
|
|
20413
|
+
*
|
|
20414
|
+
* On MESURE au lieu de calculer : les colonnes sont en `minmax(0, 1fr)`, donc leur largeur
|
|
20415
|
+
* dépend du conteneur, de la barre de défilement et du zoom. Un calcul serait faux dès que
|
|
20416
|
+
* l'un des trois change, et le glisser dériverait d'un jour sans que rien ne le signale.
|
|
20417
|
+
*/
|
|
20418
|
+
largeurColonne() {
|
|
20419
|
+
const el = this.hote.nativeElement;
|
|
20420
|
+
return el.querySelector('.heures__col') ?? el.querySelector('.mois__jour');
|
|
20421
|
+
}
|
|
20422
|
+
plan = input(null, ...(ngDevMode ? [{ debugName: "plan" }] : /* istanbul ignore next */ []));
|
|
20423
|
+
/** Agendas superposables. Un `bucket` sans source déclarée prend l'accent et son propre nom. */
|
|
20424
|
+
sources = input([], ...(ngDevMode ? [{ debugName: "sources" }] : /* istanbul ignore next */ []));
|
|
20425
|
+
/** Agendas décochés. État de PRÉSENTATION, donc porté ici et lié en deux sens. */
|
|
20426
|
+
hidden = model(new Set(), ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
20427
|
+
view = model('auto', ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
|
|
20428
|
+
/** Date d'ancrage : le mois, la semaine ou le jour affiché. */
|
|
20429
|
+
date = model(new Date(), ...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
|
|
20430
|
+
/** 1 = lundi (défaut), 0 = dimanche. Touche le mois, la semaine ET le mini-calendrier. */
|
|
20431
|
+
firstDay = input(1, ...(ngDevMode ? [{ debugName: "firstDay" }] : /* istanbul ignore next */ []));
|
|
20432
|
+
/** Plage horaire affichée, en heures. Réglable : 7 → 20 par défaut. */
|
|
20433
|
+
dayStart = input(7, ...(ngDevMode ? [{ debugName: "dayStart" }] : /* istanbul ignore next */ []));
|
|
20434
|
+
dayEnd = input(20, ...(ngDevMode ? [{ debugName: "dayEnd" }] : /* istanbul ignore next */ []));
|
|
20435
|
+
hourHeight = input(34, ...(ngDevMode ? [{ debugName: "hourHeight" }] : /* istanbul ignore next */ []));
|
|
20436
|
+
/** Événements montrés dans une case de mois avant le « +N ». */
|
|
20437
|
+
maxPerDay = input(3, ...(ngDevMode ? [{ debugName: "maxPerDay" }] : /* istanbul ignore next */ []));
|
|
20438
|
+
workCalendar = input(null, ...(ngDevMode ? [{ debugName: "workCalendar" }] : /* istanbul ignore next */ []));
|
|
20439
|
+
today = input(null, ...(ngDevMode ? [{ debugName: "today" }] : /* istanbul ignore next */ []));
|
|
20440
|
+
locale = input('fr-FR', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
20441
|
+
emptyText = input('Aucun événement sur la période', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
20442
|
+
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
20443
|
+
showNow = input(true, { ...(ngDevMode ? { debugName: "showNow" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
20444
|
+
eventClick = output();
|
|
20445
|
+
dateClick = output();
|
|
20446
|
+
/** La fenêtre affichée a changé : l'hôte peut aller chercher les données qui manquent. */
|
|
20447
|
+
rangeChange = output();
|
|
20448
|
+
/** Gestes d'édition. `false` = vue en lecture, sans poignée ni curseur trompeur. */
|
|
20449
|
+
editable = input(false, { ...(ngDevMode ? { debugName: "editable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
20450
|
+
/** Aimantage, en minutes. Sans lui on crée des réunions de 9 h 07 que personne n'a voulues. */
|
|
20451
|
+
snap = input(15, ...(ngDevMode ? [{ debugName: "snap" }] : /* istanbul ignore next */ []));
|
|
20452
|
+
eventMove = output();
|
|
20453
|
+
eventResize = output();
|
|
20454
|
+
eventCreate = output();
|
|
20455
|
+
/** `auto` : agenda sous le seuil. Une grille de mois à 55 px par jour ne montre plus rien. */
|
|
20456
|
+
vueEffective = computed(() => {
|
|
20457
|
+
const v = this.view();
|
|
20458
|
+
if (v !== 'auto')
|
|
20459
|
+
return v;
|
|
20460
|
+
return this.bp.isMobile() ? 'agenda' : 'month';
|
|
20461
|
+
}, ...(ngDevMode ? [{ debugName: "vueEffective" }] : /* istanbul ignore next */ []));
|
|
20462
|
+
normalise = computed(() => cadNormalizePlan(this.plan()), ...(ngDevMode ? [{ debugName: "normalise" }] : /* istanbul ignore next */ []));
|
|
20463
|
+
ajd = computed(() => cadMinuit(this.today() ?? new Date()), ...(ngDevMode ? [{ debugName: "ajd" }] : /* istanbul ignore next */ []));
|
|
20464
|
+
hauteurHeure = computed(() => this.hourHeight(), ...(ngDevMode ? [{ debugName: "hauteurHeure" }] : /* istanbul ignore next */ []));
|
|
20465
|
+
hauteurTotale = computed(() => (this.dayEnd() - this.dayStart()) * this.hourHeight(), ...(ngDevMode ? [{ debugName: "hauteurTotale" }] : /* istanbul ignore next */ []));
|
|
20466
|
+
heuresAffichees = computed(() => {
|
|
20467
|
+
const out = [];
|
|
20468
|
+
for (let h = this.dayStart(); h <= this.dayEnd(); h++)
|
|
20469
|
+
out.push(h);
|
|
20470
|
+
return out;
|
|
20471
|
+
}, ...(ngDevMode ? [{ debugName: "heuresAffichees" }] : /* istanbul ignore next */ []));
|
|
20472
|
+
/** Fenêtre couverte par la vue courante. Elle borne le dépliage : on ne projette pas l'infini. */
|
|
20473
|
+
fenetre = computed(() => {
|
|
20474
|
+
const d = this.date();
|
|
20475
|
+
const v = this.vueEffective();
|
|
20476
|
+
if (v === 'day') {
|
|
20477
|
+
const s = cadMinuit(d);
|
|
20478
|
+
return { start: s, end: new Date(s.getFullYear(), s.getMonth(), s.getDate(), 23, 59, 59) };
|
|
20479
|
+
}
|
|
20480
|
+
if (v === 'week') {
|
|
20481
|
+
const s = cadDebutSemaine(d, this.firstDay());
|
|
20482
|
+
const e = new Date(s);
|
|
20483
|
+
e.setDate(e.getDate() + 6);
|
|
20484
|
+
e.setHours(23, 59, 59);
|
|
20485
|
+
return { start: s, end: e };
|
|
20486
|
+
}
|
|
20487
|
+
if (v === 'year') {
|
|
20488
|
+
return { start: new Date(d.getFullYear(), 0, 1), end: new Date(d.getFullYear(), 11, 31, 23, 59, 59) };
|
|
20489
|
+
}
|
|
20490
|
+
const g = cadMonthGrid(d, this.firstDay());
|
|
20491
|
+
const dernier = g.semaines[g.semaines.length - 1][6];
|
|
20492
|
+
return { start: g.debut, end: new Date(dernier.getFullYear(), dernier.getMonth(), dernier.getDate(), 23, 59, 59) };
|
|
20493
|
+
}, ...(ngDevMode ? [{ debugName: "fenetre" }] : /* istanbul ignore next */ []));
|
|
20494
|
+
/**
|
|
20495
|
+
* CORRECTIF OPTIMISTE. Le composant n'écrit jamais dans le document — mais sans état local
|
|
20496
|
+
* le geste serait mort à l'écran : on relâche, et la barre revient à sa place en attendant
|
|
20497
|
+
* que l'hôte réponde. On garde donc les bornes proposées, ATTACHÉES au `plan` d'origine, et
|
|
20498
|
+
* on les abandonne dès qu'un nouveau `plan` arrive. Même patron que `cad-taskboard`.
|
|
20499
|
+
*/
|
|
20500
|
+
patch = signal({ src: null, map: new Map() }, ...(ngDevMode ? [{ debugName: "patch" }] : /* istanbul ignore next */ []));
|
|
20501
|
+
occurrences = computed(() => {
|
|
20502
|
+
const f = this.fenetre();
|
|
20503
|
+
const caches = this.hidden();
|
|
20504
|
+
const { occurrences } = cadExpandOccurrences(this.normalise().tasks, f.start, f.end);
|
|
20505
|
+
const base = occurrences.filter((o) => !caches.has(o.task.bucket ?? ''));
|
|
20506
|
+
const p = this.patch();
|
|
20507
|
+
if (p.src !== this.plan() || !p.map.size)
|
|
20508
|
+
return base;
|
|
20509
|
+
return base.map((o) => {
|
|
20510
|
+
const n = p.map.get(o.id);
|
|
20511
|
+
return n ? { ...o, start: n.start, end: n.end } : o;
|
|
20512
|
+
});
|
|
20513
|
+
}, ...(ngDevMode ? [{ debugName: "occurrences" }] : /* istanbul ignore next */ []));
|
|
20514
|
+
/** Fantôme du geste en cours : il montre où l'on va tomber, APRÈS aimantage. */
|
|
20515
|
+
fantome = signal(null, ...(ngDevMode ? [{ debugName: "fantome" }] : /* istanbul ignore next */ []));
|
|
20516
|
+
/** Anomalies de dépliage — jamais tues : l'hôte peut les montrer. */
|
|
20517
|
+
issues = computed(() => cadExpandOccurrences(this.normalise().tasks, this.fenetre().start, this.fenetre().end).issues, ...(ngDevMode ? [{ debugName: "issues" }] : /* istanbul ignore next */ []));
|
|
20518
|
+
parJour = computed(() => {
|
|
20519
|
+
const carte = new Map();
|
|
20520
|
+
for (const o of this.occurrences()) {
|
|
20521
|
+
const d0 = cadNumeroJour(o.start), d1 = cadNumeroJour(o.end);
|
|
20522
|
+
for (let n = d0; n <= d1; n++)
|
|
20523
|
+
(carte.get(n) ?? carte.set(n, []).get(n)).push(o);
|
|
20524
|
+
}
|
|
20525
|
+
return carte;
|
|
20526
|
+
}, ...(ngDevMode ? [{ debugName: "parJour" }] : /* istanbul ignore next */ []));
|
|
20527
|
+
nomsJours = computed(() => {
|
|
20528
|
+
const base = ['lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim'];
|
|
20529
|
+
return this.firstDay() === 0 ? ['dim', ...base.slice(0, 6)] : base;
|
|
20530
|
+
}, ...(ngDevMode ? [{ debugName: "nomsJours" }] : /* istanbul ignore next */ []));
|
|
20531
|
+
semaines = computed(() => {
|
|
20532
|
+
const g = cadMonthGrid(this.date(), this.firstDay());
|
|
20533
|
+
const occ = this.occurrences();
|
|
20534
|
+
const wc = this.workCalendar();
|
|
20535
|
+
const ajd = cadNumeroJour(this.ajd());
|
|
20536
|
+
const max = this.maxPerDay();
|
|
20537
|
+
return g.semaines.map((jours) => {
|
|
20538
|
+
const { bandes, voies } = cadWeekBands(occ, jours);
|
|
20539
|
+
return {
|
|
20540
|
+
voies,
|
|
20541
|
+
bandes,
|
|
20542
|
+
jours: jours.map((date) => {
|
|
20543
|
+
const n = cadNumeroJour(date);
|
|
20544
|
+
const duJour = (this.parJour().get(n) ?? []).filter((o) => !cadEstBandeau(o));
|
|
20545
|
+
return {
|
|
20546
|
+
date, cle: n, n: date.getDate(),
|
|
20547
|
+
hors: date.getMonth() !== g.mois,
|
|
20548
|
+
auj: n === ajd,
|
|
20549
|
+
ferme: wc ? !wc.isWorkday(date) : false,
|
|
20550
|
+
puces: duJour.slice(0, max),
|
|
20551
|
+
reste: Math.max(0, duJour.length - max),
|
|
20552
|
+
};
|
|
20553
|
+
}),
|
|
20554
|
+
};
|
|
20555
|
+
});
|
|
20556
|
+
}, ...(ngDevMode ? [{ debugName: "semaines" }] : /* istanbul ignore next */ []));
|
|
20557
|
+
joursGrille = computed(() => {
|
|
20558
|
+
const v = this.vueEffective();
|
|
20559
|
+
const jours = v === 'day'
|
|
20560
|
+
? [cadMinuit(this.date())]
|
|
20561
|
+
: Array.from({ length: 7 }, (_, k) => { const d = cadDebutSemaine(this.date(), this.firstDay()); d.setDate(d.getDate() + k); return d; });
|
|
20562
|
+
const wc = this.workCalendar();
|
|
20563
|
+
const ajd = cadNumeroJour(this.ajd());
|
|
20564
|
+
return jours.map((date) => {
|
|
20565
|
+
const n = cadNumeroJour(date);
|
|
20566
|
+
return {
|
|
20567
|
+
date, cle: n, n: date.getDate(), jsem: this.nomsJours()[(date.getDay() + 6) % 7],
|
|
20568
|
+
auj: n === ajd,
|
|
20569
|
+
ferme: wc ? !wc.isWorkday(date) : false,
|
|
20570
|
+
slots: cadPackDay(this.parJour().get(n) ?? [], this.dayStart() * 60, this.dayEnd() * 60),
|
|
20571
|
+
};
|
|
20572
|
+
});
|
|
20573
|
+
}, ...(ngDevMode ? [{ debugName: "joursGrille" }] : /* istanbul ignore next */ []));
|
|
20574
|
+
bandeGrille = computed(() => cadWeekBands(this.occurrences(), this.joursGrille().map((j) => j.date)), ...(ngDevMode ? [{ debugName: "bandeGrille" }] : /* istanbul ignore next */ []));
|
|
20575
|
+
colonnes = computed(() => `54px repeat(${this.joursGrille().length}, minmax(0, 1fr))`, ...(ngDevMode ? [{ debugName: "colonnes" }] : /* istanbul ignore next */ []));
|
|
20576
|
+
maintenant = computed(() => {
|
|
20577
|
+
if (!this.showNow())
|
|
20578
|
+
return null;
|
|
20579
|
+
const n = this.today() ?? new Date();
|
|
20580
|
+
const m = n.getHours() * 60 + n.getMinutes();
|
|
20581
|
+
if (m < this.dayStart() * 60 || m > this.dayEnd() * 60)
|
|
20582
|
+
return null;
|
|
20583
|
+
return (m - this.dayStart() * 60) / 60 * this.hourHeight();
|
|
20584
|
+
}, ...(ngDevMode ? [{ debugName: "maintenant" }] : /* istanbul ignore next */ []));
|
|
20585
|
+
groupes = computed(() => {
|
|
20586
|
+
const carte = new Map();
|
|
20587
|
+
for (const [n, liste] of this.parJour())
|
|
20588
|
+
carte.set(n, liste);
|
|
20589
|
+
const ajd = cadNumeroJour(this.ajd());
|
|
20590
|
+
return [...carte.entries()]
|
|
20591
|
+
.sort((a, b) => a[0] - b[0])
|
|
20592
|
+
.map(([n, evts]) => {
|
|
20593
|
+
const date = evts[0].start;
|
|
20594
|
+
const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
20595
|
+
// Le jour du groupe est celui de la CASE, pas du premier événement : un multi-jours
|
|
20596
|
+
// apparaît dans chaque jour qu'il couvre, avec le bon quantième.
|
|
20597
|
+
const vrai = new Date(d);
|
|
20598
|
+
vrai.setDate(vrai.getDate() + (n - cadNumeroJour(d)));
|
|
20599
|
+
return {
|
|
20600
|
+
cle: n, n: vrai.getDate(), auj: n === ajd,
|
|
20601
|
+
jsem: this.nomsJours()[(vrai.getDay() + 6) % 7],
|
|
20602
|
+
evts: [...evts].sort((a, b) => Number(b.allDay) - Number(a.allDay) || a.start.getTime() - b.start.getTime()),
|
|
20603
|
+
};
|
|
20604
|
+
});
|
|
20605
|
+
}, ...(ngDevMode ? [{ debugName: "groupes" }] : /* istanbul ignore next */ []));
|
|
20606
|
+
miniMois = computed(() => {
|
|
20607
|
+
const an = this.date().getFullYear();
|
|
20608
|
+
const ajd = cadNumeroJour(this.ajd());
|
|
20609
|
+
const charge = this.parJour();
|
|
20610
|
+
return Array.from({ length: 12 }, (_, m) => {
|
|
20611
|
+
const g = cadMonthGrid(new Date(an, m, 1), this.firstDay());
|
|
20612
|
+
return {
|
|
20613
|
+
mois: m,
|
|
20614
|
+
titre: new Date(an, m, 1).toLocaleDateString(this.locale(), { month: 'long' }),
|
|
20615
|
+
jours: g.semaines.flat().map((date) => {
|
|
20616
|
+
const n = cadNumeroJour(date);
|
|
20617
|
+
return { date, cle: n, n: date.getDate(), hors: date.getMonth() !== m, auj: n === ajd, charge: (charge.get(n) ?? []).length };
|
|
20618
|
+
}),
|
|
20619
|
+
};
|
|
20620
|
+
});
|
|
20621
|
+
}, ...(ngDevMode ? [{ debugName: "miniMois" }] : /* istanbul ignore next */ []));
|
|
20622
|
+
couleur(o) {
|
|
20623
|
+
const b = o.task.bucket ?? '';
|
|
20624
|
+
const s = this.sources().find((x) => x.id === b);
|
|
20625
|
+
return o.task.color || s?.color || 'var(--cad-accent)';
|
|
20626
|
+
}
|
|
20627
|
+
nomSource(o) {
|
|
20628
|
+
const b = o.task.bucket ?? '';
|
|
20629
|
+
return this.sources().find((x) => x.id === b)?.label ?? b;
|
|
20630
|
+
}
|
|
20631
|
+
heure(d) {
|
|
20632
|
+
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
20633
|
+
}
|
|
20634
|
+
titre(o) {
|
|
20635
|
+
const q = o.allDay ? 'journée entière' : `${this.heure(o.start)} – ${this.heure(o.end)}`;
|
|
20636
|
+
const s = this.nomSource(o);
|
|
20637
|
+
return `${o.task.label} · ${q}${s ? ' · ' + s : ''}${o.recurring ? ' · récurrent' : ''}`;
|
|
20638
|
+
}
|
|
20639
|
+
/**
|
|
20640
|
+
* Une occurrence de SÉRIE n'est pas manipulable tant que l'édition de récurrence n'existe
|
|
20641
|
+
* pas. Bouger l'une d'elles sans savoir si l'on modifie l'occurrence, la suite ou toute la
|
|
20642
|
+
* série perdrait la modification EN SILENCE. On le montre (hachures, curseur interdit,
|
|
20643
|
+
* motif au survol) plutôt que de laisser essayer et rater.
|
|
20644
|
+
*/
|
|
20645
|
+
manipulable(o) {
|
|
20646
|
+
return this.editable() && !o.recurring && !o.task.locked;
|
|
20647
|
+
}
|
|
20648
|
+
motifVerrou(o) {
|
|
20649
|
+
if (!this.editable())
|
|
20650
|
+
return '';
|
|
20651
|
+
if (o.task.locked)
|
|
20652
|
+
return 'Verrouillé';
|
|
20653
|
+
if (o.recurring)
|
|
20654
|
+
return 'Série : édition au lot récurrence';
|
|
20655
|
+
return '';
|
|
20656
|
+
}
|
|
20657
|
+
geometrie(largeurJour) {
|
|
20658
|
+
return {
|
|
20659
|
+
hauteurHeure: this.hourHeight(),
|
|
20660
|
+
largeurJour,
|
|
20661
|
+
pas: Math.max(1, this.snap()),
|
|
20662
|
+
plageDebut: this.dayStart() * 60,
|
|
20663
|
+
plageFin: this.dayEnd() * 60,
|
|
20664
|
+
};
|
|
20665
|
+
}
|
|
20666
|
+
/**
|
|
20667
|
+
* Un glisser ne doit PAS produire aussi un clic.
|
|
20668
|
+
*
|
|
20669
|
+
* Le navigateur synthétise un `click` après tout `pointerup` sur un bouton : sans cette
|
|
20670
|
+
* garde, déplacer un événement émettait `eventMove` PUIS `eventClick`, et un hôte qui ouvre
|
|
20671
|
+
* une fiche au clic en ouvrirait une à chaque déplacement. Constaté au navigateur le
|
|
20672
|
+
* 22/08/2026 — un test unitaire ne l'aurait pas vu, le clic synthétique n'existe pas en jsdom.
|
|
20673
|
+
*/
|
|
20674
|
+
finGeste = 0;
|
|
20675
|
+
poser(o, bornes) {
|
|
20676
|
+
const p = this.patch();
|
|
20677
|
+
const map = p.src === this.plan() ? new Map(p.map) : new Map();
|
|
20678
|
+
map.set(o.id, bornes);
|
|
20679
|
+
this.patch.set({ src: this.plan(), map });
|
|
20680
|
+
}
|
|
20681
|
+
/**
|
|
20682
|
+
* Un geste au pointeur. Le patron vient de `cadStartResize` de `cad-table` : on écoute sur
|
|
20683
|
+
* le DOCUMENT, pas sur l'élément — sinon le geste s'interrompt dès que le pointeur sort de
|
|
20684
|
+
* la barre, ce qui arrive tout le temps quand on déplace vite.
|
|
20685
|
+
*/
|
|
20686
|
+
geste(kind, o, ev, colonne) {
|
|
20687
|
+
if (!this.manipulable(o))
|
|
20688
|
+
return;
|
|
20689
|
+
ev.preventDefault();
|
|
20690
|
+
ev.stopPropagation();
|
|
20691
|
+
const g = this.geometrie(colonne?.getBoundingClientRect().width ?? 0);
|
|
20692
|
+
const base = { start: o.start, end: o.end, allDay: o.allDay };
|
|
20693
|
+
const x0 = ev.clientX, y0 = ev.clientY;
|
|
20694
|
+
let bouge = false;
|
|
20695
|
+
const projette = (e) => cadProjeterEdition(kind, base, e.clientX - x0, e.clientY - y0, g);
|
|
20696
|
+
const move = (e) => { bouge = true; this.poser(o, projette(e)); };
|
|
20697
|
+
const up = (e) => {
|
|
20698
|
+
document.removeEventListener('pointermove', move);
|
|
20699
|
+
document.removeEventListener('pointerup', up);
|
|
20700
|
+
if (!bouge)
|
|
20701
|
+
return;
|
|
20702
|
+
this.finGeste = Date.now();
|
|
20703
|
+
const b = projette(e);
|
|
20704
|
+
if (b.start.getTime() === base.start.getTime() && b.end.getTime() === base.end.getTime())
|
|
20705
|
+
return;
|
|
20706
|
+
this.poser(o, b);
|
|
20707
|
+
const charge = { occurrence: o, start: b.start, end: b.end };
|
|
20708
|
+
(kind === 'move' ? this.eventMove : this.eventResize).emit(charge);
|
|
20709
|
+
};
|
|
20710
|
+
document.addEventListener('pointermove', move);
|
|
20711
|
+
document.addEventListener('pointerup', up);
|
|
20712
|
+
}
|
|
20713
|
+
/**
|
|
20714
|
+
* Geste dans la GRILLE DE MOIS — projection différente, et c'est la correction du 22/08/2026.
|
|
20715
|
+
*
|
|
20716
|
+
* Ici l'axe vertical n'est pas l'heure : c'est la semaine. Une rangée vaut SEPT JOURS et
|
|
20717
|
+
* l'heure ne bouge pas. En passant par la projection horaire, glisser d'une rangée déplaçait
|
|
20718
|
+
* un rendez-vous de 10 h 30 à 13 h 30 sans changer de date — mesuré au navigateur, signalé
|
|
20719
|
+
* par SZU. On MESURE la rangée plutôt que de la calculer : sa hauteur dépend du nombre de
|
|
20720
|
+
* voies de bandeaux, donc elle change d'une semaine à l'autre.
|
|
20721
|
+
*/
|
|
20722
|
+
gesteMois(kind, o, ev) {
|
|
20723
|
+
if (!this.manipulable(o))
|
|
20724
|
+
return;
|
|
20725
|
+
ev.preventDefault();
|
|
20726
|
+
ev.stopPropagation();
|
|
20727
|
+
const el = this.hote.nativeElement;
|
|
20728
|
+
const rangee = ev.target.closest('.mois__sem') ?? el.querySelector('.mois__sem');
|
|
20729
|
+
const g = {
|
|
20730
|
+
largeurJour: el.querySelector('.mois__jour')?.getBoundingClientRect().width ?? 0,
|
|
20731
|
+
hauteurRangee: rangee?.getBoundingClientRect().height ?? 0,
|
|
20732
|
+
};
|
|
20733
|
+
const base = { start: o.start, end: o.end, allDay: o.allDay };
|
|
20734
|
+
const x0 = ev.clientX, y0 = ev.clientY;
|
|
20735
|
+
let bouge = false;
|
|
20736
|
+
const projette = (e) => cadProjeterMois(kind, base, e.clientX - x0, e.clientY - y0, g);
|
|
20737
|
+
const move = (e) => { bouge = true; this.poser(o, projette(e)); };
|
|
20738
|
+
const up = (e) => {
|
|
20739
|
+
document.removeEventListener('pointermove', move);
|
|
20740
|
+
document.removeEventListener('pointerup', up);
|
|
20741
|
+
if (!bouge)
|
|
20742
|
+
return;
|
|
20743
|
+
this.finGeste = Date.now();
|
|
20744
|
+
const b = projette(e);
|
|
20745
|
+
if (b.start.getTime() === base.start.getTime() && b.end.getTime() === base.end.getTime())
|
|
20746
|
+
return;
|
|
20747
|
+
this.poser(o, b);
|
|
20748
|
+
const charge = { occurrence: o, start: b.start, end: b.end };
|
|
20749
|
+
(kind === 'move' ? this.eventMove : this.eventResize).emit(charge);
|
|
20750
|
+
};
|
|
20751
|
+
document.addEventListener('pointermove', move);
|
|
20752
|
+
document.addEventListener('pointerup', up);
|
|
20753
|
+
}
|
|
20754
|
+
/** Création par glisser sur une plage libre. Un clic sec produit un pas, jamais rien. */
|
|
20755
|
+
creer(jour, ev, colonne) {
|
|
20756
|
+
if (!this.editable())
|
|
20757
|
+
return;
|
|
20758
|
+
if (ev.target.closest('.evt'))
|
|
20759
|
+
return;
|
|
20760
|
+
ev.preventDefault();
|
|
20761
|
+
const rect = colonne.getBoundingClientRect();
|
|
20762
|
+
const g = this.geometrie(rect.width);
|
|
20763
|
+
const y0 = ev.clientY - rect.top;
|
|
20764
|
+
const cle = cadNumeroJour(jour);
|
|
20765
|
+
const dessine = (y1) => {
|
|
20766
|
+
const b = cadProjeterCreation(jour, y0, y1, g);
|
|
20767
|
+
const h = (o) => (o.getHours() * 60 + o.getMinutes() - g.plageDebut) / 60 * g.hauteurHeure;
|
|
20768
|
+
this.fantome.set({ jour: cle, haut: h(b.start), hauteur: Math.max(12, h(b.end) - h(b.start)),
|
|
20769
|
+
texte: this.heure(b.start) + ' – ' + this.heure(b.end) });
|
|
20770
|
+
return b;
|
|
20771
|
+
};
|
|
20772
|
+
dessine(y0);
|
|
20773
|
+
const move = (e) => dessine(e.clientY - rect.top);
|
|
20774
|
+
const up = (e) => {
|
|
20775
|
+
document.removeEventListener('pointermove', move);
|
|
20776
|
+
document.removeEventListener('pointerup', up);
|
|
20777
|
+
const b = cadProjeterCreation(jour, y0, e.clientY - rect.top, g);
|
|
20778
|
+
this.fantome.set(null);
|
|
20779
|
+
this.finGeste = Date.now();
|
|
20780
|
+
this.eventCreate.emit({ start: b.start, end: b.end, allDay: false });
|
|
20781
|
+
};
|
|
20782
|
+
document.addEventListener('pointermove', move);
|
|
20783
|
+
document.addEventListener('pointerup', up);
|
|
20784
|
+
}
|
|
20785
|
+
/**
|
|
20786
|
+
* Le CLAVIER passe par la MÊME projection que le pointeur — une seule logique de placement
|
|
20787
|
+
* à éprouver, et l'accessibilité n'est pas une reprise ultérieure qui dérive.
|
|
20788
|
+
* Ctrl/Cmd + flèches déplacent, Maj + flèches verticales redimensionnent par le bas.
|
|
20789
|
+
*/
|
|
20790
|
+
clavier(o, e) {
|
|
20791
|
+
if (!this.manipulable(o))
|
|
20792
|
+
return;
|
|
20793
|
+
const fleches = {
|
|
20794
|
+
ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1],
|
|
20795
|
+
};
|
|
20796
|
+
const d = fleches[e.key];
|
|
20797
|
+
if (!d)
|
|
20798
|
+
return;
|
|
20799
|
+
const deplace = e.ctrlKey || e.metaKey;
|
|
20800
|
+
const taille = e.shiftKey;
|
|
20801
|
+
if (!deplace && !taille)
|
|
20802
|
+
return;
|
|
20803
|
+
e.preventDefault();
|
|
20804
|
+
const g = this.geometrie(1);
|
|
20805
|
+
const base = { start: o.start, end: o.end, allDay: o.allDay };
|
|
20806
|
+
const kind = taille ? 'resize-end' : 'move';
|
|
20807
|
+
const b = cadProjeterEdition(kind, base, d[0] * g.largeurJour, (d[1] * g.pas / 60) * g.hauteurHeure, g);
|
|
20808
|
+
this.poser(o, b);
|
|
20809
|
+
(kind === 'move' ? this.eventMove : this.eventResize).emit({ occurrence: o, start: b.start, end: b.end });
|
|
20810
|
+
}
|
|
20811
|
+
clicEvenement(occurrence, originalEvent) {
|
|
20812
|
+
// Le clic qui SUIT un glisser est celui du navigateur, pas celui de l'utilisateur.
|
|
20813
|
+
if (Date.now() - this.finGeste < 300)
|
|
20814
|
+
return;
|
|
20815
|
+
this.eventClick.emit({ occurrence, originalEvent });
|
|
20816
|
+
}
|
|
20817
|
+
/** Le « +N » et un jour de la vue année mènent au JOUR : c'est ce qu'on cherchait. */
|
|
20818
|
+
voirJour(date, originalEvent) {
|
|
20819
|
+
this.dateClick.emit({ date, originalEvent });
|
|
20820
|
+
this.date.set(date);
|
|
20821
|
+
if (this.view() !== 'auto')
|
|
20822
|
+
this.view.set('day');
|
|
20823
|
+
}
|
|
20824
|
+
constructor() {
|
|
20825
|
+
// La fenêtre est annoncée : un hôte qui charge à la demande sait quoi aller chercher.
|
|
20826
|
+
let derniere = '';
|
|
20827
|
+
setTimeout(() => {
|
|
20828
|
+
const f = this.fenetre();
|
|
20829
|
+
const k = f.start.toISOString() + f.end.toISOString();
|
|
20830
|
+
if (k !== derniere) {
|
|
20831
|
+
derniere = k;
|
|
20832
|
+
this.rangeChange.emit(f);
|
|
20833
|
+
}
|
|
20834
|
+
});
|
|
20835
|
+
}
|
|
20836
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
20837
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CadCalendarComponent, isStandalone: true, selector: "cad-calendar", inputs: { plan: { classPropertyName: "plan", publicName: "plan", isSignal: true, isRequired: false, transformFunction: null }, sources: { classPropertyName: "sources", publicName: "sources", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, firstDay: { classPropertyName: "firstDay", publicName: "firstDay", isSignal: true, isRequired: false, transformFunction: null }, dayStart: { classPropertyName: "dayStart", publicName: "dayStart", isSignal: true, isRequired: false, transformFunction: null }, dayEnd: { classPropertyName: "dayEnd", publicName: "dayEnd", isSignal: true, isRequired: false, transformFunction: null }, hourHeight: { classPropertyName: "hourHeight", publicName: "hourHeight", isSignal: true, isRequired: false, transformFunction: null }, maxPerDay: { classPropertyName: "maxPerDay", publicName: "maxPerDay", isSignal: true, isRequired: false, transformFunction: null }, workCalendar: { classPropertyName: "workCalendar", publicName: "workCalendar", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, showNow: { classPropertyName: "showNow", publicName: "showNow", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hidden: "hiddenChange", view: "viewChange", date: "dateChange", eventClick: "eventClick", dateClick: "dateClick", rangeChange: "rangeChange", eventMove: "eventMove", eventResize: "eventResize", eventCreate: "eventCreate" }, host: { attributes: { "role": "region" }, properties: { "attr.aria-label": "ariaLabel() || null" }, classAttribute: "cad-calendar" }, ngImport: i0, template: `
|
|
20838
|
+
<div class="cal__corps">
|
|
20839
|
+
<div class="cal__vue">
|
|
20840
|
+
@switch (vueEffective()) {
|
|
20841
|
+
@case ('month') {
|
|
20842
|
+
<div class="mois">
|
|
20843
|
+
@for (n of nomsJours(); track $index) { <div class="mois__jsem">{{ n }}</div> }
|
|
20844
|
+
@for (sem of semaines(); track $index) {
|
|
20845
|
+
<div class="mois__sem">
|
|
20846
|
+
@for (j of sem.jours; track j.cle) {
|
|
20847
|
+
<div class="mois__jour"
|
|
20848
|
+
[class.is-hors]="j.hors" [class.is-auj]="j.auj" [class.is-ferme]="j.ferme">
|
|
20849
|
+
<div class="mois__n">{{ j.n }}</div>
|
|
20850
|
+
<!-- La place des bandeaux est RÉSERVÉE dans chaque case : le calque étant
|
|
20851
|
+
en position absolue, sans cette réserve il recouvrirait les puces. -->
|
|
20852
|
+
<div class="mois__reserve" [style.height.px]="sem.voies * 19"></div>
|
|
20853
|
+
@for (o of j.puces; track o.id) {
|
|
20854
|
+
<button type="button" class="puce"
|
|
20855
|
+
[class.is-editable]="manipulable(o)"
|
|
20856
|
+
[class.is-verrouille]="editable() && !manipulable(o)"
|
|
20857
|
+
[style.--_c]="couleur(o)"
|
|
20858
|
+
[title]="titre(o) + (motifVerrou(o) ? ' · ' + motifVerrou(o) : '')"
|
|
20859
|
+
(pointerdown)="gesteMois('move', o, $event)"
|
|
20860
|
+
(keydown)="clavier(o, $event)"
|
|
20861
|
+
(click)="clicEvenement(o, $event)">
|
|
20862
|
+
@if (!o.allDay) { <span class="puce__h">{{ heure(o.start) }}</span> }
|
|
20863
|
+
<span class="puce__t">{{ o.task.label }}</span>
|
|
20864
|
+
@if (o.recurring) { <span aria-hidden="true">↻</span> }
|
|
20865
|
+
</button>
|
|
20866
|
+
}
|
|
20867
|
+
@if (j.reste > 0) {
|
|
20868
|
+
<button type="button" class="plus" (click)="voirJour(j.date, $event)">
|
|
20869
|
+
+{{ j.reste }} autre{{ j.reste > 1 ? 's' : '' }}
|
|
20870
|
+
</button>
|
|
20871
|
+
}
|
|
20872
|
+
</div>
|
|
20873
|
+
}
|
|
20874
|
+
<!-- CALQUE DES BANDEAUX : un multi-jours doit être CONTINU d'un jour à
|
|
20875
|
+
l'autre. Le dessiner dans chaque case le répéterait ; il vit donc
|
|
20876
|
+
au-dessus de la rangée, en pourcentages de sa largeur. -->
|
|
20877
|
+
<div class="calque" [style.top.px]="20">
|
|
20878
|
+
@for (b of sem.bandes; track b.occurrence.id) {
|
|
20879
|
+
<button type="button" class="bandeau"
|
|
20880
|
+
[class.is-avant]="b.continueAvant" [class.is-apres]="b.continueApres"
|
|
20881
|
+
[class.is-editable]="manipulable(b.occurrence)"
|
|
20882
|
+
[class.is-verrouille]="editable() && !manipulable(b.occurrence)"
|
|
20883
|
+
[style.--_c]="couleur(b.occurrence)"
|
|
20884
|
+
[style.left.%]="b.colDebut / 7 * 100"
|
|
20885
|
+
[style.width.%]="(b.colFin - b.colDebut + 1) / 7 * 100"
|
|
20886
|
+
[style.top.px]="b.voie * 19"
|
|
20887
|
+
[title]="titre(b.occurrence) + (motifVerrou(b.occurrence) ? ' · ' + motifVerrou(b.occurrence) : '')"
|
|
20888
|
+
(pointerdown)="gesteMois('move', b.occurrence, $event)"
|
|
20889
|
+
(keydown)="clavier(b.occurrence, $event)"
|
|
20890
|
+
(click)="clicEvenement(b.occurrence, $event)">
|
|
20891
|
+
{{ b.continueAvant ? '◀ ' : '' }}{{ b.occurrence.task.label }}{{ b.continueApres ? ' ▶' : '' }}
|
|
20892
|
+
@if (manipulable(b.occurrence)) {
|
|
20893
|
+
<span class="poignee is-haut" (pointerdown)="gesteMois('resize-start', b.occurrence, $event)"></span>
|
|
20894
|
+
<span class="poignee is-bas" (pointerdown)="gesteMois('resize-end', b.occurrence, $event)"></span>
|
|
20895
|
+
}
|
|
20896
|
+
</button>
|
|
20897
|
+
}
|
|
20898
|
+
</div>
|
|
20899
|
+
</div>
|
|
20900
|
+
}
|
|
20901
|
+
</div>
|
|
20902
|
+
}
|
|
20903
|
+
|
|
20904
|
+
@case ('agenda') {
|
|
20905
|
+
<div class="agenda">
|
|
20906
|
+
@for (g of groupes(); track g.cle) {
|
|
20907
|
+
<div class="ag__jour">
|
|
20908
|
+
<div class="ag__d" [class.is-auj]="g.auj"><b>{{ g.n }}</b><span>{{ g.jsem }}</span></div>
|
|
20909
|
+
<div class="ag__l">
|
|
20910
|
+
@for (o of g.evts; track o.id) {
|
|
20911
|
+
<button type="button" class="ag__e" [style.--_c]="couleur(o)" (click)="clicEvenement(o, $event)">
|
|
20912
|
+
<span class="ag__p"></span>
|
|
20913
|
+
<span class="ag__h">{{ o.allDay ? 'journée' : heure(o.start) + ' – ' + heure(o.end) }}</span>
|
|
20914
|
+
<span class="ag__t">{{ o.task.label }}@if (o.recurring) { <span aria-hidden="true"> ↻</span> }</span>
|
|
20915
|
+
<span class="ag__c">{{ nomSource(o) }}</span>
|
|
20916
|
+
</button>
|
|
20917
|
+
}
|
|
20918
|
+
</div>
|
|
20919
|
+
</div>
|
|
20920
|
+
} @empty { <div class="vide">{{ emptyText() }}</div> }
|
|
20921
|
+
</div>
|
|
20922
|
+
}
|
|
20923
|
+
|
|
20924
|
+
@case ('year') {
|
|
20925
|
+
<div class="annee">
|
|
20926
|
+
@for (m of miniMois(); track m.mois) {
|
|
20927
|
+
<div class="mm">
|
|
20928
|
+
<div class="mm__t">{{ m.titre }}</div>
|
|
20929
|
+
<div class="mm__g">
|
|
20930
|
+
@for (n of nomsJours(); track $index) { <span>{{ n.charAt(0) }}</span> }
|
|
20931
|
+
@for (j of m.jours; track j.cle) {
|
|
20932
|
+
<button type="button" [class.is-hors]="j.hors" [class.is-auj]="j.auj"
|
|
20933
|
+
[title]="j.charge ? j.charge + ' événement(s)' : ''"
|
|
20934
|
+
(click)="voirJour(j.date, $event)">
|
|
20935
|
+
{{ j.n }}@if (j.charge) { <span class="mm__pt"></span> }
|
|
20936
|
+
</button>
|
|
20937
|
+
}
|
|
20938
|
+
</div>
|
|
20939
|
+
</div>
|
|
20940
|
+
}
|
|
20941
|
+
</div>
|
|
20942
|
+
}
|
|
20943
|
+
|
|
20944
|
+
@default {
|
|
20945
|
+
<div class="grille">
|
|
20946
|
+
<div class="grille__hd" [style.grid-template-columns]="colonnes()">
|
|
20947
|
+
<div></div>
|
|
20948
|
+
@for (j of joursGrille(); track j.cle) {
|
|
20949
|
+
<div [class.is-auj]="j.auj"><div class="grille__d">{{ j.jsem }}</div><div class="grille__n">{{ j.n }}</div></div>
|
|
20950
|
+
}
|
|
20951
|
+
</div>
|
|
20952
|
+
|
|
20953
|
+
<div class="bande" [style.grid-template-columns]="colonnes()">
|
|
20954
|
+
<div class="bande__lbl">jour</div>
|
|
20955
|
+
<div class="bande__zone" [style.height.px]="Math.max(1, bandeGrille().voies) * 19 + 2">
|
|
20956
|
+
@for (b of bandeGrille().bandes; track b.occurrence.id) {
|
|
20957
|
+
<button type="button" class="bandeau" #bd
|
|
20958
|
+
[class.is-avant]="b.continueAvant" [class.is-apres]="b.continueApres"
|
|
20959
|
+
[class.is-editable]="manipulable(b.occurrence)"
|
|
20960
|
+
[class.is-verrouille]="editable() && !manipulable(b.occurrence)"
|
|
20961
|
+
[style.--_c]="couleur(b.occurrence)"
|
|
20962
|
+
[style.left.%]="b.colDebut / joursGrille().length * 100"
|
|
20963
|
+
[style.width.%]="(b.colFin - b.colDebut + 1) / joursGrille().length * 100"
|
|
20964
|
+
[style.top.px]="b.voie * 19"
|
|
20965
|
+
[title]="titre(b.occurrence) + (motifVerrou(b.occurrence) ? ' · ' + motifVerrou(b.occurrence) : '')"
|
|
20966
|
+
(pointerdown)="geste('move', b.occurrence, $event, largeurColonne())"
|
|
20967
|
+
(keydown)="clavier(b.occurrence, $event)"
|
|
20968
|
+
(click)="clicEvenement(b.occurrence, $event)">
|
|
20969
|
+
{{ b.continueAvant ? '◀ ' : '' }}{{ b.occurrence.task.label }}{{ b.continueApres ? ' ▶' : '' }}
|
|
20970
|
+
@if (manipulable(b.occurrence)) {
|
|
20971
|
+
<span class="poignee is-haut" (pointerdown)="geste('resize-start', b.occurrence, $event, largeurColonne())"></span>
|
|
20972
|
+
<span class="poignee is-bas" (pointerdown)="geste('resize-end', b.occurrence, $event, largeurColonne())"></span>
|
|
20973
|
+
}
|
|
20974
|
+
</button>
|
|
20975
|
+
}
|
|
20976
|
+
</div>
|
|
20977
|
+
</div>
|
|
20978
|
+
|
|
20979
|
+
<div class="heures" [style.grid-template-columns]="colonnes()">
|
|
20980
|
+
<div class="heures__lbl">
|
|
20981
|
+
@for (h of heuresAffichees(); track h) { <div [style.height.px]="hauteurHeure()">{{ h }}h</div> }
|
|
20982
|
+
</div>
|
|
20983
|
+
@for (j of joursGrille(); track j.cle) {
|
|
20984
|
+
<div class="heures__col" #col [class.is-ferme]="j.ferme"
|
|
20985
|
+
(pointerdown)="creer(j.date, $event, col)">
|
|
20986
|
+
@for (h of heuresAffichees(); track h) {
|
|
20987
|
+
<div class="trait" [style.top.px]="(h - dayStart()) * hauteurHeure()"></div>
|
|
20988
|
+
<div class="trait is-demi" [style.top.px]="(h - dayStart()) * hauteurHeure() + hauteurHeure() / 2"></div>
|
|
20989
|
+
}
|
|
20990
|
+
@for (s of j.slots; track s.occurrence.id) {
|
|
20991
|
+
<button type="button" class="evt"
|
|
20992
|
+
[class.is-editable]="manipulable(s.occurrence)"
|
|
20993
|
+
[class.is-verrouille]="editable() && !manipulable(s.occurrence)"
|
|
20994
|
+
[style.--_c]="couleur(s.occurrence)"
|
|
20995
|
+
[style.top.px]="s.haut * hauteurTotale()"
|
|
20996
|
+
[style.height.px]="Math.max(16, s.hauteur * hauteurTotale() - 2)"
|
|
20997
|
+
[style.left]="'calc(' + (s.voie / s.voies * 100) + '% + 1px)'"
|
|
20998
|
+
[style.width]="'calc(' + (100 / s.voies) + '% - 3px)'"
|
|
20999
|
+
[title]="titre(s.occurrence) + (motifVerrou(s.occurrence) ? ' · ' + motifVerrou(s.occurrence) : '')"
|
|
21000
|
+
(pointerdown)="geste('move', s.occurrence, $event, col)"
|
|
21001
|
+
(keydown)="clavier(s.occurrence, $event)"
|
|
21002
|
+
(click)="clicEvenement(s.occurrence, $event)">
|
|
21003
|
+
<span class="evt__t">{{ s.occurrence.task.label }}@if (s.occurrence.recurring) { <span aria-hidden="true"> ↻</span> }</span>
|
|
21004
|
+
@if (s.hauteur * hauteurTotale() > 30) {
|
|
21005
|
+
<span class="evt__h">{{ heure(s.occurrence.start) }} – {{ heure(s.occurrence.end) }}</span>
|
|
21006
|
+
}
|
|
21007
|
+
@if (manipulable(s.occurrence)) {
|
|
21008
|
+
<span class="poignee is-haut" (pointerdown)="geste('resize-start', s.occurrence, $event, col)"></span>
|
|
21009
|
+
<span class="poignee is-bas" (pointerdown)="geste('resize-end', s.occurrence, $event, col)"></span>
|
|
21010
|
+
}
|
|
21011
|
+
</button>
|
|
21012
|
+
}
|
|
21013
|
+
@if (fantome(); as f) {
|
|
21014
|
+
@if (f.jour === j.cle) {
|
|
21015
|
+
<div class="fantome" [style.top.px]="f.haut" [style.height.px]="f.hauteur"
|
|
21016
|
+
[style.left.px]="2" [style.right.px]="2">{{ f.texte }}</div>
|
|
21017
|
+
}
|
|
21018
|
+
}
|
|
21019
|
+
@if (j.auj && maintenant() !== null) {
|
|
21020
|
+
<div class="maintenant" [style.top.px]="maintenant()"></div>
|
|
21021
|
+
}
|
|
21022
|
+
</div>
|
|
21023
|
+
}
|
|
21024
|
+
</div>
|
|
21025
|
+
</div>
|
|
21026
|
+
}
|
|
21027
|
+
}
|
|
21028
|
+
</div>
|
|
21029
|
+
</div>
|
|
21030
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;min-height:0;height:100%;font:var(--cad-font-size)/1.4 var(--cad-font);color:var(--cad-fg);background:var(--cad-bg)}.cal__corps{flex:1;min-height:0;display:flex}.cal__vue{flex:1;min-width:0;min-height:0;overflow:auto}.mois{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));grid-template-rows:auto;grid-auto-rows:minmax(84px,1fr);min-height:100%}.mois__jsem{padding:6px 8px;text-align:right;border-bottom:1px solid var(--cad-border-strong);background:var(--cad-bg-subtle);position:sticky;top:0;z-index:4;font:600 10.5px var(--cad-font-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--cad-fg-muted)}.mois__sem{grid-column:1 / -1;display:grid;grid-template-columns:repeat(7,minmax(0,1fr));position:relative;border-bottom:1px solid var(--cad-border);min-height:84px}.mois__jour{border-right:1px solid var(--cad-border);padding:3px;min-width:0;display:flex;flex-direction:column;gap:2px}.mois__jour:last-child{border-right:0}.mois__jour.is-hors,.mois__jour.is-ferme{background:var(--cad-bg-subtle)}.mois__n{font:11px var(--cad-font-mono);color:var(--cad-fg-secondary);text-align:right;padding:1px 3px;font-variant-numeric:tabular-nums;flex:none}.mois__jour.is-auj .mois__n{background:var(--cad-accent);color:var(--cad-fg-on-accent);border-radius:var(--cad-radius-sm);font-weight:600}.mois__reserve{flex:none}.calque{position:absolute;left:0;right:0;pointer-events:none;z-index:2}.bandeau{position:absolute;height:17px;border-radius:var(--cad-radius-sm);padding:0 7px;font-size:10.5px;line-height:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:auto;cursor:pointer;background:color-mix(in srgb,var(--_c) 22%,transparent);color:var(--_c);border:1px solid color-mix(in srgb,var(--_c) 42%,transparent)}.bandeau.is-avant{border-top-left-radius:0;border-bottom-left-radius:0;border-left-style:dashed}.bandeau.is-apres{border-top-right-radius:0;border-bottom-right-radius:0;border-right-style:dashed}.puce{display:flex;align-items:center;gap:5px;font-size:10.5px;padding:1px 5px;border-radius:var(--cad-radius-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;border-left:2px solid var(--_c);background:color-mix(in srgb,var(--_c) 13%,transparent);color:var(--_c)}.puce:hover{background:color-mix(in srgb,var(--_c) 22%,transparent)}.puce__h{font:9.5px var(--cad-font-mono);opacity:.85;flex:none;font-variant-numeric:tabular-nums}.puce__t{min-width:0;overflow:hidden;text-overflow:ellipsis}.plus{all:unset;box-sizing:border-box;font-size:10px;color:var(--cad-fg-muted);padding:1px 5px;cursor:pointer;border-radius:var(--cad-radius-sm)}.plus:hover{color:var(--cad-accent);background:var(--cad-hover)}.grille{min-width:0;display:flex;flex-direction:column;height:100%}.grille__hd{display:grid;position:sticky;top:0;z-index:5;background:var(--cad-bg-subtle);border-bottom:1px solid var(--cad-border-strong);flex:none}.grille__hd>div{padding:5px 6px;text-align:center;border-right:1px solid var(--cad-border);min-width:0}.grille__hd>div:last-child{border-right:0}.grille__d{font:10px var(--cad-font-mono);color:var(--cad-fg-muted);text-transform:uppercase;letter-spacing:.06em}.grille__n{font-size:16px;font-weight:600;letter-spacing:-.02em}.is-auj .grille__n{color:var(--cad-accent)}.bande{display:grid;border-bottom:1px solid var(--cad-border-strong);background:var(--cad-bg-subtle);flex:none}.bande__lbl{font:9px var(--cad-font-mono);color:var(--cad-fg-muted);padding:4px 6px;text-align:right;border-right:1px solid var(--cad-border)}.bande__zone{grid-column:2 / -1;position:relative;padding:2px 0}.heures{display:grid;position:relative;flex:1;min-height:0;overflow:auto}.heures__lbl{border-right:1px solid var(--cad-border)}.heures__lbl>div{font:9.5px var(--cad-font-mono);color:var(--cad-fg-muted);text-align:right;padding-right:6px;transform:translateY(-6px)}.heures__col{border-right:1px solid var(--cad-border);position:relative;min-width:0}.heures__col:last-child{border-right:0}.heures__col.is-ferme{background:var(--cad-bg-subtle)}.trait{position:absolute;left:0;right:0;border-top:1px solid var(--cad-border);pointer-events:none}.trait.is-demi{border-top-style:dotted;opacity:.55}.evt{position:absolute;border-radius:var(--cad-radius-sm);padding:1px 5px;overflow:hidden;cursor:pointer;border-left:3px solid var(--_c);font-size:10.5px;line-height:1.25;background:color-mix(in srgb,var(--_c) 15%,transparent);color:var(--_c)}.evt:hover{background:color-mix(in srgb,var(--_c) 26%,transparent)}.evt__t{display:block;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.evt__h{font:9px var(--cad-font-mono);opacity:.85}.maintenant{position:absolute;left:0;right:0;height:2px;background:var(--cad-danger);z-index:4;pointer-events:none}.maintenant:before{content:\"\";position:absolute;left:-3px;top:-3px;width:8px;height:8px;border-radius:50%;background:var(--cad-danger)}.agenda{display:flex;flex-direction:column}.ag__jour{display:grid;grid-template-columns:72px minmax(0,1fr);border-bottom:1px solid var(--cad-border)}.ag__d{padding:9px 11px;border-right:1px solid var(--cad-border);background:var(--cad-bg-subtle)}.ag__d b{display:block;font-size:18px;font-weight:600;letter-spacing:-.02em}.ag__d span{display:block;font:9.5px var(--cad-font-mono);color:var(--cad-fg-muted);text-transform:uppercase}.ag__d.is-auj b{color:var(--cad-accent)}.ag__l{padding:5px 0;min-width:0}.ag__e{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:9px;padding:6px 11px;cursor:pointer;width:100%;min-height:34px}.ag__e:hover{background:var(--cad-hover)}.ag__e:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.ag__p{width:8px;height:8px;border-radius:50%;background:var(--_c);flex:none}.ag__h{font:11px var(--cad-font-mono);color:var(--cad-fg-secondary);flex:none;width:92px;font-variant-numeric:tabular-nums}.ag__t{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag__c{margin-left:auto;font-size:10.5px;color:var(--cad-fg-muted);flex:none}.annee{display:grid;grid-template-columns:repeat(auto-fill,minmax(178px,1fr));gap:14px;padding:14px}.mm{border:1px solid var(--cad-border);border-radius:var(--cad-radius);padding:8px;background:var(--cad-surface)}.mm__t{font-size:12px;font-weight:600;margin-bottom:5px;text-transform:capitalize}.mm__g{display:grid;grid-template-columns:repeat(7,1fr);gap:1px}.mm__g>span,.mm__g>button{all:unset;box-sizing:border-box;text-align:center;font:9.5px var(--cad-font-mono);padding:2px 0;border-radius:3px;color:var(--cad-fg-secondary);font-variant-numeric:tabular-nums}.mm__g>span{color:var(--cad-fg-muted);font-weight:500}.mm__g>button{cursor:pointer;position:relative}.mm__g>button:hover{background:var(--cad-hover)}.mm__g>button.is-hors{opacity:.35}.mm__g>button.is-auj{background:var(--cad-accent);color:var(--cad-fg-on-accent);font-weight:600}.mm__pt{position:absolute;left:50%;bottom:0;transform:translate(-50%);width:3px;height:3px;border-radius:50%;background:var(--cad-accent)}.mm__g>button.is-auj .mm__pt{background:var(--cad-fg-on-accent)}.evt.is-editable,.bandeau.is-editable,.puce.is-editable{cursor:grab}.evt.is-editable:active,.bandeau.is-editable:active,.puce.is-editable:active{cursor:grabbing}.poignee{position:absolute;z-index:2}.evt .poignee{left:0;right:0;height:6px;cursor:ns-resize}.evt .poignee.is-haut{top:0}.evt .poignee.is-bas{bottom:0}.bandeau .poignee{top:0;bottom:0;width:6px;cursor:ew-resize}.bandeau .poignee.is-haut{left:0}.bandeau .poignee.is-bas{right:0}.poignee:after{content:\"\";position:absolute;inset:0}.evt.is-verrouille,.bandeau.is-verrouille,.puce.is-verrouille{cursor:not-allowed;position:relative}.evt.is-verrouille:after,.bandeau.is-verrouille:after,.puce.is-verrouille:after{content:\"\";position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(-45deg,currentColor 0 1px,transparent 1px 5px);opacity:.18}.fantome{position:absolute;z-index:6;pointer-events:none;border-radius:var(--cad-radius-sm);background:var(--cad-accent-soft);border:1px dashed var(--cad-accent);color:var(--cad-accent);font:10.5px var(--cad-font-mono);padding:1px 5px;overflow:hidden;white-space:nowrap}.vide{padding:28px 16px;color:var(--cad-fg-muted);text-align:center}@media(prefers-reduced-motion:reduce){*{transition:none!important}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
21031
|
+
}
|
|
21032
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CadCalendarComponent, decorators: [{
|
|
21033
|
+
type: Component,
|
|
21034
|
+
args: [{ selector: 'cad-calendar', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cad-calendar', '[attr.aria-label]': 'ariaLabel() || null', role: 'region' }, template: `
|
|
21035
|
+
<div class="cal__corps">
|
|
21036
|
+
<div class="cal__vue">
|
|
21037
|
+
@switch (vueEffective()) {
|
|
21038
|
+
@case ('month') {
|
|
21039
|
+
<div class="mois">
|
|
21040
|
+
@for (n of nomsJours(); track $index) { <div class="mois__jsem">{{ n }}</div> }
|
|
21041
|
+
@for (sem of semaines(); track $index) {
|
|
21042
|
+
<div class="mois__sem">
|
|
21043
|
+
@for (j of sem.jours; track j.cle) {
|
|
21044
|
+
<div class="mois__jour"
|
|
21045
|
+
[class.is-hors]="j.hors" [class.is-auj]="j.auj" [class.is-ferme]="j.ferme">
|
|
21046
|
+
<div class="mois__n">{{ j.n }}</div>
|
|
21047
|
+
<!-- La place des bandeaux est RÉSERVÉE dans chaque case : le calque étant
|
|
21048
|
+
en position absolue, sans cette réserve il recouvrirait les puces. -->
|
|
21049
|
+
<div class="mois__reserve" [style.height.px]="sem.voies * 19"></div>
|
|
21050
|
+
@for (o of j.puces; track o.id) {
|
|
21051
|
+
<button type="button" class="puce"
|
|
21052
|
+
[class.is-editable]="manipulable(o)"
|
|
21053
|
+
[class.is-verrouille]="editable() && !manipulable(o)"
|
|
21054
|
+
[style.--_c]="couleur(o)"
|
|
21055
|
+
[title]="titre(o) + (motifVerrou(o) ? ' · ' + motifVerrou(o) : '')"
|
|
21056
|
+
(pointerdown)="gesteMois('move', o, $event)"
|
|
21057
|
+
(keydown)="clavier(o, $event)"
|
|
21058
|
+
(click)="clicEvenement(o, $event)">
|
|
21059
|
+
@if (!o.allDay) { <span class="puce__h">{{ heure(o.start) }}</span> }
|
|
21060
|
+
<span class="puce__t">{{ o.task.label }}</span>
|
|
21061
|
+
@if (o.recurring) { <span aria-hidden="true">↻</span> }
|
|
21062
|
+
</button>
|
|
21063
|
+
}
|
|
21064
|
+
@if (j.reste > 0) {
|
|
21065
|
+
<button type="button" class="plus" (click)="voirJour(j.date, $event)">
|
|
21066
|
+
+{{ j.reste }} autre{{ j.reste > 1 ? 's' : '' }}
|
|
21067
|
+
</button>
|
|
21068
|
+
}
|
|
21069
|
+
</div>
|
|
21070
|
+
}
|
|
21071
|
+
<!-- CALQUE DES BANDEAUX : un multi-jours doit être CONTINU d'un jour à
|
|
21072
|
+
l'autre. Le dessiner dans chaque case le répéterait ; il vit donc
|
|
21073
|
+
au-dessus de la rangée, en pourcentages de sa largeur. -->
|
|
21074
|
+
<div class="calque" [style.top.px]="20">
|
|
21075
|
+
@for (b of sem.bandes; track b.occurrence.id) {
|
|
21076
|
+
<button type="button" class="bandeau"
|
|
21077
|
+
[class.is-avant]="b.continueAvant" [class.is-apres]="b.continueApres"
|
|
21078
|
+
[class.is-editable]="manipulable(b.occurrence)"
|
|
21079
|
+
[class.is-verrouille]="editable() && !manipulable(b.occurrence)"
|
|
21080
|
+
[style.--_c]="couleur(b.occurrence)"
|
|
21081
|
+
[style.left.%]="b.colDebut / 7 * 100"
|
|
21082
|
+
[style.width.%]="(b.colFin - b.colDebut + 1) / 7 * 100"
|
|
21083
|
+
[style.top.px]="b.voie * 19"
|
|
21084
|
+
[title]="titre(b.occurrence) + (motifVerrou(b.occurrence) ? ' · ' + motifVerrou(b.occurrence) : '')"
|
|
21085
|
+
(pointerdown)="gesteMois('move', b.occurrence, $event)"
|
|
21086
|
+
(keydown)="clavier(b.occurrence, $event)"
|
|
21087
|
+
(click)="clicEvenement(b.occurrence, $event)">
|
|
21088
|
+
{{ b.continueAvant ? '◀ ' : '' }}{{ b.occurrence.task.label }}{{ b.continueApres ? ' ▶' : '' }}
|
|
21089
|
+
@if (manipulable(b.occurrence)) {
|
|
21090
|
+
<span class="poignee is-haut" (pointerdown)="gesteMois('resize-start', b.occurrence, $event)"></span>
|
|
21091
|
+
<span class="poignee is-bas" (pointerdown)="gesteMois('resize-end', b.occurrence, $event)"></span>
|
|
21092
|
+
}
|
|
21093
|
+
</button>
|
|
21094
|
+
}
|
|
21095
|
+
</div>
|
|
21096
|
+
</div>
|
|
21097
|
+
}
|
|
21098
|
+
</div>
|
|
21099
|
+
}
|
|
21100
|
+
|
|
21101
|
+
@case ('agenda') {
|
|
21102
|
+
<div class="agenda">
|
|
21103
|
+
@for (g of groupes(); track g.cle) {
|
|
21104
|
+
<div class="ag__jour">
|
|
21105
|
+
<div class="ag__d" [class.is-auj]="g.auj"><b>{{ g.n }}</b><span>{{ g.jsem }}</span></div>
|
|
21106
|
+
<div class="ag__l">
|
|
21107
|
+
@for (o of g.evts; track o.id) {
|
|
21108
|
+
<button type="button" class="ag__e" [style.--_c]="couleur(o)" (click)="clicEvenement(o, $event)">
|
|
21109
|
+
<span class="ag__p"></span>
|
|
21110
|
+
<span class="ag__h">{{ o.allDay ? 'journée' : heure(o.start) + ' – ' + heure(o.end) }}</span>
|
|
21111
|
+
<span class="ag__t">{{ o.task.label }}@if (o.recurring) { <span aria-hidden="true"> ↻</span> }</span>
|
|
21112
|
+
<span class="ag__c">{{ nomSource(o) }}</span>
|
|
21113
|
+
</button>
|
|
21114
|
+
}
|
|
21115
|
+
</div>
|
|
21116
|
+
</div>
|
|
21117
|
+
} @empty { <div class="vide">{{ emptyText() }}</div> }
|
|
21118
|
+
</div>
|
|
21119
|
+
}
|
|
21120
|
+
|
|
21121
|
+
@case ('year') {
|
|
21122
|
+
<div class="annee">
|
|
21123
|
+
@for (m of miniMois(); track m.mois) {
|
|
21124
|
+
<div class="mm">
|
|
21125
|
+
<div class="mm__t">{{ m.titre }}</div>
|
|
21126
|
+
<div class="mm__g">
|
|
21127
|
+
@for (n of nomsJours(); track $index) { <span>{{ n.charAt(0) }}</span> }
|
|
21128
|
+
@for (j of m.jours; track j.cle) {
|
|
21129
|
+
<button type="button" [class.is-hors]="j.hors" [class.is-auj]="j.auj"
|
|
21130
|
+
[title]="j.charge ? j.charge + ' événement(s)' : ''"
|
|
21131
|
+
(click)="voirJour(j.date, $event)">
|
|
21132
|
+
{{ j.n }}@if (j.charge) { <span class="mm__pt"></span> }
|
|
21133
|
+
</button>
|
|
21134
|
+
}
|
|
21135
|
+
</div>
|
|
21136
|
+
</div>
|
|
21137
|
+
}
|
|
21138
|
+
</div>
|
|
21139
|
+
}
|
|
21140
|
+
|
|
21141
|
+
@default {
|
|
21142
|
+
<div class="grille">
|
|
21143
|
+
<div class="grille__hd" [style.grid-template-columns]="colonnes()">
|
|
21144
|
+
<div></div>
|
|
21145
|
+
@for (j of joursGrille(); track j.cle) {
|
|
21146
|
+
<div [class.is-auj]="j.auj"><div class="grille__d">{{ j.jsem }}</div><div class="grille__n">{{ j.n }}</div></div>
|
|
21147
|
+
}
|
|
21148
|
+
</div>
|
|
21149
|
+
|
|
21150
|
+
<div class="bande" [style.grid-template-columns]="colonnes()">
|
|
21151
|
+
<div class="bande__lbl">jour</div>
|
|
21152
|
+
<div class="bande__zone" [style.height.px]="Math.max(1, bandeGrille().voies) * 19 + 2">
|
|
21153
|
+
@for (b of bandeGrille().bandes; track b.occurrence.id) {
|
|
21154
|
+
<button type="button" class="bandeau" #bd
|
|
21155
|
+
[class.is-avant]="b.continueAvant" [class.is-apres]="b.continueApres"
|
|
21156
|
+
[class.is-editable]="manipulable(b.occurrence)"
|
|
21157
|
+
[class.is-verrouille]="editable() && !manipulable(b.occurrence)"
|
|
21158
|
+
[style.--_c]="couleur(b.occurrence)"
|
|
21159
|
+
[style.left.%]="b.colDebut / joursGrille().length * 100"
|
|
21160
|
+
[style.width.%]="(b.colFin - b.colDebut + 1) / joursGrille().length * 100"
|
|
21161
|
+
[style.top.px]="b.voie * 19"
|
|
21162
|
+
[title]="titre(b.occurrence) + (motifVerrou(b.occurrence) ? ' · ' + motifVerrou(b.occurrence) : '')"
|
|
21163
|
+
(pointerdown)="geste('move', b.occurrence, $event, largeurColonne())"
|
|
21164
|
+
(keydown)="clavier(b.occurrence, $event)"
|
|
21165
|
+
(click)="clicEvenement(b.occurrence, $event)">
|
|
21166
|
+
{{ b.continueAvant ? '◀ ' : '' }}{{ b.occurrence.task.label }}{{ b.continueApres ? ' ▶' : '' }}
|
|
21167
|
+
@if (manipulable(b.occurrence)) {
|
|
21168
|
+
<span class="poignee is-haut" (pointerdown)="geste('resize-start', b.occurrence, $event, largeurColonne())"></span>
|
|
21169
|
+
<span class="poignee is-bas" (pointerdown)="geste('resize-end', b.occurrence, $event, largeurColonne())"></span>
|
|
21170
|
+
}
|
|
21171
|
+
</button>
|
|
21172
|
+
}
|
|
21173
|
+
</div>
|
|
21174
|
+
</div>
|
|
21175
|
+
|
|
21176
|
+
<div class="heures" [style.grid-template-columns]="colonnes()">
|
|
21177
|
+
<div class="heures__lbl">
|
|
21178
|
+
@for (h of heuresAffichees(); track h) { <div [style.height.px]="hauteurHeure()">{{ h }}h</div> }
|
|
21179
|
+
</div>
|
|
21180
|
+
@for (j of joursGrille(); track j.cle) {
|
|
21181
|
+
<div class="heures__col" #col [class.is-ferme]="j.ferme"
|
|
21182
|
+
(pointerdown)="creer(j.date, $event, col)">
|
|
21183
|
+
@for (h of heuresAffichees(); track h) {
|
|
21184
|
+
<div class="trait" [style.top.px]="(h - dayStart()) * hauteurHeure()"></div>
|
|
21185
|
+
<div class="trait is-demi" [style.top.px]="(h - dayStart()) * hauteurHeure() + hauteurHeure() / 2"></div>
|
|
21186
|
+
}
|
|
21187
|
+
@for (s of j.slots; track s.occurrence.id) {
|
|
21188
|
+
<button type="button" class="evt"
|
|
21189
|
+
[class.is-editable]="manipulable(s.occurrence)"
|
|
21190
|
+
[class.is-verrouille]="editable() && !manipulable(s.occurrence)"
|
|
21191
|
+
[style.--_c]="couleur(s.occurrence)"
|
|
21192
|
+
[style.top.px]="s.haut * hauteurTotale()"
|
|
21193
|
+
[style.height.px]="Math.max(16, s.hauteur * hauteurTotale() - 2)"
|
|
21194
|
+
[style.left]="'calc(' + (s.voie / s.voies * 100) + '% + 1px)'"
|
|
21195
|
+
[style.width]="'calc(' + (100 / s.voies) + '% - 3px)'"
|
|
21196
|
+
[title]="titre(s.occurrence) + (motifVerrou(s.occurrence) ? ' · ' + motifVerrou(s.occurrence) : '')"
|
|
21197
|
+
(pointerdown)="geste('move', s.occurrence, $event, col)"
|
|
21198
|
+
(keydown)="clavier(s.occurrence, $event)"
|
|
21199
|
+
(click)="clicEvenement(s.occurrence, $event)">
|
|
21200
|
+
<span class="evt__t">{{ s.occurrence.task.label }}@if (s.occurrence.recurring) { <span aria-hidden="true"> ↻</span> }</span>
|
|
21201
|
+
@if (s.hauteur * hauteurTotale() > 30) {
|
|
21202
|
+
<span class="evt__h">{{ heure(s.occurrence.start) }} – {{ heure(s.occurrence.end) }}</span>
|
|
21203
|
+
}
|
|
21204
|
+
@if (manipulable(s.occurrence)) {
|
|
21205
|
+
<span class="poignee is-haut" (pointerdown)="geste('resize-start', s.occurrence, $event, col)"></span>
|
|
21206
|
+
<span class="poignee is-bas" (pointerdown)="geste('resize-end', s.occurrence, $event, col)"></span>
|
|
21207
|
+
}
|
|
21208
|
+
</button>
|
|
21209
|
+
}
|
|
21210
|
+
@if (fantome(); as f) {
|
|
21211
|
+
@if (f.jour === j.cle) {
|
|
21212
|
+
<div class="fantome" [style.top.px]="f.haut" [style.height.px]="f.hauteur"
|
|
21213
|
+
[style.left.px]="2" [style.right.px]="2">{{ f.texte }}</div>
|
|
21214
|
+
}
|
|
21215
|
+
}
|
|
21216
|
+
@if (j.auj && maintenant() !== null) {
|
|
21217
|
+
<div class="maintenant" [style.top.px]="maintenant()"></div>
|
|
21218
|
+
}
|
|
21219
|
+
</div>
|
|
21220
|
+
}
|
|
21221
|
+
</div>
|
|
21222
|
+
</div>
|
|
21223
|
+
}
|
|
21224
|
+
}
|
|
21225
|
+
</div>
|
|
21226
|
+
</div>
|
|
21227
|
+
`, styles: [":host{display:flex;flex-direction:column;min-height:0;height:100%;font:var(--cad-font-size)/1.4 var(--cad-font);color:var(--cad-fg);background:var(--cad-bg)}.cal__corps{flex:1;min-height:0;display:flex}.cal__vue{flex:1;min-width:0;min-height:0;overflow:auto}.mois{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));grid-template-rows:auto;grid-auto-rows:minmax(84px,1fr);min-height:100%}.mois__jsem{padding:6px 8px;text-align:right;border-bottom:1px solid var(--cad-border-strong);background:var(--cad-bg-subtle);position:sticky;top:0;z-index:4;font:600 10.5px var(--cad-font-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--cad-fg-muted)}.mois__sem{grid-column:1 / -1;display:grid;grid-template-columns:repeat(7,minmax(0,1fr));position:relative;border-bottom:1px solid var(--cad-border);min-height:84px}.mois__jour{border-right:1px solid var(--cad-border);padding:3px;min-width:0;display:flex;flex-direction:column;gap:2px}.mois__jour:last-child{border-right:0}.mois__jour.is-hors,.mois__jour.is-ferme{background:var(--cad-bg-subtle)}.mois__n{font:11px var(--cad-font-mono);color:var(--cad-fg-secondary);text-align:right;padding:1px 3px;font-variant-numeric:tabular-nums;flex:none}.mois__jour.is-auj .mois__n{background:var(--cad-accent);color:var(--cad-fg-on-accent);border-radius:var(--cad-radius-sm);font-weight:600}.mois__reserve{flex:none}.calque{position:absolute;left:0;right:0;pointer-events:none;z-index:2}.bandeau{position:absolute;height:17px;border-radius:var(--cad-radius-sm);padding:0 7px;font-size:10.5px;line-height:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:auto;cursor:pointer;background:color-mix(in srgb,var(--_c) 22%,transparent);color:var(--_c);border:1px solid color-mix(in srgb,var(--_c) 42%,transparent)}.bandeau.is-avant{border-top-left-radius:0;border-bottom-left-radius:0;border-left-style:dashed}.bandeau.is-apres{border-top-right-radius:0;border-bottom-right-radius:0;border-right-style:dashed}.puce{display:flex;align-items:center;gap:5px;font-size:10.5px;padding:1px 5px;border-radius:var(--cad-radius-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;border-left:2px solid var(--_c);background:color-mix(in srgb,var(--_c) 13%,transparent);color:var(--_c)}.puce:hover{background:color-mix(in srgb,var(--_c) 22%,transparent)}.puce__h{font:9.5px var(--cad-font-mono);opacity:.85;flex:none;font-variant-numeric:tabular-nums}.puce__t{min-width:0;overflow:hidden;text-overflow:ellipsis}.plus{all:unset;box-sizing:border-box;font-size:10px;color:var(--cad-fg-muted);padding:1px 5px;cursor:pointer;border-radius:var(--cad-radius-sm)}.plus:hover{color:var(--cad-accent);background:var(--cad-hover)}.grille{min-width:0;display:flex;flex-direction:column;height:100%}.grille__hd{display:grid;position:sticky;top:0;z-index:5;background:var(--cad-bg-subtle);border-bottom:1px solid var(--cad-border-strong);flex:none}.grille__hd>div{padding:5px 6px;text-align:center;border-right:1px solid var(--cad-border);min-width:0}.grille__hd>div:last-child{border-right:0}.grille__d{font:10px var(--cad-font-mono);color:var(--cad-fg-muted);text-transform:uppercase;letter-spacing:.06em}.grille__n{font-size:16px;font-weight:600;letter-spacing:-.02em}.is-auj .grille__n{color:var(--cad-accent)}.bande{display:grid;border-bottom:1px solid var(--cad-border-strong);background:var(--cad-bg-subtle);flex:none}.bande__lbl{font:9px var(--cad-font-mono);color:var(--cad-fg-muted);padding:4px 6px;text-align:right;border-right:1px solid var(--cad-border)}.bande__zone{grid-column:2 / -1;position:relative;padding:2px 0}.heures{display:grid;position:relative;flex:1;min-height:0;overflow:auto}.heures__lbl{border-right:1px solid var(--cad-border)}.heures__lbl>div{font:9.5px var(--cad-font-mono);color:var(--cad-fg-muted);text-align:right;padding-right:6px;transform:translateY(-6px)}.heures__col{border-right:1px solid var(--cad-border);position:relative;min-width:0}.heures__col:last-child{border-right:0}.heures__col.is-ferme{background:var(--cad-bg-subtle)}.trait{position:absolute;left:0;right:0;border-top:1px solid var(--cad-border);pointer-events:none}.trait.is-demi{border-top-style:dotted;opacity:.55}.evt{position:absolute;border-radius:var(--cad-radius-sm);padding:1px 5px;overflow:hidden;cursor:pointer;border-left:3px solid var(--_c);font-size:10.5px;line-height:1.25;background:color-mix(in srgb,var(--_c) 15%,transparent);color:var(--_c)}.evt:hover{background:color-mix(in srgb,var(--_c) 26%,transparent)}.evt__t{display:block;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.evt__h{font:9px var(--cad-font-mono);opacity:.85}.maintenant{position:absolute;left:0;right:0;height:2px;background:var(--cad-danger);z-index:4;pointer-events:none}.maintenant:before{content:\"\";position:absolute;left:-3px;top:-3px;width:8px;height:8px;border-radius:50%;background:var(--cad-danger)}.agenda{display:flex;flex-direction:column}.ag__jour{display:grid;grid-template-columns:72px minmax(0,1fr);border-bottom:1px solid var(--cad-border)}.ag__d{padding:9px 11px;border-right:1px solid var(--cad-border);background:var(--cad-bg-subtle)}.ag__d b{display:block;font-size:18px;font-weight:600;letter-spacing:-.02em}.ag__d span{display:block;font:9.5px var(--cad-font-mono);color:var(--cad-fg-muted);text-transform:uppercase}.ag__d.is-auj b{color:var(--cad-accent)}.ag__l{padding:5px 0;min-width:0}.ag__e{all:unset;box-sizing:border-box;display:flex;align-items:center;gap:9px;padding:6px 11px;cursor:pointer;width:100%;min-height:34px}.ag__e:hover{background:var(--cad-hover)}.ag__e:focus-visible{outline:2px solid var(--cad-accent);outline-offset:-2px}.ag__p{width:8px;height:8px;border-radius:50%;background:var(--_c);flex:none}.ag__h{font:11px var(--cad-font-mono);color:var(--cad-fg-secondary);flex:none;width:92px;font-variant-numeric:tabular-nums}.ag__t{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag__c{margin-left:auto;font-size:10.5px;color:var(--cad-fg-muted);flex:none}.annee{display:grid;grid-template-columns:repeat(auto-fill,minmax(178px,1fr));gap:14px;padding:14px}.mm{border:1px solid var(--cad-border);border-radius:var(--cad-radius);padding:8px;background:var(--cad-surface)}.mm__t{font-size:12px;font-weight:600;margin-bottom:5px;text-transform:capitalize}.mm__g{display:grid;grid-template-columns:repeat(7,1fr);gap:1px}.mm__g>span,.mm__g>button{all:unset;box-sizing:border-box;text-align:center;font:9.5px var(--cad-font-mono);padding:2px 0;border-radius:3px;color:var(--cad-fg-secondary);font-variant-numeric:tabular-nums}.mm__g>span{color:var(--cad-fg-muted);font-weight:500}.mm__g>button{cursor:pointer;position:relative}.mm__g>button:hover{background:var(--cad-hover)}.mm__g>button.is-hors{opacity:.35}.mm__g>button.is-auj{background:var(--cad-accent);color:var(--cad-fg-on-accent);font-weight:600}.mm__pt{position:absolute;left:50%;bottom:0;transform:translate(-50%);width:3px;height:3px;border-radius:50%;background:var(--cad-accent)}.mm__g>button.is-auj .mm__pt{background:var(--cad-fg-on-accent)}.evt.is-editable,.bandeau.is-editable,.puce.is-editable{cursor:grab}.evt.is-editable:active,.bandeau.is-editable:active,.puce.is-editable:active{cursor:grabbing}.poignee{position:absolute;z-index:2}.evt .poignee{left:0;right:0;height:6px;cursor:ns-resize}.evt .poignee.is-haut{top:0}.evt .poignee.is-bas{bottom:0}.bandeau .poignee{top:0;bottom:0;width:6px;cursor:ew-resize}.bandeau .poignee.is-haut{left:0}.bandeau .poignee.is-bas{right:0}.poignee:after{content:\"\";position:absolute;inset:0}.evt.is-verrouille,.bandeau.is-verrouille,.puce.is-verrouille{cursor:not-allowed;position:relative}.evt.is-verrouille:after,.bandeau.is-verrouille:after,.puce.is-verrouille:after{content:\"\";position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(-45deg,currentColor 0 1px,transparent 1px 5px);opacity:.18}.fantome{position:absolute;z-index:6;pointer-events:none;border-radius:var(--cad-radius-sm);background:var(--cad-accent-soft);border:1px dashed var(--cad-accent);color:var(--cad-accent);font:10.5px var(--cad-font-mono);padding:1px 5px;overflow:hidden;white-space:nowrap}.vide{padding:28px 16px;color:var(--cad-fg-muted);text-align:center}@media(prefers-reduced-motion:reduce){*{transition:none!important}}\n"] }]
|
|
21228
|
+
}], ctorParameters: () => [], propDecorators: { plan: [{ type: i0.Input, args: [{ isSignal: true, alias: "plan", required: false }] }], sources: [{ type: i0.Input, args: [{ isSignal: true, alias: "sources", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }, { type: i0.Output, args: ["hiddenChange"] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }, { type: i0.Output, args: ["dateChange"] }], firstDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDay", required: false }] }], dayStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayStart", required: false }] }], dayEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayEnd", required: false }] }], hourHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "hourHeight", required: false }] }], maxPerDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPerDay", required: false }] }], workCalendar: [{ type: i0.Input, args: [{ isSignal: true, alias: "workCalendar", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], showNow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNow", required: false }] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }], dateClick: [{ type: i0.Output, args: ["dateClick"] }], rangeChange: [{ type: i0.Output, args: ["rangeChange"] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], snap: [{ type: i0.Input, args: [{ isSignal: true, alias: "snap", required: false }] }], eventMove: [{ type: i0.Output, args: ["eventMove"] }], eventResize: [{ type: i0.Output, args: ["eventResize"] }], eventCreate: [{ type: i0.Output, args: ["eventCreate"] }] } });
|
|
21229
|
+
|
|
19676
21230
|
/**
|
|
19677
21231
|
* Generated bundle index. Do not edit.
|
|
19678
21232
|
*/
|
|
19679
21233
|
|
|
19680
|
-
export { BanGeocoder, CAD_BREAKPOINTS, CAD_CHART_PALETTE, CAD_COLOR_PRESETS, CAD_DEFAULT_BASEMAPS, CAD_DEFAULT_PRIORITY_BREAKPOINTS, CAD_DIALOG_DATA, CAD_FILTER_OPERATORS, CAD_GEOCODER, CAD_ICONS, CAD_ICONS_CEREMA, CAD_ICONS_CORE, CAD_ICONS_DATA, CAD_ICONS_FILES, CAD_ICONS_GEO, CAD_ICONS_SUBSET, CAD_ICONS_TOKEN, CAD_ICONS_UI, CAD_ICON_ALIASES, CAD_ICON_CATEGORIES, CAD_MAPLIBRE_CSS_URL, CAD_MAP_CHILD, CAD_MAP_DEFAULTS, CAD_MAP_GLOBAL_STYLES, CAD_MAP_GLYPHS, CAD_MAP_PALETTE, CAD_MASK_PRESETS, CAD_MOBILE_BREAKPOINT, CAD_SELECT_POSITIONS, CAD_SELECT_TRIGGER_STYLES, CAD_SHEET_DATA, CAD_SHELL, CAD_TOUCH_DENSITY, CadAccordionComponent, CadAccordionPanelComponent, CadAppShellComponent, CadAvatarComponent, CadAvatarGroupComponent, CadBadgeComponent, CadBadgeDirective, CadBlockDirective, CadBlockOverlayComponent, CadBlockUiComponent, CadBottomNavComponent, CadBreadcrumbComponent, CadBreadcrumbItemDirective, CadBreakpointService, CadButtonComponent, CadButtonGroupComponent, CadCaptionDirective, CadCardComponent, CadCardDirective, CadCardHeaderDirective, CadCarouselComponent, CadCarouselItemDirective, CadCascadeSelectComponent, CadCellTemplateDirective, CadCenterComponent, CadChartComponent, CadCheckboxComponent, CadChipComponent, CadClusterComponent, CadColDirective, CadColorpickerComponent, CadColumnComponent, CadComboComponent, CadConfirmContentComponent, CadConfirmDialogComponent, CadConfirmService, CadContainerComponent, CadDataviewComponent, CadDataviewGridDirective, CadDataviewListDirective, CadDatepickerComponent, CadDialogComponent, CadDialogContainerComponent, CadDialogFooterDirective, CadDialogHeaderDirective, CadDialogRef, CadDialogService, CadDividerComponent, CadDrawerComponent, CadEditFocusDirective, CadEditorTemplateDirective, CadEmptyDirective, CadFieldComponent, CadFieldsetComponent, CadFieldsetLegendDirective, CadFilterChipComponent, CadFilterTemplateDirective, CadGeocodingService, CadGridComponent, CadGroupFooterDirective, CadGroupHeaderDirective, CadHeaderTemplateDirective, CadIconComponent, CadIconRegistry, CadIfDirective, CadImageComponent, CadInplaceComponent, CadInplaceContentDirective, CadInplaceDisplayDirective, CadInputDirective, CadJsonEditorComponent, CadKeyFilterDirective, CadListEmptyDirective, CadListFooterDirective, CadListHeaderDirective, CadListItemDirective, CadListboxComponent, CadMapBasemapRegistry, CadMapBasemapsComponent, CadMapCardComponent, CadMapChromeComponent, CadMapComponent, CadMapDrawEngine, CadMapLayerComponent, CadMapLayerGroupComponent, CadMapLayerToggleComponent, CadMapLegendComponent, CadMapMenuComponent, CadMapPanelComponent, CadMapPanels, CadMapRailComponent, CadMapRailItemComponent, CadMapSearchComponent, CadMapSectionComponent, CadMaskDirective, CadMenuComponent, CadMenubarComponent, CadMessageComponent, CadMeterComponent, CadMeterLabelDirective, CadMultiselectComponent, CadNavDrawerComponent, CadNavDrawerFooterDirective, CadNavDrawerHeaderDirective, CadNumberComponent, CadOptionListComponent, CadOrderlistComponent, CadOrgNodeDirective, CadOrgchartComponent, CadOverlayBase, CadPaginatorComponent, CadPanelComponent, CadPanelContentDirective, CadPanelHeaderDirective, CadPanelHeadingDirective, CadPasswordComponent, CadPicklistComponent, CadPopoverComponent, CadProgressComponent, CadRadioComponent, CadRadioGroupComponent, CadRatingComponent, CadRowActionsDirective, CadRowCountDirective, CadRowExpansionDirective, CadScrollTopComponent, CadSegmentedComponent, CadSelectBase, CadSelectComponent, CadSelectEmptyDirective, CadSelectFooterDirective, CadSelectHeaderDirective, CadSelectItemDirective, CadSelectSelectedDirective, CadSheetContainerComponent, CadSheetRef, CadSheetService, CadSidebarComponent, CadSidebarPanelDirective, CadSkeletonComponent, CadSliderComponent, CadSpacerComponent, CadSpeedDialComponent, CadSpinnerComponent, CadSplitButtonComponent, CadSplitComponent, CadSplitterComponent, CadSplitterPanelComponent, CadStackComponent, CadStepComponent, CadStepperComponent, CadSwitchComponent, CadTabComponent, CadTabContentDirective, CadTabLabelDirective, CadTableBase, CadTableComponent, CadTableEdit, CadTableEditUi, CadTableFooterDirective, CadTablePanelComponent, CadTableResponsive, CadTableSelection, CadTableSticky, CadTableVirtual, CadTabsComponent, CadTagComponent, CadTaskCardDirective, CadTaskRowDirective, CadTaskboardComponent, CadTimeAxis, CadTimelineComponent, CadTimelineContentDirective, CadTimelineMarkerDirective, CadTimelineOppositeDirective, CadToastComponent, CadToastItemDirective, CadToastService, CadToggleButtonComponent, CadTooltipComponent, CadTooltipDirective, CadTreeAdapter, CadTreeComponent, CadTreeNodeDirective, CadTreeselectComponent, CadUploadComponent, CadUploadContentDirective, CadUploadEmptyDirective, CadUploadItemDirective, CadValueAccessor, CadWorkCalendar, MAPLIBRE_MIN_CSS, addDays, addMonths, addYears, cadAggregate, cadApplyColumnOrder, cadApplyMask, cadArea, cadAutosizeWidth, cadBasemapThumbnail, cadBoundsOf, cadCategories, cadCentroid, cadColorExpr, cadCompare, cadCompareRank, cadCurrentOperator, cadDefaultMatchMode, cadDistance, cadDownloadCsv, cadDurationToMinutes, cadFilterData, cadFilterKindOf, cadFilterOperators, cadFilterRows, cadFindLinkCycle, cadFmtArea, cadFmtLength, cadFormatCell, cadFormatColor, cadFormatDuration, cadFormatSize, cadFrenchHolidays, cadGap, cadGeoJSONStats, cadGeometryKinds, cadHeaderMenuItems, cadHighlightJson, cadHslToRgb, cadHsvToRgb, cadId, cadIsGroupKey, cadIsRank, cadJsonErrorPos, cadLegendFromStyle, cadLength, cadMapEstVivante, cadMapLayerOf, cadMapLayersOf, cadMatch, cadMoveColumn, cadMoveItems, cadNormalize, cadNormalizeOptions, cadNormalizePlan, cadNumberExpr, cadPadBounds, cadParseColor, cadParseDuration, cadParseResponsive, cadPlanHeader, cadPlanTicks, cadPropsTable, cadRankBetween, cadRankFirst, cadRankLast, cadRankSequence, cadResolve, cadResolveResponsive, cadResponsive, cadRgbToHsl, cadRgbToHsv, cadRingArea, cadRowKey, cadRowPredicate, cadSameValue, cadScaleNext, cadScaleStart, cadSortData, cadStartResize, cadStyleToExpressions, cadUnmask, cadUnscheduled, cadValidateGeoJSON, cadValueForOperator, cadWriteField, compareDay, daysInMonth, endOfMonth, ensureMaplibreCss, formatDate, isBetweenDay, isSameDay, isSameMonth, isValidDate, isoOf, isoWeek, loadChartJs, loadMaplibre, monthNames, pad2, parseIso, parseWithFormat, provideCadIcons, startOfDay, startOfMonth, toDate, weekdayNames };
|
|
21234
|
+
export { BanGeocoder, CAD_BREAKPOINTS, CAD_CHART_PALETTE, CAD_COLOR_PRESETS, CAD_DEFAULT_BASEMAPS, CAD_DEFAULT_PRIORITY_BREAKPOINTS, CAD_DIALOG_DATA, CAD_FILTER_OPERATORS, CAD_GEOCODER, CAD_ICONS, CAD_ICONS_CEREMA, CAD_ICONS_CORE, CAD_ICONS_DATA, CAD_ICONS_FILES, CAD_ICONS_GEO, CAD_ICONS_SUBSET, CAD_ICONS_TOKEN, CAD_ICONS_UI, CAD_ICON_ALIASES, CAD_ICON_CATEGORIES, CAD_MAPLIBRE_CSS_URL, CAD_MAP_CHILD, CAD_MAP_DEFAULTS, CAD_MAP_GLOBAL_STYLES, CAD_MAP_GLYPHS, CAD_MAP_PALETTE, CAD_MASK_PRESETS, CAD_MOBILE_BREAKPOINT, CAD_RECURRENCE_INTERNE, CAD_SELECT_POSITIONS, CAD_SELECT_TRIGGER_STYLES, CAD_SHEET_DATA, CAD_SHELL, CAD_TOUCH_DENSITY, CadAccordionComponent, CadAccordionPanelComponent, CadAppShellComponent, CadAvatarComponent, CadAvatarGroupComponent, CadBadgeComponent, CadBadgeDirective, CadBlockDirective, CadBlockOverlayComponent, CadBlockUiComponent, CadBottomNavComponent, CadBreadcrumbComponent, CadBreadcrumbItemDirective, CadBreakpointService, CadButtonComponent, CadButtonGroupComponent, CadCalendarComponent, CadCaptionDirective, CadCardComponent, CadCardDirective, CadCardHeaderDirective, CadCarouselComponent, CadCarouselItemDirective, CadCascadeSelectComponent, CadCellTemplateDirective, CadCenterComponent, CadChartComponent, CadCheckboxComponent, CadChipComponent, CadClusterComponent, CadColDirective, CadColorpickerComponent, CadColumnComponent, CadComboComponent, CadConfirmContentComponent, CadConfirmDialogComponent, CadConfirmService, CadContainerComponent, CadDataviewComponent, CadDataviewGridDirective, CadDataviewListDirective, CadDatepickerComponent, CadDialogComponent, CadDialogContainerComponent, CadDialogFooterDirective, CadDialogHeaderDirective, CadDialogRef, CadDialogService, CadDividerComponent, CadDrawerComponent, CadEditFocusDirective, CadEditorTemplateDirective, CadEmptyDirective, CadFieldComponent, CadFieldsetComponent, CadFieldsetLegendDirective, CadFilterChipComponent, CadFilterTemplateDirective, CadGeocodingService, CadGridComponent, CadGroupFooterDirective, CadGroupHeaderDirective, CadHeaderTemplateDirective, CadIconComponent, CadIconRegistry, CadIfDirective, CadImageComponent, CadInplaceComponent, CadInplaceContentDirective, CadInplaceDisplayDirective, CadInputDirective, CadJsonEditorComponent, CadKeyFilterDirective, CadListEmptyDirective, CadListFooterDirective, CadListHeaderDirective, CadListItemDirective, CadListboxComponent, CadMapBasemapRegistry, CadMapBasemapsComponent, CadMapCardComponent, CadMapChromeComponent, CadMapComponent, CadMapDrawEngine, CadMapLayerComponent, CadMapLayerGroupComponent, CadMapLayerToggleComponent, CadMapLegendComponent, CadMapMenuComponent, CadMapPanelComponent, CadMapPanels, CadMapRailComponent, CadMapRailItemComponent, CadMapSearchComponent, CadMapSectionComponent, CadMaskDirective, CadMenuComponent, CadMenubarComponent, CadMessageComponent, CadMeterComponent, CadMeterLabelDirective, CadMultiselectComponent, CadNavDrawerComponent, CadNavDrawerFooterDirective, CadNavDrawerHeaderDirective, CadNumberComponent, CadOptionListComponent, CadOrderlistComponent, CadOrgNodeDirective, CadOrgchartComponent, CadOverlayBase, CadPaginatorComponent, CadPanelComponent, CadPanelContentDirective, CadPanelHeaderDirective, CadPanelHeadingDirective, CadPasswordComponent, CadPicklistComponent, CadPopoverComponent, CadProgressComponent, CadRadioComponent, CadRadioGroupComponent, CadRatingComponent, CadRowActionsDirective, CadRowCountDirective, CadRowExpansionDirective, CadScrollTopComponent, CadSegmentedComponent, CadSelectBase, CadSelectComponent, CadSelectEmptyDirective, CadSelectFooterDirective, CadSelectHeaderDirective, CadSelectItemDirective, CadSelectSelectedDirective, CadSheetContainerComponent, CadSheetRef, CadSheetService, CadSidebarComponent, CadSidebarPanelDirective, CadSkeletonComponent, CadSliderComponent, CadSpacerComponent, CadSpeedDialComponent, CadSpinnerComponent, CadSplitButtonComponent, CadSplitComponent, CadSplitterComponent, CadSplitterPanelComponent, CadStackComponent, CadStepComponent, CadStepperComponent, CadSwitchComponent, CadTabComponent, CadTabContentDirective, CadTabLabelDirective, CadTableBase, CadTableComponent, CadTableEdit, CadTableEditUi, CadTableFooterDirective, CadTablePanelComponent, CadTableResponsive, CadTableSelection, CadTableSticky, CadTableVirtual, CadTabsComponent, CadTagComponent, CadTaskCardDirective, CadTaskRowDirective, CadTaskboardComponent, CadTimeAxis, CadTimelineComponent, CadTimelineContentDirective, CadTimelineMarkerDirective, CadTimelineOppositeDirective, CadToastComponent, CadToastItemDirective, CadToastService, CadToggleButtonComponent, CadTooltipComponent, CadTooltipDirective, CadTreeAdapter, CadTreeComponent, CadTreeNodeDirective, CadTreeselectComponent, CadUploadComponent, CadUploadContentDirective, CadUploadEmptyDirective, CadUploadItemDirective, CadValueAccessor, CadWorkCalendar, MAPLIBRE_MIN_CSS, addDays, addMonths, addYears, cadAggregate, cadApplyColumnOrder, cadApplyMask, cadArea, cadAutosizeWidth, cadBasemapThumbnail, cadBoundsOf, cadCategories, cadCentroid, cadColorExpr, cadCompare, cadCompareRank, cadCurrentOperator, cadDebutSemaine, cadDefaultMatchMode, cadDistance, cadDownloadCsv, cadDurationToMinutes, cadEstBandeau, cadExpandOccurrences, cadFilterData, cadFilterKindOf, cadFilterOperators, cadFilterRows, cadFindLinkCycle, cadFmtArea, cadFmtLength, cadFormatCell, cadFormatColor, cadFormatDuration, cadFormatSize, cadFrenchHolidays, cadGap, cadGeoJSONStats, cadGeometryKinds, cadHeaderMenuItems, cadHighlightJson, cadHslToRgb, cadHsvToRgb, cadId, cadIsGroupKey, cadIsRank, cadJsonErrorPos, cadLegendFromStyle, cadLength, cadMapEstVivante, cadMapLayerOf, cadMapLayersOf, cadMatch, cadMinuit, cadMonthGrid, cadMoveColumn, cadMoveItems, cadNormalize, cadNormalizeOptions, cadNormalizePlan, cadNumberExpr, cadNumeroJour, cadPackDay, cadPadBounds, cadParseColor, cadParseDuration, cadParseRRule, cadParseResponsive, cadPlanHeader, cadPlanTicks, cadProjeterClavier, cadProjeterCreation, cadProjeterEdition, cadProjeterMois, cadPropsTable, cadRankBetween, cadRankFirst, cadRankLast, cadRankSequence, cadResolve, cadResolveResponsive, cadResponsive, cadRgbToHsl, cadRgbToHsv, cadRingArea, cadRowKey, cadRowPredicate, cadSameValue, cadScaleNext, cadScaleStart, cadSortData, cadStartResize, cadStyleToExpressions, cadUnmask, cadUnscheduled, cadValidateGeoJSON, cadValueForOperator, cadWeekBands, cadWriteField, compareDay, daysInMonth, endOfMonth, ensureMaplibreCss, formatDate, isBetweenDay, isSameDay, isSameMonth, isValidDate, isoOf, isoWeek, loadChartJs, loadMaplibre, monthNames, pad2, parseIso, parseWithFormat, provideCadIcons, startOfDay, startOfMonth, toDate, weekdayNames };
|
|
19681
21235
|
//# sourceMappingURL=cadriciel-ui.mjs.map
|