bits-ui 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bits/index.d.ts +1 -0
- package/dist/bits/index.js +1 -0
- package/dist/bits/rating-group/components/rating-group-input.svelte +10 -0
- package/dist/bits/rating-group/components/rating-group-input.svelte.d.ts +18 -0
- package/dist/bits/rating-group/components/rating-group-item.svelte +38 -0
- package/dist/bits/rating-group/components/rating-group-item.svelte.d.ts +4 -0
- package/dist/bits/rating-group/components/rating-group.svelte +80 -0
- package/dist/bits/rating-group/components/rating-group.svelte.d.ts +4 -0
- package/dist/bits/rating-group/exports.d.ts +3 -0
- package/dist/bits/rating-group/exports.js +2 -0
- package/dist/bits/rating-group/index.d.ts +1 -0
- package/dist/bits/rating-group/index.js +1 -0
- package/dist/bits/rating-group/rating-group.svelte.d.ts +114 -0
- package/dist/bits/rating-group/rating-group.svelte.js +295 -0
- package/dist/bits/rating-group/types.d.ts +111 -0
- package/dist/bits/rating-group/types.js +1 -0
- package/dist/bits/slider/helpers.js +33 -2
- package/dist/bits/tooltip/components/tooltip-content-static.svelte +2 -0
- package/dist/bits/tooltip/components/tooltip-content.svelte +2 -0
- package/dist/bits/tooltip/components/tooltip-trigger.svelte +1 -1
- package/dist/bits/tooltip/components/tooltip.svelte +1 -1
- package/dist/bits/tooltip/tooltip.svelte.d.ts +16 -18
- package/dist/bits/tooltip/tooltip.svelte.js +3 -1
- package/dist/bits/utilities/floating-layer/components/floating-layer-anchor.svelte +9 -6
- package/dist/bits/utilities/floating-layer/components/floating-layer-content.svelte +25 -21
- package/dist/bits/utilities/floating-layer/components/floating-layer.svelte +2 -2
- package/dist/bits/utilities/floating-layer/components/floating-layer.svelte.d.ts +1 -0
- package/dist/bits/utilities/floating-layer/types.d.ts +18 -0
- package/dist/bits/utilities/floating-layer/use-floating-layer.svelte.d.ts +3 -3
- package/dist/bits/utilities/floating-layer/use-floating-layer.svelte.js +13 -9
- package/dist/bits/utilities/popper-layer/popper-layer-inner.svelte +2 -0
- package/dist/bits/utilities/popper-layer/types.d.ts +9 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/dist/bits/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export { Popover } from "./popover/index.js";
|
|
|
26
26
|
export { Progress } from "./progress/index.js";
|
|
27
27
|
export { RadioGroup } from "./radio-group/index.js";
|
|
28
28
|
export { RangeCalendar } from "./range-calendar/index.js";
|
|
29
|
+
export { RatingGroup } from "./rating-group/index.js";
|
|
29
30
|
export { ScrollArea } from "./scroll-area/index.js";
|
|
30
31
|
export { Select } from "./select/index.js";
|
|
31
32
|
export { Separator } from "./separator/index.js";
|
package/dist/bits/index.js
CHANGED
|
@@ -26,6 +26,7 @@ export { Popover } from "./popover/index.js";
|
|
|
26
26
|
export { Progress } from "./progress/index.js";
|
|
27
27
|
export { RadioGroup } from "./radio-group/index.js";
|
|
28
28
|
export { RangeCalendar } from "./range-calendar/index.js";
|
|
29
|
+
export { RatingGroup } from "./rating-group/index.js";
|
|
29
30
|
export { ScrollArea } from "./scroll-area/index.js";
|
|
30
31
|
export { Select } from "./select/index.js";
|
|
31
32
|
export { Separator } from "./separator/index.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useRatingGroupHiddenInput } from "../rating-group.svelte.js";
|
|
3
|
+
import HiddenInput from "../../utilities/hidden-input.svelte";
|
|
4
|
+
|
|
5
|
+
const inputState = useRatingGroupHiddenInput();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
{#if inputState.shouldRender}
|
|
9
|
+
<HiddenInput {...inputState.props} />
|
|
10
|
+
{/if}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const RatingGroupInput: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type RatingGroupInput = InstanceType<typeof RatingGroupInput>;
|
|
18
|
+
export default RatingGroupInput;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { box, mergeProps } from "svelte-toolbelt";
|
|
3
|
+
import type { RatingGroupItemProps } from "../types.js";
|
|
4
|
+
import { useRatingGroupItem } from "../rating-group.svelte.js";
|
|
5
|
+
import { createId } from "../../../internal/create-id.js";
|
|
6
|
+
|
|
7
|
+
const uid = $props.id();
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
disabled = false,
|
|
11
|
+
index,
|
|
12
|
+
children,
|
|
13
|
+
child,
|
|
14
|
+
ref = $bindable(null),
|
|
15
|
+
id = createId(uid),
|
|
16
|
+
...restProps
|
|
17
|
+
}: RatingGroupItemProps = $props();
|
|
18
|
+
|
|
19
|
+
const itemState = useRatingGroupItem({
|
|
20
|
+
disabled: box.with(() => Boolean(disabled)),
|
|
21
|
+
index: box.with(() => index),
|
|
22
|
+
id: box.with(() => id),
|
|
23
|
+
ref: box.with(
|
|
24
|
+
() => ref,
|
|
25
|
+
(v) => (ref = v)
|
|
26
|
+
),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const mergedProps = $derived(mergeProps(restProps, itemState.props));
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
{#if child}
|
|
33
|
+
{@render child({ props: mergedProps, ...itemState.snippetProps })}
|
|
34
|
+
{:else}
|
|
35
|
+
<div {...mergedProps}>
|
|
36
|
+
{@render children?.(itemState.snippetProps)}
|
|
37
|
+
</div>
|
|
38
|
+
{/if}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { box, mergeProps } from "svelte-toolbelt";
|
|
3
|
+
import type { RatingGroupRootProps } from "../types.js";
|
|
4
|
+
import { useRatingGroupRoot } from "../rating-group.svelte.js";
|
|
5
|
+
import RatingGroupInput from "./rating-group-input.svelte";
|
|
6
|
+
import { createId } from "../../../internal/create-id.js";
|
|
7
|
+
import { noop } from "../../../internal/noop.js";
|
|
8
|
+
|
|
9
|
+
const uid = $props.id();
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
disabled = false,
|
|
13
|
+
children,
|
|
14
|
+
child,
|
|
15
|
+
value = $bindable(0),
|
|
16
|
+
ref = $bindable(null),
|
|
17
|
+
orientation = "horizontal",
|
|
18
|
+
name = undefined,
|
|
19
|
+
required = false,
|
|
20
|
+
min = 0,
|
|
21
|
+
max = 5,
|
|
22
|
+
allowHalf = false,
|
|
23
|
+
readonly = false,
|
|
24
|
+
id = createId(uid),
|
|
25
|
+
onValueChange = noop,
|
|
26
|
+
"aria-label": ariaLabel,
|
|
27
|
+
"aria-valuetext": ariaValuetextProp,
|
|
28
|
+
hoverPreview = true,
|
|
29
|
+
...restProps
|
|
30
|
+
}: RatingGroupRootProps = $props();
|
|
31
|
+
|
|
32
|
+
if (value < min || value > max) {
|
|
33
|
+
value = Math.max(min, Math.min(max, value));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ariaValuetext: NonNullable<RatingGroupRootProps["aria-valuetext"]> = $derived.by(() => {
|
|
37
|
+
if (ariaValuetextProp) return ariaValuetextProp;
|
|
38
|
+
return (value: number, max: number) => `${value} out of ${max}`;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const rootState = useRatingGroupRoot({
|
|
42
|
+
orientation: box.with(() => orientation),
|
|
43
|
+
disabled: box.with(() => disabled),
|
|
44
|
+
name: box.with(() => name),
|
|
45
|
+
required: box.with(() => required),
|
|
46
|
+
min: box.with(() => min),
|
|
47
|
+
max: box.with(() => max),
|
|
48
|
+
allowHalf: box.with(() => allowHalf),
|
|
49
|
+
readonly: box.with(() => readonly),
|
|
50
|
+
id: box.with(() => id),
|
|
51
|
+
value: box.with(
|
|
52
|
+
() => value,
|
|
53
|
+
(v) => {
|
|
54
|
+
if (v === value) return;
|
|
55
|
+
value = v;
|
|
56
|
+
onValueChange?.(v);
|
|
57
|
+
}
|
|
58
|
+
),
|
|
59
|
+
ref: box.with(
|
|
60
|
+
() => ref,
|
|
61
|
+
(v) => (ref = v)
|
|
62
|
+
),
|
|
63
|
+
ariaValuetext: box.with(() => ariaValuetext),
|
|
64
|
+
hoverPreview: box.with(() => hoverPreview),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const mergedProps = $derived(
|
|
68
|
+
mergeProps(restProps, rootState.props, { "aria-label": ariaLabel })
|
|
69
|
+
);
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
{#if child}
|
|
73
|
+
{@render child({ props: mergedProps, ...rootState.snippetProps })}
|
|
74
|
+
{:else}
|
|
75
|
+
<div {...mergedProps}>
|
|
76
|
+
{@render children?.(rootState.snippetProps)}
|
|
77
|
+
</div>
|
|
78
|
+
{/if}
|
|
79
|
+
|
|
80
|
+
<RatingGroupInput />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as RatingGroup from "./exports.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as RatingGroup from "./exports.js";
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { DOMContext } from "svelte-toolbelt";
|
|
2
|
+
import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
|
|
3
|
+
import type { BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent, WithRefProps } from "../../internal/types.js";
|
|
4
|
+
import type { RatingGroupAriaValuetext, RatingGroupItemState as RatingGroupItemStateType } from "./types.js";
|
|
5
|
+
import type { Orientation } from "../../shared/index.js";
|
|
6
|
+
type RatingGroupRootStateProps = WithRefProps<ReadableBoxedValues<{
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
required: boolean;
|
|
9
|
+
orientation: Orientation;
|
|
10
|
+
name: string | undefined;
|
|
11
|
+
min: number;
|
|
12
|
+
max: number;
|
|
13
|
+
allowHalf: boolean;
|
|
14
|
+
readonly: boolean;
|
|
15
|
+
hoverPreview: boolean;
|
|
16
|
+
ariaValuetext: NonNullable<RatingGroupAriaValuetext>;
|
|
17
|
+
}> & WritableBoxedValues<{
|
|
18
|
+
value: number;
|
|
19
|
+
}>>;
|
|
20
|
+
declare class RatingGroupRootState {
|
|
21
|
+
#private;
|
|
22
|
+
readonly opts: RatingGroupRootStateProps;
|
|
23
|
+
domContext: DOMContext;
|
|
24
|
+
readonly hasValue: boolean;
|
|
25
|
+
readonly valueToUse: number;
|
|
26
|
+
readonly isRTL: boolean;
|
|
27
|
+
readonly ariaValuetext: string;
|
|
28
|
+
readonly items: {
|
|
29
|
+
index: number;
|
|
30
|
+
state: RatingGroupItemStateType;
|
|
31
|
+
}[];
|
|
32
|
+
constructor(opts: RatingGroupRootStateProps);
|
|
33
|
+
isActive(itemIndex: number): boolean;
|
|
34
|
+
isPartial(itemIndex: number): boolean;
|
|
35
|
+
setHoverValue(value: number | null): void;
|
|
36
|
+
setValue(value: number): void;
|
|
37
|
+
calculateRatingFromPointer(itemIndex: number, event: {
|
|
38
|
+
clientX: number;
|
|
39
|
+
clientY: number;
|
|
40
|
+
currentTarget: HTMLElement;
|
|
41
|
+
}): number;
|
|
42
|
+
onpointerleave(): void;
|
|
43
|
+
readonly handlers: Record<string, () => void>;
|
|
44
|
+
onkeydown(e: BitsKeyboardEvent): void;
|
|
45
|
+
readonly snippetProps: {
|
|
46
|
+
items: {
|
|
47
|
+
index: number;
|
|
48
|
+
state: RatingGroupItemStateType;
|
|
49
|
+
}[];
|
|
50
|
+
value: number;
|
|
51
|
+
max: number;
|
|
52
|
+
};
|
|
53
|
+
readonly props: {
|
|
54
|
+
readonly id: string;
|
|
55
|
+
readonly role: "slider";
|
|
56
|
+
readonly "aria-valuenow": number;
|
|
57
|
+
readonly "aria-valuemin": number;
|
|
58
|
+
readonly "aria-valuemax": number;
|
|
59
|
+
readonly "aria-valuetext": string;
|
|
60
|
+
readonly "aria-orientation": Orientation;
|
|
61
|
+
readonly "aria-required": "true" | "false";
|
|
62
|
+
readonly "aria-disabled": "true" | undefined;
|
|
63
|
+
readonly "aria-label": "Rating";
|
|
64
|
+
readonly "data-disabled": "" | undefined;
|
|
65
|
+
readonly "data-readonly": "" | undefined;
|
|
66
|
+
readonly "data-orientation": Orientation;
|
|
67
|
+
readonly tabindex: 0 | -1;
|
|
68
|
+
readonly "data-rating-group-root": "";
|
|
69
|
+
readonly onkeydown: (e: BitsKeyboardEvent) => void;
|
|
70
|
+
readonly onpointerleave: () => void;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
type RatingGroupItemStateProps = WithRefProps<ReadableBoxedValues<{
|
|
74
|
+
disabled: boolean;
|
|
75
|
+
index: number;
|
|
76
|
+
}>>;
|
|
77
|
+
declare class RatingGroupItemState {
|
|
78
|
+
#private;
|
|
79
|
+
readonly opts: RatingGroupItemStateProps;
|
|
80
|
+
readonly root: RatingGroupRootState;
|
|
81
|
+
constructor(opts: RatingGroupItemStateProps, root: RatingGroupRootState);
|
|
82
|
+
onclick(e: BitsMouseEvent): void;
|
|
83
|
+
onpointermove(e: BitsPointerEvent): void;
|
|
84
|
+
readonly snippetProps: {
|
|
85
|
+
readonly state: RatingGroupItemStateType;
|
|
86
|
+
};
|
|
87
|
+
readonly props: {
|
|
88
|
+
readonly id: string;
|
|
89
|
+
readonly role: "presentation";
|
|
90
|
+
readonly "data-value": number;
|
|
91
|
+
readonly "data-orientation": Orientation;
|
|
92
|
+
readonly "data-disabled": "" | undefined;
|
|
93
|
+
readonly "data-readonly": "" | undefined;
|
|
94
|
+
readonly "data-state": RatingGroupItemStateType;
|
|
95
|
+
readonly "data-rating-group-item": "";
|
|
96
|
+
readonly onclick: (e: BitsMouseEvent) => void;
|
|
97
|
+
readonly onpointermove: (e: BitsPointerEvent) => void;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
declare class RatingGroupHiddenInputState {
|
|
101
|
+
readonly root: RatingGroupRootState;
|
|
102
|
+
readonly shouldRender: boolean;
|
|
103
|
+
readonly props: {
|
|
104
|
+
readonly name: string | undefined;
|
|
105
|
+
readonly value: number;
|
|
106
|
+
readonly required: boolean;
|
|
107
|
+
readonly disabled: boolean;
|
|
108
|
+
};
|
|
109
|
+
constructor(root: RatingGroupRootState);
|
|
110
|
+
}
|
|
111
|
+
export declare function useRatingGroupRoot(props: RatingGroupRootStateProps): RatingGroupRootState;
|
|
112
|
+
export declare function useRatingGroupItem(props: RatingGroupItemStateProps): RatingGroupItemState;
|
|
113
|
+
export declare function useRatingGroupHiddenInput(): RatingGroupHiddenInputState;
|
|
114
|
+
export {};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { attachRef, DOMContext } from "svelte-toolbelt";
|
|
2
|
+
import { Context } from "runed";
|
|
3
|
+
import { getAriaRequired, getDataDisabled } from "../../internal/attrs.js";
|
|
4
|
+
import { kbd } from "../../internal/kbd.js";
|
|
5
|
+
const RATING_GROUP_ROOT_ATTR = "data-rating-group-root";
|
|
6
|
+
const RATING_GROUP_ITEM_ATTR = "data-rating-group-item";
|
|
7
|
+
class RatingGroupRootState {
|
|
8
|
+
opts;
|
|
9
|
+
#hoverValue = $state(null);
|
|
10
|
+
#keySequence = $state("");
|
|
11
|
+
#keySequenceTimeout = null;
|
|
12
|
+
domContext;
|
|
13
|
+
hasValue = $derived.by(() => this.opts.value.current > 0);
|
|
14
|
+
valueToUse = $derived.by(() => this.#hoverValue ?? this.opts.value.current);
|
|
15
|
+
isRTL = $derived.by(() => {
|
|
16
|
+
const element = this.opts.ref.current;
|
|
17
|
+
if (!element)
|
|
18
|
+
return false;
|
|
19
|
+
const style = getComputedStyle(element);
|
|
20
|
+
return style.direction === "rtl";
|
|
21
|
+
});
|
|
22
|
+
ariaValuetext = $derived.by(() => {
|
|
23
|
+
return typeof this.opts.ariaValuetext.current === "function"
|
|
24
|
+
? this.opts.ariaValuetext.current(this.opts.value.current, this.opts.max.current)
|
|
25
|
+
: this.opts.ariaValuetext.current;
|
|
26
|
+
});
|
|
27
|
+
items = $derived.by(() => {
|
|
28
|
+
const { max, allowHalf } = this.opts;
|
|
29
|
+
const value = this.valueToUse;
|
|
30
|
+
return Array.from({ length: max.current }, (_, i) => {
|
|
31
|
+
const itemValue = i + 1;
|
|
32
|
+
const halfValue = itemValue - 0.5;
|
|
33
|
+
const state = value >= itemValue
|
|
34
|
+
? "active"
|
|
35
|
+
: allowHalf.current && value >= halfValue
|
|
36
|
+
? "partial"
|
|
37
|
+
: "inactive";
|
|
38
|
+
return { index: i, state };
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
constructor(opts) {
|
|
42
|
+
this.opts = opts;
|
|
43
|
+
this.onkeydown = this.onkeydown.bind(this);
|
|
44
|
+
this.onpointerleave = this.onpointerleave.bind(this);
|
|
45
|
+
this.domContext = new DOMContext(this.opts.ref);
|
|
46
|
+
}
|
|
47
|
+
isActive(itemIndex) {
|
|
48
|
+
return this.valueToUse >= itemIndex + 1;
|
|
49
|
+
}
|
|
50
|
+
isPartial(itemIndex) {
|
|
51
|
+
if (!this.opts.allowHalf.current)
|
|
52
|
+
return false;
|
|
53
|
+
const itemValue = itemIndex + 1;
|
|
54
|
+
return this.valueToUse >= itemValue - 0.5 && this.valueToUse < itemValue;
|
|
55
|
+
}
|
|
56
|
+
setHoverValue(value) {
|
|
57
|
+
if (this.opts.readonly.current ||
|
|
58
|
+
this.opts.disabled.current ||
|
|
59
|
+
!this.opts.hoverPreview.current)
|
|
60
|
+
return;
|
|
61
|
+
this.#hoverValue =
|
|
62
|
+
value === null
|
|
63
|
+
? null
|
|
64
|
+
: Math.max(this.opts.min.current, Math.min(this.opts.max.current, value));
|
|
65
|
+
}
|
|
66
|
+
setValue(value) {
|
|
67
|
+
if (this.opts.readonly.current || this.opts.disabled.current)
|
|
68
|
+
return;
|
|
69
|
+
this.opts.value.current = Math.max(this.opts.min.current, Math.min(this.opts.max.current, value));
|
|
70
|
+
}
|
|
71
|
+
calculateRatingFromPointer(itemIndex, event) {
|
|
72
|
+
const ratingValue = itemIndex + 1;
|
|
73
|
+
if (!this.opts.allowHalf.current)
|
|
74
|
+
return ratingValue;
|
|
75
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
76
|
+
const style = getComputedStyle(event.currentTarget);
|
|
77
|
+
const isHorizontal = this.opts.orientation.current === "horizontal";
|
|
78
|
+
const position = isHorizontal
|
|
79
|
+
? (event.clientX - rect.left) / rect.width
|
|
80
|
+
: (event.clientY - rect.top) / rect.height;
|
|
81
|
+
const normalizedPosition = style.direction === "rtl" ? 1 - position : position;
|
|
82
|
+
return normalizedPosition < 0.5 ? ratingValue - 0.5 : ratingValue;
|
|
83
|
+
}
|
|
84
|
+
onpointerleave() {
|
|
85
|
+
this.setHoverValue(null);
|
|
86
|
+
}
|
|
87
|
+
handlers = {
|
|
88
|
+
[kbd.ARROW_UP]: () => this.#adjustValue(this.opts.allowHalf.current ? 0.5 : 1),
|
|
89
|
+
[kbd.ARROW_RIGHT]: () => {
|
|
90
|
+
const increment = this.opts.allowHalf.current ? 0.5 : 1;
|
|
91
|
+
// in RTL mode, right arrow should decrement
|
|
92
|
+
this.#adjustValue(this.isRTL ? -increment : increment);
|
|
93
|
+
},
|
|
94
|
+
[kbd.ARROW_DOWN]: () => this.#adjustValue(this.opts.allowHalf.current ? -0.5 : -1),
|
|
95
|
+
[kbd.ARROW_LEFT]: () => {
|
|
96
|
+
const increment = this.opts.allowHalf.current ? 0.5 : 1;
|
|
97
|
+
// in RTL mode, left arrow should increment
|
|
98
|
+
this.#adjustValue(this.isRTL ? increment : -increment);
|
|
99
|
+
},
|
|
100
|
+
[kbd.HOME]: () => this.setValue(this.opts.min.current),
|
|
101
|
+
[kbd.END]: () => this.setValue(this.opts.max.current),
|
|
102
|
+
[kbd.PAGE_UP]: () => this.#adjustValue(1),
|
|
103
|
+
[kbd.PAGE_DOWN]: () => this.#adjustValue(-1),
|
|
104
|
+
};
|
|
105
|
+
onkeydown(e) {
|
|
106
|
+
if (this.opts.disabled.current || this.opts.readonly.current)
|
|
107
|
+
return;
|
|
108
|
+
if (this.handlers[e.key]) {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
this.#clearKeySequence();
|
|
111
|
+
this.handlers[e.key]?.();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (this.opts.allowHalf.current && this.#handleDecimalInput(e))
|
|
115
|
+
return;
|
|
116
|
+
// handle direct number input
|
|
117
|
+
const num = parseInt(e.key || "");
|
|
118
|
+
if (!isNaN(num) && e.key) {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
if (num >= this.opts.min.current && num <= this.opts.max.current) {
|
|
121
|
+
this.setValue(num);
|
|
122
|
+
if (this.opts.allowHalf.current) {
|
|
123
|
+
this.#startDecimalListening(num);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.#clearKeySequence();
|
|
129
|
+
}
|
|
130
|
+
#adjustValue(delta) {
|
|
131
|
+
this.setValue(this.opts.value.current + delta);
|
|
132
|
+
}
|
|
133
|
+
#handleDecimalInput(e) {
|
|
134
|
+
if (!e.key)
|
|
135
|
+
return false;
|
|
136
|
+
if (e.key === ".") {
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
this.#keySequence += e.key;
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
if (e.key === "5" && this.#keySequence.match(/^\d+\.$/)) {
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
this.#keySequence += e.key;
|
|
144
|
+
const match = this.#keySequence.match(/^(\d+)\.5$/);
|
|
145
|
+
if (match?.[1]) {
|
|
146
|
+
const value = parseFloat(this.#keySequence);
|
|
147
|
+
if (value >= this.opts.min.current && value <= this.opts.max.current) {
|
|
148
|
+
this.setValue(value);
|
|
149
|
+
this.#clearKeySequence();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
#startDecimalListening(baseValue) {
|
|
157
|
+
this.#keySequence = baseValue.toString();
|
|
158
|
+
if (this.#keySequenceTimeout) {
|
|
159
|
+
this.domContext.clearTimeout(this.#keySequenceTimeout);
|
|
160
|
+
}
|
|
161
|
+
this.#keySequenceTimeout = this.domContext.setTimeout(() => this.#clearKeySequence(), 1000);
|
|
162
|
+
}
|
|
163
|
+
#clearKeySequence() {
|
|
164
|
+
this.#keySequence = "";
|
|
165
|
+
if (this.#keySequenceTimeout) {
|
|
166
|
+
this.domContext.clearTimeout(this.#keySequenceTimeout);
|
|
167
|
+
this.#keySequenceTimeout = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
snippetProps = $derived.by(() => ({
|
|
171
|
+
items: this.items,
|
|
172
|
+
value: this.opts.value.current,
|
|
173
|
+
max: this.opts.max.current,
|
|
174
|
+
}));
|
|
175
|
+
props = $derived.by(() => {
|
|
176
|
+
return {
|
|
177
|
+
id: this.opts.id.current,
|
|
178
|
+
role: "slider",
|
|
179
|
+
"aria-valuenow": this.opts.value.current,
|
|
180
|
+
"aria-valuemin": this.opts.min.current,
|
|
181
|
+
"aria-valuemax": this.opts.max.current,
|
|
182
|
+
"aria-valuetext": this.ariaValuetext,
|
|
183
|
+
"aria-orientation": this.opts.orientation.current,
|
|
184
|
+
"aria-required": getAriaRequired(this.opts.required.current),
|
|
185
|
+
"aria-disabled": this.opts.disabled.current ? "true" : undefined,
|
|
186
|
+
"aria-label": "Rating",
|
|
187
|
+
"data-disabled": getDataDisabled(this.opts.disabled.current),
|
|
188
|
+
"data-readonly": this.opts.readonly.current ? "" : undefined,
|
|
189
|
+
"data-orientation": this.opts.orientation.current,
|
|
190
|
+
tabindex: this.opts.disabled.current ? -1 : 0,
|
|
191
|
+
[RATING_GROUP_ROOT_ATTR]: "",
|
|
192
|
+
onkeydown: this.onkeydown,
|
|
193
|
+
onpointerleave: this.onpointerleave,
|
|
194
|
+
...attachRef(this.opts.ref),
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
class RatingGroupItemState {
|
|
199
|
+
opts;
|
|
200
|
+
root;
|
|
201
|
+
#isDisabled = $derived.by(() => this.opts.disabled.current || this.root.opts.disabled.current);
|
|
202
|
+
#isActive = $derived.by(() => this.root.isActive(this.opts.index.current));
|
|
203
|
+
#isPartial = $derived.by(() => this.root.isPartial(this.opts.index.current));
|
|
204
|
+
#state = $derived.by(() => {
|
|
205
|
+
if (this.#isActive)
|
|
206
|
+
return "active";
|
|
207
|
+
if (this.#isPartial)
|
|
208
|
+
return "partial";
|
|
209
|
+
return "inactive";
|
|
210
|
+
});
|
|
211
|
+
constructor(opts, root) {
|
|
212
|
+
this.opts = opts;
|
|
213
|
+
this.root = root;
|
|
214
|
+
this.onclick = this.onclick.bind(this);
|
|
215
|
+
this.onpointermove = this.onpointermove.bind(this);
|
|
216
|
+
}
|
|
217
|
+
onclick(e) {
|
|
218
|
+
if (this.#isDisabled || this.root.opts.readonly.current)
|
|
219
|
+
return;
|
|
220
|
+
// handle clearing when clicking on first item (index 0) that's already
|
|
221
|
+
// active and min is 0
|
|
222
|
+
if (this.opts.index.current === 0 &&
|
|
223
|
+
this.root.opts.min.current === 0 &&
|
|
224
|
+
this.root.opts.value.current > 0) {
|
|
225
|
+
const newValue = this.root.calculateRatingFromPointer(this.opts.index.current, e);
|
|
226
|
+
const currentValue = this.root.opts.value.current;
|
|
227
|
+
// only clear if the calculated rating exactly matches current value
|
|
228
|
+
if (newValue === currentValue) {
|
|
229
|
+
this.root.setValue(0);
|
|
230
|
+
if (this.root.opts.ref.current) {
|
|
231
|
+
this.root.opts.ref.current.focus();
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const newValue = this.root.calculateRatingFromPointer(this.opts.index.current, e);
|
|
237
|
+
this.root.setValue(newValue);
|
|
238
|
+
if (this.root.opts.ref.current) {
|
|
239
|
+
this.root.opts.ref.current.focus();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
onpointermove(e) {
|
|
243
|
+
if (this.#isDisabled ||
|
|
244
|
+
this.root.opts.readonly.current ||
|
|
245
|
+
!this.root.opts.hoverPreview.current)
|
|
246
|
+
return;
|
|
247
|
+
// skip hover preview for touch devices
|
|
248
|
+
if (e.pointerType === "touch")
|
|
249
|
+
return;
|
|
250
|
+
const hoverValue = this.root.calculateRatingFromPointer(this.opts.index.current, e);
|
|
251
|
+
this.root.setHoverValue(hoverValue);
|
|
252
|
+
}
|
|
253
|
+
snippetProps = $derived.by(() => {
|
|
254
|
+
return {
|
|
255
|
+
state: this.#state,
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
props = $derived.by(() => ({
|
|
259
|
+
id: this.opts.id.current,
|
|
260
|
+
role: "presentation",
|
|
261
|
+
"data-value": this.opts.index.current + 1,
|
|
262
|
+
"data-orientation": this.root.opts.orientation.current,
|
|
263
|
+
"data-disabled": getDataDisabled(this.#isDisabled),
|
|
264
|
+
"data-readonly": this.root.opts.readonly.current ? "" : undefined,
|
|
265
|
+
"data-state": this.#state,
|
|
266
|
+
[RATING_GROUP_ITEM_ATTR]: "",
|
|
267
|
+
//
|
|
268
|
+
onclick: this.onclick,
|
|
269
|
+
onpointermove: this.onpointermove,
|
|
270
|
+
...attachRef(this.opts.ref),
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
class RatingGroupHiddenInputState {
|
|
274
|
+
root;
|
|
275
|
+
shouldRender = $derived.by(() => this.root.opts.name.current !== undefined);
|
|
276
|
+
props = $derived.by(() => ({
|
|
277
|
+
name: this.root.opts.name.current,
|
|
278
|
+
value: this.root.opts.value.current,
|
|
279
|
+
required: this.root.opts.required.current,
|
|
280
|
+
disabled: this.root.opts.disabled.current,
|
|
281
|
+
}));
|
|
282
|
+
constructor(root) {
|
|
283
|
+
this.root = root;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const RatingGroupRootContext = new Context("RatingGroup.Root");
|
|
287
|
+
export function useRatingGroupRoot(props) {
|
|
288
|
+
return RatingGroupRootContext.set(new RatingGroupRootState(props));
|
|
289
|
+
}
|
|
290
|
+
export function useRatingGroupItem(props) {
|
|
291
|
+
return new RatingGroupItemState(props, RatingGroupRootContext.get());
|
|
292
|
+
}
|
|
293
|
+
export function useRatingGroupHiddenInput() {
|
|
294
|
+
return new RatingGroupHiddenInputState(RatingGroupRootContext.get());
|
|
295
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { OnChangeFn, WithChild, Without } from "../../internal/types.js";
|
|
2
|
+
import type { Orientation } from "../../index.js";
|
|
3
|
+
import type { BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
|
|
4
|
+
export type RatingGroupItemState = "active" | "partial" | "inactive";
|
|
5
|
+
export type RatingGroupItemData = {
|
|
6
|
+
index: number;
|
|
7
|
+
state: RatingGroupItemState;
|
|
8
|
+
};
|
|
9
|
+
export type RatingGroupRootSnippetProps = {
|
|
10
|
+
items: RatingGroupItemData[];
|
|
11
|
+
value: number;
|
|
12
|
+
max: number;
|
|
13
|
+
};
|
|
14
|
+
export type RatingGroupAriaValuetext = BitsPrimitiveDivAttributes["aria-valuetext"] | ((value: number, max: number) => string);
|
|
15
|
+
export type RatingGroupRootPropsWithoutHTML = WithChild<{
|
|
16
|
+
/**
|
|
17
|
+
* The orientation of the rating group. Used to determine
|
|
18
|
+
* how keyboard interactions work.
|
|
19
|
+
*
|
|
20
|
+
* @default "horizontal"
|
|
21
|
+
*/
|
|
22
|
+
orientation?: Orientation;
|
|
23
|
+
/**
|
|
24
|
+
* The current rating value.
|
|
25
|
+
*
|
|
26
|
+
* @default 0
|
|
27
|
+
*/
|
|
28
|
+
value?: number;
|
|
29
|
+
/**
|
|
30
|
+
* The callback to call when the rating value changes.
|
|
31
|
+
*/
|
|
32
|
+
onValueChange?: OnChangeFn<number>;
|
|
33
|
+
/**
|
|
34
|
+
* The name to apply to the rating group's input element for
|
|
35
|
+
* form submission. If not provided, a hidden input will not
|
|
36
|
+
* be rendered and the rating group will not be part of a form.
|
|
37
|
+
*
|
|
38
|
+
* @default undefined
|
|
39
|
+
*/
|
|
40
|
+
name?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the rating group is disabled.
|
|
43
|
+
*
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
disabled?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the rating group is required for form submission.
|
|
49
|
+
* If `true`, ensure you provide a `name` prop so the hidden
|
|
50
|
+
* input is rendered.
|
|
51
|
+
*
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
required?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The minimum rating value.
|
|
57
|
+
*
|
|
58
|
+
* @default 0
|
|
59
|
+
*/
|
|
60
|
+
min?: number;
|
|
61
|
+
/**
|
|
62
|
+
* The maximum rating value (number of items).
|
|
63
|
+
*
|
|
64
|
+
* @default 5
|
|
65
|
+
*/
|
|
66
|
+
max?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Whether to allow half-star ratings.
|
|
69
|
+
*
|
|
70
|
+
* @default false
|
|
71
|
+
*/
|
|
72
|
+
allowHalf?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Whether the rating group is readonly.
|
|
75
|
+
*
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
readonly?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Whether to show a preview when hovering over rating items.
|
|
81
|
+
* Touch events are ignored to prevent accidental previews.
|
|
82
|
+
*
|
|
83
|
+
* @default true
|
|
84
|
+
*/
|
|
85
|
+
hoverPreview?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* An extended `aria-valuetext` property to use for the rating group.
|
|
88
|
+
* Can either be a string, or a function that receives the current value
|
|
89
|
+
* and max value and returns a string.
|
|
90
|
+
*
|
|
91
|
+
* @default ((value: number, max: number) => `${value} out of ${max}`)
|
|
92
|
+
*/
|
|
93
|
+
"aria-valuetext"?: RatingGroupAriaValuetext;
|
|
94
|
+
}, RatingGroupRootSnippetProps>;
|
|
95
|
+
export type RatingGroupRootProps = RatingGroupRootPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, RatingGroupRootPropsWithoutHTML>;
|
|
96
|
+
export type RatingGroupItemSnippetProps = {
|
|
97
|
+
state: RatingGroupItemState;
|
|
98
|
+
};
|
|
99
|
+
export type RatingGroupItemPropsWithoutHTML = WithChild<{
|
|
100
|
+
/**
|
|
101
|
+
* The index of the rating item (0-based index).
|
|
102
|
+
*/
|
|
103
|
+
index: number;
|
|
104
|
+
/**
|
|
105
|
+
* Whether the rating item is disabled.
|
|
106
|
+
*
|
|
107
|
+
* @default false
|
|
108
|
+
*/
|
|
109
|
+
disabled?: boolean | null | undefined;
|
|
110
|
+
}, RatingGroupItemSnippetProps>;
|
|
111
|
+
export type RatingGroupItemProps = RatingGroupItemPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, RatingGroupItemPropsWithoutHTML>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -132,6 +132,29 @@ export function getThumbLabelStyles(direction, thumbPosition, labelPosition = "t
|
|
|
132
132
|
}
|
|
133
133
|
return style;
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Gets the number of decimal places in a number
|
|
137
|
+
*/
|
|
138
|
+
function getDecimalPlaces(num) {
|
|
139
|
+
if (Math.floor(num) === num)
|
|
140
|
+
return 0;
|
|
141
|
+
const str = num.toString();
|
|
142
|
+
if (str.indexOf(".") !== -1 && str.indexOf("e-") === -1) {
|
|
143
|
+
return str.split(".")[1].length;
|
|
144
|
+
}
|
|
145
|
+
else if (str.indexOf("e-") !== -1) {
|
|
146
|
+
const parts = str.split("e-");
|
|
147
|
+
return parseInt(parts[1], 10);
|
|
148
|
+
}
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Rounds a number to the specified number of decimal places
|
|
153
|
+
*/
|
|
154
|
+
function roundToPrecision(num, precision) {
|
|
155
|
+
const factor = Math.pow(10, precision);
|
|
156
|
+
return Math.round(num * factor) / factor;
|
|
157
|
+
}
|
|
135
158
|
/**
|
|
136
159
|
* Normalizes step to always be a sorted array of valid values within min/max range
|
|
137
160
|
*/
|
|
@@ -140,13 +163,21 @@ export function normalizeSteps(step, min, max) {
|
|
|
140
163
|
// generate regular steps - match original behavior exactly
|
|
141
164
|
const difference = max - min;
|
|
142
165
|
let count = Math.ceil(difference / step);
|
|
143
|
-
|
|
166
|
+
// Get precision from step to avoid floating point errors
|
|
167
|
+
const precision = getDecimalPlaces(step);
|
|
168
|
+
// Check if difference is divisible by step using integer arithmetic to avoid floating point errors
|
|
169
|
+
const factor = Math.pow(10, precision);
|
|
170
|
+
const intDifference = Math.round(difference * factor);
|
|
171
|
+
const intStep = Math.round(step * factor);
|
|
172
|
+
if (intDifference % intStep === 0) {
|
|
144
173
|
count++;
|
|
145
174
|
}
|
|
146
175
|
const steps = [];
|
|
147
176
|
for (let i = 0; i < count; i++) {
|
|
148
177
|
const value = min + i * step;
|
|
149
|
-
|
|
178
|
+
// Round to the precision of the step to avoid floating point errors
|
|
179
|
+
const roundedValue = roundToPrecision(value, precision);
|
|
180
|
+
steps.push(roundedValue);
|
|
150
181
|
}
|
|
151
182
|
return steps;
|
|
152
183
|
}
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
preventScroll={false}
|
|
47
47
|
forceMount={true}
|
|
48
48
|
ref={contentState.opts.ref}
|
|
49
|
+
tooltip={true}
|
|
49
50
|
>
|
|
50
51
|
{#snippet popper({ props })}
|
|
51
52
|
{@const mergedProps = mergeProps(props, {
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
<PopperLayer
|
|
65
66
|
{...mergedProps}
|
|
66
67
|
{...contentState.popperProps}
|
|
68
|
+
tooltip={true}
|
|
67
69
|
isStatic
|
|
68
70
|
present={contentState.root.opts.open.current}
|
|
69
71
|
{id}
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
preventScroll={false}
|
|
65
65
|
forceMount={true}
|
|
66
66
|
ref={contentState.opts.ref}
|
|
67
|
+
tooltip={true}
|
|
67
68
|
>
|
|
68
69
|
{#snippet popper({ props, wrapperProps })}
|
|
69
70
|
{@const mergedProps = mergeProps(props, {
|
|
@@ -91,6 +92,7 @@
|
|
|
91
92
|
preventScroll={false}
|
|
92
93
|
forceMount={false}
|
|
93
94
|
ref={contentState.opts.ref}
|
|
95
|
+
tooltip={true}
|
|
94
96
|
>
|
|
95
97
|
{#snippet popper({ props, wrapperProps })}
|
|
96
98
|
{@const mergedProps = mergeProps(props, {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
const mergedProps = $derived(mergeProps(restProps, triggerState.props, { type }));
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
|
-
<FloatingLayerAnchor {id} ref={triggerState.opts.ref}>
|
|
32
|
+
<FloatingLayerAnchor {id} ref={triggerState.opts.ref} tooltip={true}>
|
|
33
33
|
{#if child}
|
|
34
34
|
{@render child({ props: mergedProps })}
|
|
35
35
|
{:else}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DOMContext } from "svelte-toolbelt";
|
|
2
2
|
import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
|
|
3
3
|
import type { WithRefProps } from "../../internal/types.js";
|
|
4
|
-
import type { PointerEventHandler } from "svelte/elements";
|
|
4
|
+
import type { FocusEventHandler, MouseEventHandler, PointerEventHandler } from "svelte/elements";
|
|
5
5
|
type TooltipProviderStateProps = ReadableBoxedValues<{
|
|
6
6
|
delayDuration: number;
|
|
7
7
|
disableHoverableContent: boolean;
|
|
@@ -58,23 +58,21 @@ declare class TooltipTriggerState {
|
|
|
58
58
|
constructor(opts: TooltipTriggerStateProps, root: TooltipRootState);
|
|
59
59
|
handlePointerUp: () => void;
|
|
60
60
|
props: {
|
|
61
|
-
id: string;
|
|
62
|
-
"aria-describedby": string | undefined;
|
|
63
|
-
"data-state": string;
|
|
64
|
-
"data-disabled": "" | undefined;
|
|
65
|
-
"data-delay-duration":
|
|
66
|
-
"data-tooltip-trigger":
|
|
67
|
-
tabindex:
|
|
68
|
-
disabled: boolean;
|
|
69
|
-
onpointerup:
|
|
70
|
-
onpointerdown:
|
|
71
|
-
onpointermove: PointerEventHandler<HTMLElement>;
|
|
72
|
-
onpointerleave:
|
|
73
|
-
onfocus:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
onblur: () => void;
|
|
77
|
-
onclick: () => void;
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly "aria-describedby": string | undefined;
|
|
63
|
+
readonly "data-state": string;
|
|
64
|
+
readonly "data-disabled": "" | undefined;
|
|
65
|
+
readonly "data-delay-duration": `${number}`;
|
|
66
|
+
readonly "data-tooltip-trigger": "";
|
|
67
|
+
readonly tabindex: 0 | undefined;
|
|
68
|
+
readonly disabled: boolean;
|
|
69
|
+
readonly onpointerup: PointerEventHandler<HTMLElement>;
|
|
70
|
+
readonly onpointerdown: PointerEventHandler<HTMLElement>;
|
|
71
|
+
readonly onpointermove: PointerEventHandler<HTMLElement>;
|
|
72
|
+
readonly onpointerleave: PointerEventHandler<HTMLElement>;
|
|
73
|
+
readonly onfocus: FocusEventHandler<HTMLElement>;
|
|
74
|
+
readonly onblur: FocusEventHandler<HTMLElement>;
|
|
75
|
+
readonly onclick: MouseEventHandler<HTMLElement>;
|
|
78
76
|
};
|
|
79
77
|
}
|
|
80
78
|
type TooltipContentStateProps = WithRefProps & ReadableBoxedValues<{
|
|
@@ -194,7 +194,9 @@ class TooltipTriggerState {
|
|
|
194
194
|
};
|
|
195
195
|
props = $derived.by(() => ({
|
|
196
196
|
id: this.opts.id.current,
|
|
197
|
-
"aria-describedby": this.root.opts.open.current
|
|
197
|
+
"aria-describedby": this.root.opts.open.current
|
|
198
|
+
? this.root.contentNode?.id
|
|
199
|
+
: undefined,
|
|
198
200
|
"data-state": this.root.stateAttr,
|
|
199
201
|
"data-disabled": getDataDisabled(this.#isDisabled),
|
|
200
202
|
"data-delay-duration": `${this.root.delayDuration}`,
|
|
@@ -4,13 +4,16 @@
|
|
|
4
4
|
import type { AnchorProps } from "./index.js";
|
|
5
5
|
import type { Measurable } from "../../../../internal/floating-svelte/types.js";
|
|
6
6
|
|
|
7
|
-
let { id, children, virtualEl, ref }: AnchorProps = $props();
|
|
7
|
+
let { id, children, virtualEl, ref, tooltip = false }: AnchorProps = $props();
|
|
8
8
|
|
|
9
|
-
useFloatingAnchorState(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
useFloatingAnchorState(
|
|
10
|
+
{
|
|
11
|
+
id: box.with(() => id),
|
|
12
|
+
virtualEl: box.with(() => virtualEl as unknown as Measurable | null),
|
|
13
|
+
ref,
|
|
14
|
+
},
|
|
15
|
+
tooltip
|
|
16
|
+
);
|
|
14
17
|
</script>
|
|
15
18
|
|
|
16
19
|
{@render children?.()}
|
|
@@ -25,29 +25,33 @@
|
|
|
25
25
|
wrapperId = useId(),
|
|
26
26
|
customAnchor = null,
|
|
27
27
|
enabled,
|
|
28
|
+
tooltip = false,
|
|
28
29
|
}: ContentImplProps = $props();
|
|
29
30
|
|
|
30
|
-
const contentState = useFloatingContentState(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
31
|
+
const contentState = useFloatingContentState(
|
|
32
|
+
{
|
|
33
|
+
side: box.with(() => side),
|
|
34
|
+
sideOffset: box.with(() => sideOffset),
|
|
35
|
+
align: box.with(() => align),
|
|
36
|
+
alignOffset: box.with(() => alignOffset),
|
|
37
|
+
id: box.with(() => id),
|
|
38
|
+
arrowPadding: box.with(() => arrowPadding),
|
|
39
|
+
avoidCollisions: box.with(() => avoidCollisions),
|
|
40
|
+
collisionBoundary: box.with(() => collisionBoundary),
|
|
41
|
+
collisionPadding: box.with(() => collisionPadding),
|
|
42
|
+
hideWhenDetached: box.with(() => hideWhenDetached),
|
|
43
|
+
onPlaced: box.with(() => onPlaced),
|
|
44
|
+
sticky: box.with(() => sticky),
|
|
45
|
+
updatePositionStrategy: box.with(() => updatePositionStrategy),
|
|
46
|
+
strategy: box.with(() => strategy),
|
|
47
|
+
dir: box.with(() => dir),
|
|
48
|
+
style: box.with(() => style),
|
|
49
|
+
enabled: box.with(() => enabled),
|
|
50
|
+
wrapperId: box.with(() => wrapperId),
|
|
51
|
+
customAnchor: box.with(() => customAnchor),
|
|
52
|
+
},
|
|
53
|
+
tooltip
|
|
54
|
+
);
|
|
51
55
|
|
|
52
56
|
const mergedProps = $derived(
|
|
53
57
|
mergeProps(contentState.wrapperProps, {
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import { useFloatingRootState } from "../use-floating-layer.svelte.js";
|
|
4
4
|
|
|
5
|
-
let { children }: { children?: Snippet } = $props();
|
|
5
|
+
let { children, tooltip = false }: { children?: Snippet; tooltip?: boolean } = $props();
|
|
6
6
|
|
|
7
|
-
useFloatingRootState();
|
|
7
|
+
useFloatingRootState(tooltip);
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
10
|
{@render children?.()}
|
|
@@ -107,10 +107,28 @@ export type FloatingLayerContentImplProps = {
|
|
|
107
107
|
*/
|
|
108
108
|
onPlaced?: () => void;
|
|
109
109
|
enabled: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Tooltips are special in that they are commonly composed
|
|
112
|
+
* with other floating components, where the same trigger is
|
|
113
|
+
* used for both the tooltip and the popover.
|
|
114
|
+
*
|
|
115
|
+
* For situations like this, we need to use a different context
|
|
116
|
+
* symbol so that conflicts don't occur.
|
|
117
|
+
*/
|
|
118
|
+
tooltip?: boolean;
|
|
110
119
|
} & FloatingLayerContentProps;
|
|
111
120
|
export type FloatingLayerAnchorProps = {
|
|
112
121
|
id: string;
|
|
113
122
|
children?: Snippet;
|
|
114
123
|
virtualEl?: ReadableBox<Measurable | null>;
|
|
115
124
|
ref: ReadableBox<HTMLElement | null>;
|
|
125
|
+
/**
|
|
126
|
+
* Tooltips are special in that they are commonly composed
|
|
127
|
+
* with other floating components, where the same trigger is
|
|
128
|
+
* used for both the tooltip and the popover.
|
|
129
|
+
*
|
|
130
|
+
* For situations like this, we need to use a different context
|
|
131
|
+
* symbol so that conflicts don't occur.
|
|
132
|
+
*/
|
|
133
|
+
tooltip?: boolean;
|
|
116
134
|
};
|
|
@@ -942,10 +942,10 @@ declare class FloatingAnchorState {
|
|
|
942
942
|
readonly root: FloatingRootState;
|
|
943
943
|
constructor(opts: FloatingAnchorStateProps, root: FloatingRootState);
|
|
944
944
|
}
|
|
945
|
-
export declare function useFloatingRootState(): FloatingRootState;
|
|
946
|
-
export declare function useFloatingContentState(props: FloatingContentStateProps): FloatingContentState;
|
|
945
|
+
export declare function useFloatingRootState(tooltip?: boolean): FloatingRootState;
|
|
946
|
+
export declare function useFloatingContentState(props: FloatingContentStateProps, tooltip?: boolean): FloatingContentState;
|
|
947
947
|
export declare function useFloatingArrowState(props: FloatingArrowStateProps): FloatingArrowState;
|
|
948
|
-
export declare function useFloatingAnchorState(props: FloatingAnchorStateProps): FloatingAnchorState;
|
|
948
|
+
export declare function useFloatingAnchorState(props: FloatingAnchorStateProps, tooltip?: boolean): FloatingAnchorState;
|
|
949
949
|
export declare function getSideFromPlacement(placement: Placement): "left" | "right" | "top" | "bottom";
|
|
950
950
|
export declare function getAlignFromPlacement(placement: Placement): "end" | "center" | "start";
|
|
951
951
|
export {};
|
|
@@ -140,9 +140,6 @@ class FloatingContentState {
|
|
|
140
140
|
"data-align": this.placedAlign,
|
|
141
141
|
style: styleToString({
|
|
142
142
|
...this.#transformedStyle,
|
|
143
|
-
// if the FloatingContent hasn't been placed yet (not all measurements done)
|
|
144
|
-
// we prevent animations so that users's animation don't kick in too early referring wrong sides
|
|
145
|
-
// animation: !this.floating.isPositioned ? "none" : undefined,
|
|
146
143
|
}),
|
|
147
144
|
...attachRef(this.contentRef),
|
|
148
145
|
}));
|
|
@@ -233,17 +230,24 @@ class FloatingAnchorState {
|
|
|
233
230
|
}
|
|
234
231
|
const FloatingRootContext = new Context("Floating.Root");
|
|
235
232
|
const FloatingContentContext = new Context("Floating.Content");
|
|
236
|
-
|
|
237
|
-
|
|
233
|
+
const FloatingTooltipRootContext = new Context("Floating.Root");
|
|
234
|
+
export function useFloatingRootState(tooltip = false) {
|
|
235
|
+
return tooltip
|
|
236
|
+
? FloatingTooltipRootContext.set(new FloatingRootState())
|
|
237
|
+
: FloatingRootContext.set(new FloatingRootState());
|
|
238
238
|
}
|
|
239
|
-
export function useFloatingContentState(props) {
|
|
240
|
-
return
|
|
239
|
+
export function useFloatingContentState(props, tooltip = false) {
|
|
240
|
+
return tooltip
|
|
241
|
+
? FloatingContentContext.set(new FloatingContentState(props, FloatingTooltipRootContext.get()))
|
|
242
|
+
: FloatingContentContext.set(new FloatingContentState(props, FloatingRootContext.get()));
|
|
241
243
|
}
|
|
242
244
|
export function useFloatingArrowState(props) {
|
|
243
245
|
return new FloatingArrowState(props, FloatingContentContext.get());
|
|
244
246
|
}
|
|
245
|
-
export function useFloatingAnchorState(props) {
|
|
246
|
-
return
|
|
247
|
+
export function useFloatingAnchorState(props, tooltip = false) {
|
|
248
|
+
return tooltip
|
|
249
|
+
? new FloatingAnchorState(props, FloatingTooltipRootContext.get())
|
|
250
|
+
: new FloatingAnchorState(props, FloatingRootContext.get());
|
|
247
251
|
}
|
|
248
252
|
//
|
|
249
253
|
// HELPERS
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
isStatic = false,
|
|
46
46
|
enabled,
|
|
47
47
|
ref,
|
|
48
|
+
tooltip = false,
|
|
48
49
|
...restProps
|
|
49
50
|
}: Omit<PopperLayerImplProps, "present" | "children"> & {
|
|
50
51
|
enabled: boolean;
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
{onPlaced}
|
|
73
74
|
{customAnchor}
|
|
74
75
|
{enabled}
|
|
76
|
+
{tooltip}
|
|
75
77
|
>
|
|
76
78
|
{#snippet content({ props: floatingProps, wrapperProps })}
|
|
77
79
|
{#if restProps.forceMount && enabled}
|
|
@@ -22,4 +22,13 @@ export type PopperLayerImplProps = Omit<EscapeLayerImplProps & DismissibleLayerI
|
|
|
22
22
|
}
|
|
23
23
|
]>;
|
|
24
24
|
isStatic?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Tooltips are special in that they are commonly composed
|
|
27
|
+
* with other floating components, where the same trigger is
|
|
28
|
+
* used for both the tooltip and the popover.
|
|
29
|
+
*
|
|
30
|
+
* For situations like this, we need to use a different context
|
|
31
|
+
* symbol so that conflicts don't occur.
|
|
32
|
+
*/
|
|
33
|
+
tooltip?: boolean;
|
|
25
34
|
}, "enabled">;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Accordion, AlertDialog, AspectRatio, Avatar, Button, Calendar, Checkbox, Collapsible, Combobox, Command, ContextMenu, DateField, DatePicker, DateRangeField, DateRangePicker, Dialog, DropdownMenu, Label, LinkPreview, Menubar, Meter, NavigationMenu, Pagination, PinInput, Popover, Progress, RadioGroup, RangeCalendar, ScrollArea, Select, Separator, Slider, Switch, Tabs, TimeField, TimeRangeField, Toggle, ToggleGroup, Toolbar, Tooltip, Portal, IsUsingKeyboard, computeCommandScore, } from "./bits/index.js";
|
|
1
|
+
export { Accordion, AlertDialog, AspectRatio, Avatar, Button, Calendar, Checkbox, Collapsible, Combobox, Command, ContextMenu, DateField, DatePicker, DateRangeField, DateRangePicker, Dialog, DropdownMenu, Label, LinkPreview, Menubar, Meter, NavigationMenu, Pagination, PinInput, Popover, Progress, RadioGroup, RangeCalendar, RatingGroup as unstable_RatingGroup, ScrollArea, Select, Separator, Slider, Switch, Tabs, TimeField, TimeRangeField, Toggle, ToggleGroup, Toolbar, Tooltip, Portal, IsUsingKeyboard, computeCommandScore, } from "./bits/index.js";
|
|
2
2
|
export * from "./shared/index.js";
|
|
3
3
|
export type * from "./shared/index.js";
|
|
4
4
|
export * from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Accordion, AlertDialog, AspectRatio, Avatar, Button, Calendar, Checkbox, Collapsible, Combobox, Command, ContextMenu, DateField, DatePicker, DateRangeField, DateRangePicker, Dialog, DropdownMenu, Label, LinkPreview, Menubar, Meter, NavigationMenu, Pagination, PinInput, Popover, Progress, RadioGroup, RangeCalendar, ScrollArea, Select, Separator, Slider, Switch, Tabs, TimeField, TimeRangeField, Toggle, ToggleGroup, Toolbar, Tooltip, Portal, IsUsingKeyboard, computeCommandScore, } from "./bits/index.js";
|
|
1
|
+
export { Accordion, AlertDialog, AspectRatio, Avatar, Button, Calendar, Checkbox, Collapsible, Combobox, Command, ContextMenu, DateField, DatePicker, DateRangeField, DateRangePicker, Dialog, DropdownMenu, Label, LinkPreview, Menubar, Meter, NavigationMenu, Pagination, PinInput, Popover, Progress, RadioGroup, RangeCalendar, RatingGroup as unstable_RatingGroup, ScrollArea, Select, Separator, Slider, Switch, Tabs, TimeField, TimeRangeField, Toggle, ToggleGroup, Toolbar, Tooltip, Portal, IsUsingKeyboard, computeCommandScore, } from "./bits/index.js";
|
|
2
2
|
export * from "./shared/index.js";
|
|
3
3
|
export * from "./types.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type * from "./bits/popover/types.js";
|
|
|
27
27
|
export type * from "./bits/progress/types.js";
|
|
28
28
|
export type * from "./bits/radio-group/types.js";
|
|
29
29
|
export type * from "./bits/range-calendar/types.js";
|
|
30
|
+
export type * from "./bits/rating-group/types.js";
|
|
30
31
|
export type * from "./bits/scroll-area/types.js";
|
|
31
32
|
export type * from "./bits/select/types.js";
|
|
32
33
|
export type * from "./bits/separator/types.js";
|