@fusion.dev/helix-core 0.159.0 → 0.160.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +403 -18
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/mixins/mixin-field/mixin-field.js +2 -1
- package/dist/mixins/mixin-picker-feedback/mixin-picker-feedback.d.ts +13 -1
- package/dist/mixins/mixin-picker-feedback/mixin-picker-feedback.js +26 -3
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +12 -0
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +96 -39
- package/dist/utils/calendar/calendar.d.ts +50 -22
- package/dist/utils/calendar/calendar.js +134 -32
- package/dist/utils/date-segments/date-segments.d.ts +46 -14
- package/dist/utils/date-segments/date-segments.js +175 -27
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -50,7 +50,7 @@ export const calendarLocale = (locale, calendar = '') => {
|
|
|
50
50
|
let tag = tagged.get(key);
|
|
51
51
|
if (tag === undefined) {
|
|
52
52
|
const own = new Intl.DateTimeFormat(locale || undefined).resolvedOptions();
|
|
53
|
-
const wanted = DRAWN.includes(calendar)
|
|
53
|
+
const wanted = DRAWN.includes(calendar) || MONTH_CALENDARS.includes(calendar)
|
|
54
54
|
? calendar
|
|
55
55
|
: DRAWN.includes(own.calendar)
|
|
56
56
|
? ''
|
|
@@ -69,39 +69,64 @@ export const addMonths = (date, months) => {
|
|
|
69
69
|
return utcDate(target.getUTCFullYear(), target.getUTCMonth(), Math.min(date.getUTCDate(), last));
|
|
70
70
|
};
|
|
71
71
|
export const MONTH_CALENDARS = [
|
|
72
|
+
'chinese',
|
|
72
73
|
'coptic',
|
|
74
|
+
'dangi',
|
|
75
|
+
'ethioaa',
|
|
73
76
|
'ethiopic',
|
|
74
77
|
'hebrew',
|
|
75
78
|
'indian',
|
|
79
|
+
'islamic',
|
|
76
80
|
'islamic-civil',
|
|
81
|
+
'islamic-rgsa',
|
|
82
|
+
'islamic-tbla',
|
|
77
83
|
'islamic-umalqura',
|
|
78
84
|
'persian',
|
|
79
85
|
];
|
|
86
|
+
const gregorianDays = (year, month) => utcDate(year, month, 0).getUTCDate();
|
|
80
87
|
const GREGORIAN = {
|
|
81
88
|
add: (date, months) => addMonths(date, months),
|
|
89
|
+
addYears: (date, years) => addMonths(date, years * 12),
|
|
82
90
|
day: (date) => date.getUTCDate(),
|
|
91
|
+
days: gregorianDays,
|
|
92
|
+
from: (year, month, day) => {
|
|
93
|
+
const kept = Math.min(12, Math.max(1, month));
|
|
94
|
+
return utcDate(year, kept - 1, Math.min(Math.max(1, day), gregorianDays(year, kept)));
|
|
95
|
+
},
|
|
83
96
|
key: (date) => monthOf(date),
|
|
97
|
+
months: () => 12,
|
|
98
|
+
parts: (date) => ({
|
|
99
|
+
day: date.getUTCDate(),
|
|
100
|
+
month: date.getUTCMonth() + 1,
|
|
101
|
+
year: date.getUTCFullYear(),
|
|
102
|
+
}),
|
|
84
103
|
same: (left, right) => sameMonth(left, right),
|
|
85
104
|
start: (date) => utcDate(date.getUTCFullYear(), date.getUTCMonth(), 1),
|
|
86
105
|
};
|
|
87
106
|
const intlMonths = (calendar) => {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
let found = days.get(at);
|
|
107
|
+
const tag = `en-u-ca-${calendar}-nu-latn`;
|
|
108
|
+
const format = new Intl.DateTimeFormat(tag, { day: 'numeric', timeZone: 'UTC', year: 'numeric' });
|
|
109
|
+
const named = new Intl.DateTimeFormat(tag, { month: 'long', timeZone: 'UTC' });
|
|
110
|
+
const read = new Map();
|
|
111
|
+
const at = (date) => {
|
|
112
|
+
const time = date.getTime();
|
|
113
|
+
let found = read.get(time);
|
|
96
114
|
if (found === undefined) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
const parts = format.formatToParts(date);
|
|
116
|
+
const part = (type) => Number(parts.find((one) => one.type === type)?.value);
|
|
117
|
+
const year = part('relatedYear');
|
|
118
|
+
found = [Number.isNaN(year) ? part('year') : year, part('day')];
|
|
119
|
+
if (!(found[1] >= 1)) {
|
|
120
|
+
found[1] = at(addDays(date, -1))[1] + 1;
|
|
121
|
+
}
|
|
122
|
+
if (read.size > 4000) {
|
|
123
|
+
read.clear();
|
|
100
124
|
}
|
|
101
|
-
|
|
125
|
+
read.set(time, found);
|
|
102
126
|
}
|
|
103
127
|
return found;
|
|
104
128
|
};
|
|
129
|
+
const day = (date) => at(date)[1];
|
|
105
130
|
const start = (date) => addDays(date, 1 - day(date));
|
|
106
131
|
const length = (first) => {
|
|
107
132
|
for (const count of [29, 30, 31, 28, 32]) {
|
|
@@ -116,16 +141,78 @@ const intlMonths = (calendar) => {
|
|
|
116
141
|
}
|
|
117
142
|
return 30;
|
|
118
143
|
};
|
|
144
|
+
const anchor = start(utcDate(2000, 0, 1));
|
|
145
|
+
const anchorYear = at(anchor)[0];
|
|
146
|
+
const span = 36_525 / (at(utcDate(2100, 0, 1))[0] - anchorYear);
|
|
147
|
+
const years = new Map();
|
|
148
|
+
const firsts = (year) => {
|
|
149
|
+
let found = years.get(year);
|
|
150
|
+
if (found !== undefined) {
|
|
151
|
+
return found;
|
|
152
|
+
}
|
|
153
|
+
let probe = addDays(anchor, Math.round((year - anchorYear + 0.5) * span));
|
|
154
|
+
for (let step = 0; step < 60 && at(probe)[0] !== year; step += 1) {
|
|
155
|
+
probe = addDays(probe, (year - at(probe)[0]) * 20);
|
|
156
|
+
}
|
|
157
|
+
let first = start(probe);
|
|
158
|
+
while (at(addDays(first, -1))[0] === year) {
|
|
159
|
+
const earlier = start(addDays(first, -1));
|
|
160
|
+
if (earlier.getTime() >= first.getTime()) {
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
first = earlier;
|
|
164
|
+
}
|
|
165
|
+
found = [];
|
|
166
|
+
while (at(first)[0] === year && found.length < 14) {
|
|
167
|
+
found.push(first);
|
|
168
|
+
first = addDays(first, length(first));
|
|
169
|
+
}
|
|
170
|
+
if (years.size > 400) {
|
|
171
|
+
years.clear();
|
|
172
|
+
}
|
|
173
|
+
years.set(year, found);
|
|
174
|
+
return found;
|
|
175
|
+
};
|
|
176
|
+
const months = (year) => Math.max(1, firsts(year).length);
|
|
177
|
+
const firstOf = (year, month) => firsts(year)[Math.min(months(year), Math.max(1, month)) - 1] ?? anchor;
|
|
178
|
+
const days = (year, month) => length(firstOf(year, month));
|
|
179
|
+
const from = (year, month, wanted) => {
|
|
180
|
+
const first = firstOf(year, month);
|
|
181
|
+
return addDays(first, Math.min(Math.max(1, wanted), length(first)) - 1);
|
|
182
|
+
};
|
|
183
|
+
const parts = (date) => {
|
|
184
|
+
const [year, dayOf] = at(date);
|
|
185
|
+
const all = firsts(year);
|
|
186
|
+
let month = all.length;
|
|
187
|
+
while (month > 1 && all[month - 1].getTime() > date.getTime()) {
|
|
188
|
+
month -= 1;
|
|
189
|
+
}
|
|
190
|
+
return { day: dayOf, month, year };
|
|
191
|
+
};
|
|
119
192
|
return {
|
|
120
|
-
add: (date,
|
|
193
|
+
add: (date, count) => {
|
|
121
194
|
let first = start(date);
|
|
122
|
-
for (let step = 0; step < Math.abs(
|
|
123
|
-
first =
|
|
195
|
+
for (let step = 0; step < Math.abs(count); step += 1) {
|
|
196
|
+
first = count > 0 ? addDays(first, length(first)) : start(addDays(first, -1));
|
|
124
197
|
}
|
|
125
198
|
return addDays(first, Math.min(day(date), length(first)) - 1);
|
|
126
199
|
},
|
|
200
|
+
addYears: (date, count) => {
|
|
201
|
+
const { day: dayOf, month, year } = parts(date);
|
|
202
|
+
const name = named.format(firstOf(year, month));
|
|
203
|
+
const names = firsts(year + count).map((first) => named.format(first));
|
|
204
|
+
let index = names.indexOf(name);
|
|
205
|
+
if (index < 0) {
|
|
206
|
+
index = names.findLastIndex((one) => one.startsWith(name) || name.startsWith(one));
|
|
207
|
+
}
|
|
208
|
+
return from(year + count, index < 0 ? month : index + 1, dayOf);
|
|
209
|
+
},
|
|
127
210
|
day,
|
|
211
|
+
days,
|
|
212
|
+
from,
|
|
128
213
|
key: (date) => fromDate(start(date)),
|
|
214
|
+
months,
|
|
215
|
+
parts,
|
|
129
216
|
same: (left, right) => start(left).getTime() === start(right).getTime(),
|
|
130
217
|
start,
|
|
131
218
|
};
|
|
@@ -181,27 +268,29 @@ export const periodMove = (key, at, along) => {
|
|
|
181
268
|
return null;
|
|
182
269
|
}
|
|
183
270
|
};
|
|
184
|
-
export const monthOut = (first, min, max) =>
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
export const
|
|
189
|
-
export const periodCells = ({ max, min, page, shown, today, view }) => {
|
|
271
|
+
export const monthOut = (first, min, max, system = GREGORIAN) => (min !== '' && fromDate(addDays(system.add(first, 1), -1)) < min) ||
|
|
272
|
+
(max !== '' && fromDate(first) > max);
|
|
273
|
+
export const yearOut = (year, min, max, system = GREGORIAN) => (min !== '' && fromDate(addDays(system.from(year + 1, 1, 1), -1)) < min) ||
|
|
274
|
+
(max !== '' && fromDate(system.from(year, 1, 1)) > max);
|
|
275
|
+
export const periodCells = ({ max, min, page, shown, system = GREGORIAN, today, view, }) => {
|
|
190
276
|
const years = view === 'year';
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
277
|
+
const now = asDate(today);
|
|
278
|
+
const held = system.parts(shown).year;
|
|
279
|
+
const count = years ? YEARS_SHOWN : system.months(page);
|
|
280
|
+
const startYear = years ? yearPageOf(page) : page;
|
|
281
|
+
return Array.from({ length: count }, (_, index) => {
|
|
282
|
+
const at = years ? startYear + index : index;
|
|
283
|
+
const first = years ? system.from(at, 1, 1) : system.from(page, index + 1, 1);
|
|
195
284
|
return {
|
|
196
285
|
at,
|
|
197
|
-
chosen: years ? at ===
|
|
198
|
-
current: years ?
|
|
286
|
+
chosen: years ? at === held : system.same(first, shown),
|
|
287
|
+
current: now !== null && (years ? system.parts(now).year === at : system.same(first, now)),
|
|
199
288
|
first,
|
|
200
|
-
out: years ? yearOut(at, min, max) : monthOut(first, min, max),
|
|
289
|
+
out: years ? yearOut(at, min, max, system) : monthOut(first, min, max, system),
|
|
201
290
|
};
|
|
202
291
|
});
|
|
203
292
|
};
|
|
204
|
-
export const periodKey = (view, stop, year, key, along) => {
|
|
293
|
+
export const periodKey = (view, stop, year, key, along, months = () => 12) => {
|
|
205
294
|
const index = view === 'year' ? stop - yearPageOf(stop) : stop;
|
|
206
295
|
const delta = periodMove(key, index, along);
|
|
207
296
|
if (delta === null) {
|
|
@@ -210,6 +299,19 @@ export const periodKey = (view, stop, year, key, along) => {
|
|
|
210
299
|
if (view === 'year') {
|
|
211
300
|
return { focus: stop + delta, pageYear: stop + delta };
|
|
212
301
|
}
|
|
213
|
-
|
|
214
|
-
|
|
302
|
+
if (key === 'PageUp' || key === 'PageDown') {
|
|
303
|
+
const to = year + Math.sign(delta);
|
|
304
|
+
return { focus: Math.min(stop, months(to) - 1), pageYear: to };
|
|
305
|
+
}
|
|
306
|
+
let at = stop + delta;
|
|
307
|
+
let on = year;
|
|
308
|
+
while (at < 0) {
|
|
309
|
+
on -= 1;
|
|
310
|
+
at += months(on);
|
|
311
|
+
}
|
|
312
|
+
while (at >= months(on)) {
|
|
313
|
+
at -= months(on);
|
|
314
|
+
on += 1;
|
|
315
|
+
}
|
|
316
|
+
return { focus: at, pageYear: on };
|
|
215
317
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { MonthSystem } from '../calendar/calendar.js';
|
|
1
2
|
/** One part of a date or a time a reader sets on its own. */
|
|
2
3
|
export type SegmentKind = 'day' | 'dayPeriod' | 'hour' | 'minute' | 'month' | 'second' | 'year';
|
|
3
4
|
/** What a segmented field holds: a date, or a time of day. */
|
|
4
5
|
export type SegmentedKind = 'date' | 'time';
|
|
5
6
|
/**
|
|
6
|
-
* The parts set so far, each a number: the month 1 to 12,
|
|
7
|
+
* The parts set so far, each a number: the month 1 to 12, or to 13 in a
|
|
8
|
+
* calendar whose year can have 13, the hour 0 to 23
|
|
7
9
|
* whatever clock it is shown on, and the half of the day 0 for before noon and
|
|
8
10
|
* 1 for after. A part not yet set is absent.
|
|
9
11
|
*/
|
|
@@ -37,7 +39,7 @@ export declare const isTwelveHourLayout: (layout: readonly SegmentSlot[]) => boo
|
|
|
37
39
|
* The lowest and highest a segment can be, as it is shown: the hour on a twelve
|
|
38
40
|
* hour clock is 1 to 12, and a day goes as high as its month allows.
|
|
39
41
|
*/
|
|
40
|
-
export declare const segmentRange: (kind: SegmentKind, segments: Segments, twelve: boolean) => {
|
|
42
|
+
export declare const segmentRange: (kind: SegmentKind, segments: Segments, twelve: boolean, system?: MonthSystem) => {
|
|
41
43
|
max: number;
|
|
42
44
|
min: number;
|
|
43
45
|
};
|
|
@@ -45,16 +47,16 @@ export declare const segmentRange: (kind: SegmentKind, segments: Segments, twelv
|
|
|
45
47
|
export declare const shownValue: (kind: SegmentKind, segments: Segments, twelve: boolean) => number | undefined;
|
|
46
48
|
/**
|
|
47
49
|
* Sets a segment from the number shown for it, which on a twelve hour clock
|
|
48
|
-
* is put back together with the half of the day, and keeps the
|
|
49
|
-
* month.
|
|
50
|
+
* is put back together with the half of the day, and keeps the month inside
|
|
51
|
+
* its year and the day inside its month.
|
|
50
52
|
*/
|
|
51
|
-
export declare const setSegment: (segments: Segments, kind: SegmentKind, shown: number | undefined, twelve: boolean) => Segments;
|
|
53
|
+
export declare const setSegment: (segments: Segments, kind: SegmentKind, shown: number | undefined, twelve: boolean, system?: MonthSystem) => Segments;
|
|
52
54
|
/**
|
|
53
55
|
* A segment moved by a key's step, wrapping at its ends as a dial does, and
|
|
54
56
|
* starting from where a reader would look for it when it is empty: the lowest
|
|
55
57
|
* going up, the highest going down, and this year for a year.
|
|
56
58
|
*/
|
|
57
|
-
export declare const stepSegment: (segments: Segments, kind: SegmentKind, by: number, twelve: boolean, thisYear?: number) => Segments;
|
|
59
|
+
export declare const stepSegment: (segments: Segments, kind: SegmentKind, by: number, twelve: boolean, thisYear?: number, system?: MonthSystem) => Segments;
|
|
58
60
|
/**
|
|
59
61
|
* A digit typed into a segment, given what was typed into it already.
|
|
60
62
|
*
|
|
@@ -64,7 +66,7 @@ export declare const stepSegment: (segments: Segments, kind: SegmentKind, by: nu
|
|
|
64
66
|
* on, which is React Aria's typing: `3` in a month is March and moves to the
|
|
65
67
|
* day, `1` waits for a second digit.
|
|
66
68
|
*/
|
|
67
|
-
export declare const typeDigit: (segments: Segments, kind: SegmentKind, typed: string, digit: string, twelve: boolean) => {
|
|
69
|
+
export declare const typeDigit: (segments: Segments, kind: SegmentKind, typed: string, digit: string, twelve: boolean, system?: MonthSystem) => {
|
|
68
70
|
advance: boolean;
|
|
69
71
|
segments: Segments;
|
|
70
72
|
typed: string;
|
|
@@ -77,6 +79,8 @@ export interface SegmentKeyState {
|
|
|
77
79
|
locale: string;
|
|
78
80
|
/** The field's parts. */
|
|
79
81
|
segments: Segments;
|
|
82
|
+
/** The calendar a date's parts are counted in, Gregorian where none is given. */
|
|
83
|
+
system?: MonthSystem | undefined;
|
|
80
84
|
/** Whether the field is on a twelve hour clock. */
|
|
81
85
|
twelve: boolean;
|
|
82
86
|
/** The digits typed into the segment so far. */
|
|
@@ -92,13 +96,16 @@ export interface SegmentKeyState {
|
|
|
92
96
|
* the half whose word starts with it. `advance` says the segment is full and
|
|
93
97
|
* focus moves on.
|
|
94
98
|
*/
|
|
95
|
-
export declare const segmentKey: (key: string, { kind, locale, segments, twelve, typed }: SegmentKeyState) => {
|
|
99
|
+
export declare const segmentKey: (key: string, { kind, locale, segments, system, twelve, typed }: SegmentKeyState) => {
|
|
96
100
|
advance: boolean;
|
|
97
101
|
segments: Segments;
|
|
98
102
|
typed: string;
|
|
99
103
|
} | null;
|
|
100
|
-
/**
|
|
101
|
-
|
|
104
|
+
/**
|
|
105
|
+
* The segments a value names, or none where it names nothing whole: a date's
|
|
106
|
+
* year, month, and day as `system` counts them, Gregorian where none is given.
|
|
107
|
+
*/
|
|
108
|
+
export declare const segmentsOf: (kind: SegmentedKind, value: string, system?: MonthSystem) => Segments;
|
|
102
109
|
/**
|
|
103
110
|
* The value the segments add up to, or the empty string while any segment the
|
|
104
111
|
* layout shows is unset.
|
|
@@ -106,9 +113,9 @@ export declare const segmentsOf: (kind: SegmentedKind, value: string) => Segment
|
|
|
106
113
|
* A date that does not exist, the thirtieth of February, cannot be reached,
|
|
107
114
|
* the day being kept inside its month as it is set.
|
|
108
115
|
*/
|
|
109
|
-
export declare const segmentsValue: (kind: SegmentedKind, segments: Segments, layout: readonly SegmentSlot[]) => string;
|
|
116
|
+
export declare const segmentsValue: (kind: SegmentedKind, segments: Segments, layout: readonly SegmentSlot[], system?: MonthSystem) => string;
|
|
110
117
|
/** Whether a reader has set some segments and not all of them. */
|
|
111
|
-
export declare const isPartial: (kind: SegmentedKind, segments: Segments, layout: readonly SegmentSlot[]) => boolean;
|
|
118
|
+
export declare const isPartial: (kind: SegmentedKind, segments: Segments, layout: readonly SegmentSlot[], system?: MonthSystem) => boolean;
|
|
112
119
|
/**
|
|
113
120
|
* What a segment shows: its number, padded the way the locale pads it, the
|
|
114
121
|
* locale's own word for the half of the day, or dashes while it is unset.
|
|
@@ -117,8 +124,33 @@ export declare const segmentText: (kind: SegmentKind, segments: Segments, twelve
|
|
|
117
124
|
/**
|
|
118
125
|
* What a screen reader hears for a segment's value: what it shows, and for a
|
|
119
126
|
* month its name as well, "8 – August", which is React Aria's reading. A
|
|
120
|
-
* month heard as a number alone leaves a reader counting.
|
|
127
|
+
* month heard as a number alone leaves a reader counting. In a calendar with
|
|
128
|
+
* months of its own, the name is that calendar's month in the year the
|
|
129
|
+
* segments hold, or in this year: "7 – Mehr".
|
|
121
130
|
*/
|
|
122
|
-
export declare const segmentValueText: (kind: SegmentKind, segments: Segments, twelve: boolean, locale: string) => string;
|
|
131
|
+
export declare const segmentValueText: (kind: SegmentKind, segments: Segments, twelve: boolean, locale: string, system?: MonthSystem) => string;
|
|
123
132
|
/** What a locale calls a segment, such as "month" or "AM/PM", from `Intl`. */
|
|
124
133
|
export declare const segmentName: (kind: SegmentKind, locale: string) => string;
|
|
134
|
+
/**
|
|
135
|
+
* The date a reader wrote in the order and the digits of `locale`, as
|
|
136
|
+
* `YYYY-MM-DD`, or empty where it is no date.
|
|
137
|
+
*
|
|
138
|
+
* `8/4/2026` in `en-US` and `4.8.2026` in `de-DE` are August 4, 2026, and a
|
|
139
|
+
* month written as a word, `Aug 4, 2026` or `4. August 2026`, is read by its
|
|
140
|
+
* name in the Gregorian calendar. A first number of four digits is a year,
|
|
141
|
+
* as ISO writes it. A year of two digits is the nearest year within eighty
|
|
142
|
+
* years back and twenty forward. `Intl` formats and does not parse, so this
|
|
143
|
+
* reads the numbers back in the order `segmentLayout` finds the locale
|
|
144
|
+
* writing them. Under `system`, the numbers are that calendar's year, month,
|
|
145
|
+
* and day.
|
|
146
|
+
*/
|
|
147
|
+
export declare const readWrittenDate: (text: string, locale: string, system?: MonthSystem) => string;
|
|
148
|
+
/**
|
|
149
|
+
* The time a reader wrote, as `HH:mm`, or `HH:mm:ss` with `seconds`, or empty
|
|
150
|
+
* where it is no time.
|
|
151
|
+
*
|
|
152
|
+
* Reads `2:30 PM`, `2.30pm`, `14h30`, and `3 pm` alike, with the half of the
|
|
153
|
+
* day in the locale's own words or as `am` and `pm`, and digits in the
|
|
154
|
+
* locale's own script.
|
|
155
|
+
*/
|
|
156
|
+
export declare const readWrittenTime: (text: string, locale: string, seconds?: boolean) => string;
|