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,8 +1,5 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/password_strength_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/announce.ts
|
|
6
3
|
function announce(message, options = {}) {
|
|
7
4
|
const text = message.trim();
|
|
8
5
|
if (text.length === 0) return;
|
|
@@ -19,63 +16,59 @@ function fillTemplate(template, values) {
|
|
|
19
16
|
});
|
|
20
17
|
}
|
|
21
18
|
|
|
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
|
-
};
|
|
19
|
+
var NUMBER_BOUNDS = {
|
|
20
|
+
nonNegative: { finite: true, min: 0 }};
|
|
21
|
+
function matchesNumberBounds(value, bounds) {
|
|
22
|
+
if (!Number.isFinite(value)) {
|
|
23
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
24
|
+
if (direction === null) return false;
|
|
25
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
26
|
+
}
|
|
27
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
28
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
29
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
30
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
31
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
50
34
|
|
|
51
|
-
// src/utils/coerce.ts
|
|
52
35
|
function toFiniteNumber(raw) {
|
|
53
|
-
if (
|
|
54
|
-
|
|
36
|
+
if (typeof raw !== "number" && typeof raw !== "string") return null;
|
|
37
|
+
if (typeof raw === "string" && raw.trim().length === 0) return null;
|
|
38
|
+
const value = Number(raw);
|
|
55
39
|
return Number.isFinite(value) ? value : null;
|
|
56
40
|
}
|
|
41
|
+
function readNumber(raw, fallback, bounds) {
|
|
42
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var ListenerSet = class {
|
|
46
|
+
#abort = new AbortController();
|
|
47
|
+
add(target, type, handler, options) {
|
|
48
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
49
|
+
}
|
|
50
|
+
dispose() {
|
|
51
|
+
this.#abort.abort();
|
|
52
|
+
this.#abort = new AbortController();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
57
55
|
|
|
58
|
-
// src/utils/microtask_coalescer.ts
|
|
59
56
|
var MicrotaskCoalescer = class {
|
|
60
57
|
#run;
|
|
61
58
|
#queued = false;
|
|
62
59
|
#active = false;
|
|
63
60
|
#generation = 0;
|
|
64
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
65
61
|
constructor(run) {
|
|
66
62
|
this.#run = run;
|
|
67
63
|
}
|
|
68
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
69
64
|
activate() {
|
|
70
65
|
this.#active = true;
|
|
71
66
|
}
|
|
72
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
73
67
|
cancel() {
|
|
74
68
|
this.#active = false;
|
|
75
69
|
this.#queued = false;
|
|
76
70
|
this.#generation += 1;
|
|
77
71
|
}
|
|
78
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
79
72
|
schedule() {
|
|
80
73
|
if (!this.#active || this.#queued) return;
|
|
81
74
|
this.#queued = true;
|
|
@@ -88,43 +81,73 @@ var MicrotaskCoalescer = class {
|
|
|
88
81
|
}
|
|
89
82
|
};
|
|
90
83
|
|
|
91
|
-
|
|
84
|
+
var MorphRenderWatcher = class {
|
|
85
|
+
#listeners = new ListenerSet();
|
|
86
|
+
#pass;
|
|
87
|
+
#element = null;
|
|
88
|
+
#onMorph = () => this.#pass.schedule();
|
|
89
|
+
constructor(run) {
|
|
90
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
91
|
+
}
|
|
92
|
+
observe(element) {
|
|
93
|
+
if (this.#element === element) return;
|
|
94
|
+
this.disconnect();
|
|
95
|
+
this.#element = element;
|
|
96
|
+
this.#pass.activate();
|
|
97
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
98
|
+
}
|
|
99
|
+
schedule() {
|
|
100
|
+
this.#pass.schedule();
|
|
101
|
+
}
|
|
102
|
+
disconnect() {
|
|
103
|
+
this.#listeners.dispose();
|
|
104
|
+
this.#pass.cancel();
|
|
105
|
+
this.#element = null;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
var NumberValueReader = class {
|
|
110
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
111
|
+
read(owner, name, raw, fallback, bounds) {
|
|
112
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
113
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
114
|
+
this.#lastRejected.delete(name);
|
|
115
|
+
return resolved;
|
|
116
|
+
}
|
|
117
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
118
|
+
const literal = owner.element.getAttribute(attribute);
|
|
119
|
+
if (literal === null) {
|
|
120
|
+
this.#lastRejected.delete(name);
|
|
121
|
+
return resolved;
|
|
122
|
+
}
|
|
123
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
124
|
+
this.#lastRejected.set(name, literal);
|
|
125
|
+
console.warn(
|
|
126
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return resolved;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
92
133
|
var TimerRegistry = class {
|
|
93
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
94
134
|
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
135
|
clear(id) {
|
|
102
136
|
if (this.ids.delete(id)) {
|
|
103
137
|
this.cancel(id);
|
|
104
138
|
}
|
|
105
139
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
108
|
-
* to guarantee no timer outlives the element.
|
|
109
|
-
*/
|
|
110
140
|
clearAll() {
|
|
111
141
|
for (const id of this.ids) {
|
|
112
142
|
this.cancel(id);
|
|
113
143
|
}
|
|
114
144
|
this.ids.clear();
|
|
115
145
|
}
|
|
116
|
-
/** Number of timers currently tracked (pending). */
|
|
117
146
|
get size() {
|
|
118
147
|
return this.ids.size;
|
|
119
148
|
}
|
|
120
149
|
};
|
|
121
150
|
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
151
|
set(callback, delay) {
|
|
129
152
|
const id = this.schedule(() => {
|
|
130
153
|
this.ids.delete(id);
|
|
@@ -141,7 +164,6 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
141
164
|
}
|
|
142
165
|
};
|
|
143
166
|
|
|
144
|
-
// src/utils/string_list.ts
|
|
145
167
|
function parseStringList(raw, fallback = []) {
|
|
146
168
|
const text = raw.trim();
|
|
147
169
|
if (text.length === 0) return [...fallback];
|
|
@@ -155,7 +177,6 @@ function parseStringList(raw, fallback = []) {
|
|
|
155
177
|
return parsed.filter((entry) => typeof entry === "string");
|
|
156
178
|
}
|
|
157
179
|
|
|
158
|
-
// src/controllers/password_strength_controller.ts
|
|
159
180
|
var CLASS_PATTERNS = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/];
|
|
160
181
|
var DEFAULT_LEVELS = ["weak", "fair", "good", "strong"];
|
|
161
182
|
var LENGTH_MILESTONES = [8, 12, 16];
|
|
@@ -163,107 +184,85 @@ var MAX_POINTS = LENGTH_MILESTONES.length + (CLASS_PATTERNS.length - 1);
|
|
|
163
184
|
var STRENGTH_BANDS = ["weak", "fair", "good", "strong"];
|
|
164
185
|
var MIN_LEVELS = 2;
|
|
165
186
|
var PasswordStrengthController = class _PasswordStrengthController extends Controller {
|
|
187
|
+
#numbers = new NumberValueReader();
|
|
166
188
|
static targets = ["input", "meter", "label"];
|
|
167
189
|
static values = {
|
|
168
190
|
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
191
|
levels: { type: String, default: "" },
|
|
173
192
|
announceText: { type: String, default: "" }
|
|
174
193
|
};
|
|
194
|
+
static valueConstraints = {
|
|
195
|
+
minScore: NUMBER_BOUNDS.nonNegative
|
|
196
|
+
};
|
|
175
197
|
static actions = ["evaluate", "setScore"];
|
|
176
198
|
static events = ["change", "reconcile"];
|
|
177
|
-
/** Delay (ms) before one settled level is sent to the shared announcer. */
|
|
178
199
|
static #announceDelay = 200;
|
|
179
200
|
#timers = new SafeTimeout();
|
|
180
|
-
#
|
|
181
|
-
#repaint = new MicrotaskCoalescer(() => this.#reconcile());
|
|
201
|
+
#repaint = new MorphRenderWatcher(() => this.#reconcile());
|
|
182
202
|
#levels = [...DEFAULT_LEVELS];
|
|
183
203
|
#announceId = null;
|
|
184
204
|
#announcedLevel = null;
|
|
185
205
|
#externalScore = null;
|
|
186
206
|
#lastDetail = null;
|
|
187
|
-
/** Reflects the current DOM state and opens the reconciliation window. */
|
|
188
207
|
connect() {
|
|
189
|
-
this.#repaint.
|
|
190
|
-
this.#beforeCache.activate();
|
|
208
|
+
this.#repaint.observe(this.element);
|
|
191
209
|
this.#lastDetail = this.#detail(this.#render());
|
|
210
|
+
this.#repaint.schedule();
|
|
192
211
|
}
|
|
193
|
-
/** Releases the reconciliation window, the cache subscription and the debounce. */
|
|
194
212
|
disconnect() {
|
|
195
|
-
this.#repaint.
|
|
196
|
-
this.#beforeCache.deactivate();
|
|
213
|
+
this.#repaint.disconnect();
|
|
197
214
|
this.#cancelAnnouncement();
|
|
198
215
|
this.#announcedLevel = null;
|
|
199
216
|
this.#externalScore = null;
|
|
200
217
|
this.#lastDetail = null;
|
|
201
218
|
}
|
|
202
|
-
/** Re-evaluates strength from the input. Bound via `data-action` (`input`). */
|
|
203
219
|
evaluate() {
|
|
204
220
|
this.#externalScore = null;
|
|
205
221
|
this.#commit();
|
|
206
222
|
}
|
|
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
223
|
setScore(event) {
|
|
213
224
|
const next = toFiniteNumber(event.params?.score ?? event.detail?.score);
|
|
214
225
|
if (next === null) return;
|
|
215
226
|
this.#externalScore = next;
|
|
216
227
|
this.#commit();
|
|
217
228
|
}
|
|
218
|
-
/** Re-reads the scale when application code or a Turbo morph changes `levels`. */
|
|
219
229
|
levelsValueChanged() {
|
|
220
230
|
this.#levels = this.#readLevels();
|
|
221
231
|
this.#repaint.schedule();
|
|
222
232
|
}
|
|
223
|
-
/** Repaints when application code or a Turbo morph changes `minScore`. */
|
|
224
233
|
minScoreValueChanged() {
|
|
225
234
|
this.#repaint.schedule();
|
|
226
235
|
}
|
|
227
|
-
/** Repaints when application code or a Turbo morph changes `announceText`. */
|
|
228
236
|
announceTextValueChanged() {
|
|
229
237
|
this.#repaint.schedule();
|
|
230
238
|
}
|
|
231
|
-
/** Scores the field added or replaced at runtime. */
|
|
232
239
|
inputTargetConnected() {
|
|
233
240
|
this.#repaint.schedule();
|
|
234
241
|
}
|
|
235
|
-
/** Falls back to the pristine state after the scored field is removed. */
|
|
236
242
|
inputTargetDisconnected() {
|
|
237
243
|
this.#repaint.schedule();
|
|
238
244
|
}
|
|
239
|
-
/** Syncs a meter added or replaced at runtime, which carries no value yet. */
|
|
240
245
|
meterTargetConnected() {
|
|
241
246
|
this.#repaint.schedule();
|
|
242
247
|
}
|
|
243
|
-
/** Repaints the remaining output after a meter is removed. */
|
|
244
248
|
meterTargetDisconnected() {
|
|
245
249
|
this.#repaint.schedule();
|
|
246
250
|
}
|
|
247
|
-
/** Fills a readout added or replaced at runtime. */
|
|
248
251
|
labelTargetConnected() {
|
|
249
252
|
this.#repaint.schedule();
|
|
250
253
|
}
|
|
251
|
-
/** Repaints the remaining output after a readout is removed. */
|
|
252
254
|
labelTargetDisconnected() {
|
|
253
255
|
this.#repaint.schedule();
|
|
254
256
|
}
|
|
255
|
-
/** Reflects one confirmed scoring pass and offers its level to the reader. */
|
|
256
257
|
#commit() {
|
|
257
258
|
const reading = this.#render();
|
|
258
259
|
this.#lastDetail = this.#detail(reading);
|
|
259
260
|
this.dispatch("change", { detail: this.#lastDetail });
|
|
260
261
|
this.#scheduleAnnouncement(reading);
|
|
261
262
|
}
|
|
262
|
-
/** Repaints one mutation batch and reports a changed controller-derived state. */
|
|
263
263
|
#reconcile() {
|
|
264
264
|
const previous = this.#lastDetail;
|
|
265
265
|
const owed = this.#announceId !== null;
|
|
266
|
-
this.#cancelAnnouncement();
|
|
267
266
|
const reading = this.#render();
|
|
268
267
|
const detail = this.#detail(reading);
|
|
269
268
|
this.#lastDetail = detail;
|
|
@@ -273,12 +272,6 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
273
272
|
this.dispatch("reconcile", { detail });
|
|
274
273
|
}
|
|
275
274
|
}
|
|
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
275
|
#render() {
|
|
283
276
|
const max = this.#levels.length;
|
|
284
277
|
const score = this.#currentScore(max);
|
|
@@ -290,58 +283,35 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
290
283
|
this.#writeLabel(level);
|
|
291
284
|
return { score, level, max, meetsMin, band };
|
|
292
285
|
}
|
|
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
286
|
get #minScore() {
|
|
301
|
-
return
|
|
287
|
+
return this.#safeMinScore;
|
|
302
288
|
}
|
|
303
|
-
/** The externally supplied score when one stands, else the heuristic's. */
|
|
304
289
|
#currentScore(max) {
|
|
305
290
|
if (this.#externalScore !== null) {
|
|
306
291
|
return Math.min(max, Math.max(0, Math.round(this.#externalScore)));
|
|
307
292
|
}
|
|
308
293
|
return this.#score(this.hasInputTarget ? this.inputTarget.value : "", max);
|
|
309
294
|
}
|
|
310
|
-
/** Syncs the meter target's ARIA value attributes (`0..levels.length`). */
|
|
311
295
|
#reflectMeter(score, max) {
|
|
312
296
|
if (!this.hasMeterTarget) return;
|
|
313
297
|
this.meterTarget.setAttribute("aria-valuemin", "0");
|
|
314
298
|
this.meterTarget.setAttribute("aria-valuemax", String(max));
|
|
315
299
|
this.meterTarget.setAttribute("aria-valuenow", String(score));
|
|
316
300
|
}
|
|
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
301
|
#reflectRoot(score, max, band, meetsMin) {
|
|
323
302
|
this.#toggle("data-strength", band, band.length > 0);
|
|
324
303
|
this.#toggle("data-below-min", "true", score > 0 && !meetsMin);
|
|
325
304
|
this.element.style.setProperty("--stimeo--password-strength", String(score / max));
|
|
326
305
|
}
|
|
327
|
-
/** Writes the visible readout only when its text actually changed. */
|
|
328
306
|
#writeLabel(text) {
|
|
329
307
|
if (!this.hasLabelTarget || this.labelTarget.textContent === text) return;
|
|
330
308
|
this.labelTarget.textContent = text;
|
|
331
309
|
}
|
|
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
310
|
#band(score, max) {
|
|
340
311
|
if (score <= 0) return "";
|
|
341
312
|
const index = Math.round((score - 1) / (max - 1) * (STRENGTH_BANDS.length - 1));
|
|
342
313
|
return STRENGTH_BANDS[index] ?? STRENGTH_BANDS[0];
|
|
343
314
|
}
|
|
344
|
-
/** Sets `name` to `value` when `on`, else removes it (value/presence data hook). */
|
|
345
315
|
#toggle(name, value, on) {
|
|
346
316
|
if (on) {
|
|
347
317
|
this.element.setAttribute(name, value);
|
|
@@ -349,19 +319,10 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
349
319
|
this.element.removeAttribute(name);
|
|
350
320
|
}
|
|
351
321
|
}
|
|
352
|
-
/** The declared level labels, or the defaults when the scale cannot order. */
|
|
353
322
|
#readLevels() {
|
|
354
323
|
const declared = parseStringList(this.levelsValue, DEFAULT_LEVELS);
|
|
355
324
|
return declared.length >= MIN_LEVELS ? declared : [...DEFAULT_LEVELS];
|
|
356
325
|
}
|
|
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
326
|
#score(password, max) {
|
|
366
327
|
if (password.length === 0) return 0;
|
|
367
328
|
let points = 0;
|
|
@@ -373,7 +334,6 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
373
334
|
const bucketed = Math.round(points / MAX_POINTS * max);
|
|
374
335
|
return Math.min(max, Math.max(1, bucketed));
|
|
375
336
|
}
|
|
376
|
-
/** Debounces one i18n-neutral message for a level transition into the announcer. */
|
|
377
337
|
#scheduleAnnouncement(reading) {
|
|
378
338
|
this.#cancelAnnouncement();
|
|
379
339
|
if (reading.score === 0) {
|
|
@@ -394,31 +354,10 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
394
354
|
this.#announceId = null;
|
|
395
355
|
}, _PasswordStrengthController.#announceDelay);
|
|
396
356
|
}
|
|
397
|
-
/** Cancels the one outstanding message without touching visible output. */
|
|
398
357
|
#cancelAnnouncement() {
|
|
399
358
|
if (this.#announceId !== null) this.#timers.clear(this.#announceId);
|
|
400
359
|
this.#announceId = null;
|
|
401
360
|
}
|
|
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
361
|
#detail(reading) {
|
|
423
362
|
return {
|
|
424
363
|
score: reading.score,
|
|
@@ -427,12 +366,19 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
|
|
|
427
366
|
meetsMin: reading.meetsMin
|
|
428
367
|
};
|
|
429
368
|
}
|
|
430
|
-
/** Compares exactly the state carried by `change` and `reconcile`. */
|
|
431
369
|
#detailsDiffer(left, right) {
|
|
432
370
|
return left.score !== right.score || left.level !== right.level || left.max !== right.max || left.meetsMin !== right.meetsMin;
|
|
433
371
|
}
|
|
372
|
+
get #safeMinScore() {
|
|
373
|
+
return this.#numbers.read(
|
|
374
|
+
this,
|
|
375
|
+
"minScore",
|
|
376
|
+
this.minScoreValue,
|
|
377
|
+
_PasswordStrengthController.values.minScore.default,
|
|
378
|
+
_PasswordStrengthController.valueConstraints.minScore
|
|
379
|
+
);
|
|
380
|
+
}
|
|
434
381
|
};
|
|
435
382
|
|
|
436
383
|
export { PasswordStrengthController };
|
|
437
|
-
//# sourceMappingURL=password_strength_controller.js.map
|
|
438
384
|
//# sourceMappingURL=password_strength_controller.js.map
|