stimeo-ui 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +184 -0
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.d.ts +3 -5
- package/dist/controllers/aspect_ratio_controller.js +19 -11
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.d.ts +36 -15
- package/dist/controllers/avatar_controller.js +195 -40
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +5 -1
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +12 -3
- package/dist/controllers/carousel_controller.js +90 -10
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +31 -5
- package/dist/controllers/checkbox_controller.js +136 -25
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.js +8 -3
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +1 -1
- package/dist/controllers/collapsible_controller.js +4 -1
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +9 -3
- package/dist/controllers/color_picker_controller.js +41 -11
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +1 -1
- package/dist/controllers/context_menu_controller.js +2 -2
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +5 -3
- package/dist/controllers/countdown_controller.js +5 -1
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +23 -4
- package/dist/controllers/date_range_picker_controller.js +162 -31
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +1 -1
- package/dist/controllers/direct_upload_controller.js +3 -3
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.d.ts +27 -7
- package/dist/controllers/empty_state_controller.js +107 -16
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +7 -1
- package/dist/controllers/file_dropzone_controller.js +26 -3
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +25 -5
- package/dist/controllers/flash_controller.js +161 -21
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +2 -3
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +8 -2
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +19 -2
- package/dist/controllers/frame_loading_controller.js +94 -22
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +8 -4
- package/dist/controllers/highlight_controller.js +38 -1
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +2 -2
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +5 -4
- package/dist/controllers/idle_controller.js +39 -6
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.js +2 -0
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +1 -1
- package/dist/controllers/masonry_controller.js +1 -1
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +5 -3
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.js +3 -1
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +46 -12
- package/dist/controllers/multi_select_controller.js +460 -151
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +15 -13
- package/dist/controllers/network_status_controller.js +1 -3
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +29 -3
- package/dist/controllers/number_input_controller.js +455 -64
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.js +5 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.js +38 -1
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +4 -4
- package/dist/controllers/password_strength_controller.js +21 -3
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +4 -4
- package/dist/controllers/persist_controller.js +24 -5
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.js +10 -0
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +2 -2
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +1 -2
- package/dist/controllers/portal_controller.js +10 -0
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +1 -1
- package/dist/controllers/progress_controller.js +7 -3
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +41 -15
- package/dist/controllers/radio_group_controller.js +540 -56
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +23 -0
- package/dist/controllers/range_slider_controller.js +385 -94
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +36 -31
- package/dist/controllers/rating_controller.js +274 -89
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.js +2 -0
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.js +33 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +4 -0
- package/dist/controllers/roving_controller.js +60 -5
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -1
- package/dist/controllers/scroll_area_controller.js +155 -23
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.js +33 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/separator_controller.js +13 -17
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +2 -2
- package/dist/controllers/skeleton_controller.js +71 -3
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +17 -1
- package/dist/controllers/slider_controller.js +325 -48
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +15 -10
- package/dist/controllers/spinner_controller.js +18 -3
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +1 -1
- package/dist/controllers/step_indicator_controller.js +3 -1
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.js +2 -0
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +12 -8
- package/dist/controllers/switch_controller.js +162 -18
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +41 -11
- package/dist/controllers/tags_input_controller.js +356 -120
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.js +1 -1
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +42 -10
- package/dist/controllers/time_picker_controller.js +296 -104
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +39 -13
- package/dist/controllers/toggle_group_controller.js +378 -55
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +5 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +2 -2
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +2 -3
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +1 -2
- package/dist/controllers/tree_view_controller.js +24 -4
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +3811 -1048
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +92 -4
- package/dist/inspector/cli.js +174 -6
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +234 -8
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +24 -24
- package/dist/inspector/manifest.json +573 -59
- package/dist/positioning/index.d.ts +2 -2
- package/dist/positioning/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -13,6 +13,72 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
13
13
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// src/utils/attribute_lease.ts
|
|
17
|
+
var AttributeLease = class {
|
|
18
|
+
#attribute;
|
|
19
|
+
#records = /* @__PURE__ */ new Map();
|
|
20
|
+
/** @param attribute - The attribute whose temporary values this lease owns. */
|
|
21
|
+
constructor(attribute) {
|
|
22
|
+
this.#attribute = attribute;
|
|
23
|
+
}
|
|
24
|
+
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
25
|
+
write(element, value) {
|
|
26
|
+
const existing = this.#records.get(element);
|
|
27
|
+
if (existing) {
|
|
28
|
+
existing.written = value;
|
|
29
|
+
} else {
|
|
30
|
+
this.#records.set(element, {
|
|
31
|
+
original: element.getAttribute(this.#attribute),
|
|
32
|
+
written: value
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (value === null) element.removeAttribute(this.#attribute);
|
|
36
|
+
else element.setAttribute(this.#attribute, value);
|
|
37
|
+
}
|
|
38
|
+
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
39
|
+
return(element) {
|
|
40
|
+
const record = this.#records.get(element);
|
|
41
|
+
if (!record) return;
|
|
42
|
+
this.#records.delete(element);
|
|
43
|
+
if (element.getAttribute(this.#attribute) !== record.written) return;
|
|
44
|
+
if (record.original === null) element.removeAttribute(this.#attribute);
|
|
45
|
+
else element.setAttribute(this.#attribute, record.original);
|
|
46
|
+
}
|
|
47
|
+
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
48
|
+
returnAll() {
|
|
49
|
+
for (const element of Array.from(this.#records.keys())) this.return(element);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/utils/before_cache_reset.ts
|
|
54
|
+
var BeforeCacheReset = class _BeforeCacheReset {
|
|
55
|
+
/** Every subscribed instance, iterated by the one shared document listener. */
|
|
56
|
+
static #subscribers = /* @__PURE__ */ new Set();
|
|
57
|
+
/** The shared listener; installed while at least one instance is subscribed. */
|
|
58
|
+
static #onBeforeCache = () => {
|
|
59
|
+
for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
|
|
60
|
+
};
|
|
61
|
+
#rewind;
|
|
62
|
+
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
63
|
+
constructor(rewind) {
|
|
64
|
+
this.#rewind = rewind;
|
|
65
|
+
}
|
|
66
|
+
/** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
|
|
67
|
+
activate() {
|
|
68
|
+
const first = _BeforeCacheReset.#subscribers.size === 0;
|
|
69
|
+
_BeforeCacheReset.#subscribers.add(this);
|
|
70
|
+
if (first) {
|
|
71
|
+
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
|
|
75
|
+
deactivate() {
|
|
76
|
+
_BeforeCacheReset.#subscribers.delete(this);
|
|
77
|
+
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
78
|
+
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
16
82
|
// src/utils/microtask_coalescer.ts
|
|
17
83
|
var MicrotaskCoalescer = class {
|
|
18
84
|
#run;
|
|
@@ -68,10 +134,12 @@ var RovingTabindex = class {
|
|
|
68
134
|
* "nothing is currently tabbable".
|
|
69
135
|
*
|
|
70
136
|
* @param index - Position of the item to make tabbable.
|
|
71
|
-
* @param options - Pass `{ focus: true }` to also move DOM focus to that item
|
|
137
|
+
* @param options - Pass `{ focus: true }` to also move DOM focus to that item,
|
|
138
|
+
* and `items` to reuse an event-scoped collection snapshot.
|
|
72
139
|
*/
|
|
73
|
-
setActive(index,
|
|
74
|
-
const
|
|
140
|
+
setActive(index, options = {}) {
|
|
141
|
+
const { focus = false } = options;
|
|
142
|
+
const items = options.items ?? this.#getItems();
|
|
75
143
|
items.forEach((item, i) => {
|
|
76
144
|
item.tabIndex = i === index ? 0 : -1;
|
|
77
145
|
});
|
|
@@ -79,165 +147,282 @@ var RovingTabindex = class {
|
|
|
79
147
|
}
|
|
80
148
|
};
|
|
81
149
|
|
|
150
|
+
// src/utils/tabindex_loan.ts
|
|
151
|
+
var TabindexLoan = class {
|
|
152
|
+
#value;
|
|
153
|
+
#lent = /* @__PURE__ */ new Set();
|
|
154
|
+
/** Returns live loans before Turbo can copy them into its page snapshot. */
|
|
155
|
+
#beforeCache = new BeforeCacheReset(() => this.returnAll());
|
|
156
|
+
/**
|
|
157
|
+
* @param value - the `tabindex` to lend. `"-1"` (the default) is
|
|
158
|
+
* programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
|
|
159
|
+
* which a scroll region with no focusable content of its own needs.
|
|
160
|
+
*/
|
|
161
|
+
constructor(value = "-1") {
|
|
162
|
+
this.#value = value;
|
|
163
|
+
}
|
|
164
|
+
/** Lends `element` the value; no-ops when it already carries a `tabindex`. */
|
|
165
|
+
lend(element) {
|
|
166
|
+
if (element.hasAttribute("tabindex")) return;
|
|
167
|
+
element.setAttribute("tabindex", this.#value);
|
|
168
|
+
this.#lent.add(element);
|
|
169
|
+
this.#beforeCache.activate();
|
|
170
|
+
}
|
|
171
|
+
/** Takes back every loan whose value is still the one that was lent. */
|
|
172
|
+
returnAll() {
|
|
173
|
+
for (const element of this.#lent) {
|
|
174
|
+
if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
|
|
175
|
+
}
|
|
176
|
+
this.#lent.clear();
|
|
177
|
+
this.#beforeCache.deactivate();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
82
181
|
// src/controllers/rating_controller.ts
|
|
83
182
|
var RatingController = class extends Controller {
|
|
84
183
|
static targets = ["symbol", "field"];
|
|
85
184
|
static values = {
|
|
86
185
|
value: { type: Number, default: 0 },
|
|
87
|
-
max: { type: Number, default: 5 },
|
|
88
186
|
clearable: { type: Boolean, default: true },
|
|
89
187
|
readonly: { type: Boolean, default: false }
|
|
90
188
|
};
|
|
91
189
|
static actions = ["endPreview", "onKeydown", "preview", "select"];
|
|
92
|
-
static events = ["change"];
|
|
190
|
+
static events = ["change", "reconcile"];
|
|
93
191
|
#roving = new RovingTabindex(() => this.symbolTargets);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
this.#apply(this.#clamp(this.valueValue), { focus: false });
|
|
105
|
-
});
|
|
192
|
+
#rootRole = new AttributeLease("role");
|
|
193
|
+
#symbolRole = new AttributeLease("role");
|
|
194
|
+
#symbolAriaHidden = new AttributeLease("aria-hidden");
|
|
195
|
+
#rootTabindex = new TabindexLoan();
|
|
196
|
+
#repaint = new MicrotaskCoalescer(() => this.#reconcileScale());
|
|
197
|
+
#beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
|
|
198
|
+
#connected = false;
|
|
199
|
+
#rescuedFocus = false;
|
|
200
|
+
/** Reflects declarative state without announcing an initial user change. */
|
|
106
201
|
connect() {
|
|
107
202
|
this.#repaint.activate();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
this.#apply(this.#clamp(this.valueValue), { focus: false });
|
|
203
|
+
this.#beforeCache.activate();
|
|
204
|
+
this.#apply(this.#normalize(this.valueValue), { focus: false });
|
|
205
|
+
this.#connected = true;
|
|
113
206
|
}
|
|
114
|
-
/**
|
|
207
|
+
/** Drops a queued reconciliation and hands every borrowed attribute back. */
|
|
115
208
|
disconnect() {
|
|
209
|
+
this.#connected = false;
|
|
116
210
|
this.#repaint.cancel();
|
|
211
|
+
this.#beforeCache.deactivate();
|
|
212
|
+
this.#releaseReadonly();
|
|
213
|
+
}
|
|
214
|
+
/** Removes a runtime-added symbol's authored Tab stop before the batch repaint. */
|
|
215
|
+
symbolTargetConnected(symbol) {
|
|
216
|
+
if (this.#connected === false) return;
|
|
217
|
+
symbol.tabIndex = -1;
|
|
218
|
+
this.#repaint.schedule();
|
|
219
|
+
}
|
|
220
|
+
/** Releases readonly ownership and reconciles the remaining DOM-ordered scale. */
|
|
221
|
+
symbolTargetDisconnected(symbol) {
|
|
222
|
+
this.#symbolRole.return(symbol);
|
|
223
|
+
this.#symbolAriaHidden.return(symbol);
|
|
224
|
+
this.#repaint.schedule();
|
|
225
|
+
}
|
|
226
|
+
/** Reflects into a hidden field added or replaced after connection. */
|
|
227
|
+
fieldTargetConnected() {
|
|
228
|
+
this.#repaint.schedule();
|
|
117
229
|
}
|
|
118
|
-
/**
|
|
230
|
+
/** Reconciles after a hidden field is removed or replaced. */
|
|
231
|
+
fieldTargetDisconnected() {
|
|
232
|
+
this.#repaint.schedule();
|
|
233
|
+
}
|
|
234
|
+
/** Repaints when application code or a Turbo morph changes `value`. */
|
|
119
235
|
valueValueChanged() {
|
|
120
236
|
this.#repaint.schedule();
|
|
121
237
|
}
|
|
122
|
-
/**
|
|
238
|
+
/** Repaints when application code changes whether value 0 is permitted. */
|
|
239
|
+
clearableValueChanged() {
|
|
240
|
+
this.#repaint.schedule();
|
|
241
|
+
}
|
|
242
|
+
/** Repaints when application code enters or leaves the readonly snapshot. */
|
|
243
|
+
readonlyValueChanged() {
|
|
244
|
+
this.#repaint.schedule();
|
|
245
|
+
}
|
|
246
|
+
/** Selects or clears the clicked symbol. Bound via `data-action` (click). */
|
|
123
247
|
select(event) {
|
|
124
248
|
if (this.readonlyValue) return;
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
249
|
+
const ordinal = this.#symbolOrdinal(event.currentTarget);
|
|
250
|
+
if (ordinal === null) return;
|
|
251
|
+
const current = this.#normalize(this.valueValue);
|
|
252
|
+
this.#commit(this.clearableValue && ordinal === current ? 0 : ordinal, {
|
|
253
|
+
focus: ordinal === current
|
|
254
|
+
});
|
|
131
255
|
}
|
|
132
|
-
/** Previews a fill range on hover
|
|
256
|
+
/** Previews a fill range on hover or focus without committing it. */
|
|
133
257
|
preview(event) {
|
|
134
258
|
if (this.readonlyValue) return;
|
|
135
|
-
this.#
|
|
259
|
+
const ordinal = this.#symbolOrdinal(event.currentTarget);
|
|
260
|
+
if (ordinal !== null) this.#setFillRange(ordinal);
|
|
136
261
|
}
|
|
137
|
-
/** Restores the fill range
|
|
262
|
+
/** Restores the fill range after hover or focus leaves a symbol. */
|
|
138
263
|
endPreview() {
|
|
139
264
|
if (this.readonlyValue) return;
|
|
140
|
-
this.#setFillRange(this.valueValue);
|
|
265
|
+
this.#setFillRange(this.#normalize(this.valueValue));
|
|
141
266
|
}
|
|
142
|
-
/** Arrow/Home/End/Space keyboard control, clamped
|
|
267
|
+
/** Arrow/Home/End/Space/Delete keyboard control, clamped without wrapping. */
|
|
143
268
|
onKeydown(event) {
|
|
144
|
-
if (event.defaultPrevented) return;
|
|
145
|
-
|
|
146
|
-
if (this.readonlyValue) return;
|
|
269
|
+
if (event.defaultPrevented || isReservedArrowChord(event) || this.readonlyValue) return;
|
|
270
|
+
const current = this.#normalize(this.valueValue);
|
|
147
271
|
let next = null;
|
|
148
272
|
const rtl = isRtl(this.element);
|
|
149
273
|
switch (event.key) {
|
|
150
274
|
case "ArrowRight":
|
|
151
275
|
case "ArrowUp":
|
|
152
|
-
next =
|
|
276
|
+
next = current + (event.key === "ArrowRight" && rtl ? -1 : 1);
|
|
153
277
|
break;
|
|
154
278
|
case "ArrowLeft":
|
|
155
279
|
case "ArrowDown":
|
|
156
|
-
next =
|
|
280
|
+
next = current - (event.key === "ArrowLeft" && rtl ? -1 : 1);
|
|
157
281
|
break;
|
|
158
282
|
case "Home":
|
|
159
283
|
next = this.#minValue;
|
|
160
284
|
break;
|
|
161
285
|
case "End":
|
|
162
|
-
next = this.
|
|
286
|
+
next = this.symbolTargets.length;
|
|
163
287
|
break;
|
|
164
288
|
case " ":
|
|
165
289
|
case "Enter":
|
|
166
|
-
next = this.#
|
|
290
|
+
next = this.#symbolOrdinal(event.currentTarget);
|
|
291
|
+
break;
|
|
292
|
+
case "Delete":
|
|
293
|
+
case "Backspace":
|
|
294
|
+
if (!this.clearableValue) return;
|
|
295
|
+
next = 0;
|
|
167
296
|
break;
|
|
168
297
|
default:
|
|
169
298
|
return;
|
|
170
299
|
}
|
|
300
|
+
if (next === null) return;
|
|
171
301
|
event.preventDefault();
|
|
172
|
-
this.#
|
|
302
|
+
this.#commit(next, { focus: true });
|
|
173
303
|
}
|
|
174
304
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
305
|
+
* Repaints one settled target/Value mutation batch and reports only a value
|
|
306
|
+
* this controller had to normalize.
|
|
307
|
+
*
|
|
308
|
+
* @stimeoRenderRoot
|
|
178
309
|
*/
|
|
179
|
-
#
|
|
310
|
+
#reconcileScale() {
|
|
311
|
+
const requested = this.valueValue;
|
|
312
|
+
const value = this.#normalize(requested);
|
|
313
|
+
this.#apply(value, { focus: false });
|
|
314
|
+
if (!Object.is(value, requested)) {
|
|
315
|
+
this.dispatch("reconcile", { detail: { value } });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** Applies one user operation and emits only when its committed value changes. */
|
|
319
|
+
#commit(raw, { focus }) {
|
|
320
|
+
const previous = this.#normalize(this.valueValue);
|
|
321
|
+
const value = this.#normalize(raw);
|
|
180
322
|
this.#apply(value, { focus });
|
|
181
|
-
this.dispatch("change", { detail: { value } });
|
|
323
|
+
if (value !== previous) this.dispatch("change", { detail: { value } });
|
|
182
324
|
}
|
|
183
|
-
/**
|
|
184
|
-
* Stores `value`, syncs `aria-checked` and the roving Tab stop, the hidden
|
|
185
|
-
* field, and the fill range — without dispatching `change`. Idempotent and
|
|
186
|
-
* safe on connect (and across Turbo morphing).
|
|
187
|
-
*/
|
|
325
|
+
/** Synchronizes value, ARIA, roving focus, form state, and the visual fill hook. */
|
|
188
326
|
#apply(value, { focus }) {
|
|
189
|
-
this.valueValue = value;
|
|
190
|
-
this.symbolTargets.forEach((symbol) => {
|
|
191
|
-
symbol.setAttribute(
|
|
192
|
-
"aria-checked",
|
|
193
|
-
value > 0 && this.#symbolValue(symbol) === value ? "true" : "false"
|
|
194
|
-
);
|
|
327
|
+
if (!Object.is(this.valueValue, value)) this.valueValue = value;
|
|
328
|
+
this.symbolTargets.forEach((symbol, index) => {
|
|
329
|
+
symbol.setAttribute("aria-checked", value > 0 && index + 1 === value ? "true" : "false");
|
|
195
330
|
});
|
|
196
|
-
this
|
|
331
|
+
if (this.readonlyValue) {
|
|
332
|
+
this.#applyReadonly();
|
|
333
|
+
} else {
|
|
334
|
+
const returning = this.#releaseReadonly();
|
|
335
|
+
this.#roving.setActive(value > 0 ? value - 1 : 0, { focus: focus || returning });
|
|
336
|
+
}
|
|
197
337
|
if (this.hasFieldTarget) this.fieldTarget.value = String(value);
|
|
198
338
|
this.#setFillRange(value);
|
|
199
339
|
}
|
|
200
|
-
/** Marks
|
|
340
|
+
/** Marks the first `range` symbols with the consumer-owned fill hook. */
|
|
201
341
|
#setFillRange(range) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
} else {
|
|
206
|
-
symbol.removeAttribute("data-rating-hover");
|
|
207
|
-
}
|
|
208
|
-
}
|
|
342
|
+
this.symbolTargets.forEach((symbol, index) => {
|
|
343
|
+
symbol.toggleAttribute("data-rating-hover", range > 0 && index < range);
|
|
344
|
+
});
|
|
209
345
|
}
|
|
210
|
-
/**
|
|
346
|
+
/**
|
|
347
|
+
* Temporarily turns the radiogroup into a non-interactive image snapshot.
|
|
348
|
+
*
|
|
349
|
+
* Each lease is returned before it is taken again, so a value the consumer
|
|
350
|
+
* wrote while readonly becomes the value the lease restores on release. The
|
|
351
|
+
* return is a no-op on an attribute still holding this controller's own
|
|
352
|
+
* write, which is the ordinary case.
|
|
353
|
+
*/
|
|
211
354
|
#applyReadonly() {
|
|
212
|
-
|
|
213
|
-
this.
|
|
214
|
-
this.element
|
|
355
|
+
this.#rescueFocus();
|
|
356
|
+
this.#rootRole.return(this.element);
|
|
357
|
+
this.#rootRole.write(this.element, "img");
|
|
215
358
|
for (const symbol of this.symbolTargets) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
symbol
|
|
359
|
+
this.#symbolRole.return(symbol);
|
|
360
|
+
this.#symbolRole.write(symbol, null);
|
|
361
|
+
this.#symbolAriaHidden.return(symbol);
|
|
362
|
+
this.#symbolAriaHidden.write(symbol, "true");
|
|
219
363
|
}
|
|
220
|
-
|
|
221
|
-
this.#setFillRange(value);
|
|
364
|
+
this.#roving.setActive(-1);
|
|
222
365
|
}
|
|
223
366
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
367
|
+
* Hands every readonly borrowing back before Turbo clones the page.
|
|
368
|
+
*
|
|
369
|
+
* The snapshot is taken while the controller is still connected, so an
|
|
370
|
+
* element left as `role="img"` with hidden symbols is what a restored page
|
|
371
|
+
* connects against — and that markup would be read as the authored one,
|
|
372
|
+
* leaving no way back to the radiogroup. Rewinding first keeps the cached
|
|
373
|
+
* copy identical to what the consumer wrote.
|
|
228
374
|
*/
|
|
229
|
-
#
|
|
230
|
-
|
|
231
|
-
|
|
375
|
+
#rewindForCache() {
|
|
376
|
+
this.#releaseReadonly();
|
|
377
|
+
const value = this.#normalize(this.valueValue);
|
|
378
|
+
this.#roving.setActive(value > 0 ? value - 1 : 0);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Lands focus on the root before the symbols leave the accessibility tree.
|
|
382
|
+
*
|
|
383
|
+
* A symbol holding focus when readonly begins would keep it while losing its
|
|
384
|
+
* role and gaining `aria-hidden`, stranding the user on a node no longer in
|
|
385
|
+
* the tree. The root is the one element that survives the transition named:
|
|
386
|
+
* it carries the consumer's accessible name under `role="img"`.
|
|
387
|
+
*/
|
|
388
|
+
#rescueFocus() {
|
|
389
|
+
const active = document.activeElement;
|
|
390
|
+
if (!(active instanceof HTMLElement) || active === this.element) return;
|
|
391
|
+
if (!this.element.contains(active)) return;
|
|
392
|
+
this.#rootTabindex.lend(this.element);
|
|
393
|
+
this.element.focus();
|
|
394
|
+
this.#rescuedFocus = true;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Restores authored roles and visibility after leaving readonly mode.
|
|
398
|
+
*
|
|
399
|
+
* @returns whether focus is standing on the root because {@link #rescueFocus}
|
|
400
|
+
* put it there, and therefore belongs back on the Tab stop.
|
|
401
|
+
*/
|
|
402
|
+
#releaseReadonly() {
|
|
403
|
+
const returning = this.#rescuedFocus && document.activeElement === this.element;
|
|
404
|
+
this.#rescuedFocus = false;
|
|
405
|
+
this.#rootRole.return(this.element);
|
|
406
|
+
this.#symbolRole.returnAll();
|
|
407
|
+
this.#symbolAriaHidden.returnAll();
|
|
408
|
+
this.#rootTabindex.returnAll();
|
|
409
|
+
return returning;
|
|
410
|
+
}
|
|
411
|
+
/** Normalizes a raw value to an integer ordinal in the live DOM range. */
|
|
412
|
+
#normalize(raw) {
|
|
413
|
+
const maximum = this.symbolTargets.length;
|
|
414
|
+
const ordinal = Number.isFinite(raw) ? Math.round(raw) : this.#minValue;
|
|
415
|
+
return Math.min(maximum, Math.max(this.#minValue, ordinal));
|
|
232
416
|
}
|
|
233
417
|
/** Lowest selectable value: 0 when clearable, otherwise 1. */
|
|
234
418
|
get #minValue() {
|
|
235
419
|
return this.clearableValue ? 0 : 1;
|
|
236
420
|
}
|
|
237
|
-
/**
|
|
238
|
-
#
|
|
239
|
-
const
|
|
240
|
-
|
|
421
|
+
/** Returns a target's 1-based position, or null when the action host is invalid. */
|
|
422
|
+
#symbolOrdinal(target) {
|
|
423
|
+
const targets = this.symbolTargets;
|
|
424
|
+
const index = targets.indexOf(target);
|
|
425
|
+
return index < 0 ? null : index + 1;
|
|
241
426
|
}
|
|
242
427
|
};
|
|
243
428
|
|