Skip to content

Commit

Permalink
some context menu progress
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed May 11, 2024
1 parent b1f6cf6 commit 4ea3399
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 8 deletions.
97 changes: 97 additions & 0 deletions packages/bits-ui/src/lib/bits/menu/menu.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,103 @@ class DropdownMenuTriggerState {
);
}

type ContextMenuTriggerStateProps = ReadableBoxedValues<{
id: string;
disabled: boolean;
}>;

class ContextMenuTriggerState {
#parentMenu: MenuMenuState;
#disabled: ContextMenuTriggerStateProps["disabled"];
#point = $state({ x: 0, y: 0 });
#virtual = box.with(() => ({
getBoundingClientRect: () => DOMRect.fromRect({ width: 0, height: 0, ...this.#point }),
}));
#longPressTimer = $state<number | null>(null);

constructor(props: ContextMenuTriggerStateProps, parentMenu: MenuMenuState) {
this.#parentMenu = parentMenu;
this.#disabled = props.disabled;
this.#parentMenu.triggerId = props.id;

$effect(() => {
if (this.#disabled.value) {
this.#clearLongPressTimer();
}
});

$effect(() => {
return () => {
this.#clearLongPressTimer();
};
});
}

#clearLongPressTimer() {
if (this.#longPressTimer === null) return;
window.clearTimeout(this.#longPressTimer);
}

#handleOpen = (e: MouseEvent | PointerEvent) => {
this.#point = { x: e.clientX, y: e.clientY };
this.#parentMenu.onOpen();
};

#oncontextmenu = (e: MouseEvent) => {
if (this.#disabled.value) return;
this.#clearLongPressTimer();
this.#handleOpen(e);
e.preventDefault();
};

#onpointerdown = (e: PointerEvent) => {
if (this.#disabled.value || isMouseEvent(e)) return;
this.#clearLongPressTimer();
this.#longPressTimer = window.setTimeout(() => this.#handleOpen(e), 700);
};

#onpointermove = (e: PointerEvent) => {
if (this.#disabled.value || isMouseEvent(e)) return;
this.#clearLongPressTimer();
};

#onpointercancel = (e: PointerEvent) => {
if (this.#disabled.value || isMouseEvent(e)) return;
this.#clearLongPressTimer();
};

#onpointerup = (e: PointerEvent) => {
if (this.#disabled.value || isMouseEvent(e)) return;
this.#clearLongPressTimer();
};

#ariaControls = $derived.by(() => {
if (this.#parentMenu.open.value && this.#parentMenu.contentNode.value)
return this.#parentMenu.contentNode.value.id;
return undefined;
});

props = $derived.by(
() =>
({
id: this.#parentMenu.triggerId.value,
disabled: this.#disabled.value,
"aria-haspopup": "menu",
"aria-expanded": getAriaExpanded(this.#parentMenu.open.value),
"aria-controls": this.#ariaControls,
"data-disabled": getDataDisabled(this.#disabled.value),
"data-state": getDataOpenClosed(this.#parentMenu.open.value),
[TRIGGER_ATTR]: "",
//
onpointerdown: this.#onpointerdown,
onpointermove: this.#onpointermove,
onpointercancel: this.#onpointercancel,
onpointerup: this.#onpointerup,
oncontextmenu: this.#oncontextmenu,
}) as const
);
}

type MenuItemCombinedProps = MenuItemSharedStateProps & MenuItemStateProps;

//
Expand Down
1 change: 1 addition & 0 deletions packages/bits-ui/src/lib/bits/menu/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { EventCallback } from "$lib/internal/events.js";
import { kbd } from "$lib/internal/kbd.js";
import type { Direction } from "$lib/shared/index.js";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from "$lib/internal/index.js";
import { useSize } from "$lib/internal/useSize.svelte.js";
import { useFloating } from "$lib/internal/floating-svelte/useFloating.svelte.js";
import type { UseFloatingReturn } from "$lib/internal/floating-svelte/types.js";
import type { Measurable, UseFloatingReturn } from "$lib/internal/floating-svelte/types.js";
import type { Direction, StyleProperties } from "$lib/shared/index.js";
import { createContext } from "$lib/internal/createContext.js";

Expand All @@ -41,7 +41,7 @@ export type Align = (typeof ALIGN_OPTIONS)[number];
export type Boundary = Element | null;

class FloatingRootState {
anchorNode = undefined as unknown as WritableBox<HTMLElement | null>;
anchorNode = undefined as unknown as WritableBox<Measurable | HTMLElement | null>;

createAnchor(props: FloatingAnchorStateProps) {
return new FloatingAnchorState(props, this);
Expand Down
20 changes: 14 additions & 6 deletions packages/bits-ui/src/lib/internal/floating-svelte/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import type {
ReferenceElement,
Strategy,
} from "@floating-ui/dom";
import type { VirtualElement } from "@floating-ui/core";
import type { WritableBox } from "runed";

type ValueOrGetValue<T> = T | (() => T);

export type ReferenceType = Element | VirtualElement;
export type Measurable = {
getBoundingClientRect: () => DOMRect;
};

export type UseFloatingOptions = {
/**
Expand Down Expand Up @@ -45,7 +46,7 @@ export type UseFloatingOptions = {
/**
* Reference / Anchor element to position the floating element relative to
*/
reference: WritableBox<HTMLElement | null>;
reference: WritableBox<Measurable | HTMLElement | null>;

/**
* Callback to handle mounting/unmounting of the elements.
Expand All @@ -60,29 +61,35 @@ export type UseFloatingOptions = {

export type UseFloatingReturn = {
/**
* The action used to obtain the reference element.
* The reference element to position the floating element relative to.
*/
reference: WritableBox<HTMLElement | null>;
reference: WritableBox<Measurable | HTMLElement | null>;

/**
* The action used to obtain the floating element.
* The floating element to position.
*/
floating: WritableBox<HTMLElement | null>;

/**
* The stateful placement, which can be different from the initial `placement` passed as options.
*/
placement: Readonly<Placement>;

/**
* The type of CSS position property to use.
*/
strategy: Readonly<Strategy>;

/**
* Additional data from middleware.
*/
middlewareData: Readonly<MiddlewareData>;

/**
* The boolean that let you know if the floating element has been positioned.
*/
isPositioned: Readonly<boolean>;

/**
* CSS styles to apply to the floating element to position it.
*/
Expand All @@ -93,6 +100,7 @@ export type UseFloatingReturn = {
transform?: string;
willChange?: string;
}>;

/**
* The function to update floating position manually.
*/
Expand Down

0 comments on commit 4ea3399

Please sign in to comment.