@synapxlab/ladder-timeline 0.0.1 → 0.1.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.
@@ -0,0 +1,1033 @@
1
+ var et = Object.defineProperty;
2
+ var st = (s, t, e) => t in s ? et(s, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : s[t] = e;
3
+ var f = (s, t, e) => st(s, typeof t != "symbol" ? t + "" : t, e);
4
+ function I(s) {
5
+ return new Date(s.getFullYear(), s.getMonth(), s.getDate());
6
+ }
7
+ function R(s) {
8
+ return new Date(s.getTime());
9
+ }
10
+ function j(s, t) {
11
+ const e = R(s);
12
+ return e.setDate(e.getDate() + t), e;
13
+ }
14
+ function O(s, t = 1) {
15
+ const e = I(s), r = (e.getDay() - t + 7) % 7;
16
+ return j(e, -r);
17
+ }
18
+ function $(s) {
19
+ return j(s, 6);
20
+ }
21
+ function C(s) {
22
+ return s instanceof Date && !isNaN(s.getTime());
23
+ }
24
+ const U = "weekchange", it = "navigate", rt = "select";
25
+ function at(s) {
26
+ return new CustomEvent(U, {
27
+ detail: s,
28
+ bubbles: !0,
29
+ composed: !0
30
+ });
31
+ }
32
+ function nt(s) {
33
+ return new CustomEvent(rt, {
34
+ detail: s,
35
+ bubbles: !0,
36
+ composed: !0
37
+ });
38
+ }
39
+ function lt(s) {
40
+ return new CustomEvent(it, {
41
+ detail: s,
42
+ bubbles: !0,
43
+ composed: !0
44
+ });
45
+ }
46
+ function ot(s, t) {
47
+ const e = (i) => t(i);
48
+ return s.addEventListener(U, e), () => s.removeEventListener(U, e);
49
+ }
50
+ const ct = -271820, ht = 275759;
51
+ function b(s) {
52
+ return s >= ct && s <= ht;
53
+ }
54
+ function _(s) {
55
+ const t = Math.floor(s), e = s - t, i = new Date(t, 0, 1).getTime(), r = new Date(t + 1, 0, 1).getTime();
56
+ return new Date(Math.round(i + e * (r - i)));
57
+ }
58
+ function v(s) {
59
+ const t = s.getFullYear(), e = new Date(t, 0, 1).getTime(), i = new Date(t + 1, 0, 1).getTime();
60
+ return t + (s.getTime() - e) / (i - e);
61
+ }
62
+ function y() {
63
+ return v(/* @__PURE__ */ new Date());
64
+ }
65
+ function q(s) {
66
+ const t = Math.abs(s);
67
+ return t >= 1e9 ? `${(s / 1e9).toFixed(2)} Ga` : t >= 1e6 ? `${(s / 1e6).toFixed(2)} Ma` : t >= 1e3 ? `${(s / 1e3).toFixed(1)} ka` : s < 0 ? `${Math.round(s)}` : `+${Math.round(s)}`;
68
+ }
69
+ function w(s) {
70
+ const t = Math.round(s);
71
+ return t < 0 ? `${t} av.` : `${t}`;
72
+ }
73
+ function L(s, t, e) {
74
+ return {
75
+ id: s,
76
+ label: t,
77
+ step: e,
78
+ snap(i) {
79
+ return Math.round(i / e) * e;
80
+ },
81
+ add(i, r) {
82
+ return i + r * e;
83
+ },
84
+ build(i, r, a, o) {
85
+ const n = Math.floor(r / 2), h = this.snap(i) - n * e, c = y(), d = [];
86
+ for (let u = 0; u < r; u++) {
87
+ const l = h + u * e;
88
+ d.push({
89
+ year: l,
90
+ label: q(l),
91
+ sublabel: q(l + e) + " →",
92
+ header: "",
93
+ compactLabel: q(l),
94
+ isCurrent: Math.abs(c - l) < e / 2,
95
+ isSelected: Math.abs(this.snap(a) - l) < e / 2
96
+ });
97
+ }
98
+ return d;
99
+ }
100
+ };
101
+ }
102
+ function H(s, t, e) {
103
+ return {
104
+ id: s,
105
+ label: t,
106
+ step: e,
107
+ snap(i) {
108
+ return Math.floor(i / e) * e;
109
+ },
110
+ add(i, r) {
111
+ return i + r * e;
112
+ },
113
+ build(i, r, a, o) {
114
+ const n = Math.floor(r / 2), h = this.snap(i) - n * e, c = y(), d = [];
115
+ for (let u = 0; u < r; u++) {
116
+ const l = h + u * e, m = Math.round(l), M = m + e, p = e === 1e3 ? `${w(m)} → ${w(M)}` : e === 100 ? `${w(m)}s` : e === 10 ? `${w(m)}s` : `${w(m)}`, E = e === 1e3 ? "millénaire" : e === 100 ? "siècle" : e === 10 ? "décennie" : "", g = String(Math.abs(m) % (e * 10) | 0);
117
+ d.push({
118
+ year: l,
119
+ label: p,
120
+ sublabel: E,
121
+ header: "",
122
+ compactLabel: g,
123
+ isCurrent: c >= l && c < l + e,
124
+ isSelected: a >= l && a < l + e
125
+ });
126
+ }
127
+ return d;
128
+ }
129
+ };
130
+ }
131
+ const dt = {
132
+ id: "year",
133
+ label: "Année",
134
+ step: 1,
135
+ snap(s) {
136
+ return Math.floor(s);
137
+ },
138
+ add(s, t) {
139
+ return s + t;
140
+ },
141
+ build(s, t, e, i) {
142
+ const r = Math.floor(t / 2), a = Math.floor(s) - r, o = y(), n = [];
143
+ for (let h = 0; h < t; h++) {
144
+ const c = a + h, d = c % 10 === 0;
145
+ n.push({
146
+ year: c,
147
+ label: w(c),
148
+ sublabel: d ? `${c}s` : "",
149
+ header: d ? `${c}s` : "",
150
+ compactLabel: String(Math.abs(c) % 100),
151
+ // "0".."99"
152
+ isCurrent: Math.floor(o) === c,
153
+ isSelected: Math.floor(e) === c
154
+ });
155
+ }
156
+ return n;
157
+ }
158
+ }, ut = {
159
+ id: "month",
160
+ label: "Mois",
161
+ step: 1 / 12,
162
+ snap(s) {
163
+ if (!b(s)) return Math.floor(s * 12) / 12;
164
+ const t = _(s);
165
+ return v(new Date(t.getFullYear(), t.getMonth(), 1));
166
+ },
167
+ add(s, t) {
168
+ if (!b(s)) return s + t / 12;
169
+ const e = _(s);
170
+ return v(new Date(e.getFullYear(), e.getMonth() + t, 1));
171
+ },
172
+ build(s, t, e, i) {
173
+ const r = Math.floor(t / 2), a = b(s) ? _(this.snap(s)) : null, o = /* @__PURE__ */ new Date(), n = new Date(o.getFullYear(), o.getMonth(), 1).getTime(), h = b(e) ? new Date(_(this.snap(e)).getFullYear(), _(this.snap(e)).getMonth(), 1).getTime() : null, c = new Intl.DateTimeFormat(i, { month: "long" }), d = new Intl.DateTimeFormat(i, { month: "short" }), u = [];
174
+ for (let l = -r; l < t - r; l++) {
175
+ if (!a) {
176
+ const D = this.snap(s) + l / 12;
177
+ u.push({ year: D, label: "—", sublabel: "", header: "", compactLabel: "—", isCurrent: !1, isSelected: !1 });
178
+ continue;
179
+ }
180
+ const m = new Date(a.getFullYear(), a.getMonth() + l, 1), M = c.format(m), p = d.format(m), E = M.charAt(0).toUpperCase() + M.slice(1), g = p.charAt(0).toUpperCase() + p.slice(1), S = m.getMonth() === 0;
181
+ u.push({
182
+ year: v(m),
183
+ label: S ? `${g} ${m.getFullYear()}` : g,
184
+ sublabel: String(m.getFullYear()),
185
+ header: E,
186
+ compactLabel: String(m.getMonth() + 1),
187
+ // 1..12
188
+ isCurrent: m.getTime() === n,
189
+ isSelected: h !== null && m.getTime() === h
190
+ });
191
+ }
192
+ return u;
193
+ }
194
+ }, P = 7 / 365.25;
195
+ function x(s, t = 1) {
196
+ const i = (s.getDay() - t + 7) % 7;
197
+ return new Date(s.getFullYear(), s.getMonth(), s.getDate() - i);
198
+ }
199
+ function ft(s) {
200
+ const t = new Date(s.getFullYear(), s.getMonth(), s.getDate());
201
+ t.setDate(t.getDate() + 4 - (t.getDay() || 7));
202
+ const e = new Date(t.getFullYear(), 0, 1);
203
+ return Math.ceil(((t.getTime() - e.getTime()) / 864e5 + 1) / 7);
204
+ }
205
+ const mt = {
206
+ id: "week",
207
+ label: "Semaine",
208
+ step: P,
209
+ snap(s) {
210
+ return b(s) ? v(x(_(s))) : s;
211
+ },
212
+ add(s, t) {
213
+ if (!b(s)) return s + t * P;
214
+ const e = _(s);
215
+ return v(new Date(e.getFullYear(), e.getMonth(), e.getDate() + t * 7));
216
+ },
217
+ build(s, t, e, i) {
218
+ const r = Math.floor(t / 2), a = x(/* @__PURE__ */ new Date()).getTime(), o = b(e) ? x(_(e)).getTime() : null, n = new Intl.DateTimeFormat(i, { day: "numeric", month: "short" }), h = new Intl.DateTimeFormat(i, { month: "long" }), c = [];
219
+ if (!b(s)) {
220
+ for (let u = -r; u < t - r; u++) {
221
+ const l = this.snap(s) + u * P;
222
+ c.push({ year: l, label: "—", sublabel: "", header: "", compactLabel: "—", isCurrent: !1, isSelected: !1 });
223
+ }
224
+ return c;
225
+ }
226
+ const d = x(_(s));
227
+ for (let u = -r; u < t - r; u++) {
228
+ const l = new Date(d.getFullYear(), d.getMonth(), d.getDate() + u * 7), m = new Date(l.getFullYear(), l.getMonth(), l.getDate() + 6), M = n.formatRange?.(l, m) ?? `${n.format(l)} – ${n.format(m)}`, p = h.format(l);
229
+ c.push({
230
+ year: v(l),
231
+ label: M,
232
+ sublabel: `S${ft(l)} · ${l.getFullYear()}`,
233
+ header: p.charAt(0).toUpperCase() + p.slice(1),
234
+ compactLabel: String(l.getDate()),
235
+ // jour du début de semaine
236
+ isCurrent: l.getTime() === a,
237
+ isSelected: o !== null && l.getTime() === o
238
+ });
239
+ }
240
+ return c;
241
+ }
242
+ }, X = 1 / 365.25, pt = {
243
+ id: "day",
244
+ label: "Jour",
245
+ step: X,
246
+ snap(s) {
247
+ if (!b(s)) return s;
248
+ const t = _(s);
249
+ return v(new Date(t.getFullYear(), t.getMonth(), t.getDate()));
250
+ },
251
+ add(s, t) {
252
+ if (!b(s)) return s + t * X;
253
+ const e = _(s);
254
+ return v(new Date(e.getFullYear(), e.getMonth(), e.getDate() + t));
255
+ },
256
+ build(s, t, e, i) {
257
+ const r = Math.floor(t / 2), a = /* @__PURE__ */ new Date(), o = new Date(a.getFullYear(), a.getMonth(), a.getDate()).getTime(), n = b(e) ? new Date(_(e).getFullYear(), _(e).getMonth(), _(e).getDate()).getTime() : null, h = new Intl.DateTimeFormat(i, { weekday: "short" }), c = new Intl.DateTimeFormat(i, { month: "long" }), d = [];
258
+ if (!b(s)) {
259
+ for (let l = -r; l < t - r; l++)
260
+ d.push({ year: this.snap(s) + l * X, label: "—", sublabel: "", header: "", compactLabel: "—", isCurrent: !1, isSelected: !1 });
261
+ return d;
262
+ }
263
+ const u = _(this.snap(s));
264
+ for (let l = -r; l < t - r; l++) {
265
+ const m = new Date(u.getFullYear(), u.getMonth(), u.getDate() + l), M = h.format(m), p = c.format(m);
266
+ d.push({
267
+ year: v(m),
268
+ label: `${M.charAt(0).toUpperCase()}${M.slice(1)} ${m.getDate()}`,
269
+ sublabel: `${p.slice(0, 3)} ${m.getFullYear()}`,
270
+ header: p.charAt(0).toUpperCase() + p.slice(1),
271
+ compactLabel: String(m.getDate()),
272
+ // jour du mois
273
+ isCurrent: m.getTime() === o,
274
+ isSelected: n !== null && m.getTime() === n
275
+ });
276
+ }
277
+ return d;
278
+ }
279
+ }, gt = 365.25 * 24 * 3600 * 1e3;
280
+ function K(s, t, e, i, r) {
281
+ const a = e / gt;
282
+ return {
283
+ id: s,
284
+ label: t,
285
+ step: a,
286
+ snap(o) {
287
+ if (!b(o)) return o;
288
+ const n = _(o).getTime();
289
+ return v(new Date(Math.floor(n / e) * e));
290
+ },
291
+ add(o, n) {
292
+ if (!b(o)) return o + n * a;
293
+ const h = _(o).getTime();
294
+ return v(new Date(h + n * e));
295
+ },
296
+ build(o, n, h, c) {
297
+ const d = Math.floor(n / 2), u = Date.now(), l = Math.floor(u / e) * e, m = b(h) ? Math.floor(_(h).getTime() / e) * e : null, M = new Intl.DateTimeFormat(c, { day: "numeric", month: "short", year: "numeric" }), p = [];
298
+ if (!b(o)) {
299
+ for (let g = -d; g < n - d; g++)
300
+ p.push({ year: this.snap(o) + g * a, label: "—", sublabel: "", header: "", compactLabel: "—", isCurrent: !1, isSelected: !1 });
301
+ return p;
302
+ }
303
+ const E = Math.floor(_(this.snap(o)).getTime() / e) * e;
304
+ for (let g = -d; g < n - d; g++) {
305
+ const S = E + g * e, D = new Date(S);
306
+ p.push({
307
+ year: v(D),
308
+ label: i(D),
309
+ sublabel: M.format(D),
310
+ header: i(D),
311
+ compactLabel: r(D),
312
+ isCurrent: S === l,
313
+ isSelected: m !== null && S === m
314
+ });
315
+ }
316
+ return p;
317
+ }
318
+ };
319
+ }
320
+ const _t = K(
321
+ "hour",
322
+ "Heure",
323
+ 3600 * 1e3,
324
+ (s) => `${String(s.getHours()).padStart(2, "0")}:00`,
325
+ (s) => String(s.getHours())
326
+ ), bt = K(
327
+ "minute",
328
+ "Minute",
329
+ 60 * 1e3,
330
+ (s) => `${String(s.getHours()).padStart(2, "0")}:${String(s.getMinutes()).padStart(2, "0")}`,
331
+ (s) => `:${String(s.getMinutes()).padStart(2, "0")}`
332
+ ), Mt = K(
333
+ "second",
334
+ "Seconde",
335
+ 1e3,
336
+ (s) => `${String(s.getHours()).padStart(2, "0")}:${String(s.getMinutes()).padStart(2, "0")}:${String(s.getSeconds()).padStart(2, "0")}`,
337
+ (s) => `:${String(s.getSeconds()).padStart(2, "0")}`
338
+ );
339
+ function vt(s, t, e, i) {
340
+ const r = 1 / i / 31557600;
341
+ return {
342
+ id: s,
343
+ label: t,
344
+ step: r,
345
+ snap(a) {
346
+ return a;
347
+ },
348
+ add(a, o) {
349
+ return a + o * r;
350
+ },
351
+ build(a, o, n, h) {
352
+ const c = Math.floor(o / 2), d = Math.round((n - a) / r), u = [];
353
+ for (let l = -c; l < o - c; l++)
354
+ u.push({
355
+ year: a + l * r,
356
+ label: `${l >= 0 ? "+" : ""}${l} ${e}`,
357
+ sublabel: "",
358
+ header: `Δ${e}`,
359
+ compactLabel: `${l >= 0 ? "+" : ""}${l}`,
360
+ isCurrent: l === 0,
361
+ isSelected: l === d
362
+ });
363
+ return u;
364
+ }
365
+ };
366
+ }
367
+ const Et = vt("ms", "Milliseconde", "ms", 1e3), N = [
368
+ L("Ga", "Milliard d'années", 1e9),
369
+ L("100Ma", "Centaine de millions d'années", 1e8),
370
+ L("Ma", "Million d'années", 1e6),
371
+ L("100ka", "Centaine de milliers d'années", 1e5),
372
+ L("10ka", "10 000 ans", 1e4),
373
+ H("millennium", "Millénaire", 1e3),
374
+ H("century", "Siècle", 100),
375
+ H("decade", "Décennie", 10),
376
+ dt,
377
+ ut,
378
+ mt,
379
+ pt,
380
+ _t,
381
+ bt,
382
+ Mt,
383
+ Et
384
+ ], St = N.reduce((s, t) => (s[t.id] = t, s), {});
385
+ function z(s) {
386
+ const t = St[s];
387
+ if (!t) throw new Error(`[Scale] unknown id: ${s}`);
388
+ return t;
389
+ }
390
+ const Dt = (() => {
391
+ const s = {};
392
+ return N.forEach((t, e) => {
393
+ s[t.id] = e;
394
+ }), s;
395
+ })();
396
+ function Y(s) {
397
+ return Dt[s];
398
+ }
399
+ function A(s) {
400
+ return N[s];
401
+ }
402
+ const It = N.length, yt = 51, k = 8, Ct = 0.9, B = 0.08, kt = 0.82, wt = 0.4, Lt = 250, F = {
403
+ firstDayOfWeek: 1,
404
+ // Suit la langue du navigateur ; fallback 'fr-FR' en SSR / environnement sans navigator
405
+ locale: typeof navigator < "u" ? navigator.language : "fr-FR",
406
+ compact: !1,
407
+ scale: "week"
408
+ }, W = class W {
409
+ // ─── Constructor ────────────────────────────────────────────────────────────
410
+ constructor(t) {
411
+ // Config
412
+ f(this, "container");
413
+ f(this, "firstDayOfWeek");
414
+ f(this, "locale");
415
+ f(this, "compact");
416
+ f(this, "theme");
417
+ f(this, "storageKey");
418
+ f(this, "onWeekChangeCb");
419
+ f(this, "onWeekPreviewCb");
420
+ f(this, "onItemChangeCb");
421
+ f(this, "onItemPreviewCb");
422
+ f(this, "onMarkerClickCb");
423
+ // Markers
424
+ f(this, "markers", []);
425
+ // Dark mode auto
426
+ f(this, "_darkMq");
427
+ f(this, "_darkMqHandler");
428
+ // State — cursor en decimal year
429
+ f(this, "selectedYear");
430
+ f(this, "referenceYear");
431
+ f(this, "scale");
432
+ f(this, "displayMode");
433
+ // Bornes (immutable après construction)
434
+ f(this, "minYear");
435
+ f(this, "maxYear");
436
+ f(this, "coarsestScaleIdx");
437
+ // index dans SCALES — petit = grossier (Ga=0)
438
+ f(this, "finestScaleIdx");
439
+ // index dans SCALES — grand = fin (ns=17)
440
+ // DOM
441
+ f(this, "root");
442
+ f(this, "listWrapper");
443
+ f(this, "listEl");
444
+ f(this, "centerMark");
445
+ f(this, "liveRegion");
446
+ // Drag
447
+ f(this, "isDragging", !1);
448
+ f(this, "dragStartX", 0);
449
+ f(this, "dragStartScroll", 0);
450
+ // Momentum
451
+ f(this, "velX", 0);
452
+ f(this, "lastMoveX", 0);
453
+ f(this, "lastMoveT", 0);
454
+ // RAF
455
+ f(this, "rafId");
456
+ // Molette — #4
457
+ f(this, "wheelDebounceId");
458
+ // Extension proactive
459
+ f(this, "_isExtending", !1);
460
+ // Spring snap
461
+ f(this, "springRafId");
462
+ // Pinch (touch multi-fingers)
463
+ f(this, "_isPinching", !1);
464
+ f(this, "_pinchInitialDist", 0);
465
+ // Misc
466
+ f(this, "cleanupFns", []);
467
+ f(this, "resizeObserver");
468
+ f(this, "calendarAdapter");
469
+ if (!t.container) throw new Error("[LadderTimeline] container is required");
470
+ this.container = t.container, this.firstDayOfWeek = t.firstDayOfWeek ?? F.firstDayOfWeek, this.locale = t.locale ?? F.locale, this.compact = t.compact ?? F.compact, this.theme = t.theme ?? "light", this.storageKey = t.storageKey, this.onWeekChangeCb = t.onWeekChange, this.onWeekPreviewCb = t.onWeekPreview, this.onItemChangeCb = t.onItemChange, this.onItemPreviewCb = t.onItemPreview, this.onMarkerClickCb = t.onMarkerClick, t.markers && (this.markers = t.markers.slice()), this.minYear = typeof t.minYear == "number" ? t.minYear : C(t.minDate) ? v(t.minDate) : -1 / 0, this.maxYear = typeof t.maxYear == "number" ? t.maxYear : C(t.maxDate) ? v(t.maxDate) : 1 / 0, this.coarsestScaleIdx = t.maxScale ? Y(t.maxScale) : 0, this.finestScaleIdx = t.minScale ? Y(t.minScale) : It - 1;
471
+ const e = t.scale ?? F.scale;
472
+ this.scale = z(this._clampScaleId(e)), this.displayMode = t.displayMode ?? "expanded";
473
+ const i = y();
474
+ if (this.selectedYear = C(t.selectedDate) ? v(I(t.selectedDate)) : i, this.referenceYear = C(t.referenceDate) ? v(I(t.referenceDate)) : this.selectedYear, this.storageKey && !C(t.selectedDate)) {
475
+ const r = this._readStorage();
476
+ r !== null && (this.selectedYear = r, this.referenceYear = r);
477
+ }
478
+ if (this.selectedYear = this._snapAndClamp(this.selectedYear), this.referenceYear = this._snapAndClamp(this.referenceYear), this.scale.step < 1 && !b(this.selectedYear)) {
479
+ const r = this._snapAndClamp(y());
480
+ this.selectedYear = r, this.referenceYear = r;
481
+ }
482
+ this.render();
483
+ }
484
+ // ─── Public API ─────────────────────────────────────────────────────────────
485
+ setDate(t) {
486
+ if (!C(t)) return;
487
+ const e = v(I(t));
488
+ this.selectedYear = this._snapAndClamp(e), this.referenceYear = this.selectedYear, this._updateListDOM(!1), this._emitItemChange(this.selectedYear);
489
+ }
490
+ getDate() {
491
+ return b(this.selectedYear) ? _(this.selectedYear) : /* @__PURE__ */ new Date();
492
+ }
493
+ goToToday() {
494
+ const t = this._snapAndClamp(y());
495
+ this.selectedYear = t, this.referenceYear = t, this._updateListDOM(!0), this._emitNavigate("today"), this._emitItemChange(this.selectedYear);
496
+ }
497
+ goToPrevious() {
498
+ const t = this._clampYear(this.scale.add(this.referenceYear, -1));
499
+ t !== this.referenceYear && (this.referenceYear = t, this.selectedYear = t, this._updateListDOM(!0), this._emitNavigate("prev"), this._emitItemChange(this.selectedYear));
500
+ }
501
+ goToNext() {
502
+ const t = this._clampYear(this.scale.add(this.referenceYear, 1));
503
+ t !== this.referenceYear && (this.referenceYear = t, this.selectedYear = t, this._updateListDOM(!0), this._emitNavigate("next"), this._emitItemChange(this.selectedYear));
504
+ }
505
+ /** Change l'échelle courante et re-rend la barre alignée dessus */
506
+ setScale(t) {
507
+ const e = this._clampScaleId(t);
508
+ if (this.scale.id === e) return;
509
+ this.scale = z(e);
510
+ let i = this.selectedYear;
511
+ this.scale.step < 1 && !b(i) && (i = y()), i = this._snapAndClamp(i), this.selectedYear = i, this.referenceYear = i, this._updateListDOM(!1), this._emitItemChange(this.selectedYear);
512
+ }
513
+ getScale() {
514
+ return this.scale.id;
515
+ }
516
+ setDisplayMode(t) {
517
+ if (this.displayMode === t) return;
518
+ this.displayMode = t, this._renderItems(), this._syncCenterMark();
519
+ const e = this.listEl.querySelector(".lt__item--selected");
520
+ e && (this._scrollToItem(e, !1), this._applyScaleEffect());
521
+ }
522
+ getDisplayMode() {
523
+ return this.displayMode;
524
+ }
525
+ // ─── Markers ───────────────────────────────────────────────────────────────
526
+ /** Remplace tous les markers. */
527
+ setMarkers(t) {
528
+ this.markers = t.slice(), this._renderItems();
529
+ }
530
+ /** Ajoute un marker (ne dédoublonne pas sur id — c'est à l'appelant). */
531
+ addMarker(t) {
532
+ this.markers.push(t), this._renderItems();
533
+ }
534
+ /** Supprime tous les markers ayant cet id. No-op si id absent ou inconnu. */
535
+ removeMarker(t) {
536
+ const e = this.markers.length;
537
+ this.markers = this.markers.filter((i) => i.id !== t), this.markers.length !== e && this._renderItems();
538
+ }
539
+ /** Renvoie une copie de la liste courante. */
540
+ getMarkers() {
541
+ return this.markers.slice();
542
+ }
543
+ /** Renvoie les bornes effectives configurées sur cette instance. */
544
+ getBounds() {
545
+ const t = A(this.finestScaleIdx), e = A(this.coarsestScaleIdx);
546
+ return {
547
+ minYear: this.minYear,
548
+ maxYear: this.maxYear,
549
+ minScale: t?.id ?? "ms",
550
+ maxScale: e?.id ?? "Ga"
551
+ };
552
+ }
553
+ // ─── Clamp helpers ──────────────────────────────────────────────────────────
554
+ _clampYear(t) {
555
+ return t < this.minYear ? this.minYear : t > this.maxYear ? this.maxYear : t;
556
+ }
557
+ /**
558
+ * Snap puis clamp un year en garantissant qu'on reste sur une frontière
559
+ * d'échelle (sauf bornes pathologiques sans aucune frontière dans la plage).
560
+ * Cas géré : snap (souvent Math.floor) tombe juste sous minYear → on monte
561
+ * d'une unité d'échelle pour retomber dans les bornes.
562
+ */
563
+ _snapAndClamp(t) {
564
+ const e = this.scale.snap(this._clampYear(t));
565
+ if (e < this.minYear) {
566
+ const i = this.scale.add(e, 1);
567
+ if (i <= this.maxYear) return i;
568
+ }
569
+ if (e > this.maxYear) {
570
+ const i = this.scale.add(e, -1);
571
+ if (i >= this.minYear) return i;
572
+ }
573
+ return this._clampYear(e);
574
+ }
575
+ _clampScaleId(t) {
576
+ const e = Y(t), i = Math.max(this.coarsestScaleIdx, Math.min(this.finestScaleIdx, e));
577
+ return A(i)?.id ?? t;
578
+ }
579
+ getEventTarget() {
580
+ return this.container;
581
+ }
582
+ connectAdapter(t) {
583
+ this.calendarAdapter = t;
584
+ }
585
+ render() {
586
+ this._cleanup(), this.container.innerHTML = "", this._buildDOM(), this._setupTheme(), this._bindDragEvents(), this._bindWheelEvents(), this._bindKeyboardEvents(), this._bindPinchEvents(), this._updateListDOM(!1), this._initResizeObserver();
587
+ }
588
+ destroy() {
589
+ this._cleanup(), this.resizeObserver?.disconnect(), this.rafId !== void 0 && cancelAnimationFrame(this.rafId), this.springRafId !== void 0 && cancelAnimationFrame(this.springRafId), this.wheelDebounceId !== void 0 && clearTimeout(this.wheelDebounceId), this._darkMq && this._darkMqHandler && this._darkMq.removeEventListener("change", this._darkMqHandler), this.container.innerHTML = "";
590
+ }
591
+ on(t, e) {
592
+ return this.container.addEventListener(t, e), () => this.container.removeEventListener(t, e);
593
+ }
594
+ // ─── DOM ────────────────────────────────────────────────────────────────────
595
+ _buildDOM() {
596
+ this.root = document.createElement("div"), this.root.className = ["lt", this.compact ? "lt--compact" : ""].filter(Boolean).join(" "), this.root.setAttribute("role", "toolbar"), this.root.setAttribute("aria-label", "Navigation temporelle");
597
+ const t = document.createElement("div");
598
+ t.className = "lt__header", this.listWrapper = document.createElement("div"), this.listWrapper.className = "lt__list-wrapper", this.listWrapper.tabIndex = 0, this.listWrapper.setAttribute("aria-label", "Glisser ou utiliser la molette pour naviguer"), this.centerMark = document.createElement("div"), this.centerMark.className = "lt__center-mark", this.centerMark.setAttribute("aria-hidden", "true"), this.listWrapper.appendChild(this.centerMark), this.listEl = document.createElement("ol"), this.listEl.className = "lt__list", this.listEl.setAttribute("role", "listbox"), this.listEl.setAttribute("aria-orientation", "horizontal"), this.listWrapper.appendChild(this.listEl), t.appendChild(this.listWrapper), this.root.appendChild(t), this.liveRegion = document.createElement("div"), this.liveRegion.className = "lt__live", this.liveRegion.setAttribute("aria-live", "polite"), this.liveRegion.setAttribute("aria-atomic", "true"), this.root.appendChild(this.liveRegion), this.container.appendChild(this.root);
599
+ }
600
+ // ─── List DOM ──────────────────────────────────────────────────────────────
601
+ /**
602
+ * Rend tous les <li> dans listEl à partir de referenceYear / selectedYear
603
+ * et de l'échelle courante. Applique le padding carousel immédiatement.
604
+ */
605
+ _renderItems() {
606
+ const t = this.scale.build(
607
+ this.referenceYear,
608
+ yt,
609
+ this.selectedYear,
610
+ this.locale
611
+ ), e = A(Y(this.scale.id) - 1), i = this.scale.step * 0.5;
612
+ this.listEl.innerHTML = "";
613
+ let r = "__init__";
614
+ for (const a of t) {
615
+ const o = a.header !== "" && a.header !== r;
616
+ r = a.header;
617
+ const n = !e || Math.abs(e.snap(a.year) - a.year) < i, h = this.displayMode === "compact", c = h && !n && !a.isSelected, d = document.createElement("li");
618
+ d.className = "lt__item" + (a.isSelected ? " lt__item--selected" : "") + (a.isCurrent ? " lt__item--current" : "") + (h ? n ? " lt__item--major" : " lt__item--minor" : ""), d.setAttribute("role", "option"), d.setAttribute("aria-selected", String(a.isSelected)), d.setAttribute("tabindex", a.isSelected ? "0" : "-1"), d.setAttribute("data-year", String(a.year)), d.setAttribute("data-month-text", a.header), d.setAttribute("data-orig-label", o ? a.header : "");
619
+ const u = document.createElement("span");
620
+ u.className = "lt__month-label", u.setAttribute("aria-hidden", "true"), c || (a.isSelected || o) && (u.textContent = a.header);
621
+ const l = document.createElement("span");
622
+ l.className = "lt__item-label", l.textContent = c ? a.compactLabel : a.label;
623
+ const m = document.createElement("span");
624
+ if (m.className = "lt__item-sublabel", c || (m.textContent = a.isCurrent && a.sublabel ? `${a.sublabel} · Auj.` : a.sublabel), d.append(u, l, m), a.isCurrent) {
625
+ const p = document.createElement("span");
626
+ p.className = "lt__item-dot", p.setAttribute("aria-hidden", "true"), d.appendChild(p);
627
+ }
628
+ const M = this._markersForItem(a.year);
629
+ if (M.length > 0) {
630
+ const p = document.createElement("span");
631
+ p.className = "lt__marker-stack", p.setAttribute("aria-hidden", "true");
632
+ for (const E of M) {
633
+ const g = document.createElement("button");
634
+ g.type = "button", g.className = "lt__marker", E.color && (g.style.background = E.color), g.title = E.label, g.setAttribute("aria-label", E.label), E.id && g.setAttribute("data-marker-id", E.id), g.addEventListener("click", (S) => {
635
+ S.stopPropagation(), this._handleMarkerClick(E);
636
+ }), p.appendChild(g);
637
+ }
638
+ d.appendChild(p);
639
+ }
640
+ this.listEl.appendChild(d);
641
+ }
642
+ this._applyCarouselPadding();
643
+ }
644
+ /** Renvoie les markers dont year tombe dans [itemYear, itemYear + step). */
645
+ _markersForItem(t) {
646
+ const e = t + this.scale.step;
647
+ return this.markers.filter((i) => i.year >= t && i.year < e);
648
+ }
649
+ _handleMarkerClick(t) {
650
+ this.onMarkerClickCb?.(t), this.container.dispatchEvent(new CustomEvent("markerclick", {
651
+ detail: t,
652
+ bubbles: !0,
653
+ composed: !0
654
+ }));
655
+ }
656
+ _updateListDOM(t) {
657
+ this._renderItems(), this._syncCenterMark();
658
+ const e = this.listEl.querySelector(".lt__item--selected");
659
+ e && (this._scrollToItem(e, t), requestAnimationFrame(() => this._applyScaleEffect()));
660
+ }
661
+ // ─── Extension proactive ─────────────────────────────────────────────────────
662
+ /**
663
+ * Vérifie pendant le drag si le centre approche du bord (seuil × 2).
664
+ * Si oui, reconstruit la liste autour de l'item central visible en
665
+ * conservant la position de scroll — le drag continue sans saut.
666
+ */
667
+ _checkAndExtend() {
668
+ if (this._isExtending) return;
669
+ const t = Array.from(this.listEl.querySelectorAll(".lt__item"));
670
+ if (t.length === 0) return;
671
+ const e = this.listWrapper, i = e.scrollLeft + e.clientWidth / 2, r = t.length > 1 && t[1] && t[0] ? t[1].offsetLeft - t[0].offsetLeft : 100;
672
+ let a = -1, o = 1 / 0;
673
+ if (t.forEach((p, E) => {
674
+ const g = Math.abs(p.offsetLeft + p.offsetWidth / 2 - i);
675
+ g < o && (o = g, a = E);
676
+ }), a === -1) return;
677
+ const n = k * 2, h = e.scrollWidth - e.clientWidth - e.scrollLeft, c = e.scrollLeft;
678
+ if (!(a < n || a >= t.length - n || h < r * n || c < r * n)) return;
679
+ const u = t[a];
680
+ if (!u) return;
681
+ const l = u.getAttribute("data-year");
682
+ if (!l) return;
683
+ this._isExtending = !0;
684
+ const m = e.scrollLeft - (u.offsetLeft + u.offsetWidth / 2 - e.clientWidth / 2);
685
+ this.referenceYear = parseFloat(l), this._renderItems(), this._syncCenterMark();
686
+ const M = this.listEl.querySelector(`[data-year="${l}"]`);
687
+ if (M) {
688
+ const p = M.offsetLeft + M.offsetWidth / 2 - e.clientWidth / 2 + m;
689
+ e.scrollLeft = p, this.isDragging && (this.dragStartScroll = p - (this.dragStartX - this.lastMoveX));
690
+ }
691
+ this._applyScaleEffect(), this._isExtending = !1;
692
+ }
693
+ // ─── Carousel helpers ────────────────────────────────────────────────────────
694
+ _applyCarouselPadding() {
695
+ const t = this.listWrapper.clientWidth, e = this.listEl.querySelectorAll(".lt__item"), i = e[0], r = e[e.length - 1];
696
+ if (!i || t === 0) return;
697
+ const a = Math.max(0, t / 2 - i.offsetWidth / 2), o = Math.max(0, t / 2 - (r ? r.offsetWidth / 2 : i.offsetWidth / 2));
698
+ this.listEl.style.paddingLeft = `${a}px`, this.listEl.style.paddingRight = `${o}px`;
699
+ }
700
+ _syncCenterMark() {
701
+ const t = this.listEl.querySelector(".lt__item");
702
+ t && (this.centerMark.style.width = `${t.offsetWidth}px`, this.centerMark.style.height = `${t.offsetHeight}px`);
703
+ }
704
+ _scrollToItem(t, e, i) {
705
+ const r = t.offsetLeft + t.offsetWidth / 2 - this.listWrapper.clientWidth / 2;
706
+ this.rafId !== void 0 && (cancelAnimationFrame(this.rafId), this.rafId = void 0), e ? this._smoothScrollTo(r, i) : (this.listWrapper.scrollLeft = r, i?.());
707
+ }
708
+ _smoothScrollTo(t, e) {
709
+ const r = this.listWrapper.scrollLeft, a = t - r;
710
+ if (Math.abs(a) < 1) {
711
+ e?.();
712
+ return;
713
+ }
714
+ const o = performance.now(), n = (c) => 1 - (1 - c) ** 3, h = (c) => {
715
+ const d = Math.min((c - o) / 280, 1);
716
+ this.listWrapper.scrollLeft = r + a * n(d), d < 1 ? this.rafId = requestAnimationFrame(h) : (this.listWrapper.scrollLeft = t, this.rafId = void 0, e?.());
717
+ };
718
+ this.rafId = requestAnimationFrame(h);
719
+ }
720
+ _getCenterItem() {
721
+ const t = this.listWrapper.scrollLeft + this.listWrapper.clientWidth / 2, e = Array.from(this.listEl.querySelectorAll(".lt__item"));
722
+ let i = null, r = 1 / 0;
723
+ for (const a of e) {
724
+ const o = Math.abs(a.offsetLeft + a.offsetWidth / 2 - t);
725
+ o < r && (r = o, i = a);
726
+ }
727
+ return i;
728
+ }
729
+ // ─── Scale + opacité (#3) ─────────────────────────────────────────────────
730
+ _applyScaleEffect() {
731
+ const t = this.listWrapper.scrollLeft + this.listWrapper.clientWidth / 2, e = this.listWrapper.clientWidth * 0.55;
732
+ this.listEl.querySelectorAll(".lt__item").forEach((i) => {
733
+ const r = Math.min(Math.abs(i.offsetLeft + i.offsetWidth / 2 - t) / e, 1), a = 1 - r * (1 - kt), o = 1 - r * (1 - wt);
734
+ i.style.transform = `scale(${a.toFixed(3)})`, i.style.opacity = o.toFixed(3);
735
+ });
736
+ }
737
+ // ─── Spring snap ─────────────────────────────────────────────────────────────
738
+ _springSnap(t) {
739
+ this.springRafId !== void 0 && (cancelAnimationFrame(this.springRafId), this.springRafId = void 0);
740
+ const e = 420, i = 0.1, r = 2.2, a = performance.now(), o = (h) => 1 + i * Math.exp(-7 * h) * Math.sin(r * Math.PI * h), n = (h) => {
741
+ const c = Math.min((h - a) / e, 1);
742
+ t.style.transform = `scale(${o(c).toFixed(4)})`, t.style.opacity = "1", c < 1 ? this.springRafId = requestAnimationFrame(n) : (this.springRafId = void 0, t.style.transform = "scale(1)");
743
+ };
744
+ this.springRafId = requestAnimationFrame(n);
745
+ }
746
+ // ─── Visuals unifiés ─────────────────────────────────────────────────────────
747
+ _updateVisuals() {
748
+ this._updateCenterHighlight(), this._applyScaleEffect();
749
+ }
750
+ _scheduleVisuals() {
751
+ this.rafId === void 0 && (this.rafId = requestAnimationFrame(() => {
752
+ this.rafId = void 0, this._checkAndExtend(), this._updateVisuals();
753
+ }));
754
+ }
755
+ _updateCenterHighlight() {
756
+ const t = this._getCenterItem();
757
+ if (!t) return;
758
+ this.listEl.querySelectorAll(".lt__item").forEach((i) => {
759
+ const r = i === t, a = i.classList.contains("lt__item--selected");
760
+ if (i.classList.toggle("lt__item--selected", r), i.setAttribute("aria-selected", String(r)), i.tabIndex = r ? 0 : -1, r === a) return;
761
+ const o = i.querySelector(".lt__month-label");
762
+ o && (r ? o.textContent = i.getAttribute("data-month-text") ?? "" : o.textContent = i.getAttribute("data-orig-label") ?? "");
763
+ });
764
+ const e = t.getAttribute("data-year");
765
+ if (e) {
766
+ const i = parseFloat(e);
767
+ if (this._emitItemPreview(i), this.onWeekPreviewCb && b(i)) {
768
+ const r = I(_(i)), a = O(r, this.firstDayOfWeek);
769
+ this.onWeekPreviewCb({ date: R(r), week: { start: a, end: $(a) } });
770
+ }
771
+ }
772
+ }
773
+ // ─── Drag (Pointer Events) ───────────────────────────────────────────────────
774
+ _bindDragEvents() {
775
+ const t = (r) => {
776
+ r.button !== 0 && r.pointerType === "mouse" || this._isPinching || (this.rafId !== void 0 && (cancelAnimationFrame(this.rafId), this.rafId = void 0), this.springRafId !== void 0 && (cancelAnimationFrame(this.springRafId), this.springRafId = void 0), this.listWrapper.setPointerCapture(r.pointerId), this.isDragging = !0, this.dragStartX = r.clientX, this.dragStartScroll = this.listWrapper.scrollLeft, this.velX = 0, this.lastMoveX = r.clientX, this.lastMoveT = performance.now(), this.listWrapper.classList.add("is-dragging"));
777
+ }, e = (r) => {
778
+ if (!this.isDragging) return;
779
+ this.listWrapper.scrollLeft = this.dragStartScroll + (this.dragStartX - r.clientX);
780
+ const a = performance.now(), o = a - this.lastMoveT;
781
+ o > 0 && o < 100 && (this.velX = (this.lastMoveX - r.clientX) / o), this.lastMoveX = r.clientX, this.lastMoveT = a, this._scheduleVisuals();
782
+ }, i = () => {
783
+ this.isDragging && (this.isDragging = !1, this.listWrapper.classList.remove("is-dragging"), this._startMomentum());
784
+ };
785
+ this.listWrapper.addEventListener("pointerdown", t), this.listWrapper.addEventListener("pointermove", e), this.listWrapper.addEventListener("pointerup", i), this.listWrapper.addEventListener("pointercancel", i), this.cleanupFns.push(
786
+ () => this.listWrapper.removeEventListener("pointerdown", t),
787
+ () => this.listWrapper.removeEventListener("pointermove", e),
788
+ () => this.listWrapper.removeEventListener("pointerup", i),
789
+ () => this.listWrapper.removeEventListener("pointercancel", i)
790
+ );
791
+ }
792
+ // ─── Molette souris ─────────────────────────────────────────────────────────
793
+ _bindWheelEvents() {
794
+ const t = (e) => {
795
+ e.preventDefault(), this.wheelDebounceId === void 0 && (this.wheelDebounceId = window.setTimeout(() => {
796
+ this.wheelDebounceId = void 0;
797
+ }, Lt), e.deltaY > 0 || e.deltaX > 0 ? this.goToNext() : this.goToPrevious());
798
+ };
799
+ this.listWrapper.addEventListener("wheel", t, { passive: !1 }), this.cleanupFns.push(() => this.listWrapper.removeEventListener("wheel", t));
800
+ }
801
+ // ─── Momentum ──────────────────────────────────────────────────────────────
802
+ _startMomentum() {
803
+ if (Math.abs(this.velX) < B) {
804
+ this._snapToCenter();
805
+ return;
806
+ }
807
+ const t = () => {
808
+ if (this.velX *= Ct, this.listWrapper.scrollLeft += this.velX * 16, this._updateVisuals(), Math.abs(this.velX) < B) {
809
+ this.rafId = void 0, this._snapToCenter();
810
+ return;
811
+ }
812
+ this.rafId = requestAnimationFrame(t);
813
+ };
814
+ this.rafId = requestAnimationFrame(t);
815
+ }
816
+ // ─── Snap ───────────────────────────────────────────────────────────────────
817
+ _snapToCenter() {
818
+ const t = this._getCenterItem();
819
+ if (!t) return;
820
+ const e = t.getAttribute("data-year");
821
+ if (!e) return;
822
+ const i = parseFloat(e), r = Array.from(this.listEl.querySelectorAll(".lt__item")), a = r.indexOf(t);
823
+ this._commitSelection(i), this._hapticFeedback();
824
+ const o = r.length > 1 && r[1] && r[0] ? r[1].offsetLeft - r[0].offsetLeft : 100, n = this.listWrapper, h = n.scrollWidth - n.clientWidth - n.scrollLeft, c = n.scrollLeft;
825
+ if (a < k || a >= r.length - k || h < o * 2 || c < o * 2) {
826
+ this.referenceYear = this.selectedYear, this._updateListDOM(!1);
827
+ const u = this.listEl.querySelector(".lt__item--selected");
828
+ u && this._springSnap(u);
829
+ return;
830
+ }
831
+ this._scrollToItem(t, !0, () => {
832
+ this._applyScaleEffect(), this._springSnap(t);
833
+ });
834
+ }
835
+ // ─── Keyboard ───────────────────────────────────────────────────────────────
836
+ _bindKeyboardEvents() {
837
+ const t = (e) => {
838
+ switch (e.key) {
839
+ case "ArrowLeft":
840
+ e.preventDefault(), this.goToPrevious();
841
+ break;
842
+ case "ArrowRight":
843
+ e.preventDefault(), this.goToNext();
844
+ break;
845
+ case "Home":
846
+ e.preventDefault(), this.goToToday();
847
+ break;
848
+ }
849
+ };
850
+ this.listWrapper.addEventListener("keydown", t), this.cleanupFns.push(() => this.listWrapper.removeEventListener("keydown", t));
851
+ }
852
+ // ─── Pinch-to-zoom (touch) ───────────────────────────────────────────────────
853
+ _bindPinchEvents() {
854
+ const i = (n, h) => Math.hypot(h.clientX - n.clientX, h.clientY - n.clientY), r = (n) => {
855
+ if (n.touches.length !== 2) return;
856
+ const h = n.touches[0], c = n.touches[1];
857
+ !h || !c || (this._isPinching = !0, this._pinchInitialDist = i(h, c), this.isDragging = !1, this.listWrapper.classList.remove("is-dragging"));
858
+ }, a = (n) => {
859
+ if (!this._isPinching || n.touches.length !== 2) return;
860
+ n.preventDefault();
861
+ const h = n.touches[0], c = n.touches[1];
862
+ if (!h || !c || this._pinchInitialDist === 0) return;
863
+ const d = i(h, c) / this._pinchInitialDist;
864
+ d > 1.3 ? (this._stepScale(1), this._pinchInitialDist = i(h, c)) : d < 0.77 && (this._stepScale(-1), this._pinchInitialDist = i(h, c));
865
+ }, o = (n) => {
866
+ n.touches.length < 2 && (this._isPinching = !1, this._pinchInitialDist = 0);
867
+ };
868
+ this.listWrapper.addEventListener("touchstart", r, { passive: !0 }), this.listWrapper.addEventListener("touchmove", a, { passive: !1 }), this.listWrapper.addEventListener("touchend", o), this.listWrapper.addEventListener("touchcancel", o), this.cleanupFns.push(
869
+ () => this.listWrapper.removeEventListener("touchstart", r),
870
+ () => this.listWrapper.removeEventListener("touchmove", a),
871
+ () => this.listWrapper.removeEventListener("touchend", o),
872
+ () => this.listWrapper.removeEventListener("touchcancel", o)
873
+ );
874
+ }
875
+ /** Décale l'échelle de `delta` positions (+1 = plus fine, -1 = plus grossière). */
876
+ _stepScale(t) {
877
+ const e = Y(this.scale.id) + t, i = A(e);
878
+ i && this.setScale(i.id);
879
+ }
880
+ // ─── Selection ──────────────────────────────────────────────────────────────
881
+ /** Construit un TimelineItemInfo pour un year donné, à l'échelle courante. */
882
+ _buildItemInfo(t) {
883
+ const i = this.scale.build(t, 1, t, this.locale)[0];
884
+ return i ? {
885
+ scale: this.scale.id,
886
+ year: i.year,
887
+ label: i.label,
888
+ sublabel: i.sublabel,
889
+ header: i.header,
890
+ date: b(i.year) ? _(i.year) : null
891
+ } : null;
892
+ }
893
+ _emitItemChange(t) {
894
+ const e = this._buildItemInfo(t);
895
+ if (e) {
896
+ if (this.liveRegion) {
897
+ const i = [e.label, e.sublabel].filter((r) => r && r.trim().length > 0);
898
+ this.liveRegion.textContent = i.join(", ");
899
+ }
900
+ this.onItemChangeCb?.(e);
901
+ }
902
+ }
903
+ _emitItemPreview(t) {
904
+ if (!this.onItemPreviewCb) return;
905
+ const e = this._buildItemInfo(t);
906
+ e && this.onItemPreviewCb(e);
907
+ }
908
+ _commitSelection(t) {
909
+ const e = this._clampYear(t);
910
+ if (this.selectedYear = e, this.referenceYear = e, this._writeStorage(), this._emitItemChange(e), t = e, !b(t)) return;
911
+ const i = I(_(t)), r = O(i, this.firstDayOfWeek), a = {
912
+ date: R(i),
913
+ week: { start: r, end: $(r) }
914
+ };
915
+ this.container.dispatchEvent(at(a)), this.container.dispatchEvent(nt(a)), this.onWeekChangeCb?.(a), this.calendarAdapter?.gotoDate(a.week.start);
916
+ }
917
+ _emitNavigate(t) {
918
+ if (!b(this.selectedYear)) return;
919
+ const e = I(_(this.selectedYear)), i = O(e, this.firstDayOfWeek);
920
+ this.container.dispatchEvent(
921
+ lt({ direction: t, date: R(e), week: { start: i, end: $(i) } })
922
+ );
923
+ }
924
+ // ─── Feedback haptique mobile ───────────────────────────────────────────────
925
+ _hapticFeedback() {
926
+ "vibrate" in navigator && navigator.vibrate(8);
927
+ }
928
+ // ─── Thème ──────────────────────────────────────────────────────────────────
929
+ _setupTheme() {
930
+ this._darkMq && this._darkMqHandler && (this._darkMq.removeEventListener("change", this._darkMqHandler), this._darkMqHandler = void 0, this._darkMq = void 0), this.theme === "dark" ? this.root.classList.add("lt--dark") : this.theme === "auto" && "matchMedia" in window && (this._darkMq = window.matchMedia("(prefers-color-scheme: dark)"), this.root.classList.toggle("lt--dark", this._darkMq.matches), this._darkMqHandler = (t) => {
931
+ this.root.classList.toggle("lt--dark", t.matches);
932
+ }, this._darkMq.addEventListener("change", this._darkMqHandler));
933
+ }
934
+ _readStorage() {
935
+ if (!this.storageKey) return null;
936
+ try {
937
+ const t = localStorage.getItem(this.storageKey);
938
+ if (!t) return null;
939
+ const e = `${W.STORAGE_VERSION}:`;
940
+ if (!t.startsWith(e)) return null;
941
+ const i = parseFloat(t.slice(e.length));
942
+ return Number.isFinite(i) ? i : null;
943
+ } catch {
944
+ return null;
945
+ }
946
+ }
947
+ _writeStorage() {
948
+ if (this.storageKey)
949
+ try {
950
+ localStorage.setItem(this.storageKey, `${W.STORAGE_VERSION}:${this.selectedYear}`);
951
+ } catch {
952
+ }
953
+ }
954
+ // ─── Resize ─────────────────────────────────────────────────────────────────
955
+ _initResizeObserver() {
956
+ "ResizeObserver" in window && (this.resizeObserver = new ResizeObserver(() => {
957
+ this._applyCarouselPadding(), this._syncCenterMark();
958
+ const t = this.listEl.querySelector(".lt__item--selected");
959
+ t && (this._scrollToItem(t, !1), this._applyScaleEffect());
960
+ }), this.resizeObserver.observe(this.listWrapper));
961
+ }
962
+ // ─── Debug ──────────────────────────────────────────────────────────────────
963
+ debugState() {
964
+ const t = Array.from(this.listEl?.querySelectorAll(".lt__item") ?? []), e = this.listWrapper, i = e?.scrollLeft ?? 0, r = e?.clientWidth ?? 0, a = e?.scrollWidth ?? 0, o = i + r / 2;
965
+ let n = -1, h = 1 / 0;
966
+ t.forEach((T, tt) => {
967
+ const V = Math.abs(T.offsetLeft + T.offsetWidth / 2 - o);
968
+ V < h && (h = V, n = tt);
969
+ });
970
+ const c = t.findIndex((T) => T.classList.contains("lt__item--selected")), d = t[0]?.getAttribute("data-year") ?? "—", u = t[t.length - 1]?.getAttribute("data-year") ?? "—", l = a - r, M = t[n]?.getAttribute("data-year") ?? "—", E = t[c]?.getAttribute("data-year") ?? "—", g = Math.round(l - i), S = n > t.length - 1 - k, D = n < k, J = t[1] && t[0] ? t[1].offsetLeft - t[0].offsetLeft : 0, Q = t.length - 1 - n, Z = n;
971
+ return {
972
+ scale: this.scale.id,
973
+ selectedYear: this.selectedYear,
974
+ referenceYear: this.referenceYear,
975
+ isDragging: this.isDragging,
976
+ rafActive: this.rafId !== void 0,
977
+ velX: +this.velX.toFixed(3),
978
+ scrollLeft: Math.round(i),
979
+ maxScrollLeft: Math.round(l),
980
+ distToRight: g,
981
+ scrollWidth: Math.round(a),
982
+ clientWidth: Math.round(r),
983
+ paddingLeft: this.listEl?.style.paddingLeft ?? "?",
984
+ paddingRight: this.listEl?.style.paddingRight ?? "?",
985
+ itemWidth: Math.round(J),
986
+ totalItems: t.length,
987
+ weeksToLeft: Z,
988
+ weeksToRight: Q,
989
+ centerIdx: n,
990
+ centerYear: M,
991
+ selIdx: c,
992
+ selYear: E,
993
+ centerMatchSel: n === c,
994
+ EXTEND_THRESHOLD: k,
995
+ nearEdgeLeft: D,
996
+ nearEdgeRight: S,
997
+ extendWillFire: D || S,
998
+ firstYear: d,
999
+ lastYear: u
1000
+ };
1001
+ }
1002
+ // ─── Cleanup ────────────────────────────────────────────────────────────────
1003
+ _cleanup() {
1004
+ this.cleanupFns.forEach((t) => t()), this.cleanupFns = [];
1005
+ }
1006
+ };
1007
+ // ─── Persistence ────────────────────────────────────────────────────────────
1008
+ /**
1009
+ * Format storage : `v1:{year}`. Toute autre forme (ancien format, version
1010
+ * future inconnue) est ignorée silencieusement — permet de faire évoluer
1011
+ * la sérialisation sans casser les sessions existantes.
1012
+ */
1013
+ f(W, "STORAGE_VERSION", "v1");
1014
+ let G = W;
1015
+ function Yt(s) {
1016
+ return {
1017
+ gotoDate(t) {
1018
+ s.gotoDate(t);
1019
+ }
1020
+ };
1021
+ }
1022
+ function Wt(s, t) {
1023
+ const e = Yt(t);
1024
+ return s.connectAdapter(e), ot(s.getEventTarget(), (r) => {
1025
+ t.gotoDate(r.detail.week.start);
1026
+ });
1027
+ }
1028
+ export {
1029
+ G as LadderTimeline,
1030
+ Wt as bindTimelineToCalendar,
1031
+ Yt as createFullCalendarAdapter
1032
+ };
1033
+ //# sourceMappingURL=ladder-timeline.es.js.map