stimeo-ui 0.15.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 +209 -0
  3. data/README.md +128 -1
  4. data/dist/cable/index.js +392 -417
  5. data/dist/controllers/accordion_controller.js +228 -28
  6. data/dist/controllers/alert_dialog_controller.js +862 -221
  7. data/dist/controllers/announcer_controller.js +135 -191
  8. data/dist/controllers/aspect_ratio_controller.js +0 -10
  9. data/dist/controllers/auto_submit_controller.js +295 -110
  10. data/dist/controllers/avatar_controller.js +172 -106
  11. data/dist/controllers/breadcrumb_controller.js +0 -129
  12. data/dist/controllers/bulk_select_controller.js +264 -53
  13. data/dist/controllers/calendar_controller.js +346 -180
  14. data/dist/controllers/carousel_controller.js +587 -300
  15. data/dist/controllers/character_counter_controller.js +271 -143
  16. data/dist/controllers/checkbox_controller.js +77 -75
  17. data/dist/controllers/clipboard_controller.js +187 -108
  18. data/dist/controllers/collapsible_controller.js +374 -108
  19. data/dist/controllers/color_picker_controller.js +183 -127
  20. data/dist/controllers/combobox_controller.js +426 -109
  21. data/dist/controllers/command_palette_controller.js +994 -350
  22. data/dist/controllers/conditional_fields_controller.js +212 -125
  23. data/dist/controllers/confirm_controller.js +939 -245
  24. data/dist/controllers/context_menu_controller.js +275 -127
  25. data/dist/controllers/count_up_controller.js +73 -21
  26. data/dist/controllers/countdown_controller.js +235 -97
  27. data/dist/controllers/currency_input_controller.js +241 -118
  28. data/dist/controllers/data_grid_controller.js +229 -135
  29. data/dist/controllers/date_range_picker_controller.js +359 -165
  30. data/dist/controllers/dialog_controller.js +887 -225
  31. data/dist/controllers/direct_upload_controller.js +80 -142
  32. data/dist/controllers/dirty_form_controller.js +26 -47
  33. data/dist/controllers/dismissible_controller.js +175 -20
  34. data/dist/controllers/drawer_controller.js +963 -336
  35. data/dist/controllers/dropdown_controller.js +342 -85
  36. data/dist/controllers/editable_controller.js +182 -58
  37. data/dist/controllers/empty_state_controller.js +67 -53
  38. data/dist/controllers/file_dropzone_controller.js +426 -229
  39. data/dist/controllers/filter_controller.js +78 -36
  40. data/dist/controllers/flash_controller.js +328 -211
  41. data/dist/controllers/focus_controller.js +654 -239
  42. data/dist/controllers/form_field_controller.js +153 -136
  43. data/dist/controllers/form_validation_controller.js +29 -96
  44. data/dist/controllers/frame_loading_controller.js +263 -198
  45. data/dist/controllers/highlight_controller.js +119 -70
  46. data/dist/controllers/hover_card_controller.js +335 -118
  47. data/dist/controllers/idle_controller.js +316 -65
  48. data/dist/controllers/input_mask_controller.js +116 -61
  49. data/dist/controllers/intersection_controller.js +147 -103
  50. data/dist/controllers/lazy_frame_controller.js +9 -56
  51. data/dist/controllers/listbox_controller.js +413 -153
  52. data/dist/controllers/local_time_controller.js +54 -64
  53. data/dist/controllers/masonry_controller.js +70 -95
  54. data/dist/controllers/menu_controller.js +254 -167
  55. data/dist/controllers/menubar_controller.js +206 -331
  56. data/dist/controllers/meter_controller.js +159 -71
  57. data/dist/controllers/multi_select_controller.js +797 -388
  58. data/dist/controllers/navigation_menu_controller.js +156 -231
  59. data/dist/controllers/nested_form_controller.js +373 -174
  60. data/dist/controllers/network_status_controller.js +241 -45
  61. data/dist/controllers/number_input_controller.js +387 -227
  62. data/dist/controllers/optimistic_controller.js +28 -75
  63. data/dist/controllers/otp_controller.js +344 -196
  64. data/dist/controllers/overflow_indicator_controller.js +167 -93
  65. data/dist/controllers/overflow_menu_controller.js +408 -277
  66. data/dist/controllers/pagination_controller.js +342 -120
  67. data/dist/controllers/password_reveal_controller.js +424 -99
  68. data/dist/controllers/password_strength_controller.js +99 -153
  69. data/dist/controllers/persist_controller.js +252 -96
  70. data/dist/controllers/pointer_drag_controller.js +263 -164
  71. data/dist/controllers/popover_controller.js +267 -94
  72. data/dist/controllers/portal_controller.js +209 -72
  73. data/dist/controllers/preview_guard_controller.js +243 -112
  74. data/dist/controllers/progress_controller.js +132 -59
  75. data/dist/controllers/radio_group_controller.js +138 -133
  76. data/dist/controllers/range_slider_controller.js +245 -112
  77. data/dist/controllers/rating_controller.js +429 -194
  78. data/dist/controllers/read_more_controller.js +185 -47
  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 +294 -118
  83. data/dist/controllers/roving_controller.js +39 -84
  84. data/dist/controllers/scroll_area_controller.js +428 -221
  85. data/dist/controllers/scroll_restore_controller.js +0 -53
  86. data/dist/controllers/scroll_visibility_controller.js +395 -146
  87. data/dist/controllers/scrollspy_controller.js +181 -236
  88. data/dist/controllers/separator_controller.js +288 -132
  89. data/dist/controllers/sidebar_controller.js +1071 -375
  90. data/dist/controllers/skeleton_controller.js +248 -92
  91. data/dist/controllers/slider_controller.js +211 -96
  92. data/dist/controllers/smart_sticky_header_controller.js +163 -54
  93. data/dist/controllers/sortable_controller.js +13 -110
  94. data/dist/controllers/spinner_controller.js +277 -161
  95. data/dist/controllers/step_indicator_controller.js +107 -53
  96. data/dist/controllers/stepper_controller.js +171 -48
  97. data/dist/controllers/stick_to_bottom_controller.js +310 -116
  98. data/dist/controllers/sticky_observer_controller.js +214 -49
  99. data/dist/controllers/submit_once_controller.js +294 -192
  100. data/dist/controllers/switch_controller.js +107 -43
  101. data/dist/controllers/tabs_controller.js +50 -25
  102. data/dist/controllers/tags_input_controller.js +290 -149
  103. data/dist/controllers/textarea_autosize_controller.js +71 -58
  104. data/dist/controllers/theme_controller.js +148 -80
  105. data/dist/controllers/time_picker_controller.js +162 -81
  106. data/dist/controllers/toast_controller.js +365 -193
  107. data/dist/controllers/toggle_group_controller.js +194 -99
  108. data/dist/controllers/toolbar_controller.js +92 -90
  109. data/dist/controllers/tooltip_controller.js +185 -125
  110. data/dist/controllers/transition_controller.js +123 -115
  111. data/dist/controllers/tree_view_controller.js +248 -290
  112. data/dist/index.js +8649 -8572
  113. data/dist/positioning/index.js +148 -45
  114. data/lib/stimeo/ui/version.rb +1 -1
  115. metadata +3 -3
@@ -1,20 +1,21 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/tooltip_controller.ts
3
+ function sharedRegistry(key, create) {
4
+ const symbol = Symbol.for(key);
5
+ const scope = globalThis;
6
+ const existing = scope[symbol];
7
+ if (existing !== void 0) return existing;
8
+ const registry = create();
9
+ Object.defineProperty(globalThis, symbol, { value: registry });
10
+ return registry;
11
+ }
4
12
 
5
- // src/utils/escape_layer.ts
6
13
  var EscapeLayer = class _EscapeLayer {
7
- static #registries = /* @__PURE__ */ new WeakMap();
8
- #ownerDocument = null;
9
- /** Dismissal callback while active; `null` when inactive. */
10
- #onDismiss = null;
11
- /** Live predicate deciding whether the layer claims a press; `null` = always. */
12
- #claims = null;
13
- /**
14
- * Activates this layer at the top of its document's Escape stack, installing
15
- * the document's shared resolver listener if this is its first layer.
16
- * Re-activating an already-active layer moves it to the top.
17
- */
14
+ static #registries = sharedRegistry(
15
+ "stimeo-ui.escape-layer.registry.v1",
16
+ () => /* @__PURE__ */ new WeakMap()
17
+ );
18
+ #active = null;
18
19
  activate(ownerDocument = document, options) {
19
20
  this.deactivate();
20
21
  let registry = _EscapeLayer.#registries.get(ownerDocument);
@@ -23,44 +24,31 @@ var EscapeLayer = class _EscapeLayer {
23
24
  _EscapeLayer.#registries.set(ownerDocument, registry);
24
25
  ownerDocument.addEventListener("keydown", registry.onKeydown);
25
26
  }
26
- registry.stack.push(this);
27
- this.#ownerDocument = ownerDocument;
28
- this.#onDismiss = options.onDismiss;
29
- this.#claims = options.claims ?? null;
30
- }
31
- /**
32
- * Removes this layer from its document's Escape stack, uninstalling the
33
- * shared listener when the stack empties. Safe to call when inactive.
34
- */
27
+ const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
28
+ registry.stack.push(entry);
29
+ this.#active = { document: ownerDocument, entry };
30
+ }
35
31
  deactivate() {
36
- const ownerDocument = this.#ownerDocument;
37
- if (!ownerDocument) return;
38
- const registry = _EscapeLayer.#registries.get(ownerDocument);
32
+ const active = this.#active;
33
+ if (!active) return;
34
+ const registry = _EscapeLayer.#registries.get(active.document);
39
35
  if (registry) {
40
- const index = registry.stack.lastIndexOf(this);
36
+ const index = registry.stack.lastIndexOf(active.entry);
41
37
  if (index >= 0) registry.stack.splice(index, 1);
42
38
  if (registry.stack.length === 0) {
43
- ownerDocument.removeEventListener("keydown", registry.onKeydown);
44
- _EscapeLayer.#registries.delete(ownerDocument);
39
+ active.document.removeEventListener("keydown", registry.onKeydown);
40
+ _EscapeLayer.#registries.delete(active.document);
45
41
  }
46
42
  }
47
- this.#ownerDocument = null;
48
- this.#onDismiss = null;
49
- this.#claims = null;
50
- }
51
- /**
52
- * Whether this active layer would own a press right now: it is the topmost
53
- * layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
54
- * and diagnostics — production dismissal goes through the shared listener.
55
- */
43
+ this.#active = null;
44
+ }
56
45
  get ownsEscape() {
57
- const ownerDocument = this.#ownerDocument;
58
- if (!ownerDocument) return false;
59
- const registry = _EscapeLayer.#registries.get(ownerDocument);
46
+ const active = this.#active;
47
+ if (!active) return false;
48
+ const registry = _EscapeLayer.#registries.get(active.document);
60
49
  if (!registry) return false;
61
- return _EscapeLayer.#resolveOwner(registry.stack) === this;
50
+ return _EscapeLayer.#resolveOwner(registry.stack) === active.entry;
62
51
  }
63
- /** Builds a document's registry with its shared resolver listener. */
64
52
  static #createRegistry() {
65
53
  const registry = {
66
54
  stack: [],
@@ -69,60 +57,86 @@ var EscapeLayer = class _EscapeLayer {
69
57
  const owner = _EscapeLayer.#resolveOwner(registry.stack);
70
58
  if (!owner) return;
71
59
  event.preventDefault();
72
- owner.#onDismiss?.();
60
+ owner.onDismiss();
73
61
  }
74
62
  };
75
63
  return registry;
76
64
  }
77
- /** The topmost stack layer whose claims predicate passes, or `null`. */
78
65
  static #resolveOwner(stack) {
79
66
  for (let index = stack.length - 1; index >= 0; index--) {
80
67
  const layer = stack[index];
81
68
  if (!layer) continue;
82
- if (layer.#claims && !layer.#claims()) continue;
69
+ if (layer.claims && !layer.claims()) continue;
83
70
  return layer;
84
71
  }
85
72
  return null;
86
73
  }
87
74
  };
88
75
 
89
- // src/utils/safe_timeout.ts
76
+ var MAX_TIMER_DELAY_MS = 2147483647;
77
+
78
+ var NUMBER_BOUNDS = {
79
+ timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
80
+ function matchesNumberBounds(value, bounds) {
81
+ if (!Number.isFinite(value)) {
82
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
83
+ if (direction === null) return false;
84
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
85
+ }
86
+ if (bounds.min !== void 0 && value < bounds.min) return false;
87
+ if (bounds.max !== void 0 && value > bounds.max) return false;
88
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
89
+ if (bounds.integer && !Number.isInteger(value)) return false;
90
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
91
+ return true;
92
+ }
93
+
94
+ function readNumber(raw, fallback, bounds) {
95
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
96
+ }
97
+
98
+ var NumberValueReader = class {
99
+ #lastRejected = /* @__PURE__ */ new Map();
100
+ read(owner, name, raw, fallback, bounds) {
101
+ const resolved = readNumber(raw, fallback, bounds);
102
+ if (matchesNumberBounds(raw, bounds)) {
103
+ this.#lastRejected.delete(name);
104
+ return resolved;
105
+ }
106
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
107
+ const literal = owner.element.getAttribute(attribute);
108
+ if (literal === null) {
109
+ this.#lastRejected.delete(name);
110
+ return resolved;
111
+ }
112
+ if (this.#lastRejected.get(name) !== literal) {
113
+ this.#lastRejected.set(name, literal);
114
+ console.warn(
115
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
116
+ );
117
+ }
118
+ return resolved;
119
+ }
120
+ };
121
+
90
122
  var TimerRegistry = class {
91
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
92
123
  ids = /* @__PURE__ */ new Set();
93
- /**
94
- * Cancels a single tracked timer.
95
- *
96
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
97
- * registry), so callers can clear defensively without guarding.
98
- */
99
124
  clear(id) {
100
125
  if (this.ids.delete(id)) {
101
126
  this.cancel(id);
102
127
  }
103
128
  }
104
- /**
105
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
106
- * to guarantee no timer outlives the element.
107
- */
108
129
  clearAll() {
109
130
  for (const id of this.ids) {
110
131
  this.cancel(id);
111
132
  }
112
133
  this.ids.clear();
113
134
  }
114
- /** Number of timers currently tracked (pending). */
115
135
  get size() {
116
136
  return this.ids.size;
117
137
  }
118
138
  };
119
139
  var SafeTimeout = class extends TimerRegistry {
120
- /**
121
- * Schedules `callback` after `delay` ms and returns the timer id.
122
- *
123
- * The id is removed from the registry automatically when the timeout fires,
124
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
125
- */
126
140
  set(callback, delay) {
127
141
  const id = this.schedule(() => {
128
142
  this.ids.delete(id);
@@ -139,7 +153,6 @@ var SafeTimeout = class extends TimerRegistry {
139
153
  }
140
154
  };
141
155
 
142
- // src/utils/scroll_dismiss.ts
143
156
  function observeScrollDismiss(element, onScroll) {
144
157
  const targets = [...scrollParents(element), window];
145
158
  const handler = () => onScroll();
@@ -164,107 +177,135 @@ function isScrollable(element) {
164
177
  return /auto|scroll|overlay/.test(style.overflow + style.overflowX + style.overflowY);
165
178
  }
166
179
 
167
- // src/controllers/tooltip_controller.ts
168
- var TooltipController = class extends Controller {
180
+ var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
181
+ var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
182
+ function stateReasonFor(event) {
183
+ if (!event) return "api";
184
+ if (FOCUS_EVENTS.has(event.type)) return "focus";
185
+ if (POINTER_EVENTS.has(event.type)) return "pointer";
186
+ return "user";
187
+ }
188
+
189
+ var TooltipController = class _TooltipController extends Controller {
190
+ #numbers = new NumberValueReader();
169
191
  static targets = ["trigger", "content"];
170
192
  static values = {
171
193
  showDelay: { type: Number, default: 0 },
172
194
  hideDelay: { type: Number, default: 0 },
173
195
  closeOnScroll: { type: Boolean, default: false }
174
196
  };
197
+ static valueConstraints = {
198
+ showDelay: NUMBER_BOUNDS.timer,
199
+ hideDelay: NUMBER_BOUNDS.timer
200
+ };
175
201
  static actions = ["hide", "show"];
176
- /** Registry whose pending timers are cancelled individually with their guard ids. */
202
+ static events = ["close", "open"];
177
203
  #timers = new SafeTimeout();
178
- /** Escape-stack membership while shown; the shared resolver dismisses via it. */
179
204
  #escapeLayer = new EscapeLayer();
180
- /** The id of the currently pending show timer, if any. */
181
205
  #pendingShow = null;
182
- /** The id of the currently pending hide timer, if any. */
183
206
  #pendingHide = null;
184
- /** Whether focus or the pointer currently requires the tooltip to persist. */
185
207
  #focusActive = false;
186
208
  #pointerActive = false;
187
- /** Cleanup for the dismiss-on-scroll listeners while shown, or `null`. */
188
209
  #stopScrollDismiss = null;
189
- /** Starts hidden with no stale timer or interaction state from a prior connection. */
210
+ #shownContent = null;
211
+ #reporting = false;
190
212
  connect() {
191
- this.#cancelShow();
192
- this.#cancelHide();
213
+ this.#cancelTimers();
193
214
  this.#resetInteractionState();
194
- this.#conceal();
215
+ this.#conceal("api");
216
+ this.#reporting = true;
195
217
  }
196
- /** Clears timers, the Escape-stack membership, and scroll listeners so nothing outlives the element. */
197
218
  disconnect() {
198
- this.#cancelShow();
199
- this.#cancelHide();
200
- this.#resetInteractionState();
201
- this.#escapeLayer.deactivate();
202
- this.#stopScrollDismiss?.();
203
- this.#stopScrollDismiss = null;
219
+ this.#reporting = false;
220
+ this.#cancelTimers();
221
+ this.#release();
222
+ }
223
+ closeOnScrollValueChanged() {
224
+ if (this.#shownContent) this.#syncScrollDismiss();
225
+ }
226
+ contentTargetDisconnected(content) {
227
+ if (content !== this.#shownContent) return;
228
+ if (this.contentTargets.includes(content)) return;
229
+ this.#cancelTimers();
230
+ this.#release();
231
+ content.hidden = true;
232
+ content.setAttribute("data-state", "closed");
233
+ this.#reflectHidden();
204
234
  }
205
- /** Shows after `showDelay`, recording the focus/pointer reason supplied by an action event. */
206
235
  show(event) {
236
+ const reason = stateReasonFor(event);
207
237
  this.#activateInteraction(event);
208
238
  this.#cancelHide();
209
239
  if (this.#isVisible || this.#pendingShow !== null) return;
210
- if (this.showDelayValue <= 0) {
211
- this.#reveal();
240
+ if (this.#safeShowDelay <= 0) {
241
+ this.#reveal(reason);
212
242
  return;
213
243
  }
214
244
  this.#pendingShow = this.#timers.set(() => {
215
245
  this.#pendingShow = null;
216
- this.#reveal();
217
- }, this.showDelayValue);
246
+ this.#reveal(reason);
247
+ }, this.#safeShowDelay);
218
248
  }
219
- /** Hides after `hideDelay` once no focus/pointer reason remains; eventless calls are explicit. */
220
249
  hide(event) {
250
+ const reason = stateReasonFor(event);
221
251
  const interactionEnded = this.#deactivateInteraction(event);
222
- if (interactionEnded && this.#hasActiveInteraction) {
223
- this.#cancelHide();
224
- return;
225
- }
252
+ if (interactionEnded && this.#hasActiveInteraction) return;
226
253
  this.#cancelShow();
227
- if (!this.#isVisible || this.#pendingHide !== null) return;
228
- if (this.hideDelayValue <= 0) {
229
- this.#conceal();
254
+ if (!this.#hidable || this.#pendingHide !== null) return;
255
+ if (this.#safeHideDelay <= 0) {
256
+ this.#conceal(reason);
230
257
  return;
231
258
  }
232
259
  this.#pendingHide = this.#timers.set(() => {
233
260
  this.#pendingHide = null;
234
- this.#conceal();
235
- }, this.hideDelayValue);
261
+ this.#conceal(reason);
262
+ }, this.#safeHideDelay);
236
263
  }
237
- /** Reveals the content and joins the Escape stack / starts the scroll watcher. */
238
- #reveal() {
264
+ #reveal(reason) {
239
265
  if (!this.hasContentTarget) return;
240
- this.contentTarget.hidden = false;
241
- this.contentTarget.setAttribute("data-state", "open");
242
- this.#escapeLayer.activate(document, { onDismiss: () => this.#dismiss() });
243
- if (this.closeOnScrollValue && !this.#stopScrollDismiss) {
244
- this.#stopScrollDismiss = observeScrollDismiss(this.element, () => this.#dismiss());
245
- }
266
+ const content = this.contentTarget;
267
+ const was = this.#isVisible;
268
+ content.hidden = false;
269
+ content.setAttribute("data-state", "open");
270
+ this.#shownContent = content;
271
+ if (!was && this.#reporting) this.dispatch("open", { detail: { reason }, cancelable: false });
272
+ if (this.#shownContent !== content) return;
273
+ this.#escapeLayer.activate(document, { onDismiss: () => this.#dismiss("escape") });
274
+ this.#syncScrollDismiss();
246
275
  }
247
- /** Hides the content and leaves the Escape stack / stops the scroll watcher. */
248
- #conceal() {
249
- this.#escapeLayer.deactivate();
250
- this.#stopScrollDismiss?.();
251
- this.#stopScrollDismiss = null;
276
+ #conceal(reason) {
277
+ const was = this.#isVisible;
278
+ this.#release();
279
+ this.#reflectHidden();
280
+ if (was && this.#reporting) this.dispatch("close", { detail: { reason }, cancelable: false });
281
+ }
282
+ #reflectHidden() {
252
283
  if (!this.hasContentTarget) return;
253
284
  this.contentTarget.hidden = true;
254
285
  this.contentTarget.setAttribute("data-state", "closed");
255
286
  }
256
- /** Cancels pending timers and conceals immediately (shared Escape / scroll path). */
257
- #dismiss() {
258
- this.#cancelShow();
259
- this.#cancelHide();
260
- this.#conceal();
287
+ #release() {
288
+ this.#shownContent = null;
289
+ this.#escapeLayer.deactivate();
290
+ this.#stopScrollDismiss?.();
291
+ this.#stopScrollDismiss = null;
292
+ }
293
+ #syncScrollDismiss() {
294
+ if (this.closeOnScrollValue) {
295
+ this.#stopScrollDismiss ??= observeScrollDismiss(this.element, () => this.#dismiss("scroll"));
296
+ return;
297
+ }
298
+ this.#stopScrollDismiss?.();
299
+ this.#stopScrollDismiss = null;
300
+ }
301
+ #dismiss(reason) {
302
+ this.#cancelTimers();
303
+ this.#conceal(reason);
261
304
  }
262
- /** Records the modality whose enter/focus event requires the tooltip to stay visible. */
263
305
  #activateInteraction(event) {
264
306
  if (event?.type === "mouseenter") this.#pointerActive = true;
265
307
  if (event?.type === "focusin") this.#focusActive = true;
266
308
  }
267
- /** Clears a modality on leave/blur and reports whether the event represented such a change. */
268
309
  #deactivateInteraction(event) {
269
310
  if (event?.type === "mouseleave") {
270
311
  this.#pointerActive = false;
@@ -276,35 +317,54 @@ var TooltipController = class extends Controller {
276
317
  }
277
318
  return false;
278
319
  }
279
- /** Discards interaction reasons at a lifecycle boundary. */
280
320
  #resetInteractionState() {
281
321
  this.#focusActive = false;
282
322
  this.#pointerActive = false;
283
323
  }
284
- /** Whether focus or pointer presence still requires a persistent tooltip. */
285
324
  get #hasActiveInteraction() {
286
325
  return this.#focusActive || this.#pointerActive;
287
326
  }
288
- /** Cancels any pending show timer. */
327
+ #cancelTimers() {
328
+ this.#cancelShow();
329
+ this.#cancelHide();
330
+ }
289
331
  #cancelShow() {
290
332
  if (this.#pendingShow !== null) {
291
333
  this.#timers.clear(this.#pendingShow);
292
334
  this.#pendingShow = null;
293
335
  }
294
336
  }
295
- /** Cancels any pending hide timer. */
296
337
  #cancelHide() {
297
338
  if (this.#pendingHide !== null) {
298
339
  this.#timers.clear(this.#pendingHide);
299
340
  this.#pendingHide = null;
300
341
  }
301
342
  }
302
- /** Whether the tooltip is currently shown. */
303
343
  get #isVisible() {
304
344
  return this.hasContentTarget && !this.contentTarget.hidden;
305
345
  }
346
+ get #hidable() {
347
+ return this.#isVisible || this.#shownContent !== null;
348
+ }
349
+ get #safeShowDelay() {
350
+ return this.#numbers.read(
351
+ this,
352
+ "showDelay",
353
+ this.showDelayValue,
354
+ _TooltipController.values.showDelay.default,
355
+ _TooltipController.valueConstraints.showDelay
356
+ );
357
+ }
358
+ get #safeHideDelay() {
359
+ return this.#numbers.read(
360
+ this,
361
+ "hideDelay",
362
+ this.hideDelayValue,
363
+ _TooltipController.values.hideDelay.default,
364
+ _TooltipController.valueConstraints.hideDelay
365
+ );
366
+ }
306
367
  };
307
368
 
308
369
  export { TooltipController };
309
- //# sourceMappingURL=tooltip_controller.js.map
310
370
  //# sourceMappingURL=tooltip_controller.js.map