stimeo-ui 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +184 -0
  3. data/dist/controllers/aspect_ratio_controller.js +19 -11
  4. data/dist/controllers/avatar_controller.js +195 -40
  5. data/dist/controllers/breadcrumb_controller.js +5 -1
  6. data/dist/controllers/carousel_controller.js +90 -10
  7. data/dist/controllers/checkbox_controller.js +136 -25
  8. data/dist/controllers/clipboard_controller.js +8 -3
  9. data/dist/controllers/collapsible_controller.js +4 -1
  10. data/dist/controllers/color_picker_controller.js +41 -11
  11. data/dist/controllers/context_menu_controller.js +2 -2
  12. data/dist/controllers/countdown_controller.js +5 -1
  13. data/dist/controllers/date_range_picker_controller.js +162 -31
  14. data/dist/controllers/direct_upload_controller.js +3 -3
  15. data/dist/controllers/empty_state_controller.js +107 -16
  16. data/dist/controllers/file_dropzone_controller.js +26 -3
  17. data/dist/controllers/flash_controller.js +161 -21
  18. data/dist/controllers/form_validation_controller.js +8 -2
  19. data/dist/controllers/frame_loading_controller.js +94 -22
  20. data/dist/controllers/highlight_controller.js +38 -1
  21. data/dist/controllers/idle_controller.js +39 -6
  22. data/dist/controllers/local_time_controller.js +2 -0
  23. data/dist/controllers/masonry_controller.js +1 -1
  24. data/dist/controllers/menubar_controller.js +5 -3
  25. data/dist/controllers/meter_controller.js +3 -1
  26. data/dist/controllers/multi_select_controller.js +460 -151
  27. data/dist/controllers/network_status_controller.js +1 -3
  28. data/dist/controllers/number_input_controller.js +455 -64
  29. data/dist/controllers/overflow_menu_controller.js +5 -1
  30. data/dist/controllers/pagination_controller.js +38 -1
  31. data/dist/controllers/password_strength_controller.js +21 -3
  32. data/dist/controllers/persist_controller.js +24 -5
  33. data/dist/controllers/pointer_drag_controller.js +10 -0
  34. data/dist/controllers/portal_controller.js +10 -0
  35. data/dist/controllers/progress_controller.js +7 -3
  36. data/dist/controllers/radio_group_controller.js +540 -56
  37. data/dist/controllers/range_slider_controller.js +385 -94
  38. data/dist/controllers/rating_controller.js +274 -89
  39. data/dist/controllers/relative_time_controller.js +2 -0
  40. data/dist/controllers/resizable_controller.js +33 -0
  41. data/dist/controllers/roving_controller.js +60 -5
  42. data/dist/controllers/scroll_area_controller.js +155 -23
  43. data/dist/controllers/scroll_visibility_controller.js +33 -0
  44. data/dist/controllers/separator_controller.js +13 -17
  45. data/dist/controllers/skeleton_controller.js +71 -3
  46. data/dist/controllers/slider_controller.js +325 -48
  47. data/dist/controllers/spinner_controller.js +18 -3
  48. data/dist/controllers/step_indicator_controller.js +3 -1
  49. data/dist/controllers/stepper_controller.js +2 -0
  50. data/dist/controllers/switch_controller.js +162 -18
  51. data/dist/controllers/tags_input_controller.js +356 -120
  52. data/dist/controllers/textarea_autosize_controller.js +1 -1
  53. data/dist/controllers/time_picker_controller.js +296 -104
  54. data/dist/controllers/toggle_group_controller.js +378 -55
  55. data/dist/controllers/toolbar_controller.js +5 -3
  56. data/dist/controllers/tree_view_controller.js +24 -4
  57. data/dist/index.js +3811 -1048
  58. data/lib/stimeo/ui/version.rb +1 -1
  59. metadata +2 -2
@@ -161,6 +161,8 @@ var SafeTimeout = class extends TimerRegistry {
161
161
  var TabindexLoan = class {
162
162
  #value;
163
163
  #lent = /* @__PURE__ */ new Set();
164
+ /** Returns live loans before Turbo can copy them into its page snapshot. */
165
+ #beforeCache = new BeforeCacheReset(() => this.returnAll());
164
166
  /**
165
167
  * @param value - the `tabindex` to lend. `"-1"` (the default) is
166
168
  * programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
@@ -174,6 +176,7 @@ var TabindexLoan = class {
174
176
  if (element.hasAttribute("tabindex")) return;
175
177
  element.setAttribute("tabindex", this.#value);
176
178
  this.#lent.add(element);
179
+ this.#beforeCache.activate();
177
180
  }
178
181
  /** Takes back every loan whose value is still the one that was lent. */
179
182
  returnAll() {
@@ -181,6 +184,7 @@ var TabindexLoan = class {
181
184
  if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
182
185
  }
183
186
  this.#lent.clear();
187
+ this.#beforeCache.deactivate();
184
188
  }
185
189
  };
186
190
 
@@ -190,7 +194,7 @@ var INDEX = "data-stimeo--overflow-menu-index";
190
194
  var BOUNDARY = "data-stimeo--overflow-menu-boundary";
191
195
  var SAVED_ROLE = "data-stimeo--overflow-menu-role";
192
196
  var SAVED_TABINDEX = "data-stimeo--overflow-menu-tabindex";
193
- var SAVED_MENU_TARGET = "data-stimeo--overflow-menu-menu-target";
197
+ var SAVED_MENU_TARGET = "data-stimeo--overflow-menu-saved-menu";
194
198
  var MENU_TARGET = "data-stimeo--menu-target";
195
199
  var BOOKKEEPING = [BANKED, INDEX, SAVED_ROLE, SAVED_TABINDEX, SAVED_MENU_TARGET];
196
200
  var OverflowMenuController = class extends Controller {
@@ -19,10 +19,41 @@ function canTakeFocus(element) {
19
19
  return !inheritsFieldsetDisabled(element);
20
20
  }
21
21
 
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
+ };
50
+
22
51
  // src/utils/tabindex_loan.ts
23
52
  var TabindexLoan = class {
24
53
  #value;
25
54
  #lent = /* @__PURE__ */ new Set();
55
+ /** Returns live loans before Turbo can copy them into its page snapshot. */
56
+ #beforeCache = new BeforeCacheReset(() => this.returnAll());
26
57
  /**
27
58
  * @param value - the `tabindex` to lend. `"-1"` (the default) is
28
59
  * programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
@@ -36,6 +67,7 @@ var TabindexLoan = class {
36
67
  if (element.hasAttribute("tabindex")) return;
37
68
  element.setAttribute("tabindex", this.#value);
38
69
  this.#lent.add(element);
70
+ this.#beforeCache.activate();
39
71
  }
40
72
  /** Takes back every loan whose value is still the one that was lent. */
41
73
  returnAll() {
@@ -43,6 +75,7 @@ var TabindexLoan = class {
43
75
  if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
44
76
  }
45
77
  this.#lent.clear();
78
+ this.#beforeCache.deactivate();
46
79
  }
47
80
  };
48
81
 
@@ -132,7 +165,11 @@ var PaginationController = class _PaginationController extends Controller {
132
165
  if (!Object.is(page, this.pageValue)) this.pageValue = page;
133
166
  this.#render();
134
167
  }
135
- /** Syncs `aria-current` on the page buttons and the prev/next `disabled` state. */
168
+ /**
169
+ * Syncs `aria-current` on the page buttons and the prev/next `disabled` state.
170
+ *
171
+ * @stimeoRenderRoot
172
+ */
136
173
  #render() {
137
174
  const page = this.#page;
138
175
  for (const button of this.pageTargets) {
@@ -55,8 +55,23 @@ var SafeTimeout = class extends TimerRegistry {
55
55
  }
56
56
  };
57
57
 
58
+ // src/utils/string_list.ts
59
+ function parseStringList(raw, fallback = []) {
60
+ const text = raw.trim();
61
+ if (text.length === 0) return [...fallback];
62
+ let parsed;
63
+ try {
64
+ parsed = JSON.parse(text);
65
+ } catch {
66
+ return [...fallback];
67
+ }
68
+ if (!Array.isArray(parsed)) return [...fallback];
69
+ return parsed.filter((entry) => typeof entry === "string");
70
+ }
71
+
58
72
  // src/controllers/password_strength_controller.ts
59
73
  var CLASS_PATTERNS = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/];
74
+ var DEFAULT_LEVELS = ["weak", "fair", "good", "strong"];
60
75
  var LENGTH_MILESTONES = [8, 12, 16];
61
76
  var MAX_POINTS = LENGTH_MILESTONES.length + (CLASS_PATTERNS.length - 1);
62
77
  var STRENGTH_BANDS = ["weak", "fair", "good", "strong"];
@@ -64,7 +79,10 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
64
79
  static targets = ["input", "meter", "label"];
65
80
  static values = {
66
81
  minScore: { type: Number, default: 0 },
67
- levels: { type: Array, default: ["weak", "fair", "good", "strong"] }
82
+ // A JSON list read through `parseStringList` rather than Stimulus's `Array`
83
+ // type: that reader throws out of the value observer before any callback
84
+ // runs, so one malformed attribute would stop the meter connecting.
85
+ levels: { type: String, default: "" }
68
86
  };
69
87
  static actions = ["evaluate"];
70
88
  static events = ["change"];
@@ -90,7 +108,7 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
90
108
  */
91
109
  #update(options = {}) {
92
110
  const password = this.hasInputTarget ? this.inputTarget.value : "";
93
- const labels = this.levelsValue;
111
+ const labels = parseStringList(this.levelsValue, DEFAULT_LEVELS);
94
112
  const max = labels.length;
95
113
  const score = this.#score(password, max);
96
114
  const label = score > 0 ? labels[score - 1] ?? "" : "";
@@ -126,7 +144,7 @@ var PasswordStrengthController = class _PasswordStrengthController extends Contr
126
144
  this.#toggle("data-strength", band, band.length > 0);
127
145
  this.#toggle("data-below-min", "true", score > 0 && score < this.minScoreValue);
128
146
  const ratio = max > 0 ? score / max : 0;
129
- this.element.style.setProperty("--stimeo-password-strength", String(ratio));
147
+ this.element.style.setProperty("--stimeo--password-strength", String(ratio));
130
148
  }
131
149
  #writeLabel(label) {
132
150
  if (this.hasLabelTarget) this.labelTarget.textContent = label;
@@ -55,8 +55,23 @@ var SafeTimeout = class extends TimerRegistry {
55
55
  }
56
56
  };
57
57
 
58
+ // src/utils/string_list.ts
59
+ function parseStringList(raw, fallback = []) {
60
+ const text = raw.trim();
61
+ if (text.length === 0) return [...fallback];
62
+ let parsed;
63
+ try {
64
+ parsed = JSON.parse(text);
65
+ } catch {
66
+ return [...fallback];
67
+ }
68
+ if (!Array.isArray(parsed)) return [...fallback];
69
+ return parsed.filter((entry) => typeof entry === "string");
70
+ }
71
+
58
72
  // src/controllers/persist_controller.ts
59
73
  var NON_VALUE_TYPES = /* @__PURE__ */ new Set(["file", "submit", "reset", "button", "image"]);
74
+ var DEFAULT_EXCLUDE = ["password"];
60
75
  var STORAGE_PREFIX = "stimeo--persist:";
61
76
  var OCCURRENCE_SEP = "\0";
62
77
  var PersistController = class extends Controller {
@@ -64,7 +79,10 @@ var PersistController = class extends Controller {
64
79
  static values = {
65
80
  key: { type: String, default: "" },
66
81
  debounce: { type: Number, default: 400 },
67
- exclude: { type: Array, default: ["password"] },
82
+ // A JSON list read through `parseStringList` rather than Stimulus's `Array`
83
+ // type: that reader throws out of the value observer before any callback
84
+ // runs, so one malformed attribute would stop the controller connecting.
85
+ exclude: { type: String, default: "" },
68
86
  clearOn: { type: String, default: "" }
69
87
  };
70
88
  static actions = ["clear"];
@@ -208,15 +226,16 @@ var PersistController = class extends Controller {
208
226
  /** The fields to persist: `field` targets, or the element's named controls. */
209
227
  #fields() {
210
228
  const candidates = this.hasFieldTarget ? this.fieldTargets : Array.from(this.element.querySelectorAll("input, textarea, select"));
211
- return candidates.filter((field) => this.#persistable(field));
229
+ const excluded = parseStringList(this.excludeValue, DEFAULT_EXCLUDE);
230
+ return candidates.filter((field) => this.#persistable(field, excluded));
212
231
  }
213
232
  /** Whether a field carries a restorable, non-excluded value. */
214
- #persistable(field) {
233
+ #persistable(field, excluded) {
215
234
  if (this.#keyOf(field) === null) return false;
216
235
  const type = field instanceof HTMLInputElement ? field.type : "";
217
236
  if (NON_VALUE_TYPES.has(type)) return false;
218
- if (this.excludeValue.includes(type)) return false;
219
- if (field.name.length > 0 && this.excludeValue.includes(field.name)) return false;
237
+ if (excluded.includes(type)) return false;
238
+ if (field.name.length > 0 && excluded.includes(field.name)) return false;
220
239
  return true;
221
240
  }
222
241
  /** A stable storage sub-key for a field (its name, else id). */
@@ -12,6 +12,16 @@ function isReservedArrowChord(event, allow = []) {
12
12
  var DetachGate = class _DetachGate {
13
13
  /** Set while a probe is queued, waiting for a reconnect to cancel it. */
14
14
  #pending = false;
15
+ /**
16
+ * True while a probe is queued — the last disconnect was ambiguous and no
17
+ * reconnect has cancelled it yet. Read it from `connect()` to tell the
18
+ * reconnect half of an in-page move from a first connect: a controller whose
19
+ * initialisation restarts a measurement (a min-duration floor, an elapsed
20
+ * counter) must skip it for the move, where nothing actually restarted.
21
+ */
22
+ get pending() {
23
+ return this.#pending;
24
+ }
15
25
  /**
16
26
  * True when the disconnect is definitely a real detach — the element left
17
27
  * the document, or `data-controller` no longer lists the identifier. False
@@ -6,6 +6,16 @@ import { Controller } from '@hotwired/stimulus';
6
6
  var DetachGate = class _DetachGate {
7
7
  /** Set while a probe is queued, waiting for a reconnect to cancel it. */
8
8
  #pending = false;
9
+ /**
10
+ * True while a probe is queued — the last disconnect was ambiguous and no
11
+ * reconnect has cancelled it yet. Read it from `connect()` to tell the
12
+ * reconnect half of an in-page move from a first connect: a controller whose
13
+ * initialisation restarts a measurement (a min-duration floor, an elapsed
14
+ * counter) must skip it for the move, where nothing actually restarted.
15
+ */
16
+ get pending() {
17
+ return this.#pending;
18
+ }
9
19
  /**
10
20
  * True when the disconnect is definitely a real detach — the element left
11
21
  * the document, or `data-controller` no longer lists the identifier. False
@@ -153,20 +153,24 @@ var ProgressController = class extends Controller {
153
153
  get #ratio() {
154
154
  return rangeFraction(this.valueValue, this.minValue, this.maxValue);
155
155
  }
156
- /** Reflects value/range/indeterminate onto ARIA, `data-state`, and the ratio. */
156
+ /**
157
+ * Reflects value/range/indeterminate onto ARIA, `data-state`, and the ratio.
158
+ *
159
+ * @stimeoRenderRoot
160
+ */
157
161
  #render() {
158
162
  this.element.setAttribute("aria-valuemin", String(this.minValue));
159
163
  this.element.setAttribute("aria-valuemax", String(this.maxValue));
160
164
  if (this.indeterminateValue) {
161
165
  this.element.removeAttribute("aria-valuenow");
162
166
  this.#clearOwnValueText();
163
- this.element.style.removeProperty("--stimeo-progress-ratio");
167
+ this.element.style.removeProperty("--stimeo--progress-ratio");
164
168
  this.element.setAttribute("data-state", "indeterminate");
165
169
  return;
166
170
  }
167
171
  const value = this.#clamp(this.valueValue);
168
172
  this.element.setAttribute("aria-valuenow", String(value));
169
- this.element.style.setProperty("--stimeo-progress-ratio", String(this.#ratio));
173
+ this.element.style.setProperty("--stimeo--progress-ratio", String(this.#ratio));
170
174
  this.element.setAttribute("data-state", "determinate");
171
175
  this.#applyValueText(value);
172
176
  }