stimeo-ui 0.16.0 → 0.18.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 +136 -0
- data/README.md +27 -11
- data/dist/cable/index.js +384 -449
- data/dist/controllers/accordion_controller.js +174 -55
- data/dist/controllers/alert_dialog_controller.js +903 -235
- data/dist/controllers/announcer_controller.js +238 -205
- data/dist/controllers/aspect_ratio_controller.js +0 -10
- data/dist/controllers/auto_submit_controller.js +414 -145
- data/dist/controllers/avatar_controller.js +185 -107
- data/dist/controllers/breadcrumb_controller.js +0 -130
- data/dist/controllers/bulk_select_controller.js +282 -58
- data/dist/controllers/calendar_controller.js +228 -260
- data/dist/controllers/carousel_controller.js +536 -436
- data/dist/controllers/character_counter_controller.js +493 -207
- data/dist/controllers/checkbox_controller.js +38 -74
- data/dist/controllers/clipboard_controller.js +261 -149
- data/dist/controllers/collapsible_controller.js +304 -120
- data/dist/controllers/color_picker_controller.js +178 -146
- data/dist/controllers/combobox_controller.js +522 -156
- data/dist/controllers/command_palette_controller.js +1156 -395
- data/dist/controllers/conditional_fields_controller.js +300 -171
- data/dist/controllers/confirm_controller.js +980 -262
- data/dist/controllers/context_menu_controller.js +346 -153
- data/dist/controllers/count_up_controller.js +73 -21
- data/dist/controllers/countdown_controller.js +281 -158
- data/dist/controllers/currency_input_controller.js +575 -225
- data/dist/controllers/data_grid_controller.js +248 -214
- data/dist/controllers/date_range_picker_controller.js +338 -235
- data/dist/controllers/dialog_controller.js +899 -237
- data/dist/controllers/direct_upload_controller.js +97 -167
- data/dist/controllers/dirty_form_controller.js +72 -69
- data/dist/controllers/dismissible_controller.js +196 -24
- data/dist/controllers/drawer_controller.js +974 -355
- data/dist/controllers/dropdown_controller.js +339 -96
- data/dist/controllers/editable_controller.js +315 -88
- data/dist/controllers/empty_state_controller.js +79 -58
- data/dist/controllers/file_dropzone_controller.js +322 -211
- data/dist/controllers/filter_controller.js +77 -42
- data/dist/controllers/flash_controller.js +238 -347
- data/dist/controllers/focus_controller.js +679 -240
- data/dist/controllers/form_field_controller.js +156 -132
- data/dist/controllers/form_validation_controller.js +31 -97
- data/dist/controllers/frame_loading_controller.js +275 -217
- data/dist/controllers/highlight_controller.js +107 -75
- data/dist/controllers/hover_card_controller.js +327 -130
- data/dist/controllers/idle_controller.js +271 -101
- data/dist/controllers/input_mask_controller.js +483 -97
- data/dist/controllers/intersection_controller.js +150 -106
- data/dist/controllers/lazy_frame_controller.js +24 -68
- data/dist/controllers/listbox_controller.js +523 -240
- data/dist/controllers/local_time_controller.js +102 -77
- data/dist/controllers/masonry_controller.js +80 -96
- data/dist/controllers/menu_controller.js +302 -188
- data/dist/controllers/menubar_controller.js +232 -345
- data/dist/controllers/meter_controller.js +417 -87
- data/dist/controllers/multi_select_controller.js +785 -393
- data/dist/controllers/navigation_menu_controller.js +190 -250
- data/dist/controllers/nested_form_controller.js +396 -192
- data/dist/controllers/network_status_controller.js +268 -62
- data/dist/controllers/number_input_controller.js +610 -303
- data/dist/controllers/optimistic_controller.js +45 -87
- data/dist/controllers/otp_controller.js +658 -255
- data/dist/controllers/overflow_indicator_controller.js +122 -113
- data/dist/controllers/overflow_menu_controller.js +470 -295
- data/dist/controllers/pagination_controller.js +332 -123
- data/dist/controllers/password_reveal_controller.js +392 -121
- data/dist/controllers/password_strength_controller.js +111 -158
- data/dist/controllers/persist_controller.js +165 -111
- data/dist/controllers/pointer_drag_controller.js +434 -188
- data/dist/controllers/popover_controller.js +269 -113
- data/dist/controllers/portal_controller.js +215 -76
- data/dist/controllers/preview_guard_controller.js +243 -116
- data/dist/controllers/progress_controller.js +391 -78
- data/dist/controllers/radio_group_controller.js +278 -211
- data/dist/controllers/range_slider_controller.js +220 -113
- data/dist/controllers/rating_controller.js +458 -211
- data/dist/controllers/read_more_controller.js +138 -63
- data/dist/controllers/reading_progress_controller.js +166 -137
- data/dist/controllers/relative_time_controller.js +211 -112
- data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
- data/dist/controllers/resizable_controller.js +299 -150
- data/dist/controllers/roving_controller.js +49 -88
- data/dist/controllers/scroll_area_controller.js +490 -246
- data/dist/controllers/scroll_restore_controller.js +14 -55
- data/dist/controllers/scroll_visibility_controller.js +432 -149
- data/dist/controllers/scrollspy_controller.js +193 -248
- data/dist/controllers/separator_controller.js +285 -142
- data/dist/controllers/sidebar_controller.js +1069 -404
- data/dist/controllers/skeleton_controller.js +259 -101
- data/dist/controllers/slider_controller.js +187 -86
- data/dist/controllers/smart_sticky_header_controller.js +173 -66
- data/dist/controllers/sortable_controller.js +26 -125
- data/dist/controllers/spinner_controller.js +288 -173
- data/dist/controllers/step_indicator_controller.js +105 -46
- data/dist/controllers/stepper_controller.js +120 -73
- data/dist/controllers/stick_to_bottom_controller.js +275 -140
- data/dist/controllers/sticky_observer_controller.js +220 -49
- data/dist/controllers/submit_once_controller.js +376 -227
- data/dist/controllers/switch_controller.js +201 -84
- data/dist/controllers/tabs_controller.js +41 -30
- data/dist/controllers/tags_input_controller.js +293 -173
- data/dist/controllers/textarea_autosize_controller.js +108 -74
- data/dist/controllers/theme_controller.js +140 -116
- data/dist/controllers/time_picker_controller.js +331 -127
- data/dist/controllers/toast_controller.js +244 -324
- data/dist/controllers/toggle_group_controller.js +252 -191
- data/dist/controllers/toolbar_controller.js +219 -120
- data/dist/controllers/tooltip_controller.js +158 -124
- data/dist/controllers/transition_controller.js +123 -119
- data/dist/controllers/tree_view_controller.js +283 -369
- data/dist/index.js +8573 -10410
- data/dist/positioning/index.js +154 -47
- data/lib/generators/stimeo/install/install_generator.rb +7 -2
- data/lib/generators/stimeo/install/templates/stimeo.js +19 -6
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +3 -3
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function fillTemplate(template, values) {
|
|
4
|
+
return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
|
|
5
|
+
const replacement = Object.hasOwn(values, name) ? values[name] : void 0;
|
|
6
|
+
return replacement === void 0 ? match : String(replacement);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
4
9
|
|
|
5
|
-
// src/utils/announce.ts
|
|
6
10
|
function announce(message, options = {}) {
|
|
7
11
|
const text = message.trim();
|
|
8
12
|
if (text.length === 0) return;
|
|
@@ -12,70 +16,63 @@ function announce(message, options = {}) {
|
|
|
12
16
|
})
|
|
13
17
|
);
|
|
14
18
|
}
|
|
15
|
-
function fillTemplate(template, values) {
|
|
16
|
-
return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
|
|
17
|
-
const replacement = values[name];
|
|
18
|
-
return replacement === void 0 ? match : String(replacement);
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const first = _BeforeCacheReset.#subscribers.size === 0;
|
|
38
|
-
_BeforeCacheReset.#subscribers.add(this);
|
|
39
|
-
if (first) {
|
|
40
|
-
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
/** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
|
|
44
|
-
deactivate() {
|
|
45
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
46
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
47
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
20
|
+
var NUMBER_BOUNDS = {
|
|
21
|
+
nonNegative: { finite: true, min: 0 }};
|
|
22
|
+
function matchesNumberBounds(value, bounds) {
|
|
23
|
+
if (!Number.isFinite(value)) {
|
|
24
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
25
|
+
if (direction === null) return false;
|
|
26
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
27
|
+
}
|
|
28
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
29
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
30
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
31
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
32
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
50
35
|
|
|
51
|
-
// src/utils/coerce.ts
|
|
52
36
|
function toFiniteNumber(raw) {
|
|
53
|
-
if (
|
|
54
|
-
|
|
37
|
+
if (typeof raw !== "number" && typeof raw !== "string") return null;
|
|
38
|
+
if (typeof raw === "string" && raw.trim().length === 0) return null;
|
|
39
|
+
const value = Number(raw);
|
|
55
40
|
return Number.isFinite(value) ? value : null;
|
|
56
41
|
}
|
|
42
|
+
function readNumber(raw, fallback, bounds) {
|
|
43
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var ListenerSet = class {
|
|
47
|
+
#abort = new AbortController();
|
|
48
|
+
add(target, type, handler, options) {
|
|
49
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
50
|
+
}
|
|
51
|
+
dispose() {
|
|
52
|
+
this.#abort.abort();
|
|
53
|
+
this.#abort = new AbortController();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
57
56
|
|
|
58
|
-
// src/utils/microtask_coalescer.ts
|
|
59
57
|
var MicrotaskCoalescer = class {
|
|
60
58
|
#run;
|
|
61
59
|
#queued = false;
|
|
62
60
|
#active = false;
|
|
63
61
|
#generation = 0;
|
|
64
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
65
62
|
constructor(run) {
|
|
66
63
|
this.#run = run;
|
|
67
64
|
}
|
|
68
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
69
65
|
activate() {
|
|
70
66
|
this.#active = true;
|
|
71
67
|
}
|
|
72
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
73
68
|
cancel() {
|
|
74
69
|
this.#active = false;
|
|
75
70
|
this.#queued = false;
|
|
76
71
|
this.#generation += 1;
|
|
77
72
|
}
|
|
78
|
-
|
|
73
|
+
get pending() {
|
|
74
|
+
return this.#queued;
|
|
75
|
+
}
|
|
79
76
|
schedule() {
|
|
80
77
|
if (!this.#active || this.#queued) return;
|
|
81
78
|
this.#queued = true;
|
|
@@ -88,43 +85,76 @@ var MicrotaskCoalescer = class {
|
|
|
88
85
|
}
|
|
89
86
|
};
|
|
90
87
|
|
|
91
|
-
|
|
88
|
+
var MorphRenderWatcher = class {
|
|
89
|
+
#listeners = new ListenerSet();
|
|
90
|
+
#pass;
|
|
91
|
+
#element = null;
|
|
92
|
+
#onMorph = () => this.#pass.schedule();
|
|
93
|
+
constructor(run) {
|
|
94
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
95
|
+
}
|
|
96
|
+
observe(element) {
|
|
97
|
+
if (this.#element === element) return;
|
|
98
|
+
this.disconnect();
|
|
99
|
+
this.#element = element;
|
|
100
|
+
this.#pass.activate();
|
|
101
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
102
|
+
}
|
|
103
|
+
schedule() {
|
|
104
|
+
this.#pass.schedule();
|
|
105
|
+
}
|
|
106
|
+
get pending() {
|
|
107
|
+
return this.#pass.pending;
|
|
108
|
+
}
|
|
109
|
+
disconnect() {
|
|
110
|
+
this.#listeners.dispose();
|
|
111
|
+
this.#pass.cancel();
|
|
112
|
+
this.#element = null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
var NumberValueReader = class {
|
|
117
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
118
|
+
read(owner, name, raw, fallback, bounds) {
|
|
119
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
120
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
121
|
+
this.#lastRejected.delete(name);
|
|
122
|
+
return resolved;
|
|
123
|
+
}
|
|
124
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
125
|
+
const literal = owner.element.getAttribute(attribute);
|
|
126
|
+
if (literal === null) {
|
|
127
|
+
this.#lastRejected.delete(name);
|
|
128
|
+
return resolved;
|
|
129
|
+
}
|
|
130
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
131
|
+
this.#lastRejected.set(name, literal);
|
|
132
|
+
console.warn(
|
|
133
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return resolved;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
92
140
|
var TimerRegistry = class {
|
|
93
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
94
141
|
ids = /* @__PURE__ */ new Set();
|
|
95
|
-
/**
|
|
96
|
-
* Cancels a single tracked timer.
|
|
97
|
-
*
|
|
98
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
99
|
-
* registry), so callers can clear defensively without guarding.
|
|
100
|
-
*/
|
|
101
142
|
clear(id) {
|
|
102
143
|
if (this.ids.delete(id)) {
|
|
103
144
|
this.cancel(id);
|
|
104
145
|
}
|
|
105
146
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
108
|
-
* to guarantee no timer outlives the element.
|
|
109
|
-
*/
|
|
110
147
|
clearAll() {
|
|
111
148
|
for (const id of this.ids) {
|
|
112
149
|
this.cancel(id);
|
|
113
150
|
}
|
|
114
151
|
this.ids.clear();
|
|
115
152
|
}
|
|
116
|
-
/** Number of timers currently tracked (pending). */
|
|
117
153
|
get size() {
|
|
118
154
|
return this.ids.size;
|
|
119
155
|
}
|
|
120
156
|
};
|
|
121
157
|
var SafeTimeout = class extends TimerRegistry {
|
|
122
|
-
/**
|
|
123
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
124
|
-
*
|
|
125
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
126
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
127
|
-
*/
|
|
128
158
|
set(callback, delay) {
|
|
129
159
|
const id = this.schedule(() => {
|
|
130
160
|
this.ids.delete(id);
|
|
@@ -141,7 +171,6 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
141
171
|
}
|
|
142
172
|
};
|
|
143
173
|
|
|
144
|
-
// src/utils/string_list.ts
|
|
145
174
|
function parseStringList(raw, fallback = []) {
|
|
146
175
|
const text = raw.trim();
|
|
147
176
|
if (text.length === 0) return [...fallback];
|
|
@@ -155,7 +184,6 @@ function parseStringList(raw, fallback = []) {
|
|
|
155
184
|
return parsed.filter((entry) => typeof entry === "string");
|
|
156
185
|
}
|
|
157
186
|
|
|
158
|
-
// src/controllers/password_strength_controller.ts
|
|
159
187
|
var CLASS_PATTERNS = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/];
|
|
160
188
|
var DEFAULT_LEVELS = ["weak", "fair", "good", "strong"];
|
|
161
189
|
var LENGTH_MILESTONES = [8, 12, 16];
|
|
@@ -163,107 +191,85 @@ var MAX_POINTS = LENGTH_MILESTONES.length + (CLASS_PATTERNS.length - 1);
|
|
|
163
191
|
var STRENGTH_BANDS = ["weak", "fair", "good", "strong"];
|
|
164
192
|
var MIN_LEVELS = 2;
|
|
165
193
|
var PasswordStrengthController = class _PasswordStrengthController extends Controller {
|
|
194
|
+
#numbers = new NumberValueReader();
|
|
166
195
|
static targets = ["input", "meter", "label"];
|
|
167
196
|
static values = {
|
|
168
197
|
minScore: { type: Number, default: 0 },
|
|
169
|
-
// A JSON list read through `parseStringList` rather than Stimulus's `Array`
|
|
170
|
-
// type: that reader throws out of the value observer before any callback
|
|
171
|
-
// runs, so one malformed attribute would stop the meter connecting.
|
|
172
198
|
levels: { type: String, default: "" },
|
|
173
199
|
announceText: { type: String, default: "" }
|
|
174
200
|
};
|
|
201
|
+
static valueConstraints = {
|
|
202
|
+
minScore: NUMBER_BOUNDS.nonNegative
|
|
203
|
+
};
|
|
175
204
|
static actions = ["evaluate", "setScore"];
|
|
176
205
|
static events = ["change", "reconcile"];
|
|
177
|
-
/** Delay (ms) before one settled level is sent to the shared announcer. */
|
|
178
206
|
static #announceDelay = 200;
|
|
179
207
|
#timers = new SafeTimeout();
|
|
180
|
-
#
|
|
181
|
-
#repaint = new MicrotaskCoalescer(() => this.#reconcile());
|
|
208
|
+
#repaint = new MorphRenderWatcher(() => this.#reconcile());
|
|
182
209
|
#levels = [...DEFAULT_LEVELS];
|
|
183
210
|
#announceId = null;
|
|
184
211
|
#announcedLevel = null;
|
|
185
212
|
#externalScore = null;
|
|
186
213
|
#lastDetail = null;
|
|
187
|
-
/** Reflects the current DOM state and opens the reconciliation window. */
|
|
188
214
|
connect() {
|
|
189
|
-
this.#repaint.
|
|
190
|
-
this.#beforeCache.activate();
|
|
215
|
+
this.#repaint.observe(this.element);
|
|
191
216
|
this.#lastDetail = this.#detail(this.#render());
|
|
217
|
+
this.#repaint.schedule();
|
|
192
218
|
}
|
|
193
|
-
/** Releases the reconciliation window, the cache subscription and the debounce. */
|
|
194
219
|
disconnect() {
|
|
195
|
-
this.#repaint.
|
|
196
|
-
this.#beforeCache.deactivate();
|
|
220
|
+
this.#repaint.disconnect();
|
|
197
221
|
this.#cancelAnnouncement();
|
|
198
222
|
this.#announcedLevel = null;
|
|
199
223
|
this.#externalScore = null;
|
|
200
224
|
this.#lastDetail = null;
|
|
201
225
|
}
|
|
202
|
-
/** Re-evaluates strength from the input. Bound via `data-action` (`input`). */
|
|
203
226
|
evaluate() {
|
|
204
227
|
this.#externalScore = null;
|
|
205
228
|
this.#commit();
|
|
206
229
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Adopts a score computed outside the built-in heuristic, clamped into the
|
|
209
|
-
* declared scale. An unreadable value leaves the current score standing, and
|
|
210
|
-
* the next `evaluate` hands scoring back to the heuristic.
|
|
211
|
-
*/
|
|
212
230
|
setScore(event) {
|
|
213
231
|
const next = toFiniteNumber(event.params?.score ?? event.detail?.score);
|
|
214
232
|
if (next === null) return;
|
|
215
233
|
this.#externalScore = next;
|
|
216
234
|
this.#commit();
|
|
217
235
|
}
|
|
218
|
-
/** Re-reads the scale when application code or a Turbo morph changes `levels`. */
|
|
219
236
|
levelsValueChanged() {
|
|
220
237
|
this.#levels = this.#readLevels();
|
|
221
238
|
this.#repaint.schedule();
|
|
222
239
|
}
|
|
223
|
-
/** Repaints when application code or a Turbo morph changes `minScore`. */
|
|
224
240
|
minScoreValueChanged() {
|
|
225
241
|
this.#repaint.schedule();
|
|
226
242
|
}
|
|
227
|
-
/** Repaints when application code or a Turbo morph changes `announceText`. */
|
|
228
243
|
announceTextValueChanged() {
|
|
229
244
|
this.#repaint.schedule();
|
|
230
245
|
}
|
|
231
|
-
/** Scores the field added or replaced at runtime. */
|
|
232
246
|
inputTargetConnected() {
|
|
233
247
|
this.#repaint.schedule();
|
|
234
248
|
}
|
|
235
|
-
/** Falls back to the pristine state after the scored field is removed. */
|
|
236
249
|
inputTargetDisconnected() {
|
|
237
250
|
this.#repaint.schedule();
|
|
238
251
|
}
|
|
239
|
-
/** Syncs a meter added or replaced at runtime, which carries no value yet. */
|
|
240
252
|
meterTargetConnected() {
|
|
241
253
|
this.#repaint.schedule();
|
|
242
254
|
}
|
|
243
|
-
/** Repaints the remaining output after a meter is removed. */
|
|
244
255
|
meterTargetDisconnected() {
|
|
245
256
|
this.#repaint.schedule();
|
|
246
257
|
}
|
|
247
|
-
/** Fills a readout added or replaced at runtime. */
|
|
248
258
|
labelTargetConnected() {
|
|
249
259
|
this.#repaint.schedule();
|
|
250
260
|
}
|
|
251
|
-
/** Repaints the remaining output after a readout is removed. */
|
|
252
261
|
labelTargetDisconnected() {
|
|
253
262
|
this.#repaint.schedule();
|
|
254
263
|
}
|
|
255
|
-
/** Reflects one confirmed scoring pass and offers its level to the reader. */
|
|
256
264
|
#commit() {
|
|
257
265
|
const reading = this.#render();
|
|
258
266
|
this.#lastDetail = this.#detail(reading);
|
|
259
267
|
this.dispatch("change", { detail: this.#lastDetail });
|
|
260
268
|
this.#scheduleAnnouncement(reading);
|
|
261
269
|
}
|
|
262
|
-
/** Repaints one mutation batch and reports a changed controller-derived state. */
|
|
263
270
|
#reconcile() {
|
|
264
271
|
const previous = this.#lastDetail;
|
|
265
272
|
const owed = this.#announceId !== null;
|
|
266
|
-
this.#cancelAnnouncement();
|
|
267
273
|
const reading = this.#render();
|
|
268
274
|
const detail = this.#detail(reading);
|
|
269
275
|
this.#lastDetail = detail;
|
|
@@ -273,12 +279,6 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
273
279
|
this.dispatch("reconcile", { detail });
|
|
274
280
|
}
|
|
275
281
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Synchronizes the meter ARIA, the root state hooks, the fill custom property
|
|
278
|
-
* and the visible level readout.
|
|
279
|
-
*
|
|
280
|
-
* @stimeoRenderRoot
|
|
281
|
-
*/
|
|
282
282
|
#render() {
|
|
283
283
|
const max = this.#levels.length;
|
|
284
284
|
const score = this.#currentScore(max);
|
|
@@ -290,58 +290,35 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
290
290
|
this.#writeLabel(level);
|
|
291
291
|
return { score, level, max, meetsMin, band };
|
|
292
292
|
}
|
|
293
|
-
/**
|
|
294
|
-
* The declared minimum, or the Value's default (`0`, an inert gate) when the
|
|
295
|
-
* declaration cannot be read as a number. Every comparison against an unread
|
|
296
|
-
* number answers false, which would fail even the strongest password instead
|
|
297
|
-
* of leaving the gate off, so the unreadable declaration is confined to its
|
|
298
|
-
* own Value the way an unreadable scale is.
|
|
299
|
-
*/
|
|
300
293
|
get #minScore() {
|
|
301
|
-
return
|
|
294
|
+
return this.#safeMinScore;
|
|
302
295
|
}
|
|
303
|
-
/** The externally supplied score when one stands, else the heuristic's. */
|
|
304
296
|
#currentScore(max) {
|
|
305
297
|
if (this.#externalScore !== null) {
|
|
306
298
|
return Math.min(max, Math.max(0, Math.round(this.#externalScore)));
|
|
307
299
|
}
|
|
308
300
|
return this.#score(this.hasInputTarget ? this.inputTarget.value : "", max);
|
|
309
301
|
}
|
|
310
|
-
/** Syncs the meter target's ARIA value attributes (`0..levels.length`). */
|
|
311
302
|
#reflectMeter(score, max) {
|
|
312
303
|
if (!this.hasMeterTarget) return;
|
|
313
304
|
this.meterTarget.setAttribute("aria-valuemin", "0");
|
|
314
305
|
this.meterTarget.setAttribute("aria-valuemax", String(max));
|
|
315
306
|
this.meterTarget.setAttribute("aria-valuenow", String(score));
|
|
316
307
|
}
|
|
317
|
-
/**
|
|
318
|
-
* Reflects the level onto the root: the stable `data-strength` band (absent when
|
|
319
|
-
* empty), the `data-below-min` hook when the score is under `minScore`, and the
|
|
320
|
-
* `0–1` fill the consumer's CSS turns into the bar width.
|
|
321
|
-
*/
|
|
322
308
|
#reflectRoot(score, max, band, meetsMin) {
|
|
323
309
|
this.#toggle("data-strength", band, band.length > 0);
|
|
324
310
|
this.#toggle("data-below-min", "true", score > 0 && !meetsMin);
|
|
325
311
|
this.element.style.setProperty("--stimeo--password-strength", String(score / max));
|
|
326
312
|
}
|
|
327
|
-
/** Writes the visible readout only when its text actually changed. */
|
|
328
313
|
#writeLabel(text) {
|
|
329
314
|
if (!this.hasLabelTarget || this.labelTarget.textContent === text) return;
|
|
330
315
|
this.labelTarget.textContent = text;
|
|
331
316
|
}
|
|
332
|
-
/**
|
|
333
|
-
* Locale-independent styling band (one of {@link STRENGTH_BANDS}) for `score`
|
|
334
|
-
* out of `max`. Empty input → `""`. Maps the position within the declared scale
|
|
335
|
-
* onto the position within the fixed bands, so both ends stay anchored on any
|
|
336
|
-
* level count: the weakest score is always `weak` and the strongest `strong`,
|
|
337
|
-
* and only the middle collapses when the scales differ in size.
|
|
338
|
-
*/
|
|
339
317
|
#band(score, max) {
|
|
340
318
|
if (score <= 0) return "";
|
|
341
319
|
const index = Math.round((score - 1) / (max - 1) * (STRENGTH_BANDS.length - 1));
|
|
342
320
|
return STRENGTH_BANDS[index] ?? STRENGTH_BANDS[0];
|
|
343
321
|
}
|
|
344
|
-
/** Sets `name` to `value` when `on`, else removes it (value/presence data hook). */
|
|
345
322
|
#toggle(name, value, on) {
|
|
346
323
|
if (on) {
|
|
347
324
|
this.element.setAttribute(name, value);
|
|
@@ -349,19 +326,10 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
349
326
|
this.element.removeAttribute(name);
|
|
350
327
|
}
|
|
351
328
|
}
|
|
352
|
-
/** The declared level labels, or the defaults when the scale cannot order. */
|
|
353
329
|
#readLevels() {
|
|
354
330
|
const declared = parseStringList(this.levelsValue, DEFAULT_LEVELS);
|
|
355
331
|
return declared.length >= MIN_LEVELS ? declared : [...DEFAULT_LEVELS];
|
|
356
332
|
}
|
|
357
|
-
/**
|
|
358
|
-
* Lightweight zero-dependency strength heuristic returning an integer in
|
|
359
|
-
* `[0, max]` (`max` = number of levels). Empty input is `0` (no level); any
|
|
360
|
-
* non-empty password is at least `1`. Points accrue from length milestones and
|
|
361
|
-
* character-class variety, then bucket into the level scale. A tiny alphabet
|
|
362
|
-
* (≤ 2 distinct characters, e.g. "aaaa") is capped as the weakest, so length
|
|
363
|
-
* alone cannot mask trivial repetition.
|
|
364
|
-
*/
|
|
365
333
|
#score(password, max) {
|
|
366
334
|
if (password.length === 0) return 0;
|
|
367
335
|
let points = 0;
|
|
@@ -373,7 +341,6 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
373
341
|
const bucketed = Math.round(points / MAX_POINTS * max);
|
|
374
342
|
return Math.min(max, Math.max(1, bucketed));
|
|
375
343
|
}
|
|
376
|
-
/** Debounces one i18n-neutral message for a level transition into the announcer. */
|
|
377
344
|
#scheduleAnnouncement(reading) {
|
|
378
345
|
this.#cancelAnnouncement();
|
|
379
346
|
if (reading.score === 0) {
|
|
@@ -394,31 +361,10 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
394
361
|
this.#announceId = null;
|
|
395
362
|
}, _PasswordStrengthController.#announceDelay);
|
|
396
363
|
}
|
|
397
|
-
/** Cancels the one outstanding message without touching visible output. */
|
|
398
364
|
#cancelAnnouncement() {
|
|
399
365
|
if (this.#announceId !== null) this.#timers.clear(this.#announceId);
|
|
400
366
|
this.#announceId = null;
|
|
401
367
|
}
|
|
402
|
-
/**
|
|
403
|
-
* Returns every output derived from the field value to its pristine form.
|
|
404
|
-
*
|
|
405
|
-
* The value itself is not carried in a Turbo snapshot, so a band, a fill or a
|
|
406
|
-
* readout left behind would describe a password the restored page no longer
|
|
407
|
-
* holds. The pass is silent: `connect()` derives the state again from whatever
|
|
408
|
-
* the restored field contains.
|
|
409
|
-
*/
|
|
410
|
-
#rewindForCache() {
|
|
411
|
-
this.#cancelAnnouncement();
|
|
412
|
-
this.#announcedLevel = null;
|
|
413
|
-
this.#externalScore = null;
|
|
414
|
-
this.#lastDetail = null;
|
|
415
|
-
this.#reflectMeter(0, this.#levels.length);
|
|
416
|
-
this.#toggle("data-strength", "", false);
|
|
417
|
-
this.#toggle("data-below-min", "true", false);
|
|
418
|
-
this.element.style.removeProperty("--stimeo--password-strength");
|
|
419
|
-
this.#writeLabel("");
|
|
420
|
-
}
|
|
421
|
-
/** Selects the public event state from the richer internal reading. */
|
|
422
368
|
#detail(reading) {
|
|
423
369
|
return {
|
|
424
370
|
score: reading.score,
|
|
@@ -427,12 +373,19 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
427
373
|
meetsMin: reading.meetsMin
|
|
428
374
|
};
|
|
429
375
|
}
|
|
430
|
-
/** Compares exactly the state carried by `change` and `reconcile`. */
|
|
431
376
|
#detailsDiffer(left, right) {
|
|
432
377
|
return left.score !== right.score || left.level !== right.level || left.max !== right.max || left.meetsMin !== right.meetsMin;
|
|
433
378
|
}
|
|
379
|
+
get #safeMinScore() {
|
|
380
|
+
return this.#numbers.read(
|
|
381
|
+
this,
|
|
382
|
+
"minScore",
|
|
383
|
+
this.minScoreValue,
|
|
384
|
+
_PasswordStrengthController.values.minScore.default,
|
|
385
|
+
_PasswordStrengthController.valueConstraints.minScore
|
|
386
|
+
);
|
|
387
|
+
}
|
|
434
388
|
};
|
|
435
389
|
|
|
436
390
|
export { PasswordStrengthController };
|
|
437
|
-
//# sourceMappingURL=password_strength_controller.js.map
|
|
438
391
|
//# sourceMappingURL=password_strength_controller.js.map
|