bits-ui 2.13.1 → 2.14.1

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 (57) hide show
  1. package/dist/bits/accordion/accordion.svelte.d.ts +5 -1
  2. package/dist/bits/accordion/accordion.svelte.js +21 -2
  3. package/dist/bits/accordion/components/accordion-content.svelte +12 -24
  4. package/dist/bits/alert-dialog/components/alert-dialog-content.svelte +50 -62
  5. package/dist/bits/collapsible/collapsible.svelte.d.ts +4 -1
  6. package/dist/bits/collapsible/collapsible.svelte.js +15 -2
  7. package/dist/bits/collapsible/components/collapsible-content.svelte +12 -24
  8. package/dist/bits/context-menu/components/context-menu-content-static.svelte +2 -0
  9. package/dist/bits/context-menu/components/context-menu-content.svelte +2 -0
  10. package/dist/bits/date-field/date-field.svelte.js +5 -3
  11. package/dist/bits/dialog/components/dialog-content.svelte +44 -57
  12. package/dist/bits/dialog/components/dialog-overlay.svelte +9 -12
  13. package/dist/bits/dialog/dialog.svelte.d.ts +6 -0
  14. package/dist/bits/dialog/dialog.svelte.js +17 -3
  15. package/dist/bits/dropdown-menu/components/dropdown-menu-content-static.svelte +2 -0
  16. package/dist/bits/dropdown-menu/components/dropdown-menu-content.svelte +2 -0
  17. package/dist/bits/link-preview/components/link-preview-content-static.svelte +2 -0
  18. package/dist/bits/link-preview/components/link-preview-content.svelte +2 -0
  19. package/dist/bits/link-preview/link-preview.svelte.d.ts +3 -0
  20. package/dist/bits/link-preview/link-preview.svelte.js +6 -2
  21. package/dist/bits/menu/components/menu-content-static.svelte +2 -0
  22. package/dist/bits/menu/components/menu-content.svelte +2 -0
  23. package/dist/bits/menu/components/menu-sub-content-static.svelte +2 -0
  24. package/dist/bits/menu/components/menu-sub-content.svelte +2 -0
  25. package/dist/bits/menu/menu.svelte.d.ts +3 -0
  26. package/dist/bits/menu/menu.svelte.js +6 -2
  27. package/dist/bits/popover/components/popover-content-static.svelte +2 -0
  28. package/dist/bits/popover/components/popover-content.svelte +2 -0
  29. package/dist/bits/popover/components/popover-overlay.svelte +37 -0
  30. package/dist/bits/popover/components/popover-overlay.svelte.d.ts +4 -0
  31. package/dist/bits/popover/exports.d.ts +2 -1
  32. package/dist/bits/popover/exports.js +1 -0
  33. package/dist/bits/popover/popover.svelte.d.ts +25 -0
  34. package/dist/bits/popover/popover.svelte.js +39 -3
  35. package/dist/bits/popover/types.d.ts +6 -0
  36. package/dist/bits/select/components/select-content-static.svelte +2 -0
  37. package/dist/bits/select/components/select-content.svelte +2 -0
  38. package/dist/bits/select/select.svelte.d.ts +3 -0
  39. package/dist/bits/select/select.svelte.js +6 -2
  40. package/dist/bits/tooltip/components/tooltip-content-static.svelte +2 -0
  41. package/dist/bits/tooltip/components/tooltip-content.svelte +2 -0
  42. package/dist/bits/tooltip/tooltip.svelte.d.ts +3 -0
  43. package/dist/bits/tooltip/tooltip.svelte.js +6 -2
  44. package/dist/bits/utilities/popper-layer/popper-layer-inner.svelte +1 -1
  45. package/dist/bits/utilities/popper-layer/popper-layer-inner.svelte.d.ts +1 -1
  46. package/dist/bits/utilities/popper-layer/popper-layer.svelte +43 -45
  47. package/dist/bits/utilities/popper-layer/types.d.ts +4 -0
  48. package/dist/internal/animations-complete.js +7 -15
  49. package/dist/internal/date-time/field/helpers.js +3 -1
  50. package/dist/internal/date-time/field/time-helpers.js +4 -1
  51. package/dist/internal/presence-manager.svelte.d.ts +14 -0
  52. package/dist/internal/presence-manager.svelte.js +34 -0
  53. package/dist/internal/should-enable-focus-trap.d.ts +1 -2
  54. package/dist/internal/should-enable-focus-trap.js +2 -2
  55. package/package.json +2 -2
  56. package/dist/internal/open-change-complete.d.ts +0 -13
  57. package/dist/internal/open-change-complete.js +0 -24
@@ -10,7 +10,7 @@ import { createBitsAttrs } from "../../internal/attrs.js";
10
10
  import { getFloatingContentCSSVars } from "../../internal/floating-svelte/floating-utils.svelte.js";
11
11
  import { DataTypeahead } from "../../internal/data-typeahead.svelte.js";
12
12
  import { DOMTypeahead } from "../../internal/dom-typeahead.svelte.js";
13
- import { OpenChangeComplete } from "../../internal/open-change-complete.js";
13
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
14
14
  // prettier-ignore
15
15
  export const INTERACTION_KEYS = [kbd.ARROW_LEFT, kbd.ESCAPE, kbd.ARROW_RIGHT, kbd.SHIFT, kbd.CAPS_LOCK, kbd.CONTROL, kbd.ALT, kbd.META, kbd.ENTER, kbd.F1, kbd.F2, kbd.F3, kbd.F4, kbd.F5, kbd.F6, kbd.F7, kbd.F8, kbd.F9, kbd.F10, kbd.F11, kbd.F12];
16
16
  export const FIRST_KEYS = [kbd.ARROW_DOWN, kbd.PAGE_UP, kbd.HOME];
@@ -45,6 +45,7 @@ class SelectBaseRootState {
45
45
  touchedInput = $state(false);
46
46
  inputNode = $state(null);
47
47
  contentNode = $state(null);
48
+ contentPresence;
48
49
  viewportNode = $state(null);
49
50
  triggerNode = $state(null);
50
51
  valueId = $state("");
@@ -70,7 +71,7 @@ class SelectBaseRootState {
70
71
  constructor(opts) {
71
72
  this.opts = opts;
72
73
  this.isCombobox = opts.isCombobox;
73
- new OpenChangeComplete({
74
+ this.contentPresence = new PresenceManager({
74
75
  ref: boxWith(() => this.contentNode),
75
76
  open: this.opts.open,
76
77
  onComplete: () => {
@@ -749,6 +750,9 @@ export class SelectContentState {
749
750
  onCloseAutoFocus = (e) => {
750
751
  e.preventDefault();
751
752
  };
753
+ get shouldRender() {
754
+ return this.root.contentPresence.shouldRender;
755
+ }
752
756
  snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
753
757
  props = $derived.by(() => ({
754
758
  id: this.opts.id.current,
@@ -47,6 +47,7 @@
47
47
  forceMount={true}
48
48
  ref={contentState.opts.ref}
49
49
  tooltip={true}
50
+ shouldRender={contentState.shouldRender}
50
51
  >
51
52
  {#snippet popper({ props })}
52
53
  {@const mergedProps = mergeProps(props, {
@@ -74,6 +75,7 @@
74
75
  preventScroll={false}
75
76
  forceMount={false}
76
77
  ref={contentState.opts.ref}
78
+ shouldRender={contentState.shouldRender}
77
79
  >
78
80
  {#snippet popper({ props })}
79
81
  {@const mergedProps = mergeProps(props, {
@@ -67,6 +67,7 @@
67
67
  forceMount={true}
68
68
  ref={contentState.opts.ref}
69
69
  tooltip={true}
70
+ shouldRender={contentState.shouldRender}
70
71
  >
71
72
  {#snippet popper({ props, wrapperProps })}
72
73
  {@const mergedProps = mergeProps(props, {
@@ -95,6 +96,7 @@
95
96
  forceMount={false}
96
97
  ref={contentState.opts.ref}
97
98
  tooltip={true}
99
+ shouldRender={contentState.shouldRender}
98
100
  >
99
101
  {#snippet popper({ props, wrapperProps })}
100
102
  {@const mergedProps = mergeProps(props, {
@@ -1,6 +1,7 @@
1
1
  import { DOMContext, type WritableBoxedValues, type ReadableBoxedValues } from "svelte-toolbelt";
2
2
  import type { OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
3
3
  import type { FocusEventHandler, MouseEventHandler, PointerEventHandler } from "svelte/elements";
4
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
4
5
  export declare const tooltipAttrs: import("../../internal/attrs.js").CreateBitsAttrsReturn<readonly ["content", "trigger"]>;
5
6
  interface TooltipProviderStateOpts extends ReadableBoxedValues<{
6
7
  delayDuration: number;
@@ -44,6 +45,7 @@ export declare class TooltipRootState {
44
45
  readonly disabled: boolean;
45
46
  readonly ignoreNonKeyboardFocus: boolean;
46
47
  contentNode: HTMLElement | null;
48
+ contentPresence: PresenceManager;
47
49
  triggerNode: HTMLElement | null;
48
50
  readonly stateAttr: string;
49
51
  constructor(opts: TooltipRootStateOpts, provider: TooltipProviderState);
@@ -97,6 +99,7 @@ export declare class TooltipContentState {
97
99
  onEscapeKeydown: (e: KeyboardEvent) => void;
98
100
  onOpenAutoFocus: (e: Event) => void;
99
101
  onCloseAutoFocus: (e: Event) => void;
102
+ get shouldRender(): boolean;
100
103
  readonly snippetProps: {
101
104
  open: boolean;
102
105
  };
@@ -5,7 +5,7 @@ import { isElement, isFocusVisible } from "../../internal/is.js";
5
5
  import { createBitsAttrs, boolToEmptyStrOrUndef } from "../../internal/attrs.js";
6
6
  import { TimeoutFn } from "../../internal/timeout-fn.js";
7
7
  import { GraceArea } from "../../internal/grace-area.svelte.js";
8
- import { OpenChangeComplete } from "../../internal/open-change-complete.js";
8
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
9
9
  export const tooltipAttrs = createBitsAttrs({
10
10
  component: "tooltip",
11
11
  parts: ["content", "trigger"],
@@ -72,6 +72,7 @@ export class TooltipRootState {
72
72
  ignoreNonKeyboardFocus = $derived.by(() => this.opts.ignoreNonKeyboardFocus.current ??
73
73
  this.provider.opts.ignoreNonKeyboardFocus.current);
74
74
  contentNode = $state(null);
75
+ contentPresence;
75
76
  triggerNode = $state(null);
76
77
  #wasOpenDelayed = $state(false);
77
78
  #timerFn;
@@ -87,7 +88,7 @@ export class TooltipRootState {
87
88
  this.#wasOpenDelayed = true;
88
89
  this.opts.open.current = true;
89
90
  }, this.delayDuration ?? 0);
90
- new OpenChangeComplete({
91
+ this.contentPresence = new PresenceManager({
91
92
  open: this.opts.open,
92
93
  ref: boxWith(() => this.contentNode),
93
94
  onComplete: () => {
@@ -294,6 +295,9 @@ export class TooltipContentState {
294
295
  onCloseAutoFocus = (e) => {
295
296
  e.preventDefault();
296
297
  };
298
+ get shouldRender() {
299
+ return this.root.contentPresence.shouldRender;
300
+ }
297
301
  snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
298
302
  props = $derived.by(() => ({
299
303
  id: this.opts.id.current,
@@ -47,7 +47,7 @@
47
47
  ref,
48
48
  tooltip = false,
49
49
  ...restProps
50
- }: Omit<PopperLayerImplProps, "open" | "children"> & {
50
+ }: Omit<PopperLayerImplProps, "open" | "children" | "shouldRender"> & {
51
51
  enabled: boolean;
52
52
  } = $props();
53
53
  </script>
@@ -1,5 +1,5 @@
1
1
  import type { PopperLayerImplProps } from "./types.js";
2
- type $$ComponentProps = Omit<PopperLayerImplProps, "open" | "children"> & {
2
+ type $$ComponentProps = Omit<PopperLayerImplProps, "open" | "children" | "shouldRender"> & {
3
3
  enabled: boolean;
4
4
  };
5
5
  declare const PopperLayerInner: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { PopperLayerImplProps } from "./types.js";
3
3
  import PopperLayerInner from "./popper-layer-inner.svelte";
4
- import PresenceLayer from "../presence-layer/presence-layer.svelte";
5
4
 
6
5
  let {
7
6
  popper,
@@ -40,51 +39,50 @@
40
39
  customAnchor = null,
41
40
  isStatic = false,
42
41
  ref,
42
+ shouldRender,
43
43
  ...restProps
44
44
  }: PopperLayerImplProps = $props();
45
45
  </script>
46
46
 
47
- <PresenceLayer {open} {ref}>
48
- {#snippet presence()}
49
- <PopperLayerInner
50
- {popper}
51
- {onEscapeKeydown}
52
- {escapeKeydownBehavior}
53
- {preventOverflowTextSelection}
54
- {id}
55
- {onPointerDown}
56
- {onPointerUp}
57
- {side}
58
- {sideOffset}
59
- {align}
60
- {alignOffset}
61
- {arrowPadding}
62
- {avoidCollisions}
63
- {collisionBoundary}
64
- {collisionPadding}
65
- {sticky}
66
- {hideWhenDetached}
67
- {updatePositionStrategy}
68
- {strategy}
69
- {dir}
70
- {preventScroll}
71
- {wrapperId}
72
- {style}
73
- {onPlaced}
74
- {customAnchor}
75
- {isStatic}
76
- enabled={open}
77
- {onInteractOutside}
78
- {onCloseAutoFocus}
79
- {onOpenAutoFocus}
80
- {interactOutsideBehavior}
81
- {loop}
82
- {trapFocus}
83
- {isValidEvent}
84
- {onFocusOutside}
85
- forceMount={false}
86
- {ref}
87
- {...restProps}
88
- />
89
- {/snippet}
90
- </PresenceLayer>
47
+ {#if shouldRender}
48
+ <PopperLayerInner
49
+ {popper}
50
+ {onEscapeKeydown}
51
+ {escapeKeydownBehavior}
52
+ {preventOverflowTextSelection}
53
+ {id}
54
+ {onPointerDown}
55
+ {onPointerUp}
56
+ {side}
57
+ {sideOffset}
58
+ {align}
59
+ {alignOffset}
60
+ {arrowPadding}
61
+ {avoidCollisions}
62
+ {collisionBoundary}
63
+ {collisionPadding}
64
+ {sticky}
65
+ {hideWhenDetached}
66
+ {updatePositionStrategy}
67
+ {strategy}
68
+ {dir}
69
+ {preventScroll}
70
+ {wrapperId}
71
+ {style}
72
+ {onPlaced}
73
+ {customAnchor}
74
+ {isStatic}
75
+ enabled={open}
76
+ {onInteractOutside}
77
+ {onCloseAutoFocus}
78
+ {onOpenAutoFocus}
79
+ {interactOutsideBehavior}
80
+ {loop}
81
+ {trapFocus}
82
+ {isValidEvent}
83
+ {onFocusOutside}
84
+ forceMount={false}
85
+ {ref}
86
+ {...restProps}
87
+ />
88
+ {/if}
@@ -31,4 +31,8 @@ export type PopperLayerImplProps = Omit<EscapeLayerImplProps & DismissibleLayerI
31
31
  * symbol so that conflicts don't occur.
32
32
  */
33
33
  tooltip?: boolean;
34
+ /**
35
+ * Whether the popper layer should be rendered.
36
+ */
37
+ shouldRender: boolean;
34
38
  }, "enabled">;
@@ -1,30 +1,23 @@
1
1
  import { afterTick, onDestroyEffect } from "svelte-toolbelt";
2
2
  export class AnimationsComplete {
3
3
  #opts;
4
- #currentFrame = undefined;
5
- #isRunning = false;
4
+ #currentFrame = null;
6
5
  constructor(opts) {
7
6
  this.#opts = opts;
8
7
  onDestroyEffect(() => this.#cleanup());
9
8
  }
10
9
  #cleanup() {
11
- if (this.#currentFrame) {
12
- window.cancelAnimationFrame(this.#currentFrame);
13
- this.#currentFrame = undefined;
14
- }
15
- this.#isRunning = false;
10
+ if (!this.#currentFrame)
11
+ return;
12
+ window.cancelAnimationFrame(this.#currentFrame);
13
+ this.#currentFrame = null;
16
14
  }
17
15
  run(fn) {
18
- // prevent multiple concurrent runs
19
- if (this.#isRunning)
20
- return;
16
+ // if already running, cleanup and restart
21
17
  this.#cleanup();
22
- this.#isRunning = true;
23
18
  const node = this.#opts.ref.current;
24
- if (!node) {
25
- this.#isRunning = false;
19
+ if (!node)
26
20
  return;
27
- }
28
21
  if (typeof node.getAnimations !== "function") {
29
22
  this.#executeCallback(fn);
30
23
  return;
@@ -43,7 +36,6 @@ export class AnimationsComplete {
43
36
  #executeCallback(fn) {
44
37
  const execute = () => {
45
38
  fn();
46
- this.#isRunning = false;
47
39
  };
48
40
  if (this.#opts.afterTick) {
49
41
  afterTick(execute);
@@ -60,7 +60,9 @@ function createContentObj(props) {
60
60
  * If we're operating in a 12 hour clock and the part is an hour, we handle
61
61
  * the conversion to 12 hour format with 2 digit hours and leading zeros here.
62
62
  */
63
- if (part === "hour" && "dayPeriod" in segmentValues && props.hourCycle !== 24) {
63
+ const is12HourMode = props.hourCycle === 12 ||
64
+ (props.hourCycle === undefined && getDefaultHourCycle(locale) === 12);
65
+ if (part === "hour" && is12HourMode) {
64
66
  /**
65
67
  * If the value is over 12, we convert to 12 hour format and add leading
66
68
  * zeroes if the value is less than 10.
@@ -6,6 +6,7 @@ import { styleToString } from "svelte-toolbelt";
6
6
  import { useId } from "../../use-id.js";
7
7
  import { getPlaceholder } from "../placeholders.js";
8
8
  import { isZonedDateTime } from "../utils.js";
9
+ import { getDefaultHourCycle } from "./helpers.js";
9
10
  export function initializeSegmentValues() {
10
11
  const initialParts = EDITABLE_TIME_SEGMENT_PARTS.map((part) => {
11
12
  if (part === "dayPeriod") {
@@ -50,7 +51,9 @@ function createTimeContentObj(props) {
50
51
  * If we're operating in a 12 hour clock and the part is an hour, we handle
51
52
  * the conversion to 12 hour format with 2 digit hours and leading zeros here.
52
53
  */
53
- if (part === "hour" && "dayPeriod" in segmentValues && props.hourCycle !== 24) {
54
+ const is12HourMode = props.hourCycle === 12 ||
55
+ (props.hourCycle === undefined && getDefaultHourCycle(locale) === 12);
56
+ if (part === "hour" && is12HourMode) {
54
57
  /**
55
58
  * If the value is over 12, we convert to 12 hour format and add leading
56
59
  * zeroes if the value is less than 10.
@@ -0,0 +1,14 @@
1
+ import type { ReadableBoxedValues } from "svelte-toolbelt";
2
+ interface PresenceManagerOpts extends ReadableBoxedValues<{
3
+ open: boolean;
4
+ ref: HTMLElement | null;
5
+ }> {
6
+ onComplete?: () => void;
7
+ enabled?: boolean;
8
+ }
9
+ export declare class PresenceManager {
10
+ #private;
11
+ constructor(opts: PresenceManagerOpts);
12
+ get shouldRender(): boolean;
13
+ }
14
+ export {};
@@ -0,0 +1,34 @@
1
+ import { watch } from "runed";
2
+ import { AnimationsComplete } from "./animations-complete.js";
3
+ export class PresenceManager {
4
+ #opts;
5
+ #enabled;
6
+ #afterAnimations;
7
+ #shouldRender = $state(false);
8
+ constructor(opts) {
9
+ this.#opts = opts;
10
+ this.#shouldRender = opts.open.current;
11
+ this.#enabled = opts.enabled ?? true;
12
+ this.#afterAnimations = new AnimationsComplete({
13
+ ref: this.#opts.ref,
14
+ afterTick: this.#opts.open,
15
+ });
16
+ watch(() => this.#opts.open.current, (isOpen) => {
17
+ if (isOpen)
18
+ this.#shouldRender = true;
19
+ if (!this.#enabled)
20
+ return;
21
+ this.#afterAnimations.run(() => {
22
+ if (isOpen === this.#opts.open.current) {
23
+ if (!this.#opts.open.current) {
24
+ this.#shouldRender = false;
25
+ }
26
+ this.#opts.onComplete?.();
27
+ }
28
+ });
29
+ });
30
+ }
31
+ get shouldRender() {
32
+ return this.#shouldRender;
33
+ }
34
+ }
@@ -1,5 +1,4 @@
1
- export declare function shouldEnableFocusTrap({ forceMount, present, open, }: {
1
+ export declare function shouldEnableFocusTrap({ forceMount, open, }: {
2
2
  forceMount: boolean;
3
- present: boolean;
4
3
  open: boolean;
5
4
  }): boolean;
@@ -1,5 +1,5 @@
1
- export function shouldEnableFocusTrap({ forceMount, present, open, }) {
1
+ export function shouldEnableFocusTrap({ forceMount, open, }) {
2
2
  if (forceMount)
3
3
  return open;
4
- return present && open;
4
+ return open;
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "2.13.1",
3
+ "version": "2.14.1",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",
@@ -28,7 +28,7 @@
28
28
  "csstype": "^3.1.3",
29
29
  "jsdom": "^24.1.3",
30
30
  "publint": "^0.2.12",
31
- "svelte": "^5.38.10",
31
+ "svelte": "5.38.1",
32
32
  "svelte-check": "^4.3.1",
33
33
  "typescript": "^5.9.2",
34
34
  "vite": "^7.1.5",
@@ -1,13 +0,0 @@
1
- import type { ReadableBoxedValues } from "svelte-toolbelt";
2
- interface OpenChangeCompleteOpts extends ReadableBoxedValues<{
3
- open: boolean;
4
- ref: HTMLElement | null;
5
- }> {
6
- onComplete: () => void;
7
- enabled?: boolean;
8
- }
9
- export declare class OpenChangeComplete {
10
- #private;
11
- constructor(opts: OpenChangeCompleteOpts);
12
- }
13
- export {};
@@ -1,24 +0,0 @@
1
- import { watch } from "runed";
2
- import { AnimationsComplete } from "./animations-complete.js";
3
- export class OpenChangeComplete {
4
- #opts;
5
- #enabled;
6
- #afterAnimations;
7
- constructor(opts) {
8
- this.#opts = opts;
9
- this.#enabled = opts.enabled ?? true;
10
- this.#afterAnimations = new AnimationsComplete({
11
- ref: this.#opts.ref,
12
- afterTick: this.#opts.open,
13
- });
14
- watch([() => this.#opts.open.current], ([open]) => {
15
- if (!this.#enabled)
16
- return;
17
- this.#afterAnimations.run(() => {
18
- if (open === this.#opts.open.current) {
19
- this.#opts.onComplete();
20
- }
21
- });
22
- });
23
- }
24
- }