sheer-ui 0.4.0 → 0.4.3

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 (96) hide show
  1. package/package.json +1 -1
  2. package/src/lib/components/accordion/components/accordion-item.svelte +3 -3
  3. package/src/lib/components/accordion/components/accordion.svelte +29 -13
  4. package/src/lib/components/accordion/types.ts +3 -3
  5. package/src/lib/components/avatar/avatar.svelte.ts +34 -37
  6. package/src/lib/components/avatar/components/avatar-fallback.svelte +9 -6
  7. package/src/lib/components/avatar/components/avatar-image.svelte +18 -9
  8. package/src/lib/components/avatar/components/avatar.svelte +20 -16
  9. package/src/lib/components/checkbox/checkbox.svelte.ts +45 -55
  10. package/src/lib/components/checkbox/components/checkbox-group-label.svelte +9 -6
  11. package/src/lib/components/checkbox/components/checkbox-group.svelte +25 -14
  12. package/src/lib/components/checkbox/components/checkbox.svelte +39 -21
  13. package/src/lib/components/combobox/components/combobox.svelte +2 -2
  14. package/src/lib/components/combobox/select/components/select.svelte +2 -2
  15. package/src/lib/components/context-menu/components/context-menu-trigger.svelte +1 -1
  16. package/src/lib/components/date-field/date-field.svelte.ts +2 -3
  17. package/src/lib/components/dialog/components/dialog-close.svelte +13 -8
  18. package/src/lib/components/dialog/components/dialog-content-headless.svelte +14 -11
  19. package/src/lib/components/dialog/components/dialog-description.svelte +9 -6
  20. package/src/lib/components/dialog/components/dialog-overlay.svelte +10 -7
  21. package/src/lib/components/dialog/components/dialog-title.svelte +12 -7
  22. package/src/lib/components/dialog/components/dialog-trigger.svelte +12 -7
  23. package/src/lib/components/dialog/components/dialog.svelte +10 -4
  24. package/src/lib/components/dialog/components/modal-surface.svelte +11 -8
  25. package/src/lib/components/dialog/dialog.svelte.ts +73 -61
  26. package/src/lib/components/drawer/drawer.svelte +8 -3
  27. package/src/lib/components/link-preview/components/link-preview-content.svelte +15 -8
  28. package/src/lib/components/link-preview/components/link-preview-trigger.svelte +9 -6
  29. package/src/lib/components/link-preview/components/link-preview.svelte +19 -11
  30. package/src/lib/components/link-preview/link-preview.svelte.ts +42 -43
  31. package/src/lib/components/menu/components/menu-checkbox-group.svelte +6 -4
  32. package/src/lib/components/menu/components/menu-checkbox-item.svelte +11 -7
  33. package/src/lib/components/menu/menu.svelte.ts +29 -19
  34. package/src/lib/components/menubar/menubar.svelte.ts +7 -6
  35. package/src/lib/components/meter/components/meter.svelte +18 -9
  36. package/src/lib/components/meter/meter.svelte.ts +15 -18
  37. package/src/lib/components/navigation-menu/components/navigation-menu-content.svelte +1 -1
  38. package/src/lib/components/navigation-menu/components/navigation-menu-indicator.svelte +2 -2
  39. package/src/lib/components/navigation-menu/components/navigation-menu-trigger.svelte +1 -1
  40. package/src/lib/components/navigation-menu/navigation-menu.svelte.ts +57 -60
  41. package/src/lib/components/pagination/components/pagination-next-button.svelte +12 -7
  42. package/src/lib/components/pagination/components/pagination-page.svelte +15 -8
  43. package/src/lib/components/pagination/components/pagination-prev-button.svelte +12 -7
  44. package/src/lib/components/pagination/components/pagination.svelte +31 -18
  45. package/src/lib/components/pagination/pagination.svelte.ts +48 -64
  46. package/src/lib/components/pin-input/pin-input.svelte.ts +7 -8
  47. package/src/lib/components/pin-input/usePasswordManager.svelte.ts +2 -2
  48. package/src/lib/components/popover/components/popover-close.svelte +9 -6
  49. package/src/lib/components/popover/components/popover-content.svelte +12 -7
  50. package/src/lib/components/popover/components/popover-trigger.svelte +21 -10
  51. package/src/lib/components/popover/components/popover.svelte +9 -8
  52. package/src/lib/components/popover/popover.svelte.ts +68 -74
  53. package/src/lib/components/progress/components/progress.svelte +18 -9
  54. package/src/lib/components/progress/progress.svelte.ts +14 -17
  55. package/src/lib/components/scroll-area/scroll-area.svelte.ts +9 -10
  56. package/src/lib/components/separator/components/separator.svelte +15 -8
  57. package/src/lib/components/separator/separator.svelte.ts +12 -15
  58. package/src/lib/components/sidebar/sidebar-mobile-surface.svelte +8 -4
  59. package/src/lib/components/slider/components/slider.svelte +2 -1
  60. package/src/lib/components/tabs/components/tabs-content.svelte +12 -7
  61. package/src/lib/components/tabs/components/tabs-list.svelte +9 -6
  62. package/src/lib/components/tabs/components/tabs-trigger.svelte +15 -8
  63. package/src/lib/components/tabs/components/tabs.svelte +28 -17
  64. package/src/lib/components/tabs/tabs.svelte.ts +51 -67
  65. package/src/lib/components/time-field/time-field.svelte.ts +2 -2
  66. package/src/lib/components/toggle/components/toggle.svelte +19 -14
  67. package/src/lib/components/toggle/toggle.svelte.ts +15 -20
  68. package/src/lib/components/toggle-group/components/toggle-group-item.svelte +15 -8
  69. package/src/lib/components/toggle-group/components/toggle-group.svelte +28 -16
  70. package/src/lib/components/toggle-group/toggle-group.svelte.ts +33 -35
  71. package/src/lib/components/toolbar/components/toolbar-group-item.svelte +15 -8
  72. package/src/lib/components/toolbar/components/toolbar-group.svelte +19 -14
  73. package/src/lib/components/toolbar/toolbar.svelte.ts +28 -30
  74. package/src/lib/components/tooltip/components/tooltip-content.svelte +15 -8
  75. package/src/lib/components/tooltip/components/tooltip-provider.svelte +18 -7
  76. package/src/lib/components/tooltip/components/tooltip-trigger.svelte +21 -10
  77. package/src/lib/components/tooltip/components/tooltip.svelte +34 -21
  78. package/src/lib/components/tooltip/tooltip.svelte.ts +101 -125
  79. package/src/lib/internal/dismissible-layer/use-dismissable-layer.svelte.ts +3 -4
  80. package/src/lib/internal/escape-layer/use-escape-layer.svelte.ts +13 -29
  81. package/src/lib/internal/floating-layer/use-floating-layer.svelte.ts +26 -30
  82. package/src/lib/internal/group-value.svelte.ts +11 -10
  83. package/src/lib/internal/native-popover.svelte.ts +6 -6
  84. package/src/lib/internal/{roving-focus-group.ts → roving-focus-group.svelte.ts} +17 -19
  85. package/src/lib/internal/roving-focus-item.svelte.ts +12 -17
  86. package/src/lib/internal/selection.svelte.ts +55 -41
  87. package/src/lib/internal/{state-machine.ts → state-machine.svelte.ts} +6 -8
  88. package/src/lib/internal/timeout-fn.svelte.ts +6 -4
  89. package/src/lib/internal/tools/box.svelte.ts +5 -114
  90. package/src/lib/internal/tools/index.ts +1 -11
  91. package/src/lib/internal/tools/types.ts +0 -3
  92. package/src/lib/internal/typeahead.svelte.ts +9 -13
  93. package/src/lib/internal/types.ts +6 -0
  94. package/src/lib/internal/vaul/use-drawer-content.svelte.ts +2 -2
  95. package/src/lib/internal/vaul/use-drawer-root.svelte.ts +12 -7
  96. package/src/lib/components/accordion/accordion.svelte.ts +0 -43
@@ -1,9 +1,9 @@
1
- import { attachRef, DOMContext, type WritableBoxedValues, type ReadableBoxedValues, simpleBox } from '../../internal/tools/index.js';
1
+ import { attachRef, DOMContext } from '../../internal/tools/index.js';
2
2
  import { on } from 'svelte/events';
3
3
  import { createContext, onMount, untrack } from 'svelte';
4
4
  import { isElement } from '../../internal/tools/utils/dom.js';
5
5
  import { createBitsAttrs, boolToEmptyStrOrUndef } from '../../internal/attrs.js';
6
- import type { OnChangeFn, RefAttachment, WithRefOpts } from '../../internal/types.js';
6
+ import type { OnChangeFn, RefAttachment, RefOpts } from '../../internal/types.js';
7
7
  import type { FocusEventHandler, MouseEventHandler, PointerEventHandler } from 'svelte/elements';
8
8
  import { createEffectTimeout } from '../../internal/timeout-fn.svelte.js';
9
9
  import { SafePolygon } from '../../internal/safe-polygon.svelte.js';
@@ -122,7 +122,7 @@ export class TooltipTether<Payload = never> {
122
122
  }
123
123
 
124
124
  get isOpen() {
125
- return this.#state.root?.opts.open.current ?? false;
125
+ return this.#state.root?.opts.open ?? false;
126
126
  }
127
127
  }
128
128
 
@@ -130,14 +130,14 @@ export function createTooltipTether<Payload = never>() {
130
130
  return new TooltipTether<Payload>();
131
131
  }
132
132
 
133
- interface TooltipProviderStateOpts extends ReadableBoxedValues<{
134
- delayDuration: number;
135
- disableHoverableContent: boolean;
136
- disableCloseOnTriggerClick: boolean;
137
- disabled: boolean;
138
- ignoreNonKeyboardFocus: boolean;
139
- skipDelayDuration: number;
140
- }> {}
133
+ interface TooltipProviderStateOpts {
134
+ readonly delayDuration: number;
135
+ readonly disableHoverableContent: boolean;
136
+ readonly disableCloseOnTriggerClick: boolean;
137
+ readonly disabled: boolean;
138
+ readonly ignoreNonKeyboardFocus: boolean;
139
+ readonly skipDelayDuration: number;
140
+ }
141
141
 
142
142
  export class TooltipProviderState {
143
143
  static create(opts: TooltipProviderStateOpts) {
@@ -145,7 +145,6 @@ export class TooltipProviderState {
145
145
  }
146
146
  readonly opts: TooltipProviderStateOpts;
147
147
  isOpenDelayed = $state<boolean>(true);
148
- isPointerInTransit = simpleBox(false);
149
148
  readonly #timerFn: ReturnType<typeof createEffectTimeout<() => void>>;
150
149
  #openTooltip = $state<TooltipRootState | null>(null);
151
150
 
@@ -155,7 +154,7 @@ export class TooltipProviderState {
155
154
  () => {
156
155
  this.isOpenDelayed = true;
157
156
  },
158
- () => this.opts.skipDelayDuration.current,
157
+ () => this.opts.skipDelayDuration,
159
158
  );
160
159
 
161
160
  onMount(() =>
@@ -176,7 +175,7 @@ export class TooltipProviderState {
176
175
  }
177
176
 
178
177
  #startTimer = () => {
179
- const skipDuration = this.opts.skipDelayDuration.current;
178
+ const skipDuration = this.opts.skipDelayDuration;
180
179
 
181
180
  if (skipDuration === 0) {
182
181
  // no grace period — reset immediately so next trigger waits the full delay
@@ -213,21 +212,17 @@ export class TooltipProviderState {
213
212
  };
214
213
  }
215
214
 
216
- interface TooltipRootStateOpts
217
- extends
218
- ReadableBoxedValues<{
219
- delayDuration: number | undefined;
220
- disableHoverableContent: boolean | undefined;
221
- disableCloseOnTriggerClick: boolean | undefined;
222
- disabled: boolean | undefined;
223
- ignoreNonKeyboardFocus: boolean | undefined;
224
- onOpenChangeComplete: OnChangeFn<boolean>;
225
- tether: TooltipTether<unknown> | undefined;
226
- }>,
227
- WritableBoxedValues<{
228
- open: boolean;
229
- triggerId: string | null;
230
- }> {}
215
+ interface TooltipRootStateOpts {
216
+ readonly delayDuration: number | undefined;
217
+ readonly disableHoverableContent: boolean | undefined;
218
+ readonly disableCloseOnTriggerClick: boolean | undefined;
219
+ readonly disabled: boolean | undefined;
220
+ readonly ignoreNonKeyboardFocus: boolean | undefined;
221
+ readonly onOpenChangeComplete: OnChangeFn<boolean>;
222
+ readonly tether: TooltipTether<unknown> | undefined;
223
+ open: boolean;
224
+ triggerId: string | null;
225
+ }
231
226
 
232
227
  export class TooltipRootState {
233
228
  static create(opts: TooltipRootStateOpts) {
@@ -235,16 +230,16 @@ export class TooltipRootState {
235
230
  }
236
231
  readonly opts: TooltipRootStateOpts;
237
232
  readonly provider: TooltipProviderState;
238
- readonly delayDuration = $derived.by(() => this.opts.delayDuration.current ?? this.provider.opts.delayDuration.current);
233
+ readonly delayDuration = $derived.by(() => this.opts.delayDuration ?? this.provider.opts.delayDuration);
239
234
  readonly disableHoverableContent = $derived.by(
240
- () => this.opts.disableHoverableContent.current ?? this.provider.opts.disableHoverableContent.current,
235
+ () => this.opts.disableHoverableContent ?? this.provider.opts.disableHoverableContent,
241
236
  );
242
237
  readonly disableCloseOnTriggerClick = $derived.by(
243
- () => this.opts.disableCloseOnTriggerClick.current ?? this.provider.opts.disableCloseOnTriggerClick.current,
238
+ () => this.opts.disableCloseOnTriggerClick ?? this.provider.opts.disableCloseOnTriggerClick,
244
239
  );
245
- readonly disabled = $derived.by(() => this.opts.disabled.current ?? this.provider.opts.disabled.current);
240
+ readonly disabled = $derived.by(() => this.opts.disabled ?? this.provider.opts.disabled);
246
241
  readonly ignoreNonKeyboardFocus = $derived.by(
247
- () => this.opts.ignoreNonKeyboardFocus.current ?? this.provider.opts.ignoreNonKeyboardFocus.current,
242
+ () => this.opts.ignoreNonKeyboardFocus ?? this.provider.opts.ignoreNonKeyboardFocus,
248
243
  );
249
244
  readonly registry: TooltipTriggerRegistryState;
250
245
  readonly tether: TooltipTetherState | null;
@@ -252,19 +247,19 @@ export class TooltipRootState {
252
247
  #wasOpenDelayed = $state(false);
253
248
  readonly #timerFn: ReturnType<typeof createEffectTimeout<() => void>>;
254
249
  readonly stateAttr = $derived.by(() => {
255
- if (!this.opts.open.current) return 'closed';
250
+ if (!this.opts.open) return 'closed';
256
251
  return this.#wasOpenDelayed ? 'delayed-open' : 'instant-open';
257
252
  });
258
253
 
259
254
  constructor(opts: TooltipRootStateOpts, provider: TooltipProviderState) {
260
255
  this.opts = opts;
261
256
  this.provider = provider;
262
- this.tether = opts.tether.current?.state ?? null;
257
+ this.tether = opts.tether?.state ?? null;
263
258
  this.registry = this.tether?.registry ?? new TooltipTriggerRegistryState();
264
259
  this.#timerFn = createEffectTimeout(
265
260
  () => {
266
261
  this.#wasOpenDelayed = true;
267
- this.opts.open.current = true;
262
+ this.opts.open = true;
268
263
  },
269
264
  () => this.delayDuration ?? 0,
270
265
  );
@@ -282,7 +277,7 @@ export class TooltipRootState {
282
277
 
283
278
  let openStarted = false;
284
279
  $effect(() => {
285
- const isOpen = this.opts.open.current;
280
+ const isOpen = this.opts.open;
286
281
  if (!openStarted) {
287
282
  openStarted = true;
288
283
  return;
@@ -298,7 +293,7 @@ export class TooltipRootState {
298
293
  });
299
294
 
300
295
  $effect(() => {
301
- const triggerId = this.opts.triggerId.current;
296
+ const triggerId = this.opts.triggerId;
302
297
  untrack(() => {
303
298
  if (triggerId === this.registry.activeTriggerId) return;
304
299
  this.registry.setActiveTrigger(triggerId);
@@ -308,8 +303,8 @@ export class TooltipRootState {
308
303
  $effect(() => {
309
304
  const activeTriggerId = this.registry.activeTriggerId;
310
305
  untrack(() => {
311
- if (this.opts.triggerId.current === activeTriggerId) return;
312
- this.opts.triggerId.current = activeTriggerId;
306
+ if (this.opts.triggerId === activeTriggerId) return;
307
+ this.opts.triggerId = activeTriggerId;
313
308
  });
314
309
  });
315
310
  }
@@ -318,12 +313,12 @@ export class TooltipRootState {
318
313
  this.#timerFn.stop();
319
314
  this.#wasOpenDelayed = false;
320
315
  this.ensureActiveTrigger();
321
- this.opts.open.current = true;
316
+ this.opts.open = true;
322
317
  };
323
318
 
324
319
  handleClose = () => {
325
320
  this.#timerFn.stop();
326
- this.opts.open.current = false;
321
+ this.opts.open = false;
327
322
  };
328
323
 
329
324
  #handleDelayedOpen = () => {
@@ -335,7 +330,7 @@ export class TooltipRootState {
335
330
  // if no delay needed (either skip delay active or delay is 0), open immediately
336
331
  if (shouldSkipDelay || delayDuration === 0) {
337
332
  this.#wasOpenDelayed = false;
338
- this.opts.open.current = true;
333
+ this.opts.open = true;
339
334
  } else {
340
335
  // use timer for actual delays
341
336
  this.#timerFn.start();
@@ -360,8 +355,8 @@ export class TooltipRootState {
360
355
  return;
361
356
  }
362
357
 
363
- if (this.opts.triggerId.current !== null && this.registry.has(this.opts.triggerId.current)) {
364
- this.registry.setActiveTrigger(this.opts.triggerId.current);
358
+ if (this.opts.triggerId !== null && this.registry.has(this.opts.triggerId)) {
359
+ this.registry.setActiveTrigger(this.opts.triggerId);
365
360
  return;
366
361
  }
367
362
 
@@ -376,7 +371,7 @@ export class TooltipRootState {
376
371
  registerTrigger = (trigger: TooltipTriggerRecord) => {
377
372
  this.registry.register(trigger);
378
373
 
379
- if (trigger.disabled && this.registry.activeTriggerId === trigger.id && this.opts.open.current) {
374
+ if (trigger.disabled && this.registry.activeTriggerId === trigger.id && this.opts.open) {
380
375
  this.handleClose();
381
376
  }
382
377
  };
@@ -384,7 +379,7 @@ export class TooltipRootState {
384
379
  updateTrigger = (trigger: TooltipTriggerRecord) => {
385
380
  this.registry.update(trigger);
386
381
 
387
- if (trigger.disabled && this.registry.activeTriggerId === trigger.id && this.opts.open.current) {
382
+ if (trigger.disabled && this.registry.activeTriggerId === trigger.id && this.opts.open) {
388
383
  this.handleClose();
389
384
  }
390
385
  };
@@ -393,7 +388,7 @@ export class TooltipRootState {
393
388
  const isActive = this.registry.activeTriggerId === id;
394
389
  this.registry.unregister(id);
395
390
 
396
- if (isActive && this.opts.open.current) {
391
+ if (isActive && this.opts.open) {
397
392
  this.handleClose();
398
393
  }
399
394
  };
@@ -415,20 +410,18 @@ export class TooltipRootState {
415
410
  }
416
411
  }
417
412
 
418
- interface TooltipTriggerStateOpts
419
- extends
420
- WithRefOpts,
421
- ReadableBoxedValues<{
422
- disabled: boolean;
423
- tabindex: number;
424
- payload: unknown;
425
- tether: TooltipTether<unknown> | undefined;
426
- }> {}
413
+ interface TooltipTriggerStateOpts extends RefOpts {
414
+ readonly disabled: boolean;
415
+ readonly tabindex: number;
416
+ readonly payload: unknown;
417
+ readonly tether: TooltipTether<unknown> | undefined;
418
+ }
427
419
 
428
420
  export class TooltipTriggerState {
429
421
  static create(opts: TooltipTriggerStateOpts) {
430
- if (opts.tether.current) {
431
- return new TooltipTriggerState(opts, null, opts.tether.current.state);
422
+ const tether = opts.tether;
423
+ if (tether) {
424
+ return new TooltipTriggerState(opts, null, tether.state);
432
425
  }
433
426
  return new TooltipTriggerState(opts, getTooltipRoot(), null);
434
427
  }
@@ -436,18 +429,9 @@ export class TooltipTriggerState {
436
429
  readonly root: TooltipRootState | null;
437
430
  readonly tether: TooltipTetherState | null;
438
431
  readonly attachment: RefAttachment;
439
- #isPointerDown = simpleBox(false);
432
+ #isPointerDown = false;
440
433
  #hasPointerMoveOpened = $state(false);
441
434
  domContext: DOMContext;
442
- // Fired on pointerenter while the pointer is in transit: if still in transit after the delay, the
443
- // user is likely staying on this trigger.
444
- #transitCheck = createEffectTimeout(() => {
445
- const root = this.#getRoot();
446
- if (!root || !root.provider.isPointerInTransit.current) return;
447
- root.provider.isPointerInTransit.current = false;
448
- root.onTriggerEnter(this.opts.id.current);
449
- this.#hasPointerMoveOpened = true;
450
- }, () => 250);
451
435
  #mounted = false;
452
436
  #lastRegisteredId: string | null = null;
453
437
 
@@ -455,31 +439,34 @@ export class TooltipTriggerState {
455
439
  this.opts = opts;
456
440
  this.root = root;
457
441
  this.tether = tether;
458
- this.domContext = new DOMContext(opts.ref);
459
- this.attachment = attachRef(this.opts.ref, (v) => this.#register(v));
442
+ this.domContext = new DOMContext(() => opts.ref);
443
+ this.attachment = attachRef<HTMLElement>(
444
+ (v) => (opts.ref = v),
445
+ (v) => this.#register(v),
446
+ );
460
447
 
461
448
  $effect(() => {
462
- const _id = this.opts.id.current;
449
+ const _id = this.opts.id;
463
450
  untrack(() => {
464
- this.#register(this.opts.ref.current);
451
+ this.#register(this.opts.ref);
465
452
  });
466
453
  });
467
454
  $effect(() => {
468
- const _payload = this.opts.payload.current;
455
+ const _payload = this.opts.payload;
469
456
  untrack(() => {
470
- this.#register(this.opts.ref.current);
457
+ this.#register(this.opts.ref);
471
458
  });
472
459
  });
473
460
  $effect(() => {
474
- const _disabled = this.opts.disabled.current;
461
+ const _disabled = this.opts.disabled;
475
462
  untrack(() => {
476
- this.#register(this.opts.ref.current);
463
+ this.#register(this.opts.ref);
477
464
  });
478
465
  });
479
466
 
480
467
  onMount(() => {
481
468
  this.#mounted = true;
482
- this.#register(this.opts.ref.current);
469
+ this.#register(this.opts.ref);
483
470
 
484
471
  return () => {
485
472
  const root = this.#getRoot();
@@ -503,14 +490,14 @@ export class TooltipTriggerState {
503
490
 
504
491
  #isDisabled = () => {
505
492
  const root = this.#getRoot();
506
- return this.opts.disabled.current || Boolean(root?.disabled);
493
+ return this.opts.disabled || Boolean(root?.disabled);
507
494
  };
508
495
 
509
496
  #register = (node: HTMLElement | null) => {
510
497
  if (!this.#mounted) return;
511
- const id = this.opts.id.current;
512
- const payload = this.opts.payload.current;
513
- const disabled = this.opts.disabled.current;
498
+ const id = this.opts.id;
499
+ const payload = this.opts.payload;
500
+ const disabled = this.opts.disabled;
514
501
 
515
502
  if (this.#lastRegisteredId && this.#lastRegisteredId !== id) {
516
503
  const root = this.#getRoot();
@@ -536,7 +523,7 @@ export class TooltipTriggerState {
536
523
  this.tether.registry.register(triggerRecord);
537
524
  }
538
525
 
539
- if (disabled && this.tether.registry.activeTriggerId === id && root?.opts.open.current) {
526
+ if (disabled && this.tether.registry.activeTriggerId === id && root?.opts.open) {
540
527
  root.handleClose();
541
528
  }
542
529
  } else {
@@ -551,17 +538,17 @@ export class TooltipTriggerState {
551
538
  };
552
539
 
553
540
  handlePointerUp = () => {
554
- this.#isPointerDown.current = false;
541
+ this.#isPointerDown = false;
555
542
  };
556
543
 
557
544
  #onpointerup: PointerEventHandler<HTMLElement> = () => {
558
545
  if (this.#isDisabled()) return;
559
- this.#isPointerDown.current = false;
546
+ this.#isPointerDown = false;
560
547
  };
561
548
 
562
549
  #onpointerdown: PointerEventHandler<HTMLElement> = () => {
563
550
  if (this.#isDisabled()) return;
564
- this.#isPointerDown.current = true;
551
+ this.#isPointerDown = true;
565
552
 
566
553
  on(
567
554
  this.domContext.getDocument(),
@@ -577,20 +564,14 @@ export class TooltipTriggerState {
577
564
  const root = this.#getRoot();
578
565
  if (!root) return;
579
566
  if (this.#isDisabled()) {
580
- if (root.opts.open.current) {
567
+ if (root.opts.open) {
581
568
  root.handleClose();
582
569
  }
583
570
  return;
584
571
  }
585
572
  if (e.pointerType === 'touch') return;
586
573
 
587
- // if in transit, wait briefly to see if user is actually heading to old content or staying here
588
- if (root.provider.isPointerInTransit.current) {
589
- this.#transitCheck.start();
590
- return;
591
- }
592
-
593
- root.onTriggerEnter(this.opts.id.current);
574
+ root.onTriggerEnter(this.opts.id);
594
575
  this.#hasPointerMoveOpened = true;
595
576
  };
596
577
 
@@ -598,7 +579,7 @@ export class TooltipTriggerState {
598
579
  const root = this.#getRoot();
599
580
  if (!root) return;
600
581
  if (this.#isDisabled()) {
601
- if (root.opts.open.current) {
582
+ if (root.opts.open) {
602
583
  root.handleClose();
603
584
  }
604
585
  return;
@@ -606,11 +587,7 @@ export class TooltipTriggerState {
606
587
  if (e.pointerType === 'touch') return;
607
588
  if (this.#hasPointerMoveOpened) return;
608
589
 
609
- // moving within trigger means we're definitely not in transit anymore
610
- this.#transitCheck.stop();
611
- root.provider.isPointerInTransit.current = false;
612
-
613
- root.onTriggerEnter(this.opts.id.current);
590
+ root.onTriggerEnter(this.opts.id);
614
591
  this.#hasPointerMoveOpened = true;
615
592
  };
616
593
 
@@ -618,8 +595,7 @@ export class TooltipTriggerState {
618
595
  const root = this.#getRoot();
619
596
  if (!root) return;
620
597
  if (this.#isDisabled()) return;
621
- this.#transitCheck.stop();
622
- if (!root.isActiveTrigger(this.opts.id.current)) {
598
+ if (!root.isActiveTrigger(this.opts.id)) {
623
599
  this.#hasPointerMoveOpened = false;
624
600
  return;
625
601
  }
@@ -632,7 +608,7 @@ export class TooltipTriggerState {
632
608
  if (isElement(relatedTarget)) {
633
609
  for (const record of root.registry.triggers.values()) {
634
610
  if (record.node !== relatedTarget) continue;
635
- if (root.provider.opts.skipDelayDuration.current > 0) {
611
+ if (root.provider.opts.skipDelayDuration > 0) {
636
612
  this.#hasPointerMoveOpened = false;
637
613
  return;
638
614
  }
@@ -649,16 +625,16 @@ export class TooltipTriggerState {
649
625
  #onfocus: FocusEventHandler<HTMLElement> = (e) => {
650
626
  const root = this.#getRoot();
651
627
  if (!root) return;
652
- if (this.#isPointerDown.current) return;
628
+ if (this.#isPointerDown) return;
653
629
  if (this.#isDisabled()) {
654
- if (root.opts.open.current) {
630
+ if (root.opts.open) {
655
631
  root.handleClose();
656
632
  }
657
633
  return;
658
634
  }
659
635
 
660
636
  if (root.ignoreNonKeyboardFocus && !e.currentTarget.matches(':focus-visible')) return;
661
- root.setActiveTrigger(this.opts.id.current);
637
+ root.setActiveTrigger(this.opts.id);
662
638
  root.handleOpen();
663
639
  };
664
640
 
@@ -676,18 +652,18 @@ export class TooltipTriggerState {
676
652
 
677
653
  readonly props = $derived.by(() => {
678
654
  const root = this.#getRoot();
679
- const isOpenForTrigger = Boolean(root?.opts.open.current && root.isActiveTrigger(this.opts.id.current));
655
+ const isOpenForTrigger = Boolean(root?.opts.open && root.isActiveTrigger(this.opts.id));
680
656
  const isDisabled = this.#isDisabled();
681
657
 
682
658
  return {
683
- id: this.opts.id.current,
659
+ id: this.opts.id,
684
660
  'aria-describedby': isOpenForTrigger ? root?.contentNode?.id : undefined,
685
661
  'data-state': isOpenForTrigger ? root?.stateAttr : 'closed',
686
662
  'data-disabled': boolToEmptyStrOrUndef(isDisabled),
687
663
  'data-delay-duration': `${root?.delayDuration ?? 0}`,
688
664
  [tooltipAttrs.trigger]: '',
689
- tabindex: isDisabled ? undefined : this.opts.tabindex.current,
690
- disabled: this.opts.disabled.current,
665
+ tabindex: isDisabled ? undefined : this.opts.tabindex,
666
+ disabled: this.opts.disabled,
691
667
  onpointerup: this.#onpointerup,
692
668
  onpointerdown: this.#onpointerdown,
693
669
  onpointerenter: this.#onpointerenter,
@@ -701,13 +677,10 @@ export class TooltipTriggerState {
701
677
  });
702
678
  }
703
679
 
704
- interface TooltipContentStateOpts
705
- extends
706
- WithRefOpts,
707
- ReadableBoxedValues<{
708
- onInteractOutside: (e: PointerEvent) => void;
709
- onEscapeKeydown: (e: KeyboardEvent) => void;
710
- }> {}
680
+ interface TooltipContentStateOpts extends RefOpts {
681
+ readonly onInteractOutside: (e: PointerEvent) => void;
682
+ readonly onEscapeKeydown: (e: KeyboardEvent) => void;
683
+ }
711
684
 
712
685
  export class TooltipContentState {
713
686
  static create(opts: TooltipContentStateOpts) {
@@ -719,17 +692,20 @@ export class TooltipContentState {
719
692
  constructor(opts: TooltipContentStateOpts, root: TooltipRootState) {
720
693
  this.opts = opts;
721
694
  this.root = root;
722
- this.attachment = attachRef(this.opts.ref, (v) => (this.root.contentNode = v));
695
+ this.attachment = attachRef<HTMLElement>(
696
+ (v) => (opts.ref = v),
697
+ (v) => (this.root.contentNode = v),
698
+ );
723
699
 
724
700
  new SafePolygon({
725
701
  triggerNode: () => this.root.triggerNode,
726
702
  contentNode: () => this.root.contentNode,
727
- enabled: () => this.root.opts.open.current && !this.root.disableHoverableContent,
703
+ enabled: () => this.root.opts.open && !this.root.disableHoverableContent,
728
704
  transitIntentTimeout: 180,
729
705
  ignoredTargets: () => {
730
706
  // only skip closing for sibling triggers when there's a skip-delay grace period;
731
707
  // with skipDelayDuration=0 the close+reopen is intentional (full delay + re-animation)
732
- if (this.root.provider.opts.skipDelayDuration.current === 0) return [];
708
+ if (this.root.provider.opts.skipDelayDuration === 0) return [];
733
709
  const nodes: HTMLElement[] = [];
734
710
  const activeTriggerNode = this.root.triggerNode;
735
711
  for (const record of this.root.registry.triggers.values()) {
@@ -752,13 +728,13 @@ export class TooltipContentState {
752
728
  e.preventDefault();
753
729
  return;
754
730
  }
755
- this.opts.onInteractOutside.current(e);
731
+ this.opts.onInteractOutside(e);
756
732
  if (e.defaultPrevented) return;
757
733
  this.root.handleClose();
758
734
  };
759
735
 
760
736
  onEscapeKeydown = (e: KeyboardEvent) => {
761
- this.opts.onEscapeKeydown.current?.(e);
737
+ this.opts.onEscapeKeydown?.(e);
762
738
  if (e.defaultPrevented) return;
763
739
  this.root.handleClose();
764
740
  };
@@ -771,12 +747,12 @@ export class TooltipContentState {
771
747
  e.preventDefault();
772
748
  };
773
749
 
774
- readonly snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
750
+ readonly snippetProps = $derived.by(() => ({ open: this.root.opts.open }));
775
751
 
776
752
  readonly props = $derived.by(
777
753
  () =>
778
754
  ({
779
- id: this.opts.id.current,
755
+ id: this.opts.id,
780
756
  'data-state': this.root.stateAttr,
781
757
  'data-disabled': boolToEmptyStrOrUndef(this.root.disabled),
782
758
  style: {
@@ -1,5 +1,4 @@
1
1
  import {
2
- simpleBox,
3
2
  boxWith,
4
3
  attachRef,
5
4
  type Getter,
@@ -219,7 +218,7 @@ export function interactOutsideAttachment(opts: {
219
218
  enabled: Getter<boolean>;
220
219
  isValidEvent?: Getter<((e: PointerEvent, node: HTMLElement) => boolean) | undefined>;
221
220
  }): { props: { onfocuscapture: () => void; onblurcapture: () => void }; attachment: RefAttachment<HTMLElement> } {
222
- const ref = simpleBox<HTMLElement | null>(null);
221
+ let node = $state.raw<HTMLElement | null>(null);
223
222
  const state = DismissibleLayerState.create({
224
223
  id: boxWith(opts.id),
225
224
  interactOutsideBehavior: boxWith(opts.interactOutsideBehavior),
@@ -227,9 +226,9 @@ export function interactOutsideAttachment(opts: {
227
226
  enabled: boxWith(opts.enabled),
228
227
  onFocusOutside: boxWith(() => opts.onFocusOutside?.()),
229
228
  isValidEvent: boxWith(() => opts.isValidEvent?.()),
230
- ref,
229
+ ref: boxWith(() => node, (v) => (node = v)),
231
230
  });
232
- return { props: state.props, attachment: attachRef(ref) };
231
+ return { props: state.props, attachment: attachRef((v) => (node = v)) };
233
232
  }
234
233
 
235
234
  function isValidEvent(e: PointerEvent, node: HTMLElement): boolean {
@@ -1,28 +1,21 @@
1
- import {
2
- DOMContext,
3
- boxWith,
4
- type Box,
5
- type Getter,
6
- type ReadableBox,
7
- type ReadableBoxedValues,
8
- type RefAttachment,
9
- } from '../tools/index.js';
1
+ import { DOMContext, type Getter, type RefAttachment } from '../tools/index.js';
10
2
  import { untrack } from 'svelte';
11
3
  import { on } from 'svelte/events';
12
4
  import { createAttachmentKey } from 'svelte/attachments';
13
- import type { EscapeBehaviorType, EscapeLayerImplProps } from './types.js';
5
+ import type { EscapeBehaviorType } from './types.js';
14
6
  import { kbd } from '../kbd.js';
15
7
  import { createLayerStack } from '../layer-stack.js';
16
8
  import { globalSingleton } from '../global-singleton.js';
17
9
 
18
10
  const escapeLayers = globalSingleton('bitsEscapeLayers', () =>
19
- createLayerStack<EscapeLayerState, ReadableBox<EscapeBehaviorType>>(
20
- (box) => box.current === 'close' || box.current === 'ignore',
21
- ),
11
+ createLayerStack<EscapeLayerState, Getter<EscapeBehaviorType>>((behavior) => behavior() === 'close' || behavior() === 'ignore'),
22
12
  );
23
13
 
24
- interface EscapeLayerStateOpts extends ReadableBoxedValues<Required<Omit<EscapeLayerImplProps, 'children' | 'ref'>>> {
25
- ref: Box<HTMLElement | null>;
14
+ interface EscapeLayerStateOpts {
15
+ escapeKeydownBehavior: Getter<EscapeBehaviorType>;
16
+ onEscapeKeydown: Getter<(e: KeyboardEvent) => void>;
17
+ enabled: Getter<boolean>;
18
+ ref: Getter<HTMLElement | null>;
26
19
  }
27
20
 
28
21
  export class EscapeLayerState {
@@ -34,10 +27,10 @@ export class EscapeLayerState {
34
27
 
35
28
  constructor(opts: EscapeLayerStateOpts) {
36
29
  this.opts = opts;
37
- this.domContext = new DOMContext(this.opts.ref);
30
+ this.domContext = new DOMContext(opts.ref);
38
31
 
39
32
  $effect(() => {
40
- if (!opts.enabled.current) return;
33
+ if (!opts.enabled()) return;
41
34
 
42
35
  const unsubEvents = untrack(() => {
43
36
  escapeLayers.register(this, opts.escapeKeydownBehavior);
@@ -55,9 +48,9 @@ export class EscapeLayerState {
55
48
  if (e.key !== kbd.ESCAPE || !escapeLayers.isResponsible(this)) return;
56
49
  const clonedEvent = new KeyboardEvent(e.type, e);
57
50
  e.preventDefault();
58
- const behaviorType = this.opts.escapeKeydownBehavior.current;
51
+ const behaviorType = this.opts.escapeKeydownBehavior();
59
52
  if (behaviorType !== 'close' && behaviorType !== 'defer-otherwise-close') return;
60
- this.opts.onEscapeKeydown.current(clonedEvent);
53
+ this.opts.onEscapeKeydown()(clonedEvent);
61
54
  };
62
55
  }
63
56
 
@@ -76,16 +69,7 @@ export function escapeKeydownAttachment(opts: {
76
69
  }): RefAttachment<HTMLElement> {
77
70
  return {
78
71
  [createAttachmentKey()]: (node) => {
79
- EscapeLayerState.create({
80
- escapeKeydownBehavior: boxWith(opts.escapeKeydownBehavior),
81
- onEscapeKeydown: boxWith(opts.onEscapeKeydown),
82
- enabled: boxWith(opts.enabled),
83
- // The state only reads the ref (to resolve the owner document), so a no-op setter is fine.
84
- ref: boxWith(
85
- () => node,
86
- () => {},
87
- ),
88
- });
72
+ EscapeLayerState.create({ ...opts, ref: () => node });
89
73
  },
90
74
  };
91
75
  }