Skip to content

Commit 3dfb82f

Browse files
committed
more things
1 parent db9e494 commit 3dfb82f

13 files changed

+345
-224
lines changed

packages/floating-ui-svelte/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"dependencies": {
4646
"@floating-ui/dom": "^1.6.12",
4747
"@floating-ui/utils": "^0.2.8",
48-
"esm-env": "^1.2.1"
48+
"esm-env": "^1.2.1",
49+
"style-to-object": "^1.0.8"
4950
}
5051
}

packages/floating-ui-svelte/src/components/floating-tree/floating-arrow.svelte

Lines changed: 98 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,19 @@
4242
* @default 0
4343
*/
4444
strokeWidth?: number;
45-
46-
// Styling ---
47-
/** Set transform styles. */
48-
transform?: string;
49-
/** Set fill styles. */
50-
fill?: string;
5145
}
5246
</script>
5347

5448
<script lang="ts">
5549
import type { Alignment, Side } from "@floating-ui/dom";
5650
import { platform } from "@floating-ui/dom";
5751
import { useId } from "../../hooks/use-id.js";
58-
import { styleObjectToString } from "../../internal/style-object-to-string.js";
52+
import {
53+
styleObjectToString,
54+
styleStringToObject,
55+
} from "../../internal/style-object-to-string.js";
56+
import parse from "style-to-object";
57+
import { watch } from "../../internal/watch.svelte.js";
5958
6059
let {
6160
ref = $bindable(null),
@@ -68,15 +67,44 @@
6867
staticOffset,
6968
stroke,
7069
d,
71-
// ---
72-
transform,
73-
fill,
74-
// ---
70+
style: styleProp = "",
7571
...rest
7672
}: FloatingArrowProps = $props();
7773
74+
const { transform, ...restStyle } = $derived(
75+
styleStringToObject(styleProp)
76+
);
77+
7878
const clipPathId = useId();
7979
80+
let isRTL = $state(false);
81+
82+
// https://github.com/floating-ui/floating-ui/issues/2932
83+
watch(
84+
() => context.elements.floating,
85+
(floatingEl) => {
86+
if (!floatingEl) return;
87+
if (getComputedStyle(floatingEl).direction === "rtl") {
88+
isRTL = true;
89+
}
90+
}
91+
);
92+
93+
const [side, alignment] = $derived(
94+
context.placement.split("-") as [Side, Alignment]
95+
);
96+
const isVerticalSide = $derived(side === "top" || side === "bottom");
97+
98+
const computedStaticOffset = $derived.by(() => {
99+
if (
100+
(isVerticalSide && context.middlewareData.shift?.x) ||
101+
(!isVerticalSide && context.middlewareData.shift?.y)
102+
) {
103+
return null;
104+
}
105+
return staticOffset;
106+
});
107+
80108
// Strokes must be double the border width, this ensures the stroke's width
81109
// works as you'd expect.
82110
const computedStrokeWidth = $derived(strokeWidth * 2);
@@ -85,33 +113,27 @@
85113
const svgX = $derived((width / 2) * (tipRadius / -8 + 1));
86114
const svgY = $derived(((height / 2) * tipRadius) / 4);
87115
88-
const [side, alignment] = $derived(
89-
context.placement.split("-") as [Side, Alignment]
90-
);
91-
const isRTL = $derived(
92-
context.elements.floating && platform.isRTL(context.elements.floating)
93-
);
94116
const isCustomShape = $derived(!!d);
95-
const isVerticalSide = $derived(side === "top" || side === "bottom");
96117
97118
const yOffsetProp = $derived(
98-
staticOffset && alignment === "end" ? "bottom" : "top"
119+
computedStaticOffset && alignment === "end" ? "bottom" : "top"
99120
);
100121
const xOffsetProp = $derived.by(() => {
101-
if (!staticOffset) {
102-
return "left";
103-
}
104-
if (isRTL) {
105-
return alignment === "end" ? "right" : "left";
122+
if (computedStaticOffset && isRTL) {
123+
return alignment === "end" ? "left" : "right";
106124
}
107125
return alignment === "end" ? "right" : "left";
108126
});
109127
110128
const arrowX = $derived(
111-
arrow?.x != null ? staticOffset || `${arrow.x}px` : ""
129+
context.middlewareData.arrow?.x != null
130+
? staticOffset || `${context.middlewareData.arrow.x}px`
131+
: ""
112132
);
113133
const arrowY = $derived(
114-
arrow?.y != null ? staticOffset || `${arrow.y}px` : ""
134+
context.middlewareData.arrow?.y != null
135+
? staticOffset || `${context.middlewareData.arrow.y}px`
136+
: ""
115137
);
116138
117139
const dValue = $derived(
@@ -120,61 +142,61 @@
120142
);
121143
122144
const rotation = $derived.by(() => {
123-
switch (side) {
124-
case "top":
125-
return isCustomShape ? "rotate(180deg)" : "";
126-
case "left":
127-
return isCustomShape ? "rotate(90deg)" : "rotate(-90deg)";
128-
case "bottom":
129-
return isCustomShape ? "" : "rotate(180deg)";
130-
case "right":
131-
return isCustomShape ? "rotate(-90deg)" : "rotate(90deg)";
132-
}
145+
return {
146+
top: isCustomShape ? "rotate(180deg)" : "",
147+
left: isCustomShape ? "rotate(90deg)" : "rotate(-90deg)",
148+
bottom: isCustomShape ? "" : "rotate(180deg)",
149+
right: isCustomShape ? "rotate(-90deg)" : "rotate(90deg)",
150+
}[side];
133151
});
134152
</script>
135153

136154
<!-- FIXME: extend styleObjectToString type to accept `rest.styles` -->
137155

138-
<svg
139-
bind:this={ref}
140-
width={isCustomShape ? width : width + computedStrokeWidth}
141-
height={width}
142-
viewBox={`0 0 ${width} ${height > width ? height : width}`}
143-
aria-hidden="true"
144-
style={styleObjectToString({
145-
position: "absolute",
146-
"pointer-events": "none",
147-
[xOffsetProp]: `${arrowX}`,
148-
[yOffsetProp]: `${arrowY}`,
149-
[side]:
150-
isVerticalSide || isCustomShape
151-
? "100%"
152-
: `calc(100% - ${computedStrokeWidth / 2}px)`,
153-
transform: `${rotation} ${transform ?? ""}`,
154-
fill,
155-
})}
156-
data-testid="floating-arrow"
157-
{...rest}>
158-
{#if computedStrokeWidth > 0}
159-
<!-- Account for the stroke on the fill path rendered below. -->
160-
<path
161-
fill="none"
162-
{stroke}
163-
clip-path={`url(#${clipPathId})`}
164-
stroke-width={computedStrokeWidth + (d ? 0 : 1)}
165-
d={dValue} />
166-
{/if}
167-
<!--
156+
{#if context.elements.floating}
157+
<svg
158+
bind:this={ref}
159+
{...rest}
160+
width={isCustomShape ? width : width + computedStrokeWidth}
161+
height={width}
162+
viewBox={`0 0 ${width} ${height > width ? height : width}`}
163+
aria-hidden="true"
164+
style={styleObjectToString({
165+
position: "absolute",
166+
"pointer-events": "none",
167+
[xOffsetProp]: `${arrowX}`,
168+
[yOffsetProp]: `${arrowY}`,
169+
[side]:
170+
isVerticalSide || isCustomShape
171+
? "100%"
172+
: `calc(100% - ${computedStrokeWidth / 2}px)`,
173+
transform: [rotation, transform].filter((t) => !!t).join(" "),
174+
...restStyle,
175+
})}
176+
data-testid="floating-arrow">
177+
{#if computedStrokeWidth > 0}
178+
<!-- Account for the stroke on the fill path rendered below. -->
179+
<path
180+
clip-path={`url(#${clipPathId})`}
181+
fill="none"
182+
{stroke}
183+
stroke-width={computedStrokeWidth + (d ? 0 : 1)}
184+
d={dValue} />
185+
{/if}
186+
<!--
168187
In Firefox, for left/right placements there's a ~0.5px gap where the
169188
border can show through. Adding a stroke on the fill removes it.
170189
-->
171-
<path stroke={computedStrokeWidth && !d ? fill : "none"} d={dValue} />
172-
<!-- Assumes the border-width of the floating element matches the stroke. -->
173-
<clipPath id={clipPathId}>
174-
<rect
175-
x={-halfStrokeWidth}
176-
y={halfStrokeWidth * (isCustomShape ? -1 : 1)}
177-
width={width + computedStrokeWidth}
178-
height={width} />
179-
</clipPath>
180-
</svg>
190+
<path
191+
stroke={computedStrokeWidth && !d ? rest.fill : "none"}
192+
d={dValue} />
193+
<!-- Assumes the border-width of the floating element matches the stroke. -->
194+
<clipPath id={clipPathId}>
195+
<rect
196+
x={-halfStrokeWidth}
197+
y={halfStrokeWidth * (isCustomShape ? -1 : 1)}
198+
width={width + computedStrokeWidth}
199+
height={width} />
200+
</clipPath>
201+
</svg>
202+
{/if}

packages/floating-ui-svelte/src/components/floating-tree/floating-tree.svelte

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
<script lang="ts" module>
22
import type { Snippet } from "svelte";
3-
import type {
4-
FloatingNodeType,
5-
FloatingTreeType,
6-
ReferenceType,
7-
} from "../../types.js";
3+
import type { FloatingNodeType } from "../../types.js";
84
import { createPubSub } from "../../internal/create-pub-sub.js";
95
import { FloatingTreeContext } from "./hooks.svelte.js";
106

packages/floating-ui-svelte/src/hooks/use-floating-root-context.svelte.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import type { ReferenceElement } from "@floating-ui/dom";
2-
import type {
3-
ContextData,
4-
OnOpenChange,
5-
OpenChangeReason,
6-
ReferenceType,
7-
} from "../types.js";
2+
import type { ContextData, OpenChangeReason, ReferenceType } from "../types.js";
83
import { useId } from "./use-id.js";
94
import { createPubSub } from "../internal/create-pub-sub.js";
105
import { useFloatingParentNodeId } from "../components/floating-tree/hooks.svelte.js";

packages/floating-ui-svelte/src/hooks/use-floating.svelte.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -78,32 +78,6 @@ class FloatingContext<RT extends ReferenceType = ReferenceType>
7878
{
7979
constructor(private readonly opts: FloatingContextOptions<RT>) {}
8080

81-
/**
82-
* INTERNAL ONLY. DO NOT USE.
83-
*
84-
* @internal
85-
*/
86-
// prefix with `z` to push to bottom of intellisense
87-
readonly z_internal_current: FloatingContextData<RT> = $derived.by(() => {
88-
return {
89-
elements: this.opts.getElements(this.opts.floating),
90-
x: this.opts.floating.x,
91-
y: this.opts.floating.y,
92-
placement: this.opts.floating.placement,
93-
strategy: this.opts.floating.strategy,
94-
middlewareData: this.opts.floating.middlewareData,
95-
isPositioned: this.opts.floating.isPositioned,
96-
update: this.opts.floating.update,
97-
floatingStyles: this.opts.floating.floatingStyles,
98-
onOpenChange: this.opts.rootContext.onOpenChange,
99-
open: this.opts.rootContext.open,
100-
data: this.opts.rootContext.data,
101-
floatingId: this.opts.rootContext.floatingId,
102-
events: this.opts.rootContext.events,
103-
nodeId: this.opts.floatingOptions.nodeId,
104-
};
105-
});
106-
10781
get elements() {
10882
return this.opts.getElements(this.opts.floating);
10983
}

packages/floating-ui-svelte/src/hooks/use-focus.svelte.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,11 @@ class FocusInteraction {
9595
if (isVirtualPointerEvent(event)) return;
9696
this.#keyboardModality = false;
9797
};
98+
9899
#onmouseleave = () => {
99100
this.#blockFocus = false;
100101
};
102+
101103
#onfocus = (event: FocusEvent) => {
102104
if (this.#blockFocus) {
103105
return;
@@ -122,6 +124,7 @@ class FocusInteraction {
122124

123125
this.context.onOpenChange(true, event, "focus");
124126
};
127+
125128
#onblur = (event: FocusEvent) => {
126129
this.#blockFocus = false;
127130
const relatedTarget = event.relatedTarget;

0 commit comments

Comments
 (0)