@sentropic/design-system-svelte 0.13.0 → 0.14.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,185 @@
1
+ <script lang="ts" module>
2
+ export type AutosaveStatus = "idle" | "saving" | "saved" | "error";
3
+
4
+ export type AutosaveLabels = {
5
+ idle?: string;
6
+ saving?: string;
7
+ saved?: string;
8
+ error?: string;
9
+ };
10
+ </script>
11
+
12
+ <script lang="ts">
13
+ import { LoaderCircle, CircleCheck, CircleAlert } from "@lucide/svelte";
14
+ import type { HTMLAttributes } from "svelte/elements";
15
+
16
+ type AutosaveProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
17
+ status?: AutosaveStatus;
18
+ /** Horodatage de la dernière sauvegarde réussie. */
19
+ lastSaved?: string | Date;
20
+ /** Affiche un bouton « Réessayer » sur le statut `error`. */
21
+ onRetry?: () => void;
22
+ /** Surcharge des libellés par statut. */
23
+ labels?: AutosaveLabels;
24
+ /** Étiquette du bouton de relance. */
25
+ retryLabel?: string;
26
+ locale?: string;
27
+ class?: string;
28
+ };
29
+
30
+ let {
31
+ status = "idle",
32
+ lastSaved,
33
+ onRetry,
34
+ labels,
35
+ retryLabel,
36
+ locale = "fr-FR",
37
+ class: className,
38
+ ...rest
39
+ }: AutosaveProps = $props();
40
+
41
+ const isFr = $derived((locale ?? "fr-FR").toLowerCase().startsWith("fr"));
42
+
43
+ const DEFAULT_LABELS = $derived<Required<AutosaveLabels>>(
44
+ isFr
45
+ ? {
46
+ idle: "Modifications enregistrées",
47
+ saving: "Enregistrement…",
48
+ saved: "Enregistré",
49
+ error: "Échec de l'enregistrement"
50
+ }
51
+ : {
52
+ idle: "All changes saved",
53
+ saving: "Saving…",
54
+ saved: "Saved",
55
+ error: "Failed to save"
56
+ }
57
+ );
58
+
59
+ const resolvedRetryLabel = $derived(retryLabel ?? (isFr ? "Réessayer" : "Retry"));
60
+
61
+ const statusLabel = $derived(labels?.[status] ?? DEFAULT_LABELS[status]);
62
+
63
+ const classes = $derived(
64
+ ["st-autosave", `st-autosave--${status}`, className].filter(Boolean).join(" ")
65
+ );
66
+
67
+ const role = $derived(status === "error" ? "alert" : "status");
68
+
69
+ // Heure relative de la dernière sauvegarde (rendu uniquement sur idle/saved).
70
+ const relativeTime = $derived.by(() => {
71
+ if (!lastSaved) return "";
72
+ const date = lastSaved instanceof Date ? lastSaved : new Date(lastSaved);
73
+ if (Number.isNaN(date.getTime())) return "";
74
+ const diffMs = Date.now() - date.getTime();
75
+ const diffSec = Math.round(diffMs / 1000);
76
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
77
+ if (Math.abs(diffSec) < 60) return rtf.format(-diffSec, "second");
78
+ const diffMin = Math.round(diffSec / 60);
79
+ if (Math.abs(diffMin) < 60) return rtf.format(-diffMin, "minute");
80
+ const diffHour = Math.round(diffMin / 60);
81
+ if (Math.abs(diffHour) < 24) return rtf.format(-diffHour, "hour");
82
+ const diffDay = Math.round(diffHour / 24);
83
+ return rtf.format(-diffDay, "day");
84
+ });
85
+
86
+ const showRelative = $derived(
87
+ (status === "saved" || status === "idle") && relativeTime !== ""
88
+ );
89
+ </script>
90
+
91
+ <div {...rest} class={classes} role={role} aria-live="polite">
92
+ <span class="st-autosave__icon" aria-hidden="true">
93
+ {#if status === "saving"}
94
+ <span class="st-autosave__spinner">
95
+ <LoaderCircle size={16} strokeWidth={2} aria-hidden="true" />
96
+ </span>
97
+ {:else if status === "saved"}
98
+ <CircleCheck size={16} strokeWidth={2} aria-hidden="true" />
99
+ {:else if status === "error"}
100
+ <CircleAlert size={16} strokeWidth={2} aria-hidden="true" />
101
+ {/if}
102
+ </span>
103
+ <span class="st-autosave__label">{statusLabel}</span>
104
+ {#if showRelative}
105
+ <span class="st-autosave__time">{relativeTime}</span>
106
+ {/if}
107
+ {#if status === "error" && onRetry}
108
+ <button type="button" class="st-autosave__retry" onclick={() => onRetry?.()}>
109
+ {resolvedRetryLabel}
110
+ </button>
111
+ {/if}
112
+ </div>
113
+
114
+ <style>
115
+ .st-autosave {
116
+ align-items: center;
117
+ color: var(--st-semantic-text-secondary);
118
+ display: inline-flex;
119
+ font-size: 0.875rem;
120
+ gap: var(--st-spacing-2, 0.5rem);
121
+ }
122
+
123
+ .st-autosave__icon {
124
+ align-items: center;
125
+ display: inline-flex;
126
+ justify-content: center;
127
+ line-height: 0;
128
+ }
129
+
130
+ .st-autosave__spinner {
131
+ align-items: center;
132
+ animation: st-autosave-spin 0.9s linear infinite;
133
+ display: inline-flex;
134
+ justify-content: center;
135
+ line-height: 0;
136
+ }
137
+
138
+ .st-autosave--saving .st-autosave__icon {
139
+ color: var(--st-semantic-action-primary);
140
+ }
141
+
142
+ .st-autosave--saved {
143
+ color: var(--st-semantic-feedback-success);
144
+ }
145
+
146
+ .st-autosave--error {
147
+ color: var(--st-semantic-feedback-error);
148
+ }
149
+
150
+ .st-autosave--idle {
151
+ color: var(--st-semantic-text-muted);
152
+ }
153
+
154
+ .st-autosave__time {
155
+ color: var(--st-semantic-text-muted);
156
+ }
157
+
158
+ .st-autosave__retry {
159
+ background: transparent;
160
+ border: 0;
161
+ color: var(--st-semantic-feedback-error);
162
+ cursor: pointer;
163
+ font: inherit;
164
+ font-weight: 600;
165
+ padding: 0;
166
+ text-decoration: underline;
167
+ }
168
+
169
+ .st-autosave__retry:focus-visible {
170
+ outline: 2px solid var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
171
+ outline-offset: 2px;
172
+ }
173
+
174
+ @keyframes st-autosave-spin {
175
+ to {
176
+ transform: rotate(360deg);
177
+ }
178
+ }
179
+
180
+ @media (prefers-reduced-motion: reduce) {
181
+ .st-autosave__spinner {
182
+ animation: none;
183
+ }
184
+ }
185
+ </style>
@@ -0,0 +1,25 @@
1
+ export type AutosaveStatus = "idle" | "saving" | "saved" | "error";
2
+ export type AutosaveLabels = {
3
+ idle?: string;
4
+ saving?: string;
5
+ saved?: string;
6
+ error?: string;
7
+ };
8
+ import type { HTMLAttributes } from "svelte/elements";
9
+ type AutosaveProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
10
+ status?: AutosaveStatus;
11
+ /** Horodatage de la dernière sauvegarde réussie. */
12
+ lastSaved?: string | Date;
13
+ /** Affiche un bouton « Réessayer » sur le statut `error`. */
14
+ onRetry?: () => void;
15
+ /** Surcharge des libellés par statut. */
16
+ labels?: AutosaveLabels;
17
+ /** Étiquette du bouton de relance. */
18
+ retryLabel?: string;
19
+ locale?: string;
20
+ class?: string;
21
+ };
22
+ declare const Autosave: import("svelte").Component<AutosaveProps, {}, "">;
23
+ type Autosave = ReturnType<typeof Autosave>;
24
+ export default Autosave;
25
+ //# sourceMappingURL=Autosave.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Autosave.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Autosave.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnE,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAIJ,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,GAAG;IACnE,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,yCAAyC;IACzC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAgGJ,QAAA,MAAM,QAAQ,mDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,406 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * En mode simple : `string | null` ("YYYY-MM-DD").
4
+ * En mode plage (`range`) : tuple `[start, end]` où chaque borne est
5
+ * "YYYY-MM-DD" ou null.
6
+ */
7
+ export type CalendarValue = string | null | [string | null, string | null];
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import { ChevronLeft, ChevronRight } from "@lucide/svelte";
12
+ import type { HTMLAttributes } from "svelte/elements";
13
+
14
+ type CalendarProps = Omit<HTMLAttributes<HTMLDivElement>, "class" | "onchange"> & {
15
+ /** Date sélectionnée ("YYYY-MM-DD") ou tuple [start,end] si `range`. */
16
+ value?: CalendarValue;
17
+ /** Appelé avec la nouvelle date (ou le tuple en mode plage). */
18
+ onChange?: (value: CalendarValue) => void;
19
+ /** Borne minimale "YYYY-MM-DD" (inclusive). */
20
+ min?: string;
21
+ /** Borne maximale "YYYY-MM-DD" (inclusive). */
22
+ max?: string;
23
+ /** Sélection d'une plage de deux dates. */
24
+ range?: boolean;
25
+ /** Premier jour de la semaine : 0 = dimanche, 1 = lundi. */
26
+ weekStartsOn?: 0 | 1;
27
+ locale?: string;
28
+ /** Mois affiché ("YYYY-MM"), contrôlable de l'extérieur. */
29
+ month?: string;
30
+ class?: string;
31
+ previousMonthLabel?: string;
32
+ nextMonthLabel?: string;
33
+ };
34
+
35
+ let {
36
+ value = null,
37
+ onChange,
38
+ min,
39
+ max,
40
+ range = false,
41
+ weekStartsOn = 1,
42
+ locale = "fr-FR",
43
+ month,
44
+ class: className,
45
+ previousMonthLabel,
46
+ nextMonthLabel,
47
+ ...rest
48
+ }: CalendarProps = $props();
49
+
50
+ const isFr = $derived((locale ?? "fr-FR").toLowerCase().startsWith("fr"));
51
+ const resolvedPrevLabel = $derived(
52
+ previousMonthLabel ?? (isFr ? "Mois précédent" : "Previous month")
53
+ );
54
+ const resolvedNextLabel = $derived(nextMonthLabel ?? (isFr ? "Mois suivant" : "Next month"));
55
+
56
+ const monthFormatter = $derived(
57
+ new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" })
58
+ );
59
+ const weekdayFormatter = $derived(new Intl.DateTimeFormat(locale, { weekday: "short" }));
60
+ const cellFormatter = $derived(
61
+ new Intl.DateTimeFormat(locale, { day: "numeric", month: "long", year: "numeric" })
62
+ );
63
+
64
+ // --- Helpers de dates (alignés sur DatePicker.svelte) --------------------
65
+ function startOfDay(date: Date): Date {
66
+ const d = new Date(date);
67
+ d.setHours(0, 0, 0, 0);
68
+ return d;
69
+ }
70
+
71
+ function toISO(date: Date): string {
72
+ const y = date.getFullYear();
73
+ const m = String(date.getMonth() + 1).padStart(2, "0");
74
+ const d = String(date.getDate()).padStart(2, "0");
75
+ return `${y}-${m}-${d}`;
76
+ }
77
+
78
+ function parseISO(iso: string | null | undefined): Date | null {
79
+ if (!iso) return null;
80
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
81
+ if (!match) return null;
82
+ const d = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
83
+ return Number.isNaN(d.getTime()) ? null : startOfDay(d);
84
+ }
85
+
86
+ function isSameDay(a: Date | null, b: Date | null): boolean {
87
+ if (!a || !b) return false;
88
+ return (
89
+ a.getFullYear() === b.getFullYear() &&
90
+ a.getMonth() === b.getMonth() &&
91
+ a.getDate() === b.getDate()
92
+ );
93
+ }
94
+
95
+ // --- Valeurs normalisées --------------------------------------------------
96
+ const single = $derived<Date | null>(range ? null : parseISO(value as string | null));
97
+
98
+ const rangeStart = $derived<Date | null>(
99
+ range && Array.isArray(value) ? parseISO(value[0]) : null
100
+ );
101
+ const rangeEnd = $derived<Date | null>(
102
+ range && Array.isArray(value) ? parseISO(value[1]) : null
103
+ );
104
+
105
+ function pickInitialMonth(): Date {
106
+ const parsed = parseISO(month ? `${month}-01` : undefined);
107
+ if (parsed) return parsed;
108
+ if (!range && single) return single;
109
+ if (range && rangeStart) return rangeStart;
110
+ return startOfDay(new Date());
111
+ }
112
+
113
+ let viewYear = $state(pickInitialMonth().getFullYear());
114
+ let viewMonth = $state(pickInitialMonth().getMonth());
115
+
116
+ // Resynchronise le mois affiché lorsque la prop `month` change.
117
+ $effect(() => {
118
+ const parsed = parseISO(month ? `${month}-01` : undefined);
119
+ if (parsed) {
120
+ viewYear = parsed.getFullYear();
121
+ viewMonth = parsed.getMonth();
122
+ }
123
+ });
124
+
125
+ const today = startOfDay(new Date());
126
+
127
+ const weekdayLabels = $derived.by(() => {
128
+ // 2024-01-07 est un dimanche : on énumère puis on tourne selon weekStartsOn.
129
+ const sample = new Date(Date.UTC(2024, 0, 7));
130
+ const labels: string[] = [];
131
+ for (let i = 0; i < 7; i++) {
132
+ const d = new Date(sample);
133
+ d.setUTCDate(sample.getUTCDate() + i);
134
+ labels.push(weekdayFormatter.format(d));
135
+ }
136
+ return [...labels.slice(weekStartsOn), ...labels.slice(0, weekStartsOn)];
137
+ });
138
+
139
+ type Cell = { date: Date; inMonth: boolean };
140
+
141
+ const grid = $derived.by<Cell[]>(() => {
142
+ const first = new Date(viewYear, viewMonth, 1);
143
+ const firstDayIdx = first.getDay();
144
+ const offset = (firstDayIdx - weekStartsOn + 7) % 7;
145
+ const start = new Date(viewYear, viewMonth, 1 - offset);
146
+ const cells: Cell[] = [];
147
+ for (let i = 0; i < 42; i++) {
148
+ const d = new Date(start);
149
+ d.setDate(start.getDate() + i);
150
+ cells.push({ date: startOfDay(d), inMonth: d.getMonth() === viewMonth });
151
+ }
152
+ return cells;
153
+ });
154
+
155
+ const minDate = $derived(parseISO(min));
156
+ const maxDate = $derived(parseISO(max));
157
+
158
+ function isOutOfBounds(date: Date): boolean {
159
+ const d = startOfDay(date).getTime();
160
+ if (minDate && d < minDate.getTime()) return true;
161
+ if (maxDate && d > maxDate.getTime()) return true;
162
+ return false;
163
+ }
164
+
165
+ function isSelected(date: Date): boolean {
166
+ if (!range) return isSameDay(single, date);
167
+ return isSameDay(rangeStart, date) || isSameDay(rangeEnd, date);
168
+ }
169
+
170
+ function isInRange(date: Date): boolean {
171
+ if (!range || !rangeStart || !rangeEnd) return false;
172
+ const d = startOfDay(date).getTime();
173
+ return d > rangeStart.getTime() && d < rangeEnd.getTime();
174
+ }
175
+
176
+ function previousMonth() {
177
+ if (viewMonth === 0) {
178
+ viewMonth = 11;
179
+ viewYear -= 1;
180
+ } else {
181
+ viewMonth -= 1;
182
+ }
183
+ }
184
+
185
+ function nextMonth() {
186
+ if (viewMonth === 11) {
187
+ viewMonth = 0;
188
+ viewYear += 1;
189
+ } else {
190
+ viewMonth += 1;
191
+ }
192
+ }
193
+
194
+ function pickDate(date: Date) {
195
+ if (isOutOfBounds(date)) return;
196
+ const picked = startOfDay(date);
197
+ const iso = toISO(picked);
198
+
199
+ if (!range) {
200
+ value = iso;
201
+ onChange?.(iso);
202
+ return;
203
+ }
204
+
205
+ // Mode plage : (re)démarrage si pas de début, ou si plage déjà complète,
206
+ // ou si la date est antérieure au début courant.
207
+ if (!rangeStart || (rangeStart && rangeEnd) || picked.getTime() < rangeStart.getTime()) {
208
+ const next: CalendarValue = [iso, null];
209
+ value = next;
210
+ onChange?.(next);
211
+ return;
212
+ }
213
+ const next: CalendarValue = [toISO(rangeStart), iso];
214
+ value = next;
215
+ onChange?.(next);
216
+ }
217
+
218
+ const monthLabel = $derived(monthFormatter.format(new Date(viewYear, viewMonth, 1)));
219
+
220
+ function onKeyDown(event: KeyboardEvent) {
221
+ if (event.key === "PageUp") {
222
+ event.preventDefault();
223
+ previousMonth();
224
+ } else if (event.key === "PageDown") {
225
+ event.preventDefault();
226
+ nextMonth();
227
+ }
228
+ }
229
+ </script>
230
+
231
+ <div class={["st-calendar", className].filter(Boolean).join(" ")} {...rest}>
232
+ <div class="st-calendar__nav">
233
+ <button
234
+ type="button"
235
+ class="st-calendar__navBtn"
236
+ aria-label={resolvedPrevLabel}
237
+ onclick={previousMonth}
238
+ >
239
+ <ChevronLeft size={18} aria-hidden="true" />
240
+ </button>
241
+ <span class="st-calendar__monthLabel" aria-live="polite">{monthLabel}</span>
242
+ <button
243
+ type="button"
244
+ class="st-calendar__navBtn"
245
+ aria-label={resolvedNextLabel}
246
+ onclick={nextMonth}
247
+ >
248
+ <ChevronRight size={18} aria-hidden="true" />
249
+ </button>
250
+ </div>
251
+ <div
252
+ class="st-calendar__grid"
253
+ role="grid"
254
+ tabindex="-1"
255
+ aria-label={monthLabel}
256
+ onkeydown={onKeyDown}
257
+ >
258
+ <div class="st-calendar__weekdays" role="row">
259
+ {#each weekdayLabels as wd (wd)}
260
+ <span class="st-calendar__weekday" role="columnheader">{wd}</span>
261
+ {/each}
262
+ </div>
263
+ <div class="st-calendar__days">
264
+ {#each grid as cell, i (i)}
265
+ {@const oob = isOutOfBounds(cell.date)}
266
+ {@const selected = isSelected(cell.date)}
267
+ {@const inRange = isInRange(cell.date)}
268
+ {@const isToday = isSameDay(cell.date, today)}
269
+ <button
270
+ type="button"
271
+ class="st-calendar__day"
272
+ class:st-calendar__day--outside={!cell.inMonth}
273
+ class:st-calendar__day--selected={selected}
274
+ class:st-calendar__day--inRange={inRange}
275
+ class:st-calendar__day--today={isToday}
276
+ role="gridcell"
277
+ aria-label={cellFormatter.format(cell.date)}
278
+ aria-selected={selected ? "true" : "false"}
279
+ aria-current={isToday ? "date" : undefined}
280
+ aria-disabled={oob ? "true" : undefined}
281
+ disabled={oob}
282
+ onclick={() => pickDate(cell.date)}
283
+ >
284
+ {cell.date.getDate()}
285
+ </button>
286
+ {/each}
287
+ </div>
288
+ </div>
289
+ </div>
290
+
291
+ <style>
292
+ .st-calendar {
293
+ background: var(--st-component-popover-background, var(--st-semantic-surface-raised));
294
+ border: 1px solid var(--st-component-popover-border, var(--st-semantic-border-subtle));
295
+ border-radius: var(--st-component-popover-radius, 0.5rem);
296
+ color: var(--st-component-popover-text, var(--st-semantic-text-primary));
297
+ display: inline-grid;
298
+ gap: var(--st-spacing-3, 0.75rem);
299
+ min-width: 18rem;
300
+ padding: var(--st-spacing-3, 0.75rem);
301
+ }
302
+
303
+ .st-calendar__nav {
304
+ align-items: center;
305
+ display: grid;
306
+ gap: var(--st-spacing-2, 0.5rem);
307
+ grid-template-columns: auto 1fr auto;
308
+ }
309
+
310
+ .st-calendar__navBtn {
311
+ align-items: center;
312
+ background: transparent;
313
+ border: 0;
314
+ border-radius: var(--st-component-control-radius, 0.375rem);
315
+ color: inherit;
316
+ cursor: pointer;
317
+ display: inline-flex;
318
+ justify-content: center;
319
+ line-height: 0;
320
+ padding: 0.25rem 0.5rem;
321
+ }
322
+
323
+ .st-calendar__navBtn:hover {
324
+ background: var(--st-component-control-hoverBackground, var(--st-semantic-surface-subtle));
325
+ }
326
+
327
+ .st-calendar__navBtn:focus-visible {
328
+ outline: 2px solid var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
329
+ outline-offset: 2px;
330
+ }
331
+
332
+ .st-calendar__monthLabel {
333
+ font-weight: 600;
334
+ text-align: center;
335
+ text-transform: capitalize;
336
+ }
337
+
338
+ .st-calendar__grid {
339
+ display: grid;
340
+ gap: var(--st-spacing-1, 0.25rem);
341
+ }
342
+
343
+ .st-calendar__weekdays,
344
+ .st-calendar__days {
345
+ display: grid;
346
+ gap: 2px;
347
+ grid-template-columns: repeat(7, minmax(2rem, 1fr));
348
+ }
349
+
350
+ .st-calendar__weekday {
351
+ color: var(--st-semantic-text-secondary);
352
+ font-size: 0.75rem;
353
+ font-weight: 600;
354
+ padding: 0.25rem 0;
355
+ text-align: center;
356
+ text-transform: capitalize;
357
+ }
358
+
359
+ .st-calendar__day {
360
+ aspect-ratio: 1 / 1;
361
+ background: transparent;
362
+ border: 0;
363
+ border-radius: var(--st-component-control-radius, 0.375rem);
364
+ color: inherit;
365
+ cursor: pointer;
366
+ font: inherit;
367
+ font-size: 0.875rem;
368
+ line-height: 1;
369
+ min-width: 0;
370
+ padding: 0;
371
+ text-align: center;
372
+ }
373
+
374
+ .st-calendar__day:hover:not(:disabled) {
375
+ background: var(--st-component-control-hoverBackground, var(--st-semantic-surface-subtle));
376
+ }
377
+
378
+ .st-calendar__day:focus-visible {
379
+ outline: 2px solid var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
380
+ outline-offset: -2px;
381
+ }
382
+
383
+ .st-calendar__day--outside {
384
+ color: var(--st-semantic-text-muted);
385
+ }
386
+
387
+ .st-calendar__day--today {
388
+ font-weight: 700;
389
+ box-shadow: inset 0 0 0 1px var(--st-semantic-border-interactive);
390
+ }
391
+
392
+ .st-calendar__day--inRange {
393
+ background: var(--st-component-control-hoverBackground, var(--st-semantic-surface-subtle));
394
+ }
395
+
396
+ .st-calendar__day--selected {
397
+ background: var(--st-component-dropdown-selectedBackground, var(--st-semantic-action-primary));
398
+ color: var(--st-component-dropdown-selectedText, var(--st-semantic-action-primaryText));
399
+ }
400
+
401
+ .st-calendar__day:disabled {
402
+ color: var(--st-semantic-text-muted);
403
+ cursor: not-allowed;
404
+ opacity: 0.5;
405
+ }
406
+ </style>
@@ -0,0 +1,31 @@
1
+ /**
2
+ * En mode simple : `string | null` ("YYYY-MM-DD").
3
+ * En mode plage (`range`) : tuple `[start, end]` où chaque borne est
4
+ * "YYYY-MM-DD" ou null.
5
+ */
6
+ export type CalendarValue = string | null | [string | null, string | null];
7
+ import type { HTMLAttributes } from "svelte/elements";
8
+ type CalendarProps = Omit<HTMLAttributes<HTMLDivElement>, "class" | "onchange"> & {
9
+ /** Date sélectionnée ("YYYY-MM-DD") ou tuple [start,end] si `range`. */
10
+ value?: CalendarValue;
11
+ /** Appelé avec la nouvelle date (ou le tuple en mode plage). */
12
+ onChange?: (value: CalendarValue) => void;
13
+ /** Borne minimale "YYYY-MM-DD" (inclusive). */
14
+ min?: string;
15
+ /** Borne maximale "YYYY-MM-DD" (inclusive). */
16
+ max?: string;
17
+ /** Sélection d'une plage de deux dates. */
18
+ range?: boolean;
19
+ /** Premier jour de la semaine : 0 = dimanche, 1 = lundi. */
20
+ weekStartsOn?: 0 | 1;
21
+ locale?: string;
22
+ /** Mois affiché ("YYYY-MM"), contrôlable de l'extérieur. */
23
+ month?: string;
24
+ class?: string;
25
+ previousMonthLabel?: string;
26
+ nextMonthLabel?: string;
27
+ };
28
+ declare const Calendar: import("svelte").Component<CalendarProps, {}, "">;
29
+ type Calendar = ReturnType<typeof Calendar>;
30
+ export default Calendar;
31
+ //# sourceMappingURL=Calendar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Calendar.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Calendar.svelte.ts"],"names":[],"mappings":"AAGE;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;AAI7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IAChF,wEAAwE;IACxE,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AA+OJ,QAAA,MAAM,QAAQ,mDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}