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,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,7 +177,6 @@ function isScrollable(element) {
164
177
  return /auto|scroll|overlay/.test(style.overflow + style.overflowX + style.overflowY);
165
178
  }
166
179
 
167
- // src/utils/state_reason.ts
168
180
  var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
169
181
  var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
170
182
  function stateReasonFor(event) {
@@ -174,123 +186,126 @@ function stateReasonFor(event) {
174
186
  return "user";
175
187
  }
176
188
 
177
- // src/controllers/tooltip_controller.ts
178
- var TooltipController = class extends Controller {
189
+ var TooltipController = class _TooltipController extends Controller {
190
+ #numbers = new NumberValueReader();
179
191
  static targets = ["trigger", "content"];
180
192
  static values = {
181
193
  showDelay: { type: Number, default: 0 },
182
194
  hideDelay: { type: Number, default: 0 },
183
195
  closeOnScroll: { type: Boolean, default: false }
184
196
  };
197
+ static valueConstraints = {
198
+ showDelay: NUMBER_BOUNDS.timer,
199
+ hideDelay: NUMBER_BOUNDS.timer
200
+ };
185
201
  static actions = ["hide", "show"];
186
202
  static events = ["close", "open"];
187
- /** Registry whose pending timers are cancelled individually with their guard ids. */
188
203
  #timers = new SafeTimeout();
189
- /** Escape-stack membership while shown; the shared resolver dismisses via it. */
190
204
  #escapeLayer = new EscapeLayer();
191
- /** The id of the currently pending show timer, if any. */
192
205
  #pendingShow = null;
193
- /** The id of the currently pending hide timer, if any. */
194
206
  #pendingHide = null;
195
- /** Whether focus or the pointer currently requires the tooltip to persist. */
196
207
  #focusActive = false;
197
208
  #pointerActive = false;
198
- /** Cleanup for the dismiss-on-scroll listeners while shown, or `null`. */
199
209
  #stopScrollDismiss = null;
200
- /** Whether state moves are reported: set once `connect()` settled the baseline. */
210
+ #shownContent = null;
201
211
  #reporting = false;
202
- /** Starts hidden with no stale timer or interaction state from a prior connection. */
203
212
  connect() {
204
- this.#cancelShow();
205
- this.#cancelHide();
213
+ this.#cancelTimers();
206
214
  this.#resetInteractionState();
207
215
  this.#conceal("api");
208
216
  this.#reporting = true;
209
217
  }
210
- /** Clears timers, the Escape-stack membership, and scroll listeners so nothing outlives the element. */
211
218
  disconnect() {
212
219
  this.#reporting = false;
213
- this.#cancelShow();
214
- this.#cancelHide();
215
- this.#resetInteractionState();
216
- this.#escapeLayer.deactivate();
217
- this.#stopScrollDismiss?.();
218
- this.#stopScrollDismiss = null;
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();
219
234
  }
220
- /** Shows after `showDelay`, recording the focus/pointer reason supplied by an action event. */
221
235
  show(event) {
222
236
  const reason = stateReasonFor(event);
223
237
  this.#activateInteraction(event);
224
238
  this.#cancelHide();
225
239
  if (this.#isVisible || this.#pendingShow !== null) return;
226
- if (this.showDelayValue <= 0) {
240
+ if (this.#safeShowDelay <= 0) {
227
241
  this.#reveal(reason);
228
242
  return;
229
243
  }
230
244
  this.#pendingShow = this.#timers.set(() => {
231
245
  this.#pendingShow = null;
232
246
  this.#reveal(reason);
233
- }, this.showDelayValue);
247
+ }, this.#safeShowDelay);
234
248
  }
235
- /** Hides after `hideDelay` once no focus/pointer reason remains; eventless calls are explicit. */
236
249
  hide(event) {
237
250
  const reason = stateReasonFor(event);
238
251
  const interactionEnded = this.#deactivateInteraction(event);
239
- if (interactionEnded && this.#hasActiveInteraction) {
240
- this.#cancelHide();
241
- return;
242
- }
252
+ if (interactionEnded && this.#hasActiveInteraction) return;
243
253
  this.#cancelShow();
244
- if (!this.#isVisible || this.#pendingHide !== null) return;
245
- if (this.hideDelayValue <= 0) {
254
+ if (!this.#hidable || this.#pendingHide !== null) return;
255
+ if (this.#safeHideDelay <= 0) {
246
256
  this.#conceal(reason);
247
257
  return;
248
258
  }
249
259
  this.#pendingHide = this.#timers.set(() => {
250
260
  this.#pendingHide = null;
251
261
  this.#conceal(reason);
252
- }, this.hideDelayValue);
253
- }
254
- /**
255
- * Reveals the content, reports a move, and joins the Escape stack / scroll watcher.
256
- *
257
- * @stimeoRuntimeOnly `closeOnScroll` decides whether this reveal wires the scroll dismissal; what
258
- * is shown does not depend on it.
259
- */
262
+ }, this.#safeHideDelay);
263
+ }
260
264
  #reveal(reason) {
261
265
  if (!this.hasContentTarget) return;
266
+ const content = this.contentTarget;
262
267
  const was = this.#isVisible;
263
- this.contentTarget.hidden = false;
264
- this.contentTarget.setAttribute("data-state", "open");
268
+ content.hidden = false;
269
+ content.setAttribute("data-state", "open");
270
+ this.#shownContent = content;
265
271
  if (!was && this.#reporting) this.dispatch("open", { detail: { reason }, cancelable: false });
272
+ if (this.#shownContent !== content) return;
266
273
  this.#escapeLayer.activate(document, { onDismiss: () => this.#dismiss("escape") });
267
- if (this.closeOnScrollValue && !this.#stopScrollDismiss) {
268
- this.#stopScrollDismiss = observeScrollDismiss(this.element, () => this.#dismiss("scroll"));
269
- }
274
+ this.#syncScrollDismiss();
270
275
  }
271
- /** Hides the content, reports a move, and leaves the Escape stack / scroll watcher. */
272
276
  #conceal(reason) {
273
277
  const was = this.#isVisible;
274
- this.#escapeLayer.deactivate();
275
- this.#stopScrollDismiss?.();
276
- this.#stopScrollDismiss = null;
278
+ this.#release();
279
+ this.#reflectHidden();
280
+ if (was && this.#reporting) this.dispatch("close", { detail: { reason }, cancelable: false });
281
+ }
282
+ #reflectHidden() {
277
283
  if (!this.hasContentTarget) return;
278
284
  this.contentTarget.hidden = true;
279
285
  this.contentTarget.setAttribute("data-state", "closed");
280
- if (was && this.#reporting) this.dispatch("close", { detail: { reason }, cancelable: false });
281
286
  }
282
- /** Cancels pending timers and conceals immediately (shared Escape / scroll path). */
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
+ }
283
301
  #dismiss(reason) {
284
- this.#cancelShow();
285
- this.#cancelHide();
302
+ this.#cancelTimers();
286
303
  this.#conceal(reason);
287
304
  }
288
- /** Records the modality whose enter/focus event requires the tooltip to stay visible. */
289
305
  #activateInteraction(event) {
290
306
  if (event?.type === "mouseenter") this.#pointerActive = true;
291
307
  if (event?.type === "focusin") this.#focusActive = true;
292
308
  }
293
- /** Clears a modality on leave/blur and reports whether the event represented such a change. */
294
309
  #deactivateInteraction(event) {
295
310
  if (event?.type === "mouseleave") {
296
311
  this.#pointerActive = false;
@@ -302,35 +317,54 @@ var TooltipController = class extends Controller {
302
317
  }
303
318
  return false;
304
319
  }
305
- /** Discards interaction reasons at a lifecycle boundary. */
306
320
  #resetInteractionState() {
307
321
  this.#focusActive = false;
308
322
  this.#pointerActive = false;
309
323
  }
310
- /** Whether focus or pointer presence still requires a persistent tooltip. */
311
324
  get #hasActiveInteraction() {
312
325
  return this.#focusActive || this.#pointerActive;
313
326
  }
314
- /** Cancels any pending show timer. */
327
+ #cancelTimers() {
328
+ this.#cancelShow();
329
+ this.#cancelHide();
330
+ }
315
331
  #cancelShow() {
316
332
  if (this.#pendingShow !== null) {
317
333
  this.#timers.clear(this.#pendingShow);
318
334
  this.#pendingShow = null;
319
335
  }
320
336
  }
321
- /** Cancels any pending hide timer. */
322
337
  #cancelHide() {
323
338
  if (this.#pendingHide !== null) {
324
339
  this.#timers.clear(this.#pendingHide);
325
340
  this.#pendingHide = null;
326
341
  }
327
342
  }
328
- /** Whether the tooltip is currently shown. */
329
343
  get #isVisible() {
330
344
  return this.hasContentTarget && !this.contentTarget.hidden;
331
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
+ }
332
367
  };
333
368
 
334
369
  export { TooltipController };
335
- //# sourceMappingURL=tooltip_controller.js.map
336
370
  //# sourceMappingURL=tooltip_controller.js.map