@sentropic/design-system-vue 0.8.0 → 0.9.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,92 @@
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
+ export type AutosaveProps = {
9
+ status?: AutosaveStatus;
10
+ /** Horodatage de la dernière sauvegarde réussie. */
11
+ lastSaved?: string | Date;
12
+ /** Affiche un bouton « Réessayer » sur le statut `error`. */
13
+ onRetry?: () => void;
14
+ /** Surcharge des libellés par statut. */
15
+ labels?: AutosaveLabels;
16
+ /** Étiquette du bouton de relance. */
17
+ retryLabel?: string;
18
+ locale?: string;
19
+ class?: string;
20
+ };
21
+ export declare const Autosave: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
22
+ status: {
23
+ type: () => AutosaveStatus;
24
+ default: string;
25
+ };
26
+ lastSaved: {
27
+ type: () => string | Date;
28
+ default: undefined;
29
+ };
30
+ onRetry: {
31
+ type: () => () => void;
32
+ default: undefined;
33
+ };
34
+ labels: {
35
+ type: () => AutosaveLabels;
36
+ default: undefined;
37
+ };
38
+ retryLabel: {
39
+ type: StringConstructor;
40
+ default: undefined;
41
+ };
42
+ locale: {
43
+ type: StringConstructor;
44
+ default: string;
45
+ };
46
+ class: {
47
+ type: StringConstructor;
48
+ default: undefined;
49
+ };
50
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
51
+ [key: string]: any;
52
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "retry"[], "retry", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
53
+ status: {
54
+ type: () => AutosaveStatus;
55
+ default: string;
56
+ };
57
+ lastSaved: {
58
+ type: () => string | Date;
59
+ default: undefined;
60
+ };
61
+ onRetry: {
62
+ type: () => () => void;
63
+ default: undefined;
64
+ };
65
+ labels: {
66
+ type: () => AutosaveLabels;
67
+ default: undefined;
68
+ };
69
+ retryLabel: {
70
+ type: StringConstructor;
71
+ default: undefined;
72
+ };
73
+ locale: {
74
+ type: StringConstructor;
75
+ default: string;
76
+ };
77
+ class: {
78
+ type: StringConstructor;
79
+ default: undefined;
80
+ };
81
+ }>> & Readonly<{
82
+ onRetry?: ((...args: any[]) => any) | undefined;
83
+ }>, {
84
+ class: string;
85
+ status: AutosaveStatus;
86
+ lastSaved: string | Date;
87
+ onRetry: () => void;
88
+ labels: AutosaveLabels;
89
+ retryLabel: string;
90
+ locale: string;
91
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
92
+ //# sourceMappingURL=Autosave.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Autosave.d.ts","sourceRoot":"","sources":["../src/Autosave.ts"],"names":[],"mappings":"AAGA,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;AAIF,MAAM,MAAM,aAAa,GAAG;IAC1B,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;AA+DF,eAAO,MAAM,QAAQ;;cAGS,MAAM,cAAc;;;;cACE,MAAM,MAAM,GAAG,IAAI;;;;cAC3B,MAAM,MAAM,IAAI;;;;cAC9B,MAAM,cAAc;;;;;;;;;;;;;;;;;;;cAHpB,MAAM,cAAc;;;;cACE,MAAM,MAAM,GAAG,IAAI;;;;cAC3B,MAAM,MAAM,IAAI;;;;cAC9B,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;mBADM,IAAI;;;;4EA4F1D,CAAC"}
@@ -0,0 +1,137 @@
1
+ import { defineComponent, h } from "vue";
2
+ import { classNames } from "./classNames.js";
3
+ function LoaderCircleIcon(size) {
4
+ return h("svg", {
5
+ width: size,
6
+ height: size,
7
+ viewBox: "0 0 24 24",
8
+ fill: "none",
9
+ stroke: "currentColor",
10
+ "stroke-width": 2,
11
+ "stroke-linecap": "round",
12
+ "stroke-linejoin": "round",
13
+ "aria-hidden": "true",
14
+ }, [h("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })]);
15
+ }
16
+ function CircleCheckIcon(size) {
17
+ return h("svg", {
18
+ width: size,
19
+ height: size,
20
+ viewBox: "0 0 24 24",
21
+ fill: "none",
22
+ stroke: "currentColor",
23
+ "stroke-width": 2,
24
+ "stroke-linecap": "round",
25
+ "stroke-linejoin": "round",
26
+ "aria-hidden": "true",
27
+ }, [
28
+ h("circle", { cx: "12", cy: "12", r: "10" }),
29
+ h("path", { d: "m9 12 2 2 4-4" }),
30
+ ]);
31
+ }
32
+ function CircleAlertIcon(size) {
33
+ return h("svg", {
34
+ width: size,
35
+ height: size,
36
+ viewBox: "0 0 24 24",
37
+ fill: "none",
38
+ stroke: "currentColor",
39
+ "stroke-width": 2,
40
+ "stroke-linecap": "round",
41
+ "stroke-linejoin": "round",
42
+ "aria-hidden": "true",
43
+ }, [
44
+ h("circle", { cx: "12", cy: "12", r: "10" }),
45
+ h("line", { x1: "12", x2: "12", y1: "8", y2: "12" }),
46
+ h("line", { x1: "12", x2: "12.01", y1: "16", y2: "16" }),
47
+ ]);
48
+ }
49
+ export const Autosave = defineComponent({
50
+ name: "Autosave",
51
+ props: {
52
+ status: { type: String, default: "idle" },
53
+ lastSaved: { type: [String, Date], default: undefined },
54
+ onRetry: { type: Function, default: undefined },
55
+ labels: { type: Object, default: undefined },
56
+ retryLabel: { type: String, default: undefined },
57
+ locale: { type: String, default: "fr-FR" },
58
+ class: { type: String, default: undefined },
59
+ },
60
+ emits: ["retry"],
61
+ setup(props, { emit, attrs }) {
62
+ return () => {
63
+ const status = props.status;
64
+ const locale = props.locale;
65
+ const isFr = (locale ?? "fr-FR").toLowerCase().startsWith("fr");
66
+ const DEFAULT_LABELS = isFr
67
+ ? {
68
+ idle: "Modifications enregistrées",
69
+ saving: "Enregistrement…",
70
+ saved: "Enregistré",
71
+ error: "Échec de l'enregistrement",
72
+ }
73
+ : {
74
+ idle: "All changes saved",
75
+ saving: "Saving…",
76
+ saved: "Saved",
77
+ error: "Failed to save",
78
+ };
79
+ const resolvedRetryLabel = props.retryLabel ?? (isFr ? "Réessayer" : "Retry");
80
+ const statusLabel = props.labels?.[status] ?? DEFAULT_LABELS[status];
81
+ const role = status === "error" ? "alert" : "status";
82
+ // Heure relative de la dernière sauvegarde (rendu uniquement sur idle/saved).
83
+ const relativeTime = (() => {
84
+ if (!props.lastSaved)
85
+ return "";
86
+ const date = props.lastSaved instanceof Date ? props.lastSaved : new Date(props.lastSaved);
87
+ if (Number.isNaN(date.getTime()))
88
+ return "";
89
+ const diffMs = Date.now() - date.getTime();
90
+ const diffSec = Math.round(diffMs / 1000);
91
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
92
+ if (Math.abs(diffSec) < 60)
93
+ return rtf.format(-diffSec, "second");
94
+ const diffMin = Math.round(diffSec / 60);
95
+ if (Math.abs(diffMin) < 60)
96
+ return rtf.format(-diffMin, "minute");
97
+ const diffHour = Math.round(diffMin / 60);
98
+ if (Math.abs(diffHour) < 24)
99
+ return rtf.format(-diffHour, "hour");
100
+ const diffDay = Math.round(diffHour / 24);
101
+ return rtf.format(-diffDay, "day");
102
+ })();
103
+ const showRelative = (status === "saved" || status === "idle") && relativeTime !== "";
104
+ const icon = status === "saving"
105
+ ? h("span", { class: "st-autosave__spinner" }, [LoaderCircleIcon(16)])
106
+ : status === "saved"
107
+ ? CircleCheckIcon(16)
108
+ : status === "error"
109
+ ? CircleAlertIcon(16)
110
+ : null;
111
+ const triggerRetry = () => {
112
+ emit("retry");
113
+ props.onRetry?.();
114
+ };
115
+ return h("div", {
116
+ ...attrs,
117
+ class: classNames("st-autosave", `st-autosave--${status}`, props.class),
118
+ role,
119
+ "aria-live": "polite",
120
+ }, [
121
+ h("span", { class: "st-autosave__icon", "aria-hidden": "true" }, [icon]),
122
+ h("span", { class: "st-autosave__label" }, statusLabel),
123
+ showRelative
124
+ ? h("span", { class: "st-autosave__time" }, relativeTime)
125
+ : null,
126
+ status === "error" && props.onRetry
127
+ ? h("button", {
128
+ type: "button",
129
+ class: "st-autosave__retry",
130
+ onClick: triggerRetry,
131
+ }, resolvedRetryLabel)
132
+ : null,
133
+ ]);
134
+ };
135
+ },
136
+ });
137
+ //# sourceMappingURL=Autosave.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Autosave.js","sourceRoot":"","sources":["../src/Autosave.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
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
+ export type CalendarProps = {
8
+ /** Date sélectionnée ("YYYY-MM-DD") ou tuple [start,end] si `range`. */
9
+ value?: CalendarValue;
10
+ /** Appelé avec la nouvelle date (ou le tuple en mode plage). */
11
+ onChange?: (value: CalendarValue) => void;
12
+ /** Borne minimale "YYYY-MM-DD" (inclusive). */
13
+ min?: string;
14
+ /** Borne maximale "YYYY-MM-DD" (inclusive). */
15
+ max?: string;
16
+ /** Sélection d'une plage de deux dates. */
17
+ range?: boolean;
18
+ /** Premier jour de la semaine : 0 = dimanche, 1 = lundi. */
19
+ weekStartsOn?: 0 | 1;
20
+ locale?: string;
21
+ /** Mois affiché ("YYYY-MM"), contrôlable de l'extérieur. */
22
+ month?: string;
23
+ class?: string;
24
+ previousMonthLabel?: string;
25
+ nextMonthLabel?: string;
26
+ };
27
+ export declare const Calendar: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
28
+ value: {
29
+ type: () => CalendarValue;
30
+ default: null;
31
+ };
32
+ onChange: {
33
+ type: () => (value: CalendarValue) => void;
34
+ default: undefined;
35
+ };
36
+ min: {
37
+ type: StringConstructor;
38
+ default: undefined;
39
+ };
40
+ max: {
41
+ type: StringConstructor;
42
+ default: undefined;
43
+ };
44
+ range: {
45
+ type: BooleanConstructor;
46
+ default: boolean;
47
+ };
48
+ weekStartsOn: {
49
+ type: () => 0 | 1;
50
+ default: number;
51
+ };
52
+ locale: {
53
+ type: StringConstructor;
54
+ default: string;
55
+ };
56
+ month: {
57
+ type: StringConstructor;
58
+ default: undefined;
59
+ };
60
+ class: {
61
+ type: StringConstructor;
62
+ default: undefined;
63
+ };
64
+ previousMonthLabel: {
65
+ type: StringConstructor;
66
+ default: undefined;
67
+ };
68
+ nextMonthLabel: {
69
+ type: StringConstructor;
70
+ default: undefined;
71
+ };
72
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
73
+ [key: string]: any;
74
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("change" | "update:modelValue")[], "change" | "update:modelValue", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
75
+ value: {
76
+ type: () => CalendarValue;
77
+ default: null;
78
+ };
79
+ onChange: {
80
+ type: () => (value: CalendarValue) => void;
81
+ default: undefined;
82
+ };
83
+ min: {
84
+ type: StringConstructor;
85
+ default: undefined;
86
+ };
87
+ max: {
88
+ type: StringConstructor;
89
+ default: undefined;
90
+ };
91
+ range: {
92
+ type: BooleanConstructor;
93
+ default: boolean;
94
+ };
95
+ weekStartsOn: {
96
+ type: () => 0 | 1;
97
+ default: number;
98
+ };
99
+ locale: {
100
+ type: StringConstructor;
101
+ default: string;
102
+ };
103
+ month: {
104
+ type: StringConstructor;
105
+ default: undefined;
106
+ };
107
+ class: {
108
+ type: StringConstructor;
109
+ default: undefined;
110
+ };
111
+ previousMonthLabel: {
112
+ type: StringConstructor;
113
+ default: undefined;
114
+ };
115
+ nextMonthLabel: {
116
+ type: StringConstructor;
117
+ default: undefined;
118
+ };
119
+ }>> & Readonly<{
120
+ onChange?: ((...args: any[]) => any) | undefined;
121
+ "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
122
+ }>, {
123
+ class: string;
124
+ onChange: (value: CalendarValue) => void;
125
+ min: string;
126
+ max: string;
127
+ locale: string;
128
+ month: string;
129
+ range: boolean;
130
+ value: CalendarValue;
131
+ weekStartsOn: 0 | 1;
132
+ previousMonthLabel: string;
133
+ nextMonthLabel: string;
134
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
135
+ //# sourceMappingURL=Calendar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../src/Calendar.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;AAI3E,MAAM,MAAM,aAAa,GAAG;IAC1B,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;AAoEF,eAAO,MAAM,QAAQ;;cAI6B,MAAM,aAAa;;;;cAIlC,MAAM,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI;;;;;;;;;;;;;;;;cAMnC,MAAM,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;cAVG,MAAM,aAAa;;;;cAIlC,MAAM,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI;;;;;;;;;;;;;;;;cAMnC,MAAM,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBANE,aAAa,KAAK,IAAI;;;;;;;;;;4EA0QrE,CAAC"}
@@ -0,0 +1,274 @@
1
+ import { defineComponent, h, ref, watch } from "vue";
2
+ import { classNames } from "./classNames.js";
3
+ function calStartOfDay(date) {
4
+ const d = new Date(date);
5
+ d.setHours(0, 0, 0, 0);
6
+ return d;
7
+ }
8
+ function calToISO(date) {
9
+ const y = date.getFullYear();
10
+ const m = String(date.getMonth() + 1).padStart(2, "0");
11
+ const d = String(date.getDate()).padStart(2, "0");
12
+ return `${y}-${m}-${d}`;
13
+ }
14
+ function calParseISO(iso) {
15
+ if (!iso)
16
+ return null;
17
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
18
+ if (!match)
19
+ return null;
20
+ const d = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
21
+ return Number.isNaN(d.getTime()) ? null : calStartOfDay(d);
22
+ }
23
+ function calIsSameDay(a, b) {
24
+ if (!a || !b)
25
+ return false;
26
+ return (a.getFullYear() === b.getFullYear() &&
27
+ a.getMonth() === b.getMonth() &&
28
+ a.getDate() === b.getDate());
29
+ }
30
+ function ChevronLeftIcon(size) {
31
+ return h("svg", {
32
+ width: size,
33
+ height: size,
34
+ viewBox: "0 0 24 24",
35
+ fill: "none",
36
+ stroke: "currentColor",
37
+ "stroke-width": 2,
38
+ "stroke-linecap": "round",
39
+ "stroke-linejoin": "round",
40
+ "aria-hidden": "true",
41
+ }, [h("polyline", { points: "15 18 9 12 15 6" })]);
42
+ }
43
+ function ChevronRightIcon(size) {
44
+ return h("svg", {
45
+ width: size,
46
+ height: size,
47
+ viewBox: "0 0 24 24",
48
+ fill: "none",
49
+ stroke: "currentColor",
50
+ "stroke-width": 2,
51
+ "stroke-linecap": "round",
52
+ "stroke-linejoin": "round",
53
+ "aria-hidden": "true",
54
+ }, [h("polyline", { points: "9 18 15 12 9 6" })]);
55
+ }
56
+ export const Calendar = defineComponent({
57
+ name: "Calendar",
58
+ props: {
59
+ value: {
60
+ type: [String, Array, Object],
61
+ default: null,
62
+ },
63
+ onChange: {
64
+ type: Function,
65
+ default: undefined,
66
+ },
67
+ min: { type: String, default: undefined },
68
+ max: { type: String, default: undefined },
69
+ range: { type: Boolean, default: false },
70
+ weekStartsOn: { type: Number, default: 1 },
71
+ locale: { type: String, default: "fr-FR" },
72
+ month: { type: String, default: undefined },
73
+ class: { type: String, default: undefined },
74
+ previousMonthLabel: { type: String, default: undefined },
75
+ nextMonthLabel: { type: String, default: undefined },
76
+ },
77
+ emits: ["change", "update:modelValue"],
78
+ setup(props, { emit, attrs }) {
79
+ const internal = ref(props.value ?? null);
80
+ const pickInitialMonth = () => {
81
+ const parsed = calParseISO(props.month ? `${props.month}-01` : undefined);
82
+ if (parsed)
83
+ return parsed;
84
+ const v = props.value ?? internal.value;
85
+ if (!props.range) {
86
+ const single = calParseISO(v);
87
+ if (single)
88
+ return single;
89
+ }
90
+ else if (Array.isArray(v)) {
91
+ const start = calParseISO(v[0]);
92
+ if (start)
93
+ return start;
94
+ }
95
+ return calStartOfDay(new Date());
96
+ };
97
+ const initial = pickInitialMonth();
98
+ const viewYear = ref(initial.getFullYear());
99
+ const viewMonth = ref(initial.getMonth());
100
+ // Resynchronise le mois affiché lorsque la prop `month` change.
101
+ watch(() => props.month, (m) => {
102
+ const parsed = calParseISO(m ? `${m}-01` : undefined);
103
+ if (parsed) {
104
+ viewYear.value = parsed.getFullYear();
105
+ viewMonth.value = parsed.getMonth();
106
+ }
107
+ });
108
+ const previousMonth = () => {
109
+ if (viewMonth.value === 0) {
110
+ viewMonth.value = 11;
111
+ viewYear.value -= 1;
112
+ }
113
+ else {
114
+ viewMonth.value -= 1;
115
+ }
116
+ };
117
+ const nextMonth = () => {
118
+ if (viewMonth.value === 11) {
119
+ viewMonth.value = 0;
120
+ viewYear.value += 1;
121
+ }
122
+ else {
123
+ viewMonth.value += 1;
124
+ }
125
+ };
126
+ return () => {
127
+ const activeValue = props.value ?? internal.value;
128
+ const locale = props.locale;
129
+ const isFr = (locale ?? "fr-FR").toLowerCase().startsWith("fr");
130
+ const resolvedPrevLabel = props.previousMonthLabel ?? (isFr ? "Mois précédent" : "Previous month");
131
+ const resolvedNextLabel = props.nextMonthLabel ?? (isFr ? "Mois suivant" : "Next month");
132
+ const monthFormatter = new Intl.DateTimeFormat(locale, {
133
+ month: "long",
134
+ year: "numeric",
135
+ });
136
+ const weekdayFormatter = new Intl.DateTimeFormat(locale, { weekday: "short" });
137
+ const cellFormatter = new Intl.DateTimeFormat(locale, {
138
+ day: "numeric",
139
+ month: "long",
140
+ year: "numeric",
141
+ });
142
+ const single = props.range ? null : calParseISO(activeValue);
143
+ const rangeStart = props.range && Array.isArray(activeValue) ? calParseISO(activeValue[0]) : null;
144
+ const rangeEnd = props.range && Array.isArray(activeValue) ? calParseISO(activeValue[1]) : null;
145
+ const today = calStartOfDay(new Date());
146
+ // 2024-01-07 est un dimanche : on énumère puis on tourne selon weekStartsOn.
147
+ const sample = new Date(Date.UTC(2024, 0, 7));
148
+ const rawLabels = [];
149
+ for (let i = 0; i < 7; i++) {
150
+ const d = new Date(sample);
151
+ d.setUTCDate(sample.getUTCDate() + i);
152
+ rawLabels.push(weekdayFormatter.format(d));
153
+ }
154
+ const weekdayLabels = [
155
+ ...rawLabels.slice(props.weekStartsOn),
156
+ ...rawLabels.slice(0, props.weekStartsOn),
157
+ ];
158
+ const first = new Date(viewYear.value, viewMonth.value, 1);
159
+ const firstDayIdx = first.getDay();
160
+ const offset = (firstDayIdx - props.weekStartsOn + 7) % 7;
161
+ const start = new Date(viewYear.value, viewMonth.value, 1 - offset);
162
+ const grid = [];
163
+ for (let i = 0; i < 42; i++) {
164
+ const d = new Date(start);
165
+ d.setDate(start.getDate() + i);
166
+ grid.push({ date: calStartOfDay(d), inMonth: d.getMonth() === viewMonth.value });
167
+ }
168
+ const minDate = calParseISO(props.min);
169
+ const maxDate = calParseISO(props.max);
170
+ const isOutOfBounds = (date) => {
171
+ const d = calStartOfDay(date).getTime();
172
+ if (minDate && d < minDate.getTime())
173
+ return true;
174
+ if (maxDate && d > maxDate.getTime())
175
+ return true;
176
+ return false;
177
+ };
178
+ const isSelected = (date) => {
179
+ if (!props.range)
180
+ return calIsSameDay(single, date);
181
+ return calIsSameDay(rangeStart, date) || calIsSameDay(rangeEnd, date);
182
+ };
183
+ const isInRange = (date) => {
184
+ if (!props.range || !rangeStart || !rangeEnd)
185
+ return false;
186
+ const d = calStartOfDay(date).getTime();
187
+ return d > rangeStart.getTime() && d < rangeEnd.getTime();
188
+ };
189
+ const commit = (next) => {
190
+ if (props.value === undefined)
191
+ internal.value = next;
192
+ emit("update:modelValue", next);
193
+ emit("change", next);
194
+ props.onChange?.(next);
195
+ };
196
+ const pickDate = (date) => {
197
+ if (isOutOfBounds(date))
198
+ return;
199
+ const picked = calStartOfDay(date);
200
+ const iso = calToISO(picked);
201
+ if (!props.range) {
202
+ commit(iso);
203
+ return;
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
+ commit([iso, null]);
209
+ return;
210
+ }
211
+ commit([calToISO(rangeStart), iso]);
212
+ };
213
+ const monthLabel = monthFormatter.format(new Date(viewYear.value, viewMonth.value, 1));
214
+ return h("div", { ...attrs, class: classNames("st-calendar", props.class) }, [
215
+ h("div", { class: "st-calendar__nav" }, [
216
+ h("button", {
217
+ type: "button",
218
+ class: "st-calendar__navBtn",
219
+ "aria-label": resolvedPrevLabel,
220
+ onClick: previousMonth,
221
+ }, [ChevronLeftIcon(18)]),
222
+ h("span", { class: "st-calendar__monthLabel", "aria-live": "polite" }, monthLabel),
223
+ h("button", {
224
+ type: "button",
225
+ class: "st-calendar__navBtn",
226
+ "aria-label": resolvedNextLabel,
227
+ onClick: nextMonth,
228
+ }, [ChevronRightIcon(18)]),
229
+ ]),
230
+ h("div", {
231
+ class: "st-calendar__grid",
232
+ role: "grid",
233
+ tabindex: -1,
234
+ "aria-label": monthLabel,
235
+ onKeydown: (event) => {
236
+ if (event.key === "PageUp") {
237
+ event.preventDefault();
238
+ previousMonth();
239
+ }
240
+ else if (event.key === "PageDown") {
241
+ event.preventDefault();
242
+ nextMonth();
243
+ }
244
+ },
245
+ }, [
246
+ h("div", { class: "st-calendar__weekdays", role: "row" }, weekdayLabels.map((wd, i) => h("span", {
247
+ key: `${wd}-${i}`,
248
+ class: "st-calendar__weekday",
249
+ role: "columnheader",
250
+ }, wd))),
251
+ h("div", { class: "st-calendar__days" }, grid.map((cell, i) => {
252
+ const oob = isOutOfBounds(cell.date);
253
+ const selected = isSelected(cell.date);
254
+ const inRange = isInRange(cell.date);
255
+ const isToday = calIsSameDay(cell.date, today);
256
+ return h("button", {
257
+ key: i,
258
+ type: "button",
259
+ class: classNames("st-calendar__day", !cell.inMonth && "st-calendar__day--outside", selected && "st-calendar__day--selected", inRange && "st-calendar__day--inRange", isToday && "st-calendar__day--today"),
260
+ role: "gridcell",
261
+ "aria-label": cellFormatter.format(cell.date),
262
+ "aria-selected": selected ? "true" : "false",
263
+ "aria-current": isToday ? "date" : undefined,
264
+ "aria-disabled": oob ? "true" : undefined,
265
+ disabled: oob,
266
+ onClick: () => pickDate(cell.date),
267
+ }, String(cell.date.getDate()));
268
+ })),
269
+ ]),
270
+ ]);
271
+ };
272
+ },
273
+ });
274
+ //# sourceMappingURL=Calendar.js.map