feat: Phase 1 — extract the client (web SPA + desktop) to dtoro/oikos-web
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled

Problem: the hexagonal refactor churns the backend tree for nine more
phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper,
compose/web image) must move to its own repo first so doc/layout
rewrites land once on a backend-only tree.

Change:
- New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/
  (updateURL repointed to oikos-web releases), compose/, own CI (web +
  desktop jobs), own deploy script (CI-green gate, TOCTOU guard,
  version-tagged images, prune-to-3), own webhook receiver on :9798 +
  launchd unit, own compose project publishing the same 8091:80.
- Cutover executed on mac-mini in order: oikos stack's web service
  stopped+removed, oikos-web project brought up on 8091; outer Caddy
  untouched (targets the published port) — serving + Authentik flow +
  /wails 404 quirk verified post-cutover.
- Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI
  workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install
  targets, the compose web service, oikos-web from deploy.sh's fallback
  prune list; wails + go-keyring dropped from go.mod, vendor synced.
- README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now
  point at the new repo; mbse + mascot design docs carry a path note.

Risk: production SPA serving depends on the new pipeline now; rollback
is versioned-image re-up of the old web service from a pre-split
checkout (port 8091). Desktop builds installed before the split still
check dtoro/oikos releases — one manual reinstall, noted in the
oikos-web release notes.

Verification: go vet, make test (race), make generate-check, golangci
(no new findings; baseline down 400→365); post-cutover curls —
localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
This commit is contained in:
2026-08-15 22:27:52 +02:00
parent e074f04bdf
commit d4d99a7473
18854 changed files with 2615729 additions and 173735 deletions

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} />

View File

@@ -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;

View File

@@ -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}

View File

@@ -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;

View File

@@ -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}

View File

@@ -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;

View File

@@ -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}

View File

@@ -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;

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}

View File

@@ -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;

View File

@@ -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}

View File

@@ -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;

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}

View File

@@ -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;

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}

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;

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}

View File

@@ -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;

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}

View File

@@ -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;

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}

View File

@@ -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;

View File

@@ -0,0 +1,185 @@
<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;
if (!isHTMLElement(e.target)) return;
if (e.target.id === subContentState.parentMenu.triggerNode?.id) return;
const parentContent = subContentState.parentMenu.parentMenu?.contentNode;
if (parentContent?.contains(e.target)) {
subContentState.parentMenu.onClose();
e.preventDefault();
return;
}
// focus moved to a descendant sub-content (rendered in a portal)
const subContentSelector = `[${subContentState.parentMenu.root.getBitsAttr("sub-content")}]`;
if (e.target.closest(subContentSelector)) {
e.preventDefault();
return;
}
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}

View File

@@ -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;

View File

@@ -0,0 +1,197 @@
<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;
if (!isHTMLElement(e.target)) return;
if (e.target.id === subContentState.parentMenu.triggerNode?.id) return;
const parentContent = subContentState.parentMenu.parentMenu?.contentNode;
if (parentContent?.contains(e.target)) {
subContentState.parentMenu.onClose();
e.preventDefault();
return;
}
// focus moved to a descendant sub-content rendered in a portal
const subContentSelector = `[${subContentState.parentMenu.root.getBitsAttr("sub-content")}]`;
if (e.target.closest(subContentSelector)) {
e.preventDefault();
return;
}
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}

View File

@@ -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;

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 = 0,
...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>

View File

@@ -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;

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>

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;

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>

View File

@@ -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;

View File

@@ -0,0 +1,50 @@
<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",
// debugMode = false,
onOpenChange = noop,
onOpenChangeComplete = noop,
_internal_variant: variant = "dropdown-menu",
_internal_should_skip_exit_animation: shouldSkipExitAnimation = undefined,
children,
}: MenuRootProps & {
_internal_variant?: "context-menu" | "dropdown-menu" | "menubar";
_internal_should_skip_exit_animation?: () => boolean;
} = $props();
const root = MenuRootState.create({
variant: boxWith(() => variant),
dir: boxWith(() => dir),
// debugMode: boxWith(() => debugMode),
onClose: () => {
open = false;
onOpenChange(false);
},
shouldSkipExitAnimation: () => shouldSkipExitAnimation?.() ?? false,
});
MenuMenuState.create(
{
open: boxWith(
() => open,
(v) => {
open = v;
onOpenChange(v);
}
),
onOpenChangeComplete: boxWith(() => onOpenChangeComplete),
},
root
);
</script>
<FloatingLayer>
{@render children?.()}
</FloatingLayer>

View File

@@ -0,0 +1,8 @@
import type { MenuRootProps } from "../types.js";
type $$ComponentProps = MenuRootProps & {
_internal_variant?: "context-menu" | "dropdown-menu" | "menubar";
_internal_should_skip_exit_animation?: () => boolean;
};
declare const Menu: import("svelte").Component<$$ComponentProps, {}, "open">;
type Menu = ReturnType<typeof Menu>;
export default Menu;

19
web/node_modules/bits-ui/dist/bits/menu/exports.d.ts generated vendored Normal file
View File

@@ -0,0 +1,19 @@
export { default as Root } from "./components/menu.svelte";
export { default as Arrow } from "./components/menu-arrow.svelte";
export { default as CheckboxGroup } from "./components/menu-checkbox-group.svelte";
export { default as CheckboxItem } from "./components/menu-checkbox-item.svelte";
export { default as Content } from "./components/menu-content.svelte";
export { default as ContentStatic } from "./components/menu-content-static.svelte";
export { default as Group } from "./components/menu-group.svelte";
export { default as Item } from "./components/menu-item.svelte";
export { default as GroupHeading } from "./components/menu-group-heading.svelte";
export { default as Portal } from "../utilities/portal/portal.svelte";
export { default as RadioGroup } from "./components/menu-radio-group.svelte";
export { default as RadioItem } from "./components/menu-radio-item.svelte";
export { default as Separator } from "./components/menu-separator.svelte";
export { default as Sub } from "./components/menu-sub.svelte";
export { default as SubContent } from "./components/menu-sub-content.svelte";
export { default as SubTrigger } from "./components/menu-sub-trigger.svelte";
export { default as Trigger } from "./components/menu-trigger.svelte";
export { default as SubContentStatic } from "./components/menu-sub-content-static.svelte";
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";

18
web/node_modules/bits-ui/dist/bits/menu/exports.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
export { default as Root } from "./components/menu.svelte";
export { default as Arrow } from "./components/menu-arrow.svelte";
export { default as CheckboxGroup } from "./components/menu-checkbox-group.svelte";
export { default as CheckboxItem } from "./components/menu-checkbox-item.svelte";
export { default as Content } from "./components/menu-content.svelte";
export { default as ContentStatic } from "./components/menu-content-static.svelte";
export { default as Group } from "./components/menu-group.svelte";
export { default as Item } from "./components/menu-item.svelte";
export { default as GroupHeading } from "./components/menu-group-heading.svelte";
export { default as Portal } from "../utilities/portal/portal.svelte";
export { default as RadioGroup } from "./components/menu-radio-group.svelte";
export { default as RadioItem } from "./components/menu-radio-item.svelte";
export { default as Separator } from "./components/menu-separator.svelte";
export { default as Sub } from "./components/menu-sub.svelte";
export { default as SubContent } from "./components/menu-sub-content.svelte";
export { default as SubTrigger } from "./components/menu-sub-trigger.svelte";
export { default as Trigger } from "./components/menu-trigger.svelte";
export { default as SubContentStatic } from "./components/menu-sub-content-static.svelte";

View File

@@ -0,0 +1,434 @@
import { DOMContext, type ReadableBoxedValues, type WritableBoxedValues, type ReadableBox } from "svelte-toolbelt";
import { Context } from "runed";
import { CustomEventDispatcher } from "../../internal/events.js";
import type { AnyFn, BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent, OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
import type { Direction } from "../../shared/index.js";
import { IsUsingKeyboard } from "../utilities/is-using-keyboard/is-using-keyboard.svelte.js";
import type { KeyboardEventHandler, PointerEventHandler, MouseEventHandler } from "svelte/elements";
import { RovingFocusGroup } from "../../internal/roving-focus-group.js";
import { PresenceManager } from "../../internal/presence-manager.svelte.js";
export declare const CONTEXT_MENU_TRIGGER_ATTR = "data-context-menu-trigger";
export declare const CONTEXT_MENU_CONTENT_ATTR = "data-context-menu-content";
export declare const MenuCheckboxGroupContext: Context<MenuCheckboxGroupState>;
type MenuVariant = "context-menu" | "dropdown-menu" | "menubar";
export interface MenuRootStateOpts extends ReadableBoxedValues<{
dir: Direction;
variant: MenuVariant;
}> {
onClose: AnyFn;
/** When closing, if this returns true, exit animations are skipped (instant unmount). */
shouldSkipExitAnimation?: () => boolean;
}
export declare const MenuOpenEvent: CustomEventDispatcher<unknown>;
export declare const menuAttrs: import("../../internal/attrs.js").CreateBitsAttrsReturn<readonly ["trigger", "content", "sub-trigger", "item", "group", "group-heading", "checkbox-group", "checkbox-item", "radio-group", "radio-item", "separator", "sub-content", "arrow"]>;
export declare class MenuRootState {
static create(opts: MenuRootStateOpts): MenuRootState;
readonly opts: MenuRootStateOpts;
readonly isUsingKeyboard: IsUsingKeyboard;
ignoreCloseAutoFocus: boolean;
isPointerInTransit: boolean;
constructor(opts: MenuRootStateOpts);
getBitsAttr: typeof menuAttrs.getAttr;
}
interface MenuMenuStateOpts extends WritableBoxedValues<{
open: boolean;
}>, ReadableBoxedValues<{
onOpenChangeComplete: OnChangeFn<boolean>;
}> {
}
export declare class MenuMenuState {
static create(opts: MenuMenuStateOpts, root: MenuRootState): MenuMenuState;
readonly opts: MenuMenuStateOpts;
readonly root: MenuRootState;
readonly parentMenu: MenuMenuState | null;
contentId: ReadableBox<string>;
contentNode: HTMLElement | null;
contentPresence: PresenceManager;
triggerNode: HTMLElement | null;
constructor(opts: MenuMenuStateOpts, root: MenuRootState, parentMenu: MenuMenuState | null);
toggleOpen(): void;
onOpen(): void;
onClose(): void;
}
interface MenuContentStateOpts extends WithRefOpts, ReadableBoxedValues<{
loop: boolean;
onCloseAutoFocus: (event: Event) => void;
}> {
isSub?: boolean;
}
export declare class MenuContentState {
#private;
static create(opts: MenuContentStateOpts): MenuContentState;
readonly opts: MenuContentStateOpts;
readonly parentMenu: MenuMenuState;
readonly rovingFocusGroup: RovingFocusGroup;
readonly domContext: DOMContext;
readonly attachment: RefAttachment;
search: string;
mounted: boolean;
constructor(opts: MenuContentStateOpts, parentMenu: MenuMenuState);
onCloseAutoFocus: (e: Event) => void;
handleTabKeyDown(e: BitsKeyboardEvent): void;
onkeydown(e: BitsKeyboardEvent): void;
onblur(e: BitsFocusEvent): void;
onfocus(_: BitsFocusEvent): void;
onItemEnter(): boolean;
onItemLeave(e: BitsPointerEvent): void;
onTriggerLeave(): boolean;
handleInteractOutside(e: PointerEvent): void;
get shouldRender(): boolean;
readonly snippetProps: {
open: boolean;
};
readonly props: {
readonly onkeydown: (e: BitsKeyboardEvent) => void;
readonly onblur: (e: BitsFocusEvent) => void;
readonly onfocus: (_: BitsFocusEvent) => void;
readonly dir: Direction;
readonly style: {
readonly pointerEvents: "auto";
readonly contain: "layout style";
};
readonly "data-starting-style"?: "";
readonly "data-ending-style"?: "";
readonly id: string;
readonly role: "menu";
readonly "aria-orientation": "vertical";
readonly "data-state": "open" | "closed";
};
readonly popperProps: {
onCloseAutoFocus: (e: Event) => void;
};
}
interface MenuItemSharedStateOpts extends WithRefOpts, ReadableBoxedValues<{
disabled: boolean;
}> {
}
declare class MenuItemSharedState {
#private;
readonly opts: MenuItemSharedStateOpts;
readonly content: MenuContentState;
readonly attachment: RefAttachment;
constructor(opts: MenuItemSharedStateOpts, content: MenuContentState);
onpointermove(e: BitsPointerEvent): void;
onpointerleave(e: BitsPointerEvent): void;
onfocus(e: BitsFocusEvent): void;
onblur(e: BitsFocusEvent): void;
readonly props: {
readonly id: string;
readonly tabindex: -1;
readonly role: "menuitem";
readonly "aria-disabled": "true" | "false";
readonly "data-disabled": "" | undefined;
readonly "data-highlighted": "" | undefined;
readonly onpointermove: (e: BitsPointerEvent) => void;
readonly onpointerleave: (e: BitsPointerEvent) => void;
readonly onfocus: (e: BitsFocusEvent) => void;
readonly onblur: (e: BitsFocusEvent) => void;
};
}
type MenuItemCombinedProps = MenuItemSharedStateOpts & MenuItemStateOpts;
interface MenuItemStateOpts extends ReadableBoxedValues<{
onSelect: AnyFn;
closeOnSelect: boolean;
}> {
}
export declare class MenuItemState {
#private;
static create(opts: MenuItemCombinedProps): MenuItemState;
readonly opts: MenuItemStateOpts;
readonly item: MenuItemSharedState;
readonly root: MenuRootState;
constructor(opts: MenuItemStateOpts, item: MenuItemSharedState);
onkeydown(e: BitsKeyboardEvent): void;
onclick(_: BitsMouseEvent): void;
onpointerup(e: BitsPointerEvent): void;
onpointerdown(_: BitsPointerEvent): void;
readonly props: {
readonly id: string;
readonly tabindex: -1;
readonly role: "menuitem";
readonly "aria-disabled": "true" | "false";
readonly "data-disabled": "" | undefined;
readonly "data-highlighted": "" | undefined;
readonly onpointermove: (e: BitsPointerEvent) => void;
readonly onpointerleave: (e: BitsPointerEvent) => void;
readonly onfocus: (e: BitsFocusEvent) => void;
readonly onblur: (e: BitsFocusEvent) => void;
} & {
onclick: (_: BitsMouseEvent) => void;
onpointerdown: (_: BitsPointerEvent) => void;
onpointerup: (e: BitsPointerEvent) => void;
onkeydown: (e: BitsKeyboardEvent) => void;
} & {
style?: string;
};
}
interface MenuSubTriggerStateOpts extends MenuItemSharedStateOpts, Pick<MenuItemStateOpts, "onSelect"> {
openDelay: ReadableBox<number>;
}
export declare class MenuSubTriggerState {
#private;
static create(opts: MenuSubTriggerStateOpts): MenuSubTriggerState;
readonly opts: MenuSubTriggerStateOpts;
readonly item: MenuItemSharedState;
readonly content: MenuContentState;
readonly submenu: MenuMenuState;
readonly attachment: RefAttachment;
constructor(opts: MenuSubTriggerStateOpts, item: MenuItemSharedState, content: MenuContentState, submenu: MenuMenuState);
onpointermove(e: BitsPointerEvent): void;
onpointerleave(e: BitsPointerEvent): void;
onkeydown(e: BitsKeyboardEvent): void;
onclick(e: BitsMouseEvent): void;
readonly props: {
readonly id: string;
readonly tabindex: -1;
readonly role: "menuitem";
readonly "aria-disabled": "true" | "false";
readonly "data-disabled": "" | undefined;
readonly "data-highlighted": "" | undefined;
readonly onpointermove: (e: BitsPointerEvent) => void;
readonly onpointerleave: (e: BitsPointerEvent) => void;
readonly onfocus: (e: BitsFocusEvent) => void;
readonly onblur: (e: BitsFocusEvent) => void;
} & {
"aria-haspopup": string;
"aria-expanded": "true" | "false";
"data-state": "open" | "closed";
"aria-controls": string | undefined;
onclick: (e: BitsMouseEvent) => void;
onpointermove: (e: BitsPointerEvent) => void;
onpointerleave: (e: BitsPointerEvent) => void;
onkeydown: (e: BitsKeyboardEvent) => void;
} & {
style?: string;
};
}
interface MenuCheckboxItemStateOpts extends WritableBoxedValues<{
checked: boolean;
indeterminate: boolean;
}>, ReadableBoxedValues<{
value: string;
}> {
}
export declare class MenuCheckboxItemState {
static create(opts: MenuItemCombinedProps & MenuCheckboxItemStateOpts, checkboxGroup: MenuCheckboxGroupState | null): MenuCheckboxItemState;
readonly opts: MenuCheckboxItemStateOpts;
readonly item: MenuItemState;
readonly group: MenuCheckboxGroupState | null;
constructor(opts: MenuCheckboxItemStateOpts, item: MenuItemState, group?: MenuCheckboxGroupState | null);
toggleChecked(): void;
readonly snippetProps: {
checked: boolean;
indeterminate: boolean;
};
readonly props: {
readonly role: "menuitemcheckbox";
readonly "aria-checked": "true" | "false" | "mixed";
readonly "data-state": "checked" | "indeterminate" | "unchecked";
readonly id: string;
readonly tabindex: -1;
readonly "aria-disabled": "true" | "false";
readonly "data-disabled": "" | undefined;
readonly "data-highlighted": "" | undefined;
readonly onpointermove: (e: BitsPointerEvent) => void;
readonly onpointerleave: (e: BitsPointerEvent) => void;
readonly onfocus: (e: BitsFocusEvent) => void;
readonly onblur: (e: BitsFocusEvent) => void;
readonly onclick: (_: BitsMouseEvent) => void;
readonly onpointerdown: (_: BitsPointerEvent) => void;
readonly onpointerup: (e: BitsPointerEvent) => void;
readonly onkeydown: (e: BitsKeyboardEvent) => void;
readonly style?: string;
};
}
interface MenuGroupStateOpts extends WithRefOpts {
}
export declare class MenuGroupState {
static create(opts: MenuGroupStateOpts): MenuGroupState | MenuRadioGroupState;
readonly opts: MenuGroupStateOpts;
readonly root: MenuRootState;
readonly attachment: RefAttachment;
groupHeadingId: string | undefined;
constructor(opts: MenuGroupStateOpts, root: MenuRootState);
readonly props: {
readonly id: string;
readonly role: "group";
readonly "aria-labelledby": string | undefined;
};
}
interface MenuGroupHeadingStateOpts extends WithRefOpts {
}
export declare class MenuGroupHeadingState {
static create(opts: MenuGroupHeadingStateOpts): MenuGroupHeadingState;
readonly opts: MenuGroupHeadingStateOpts;
readonly group: MenuGroupState | MenuRadioGroupState | MenuCheckboxGroupState;
readonly attachment: RefAttachment;
constructor(opts: MenuGroupHeadingStateOpts, group: MenuGroupState | MenuRadioGroupState | MenuCheckboxGroupState);
readonly props: {
readonly id: string;
readonly role: "group";
};
}
interface MenuSeparatorStateOpts extends WithRefOpts {
}
export declare class MenuSeparatorState {
static create(opts: MenuSeparatorStateOpts): MenuSeparatorState;
readonly opts: MenuSeparatorStateOpts;
readonly root: MenuRootState;
readonly attachment: RefAttachment;
constructor(opts: MenuSeparatorStateOpts, root: MenuRootState);
readonly props: {
readonly id: string;
readonly role: "group";
};
}
export declare class MenuArrowState {
static create(): MenuArrowState;
readonly root: MenuRootState;
constructor(root: MenuRootState);
readonly props: {
readonly [x: string]: "";
};
}
interface MenuRadioGroupStateOpts extends WithRefOpts, WritableBoxedValues<{
value: string;
}> {
}
export declare class MenuRadioGroupState {
static create(opts: MenuRadioGroupStateOpts): MenuGroupState | MenuRadioGroupState;
readonly opts: MenuRadioGroupStateOpts;
readonly content: MenuContentState;
readonly attachment: RefAttachment;
groupHeadingId: string | null;
root: MenuRootState;
constructor(opts: MenuRadioGroupStateOpts, content: MenuContentState);
setValue(v: string): void;
readonly props: {
readonly id: string;
readonly role: "group";
readonly "aria-labelledby": string | null;
};
}
interface MenuRadioItemStateOpts extends WithRefOpts, ReadableBoxedValues<{
value: string;
closeOnSelect: boolean;
}> {
}
export declare class MenuRadioItemState {
static create(opts: MenuRadioItemStateOpts & MenuItemCombinedProps): MenuRadioItemState;
readonly opts: MenuRadioItemStateOpts;
readonly item: MenuItemState;
readonly group: MenuRadioGroupState;
readonly attachment: RefAttachment;
readonly isChecked: boolean;
constructor(opts: MenuRadioItemStateOpts, item: MenuItemState, group: MenuRadioGroupState);
selectValue(): void;
readonly props: {
readonly role: "menuitemradio";
readonly "aria-checked": "true" | "false" | "mixed";
readonly "data-state": "checked" | "indeterminate" | "unchecked";
readonly id: string;
readonly tabindex: -1;
readonly "aria-disabled": "true" | "false";
readonly "data-disabled": "" | undefined;
readonly "data-highlighted": "" | undefined;
readonly onpointermove: (e: BitsPointerEvent) => void;
readonly onpointerleave: (e: BitsPointerEvent) => void;
readonly onfocus: (e: BitsFocusEvent) => void;
readonly onblur: (e: BitsFocusEvent) => void;
readonly onclick: (_: BitsMouseEvent) => void;
readonly onpointerdown: (_: BitsPointerEvent) => void;
readonly onpointerup: (e: BitsPointerEvent) => void;
readonly onkeydown: (e: BitsKeyboardEvent) => void;
readonly style?: string;
};
}
interface DropdownMenuTriggerStateOpts extends WithRefOpts, ReadableBoxedValues<{
disabled: boolean;
}> {
}
export declare class DropdownMenuTriggerState {
#private;
static create(opts: DropdownMenuTriggerStateOpts): DropdownMenuTriggerState;
readonly opts: DropdownMenuTriggerStateOpts;
readonly parentMenu: MenuMenuState;
readonly attachment: RefAttachment;
constructor(opts: DropdownMenuTriggerStateOpts, parentMenu: MenuMenuState);
onclick: MouseEventHandler<HTMLElement>;
onpointerdown: PointerEventHandler<HTMLElement>;
onpointerup: PointerEventHandler<HTMLElement>;
onkeydown: KeyboardEventHandler<HTMLElement>;
readonly props: {
readonly id: string;
readonly disabled: boolean;
readonly "aria-haspopup": "menu";
readonly "aria-expanded": "true" | "false";
readonly "aria-controls": string | undefined;
readonly "data-disabled": "" | undefined;
readonly "data-state": "open" | "closed";
readonly onclick: MouseEventHandler<HTMLElement>;
readonly onpointerdown: PointerEventHandler<HTMLElement>;
readonly onpointerup: PointerEventHandler<HTMLElement>;
readonly onkeydown: KeyboardEventHandler<HTMLElement>;
};
}
interface ContextMenuTriggerStateOpts extends WithRefOpts, ReadableBoxedValues<{
disabled: boolean;
}> {
}
export declare class ContextMenuTriggerState {
#private;
static create(opts: ContextMenuTriggerStateOpts): ContextMenuTriggerState;
readonly opts: ContextMenuTriggerStateOpts;
readonly parentMenu: MenuMenuState;
readonly attachment: RefAttachment;
virtualElement: import("svelte-toolbelt").WritableBox<{
getBoundingClientRect: () => DOMRect;
}>;
constructor(opts: ContextMenuTriggerStateOpts, parentMenu: MenuMenuState);
oncontextmenu(e: BitsMouseEvent): void;
onpointerdown(e: BitsPointerEvent): void;
onpointermove(e: BitsPointerEvent): void;
onpointercancel(e: BitsPointerEvent): void;
onpointerup(e: BitsPointerEvent): void;
readonly props: {
readonly id: string;
readonly disabled: boolean;
readonly "data-disabled": "" | undefined;
readonly "data-state": "open" | "closed";
readonly "data-context-menu-trigger": "";
readonly tabindex: -1;
readonly onpointerdown: (e: BitsPointerEvent) => void;
readonly onpointermove: (e: BitsPointerEvent) => void;
readonly onpointercancel: (e: BitsPointerEvent) => void;
readonly onpointerup: (e: BitsPointerEvent) => void;
readonly oncontextmenu: (e: BitsMouseEvent) => void;
};
}
interface MenuCheckboxGroupStateOpts extends WithRefOpts, ReadableBoxedValues<{
onValueChange: (value: string[]) => void;
}>, WritableBoxedValues<{
value: string[];
}> {
}
export declare class MenuCheckboxGroupState {
static create(opts: MenuCheckboxGroupStateOpts): MenuCheckboxGroupState;
readonly opts: MenuCheckboxGroupStateOpts;
readonly content: MenuContentState;
readonly root: MenuRootState;
readonly attachment: RefAttachment;
groupHeadingId: string | null;
constructor(opts: MenuCheckboxGroupStateOpts, content: MenuContentState);
addValue(checkboxValue: string | undefined): void;
removeValue(checkboxValue: string | undefined): void;
readonly props: {
readonly id: string;
readonly role: "group";
readonly "aria-labelledby": string | null;
};
}
export declare class MenuSubmenuState {
static create(opts: MenuMenuStateOpts): MenuMenuState;
}
export {};

1621
web/node_modules/bits-ui/dist/bits/menu/menu.svelte.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

203
web/node_modules/bits-ui/dist/bits/menu/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,203 @@
import type { Expand } from "svelte-toolbelt";
import type { PopperLayerProps, PopperLayerStaticProps } from "../utilities/popper-layer/types.js";
import type { ArrowProps, ArrowPropsWithoutHTML } from "../utilities/arrow/types.js";
import type { OnChangeFn, WithChild, WithChildNoChildrenSnippetProps, WithChildren, Without } from "../../internal/types.js";
import type { BitsPrimitiveButtonAttributes, BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
import type { Direction } from "../../shared/index.js";
import type { PortalProps } from "../utilities/portal/types.js";
import type { FloatingContentSnippetProps, StaticContentSnippetProps } from "../../shared/types.js";
export type MenuRootPropsWithoutHTML = WithChildren<{
/**
* The open state of the menu.
*/
open?: boolean;
/**
* A callback that is called when the menu is opened or closed.
*/
onOpenChange?: OnChangeFn<boolean>;
/**
* A callback that is called when the menu is opened or closed.
*/
onOpenChangeComplete?: OnChangeFn<boolean>;
/**
* The direction of the site.
*
* @defaultValue "ltr"
*/
dir?: Direction;
}>;
export type MenuRootProps = MenuRootPropsWithoutHTML;
export type _SharedMenuContentProps = {
/**
* When `true`, the menu will loop through items when navigating with the keyboard.
*
* @defaultValue false
*/
loop?: boolean;
};
export type MenuContentPropsWithoutHTML = Expand<WithChildNoChildrenSnippetProps<Omit<PopperLayerProps, "content"> & _SharedMenuContentProps, FloatingContentSnippetProps>>;
export type MenuContentProps = MenuContentPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuContentPropsWithoutHTML>;
export type MenuContentStaticPropsWithoutHTML = Expand<WithChildNoChildrenSnippetProps<Omit<PopperLayerStaticProps, "content"> & _SharedMenuContentProps, StaticContentSnippetProps>>;
export type MenuContentStaticProps = MenuContentStaticPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuContentStaticPropsWithoutHTML>;
export type MenuItemPropsWithoutHTML<U extends Record<PropertyKey, unknown> = {
_default: never;
}> = WithChild<{
/**
* When `true`, the user will not be able to interact with the menu item.
*
* @defaultValue false
*/
disabled?: boolean;
/**
* Optional text to use for typeahead filtering. By default, typeahead will use
* the `.textContent` of the menu item. When the content is more complex, you
* can provide a string here instead.
*
* @defaultValue undefined
*/
textValue?: string;
/**
* A callback fired when the menu item is selected.
*
* Prevent default behavior of selection with `event.preventDefault()`.
*/
onSelect?: (event: Event) => void;
/**
* Whether or not the menu item should close when selected.
* @defaultValue true
*/
closeOnSelect?: boolean;
}, U>;
export type MenuItemProps = MenuItemPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuItemPropsWithoutHTML>;
export type MenuCheckboxItemSnippetProps = {
checked: boolean;
indeterminate: boolean;
};
export type MenuCheckboxItemPropsWithoutHTML = MenuItemPropsWithoutHTML<MenuCheckboxItemSnippetProps> & {
/**
* The checked state of the checkbox. It can be one of:
* - `true` for checked
* - `false` for unchecked
*
* @defaultValue false
*/
checked?: boolean;
/**
* A callback that is fired when the checked state changes.
*/
onCheckedChange?: OnChangeFn<boolean>;
/**
* Whether the checkbox is in an indeterminate state or not.
*
* @defaultValue false
*/
indeterminate?: boolean;
/**
* A callback function called when the indeterminate state changes.
*/
onIndeterminateChange?: OnChangeFn<boolean>;
/**
* Whether or not the menu item should close when selected.
*
* @defaultValue true
*/
closeOnSelect?: boolean;
/**
* The value of the checkbox item when used in a checkbox group.
*/
value?: string;
};
export type MenuCheckboxItemProps = MenuCheckboxItemPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuCheckboxItemPropsWithoutHTML>;
export type MenuCheckboxGroupPropsWithoutHTML = WithChild<{
/**
* The values of the selected checkbox items.
*
* Supports two-way binding with `bind:value`.
*/
value?: string[];
/**
* A callback that is fired when the selected checkbox items change.
*/
onValueChange?: OnChangeFn<string[]>;
}>;
export type MenuCheckboxGroupProps = MenuCheckboxGroupPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuCheckboxGroupPropsWithoutHTML>;
export type MenuTriggerPropsWithoutHTML = WithChild<{
/**
* Whether the trigger is disabled.
*
* @defaultValue false
*/
disabled?: boolean | null | undefined;
}>;
export type MenuTriggerProps = MenuTriggerPropsWithoutHTML & Without<BitsPrimitiveButtonAttributes, MenuTriggerPropsWithoutHTML>;
export type MenuSubPropsWithoutHTML = WithChildren<{
/**
* The open state of the menu.
*/
open?: boolean;
/**
* A callback that is called when the menu is opened or closed.
*/
onOpenChange?: OnChangeFn<boolean>;
/**
* A callback that is called when the menu finishes opening/closing animations.
*/
onOpenChangeComplete?: OnChangeFn<boolean>;
}>;
export type MenuSubProps = MenuSubPropsWithoutHTML;
export type MenuSubContentPropsWithoutHTML = Expand<WithChildNoChildrenSnippetProps<Omit<PopperLayerProps, "content" | "preventScroll"> & _SharedMenuContentProps, FloatingContentSnippetProps>>;
export type MenuSubContentProps = MenuSubContentPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuSubContentPropsWithoutHTML>;
export type MenuSubContentStaticPropsWithoutHTML = Expand<WithChildNoChildrenSnippetProps<Omit<PopperLayerStaticProps, "content" | "preventScroll"> & _SharedMenuContentProps, StaticContentSnippetProps>>;
export type MenuSubContentStaticProps = MenuSubContentStaticPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuSubContentStaticPropsWithoutHTML>;
export type MenuSubTriggerPropsWithoutHTML = Omit<MenuItemPropsWithoutHTML, "closeOnSelect"> & {
/**
* The amount of time in ms from when the mouse enters the subtrigger until
* the submenu opens. This is useful for preventing the submenu from opening
* as a user is moving their mouse through the menu without a true intention to open that
* submenu.
*
* To disable the behavior, set it to `0`.
*
* @default 100
*/
openDelay?: number;
};
export type MenuSubTriggerProps = MenuSubTriggerPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuSubTriggerPropsWithoutHTML>;
export type MenuSeparatorPropsWithoutHTML = WithChild;
export type MenuSeparatorProps = MenuSeparatorPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuSeparatorPropsWithoutHTML>;
export type MenuArrowPropsWithoutHTML = ArrowPropsWithoutHTML;
export type MenuArrowProps = ArrowProps;
export type MenuGroupPropsWithoutHTML = WithChild;
export type MenuGroupProps = MenuGroupPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuGroupPropsWithoutHTML>;
export type MenuGroupHeadingPropsWithoutHTML = WithChild;
export type MenuGroupHeadingProps = MenuGroupHeadingPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuGroupHeadingPropsWithoutHTML>;
export type MenuRadioGroupPropsWithoutHTML = WithChild<{
/**
* The value of the selected radio item.
*
* Supports two-way binding with `bind:value`.
*/
value?: string;
/**
* A callback that is fired when the selected radio item changes.
*/
onValueChange?: OnChangeFn<string>;
}>;
export type MenuRadioGroupProps = MenuRadioGroupPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuRadioGroupPropsWithoutHTML>;
export type MenuRadioItemSnippetProps = {
checked: boolean;
};
export type MenuRadioItemPropsWithoutHTML = MenuItemPropsWithoutHTML<MenuRadioItemSnippetProps> & {
/**
* The value of the radio item.
*/
value: string;
/**
* Whether or not the menu item should close when selected.
* @defaultValue true
*/
closeOnSelect?: boolean;
};
export type MenuRadioItemProps = MenuRadioItemPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuRadioItemPropsWithoutHTML>;
export type MenuPortalPropsWithoutHTML = PortalProps;
export type MenuPortalProps = MenuPortalPropsWithoutHTML;

1
web/node_modules/bits-ui/dist/bits/menu/types.js generated vendored Normal file
View File

@@ -0,0 +1 @@
export {};

11
web/node_modules/bits-ui/dist/bits/menu/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import type { Direction } from "../../shared/index.js";
export type CheckedState = boolean | "indeterminate";
export declare const SELECTION_KEYS: string[];
export declare const FIRST_KEYS: string[];
export declare const LAST_KEYS: string[];
export declare const FIRST_LAST_KEYS: string[];
export declare const SUB_OPEN_KEYS: Record<Direction, string[]>;
export declare const SUB_CLOSE_KEYS: Record<Direction, string[]>;
export declare function isIndeterminate(checked?: CheckedState): checked is "indeterminate";
export declare function getCheckedState(checked: CheckedState): "checked" | "unchecked" | "indeterminate";
export declare function isMouseEvent(event: PointerEvent): boolean;

22
web/node_modules/bits-ui/dist/bits/menu/utils.js generated vendored Normal file
View File

@@ -0,0 +1,22 @@
import { kbd } from "../../internal/kbd.js";
export const SELECTION_KEYS = [kbd.ENTER, kbd.SPACE];
export const FIRST_KEYS = [kbd.ARROW_DOWN, kbd.PAGE_UP, kbd.HOME];
export const LAST_KEYS = [kbd.ARROW_UP, kbd.PAGE_DOWN, kbd.END];
export const FIRST_LAST_KEYS = [...FIRST_KEYS, ...LAST_KEYS];
export const SUB_OPEN_KEYS = {
ltr: [...SELECTION_KEYS, kbd.ARROW_RIGHT],
rtl: [...SELECTION_KEYS, kbd.ARROW_LEFT],
};
export const SUB_CLOSE_KEYS = {
ltr: [kbd.ARROW_LEFT],
rtl: [kbd.ARROW_RIGHT],
};
export function isIndeterminate(checked) {
return checked === "indeterminate";
}
export function getCheckedState(checked) {
return isIndeterminate(checked) ? "indeterminate" : checked ? "checked" : "unchecked";
}
export function isMouseEvent(event) {
return event.pointerType === "mouse";
}