@lekoala/date-picker 0.5.2 → 0.6.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/date-picker.css +2 -13
- package/dist/date-picker.js +222 -252
- package/dist/date-picker.min.css +1 -1
- package/dist/date-picker.min.js +12 -13
- package/dist/date-picker.standalone.min.js +11 -12
- package/dist/types/date-calendar.d.ts +2 -98
- package/dist/types/date-picker.d.ts +0 -316
- package/dist/types/date-range.d.ts +10 -33
- package/dist/types/date-range.d.ts.map +1 -1
- package/dist/types/date.d.ts.map +1 -1
- package/dist/types/session.d.ts +17 -0
- package/dist/types/session.d.ts.map +1 -0
- package/docs/ACCESSIBILITY.md +1 -1
- package/docs/API.md +14 -25
- package/docs/ARCHITECTURE.md +74 -0
- package/docs/CALENDAR-INTEGRATION.md +97 -0
- package/docs/DECISIONS.md +30 -3
- package/docs/TESTING.md +8 -0
- package/docs/USE_CASES.md +1 -2
- package/package.json +2 -2
- package/src/date-calendar.js +90 -18
- package/src/date-field.js +32 -9
- package/src/date-picker.css +2 -13
- package/src/date-picker.js +134 -228
- package/src/date-range.js +12 -54
- package/src/date.js +4 -1
- package/src/session.js +23 -0
- package/dist/types/activation-intents.d.ts +0 -10
- package/dist/types/activation-intents.d.ts.map +0 -1
- package/dist/types/date-calendar.d.ts.map +0 -1
- package/dist/types/date-field.d.ts +0 -118
- package/dist/types/date-field.d.ts.map +0 -1
- package/dist/types/date-picker.d.ts.map +0 -1
- package/dist/types/time.d.ts +0 -8
- package/dist/types/time.d.ts.map +0 -1
- package/dist/types/upgrade-properties.d.ts +0 -8
- package/dist/types/upgrade-properties.d.ts.map +0 -1
|
@@ -1,83 +1,5 @@
|
|
|
1
1
|
import { DateCalendarElement } from "./date-calendar.js";
|
|
2
|
-
import { DateFieldController } from "./date-field.js";
|
|
3
|
-
import { DateRangeController } from "./date-range.js";
|
|
4
|
-
export type PickIntent = {
|
|
5
|
-
id: number;
|
|
6
|
-
endpoint: "" | "start" | "end";
|
|
7
|
-
};
|
|
8
|
-
/** A grid activation intent: the generation at interaction time plus the bound
|
|
9
|
-
* a drag drop targets, empty for a normal pick.
|
|
10
|
-
* @typedef {{id: number, endpoint: "" | "start" | "end"}} PickIntent */
|
|
11
2
|
export declare class DatePickerElement extends HTMLElement {
|
|
12
|
-
_id: string;
|
|
13
|
-
_connected: boolean;
|
|
14
|
-
_reflecting: boolean;
|
|
15
|
-
_value: string;
|
|
16
|
-
_input: HTMLInputElement | null;
|
|
17
|
-
/** @type {DateFieldController | null} */
|
|
18
|
-
_field: DateFieldController | null;
|
|
19
|
-
/** @type {DateFieldController[] | null} */
|
|
20
|
-
_fields: DateFieldController[] | null;
|
|
21
|
-
/** @type {DateRangeController | null} */
|
|
22
|
-
_range: DateRangeController | null;
|
|
23
|
-
_orderTags: Set<any>;
|
|
24
|
-
/** Native time companions, [start, end]. @type {[HTMLInputElement | null, HTMLInputElement | null]} */
|
|
25
|
-
_timeFields: [HTMLInputElement | null, HTMLInputElement | null];
|
|
26
|
-
/** Time inputs currently holding our order error. @type {Set<HTMLInputElement>} */
|
|
27
|
-
_timeOrderOwned: Set<HTMLInputElement>;
|
|
28
|
-
/** @type {"" | "start" | "end"} */
|
|
29
|
-
_lastFocusEndpoint: "" | "start" | "end";
|
|
30
|
-
/** Activation generation. A grid intent records it at interaction time
|
|
31
|
-
* and travels with its own activation (see `activationIntents`), so a late
|
|
32
|
-
* response is checked against the generation, never looked up by date. */
|
|
33
|
-
_activationId: number;
|
|
34
|
-
/** Candidate date currently projected onto the calendar band, if any. */
|
|
35
|
-
_previewDate: string;
|
|
36
|
-
/** Live endpoint drag, or null. @type {{pointerId:number, endpoint:"start"|"end", x:number, y:number, date:string, active:boolean} | null} */
|
|
37
|
-
_drag: {
|
|
38
|
-
pointerId: number;
|
|
39
|
-
endpoint: "start" | "end";
|
|
40
|
-
x: number;
|
|
41
|
-
y: number;
|
|
42
|
-
date: string;
|
|
43
|
-
active: boolean;
|
|
44
|
-
} | null;
|
|
45
|
-
/** A real drag already committed; swallow the click that follows it. */
|
|
46
|
-
_suppressGridClick: boolean;
|
|
47
|
-
/** Overlaid calendar triggers, one per field (single) or per bound (range). @type {{button: HTMLButtonElement, endpoint: "" | "start" | "end"}[]} */
|
|
48
|
-
_buttons: {
|
|
49
|
-
button: HTMLButtonElement;
|
|
50
|
-
endpoint: "" | "start" | "end";
|
|
51
|
-
}[];
|
|
52
|
-
/** Control that opened the popover, so single mode can restore focus to it.
|
|
53
|
-
* @type {HTMLElement | null} */
|
|
54
|
-
_returnFocus: HTMLElement | null;
|
|
55
|
-
_panel: HTMLDivElement | null;
|
|
56
|
-
_calendar: DateCalendarElement | null;
|
|
57
|
-
_originalDescribedBy: string;
|
|
58
|
-
_rangeOriginalDescribedBy: string[];
|
|
59
|
-
_formatHint: HTMLSpanElement | null;
|
|
60
|
-
_generatedPlaceholder: boolean;
|
|
61
|
-
_controller: AbortController | null;
|
|
62
|
-
_stopTracking: (() => void) | null;
|
|
63
|
-
_open: boolean;
|
|
64
|
-
_suppressFocusOpen: boolean;
|
|
65
|
-
/** @type {typeof import("./messages.js").DEFAULT_MESSAGES | null} */
|
|
66
|
-
_messages: typeof import("./messages.js").DEFAULT_MESSAGES | null;
|
|
67
|
-
/** @type {Partial<import("./control-classes.js").ControlClasses> | null} */
|
|
68
|
-
_controlClasses: Partial<import("./control-classes.js").ControlClasses> | null;
|
|
69
|
-
/** @type {(() => void) | null} */
|
|
70
|
-
_stopDefaultMessages: (() => void) | null;
|
|
71
|
-
/** @type {import("./date-calendar.js").DateSource | null} */
|
|
72
|
-
_source: import("./date-calendar.js").DateSource | null;
|
|
73
|
-
/** @type {import("./date-calendar.js").DateStateResolver | null} */
|
|
74
|
-
_dateState: import("./date-calendar.js").DateStateResolver | null;
|
|
75
|
-
/** @type {import("./date-calendar.js").DayRenderer | null} */
|
|
76
|
-
_renderDay: import("./date-calendar.js").DayRenderer | null;
|
|
77
|
-
/** @type {import("./date-calendar.js").DateDisabledPredicate | null} */
|
|
78
|
-
_isDateDisabled: import("./date-calendar.js").DateDisabledPredicate | null;
|
|
79
|
-
/** Public picker coordinate-space request. @type {"viewport" | "document"} */
|
|
80
|
-
_coordinateSpace: "viewport" | "document";
|
|
81
3
|
static observedAttributes: string[];
|
|
82
4
|
/** @public @param {Partial<import("./control-classes.js").ControlClasses>} classes */
|
|
83
5
|
static setDefaultControlClasses(classes: Partial<import("./control-classes.js").ControlClasses>): void;
|
|
@@ -111,19 +33,8 @@ export declare class DatePickerElement extends HTMLElement {
|
|
|
111
33
|
rangeOrderEnd: string;
|
|
112
34
|
};
|
|
113
35
|
constructor();
|
|
114
|
-
_rangeMode(): boolean;
|
|
115
36
|
connectedCallback(): void;
|
|
116
|
-
_connectSingle(): void;
|
|
117
|
-
/**
|
|
118
|
-
* Find optional native time companions. They stay fully native (no hidden
|
|
119
|
-
* input, no field controller); the picker only coordinates from/to order.
|
|
120
|
-
* A bad composition never breaks the date picker: warn and leave times alone.
|
|
121
|
-
*/
|
|
122
|
-
_discoverTimeFields(): void;
|
|
123
|
-
_connectRange(): void;
|
|
124
37
|
disconnectedCallback(): void;
|
|
125
|
-
/** @param {DateFieldController | null} field */
|
|
126
|
-
_teardownField(field: DateFieldController | null): void;
|
|
127
38
|
/** @param {string} name @param {string|null} oldValue @param {string|null} newValue */
|
|
128
39
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
129
40
|
/** @public */
|
|
@@ -171,7 +82,6 @@ export declare class DatePickerElement extends HTMLElement {
|
|
|
171
82
|
get messages(): typeof import("./messages.js").DEFAULT_MESSAGES;
|
|
172
83
|
/** @param {Partial<typeof import("./messages.js").DEFAULT_MESSAGES> | null} value */
|
|
173
84
|
set messages(value: Partial<typeof import("./messages.js").DEFAULT_MESSAGES> | null);
|
|
174
|
-
_refreshMessages(): void;
|
|
175
85
|
/** @public @returns {import("./date-calendar.js").DateSource | null} */
|
|
176
86
|
get source(): import("./date-calendar.js").DateSource | null;
|
|
177
87
|
set source(value: import("./date-calendar.js").DateSource | null);
|
|
@@ -187,224 +97,6 @@ export declare class DatePickerElement extends HTMLElement {
|
|
|
187
97
|
/** @public JS-only coordinate-space policy: "viewport" by default, "document" as an explicit opt-in; a change made while open applies to the next opening only. @returns {"viewport" | "document"} */
|
|
188
98
|
get coordinateSpace(): "viewport" | "document";
|
|
189
99
|
set coordinateSpace(value: "viewport" | "document");
|
|
190
|
-
_adapter(): {
|
|
191
|
-
locale: string;
|
|
192
|
-
placeholder: string;
|
|
193
|
-
format(value: string): string;
|
|
194
|
-
parse(text: string): string;
|
|
195
|
-
};
|
|
196
|
-
_setupFormValue(): void;
|
|
197
|
-
_syncInputState(): void;
|
|
198
|
-
_syncRangeFields(): void;
|
|
199
|
-
_setRangeAria(): void;
|
|
200
|
-
/**
|
|
201
|
-
* Create one calendar trigger. It is a real button overlaid inside the
|
|
202
|
-
* field's own box (see the CSS): the field reserves the room, the trigger
|
|
203
|
-
* stays transparent, and the field's focus ring wraps both.
|
|
204
|
-
* @param {"" | "start" | "end"} endpoint
|
|
205
|
-
* @returns {HTMLButtonElement}
|
|
206
|
-
*/
|
|
207
|
-
_createTrigger(endpoint: "" | "start" | "end"): HTMLButtonElement;
|
|
208
|
-
/**
|
|
209
|
-
* Build the popup once and one trigger per field. Each trigger is inserted
|
|
210
|
-
* immediately after its own field so DOM order follows visual/tab order
|
|
211
|
-
* (`date -> its trigger -> next control`). The hidden ISO field may sit
|
|
212
|
-
* between the two; it is not focusable.
|
|
213
|
-
*/
|
|
214
|
-
_build(): void;
|
|
215
|
-
/** @param {AbortSignal} signal @param {HTMLInputElement | null | undefined} input @param {() => void} restore */
|
|
216
|
-
_bindPopup(signal: AbortSignal, input: HTMLInputElement | null | undefined, restore: () => void): void;
|
|
217
|
-
_bind(): void;
|
|
218
|
-
_bindRange(): void;
|
|
219
|
-
/**
|
|
220
|
-
* Point the shared calendar at one range bound and invalidate any pending
|
|
221
|
-
* activation, so a late response can never commit on the wrong bound.
|
|
222
|
-
* @param {"start" | "end"} endpoint
|
|
223
|
-
*/
|
|
224
|
-
_activateRangeEndpoint(endpoint: "start" | "end"): void;
|
|
225
|
-
/**
|
|
226
|
-
* Range has one trigger per bound. A press on the other bound's trigger is a
|
|
227
|
-
* local switch, not a toggle: it repoints the single open popup instead of
|
|
228
|
-
* closing it. Only a press on the already-active bound toggles shut.
|
|
229
|
-
* @param {"start" | "end"} endpoint
|
|
230
|
-
* @param {MouseEvent} event
|
|
231
|
-
*/
|
|
232
|
-
_onRangeTriggerClick(endpoint: "start" | "end", event: MouseEvent): void;
|
|
233
|
-
/**
|
|
234
|
-
* ArrowDown on a trigger opens the popup and drops into the grid, like the
|
|
235
|
-
* field itself (Enter/Space go through the click path). Needed because focus
|
|
236
|
-
* now returns to the trigger that opened the popover.
|
|
237
|
-
* @param {"" | "start" | "end"} endpoint
|
|
238
|
-
* @param {KeyboardEvent} event
|
|
239
|
-
*/
|
|
240
|
-
_onTriggerKeyDown(endpoint: "" | "start" | "end", event: KeyboardEvent): void;
|
|
241
|
-
/** @param {"" | "start" | "end"} endpoint @param {KeyboardEvent} event */
|
|
242
|
-
_onFieldKeyDown(endpoint: "" | "start" | "end", event: KeyboardEvent): void;
|
|
243
|
-
/** @param {"" | "start" | "end"} endpoint @param {FocusEvent} event */
|
|
244
|
-
_onFieldFocus(endpoint: "" | "start" | "end", event: FocusEvent): void;
|
|
245
|
-
/** Record grid activation intent (click or keyboard) so a stale,
|
|
246
|
-
* late-confirmed dateactivate can be recognized and dropped. Each new grid
|
|
247
|
-
* activation supersedes every earlier pending one.
|
|
248
|
-
* @param {Event} event */
|
|
249
|
-
_captureGridIntent(event: Event): void;
|
|
250
|
-
/**
|
|
251
|
-
* Hand a new intent to the calendar's next activation of `date`. Taking a
|
|
252
|
-
* new generation supersedes every earlier pending activation.
|
|
253
|
-
* @param {string} date @param {"" | "start" | "end"} endpoint
|
|
254
|
-
*/
|
|
255
|
-
_attachIntent(date: string, endpoint: "" | "start" | "end"): void;
|
|
256
|
-
/**
|
|
257
|
-
* Invalidate every pending grid activation: each one carries its own intent,
|
|
258
|
-
* which now fails the generation check. A programmatic
|
|
259
|
-
* `calendar.activateDate()` carries none.
|
|
260
|
-
*/
|
|
261
|
-
_supersedeActivations(): void;
|
|
262
|
-
/** Whether one bound can receive a user pick right now.
|
|
263
|
-
* @param {"start" | "end"} bound */
|
|
264
|
-
_boundEditable(bound: "start" | "end"): boolean;
|
|
265
|
-
/**
|
|
266
|
-
* Single write path for a calendar-driven range change. Both fields are
|
|
267
|
-
* synchronized before anything is announced, because one transition can move
|
|
268
|
-
* both bounds at once (`10` then `5` commits `start 10 -> 5` together with
|
|
269
|
-
* `end "" -> 10`); writing bound by bound would expose an intermediate
|
|
270
|
-
* inverted pair. Synthetic `input`/`change` stay on the bounds that moved.
|
|
271
|
-
* @param {import("./date-range.js").RangeTransition} transition
|
|
272
|
-
*/
|
|
273
|
-
_applyRangeTransition(transition: import("./date-range.js").RangeTransition): void;
|
|
274
|
-
/**
|
|
275
|
-
* Project a candidate date onto the calendar band. Purely visual: no
|
|
276
|
-
* `rangechange`, no field write, no validation and no source request — the
|
|
277
|
-
* projection reads the month state already loaded, and the real availability
|
|
278
|
-
* check still runs on commit.
|
|
279
|
-
* @param {string} date @param {"" | "start" | "end"} [endpoint]
|
|
280
|
-
*/
|
|
281
|
-
_setPreview(date: string, endpoint?: "" | "start" | "end"): void;
|
|
282
|
-
/** Drop the projection and put the committed range back on the band. */
|
|
283
|
-
_clearPreview(): void;
|
|
284
|
-
/** @param {Event} event */
|
|
285
|
-
_onGridDateFocus(event: Event): void;
|
|
286
|
-
/**
|
|
287
|
-
* Hover candidate. `focusedDate` stays the keyboard target only: pointer
|
|
288
|
-
* hover never moves it, it just feeds the same projection.
|
|
289
|
-
* @param {Event} event
|
|
290
|
-
*/
|
|
291
|
-
_onGridPointerOver(event: Event): void;
|
|
292
|
-
_onGridPointerLeave(): void;
|
|
293
|
-
/** @param {EventTarget | null} target @returns {string} */
|
|
294
|
-
_cellDate(target: EventTarget | null): string;
|
|
295
|
-
/** @param {number} x @param {number} y @returns {string} */
|
|
296
|
-
_dateAtPoint(x: number, y: number): string;
|
|
297
|
-
/**
|
|
298
|
-
* Arm an endpoint drag. Only a complete, ordered range offers two distinct
|
|
299
|
-
* handles; a one-day range carries both markers on the same cell, and
|
|
300
|
-
* guessing which one the user meant would be worse than not dragging.
|
|
301
|
-
* @param {Event} event
|
|
302
|
-
*/
|
|
303
|
-
_onGridPointerDown(event: Event): void;
|
|
304
|
-
/** @param {Event} event */
|
|
305
|
-
_onGridPointerMove(event: Event): void;
|
|
306
|
-
/** @param {Event} event */
|
|
307
|
-
_onGridPointerUp(event: Event): void;
|
|
308
|
-
/** @param {Event} event */
|
|
309
|
-
_onGridPointerCancel(event: Event): void;
|
|
310
|
-
/** @param {number} pointerId */
|
|
311
|
-
_releaseDragCapture(pointerId: number): void;
|
|
312
|
-
/** Expose whether the band currently offers draggable handles, and whether
|
|
313
|
-
* one is being dragged, so the cursor can say so. */
|
|
314
|
-
_syncDragAffordance(): void;
|
|
315
|
-
/** @param {KeyboardEvent} event */
|
|
316
|
-
_onEscape(event: KeyboardEvent): void;
|
|
317
|
-
_syncCalendarOptions(): void;
|
|
318
|
-
_refreshLocale(): void;
|
|
319
|
-
_refreshButtonLabel(): void;
|
|
320
|
-
/** Each bound has its own trigger, so each needs its own accessible name: the
|
|
321
|
-
* bound label is what tells two otherwise identical triggers apart. */
|
|
322
|
-
_refreshRangeButtonLabel(): void;
|
|
323
|
-
/** @param {string} value */
|
|
324
|
-
_reflectValue(value: string): void;
|
|
325
|
-
/** @param {string} value @param {{emit?:boolean,format?:boolean,reflect?:boolean,moveDisplay?:boolean}} [options] */
|
|
326
|
-
_setValue(value: string, options?: {
|
|
327
|
-
emit?: boolean;
|
|
328
|
-
format?: boolean;
|
|
329
|
-
reflect?: boolean;
|
|
330
|
-
moveDisplay?: boolean;
|
|
331
|
-
}): void;
|
|
332
|
-
/**
|
|
333
|
-
* Calendar-backed availability gate injected into the field controllers.
|
|
334
|
-
* A cancelled or failed source load confirms nothing: do not fill the
|
|
335
|
-
* submitted ISO field nor treat the date as available.
|
|
336
|
-
* @param {string} date @param {{moveDisplay?: boolean}} [options]
|
|
337
|
-
* @returns {Promise<{ok: boolean, message?: string}>}
|
|
338
|
-
*/
|
|
339
|
-
_confirmDate(date: string, options?: {
|
|
340
|
-
moveDisplay?: boolean;
|
|
341
|
-
}): Promise<{
|
|
342
|
-
ok: boolean;
|
|
343
|
-
message?: string;
|
|
344
|
-
}>;
|
|
345
|
-
/** @param {boolean} emit @param {{force?: boolean, moveDisplay?: boolean}} [options] */
|
|
346
|
-
_commitText(emit: boolean, options?: {
|
|
347
|
-
force?: boolean;
|
|
348
|
-
moveDisplay?: boolean;
|
|
349
|
-
}): Promise<boolean>;
|
|
350
|
-
/** @param {"start" | "end"} which */
|
|
351
|
-
_commitFieldText(which: "start" | "end"): Promise<boolean>;
|
|
352
|
-
/** @param {"start" | "end"} which @param {string} value
|
|
353
|
-
* @param {{emit?:boolean, format?:boolean, user?:boolean}} [options] @returns {boolean} */
|
|
354
|
-
_setBound(which: "start" | "end", value: string, options?: {
|
|
355
|
-
emit?: boolean;
|
|
356
|
-
format?: boolean;
|
|
357
|
-
user?: boolean;
|
|
358
|
-
}): boolean;
|
|
359
|
-
/** Atomic pair update: canonical + visible + hidden before one event.
|
|
360
|
-
* @param {string} start @param {string} end @param {{emit?:boolean, format?:boolean}} [options] */
|
|
361
|
-
_setRange(start: string, end: string, options?: {
|
|
362
|
-
emit?: boolean;
|
|
363
|
-
format?: boolean;
|
|
364
|
-
}): void;
|
|
365
|
-
/** @param {"start" | "end"} which */
|
|
366
|
-
_boundCanonical(which: "start" | "end"): string;
|
|
367
|
-
_emitRangeChange(): void;
|
|
368
|
-
/** Displayable projection of the business range. An inverted or start-less
|
|
369
|
-
* pair shows no misleading band. @returns {{start:string, end:string}} */
|
|
370
|
-
_displayRange(): {
|
|
371
|
-
start: string;
|
|
372
|
-
end: string;
|
|
373
|
-
};
|
|
374
|
-
/** Put the committed range back on the calendar band, dropping any
|
|
375
|
-
* projection currently shown over it. */
|
|
376
|
-
_syncHighlight(): void;
|
|
377
|
-
/**
|
|
378
|
-
* Cross-bound validity: the order error is attributed to the bound that was
|
|
379
|
-
* just modified; the other bound only loses a stale order error, never its
|
|
380
|
-
* independent parse/source validity.
|
|
381
|
-
* @param {"start" | "end"} which
|
|
382
|
-
*/
|
|
383
|
-
_revalidateRange(which: "start" | "end"): void;
|
|
384
|
-
/**
|
|
385
|
-
* Single-mode from/to order on the shared date: `start <= end`.
|
|
386
|
-
* The order error is attributed to the bound that was just modified; the
|
|
387
|
-
* other bound only loses a stale order error, never its native validity.
|
|
388
|
-
* Missing, empty or disabled times fall back to no order constraint: times
|
|
389
|
-
* are never implicitly required. Equality stays valid (duration rules are
|
|
390
|
-
* application-owned).
|
|
391
|
-
* @param {"start" | "end"} which
|
|
392
|
-
*/
|
|
393
|
-
_revalidateTimeOrder(which: "start" | "end"): void;
|
|
394
|
-
_clearTimeOrderValidity(): void;
|
|
395
|
-
_restoreDefault(): void;
|
|
396
|
-
_restoreRangeDefault(): void;
|
|
397
|
-
/** @param {"start" | "end"} which */
|
|
398
|
-
_focusField(which: "start" | "end"): void;
|
|
399
|
-
/**
|
|
400
|
-
* Restore focus to the control that opened the popover (single mode). focus()
|
|
401
|
-
* must not run synchronously from a keydown handler (Chromium drops focus
|
|
402
|
-
* changes there) and must not re-open the popover under the default
|
|
403
|
-
* open-on-focus. Deferring keeps _suppressFocusOpen active for the call.
|
|
404
|
-
*/
|
|
405
|
-
_restoreFocus(): void;
|
|
406
|
-
/** @returns {"" | "start" | "end"} */
|
|
407
|
-
_resolveRangeEndpoint(): "" | "start" | "end";
|
|
408
100
|
/** @public */
|
|
409
101
|
validate(): Promise<boolean>;
|
|
410
102
|
/** @public @param {{moveFocus?:boolean, endpoint?:"start"|"end", returnFocus?:HTMLElement}} [options] */
|
|
@@ -413,14 +105,6 @@ export declare class DatePickerElement extends HTMLElement {
|
|
|
413
105
|
endpoint?: "start" | "end";
|
|
414
106
|
returnFocus?: HTMLElement;
|
|
415
107
|
}): void;
|
|
416
|
-
/** @param {boolean} expanded */
|
|
417
|
-
_setExpanded(expanded: boolean): void;
|
|
418
108
|
/** @public @param {boolean} [restoreFocus] */
|
|
419
109
|
hide(restoreFocus?: boolean): void;
|
|
420
|
-
/** The bound to restore focus to after closing a range popup: the active
|
|
421
|
-
* endpoint first (it moved after the first selection), then the next
|
|
422
|
-
* focusable bound.
|
|
423
|
-
* @returns {"" | "start" | "end"} */
|
|
424
|
-
_restoreFocusEndpoint(): "" | "start" | "end";
|
|
425
110
|
}
|
|
426
|
-
//# sourceMappingURL=date-picker.d.ts.map
|
|
@@ -78,10 +78,9 @@ export type RangeTransition = {
|
|
|
78
78
|
* - activating through `start` commits `start`, moves the active endpoint to
|
|
79
79
|
* `end` and leaves the popup open for the second selection.
|
|
80
80
|
*
|
|
81
|
-
* Each rule exists once, as a `project
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* into the DOM layer.
|
|
81
|
+
* Each rule exists once, as a `project()` projection; `activate()` only
|
|
82
|
+
* applies one. Keyboard and pointer previews read the same projection, so a
|
|
83
|
+
* second copy of the interaction rules cannot drift into the DOM layer.
|
|
85
84
|
*
|
|
86
85
|
* Text entry is deliberately outside this machine: a typed pair may be
|
|
87
86
|
* temporarily inverted and report a validation error. It is the coordinator's
|
|
@@ -114,34 +113,20 @@ export declare class DateRangeController {
|
|
|
114
113
|
* @returns {RangeTransition}
|
|
115
114
|
*/
|
|
116
115
|
project(date: string, editable?: (bound: "start" | "end") => boolean): RangeTransition;
|
|
117
|
-
/**
|
|
118
|
-
* Project a direct move of one bound (endpoint drag), without mutating.
|
|
119
|
-
*
|
|
120
|
-
* Only a complete, ordered pair offers two handles to grab, and a handle
|
|
121
|
-
* never crosses the other one: the candidate is clamped to the opposite
|
|
122
|
-
* bound, so the previewed band is exactly what a drop commits. Equality is
|
|
123
|
-
* allowed (a one-day range).
|
|
124
|
-
* @param {string} date
|
|
125
|
-
* @param {"start" | "end"} endpoint
|
|
126
|
-
* @param {(bound: "start" | "end") => boolean} [editable]
|
|
127
|
-
* @returns {RangeTransition}
|
|
128
|
-
*/
|
|
129
|
-
projectEndpoint(date: string, endpoint: "start" | "end", editable?: (bound: "start" | "end") => boolean): RangeTransition;
|
|
130
116
|
/**
|
|
131
117
|
* Displayable band a candidate date would produce, or `null` when there is
|
|
132
|
-
* nothing to promise. Single source of truth for hover
|
|
133
|
-
*
|
|
118
|
+
* nothing to promise. Single source of truth for hover and keyboard previews:
|
|
119
|
+
* the projection rules with no mutation and no event.
|
|
134
120
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
121
|
+
* A preview only speaks during range creation, when exactly one bound is
|
|
122
|
+
* anchored and the other is still open. An empty range has no anchor to
|
|
123
|
+
* project from, and a complete pair is not a promise in progress —
|
|
124
|
+
* previewing it would repaint the committed band on every hover.
|
|
139
125
|
* @param {string} date
|
|
140
|
-
* @param {"" | "start" | "end"} [endpoint] drag handle, empty for a normal pick
|
|
141
126
|
* @param {(bound: "start" | "end") => boolean} [editable]
|
|
142
127
|
* @returns {DateRange | null}
|
|
143
128
|
*/
|
|
144
|
-
previewRange(date: string,
|
|
129
|
+
previewRange(date: string, editable?: (bound: "start" | "end") => boolean): DateRange | null;
|
|
145
130
|
/** @param {RangeTransition} transition @returns {RangeTransition} */
|
|
146
131
|
_apply(transition: RangeTransition): RangeTransition;
|
|
147
132
|
/**
|
|
@@ -151,14 +136,6 @@ export declare class DateRangeController {
|
|
|
151
136
|
* @returns {RangeTransition}
|
|
152
137
|
*/
|
|
153
138
|
activate(date: string, editable?: (bound: "start" | "end") => boolean): RangeTransition;
|
|
154
|
-
/**
|
|
155
|
-
* Apply a direct move of one bound (endpoint drag).
|
|
156
|
-
* @param {string} date
|
|
157
|
-
* @param {"start" | "end"} endpoint
|
|
158
|
-
* @param {(bound: "start" | "end") => boolean} [editable]
|
|
159
|
-
* @returns {RangeTransition}
|
|
160
|
-
*/
|
|
161
|
-
moveEndpoint(date: string, endpoint: "start" | "end", editable?: (bound: "start" | "end") => boolean): RangeTransition;
|
|
162
139
|
}
|
|
163
140
|
/**
|
|
164
141
|
* Link two date pickers without teaching either picker about its sibling.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date-range.d.ts","sourceRoot":"","sources":["../../src/date-range.js"],"names":[],"mappings":"AAEI,YAAsC,SAAS,GAArC;IAAC,KAAK,EAAC,MAAM,CAAC;IAAC,GAAG,EAAC,MAAM,CAAA;CAAC,CAAW;AAAnD,sDAAsD;AAEtD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAHzB;IAAC,KAAK,CAAC,EAAC,MAAM,CAAC;IAAC,GAAG,CAAC,EAAC,MAAM,CAAA;CAAC,GAAC,IAAI,GAAC,SAGT,GAFvB,SAAS,CAYrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAJvB,MAIuB,EAAE,KAAK,EAH9B,SAAS,GAAC,IAAI,GAAC,SAGe,GAF5B,EAAE,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,QAAQ,CAUlD;AAGE,YAAkB,eAAe,GACjC;IAA+C,MAAM,EAA1C,SAAS,GAAG,UAAU,GAAG,SAAS,CAC7C;;;;IAA4B,QAAQ,EAAzB,OAAO,GAAG,KAAK,CAC1B;;;;IAAgC,gBAAgB,EAArC,CAAC,OAAO,GAAG,KAAK,CAAC,EAAE,CAC9B;;;;IAAsB,KAAK,EAAhB,SAAS,CACpB;;;;IAAiC,cAAc,EAApC,EAAE,GAAG,OAAO,GAAG,KAAK,CAC/B;;;;IAAqB,KAAK,AAA1B,CACF,EADa,OAAO,CACpB;CAAA,CAAA;AARD;;;;;;;;GAQG;AAEH
|
|
1
|
+
{"version":3,"file":"date-range.d.ts","sourceRoot":"","sources":["../../src/date-range.js"],"names":[],"mappings":"AAEI,YAAsC,SAAS,GAArC;IAAC,KAAK,EAAC,MAAM,CAAC;IAAC,GAAG,EAAC,MAAM,CAAA;CAAC,CAAW;AAAnD,sDAAsD;AAEtD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAHzB;IAAC,KAAK,CAAC,EAAC,MAAM,CAAC;IAAC,GAAG,CAAC,EAAC,MAAM,CAAA;CAAC,GAAC,IAAI,GAAC,SAGT,GAFvB,SAAS,CAYrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAJvB,MAIuB,EAAE,KAAK,EAH9B,SAAS,GAAC,IAAI,GAAC,SAGe,GAF5B,EAAE,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,QAAQ,CAUlD;AAGE,YAAkB,eAAe,GACjC;IAA+C,MAAM,EAA1C,SAAS,GAAG,UAAU,GAAG,SAAS,CAC7C;;;;IAA4B,QAAQ,EAAzB,OAAO,GAAG,KAAK,CAC1B;;;;IAAgC,gBAAgB,EAArC,CAAC,OAAO,GAAG,KAAK,CAAC,EAAE,CAC9B;;;;IAAsB,KAAK,EAAhB,SAAS,CACpB;;;;IAAiC,cAAc,EAApC,EAAE,GAAG,OAAO,GAAG,KAAK,CAC/B;;;;IAAqB,KAAK,AAA1B,CACF,EADa,OAAO,CACpB;CAAA,CAAA;AARD;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,mBAAmB;IAE5B,mCAAmC;IAC9B,cAAc,EADR,EAAE,GAAG,OAAO,GAAG,KAAK;IAE1B,KAAK;IACL,GAAG;IAJV,cAKC;IAED,2BAA2B;IAC3B,IAAI,KAAK,IADK,SAAS,CAGtB;IAED,gEAAgE;IAChE,IAAI,QAAQ,YAEX;IAED,6CAA6C;IAC7C,KAAK,CAAC,QAAQ,EADF,EAAE,GAAG,OAAO,GAAG,KACb,QAEb;IAED,mEAAmE;IACnE,OAAO,CAAC,QAAQ,EADJ,OAAO,GAAG,KACN,GADgC,eAAe,CAS9D;IAED;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,IAAI,EAJD,MAIC,EAAE,QAAQ,AAHnB,CACA,EADQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,KAAK,OAGJ,GAFtB,eAAe,CAoE3B;IAED;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,IAAI,EAJN,MAIM,EAAE,QAAQ,AAHxB,CACA,EADQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,KAAK,OAGC,GAF3B,SAAS,GAAG,IAAI,CAU5B;IAED,qEAAqE;IACrE,MAAM,CAAC,UAAU,EADL,eACK,GADiC,eAAe,CAOhE;IAED;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAJF,MAIE,EAAE,QAAQ,AAHpB,CACA,EADQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,KAAK,OAGH,GAFvB,eAAe,CAI3B;CACF;AAgBD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAJxB,OAAO,kBAAkB,EAAE,iBAIH,EAAE,GAAG,EAH7B,OAAO,kBAAkB,EAAE,iBAGE,GAF3B,MAAM,IAAI,CAyBtB"}
|
package/dist/types/date.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../../src/date.js"],"names":[],"mappings":"AAGA,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,IAAI,EADnB,MACmB,WAE9B;AAED,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,IAAI,EADpB,MACoB,EAAE,KAAK,EADN,MACM,qBAGtC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAHpB,MAGoB,GAFlB;IAAC,IAAI,EAAC,MAAM,CAAC;IAAC,KAAK,EAAC,MAAM,CAAC;IAAC,GAAG,EAAC,MAAM,CAAA;CAAC,GAAG,IAAI,CAW1D;AAED,4BAA4B;AAC5B,wBAAgB,MAAM,CAAC,KAAK,EADhB,MACgB,WAE3B;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAHnB,MAGmB,GAFjB,MAAM,
|
|
1
|
+
{"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../../src/date.js"],"names":[],"mappings":"AAGA,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,IAAI,EADnB,MACmB,WAE9B;AAED,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,IAAI,EADpB,MACoB,EAAE,KAAK,EADN,MACM,qBAGtC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAHpB,MAGoB,GAFlB;IAAC,IAAI,EAAC,MAAM,CAAC;IAAC,KAAK,EAAC,MAAM,CAAC;IAAC,GAAG,EAAC,MAAM,CAAA;CAAC,GAAG,IAAI,CAW1D;AAED,4BAA4B;AAC5B,wBAAgB,MAAM,CAAC,KAAK,EADhB,MACgB,WAE3B;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAHnB,MAGmB,GAFjB,MAAM,CAelB;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,IAAI,EADlB,MACkB,EAAE,KAAK,EADJ,MACI,EAAE,GAAG,EADa,MACb,UAIzC;AAeD,4BAA4B;AAC5B,wBAAgB,UAAU,CAAC,KAAK,EADpB,MACoB,QAI/B;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,CAAC,EADlB,MACkB,EAAE,CAAC,EADH,MACG,cAGhC;AAED,mDAAmD;AACnD,wBAAgB,OAAO,CAAC,KAAK,EADjB,MACiB,EAAE,MAAM,EADH,MACG,UAOpC;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,KAAK,EADnB,MACmB,EAAE,MAAM,EADL,MACK,UAUtC;AAED,mDAAmD;AACnD,wBAAgB,QAAQ,CAAC,KAAK,EADlB,MACkB,EAAE,MAAM,EADJ,MACI,UAOrC;AAED,mDAAmD;AACnD,wBAAgB,UAAU,CAAC,KAAK,EADpB,MACoB,EAAE,MAAM,EADN,MACM,UAGvC;AAED,4BAA4B;AAC5B,wBAAgB,YAAY,CAAC,KAAK,EADtB,MACsB,UAEjC;AAED,4BAA4B;AAC5B,wBAAgB,UAAU,CAAC,KAAK,EADpB,MACoB,UAI/B;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,KAAK,EADM,MACN,UAI9B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAF/B,MAE+B,UAKzC;AAED,qDAAqD;AACrD,wBAAgB,WAAW,CAAC,KAAK,EADrB,MACqB,EAAE,QAAQ,GADT,MACa,UAG9C;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,KAAK,EADnB,MACmB,EAAE,QAAQ,GADP,MACW,UAE5C;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAHxB,MAGwB,EAAE,OAAO,AAFzC,CAA4D,EAApD;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAEF,cAsBhD;AAaD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAJ5B,MAI4B,EAAE,QAAQ,AAH9C,CACA,EADQ,MAG0C,GAFxC,MAAM,CAyBlB;AAED,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,KAAK,EADnB,MACmB,EAAE,GAAG,AADV,CAAsB,EAAd,MACO,EAAE,GAAG,AADE,CAAqB,EAAb,MACL,UAKlD;AAED,2EAA2E;AAC3E,wBAAgB,QAAQ,WAGvB;AAED,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,KAAK,EADD,MACC,UAUlC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One lifetime: a picker connection, a calendar connection or a field
|
|
3
|
+
* controller. The returned controller is the token an operation captures
|
|
4
|
+
* before it suspends.
|
|
5
|
+
* @returns {AbortController}
|
|
6
|
+
*/
|
|
7
|
+
export declare function createSession(): AbortController;
|
|
8
|
+
/**
|
|
9
|
+
* Protect a callback with the signal captured now. The caller keeps the
|
|
10
|
+
* scheduling choice (`queueMicrotask` vs `setTimeout`), because focus behavior
|
|
11
|
+
* in Chromium depends on it.
|
|
12
|
+
* @param {AbortSignal} signal
|
|
13
|
+
* @param {() => void} callback
|
|
14
|
+
* @returns {() => void}
|
|
15
|
+
*/
|
|
16
|
+
export declare function guard(signal: AbortSignal, callback: () => void): () => void;
|
|
17
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/session.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,aAAa,IAFhB,eAAe,CAI3B;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAJjB,WAIiB,EAAE,QAAQ,EAH3B,MAAM,IAGqB,GAFzB,MAAM,IAAI,CAMtB"}
|
package/docs/ACCESSIBILITY.md
CHANGED
|
@@ -42,7 +42,7 @@ Moving focus never changes selection. Each cell that actually receives focus dis
|
|
|
42
42
|
|
|
43
43
|
A range preview projected onto the band is visual only: it never contributes a range start/end to a day's accessible name, so a proposal is never announced as a committed selection.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
An application-owned pointer gesture that moves a range bound must end in the same `activateDate()` + `dateactivate` path as the keyboard: availability is checked and the activation is cancellable, so a pointer gesture cannot bypass the keyboard contract.
|
|
46
46
|
|
|
47
47
|
## Month/year controls
|
|
48
48
|
|
package/docs/API.md
CHANGED
|
@@ -27,6 +27,16 @@ The public surface — flat event names, `selection` values, `--dp-*` styling to
|
|
|
27
27
|
|
|
28
28
|
Presentation-only range band. It never changes `value`, `selection` or `focusedDate`. It accepts an ordered `{ start, end }` pair of `YYYY-MM-DD` values; `end` may be empty (range in progress), and a `start === end` range renders both endpoints with a dedicated label. `null` and `{ start: "", end: "" }` reset the band. Inverted ranges and `{ start: "", end: "…" }` throw a `TypeError` — the property never reorders or guesses. Styled spans render `data-range-start="true"`, `data-in-range="true"`, `data-range-end="true"` and extend each day's `aria-label` accordingly.
|
|
29
29
|
|
|
30
|
+
### Day cell data attributes
|
|
31
|
+
|
|
32
|
+
Day cells stay component-owned DOM (D3): `.dp-*` classes are not a styling API and the cell cannot be replaced. These read-only data attributes are stable hooks for application-owned interactions:
|
|
33
|
+
|
|
34
|
+
- `data-date="YYYY-MM-DD"` — canonical date of the cell;
|
|
35
|
+
- `data-range-start` / `data-in-range` / `data-range-end` — band position painted from `highlightedRange` (a one-day range carries both start and end);
|
|
36
|
+
- `data-range-preview` — on the calendar, not the cell: the band is a proposal, so cells stop contributing a range start/end to their accessible name while it is set.
|
|
37
|
+
|
|
38
|
+
Applications may read them (for example to hit-test a pointer gesture, see [CALENDAR-INTEGRATION](CALENDAR-INTEGRATION.md)); they must not write them.
|
|
39
|
+
|
|
30
40
|
### Callback properties
|
|
31
41
|
|
|
32
42
|
```js
|
|
@@ -149,7 +159,7 @@ Markup:
|
|
|
149
159
|
</date-picker>
|
|
150
160
|
```
|
|
151
161
|
|
|
152
|
-
The direct child text input is required. On enhancement its original `name` moves to an internal hidden input that submits the canonical ISO value.
|
|
162
|
+
The direct child text input is required. On enhancement its original `name` moves to an internal hidden input that submits the canonical ISO value. Dynamic `name`/`form`/`disabled` changes are synchronized by a `MutationObserver`; code that reads `FormData` in the same task as such a mutation must wait for the next microtask. Removing `name` from the visible enhanced input does not unregister the canonical field because that input no longer owns the submitted name; disable the field to omit it from submission.
|
|
153
163
|
|
|
154
164
|
### Attributes / properties
|
|
155
165
|
|
|
@@ -211,6 +221,8 @@ picker.hide();
|
|
|
211
221
|
await picker.validate();
|
|
212
222
|
```
|
|
213
223
|
|
|
224
|
+
When disconnected, `validate()` cannot run source availability checks and returns the current native validity of the authored date/time inputs.
|
|
225
|
+
|
|
214
226
|
### Events
|
|
215
227
|
|
|
216
228
|
- `valuechange` — canonical value changed;
|
|
@@ -303,7 +315,7 @@ The business range may be temporarily incomplete or inverted; `calendar.highligh
|
|
|
303
315
|
- **Calendar interaction never produces an inverted range.** While a range is being created, a second pick before the anchor is sorted rather than refused: `10` then `15` gives `10 -> 15`, `10` then `5` gives `5 -> 10`, and `10` then `10` a one-day range. That transition moves **both** bounds at once and is applied in a single write — one `rangechange`, with synthetic `input`/`change` on each bound that moved, and no intermediate inverted pair ever observable. Typed text keeps the opposite contract and may be temporarily inverted (see [DECISIONS D6](DECISIONS.md)).
|
|
304
316
|
- Changing one bound of an already complete pair keeps that bound's identity: moving `end` before `start` is refused (`dateinvalid`, nothing changes) instead of rewriting the other field.
|
|
305
317
|
- **Preview.** While a range is being created, hovering a day or moving the keyboard focus projects the range the next pick would commit onto `calendar.highlightedRange`, and the pick commits exactly what was projected. The projection is visual only: no `rangechange`, no field write, no validation and no source request. It reads the month already loaded, so a day known to be unavailable promises nothing; the real availability check still runs on commit. Pointer hover never moves `focusedDate`, which stays the keyboard target. Leaving the grid, Escape and closing the popup restore the committed band.
|
|
306
|
-
-
|
|
318
|
+
- Adjusting an existing bound by pointer (dragging an endpoint) is application-owned: see [DECISIONS D9](DECISIONS.md) and the recipe in [CALENDAR-INTEGRATION.md](CALENDAR-INTEGRATION.md). The picker itself only moves a bound through a calendar activation (`activateDate()` / `dateactivate`).
|
|
307
319
|
- Each bound owns its trigger (`input[data-range-start]` then its trigger, `input[data-range-end]` then its trigger); the two triggers share **one** popup, calendar, source and state. Pressing the other bound's trigger while the popup is open switches the active bound and keeps it open instead of toggling it shut. A trigger never opens a `readonly`/`disabled` bound.
|
|
308
320
|
- Typing stays independent per field. Moving `start` past `end` keeps both values and flags the *modified* bound with `rangeOrderStart`; fixing either side revalidates both. Parse/source errors are never cleared by cross-bound revalidation.
|
|
309
321
|
- A stale availability response resolving after the active endpoint changed (or after a newer selection) never commits; a field focus that switches the endpoint invalidates in-flight activations.
|
|
@@ -328,29 +340,6 @@ rangePosition("2026-09-12", range); // "start" | "in" | "end" | "single" | ""
|
|
|
328
340
|
|
|
329
341
|
`normalizeRange` validates an ordered displayable range and never reorders: an inversion or an `end` without a `start` throws. `rangePosition` classifies one day within a displayable range (see [`highlightedRange`](#highlightedrange)).
|
|
330
342
|
|
|
331
|
-
## `DateRangeController`
|
|
332
|
-
|
|
333
|
-
The pure two-bound state machine behind `date-picker[range]`. It owns the
|
|
334
|
-
selection rules and knows nothing about DOM, popups or async availability.
|
|
335
|
-
|
|
336
|
-
```js
|
|
337
|
-
const model = new DateRangeController();
|
|
338
|
-
model.focus("start");
|
|
339
|
-
model.activate("2026-09-10"); // { status: "pending", endpoint: "start", … }
|
|
340
|
-
model.previewRange("2026-09-05"); // { start: "2026-09-05", end: "2026-09-10" }
|
|
341
|
-
model.activate("2026-09-05"); // changedEndpoints: ["start", "end"]
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
- `project(date, editable?)` / `projectEndpoint(date, endpoint, editable?)` describe a transition without applying it;
|
|
345
|
-
- `activate(date, editable?)` / `moveEndpoint(date, endpoint, editable?)` apply one;
|
|
346
|
-
- `previewRange(date, endpoint?, editable?)` returns the displayable band a candidate would produce, or `null` when there is nothing to promise.
|
|
347
|
-
|
|
348
|
-
A transition is `{ status, endpoint, changedEndpoints, range, activeEndpoint, close? }`.
|
|
349
|
-
`range` is the whole resulting pair, because one interaction can move both
|
|
350
|
-
bounds; `changedEndpoints` names the ones that actually move. Every rule exists
|
|
351
|
-
once, as a projection, and the previews read the same projections the commit
|
|
352
|
-
applies.
|
|
353
|
-
|
|
354
343
|
## `createDateAdapter(locale)`
|
|
355
344
|
|
|
356
345
|
Locale-aware editable numeric adapter:
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -126,6 +126,8 @@ Supported response shapes:
|
|
|
126
126
|
|
|
127
127
|
No server URL, query parameter names or backend schema are hard-coded into the core. `createFetchSource()` is only a convenience adapter.
|
|
128
128
|
|
|
129
|
+
A commit belongs to the field controller and connection that started it. Disconnect tears controllers down: pending validation is invalidated, and neither the controller nor the picker may apply a result produced before the rebuild. A reconnected picker re-derives its state from the live DOM instead of resuming pending work.
|
|
130
|
+
|
|
129
131
|
## 7. Picker composition
|
|
130
132
|
|
|
131
133
|
`<date-picker>` enhances a direct child text input.
|
|
@@ -195,3 +197,75 @@ Expected homes:
|
|
|
195
197
|
- start/end relationship → `date-range.js`.
|
|
196
198
|
|
|
197
199
|
Do not add a plugin system to solve a one-off rendering need.
|
|
200
|
+
|
|
201
|
+
## 11. Lifecycle and supported mutations
|
|
202
|
+
|
|
203
|
+
### 11.1 One session token
|
|
204
|
+
|
|
205
|
+
Every lifetime owns one `AbortController`: a picker connection, a calendar connection, a `DateFieldController`. Listeners are attached with its `signal`, and teardown aborts it.
|
|
206
|
+
|
|
207
|
+
Any write that follows a suspension (`await`, timer, microtask) is checked against **the session captured when the operation started**, never against the current one:
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
const session = this._session;
|
|
211
|
+
const result = await field.commit();
|
|
212
|
+
if (session.signal.aborted) return;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
A reconnected component has a new, live session. Testing `this._session` after the await would let a stale continuation through — that is the exact failure `1977785` fixed locally. This rule replaces per-path guards; it is not one more guard.
|
|
216
|
+
|
|
217
|
+
Scheduling stays at the call site (`queueMicrotask` vs `setTimeout`) because Chromium focus behavior depends on it. Only the guard is shared.
|
|
218
|
+
|
|
219
|
+
### 11.2 Three distinct questions, three mechanisms
|
|
220
|
+
|
|
221
|
+
| Question | Mechanism | Scope |
|
|
222
|
+
|---|---|---|
|
|
223
|
+
| Is the session I started in still alive? | captured `AbortSignal` | per connection / controller |
|
|
224
|
+
| Is my operation still the newest intent? | monotonic generation (`commitId`, activation id) | per field / per activation |
|
|
225
|
+
| Is the action still allowed now? | native state + interaction state (`isEditable()`, `:disabled`, `_open`, active bound) | read at apply time |
|
|
226
|
+
|
|
227
|
+
Do not merge them. The signal says nothing about a newer intent, and neither says whether a `<fieldset>` was disabled or the popup closed.
|
|
228
|
+
|
|
229
|
+
### 11.3 Supported mutations
|
|
230
|
+
|
|
231
|
+
| Element | Policy |
|
|
232
|
+
|---|---|
|
|
233
|
+
| single/range mode | read at connect; changing it requires reconnecting |
|
|
234
|
+
| identity of `[data-range-start]` / `[data-range-end]` inputs | fixed at connect |
|
|
235
|
+
| presence and role of `[data-time-start]` / `[data-time-end]` | fixed at connect |
|
|
236
|
+
| initial values (`input.value`, `defaultValue`) | read at connect |
|
|
237
|
+
| public API (`value`, `range`, `min`, `max`, `locale`, `source`, `dateState`, `renderDay`, `isDateDisabled`, `messages`, `controlClasses`, `monthFormat`, `openOnFocus`, `coordinateSpace`) | live; each setter documents whether it invalidates pending work |
|
|
238
|
+
| `name`, `form`, `disabled`, `readonly`, disabled fieldset, form reset, `defaultValue` | followed: this is the browser contract, not an arbitrary mutation |
|
|
239
|
+
| visible input text | draft, read at commit (blur/change/validate) |
|
|
240
|
+
| replacing child inputs | unsupported without reconnecting |
|
|
241
|
+
|
|
242
|
+
Undocumented structural mutations carry no guarantee:
|
|
243
|
+
|
|
244
|
+
> Undocumented structural mutations provide no resynchronization guarantee and no guarantee that interaction state is preserved. The component supports only the structure captured at connect time and the mutations listed above. To change the structure, disconnect and reconnect the component.
|
|
245
|
+
|
|
246
|
+
A security or native-data-integrity defect is still acceptable **inside a supported usage**, with a reproducible scenario. It is not required to be robust against arbitrary combinations of DOM mutations.
|
|
247
|
+
|
|
248
|
+
The initial `name` is captured for restoration; the current `name` stays dynamic.
|
|
249
|
+
|
|
250
|
+
### 11.4 Reconnect is a new session
|
|
251
|
+
|
|
252
|
+
Moving an element in the DOM disconnects and reconnects it. That rebuilds the controllers and calendar and abandons in-flight interaction without emitting an event. Native values and public configuration survive; popup, focus and active-bound state do not. This is the contract, not an edge case to patch.
|
|
253
|
+
|
|
254
|
+
### 11.5 State authorities
|
|
255
|
+
|
|
256
|
+
Name the authority before touching sync code; everything else is a projection recomputed from it.
|
|
257
|
+
|
|
258
|
+
| Mode | Authority | Projections |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| single | `DatePickerElement._value` | field canonical, hidden ISO, formatted text, `calendar.value`, `value` attribute |
|
|
261
|
+
| range | `DateRangeController.start/end` | each field canonical, hidden ISO, texts, `calendar.highlightedRange` (only when displayable) |
|
|
262
|
+
| calendar | `CalendarModel` | DOM, `_renderedStates` (paint cache) |
|
|
263
|
+
| draft | input text | re-read at commit; `_dirty` says whether it differs from the authority |
|
|
264
|
+
| native default | `input.defaultValue` | restored by reset; `_generatedDefault` separates a generated default from an application one |
|
|
265
|
+
|
|
266
|
+
Priority rules:
|
|
267
|
+
|
|
268
|
+
- assigning `value`/`range` invalidates pending activations and commits;
|
|
269
|
+
- a form reset wins and resynchronizes everything;
|
|
270
|
+
- a direct native write to the input is read at commit, not watched;
|
|
271
|
+
- a commit result is applied only if the captured session is alive **and** its `commitId` is current.
|