This commit is contained in:
eewing
2026-02-17 14:10:16 -06:00
parent 2bca5834c5
commit cf73cd3b4c
11246 changed files with 1690552 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
<script lang="ts">
import { mergeProps } from "svelte-toolbelt";
import type { MenuArrowProps } from "../types.js";
import { MenuArrowState } from "../menu.svelte.js";
import FloatingLayerArrow from "../../utilities/floating-layer/components/floating-layer-arrow.svelte";
let { ref = $bindable(null), ...restProps }: MenuArrowProps = $props();
const arrowState = MenuArrowState.create();
const mergedProps = $derived(mergeProps(restProps, arrowState.props));
</script>
<FloatingLayerArrow bind:ref {...mergedProps} />
@@ -0,0 +1,3 @@
declare const MenuArrow: import("svelte").Component<import("../../utilities/arrow/types.js").ArrowProps, {}, "ref">;
type MenuArrow = ReturnType<typeof MenuArrow>;
export default MenuArrow;
@@ -0,0 +1,45 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuCheckboxGroupProps } from "../types.js";
import { MenuCheckboxGroupState } from "../menu.svelte.js";
import { noop } from "../../../internal/noop.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
id = createId(uid),
children,
child,
ref = $bindable(null),
value = $bindable([]),
onValueChange = noop,
...restProps
}: MenuCheckboxGroupProps = $props();
const checkboxGroupState = MenuCheckboxGroupState.create({
value: boxWith(
() => $state.snapshot(value),
(v) => {
value = $state.snapshot(v);
onValueChange(v);
}
),
onValueChange: boxWith(() => onValueChange),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
id: boxWith(() => id),
});
const mergedProps = $derived(mergeProps(restProps, checkboxGroupState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
@@ -0,0 +1,4 @@
import type { MenuCheckboxGroupProps } from "../types.js";
declare const MenuCheckboxGroup: import("svelte").Component<MenuCheckboxGroupProps, {}, "value" | "ref">;
type MenuCheckboxGroup = ReturnType<typeof MenuCheckboxGroup>;
export default MenuCheckboxGroup;
@@ -0,0 +1,98 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuCheckboxItemProps } from "../types.js";
import { MenuCheckboxGroupContext, MenuCheckboxItemState } from "../menu.svelte.js";
import { createId } from "../../../internal/create-id.js";
import { noop } from "../../../internal/noop.js";
import { watch } from "runed";
const uid = $props.id();
let {
child,
children,
ref = $bindable(null),
checked = $bindable(false),
id = createId(uid),
onCheckedChange = noop,
disabled = false,
onSelect = noop,
closeOnSelect = true,
indeterminate = $bindable(false),
onIndeterminateChange = noop,
value = "",
...restProps
}: MenuCheckboxItemProps = $props();
const group = MenuCheckboxGroupContext.getOr(null);
if (group && value) {
if (group.opts.value.current.includes(value)) {
checked = true;
} else {
checked = false;
}
}
watch.pre(
() => value,
() => {
if (group && value) {
if (group.opts.value.current.includes(value)) {
checked = true;
} else {
checked = false;
}
}
}
);
const checkboxItemState = MenuCheckboxItemState.create(
{
checked: boxWith(
() => checked,
(v) => {
if (v !== checked) {
checked = v;
onCheckedChange(v);
}
}
),
id: boxWith(() => id),
disabled: boxWith(() => disabled),
onSelect: boxWith(() => handleSelect),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
closeOnSelect: boxWith(() => closeOnSelect),
indeterminate: boxWith(
() => indeterminate,
(v) => {
if (v !== indeterminate) {
indeterminate = v;
onIndeterminateChange(v);
}
}
),
value: boxWith(() => value),
},
group
);
function handleSelect(e: Event) {
onSelect(e);
if (e.defaultPrevented) return;
checkboxItemState.toggleChecked();
}
const mergedProps = $derived(mergeProps(restProps, checkboxItemState.props));
</script>
{#if child}
{@render child({ checked, indeterminate, props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.({ checked, indeterminate })}
</div>
{/if}
@@ -0,0 +1,4 @@
import type { MenuCheckboxItemProps } from "../types.js";
declare const MenuCheckboxItem: import("svelte").Component<MenuCheckboxItemProps, {}, "checked" | "indeterminate" | "ref">;
type MenuCheckboxItem = ReturnType<typeof MenuCheckboxItem>;
export default MenuCheckboxItem;
@@ -0,0 +1,116 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuContentStaticProps } from "../types.js";
import { MenuContentState } from "../menu.svelte.js";
import { createId } from "../../../internal/create-id.js";
import { noop } from "../../../internal/noop.js";
import PopperLayer from "../../utilities/popper-layer/popper-layer.svelte";
import { getFloatingContentCSSVars } from "../../../internal/floating-svelte/floating-utils.svelte.js";
import PopperLayerForceMount from "../../utilities/popper-layer/popper-layer-force-mount.svelte";
const uid = $props.id();
let {
id = createId(uid),
child,
children,
ref = $bindable(null),
loop = true,
onInteractOutside = noop,
onEscapeKeydown = noop,
onCloseAutoFocus: onCloseAutoFocusProp = noop,
forceMount = false,
style,
...restProps
}: MenuContentStaticProps = $props();
const contentState = MenuContentState.create({
id: boxWith(() => id),
loop: boxWith(() => loop),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
onCloseAutoFocus: boxWith(() => onCloseAutoFocusProp),
});
const mergedProps = $derived(
mergeProps(restProps, contentState.props, {
style: { outline: "none" },
})
);
function handleInteractOutside(e: PointerEvent) {
onInteractOutside(e);
if (e.defaultPrevented) return;
contentState.parentMenu.onClose();
}
function handleEscapeKeydown(e: KeyboardEvent) {
onEscapeKeydown(e);
if (e.defaultPrevented) return;
contentState.parentMenu.onClose();
}
</script>
{#if forceMount}
<PopperLayerForceMount
{...mergedProps}
{...contentState.popperProps}
ref={contentState.opts.ref}
enabled={contentState.parentMenu.opts.open.current}
onInteractOutside={handleInteractOutside}
onEscapeKeydown={handleEscapeKeydown}
trapFocus
{loop}
forceMount={true}
isStatic
{id}
shouldRender={contentState.shouldRender}
>
{#snippet popper({ props })}
{@const finalProps = mergeProps(
props,
{ style: { outline: "none", ...getFloatingContentCSSVars("menu") } },
{ style }
)}
{#if child}
{@render child({ props: finalProps, ...contentState.snippetProps })}
{:else}
<div {...finalProps}>
{@render children?.()}
</div>
{/if}
{/snippet}
</PopperLayerForceMount>
{:else if !forceMount}
<PopperLayer
{...mergedProps}
{...contentState.popperProps}
ref={contentState.opts.ref}
open={contentState.parentMenu.opts.open.current}
onInteractOutside={handleInteractOutside}
onEscapeKeydown={handleEscapeKeydown}
trapFocus
{loop}
forceMount={false}
isStatic
{id}
shouldRender={contentState.shouldRender}
>
{#snippet popper({ props })}
{@const finalProps = mergeProps(
props,
{ style: { outline: "none", ...getFloatingContentCSSVars("menu") } },
{ style }
)}
{#if child}
{@render child({ props: finalProps, ...contentState.snippetProps })}
{:else}
<div {...finalProps}>
{@render children?.()}
</div>
{/if}
{/snippet}
</PopperLayer>
{/if}
@@ -0,0 +1,4 @@
import type { MenuContentStaticProps } from "../types.js";
declare const MenuContentStatic: import("svelte").Component<MenuContentStaticProps, {}, "ref">;
type MenuContentStatic = ReturnType<typeof MenuContentStatic>;
export default MenuContentStatic;
+123
View File
@@ -0,0 +1,123 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuContentProps } from "../types.js";
import { MenuContentState } from "../menu.svelte.js";
import { createId } from "../../../internal/create-id.js";
import { noop } from "../../../internal/noop.js";
import PopperLayer from "../../utilities/popper-layer/popper-layer.svelte";
import { getFloatingContentCSSVars } from "../../../internal/floating-svelte/floating-utils.svelte.js";
import PopperLayerForceMount from "../../utilities/popper-layer/popper-layer-force-mount.svelte";
const uid = $props.id();
let {
id = createId(uid),
child,
children,
ref = $bindable(null),
loop = true,
onInteractOutside = noop,
onEscapeKeydown = noop,
onCloseAutoFocus: onCloseAutoFocusProp = noop,
forceMount = false,
style,
...restProps
}: MenuContentProps = $props();
const contentState = MenuContentState.create({
id: boxWith(() => id),
loop: boxWith(() => loop),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
onCloseAutoFocus: boxWith(() => onCloseAutoFocusProp),
});
const mergedProps = $derived(
mergeProps(restProps, contentState.props, {
style: { outline: "none" },
})
);
function handleInteractOutside(e: PointerEvent) {
onInteractOutside(e);
if (e.defaultPrevented) return;
// don't close if the interaction is with a submenu content or items
if (e.target && e.target instanceof Element) {
const subContentSelector = `[${contentState.parentMenu.root.getBitsAttr("sub-content")}]`;
if (e.target.closest(subContentSelector)) return;
}
contentState.parentMenu.onClose();
}
function handleEscapeKeydown(e: KeyboardEvent) {
onEscapeKeydown(e);
if (e.defaultPrevented) return;
contentState.parentMenu.onClose();
}
</script>
{#if forceMount}
<PopperLayerForceMount
{...mergedProps}
{...contentState.popperProps}
ref={contentState.opts.ref}
enabled={contentState.parentMenu.opts.open.current}
onInteractOutside={handleInteractOutside}
onEscapeKeydown={handleEscapeKeydown}
trapFocus
{loop}
forceMount={true}
{id}
shouldRender={contentState.shouldRender}
>
{#snippet popper({ props, wrapperProps })}
{@const finalProps = mergeProps(
props,
{ style: { outline: "none", ...getFloatingContentCSSVars("menu") } },
{ style }
)}
{#if child}
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
{:else}
<div {...wrapperProps}>
<div {...finalProps}>
{@render children?.()}
</div>
</div>
{/if}
{/snippet}
</PopperLayerForceMount>
{:else if !forceMount}
<PopperLayer
{...mergedProps}
{...contentState.popperProps}
ref={contentState.opts.ref}
open={contentState.parentMenu.opts.open.current}
onInteractOutside={handleInteractOutside}
onEscapeKeydown={handleEscapeKeydown}
trapFocus
{loop}
forceMount={false}
{id}
shouldRender={contentState.shouldRender}
>
{#snippet popper({ props, wrapperProps })}
{@const finalProps = mergeProps(
props,
{ style: { outline: "none", ...getFloatingContentCSSVars("menu") } },
{ style }
)}
{#if child}
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
{:else}
<div {...wrapperProps}>
<div {...finalProps}>
{@render children?.()}
</div>
</div>
{/if}
{/snippet}
</PopperLayer>
{/if}
@@ -0,0 +1,4 @@
import type { MenuContentProps } from "../types.js";
declare const MenuContent: import("svelte").Component<MenuContentProps, {}, "ref">;
type MenuContent = ReturnType<typeof MenuContent>;
export default MenuContent;
@@ -0,0 +1,33 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuGroupHeadingProps } from "../types.js";
import { MenuGroupHeadingState } from "../menu.svelte.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
children,
child,
ref = $bindable(null),
id = createId(uid),
...restProps
}: MenuGroupHeadingProps = $props();
const groupHeadingState = MenuGroupHeadingState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, groupHeadingState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
@@ -0,0 +1,4 @@
import type { MenuGroupHeadingProps } from "../types.js";
declare const MenuGroupHeading: import("svelte").Component<MenuGroupHeadingProps, {}, "ref">;
type MenuGroupHeading = ReturnType<typeof MenuGroupHeading>;
export default MenuGroupHeading;
+33
View File
@@ -0,0 +1,33 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuGroupProps } from "../types.js";
import { MenuGroupState } from "../menu.svelte.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
children,
child,
ref = $bindable(null),
id = createId(uid),
...restProps
}: MenuGroupProps = $props();
const groupState = MenuGroupState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, groupState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
@@ -0,0 +1,4 @@
import type { MenuGroupProps } from "../types.js";
declare const MenuGroup: import("svelte").Component<MenuGroupProps, {}, "ref">;
type MenuGroup = ReturnType<typeof MenuGroup>;
export default MenuGroup;
+41
View File
@@ -0,0 +1,41 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuItemProps } from "../types.js";
import { MenuItemState } from "../menu.svelte.js";
import { createId } from "../../../internal/create-id.js";
import { noop } from "../../../internal/noop.js";
const uid = $props.id();
let {
child,
children,
ref = $bindable(null),
id = createId(uid),
disabled = false,
onSelect = noop,
closeOnSelect = true,
...restProps
}: MenuItemProps = $props();
const itemState = MenuItemState.create({
id: boxWith(() => id),
disabled: boxWith(() => disabled),
onSelect: boxWith(() => onSelect),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
closeOnSelect: boxWith(() => closeOnSelect),
});
const mergedProps = $derived(mergeProps(restProps, itemState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
+4
View File
@@ -0,0 +1,4 @@
import type { MenuItemProps } from "../types.js";
declare const MenuItem: import("svelte").Component<MenuItemProps, {}, "ref">;
type MenuItem = ReturnType<typeof MenuItem>;
export default MenuItem;
+44
View File
@@ -0,0 +1,44 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuRadioGroupProps } from "../types.js";
import { MenuRadioGroupState } from "../menu.svelte.js";
import { noop } from "../../../internal/noop.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
id = createId(uid),
children,
child,
ref = $bindable(null),
value = $bindable(""),
onValueChange = noop,
...restProps
}: MenuRadioGroupProps = $props();
const radioGroupState = MenuRadioGroupState.create({
value: boxWith(
() => value,
(v) => {
value = v;
onValueChange(v);
}
),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
id: boxWith(() => id),
});
const mergedProps = $derived(mergeProps(restProps, radioGroupState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
@@ -0,0 +1,4 @@
import type { MenuRadioGroupProps } from "../types.js";
declare const MenuRadioGroup: import("svelte").Component<MenuRadioGroupProps, {}, "value" | "ref">;
type MenuRadioGroup = ReturnType<typeof MenuRadioGroup>;
export default MenuRadioGroup;
+49
View File
@@ -0,0 +1,49 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuRadioItemProps } from "../types.js";
import { MenuRadioItemState } from "../menu.svelte.js";
import { createId } from "../../../internal/create-id.js";
import { noop } from "../../../internal/noop.js";
const uid = $props.id();
let {
children,
child,
ref = $bindable(null),
value,
onSelect = noop,
id = createId(uid),
disabled = false,
closeOnSelect = true,
...restProps
}: MenuRadioItemProps = $props();
const radioItemState = MenuRadioItemState.create({
value: boxWith(() => value),
id: boxWith(() => id),
disabled: boxWith(() => disabled),
onSelect: boxWith(() => handleSelect),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
closeOnSelect: boxWith(() => closeOnSelect),
});
function handleSelect(e: Event) {
onSelect(e);
if (e.defaultPrevented) return;
radioItemState.selectValue();
}
const mergedProps = $derived(mergeProps(restProps, radioItemState.props));
</script>
{#if child}
{@render child({ props: mergedProps, checked: radioItemState.isChecked })}
{:else}
<div {...mergedProps}>
{@render children?.({ checked: radioItemState.isChecked })}
</div>
{/if}
@@ -0,0 +1,4 @@
import type { MenuRadioItemProps } from "../types.js";
declare const MenuRadioItem: import("svelte").Component<MenuRadioItemProps, {}, "ref">;
type MenuRadioItem = ReturnType<typeof MenuRadioItem>;
export default MenuRadioItem;
+34
View File
@@ -0,0 +1,34 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuSeparatorProps } from "../types.js";
import { MenuSeparatorState } from "../menu.svelte.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
ref = $bindable(null),
id = createId(uid),
child,
children,
...restProps
}: MenuSeparatorProps = $props();
const separatorState = MenuSeparatorState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, separatorState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
@@ -0,0 +1,4 @@
import type { MenuSeparatorProps } from "../types.js";
declare const MenuSeparator: import("svelte").Component<MenuSeparatorProps, {}, "ref">;
type MenuSeparator = ReturnType<typeof MenuSeparator>;
export default MenuSeparator;
@@ -0,0 +1,176 @@
<script lang="ts">
import { afterTick, boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuSubContentStaticProps } from "../types.js";
import { MenuContentState } from "../menu.svelte.js";
import { SUB_CLOSE_KEYS } from "../utils.js";
import { createId } from "../../../internal/create-id.js";
import PopperLayer from "../../utilities/popper-layer/popper-layer.svelte";
import { noop } from "../../../internal/noop.js";
import { isHTMLElement } from "../../../internal/is.js";
import { getFloatingContentCSSVars } from "../../../internal/floating-svelte/floating-utils.svelte.js";
import PopperLayerForceMount from "../../utilities/popper-layer/popper-layer-force-mount.svelte";
const uid = $props.id();
let {
id = createId(uid),
ref = $bindable(null),
children,
child,
loop = true,
onInteractOutside = noop,
forceMount = false,
onEscapeKeydown = noop,
interactOutsideBehavior = "defer-otherwise-close",
escapeKeydownBehavior = "defer-otherwise-close",
onOpenAutoFocus: onOpenAutoFocusProp = noop,
onCloseAutoFocus: onCloseAutoFocusProp = noop,
onFocusOutside = noop,
trapFocus = false,
style,
...restProps
}: MenuSubContentStaticProps = $props();
const subContentState = MenuContentState.create({
id: boxWith(() => id),
loop: boxWith(() => loop),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
onCloseAutoFocus: boxWith(() => handleCloseAutoFocus),
isSub: true,
});
function onkeydown(e: KeyboardEvent) {
const isKeyDownInside = (e.currentTarget as HTMLElement).contains(e.target as HTMLElement);
const isCloseKey = SUB_CLOSE_KEYS[
subContentState.parentMenu.root.opts.dir.current
].includes(e.key);
if (isKeyDownInside && isCloseKey) {
subContentState.parentMenu.onClose();
const triggerNode = subContentState.parentMenu.triggerNode;
triggerNode?.focus();
e.preventDefault();
}
}
const dataAttr = $derived(subContentState.parentMenu.root.getBitsAttr("sub-content"));
const mergedProps = $derived(
mergeProps(restProps, subContentState.props, {
onkeydown,
[dataAttr]: "",
})
);
function handleOpenAutoFocus(e: Event) {
onOpenAutoFocusProp(e);
if (e.defaultPrevented) return;
afterTick(() => {
e.preventDefault();
if (subContentState.parentMenu.root.isUsingKeyboard) {
const subContentEl = subContentState.parentMenu.contentNode;
subContentEl?.focus();
}
});
}
function handleCloseAutoFocus(e: Event) {
onCloseAutoFocusProp(e);
if (e.defaultPrevented) return;
e.preventDefault();
}
function handleInteractOutside(e: PointerEvent) {
onInteractOutside(e);
if (e.defaultPrevented) return;
subContentState.parentMenu.onClose();
}
function handleEscapeKeydown(e: KeyboardEvent) {
onEscapeKeydown(e);
if (e.defaultPrevented) return;
subContentState.parentMenu.onClose();
}
function handleOnFocusOutside(e: FocusEvent) {
onFocusOutside(e);
if (e.defaultPrevented) return;
// We prevent closing when the trigger is focused to avoid triggering a re-open animation
// on pointer interaction.
if (!isHTMLElement(e.target)) return;
if (e.target.id !== subContentState.parentMenu.triggerNode?.id) {
subContentState.parentMenu.onClose();
}
}
</script>
{#if forceMount}
<PopperLayerForceMount
{...mergedProps}
ref={subContentState.opts.ref}
{interactOutsideBehavior}
{escapeKeydownBehavior}
onOpenAutoFocus={handleOpenAutoFocus}
enabled={subContentState.parentMenu.opts.open.current}
onInteractOutside={handleInteractOutside}
onEscapeKeydown={handleEscapeKeydown}
onFocusOutside={handleOnFocusOutside}
preventScroll={false}
{loop}
{trapFocus}
isStatic
shouldRender={subContentState.shouldRender}
>
{#snippet popper({ props })}
{@const finalProps = mergeProps(
props,
mergedProps,
{ style: getFloatingContentCSSVars("menu") },
{ style }
)}
{#if child}
{@render child({ props: finalProps, ...subContentState.snippetProps })}
{:else}
<div {...finalProps}>
{@render children?.()}
</div>
{/if}
{/snippet}
</PopperLayerForceMount>
{:else if !forceMount}
<PopperLayer
{...mergedProps}
ref={subContentState.opts.ref}
{interactOutsideBehavior}
{escapeKeydownBehavior}
onCloseAutoFocus={handleCloseAutoFocus}
onOpenAutoFocus={handleOpenAutoFocus}
open={subContentState.parentMenu.opts.open.current}
onInteractOutside={handleInteractOutside}
onEscapeKeydown={handleEscapeKeydown}
onFocusOutside={handleOnFocusOutside}
preventScroll={false}
{loop}
{trapFocus}
isStatic
shouldRender={subContentState.shouldRender}
>
{#snippet popper({ props })}
{@const finalProps = mergeProps(
props,
mergedProps,
{ style: getFloatingContentCSSVars("menu") },
{ style }
)}
{#if child}
{@render child({ props: finalProps, ...subContentState.snippetProps })}
{:else}
<div {...finalProps}>
{@render children?.()}
</div>
{/if}
{/snippet}
</PopperLayer>
{/if}
@@ -0,0 +1,4 @@
import type { MenuSubContentStaticProps } from "../types.js";
declare const MenuSubContentStatic: import("svelte").Component<MenuSubContentStaticProps, {}, "ref">;
type MenuSubContentStatic = ReturnType<typeof MenuSubContentStatic>;
export default MenuSubContentStatic;
+188
View File
@@ -0,0 +1,188 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuSubContentProps } from "../types.js";
import { MenuOpenEvent, MenuContentState } from "../menu.svelte.js";
import { SUB_CLOSE_KEYS } from "../utils.js";
import { createId } from "../../../internal/create-id.js";
import PopperLayer from "../../utilities/popper-layer/popper-layer.svelte";
import { noop } from "../../../internal/noop.js";
import { isHTMLElement } from "../../../internal/is.js";
import { getFloatingContentCSSVars } from "../../../internal/floating-svelte/floating-utils.svelte.js";
import PopperLayerForceMount from "../../utilities/popper-layer/popper-layer-force-mount.svelte";
const uid = $props.id();
let {
id = createId(uid),
ref = $bindable(null),
children,
child,
loop = true,
onInteractOutside = noop,
forceMount = false,
onEscapeKeydown = noop,
interactOutsideBehavior = "defer-otherwise-close",
escapeKeydownBehavior = "defer-otherwise-close",
onOpenAutoFocus: onOpenAutoFocusProp = noop,
onCloseAutoFocus: onCloseAutoFocusProp = noop,
onFocusOutside = noop,
side = "right",
trapFocus = false,
style,
...restProps
}: MenuSubContentProps = $props();
const subContentState = MenuContentState.create({
id: boxWith(() => id),
loop: boxWith(() => loop),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
isSub: true,
onCloseAutoFocus: boxWith(() => handleCloseAutoFocus),
});
function onkeydown(e: KeyboardEvent) {
const isKeyDownInside = (e.currentTarget as HTMLElement).contains(e.target as HTMLElement);
const isCloseKey = SUB_CLOSE_KEYS[
subContentState.parentMenu.root.opts.dir.current
].includes(e.key);
if (isKeyDownInside && isCloseKey) {
subContentState.parentMenu.onClose();
const triggerNode = subContentState.parentMenu.triggerNode;
triggerNode?.focus();
e.preventDefault();
}
}
const dataAttr = $derived(subContentState.parentMenu.root.getBitsAttr("sub-content"));
const mergedProps = $derived(
mergeProps(restProps, subContentState.props, {
side,
onkeydown,
[dataAttr]: "",
})
);
function handleOpenAutoFocus(e: Event) {
onOpenAutoFocusProp(e);
if (e.defaultPrevented) return;
e.preventDefault();
if (
subContentState.parentMenu.root.isUsingKeyboard &&
subContentState.parentMenu.contentNode
) {
MenuOpenEvent.dispatch(subContentState.parentMenu.contentNode);
}
}
function handleCloseAutoFocus(e: Event) {
onCloseAutoFocusProp(e);
if (e.defaultPrevented) return;
e.preventDefault();
}
function handleInteractOutside(e: PointerEvent) {
onInteractOutside(e);
if (e.defaultPrevented) return;
subContentState.parentMenu.onClose();
}
function handleEscapeKeydown(e: KeyboardEvent) {
onEscapeKeydown(e);
if (e.defaultPrevented) return;
subContentState.parentMenu.onClose();
}
function handleOnFocusOutside(e: FocusEvent) {
onFocusOutside(e);
if (e.defaultPrevented) return;
// We prevent closing when the trigger is focused to avoid triggering a re-open animation
// on pointer interaction.
if (!isHTMLElement(e.target)) return;
if (e.target.id !== subContentState.parentMenu.triggerNode?.id) {
subContentState.parentMenu.onClose();
}
}
</script>
{#if forceMount}
<PopperLayerForceMount
{...mergedProps}
ref={subContentState.opts.ref}
{interactOutsideBehavior}
{escapeKeydownBehavior}
onOpenAutoFocus={handleOpenAutoFocus}
enabled={subContentState.parentMenu.opts.open.current}
onInteractOutside={handleInteractOutside}
onEscapeKeydown={handleEscapeKeydown}
onFocusOutside={handleOnFocusOutside}
preventScroll={false}
{loop}
{trapFocus}
shouldRender={subContentState.shouldRender}
>
{#snippet popper({ props, wrapperProps })}
{@const finalProps = mergeProps(
props,
mergedProps,
{ style: getFloatingContentCSSVars("menu") },
{ style }
)}
{#if child}
{@render child({
props: finalProps,
wrapperProps,
...subContentState.snippetProps,
})}
{:else}
<div {...wrapperProps}>
<div {...finalProps}>
{@render children?.()}
</div>
</div>
{/if}
{/snippet}
</PopperLayerForceMount>
{:else if !forceMount}
<PopperLayer
{...mergedProps}
ref={subContentState.opts.ref}
{interactOutsideBehavior}
{escapeKeydownBehavior}
onCloseAutoFocus={handleCloseAutoFocus}
onOpenAutoFocus={handleOpenAutoFocus}
open={subContentState.parentMenu.opts.open.current}
onInteractOutside={handleInteractOutside}
onEscapeKeydown={handleEscapeKeydown}
onFocusOutside={handleOnFocusOutside}
preventScroll={false}
{loop}
{trapFocus}
shouldRender={subContentState.shouldRender}
>
{#snippet popper({ props, wrapperProps })}
{@const finalProps = mergeProps(
props,
mergedProps,
{ style: getFloatingContentCSSVars("menu") },
{ style }
)}
{#if child}
{@render child({
props: finalProps,
wrapperProps,
...subContentState.snippetProps,
})}
{:else}
<div {...wrapperProps}>
<div {...finalProps}>
{@render children?.()}
</div>
</div>
{/if}
{/snippet}
</PopperLayer>
{/if}
@@ -0,0 +1,4 @@
import type { MenuSubContentProps } from "../types.js";
declare const MenuSubContent: import("svelte").Component<MenuSubContentProps, {}, "ref">;
type MenuSubContent = ReturnType<typeof MenuSubContent>;
export default MenuSubContent;
+44
View File
@@ -0,0 +1,44 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuSubTriggerProps } from "../types.js";
import { MenuSubTriggerState } from "../menu.svelte.js";
import FloatingLayerAnchor from "../../utilities/floating-layer/components/floating-layer-anchor.svelte";
import { noop } from "../../../internal/noop.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
id = createId(uid),
disabled = false,
ref = $bindable(null),
children,
child,
onSelect = noop,
openDelay = 100,
...restProps
}: MenuSubTriggerProps = $props();
const subTriggerState = MenuSubTriggerState.create({
disabled: boxWith(() => disabled),
onSelect: boxWith(() => onSelect),
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
openDelay: boxWith(() => openDelay),
});
const mergedProps = $derived(mergeProps(restProps, subTriggerState.props));
</script>
<FloatingLayerAnchor {id} ref={subTriggerState.opts.ref}>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
</FloatingLayerAnchor>
@@ -0,0 +1,4 @@
import type { MenuSubTriggerProps } from "../types.js";
declare const MenuSubTrigger: import("svelte").Component<MenuSubTriggerProps, {}, "ref">;
type MenuSubTrigger = ReturnType<typeof MenuSubTrigger>;
export default MenuSubTrigger;
+29
View File
@@ -0,0 +1,29 @@
<script lang="ts">
import { boxWith } from "svelte-toolbelt";
import type { MenuSubProps } from "../types.js";
import { MenuSubmenuState } from "../menu.svelte.js";
import FloatingLayer from "../../utilities/floating-layer/components/floating-layer.svelte";
import { noop } from "../../../internal/noop.js";
let {
open = $bindable(false),
onOpenChange = noop,
onOpenChangeComplete = noop,
children,
}: MenuSubProps = $props();
MenuSubmenuState.create({
open: boxWith(
() => open,
(v) => {
open = v;
onOpenChange?.(v);
}
),
onOpenChangeComplete: boxWith(() => onOpenChangeComplete),
});
</script>
<FloatingLayer>
{@render children?.()}
</FloatingLayer>
+3
View File
@@ -0,0 +1,3 @@
declare const MenuSub: import("svelte").Component<import("../types.js").MenuSubPropsWithoutHTML, {}, "open">;
type MenuSub = ReturnType<typeof MenuSub>;
export default MenuSub;
+40
View File
@@ -0,0 +1,40 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { MenuTriggerProps } from "../types.js";
import { DropdownMenuTriggerState } from "../menu.svelte.js";
import { createId } from "../../../internal/create-id.js";
import FloatingLayerAnchor from "../../utilities/floating-layer/components/floating-layer-anchor.svelte";
const uid = $props.id();
let {
id = createId(uid),
ref = $bindable(null),
child,
children,
disabled = false,
type = "button",
...restProps
}: MenuTriggerProps = $props();
const triggerState = DropdownMenuTriggerState.create({
id: boxWith(() => id),
disabled: boxWith(() => disabled ?? false),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, triggerState.props, { type }));
</script>
<FloatingLayerAnchor {id} ref={triggerState.opts.ref}>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<button {...mergedProps}>
{@render children?.()}
</button>
{/if}
</FloatingLayerAnchor>
@@ -0,0 +1,4 @@
import type { MenuTriggerProps } from "../types.js";
declare const MenuTrigger: import("svelte").Component<MenuTriggerProps, {}, "ref">;
type MenuTrigger = ReturnType<typeof MenuTrigger>;
export default MenuTrigger;
+45
View File
@@ -0,0 +1,45 @@
<script lang="ts">
import { boxWith } from "svelte-toolbelt";
import type { MenuRootProps } from "../types.js";
import { MenuMenuState, MenuRootState } from "../menu.svelte.js";
import { noop } from "../../../internal/noop.js";
import FloatingLayer from "../../utilities/floating-layer/components/floating-layer.svelte";
let {
open = $bindable(false),
dir = "ltr",
onOpenChange = noop,
onOpenChangeComplete = noop,
_internal_variant: variant = "dropdown-menu",
children,
}: MenuRootProps & {
_internal_variant?: "context-menu" | "dropdown-menu" | "menubar";
} = $props();
const root = MenuRootState.create({
variant: boxWith(() => variant),
dir: boxWith(() => dir),
onClose: () => {
open = false;
onOpenChange(false);
},
});
MenuMenuState.create(
{
open: boxWith(
() => open,
(v) => {
open = v;
onOpenChange(v);
}
),
onOpenChangeComplete: boxWith(() => onOpenChangeComplete),
},
root
);
</script>
<FloatingLayer>
{@render children?.()}
</FloatingLayer>
+7
View File
@@ -0,0 +1,7 @@
import type { MenuRootProps } from "../types.js";
type $$ComponentProps = MenuRootProps & {
_internal_variant?: "context-menu" | "dropdown-menu" | "menubar";
};
declare const Menu: import("svelte").Component<$$ComponentProps, {}, "open">;
type Menu = ReturnType<typeof Menu>;
export default Menu;