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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +8 -1
  4. data/dist/cable/index.js +363 -436
  5. data/dist/controllers/accordion_controller.js +166 -55
  6. data/dist/controllers/alert_dialog_controller.js +862 -221
  7. data/dist/controllers/announcer_controller.js +135 -198
  8. data/dist/controllers/aspect_ratio_controller.js +0 -10
  9. data/dist/controllers/auto_submit_controller.js +271 -127
  10. data/dist/controllers/avatar_controller.js +171 -105
  11. data/dist/controllers/breadcrumb_controller.js +0 -130
  12. data/dist/controllers/bulk_select_controller.js +262 -52
  13. data/dist/controllers/calendar_controller.js +175 -209
  14. data/dist/controllers/carousel_controller.js +461 -399
  15. data/dist/controllers/character_counter_controller.js +246 -156
  16. data/dist/controllers/checkbox_controller.js +32 -74
  17. data/dist/controllers/clipboard_controller.js +168 -147
  18. data/dist/controllers/collapsible_controller.js +301 -120
  19. data/dist/controllers/color_picker_controller.js +149 -139
  20. data/dist/controllers/combobox_controller.js +360 -133
  21. data/dist/controllers/command_palette_controller.js +970 -358
  22. data/dist/controllers/conditional_fields_controller.js +166 -124
  23. data/dist/controllers/confirm_controller.js +939 -248
  24. data/dist/controllers/context_menu_controller.js +256 -128
  25. data/dist/controllers/count_up_controller.js +73 -21
  26. data/dist/controllers/countdown_controller.js +183 -148
  27. data/dist/controllers/currency_input_controller.js +134 -158
  28. data/dist/controllers/data_grid_controller.js +114 -186
  29. data/dist/controllers/date_range_picker_controller.js +288 -206
  30. data/dist/controllers/dialog_controller.js +858 -223
  31. data/dist/controllers/direct_upload_controller.js +73 -153
  32. data/dist/controllers/dirty_form_controller.js +14 -48
  33. data/dist/controllers/dismissible_controller.js +175 -21
  34. data/dist/controllers/drawer_controller.js +933 -341
  35. data/dist/controllers/dropdown_controller.js +312 -82
  36. data/dist/controllers/editable_controller.js +158 -68
  37. data/dist/controllers/empty_state_controller.js +67 -53
  38. data/dist/controllers/file_dropzone_controller.js +311 -207
  39. data/dist/controllers/filter_controller.js +67 -39
  40. data/dist/controllers/flash_controller.js +172 -314
  41. data/dist/controllers/focus_controller.js +654 -240
  42. data/dist/controllers/form_field_controller.js +147 -132
  43. data/dist/controllers/form_validation_controller.js +13 -86
  44. data/dist/controllers/frame_loading_controller.js +264 -213
  45. data/dist/controllers/highlight_controller.js +107 -75
  46. data/dist/controllers/hover_card_controller.js +309 -118
  47. data/dist/controllers/idle_controller.js +267 -101
  48. data/dist/controllers/input_mask_controller.js +71 -72
  49. data/dist/controllers/intersection_controller.js +147 -106
  50. data/dist/controllers/lazy_frame_controller.js +9 -65
  51. data/dist/controllers/listbox_controller.js +306 -204
  52. data/dist/controllers/local_time_controller.js +54 -64
  53. data/dist/controllers/masonry_controller.js +70 -93
  54. data/dist/controllers/menu_controller.js +214 -156
  55. data/dist/controllers/menubar_controller.js +170 -329
  56. data/dist/controllers/meter_controller.js +151 -67
  57. data/dist/controllers/multi_select_controller.js +593 -358
  58. data/dist/controllers/navigation_menu_controller.js +124 -232
  59. data/dist/controllers/nested_form_controller.js +352 -182
  60. data/dist/controllers/network_status_controller.js +242 -54
  61. data/dist/controllers/number_input_controller.js +320 -263
  62. data/dist/controllers/optimistic_controller.js +16 -76
  63. data/dist/controllers/otp_controller.js +198 -193
  64. data/dist/controllers/overflow_indicator_controller.js +116 -113
  65. data/dist/controllers/overflow_menu_controller.js +343 -283
  66. data/dist/controllers/pagination_controller.js +295 -119
  67. data/dist/controllers/password_reveal_controller.js +389 -121
  68. data/dist/controllers/password_strength_controller.js +99 -153
  69. data/dist/controllers/persist_controller.js +237 -103
  70. data/dist/controllers/pointer_drag_controller.js +185 -165
  71. data/dist/controllers/popover_controller.js +236 -97
  72. data/dist/controllers/portal_controller.js +209 -76
  73. data/dist/controllers/preview_guard_controller.js +232 -116
  74. data/dist/controllers/progress_controller.js +125 -56
  75. data/dist/controllers/radio_group_controller.js +117 -137
  76. data/dist/controllers/range_slider_controller.js +192 -105
  77. data/dist/controllers/rating_controller.js +418 -207
  78. data/dist/controllers/read_more_controller.js +134 -63
  79. data/dist/controllers/reading_progress_controller.js +147 -135
  80. data/dist/controllers/relative_time_controller.js +145 -110
  81. data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  82. data/dist/controllers/resizable_controller.js +255 -139
  83. data/dist/controllers/roving_controller.js +39 -84
  84. data/dist/controllers/scroll_area_controller.js +428 -225
  85. data/dist/controllers/scroll_restore_controller.js +0 -53
  86. data/dist/controllers/scroll_visibility_controller.js +395 -145
  87. data/dist/controllers/scrollspy_controller.js +181 -242
  88. data/dist/controllers/separator_controller.js +267 -140
  89. data/dist/controllers/sidebar_controller.js +1023 -386
  90. data/dist/controllers/skeleton_controller.js +248 -97
  91. data/dist/controllers/slider_controller.js +159 -79
  92. data/dist/controllers/smart_sticky_header_controller.js +162 -63
  93. data/dist/controllers/sortable_controller.js +0 -112
  94. data/dist/controllers/spinner_controller.js +277 -169
  95. data/dist/controllers/step_indicator_controller.js +99 -46
  96. data/dist/controllers/stepper_controller.js +114 -73
  97. data/dist/controllers/stick_to_bottom_controller.js +262 -138
  98. data/dist/controllers/sticky_observer_controller.js +214 -49
  99. data/dist/controllers/submit_once_controller.js +294 -205
  100. data/dist/controllers/switch_controller.js +33 -60
  101. data/dist/controllers/tabs_controller.js +35 -29
  102. data/dist/controllers/tags_input_controller.js +136 -145
  103. data/dist/controllers/textarea_autosize_controller.js +71 -58
  104. data/dist/controllers/theme_controller.js +130 -112
  105. data/dist/controllers/time_picker_controller.js +146 -80
  106. data/dist/controllers/toast_controller.js +186 -297
  107. data/dist/controllers/toggle_group_controller.js +95 -136
  108. data/dist/controllers/toolbar_controller.js +68 -98
  109. data/dist/controllers/tooltip_controller.js +158 -124
  110. data/dist/controllers/transition_controller.js +123 -119
  111. data/dist/controllers/tree_view_controller.js +150 -345
  112. data/dist/index.js +6370 -9346
  113. data/dist/positioning/index.js +148 -47
  114. data/lib/stimeo/ui/version.rb +1 -1
  115. 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
- // src/utils/before_cache_reset.ts
23
- var BeforeCacheReset = class _BeforeCacheReset {
24
- /** Every subscribed instance, iterated by the one shared document listener. */
25
- static #subscribers = /* @__PURE__ */ new Set();
26
- /** The shared listener; installed while at least one instance is subscribed. */
27
- static #onBeforeCache = () => {
28
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
29
- };
30
- #rewind;
31
- /** @param rewind - the pass that returns this controller's state to its initial form. */
32
- constructor(rewind) {
33
- this.#rewind = rewind;
34
- }
35
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
36
- activate() {
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 (raw === null || raw === void 0 || raw === "") return null;
54
- const value = typeof raw === "number" ? raw : Number(raw);
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
- // src/utils/safe_timeout.ts
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
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
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.activate();
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.cancel();
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 Number.isFinite(this.minScoreValue) ? this.minScoreValue : 0;
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