This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { TooltipArrowProps } from "../types.js";
|
||||
import FloatingLayerArrow from "../../utilities/floating-layer/components/floating-layer-arrow.svelte";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: TooltipArrowProps = $props();
|
||||
</script>
|
||||
|
||||
<FloatingLayerArrow bind:ref {...restProps} />
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
declare const TooltipArrow: import("svelte").Component<import("../../utilities/arrow/types.js").ArrowProps, {}, "ref">;
|
||||
type TooltipArrow = ReturnType<typeof TooltipArrow>;
|
||||
export default TooltipArrow;
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
<script lang="ts">
|
||||
import { boxWith, mergeProps } from "svelte-toolbelt";
|
||||
import type { TooltipContentStaticProps } from "../types.js";
|
||||
import { TooltipContentState } from "../tooltip.svelte.js";
|
||||
import { createId } from "../../../internal/create-id.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";
|
||||
import { noop } from "../../../internal/noop.js";
|
||||
|
||||
const uid = $props.id();
|
||||
|
||||
let {
|
||||
children,
|
||||
child,
|
||||
id = createId(uid),
|
||||
ref = $bindable(null),
|
||||
onInteractOutside = noop,
|
||||
onEscapeKeydown = noop,
|
||||
forceMount = false,
|
||||
style,
|
||||
...restProps
|
||||
}: TooltipContentStaticProps = $props();
|
||||
|
||||
const contentState = TooltipContentState.create({
|
||||
id: boxWith(() => id),
|
||||
ref: boxWith(
|
||||
() => ref,
|
||||
(v) => (ref = v)
|
||||
),
|
||||
onInteractOutside: boxWith(() => onInteractOutside),
|
||||
onEscapeKeydown: boxWith(() => onEscapeKeydown),
|
||||
});
|
||||
|
||||
const mergedProps = $derived(mergeProps(restProps, contentState.props));
|
||||
</script>
|
||||
|
||||
{#if forceMount}
|
||||
<PopperLayerForceMount
|
||||
{...mergedProps}
|
||||
{...contentState.popperProps}
|
||||
isStatic
|
||||
enabled={contentState.root.opts.open.current}
|
||||
{id}
|
||||
trapFocus={false}
|
||||
loop={false}
|
||||
preventScroll={false}
|
||||
forceMount={true}
|
||||
ref={contentState.opts.ref}
|
||||
tooltip={true}
|
||||
shouldRender={contentState.shouldRender}
|
||||
>
|
||||
{#snippet popper({ props })}
|
||||
{@const finalProps = mergeProps(
|
||||
props,
|
||||
{ style: getFloatingContentCSSVars("tooltip") },
|
||||
{ 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}
|
||||
tooltip={true}
|
||||
isStatic
|
||||
open={contentState.root.opts.open.current}
|
||||
{id}
|
||||
trapFocus={false}
|
||||
loop={false}
|
||||
preventScroll={false}
|
||||
forceMount={false}
|
||||
ref={contentState.opts.ref}
|
||||
shouldRender={contentState.shouldRender}
|
||||
>
|
||||
{#snippet popper({ props })}
|
||||
{@const finalProps = mergeProps(
|
||||
props,
|
||||
{ style: getFloatingContentCSSVars("tooltip") },
|
||||
{ style }
|
||||
)}
|
||||
{#if child}
|
||||
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
||||
{:else}
|
||||
<div {...finalProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</PopperLayer>
|
||||
{/if}
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import type { TooltipContentStaticProps } from "../types.js";
|
||||
declare const TooltipContentStatic: import("svelte").Component<TooltipContentStaticProps, {}, "ref">;
|
||||
type TooltipContentStatic = ReturnType<typeof TooltipContentStatic>;
|
||||
export default TooltipContentStatic;
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
<script lang="ts">
|
||||
import { boxWith, mergeProps } from "svelte-toolbelt";
|
||||
import type { TooltipContentProps } from "../types.js";
|
||||
import { TooltipContentState } from "../tooltip.svelte.js";
|
||||
import { createId } from "../../../internal/create-id.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";
|
||||
import { noop } from "../../../internal/noop.js";
|
||||
|
||||
const uid = $props.id();
|
||||
|
||||
let {
|
||||
children,
|
||||
child,
|
||||
id = createId(uid),
|
||||
ref = $bindable(null),
|
||||
side = "top",
|
||||
sideOffset = 0,
|
||||
align = "center",
|
||||
avoidCollisions = true,
|
||||
arrowPadding = 0,
|
||||
sticky = "partial",
|
||||
strategy,
|
||||
hideWhenDetached = false,
|
||||
collisionPadding = 0,
|
||||
onInteractOutside = noop,
|
||||
onEscapeKeydown = noop,
|
||||
forceMount = false,
|
||||
style,
|
||||
...restProps
|
||||
}: TooltipContentProps = $props();
|
||||
|
||||
const contentState = TooltipContentState.create({
|
||||
id: boxWith(() => id),
|
||||
ref: boxWith(
|
||||
() => ref,
|
||||
(v) => (ref = v)
|
||||
),
|
||||
onInteractOutside: boxWith(() => onInteractOutside),
|
||||
onEscapeKeydown: boxWith(() => onEscapeKeydown),
|
||||
});
|
||||
|
||||
const floatingProps = $derived({
|
||||
side,
|
||||
sideOffset,
|
||||
align,
|
||||
avoidCollisions,
|
||||
arrowPadding,
|
||||
sticky,
|
||||
hideWhenDetached,
|
||||
collisionPadding,
|
||||
strategy,
|
||||
});
|
||||
|
||||
const mergedProps = $derived(mergeProps(restProps, floatingProps, contentState.props));
|
||||
</script>
|
||||
|
||||
{#if forceMount}
|
||||
<PopperLayerForceMount
|
||||
{...mergedProps}
|
||||
{...contentState.popperProps}
|
||||
enabled={contentState.root.opts.open.current}
|
||||
{id}
|
||||
trapFocus={false}
|
||||
loop={false}
|
||||
preventScroll={false}
|
||||
forceMount={true}
|
||||
ref={contentState.opts.ref}
|
||||
tooltip={true}
|
||||
shouldRender={contentState.shouldRender}
|
||||
contentPointerEvents={contentState.root.disableHoverableContent ? "none" : "auto"}
|
||||
>
|
||||
{#snippet popper({ props, wrapperProps })}
|
||||
{@const finalProps = mergeProps(
|
||||
props,
|
||||
{ style: getFloatingContentCSSVars("tooltip") },
|
||||
{ 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}
|
||||
open={contentState.root.opts.open.current}
|
||||
{id}
|
||||
trapFocus={false}
|
||||
loop={false}
|
||||
preventScroll={false}
|
||||
forceMount={false}
|
||||
ref={contentState.opts.ref}
|
||||
tooltip={true}
|
||||
shouldRender={contentState.shouldRender}
|
||||
contentPointerEvents={contentState.root.disableHoverableContent ? "none" : "auto"}
|
||||
>
|
||||
{#snippet popper({ props, wrapperProps })}
|
||||
{@const finalProps = mergeProps(
|
||||
props,
|
||||
{ style: getFloatingContentCSSVars("tooltip") },
|
||||
{ style }
|
||||
)}
|
||||
{#if child}
|
||||
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
||||
{:else}
|
||||
<div {...wrapperProps}>
|
||||
<div {...finalProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</PopperLayer>
|
||||
{/if}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import type { TooltipContentProps } from "../types.js";
|
||||
declare const TooltipContent: import("svelte").Component<TooltipContentProps, {}, "ref">;
|
||||
type TooltipContent = ReturnType<typeof TooltipContent>;
|
||||
export default TooltipContent;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { boxWith } from "svelte-toolbelt";
|
||||
import type { TooltipProviderProps } from "../types.js";
|
||||
import { TooltipProviderState } from "../tooltip.svelte.js";
|
||||
|
||||
let {
|
||||
children,
|
||||
delayDuration = 700,
|
||||
disableCloseOnTriggerClick = false,
|
||||
disableHoverableContent = false,
|
||||
disabled = false,
|
||||
ignoreNonKeyboardFocus = false,
|
||||
skipDelayDuration = 300,
|
||||
}: TooltipProviderProps = $props();
|
||||
|
||||
TooltipProviderState.create({
|
||||
delayDuration: boxWith(() => delayDuration),
|
||||
disableCloseOnTriggerClick: boxWith(() => disableCloseOnTriggerClick),
|
||||
disableHoverableContent: boxWith(() => disableHoverableContent),
|
||||
disabled: boxWith(() => disabled),
|
||||
ignoreNonKeyboardFocus: boxWith(() => ignoreNonKeyboardFocus),
|
||||
skipDelayDuration: boxWith(() => skipDelayDuration),
|
||||
});
|
||||
</script>
|
||||
|
||||
{@render children?.()}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
declare const TooltipProvider: import("svelte").Component<import("../types.js").TooltipProviderPropsWithoutHTML, {}, "">;
|
||||
type TooltipProvider = ReturnType<typeof TooltipProvider>;
|
||||
export default TooltipProvider;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { boxWith, mergeProps } from "svelte-toolbelt";
|
||||
import type { TooltipTriggerProps } from "../types.js";
|
||||
import { TooltipTriggerState } from "../tooltip.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 {
|
||||
children,
|
||||
child,
|
||||
id = createId(uid),
|
||||
disabled = false,
|
||||
type = "button",
|
||||
tabindex = 0,
|
||||
ref = $bindable(null),
|
||||
...restProps
|
||||
}: TooltipTriggerProps = $props();
|
||||
|
||||
const triggerState = TooltipTriggerState.create({
|
||||
id: boxWith(() => id),
|
||||
disabled: boxWith(() => disabled ?? false),
|
||||
tabindex: boxWith(() => tabindex ?? 0),
|
||||
ref: boxWith(
|
||||
() => ref,
|
||||
(v) => (ref = v)
|
||||
),
|
||||
});
|
||||
|
||||
const mergedProps = $derived(mergeProps(restProps, triggerState.props, { type }));
|
||||
</script>
|
||||
|
||||
<FloatingLayerAnchor {id} ref={triggerState.opts.ref} tooltip={true}>
|
||||
{#if child}
|
||||
{@render child({ props: mergedProps })}
|
||||
{:else}
|
||||
<button {...mergedProps}>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
||||
</FloatingLayerAnchor>
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import type { TooltipTriggerProps } from "../types.js";
|
||||
declare const TooltipTrigger: import("svelte").Component<TooltipTriggerProps, {}, "ref">;
|
||||
type TooltipTrigger = ReturnType<typeof TooltipTrigger>;
|
||||
export default TooltipTrigger;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { boxWith } from "svelte-toolbelt";
|
||||
import type { TooltipRootProps } from "../types.js";
|
||||
import { TooltipRootState } from "../tooltip.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,
|
||||
disabled,
|
||||
delayDuration,
|
||||
disableCloseOnTriggerClick,
|
||||
disableHoverableContent,
|
||||
ignoreNonKeyboardFocus,
|
||||
children,
|
||||
}: TooltipRootProps = $props();
|
||||
|
||||
TooltipRootState.create({
|
||||
open: boxWith(
|
||||
() => open,
|
||||
(v) => {
|
||||
open = v;
|
||||
onOpenChange(v);
|
||||
}
|
||||
),
|
||||
delayDuration: boxWith(() => delayDuration),
|
||||
disableCloseOnTriggerClick: boxWith(() => disableCloseOnTriggerClick),
|
||||
disableHoverableContent: boxWith(() => disableHoverableContent),
|
||||
ignoreNonKeyboardFocus: boxWith(() => ignoreNonKeyboardFocus),
|
||||
disabled: boxWith(() => disabled),
|
||||
onOpenChangeComplete: boxWith(() => onOpenChangeComplete),
|
||||
});
|
||||
</script>
|
||||
|
||||
<FloatingLayer tooltip>
|
||||
{@render children?.()}
|
||||
</FloatingLayer>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
declare const Tooltip: import("svelte").Component<import("../types.js").TooltipRootPropsWithoutHTML, {}, "open">;
|
||||
type Tooltip = ReturnType<typeof Tooltip>;
|
||||
export default Tooltip;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export { default as Root } from "./components/tooltip.svelte";
|
||||
export { default as Content } from "./components/tooltip-content.svelte";
|
||||
export { default as ContentStatic } from "./components/tooltip-content-static.svelte";
|
||||
export { default as Trigger } from "./components/tooltip-trigger.svelte";
|
||||
export { default as Arrow } from "./components/tooltip-arrow.svelte";
|
||||
export { default as Provider } from "./components/tooltip-provider.svelte";
|
||||
export { default as Portal } from "../utilities/portal/portal.svelte";
|
||||
export type { TooltipProviderPropsWithoutHTML as ProviderProps, TooltipRootPropsWithoutHTML as RootProps, TooltipArrowProps as ArrowProps, TooltipContentProps as ContentProps, TooltipContentStaticProps as ContentStaticProps, TooltipTriggerProps as TriggerProps, } from "./types.js";
|
||||
export type { PortalProps } from "../utilities/portal/types.js";
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export { default as Root } from "./components/tooltip.svelte";
|
||||
export { default as Content } from "./components/tooltip-content.svelte";
|
||||
export { default as ContentStatic } from "./components/tooltip-content-static.svelte";
|
||||
export { default as Trigger } from "./components/tooltip-trigger.svelte";
|
||||
export { default as Arrow } from "./components/tooltip-arrow.svelte";
|
||||
export { default as Provider } from "./components/tooltip-provider.svelte";
|
||||
export { default as Portal } from "../utilities/portal/portal.svelte";
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * as Tooltip from "./exports.js";
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * as Tooltip from "./exports.js";
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
import { DOMContext, type WritableBoxedValues, type ReadableBoxedValues } from "svelte-toolbelt";
|
||||
import type { OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
|
||||
import type { FocusEventHandler, MouseEventHandler, PointerEventHandler } from "svelte/elements";
|
||||
import { PresenceManager } from "../../internal/presence-manager.svelte.js";
|
||||
export declare const tooltipAttrs: import("../../internal/attrs.js").CreateBitsAttrsReturn<readonly ["content", "trigger"]>;
|
||||
interface TooltipProviderStateOpts extends ReadableBoxedValues<{
|
||||
delayDuration: number;
|
||||
disableHoverableContent: boolean;
|
||||
disableCloseOnTriggerClick: boolean;
|
||||
disabled: boolean;
|
||||
ignoreNonKeyboardFocus: boolean;
|
||||
skipDelayDuration: number;
|
||||
}> {
|
||||
}
|
||||
export declare class TooltipProviderState {
|
||||
#private;
|
||||
static create(opts: TooltipProviderStateOpts): TooltipProviderState;
|
||||
readonly opts: TooltipProviderStateOpts;
|
||||
isOpenDelayed: boolean;
|
||||
isPointerInTransit: import("svelte-toolbelt").WritableBox<boolean>;
|
||||
constructor(opts: TooltipProviderStateOpts);
|
||||
onOpen: (tooltip: TooltipRootState) => void;
|
||||
onClose: (tooltip: TooltipRootState) => void;
|
||||
isTooltipOpen: (tooltip: TooltipRootState) => boolean;
|
||||
}
|
||||
interface TooltipRootStateOpts extends ReadableBoxedValues<{
|
||||
delayDuration: number | undefined;
|
||||
disableHoverableContent: boolean | undefined;
|
||||
disableCloseOnTriggerClick: boolean | undefined;
|
||||
disabled: boolean | undefined;
|
||||
ignoreNonKeyboardFocus: boolean | undefined;
|
||||
onOpenChangeComplete: OnChangeFn<boolean>;
|
||||
}>, WritableBoxedValues<{
|
||||
open: boolean;
|
||||
}> {
|
||||
}
|
||||
export declare class TooltipRootState {
|
||||
#private;
|
||||
static create(opts: TooltipRootStateOpts): TooltipRootState;
|
||||
readonly opts: TooltipRootStateOpts;
|
||||
readonly provider: TooltipProviderState;
|
||||
readonly delayDuration: number;
|
||||
readonly disableHoverableContent: boolean;
|
||||
readonly disableCloseOnTriggerClick: boolean;
|
||||
readonly disabled: boolean;
|
||||
readonly ignoreNonKeyboardFocus: boolean;
|
||||
contentNode: HTMLElement | null;
|
||||
contentPresence: PresenceManager;
|
||||
triggerNode: HTMLElement | null;
|
||||
readonly stateAttr: string;
|
||||
constructor(opts: TooltipRootStateOpts, provider: TooltipProviderState);
|
||||
handleOpen: () => void;
|
||||
handleClose: () => void;
|
||||
onTriggerEnter: () => void;
|
||||
onTriggerLeave: () => void;
|
||||
}
|
||||
interface TooltipTriggerStateOpts extends WithRefOpts, ReadableBoxedValues<{
|
||||
disabled: boolean;
|
||||
tabindex: number;
|
||||
}> {
|
||||
}
|
||||
export declare class TooltipTriggerState {
|
||||
#private;
|
||||
static create(opts: TooltipTriggerStateOpts): TooltipTriggerState;
|
||||
readonly opts: TooltipTriggerStateOpts;
|
||||
readonly root: TooltipRootState;
|
||||
readonly attachment: RefAttachment;
|
||||
domContext: DOMContext;
|
||||
constructor(opts: TooltipTriggerStateOpts, root: TooltipRootState);
|
||||
handlePointerUp: () => void;
|
||||
readonly props: {
|
||||
readonly id: string;
|
||||
readonly "aria-describedby": string | undefined;
|
||||
readonly "data-state": "closed" | "delayed-open" | "instant-open";
|
||||
readonly "data-disabled": "" | undefined;
|
||||
readonly "data-delay-duration": `${number}`;
|
||||
readonly tabindex: number | undefined;
|
||||
readonly disabled: boolean;
|
||||
readonly onpointerup: PointerEventHandler<HTMLElement>;
|
||||
readonly onpointerdown: PointerEventHandler<HTMLElement>;
|
||||
readonly onpointerenter: PointerEventHandler<HTMLElement>;
|
||||
readonly onpointermove: PointerEventHandler<HTMLElement>;
|
||||
readonly onpointerleave: PointerEventHandler<HTMLElement>;
|
||||
readonly onfocus: FocusEventHandler<HTMLElement>;
|
||||
readonly onblur: FocusEventHandler<HTMLElement>;
|
||||
readonly onclick: MouseEventHandler<HTMLElement>;
|
||||
};
|
||||
}
|
||||
interface TooltipContentStateOpts extends WithRefOpts, ReadableBoxedValues<{
|
||||
onInteractOutside: (e: PointerEvent) => void;
|
||||
onEscapeKeydown: (e: KeyboardEvent) => void;
|
||||
}> {
|
||||
}
|
||||
export declare class TooltipContentState {
|
||||
#private;
|
||||
static create(opts: TooltipContentStateOpts): TooltipContentState;
|
||||
readonly opts: TooltipContentStateOpts;
|
||||
readonly root: TooltipRootState;
|
||||
readonly attachment: RefAttachment;
|
||||
constructor(opts: TooltipContentStateOpts, root: TooltipRootState);
|
||||
onInteractOutside: (e: PointerEvent) => void;
|
||||
onEscapeKeydown: (e: KeyboardEvent) => void;
|
||||
onOpenAutoFocus: (e: Event) => void;
|
||||
onCloseAutoFocus: (e: Event) => void;
|
||||
get shouldRender(): boolean;
|
||||
readonly snippetProps: {
|
||||
open: boolean;
|
||||
};
|
||||
readonly props: {
|
||||
readonly id: string;
|
||||
readonly "data-state": "closed" | "delayed-open" | "instant-open";
|
||||
readonly "data-disabled": "" | undefined;
|
||||
readonly style: {
|
||||
readonly outline: "none";
|
||||
};
|
||||
};
|
||||
readonly popperProps: {
|
||||
onInteractOutside: (e: PointerEvent) => void;
|
||||
onEscapeKeydown: (e: KeyboardEvent) => void;
|
||||
onOpenAutoFocus: (e: Event) => void;
|
||||
onCloseAutoFocus: (e: Event) => void;
|
||||
};
|
||||
}
|
||||
export {};
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
import { onMountEffect, attachRef, DOMContext, simpleBox, boxWith, } from "svelte-toolbelt";
|
||||
import { on } from "svelte/events";
|
||||
import { Context, watch } from "runed";
|
||||
import { isElement, isFocusVisible } from "../../internal/is.js";
|
||||
import { createBitsAttrs, boolToEmptyStrOrUndef } from "../../internal/attrs.js";
|
||||
import { TimeoutFn } from "../../internal/timeout-fn.js";
|
||||
import { SafePolygon } from "../../internal/safe-polygon.svelte.js";
|
||||
import { PresenceManager } from "../../internal/presence-manager.svelte.js";
|
||||
export const tooltipAttrs = createBitsAttrs({
|
||||
component: "tooltip",
|
||||
parts: ["content", "trigger"],
|
||||
});
|
||||
const TooltipProviderContext = new Context("Tooltip.Provider");
|
||||
const TooltipRootContext = new Context("Tooltip.Root");
|
||||
export class TooltipProviderState {
|
||||
static create(opts) {
|
||||
return TooltipProviderContext.set(new TooltipProviderState(opts));
|
||||
}
|
||||
opts;
|
||||
isOpenDelayed = $state(true);
|
||||
isPointerInTransit = simpleBox(false);
|
||||
#timerFn;
|
||||
#openTooltip = $state(null);
|
||||
constructor(opts) {
|
||||
this.opts = opts;
|
||||
this.#timerFn = new TimeoutFn(() => {
|
||||
this.isOpenDelayed = true;
|
||||
}, this.opts.skipDelayDuration.current);
|
||||
onMountEffect(() => on(window, "scroll", (e) => {
|
||||
const activeTooltip = this.#openTooltip;
|
||||
if (!activeTooltip)
|
||||
return;
|
||||
const triggerNode = activeTooltip.triggerNode;
|
||||
if (!triggerNode)
|
||||
return;
|
||||
const target = e.target;
|
||||
if (!(target instanceof Element || target instanceof Document))
|
||||
return;
|
||||
if (target.contains(triggerNode)) {
|
||||
activeTooltip.handleClose();
|
||||
}
|
||||
}));
|
||||
}
|
||||
#startTimer = () => {
|
||||
const skipDuration = this.opts.skipDelayDuration.current;
|
||||
if (skipDuration === 0) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
this.#timerFn.start();
|
||||
}
|
||||
};
|
||||
#clearTimer = () => {
|
||||
this.#timerFn.stop();
|
||||
};
|
||||
onOpen = (tooltip) => {
|
||||
if (this.#openTooltip && this.#openTooltip !== tooltip) {
|
||||
this.#openTooltip.handleClose();
|
||||
}
|
||||
this.#clearTimer();
|
||||
this.isOpenDelayed = false;
|
||||
this.#openTooltip = tooltip;
|
||||
};
|
||||
onClose = (tooltip) => {
|
||||
if (this.#openTooltip === tooltip) {
|
||||
this.#openTooltip = null;
|
||||
}
|
||||
this.#startTimer();
|
||||
};
|
||||
isTooltipOpen = (tooltip) => {
|
||||
return this.#openTooltip === tooltip;
|
||||
};
|
||||
}
|
||||
export class TooltipRootState {
|
||||
static create(opts) {
|
||||
return TooltipRootContext.set(new TooltipRootState(opts, TooltipProviderContext.get()));
|
||||
}
|
||||
opts;
|
||||
provider;
|
||||
delayDuration = $derived.by(() => this.opts.delayDuration.current ?? this.provider.opts.delayDuration.current);
|
||||
disableHoverableContent = $derived.by(() => this.opts.disableHoverableContent.current ??
|
||||
this.provider.opts.disableHoverableContent.current);
|
||||
disableCloseOnTriggerClick = $derived.by(() => this.opts.disableCloseOnTriggerClick.current ??
|
||||
this.provider.opts.disableCloseOnTriggerClick.current);
|
||||
disabled = $derived.by(() => this.opts.disabled.current ?? this.provider.opts.disabled.current);
|
||||
ignoreNonKeyboardFocus = $derived.by(() => this.opts.ignoreNonKeyboardFocus.current ??
|
||||
this.provider.opts.ignoreNonKeyboardFocus.current);
|
||||
contentNode = $state(null);
|
||||
contentPresence;
|
||||
triggerNode = $state(null);
|
||||
#wasOpenDelayed = $state(false);
|
||||
#timerFn;
|
||||
stateAttr = $derived.by(() => {
|
||||
if (!this.opts.open.current)
|
||||
return "closed";
|
||||
return this.#wasOpenDelayed ? "delayed-open" : "instant-open";
|
||||
});
|
||||
constructor(opts, provider) {
|
||||
this.opts = opts;
|
||||
this.provider = provider;
|
||||
this.#timerFn = new TimeoutFn(() => {
|
||||
this.#wasOpenDelayed = true;
|
||||
this.opts.open.current = true;
|
||||
}, this.delayDuration ?? 0);
|
||||
this.contentPresence = new PresenceManager({
|
||||
open: this.opts.open,
|
||||
ref: boxWith(() => this.contentNode),
|
||||
onComplete: () => {
|
||||
this.opts.onOpenChangeComplete.current(this.opts.open.current);
|
||||
},
|
||||
});
|
||||
watch(() => this.delayDuration, () => {
|
||||
if (this.delayDuration === undefined)
|
||||
return;
|
||||
this.#timerFn = new TimeoutFn(() => {
|
||||
this.#wasOpenDelayed = true;
|
||||
this.opts.open.current = true;
|
||||
}, this.delayDuration);
|
||||
});
|
||||
watch(() => this.opts.open.current, (isOpen) => {
|
||||
if (isOpen) {
|
||||
this.provider.onOpen(this);
|
||||
}
|
||||
else {
|
||||
this.provider.onClose(this);
|
||||
}
|
||||
}, { lazy: true });
|
||||
}
|
||||
handleOpen = () => {
|
||||
this.#timerFn.stop();
|
||||
this.#wasOpenDelayed = false;
|
||||
this.opts.open.current = true;
|
||||
};
|
||||
handleClose = () => {
|
||||
this.#timerFn.stop();
|
||||
this.opts.open.current = false;
|
||||
};
|
||||
#handleDelayedOpen = () => {
|
||||
this.#timerFn.stop();
|
||||
const shouldSkipDelay = !this.provider.isOpenDelayed;
|
||||
const delayDuration = this.delayDuration ?? 0;
|
||||
// if no delay needed (either skip delay active or delay is 0), open immediately
|
||||
if (shouldSkipDelay || delayDuration === 0) {
|
||||
// set wasOpenDelayed based on whether we actually had a delay
|
||||
this.#wasOpenDelayed = delayDuration > 0 && shouldSkipDelay;
|
||||
this.opts.open.current = true;
|
||||
}
|
||||
else {
|
||||
// use timer for actual delays
|
||||
this.#timerFn.start();
|
||||
}
|
||||
};
|
||||
onTriggerEnter = () => {
|
||||
this.#handleDelayedOpen();
|
||||
};
|
||||
onTriggerLeave = () => {
|
||||
if (this.disableHoverableContent) {
|
||||
this.handleClose();
|
||||
}
|
||||
else {
|
||||
this.#timerFn.stop();
|
||||
}
|
||||
};
|
||||
}
|
||||
export class TooltipTriggerState {
|
||||
static create(opts) {
|
||||
return new TooltipTriggerState(opts, TooltipRootContext.get());
|
||||
}
|
||||
opts;
|
||||
root;
|
||||
attachment;
|
||||
#isPointerDown = simpleBox(false);
|
||||
#hasPointerMoveOpened = $state(false);
|
||||
#isDisabled = $derived.by(() => this.opts.disabled.current || this.root.disabled);
|
||||
domContext;
|
||||
#transitCheckTimeout = null;
|
||||
constructor(opts, root) {
|
||||
this.opts = opts;
|
||||
this.root = root;
|
||||
this.domContext = new DOMContext(opts.ref);
|
||||
this.attachment = attachRef(this.opts.ref, (v) => (this.root.triggerNode = v));
|
||||
}
|
||||
#clearTransitCheck = () => {
|
||||
if (this.#transitCheckTimeout !== null) {
|
||||
clearTimeout(this.#transitCheckTimeout);
|
||||
this.#transitCheckTimeout = null;
|
||||
}
|
||||
};
|
||||
handlePointerUp = () => {
|
||||
this.#isPointerDown.current = false;
|
||||
};
|
||||
#onpointerup = () => {
|
||||
if (this.#isDisabled)
|
||||
return;
|
||||
this.#isPointerDown.current = false;
|
||||
};
|
||||
#onpointerdown = () => {
|
||||
if (this.#isDisabled)
|
||||
return;
|
||||
this.#isPointerDown.current = true;
|
||||
this.domContext.getDocument().addEventListener("pointerup", () => {
|
||||
this.handlePointerUp();
|
||||
}, { once: true });
|
||||
};
|
||||
#onpointerenter = (e) => {
|
||||
if (this.#isDisabled)
|
||||
return;
|
||||
if (e.pointerType === "touch")
|
||||
return;
|
||||
// if in transit, wait briefly to see if user is actually heading to old content or staying here
|
||||
if (this.root.provider.isPointerInTransit.current) {
|
||||
this.#clearTransitCheck();
|
||||
this.#transitCheckTimeout = window.setTimeout(() => {
|
||||
// if still in transit after delay, user is likely staying on this trigger
|
||||
if (this.root.provider.isPointerInTransit.current) {
|
||||
this.root.provider.isPointerInTransit.current = false;
|
||||
this.root.onTriggerEnter();
|
||||
this.#hasPointerMoveOpened = true;
|
||||
}
|
||||
}, 250);
|
||||
return;
|
||||
}
|
||||
this.root.onTriggerEnter();
|
||||
this.#hasPointerMoveOpened = true;
|
||||
};
|
||||
#onpointermove = (e) => {
|
||||
if (this.#isDisabled)
|
||||
return;
|
||||
if (e.pointerType === "touch")
|
||||
return;
|
||||
if (this.#hasPointerMoveOpened)
|
||||
return;
|
||||
// moving within trigger means we're definitely not in transit anymore
|
||||
this.#clearTransitCheck();
|
||||
this.root.provider.isPointerInTransit.current = false;
|
||||
this.root.onTriggerEnter();
|
||||
this.#hasPointerMoveOpened = true;
|
||||
};
|
||||
#onpointerleave = (e) => {
|
||||
if (this.#isDisabled)
|
||||
return;
|
||||
this.#clearTransitCheck();
|
||||
const relatedTarget = e.relatedTarget;
|
||||
if (!this.root.disableHoverableContent &&
|
||||
this.root.opts.open.current &&
|
||||
isElement(relatedTarget) &&
|
||||
this.root.contentNode &&
|
||||
!this.root.contentNode.contains(relatedTarget)) {
|
||||
this.root.handleClose();
|
||||
}
|
||||
else {
|
||||
this.root.onTriggerLeave();
|
||||
}
|
||||
this.#hasPointerMoveOpened = false;
|
||||
};
|
||||
#onfocus = (e) => {
|
||||
if (this.#isPointerDown.current || this.#isDisabled)
|
||||
return;
|
||||
if (this.root.ignoreNonKeyboardFocus && !isFocusVisible(e.currentTarget))
|
||||
return;
|
||||
this.root.handleOpen();
|
||||
};
|
||||
#onblur = () => {
|
||||
if (this.#isDisabled)
|
||||
return;
|
||||
this.root.handleClose();
|
||||
};
|
||||
#onclick = () => {
|
||||
if (this.root.disableCloseOnTriggerClick || this.#isDisabled)
|
||||
return;
|
||||
this.root.handleClose();
|
||||
};
|
||||
props = $derived.by(() => ({
|
||||
id: this.opts.id.current,
|
||||
"aria-describedby": this.root.opts.open.current
|
||||
? this.root.contentNode?.id
|
||||
: undefined,
|
||||
"data-state": this.root.stateAttr,
|
||||
"data-disabled": boolToEmptyStrOrUndef(this.#isDisabled),
|
||||
"data-delay-duration": `${this.root.delayDuration}`,
|
||||
[tooltipAttrs.trigger]: "",
|
||||
tabindex: this.#isDisabled ? undefined : this.opts.tabindex.current,
|
||||
disabled: this.opts.disabled.current,
|
||||
onpointerup: this.#onpointerup,
|
||||
onpointerdown: this.#onpointerdown,
|
||||
onpointerenter: this.#onpointerenter,
|
||||
onpointermove: this.#onpointermove,
|
||||
onpointerleave: this.#onpointerleave,
|
||||
onfocus: this.#onfocus,
|
||||
onblur: this.#onblur,
|
||||
onclick: this.#onclick,
|
||||
...this.attachment,
|
||||
}));
|
||||
}
|
||||
export class TooltipContentState {
|
||||
static create(opts) {
|
||||
return new TooltipContentState(opts, TooltipRootContext.get());
|
||||
}
|
||||
opts;
|
||||
root;
|
||||
attachment;
|
||||
#safePolygon = null;
|
||||
#createSafePolygon = () => {
|
||||
if (this.#safePolygon)
|
||||
return;
|
||||
this.#safePolygon = new SafePolygon({
|
||||
triggerNode: () => this.root.triggerNode,
|
||||
contentNode: () => this.root.contentNode,
|
||||
enabled: () => this.root.opts.open.current && !this.root.disableHoverableContent,
|
||||
onPointerExit: () => {
|
||||
if (this.root.provider.isTooltipOpen(this.root)) {
|
||||
this.root.handleClose();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
constructor(opts, root) {
|
||||
this.opts = opts;
|
||||
this.root = root;
|
||||
this.attachment = attachRef(this.opts.ref, (v) => (this.root.contentNode = v));
|
||||
watch(() => this.root.opts.open.current && !this.root.disableHoverableContent, (shouldTrackSafePolygon) => {
|
||||
if (!shouldTrackSafePolygon)
|
||||
return;
|
||||
this.#createSafePolygon();
|
||||
}, { lazy: true });
|
||||
}
|
||||
onInteractOutside = (e) => {
|
||||
if (isElement(e.target) &&
|
||||
this.root.triggerNode?.contains(e.target) &&
|
||||
this.root.disableCloseOnTriggerClick) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
this.opts.onInteractOutside.current(e);
|
||||
if (e.defaultPrevented)
|
||||
return;
|
||||
this.root.handleClose();
|
||||
};
|
||||
onEscapeKeydown = (e) => {
|
||||
this.opts.onEscapeKeydown.current?.(e);
|
||||
if (e.defaultPrevented)
|
||||
return;
|
||||
this.root.handleClose();
|
||||
};
|
||||
onOpenAutoFocus = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
onCloseAutoFocus = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
get shouldRender() {
|
||||
return this.root.contentPresence.shouldRender;
|
||||
}
|
||||
snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
|
||||
props = $derived.by(() => ({
|
||||
id: this.opts.id.current,
|
||||
"data-state": this.root.stateAttr,
|
||||
"data-disabled": boolToEmptyStrOrUndef(this.root.disabled),
|
||||
style: {
|
||||
outline: "none",
|
||||
},
|
||||
[tooltipAttrs.content]: "",
|
||||
...this.attachment,
|
||||
}));
|
||||
popperProps = {
|
||||
onInteractOutside: this.onInteractOutside,
|
||||
onEscapeKeydown: this.onEscapeKeydown,
|
||||
onOpenAutoFocus: this.onOpenAutoFocus,
|
||||
onCloseAutoFocus: this.onCloseAutoFocus,
|
||||
};
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import type { FloatingLayerContentProps } from "../utilities/floating-layer/types.js";
|
||||
import type { ArrowProps, ArrowPropsWithoutHTML } from "../utilities/arrow/types.js";
|
||||
import type { DismissibleLayerProps } from "../utilities/dismissible-layer/types.js";
|
||||
import type { EscapeLayerProps } from "../utilities/escape-layer/types.js";
|
||||
import type { OnChangeFn, WithChild, WithChildNoChildrenSnippetProps, WithChildren, Without } from "../../internal/types.js";
|
||||
import type { BitsPrimitiveButtonAttributes, BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
|
||||
import type { PortalProps } from "../utilities/portal/types.js";
|
||||
import type { FloatingContentSnippetProps, StaticContentSnippetProps } from "../../shared/types.js";
|
||||
export type TooltipProviderPropsWithoutHTML = WithChildren<{
|
||||
/**
|
||||
* The delay in milliseconds before the tooltip opens.
|
||||
*
|
||||
* @defaultValue 700
|
||||
*/
|
||||
delayDuration?: number;
|
||||
/**
|
||||
* How much time a user has to enter another trigger without
|
||||
* incurring a delay again.
|
||||
* @defaultValue 300
|
||||
*/
|
||||
skipDelayDuration?: number;
|
||||
/**
|
||||
* Prevents tooltip from remaining open when hovering over the content.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
disableHoverableContent?: boolean;
|
||||
/**
|
||||
* When `true`, the tooltip will not close when you click on the trigger.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
disableCloseOnTriggerClick?: boolean;
|
||||
/**
|
||||
* When `true`, the tooltip will be disabled and will not open.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* Prevent the tooltip from opening if the focus did not come using
|
||||
* the keyboard.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
ignoreNonKeyboardFocus?: boolean;
|
||||
}>;
|
||||
export type TooltipProviderProps = TooltipProviderPropsWithoutHTML;
|
||||
export type TooltipRootPropsWithoutHTML = WithChildren<{
|
||||
/**
|
||||
* The open state of the tooltip.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
open?: boolean;
|
||||
/**
|
||||
* A callback that will be called when the tooltip is opened or closed.
|
||||
*/
|
||||
onOpenChange?: OnChangeFn<boolean>;
|
||||
/**
|
||||
* A callback that will be called when the tooltip is opened or closed.
|
||||
*/
|
||||
onOpenChangeComplete?: OnChangeFn<boolean>;
|
||||
/**
|
||||
* The delay in milliseconds before the tooltip opens.
|
||||
*
|
||||
* @defaultValue 700
|
||||
*/
|
||||
delayDuration?: number;
|
||||
/**
|
||||
* Prevents tooltip from remaining open when hovering over the content.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
disableHoverableContent?: boolean;
|
||||
/**
|
||||
* When `true`, the tooltip will not close when you click on the trigger.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
disableCloseOnTriggerClick?: boolean;
|
||||
/**
|
||||
* When `true`, the tooltip will be disabled and will not open.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* Prevent the tooltip from opening if the focus did not come using
|
||||
* the keyboard.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
ignoreNonKeyboardFocus?: boolean;
|
||||
}>;
|
||||
export type TooltipRootProps = TooltipRootPropsWithoutHTML;
|
||||
export type TooltipContentPropsWithoutHTML = WithChildNoChildrenSnippetProps<Omit<FloatingLayerContentProps, "content" | "preventScroll"> & Omit<DismissibleLayerProps, "onInteractOutsideStart"> & EscapeLayerProps & {
|
||||
/**
|
||||
* When `true`, the tooltip will be forced to mount in the DOM.
|
||||
*
|
||||
* Useful for more control over the transition behavior.
|
||||
*/
|
||||
forceMount?: boolean;
|
||||
}, FloatingContentSnippetProps>;
|
||||
export type TooltipContentProps = TooltipContentPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, TooltipContentPropsWithoutHTML>;
|
||||
export type TooltipContentStaticPropsWithoutHTML = WithChildNoChildrenSnippetProps<Pick<FloatingLayerContentProps, "dir"> & Omit<DismissibleLayerProps, "onInteractOutsideStart"> & EscapeLayerProps & {
|
||||
/**
|
||||
* When `true`, the tooltip will be forced to mount in the DOM.
|
||||
*
|
||||
* Useful for more control over the transition behavior.
|
||||
*/
|
||||
forceMount?: boolean;
|
||||
}, StaticContentSnippetProps>;
|
||||
export type TooltipContentStaticProps = TooltipContentStaticPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, TooltipContentStaticPropsWithoutHTML>;
|
||||
export type TooltipArrowPropsWithoutHTML = ArrowPropsWithoutHTML;
|
||||
export type TooltipArrowProps = ArrowProps;
|
||||
export type TooltipPortalPropsWithoutHTML = PortalProps;
|
||||
export type TooltipPortalProps = PortalProps;
|
||||
export type TooltipTriggerPropsWithoutHTML = WithChild<{
|
||||
/**
|
||||
* Whether the tooltip trigger is disabled or not.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
disabled?: boolean | null | undefined;
|
||||
}>;
|
||||
export type TooltipTriggerProps = TooltipTriggerPropsWithoutHTML & Without<BitsPrimitiveButtonAttributes, TooltipTriggerPropsWithoutHTML>;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare const TOOLTIP_OPEN_EVENT = "tooltip.open";
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const TOOLTIP_OPEN_EVENT = "tooltip.open";
|
||||
Reference in New Issue
Block a user