@nepdatejs/datepicker 0.0.0-bootstrap → 0.1.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/src/range.ts ADDED
@@ -0,0 +1,428 @@
1
+ import { maskDateText } from './mask';
2
+ import {
3
+ compareDates,
4
+ differenceInDays,
5
+ formatMessage,
6
+ getConfig,
7
+ getLocale,
8
+ toCalendar,
9
+ toIsoDate,
10
+ type AdDate,
11
+ type CalendarDate,
12
+ type CalendarSystem,
13
+ } from '@nepdatejs/core';
14
+ import { createCalendar } from '@nepdatejs/calendar';
15
+ import {
16
+ createEmitter,
17
+ formatOf,
18
+ inputLimits,
19
+ isRange,
20
+ localeOf,
21
+ panelOptions,
22
+ todayOf,
23
+ } from './base';
24
+ import {
25
+ describePreset,
26
+ resolveRangePreset,
27
+ type PresetContext,
28
+ type PresetRange,
29
+ type PresetState,
30
+ } from './presets';
31
+ import {
32
+ checkDate,
33
+ detailOf,
34
+ equivalentText,
35
+ formatInput,
36
+ parseInput,
37
+ placeholderText,
38
+ readIso,
39
+ } from './input';
40
+ import type {
41
+ DateError,
42
+ DateRangePickerActions,
43
+ DateRangePickerOptions,
44
+ DateRangePickerState,
45
+ DateRangePickerStore,
46
+ RangeField,
47
+ RangeValue,
48
+ } from './types';
49
+
50
+ type RangeError = DateError & { readonly field: RangeField | 'range' };
51
+
52
+ /**
53
+ * A BS/AD date range picker without UI: two typed fields (start, end), a panel
54
+ * calendar in range mode, minDays/maxDays checks and the BS/AD switch. Values
55
+ * are AD "YYYY-MM-DD" strings; an unfinished or invalid end is "".
56
+ */
57
+ export function createDateRangePicker(
58
+ initial: DateRangePickerOptions = {},
59
+ ): DateRangePickerStore {
60
+ let options: DateRangePickerOptions = { ...initial };
61
+ let calendar: CalendarSystem = options.calendar ?? options.defaultCalendar ?? 'BS';
62
+ let start: AdDate | null = readIso(options.value?.start ?? options.defaultValue?.start);
63
+ let end: AdDate | null = readIso(options.value?.end ?? options.defaultValue?.end);
64
+ [start, end] = ordered(start, end);
65
+ let value = valueOf(start, end);
66
+ let startText = format(start);
67
+ let endText = format(end);
68
+ let error: RangeError | null = null;
69
+ let open = false;
70
+ let destroyed = false;
71
+
72
+ const panel = createCalendar({
73
+ ...panelOptions(options, calendar),
74
+ selectionMode: 'range',
75
+ selected: { start, end },
76
+ onSelect: (selection) => {
77
+ if (isRange(selection)) {
78
+ pick(selection.start, selection.end);
79
+ }
80
+ },
81
+ });
82
+ let snapshot = build();
83
+ const emitter = createEmitter(() => {
84
+ snapshot = build();
85
+ });
86
+ panel.subscribe(() => {
87
+ emitter.notify();
88
+ });
89
+
90
+ function format(date: CalendarDate | null): string {
91
+ return formatInput(date, calendar, formatOf(options), localeOf(options));
92
+ }
93
+
94
+ function build(): DateRangePickerState {
95
+ const locale = localeOf(options);
96
+
97
+ return Object.freeze({
98
+ value,
99
+ date: Object.freeze({ start: detailOf(start), end: detailOf(end) }),
100
+ startText,
101
+ endText,
102
+ calendar,
103
+ canSwitchCalendar: options.calendar === undefined,
104
+ open: open || options.inline === true,
105
+ inline: options.inline === true,
106
+ error,
107
+ equivalentText: Object.freeze({
108
+ start: error?.field === 'start' ? null : equivalentText(start, calendar, locale),
109
+ end: error?.field === 'end' ? null : equivalentText(end, calendar, locale),
110
+ }),
111
+ placeholder: placeholderText(calendar, locale),
112
+ presets: presetStates(),
113
+ calendarState: panel.getState(),
114
+ });
115
+ }
116
+
117
+ function presetContext(): PresetContext {
118
+ return {
119
+ today: toCalendar(todayOf(options), 'AD'),
120
+ calendar,
121
+ weekStartsOn: options.weekStartsOn ?? getConfig().weekStartsOn,
122
+ };
123
+ }
124
+
125
+ /** Each shortcut's range, or null when it cannot be used now. */
126
+ function resolvedPresets(): { id: string; label: string; range: PresetRange | null }[] {
127
+ const context = presetContext();
128
+ const limits = inputLimits(options, panel);
129
+ const locale = localeOf(options);
130
+
131
+ return (options.presets ?? []).map((preset) => {
132
+ const entry = describePreset(preset, options.locale, resolveRangePreset);
133
+ const range = ((): PresetRange | null => {
134
+ try {
135
+ const candidate = entry.resolve(context);
136
+ const from = toCalendar(candidate.start, 'AD');
137
+ const to = toCalendar(candidate.end, 'AD');
138
+ const allowed =
139
+ compareDates(from, to) <= 0 &&
140
+ !checkDate(from, calendar, limits, locale) &&
141
+ !checkDate(to, calendar, limits, locale) &&
142
+ lengthError(from, to) === null;
143
+ return allowed ? candidate : null;
144
+ } catch {
145
+ return null; // e.g. outside the BS table
146
+ }
147
+ })();
148
+
149
+ return { id: entry.id, label: entry.label, range };
150
+ });
151
+ }
152
+
153
+ function presetStates(): PresetState[] {
154
+ return resolvedPresets().map(({ id, label, range }) =>
155
+ Object.freeze({
156
+ id,
157
+ label,
158
+ isActive:
159
+ range !== null &&
160
+ toIsoDate(range.start) === value.start &&
161
+ toIsoDate(range.end) === value.end,
162
+ isDisabled: range === null,
163
+ }),
164
+ );
165
+ }
166
+
167
+ function syncPanel(): void {
168
+ panel.setOptions({
169
+ ...panelOptions(options, calendar),
170
+ selected: { start, end },
171
+ ...(start ? { visibleMonth: start } : {}),
172
+ });
173
+ }
174
+
175
+ /** Too short or too long, or null. Counts both ends. */
176
+ function lengthError(from: AdDate, to: AdDate): RangeError | null {
177
+ const days = differenceInDays(to, from) + 1;
178
+ const messages = getLocale(localeOf(options)).messages;
179
+ if (options.minDays !== undefined && days < options.minDays) {
180
+ return {
181
+ field: 'range',
182
+ code: 'tooShort',
183
+ message: formatMessage(messages.rangeTooShort, { min: String(options.minDays) }),
184
+ };
185
+ }
186
+ if (options.maxDays !== undefined && days > options.maxDays) {
187
+ return {
188
+ field: 'range',
189
+ code: 'tooLong',
190
+ message: formatMessage(messages.rangeTooLong, { max: String(options.maxDays) }),
191
+ };
192
+ }
193
+
194
+ return null;
195
+ }
196
+
197
+ /** Store the dates, keep the panel in step, and queue onChange when the value moved. */
198
+ function commit(
199
+ nextStart: AdDate | null,
200
+ nextEnd: AdDate | null,
201
+ byUser: boolean,
202
+ ): void {
203
+ start = nextStart;
204
+ end = nextEnd;
205
+ const next = valueOf(start, end);
206
+ const changed = next.start !== value.start || next.end !== value.end;
207
+ value = next;
208
+ syncPanel();
209
+ if (changed && byUser) {
210
+ const detail = Object.freeze({ start: detailOf(start), end: detailOf(end) });
211
+ emitter.after(() => options.onChange?.(next, detail));
212
+ }
213
+ }
214
+
215
+ /** Put the ends in order, check the length, then commit. False when the length is not allowed. */
216
+ function settle(nextStart: AdDate | null, nextEnd: AdDate | null): boolean {
217
+ const [a, b] = ordered(nextStart, nextEnd);
218
+ if (a !== nextStart) {
219
+ [startText, endText] = [endText, startText];
220
+ }
221
+ const tooFar = a && b ? lengthError(a, b) : null;
222
+ error = tooFar ?? error;
223
+ commit(a, tooFar ? null : b, true);
224
+
225
+ return tooFar === null;
226
+ }
227
+
228
+ function pick(from: CalendarDate | null, to: CalendarDate | null): void {
229
+ emitter.act(() => {
230
+ const a = from ? toCalendar(from, 'AD') : null;
231
+ const b = to ? toCalendar(to, 'AD') : null;
232
+ error = null;
233
+ startText = format(a);
234
+ endText = format(b);
235
+ const allowed = settle(a, b);
236
+ if (b && allowed && (options.closeOnSelect ?? true)) {
237
+ open = false;
238
+ }
239
+ });
240
+ }
241
+
242
+ const actions: DateRangePickerActions = {
243
+ type(field, text) {
244
+ if (destroyed) {
245
+ return;
246
+ }
247
+ emitter.act(() => {
248
+ if (options.mask !== false) {
249
+ text = maskDateText(text, field === 'start' ? startText : endText);
250
+ }
251
+ if (field === 'start') {
252
+ startText = text;
253
+ } else {
254
+ endText = text;
255
+ }
256
+ const result = parseInput(
257
+ text,
258
+ calendar,
259
+ inputLimits(options, panel),
260
+ localeOf(options),
261
+ formatOf(options),
262
+ );
263
+ const date = result.kind === 'date' ? result.date : null;
264
+ error = result.kind === 'error' ? { ...result.error, field } : null;
265
+ const other = field === 'start' ? end : start;
266
+ const otherError = other
267
+ ? checkDate(other, calendar, inputLimits(options, panel), localeOf(options))
268
+ : null;
269
+ if (!error && otherError) {
270
+ error = { ...otherError, field: field === 'start' ? 'end' : 'start' };
271
+ }
272
+ if (field === 'start') {
273
+ settle(date, end);
274
+ } else {
275
+ settle(start, date);
276
+ }
277
+ });
278
+ },
279
+ blur(field) {
280
+ emitter.act(() => {
281
+ if (error?.field === field) {
282
+ return;
283
+ }
284
+ if (field === 'start') {
285
+ startText = format(start);
286
+ } else if (end || error?.field !== 'range') {
287
+ endText = format(end);
288
+ }
289
+ });
290
+ },
291
+ clear() {
292
+ emitter.act(() => {
293
+ startText = '';
294
+ endText = '';
295
+ error = null;
296
+ commit(null, null, true);
297
+ });
298
+ },
299
+ setValue(next) {
300
+ emitter.act(() => {
301
+ applyValue(next);
302
+ });
303
+ },
304
+ open() {
305
+ emitter.act(() => {
306
+ open = true;
307
+ panel.actions.goTo(start ?? todayOf(options));
308
+ });
309
+ },
310
+ close() {
311
+ emitter.act(() => {
312
+ open = false;
313
+ });
314
+ },
315
+ toggle() {
316
+ if (open) {
317
+ actions.close();
318
+ } else {
319
+ actions.open();
320
+ }
321
+ },
322
+ switchCalendar(next) {
323
+ if (options.calendar !== undefined || next === calendar || destroyed) {
324
+ return;
325
+ }
326
+ emitter.act(() => {
327
+ calendar = next;
328
+ if (!error) {
329
+ startText = format(start);
330
+ endText = format(end);
331
+ }
332
+ syncPanel();
333
+ emitter.after(() => options.onCalendarChange?.(next));
334
+ });
335
+ },
336
+ select(day) {
337
+ let picked = false;
338
+ if (!destroyed) {
339
+ emitter.act(() => {
340
+ picked = panel.actions.select(day);
341
+ });
342
+ }
343
+
344
+ return picked;
345
+ },
346
+ applyPreset(id) {
347
+ const preset = resolvedPresets().find((entry) => entry.id === id);
348
+ if (destroyed || !preset?.range) {
349
+ return false;
350
+ }
351
+ pick(preset.range.start, preset.range.end);
352
+
353
+ return true;
354
+ },
355
+ reset() {
356
+ emitter.act(() => {
357
+ const [a, b] = ordered(
358
+ readIso(options.defaultValue?.start),
359
+ readIso(options.defaultValue?.end),
360
+ );
361
+ error = null;
362
+ startText = format(a);
363
+ endText = format(b);
364
+ commit(a, b, true);
365
+ });
366
+ },
367
+ };
368
+
369
+ /** An outside value: replaces the texts only when the value differs (echo guard). */
370
+ function applyValue(next: RangeValue | undefined): void {
371
+ const [a, b] = ordered(readIso(next?.start), readIso(next?.end));
372
+ const incoming = valueOf(a, b);
373
+ if (incoming.start === value.start && incoming.end === value.end) {
374
+ return;
375
+ }
376
+ error = null;
377
+ startText = format(a);
378
+ endText = format(b);
379
+ commit(a, b, false);
380
+ }
381
+
382
+ return {
383
+ getState: () => snapshot,
384
+ subscribe: (listener) => emitter.subscribe(listener),
385
+ setOptions(changes) {
386
+ if (destroyed) {
387
+ return;
388
+ }
389
+ emitter.act(() => {
390
+ const before = { calendar, locale: localeOf(options), format: formatOf(options) };
391
+ options = { ...options, ...changes };
392
+ if (options.calendar !== undefined) {
393
+ calendar = options.calendar;
394
+ }
395
+ if ('value' in changes) {
396
+ applyValue(changes.value);
397
+ }
398
+ const looksChanged =
399
+ before.calendar !== calendar ||
400
+ before.locale !== localeOf(options) ||
401
+ before.format !== formatOf(options);
402
+ if (looksChanged && !error) {
403
+ startText = format(start);
404
+ endText = format(end);
405
+ }
406
+ syncPanel();
407
+ });
408
+ },
409
+ actions,
410
+ calendar: panel,
411
+ destroy() {
412
+ destroyed = true;
413
+ panel.destroy();
414
+ emitter.clear();
415
+ },
416
+ };
417
+ }
418
+
419
+ function ordered(a: AdDate | null, b: AdDate | null): [AdDate | null, AdDate | null] {
420
+ return a && b && compareDates(b, a) < 0 ? [b, a] : [a, b];
421
+ }
422
+
423
+ function valueOf(start: AdDate | null, end: AdDate | null): RangeValue {
424
+ return Object.freeze({
425
+ start: start ? toIsoDate(start) : '',
426
+ end: end ? toIsoDate(end) : '',
427
+ });
428
+ }
package/src/types.ts ADDED
@@ -0,0 +1,250 @@
1
+ import type {
2
+ AdDate,
3
+ BsDate,
4
+ CalendarDate,
5
+ CalendarSystem,
6
+ DateFormatPattern,
7
+ LocaleCode,
8
+ Weekday,
9
+ } from '@nepdatejs/core';
10
+ import type { PresetState, RangePreset, SinglePreset } from './presets';
11
+ import type {
12
+ CalendarState,
13
+ CalendarStore,
14
+ EventMarker,
15
+ HolidayInput,
16
+ HolidayLookup,
17
+ } from '@nepdatejs/calendar';
18
+
19
+ /** The same day in both calendars, sent with every change. */
20
+ export interface DateDetail {
21
+ readonly ad: AdDate;
22
+ readonly bs: BsDate;
23
+ }
24
+
25
+ export type DateErrorCode =
26
+ 'format' | 'range' | 'disabled' | 'tooShort' | 'tooLong' | 'tooMany';
27
+
28
+ export interface DateError {
29
+ readonly code: DateErrorCode;
30
+ /** Already translated for the picker's locale. */
31
+ readonly message: string;
32
+ }
33
+
34
+ /** Options both pickers share. Dates in options are AD "YYYY-MM-DD" strings (docs/plan D-003). */
35
+ export interface PickerBaseOptions {
36
+ /** Show and type dates in this calendar only, and hide the BS/AD switch. */
37
+ readonly calendar?: CalendarSystem | undefined;
38
+ /** The calendar shown first when switching is allowed. Default 'BS'. */
39
+ readonly defaultCalendar?: CalendarSystem | undefined;
40
+ /** Called when the user switches BS/AD (adapters may remember it). */
41
+ readonly onCalendarChange?: ((calendar: CalendarSystem) => void) | undefined;
42
+ /** Earliest and latest allowed days, as AD "YYYY-MM-DD". */
43
+ readonly min?: string | undefined;
44
+ readonly max?: string | undefined;
45
+ readonly isDateDisabled?: ((date: CalendarDate) => boolean) | undefined;
46
+ readonly locale?: LocaleCode | undefined;
47
+ /** How the input shows a date. Default 'iso' (YYYY-MM-DD), which can be typed back. */
48
+ readonly displayFormat?: DateFormatPattern | undefined;
49
+ /** Close the panel after a pick. Default true. */
50
+ readonly closeOnSelect?: boolean | undefined;
51
+ /** Show the calendar in the page, always open, instead of in a popover. Default false. */
52
+ readonly inline?: boolean | undefined;
53
+ /** Add the dashes of YYYY-MM-DD while the user types digits. Default true. */
54
+ readonly mask?: boolean | undefined;
55
+ // Passed through to the panel's calendar.
56
+ readonly holidays?: readonly HolidayInput[] | HolidayLookup | undefined;
57
+ readonly events?: ((date: CalendarDate) => readonly EventMarker[]) | undefined;
58
+ readonly showOtherCalendar?: boolean | undefined;
59
+ readonly weekStartsOn?: Weekday | undefined;
60
+ readonly weekendDays?: readonly Weekday[] | undefined;
61
+ /** Override "today" (tests, server rendering). */
62
+ readonly today?: CalendarDate | undefined;
63
+ }
64
+
65
+ export interface DatePickerOptions extends PickerBaseOptions {
66
+ /** AD "YYYY-MM-DD" or "". Passing it again through setOptions replaces the value. */
67
+ readonly value?: string | undefined;
68
+ /** The starting value when the picker keeps its own state. */
69
+ readonly defaultValue?: string | undefined;
70
+ readonly onChange?: ((value: string, detail: DateDetail | null) => void) | undefined;
71
+ /** Shortcuts shown in the panel: 'today', 'yesterday', 'tomorrow' or your own. */
72
+ readonly presets?: readonly SinglePreset[] | undefined;
73
+ }
74
+
75
+ export interface DatePickerState {
76
+ /** AD "YYYY-MM-DD", or "" when empty or invalid. Never a BS date. */
77
+ readonly value: string;
78
+ readonly date: DateDetail | null;
79
+ /** What the input shows. */
80
+ readonly text: string;
81
+ /** The calendar the input and panel use. */
82
+ readonly calendar: CalendarSystem;
83
+ readonly canSwitchCalendar: boolean;
84
+ /** The panel is showing. Always true for an inline picker. */
85
+ readonly open: boolean;
86
+ /** The calendar sits in the page and never closes (option `inline`). */
87
+ readonly inline: boolean;
88
+ readonly error: DateError | null;
89
+ /** The same day in the other calendar, e.g. "10 October 2026 AD". */
90
+ readonly equivalentText: string | null;
91
+ /** "YYYY-MM-DD (BS)" */
92
+ readonly placeholder: string;
93
+ readonly presets: readonly PresetState[];
94
+ readonly calendarState: CalendarState;
95
+ }
96
+
97
+ export interface DatePickerActions {
98
+ /** The user typed. Updates the value when the text is a valid, allowed date. */
99
+ type(text: string): void;
100
+ /** Tidy the text ("2083/6/4" → "2083-06-04") unless it has an error. */
101
+ blur(): void;
102
+ clear(): void;
103
+ /** Set the value from outside; does not call onChange. */
104
+ setValue(value: string): void;
105
+ open(): void;
106
+ close(): void;
107
+ toggle(): void;
108
+ /** Show BS or AD. The value does not change. Ignored when the calendar is fixed. */
109
+ switchCalendar(calendar: CalendarSystem): void;
110
+ /** Pick a day (e.g. from the panel). Returns false when the day is disabled. */
111
+ select(date: CalendarDate): boolean;
112
+ /** Returns false when today is not allowed. */
113
+ selectToday(): boolean;
114
+ /** Back to defaultValue (form reset). */
115
+ reset(): void;
116
+ /** Apply a shortcut by id. Returns false when it is disabled or unknown. */
117
+ applyPreset(id: string): boolean;
118
+ }
119
+
120
+ export interface DatePickerStore {
121
+ getState(): DatePickerState;
122
+ subscribe(listener: () => void): () => void;
123
+ setOptions(changes: Partial<DatePickerOptions>): void;
124
+ readonly actions: DatePickerActions;
125
+ /** The panel's calendar store, for adapters (keyboard moves, views, hover). */
126
+ readonly calendar: CalendarStore;
127
+ destroy(): void;
128
+ }
129
+
130
+ export interface RangeValue {
131
+ readonly start: string;
132
+ readonly end: string;
133
+ }
134
+
135
+ export interface RangeDetail {
136
+ readonly start: DateDetail | null;
137
+ readonly end: DateDetail | null;
138
+ }
139
+
140
+ export interface DateRangePickerOptions extends PickerBaseOptions {
141
+ readonly value?: RangeValue | undefined;
142
+ readonly defaultValue?: RangeValue | undefined;
143
+ readonly onChange?: ((value: RangeValue, detail: RangeDetail) => void) | undefined;
144
+ /** Shortest and longest allowed range, in days, counting both ends. */
145
+ readonly minDays?: number | undefined;
146
+ readonly maxDays?: number | undefined;
147
+ /** Shortcuts: 'last7Days', 'thisMonth', 'thisFiscalYear', … or your own. */
148
+ readonly presets?: readonly RangePreset[] | undefined;
149
+ }
150
+
151
+ export type RangeField = 'start' | 'end';
152
+
153
+ export interface DateRangePickerState {
154
+ readonly value: RangeValue;
155
+ readonly date: RangeDetail;
156
+ readonly startText: string;
157
+ readonly endText: string;
158
+ readonly calendar: CalendarSystem;
159
+ readonly canSwitchCalendar: boolean;
160
+ /** The panel is showing. Always true for an inline picker. */
161
+ readonly open: boolean;
162
+ /** The calendar sits in the page and never closes (option `inline`). */
163
+ readonly inline: boolean;
164
+ /** The first problem, and which field it belongs to ('range' for length problems). */
165
+ readonly error: (DateError & { readonly field: RangeField | 'range' }) | null;
166
+ readonly equivalentText: { readonly start: string | null; readonly end: string | null };
167
+ readonly placeholder: string;
168
+ readonly presets: readonly PresetState[];
169
+ readonly calendarState: CalendarState;
170
+ }
171
+
172
+ export interface DateRangePickerActions {
173
+ type(field: RangeField, text: string): void;
174
+ blur(field: RangeField): void;
175
+ clear(): void;
176
+ setValue(value: RangeValue): void;
177
+ open(): void;
178
+ close(): void;
179
+ toggle(): void;
180
+ switchCalendar(calendar: CalendarSystem): void;
181
+ /** A click in the panel: first sets the start, second the end. */
182
+ select(date: CalendarDate): boolean;
183
+ reset(): void;
184
+ /** Apply a shortcut by id. Returns false when it is disabled or unknown. */
185
+ applyPreset(id: string): boolean;
186
+ }
187
+
188
+ export interface DateRangePickerStore {
189
+ getState(): DateRangePickerState;
190
+ subscribe(listener: () => void): () => void;
191
+ setOptions(changes: Partial<DateRangePickerOptions>): void;
192
+ readonly actions: DateRangePickerActions;
193
+ readonly calendar: CalendarStore;
194
+ destroy(): void;
195
+ }
196
+
197
+ export interface MultiDatePickerOptions extends PickerBaseOptions {
198
+ /** AD "YYYY-MM-DD" days. Passing it again through setOptions replaces the value. */
199
+ readonly value?: readonly string[] | undefined;
200
+ readonly defaultValue?: readonly string[] | undefined;
201
+ readonly onChange?:
202
+ ((value: readonly string[], dates: readonly DateDetail[]) => void) | undefined;
203
+ /** The most days that may be chosen. */
204
+ readonly maxDates?: number | undefined;
205
+ }
206
+
207
+ export interface MultiDatePickerState {
208
+ /** AD "YYYY-MM-DD" days, sorted, no repeats. Never BS dates. */
209
+ readonly value: readonly string[];
210
+ readonly dates: readonly DateDetail[];
211
+ /** What the input shows: the days as YYYY-MM-DD, separated by ", ". */
212
+ readonly text: string;
213
+ readonly calendar: CalendarSystem;
214
+ readonly canSwitchCalendar: boolean;
215
+ /** The panel is showing. Always true for an inline picker. */
216
+ readonly open: boolean;
217
+ readonly inline: boolean;
218
+ readonly error: DateError | null;
219
+ readonly placeholder: string;
220
+ readonly calendarState: CalendarState;
221
+ }
222
+
223
+ export interface MultiDatePickerActions {
224
+ /** The user typed days separated by commas or spaces. */
225
+ type(text: string): void;
226
+ /** Tidy the text unless it has an error. */
227
+ blur(): void;
228
+ clear(): void;
229
+ /** Set the value from outside; does not call onChange. */
230
+ setValue(value: readonly string[]): void;
231
+ open(): void;
232
+ close(): void;
233
+ toggle(): void;
234
+ switchCalendar(calendar: CalendarSystem): void;
235
+ /** Add the day, or remove it when chosen. Returns false when disabled or over maxDates. */
236
+ select(date: CalendarDate): boolean;
237
+ /** Remove one day, by its AD "YYYY-MM-DD" value. */
238
+ remove(value: string): void;
239
+ /** Back to defaultValue (form reset). */
240
+ reset(): void;
241
+ }
242
+
243
+ export interface MultiDatePickerStore {
244
+ getState(): MultiDatePickerState;
245
+ subscribe(listener: () => void): () => void;
246
+ setOptions(changes: Partial<MultiDatePickerOptions>): void;
247
+ readonly actions: MultiDatePickerActions;
248
+ readonly calendar: CalendarStore;
249
+ destroy(): void;
250
+ }