Skip to content

Commit 0836e65

Browse files
whitphxCopilot
andauthored
Fix reactivity on the absolute positions/snapped anchor props (#134)
* Fix reactivity on the absolute positions * Fix reactivity * Refactoring * Add changeset * Update components/use-element-position.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent e796535 commit 0836e65

File tree

4 files changed

+87
-43
lines changed

4 files changed

+87
-43
lines changed

.changeset/stupid-parks-post.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"slidev-addon-fancy-arrow": patch
3+
---
4+
5+
Fix the props reactivity to work properly

components/FancyArrow.vue

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<script setup lang="ts">
22
import { ref, computed, type Ref } from "vue";
33
import { compileArrowEndpointProps } from "./parse-option";
4-
import { useElementPosition, type SnapPosition } from "./use-element-position";
4+
import {
5+
useEndpointResolution,
6+
type SnapPosition,
7+
} from "./use-element-position";
58
import { useRoughArrow, type AbsolutePosition } from "./use-rough-arrow";
69
710
const props = defineProps<{
@@ -34,7 +37,7 @@ const slideContainer = computed(() => {
3437
3538
const svgContainer = ref<SVGSVGElement>();
3639
37-
const from = computed(() =>
40+
const endpoint1 = computed(() =>
3841
compileArrowEndpointProps({
3942
shorthand: props.from,
4043
q: props.q1,
@@ -44,7 +47,7 @@ const from = computed(() =>
4447
y: props.y1,
4548
}),
4649
);
47-
const to = computed(() =>
50+
const endpoint2 = computed(() =>
4851
compileArrowEndpointProps({
4952
shorthand: props.to,
5053
q: props.q2,
@@ -55,24 +58,16 @@ const to = computed(() =>
5558
}),
5659
);
5760
58-
const point1: Ref<AbsolutePosition | undefined> =
59-
from.value && "query" in from.value
60-
? useElementPosition(
61-
slideContainer,
62-
svgContainer,
63-
from.value.query,
64-
from.value.snapPosition,
65-
)
66-
: ref(from.value);
67-
const point2: Ref<AbsolutePosition | undefined> =
68-
to.value && "query" in to.value
69-
? useElementPosition(
70-
slideContainer,
71-
svgContainer,
72-
to.value.query,
73-
to.value.snapPosition,
74-
)
75-
: ref(to.value);
61+
const point1: Ref<AbsolutePosition | undefined> = useEndpointResolution(
62+
slideContainer,
63+
svgContainer,
64+
endpoint1,
65+
);
66+
const point2: Ref<AbsolutePosition | undefined> = useEndpointResolution(
67+
slideContainer,
68+
svgContainer,
69+
endpoint2,
70+
);
7671
7772
const { arcSvg, textPosition } = useRoughArrow({
7873
point1,

components/use-element-position.ts

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "vue";
99
import { useSlideContext, useIsSlideActive } from "@slidev/client";
1010
import { AbsolutePosition } from "./use-rough-arrow";
11+
import { SnapTarget } from "./parse-option";
1112

1213
export type SnapPosition =
1314
| "top"
@@ -19,43 +20,75 @@ export type SnapPosition =
1920
| "bottomleft"
2021
| "bottomright";
2122

22-
export function useElementPosition(
23-
slideContainer: Ref<Element | undefined>,
24-
rootElement: Ref<SVGSVGElement | undefined>,
25-
selector: string,
26-
pos?: SnapPosition,
23+
export function useEndpointResolution(
24+
slideContainerRef: Ref<Element | undefined>,
25+
rootElementRef: Ref<SVGSVGElement | undefined>,
26+
endpointRef: Ref<AbsolutePosition | SnapTarget | undefined>,
2727
): Ref<AbsolutePosition | undefined> {
2828
const { $scale } = useSlideContext();
2929
const isSlideActive = useIsSlideActive();
3030

31-
const elem = computed(() => {
32-
return slideContainer.value?.querySelector(selector) ?? null;
31+
const snappedElementInfo = computed(() => {
32+
const endpoint = endpointRef.value;
33+
if (endpoint == null || !("query" in endpoint)) {
34+
return undefined;
35+
}
36+
const element =
37+
slideContainerRef.value?.querySelector(endpoint.query) ?? null;
38+
return {
39+
element,
40+
snapPosition: endpoint.snapPosition,
41+
};
3342
});
3443

3544
const point = ref<AbsolutePosition | undefined>(undefined);
3645

37-
const update = () => {
38-
if (!isSlideActive.value || !rootElement.value || !elem.value) {
46+
// Sync endpointRef -> point in case where endpoint is AbsolutePosition
47+
watch(
48+
endpointRef,
49+
(endpoint) => {
50+
if (endpoint == null) {
51+
point.value = undefined;
52+
return;
53+
} else if ("x" in endpoint) {
54+
point.value = { x: endpoint.x, y: endpoint.y };
55+
return;
56+
}
57+
},
58+
{ immediate: true },
59+
);
60+
61+
// Sync snappedElementInfo -> point in case where endpoint is SnapTarget
62+
const updateSnappedPosition = () => {
63+
if (!snappedElementInfo.value) {
64+
// This case means endpoint is AbsolutePosition
65+
// so we don't need to update point in this method
66+
// as it's done in the watch above.
67+
return;
68+
}
69+
70+
const { element, snapPosition } = snappedElementInfo.value;
71+
if (!isSlideActive.value || !rootElementRef.value || !element) {
3972
point.value = undefined;
4073
return;
4174
}
4275

43-
const rect = elem.value.getBoundingClientRect();
44-
const rootRect = rootElement.value.getBoundingClientRect();
76+
const rect = element.getBoundingClientRect();
77+
const rootRect = rootElementRef.value.getBoundingClientRect();
4578

4679
let x = (rect.left - rootRect.left) / $scale.value;
4780
let y = (rect.top - rootRect.top) / $scale.value;
4881
const width = rect.width / $scale.value;
4982
const height = rect.height / $scale.value;
5083

51-
if (pos?.includes("right")) {
84+
if (snapPosition?.includes("right")) {
5285
x += width;
53-
} else if (!pos?.includes("left")) {
86+
} else if (!snapPosition?.includes("left")) {
5487
x += width / 2;
5588
}
56-
if (pos?.includes("bottom")) {
89+
if (snapPosition?.includes("bottom")) {
5790
y += height;
58-
} else if (!pos?.includes("top")) {
91+
} else if (!snapPosition?.includes("top")) {
5992
y += height / 2;
6093
}
6194

@@ -70,16 +103,16 @@ export function useElementPosition(
70103
watch(isSlideActive, () => {
71104
setTimeout(() => {
72105
// This `setTimeout` is important to ensure `update()` is called after the DOM elements in the slide are updated after `isSlideActive` is changed.
73-
update();
106+
updateSnappedPosition();
74107
});
75108
});
76109

77110
watch(
78-
elem,
111+
snappedElementInfo,
79112
(newVal) => {
80-
if (newVal) {
81-
const observer = new MutationObserver(update);
82-
observer.observe(newVal, { attributes: true });
113+
if (newVal?.element) {
114+
const observer = new MutationObserver(updateSnappedPosition);
115+
observer.observe(newVal.element, { attributes: true });
83116

84117
onWatcherCleanup(() => {
85118
observer.disconnect();
@@ -90,12 +123,12 @@ export function useElementPosition(
90123
);
91124

92125
onMounted(() => {
93-
update();
126+
updateSnappedPosition();
94127

95128
// Some type of position/size changes can't be observed by MutationObserver.
96129
// So we need to update the position/size periodically in the polling manner.
97130
const interval = setInterval(() => {
98-
update();
131+
updateSnappedPosition();
99132
}, 100);
100133

101134
return () => clearInterval(interval);

slides.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,3 +617,14 @@ Use `pos1` and `pos2` to specify the anchor point on the snapped elements.
617617
<FancyArrow v-click="2" forward:delay-100 q1="[data-id=bar]" pos1="bottomright" q2="[data-id=baz]" pos2="topright" color="green" width="2" arc="0.3" seed="1" roughness="2" >
618618
<span text-green v-mark.green="2">Hola!</span>
619619
</FancyArrow>
620+
621+
---
622+
clicks: 4
623+
---
624+
625+
# Demo: dynamic positions
626+
627+
<div absolute left="50%" top="50%" translate-x="-50%" translate-y="-50%" data-id="center-anchor"></div>
628+
<div absolute right-10 top="50%" translate-x="-50%" translate-y="-50%" data-id="right-anchor">Anchor</div>
629+
630+
<FancyArrow q1="[data-id=center-anchor]" :x2="$clicks % 2 === 0 ? 100 : 900" :y2="$clicks / 2 < 1 ? 100 : 500" :q2="$clicks > 3 ? '[data-id=right-anchor]' : undefined" />

0 commit comments

Comments
 (0)