@fusion.dev/helix-core 0.158.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/dist/index.d.ts CHANGED
@@ -46,7 +46,7 @@ export { MixinControllerValue, type MixinControllerValueInterface, } from './mix
46
46
  export { type DismissibleElement, MixinDismissible, } from './mixins/mixin-dismissible/mixin-dismissible.js';
47
47
  export { MixinField, type MixinFieldInterface } from './mixins/mixin-field/mixin-field.js';
48
48
  export { MixinPickerEditing, type MixinPickerEditingInterface, } from './mixins/mixin-picker-editing/mixin-picker-editing.js';
49
- export { MixinPickerFeedback, type MixinPickerFeedbackInterface, } from './mixins/mixin-picker-feedback/mixin-picker-feedback.js';
49
+ export { MixinPickerFeedback, type MixinPickerFeedbackInterface, type PickerFailure, } from './mixins/mixin-picker-feedback/mixin-picker-feedback.js';
50
50
  export { MixinPickerField, type MixinPickerFieldInterface, } from './mixins/mixin-picker-field/mixin-picker-field.js';
51
51
  export { MixinSegmentedField, type MixinSegmentedFieldInterface, } from './mixins/mixin-segmented-field/mixin-segmented-field.js';
52
52
  export { MixinSelectableItem, type MixinSelectableItemInterface, } from './mixins/mixin-selectable-item/mixin-selectable-item.js';
@@ -56,7 +56,7 @@ export { anchorElement } from './utils/anchor-element/anchor-element.js';
56
56
  export { arrowReach, sideOf } from './utils/arrow/arrow.js';
57
57
  export { assignSlots } from './utils/assign-slots/assign-slots.js';
58
58
  export { type DrawAutofillOptions, drawAutofill, } from './utils/autofill-select/autofill-select.js';
59
- export { addDays, addMonths, type Barring, barredBy, type CalendarView, calendarLocale, type DateCalendar, localToday, MONTH_CALENDARS, type MonthCalendar, type MonthSystem, monthOf, monthOut, monthSystem, type PeriodCell, type PeriodPage, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
59
+ export { addDays, addMonths, type Barring, barredBy, type CalendarParts, type CalendarView, calendarLocale, type DateCalendar, localToday, MONTH_CALENDARS, type MonthCalendar, type MonthSystem, monthOf, monthOut, monthSystem, type PeriodCell, type PeriodPage, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
60
60
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
61
61
  export { centerInView } from './utils/center-in-view/center-in-view.js';
62
62
  export { ariaChord, type ChordPress, chordParts, matchesChord, printableChord, selectsAll, } from './utils/chord/chord.js';
@@ -72,7 +72,7 @@ export { createSheet } from './utils/create-sheet/create-sheet.js';
72
72
  export { type Color, type ColorFormat, formatColor, parseColor, toCss, toHsl, toOklch, toRgb, } from './utils/css-color/css-color.js';
73
73
  export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
74
74
  export { type DataOption, type DataOptionItem, dataOptions, drawOptions, windowOf, } from './utils/data-options/data-options.js';
75
- export { segmentName } from './utils/date-segments/date-segments.js';
75
+ export { readWrittenDate, readWrittenTime, segmentName, } from './utils/date-segments/date-segments.js';
76
76
  export { type DrawDismissOptions, drawDismiss, drawDismissGlyph, } from './utils/draw-dismiss/draw-dismiss.js';
77
77
  export { type DrawIconOptions, drawIcon } from './utils/draw-icon/draw-icon.js';
78
78
  export { CHART_PLOT, drawTooltip, type ReadingCursor } from './utils/draw-tooltip/draw-tooltip.js';
package/dist/index.js CHANGED
@@ -72,7 +72,7 @@ export { createSheet } from './utils/create-sheet/create-sheet.js';
72
72
  export { formatColor, parseColor, toCss, toHsl, toOklch, toRgb, } from './utils/css-color/css-color.js';
73
73
  export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
74
74
  export { dataOptions, drawOptions, windowOf, } from './utils/data-options/data-options.js';
75
- export { segmentName } from './utils/date-segments/date-segments.js';
75
+ export { readWrittenDate, readWrittenTime, segmentName, } from './utils/date-segments/date-segments.js';
76
76
  export { drawDismiss, drawDismissGlyph, } from './utils/draw-dismiss/draw-dismiss.js';
77
77
  export { drawIcon } from './utils/draw-icon/draw-icon.js';
78
78
  export { CHART_PLOT, drawTooltip } from './utils/draw-tooltip/draw-tooltip.js';
@@ -151,7 +151,8 @@ export const MixinField = (Base) => {
151
151
  super.willUpdate(changed);
152
152
  this.shownFeedback = this.resolvedFeedback;
153
153
  this.toggleState('invalid', this.shownFeedback !== '');
154
- this.internals.ariaInvalid = this.shownFeedback === '' ? null : 'true';
154
+ this.internals.ariaInvalid =
155
+ this.shownFeedback !== '' ? 'true' : this.validates ? 'false' : null;
155
156
  }
156
157
  formResetCallback() {
157
158
  ;
@@ -19,11 +19,23 @@ export declare class MixinPickerFeedbackInterface {
19
19
  * picker and says why.
20
20
  */
21
21
  invalid: boolean;
22
- /** Whether a reader is told the answer is wrong: `invalid`, or `feedback`. */
22
+ /**
23
+ * Whether a reader is told the answer is wrong: `invalid`, `feedback`, a
24
+ * held answer the picker refuses, or a missing answer once the reader has
25
+ * committed one or a form has refused the picker.
26
+ */
23
27
  protected get marked(): boolean;
28
+ /**
29
+ * What the picker's own validation finds: `missing` for a required picker
30
+ * with nothing chosen, `refused` for an answer it holds and refuses, or
31
+ * nothing. Read in `willUpdate`, so a picker answers it from its properties.
32
+ */
33
+ protected get failure(): PickerFailure | undefined;
24
34
  /** The `feedback` line, or nothing while there is none. */
25
35
  protected renderPickerFeedback(): TemplateResult | typeof nothing;
26
36
  }
37
+ /** What a picker's own validation finds, which `marked` reads. */
38
+ export type PickerFailure = 'missing' | 'refused';
27
39
  /**
28
40
  * `feedback` and `invalid` for a picker that stands on its own as a form
29
41
  * control: Color Picker, Date Picker and Time Picker.
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { html, nothing } from 'lit';
8
- import { property } from 'lit/decorators.js';
8
+ import { property, state } from 'lit/decorators.js';
9
9
  import { hostAria } from '../../utils/host-aria/host-aria.js';
10
10
  export const MixinPickerFeedback = (Base) => {
11
11
  class PickerFeedbackElement extends Base {
@@ -13,16 +13,36 @@ export const MixinPickerFeedback = (Base) => {
13
13
  super(...arguments);
14
14
  this.feedback = '';
15
15
  this.invalid = false;
16
+ this.touched = false;
16
17
  }
17
18
  get marked() {
18
- return this.invalid || this.feedback !== '';
19
+ if (this.invalid || this.feedback !== '') {
20
+ return true;
21
+ }
22
+ const failure = this.failure;
23
+ return failure === 'refused' || (failure === 'missing' && this.touched);
24
+ }
25
+ get failure() {
26
+ return undefined;
27
+ }
28
+ formResetCallback() {
29
+ super.formResetCallback();
30
+ this.touched = false;
31
+ }
32
+ firstUpdated(changed) {
33
+ super.firstUpdated(changed);
34
+ const touch = () => {
35
+ this.touched = true;
36
+ };
37
+ this.addEventListener('invalid', touch);
38
+ this.addEventListener('change', touch);
19
39
  }
20
40
  get validityFeedback() {
21
41
  return this.feedback;
22
42
  }
23
43
  willUpdate(changed) {
24
44
  super.willUpdate(changed);
25
- this.internals.ariaInvalid = this.marked ? 'true' : null;
45
+ this.internals.ariaInvalid = this.marked ? 'true' : 'false';
26
46
  this.toggleState('invalid', this.marked);
27
47
  }
28
48
  updated(changed) {
@@ -42,5 +62,8 @@ export const MixinPickerFeedback = (Base) => {
42
62
  __decorate([
43
63
  property({ reflect: true, type: Boolean })
44
64
  ], PickerFeedbackElement.prototype, "invalid", void 0);
65
+ __decorate([
66
+ state()
67
+ ], PickerFeedbackElement.prototype, "touched", void 0);
45
68
  return PickerFeedbackElement;
46
69
  };
@@ -1,5 +1,6 @@
1
1
  import { type TemplateResult } from 'lit';
2
2
  import type { HelixFormElement } from '../../helix-form-element/helix-form-element.js';
3
+ import type { MonthSystem } from '../../utils/calendar/calendar.js';
3
4
  import { type SegmentedKind, type SegmentOptions } from '../../utils/date-segments/date-segments.js';
4
5
  import type { MixinControllerOpenInterface } from '../mixin-controller-open/mixin-controller-open.js';
5
6
  import type { MixinControllerValueInterface } from '../mixin-controller-value/mixin-controller-value.js';
@@ -34,8 +35,17 @@ export declare class MixinSegmentedFieldInterface {
34
35
  * time, in place of a line of text.
35
36
  */
36
37
  segmented: boolean;
38
+ /**
39
+ * What a screen reader hears before the segments of a range's first date or
40
+ * time, after each segment's own name.
41
+ */
42
+ rangeStartLabel: string;
43
+ /** What a screen reader hears before the segments of a range's second date or time. */
44
+ rangeEndLabel: string;
37
45
  /** Whether the segments are drawn: `segmented`, where the family can hold them. */
38
46
  protected get segmentsShown(): boolean;
47
+ /** How many rows of segments the value takes: two for a range, one otherwise. */
48
+ protected get segmentGroups(): number;
39
49
  /** The row of segments, the group a description is carried to, or `null` while none is drawn. */
40
50
  protected get segmentGroup(): HTMLElement | null;
41
51
  /** Whether this family can show segments in its current shape. */
@@ -46,6 +56,8 @@ export declare class MixinSegmentedFieldInterface {
46
56
  protected get segmentLocale(): string;
47
57
  /** The clock and the seconds a time is laid out with. */
48
58
  protected get segmentOptions(): SegmentOptions;
59
+ /** The calendar a date's parts are counted in, or `undefined` for the Gregorian calendar. */
60
+ protected get segmentSystem(): MonthSystem | undefined;
49
61
  /** The row of segments, in the locale's order, with what it writes between them. */
50
62
  protected renderSegments(): TemplateResult;
51
63
  }
@@ -18,18 +18,23 @@ export const MixinSegmentedField = (Base) => {
18
18
  this.emptyMessage = '{kind, select, date {Enter a date.} other {Enter a time.}}';
19
19
  this.emptySegmentLabel = 'Empty';
20
20
  this.incompleteMessage = '{kind, select, date {Enter a whole date.} other {Enter a whole time.}}';
21
+ this.rangeEndLabel = 'End';
22
+ this.rangeStartLabel = 'Start';
21
23
  this.segmented = false;
22
- this.parts = {};
24
+ this.parts = [{}];
23
25
  this.typed = '';
24
26
  this.own = false;
25
27
  this.touchApple = appleTouch(typeof navigator === 'undefined' ? undefined : navigator);
26
28
  this.entered = undefined;
29
+ this.opener = undefined;
27
30
  this.handleSegmentKeydown = (event) => {
28
31
  const element = event.currentTarget;
29
32
  const kind = element.dataset.segment;
33
+ const row = Number(element.dataset.row ?? 0);
30
34
  const twelve = isTwelveHourLayout(this.layout);
31
35
  if (event.key === 'ArrowDown' && event.altKey) {
32
36
  event.preventDefault();
37
+ this.opener = element;
33
38
  this.handleTriggerClick();
34
39
  return;
35
40
  }
@@ -57,14 +62,15 @@ export const MixinSegmentedField = (Base) => {
57
62
  const edit = segmentKey(event.key, {
58
63
  kind,
59
64
  locale: this.segmentLocale,
60
- segments: this.parts,
65
+ segments: this.parts[row] ?? {},
66
+ system: this.segmentSystem,
61
67
  twelve,
62
68
  typed: this.typed,
63
69
  });
64
70
  if (edit) {
65
71
  event.preventDefault();
66
72
  this.typed = edit.typed;
67
- this.take(edit.segments);
73
+ this.take(row, edit.segments);
68
74
  if (edit.advance) {
69
75
  this.moveFocus(element, 1);
70
76
  }
@@ -96,12 +102,34 @@ export const MixinSegmentedField = (Base) => {
96
102
  };
97
103
  }
98
104
  static { this.words = ''; }
105
+ returnFocus() {
106
+ const opener = this.opener;
107
+ this.opener = undefined;
108
+ if (opener?.isConnected && this.segmentsShown) {
109
+ this.returning = true;
110
+ opener.focus();
111
+ this.returning = false;
112
+ return;
113
+ }
114
+ super.returnFocus();
115
+ }
99
116
  get segmentable() {
100
117
  return true;
101
118
  }
102
119
  get segmentsShown() {
103
120
  return this.segmented && this.segmentable;
104
121
  }
122
+ get segmentGroups() {
123
+ return 1;
124
+ }
125
+ joined(rows) {
126
+ const values = rows.map((one) => segmentsValue(this.segmentedKind, one, this.layout, this.segmentSystem));
127
+ return values.some((one) => one === '') ? '' : values.join(',');
128
+ }
129
+ rowsOf(value) {
130
+ const values = this.segmentGroups > 1 ? value.split(',') : [value];
131
+ return Array.from({ length: this.segmentGroups }, (_, row) => segmentsOf(this.segmentedKind, (values[row] ?? '').trim(), this.segmentSystem));
132
+ }
105
133
  get segmentedKind() {
106
134
  return 'date';
107
135
  }
@@ -111,6 +139,9 @@ export const MixinSegmentedField = (Base) => {
111
139
  get segmentOptions() {
112
140
  return {};
113
141
  }
142
+ get segmentSystem() {
143
+ return undefined;
144
+ }
114
145
  get layout() {
115
146
  return segmentLayout(this.segmentedKind, this.segmentLocale, this.segmentOptions);
116
147
  }
@@ -126,9 +157,9 @@ export const MixinSegmentedField = (Base) => {
126
157
  get segmentElements() {
127
158
  return [...(this.renderRoot?.querySelectorAll('[part~="segment"]') ?? [])];
128
159
  }
129
- take(next) {
130
- this.parts = next;
131
- const value = segmentsValue(this.segmentedKind, next, this.layout);
160
+ take(row, next) {
161
+ this.parts = this.parts.map((one, index) => (index === row ? next : one));
162
+ const value = this.joined(this.parts);
132
163
  if (value === this.value) {
133
164
  this.sync();
134
165
  return;
@@ -136,7 +167,7 @@ export const MixinSegmentedField = (Base) => {
136
167
  this.own = true;
137
168
  if (!this.requestValue(value)) {
138
169
  this.own = false;
139
- this.parts = segmentsOf(this.segmentedKind, this.value);
170
+ this.parts = this.rowsOf(this.value);
140
171
  }
141
172
  }
142
173
  moveFocus(from, by) {
@@ -149,13 +180,13 @@ export const MixinSegmentedField = (Base) => {
149
180
  this.own = false;
150
181
  if (!own &&
151
182
  this.segmentsShown &&
152
- segmentsValue(this.segmentedKind, this.parts, this.layout) !== this.value) {
153
- this.parts = segmentsOf(this.segmentedKind, this.value);
183
+ (this.parts.length !== this.segmentGroups || this.joined(this.parts) !== this.value)) {
184
+ this.parts = this.rowsOf(this.value);
154
185
  }
155
186
  }
156
187
  formResetCallback() {
157
188
  super.formResetCallback();
158
- this.parts = {};
189
+ this.parts = this.rowsOf('');
159
190
  this.typed = '';
160
191
  }
161
192
  connectedCallback() {
@@ -175,7 +206,9 @@ export const MixinSegmentedField = (Base) => {
175
206
  return;
176
207
  }
177
208
  const anchor = this.anchor ?? undefined;
178
- const partial = isPartial(this.segmentedKind, this.parts, this.layout);
209
+ const partial = this.parts.some((one) => isPartial(this.segmentedKind, one, this.layout, this.segmentSystem)) ||
210
+ (this.value === '' &&
211
+ this.parts.some((one) => Object.values(one).some((part) => part !== undefined)));
179
212
  const fault = verdictFlags(this.validate());
180
213
  if (partial) {
181
214
  this.internals.setValidity({ badInput: true }, this.badInputMessage ||
@@ -197,51 +230,67 @@ export const MixinSegmentedField = (Base) => {
197
230
  const layout = this.layout;
198
231
  const twelve = isTwelveHourLayout(layout);
199
232
  const locale = this.segmentLocale;
200
- return html `<div
201
- part="segments"
202
- id=${this.controlId}
203
- role="group"
204
- aria-labelledby=${this.labeled ? this.labelId : nothing}
205
- aria-describedby=${this.describedBy ?? nothing}
206
- aria-invalid=${this.invalidity ?? nothing}
207
- @focusin=${this.handleSegmentsFocusIn}
208
- @focusout=${this.handleSegmentsFocusOut}
209
- @keypress=${this.handleKeypress}
210
- >
211
- ${layout.map((slot) => {
212
- if ('literal' in slot) {
213
- return html `<span part="literal" aria-hidden="true">${slot.literal}</span>`;
214
- }
215
- const kind = slot.kind;
216
- const shown = shownValue(kind, this.parts, twelve);
217
- const { max, min } = segmentRange(kind, this.parts, twelve);
218
- const text = segmentText(kind, this.parts, twelve, locale);
219
- const empty = shown === undefined;
220
- const id = `${this.controlId}-${kind}`;
221
- const editable = !this.isDisabled && !this.readOnly;
222
- const spin = !this.touchApple;
223
- return html `<span
233
+ const system = this.segmentSystem;
234
+ const rows = this.segmentGroups;
235
+ const row = (index) => {
236
+ const parts = this.parts[index] ?? {};
237
+ const prefix = index === 0 ? this.controlId : `${this.controlId}-end`;
238
+ const rowName = rows > 1 ? `${prefix}-row` : '';
239
+ return html `${rows > 1
240
+ ? html `<span id=${rowName} hidden
241
+ >${index === 0 ? this.rangeStartLabel : this.rangeEndLabel}</span
242
+ >`
243
+ : nothing}${layout.map((slot) => {
244
+ if ('literal' in slot) {
245
+ return html `<span part="literal" aria-hidden="true">${slot.literal}</span>`;
246
+ }
247
+ const kind = slot.kind;
248
+ const shown = shownValue(kind, parts, twelve);
249
+ const { max, min } = segmentRange(kind, parts, twelve, system);
250
+ const text = segmentText(kind, parts, twelve, locale);
251
+ const empty = shown === undefined;
252
+ const id = `${prefix}-${kind}`;
253
+ const editable = !this.isDisabled && !this.readOnly;
254
+ const spin = !this.touchApple;
255
+ const named = [id, rowName, this.labeled ? this.labelId : ''].filter(Boolean).join(' ');
256
+ return html `<span
224
257
  part="segment${empty ? ' segment-placeholder' : ''}"
225
258
  id=${id}
259
+ data-row=${rows > 1 ? index : nothing}
226
260
  data-segment=${kind}
227
261
  role=${spin ? 'spinbutton' : 'textbox'}
228
262
  tabindex=${this.isDisabled ? '-1' : '0'}
229
263
  inputmode=${kind === 'dayPeriod' ? nothing : 'numeric'}
230
264
  enterkeyhint=${editable ? 'next' : nothing}
231
- aria-label=${this.labeled ? `${segmentName(kind, locale)},` : segmentName(kind, locale)}
232
- aria-labelledby=${this.labeled ? `${id} ${this.labelId}` : nothing}
265
+ aria-label=${named === id ? segmentName(kind, locale) : `${segmentName(kind, locale)},`}
266
+ aria-labelledby=${named === id ? nothing : named}
233
267
  aria-invalid=${this.invalidity ?? nothing}
234
268
  aria-valuemin=${spin ? min : nothing}
235
269
  aria-valuemax=${spin ? max : nothing}
236
270
  aria-valuenow=${spin ? (shown ?? nothing) : nothing}
237
- aria-valuetext=${spin ? (empty ? this.emptySegmentLabel : segmentValueText(kind, this.parts, twelve, locale)) : nothing}
271
+ aria-valuetext=${spin ? (empty ? this.emptySegmentLabel : segmentValueText(kind, parts, twelve, locale, system)) : nothing}
238
272
  aria-disabled=${this.isDisabled ? 'true' : nothing}
239
273
  aria-required=${this.required ? 'true' : nothing}
240
274
  aria-readonly=${this.readOnly ? 'true' : nothing}
241
275
  @keydown=${this.handleSegmentKeydown}
242
276
  >${text}</span
243
277
  >`;
244
- })}
278
+ })}`;
279
+ };
280
+ return html `<div
281
+ part="segments"
282
+ id=${this.controlId}
283
+ role="group"
284
+ aria-labelledby=${this.labeled ? this.labelId : nothing}
285
+ aria-describedby=${this.describedBy ?? nothing}
286
+ aria-invalid=${this.invalidity ?? nothing}
287
+ @focusin=${this.handleSegmentsFocusIn}
288
+ @focusout=${this.handleSegmentsFocusOut}
289
+ @keypress=${this.handleKeypress}
290
+ >
291
+ ${Array.from({ length: rows }, (_, index) => index === 0
292
+ ? row(0)
293
+ : html `<span part="literal range-separator" aria-hidden="true"> – </span>${row(index)}`)}
245
294
  </div>`;
246
295
  }
247
296
  }
@@ -257,6 +306,14 @@ export const MixinSegmentedField = (Base) => {
257
306
  words,
258
307
  property({ attribute: 'incomplete-message' })
259
308
  ], SegmentedFieldElement.prototype, "incompleteMessage", void 0);
309
+ __decorate([
310
+ words,
311
+ property({ attribute: 'range-end-label' })
312
+ ], SegmentedFieldElement.prototype, "rangeEndLabel", void 0);
313
+ __decorate([
314
+ words,
315
+ property({ attribute: 'range-start-label' })
316
+ ], SegmentedFieldElement.prototype, "rangeStartLabel", void 0);
260
317
  __decorate([
261
318
  property({ type: Boolean })
262
319
  ], SegmentedFieldElement.prototype, "segmented", void 0);
@@ -44,11 +44,13 @@ export type DateCalendar = 'buddhist' | 'gregory' | 'iso8601' | 'japanese' | 'ro
44
44
  * The language tag a date family formats with: `locale`, with the calendar it
45
45
  * writes dates in.
46
46
  *
47
- * That is `calendar` where it is one of the five `DateCalendar` names, then
48
- * the locale's own calendar where its months are the Gregorian months, then
49
- * `gregory`. **A calendar with months of its own, such as `persian` for `fa-IR`
50
- * or `islamic-umalqura` for `ar-SA` in some engines, is written as `gregory`.**
51
- * The arithmetic is Gregorian, so a heading in Mehr 1405 over the days of
47
+ * That is `calendar` where it names a `DateCalendar` or a `MonthCalendar`,
48
+ * then the locale's own calendar where its months are the Gregorian months,
49
+ * then `gregory`. **A calendar with months of its own that only the locale
50
+ * names, such as `persian` for `fa-IR` or `islamic-umalqura` for `ar-SA` in
51
+ * some engines, is written as `gregory`**, so a page that asked for no other
52
+ * calendar keeps the Gregorian dates it had. Before the date families drew
53
+ * such a calendar's own months, a heading in Mehr 1405 over the days of
52
54
  * October 2026 named a month the grid did not show.
53
55
  *
54
56
  * `locale` unchanged where nothing moves, so a page in a Gregorian locale
@@ -69,34 +71,55 @@ export declare const sameMonth: (left: Date, right: Date) => boolean;
69
71
  */
70
72
  export declare const addMonths: (date: Date, months: number) => Date;
71
73
  /**
72
- * The calendars Date Picker draws with months of their own, when `calendar`
73
- * names one: the months of the year in Iran, Israel, Saudi Arabia, Ethiopia,
74
- * Egypt's Coptic church, and India's national calendar. React Aria's
75
- * `createCalendar` offers the same set.
74
+ * The calendars the date families draw with months of their own, when
75
+ * `calendar` names one: the months of the year in Iran, Israel, the Islamic
76
+ * calendars, Ethiopia, Egypt's Coptic church, India's national calendar, and
77
+ * the Chinese and Korean lunisolar calendars. Every calendar `Intl` knows
78
+ * whose months are not the Gregorian months.
76
79
  */
77
- export type MonthCalendar = 'coptic' | 'ethiopic' | 'hebrew' | 'indian' | 'islamic-civil' | 'islamic-umalqura' | 'persian';
80
+ export type MonthCalendar = 'chinese' | 'coptic' | 'dangi' | 'ethioaa' | 'ethiopic' | 'hebrew' | 'indian' | 'islamic' | 'islamic-civil' | 'islamic-rgsa' | 'islamic-tbla' | 'islamic-umalqura' | 'persian';
78
81
  /** The `MonthCalendar` names, for a check at run time. */
79
82
  export declare const MONTH_CALENDARS: readonly string[];
83
+ /** A date as a calendar counts it: its year, its month from 1, and its day from 1. */
84
+ export interface CalendarParts {
85
+ day: number;
86
+ month: number;
87
+ year: number;
88
+ }
80
89
  /**
81
- * The months of a calendar, as Date Picker pages through them. Every date is
82
- * still a Gregorian `Date` at midnight UTC, and a month is the run of days
83
- * from its first.
90
+ * The days, months, and years of a calendar, as the date families page and
91
+ * edit them. Every date is still a Gregorian `Date` at midnight UTC, and a
92
+ * month is the run of days from its first.
84
93
  */
85
94
  export interface MonthSystem {
86
95
  /** A date some months away, its day kept where the month is long enough. */
87
96
  add(date: Date, months: number): Date;
97
+ /**
98
+ * A date some years away: the month of the same name where the year has
99
+ * one, so Nisan stays Nisan across a Hebrew leap year, and the day kept
100
+ * where the month is long enough.
101
+ */
102
+ addYears(date: Date, years: number): Date;
88
103
  /** The day of its month, from 1. */
89
104
  day(date: Date): number;
105
+ /** How many days a month of a year has. */
106
+ days(year: number, month: number): number;
107
+ /** The date of a year, month, and day, the month and the day kept inside the year and the month. */
108
+ from(year: number, month: number, day: number): Date;
90
109
  /** The month a date is in, as `month` writes it: `YYYY-MM`, or its first day where the months are the calendar's own. */
91
110
  key(date: Date): string;
111
+ /** How many months a year has: 12, or 13 in a Hebrew leap year, in Ethiopia, and in a Chinese leap year. */
112
+ months(year: number): number;
113
+ /** A date's year, month, and day, as the calendar counts them. */
114
+ parts(date: Date): CalendarParts;
92
115
  /** Whether two dates fall in one month. */
93
116
  same(left: Date, right: Date): boolean;
94
117
  /** The first day of the month a date is in. */
95
118
  start(date: Date): Date;
96
119
  }
97
120
  /**
98
- * The months a calendar pages through: its own for a `MonthCalendar` name,
99
- * and the Gregorian months for every other.
121
+ * The days, months, and years a calendar pages through: its own for a
122
+ * `MonthCalendar` name, and the Gregorian months for every other.
100
123
  */
101
124
  export declare const monthSystem: (calendar?: string) => MonthSystem;
102
125
  /** What bars a date, besides `min` and `max`. */
@@ -138,9 +161,9 @@ export declare const yearPageOf: (year: number) => number;
138
161
  */
139
162
  export declare const periodMove: (key: string, at: number, along: number) => number | null;
140
163
  /** Whether a month, as its first day, lies wholly outside `min` and `max`, either of which may be empty. */
141
- export declare const monthOut: (first: Date, min: string, max: string) => boolean;
164
+ export declare const monthOut: (first: Date, min: string, max: string, system?: MonthSystem) => boolean;
142
165
  /** Whether a year lies wholly outside `min` and `max`, either of which may be empty. */
143
- export declare const yearOut: (year: number, min: string, max: string) => boolean;
166
+ export declare const yearOut: (year: number, min: string, max: string, system?: MonthSystem) => boolean;
144
167
  /** One month or year in the months or the years view. */
145
168
  export interface PeriodCell {
146
169
  /** The month's index in its year, or the year. */
@@ -164,16 +187,19 @@ export interface PeriodPage {
164
187
  page: number;
165
188
  /** The first of the month the calendar shows. */
166
189
  shown: Date;
190
+ /** The calendar the months and the years are counted in, Gregorian where none is given. */
191
+ system?: MonthSystem | undefined;
167
192
  /** Today, as `YYYY-MM-DD`. */
168
193
  today: string;
169
194
  /** Which view: `month` for the months of a year, `year` for a page of years. */
170
195
  view: 'month' | 'year';
171
196
  }
172
197
  /**
173
- * The twelve cells of the months or the years view, in reading order: the
174
- * months of `page`, or the page of years that holds it.
198
+ * The cells of the months or the years view, in reading order: the months of
199
+ * `page`, twelve or the thirteen a calendar's year may have, or the twelve
200
+ * years on the page that holds it.
175
201
  */
176
- export declare const periodCells: ({ max, min, page, shown, today, view }: PeriodPage) => PeriodCell[];
202
+ export declare const periodCells: ({ max, min, page, shown, system, today, view, }: PeriodPage) => PeriodCell[];
177
203
  /**
178
204
  * Where a key takes the tab stop in the months or the years, or `null` for a
179
205
  * key the grid leaves alone.
@@ -181,9 +207,11 @@ export declare const periodCells: ({ max, min, page, shown, today, view }: Perio
181
207
  * `stop` is the month's index or the year the stop is on, `year` the year the
182
208
  * months view shows, and `along` which way the inline keys go, as
183
209
  * `periodMove` takes it. A move off the months of one year lands in the next
184
- * or the last, and a move off a page of years turns the page.
210
+ * or the last, and a move off a page of years turns the page. `months` says
211
+ * how many months a year has, twelve where it is not given. The page keys
212
+ * move the months a year, to the month in the same place.
185
213
  */
186
- export declare const periodKey: (view: 'month' | 'year', stop: number, year: number, key: string, along: number) => {
214
+ export declare const periodKey: (view: 'month' | 'year', stop: number, year: number, key: string, along: number, months?: (year: number) => number) => {
187
215
  focus: number;
188
216
  pageYear: number;
189
217
  } | null;