stimeo-ui 0.16.0 → 0.17.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +8 -1
- data/dist/cable/index.js +363 -436
- data/dist/controllers/accordion_controller.js +166 -55
- data/dist/controllers/alert_dialog_controller.js +862 -221
- data/dist/controllers/announcer_controller.js +135 -198
- data/dist/controllers/aspect_ratio_controller.js +0 -10
- data/dist/controllers/auto_submit_controller.js +271 -127
- data/dist/controllers/avatar_controller.js +171 -105
- data/dist/controllers/breadcrumb_controller.js +0 -130
- data/dist/controllers/bulk_select_controller.js +262 -52
- data/dist/controllers/calendar_controller.js +175 -209
- data/dist/controllers/carousel_controller.js +461 -399
- data/dist/controllers/character_counter_controller.js +246 -156
- data/dist/controllers/checkbox_controller.js +32 -74
- data/dist/controllers/clipboard_controller.js +168 -147
- data/dist/controllers/collapsible_controller.js +301 -120
- data/dist/controllers/color_picker_controller.js +149 -139
- data/dist/controllers/combobox_controller.js +360 -133
- data/dist/controllers/command_palette_controller.js +970 -358
- data/dist/controllers/conditional_fields_controller.js +166 -124
- data/dist/controllers/confirm_controller.js +939 -248
- data/dist/controllers/context_menu_controller.js +256 -128
- data/dist/controllers/count_up_controller.js +73 -21
- data/dist/controllers/countdown_controller.js +183 -148
- data/dist/controllers/currency_input_controller.js +134 -158
- data/dist/controllers/data_grid_controller.js +114 -186
- data/dist/controllers/date_range_picker_controller.js +288 -206
- data/dist/controllers/dialog_controller.js +858 -223
- data/dist/controllers/direct_upload_controller.js +73 -153
- data/dist/controllers/dirty_form_controller.js +14 -48
- data/dist/controllers/dismissible_controller.js +175 -21
- data/dist/controllers/drawer_controller.js +933 -341
- data/dist/controllers/dropdown_controller.js +312 -82
- data/dist/controllers/editable_controller.js +158 -68
- data/dist/controllers/empty_state_controller.js +67 -53
- data/dist/controllers/file_dropzone_controller.js +311 -207
- data/dist/controllers/filter_controller.js +67 -39
- data/dist/controllers/flash_controller.js +172 -314
- data/dist/controllers/focus_controller.js +654 -240
- data/dist/controllers/form_field_controller.js +147 -132
- data/dist/controllers/form_validation_controller.js +13 -86
- data/dist/controllers/frame_loading_controller.js +264 -213
- data/dist/controllers/highlight_controller.js +107 -75
- data/dist/controllers/hover_card_controller.js +309 -118
- data/dist/controllers/idle_controller.js +267 -101
- data/dist/controllers/input_mask_controller.js +71 -72
- data/dist/controllers/intersection_controller.js +147 -106
- data/dist/controllers/lazy_frame_controller.js +9 -65
- data/dist/controllers/listbox_controller.js +306 -204
- data/dist/controllers/local_time_controller.js +54 -64
- data/dist/controllers/masonry_controller.js +70 -93
- data/dist/controllers/menu_controller.js +214 -156
- data/dist/controllers/menubar_controller.js +170 -329
- data/dist/controllers/meter_controller.js +151 -67
- data/dist/controllers/multi_select_controller.js +593 -358
- data/dist/controllers/navigation_menu_controller.js +124 -232
- data/dist/controllers/nested_form_controller.js +352 -182
- data/dist/controllers/network_status_controller.js +242 -54
- data/dist/controllers/number_input_controller.js +320 -263
- data/dist/controllers/optimistic_controller.js +16 -76
- data/dist/controllers/otp_controller.js +198 -193
- data/dist/controllers/overflow_indicator_controller.js +116 -113
- data/dist/controllers/overflow_menu_controller.js +343 -283
- data/dist/controllers/pagination_controller.js +295 -119
- data/dist/controllers/password_reveal_controller.js +389 -121
- data/dist/controllers/password_strength_controller.js +99 -153
- data/dist/controllers/persist_controller.js +237 -103
- data/dist/controllers/pointer_drag_controller.js +185 -165
- data/dist/controllers/popover_controller.js +236 -97
- data/dist/controllers/portal_controller.js +209 -76
- data/dist/controllers/preview_guard_controller.js +232 -116
- data/dist/controllers/progress_controller.js +125 -56
- data/dist/controllers/radio_group_controller.js +117 -137
- data/dist/controllers/range_slider_controller.js +192 -105
- data/dist/controllers/rating_controller.js +418 -207
- data/dist/controllers/read_more_controller.js +134 -63
- data/dist/controllers/reading_progress_controller.js +147 -135
- data/dist/controllers/relative_time_controller.js +145 -110
- data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
- data/dist/controllers/resizable_controller.js +255 -139
- data/dist/controllers/roving_controller.js +39 -84
- data/dist/controllers/scroll_area_controller.js +428 -225
- data/dist/controllers/scroll_restore_controller.js +0 -53
- data/dist/controllers/scroll_visibility_controller.js +395 -145
- data/dist/controllers/scrollspy_controller.js +181 -242
- data/dist/controllers/separator_controller.js +267 -140
- data/dist/controllers/sidebar_controller.js +1023 -386
- data/dist/controllers/skeleton_controller.js +248 -97
- data/dist/controllers/slider_controller.js +159 -79
- data/dist/controllers/smart_sticky_header_controller.js +162 -63
- data/dist/controllers/sortable_controller.js +0 -112
- data/dist/controllers/spinner_controller.js +277 -169
- data/dist/controllers/step_indicator_controller.js +99 -46
- data/dist/controllers/stepper_controller.js +114 -73
- data/dist/controllers/stick_to_bottom_controller.js +262 -138
- data/dist/controllers/sticky_observer_controller.js +214 -49
- data/dist/controllers/submit_once_controller.js +294 -205
- data/dist/controllers/switch_controller.js +33 -60
- data/dist/controllers/tabs_controller.js +35 -29
- data/dist/controllers/tags_input_controller.js +136 -145
- data/dist/controllers/textarea_autosize_controller.js +71 -58
- data/dist/controllers/theme_controller.js +130 -112
- data/dist/controllers/time_picker_controller.js +146 -80
- data/dist/controllers/toast_controller.js +186 -297
- data/dist/controllers/toggle_group_controller.js +95 -136
- data/dist/controllers/toolbar_controller.js +68 -98
- data/dist/controllers/tooltip_controller.js +158 -124
- data/dist/controllers/transition_controller.js +123 -119
- data/dist/controllers/tree_view_controller.js +150 -345
- data/dist/index.js +6370 -9346
- data/dist/positioning/index.js +148 -47
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +3 -3
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function sharedRegistry(key, create) {
|
|
4
|
+
const symbol = Symbol.for(key);
|
|
5
|
+
const scope = globalThis;
|
|
6
|
+
const existing = scope[symbol];
|
|
7
|
+
if (existing !== void 0) return existing;
|
|
8
|
+
const registry = create();
|
|
9
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
10
|
+
return registry;
|
|
11
|
+
}
|
|
4
12
|
|
|
5
|
-
|
|
6
|
-
var counter = 0;
|
|
13
|
+
var ids = sharedRegistry("stimeo-ui.aria-ids.registry.v1", () => ({ counter: 0 }));
|
|
7
14
|
function uniqueId(prefix = "stimeo") {
|
|
8
15
|
let candidate;
|
|
9
16
|
do {
|
|
10
|
-
counter += 1;
|
|
11
|
-
candidate = `${prefix}-${counter}`;
|
|
17
|
+
ids.counter += 1;
|
|
18
|
+
candidate = `${prefix}-${ids.counter}`;
|
|
12
19
|
} while (typeof document !== "undefined" && document.getElementById(candidate) !== null);
|
|
13
20
|
return candidate;
|
|
14
21
|
}
|
|
@@ -19,12 +26,10 @@ function ensureId(element, prefix = "stimeo") {
|
|
|
19
26
|
return id;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
// src/utils/logical_scroll.ts
|
|
23
29
|
function isRtl(element) {
|
|
24
30
|
return window.getComputedStyle(element).direction === "rtl";
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
// src/utils/arrow_step.ts
|
|
28
33
|
function logicalArrowKey(key, element) {
|
|
29
34
|
if (key !== "ArrowRight" && key !== "ArrowLeft") return key;
|
|
30
35
|
if (!isRtl(element)) return key;
|
|
@@ -35,77 +40,143 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
35
40
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
function isAttributeValue(value) {
|
|
44
|
+
return value === null || typeof value === "string";
|
|
45
|
+
}
|
|
46
|
+
function readLeaseRecord(element, record) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
49
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
50
|
+
const [original, written, ...rest] = parsed;
|
|
51
|
+
const linked = rest[0] !== false;
|
|
52
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
53
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
54
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
55
|
+
} catch {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
var AttributeLease = class _AttributeLease {
|
|
60
|
+
static #registry = sharedRegistry(
|
|
61
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
62
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
63
|
+
);
|
|
40
64
|
#attribute;
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
#record;
|
|
66
|
+
#base;
|
|
67
|
+
#held = /* @__PURE__ */ new Set();
|
|
68
|
+
constructor(attribute, owner, options = {}) {
|
|
44
69
|
this.#attribute = attribute;
|
|
70
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
71
|
+
this.#base = options.base === true;
|
|
72
|
+
const names = _AttributeLease.#registry.names;
|
|
73
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
45
74
|
}
|
|
46
|
-
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
47
75
|
write(element, value) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
this.#reflect(element, value);
|
|
76
|
+
const stack = this.#stack(element);
|
|
77
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
78
|
+
this.#held.add(element);
|
|
79
|
+
const shows = !this.#covered(element, stack, hold);
|
|
80
|
+
hold.written = value;
|
|
81
|
+
this.#mark(element, stack);
|
|
82
|
+
if (shows) this.#reflect(element, value);
|
|
58
83
|
}
|
|
59
|
-
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
60
84
|
return(element) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
this.#
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
85
|
+
const held = this.#held.delete(element);
|
|
86
|
+
const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
|
|
87
|
+
const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
|
|
88
|
+
const hold = this.#take(stack);
|
|
89
|
+
if (!hold) return false;
|
|
90
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
91
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
92
|
+
const index = stack.indexOf(hold);
|
|
93
|
+
stack.splice(index, 1);
|
|
94
|
+
const above = stack[index];
|
|
95
|
+
if (above) above.linked &&= hold.linked;
|
|
96
|
+
this.#mark(element, stack);
|
|
97
|
+
if (!owns) return true;
|
|
98
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
99
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
returnAll() {
|
|
103
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
104
|
+
}
|
|
105
|
+
#stack(element) {
|
|
106
|
+
const stacks = _AttributeLease.#registry.stacks;
|
|
107
|
+
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
108
|
+
stacks.set(element, byAttribute);
|
|
109
|
+
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
110
|
+
byAttribute.set(this.#attribute, stack);
|
|
111
|
+
return stack;
|
|
112
|
+
}
|
|
113
|
+
#seed(element) {
|
|
114
|
+
const found = [];
|
|
115
|
+
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
116
|
+
const read = readLeaseRecord(element, record);
|
|
117
|
+
if (read) found.push({ ...read, record });
|
|
118
|
+
}
|
|
119
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
120
|
+
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
121
|
+
lease: null,
|
|
122
|
+
record,
|
|
123
|
+
written,
|
|
124
|
+
original,
|
|
125
|
+
linked: linked && index > 0
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
#take(stack) {
|
|
129
|
+
const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
|
|
130
|
+
if (hold) hold.lease = this;
|
|
131
|
+
return hold;
|
|
132
|
+
}
|
|
133
|
+
#acquire(element, stack) {
|
|
134
|
+
const current = element.getAttribute(this.#attribute);
|
|
135
|
+
const top = stack.at(-1);
|
|
136
|
+
const bottom = stack[0];
|
|
137
|
+
const over = top?.written === current ? top : void 0;
|
|
138
|
+
const from = this.#base ? bottom : over;
|
|
139
|
+
const hold = {
|
|
140
|
+
lease: this,
|
|
141
|
+
record: this.#record,
|
|
142
|
+
written: current,
|
|
143
|
+
original: from ? from.original : current,
|
|
144
|
+
linked: !this.#base && over !== void 0
|
|
145
|
+
};
|
|
146
|
+
if (this.#base && bottom) {
|
|
147
|
+
bottom.linked = true;
|
|
148
|
+
stack.unshift(hold);
|
|
149
|
+
} else stack.push(hold);
|
|
150
|
+
return hold;
|
|
151
|
+
}
|
|
152
|
+
#covered(element, stack, hold) {
|
|
153
|
+
const current = element.getAttribute(this.#attribute);
|
|
154
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
155
|
+
}
|
|
156
|
+
#mark(element, stack) {
|
|
157
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
158
|
+
stack.forEach((hold, index) => {
|
|
159
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
160
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
161
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
162
|
+
records.set(
|
|
163
|
+
hold.record,
|
|
164
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
for (const [name, recorded] of records) {
|
|
168
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
169
|
+
if (recorded === null) element.removeAttribute(name);
|
|
170
|
+
else element.setAttribute(name, recorded);
|
|
171
|
+
}
|
|
66
172
|
}
|
|
67
|
-
/** Reflects only a real value transition, avoiding self-triggered mutation work. */
|
|
68
173
|
#reflect(element, value) {
|
|
69
174
|
if (element.getAttribute(this.#attribute) === value) return;
|
|
70
175
|
if (value === null) element.removeAttribute(this.#attribute);
|
|
71
176
|
else element.setAttribute(this.#attribute, value);
|
|
72
177
|
}
|
|
73
|
-
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
74
|
-
returnAll() {
|
|
75
|
-
for (const element of Array.from(this.#records.keys())) this.return(element);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// src/utils/before_cache_reset.ts
|
|
80
|
-
var BeforeCacheReset = class _BeforeCacheReset {
|
|
81
|
-
/** Every subscribed instance, iterated by the one shared document listener. */
|
|
82
|
-
static #subscribers = /* @__PURE__ */ new Set();
|
|
83
|
-
/** The shared listener; installed while at least one instance is subscribed. */
|
|
84
|
-
static #onBeforeCache = () => {
|
|
85
|
-
for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
|
|
86
|
-
};
|
|
87
|
-
#rewind;
|
|
88
|
-
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
89
|
-
constructor(rewind) {
|
|
90
|
-
this.#rewind = rewind;
|
|
91
|
-
}
|
|
92
|
-
/** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
|
|
93
|
-
activate() {
|
|
94
|
-
const first = _BeforeCacheReset.#subscribers.size === 0;
|
|
95
|
-
_BeforeCacheReset.#subscribers.add(this);
|
|
96
|
-
if (first) {
|
|
97
|
-
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
|
|
101
|
-
deactivate() {
|
|
102
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
103
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
104
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
105
|
-
}
|
|
106
178
|
};
|
|
107
179
|
|
|
108
|
-
// src/utils/composition_tracker.ts
|
|
109
180
|
var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
110
181
|
"insertCompositionText",
|
|
111
182
|
"insertFromComposition",
|
|
@@ -117,13 +188,11 @@ var CompositionTracker = class {
|
|
|
117
188
|
#activeTargets = /* @__PURE__ */ new Set();
|
|
118
189
|
#onStart;
|
|
119
190
|
#onEnd;
|
|
120
|
-
/** The field whose confirming `input` is still owed, while the window is open. */
|
|
121
191
|
#confirmedTarget = null;
|
|
122
192
|
constructor(options = {}) {
|
|
123
193
|
this.#onStart = options.onStart;
|
|
124
194
|
this.#onEnd = options.onEnd;
|
|
125
195
|
}
|
|
126
|
-
/** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
|
|
127
196
|
observe(target) {
|
|
128
197
|
if (this.#observedTargets.has(target)) return;
|
|
129
198
|
target.addEventListener("compositionstart", this.#handleStart);
|
|
@@ -131,7 +200,6 @@ var CompositionTracker = class {
|
|
|
131
200
|
target.addEventListener("keydown", this.#handleKeydown);
|
|
132
201
|
this.#observedTargets.add(target);
|
|
133
202
|
}
|
|
134
|
-
/** Stops tracking one target and clears any active composition it owned. */
|
|
135
203
|
unobserve(target) {
|
|
136
204
|
if (!this.#observedTargets.delete(target)) return;
|
|
137
205
|
target.removeEventListener("compositionstart", this.#handleStart);
|
|
@@ -140,7 +208,6 @@ var CompositionTracker = class {
|
|
|
140
208
|
this.#activeTargets.delete(target);
|
|
141
209
|
if (this.#confirmedTarget === target) this.#confirmedTarget = null;
|
|
142
210
|
}
|
|
143
|
-
/** Releases every listener and clears state so reconnect starts cleanly. */
|
|
144
211
|
disconnect() {
|
|
145
212
|
for (const target of this.#observedTargets) {
|
|
146
213
|
target.removeEventListener("compositionstart", this.#handleStart);
|
|
@@ -151,16 +218,9 @@ var CompositionTracker = class {
|
|
|
151
218
|
this.#activeTargets.clear();
|
|
152
219
|
this.#confirmedTarget = null;
|
|
153
220
|
}
|
|
154
|
-
/** True when lifecycle tracking or the current event reports composition. */
|
|
155
221
|
isComposing(event) {
|
|
156
222
|
return this.#activeTargets.size > 0 || event?.isComposing === true;
|
|
157
223
|
}
|
|
158
|
-
/**
|
|
159
|
-
* Whether `event` is the `input` echoing the composition just confirmed.
|
|
160
|
-
*
|
|
161
|
-
* Asking closes the window either way, so one confirmation is folded at most
|
|
162
|
-
* once and a consumer asks once per `input`.
|
|
163
|
-
*/
|
|
164
224
|
consumesConfirmedInput(event) {
|
|
165
225
|
const confirmed = this.#confirmedTarget;
|
|
166
226
|
this.#confirmedTarget = null;
|
|
@@ -178,13 +238,11 @@ var CompositionTracker = class {
|
|
|
178
238
|
this.#confirmedTarget = event.target;
|
|
179
239
|
this.#onEnd?.(event);
|
|
180
240
|
};
|
|
181
|
-
/** A key on an observed field opens an edit of its own, so no echo is owed. */
|
|
182
241
|
#handleKeydown = () => {
|
|
183
242
|
this.#confirmedTarget = null;
|
|
184
243
|
};
|
|
185
244
|
};
|
|
186
245
|
|
|
187
|
-
// src/utils/declared_value.ts
|
|
188
246
|
function parseDeclared(raw, parse, fallback) {
|
|
189
247
|
try {
|
|
190
248
|
return parse(raw);
|
|
@@ -203,25 +261,18 @@ function compileRegExp(source, anchor = "none") {
|
|
|
203
261
|
);
|
|
204
262
|
}
|
|
205
263
|
|
|
206
|
-
// src/utils/form_reset_watcher.ts
|
|
207
264
|
var FormResetWatcher = class {
|
|
208
265
|
#owns;
|
|
209
266
|
#onReset;
|
|
210
267
|
#frames = /* @__PURE__ */ new Set();
|
|
211
268
|
#generation = 0;
|
|
212
|
-
/**
|
|
213
|
-
* @param owns - whether `form` owns a control this controller derives from.
|
|
214
|
-
* @param onReset - the reconciliation pass, run once per honoured reset.
|
|
215
|
-
*/
|
|
216
269
|
constructor(owns, onReset) {
|
|
217
270
|
this.#owns = owns;
|
|
218
271
|
this.#onReset = onReset;
|
|
219
272
|
}
|
|
220
|
-
/** Subscribes to `reset` at the document; call from `connect()`. Idempotent. */
|
|
221
273
|
observe() {
|
|
222
274
|
document.addEventListener("reset", this.#handleReset, true);
|
|
223
275
|
}
|
|
224
|
-
/** Unsubscribes and drops every pending pass; call from `disconnect()`. */
|
|
225
276
|
disconnect() {
|
|
226
277
|
document.removeEventListener("reset", this.#handleReset, true);
|
|
227
278
|
this.#generation += 1;
|
|
@@ -245,13 +296,11 @@ var FormResetWatcher = class {
|
|
|
245
296
|
this.#frames.add(frame);
|
|
246
297
|
});
|
|
247
298
|
};
|
|
248
|
-
/** Runs the pass unless a listener cancelled the reset before it landed. */
|
|
249
299
|
#settle(event) {
|
|
250
300
|
if (!event.defaultPrevented) this.#onReset();
|
|
251
301
|
}
|
|
252
302
|
};
|
|
253
303
|
|
|
254
|
-
// src/utils/half_width.ts
|
|
255
304
|
var FULL_WIDTH_SHIFT = 65248;
|
|
256
305
|
function halfWidthChar(char) {
|
|
257
306
|
if (char >= "\uFF01" && char <= "\uFF5E") {
|
|
@@ -265,27 +314,22 @@ function toHalfWidth(text) {
|
|
|
265
314
|
return out;
|
|
266
315
|
}
|
|
267
316
|
|
|
268
|
-
// src/utils/microtask_coalescer.ts
|
|
269
317
|
var MicrotaskCoalescer = class {
|
|
270
318
|
#run;
|
|
271
319
|
#queued = false;
|
|
272
320
|
#active = false;
|
|
273
321
|
#generation = 0;
|
|
274
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
275
322
|
constructor(run) {
|
|
276
323
|
this.#run = run;
|
|
277
324
|
}
|
|
278
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
279
325
|
activate() {
|
|
280
326
|
this.#active = true;
|
|
281
327
|
}
|
|
282
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
283
328
|
cancel() {
|
|
284
329
|
this.#active = false;
|
|
285
330
|
this.#queued = false;
|
|
286
331
|
this.#generation += 1;
|
|
287
332
|
}
|
|
288
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
289
333
|
schedule() {
|
|
290
334
|
if (!this.#active || this.#queued) return;
|
|
291
335
|
this.#queued = true;
|
|
@@ -298,7 +342,17 @@ var MicrotaskCoalescer = class {
|
|
|
298
342
|
}
|
|
299
343
|
};
|
|
300
344
|
|
|
301
|
-
|
|
345
|
+
var MoveCounter = class {
|
|
346
|
+
#sequence = 0;
|
|
347
|
+
record() {
|
|
348
|
+
this.#sequence += 1;
|
|
349
|
+
return this.#sequence;
|
|
350
|
+
}
|
|
351
|
+
isLatest(token) {
|
|
352
|
+
return token === this.#sequence;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
302
356
|
var LEASE_MARKERS = {
|
|
303
357
|
"aria-invalid": "data-otp-invalid-lease",
|
|
304
358
|
"aria-errormessage": "data-otp-errormessage-lease",
|
|
@@ -316,8 +370,8 @@ function readLease(field, marker) {
|
|
|
316
370
|
return null;
|
|
317
371
|
}
|
|
318
372
|
}
|
|
319
|
-
function describedByWith(described, errorId) {
|
|
320
|
-
const tokens = (described ?? "").split(/\s+/).filter((token) => token.length > 0 && token !== errorId);
|
|
373
|
+
function describedByWith(described, errorId, earlier) {
|
|
374
|
+
const tokens = (described ?? "").split(/\s+/).filter((token) => token.length > 0 && token !== errorId && token !== earlier);
|
|
321
375
|
return [...tokens, errorId].join(" ");
|
|
322
376
|
}
|
|
323
377
|
var DEFAULT_PATTERN = "[0-9]";
|
|
@@ -329,36 +383,23 @@ function statesDiffer(left, right) {
|
|
|
329
383
|
return left.value !== right.value || left.state !== right.state;
|
|
330
384
|
}
|
|
331
385
|
var OtpController = class extends Controller {
|
|
386
|
+
#moves = new MoveCounter();
|
|
332
387
|
static targets = ["field", "value", "error"];
|
|
333
388
|
static values = {
|
|
334
389
|
pattern: { type: String, default: DEFAULT_PATTERN }
|
|
335
390
|
};
|
|
336
391
|
static actions = ["onInput", "onKeydown", "onPaste", "onPointerDown", "clear"];
|
|
337
392
|
static events = ["change", "complete", "invalid", "reconcile"];
|
|
338
|
-
/** Validated matcher; the hot path never compiles a raw declaration. */
|
|
339
393
|
#pattern = DEFAULT_MATCHER;
|
|
340
|
-
/** Source of {@link #pattern}, reported in `invalid` so consumers can word it. */
|
|
341
394
|
#patternSource = DEFAULT_PATTERN;
|
|
342
|
-
/** Public state carried by the last dispatch; keeps a no-op sync silent. */
|
|
343
395
|
#published = null;
|
|
344
|
-
/** True between connect and disconnect, so pre-connect Value changes stay silent. */
|
|
345
396
|
#connected = false;
|
|
346
|
-
/** Digit each field last committed, restored when rejected input replaced it. */
|
|
347
397
|
#committed = /* @__PURE__ */ new WeakMap();
|
|
348
|
-
/** The field being composed, and the text it held when the composition began. */
|
|
349
398
|
#composing = null;
|
|
350
|
-
/** A `pattern` change whose rejected characters wait for the composition to end. */
|
|
351
399
|
#dropHeld = false;
|
|
352
|
-
/**
|
|
353
|
-
* Collapses one batch of page changes — field target and `pattern` callbacks, a
|
|
354
|
-
* form reset — into a single reconciliation.
|
|
355
|
-
*/
|
|
356
400
|
#reconcile = new MicrotaskCoalescer(() => this.#reconcileFields());
|
|
357
|
-
#errorHidden = new AttributeLease("hidden");
|
|
358
|
-
#state = new AttributeLease("data-state");
|
|
359
|
-
/** Rewinds the transient error surface before Turbo freezes the page. */
|
|
360
|
-
#beforeCache = new BeforeCacheReset(() => this.#clearError());
|
|
361
|
-
/** Reads the fields back once a native reset restored them, as a reconciliation. */
|
|
401
|
+
#errorHidden = new AttributeLease("hidden", this.identifier);
|
|
402
|
+
#state = new AttributeLease("data-state", this.identifier);
|
|
362
403
|
#formReset = new FormResetWatcher(
|
|
363
404
|
(form) => this.#ownedBy(form),
|
|
364
405
|
() => {
|
|
@@ -366,11 +407,6 @@ var OtpController = class extends Controller {
|
|
|
366
407
|
this.#reconcile.schedule();
|
|
367
408
|
}
|
|
368
409
|
);
|
|
369
|
-
/**
|
|
370
|
-
* Owns IME lifecycle state across every digit field. When a composition ends, a
|
|
371
|
-
* `pattern` drop it held back applies first — it was declared first — and the
|
|
372
|
-
* user's commit is taken after it.
|
|
373
|
-
*/
|
|
374
410
|
#composition = new CompositionTracker({
|
|
375
411
|
onStart: (event) => {
|
|
376
412
|
const field = this.#fieldFrom(event);
|
|
@@ -394,7 +430,6 @@ var OtpController = class extends Controller {
|
|
|
394
430
|
this.#connected = true;
|
|
395
431
|
for (const field of this.fieldTargets) this.#bind(field);
|
|
396
432
|
this.#formReset.observe();
|
|
397
|
-
this.#beforeCache.activate();
|
|
398
433
|
this.#reconcile.activate();
|
|
399
434
|
this.#adopt();
|
|
400
435
|
this.#sync();
|
|
@@ -404,35 +439,34 @@ var OtpController = class extends Controller {
|
|
|
404
439
|
for (const field of this.fieldTargets) this.#unbind(field);
|
|
405
440
|
this.#composition.disconnect();
|
|
406
441
|
this.#formReset.disconnect();
|
|
407
|
-
this.#beforeCache.deactivate();
|
|
408
442
|
this.#reconcile.cancel();
|
|
409
443
|
this.#dropHeld = false;
|
|
410
444
|
this.#clearError();
|
|
411
445
|
this.#state.return(this.element);
|
|
412
446
|
}
|
|
413
|
-
/**
|
|
414
|
-
* Stimulus lifecycle callback when a new field target enters the DOM.
|
|
415
|
-
* Wires the new field and folds the wider digit count into one reconciliation,
|
|
416
|
-
* which stays inert until `connect()` opens the window.
|
|
417
|
-
*/
|
|
418
447
|
fieldTargetConnected(element) {
|
|
419
448
|
this.#bind(element);
|
|
420
449
|
this.#adoptField(element);
|
|
421
450
|
this.#reconcile.schedule();
|
|
422
451
|
}
|
|
423
|
-
/** Releases a dropped field's listeners and leases, then reconciles the rest. */
|
|
424
452
|
fieldTargetDisconnected(element) {
|
|
425
453
|
this.#unbind(element);
|
|
426
454
|
this.#returnLeases(element);
|
|
427
455
|
this.#reconcile.schedule();
|
|
428
456
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
457
|
+
valueTargetConnected() {
|
|
458
|
+
this.#reconcile.schedule();
|
|
459
|
+
}
|
|
460
|
+
valueTargetDisconnected() {
|
|
461
|
+
this.#reconcile.schedule();
|
|
462
|
+
}
|
|
463
|
+
errorTargetConnected() {
|
|
464
|
+
this.#followError();
|
|
465
|
+
}
|
|
466
|
+
errorTargetDisconnected(element) {
|
|
467
|
+
if (!this.errorTargets.includes(element)) this.#errorHidden.return(element);
|
|
468
|
+
this.#followError();
|
|
469
|
+
}
|
|
436
470
|
patternValueChanged() {
|
|
437
471
|
const compiled = compileRegExp(this.patternValue, "exact");
|
|
438
472
|
this.#patternSource = compiled ? this.patternValue : DEFAULT_PATTERN;
|
|
@@ -444,7 +478,6 @@ var OtpController = class extends Controller {
|
|
|
444
478
|
}
|
|
445
479
|
if (this.#dropRejected()) this.#reconcile.schedule();
|
|
446
480
|
}
|
|
447
|
-
/** Empties every writable field holding a character the pattern rejects; whether one did. */
|
|
448
481
|
#dropRejected() {
|
|
449
482
|
let dropped = false;
|
|
450
483
|
for (const field of this.fieldTargets) {
|
|
@@ -455,7 +488,6 @@ var OtpController = class extends Controller {
|
|
|
455
488
|
}
|
|
456
489
|
return dropped;
|
|
457
490
|
}
|
|
458
|
-
/** Handles keystroke inputs, distributes autofilled text, and advances focus. */
|
|
459
491
|
onInput(event) {
|
|
460
492
|
const input = this.#fieldFrom(event);
|
|
461
493
|
if (!input) return;
|
|
@@ -463,7 +495,6 @@ var OtpController = class extends Controller {
|
|
|
463
495
|
if (this.#composition.isComposing(event)) return;
|
|
464
496
|
this.#accept(input);
|
|
465
497
|
}
|
|
466
|
-
/** Handles Backspace clearing, arrows, and home/end navigation. */
|
|
467
498
|
onKeydown(event) {
|
|
468
499
|
if (isReservedArrowChord(event)) return;
|
|
469
500
|
const input = this.#fieldFrom(event);
|
|
@@ -526,18 +557,12 @@ var OtpController = class extends Controller {
|
|
|
526
557
|
}
|
|
527
558
|
}
|
|
528
559
|
}
|
|
529
|
-
/** Divides pasted string characters across the available input fields. */
|
|
530
560
|
onPaste(event) {
|
|
531
561
|
const input = this.#fieldFrom(event);
|
|
532
562
|
if (!input) return;
|
|
533
563
|
event.preventDefault();
|
|
534
564
|
this.#distribute(input, toHalfWidth(event.clipboardData?.getData("text") ?? ""));
|
|
535
565
|
}
|
|
536
|
-
/**
|
|
537
|
-
* Redirects a pointer landing on an empty field to the earliest empty one, so
|
|
538
|
-
* a passcode is entered in order. Filled fields stay directly reachable for
|
|
539
|
-
* correction, and keyboard focus is left alone.
|
|
540
|
-
*/
|
|
541
566
|
onPointerDown(event) {
|
|
542
567
|
const input = this.#fieldFrom(event);
|
|
543
568
|
if (input?.value !== "") return;
|
|
@@ -546,7 +571,6 @@ var OtpController = class extends Controller {
|
|
|
546
571
|
event.preventDefault();
|
|
547
572
|
first.focus();
|
|
548
573
|
}
|
|
549
|
-
/** Empties every writable field and restarts entry at the first of them. */
|
|
550
574
|
clear() {
|
|
551
575
|
for (const field of this.fieldTargets) {
|
|
552
576
|
if (this.#isWritable(field)) this.#writeField(field, "");
|
|
@@ -561,7 +585,6 @@ var OtpController = class extends Controller {
|
|
|
561
585
|
input.select();
|
|
562
586
|
}
|
|
563
587
|
};
|
|
564
|
-
/** Whether a form owns at least one field or the hidden combined value. */
|
|
565
588
|
#ownedBy(form) {
|
|
566
589
|
if (this.fieldTargets.some((field) => field.form === form)) return true;
|
|
567
590
|
return this.hasValueTarget && this.valueTarget.form === form;
|
|
@@ -575,13 +598,10 @@ var OtpController = class extends Controller {
|
|
|
575
598
|
this.#composition.unobserve(field);
|
|
576
599
|
if (this.#composing?.field === field) this.#composing = null;
|
|
577
600
|
}
|
|
578
|
-
/** Reads every field back so a restored or reset group starts consistent. */
|
|
579
601
|
#adopt() {
|
|
580
602
|
for (const field of this.fieldTargets) this.#adoptField(field);
|
|
581
603
|
this.#clearError();
|
|
582
|
-
if (this.hasErrorTarget) this.errorTarget.setAttribute("hidden", "");
|
|
583
604
|
}
|
|
584
|
-
/** Writes one error attribute, keeping the first authored value it displaces. */
|
|
585
605
|
#lease(field, attribute, value) {
|
|
586
606
|
const marker = LEASE_MARKERS[attribute];
|
|
587
607
|
const held = readLease(field, marker);
|
|
@@ -589,7 +609,6 @@ var OtpController = class extends Controller {
|
|
|
589
609
|
field.setAttribute(marker, JSON.stringify({ authored, written: value }));
|
|
590
610
|
field.setAttribute(attribute, value);
|
|
591
611
|
}
|
|
592
|
-
/** Gives one field's error attributes back where they still hold what this wrote. */
|
|
593
612
|
#returnLeases(field) {
|
|
594
613
|
for (const [attribute, marker] of Object.entries(LEASE_MARKERS)) {
|
|
595
614
|
const held = readLease(field, marker);
|
|
@@ -600,31 +619,10 @@ var OtpController = class extends Controller {
|
|
|
600
619
|
else field.setAttribute(attribute, held.authored);
|
|
601
620
|
}
|
|
602
621
|
}
|
|
603
|
-
/** Takes one field's current value as the truth behind its derived state. */
|
|
604
622
|
#adoptField(field) {
|
|
605
623
|
this.#committed.set(field, field.value);
|
|
606
624
|
this.#markFilled(field, field.value);
|
|
607
625
|
}
|
|
608
|
-
/**
|
|
609
|
-
* Absorbs one batch of page changes — fields added or removed, entered digits a
|
|
610
|
-
* new `pattern` rejects, fields a native form reset restored — as one state
|
|
611
|
-
* transition.
|
|
612
|
-
*
|
|
613
|
-
* Nobody edited the passcode here, so the move is reported as `reconcile`:
|
|
614
|
-
* automation listening for `change` must not read a re-render or a reset as an
|
|
615
|
-
* edit, and a passcode that happens to end up full must not fire the
|
|
616
|
-
* `complete` that submits it.
|
|
617
|
-
*
|
|
618
|
-
* Completeness moves on its own when the field count changes: dropping a
|
|
619
|
-
* trailing empty field completes a passcode whose combined value never moved,
|
|
620
|
-
* and adding one un-completes it. Comparing the whole derived state, not the
|
|
621
|
-
* string it contains, is what makes those transitions reportable.
|
|
622
|
-
*
|
|
623
|
-
* A change already in the DOM — a field added or removed, a reset — is
|
|
624
|
-
* published at once, even while a field is composing: that field counts by the
|
|
625
|
-
* character it had committed, never by the IME's uncommitted text. Only a
|
|
626
|
-
* `pattern` drop waits for the composition to end.
|
|
627
|
-
*/
|
|
628
626
|
#reconcileFields() {
|
|
629
627
|
if (this.#dropHeld && this.#composing === null) {
|
|
630
628
|
this.#dropHeld = false;
|
|
@@ -633,13 +631,9 @@ var OtpController = class extends Controller {
|
|
|
633
631
|
const previous = this.#published;
|
|
634
632
|
const current = this.#sync();
|
|
635
633
|
if (previous && !statesDiffer(previous, current)) return;
|
|
634
|
+
this.#moves.record();
|
|
636
635
|
this.dispatch("reconcile", { detail: { value: current.value } });
|
|
637
636
|
}
|
|
638
|
-
/**
|
|
639
|
-
* Validates the text an entry point received and distributes what it accepts.
|
|
640
|
-
* `text` defaults to the field's own value; a confirmation passes the string it
|
|
641
|
-
* committed, which `maxlength` would otherwise have truncated.
|
|
642
|
-
*/
|
|
643
637
|
#accept(input, text = input.value) {
|
|
644
638
|
const raw = toHalfWidth(text);
|
|
645
639
|
if (raw === "") {
|
|
@@ -650,10 +644,6 @@ var OtpController = class extends Controller {
|
|
|
650
644
|
}
|
|
651
645
|
this.#distribute(input, raw);
|
|
652
646
|
}
|
|
653
|
-
/**
|
|
654
|
-
* Fills `text`'s accepted characters into the writable fields at and after the
|
|
655
|
-
* entry point, then leaves focus on the field after the last one filled.
|
|
656
|
-
*/
|
|
657
647
|
#distribute(from, text) {
|
|
658
648
|
const accepted = Array.from(text).filter((char) => this.#pattern.test(char));
|
|
659
649
|
let reached = false;
|
|
@@ -678,11 +668,9 @@ var OtpController = class extends Controller {
|
|
|
678
668
|
this.#clearError();
|
|
679
669
|
this.#syncAndDispatch();
|
|
680
670
|
}
|
|
681
|
-
/** Restores the digit a field committed before rejected input replaced it. */
|
|
682
671
|
#restore(field) {
|
|
683
672
|
this.#writeField(field, this.#committed.get(field) ?? "");
|
|
684
673
|
}
|
|
685
|
-
/** Commits one field's value and the derived hook that reports it as entered. */
|
|
686
674
|
#writeField(field, value) {
|
|
687
675
|
field.value = value;
|
|
688
676
|
this.#committed.set(field, value);
|
|
@@ -711,11 +699,9 @@ var OtpController = class extends Controller {
|
|
|
711
699
|
#focusableBefore(index) {
|
|
712
700
|
return this.#before(index).find((field) => this.#isFocusable(field)) ?? null;
|
|
713
701
|
}
|
|
714
|
-
/** Fields before `index`, nearest first; empty at the first field. */
|
|
715
702
|
#before(index) {
|
|
716
703
|
return this.fieldTargets.slice(0, Math.max(index, 0)).reverse();
|
|
717
704
|
}
|
|
718
|
-
/** The event's field target, or `null` when the wiring points somewhere else. */
|
|
719
705
|
#fieldFrom(event) {
|
|
720
706
|
const input = event.currentTarget;
|
|
721
707
|
return this.fieldTargets.find((field) => field === input) ?? null;
|
|
@@ -723,19 +709,13 @@ var OtpController = class extends Controller {
|
|
|
723
709
|
#combinedValue() {
|
|
724
710
|
return this.fieldTargets.map((field) => this.#publishedText(field)).join("");
|
|
725
711
|
}
|
|
726
|
-
/** Every field carries a character, and there is at least one field. */
|
|
727
712
|
#isComplete() {
|
|
728
713
|
const fields = this.fieldTargets;
|
|
729
714
|
return fields.length > 0 && fields.every((field) => this.#publishedText(field).length > 0);
|
|
730
715
|
}
|
|
731
|
-
/** A field's text as the combined value counts it: a composing field by its committed character. */
|
|
732
716
|
#publishedText(field) {
|
|
733
717
|
return field === this.#composing?.field ? this.#committed.get(field) ?? "" : field.value;
|
|
734
718
|
}
|
|
735
|
-
/**
|
|
736
|
-
* Mirrors the combined value into the form and the root's readable state, and
|
|
737
|
-
* records what was published so the next pass can compare against it.
|
|
738
|
-
*/
|
|
739
719
|
#sync() {
|
|
740
720
|
const combined = this.#combinedValue();
|
|
741
721
|
if (this.hasValueTarget) {
|
|
@@ -755,31 +735,56 @@ var OtpController = class extends Controller {
|
|
|
755
735
|
const previous = this.#published;
|
|
756
736
|
const { value: combined } = this.#sync();
|
|
757
737
|
if (previous?.value === combined) return;
|
|
738
|
+
const token = this.#moves.record();
|
|
758
739
|
this.dispatch("change", { detail: { value: combined } });
|
|
740
|
+
if (!this.#moves.isLatest(token)) return;
|
|
759
741
|
if (this.#isComplete()) {
|
|
760
742
|
this.dispatch("complete", { detail: { value: combined } });
|
|
761
743
|
}
|
|
762
744
|
}
|
|
763
|
-
/** Surfaces rejected input on every field and on the optional error target. */
|
|
764
745
|
#showError() {
|
|
765
746
|
const errorId = this.hasErrorTarget ? ensureId(this.errorTarget, "stimeo--otp-error") : null;
|
|
766
747
|
for (const field of this.fieldTargets) {
|
|
767
748
|
this.#lease(field, "aria-invalid", "true");
|
|
768
|
-
if (
|
|
769
|
-
this.#lease(field, "aria-errormessage", errorId);
|
|
770
|
-
const described = field.getAttribute("aria-describedby");
|
|
771
|
-
this.#lease(field, "aria-describedby", describedByWith(described, errorId));
|
|
749
|
+
if (errorId) this.#pointAtError(field, errorId);
|
|
772
750
|
}
|
|
773
751
|
if (this.hasErrorTarget) this.#errorHidden.write(this.errorTarget, null);
|
|
774
752
|
this.dispatch("invalid", { detail: { pattern: this.#patternSource } });
|
|
775
753
|
}
|
|
776
|
-
|
|
754
|
+
#pointAtError(field, errorId) {
|
|
755
|
+
const earlier = readLease(field, LEASE_MARKERS["aria-errormessage"])?.written ?? null;
|
|
756
|
+
this.#lease(field, "aria-errormessage", errorId);
|
|
757
|
+
const described = field.getAttribute("aria-describedby");
|
|
758
|
+
this.#lease(field, "aria-describedby", describedByWith(described, errorId, earlier));
|
|
759
|
+
}
|
|
760
|
+
#followError() {
|
|
761
|
+
if (!this.#connected) return;
|
|
762
|
+
const reported = this.fieldTargets.filter(
|
|
763
|
+
(field) => readLease(field, LEASE_MARKERS["aria-invalid"]) !== null
|
|
764
|
+
);
|
|
765
|
+
if (reported.length === 0) {
|
|
766
|
+
this.#hideErrors();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (!this.hasErrorTarget) return;
|
|
770
|
+
const errorId = ensureId(this.errorTarget, "stimeo--otp-error");
|
|
771
|
+
for (const field of reported) this.#pointAtError(field, errorId);
|
|
772
|
+
this.#errorHidden.write(this.errorTarget, null);
|
|
773
|
+
}
|
|
777
774
|
#clearError() {
|
|
778
775
|
for (const field of this.fieldTargets) this.#returnLeases(field);
|
|
779
|
-
this.#
|
|
776
|
+
if (!this.#connected) {
|
|
777
|
+
this.#errorHidden.returnAll();
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
this.#hideErrors();
|
|
781
|
+
}
|
|
782
|
+
#hideErrors() {
|
|
783
|
+
const [first, ...others] = this.errorTargets;
|
|
784
|
+
for (const other of others) this.#errorHidden.return(other);
|
|
785
|
+
if (first) this.#errorHidden.write(first, "");
|
|
780
786
|
}
|
|
781
787
|
};
|
|
782
788
|
|
|
783
789
|
export { OtpController };
|
|
784
|
-
//# sourceMappingURL=otp_controller.js.map
|
|
785
790
|
//# sourceMappingURL=otp_controller.js.map
|