@sentropic/design-system-svelte 0.34.70 → 0.34.71

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,75 @@
1
+ export type TimeRangeMode = "relative" | "absolute";
2
+ export type TimeRange = {
3
+ mode: TimeRangeMode;
4
+ /** Present ONLY for relative ranges — the resolved preset token (e.g. "30m"). */
5
+ relative?: string;
6
+ /** Epoch ms, inclusive lower bound. Always <= `to`. */
7
+ from: number;
8
+ /** Epoch ms, inclusive upper bound. */
9
+ to: number;
10
+ };
11
+ export type TimeRangePreset = string | {
12
+ token: string;
13
+ label?: string;
14
+ durationMs?: number;
15
+ };
16
+ export declare const DEFAULT_TIME_RANGE_PRESETS: string[];
17
+ /**
18
+ * Parses a preset token ("30m", "1h", "7d", "2w", ...) into a duration in ms.
19
+ * Returns null when the token doesn't match the locked grammar
20
+ * `^(\d+)(m|h|d|w)$`.
21
+ */
22
+ export declare function parsePresetMs(token: string): number | null;
23
+ /**
24
+ * Resolves a relative preset token to a concrete {@link TimeRange} anchored at
25
+ * `now`. `to` is clamped to `max` (if given) and `from` is clamped to `min`
26
+ * (if given). Returns null when the token doesn't parse OR when, after
27
+ * clamping, the window collapses (`from > to`) — the caller (the component)
28
+ * treats a null resolution as "disable this preset".
29
+ */
30
+ export declare function resolveRelative(token: string, now: number, opts?: {
31
+ min?: number;
32
+ max?: number;
33
+ }): TimeRange | null;
34
+ export type AbsoluteSplit = {
35
+ fromDate: string;
36
+ fromTime: string;
37
+ toDate: string;
38
+ toTime: string;
39
+ };
40
+ /**
41
+ * Splits an epoch-ms `from`/`to` pair into local-timezone date/time parts, one
42
+ * pair per bound. Used to seed the Custom tab's calendar + time pickers +
43
+ * typed inputs from a concrete range (relative or absolute).
44
+ */
45
+ export declare function splitAbsolute(from: number, to: number): AbsoluteSplit;
46
+ export type AbsoluteDraft = {
47
+ fromDate?: string;
48
+ fromTime?: string;
49
+ toDate?: string;
50
+ toTime?: string;
51
+ };
52
+ /**
53
+ * Composes a staged Custom-tab draft into a concrete {from,to} epoch-ms pair.
54
+ * Returns null when the draft is incomplete, any part fails to parse, or the
55
+ * composed `from` is after `to` — the caller uses null to disable Apply and
56
+ * show an inline error.
57
+ */
58
+ export declare function composeAbsolute(draft: AbsoluteDraft): {
59
+ from: number;
60
+ to: number;
61
+ } | null;
62
+ /**
63
+ * Human label for a preset token ("30m" -> "30 dernières minutes" / "Last 30
64
+ * minutes"). Falls back to the raw token when it doesn't match the grammar
65
+ * (e.g. a caller-provided custom token) so the UI never renders empty text.
66
+ */
67
+ export declare function formatPresetLabel(token: string, locale?: string): string;
68
+ /**
69
+ * Human label for the picker's trigger button: the preset label for a
70
+ * relative value, or a formatted date range for an absolute one — collapsing
71
+ * to a single date + "HH:mm–HH:mm" when both bounds fall on the same local
72
+ * calendar day.
73
+ */
74
+ export declare function formatTriggerLabel(value: TimeRange, locale?: string): string;
75
+ //# sourceMappingURL=timeRange.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeRange.d.ts","sourceRoot":"","sources":["../src/lib/timeRange.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,CAAC;AAEpD,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,aAAa,CAAC;IACpB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB,MAAM,GACN;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,0BAA0B,EAAE,MAAM,EAU9C,CAAC;AAYF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ1D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACpC,SAAS,GAAG,IAAI,CAalB;AAgBD,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,aAAa,CASrE;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAqBF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAOzF;AAuCD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAU,GAAG,MAAM,CAMzE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,SAAU,GAAG,MAAM,CA4B7E"}
@@ -0,0 +1,198 @@
1
+ // TimeRangePicker — pure, framework-free helpers. Kept separate from the
2
+ // component so the value contract (token grammar, resolution, formatting) is
3
+ // unit-testable without mounting Svelte at all.
4
+ export const DEFAULT_TIME_RANGE_PRESETS = [
5
+ "30m",
6
+ "1h",
7
+ "3h",
8
+ "6h",
9
+ "12h",
10
+ "24h",
11
+ "3d",
12
+ "7d",
13
+ "30d"
14
+ ];
15
+ /** token grammar: <digits><unit>, unit in m|h|d|w. */
16
+ const TOKEN_RE = /^(\d+)(m|h|d|w)$/;
17
+ const UNIT_MS = {
18
+ m: 60_000,
19
+ h: 3_600_000,
20
+ d: 86_400_000,
21
+ w: 604_800_000
22
+ };
23
+ /**
24
+ * Parses a preset token ("30m", "1h", "7d", "2w", ...) into a duration in ms.
25
+ * Returns null when the token doesn't match the locked grammar
26
+ * `^(\d+)(m|h|d|w)$`.
27
+ */
28
+ export function parsePresetMs(token) {
29
+ const match = TOKEN_RE.exec(token);
30
+ if (!match)
31
+ return null;
32
+ const amount = Number(match[1]);
33
+ const unit = match[2];
34
+ const unitMs = UNIT_MS[unit];
35
+ if (!unitMs)
36
+ return null;
37
+ return amount * unitMs;
38
+ }
39
+ /**
40
+ * Resolves a relative preset token to a concrete {@link TimeRange} anchored at
41
+ * `now`. `to` is clamped to `max` (if given) and `from` is clamped to `min`
42
+ * (if given). Returns null when the token doesn't parse OR when, after
43
+ * clamping, the window collapses (`from > to`) — the caller (the component)
44
+ * treats a null resolution as "disable this preset".
45
+ */
46
+ export function resolveRelative(token, now, opts) {
47
+ const durationMs = parsePresetMs(token);
48
+ if (durationMs == null)
49
+ return null;
50
+ let to = now;
51
+ let from = now - durationMs;
52
+ if (opts?.max != null)
53
+ to = Math.min(to, opts.max);
54
+ if (opts?.min != null)
55
+ from = Math.max(from, opts.min);
56
+ if (from > to)
57
+ return null;
58
+ return { mode: "relative", relative: token, from, to };
59
+ }
60
+ function pad2(n) {
61
+ return String(n).padStart(2, "0");
62
+ }
63
+ /** Local-tz "YYYY-MM-DD" for a Date. */
64
+ function toISODate(d) {
65
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
66
+ }
67
+ /** Local-tz "HH:mm" for a Date. */
68
+ function toHHMM(d) {
69
+ return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
70
+ }
71
+ /**
72
+ * Splits an epoch-ms `from`/`to` pair into local-timezone date/time parts, one
73
+ * pair per bound. Used to seed the Custom tab's calendar + time pickers +
74
+ * typed inputs from a concrete range (relative or absolute).
75
+ */
76
+ export function splitAbsolute(from, to) {
77
+ const f = new Date(from);
78
+ const t = new Date(to);
79
+ return {
80
+ fromDate: toISODate(f),
81
+ fromTime: toHHMM(f),
82
+ toDate: toISODate(t),
83
+ toTime: toHHMM(t)
84
+ };
85
+ }
86
+ const DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
87
+ const TIME_RE = /^(\d{1,2}):(\d{2})$/;
88
+ function parseLocalDateTime(dateStr, timeStr) {
89
+ const dm = DATE_RE.exec(dateStr);
90
+ const tm = TIME_RE.exec(timeStr);
91
+ if (!dm || !tm)
92
+ return null;
93
+ const year = Number(dm[1]);
94
+ const month = Number(dm[2]);
95
+ const day = Number(dm[3]);
96
+ const hour = Number(tm[1]);
97
+ const minute = Number(tm[2]);
98
+ if (month < 1 || month > 12 || day < 1 || day > 31)
99
+ return null;
100
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59)
101
+ return null;
102
+ const d = new Date(year, month - 1, day, hour, minute, 0, 0);
103
+ const ms = d.getTime();
104
+ return Number.isNaN(ms) ? null : ms;
105
+ }
106
+ /**
107
+ * Composes a staged Custom-tab draft into a concrete {from,to} epoch-ms pair.
108
+ * Returns null when the draft is incomplete, any part fails to parse, or the
109
+ * composed `from` is after `to` — the caller uses null to disable Apply and
110
+ * show an inline error.
111
+ */
112
+ export function composeAbsolute(draft) {
113
+ if (!draft.fromDate || !draft.fromTime || !draft.toDate || !draft.toTime)
114
+ return null;
115
+ const from = parseLocalDateTime(draft.fromDate, draft.fromTime);
116
+ const to = parseLocalDateTime(draft.toDate, draft.toTime);
117
+ if (from == null || to == null)
118
+ return null;
119
+ if (from > to)
120
+ return null;
121
+ return { from, to };
122
+ }
123
+ function isFrLocale(locale) {
124
+ return (locale ?? "fr-FR").toLowerCase().startsWith("fr");
125
+ }
126
+ const UNIT_META_FR = {
127
+ m: { singular: "minute", plural: "minutes", masculine: false },
128
+ h: { singular: "heure", plural: "heures", masculine: false },
129
+ d: { singular: "jour", plural: "jours", masculine: true },
130
+ w: { singular: "semaine", plural: "semaines", masculine: false }
131
+ };
132
+ const UNIT_META_EN = {
133
+ m: { singular: "minute", plural: "minutes", masculine: false },
134
+ h: { singular: "hour", plural: "hours", masculine: false },
135
+ d: { singular: "day", plural: "days", masculine: false },
136
+ w: { singular: "week", plural: "weeks", masculine: false }
137
+ };
138
+ function frPresetLabel(amount, unit) {
139
+ const meta = UNIT_META_FR[unit];
140
+ if (!meta)
141
+ return `${amount}${unit}`;
142
+ const unitLabel = amount === 1 ? meta.singular : meta.plural;
143
+ if (amount === 1) {
144
+ return `${meta.masculine ? "Dernier" : "Dernière"} ${unitLabel}`;
145
+ }
146
+ return `${amount} ${meta.masculine ? "derniers" : "dernières"} ${unitLabel}`;
147
+ }
148
+ function enPresetLabel(amount, unit) {
149
+ const meta = UNIT_META_EN[unit];
150
+ if (!meta)
151
+ return `Last ${amount}${unit}`;
152
+ if (amount === 1)
153
+ return `Last ${meta.singular}`;
154
+ return `Last ${amount} ${meta.plural}`;
155
+ }
156
+ /**
157
+ * Human label for a preset token ("30m" -> "30 dernières minutes" / "Last 30
158
+ * minutes"). Falls back to the raw token when it doesn't match the grammar
159
+ * (e.g. a caller-provided custom token) so the UI never renders empty text.
160
+ */
161
+ export function formatPresetLabel(token, locale = "fr-FR") {
162
+ const match = TOKEN_RE.exec(token);
163
+ if (!match)
164
+ return token;
165
+ const amount = Number(match[1]);
166
+ const unit = match[2];
167
+ return isFrLocale(locale) ? frPresetLabel(amount, unit) : enPresetLabel(amount, unit);
168
+ }
169
+ /**
170
+ * Human label for the picker's trigger button: the preset label for a
171
+ * relative value, or a formatted date range for an absolute one — collapsing
172
+ * to a single date + "HH:mm–HH:mm" when both bounds fall on the same local
173
+ * calendar day.
174
+ */
175
+ export function formatTriggerLabel(value, locale = "fr-FR") {
176
+ if (value.mode === "relative" && value.relative) {
177
+ return formatPresetLabel(value.relative, locale);
178
+ }
179
+ const from = new Date(value.from);
180
+ const to = new Date(value.to);
181
+ const dateFormatter = new Intl.DateTimeFormat(locale, {
182
+ day: "2-digit",
183
+ month: "2-digit",
184
+ year: "numeric"
185
+ });
186
+ const timeFormatter = new Intl.DateTimeFormat(locale, {
187
+ hour: "2-digit",
188
+ minute: "2-digit",
189
+ hour12: false
190
+ });
191
+ const sameDay = from.getFullYear() === to.getFullYear() &&
192
+ from.getMonth() === to.getMonth() &&
193
+ from.getDate() === to.getDate();
194
+ if (sameDay) {
195
+ return `${dateFormatter.format(from)} ${timeFormatter.format(from)}–${timeFormatter.format(to)}`;
196
+ }
197
+ return `${dateFormatter.format(from)} ${timeFormatter.format(from)} – ${dateFormatter.format(to)} ${timeFormatter.format(to)}`;
198
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentropic/design-system-svelte",
3
- "version": "0.34.70",
3
+ "version": "0.34.71",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"