@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.
@@ -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*()` projection; `activate()` and
82
- * `moveEndpoint()` only apply one. Hover, keyboard and drag previews read the
83
- * same projections, so a second copy of the interaction rules cannot drift
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, keyboard and drag
133
- * previews: the projection rules with no mutation and no event.
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
- * Without a drag handle a preview only speaks during range creation, when
136
- * exactly one bound is anchored and the other is still open. An empty range
137
- * has no anchor to project from, and a complete pair is not a promise in
138
- * progress — previewing it would repaint the committed band on every hover.
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, endpoint?: "" | "start" | "end", editable?: (bound: "start" | "end") => boolean): DateRange | null;
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":"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"}
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"}
@@ -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,CAYlB;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"}
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"}
@@ -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
- Endpoint dragging is a pointer enhancement. Every adjustment it offers is also reachable from the keyboard: activating before or after a complete range moves the corresponding bound.
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
- - **Endpoint drag.** On a complete, ordered range whose two ends sit on different days, each endpoint can be dragged with a mouse or pen (pointer events plus capture, armed only once the pointer travels). A handle never crosses the other one — the candidate is clamped to it, equality allowed — an unavailable cell is not a drop target, and `pointercancel` restores the committed band. The drop runs the same projection and the same `ensureDate` + cancelable `dateactivate` path as a click, emits one `rangechange`, and keeps the popup open: a drag adjusts a range, it does not validate it. A press without travel stays a plain click. Touch keeps native scrolling and stays a plain tap.
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:
@@ -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.