@fusion.dev/helix-headless 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 +1252 -140
- package/dist/date-input/root/date-input-root.d.ts +108 -19
- package/dist/date-input/root/date-input-root.js +112 -13
- package/dist/date-picker/root/date-picker-root.d.ts +81 -16
- package/dist/date-picker/root/date-picker-root.js +140 -36
- package/dist/element-events.generated.js +2 -0
- package/dist/format-date/fields.js +1 -0
- package/dist/format-date/options.d.ts +3 -1
- package/dist/format-date/options.js +4 -1
- package/dist/format-date/root/format-date-root.d.ts +27 -11
- package/dist/format-date/root/format-date-root.js +6 -0
- package/dist/listbox/root/listbox-root.js +5 -0
- package/dist/notifications/notification/notification.d.ts +1 -0
- package/dist/notifications/notification/notification.js +2 -1
- package/dist/slider/root/slider-root.d.ts +54 -3
- package/dist/slider/root/slider-root.js +124 -2
- package/dist/time-input/root/time-input-root.d.ts +55 -7
- package/dist/time-input/root/time-input-root.js +70 -10
- package/dist/time-picker/root/time-picker-root.d.ts +27 -5
- package/dist/time-picker/root/time-picker-root.js +52 -13
- package/dist/toggle/group/toggle-group.d.ts +18 -1
- package/dist/toggle/group/toggle-group.js +8 -2
- package/dist/toggle/root/toggle-root.d.ts +2 -2
- package/dist/toggle/root/toggle-root.js +2 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +660 -156
- package/types/vue.d.ts +178 -37
- package/types/words.d.ts +72 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CalendarView, type DateCalendar, HelixFormElement, type MonthCalendar } from '@fusion.dev/helix-core';
|
|
1
|
+
import { type CalendarView, type DateCalendar, HelixFormElement, type MonthCalendar, type PickerFailure } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinPickerFeedbackInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & typeof HelixFormElement;
|
|
4
4
|
/**
|
|
@@ -15,9 +15,11 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
|
|
|
15
15
|
* **Dates are `YYYY-MM-DD` strings, and every calculation is in UTC**, because
|
|
16
16
|
* a local-time `Date` shifts across a time zone or daylight-saving boundary.
|
|
17
17
|
*
|
|
18
|
-
* **The
|
|
19
|
-
* locale's own where its months are the Gregorian months, as `th-TH`
|
|
20
|
-
* 2569 for 2026, and `gregory` where they are not, as for `fa-IR
|
|
18
|
+
* **The months are the ones `calendar` counts**: the Gregorian months for the
|
|
19
|
+
* locale's own calendar where its months are the Gregorian months, as `th-TH`
|
|
20
|
+
* writes 2569 for 2026, and `gregory` where they are not, as for `fa-IR`
|
|
21
|
+
* with no `calendar`. Name a calendar with months of its own, such as
|
|
22
|
+
* `persian`, and every view counts its days, months, and years from `Intl`.
|
|
21
23
|
*
|
|
22
24
|
* `month` sets which month the grid shows, separately from `value`, so paging
|
|
23
25
|
* selects nothing and you can control the view. When unset, it follows the
|
|
@@ -25,7 +27,14 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
|
|
|
25
27
|
*
|
|
26
28
|
* `mode` sets how many dates are held. Unset holds one, `range` holds two and
|
|
27
29
|
* marks the dates between them, and `multiple` toggles each date. All three
|
|
28
|
-
* read and write the same comma-separated `value`.
|
|
30
|
+
* read and write the same comma-separated `value`. With a `mode`, the grid
|
|
31
|
+
* carries `aria-multiselectable`. While a range holds one end, the grid draws
|
|
32
|
+
* the range to the day under the pointer or the focus, marking those cells
|
|
33
|
+
* `data-range-preview`.
|
|
34
|
+
*
|
|
35
|
+
* The chosen date, range, or count of dates is the grid's description, in a
|
|
36
|
+
* region screen readers announce when it changes, as React Aria's calendars
|
|
37
|
+
* do. `selection-label` words it.
|
|
29
38
|
*
|
|
30
39
|
* `max-view` makes the heading a button that steps back to the months of the
|
|
31
40
|
* year, and with `year` to a page of years, as Zag's date picker does. The
|
|
@@ -58,7 +67,7 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
|
|
|
58
67
|
* @event {Event} input - Fired after the value changes.
|
|
59
68
|
*
|
|
60
69
|
* @csspart cell - One column of a week row. A range is painted on the cells.
|
|
61
|
-
* @csspart cell-range-end - The cell of a range's last day, beside `cell
|
|
70
|
+
* @csspart cell-range-end - The cell of a range's last day, beside `cell`, or of the day a waiting range is drawn to.
|
|
62
71
|
* @csspart cell-range-middle - A cell between a range's ends, beside `cell`.
|
|
63
72
|
* @csspart cell-range-start - The cell of a range's first day, beside `cell`.
|
|
64
73
|
* @csspart day - The shape inside a cell. A single chosen date is painted on it.
|
|
@@ -84,12 +93,17 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
|
|
|
84
93
|
* @csspart periods - The grid of months or years.
|
|
85
94
|
* @csspart previous - The control that moves to the previous month.
|
|
86
95
|
* @csspart previous-mark - The glyph on the previous month control, where none is slotted.
|
|
96
|
+
* @csspart selection - The chosen date or range in words, which screen readers hear when it changes. Drawn for screen readers only.
|
|
87
97
|
* @csspart week - One row of days.
|
|
88
98
|
* @csspart weekday - A column header naming a day of the week. It shows the short name and says the full one.
|
|
89
99
|
*
|
|
90
100
|
* @cssproperty --date-picker-column-count - How many month columns are drawn, capped at two. `--date-picker-columns` overrides it.
|
|
91
101
|
*
|
|
92
|
-
* @cssstate
|
|
102
|
+
* @cssstate blank - While no date is chosen.
|
|
103
|
+
* @cssstate invalid - While `invalid` is set, `feedback` says what is wrong, the picker holds a date outside `min` and `max` or one it bars, or it is `required`, holds no date, and the reader has answered once or a form has refused it.
|
|
104
|
+
* @cssstate month-view - While the months of a year are shown, where `max-view` is set.
|
|
105
|
+
* @cssstate range-pending - While a `range` holds its first end and waits for the second.
|
|
106
|
+
* @cssstate year-view - While a page of years is shown, where `max-view` is `year`.
|
|
93
107
|
*/
|
|
94
108
|
export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_base {
|
|
95
109
|
/**
|
|
@@ -116,14 +130,16 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
|
|
|
116
130
|
* `ja-JP-u-ca-japanese`, and `gregory` for every other, such as `persian`
|
|
117
131
|
* for `fa-IR`.
|
|
118
132
|
*
|
|
119
|
-
* `persian`, `hebrew`, `islamic-umalqura`, `islamic-civil`,
|
|
120
|
-
* `
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
133
|
+
* `persian`, `hebrew`, `islamic`, `islamic-umalqura`, `islamic-civil`,
|
|
134
|
+
* `islamic-tbla`, `islamic-rgsa`, `ethiopic`, `ethioaa`, `coptic`,
|
|
135
|
+
* `indian`, `chinese`, and `dangi` draw a calendar with months of its own,
|
|
136
|
+
* as React Aria's calendars do: each grid is one of that calendar's months,
|
|
137
|
+
* the Hebrew and Chinese leap months and Ethiopia's five-day thirteenth
|
|
138
|
+
* month included, and Previous, Next and the page keys move by those
|
|
139
|
+
* months. With `max-view`, the months view shows that calendar's year, of
|
|
140
|
+
* twelve or thirteen months, and the years view its years. `month` and
|
|
124
141
|
* `helix-navigate` then name a month by its first day, `YYYY-MM-DD`. The
|
|
125
|
-
*
|
|
126
|
-
* months only. The value is `YYYY-MM-DD` in every calendar.
|
|
142
|
+
* value is `YYYY-MM-DD` in every calendar.
|
|
127
143
|
*/
|
|
128
144
|
calendar?: DateCalendar | MonthCalendar;
|
|
129
145
|
/**
|
|
@@ -207,6 +223,8 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
|
|
|
207
223
|
mode?: 'range' | 'multiple';
|
|
208
224
|
/** The month on show, whoever set it, and `undefined` until somebody has. */
|
|
209
225
|
private monthState;
|
|
226
|
+
/** Whether the month on show is one the reader paged to, which `showChosen` forgets. */
|
|
227
|
+
private browsed;
|
|
210
228
|
/**
|
|
211
229
|
* The month shown, as `YYYY-MM`, or as its first day, `YYYY-MM-DD`, under a
|
|
212
230
|
* `calendar` with months of its own. When it is not set, it follows
|
|
@@ -284,6 +302,21 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
|
|
|
284
302
|
private choosing;
|
|
285
303
|
/** The range's ends, when there are two and the mode asks for them. */
|
|
286
304
|
private get range();
|
|
305
|
+
/**
|
|
306
|
+
* The day a reader points at or moves to while a range holds one end, which
|
|
307
|
+
* the grid draws the range to before it is chosen.
|
|
308
|
+
*/
|
|
309
|
+
private previewed;
|
|
310
|
+
/** Whether a range holds its first end and waits for the second. */
|
|
311
|
+
private get pending();
|
|
312
|
+
/**
|
|
313
|
+
* The range the grid draws: the chosen one, or, while one end waits for
|
|
314
|
+
* the other, the run to the day under the pointer or the focus.
|
|
315
|
+
*/
|
|
316
|
+
private get drawnRange();
|
|
317
|
+
/** Draws a waiting range to the day the pointer is on. */
|
|
318
|
+
private preview;
|
|
319
|
+
private readonly handleGridLeave;
|
|
287
320
|
private isSelected;
|
|
288
321
|
/** The month being shown, resolved from `month`, then `value`, then today. */
|
|
289
322
|
private get shown();
|
|
@@ -292,8 +325,6 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
|
|
|
292
325
|
* months of its own, and the Gregorian months otherwise.
|
|
293
326
|
*/
|
|
294
327
|
private get system();
|
|
295
|
-
/** Whether `calendar` names a calendar with months of its own. */
|
|
296
|
-
private get ownMonths();
|
|
297
328
|
/**
|
|
298
329
|
* The month to open on when nothing has declared one.
|
|
299
330
|
*
|
|
@@ -407,6 +438,16 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
|
|
|
407
438
|
* plain `focus()` on it did nothing and focus stayed on the trigger.
|
|
408
439
|
*/
|
|
409
440
|
focus(options?: FocusOptions): void;
|
|
441
|
+
/**
|
|
442
|
+
* Shows the days again, on the month of the chosen date, with the tab stop
|
|
443
|
+
* on that date, forgetting the month and the day the reader browsed to.
|
|
444
|
+
*
|
|
445
|
+
* Date Input calls it each time its panel opens, which is where the APG
|
|
446
|
+
* date picker dialog puts focus. Measured before this: from August 4, two
|
|
447
|
+
* presses of Page Down, Escape, and Down again opened on October 4.
|
|
448
|
+
* A `month` you set stays until the reader pages away from it.
|
|
449
|
+
*/
|
|
450
|
+
showChosen(): void;
|
|
410
451
|
connectedCallback(): void;
|
|
411
452
|
disconnectedCallback(): void;
|
|
412
453
|
/**
|
|
@@ -434,6 +475,23 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
|
|
|
434
475
|
* Stops the form from submitting while no date is chosen.
|
|
435
476
|
*/
|
|
436
477
|
required: boolean;
|
|
478
|
+
/**
|
|
479
|
+
* What a screen reader hears about the chosen dates: the grid's
|
|
480
|
+
* description, said again whenever the dates change. `{kind}` is `date`,
|
|
481
|
+
* `start` while a range holds one end, `range`, or `dates` for `multiple`.
|
|
482
|
+
* `{start}` and `{end}` are dates written in `locale`, and `{count}` is how
|
|
483
|
+
* many dates are chosen.
|
|
484
|
+
*/
|
|
485
|
+
selectionLabel: string;
|
|
486
|
+
/**
|
|
487
|
+
* `selectionLabel` for the dates chosen now, or nothing while there are none.
|
|
488
|
+
*
|
|
489
|
+
* React Aria's calendars describe the grid by the chosen date or range and
|
|
490
|
+
* announce it when it changes. Measured before this: a range of seven days
|
|
491
|
+
* was seven cells with `aria-selected` in a grid that said nothing of a
|
|
492
|
+
* range, and choosing its end was heard as one more selected cell.
|
|
493
|
+
*/
|
|
494
|
+
private get selectionWords();
|
|
437
495
|
/**
|
|
438
496
|
* How far apart the selectable dates are, in days.
|
|
439
497
|
*
|
|
@@ -505,6 +563,13 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
|
|
|
505
563
|
* browser points its bubble at.
|
|
506
564
|
*/
|
|
507
565
|
private reportRequired;
|
|
566
|
+
/**
|
|
567
|
+
* What is wrong with the answer, as validity flags and a message, or nothing.
|
|
568
|
+
* `reportRequired` hands it to the form, and `failure` to the mark.
|
|
569
|
+
*/
|
|
570
|
+
private get problem();
|
|
571
|
+
/** A missing date waits for the reader; a refused one is marked at once. */
|
|
572
|
+
protected get failure(): PickerFailure | undefined;
|
|
508
573
|
protected willUpdate(changed: PropertyValues): void;
|
|
509
574
|
/**
|
|
510
575
|
* Reports validity after render, because the bubble needs somewhere to point.
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { addDays, addMonths, valueAsDate as asDate, barredBy, calendarLocale, customElement, formatWords, dateAsValue as fromDate, HelixFormElement, HelixNavigateEvent, handedLocale, hostAria, inlineStep, commaList as list, MixinControllerValue, MixinPickerFeedback,
|
|
7
|
+
import { addDays, addMonths, valueAsDate as asDate, barredBy, calendarLocale, customElement, formatWords, dateAsValue as fromDate, HelixFormElement, HelixNavigateEvent, handedLocale, hostAria, inlineStep, commaList as list, MixinControllerValue, MixinPickerFeedback, monthOut, monthSystem, periodCells, periodKey, reportValues, requestChange, todayIn, weekendOf, weekStartOf, words, wordsLocale, YEARS_SHOWN, yearOut, yearPageOf, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretLeft, caretRight } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -34,6 +34,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
34
34
|
this.max = '';
|
|
35
35
|
this.min = '';
|
|
36
36
|
this.monthState = undefined;
|
|
37
|
+
this.browsed = false;
|
|
37
38
|
this.months = 1;
|
|
38
39
|
this.monthViewLabel = '{heading}. Choose a month';
|
|
39
40
|
this.nextLabel = 'Next month';
|
|
@@ -45,8 +46,12 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
45
46
|
this.rangeOverflowMessage = 'Choose a date on or before {max}.';
|
|
46
47
|
this.focused = '';
|
|
47
48
|
this.choosing = false;
|
|
49
|
+
this.previewed = '';
|
|
50
|
+
this.handleGridLeave = () => {
|
|
51
|
+
this.previewed = '';
|
|
52
|
+
};
|
|
48
53
|
this.handleKeydown = (event) => {
|
|
49
|
-
if (this.view !== 'day'
|
|
54
|
+
if (this.view !== 'day') {
|
|
50
55
|
this.handlePeriodKeydown(event);
|
|
51
56
|
return;
|
|
52
57
|
}
|
|
@@ -80,7 +85,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
80
85
|
}
|
|
81
86
|
else {
|
|
82
87
|
const step = event.key === 'PageUp' ? -1 : 1;
|
|
83
|
-
this.moveTo(this.system.
|
|
88
|
+
this.moveTo(event.shiftKey ? this.system.addYears(from, step) : this.system.add(from, step));
|
|
84
89
|
}
|
|
85
90
|
event.preventDefault();
|
|
86
91
|
};
|
|
@@ -91,8 +96,9 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
91
96
|
return;
|
|
92
97
|
}
|
|
93
98
|
if (this.view === 'day') {
|
|
94
|
-
|
|
95
|
-
this.
|
|
99
|
+
const { month, year } = this.system.parts(this.shown);
|
|
100
|
+
this.pageYear = year;
|
|
101
|
+
this.periodFocus = month - 1;
|
|
96
102
|
this.zoom('month');
|
|
97
103
|
}
|
|
98
104
|
else if (this.view === 'month' && this.maxView === 'year') {
|
|
@@ -103,6 +109,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
103
109
|
this.rangeUnderflowMessage = 'Choose a date on or after {min}.';
|
|
104
110
|
this.readOnly = false;
|
|
105
111
|
this.required = false;
|
|
112
|
+
this.selectionLabel = '{kind, select, date {Selected date: {start}} start {Range starts {start}. Choose the day it ends.} range {Selected range: {start} to {end}} other {{count, plural, one {# date selected} other {# dates selected}}}}';
|
|
106
113
|
this.step = 1;
|
|
107
114
|
this.stepMismatchMessage = 'Choose one of the dates the calendar offers.';
|
|
108
115
|
this.timeZone = '';
|
|
@@ -120,6 +127,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
120
127
|
}
|
|
121
128
|
set month(next) {
|
|
122
129
|
const previous = this.month;
|
|
130
|
+
this.browsed = false;
|
|
123
131
|
this.monthState = next;
|
|
124
132
|
this.requestUpdate('month', previous);
|
|
125
133
|
}
|
|
@@ -143,6 +151,30 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
143
151
|
const [first, second] = [dates[0], dates[1]];
|
|
144
152
|
return first <= second ? [first, second] : [second, first];
|
|
145
153
|
}
|
|
154
|
+
get pending() {
|
|
155
|
+
return this.mode === 'range' && this.selected.length === 1;
|
|
156
|
+
}
|
|
157
|
+
get drawnRange() {
|
|
158
|
+
const range = this.range;
|
|
159
|
+
if (range) {
|
|
160
|
+
return { ends: range, preview: false };
|
|
161
|
+
}
|
|
162
|
+
const end = this.previewed || this.focused;
|
|
163
|
+
const start = this.selected[0];
|
|
164
|
+
if (!this.pending ||
|
|
165
|
+
start === undefined ||
|
|
166
|
+
end === '' ||
|
|
167
|
+
end === start ||
|
|
168
|
+
asDate(end) === null) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return { ends: start <= end ? [start, end] : [end, start], preview: true };
|
|
172
|
+
}
|
|
173
|
+
preview(iso) {
|
|
174
|
+
if (this.pending && iso !== this.previewed) {
|
|
175
|
+
this.previewed = iso;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
146
178
|
isSelected(iso) {
|
|
147
179
|
const range = this.range;
|
|
148
180
|
if (range) {
|
|
@@ -161,9 +193,6 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
161
193
|
get system() {
|
|
162
194
|
return monthSystem(this.calendar);
|
|
163
195
|
}
|
|
164
|
-
get ownMonths() {
|
|
165
|
-
return MONTH_CALENDARS.includes(this.calendar ?? '');
|
|
166
|
-
}
|
|
167
196
|
get derivedMonth() {
|
|
168
197
|
const from = asDate(this.selected[0] ?? '') ?? asDate(todayIn(this.timeZone)) ?? new Date();
|
|
169
198
|
return this.system.start(from);
|
|
@@ -226,6 +255,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
226
255
|
requestChange({
|
|
227
256
|
apply: (month) => {
|
|
228
257
|
const previous = this.month;
|
|
258
|
+
this.browsed = true;
|
|
229
259
|
this.monthState = month;
|
|
230
260
|
this.requestUpdate('month', previous);
|
|
231
261
|
},
|
|
@@ -237,6 +267,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
237
267
|
moveTo(wanted) {
|
|
238
268
|
const date = this.clamp(wanted);
|
|
239
269
|
this.focused = fromDate(date);
|
|
270
|
+
this.previewed = '';
|
|
240
271
|
this.show(date);
|
|
241
272
|
this.updateComplete.then(() => {
|
|
242
273
|
this.renderRoot.querySelector('[part~="cell"][tabindex="0"]')?.focus();
|
|
@@ -262,23 +293,25 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
262
293
|
next = [iso];
|
|
263
294
|
}
|
|
264
295
|
this.focused = iso;
|
|
296
|
+
this.previewed = '';
|
|
265
297
|
this.choosing = true;
|
|
266
298
|
this.requestValue(next.join(','));
|
|
267
299
|
}
|
|
268
300
|
get periodYear() {
|
|
269
|
-
return this.pageYear ?? this.
|
|
301
|
+
return this.pageYear ?? this.system.parts(this.shown).year;
|
|
270
302
|
}
|
|
271
303
|
get periodStop() {
|
|
272
|
-
if (this.
|
|
273
|
-
return this.periodFocus;
|
|
304
|
+
if (this.view === 'year') {
|
|
305
|
+
return this.periodFocus ?? this.periodYear;
|
|
274
306
|
}
|
|
275
|
-
|
|
307
|
+
const index = this.periodFocus ?? this.system.parts(this.shown).month - 1;
|
|
308
|
+
return Math.min(index, this.system.months(this.periodYear) - 1);
|
|
276
309
|
}
|
|
277
310
|
monthOut(first) {
|
|
278
|
-
return monthOut(first, this.min, this.max);
|
|
311
|
+
return monthOut(first, this.min, this.max, this.system);
|
|
279
312
|
}
|
|
280
313
|
yearOut(year) {
|
|
281
|
-
return yearOut(year, this.min, this.max);
|
|
314
|
+
return yearOut(year, this.min, this.max, this.system);
|
|
282
315
|
}
|
|
283
316
|
zoom(view) {
|
|
284
317
|
this.view = view;
|
|
@@ -293,8 +326,8 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
293
326
|
return;
|
|
294
327
|
}
|
|
295
328
|
this.pageYear = at;
|
|
296
|
-
const month = this.
|
|
297
|
-
const open = Array.from({ length:
|
|
329
|
+
const month = this.system.parts(this.shown).month - 1;
|
|
330
|
+
const open = Array.from({ length: this.system.months(at) }, (_, index) => index).filter((index) => !this.monthOut(this.system.from(at, index + 1, 1)));
|
|
298
331
|
this.periodFocus = open.includes(month)
|
|
299
332
|
? month
|
|
300
333
|
: ([...open].sort((left, right) => Math.abs(left - month) - Math.abs(right - month))[0] ??
|
|
@@ -302,12 +335,12 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
302
335
|
this.zoom('month');
|
|
303
336
|
return;
|
|
304
337
|
}
|
|
305
|
-
const first =
|
|
338
|
+
const first = this.system.from(this.periodYear, at + 1, 1);
|
|
306
339
|
if (this.monthOut(first)) {
|
|
307
340
|
return;
|
|
308
341
|
}
|
|
309
342
|
this.show(first);
|
|
310
|
-
if (this.month !==
|
|
343
|
+
if (this.month !== this.system.key(first)) {
|
|
311
344
|
return;
|
|
312
345
|
}
|
|
313
346
|
this.pageYear = undefined;
|
|
@@ -344,7 +377,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
344
377
|
this.choosePeriod(stop);
|
|
345
378
|
return;
|
|
346
379
|
}
|
|
347
|
-
const moved = periodKey(this.view === 'year' ? 'year' : 'month', stop, this.periodYear, event.key, inlineStep(this, event.key));
|
|
380
|
+
const moved = periodKey(this.view === 'year' ? 'year' : 'month', stop, this.periodYear, event.key, inlineStep(this, event.key), (year) => this.system.months(year));
|
|
348
381
|
if (moved === null) {
|
|
349
382
|
return;
|
|
350
383
|
}
|
|
@@ -354,10 +387,26 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
354
387
|
void this.updateComplete.then(() => this.focus());
|
|
355
388
|
}
|
|
356
389
|
focus(options) {
|
|
390
|
+
if (this.isUpdatePending) {
|
|
391
|
+
void this.updateComplete.then(() => this.focus(options));
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
357
394
|
this.renderRoot
|
|
358
395
|
?.querySelector('[part~="cell"][tabindex="0"], [part~="period"][tabindex="0"]')
|
|
359
396
|
?.focus(options);
|
|
360
397
|
}
|
|
398
|
+
showChosen() {
|
|
399
|
+
this.focused = '';
|
|
400
|
+
this.pageYear = undefined;
|
|
401
|
+
this.periodFocus = undefined;
|
|
402
|
+
this.view = 'day';
|
|
403
|
+
if (this.browsed) {
|
|
404
|
+
this.browsed = false;
|
|
405
|
+
const previous = this.month;
|
|
406
|
+
this.monthState = undefined;
|
|
407
|
+
this.requestUpdate('month', previous);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
361
410
|
connectedCallback() {
|
|
362
411
|
super.connectedCallback();
|
|
363
412
|
this.addEventListener('keydown', this.handleKeydown);
|
|
@@ -372,6 +421,28 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
372
421
|
}
|
|
373
422
|
return super.requestValue(next);
|
|
374
423
|
}
|
|
424
|
+
get selectionWords() {
|
|
425
|
+
const dates = this.selected.map((one) => asDate(one));
|
|
426
|
+
const first = dates[0];
|
|
427
|
+
if (!first || dates.some((one) => one === null)) {
|
|
428
|
+
return '';
|
|
429
|
+
}
|
|
430
|
+
const { long } = this.formats;
|
|
431
|
+
const range = this.range;
|
|
432
|
+
const kind = range
|
|
433
|
+
? 'range'
|
|
434
|
+
: this.pending
|
|
435
|
+
? 'start'
|
|
436
|
+
: this.mode === 'multiple'
|
|
437
|
+
? 'dates'
|
|
438
|
+
: 'date';
|
|
439
|
+
return formatWords(this, 'selectionLabel', {
|
|
440
|
+
count: dates.length,
|
|
441
|
+
end: range ? long.format(asDate(range[1])) : '',
|
|
442
|
+
kind,
|
|
443
|
+
start: long.format(range ? asDate(range[0]) : first),
|
|
444
|
+
});
|
|
445
|
+
}
|
|
375
446
|
spoken(iso) {
|
|
376
447
|
const date = asDate(iso);
|
|
377
448
|
return date ? this.formats.long.format(date) : iso;
|
|
@@ -399,24 +470,38 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
399
470
|
: ['stepMismatch', this.stepMismatchMessage];
|
|
400
471
|
}
|
|
401
472
|
reportRequired() {
|
|
402
|
-
const
|
|
403
|
-
if (
|
|
404
|
-
this.internals.setValidity(
|
|
473
|
+
const problem = this.problem;
|
|
474
|
+
if (problem) {
|
|
475
|
+
this.internals.setValidity(problem[0], problem[1], this.partNamed('grid') ?? undefined);
|
|
405
476
|
return;
|
|
406
477
|
}
|
|
478
|
+
this.internals.setValidity({});
|
|
479
|
+
}
|
|
480
|
+
get problem() {
|
|
481
|
+
if (this.required && this.value === '') {
|
|
482
|
+
return [{ valueMissing: true }, this.valueMissingMessage];
|
|
483
|
+
}
|
|
407
484
|
const held = this.selected;
|
|
408
485
|
if (held.length > 0 && held.some((one) => asDate(one) === null)) {
|
|
409
|
-
|
|
410
|
-
|
|
486
|
+
return [
|
|
487
|
+
{ badInput: true },
|
|
488
|
+
formatWords(this, 'badInputMessage', { mode: this.mode ?? 'single' }),
|
|
489
|
+
];
|
|
411
490
|
}
|
|
412
491
|
for (const one of held) {
|
|
413
492
|
const fault = this.fault(one);
|
|
414
493
|
if (fault) {
|
|
415
|
-
|
|
416
|
-
return;
|
|
494
|
+
return [{ [fault[0]]: true }, fault[1]];
|
|
417
495
|
}
|
|
418
496
|
}
|
|
419
|
-
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
get failure() {
|
|
500
|
+
const problem = this.problem;
|
|
501
|
+
if (!problem) {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
return problem[0].valueMissing ? 'missing' : 'refused';
|
|
420
505
|
}
|
|
421
506
|
willUpdate(changed) {
|
|
422
507
|
super.willUpdate(changed);
|
|
@@ -431,6 +516,10 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
431
516
|
role: 'group',
|
|
432
517
|
});
|
|
433
518
|
this.internals.ariaDisabled = this.isDisabled ? 'true' : null;
|
|
519
|
+
this.toggleState('blank', this.value === '');
|
|
520
|
+
this.toggleState('range-pending', this.pending);
|
|
521
|
+
this.toggleState('month-view', this.view === 'month');
|
|
522
|
+
this.toggleState('year-view', this.view === 'year');
|
|
434
523
|
this.report();
|
|
435
524
|
}
|
|
436
525
|
updated(changed) {
|
|
@@ -448,9 +537,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
448
537
|
return Math.max(1, Math.floor(this.months) || 1);
|
|
449
538
|
}
|
|
450
539
|
get formats() {
|
|
451
|
-
const language = this.
|
|
452
|
-
? `${new Intl.Locale(this.language || new Intl.DateTimeFormat().resolvedOptions().locale, { calendar: this.calendar ?? 'gregory' })}`
|
|
453
|
-
: calendarLocale(this.language, this.calendar);
|
|
540
|
+
const language = calendarLocale(this.language, this.calendar);
|
|
454
541
|
if (this.cachedFormats?.locale === language) {
|
|
455
542
|
return this.cachedFormats;
|
|
456
543
|
}
|
|
@@ -489,7 +576,9 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
489
576
|
const weeks = this.weeksOf(month);
|
|
490
577
|
const anchor = fromDate(this.anchor);
|
|
491
578
|
const today = todayIn(this.timeZone);
|
|
492
|
-
const
|
|
579
|
+
const drawn = this.drawnRange;
|
|
580
|
+
const range = drawn?.ends ?? null;
|
|
581
|
+
const preview = drawn?.preview === true;
|
|
493
582
|
const weekend = this.weekend;
|
|
494
583
|
const first = index === 0;
|
|
495
584
|
const last = index === this.monthCount - 1;
|
|
@@ -526,8 +615,11 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
526
615
|
part="grid"
|
|
527
616
|
role="grid"
|
|
528
617
|
aria-label=${heading}
|
|
618
|
+
aria-describedby=${this.selectionWords === '' ? nothing : 'selection'}
|
|
619
|
+
aria-multiselectable=${this.mode === undefined ? nothing : 'true'}
|
|
529
620
|
aria-readonly=${this.readOnly ? 'true' : nothing}
|
|
530
621
|
data-unset=${this.value === '' ? '' : nothing}
|
|
622
|
+
@pointerleave=${this.handleGridLeave}
|
|
531
623
|
>
|
|
532
624
|
<div part="week" role="row">
|
|
533
625
|
${weeks[0]?.map((date) => html `<span part="weekday" role="columnheader" aria-label=${weekdayName.format(date)}
|
|
@@ -553,11 +645,13 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
553
645
|
data-outside=${outside ? '' : nothing}
|
|
554
646
|
data-range-end=${end ? '' : nothing}
|
|
555
647
|
data-range-middle=${middle ? '' : nothing}
|
|
648
|
+
data-range-preview=${preview && (start || middle || end) ? '' : nothing}
|
|
556
649
|
data-range-start=${start ? '' : nothing}
|
|
557
650
|
data-today=${iso === today ? '' : nothing}
|
|
558
651
|
data-weekend=${weekend.includes(date.getUTCDay()) ? '' : nothing}
|
|
559
652
|
tabindex=${!outside && iso === anchor && !this.isDisabled ? '0' : '-1'}
|
|
560
653
|
@click=${outside ? nothing : () => this.select(date)}
|
|
654
|
+
@pointerenter=${outside ? nothing : () => this.preview(iso)}
|
|
561
655
|
><span
|
|
562
656
|
part="day${!outside && selected && !middle ? ' day-selected' : ''}${iso === today ? ' day-today' : ''}${outside ? ' day-outside' : ''}${disabled ? ' day-disabled' : ''}${weekend.includes(date.getUTCDay()) ? ' day-weekend' : ''}"
|
|
563
657
|
>${this.renderDay(date, outside)}</span
|
|
@@ -573,7 +667,6 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
573
667
|
}
|
|
574
668
|
renderHeadingText(text, label) {
|
|
575
669
|
const steps = this.maxView !== undefined &&
|
|
576
|
-
!this.ownMonths &&
|
|
577
670
|
(this.view === 'day' || (this.view === 'month' && this.maxView === 'year'));
|
|
578
671
|
if (!steps) {
|
|
579
672
|
return text;
|
|
@@ -591,14 +684,15 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
591
684
|
const years = this.view === 'year';
|
|
592
685
|
const page = years ? yearPageOf(this.periodYear) : this.periodYear;
|
|
593
686
|
const heading = years
|
|
594
|
-
? `${yearName.format(
|
|
595
|
-
: yearName.format(
|
|
687
|
+
? `${yearName.format(this.system.from(page, 1, 1))} – ${yearName.format(this.system.from(page + YEARS_SHOWN - 1, 1, 1))}`
|
|
688
|
+
: yearName.format(this.system.from(page, 1, 1));
|
|
596
689
|
const stop = this.periodStop;
|
|
597
690
|
const cells = periodCells({
|
|
598
691
|
max: this.max,
|
|
599
692
|
min: this.min,
|
|
600
693
|
page: this.periodYear,
|
|
601
694
|
shown: this.shown,
|
|
695
|
+
system: this.system,
|
|
602
696
|
today: todayIn(this.timeZone),
|
|
603
697
|
view: years ? 'year' : 'month',
|
|
604
698
|
}).map(({ at, chosen, current, first, out }) => {
|
|
@@ -615,7 +709,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
615
709
|
>${words}</span
|
|
616
710
|
>`;
|
|
617
711
|
});
|
|
618
|
-
const rows =
|
|
712
|
+
const rows = Array.from({ length: Math.ceil(cells.length / 3) }, (_, row) => row * 3).map((from) => html `<div part="period-row" role="row">${cells.slice(from, from + 3)}</div>`);
|
|
619
713
|
return html `<div part="month">
|
|
620
714
|
<div part="header">
|
|
621
715
|
<button
|
|
@@ -644,7 +738,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
644
738
|
</div>`;
|
|
645
739
|
}
|
|
646
740
|
render() {
|
|
647
|
-
if (this.view !== 'day'
|
|
741
|
+
if (this.view !== 'day') {
|
|
648
742
|
return html `<slot name="header"></slot>
|
|
649
743
|
<div part="months" style="--date-picker-column-count: 1">${this.renderPeriods()}</div>
|
|
650
744
|
<slot name="footer"></slot>${this.renderPickerFeedback()}`;
|
|
@@ -655,6 +749,9 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinPickerF
|
|
|
655
749
|
<div part="months" style="--date-picker-column-count: ${Math.min(count, 2)}">
|
|
656
750
|
${Array.from({ length: count }, (_, index) => this.renderMonth(this.system.add(shown, index), index))}
|
|
657
751
|
</div>
|
|
752
|
+
<span part="selection" id="selection" data-spoken aria-live="polite"
|
|
753
|
+
>${this.selectionWords}</span
|
|
754
|
+
>
|
|
658
755
|
<slot name="footer"></slot>${this.renderPickerFeedback()}`;
|
|
659
756
|
}
|
|
660
757
|
get valueAsDate() {
|
|
@@ -755,6 +852,9 @@ __decorate([
|
|
|
755
852
|
__decorate([
|
|
756
853
|
state()
|
|
757
854
|
], HelixHeadlessDatePicker.prototype, "focused", void 0);
|
|
855
|
+
__decorate([
|
|
856
|
+
state()
|
|
857
|
+
], HelixHeadlessDatePicker.prototype, "previewed", void 0);
|
|
758
858
|
__decorate([
|
|
759
859
|
state()
|
|
760
860
|
], HelixHeadlessDatePicker.prototype, "pageYear", void 0);
|
|
@@ -771,6 +871,10 @@ __decorate([
|
|
|
771
871
|
__decorate([
|
|
772
872
|
property({ reflect: true, type: Boolean })
|
|
773
873
|
], HelixHeadlessDatePicker.prototype, "required", void 0);
|
|
874
|
+
__decorate([
|
|
875
|
+
words,
|
|
876
|
+
property({ attribute: 'selection-label' })
|
|
877
|
+
], HelixHeadlessDatePicker.prototype, "selectionLabel", void 0);
|
|
774
878
|
__decorate([
|
|
775
879
|
property({ type: Number })
|
|
776
880
|
], HelixHeadlessDatePicker.prototype, "step", void 0);
|
|
@@ -92,6 +92,7 @@ registerElementEvents({
|
|
|
92
92
|
'helix-before-change',
|
|
93
93
|
'helix-before-close',
|
|
94
94
|
'helix-before-open',
|
|
95
|
+
'helix-clear',
|
|
95
96
|
'input',
|
|
96
97
|
'toggle',
|
|
97
98
|
],
|
|
@@ -268,6 +269,7 @@ registerElementEvents({
|
|
|
268
269
|
'helix-before-change',
|
|
269
270
|
'helix-before-close',
|
|
270
271
|
'helix-before-open',
|
|
272
|
+
'helix-clear',
|
|
271
273
|
'input',
|
|
272
274
|
'toggle',
|
|
273
275
|
],
|
|
@@ -3,6 +3,7 @@ export const FIELD = {
|
|
|
3
3
|
day: (value) => html `<span part="day">${value}</span>`,
|
|
4
4
|
dayPeriod: (value) => html `<span part="day-period">${value}</span>`,
|
|
5
5
|
era: (value) => html `<span part="era">${value}</span>`,
|
|
6
|
+
fractionalSecond: (value) => html `<span part="fractional-second">${value}</span>`,
|
|
6
7
|
hour: (value) => html `<span part="hour">${value}</span>`,
|
|
7
8
|
minute: (value) => html `<span part="minute">${value}</span>`,
|
|
8
9
|
month: (value) => html `<span part="month">${value}</span>`,
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
export interface Asked {
|
|
6
6
|
dateStyle?: 'full' | 'long' | 'medium' | 'short' | undefined;
|
|
7
7
|
day?: 'numeric' | '2-digit' | undefined;
|
|
8
|
+
dayPeriod?: 'long' | 'narrow' | 'short' | undefined;
|
|
8
9
|
era?: 'long' | 'narrow' | 'short' | undefined;
|
|
10
|
+
fractionalSecondDigits?: 1 | 2 | 3 | undefined;
|
|
9
11
|
hour?: 'numeric' | '2-digit' | undefined;
|
|
10
12
|
hourCycle?: 'h11' | 'h12' | 'h23' | 'h24' | undefined;
|
|
11
13
|
minute?: 'numeric' | '2-digit' | undefined;
|
|
@@ -13,7 +15,7 @@ export interface Asked {
|
|
|
13
15
|
second?: 'numeric' | '2-digit' | undefined;
|
|
14
16
|
timeStyle?: 'full' | 'long' | 'medium' | 'short' | undefined;
|
|
15
17
|
timeZone?: string | undefined;
|
|
16
|
-
timeZoneName?: 'long' | 'short' | undefined;
|
|
18
|
+
timeZoneName?: 'long' | 'longGeneric' | 'longOffset' | 'short' | 'shortGeneric' | 'shortOffset' | undefined;
|
|
17
19
|
weekday?: 'long' | 'short' | 'narrow' | undefined;
|
|
18
20
|
year?: 'numeric' | '2-digit' | undefined;
|
|
19
21
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { FIELD } from './fields.js';
|
|
2
|
-
const FIELDS =
|
|
2
|
+
const FIELDS = [
|
|
3
|
+
...Object.keys(FIELD).filter((one) => one !== 'fractionalSecond'),
|
|
4
|
+
'fractionalSecondDigits',
|
|
5
|
+
];
|
|
3
6
|
export const formatOptions = (asked, dateOnly = false) => {
|
|
4
7
|
const options = {
|
|
5
8
|
hourCycle: asked.hourCycle,
|