stimeo-ui 0.2.0 → 0.3.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/CHANGELOG.md +163 -0
- package/dist/cable/index.d.ts +16 -16
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +3 -2
- package/dist/controllers/accordion_controller.js +10 -0
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +64 -0
- package/dist/controllers/alert_dialog_controller.js +318 -0
- package/dist/controllers/alert_dialog_controller.js.map +1 -0
- package/dist/controllers/breadcrumb_controller.d.ts +97 -10
- package/dist/controllers/breadcrumb_controller.js +225 -13
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +11 -1
- package/dist/controllers/calendar_controller.js +89 -22
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +115 -0
- package/dist/controllers/carousel_controller.js +313 -0
- package/dist/controllers/carousel_controller.js.map +1 -0
- package/dist/controllers/clipboard_controller.d.ts +72 -0
- package/dist/controllers/clipboard_controller.js +144 -0
- package/dist/controllers/clipboard_controller.js.map +1 -0
- package/dist/controllers/collapsible_controller.d.ts +70 -0
- package/dist/controllers/collapsible_controller.js +327 -0
- package/dist/controllers/collapsible_controller.js.map +1 -0
- package/dist/controllers/color_picker_controller.d.ts +87 -0
- package/dist/controllers/color_picker_controller.js +252 -0
- package/dist/controllers/color_picker_controller.js.map +1 -0
- package/dist/controllers/combobox_controller.d.ts +14 -1
- package/dist/controllers/combobox_controller.js +162 -23
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +2 -0
- package/dist/controllers/command_palette_controller.js +194 -17
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +7 -6
- package/dist/controllers/context_menu_controller.js +32 -10
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.js +8 -1
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +72 -0
- package/dist/controllers/currency_input_controller.js +147 -0
- package/dist/controllers/currency_input_controller.js.map +1 -0
- package/dist/controllers/data_grid_controller.d.ts +89 -0
- package/dist/controllers/data_grid_controller.js +246 -0
- package/dist/controllers/data_grid_controller.js.map +1 -0
- package/dist/controllers/date_range_picker_controller.d.ts +88 -0
- package/dist/controllers/date_range_picker_controller.js +441 -0
- package/dist/controllers/date_range_picker_controller.js.map +1 -0
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +57 -0
- package/dist/controllers/dismissible_controller.js +117 -0
- package/dist/controllers/dismissible_controller.js.map +1 -0
- package/dist/controllers/drawer_controller.d.ts +96 -0
- package/dist/controllers/drawer_controller.js +630 -0
- package/dist/controllers/drawer_controller.js.map +1 -0
- package/dist/controllers/dropdown_controller.d.ts +1 -2
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +73 -0
- package/dist/controllers/editable_controller.js +169 -0
- package/dist/controllers/editable_controller.js.map +1 -0
- package/dist/controllers/file_dropzone_controller.d.ts +92 -0
- package/dist/controllers/file_dropzone_controller.js +165 -0
- package/dist/controllers/file_dropzone_controller.js.map +1 -0
- package/dist/controllers/filter_controller.d.ts +60 -0
- package/dist/controllers/filter_controller.js +86 -0
- package/dist/controllers/filter_controller.js.map +1 -0
- package/dist/controllers/flash_controller.js +36 -5
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +1 -1
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +1 -1
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.js +6 -4
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +1 -1
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.js +67 -19
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.js +54 -11
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +9 -2
- package/dist/controllers/listbox_controller.js +257 -53
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.js +2 -2
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +54 -0
- package/dist/controllers/masonry_controller.js +142 -0
- package/dist/controllers/masonry_controller.js.map +1 -0
- package/dist/controllers/menu_controller.d.ts +29 -11
- package/dist/controllers/menu_controller.js +104 -17
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +142 -0
- package/dist/controllers/menubar_controller.js +785 -0
- package/dist/controllers/menubar_controller.js.map +1 -0
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +141 -0
- package/dist/controllers/multi_select_controller.js +755 -0
- package/dist/controllers/multi_select_controller.js.map +1 -0
- package/dist/controllers/navigation_menu_controller.d.ts +146 -0
- package/dist/controllers/navigation_menu_controller.js +511 -0
- package/dist/controllers/navigation_menu_controller.js.map +1 -0
- package/dist/controllers/nested_form_controller.d.ts +4 -5
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +1 -1
- package/dist/controllers/number_input_controller.js +7 -0
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/otp_controller.js +18 -1
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +15 -6
- package/dist/controllers/overflow_indicator_controller.js +246 -27
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +78 -19
- package/dist/controllers/overflow_menu_controller.js +381 -57
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +36 -5
- package/dist/controllers/pagination_controller.js +163 -32
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +49 -0
- package/dist/controllers/password_reveal_controller.js +117 -0
- package/dist/controllers/password_reveal_controller.js.map +1 -0
- package/dist/controllers/persist_controller.js +6 -6
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +2 -3
- package/dist/controllers/pointer_drag_controller.js +9 -1
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +1 -2
- package/dist/controllers/popover_controller.js +2 -2
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +4 -4
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.js +22 -3
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +97 -0
- package/dist/controllers/range_slider_controller.js +192 -0
- package/dist/controllers/range_slider_controller.js.map +1 -0
- package/dist/controllers/rating_controller.js +16 -2
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +52 -0
- package/dist/controllers/read_more_controller.js +238 -0
- package/dist/controllers/read_more_controller.js.map +1 -0
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +3 -1
- package/dist/controllers/resizable_controller.js +65 -1
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.js +17 -2
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.js +101 -14
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.d.ts +54 -0
- package/dist/controllers/scroll_restore_controller.js +93 -0
- package/dist/controllers/scroll_restore_controller.js.map +1 -0
- package/dist/controllers/scroll_visibility_controller.d.ts +6 -3
- package/dist/controllers/scroll_visibility_controller.js +40 -6
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +64 -15
- package/dist/controllers/scrollspy_controller.js +369 -74
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +69 -0
- package/dist/controllers/separator_controller.js +96 -0
- package/dist/controllers/separator_controller.js.map +1 -0
- package/dist/controllers/sidebar_controller.d.ts +107 -0
- package/dist/controllers/sidebar_controller.js +761 -0
- package/dist/controllers/sidebar_controller.js.map +1 -0
- package/dist/controllers/skeleton_controller.js +1 -1
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +11 -1
- package/dist/controllers/slider_controller.js +32 -6
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.d.ts +3 -1
- package/dist/controllers/sortable_controller.js +34 -3
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.js +1 -1
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +4 -0
- package/dist/controllers/stepper_controller.js +28 -12
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +5 -6
- package/dist/controllers/stick_to_bottom_controller.js +9 -4
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +17 -4
- package/dist/controllers/sticky_observer_controller.js +109 -20
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +2 -2
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.js +1 -0
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +13 -4
- package/dist/controllers/tabs_controller.js +26 -3
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +91 -0
- package/dist/controllers/tags_input_controller.js +295 -0
- package/dist/controllers/tags_input_controller.js.map +1 -0
- package/dist/controllers/theme_controller.d.ts +1 -1
- package/dist/controllers/theme_controller.js +42 -13
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +67 -0
- package/dist/controllers/time_picker_controller.js +231 -0
- package/dist/controllers/time_picker_controller.js.map +1 -0
- package/dist/controllers/toast_controller.d.ts +5 -14
- package/dist/controllers/toast_controller.js +40 -14
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.js +23 -2
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.d.ts +53 -13
- package/dist/controllers/toolbar_controller.js +230 -31
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +1 -1
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +14 -8
- package/dist/controllers/transition_controller.js +153 -38
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +135 -0
- package/dist/controllers/tree_view_controller.js +691 -0
- package/dist/controllers/tree_view_controller.js.map +1 -0
- package/dist/index.d.ts +27 -1818
- package/dist/index.js +4256 -915
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +375 -39
- package/dist/inspector/cli.js +228 -14
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +419 -48
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +26 -26
- package/dist/inspector/manifest.json +1054 -127
- package/package.json +5 -6
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
// src/controllers/date_range_picker_controller.ts
|
|
4
|
+
|
|
5
|
+
// src/utils/logical_scroll.ts
|
|
6
|
+
function isRtl(element) {
|
|
7
|
+
return window.getComputedStyle(element).direction === "rtl";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/utils/arrow_step.ts
|
|
11
|
+
function logicalArrowKey(key, element) {
|
|
12
|
+
if (key !== "ArrowRight" && key !== "ArrowLeft") return key;
|
|
13
|
+
if (!isRtl(element)) return key;
|
|
14
|
+
return key === "ArrowRight" ? "ArrowLeft" : "ArrowRight";
|
|
15
|
+
}
|
|
16
|
+
function isReservedArrowChord(event, allow = []) {
|
|
17
|
+
if (!event.key.startsWith("Arrow")) return false;
|
|
18
|
+
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/utils/dates.ts
|
|
22
|
+
function toISODateString(date) {
|
|
23
|
+
const year = date.getFullYear();
|
|
24
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
25
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
26
|
+
return `${year}-${month}-${day}`;
|
|
27
|
+
}
|
|
28
|
+
function parseISODateString(dateStr) {
|
|
29
|
+
if (!dateStr) return null;
|
|
30
|
+
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
31
|
+
if (!match) return null;
|
|
32
|
+
const [, y, m, d] = match;
|
|
33
|
+
const year = Number(y);
|
|
34
|
+
const month = Number(m);
|
|
35
|
+
const day = Number(d);
|
|
36
|
+
const date = new Date(year, month - 1, day);
|
|
37
|
+
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return date;
|
|
41
|
+
}
|
|
42
|
+
function parseISOMonthString(monthStr) {
|
|
43
|
+
if (!monthStr) return null;
|
|
44
|
+
const match = monthStr.match(/^(\d{4})-(\d{2})$/);
|
|
45
|
+
if (!match) return null;
|
|
46
|
+
const [, y, m] = match;
|
|
47
|
+
const month = Number(m);
|
|
48
|
+
if (month < 1 || month > 12) return null;
|
|
49
|
+
return { year: Number(y), month };
|
|
50
|
+
}
|
|
51
|
+
function toISOMonthString(date) {
|
|
52
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/utils/safe_timeout.ts
|
|
56
|
+
var TimerRegistry = class {
|
|
57
|
+
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
58
|
+
ids = /* @__PURE__ */ new Set();
|
|
59
|
+
/**
|
|
60
|
+
* Cancels a single tracked timer.
|
|
61
|
+
*
|
|
62
|
+
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
63
|
+
* registry), so callers can clear defensively without guarding.
|
|
64
|
+
*/
|
|
65
|
+
clear(id) {
|
|
66
|
+
if (this.ids.delete(id)) {
|
|
67
|
+
this.cancel(id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
72
|
+
* to guarantee no timer outlives the element.
|
|
73
|
+
*/
|
|
74
|
+
clearAll() {
|
|
75
|
+
for (const id of this.ids) {
|
|
76
|
+
this.cancel(id);
|
|
77
|
+
}
|
|
78
|
+
this.ids.clear();
|
|
79
|
+
}
|
|
80
|
+
/** Number of timers currently tracked (pending). */
|
|
81
|
+
get size() {
|
|
82
|
+
return this.ids.size;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var SafeTimeout = class extends TimerRegistry {
|
|
86
|
+
/**
|
|
87
|
+
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
88
|
+
*
|
|
89
|
+
* The id is removed from the registry automatically when the timeout fires,
|
|
90
|
+
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
91
|
+
*/
|
|
92
|
+
set(callback, delay) {
|
|
93
|
+
const id = this.schedule(() => {
|
|
94
|
+
this.ids.delete(id);
|
|
95
|
+
callback();
|
|
96
|
+
}, delay);
|
|
97
|
+
this.ids.add(id);
|
|
98
|
+
return id;
|
|
99
|
+
}
|
|
100
|
+
schedule(callback, delay) {
|
|
101
|
+
return window.setTimeout(callback, delay);
|
|
102
|
+
}
|
|
103
|
+
cancel(id) {
|
|
104
|
+
window.clearTimeout(id);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/controllers/date_range_picker_controller.ts
|
|
109
|
+
var GRID_SIZE = 42;
|
|
110
|
+
var DateRangePickerController = class extends Controller {
|
|
111
|
+
static targets = ["grid", "monthLabel", "cell", "status", "startField", "endField"];
|
|
112
|
+
static values = {
|
|
113
|
+
min: { type: String, default: "" },
|
|
114
|
+
max: { type: String, default: "" }
|
|
115
|
+
};
|
|
116
|
+
static actions = ["applyPreset", "next", "onKeydown", "prev", "previewTo", "selectDate"];
|
|
117
|
+
static events = ["change"];
|
|
118
|
+
/** The month currently rendered, as `YYYY-MM`. */
|
|
119
|
+
#viewMonth = "";
|
|
120
|
+
/** The confirmed range endpoints (ISO), or "" when unset. */
|
|
121
|
+
#startDate = "";
|
|
122
|
+
#endDate = "";
|
|
123
|
+
/** The first endpoint of an in-progress selection (ISO), or "" when idle. */
|
|
124
|
+
#pendingStart = "";
|
|
125
|
+
/** The hovered/focused date previewed while selecting (ISO), or "". */
|
|
126
|
+
#previewDate = "";
|
|
127
|
+
/** The roving-focus date in the grid (local time). */
|
|
128
|
+
#focusedDate = /* @__PURE__ */ new Date();
|
|
129
|
+
/** Deferred focus after an async month transition (cancelled on teardown). */
|
|
130
|
+
#focusTimer = new SafeTimeout();
|
|
131
|
+
/** Seeds the range from any pre-filled hidden fields and renders the grid. */
|
|
132
|
+
connect() {
|
|
133
|
+
this.#startDate = this.hasStartFieldTarget ? normalizeISO(this.startFieldTarget.value) : "";
|
|
134
|
+
this.#endDate = this.hasEndFieldTarget ? normalizeISO(this.endFieldTarget.value) : "";
|
|
135
|
+
const anchor = parseISODateString(this.#startDate) ?? this.#clampToBounds(/* @__PURE__ */ new Date()) ?? /* @__PURE__ */ new Date();
|
|
136
|
+
this.#focusedDate = anchor;
|
|
137
|
+
this.#viewMonth = toISOMonthString(anchor);
|
|
138
|
+
if (this.hasGridTarget) this.gridTarget.setAttribute("aria-multiselectable", "true");
|
|
139
|
+
this.#render();
|
|
140
|
+
}
|
|
141
|
+
/** Cancels any pending deferred focus so it never fires on a detached element. */
|
|
142
|
+
disconnect() {
|
|
143
|
+
this.#focusTimer.clearAll();
|
|
144
|
+
}
|
|
145
|
+
/** Navigates to the previous month. */
|
|
146
|
+
prev(event) {
|
|
147
|
+
event?.preventDefault();
|
|
148
|
+
this.#shiftMonth(-1);
|
|
149
|
+
}
|
|
150
|
+
/** Navigates to the next month. */
|
|
151
|
+
next(event) {
|
|
152
|
+
event?.preventDefault();
|
|
153
|
+
this.#shiftMonth(1);
|
|
154
|
+
}
|
|
155
|
+
/** Confirms a range endpoint from a clicked cell. */
|
|
156
|
+
selectDate(event) {
|
|
157
|
+
const cell = this.#cellFrom(event.target);
|
|
158
|
+
if (!cell) return;
|
|
159
|
+
const date = cell.getAttribute("data-date");
|
|
160
|
+
if (!date || cell.getAttribute("aria-disabled") === "true") return;
|
|
161
|
+
this.#choose(date);
|
|
162
|
+
}
|
|
163
|
+
/** Previews the range up to a hovered/focused cell while selecting. */
|
|
164
|
+
previewTo(event) {
|
|
165
|
+
const cell = this.#cellFrom(event.target);
|
|
166
|
+
if (!cell) return;
|
|
167
|
+
const date = cell.getAttribute("data-date");
|
|
168
|
+
if (!date) return;
|
|
169
|
+
if (event.type.startsWith("focus")) {
|
|
170
|
+
const parsed = parseISODateString(date);
|
|
171
|
+
if (parsed) this.#focusedDate = parsed;
|
|
172
|
+
if (!this.#pendingStart && cell.getAttribute("tabindex") !== "0") {
|
|
173
|
+
this.#render();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (this.#pendingStart) {
|
|
177
|
+
this.#previewDate = date;
|
|
178
|
+
this.#render();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Applies a named preset (`today` / `last7` / `last30` / `thisMonth`). */
|
|
182
|
+
applyPreset(event) {
|
|
183
|
+
const button = event.target?.closest("[data-range]");
|
|
184
|
+
const name = button?.getAttribute("data-range");
|
|
185
|
+
if (!name) return;
|
|
186
|
+
const range = computePreset(name);
|
|
187
|
+
if (!range) return;
|
|
188
|
+
const start = this.#clampISO(range.start);
|
|
189
|
+
const end = this.#clampISO(range.end);
|
|
190
|
+
if (!start || !end) return;
|
|
191
|
+
this.#startDate = start;
|
|
192
|
+
this.#endDate = end;
|
|
193
|
+
this.#pendingStart = "";
|
|
194
|
+
this.#previewDate = "";
|
|
195
|
+
const endDate = parseISODateString(end);
|
|
196
|
+
if (endDate) this.#focusedDate = endDate;
|
|
197
|
+
this.#commitFields();
|
|
198
|
+
this.#transitionTo(toISOMonthString(parseISODateString(end) ?? /* @__PURE__ */ new Date()), end);
|
|
199
|
+
this.#announce();
|
|
200
|
+
this.dispatch("change", { detail: { start, end } });
|
|
201
|
+
}
|
|
202
|
+
/** Grid keyboard navigation, selection (Enter/Space), and Escape-to-cancel. */
|
|
203
|
+
onKeydown(event) {
|
|
204
|
+
if (event.defaultPrevented) return;
|
|
205
|
+
if (isReservedArrowChord(event)) return;
|
|
206
|
+
const cell = this.#cellFrom(event.target);
|
|
207
|
+
if (!cell) return;
|
|
208
|
+
const dateStr = cell.getAttribute("data-date");
|
|
209
|
+
const date = dateStr ? parseISODateString(dateStr) : null;
|
|
210
|
+
if (!date) return;
|
|
211
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
212
|
+
event.preventDefault();
|
|
213
|
+
if (dateStr && cell.getAttribute("aria-disabled") !== "true") this.#choose(dateStr);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (event.key === "Escape") {
|
|
217
|
+
if (this.#pendingStart && !event.isComposing) {
|
|
218
|
+
event.preventDefault();
|
|
219
|
+
this.#pendingStart = "";
|
|
220
|
+
this.#previewDate = "";
|
|
221
|
+
this.#render();
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
let next = null;
|
|
226
|
+
switch (logicalArrowKey(event.key, this.element)) {
|
|
227
|
+
case "ArrowLeft":
|
|
228
|
+
next = addDays(date, -1);
|
|
229
|
+
break;
|
|
230
|
+
case "ArrowRight":
|
|
231
|
+
next = addDays(date, 1);
|
|
232
|
+
break;
|
|
233
|
+
case "ArrowUp":
|
|
234
|
+
next = addDays(date, -7);
|
|
235
|
+
break;
|
|
236
|
+
case "ArrowDown":
|
|
237
|
+
next = addDays(date, 7);
|
|
238
|
+
break;
|
|
239
|
+
case "Home":
|
|
240
|
+
next = addDays(date, -date.getDay());
|
|
241
|
+
break;
|
|
242
|
+
case "End":
|
|
243
|
+
next = addDays(date, 6 - date.getDay());
|
|
244
|
+
break;
|
|
245
|
+
case "PageUp":
|
|
246
|
+
next = shiftMonthClamped(date, -1);
|
|
247
|
+
break;
|
|
248
|
+
case "PageDown":
|
|
249
|
+
next = shiftMonthClamped(date, 1);
|
|
250
|
+
break;
|
|
251
|
+
default:
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
this.#moveFocusTo(next);
|
|
256
|
+
}
|
|
257
|
+
/** Records a chosen date as either the pending start or the confirmed end. */
|
|
258
|
+
#choose(date) {
|
|
259
|
+
if (!this.#pendingStart) {
|
|
260
|
+
this.#pendingStart = date;
|
|
261
|
+
this.#previewDate = date;
|
|
262
|
+
const parsed = parseISODateString(date);
|
|
263
|
+
if (parsed) this.#focusedDate = parsed;
|
|
264
|
+
this.#render();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const [start, end] = date < this.#pendingStart ? [date, this.#pendingStart] : [this.#pendingStart, date];
|
|
268
|
+
this.#startDate = start;
|
|
269
|
+
this.#endDate = end;
|
|
270
|
+
this.#pendingStart = "";
|
|
271
|
+
this.#previewDate = "";
|
|
272
|
+
this.#commitFields();
|
|
273
|
+
this.#render();
|
|
274
|
+
this.#announce();
|
|
275
|
+
this.dispatch("change", { detail: { start, end } });
|
|
276
|
+
}
|
|
277
|
+
/** Moves roving focus to `date`, transitioning the month when needed. */
|
|
278
|
+
#moveFocusTo(date) {
|
|
279
|
+
this.#focusedDate = date;
|
|
280
|
+
if (this.#pendingStart) this.#previewDate = toISODateString(date);
|
|
281
|
+
this.#transitionTo(toISOMonthString(date), toISODateString(date));
|
|
282
|
+
}
|
|
283
|
+
/** Renders `month`, then focuses the cell for `dateStr` (deferred if async). */
|
|
284
|
+
#transitionTo(month, dateStr) {
|
|
285
|
+
const isTransition = month !== this.#viewMonth;
|
|
286
|
+
this.#viewMonth = month;
|
|
287
|
+
this.#render();
|
|
288
|
+
const focusCell = () => {
|
|
289
|
+
this.cellTargets.find((c) => c.getAttribute("data-date") === dateStr)?.focus();
|
|
290
|
+
};
|
|
291
|
+
if (isTransition) {
|
|
292
|
+
this.#focusTimer.set(focusCell, 0);
|
|
293
|
+
} else {
|
|
294
|
+
focusCell();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/** Shifts the displayed month by `delta`, keeping focus within it. */
|
|
298
|
+
#shiftMonth(delta) {
|
|
299
|
+
const info = parseISOMonthString(this.#viewMonth);
|
|
300
|
+
if (!info) return;
|
|
301
|
+
const target = new Date(info.year, info.month - 1 + delta, 1);
|
|
302
|
+
this.#viewMonth = toISOMonthString(target);
|
|
303
|
+
const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
|
|
304
|
+
target.setDate(Math.min(this.#focusedDate.getDate(), lastDay));
|
|
305
|
+
this.#focusedDate = target;
|
|
306
|
+
this.#render();
|
|
307
|
+
}
|
|
308
|
+
/** Builds the six-week grid and binds range/roving/disabled state per cell. */
|
|
309
|
+
#render() {
|
|
310
|
+
const info = parseISOMonthString(this.#viewMonth);
|
|
311
|
+
if (!info) return;
|
|
312
|
+
const { year, month } = info;
|
|
313
|
+
if (this.hasMonthLabelTarget) {
|
|
314
|
+
const lang = document.documentElement.lang || "en";
|
|
315
|
+
const formatter = new Intl.DateTimeFormat(lang, { month: "long", year: "numeric" });
|
|
316
|
+
this.monthLabelTarget.textContent = formatter.format(new Date(year, month - 1, 1));
|
|
317
|
+
}
|
|
318
|
+
const [rangeStart, rangeEnd] = this.#visualRange();
|
|
319
|
+
const days = gridDays(year, month);
|
|
320
|
+
const focusedStr = toISODateString(this.#focusedDate);
|
|
321
|
+
const todayStr = toISODateString(/* @__PURE__ */ new Date());
|
|
322
|
+
for (let i = 0; i < GRID_SIZE; i++) {
|
|
323
|
+
const el = this.cellTargets[i];
|
|
324
|
+
const date = days[i];
|
|
325
|
+
if (!el || !date) continue;
|
|
326
|
+
const iso = toISODateString(date);
|
|
327
|
+
el.setAttribute("data-date", iso);
|
|
328
|
+
el.textContent = String(date.getDate());
|
|
329
|
+
el.setAttribute("data-outside", String(date.getMonth() !== month - 1));
|
|
330
|
+
el.setAttribute("data-today", String(iso === todayStr));
|
|
331
|
+
el.setAttribute("tabindex", iso === focusedStr ? "0" : "-1");
|
|
332
|
+
if (this.#outOfBounds(iso)) el.setAttribute("aria-disabled", "true");
|
|
333
|
+
else el.removeAttribute("aria-disabled");
|
|
334
|
+
const isStart = !!rangeStart && iso === rangeStart;
|
|
335
|
+
const isEnd = !!rangeEnd && iso === rangeEnd && rangeEnd !== rangeStart;
|
|
336
|
+
const inside = !!rangeStart && !!rangeEnd && iso > rangeStart && iso < rangeEnd;
|
|
337
|
+
el.toggleAttribute("data-range-start", isStart);
|
|
338
|
+
el.toggleAttribute("data-range-end", isEnd);
|
|
339
|
+
el.toggleAttribute("data-in-range", inside);
|
|
340
|
+
el.setAttribute(
|
|
341
|
+
"aria-selected",
|
|
342
|
+
String(
|
|
343
|
+
!!this.#startDate && iso === this.#startDate || !!this.#endDate && iso === this.#endDate
|
|
344
|
+
)
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/** The ordered [start, end] pair to paint: the preview while selecting, else confirmed. */
|
|
349
|
+
#visualRange() {
|
|
350
|
+
if (this.#pendingStart) {
|
|
351
|
+
const other = this.#previewDate || this.#pendingStart;
|
|
352
|
+
return this.#pendingStart <= other ? [this.#pendingStart, other] : [other, this.#pendingStart];
|
|
353
|
+
}
|
|
354
|
+
return [this.#startDate, this.#endDate];
|
|
355
|
+
}
|
|
356
|
+
/** Writes the confirmed range to the hidden fields. */
|
|
357
|
+
#commitFields() {
|
|
358
|
+
if (this.hasStartFieldTarget) this.startFieldTarget.value = this.#startDate;
|
|
359
|
+
if (this.hasEndFieldTarget) this.endFieldTarget.value = this.#endDate;
|
|
360
|
+
}
|
|
361
|
+
/** Announces the confirmed range in the live status region. */
|
|
362
|
+
#announce() {
|
|
363
|
+
if (this.hasStatusTarget && this.#startDate && this.#endDate) {
|
|
364
|
+
this.statusTarget.textContent = `${this.#startDate} \u2013 ${this.#endDate}`;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/** True when `iso` falls outside the `[min, max]` bounds. */
|
|
368
|
+
#outOfBounds(iso) {
|
|
369
|
+
if (this.minValue && iso < this.minValue) return true;
|
|
370
|
+
if (this.maxValue && iso > this.maxValue) return true;
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
/** Clamps an ISO date string into `[min, max]`, or "" when unparseable. */
|
|
374
|
+
#clampISO(iso) {
|
|
375
|
+
if (!iso) return "";
|
|
376
|
+
if (this.minValue && iso < this.minValue) return this.minValue;
|
|
377
|
+
if (this.maxValue && iso > this.maxValue) return this.maxValue;
|
|
378
|
+
return iso;
|
|
379
|
+
}
|
|
380
|
+
/** Clamps a Date into `[min, max]`, returning null only when unparseable. */
|
|
381
|
+
#clampToBounds(date) {
|
|
382
|
+
const clamped = this.#clampISO(toISODateString(date));
|
|
383
|
+
return parseISODateString(clamped);
|
|
384
|
+
}
|
|
385
|
+
/** Resolves the cell element from an event target, or null. */
|
|
386
|
+
#cellFrom(target) {
|
|
387
|
+
return target?.closest(
|
|
388
|
+
"[data-stimeo--date-range-picker-target='cell']"
|
|
389
|
+
) ?? null;
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
function addDays(date, n) {
|
|
393
|
+
const next = new Date(date);
|
|
394
|
+
next.setDate(next.getDate() + n);
|
|
395
|
+
return next;
|
|
396
|
+
}
|
|
397
|
+
function shiftMonthClamped(date, delta) {
|
|
398
|
+
const target = new Date(date.getFullYear(), date.getMonth() + delta, 1);
|
|
399
|
+
const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
|
|
400
|
+
target.setDate(Math.min(date.getDate(), lastDay));
|
|
401
|
+
return target;
|
|
402
|
+
}
|
|
403
|
+
function gridDays(year, month) {
|
|
404
|
+
const first = new Date(year, month - 1, 1);
|
|
405
|
+
const start = new Date(first);
|
|
406
|
+
start.setDate(first.getDate() - first.getDay());
|
|
407
|
+
const days = [];
|
|
408
|
+
const current = new Date(start);
|
|
409
|
+
for (let i = 0; i < GRID_SIZE; i++) {
|
|
410
|
+
days.push(new Date(current));
|
|
411
|
+
current.setDate(current.getDate() + 1);
|
|
412
|
+
}
|
|
413
|
+
return days;
|
|
414
|
+
}
|
|
415
|
+
function normalizeISO(value) {
|
|
416
|
+
const date = parseISODateString(value.trim());
|
|
417
|
+
return date ? toISODateString(date) : "";
|
|
418
|
+
}
|
|
419
|
+
function computePreset(name) {
|
|
420
|
+
const today = /* @__PURE__ */ new Date();
|
|
421
|
+
const todayStr = toISODateString(today);
|
|
422
|
+
switch (name) {
|
|
423
|
+
case "today":
|
|
424
|
+
return { start: todayStr, end: todayStr };
|
|
425
|
+
case "last7":
|
|
426
|
+
return { start: toISODateString(addDays(today, -6)), end: todayStr };
|
|
427
|
+
case "last30":
|
|
428
|
+
return { start: toISODateString(addDays(today, -29)), end: todayStr };
|
|
429
|
+
case "thisMonth": {
|
|
430
|
+
const start = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
431
|
+
const end = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
|
432
|
+
return { start: toISODateString(start), end: toISODateString(end) };
|
|
433
|
+
}
|
|
434
|
+
default:
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export { DateRangePickerController };
|
|
440
|
+
//# sourceMappingURL=date_range_picker_controller.js.map
|
|
441
|
+
//# sourceMappingURL=date_range_picker_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/dates.ts","../../src/utils/safe_timeout.ts","../../src/controllers/date_range_picker_controller.ts"],"names":[],"mappings":"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","file":"date_range_picker_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Local-time date helpers shared by the calendar-family controllers\n * (`calendar`, `date-range-picker`).\n *\n * All conversions are intentionally **local time** (not UTC): the grid a user\n * sees is built from `Date` components in their own timezone, so round-tripping\n * through ISO strings must use the same frame to avoid off-by-one-day drift near\n * midnight. Strings are bare `YYYY-MM-DD` / `YYYY-MM` with no time or zone.\n */\n\n/** Formats a {@link Date} as a local-time `YYYY-MM-DD` string. */\nexport function toISODateString(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Parses a `YYYY-MM-DD` string into a local-time {@link Date}, or `null`.\n *\n * Rejects calendar-invalid dates (e.g. `2026-02-31`): the constructed `Date`\n * would silently roll over to another day, so the parsed components are checked\n * back against it and a mismatch returns `null`.\n */\nexport function parseISODateString(dateStr: string): Date | null {\n if (!dateStr) return null;\n const match = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (!match) return null;\n const [, y, m, d] = match;\n const year = Number(y);\n const month = Number(m);\n const day = Number(d);\n const date = new Date(year, month - 1, day);\n if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {\n return null;\n }\n return date;\n}\n\n/**\n * Parses a `YYYY-MM` string into its year/month (1-based) parts, or `null`.\n * Rejects months outside `1..12`.\n */\nexport function parseISOMonthString(monthStr: string): { year: number; month: number } | null {\n if (!monthStr) return null;\n const match = monthStr.match(/^(\\d{4})-(\\d{2})$/);\n if (!match) return null;\n const [, y, m] = match;\n const month = Number(m);\n if (month < 1 || month > 12) return null;\n return { year: Number(y), month };\n}\n\n/** Formats a {@link Date} as the local-time `YYYY-MM` string of its month. */\nexport function toISOMonthString(date: Date): string {\n return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, \"0\")}`;\n}\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport {\n parseISODateString,\n parseISOMonthString,\n toISODateString,\n toISOMonthString,\n} from \"../utils/dates\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\n\n/** Number of cells in a six-week month grid (7 × 6). */\nconst GRID_SIZE = 42;\n\n/**\n * Headless, accessible **date range picker** behavior. A derivative of\n * {@link CalendarController}: it reuses the month-grid navigation model and adds\n * two-point range selection, in-progress range preview, and presets.\n *\n * Markup contract (identifier: `stimeo--date-range-picker`):\n * <div data-controller=\"stimeo--date-range-picker\"\n * data-stimeo--date-range-picker-min-value=\"2026-01-01\"\n * data-stimeo--date-range-picker-max-value=\"2026-12-31\">\n * <button data-action=\"stimeo--date-range-picker#prev\">Prev</button>\n * <span data-stimeo--date-range-picker-target=\"monthLabel\" aria-live=\"polite\"></span>\n * <button data-action=\"stimeo--date-range-picker#next\">Next</button>\n * <div role=\"grid\" data-stimeo--date-range-picker-target=\"grid\">\n * <!-- exactly 42 cell targets (7 days × 6 rows) -->\n * <button role=\"gridcell\" tabindex=\"-1\"\n * data-stimeo--date-range-picker-target=\"cell\"\n * data-action=\"click->stimeo--date-range-picker#selectDate\n * mouseenter->stimeo--date-range-picker#previewTo\n * focus->stimeo--date-range-picker#previewTo\n * keydown->stimeo--date-range-picker#onKeydown\"></button>\n * </div>\n * <button data-range=\"last7\" data-action=\"stimeo--date-range-picker#applyPreset\">…</button>\n * <span role=\"status\" aria-live=\"polite\" data-stimeo--date-range-picker-target=\"status\"></span>\n * <input type=\"hidden\" data-stimeo--date-range-picker-target=\"startField\" />\n * <input type=\"hidden\" data-stimeo--date-range-picker-target=\"endField\" />\n * </div>\n *\n * @remarks\n * Behavior only — the consumer styles the grid and renders the range using the\n * `data-range-start` / `data-in-range` / `data-range-end` hooks. Assistive tech\n * is told the two confirmed endpoints via `aria-selected`; inner cells use\n * `data-in-range` (visual only) so the announcement stays to the two ends. The\n * confirmed range is also mirrored to the live `status` region.\n *\n * Selection model: the first click/Enter sets a *pending* start and enters\n * \"selecting\" mode (preview follows the pointer/focus); the second confirms the\n * end (auto-swapped if earlier than the start) and dispatches `change`. Escape\n * abandons an in-progress selection, restoring the last confirmed range.\n */\nexport class DateRangePickerController extends Controller<HTMLElement> {\n static override targets = [\"grid\", \"monthLabel\", \"cell\", \"status\", \"startField\", \"endField\"];\n static override values = {\n min: { type: String, default: \"\" },\n max: { type: String, default: \"\" },\n };\n static actions = [\"applyPreset\", \"next\", \"onKeydown\", \"prev\", \"previewTo\", \"selectDate\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly gridTarget: HTMLElement;\n declare readonly hasGridTarget: boolean;\n declare readonly monthLabelTarget: HTMLElement;\n declare readonly cellTargets: HTMLElement[];\n declare readonly statusTarget: HTMLElement;\n declare readonly startFieldTarget: HTMLInputElement;\n declare readonly endFieldTarget: HTMLInputElement;\n declare readonly hasMonthLabelTarget: boolean;\n declare readonly hasStatusTarget: boolean;\n declare readonly hasStartFieldTarget: boolean;\n declare readonly hasEndFieldTarget: boolean;\n declare minValue: string;\n declare maxValue: string;\n\n /** The month currently rendered, as `YYYY-MM`. */\n #viewMonth = \"\";\n /** The confirmed range endpoints (ISO), or \"\" when unset. */\n #startDate = \"\";\n #endDate = \"\";\n /** The first endpoint of an in-progress selection (ISO), or \"\" when idle. */\n #pendingStart = \"\";\n /** The hovered/focused date previewed while selecting (ISO), or \"\". */\n #previewDate = \"\";\n /** The roving-focus date in the grid (local time). */\n #focusedDate = new Date();\n\n /** Deferred focus after an async month transition (cancelled on teardown). */\n readonly #focusTimer = new SafeTimeout();\n\n /** Seeds the range from any pre-filled hidden fields and renders the grid. */\n override connect(): void {\n this.#startDate = this.hasStartFieldTarget ? normalizeISO(this.startFieldTarget.value) : \"\";\n this.#endDate = this.hasEndFieldTarget ? normalizeISO(this.endFieldTarget.value) : \"\";\n\n const anchor =\n parseISODateString(this.#startDate) ?? this.#clampToBounds(new Date()) ?? new Date();\n this.#focusedDate = anchor;\n this.#viewMonth = toISOMonthString(anchor);\n // Two cells carry `aria-selected=\"true\"` whenever a range is confirmed, so the\n // grid has to say that more than one is selectable — otherwise a single-select\n // grid is claiming two selections.\n if (this.hasGridTarget) this.gridTarget.setAttribute(\"aria-multiselectable\", \"true\");\n this.#render();\n }\n\n /** Cancels any pending deferred focus so it never fires on a detached element. */\n override disconnect(): void {\n this.#focusTimer.clearAll();\n }\n\n /** Navigates to the previous month. */\n prev(event?: Event): void {\n event?.preventDefault();\n this.#shiftMonth(-1);\n }\n\n /** Navigates to the next month. */\n next(event?: Event): void {\n event?.preventDefault();\n this.#shiftMonth(1);\n }\n\n /** Confirms a range endpoint from a clicked cell. */\n selectDate(event: Event): void {\n const cell = this.#cellFrom(event.target);\n if (!cell) return;\n const date = cell.getAttribute(\"data-date\");\n if (!date || cell.getAttribute(\"aria-disabled\") === \"true\") return;\n this.#choose(date);\n }\n\n /** Previews the range up to a hovered/focused cell while selecting. */\n previewTo(event: Event): void {\n const cell = this.#cellFrom(event.target);\n if (!cell) return;\n const date = cell.getAttribute(\"data-date\");\n if (!date) return;\n // Focus moves the roving tabindex; hover does not.\n if (event.type.startsWith(\"focus\")) {\n const parsed = parseISODateString(date);\n if (parsed) this.#focusedDate = parsed;\n // Re-roll the roving tab stop only when focus landed on a cell that is not\n // already the tab stop — e.g. Tab/click/programmatic focus from outside the\n // grid's own keyboard navigation (which already rendered the new stop). This\n // guard avoids a redundant double-render on the controller's own arrow moves\n // (onKeydown → #render focuses the new stop, which then fires this focus).\n if (!this.#pendingStart && cell.getAttribute(\"tabindex\") !== \"0\") {\n this.#render();\n }\n }\n if (this.#pendingStart) {\n this.#previewDate = date;\n this.#render();\n }\n }\n\n /** Applies a named preset (`today` / `last7` / `last30` / `thisMonth`). */\n applyPreset(event: Event): void {\n const button = (event.target as HTMLElement | null)?.closest(\"[data-range]\");\n const name = button?.getAttribute(\"data-range\");\n if (!name) return;\n const range = computePreset(name);\n if (!range) return;\n\n const start = this.#clampISO(range.start);\n const end = this.#clampISO(range.end);\n if (!start || !end) return;\n\n this.#startDate = start;\n this.#endDate = end;\n this.#pendingStart = \"\";\n this.#previewDate = \"\";\n const endDate = parseISODateString(end);\n if (endDate) this.#focusedDate = endDate;\n this.#commitFields();\n this.#transitionTo(toISOMonthString(parseISODateString(end) ?? new Date()), end);\n this.#announce();\n this.dispatch(\"change\", { detail: { start, end } });\n }\n\n /** Grid keyboard navigation, selection (Enter/Space), and Escape-to-cancel. */\n onKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key must not ALSO move the\n // grid focus or choose a date — composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n const cell = this.#cellFrom(event.target);\n if (!cell) return;\n const dateStr = cell.getAttribute(\"data-date\");\n const date = dateStr ? parseISODateString(dateStr) : null;\n if (!date) return;\n\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n if (dateStr && cell.getAttribute(\"aria-disabled\") !== \"true\") this.#choose(dateStr);\n return;\n }\n if (event.key === \"Escape\") {\n // A press an inner handler already owned is yielded at the top of this\n // handler. What is checked here is the IME half: a press during a\n // composition steers the conversion, not the range.\n if (this.#pendingStart && !event.isComposing) {\n event.preventDefault();\n this.#pendingStart = \"\";\n this.#previewDate = \"\";\n this.#render();\n }\n return;\n }\n\n let next: Date | null = null;\n // Logical, not physical. The key is normalised rather than the\n // delta negated: these two branches are not mirror images — their guards\n // differ — so swapping the key keeps each guard with its own direction.\n switch (logicalArrowKey(event.key, this.element)) {\n case \"ArrowLeft\":\n next = addDays(date, -1);\n break;\n case \"ArrowRight\":\n next = addDays(date, 1);\n break;\n case \"ArrowUp\":\n next = addDays(date, -7);\n break;\n case \"ArrowDown\":\n next = addDays(date, 7);\n break;\n case \"Home\":\n next = addDays(date, -date.getDay());\n break;\n case \"End\":\n next = addDays(date, 6 - date.getDay());\n break;\n case \"PageUp\":\n next = shiftMonthClamped(date, -1);\n break;\n case \"PageDown\":\n next = shiftMonthClamped(date, 1);\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#moveFocusTo(next);\n }\n\n /** Records a chosen date as either the pending start or the confirmed end. */\n #choose(date: string): void {\n if (!this.#pendingStart) {\n this.#pendingStart = date;\n this.#previewDate = date;\n const parsed = parseISODateString(date);\n if (parsed) this.#focusedDate = parsed;\n this.#render();\n return;\n }\n // Second click confirms; order the two so start ≤ end.\n const [start, end] =\n date < this.#pendingStart ? [date, this.#pendingStart] : [this.#pendingStart, date];\n this.#startDate = start;\n this.#endDate = end;\n this.#pendingStart = \"\";\n this.#previewDate = \"\";\n this.#commitFields();\n this.#render();\n this.#announce();\n this.dispatch(\"change\", { detail: { start, end } });\n }\n\n /** Moves roving focus to `date`, transitioning the month when needed. */\n #moveFocusTo(date: Date): void {\n this.#focusedDate = date;\n if (this.#pendingStart) this.#previewDate = toISODateString(date);\n this.#transitionTo(toISOMonthString(date), toISODateString(date));\n }\n\n /** Renders `month`, then focuses the cell for `dateStr` (deferred if async). */\n #transitionTo(month: string, dateStr: string): void {\n const isTransition = month !== this.#viewMonth;\n this.#viewMonth = month;\n this.#render();\n const focusCell = (): void => {\n this.cellTargets.find((c) => c.getAttribute(\"data-date\") === dateStr)?.focus();\n };\n if (isTransition) {\n // Let the synchronous render settle before focusing the freshly bound cell.\n this.#focusTimer.set(focusCell, 0);\n } else {\n focusCell();\n }\n }\n\n /** Shifts the displayed month by `delta`, keeping focus within it. */\n #shiftMonth(delta: number): void {\n const info = parseISOMonthString(this.#viewMonth);\n if (!info) return;\n const target = new Date(info.year, info.month - 1 + delta, 1);\n this.#viewMonth = toISOMonthString(target);\n // Keep the same day-of-month where possible, clamped to the new month.\n const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();\n target.setDate(Math.min(this.#focusedDate.getDate(), lastDay));\n this.#focusedDate = target;\n this.#render();\n }\n\n /** Builds the six-week grid and binds range/roving/disabled state per cell. */\n #render(): void {\n const info = parseISOMonthString(this.#viewMonth);\n if (!info) return;\n const { year, month } = info;\n\n if (this.hasMonthLabelTarget) {\n const lang = document.documentElement.lang || \"en\";\n const formatter = new Intl.DateTimeFormat(lang, { month: \"long\", year: \"numeric\" });\n this.monthLabelTarget.textContent = formatter.format(new Date(year, month - 1, 1));\n }\n\n const [rangeStart, rangeEnd] = this.#visualRange();\n const days = gridDays(year, month);\n const focusedStr = toISODateString(this.#focusedDate);\n const todayStr = toISODateString(new Date());\n\n for (let i = 0; i < GRID_SIZE; i++) {\n const el = this.cellTargets[i];\n const date = days[i];\n if (!el || !date) continue;\n const iso = toISODateString(date);\n\n el.setAttribute(\"data-date\", iso);\n el.textContent = String(date.getDate());\n el.setAttribute(\"data-outside\", String(date.getMonth() !== month - 1));\n el.setAttribute(\"data-today\", String(iso === todayStr));\n el.setAttribute(\"tabindex\", iso === focusedStr ? \"0\" : \"-1\");\n\n if (this.#outOfBounds(iso)) el.setAttribute(\"aria-disabled\", \"true\");\n else el.removeAttribute(\"aria-disabled\");\n\n const isStart = !!rangeStart && iso === rangeStart;\n const isEnd = !!rangeEnd && iso === rangeEnd && rangeEnd !== rangeStart;\n const inside = !!rangeStart && !!rangeEnd && iso > rangeStart && iso < rangeEnd;\n el.toggleAttribute(\"data-range-start\", isStart);\n el.toggleAttribute(\"data-range-end\", isEnd);\n el.toggleAttribute(\"data-in-range\", inside);\n // Selection is what the user committed, never what the pointer is hovering:\n // `aria-selected` marks the two confirmed endpoints only. The preview lives\n // in the `data-*` trio above, which styling reads and AT does not.\n el.setAttribute(\n \"aria-selected\",\n String(\n (!!this.#startDate && iso === this.#startDate) ||\n (!!this.#endDate && iso === this.#endDate),\n ),\n );\n }\n }\n\n /** The ordered [start, end] pair to paint: the preview while selecting, else confirmed. */\n #visualRange(): [string, string] {\n if (this.#pendingStart) {\n const other = this.#previewDate || this.#pendingStart;\n return this.#pendingStart <= other\n ? [this.#pendingStart, other]\n : [other, this.#pendingStart];\n }\n return [this.#startDate, this.#endDate];\n }\n\n /** Writes the confirmed range to the hidden fields. */\n #commitFields(): void {\n if (this.hasStartFieldTarget) this.startFieldTarget.value = this.#startDate;\n if (this.hasEndFieldTarget) this.endFieldTarget.value = this.#endDate;\n }\n\n /** Announces the confirmed range in the live status region. */\n #announce(): void {\n if (this.hasStatusTarget && this.#startDate && this.#endDate) {\n this.statusTarget.textContent = `${this.#startDate} – ${this.#endDate}`;\n }\n }\n\n /** True when `iso` falls outside the `[min, max]` bounds. */\n #outOfBounds(iso: string): boolean {\n if (this.minValue && iso < this.minValue) return true;\n if (this.maxValue && iso > this.maxValue) return true;\n return false;\n }\n\n /** Clamps an ISO date string into `[min, max]`, or \"\" when unparseable. */\n #clampISO(iso: string): string {\n if (!iso) return \"\";\n if (this.minValue && iso < this.minValue) return this.minValue;\n if (this.maxValue && iso > this.maxValue) return this.maxValue;\n return iso;\n }\n\n /** Clamps a Date into `[min, max]`, returning null only when unparseable. */\n #clampToBounds(date: Date): Date | null {\n const clamped = this.#clampISO(toISODateString(date));\n return parseISODateString(clamped);\n }\n\n /** Resolves the cell element from an event target, or null. */\n #cellFrom(target: EventTarget | null): HTMLElement | null {\n return (\n (target as HTMLElement | null)?.closest<HTMLElement>(\n \"[data-stimeo--date-range-picker-target='cell']\",\n ) ?? null\n );\n }\n}\n\n/** Returns a new Date `n` days from `date`. */\nfunction addDays(date: Date, n: number): Date {\n const next = new Date(date);\n next.setDate(next.getDate() + n);\n return next;\n}\n\n/** Shifts `date` by `delta` months, clamping the day to the target month length. */\nfunction shiftMonthClamped(date: Date, delta: number): Date {\n const target = new Date(date.getFullYear(), date.getMonth() + delta, 1);\n const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();\n target.setDate(Math.min(date.getDate(), lastDay));\n return target;\n}\n\n/** Builds the 42 local-time dates of a Sunday-started six-week grid for a month. */\nfunction gridDays(year: number, month: number): Date[] {\n const first = new Date(year, month - 1, 1);\n const start = new Date(first);\n start.setDate(first.getDate() - first.getDay());\n const days: Date[] = [];\n const current = new Date(start);\n for (let i = 0; i < GRID_SIZE; i++) {\n days.push(new Date(current));\n current.setDate(current.getDate() + 1);\n }\n return days;\n}\n\n/** Validates and re-serializes an ISO date string, returning \"\" when invalid. */\nfunction normalizeISO(value: string): string {\n const date = parseISODateString(value.trim());\n return date ? toISODateString(date) : \"\";\n}\n\n/** Computes a preset range relative to today, or null for an unknown name. */\nfunction computePreset(name: string): { start: string; end: string } | null {\n const today = new Date();\n const todayStr = toISODateString(today);\n switch (name) {\n case \"today\":\n return { start: todayStr, end: todayStr };\n case \"last7\":\n return { start: toISODateString(addDays(today, -6)), end: todayStr };\n case \"last30\":\n return { start: toISODateString(addDays(today, -29)), end: todayStr };\n case \"thisMonth\": {\n const start = new Date(today.getFullYear(), today.getMonth(), 1);\n const end = new Date(today.getFullYear(), today.getMonth() + 1, 0);\n return { start: toISODateString(start), end: toISODateString(end) };\n }\n default:\n return null;\n }\n}\n"]}
|