bits-ui 1.6.1 → 1.8.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 (32) hide show
  1. package/dist/bits/context-menu/exports.d.ts +2 -1
  2. package/dist/bits/context-menu/exports.js +1 -0
  3. package/dist/bits/context-menu/types.d.ts +2 -2
  4. package/dist/bits/dropdown-menu/exports.d.ts +2 -1
  5. package/dist/bits/dropdown-menu/exports.js +1 -0
  6. package/dist/bits/dropdown-menu/types.d.ts +2 -2
  7. package/dist/bits/menu/components/menu-checkbox-group.svelte +43 -0
  8. package/dist/bits/menu/components/menu-checkbox-group.svelte.d.ts +4 -0
  9. package/dist/bits/menu/components/menu-checkbox-item.svelte +53 -24
  10. package/dist/bits/menu/exports.d.ts +2 -1
  11. package/dist/bits/menu/exports.js +1 -0
  12. package/dist/bits/menu/menu.svelte.d.ts +30 -4
  13. package/dist/bits/menu/menu.svelte.js +68 -3
  14. package/dist/bits/menu/types.d.ts +17 -0
  15. package/dist/bits/menubar/exports.d.ts +2 -1
  16. package/dist/bits/menubar/exports.js +1 -0
  17. package/dist/bits/menubar/types.d.ts +1 -1
  18. package/dist/bits/navigation-menu/components/navigation-menu-item.svelte +2 -0
  19. package/dist/bits/navigation-menu/components/navigation-menu-viewport.svelte +2 -0
  20. package/dist/bits/navigation-menu/navigation-menu.svelte.d.ts +29 -17
  21. package/dist/bits/navigation-menu/navigation-menu.svelte.js +94 -24
  22. package/dist/bits/navigation-menu/types.d.ts +14 -7
  23. package/dist/bits/select/components/select-scroll-down-button.svelte +1 -1
  24. package/dist/bits/select/components/select-scroll-up-button.svelte +1 -1
  25. package/dist/bits/select/select.svelte.d.ts +4 -4
  26. package/dist/bits/select/select.svelte.js +30 -21
  27. package/dist/bits/slider/components/slider.svelte +2 -0
  28. package/dist/bits/slider/slider.svelte.d.ts +2 -1
  29. package/dist/bits/slider/slider.svelte.js +4 -0
  30. package/dist/bits/slider/types.d.ts +7 -1
  31. package/dist/shared/index.d.ts +7 -0
  32. package/package.json +1 -1
@@ -15,4 +15,5 @@ export { default as SubContentStatic } from "../menu/components/menu-sub-content
15
15
  export { default as SubTrigger } from "../menu/components/menu-sub-trigger.svelte";
16
16
  export { default as CheckboxItem } from "../menu/components/menu-checkbox-item.svelte";
17
17
  export { default as Portal } from "../utilities/portal/portal.svelte";
18
- export type { ContextMenuArrowProps as ArrowProps, ContextMenuCheckboxItemProps as CheckboxItemProps, ContextMenuGroupProps as GroupProps, ContextMenuItemProps as ItemProps, ContextMenuGroupHeadingProps as GroupHeadingProps, ContextMenuRootProps as RootProps, ContextMenuRadioGroupProps as RadioGroupProps, ContextMenuRadioItemProps as RadioItemProps, ContextMenuSeparatorProps as SeparatorProps, ContextMenuSubContentProps as SubContentProps, ContextMenuSubContentStaticProps as SubContentStaticProps, ContextMenuSubProps as SubProps, ContextMenuSubTriggerProps as SubTriggerProps, ContextMenuContentProps as ContentProps, ContextMenuContentStaticProps as ContentStaticProps, ContextMenuTriggerProps as TriggerProps, ContextMenuPortalProps as PortalProps, } from "./types.js";
18
+ export { default as CheckboxGroup } from "../menu/components/menu-checkbox-group.svelte";
19
+ export type { ContextMenuArrowProps as ArrowProps, ContextMenuCheckboxItemProps as CheckboxItemProps, ContextMenuGroupProps as GroupProps, ContextMenuItemProps as ItemProps, ContextMenuGroupHeadingProps as GroupHeadingProps, ContextMenuRootProps as RootProps, ContextMenuRadioGroupProps as RadioGroupProps, ContextMenuRadioItemProps as RadioItemProps, ContextMenuSeparatorProps as SeparatorProps, ContextMenuSubContentProps as SubContentProps, ContextMenuSubContentStaticProps as SubContentStaticProps, ContextMenuSubProps as SubProps, ContextMenuSubTriggerProps as SubTriggerProps, ContextMenuContentProps as ContentProps, ContextMenuContentStaticProps as ContentStaticProps, ContextMenuTriggerProps as TriggerProps, ContextMenuPortalProps as PortalProps, ContextMenuCheckboxGroupProps as CheckboxGroupProps, } from "./types.js";
@@ -15,3 +15,4 @@ export { default as SubContentStatic } from "../menu/components/menu-sub-content
15
15
  export { default as SubTrigger } from "../menu/components/menu-sub-trigger.svelte";
16
16
  export { default as CheckboxItem } from "../menu/components/menu-checkbox-item.svelte";
17
17
  export { default as Portal } from "../utilities/portal/portal.svelte";
18
+ export { default as CheckboxGroup } from "../menu/components/menu-checkbox-group.svelte";
@@ -11,5 +11,5 @@ export type ContextMenuTriggerPropsWithoutHTML = WithChild<{
11
11
  disabled?: boolean;
12
12
  }>;
13
13
  export type ContextMenuTriggerProps = ContextMenuTriggerPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, ContextMenuTriggerPropsWithoutHTML>;
14
- export type { MenuArrowProps as ContextMenuArrowProps, MenuContentStaticProps as ContextMenuContentStaticProps, MenuCheckboxItemProps as ContextMenuCheckboxItemProps, MenuGroupProps as ContextMenuGroupProps, MenuItemProps as ContextMenuItemProps, MenuGroupHeadingProps as ContextMenuGroupHeadingProps, MenuRootProps as ContextMenuRootProps, MenuRadioGroupProps as ContextMenuRadioGroupProps, MenuRadioItemProps as ContextMenuRadioItemProps, MenuSeparatorProps as ContextMenuSeparatorProps, MenuSubContentProps as ContextMenuSubContentProps, MenuSubContentStaticProps as ContextMenuSubContentStaticProps, MenuSubProps as ContextMenuSubProps, MenuSubTriggerProps as ContextMenuSubTriggerProps, MenuPortalProps as ContextMenuPortalProps, } from "../menu/types.js";
15
- export type { MenuRootPropsWithoutHTML as ContextMenuRootPropsWithoutHTML, MenuContentStaticPropsWithoutHTML as ContextMenuContentStaticPropsWithoutHTML, MenuArrowPropsWithoutHTML as ContextMenuArrowPropsWithoutHTML, MenuCheckboxItemPropsWithoutHTML as ContextMenuCheckboxItemPropsWithoutHTML, MenuGroupPropsWithoutHTML as ContextMenuGroupPropsWithoutHTML, MenuItemPropsWithoutHTML as ContextMenuItemPropsWithoutHTML, MenuGroupHeadingPropsWithoutHTML as ContextMenuGroupHeadingPropsWithoutHTML, MenuRadioGroupPropsWithoutHTML as ContextMenuRadioGroupPropsWithoutHTML, MenuRadioItemPropsWithoutHTML as ContextMenuRadioItemPropsWithoutHTML, MenuSeparatorPropsWithoutHTML as ContextMenuSeparatorPropsWithoutHTML, MenuSubPropsWithoutHTML as ContextMenuSubPropsWithoutHTML, MenuSubTriggerPropsWithoutHTML as ContextMenuSubTriggerPropsWithoutHTML, MenuSubContentPropsWithoutHTML as ContextMenuSubContentPropsWithoutHTML, MenuSubContentStaticPropsWithoutHTML as ContextMenuSubContentStaticPropsWithoutHTML, MenuPortalPropsWithoutHTML as ContextMenuPortalPropsWithoutHTML, } from "../menu/types.js";
14
+ export type { MenuArrowProps as ContextMenuArrowProps, MenuContentStaticProps as ContextMenuContentStaticProps, MenuCheckboxItemProps as ContextMenuCheckboxItemProps, MenuGroupProps as ContextMenuGroupProps, MenuItemProps as ContextMenuItemProps, MenuGroupHeadingProps as ContextMenuGroupHeadingProps, MenuRootProps as ContextMenuRootProps, MenuRadioGroupProps as ContextMenuRadioGroupProps, MenuRadioItemProps as ContextMenuRadioItemProps, MenuSeparatorProps as ContextMenuSeparatorProps, MenuSubContentProps as ContextMenuSubContentProps, MenuSubContentStaticProps as ContextMenuSubContentStaticProps, MenuSubProps as ContextMenuSubProps, MenuSubTriggerProps as ContextMenuSubTriggerProps, MenuPortalProps as ContextMenuPortalProps, MenuCheckboxGroupProps as ContextMenuCheckboxGroupProps, } from "../menu/types.js";
15
+ export type { MenuRootPropsWithoutHTML as ContextMenuRootPropsWithoutHTML, MenuContentStaticPropsWithoutHTML as ContextMenuContentStaticPropsWithoutHTML, MenuArrowPropsWithoutHTML as ContextMenuArrowPropsWithoutHTML, MenuCheckboxItemPropsWithoutHTML as ContextMenuCheckboxItemPropsWithoutHTML, MenuGroupPropsWithoutHTML as ContextMenuGroupPropsWithoutHTML, MenuItemPropsWithoutHTML as ContextMenuItemPropsWithoutHTML, MenuGroupHeadingPropsWithoutHTML as ContextMenuGroupHeadingPropsWithoutHTML, MenuRadioGroupPropsWithoutHTML as ContextMenuRadioGroupPropsWithoutHTML, MenuRadioItemPropsWithoutHTML as ContextMenuRadioItemPropsWithoutHTML, MenuSeparatorPropsWithoutHTML as ContextMenuSeparatorPropsWithoutHTML, MenuSubPropsWithoutHTML as ContextMenuSubPropsWithoutHTML, MenuSubTriggerPropsWithoutHTML as ContextMenuSubTriggerPropsWithoutHTML, MenuSubContentPropsWithoutHTML as ContextMenuSubContentPropsWithoutHTML, MenuSubContentStaticPropsWithoutHTML as ContextMenuSubContentStaticPropsWithoutHTML, MenuPortalPropsWithoutHTML as ContextMenuPortalPropsWithoutHTML, MenuCheckboxGroupPropsWithoutHTML as ContextMenuCheckboxGroupPropsWithoutHTML, } from "../menu/types.js";
@@ -15,4 +15,5 @@ export { default as SubContentStatic } from "../menu/components/menu-sub-content
15
15
  export { default as SubTrigger } from "../menu/components/menu-sub-trigger.svelte";
16
16
  export { default as CheckboxItem } from "../menu/components/menu-checkbox-item.svelte";
17
17
  export { default as Portal } from "../utilities/portal/portal.svelte";
18
- export type { DropdownMenuArrowProps as ArrowProps, DropdownMenuCheckboxItemProps as CheckboxItemProps, DropdownMenuContentProps as ContentProps, DropdownMenuContentStaticProps as ContentStaticProps, DropdownMenuGroupProps as GroupProps, DropdownMenuItemProps as ItemProps, DropdownMenuGroupHeadingProps as GroupHeadingProps, DropdownMenuRootProps as RootProps, DropdownMenuRadioGroupProps as RadioGroupProps, DropdownMenuRadioItemProps as RadioItemProps, DropdownMenuSeparatorProps as SeparatorProps, DropdownMenuSubContentProps as SubContentProps, DropdownMenuSubContentStaticProps as SubContentStaticProps, DropdownMenuSubProps as SubProps, DropdownMenuSubTriggerProps as SubTriggerProps, DropdownMenuTriggerProps as TriggerProps, DropdownMenuPortalProps as PortalProps, } from "./types.js";
18
+ export { default as CheckboxGroup } from "../menu/components/menu-checkbox-group.svelte";
19
+ export type { DropdownMenuArrowProps as ArrowProps, DropdownMenuCheckboxItemProps as CheckboxItemProps, DropdownMenuContentProps as ContentProps, DropdownMenuContentStaticProps as ContentStaticProps, DropdownMenuGroupProps as GroupProps, DropdownMenuItemProps as ItemProps, DropdownMenuGroupHeadingProps as GroupHeadingProps, DropdownMenuRootProps as RootProps, DropdownMenuRadioGroupProps as RadioGroupProps, DropdownMenuRadioItemProps as RadioItemProps, DropdownMenuSeparatorProps as SeparatorProps, DropdownMenuSubContentProps as SubContentProps, DropdownMenuSubContentStaticProps as SubContentStaticProps, DropdownMenuSubProps as SubProps, DropdownMenuSubTriggerProps as SubTriggerProps, DropdownMenuTriggerProps as TriggerProps, DropdownMenuPortalProps as PortalProps, DropdownMenuCheckboxGroupProps as CheckboxGroupProps, } from "./types.js";
@@ -15,3 +15,4 @@ export { default as SubContentStatic } from "../menu/components/menu-sub-content
15
15
  export { default as SubTrigger } from "../menu/components/menu-sub-trigger.svelte";
16
16
  export { default as CheckboxItem } from "../menu/components/menu-checkbox-item.svelte";
17
17
  export { default as Portal } from "../utilities/portal/portal.svelte";
18
+ export { default as CheckboxGroup } from "../menu/components/menu-checkbox-group.svelte";
@@ -1,2 +1,2 @@
1
- export type { MenuArrowProps as DropdownMenuArrowProps, MenuCheckboxItemProps as DropdownMenuCheckboxItemProps, MenuContentProps as DropdownMenuContentProps, MenuContentStaticProps as DropdownMenuContentStaticProps, MenuGroupProps as DropdownMenuGroupProps, MenuItemProps as DropdownMenuItemProps, MenuGroupHeadingProps as DropdownMenuGroupHeadingProps, MenuRootProps as DropdownMenuRootProps, MenuRadioGroupProps as DropdownMenuRadioGroupProps, MenuRadioItemProps as DropdownMenuRadioItemProps, MenuSeparatorProps as DropdownMenuSeparatorProps, MenuSubContentProps as DropdownMenuSubContentProps, MenuSubContentStaticProps as DropdownMenuSubContentStaticProps, MenuSubProps as DropdownMenuSubProps, MenuSubTriggerProps as DropdownMenuSubTriggerProps, MenuTriggerProps as DropdownMenuTriggerProps, MenuPortalProps as DropdownMenuPortalProps, } from "../menu/types.js";
2
- export type { MenuRootPropsWithoutHTML as DropdownMenuRootPropsWithoutHTML, MenuArrowPropsWithoutHTML as DropdownMenuArrowPropsWithoutHTML, MenuCheckboxItemPropsWithoutHTML as DropdownMenuCheckboxItemPropsWithoutHTML, MenuContentPropsWithoutHTML as DropdownMenuContentPropsWithoutHTML, MenuContentStaticPropsWithoutHTML as DropdownMenuContentStaticPropsWithoutHTML, MenuGroupPropsWithoutHTML as DropdownMenuGroupPropsWithoutHTML, MenuItemPropsWithoutHTML as DropdownMenuItemPropsWithoutHTML, MenuGroupHeadingPropsWithoutHTML as DropdownMenuGroupHeadingPropsWithoutHTML, MenuRadioGroupPropsWithoutHTML as DropdownMenuRadioGroupPropsWithoutHTML, MenuRadioItemPropsWithoutHTML as DropdownMenuRadioItemPropsWithoutHTML, MenuSeparatorPropsWithoutHTML as DropdownMenuSeparatorPropsWithoutHTML, MenuSubPropsWithoutHTML as DropdownMenuSubPropsWithoutHTML, MenuSubTriggerPropsWithoutHTML as DropdownMenuSubTriggerPropsWithoutHTML, MenuSubContentPropsWithoutHTML as DropdownMenuSubContentPropsWithoutHTML, MenuSubContentStaticPropsWithoutHTML as DropdownMenuSubContentStaticPropsWithoutHTML, MenuTriggerPropsWithoutHTML as DropdownMenuTriggerPropsWithoutHTML, MenuPortalPropsWithoutHTML as DropdownMenuPortalPropsWithoutHTML, } from "../menu/types.js";
1
+ export type { MenuArrowProps as DropdownMenuArrowProps, MenuCheckboxItemProps as DropdownMenuCheckboxItemProps, MenuContentProps as DropdownMenuContentProps, MenuContentStaticProps as DropdownMenuContentStaticProps, MenuGroupProps as DropdownMenuGroupProps, MenuItemProps as DropdownMenuItemProps, MenuGroupHeadingProps as DropdownMenuGroupHeadingProps, MenuRootProps as DropdownMenuRootProps, MenuRadioGroupProps as DropdownMenuRadioGroupProps, MenuRadioItemProps as DropdownMenuRadioItemProps, MenuSeparatorProps as DropdownMenuSeparatorProps, MenuSubContentProps as DropdownMenuSubContentProps, MenuSubContentStaticProps as DropdownMenuSubContentStaticProps, MenuSubProps as DropdownMenuSubProps, MenuSubTriggerProps as DropdownMenuSubTriggerProps, MenuTriggerProps as DropdownMenuTriggerProps, MenuPortalProps as DropdownMenuPortalProps, MenuCheckboxGroupProps as DropdownMenuCheckboxGroupProps, } from "../menu/types.js";
2
+ export type { MenuRootPropsWithoutHTML as DropdownMenuRootPropsWithoutHTML, MenuArrowPropsWithoutHTML as DropdownMenuArrowPropsWithoutHTML, MenuCheckboxItemPropsWithoutHTML as DropdownMenuCheckboxItemPropsWithoutHTML, MenuContentPropsWithoutHTML as DropdownMenuContentPropsWithoutHTML, MenuContentStaticPropsWithoutHTML as DropdownMenuContentStaticPropsWithoutHTML, MenuGroupPropsWithoutHTML as DropdownMenuGroupPropsWithoutHTML, MenuItemPropsWithoutHTML as DropdownMenuItemPropsWithoutHTML, MenuGroupHeadingPropsWithoutHTML as DropdownMenuGroupHeadingPropsWithoutHTML, MenuRadioGroupPropsWithoutHTML as DropdownMenuRadioGroupPropsWithoutHTML, MenuRadioItemPropsWithoutHTML as DropdownMenuRadioItemPropsWithoutHTML, MenuSeparatorPropsWithoutHTML as DropdownMenuSeparatorPropsWithoutHTML, MenuSubPropsWithoutHTML as DropdownMenuSubPropsWithoutHTML, MenuSubTriggerPropsWithoutHTML as DropdownMenuSubTriggerPropsWithoutHTML, MenuSubContentPropsWithoutHTML as DropdownMenuSubContentPropsWithoutHTML, MenuSubContentStaticPropsWithoutHTML as DropdownMenuSubContentStaticPropsWithoutHTML, MenuTriggerPropsWithoutHTML as DropdownMenuTriggerPropsWithoutHTML, MenuPortalPropsWithoutHTML as DropdownMenuPortalPropsWithoutHTML, MenuCheckboxGroupPropsWithoutHTML as DropdownMenuCheckboxGroupPropsWithoutHTML, } from "../menu/types.js";
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import { box, mergeProps } from "svelte-toolbelt";
3
+ import type { MenuCheckboxGroupProps } from "../types.js";
4
+ import { useMenuCheckboxGroup } from "../menu.svelte.js";
5
+ import { noop } from "../../../internal/noop.js";
6
+ import { useId } from "../../../internal/use-id.js";
7
+
8
+ let {
9
+ id = useId(),
10
+ children,
11
+ child,
12
+ ref = $bindable(null),
13
+ value = $bindable([]),
14
+ onValueChange = noop,
15
+ ...restProps
16
+ }: MenuCheckboxGroupProps = $props();
17
+
18
+ const checkboxGroupState = useMenuCheckboxGroup({
19
+ value: box.with(
20
+ () => $state.snapshot(value),
21
+ (v) => {
22
+ value = $state.snapshot(v);
23
+ onValueChange(v);
24
+ }
25
+ ),
26
+ onValueChange: box.with(() => onValueChange),
27
+ ref: box.with(
28
+ () => ref,
29
+ (v) => (ref = v)
30
+ ),
31
+ id: box.with(() => id),
32
+ });
33
+
34
+ const mergedProps = $derived(mergeProps(restProps, checkboxGroupState.props));
35
+ </script>
36
+
37
+ {#if child}
38
+ {@render child({ props: mergedProps })}
39
+ {:else}
40
+ <div {...mergedProps}>
41
+ {@render children?.()}
42
+ </div>
43
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { MenuCheckboxGroupProps } from "../types.js";
2
+ declare const MenuCheckboxGroup: import("svelte").Component<MenuCheckboxGroupProps, {}, "value" | "ref">;
3
+ type MenuCheckboxGroup = ReturnType<typeof MenuCheckboxGroup>;
4
+ export default MenuCheckboxGroup;
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { box, mergeProps } from "svelte-toolbelt";
3
3
  import type { MenuCheckboxItemProps } from "../types.js";
4
- import { useMenuCheckboxItem } from "../menu.svelte.js";
4
+ import { MenuCheckboxGroupContext, useMenuCheckboxItem } from "../menu.svelte.js";
5
5
  import { useId } from "../../../internal/use-id.js";
6
6
  import { noop } from "../../../internal/noop.js";
7
+ import { watch } from "runed";
7
8
 
8
9
  let {
9
10
  child,
@@ -17,33 +18,61 @@
17
18
  closeOnSelect = true,
18
19
  indeterminate = $bindable(false),
19
20
  onIndeterminateChange = noop,
21
+ value = "",
20
22
  ...restProps
21
23
  }: MenuCheckboxItemProps = $props();
22
24
 
23
- const checkboxItemState = useMenuCheckboxItem({
24
- checked: box.with(
25
- () => checked,
26
- (v) => {
27
- checked = v;
28
- onCheckedChange(v);
29
- }
30
- ),
31
- id: box.with(() => id),
32
- disabled: box.with(() => disabled),
33
- onSelect: box.with(() => handleSelect),
34
- ref: box.with(
35
- () => ref,
36
- (v) => (ref = v)
37
- ),
38
- closeOnSelect: box.with(() => closeOnSelect),
39
- indeterminate: box.with(
40
- () => indeterminate,
41
- (v) => {
42
- indeterminate = v;
43
- onIndeterminateChange(v);
25
+ const group = MenuCheckboxGroupContext.getOr(null);
26
+
27
+ if (group && value) {
28
+ if (group.opts.value.current.includes(value)) {
29
+ checked = true;
30
+ } else {
31
+ checked = false;
32
+ }
33
+ }
34
+
35
+ watch.pre(
36
+ () => value,
37
+ () => {
38
+ if (group && value) {
39
+ if (group.opts.value.current.includes(value)) {
40
+ checked = true;
41
+ } else {
42
+ checked = false;
43
+ }
44
44
  }
45
- ),
46
- });
45
+ }
46
+ );
47
+
48
+ const checkboxItemState = useMenuCheckboxItem(
49
+ {
50
+ checked: box.with(
51
+ () => checked,
52
+ (v) => {
53
+ checked = v;
54
+ onCheckedChange(v);
55
+ }
56
+ ),
57
+ id: box.with(() => id),
58
+ disabled: box.with(() => disabled),
59
+ onSelect: box.with(() => handleSelect),
60
+ ref: box.with(
61
+ () => ref,
62
+ (v) => (ref = v)
63
+ ),
64
+ closeOnSelect: box.with(() => closeOnSelect),
65
+ indeterminate: box.with(
66
+ () => indeterminate,
67
+ (v) => {
68
+ indeterminate = v;
69
+ onIndeterminateChange(v);
70
+ }
71
+ ),
72
+ value: box.with(() => value),
73
+ },
74
+ group
75
+ );
47
76
 
48
77
  function handleSelect(e: Event) {
49
78
  onSelect(e);
@@ -1,5 +1,6 @@
1
1
  export { default as Root } from "./components/menu.svelte";
2
2
  export { default as Arrow } from "./components/menu-arrow.svelte";
3
+ export { default as CheckboxGroup } from "./components/menu-checkbox-group.svelte";
3
4
  export { default as CheckboxItem } from "./components/menu-checkbox-item.svelte";
4
5
  export { default as Content } from "./components/menu-content.svelte";
5
6
  export { default as ContentStatic } from "./components/menu-content-static.svelte";
@@ -15,4 +16,4 @@ export { default as SubContent } from "./components/menu-sub-content.svelte";
15
16
  export { default as SubTrigger } from "./components/menu-sub-trigger.svelte";
16
17
  export { default as Trigger } from "./components/menu-trigger.svelte";
17
18
  export { default as SubContentStatic } from "./components/menu-sub-content-static.svelte";
18
- export type { MenuRootPropsWithoutHTML as RootProps, MenuContentProps as ContentProps, MenuContentStaticProps as ContentStaticProps, MenuItemProps as ItemProps, MenuTriggerProps as TriggerProps, MenuSubPropsWithoutHTML as SubProps, MenuSubContentProps as SubContentProps, MenuSubContentStaticProps as SubContentStaticProps, MenuSeparatorProps as SeparatorProps, MenuArrowProps as ArrowProps, MenuCheckboxItemProps as CheckboxItemProps, MenuGroupHeadingProps as GroupHeadingProps, MenuGroupProps as GroupProps, MenuRadioGroupProps as RadioGroupProps, MenuRadioItemProps as RadioItemProps, MenuSubTriggerProps as SubTriggerProps, MenuPortalProps as PortalProps, } from "./types.js";
19
+ export type { MenuRootPropsWithoutHTML as RootProps, MenuContentProps as ContentProps, MenuContentStaticProps as ContentStaticProps, MenuItemProps as ItemProps, MenuTriggerProps as TriggerProps, MenuSubPropsWithoutHTML as SubProps, MenuSubContentProps as SubContentProps, MenuSubContentStaticProps as SubContentStaticProps, MenuSeparatorProps as SeparatorProps, MenuArrowProps as ArrowProps, MenuCheckboxGroupProps as CheckboxGroupProps, MenuCheckboxItemProps as CheckboxItemProps, MenuGroupHeadingProps as GroupHeadingProps, MenuGroupProps as GroupProps, MenuRadioGroupProps as RadioGroupProps, MenuRadioItemProps as RadioItemProps, MenuSubTriggerProps as SubTriggerProps, MenuPortalProps as PortalProps, } from "./types.js";
@@ -1,5 +1,6 @@
1
1
  export { default as Root } from "./components/menu.svelte";
2
2
  export { default as Arrow } from "./components/menu-arrow.svelte";
3
+ export { default as CheckboxGroup } from "./components/menu-checkbox-group.svelte";
3
4
  export { default as CheckboxItem } from "./components/menu-checkbox-item.svelte";
4
5
  export { default as Content } from "./components/menu-content.svelte";
5
6
  export { default as ContentStatic } from "./components/menu-content-static.svelte";
@@ -1,3 +1,4 @@
1
+ import { Context } from "runed";
1
2
  import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
2
3
  import { CustomEventDispatcher } from "../../internal/events.js";
3
4
  import type { AnyFn, BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent, WithRefProps } from "../../internal/types.js";
@@ -5,6 +6,7 @@ import { useRovingFocus } from "../../internal/use-roving-focus.svelte.js";
5
6
  import type { Direction } from "../../shared/index.js";
6
7
  import { IsUsingKeyboard } from "../../index.js";
7
8
  export declare const CONTEXT_MENU_TRIGGER_ATTR = "data-context-menu-trigger";
9
+ export declare const MenuCheckboxGroupContext: Context<MenuCheckboxGroupState>;
8
10
  type MenuVariant = "context-menu" | "dropdown-menu" | "menubar";
9
11
  export type MenuRootStateProps = ReadableBoxedValues<{
10
12
  dir: Direction;
@@ -184,11 +186,14 @@ declare class MenuSubTriggerState {
184
186
  type MenuCheckboxItemStateProps = WritableBoxedValues<{
185
187
  checked: boolean;
186
188
  indeterminate: boolean;
189
+ }> & ReadableBoxedValues<{
190
+ value: string;
187
191
  }>;
188
192
  declare class MenuCheckboxItemState {
189
193
  readonly opts: MenuCheckboxItemStateProps;
190
194
  readonly item: MenuItemState;
191
- constructor(opts: MenuCheckboxItemStateProps, item: MenuItemState);
195
+ readonly group: MenuCheckboxGroupState | null;
196
+ constructor(opts: MenuCheckboxItemStateProps, item: MenuItemState, group?: MenuCheckboxGroupState | null);
192
197
  toggleChecked(): void;
193
198
  snippetProps: {
194
199
  checked: boolean;
@@ -231,8 +236,8 @@ declare class MenuGroupState {
231
236
  type MenuGroupHeadingStateProps = WithRefProps;
232
237
  declare class MenuGroupHeadingState {
233
238
  readonly opts: MenuGroupHeadingStateProps;
234
- readonly group: MenuGroupState | MenuRadioGroupState;
235
- constructor(opts: MenuGroupHeadingStateProps, group: MenuGroupState | MenuRadioGroupState);
239
+ readonly group: MenuGroupState | MenuRadioGroupState | MenuCheckboxGroupState;
240
+ constructor(opts: MenuGroupHeadingStateProps, group: MenuGroupState | MenuRadioGroupState | MenuCheckboxGroupState);
236
241
  props: {
237
242
  readonly [x: string]: string;
238
243
  readonly id: string;
@@ -360,6 +365,26 @@ declare class ContextMenuTriggerState {
360
365
  readonly oncontextmenu: (e: BitsMouseEvent) => void;
361
366
  };
362
367
  }
368
+ type MenuCheckboxGroupStateProps = WithRefProps & ReadableBoxedValues<{
369
+ onValueChange: (value: string[]) => void;
370
+ }> & WritableBoxedValues<{
371
+ value: string[];
372
+ }>;
373
+ declare class MenuCheckboxGroupState {
374
+ readonly opts: MenuCheckboxGroupStateProps;
375
+ readonly content: MenuContentState;
376
+ groupHeadingId: string | null;
377
+ root: MenuRootState;
378
+ constructor(opts: MenuCheckboxGroupStateProps, content: MenuContentState);
379
+ addValue(checkboxValue: string | undefined): void;
380
+ removeValue(checkboxValue: string | undefined): void;
381
+ props: {
382
+ readonly [x: string]: string | null;
383
+ readonly id: string;
384
+ readonly role: "group";
385
+ readonly "aria-labelledby": string | null;
386
+ };
387
+ }
363
388
  type MenuItemCombinedProps = MenuItemSharedStateProps & MenuItemStateProps;
364
389
  export declare function useMenuRoot(props: MenuRootStateProps): MenuRootState;
365
390
  export declare function useMenuMenu(root: MenuRootState, props: MenuMenuStateProps): MenuMenuState;
@@ -369,11 +394,12 @@ export declare function useMenuDropdownTrigger(props: DropdownMenuTriggerStatePr
369
394
  export declare function useMenuContextTrigger(props: ContextMenuTriggerStateProps): ContextMenuTriggerState;
370
395
  export declare function useMenuContent(props: MenuContentStateProps): MenuContentState;
371
396
  export declare function useMenuItem(props: MenuItemCombinedProps): MenuItemState;
372
- export declare function useMenuCheckboxItem(props: MenuItemCombinedProps & MenuCheckboxItemStateProps): MenuCheckboxItemState;
397
+ export declare function useMenuCheckboxItem(props: MenuItemCombinedProps & MenuCheckboxItemStateProps, checkboxGroup: MenuCheckboxGroupState | null): MenuCheckboxItemState;
373
398
  export declare function useMenuRadioGroup(props: MenuRadioGroupStateProps): MenuGroupState | MenuRadioGroupState;
374
399
  export declare function useMenuRadioItem(props: MenuRadioItemStateProps & MenuItemCombinedProps): MenuRadioItemState;
375
400
  export declare function useMenuGroup(props: MenuGroupStateProps): MenuGroupState | MenuRadioGroupState;
376
401
  export declare function useMenuGroupHeading(props: MenuGroupHeadingStateProps): MenuGroupHeadingState;
377
402
  export declare function useMenuSeparator(props: MenuSeparatorStateProps): MenuSeparatorState;
378
403
  export declare function useMenuArrow(): MenuArrowState;
404
+ export declare function useMenuCheckboxGroup(props: MenuCheckboxGroupStateProps): MenuCheckboxGroupState;
379
405
  export {};
@@ -19,6 +19,7 @@ const MenuMenuContext = new Context("Menu.Root | Menu.Sub");
19
19
  const MenuContentContext = new Context("Menu.Content");
20
20
  const MenuGroupContext = new Context("Menu.Group | Menu.RadioGroup");
21
21
  const MenuRadioGroupContext = new Context("Menu.RadioGroup");
22
+ export const MenuCheckboxGroupContext = new Context("Menu.CheckboxGroup");
22
23
  export const MenuOpenEvent = new CustomEventDispatcher("bitsmenuopen", {
23
24
  bubbles: false,
24
25
  cancelable: true,
@@ -534,9 +535,26 @@ class MenuSubTriggerState {
534
535
  class MenuCheckboxItemState {
535
536
  opts;
536
537
  item;
537
- constructor(opts, item) {
538
+ group;
539
+ constructor(opts, item, group = null) {
538
540
  this.opts = opts;
539
541
  this.item = item;
542
+ this.group = group;
543
+ // Watch for value changes in the group if we're part of one
544
+ if (this.group) {
545
+ watch(() => this.group.opts.value.current, (groupValues) => {
546
+ this.opts.checked.current = groupValues.includes(this.opts.value.current);
547
+ });
548
+ // Watch for checked state changes and sync with group
549
+ watch(() => this.opts.checked.current, (checked) => {
550
+ if (checked) {
551
+ this.group.addValue(this.opts.value.current);
552
+ }
553
+ else {
554
+ this.group.removeValue(this.opts.value.current);
555
+ }
556
+ });
557
+ }
540
558
  }
541
559
  toggleChecked() {
542
560
  if (this.opts.indeterminate.current) {
@@ -818,6 +836,43 @@ class ContextMenuTriggerState {
818
836
  oncontextmenu: this.oncontextmenu,
819
837
  }));
820
838
  }
839
+ class MenuCheckboxGroupState {
840
+ opts;
841
+ content;
842
+ groupHeadingId = $state(null);
843
+ root;
844
+ constructor(opts, content) {
845
+ this.opts = opts;
846
+ this.content = content;
847
+ this.root = content.parentMenu.root;
848
+ useRefById(opts);
849
+ }
850
+ addValue(checkboxValue) {
851
+ if (!checkboxValue)
852
+ return;
853
+ if (!this.opts.value.current.includes(checkboxValue)) {
854
+ const newValue = [...$state.snapshot(this.opts.value.current), checkboxValue];
855
+ this.opts.value.current = newValue;
856
+ this.opts.onValueChange.current(newValue);
857
+ }
858
+ }
859
+ removeValue(checkboxValue) {
860
+ if (!checkboxValue)
861
+ return;
862
+ const index = this.opts.value.current.indexOf(checkboxValue);
863
+ if (index === -1)
864
+ return;
865
+ const newValue = this.opts.value.current.filter((v) => v !== checkboxValue);
866
+ this.opts.value.current = newValue;
867
+ this.opts.onValueChange.current(newValue);
868
+ }
869
+ props = $derived.by(() => ({
870
+ id: this.opts.id.current,
871
+ [this.root.getAttr("checkbox-group")]: "",
872
+ role: "group",
873
+ "aria-labelledby": this.groupHeadingId,
874
+ }));
875
+ }
821
876
  export function useMenuRoot(props) {
822
877
  const root = new MenuRootState(props);
823
878
  FocusScopeContext.set({
@@ -853,9 +908,9 @@ export function useMenuItem(props) {
853
908
  const item = new MenuItemSharedState(props, MenuContentContext.get());
854
909
  return new MenuItemState(props, item);
855
910
  }
856
- export function useMenuCheckboxItem(props) {
911
+ export function useMenuCheckboxItem(props, checkboxGroup) {
857
912
  const item = new MenuItemState(props, new MenuItemSharedState(props, MenuContentContext.get()));
858
- return new MenuCheckboxItemState(props, item);
913
+ return new MenuCheckboxItemState(props, item, checkboxGroup);
859
914
  }
860
915
  export function useMenuRadioGroup(props) {
861
916
  return MenuGroupContext.set(MenuRadioGroupContext.set(new MenuRadioGroupState(props, MenuContentContext.get())));
@@ -870,6 +925,13 @@ export function useMenuGroup(props) {
870
925
  return MenuGroupContext.set(new MenuGroupState(props, MenuRootContext.get()));
871
926
  }
872
927
  export function useMenuGroupHeading(props) {
928
+ // Try to get checkbox group first, then radio group, then regular group
929
+ const checkboxGroup = MenuCheckboxGroupContext.getOr(null);
930
+ if (checkboxGroup)
931
+ return new MenuGroupHeadingState(props, checkboxGroup);
932
+ const radioGroup = MenuRadioGroupContext.getOr(null);
933
+ if (radioGroup)
934
+ return new MenuGroupHeadingState(props, radioGroup);
873
935
  return new MenuGroupHeadingState(props, MenuGroupContext.get());
874
936
  }
875
937
  export function useMenuSeparator(props) {
@@ -878,3 +940,6 @@ export function useMenuSeparator(props) {
878
940
  export function useMenuArrow() {
879
941
  return new MenuArrowState(MenuRootContext.get());
880
942
  }
943
+ export function useMenuCheckboxGroup(props) {
944
+ return MenuCheckboxGroupContext.set(new MenuCheckboxGroupState(props, MenuContentContext.get()));
945
+ }
@@ -98,8 +98,25 @@ export type MenuCheckboxItemPropsWithoutHTML = MenuItemPropsWithoutHTML<MenuChec
98
98
  * @defaultValue true
99
99
  */
100
100
  closeOnSelect?: boolean;
101
+ /**
102
+ * The value of the checkbox item when used in a checkbox group.
103
+ */
104
+ value?: string;
101
105
  };
102
106
  export type MenuCheckboxItemProps = MenuCheckboxItemPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuCheckboxItemPropsWithoutHTML>;
107
+ export type MenuCheckboxGroupPropsWithoutHTML = WithChild<{
108
+ /**
109
+ * The values of the selected checkbox items.
110
+ *
111
+ * Supports two-way binding with `bind:value`.
112
+ */
113
+ value?: string[];
114
+ /**
115
+ * A callback that is fired when the selected checkbox items change.
116
+ */
117
+ onValueChange?: OnChangeFn<string[]>;
118
+ }>;
119
+ export type MenuCheckboxGroupProps = MenuCheckboxGroupPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuCheckboxGroupPropsWithoutHTML>;
103
120
  export type MenuTriggerPropsWithoutHTML = WithChild<{
104
121
  /**
105
122
  * Whether the trigger is disabled.
@@ -16,5 +16,6 @@ export { default as SubTrigger } from "../menu/components/menu-sub-trigger.svelt
16
16
  export { default as RadioGroup } from "../menu/components/menu-radio-group.svelte";
17
17
  export { default as CheckboxItem } from "../menu/components/menu-checkbox-item.svelte";
18
18
  export { default as Portal } from "../utilities/portal/portal.svelte";
19
+ export { default as CheckboxGroup } from "../menu/components/menu-checkbox-group.svelte";
19
20
  export type { MenubarRootProps as RootProps, MenubarMenuProps as MenuProps, MenubarTriggerProps as TriggerProps, MenubarContentProps as ContentProps, MenubarContentStaticProps as ContentStaticProps, MenubarPortalProps as PortalProps, } from "./types.js";
20
- export type { MenuSubPropsWithoutHTML as SubProps, MenuItemProps as ItemProps, MenuGroupProps as GroupProps, MenuGroupHeadingProps as GroupHeadingProps, MenuArrowProps as ArrowProps, MenuRadioItemProps as RadioItemProps, MenuSeparatorProps as SeparatorProps, MenuSubContentProps as SubContentProps, MenuSubTriggerProps as SubTriggerProps, MenuRadioGroupProps as RadioGroupProps, MenuCheckboxItemProps as CheckboxItemProps, MenuSubContentStaticProps as SubContentStaticProps, } from "../menu/types.js";
21
+ export type { MenuSubPropsWithoutHTML as SubProps, MenuItemProps as ItemProps, MenuGroupProps as GroupProps, MenuGroupHeadingProps as GroupHeadingProps, MenuArrowProps as ArrowProps, MenuRadioItemProps as RadioItemProps, MenuSeparatorProps as SeparatorProps, MenuSubContentProps as SubContentProps, MenuSubTriggerProps as SubTriggerProps, MenuRadioGroupProps as RadioGroupProps, MenuCheckboxItemProps as CheckboxItemProps, MenuSubContentStaticProps as SubContentStaticProps, MenuCheckboxGroupProps as CheckboxGroupProps, } from "../menu/types.js";
@@ -16,3 +16,4 @@ export { default as SubTrigger } from "../menu/components/menu-sub-trigger.svelt
16
16
  export { default as RadioGroup } from "../menu/components/menu-radio-group.svelte";
17
17
  export { default as CheckboxItem } from "../menu/components/menu-checkbox-item.svelte";
18
18
  export { default as Portal } from "../utilities/portal/portal.svelte";
19
+ export { default as CheckboxGroup } from "../menu/components/menu-checkbox-group.svelte";
@@ -44,6 +44,6 @@ export type MenubarTriggerPropsWithoutHTML = WithChild<{
44
44
  disabled?: boolean | null | undefined;
45
45
  }>;
46
46
  export type MenubarTriggerProps = MenubarTriggerPropsWithoutHTML & Without<BitsPrimitiveButtonAttributes, MenubarTriggerPropsWithoutHTML>;
47
- export type { MenuContentPropsWithoutHTML as MenubarContentPropsWithoutHTML, MenuContentProps as MenubarContentProps, MenuContentStaticPropsWithoutHTML as MenubarContentStaticPropsWithoutHTML, MenuContentStaticProps as MenubarContentStaticProps, MenuItemPropsWithoutHTML as MenubarItemPropsWithoutHTML, MenuItemProps as MenubarItemProps, MenuGroupPropsWithoutHTML as MenubarGroupPropsWithoutHTML, MenuGroupProps as MenubarGroupProps, MenuGroupHeadingPropsWithoutHTML as MenubarGroupHeadingPropsWithoutHTML, MenuGroupHeadingProps as MenubarGroupHeadingProps, MenuCheckboxItemPropsWithoutHTML as MenubarCheckboxItemPropsWithoutHTML, MenuCheckboxItemProps as MenubarCheckboxItemProps, MenuRadioGroupPropsWithoutHTML as MenubarRadioGroupPropsWithoutHTML, MenuRadioGroupProps as MenubarRadioGroupProps, MenuRadioItemPropsWithoutHTML as MenubarRadioItemPropsWithoutHTML, MenuRadioItemProps as MenubarRadioItemProps, MenuSeparatorPropsWithoutHTML as MenubarSeparatorPropsWithoutHTML, MenuSeparatorProps as MenubarSeparatorProps, MenuSubContentPropsWithoutHTML as MenubarSubContentPropsWithoutHTML, MenuSubContentProps as MenubarSubContentProps, MenuSubContentStaticPropsWithoutHTML as MenubarSubContentStaticPropsWithoutHTML, MenuSubContentStaticProps as MenubarSubContentStaticProps, MenuSubTriggerPropsWithoutHTML as MenubarSubTriggerPropsWithoutHTML, MenuSubTriggerProps as MenubarSubTriggerProps, MenuSubPropsWithoutHTML as MenubarSubPropsWithoutHTML, MenuPortalPropsWithoutHTML as MenubarPortalPropsWithoutHTML, MenuPortalProps as MenubarPortalProps, } from "../menu/types.js";
47
+ export type { MenuContentPropsWithoutHTML as MenubarContentPropsWithoutHTML, MenuContentProps as MenubarContentProps, MenuContentStaticPropsWithoutHTML as MenubarContentStaticPropsWithoutHTML, MenuContentStaticProps as MenubarContentStaticProps, MenuItemPropsWithoutHTML as MenubarItemPropsWithoutHTML, MenuItemProps as MenubarItemProps, MenuGroupPropsWithoutHTML as MenubarGroupPropsWithoutHTML, MenuGroupProps as MenubarGroupProps, MenuGroupHeadingPropsWithoutHTML as MenubarGroupHeadingPropsWithoutHTML, MenuGroupHeadingProps as MenubarGroupHeadingProps, MenuCheckboxItemPropsWithoutHTML as MenubarCheckboxItemPropsWithoutHTML, MenuCheckboxItemProps as MenubarCheckboxItemProps, MenuRadioGroupPropsWithoutHTML as MenubarRadioGroupPropsWithoutHTML, MenuRadioGroupProps as MenubarRadioGroupProps, MenuRadioItemPropsWithoutHTML as MenubarRadioItemPropsWithoutHTML, MenuRadioItemProps as MenubarRadioItemProps, MenuSeparatorPropsWithoutHTML as MenubarSeparatorPropsWithoutHTML, MenuSeparatorProps as MenubarSeparatorProps, MenuSubContentPropsWithoutHTML as MenubarSubContentPropsWithoutHTML, MenuSubContentProps as MenubarSubContentProps, MenuSubContentStaticPropsWithoutHTML as MenubarSubContentStaticPropsWithoutHTML, MenuSubContentStaticProps as MenubarSubContentStaticProps, MenuSubTriggerPropsWithoutHTML as MenubarSubTriggerPropsWithoutHTML, MenuSubTriggerProps as MenubarSubTriggerProps, MenuSubPropsWithoutHTML as MenubarSubPropsWithoutHTML, MenuPortalPropsWithoutHTML as MenubarPortalPropsWithoutHTML, MenuPortalProps as MenubarPortalProps, MenuCheckboxGroupPropsWithoutHTML as MenubarCheckboxGroupPropsWithoutHTML, MenuCheckboxGroupProps as MenubarCheckboxGroupProps, } from "../menu/types.js";
48
48
  export type MenubarArrowPropsWithoutHTML = ArrowPropsWithoutHTML;
49
49
  export type MenubarArrowProps = MenuArrowProps;
@@ -10,6 +10,7 @@
10
10
  ref = $bindable(null),
11
11
  child,
12
12
  children,
13
+ openOnHover = true,
13
14
  ...restProps
14
15
  }: NavigationMenuItemProps = $props();
15
16
 
@@ -20,6 +21,7 @@
20
21
  (v) => (ref = v)
21
22
  ),
22
23
  value: box.with(() => value),
24
+ openOnHover: box.with(() => openOnHover),
23
25
  });
24
26
 
25
27
  const mergedProps = $derived(mergeProps(restProps, itemState.props));
@@ -4,6 +4,7 @@
4
4
  import { useId } from "../../../internal/use-id.js";
5
5
  import PresenceLayer from "../../utilities/presence-layer/presence-layer.svelte";
6
6
  import { box, mergeProps } from "svelte-toolbelt";
7
+ import { Mounted } from "../../utilities/index.js";
7
8
 
8
9
  let {
9
10
  id = useId(),
@@ -34,5 +35,6 @@
34
35
  {@render children?.()}
35
36
  </div>
36
37
  {/if}
38
+ <Mounted bind:mounted={viewportState.mounted} />
37
39
  {/snippet}
38
40
  </PresenceLayer>
@@ -9,6 +9,7 @@ import { SvelteMap } from "svelte/reactivity";
9
9
  import { type Direction, type Orientation } from "../../shared/index.js";
10
10
  import type { BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent } from "../../internal/types.js";
11
11
  import { useRovingFocus } from "../../internal/use-roving-focus.svelte.js";
12
+ import type { FocusEventHandler, KeyboardEventHandler, MouseEventHandler, PointerEventHandler } from "svelte/elements";
12
13
  type NavigationMenuProviderStateProps = ReadableBoxedValues<{
13
14
  dir: Direction;
14
15
  orientation: Orientation;
@@ -18,11 +19,11 @@ type NavigationMenuProviderStateProps = ReadableBoxedValues<{
18
19
  previousValue: string;
19
20
  }> & {
20
21
  isRootMenu: boolean;
21
- onTriggerEnter: (itemValue: string) => void;
22
+ onTriggerEnter: (itemValue: string, itemState: NavigationMenuItemState | null) => void;
22
23
  onTriggerLeave?: () => void;
23
24
  onContentEnter?: () => void;
24
25
  onContentLeave?: () => void;
25
- onItemSelect: (itemValue: string) => void;
26
+ onItemSelect: (itemValue: string, itemState: NavigationMenuItemState | null) => void;
26
27
  onItemDismiss: () => void;
27
28
  };
28
29
  declare class NavigationMenuProviderState {
@@ -36,7 +37,10 @@ declare class NavigationMenuProviderState {
36
37
  onContentLeave: () => void;
37
38
  onItemSelect: NavigationMenuProviderStateProps["onItemSelect"];
38
39
  onItemDismiss: NavigationMenuProviderStateProps["onItemDismiss"];
40
+ activeItem: NavigationMenuItemState | null;
41
+ prevActiveItem: NavigationMenuItemState | null;
39
42
  constructor(opts: NavigationMenuProviderStateProps);
43
+ setActiveItem: (item: NavigationMenuItemState | null) => void;
40
44
  }
41
45
  type NavigationMenuRootStateProps = WithRefProps<WritableBoxedValues<{
42
46
  value: string;
@@ -53,7 +57,7 @@ declare class NavigationMenuRootState {
53
57
  previousValue: WritableBox<string>;
54
58
  isDelaySkipped: WritableBox<boolean>;
55
59
  constructor(opts: NavigationMenuRootStateProps);
56
- setValue: (newValue: string) => void;
60
+ setValue: (newValue: string, itemState: NavigationMenuItemState | null) => void;
57
61
  props: {
58
62
  readonly id: string;
59
63
  readonly "data-orientation": "horizontal" | "vertical";
@@ -71,8 +75,9 @@ declare class NavigationMenuSubState {
71
75
  readonly opts: NavigationMenuSubStateProps;
72
76
  readonly context: NavigationMenuProviderState;
73
77
  previousValue: WritableBox<string>;
78
+ subProvider: NavigationMenuProviderState;
74
79
  constructor(opts: NavigationMenuSubStateProps, context: NavigationMenuProviderState);
75
- setValue: (newValue: string) => void;
80
+ setValue: (newValue: string, itemState: NavigationMenuItemState | null) => void;
76
81
  props: {
77
82
  readonly id: string;
78
83
  readonly "data-orientation": "horizontal" | "vertical";
@@ -102,6 +107,7 @@ declare class NavigationMenuListState {
102
107
  }
103
108
  type NavigationMenuItemStateProps = WithRefProps<ReadableBoxedValues<{
104
109
  value: string;
110
+ openOnHover: boolean;
105
111
  }>>;
106
112
  export declare class NavigationMenuItemState {
107
113
  #private;
@@ -147,13 +153,14 @@ declare class NavigationMenuTriggerState {
147
153
  provider: NavigationMenuProviderState;
148
154
  item: NavigationMenuItemState;
149
155
  list: NavigationMenuListState;
156
+ sub: NavigationMenuSubState | null;
150
157
  });
151
158
  onpointerenter: (_: BitsPointerEvent<HTMLButtonElement>) => void;
152
- onpointermove: BitsPointerEventHandler<HTMLElement>;
153
- onpointerleave: BitsPointerEventHandler<HTMLElement>;
154
- onclick: (_: BitsMouseEvent<HTMLButtonElement>) => void;
155
- onkeydown: (e: BitsKeyboardEvent<HTMLButtonElement>) => void;
156
- focusProxyOnFocus: (e: BitsFocusEvent) => void;
159
+ onpointermove: PointerEventHandler<HTMLElement>;
160
+ onpointerleave: PointerEventHandler<HTMLElement>;
161
+ onclick: MouseEventHandler<HTMLButtonElement>;
162
+ onkeydown: KeyboardEventHandler<HTMLButtonElement>;
163
+ focusProxyOnFocus: FocusEventHandler<HTMLElement>;
157
164
  props: {
158
165
  readonly id: string;
159
166
  readonly disabled: boolean | null | undefined;
@@ -163,16 +170,16 @@ declare class NavigationMenuTriggerState {
163
170
  readonly "aria-expanded": "true" | "false";
164
171
  readonly "aria-controls": string | undefined;
165
172
  readonly "data-navigation-menu-trigger": "";
166
- readonly onpointermove: BitsPointerEventHandler<HTMLElement>;
167
- readonly onpointerleave: BitsPointerEventHandler<HTMLElement>;
173
+ readonly onpointermove: PointerEventHandler<HTMLElement>;
174
+ readonly onpointerleave: PointerEventHandler<HTMLElement>;
168
175
  readonly onpointerenter: (_: BitsPointerEvent<HTMLButtonElement>) => void;
169
- readonly onclick: (_: BitsMouseEvent<HTMLButtonElement>) => void;
170
- readonly onkeydown: (e: BitsKeyboardEvent<HTMLButtonElement>) => void;
176
+ readonly onclick: MouseEventHandler<HTMLButtonElement>;
177
+ readonly onkeydown: KeyboardEventHandler<HTMLButtonElement>;
171
178
  };
172
179
  focusProxyProps: {
173
180
  readonly id: string;
174
181
  readonly tabindex: 0;
175
- readonly onfocus: (e: BitsFocusEvent) => void;
182
+ readonly onfocus: FocusEventHandler<HTMLElement>;
176
183
  };
177
184
  restructureSpanProps: {
178
185
  readonly "aria-owns": string | undefined;
@@ -183,6 +190,7 @@ type NavigationMenuLinkStateProps = WithRefProps & ReadableBoxedValues<{
183
190
  onSelect: (e: Event) => void;
184
191
  }>;
185
192
  declare class NavigationMenuLinkState {
193
+ #private;
186
194
  readonly opts: NavigationMenuLinkStateProps;
187
195
  readonly context: {
188
196
  provider: NavigationMenuProviderState;
@@ -197,6 +205,8 @@ declare class NavigationMenuLinkState {
197
205
  onkeydown: (e: BitsKeyboardEvent) => void;
198
206
  onfocus: (_: BitsFocusEvent) => void;
199
207
  onblur: (_: BitsFocusEvent) => void;
208
+ onpointerenter: PointerEventHandler<HTMLAnchorElement>;
209
+ onpointermove: PointerEventHandler<HTMLElement>;
200
210
  props: {
201
211
  readonly id: string;
202
212
  readonly "data-active": "" | undefined;
@@ -206,6 +216,8 @@ declare class NavigationMenuLinkState {
206
216
  readonly onkeydown: (e: BitsKeyboardEvent) => void;
207
217
  readonly onfocus: (_: BitsFocusEvent) => void;
208
218
  readonly onblur: (_: BitsFocusEvent) => void;
219
+ readonly onpointerenter: PointerEventHandler<HTMLAnchorElement>;
220
+ readonly onpointermove: PointerEventHandler<HTMLElement>;
209
221
  readonly "data-navigation-menu-link": "";
210
222
  };
211
223
  }
@@ -266,11 +278,11 @@ declare class NavigationMenuContentState {
266
278
  list: NavigationMenuListState;
267
279
  });
268
280
  onpointerenter: (_: BitsPointerEvent) => void;
269
- onpointerleave: BitsPointerEventHandler<HTMLElement>;
281
+ onpointerleave: PointerEventHandler<HTMLElement>;
270
282
  props: {
271
283
  readonly id: string;
272
284
  readonly onpointerenter: (_: BitsPointerEvent) => void;
273
- readonly onpointerleave: BitsPointerEventHandler<HTMLElement>;
285
+ readonly onpointerleave: PointerEventHandler<HTMLElement>;
274
286
  };
275
287
  }
276
288
  type MotionAttribute = "to-start" | "to-end" | "from-start" | "from-end";
@@ -309,6 +321,7 @@ declare class NavigationMenuViewportState {
309
321
  viewportWidth: string | undefined;
310
322
  viewportHeight: string | undefined;
311
323
  activeContentValue: string;
324
+ mounted: boolean;
312
325
  constructor(opts: NavigationMenuViewportImplStateProps, context: NavigationMenuProviderState);
313
326
  props: {
314
327
  readonly id: string;
@@ -338,5 +351,4 @@ export declare function useNavigationMenuLink(props: NavigationMenuLinkStateProp
338
351
  export declare function useNavigationMenuContentImpl(props: NavigationMenuContentImplStateProps, itemState?: NavigationMenuItemState): NavigationMenuContentImplState;
339
352
  export declare function useNavigationMenuViewport(props: NavigationMenuViewportImplStateProps): NavigationMenuViewportState;
340
353
  export declare function useNavigationMenuIndicator(): NavigationMenuIndicatorState;
341
- type BitsPointerEventHandler<T extends HTMLElement = HTMLElement> = (e: BitsPointerEvent<T>) => void;
342
354
  export {};
@@ -38,6 +38,8 @@ class NavigationMenuProviderState {
38
38
  onContentLeave = noop;
39
39
  onItemSelect;
40
40
  onItemDismiss;
41
+ activeItem = null;
42
+ prevActiveItem = null;
41
43
  constructor(opts) {
42
44
  this.opts = opts;
43
45
  this.onTriggerEnter = opts.onTriggerEnter;
@@ -47,6 +49,10 @@ class NavigationMenuProviderState {
47
49
  this.onItemDismiss = opts.onItemDismiss;
48
50
  this.onItemSelect = opts.onItemSelect;
49
51
  }
52
+ setActiveItem = (item) => {
53
+ this.prevActiveItem = this.activeItem;
54
+ this.activeItem = item;
55
+ };
50
56
  }
51
57
  class NavigationMenuRootState {
52
58
  opts;
@@ -74,8 +80,8 @@ class NavigationMenuRootState {
74
80
  orientation: this.opts.orientation,
75
81
  rootNavigationMenuRef: this.opts.ref,
76
82
  isRootMenu: true,
77
- onTriggerEnter: (itemValue) => {
78
- this.#onTriggerEnter(itemValue);
83
+ onTriggerEnter: (itemValue, itemState) => {
84
+ this.#onTriggerEnter(itemValue, itemState);
79
85
  },
80
86
  onTriggerLeave: this.#onTriggerLeave,
81
87
  onContentEnter: this.#onContentEnter,
@@ -84,34 +90,44 @@ class NavigationMenuRootState {
84
90
  onItemDismiss: this.#onItemDismiss,
85
91
  });
86
92
  }
87
- #debouncedFn = useDebounce((val) => {
93
+ #debouncedFn = useDebounce((val, itemState) => {
88
94
  // passing `undefined` meant to reset the debounce timer
89
95
  if (typeof val === "string") {
90
- this.setValue(val);
96
+ this.setValue(val, itemState);
91
97
  }
92
98
  }, () => this.#derivedDelay);
93
- #onTriggerEnter = (itemValue) => {
94
- this.#debouncedFn(itemValue);
99
+ #onTriggerEnter = (itemValue, itemState) => {
100
+ this.#debouncedFn(itemValue, itemState);
95
101
  };
96
102
  #onTriggerLeave = () => {
97
103
  this.isDelaySkipped.current = false;
98
- this.#debouncedFn("");
104
+ this.#debouncedFn("", null);
99
105
  };
100
106
  #onContentEnter = () => {
101
- this.#debouncedFn();
107
+ this.#debouncedFn(undefined, null);
102
108
  };
103
109
  #onContentLeave = () => {
104
- this.#debouncedFn("");
110
+ if (this.provider.activeItem &&
111
+ this.provider.activeItem.opts.openOnHover.current === false) {
112
+ return;
113
+ }
114
+ this.#debouncedFn("", null);
105
115
  };
106
- #onItemSelect = (itemValue) => {
107
- this.setValue(itemValue);
116
+ #onItemSelect = (itemValue, itemState) => {
117
+ this.setValue(itemValue, itemState);
108
118
  };
109
119
  #onItemDismiss = () => {
110
- this.setValue("");
120
+ this.setValue("", null);
111
121
  };
112
- setValue = (newValue) => {
122
+ setValue = (newValue, itemState) => {
113
123
  this.previousValue.current = this.opts.value.current;
114
124
  this.opts.value.current = newValue;
125
+ this.provider.setActiveItem(itemState);
126
+ // When all menus are closed, we want to reset previousValue to prevent
127
+ // weird transitions from old positions when opening fresh
128
+ if (newValue === "") {
129
+ this.previousValue.current = "";
130
+ }
115
131
  };
116
132
  props = $derived.by(() => ({
117
133
  id: this.opts.id.current,
@@ -125,11 +141,12 @@ class NavigationMenuSubState {
125
141
  opts;
126
142
  context;
127
143
  previousValue = box("");
144
+ subProvider;
128
145
  constructor(opts, context) {
129
146
  this.opts = opts;
130
147
  this.context = context;
131
148
  useRefById(opts);
132
- useNavigationMenuProvider({
149
+ this.subProvider = useNavigationMenuProvider({
133
150
  isRootMenu: false,
134
151
  value: this.opts.value,
135
152
  dir: this.context.opts.dir,
@@ -137,12 +154,19 @@ class NavigationMenuSubState {
137
154
  rootNavigationMenuRef: this.opts.ref,
138
155
  onTriggerEnter: this.setValue,
139
156
  onItemSelect: this.setValue,
140
- onItemDismiss: () => this.setValue(""),
157
+ onItemDismiss: () => this.setValue("", null),
141
158
  previousValue: this.previousValue,
142
159
  });
143
160
  }
144
- setValue = (newValue) => {
161
+ setValue = (newValue, itemState) => {
162
+ this.previousValue.current = this.opts.value.current;
145
163
  this.opts.value.current = newValue;
164
+ this.subProvider.setActiveItem(itemState);
165
+ // When all menus are closed, we want to reset previousValue to prevent
166
+ // weird transitions from old positions when opening fresh
167
+ if (newValue === "") {
168
+ this.previousValue.current = "";
169
+ }
146
170
  };
147
171
  props = $derived.by(() => ({
148
172
  id: this.opts.id.current,
@@ -281,28 +305,30 @@ class NavigationMenuTriggerState {
281
305
  if (this.opts.disabled.current ||
282
306
  this.wasClickClose ||
283
307
  this.itemContext.wasEscapeClose ||
284
- this.hasPointerMoveOpened.current) {
308
+ this.hasPointerMoveOpened.current ||
309
+ !this.itemContext.opts.openOnHover.current) {
285
310
  return;
286
311
  }
287
- this.context.onTriggerEnter(this.itemContext.opts.value.current);
312
+ this.context.onTriggerEnter(this.itemContext.opts.value.current, this.itemContext);
288
313
  this.hasPointerMoveOpened.current = true;
289
314
  });
290
315
  onpointerleave = whenMouse(() => {
291
- if (this.opts.disabled.current)
316
+ if (this.opts.disabled.current || !this.itemContext.opts.openOnHover.current)
292
317
  return;
293
318
  this.context.onTriggerLeave();
294
319
  this.hasPointerMoveOpened.current = false;
295
320
  });
296
- onclick = (_) => {
321
+ onclick = () => {
297
322
  // if opened via pointer move, we prevent the click event
298
323
  if (this.hasPointerMoveOpened.current)
299
324
  return;
300
- const shouldClose = this.open && this.context.opts.isRootMenu;
325
+ const shouldClose = this.open &&
326
+ (!this.itemContext.opts.openOnHover.current || this.context.opts.isRootMenu);
301
327
  if (shouldClose) {
302
- this.context.onItemSelect("");
328
+ this.context.onItemSelect("", null);
303
329
  }
304
330
  else if (!this.open) {
305
- this.context.onItemSelect(this.itemContext.opts.value.current);
331
+ this.context.onItemSelect(this.itemContext.opts.value.current, this.itemContext);
306
332
  }
307
333
  this.wasClickClose = shouldClose;
308
334
  };
@@ -386,6 +412,23 @@ class NavigationMenuLinkState {
386
412
  onblur = (_) => {
387
413
  this.isFocused = false;
388
414
  };
415
+ #handlePointerDismiss = () => {
416
+ // only close submenu if this link is not inside the currently open submenu content
417
+ const currentlyOpenValue = this.context.provider.opts.value.current;
418
+ const isInsideOpenSubmenu = this.context.item.opts.value.current === currentlyOpenValue;
419
+ const activeItem = this.context.item.listContext.context.activeItem;
420
+ if (activeItem && !activeItem.opts.openOnHover.current)
421
+ return;
422
+ if (currentlyOpenValue && !isInsideOpenSubmenu) {
423
+ this.context.provider.onItemDismiss();
424
+ }
425
+ };
426
+ onpointerenter = () => {
427
+ this.#handlePointerDismiss();
428
+ };
429
+ onpointermove = whenMouse(() => {
430
+ this.#handlePointerDismiss();
431
+ });
389
432
  props = $derived.by(() => ({
390
433
  id: this.opts.id.current,
391
434
  "data-active": this.opts.active.current ? "" : undefined,
@@ -395,6 +438,8 @@ class NavigationMenuLinkState {
395
438
  onkeydown: this.onkeydown,
396
439
  onfocus: this.onfocus,
397
440
  onblur: this.onblur,
441
+ onpointerenter: this.onpointerenter,
442
+ onpointermove: this.onpointermove,
398
443
  [NAVIGATION_MENU_LINK_ATTR]: "",
399
444
  }));
400
445
  }
@@ -497,6 +542,8 @@ class NavigationMenuContentState {
497
542
  this.context.onContentEnter();
498
543
  };
499
544
  onpointerleave = whenMouse(() => {
545
+ if (!this.itemContext.opts.openOnHover.current)
546
+ return;
500
547
  this.context.onContentLeave();
501
548
  });
502
549
  props = $derived.by(() => ({
@@ -520,6 +567,11 @@ class NavigationMenuContentImplState {
520
567
  const prevIndex = values.indexOf(this.context.opts.previousValue.current);
521
568
  const isSelected = this.itemContext.opts.value.current === this.context.opts.value.current;
522
569
  const wasSelected = prevIndex === values.indexOf(this.itemContext.opts.value.current);
570
+ // When all menus are closed, we want to reset motion state to prevent residual animations
571
+ if (!this.context.opts.value.current && !this.context.opts.previousValue.current) {
572
+ untrack(() => (this.prevMotionAttribute = null));
573
+ return null;
574
+ }
523
575
  // We only want to update selected and the last selected content
524
576
  // this avoids animations being interrupted outside of that range
525
577
  if (!isSelected && !wasSelected)
@@ -585,8 +637,17 @@ class NavigationMenuContentImplState {
585
637
  const target = e.target;
586
638
  const isTrigger = this.listContext.listTriggers.some((trigger) => trigger.contains(target));
587
639
  const isRootViewport = this.context.opts.isRootMenu && this.context.viewportRef.current?.contains(target);
588
- if (isTrigger || isRootViewport || !this.context.opts.isRootMenu)
640
+ if (!this.context.opts.isRootMenu && !isTrigger) {
641
+ this.context.onItemDismiss();
642
+ return;
643
+ }
644
+ if (isTrigger || isRootViewport) {
589
645
  e.preventDefault();
646
+ return;
647
+ }
648
+ if (!this.itemContext.opts.openOnHover.current) {
649
+ this.context.onItemSelect("", null);
650
+ }
590
651
  };
591
652
  onkeydown = (e) => {
592
653
  // prevent parent menus handling sub-menu keydown events
@@ -661,6 +722,7 @@ class NavigationMenuViewportState {
661
722
  viewportWidth = $derived.by(() => (this.size ? `${this.size.width}px` : undefined));
662
723
  viewportHeight = $derived.by(() => (this.size ? `${this.size.height}px` : undefined));
663
724
  activeContentValue = $derived.by(() => this.context.opts.value.current);
725
+ mounted = $state(false);
664
726
  constructor(opts, context) {
665
727
  this.opts = opts;
666
728
  this.context = context;
@@ -695,6 +757,12 @@ class NavigationMenuViewportState {
695
757
  };
696
758
  }
697
759
  });
760
+ // reset size when viewport closes to prevent residual size animations
761
+ watch(() => this.mounted, () => {
762
+ if (!this.mounted && this.size) {
763
+ this.size = null;
764
+ }
765
+ });
698
766
  }
699
767
  props = $derived.by(() => ({
700
768
  id: this.opts.id.current,
@@ -714,6 +782,7 @@ const NavigationMenuProviderContext = new Context("NavigationMenu.Root");
714
782
  export const NavigationMenuItemContext = new Context("NavigationMenu.Item");
715
783
  const NavigationMenuListContext = new Context("NavigationMenu.List");
716
784
  const NavigationMenuContentContext = new Context("NavigationMenu.Content");
785
+ const NavigationMenuSubContext = new Context("NavigationMenu.Sub");
717
786
  export function useNavigationMenuRoot(props) {
718
787
  return new NavigationMenuRootState(props);
719
788
  }
@@ -740,6 +809,7 @@ export function useNavigationMenuTrigger(props) {
740
809
  provider: NavigationMenuProviderContext.get(),
741
810
  item: NavigationMenuItemContext.get(),
742
811
  list: NavigationMenuListContext.get(),
812
+ sub: NavigationMenuSubContext.getOr(null),
743
813
  });
744
814
  }
745
815
  export function useNavigationMenuContent(props) {
@@ -15,21 +15,22 @@ export type NavigationMenuRootPropsWithoutHTML = WithChild<{
15
15
  */
16
16
  onValueChange?: OnChangeFn<string>;
17
17
  /**
18
- * The duration from when the mouse enters a trigger until the content opens.
18
+ * The amount of time in ms from when the mouse enters a trigger until the content opens.
19
19
  *
20
- * @defaultValue 200
20
+ * @default 200
21
21
  */
22
22
  delayDuration?: number;
23
23
  /**
24
- * How much time a user has to enter another trigger without incurring a delay again.
24
+ * The amount of time in ms that a user has to enter another trigger without
25
+ * incurring a delay again.
25
26
  *
26
- * @defaultValue 300
27
+ * @default 300
27
28
  */
28
29
  skipDelayDuration?: number;
29
30
  /**
30
31
  * The reading direction of the content.
31
32
  *
32
- * @defaultValue "ltr"
33
+ * @default "ltr"
33
34
  */
34
35
  dir?: Direction;
35
36
  /**
@@ -68,6 +69,13 @@ export type NavigationMenuItemPropsWithoutHTML = WithChild<{
68
69
  * The value of the menu item.
69
70
  */
70
71
  value?: string;
72
+ /**
73
+ * Whether to open the menu associated with the item when the item's trigger
74
+ * is hovered.
75
+ *
76
+ * @default true
77
+ */
78
+ openOnHover?: boolean;
71
79
  }>;
72
80
  export type NavigationMenuItemProps = NavigationMenuItemPropsWithoutHTML & Without<BitsPrimitiveLiAttributes, NavigationMenuItemPropsWithoutHTML>;
73
81
  export type NavigationMenuTriggerPropsWithoutHTML = WithChild<{
@@ -82,7 +90,6 @@ export type NavigationMenuContentPropsWithoutHTML = WithChild<{
82
90
  /**
83
91
  * Callback fired when an interaction occurs outside the content.
84
92
  * Default behavior can be prevented with `event.preventDefault()`
85
- *
86
93
  */
87
94
  onInteractOutside?: (event: PointerEvent) => void;
88
95
  /**
@@ -108,7 +115,7 @@ export type NavigationMenuContentPropsWithoutHTML = WithChild<{
108
115
  * This is useful when wanting to use more custom transition and animation
109
116
  * libraries.
110
117
  *
111
- * @defaultValue false
118
+ * @default false
112
119
  */
113
120
  forceMount?: boolean;
114
121
  }>;
@@ -27,7 +27,7 @@
27
27
  </script>
28
28
 
29
29
  {#if scrollButtonState.canScrollDown}
30
- <Mounted bind:mounted={scrollButtonState.state.mounted} />
30
+ <Mounted bind:mounted={scrollButtonState.scrollButtonState.mounted} />
31
31
  {#if child}
32
32
  {@render child({ props: restProps })}
33
33
  {:else}
@@ -27,7 +27,7 @@
27
27
  </script>
28
28
 
29
29
  {#if scrollButtonState.canScrollUp}
30
- <Mounted bind:mounted={scrollButtonState.state.mounted} />
30
+ <Mounted bind:mounted={scrollButtonState.scrollButtonState.mounted} />
31
31
  {#if child}
32
32
  {@render child({ props: restProps })}
33
33
  {:else}
@@ -334,12 +334,12 @@ declare class SelectScrollButtonImplState {
334
334
  };
335
335
  }
336
336
  declare class SelectScrollDownButtonState {
337
- readonly state: SelectScrollButtonImplState;
337
+ readonly scrollButtonState: SelectScrollButtonImplState;
338
338
  content: SelectContentState;
339
339
  root: SelectBaseRootState;
340
340
  canScrollDown: boolean;
341
341
  scrollIntoViewTimer: ReturnType<typeof globalThis.setTimeout> | null;
342
- constructor(state: SelectScrollButtonImplState);
342
+ constructor(scrollButtonState: SelectScrollButtonImplState);
343
343
  /**
344
344
  * @param manual - if true, it means the function was invoked manually outside of an event
345
345
  * listener, so we don't call `handleUserScroll` to prevent the auto scroll from kicking in.
@@ -358,11 +358,11 @@ declare class SelectScrollDownButtonState {
358
358
  };
359
359
  }
360
360
  declare class SelectScrollUpButtonState {
361
- readonly state: SelectScrollButtonImplState;
361
+ readonly scrollButtonState: SelectScrollButtonImplState;
362
362
  content: SelectContentState;
363
363
  root: SelectBaseRootState;
364
364
  canScrollUp: boolean;
365
- constructor(state: SelectScrollButtonImplState);
365
+ constructor(scrollButtonState: SelectScrollButtonImplState);
366
366
  /**
367
367
  * @param manual - if true, it means the function was invoked manually outside of an event
368
368
  * listener, so we don't call `handleUserScroll` to prevent the auto scroll from kicking in.
@@ -705,7 +705,11 @@ class SelectContentState {
705
705
  trapFocus: false,
706
706
  loop: false,
707
707
  onPlaced: () => {
708
- this.isPositioned = true;
708
+ // onPlaced is also called when the menu is closed, so we need to check if the menu
709
+ // is actually open to avoid setting positioning to true when the menu is closed
710
+ if (this.root.opts.open.current) {
711
+ this.isPositioned = true;
712
+ }
709
713
  },
710
714
  };
711
715
  }
@@ -993,25 +997,24 @@ class SelectScrollButtonImplState {
993
997
  }));
994
998
  }
995
999
  class SelectScrollDownButtonState {
996
- state;
1000
+ scrollButtonState;
997
1001
  content;
998
1002
  root;
999
1003
  canScrollDown = $state(false);
1000
1004
  scrollIntoViewTimer = null;
1001
- constructor(state) {
1002
- this.state = state;
1003
- this.content = state.content;
1004
- this.root = state.root;
1005
- this.state.onAutoScroll = this.handleAutoScroll;
1005
+ constructor(scrollButtonState) {
1006
+ this.scrollButtonState = scrollButtonState;
1007
+ this.content = scrollButtonState.content;
1008
+ this.root = scrollButtonState.root;
1009
+ this.scrollButtonState.onAutoScroll = this.handleAutoScroll;
1006
1010
  watch([() => this.content.viewportNode, () => this.content.isPositioned], () => {
1007
- if (!this.content.viewportNode || !this.content.isPositioned) {
1011
+ if (!this.content.viewportNode || !this.content.isPositioned)
1008
1012
  return;
1009
- }
1010
1013
  this.handleScroll(true);
1011
1014
  return on(this.content.viewportNode, "scroll", () => this.handleScroll());
1012
1015
  });
1013
- watch(() => this.state.mounted, () => {
1014
- if (!this.state.mounted)
1016
+ watch(() => this.scrollButtonState.mounted, () => {
1017
+ if (!this.scrollButtonState.mounted)
1015
1018
  return;
1016
1019
  if (this.scrollIntoViewTimer) {
1017
1020
  clearTimeout(this.scrollIntoViewTimer);
@@ -1028,7 +1031,7 @@ class SelectScrollDownButtonState {
1028
1031
  */
1029
1032
  handleScroll = (manual = false) => {
1030
1033
  if (!manual) {
1031
- this.state.handleUserScroll();
1034
+ this.scrollButtonState.handleUserScroll();
1032
1035
  }
1033
1036
  if (!this.content.viewportNode)
1034
1037
  return;
@@ -1044,18 +1047,21 @@ class SelectScrollDownButtonState {
1044
1047
  return;
1045
1048
  viewport.scrollTop = viewport.scrollTop + selectedItem.offsetHeight;
1046
1049
  };
1047
- props = $derived.by(() => ({ ...this.state.props, [this.root.bitsAttrs["scroll-down-button"]]: "" }));
1050
+ props = $derived.by(() => ({
1051
+ ...this.scrollButtonState.props,
1052
+ [this.root.bitsAttrs["scroll-down-button"]]: "",
1053
+ }));
1048
1054
  }
1049
1055
  class SelectScrollUpButtonState {
1050
- state;
1056
+ scrollButtonState;
1051
1057
  content;
1052
1058
  root;
1053
1059
  canScrollUp = $state(false);
1054
- constructor(state) {
1055
- this.state = state;
1056
- this.content = state.content;
1057
- this.root = state.root;
1058
- this.state.onAutoScroll = this.handleAutoScroll;
1060
+ constructor(scrollButtonState) {
1061
+ this.scrollButtonState = scrollButtonState;
1062
+ this.content = scrollButtonState.content;
1063
+ this.root = scrollButtonState.root;
1064
+ this.scrollButtonState.onAutoScroll = this.handleAutoScroll;
1059
1065
  watch([() => this.content.viewportNode, () => this.content.isPositioned], () => {
1060
1066
  if (!this.content.viewportNode || !this.content.isPositioned)
1061
1067
  return;
@@ -1069,7 +1075,7 @@ class SelectScrollUpButtonState {
1069
1075
  */
1070
1076
  handleScroll = (manual = false) => {
1071
1077
  if (!manual) {
1072
- this.state.handleUserScroll();
1078
+ this.scrollButtonState.handleUserScroll();
1073
1079
  }
1074
1080
  if (!this.content.viewportNode)
1075
1081
  return;
@@ -1082,7 +1088,10 @@ class SelectScrollUpButtonState {
1082
1088
  this.content.viewportNode.scrollTop =
1083
1089
  this.content.viewportNode.scrollTop - this.root.highlightedNode.offsetHeight;
1084
1090
  };
1085
- props = $derived.by(() => ({ ...this.state.props, [this.root.bitsAttrs["scroll-up-button"]]: "" }));
1091
+ props = $derived.by(() => ({
1092
+ ...this.scrollButtonState.props,
1093
+ [this.root.bitsAttrs["scroll-up-button"]]: "",
1094
+ }));
1086
1095
  }
1087
1096
  const SelectRootContext = new Context("Select.Root | Combobox.Root");
1088
1097
  const SelectGroupContext = new Context("Select.Group | Combobox.Group");
@@ -22,6 +22,7 @@
22
22
  dir = "ltr",
23
23
  autoSort = true,
24
24
  orientation = "horizontal",
25
+ thumbPositioning = "contain",
25
26
  ...restProps
26
27
  }: SliderRootProps = $props();
27
28
 
@@ -63,6 +64,7 @@
63
64
  dir: box.with(() => dir),
64
65
  autoSort: box.with(() => autoSort),
65
66
  orientation: box.with(() => orientation),
67
+ thumbPositioning: box.with(() => thumbPositioning),
66
68
  type,
67
69
  });
68
70
 
@@ -1,7 +1,7 @@
1
1
  import { type Box, type ReadableBox } from "svelte-toolbelt";
2
2
  import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
3
3
  import type { BitsKeyboardEvent, OnChangeFn, WithRefProps } from "../../internal/types.js";
4
- import type { Direction, Orientation } from "../../shared/index.js";
4
+ import type { Direction, Orientation, SliderThumbPositioning } from "../../shared/index.js";
5
5
  type SliderBaseRootStateProps = WithRefProps<ReadableBoxedValues<{
6
6
  disabled: boolean;
7
7
  orientation: Orientation;
@@ -10,6 +10,7 @@ type SliderBaseRootStateProps = WithRefProps<ReadableBoxedValues<{
10
10
  step: number;
11
11
  dir: Direction;
12
12
  autoSort: boolean;
13
+ thumbPositioning: SliderThumbPositioning;
13
14
  }>>;
14
15
  declare class SliderBaseRootState {
15
16
  #private;
@@ -46,6 +46,10 @@ class SliderBaseRootState {
46
46
  return Array.from(node.querySelectorAll(`[${SLIDER_THUMB_ATTR}]`));
47
47
  };
48
48
  getThumbScale = () => {
49
+ if (this.opts.thumbPositioning.current === "exact") {
50
+ // User opted out of containment
51
+ return [0, 100];
52
+ }
49
53
  const isVertical = this.opts.orientation.current === "vertical";
50
54
  // this assumes all thumbs are the same width
51
55
  const activeThumb = this.getAllThumbs()[0];
@@ -1,6 +1,6 @@
1
1
  import type { OnChangeFn, WithChild, Without } from "../../internal/types.js";
2
2
  import type { BitsPrimitiveSpanAttributes } from "../../shared/attributes.js";
3
- import type { Direction, Orientation } from "../../shared/index.js";
3
+ import type { Direction, Orientation, SliderThumbPositioning } from "../../shared/index.js";
4
4
  export type SliderRootSnippetProps = {
5
5
  ticks: number[];
6
6
  thumbs: number[];
@@ -53,6 +53,12 @@ export type BaseSliderRootPropsWithoutHTML = {
53
53
  * @defaultValue false
54
54
  */
55
55
  disabled?: boolean;
56
+ /**
57
+ * The positioning of the slider thumb.
58
+ *
59
+ * @defaultValue "contain"
60
+ */
61
+ thumbPositioning?: SliderThumbPositioning;
56
62
  };
57
63
  export type SliderSingleRootPropsWithoutHTML = BaseSliderRootPropsWithoutHTML & {
58
64
  /**
@@ -12,6 +12,13 @@ export type StyleProperties = CSS.Properties & {
12
12
  };
13
13
  export type Orientation = "horizontal" | "vertical";
14
14
  export type Direction = "ltr" | "rtl";
15
+ /**
16
+ * Controls positioning of the slider thumb.
17
+ *
18
+ * - `exact`: The thumb is centered exactly at the value of the slider.
19
+ * - `contain`: The thumb is centered exactly at the value of the slider, but will be contained within the slider track at the ends.
20
+ */
21
+ export type SliderThumbPositioning = "exact" | "contain";
15
22
  export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
16
23
  export type WithoutChildren<T> = T extends {
17
24
  children?: any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "1.6.1",
3
+ "version": "1.8.0",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",