@erudit-js/prose 4.0.0-dev.5 → 4.0.1
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/app/default/Inliners.vue +11 -11
- package/dist/app/default/Mix.vue +11 -11
- package/dist/app/default/Text.vue +25 -25
- package/dist/app/shared/Prose.vue +40 -40
- package/dist/app/shared/Render.vue +51 -51
- package/dist/app/shared/assets/block.svg +2 -2
- package/dist/app/shared/assets/check.svg +2 -2
- package/dist/app/shared/assets/inliner.svg +2 -2
- package/dist/app/shared/assets/plus.svg +2 -2
- package/dist/app/shared/block/AsideMenu.vue +44 -44
- package/dist/app/shared/block/AsideMenuButton.vue +51 -51
- package/dist/app/shared/block/AsideMenuCopyLink.vue +40 -40
- package/dist/app/shared/block/AsideMenuSeparator.vue +3 -3
- package/dist/app/shared/block/Block.vue +270 -270
- package/dist/app/shared/inliner/Inliner.vue +11 -11
- package/dist/app/shared/photoswipe/style.css +22 -22
- package/dist/elements/accent/Accent.vue +88 -88
- package/dist/elements/accent/AccentColumnSection.vue +61 -61
- package/dist/elements/accent/AccentRowSections.vue +64 -64
- package/dist/elements/callout/Callout.vue +81 -81
- package/dist/elements/callout/_global.d.ts +15 -15
- package/dist/elements/caption/Caption.vue +44 -44
- package/dist/elements/caption/_global.d.ts +26 -26
- package/dist/elements/details/Details.vue +49 -49
- package/dist/elements/details/_global.d.ts +27 -27
- package/dist/elements/details/icon.svg +2 -2
- package/dist/elements/diagram/Diagram.vue +360 -360
- package/dist/elements/diagram/_global.d.ts +19 -19
- package/dist/elements/emphasis/Emphasis.vue +25 -25
- package/dist/elements/emphasis/_global.d.ts +18 -18
- package/dist/elements/flex/Flex.vue +36 -36
- package/dist/elements/flex/_global.d.ts +23 -23
- package/dist/elements/gallery/Gallery.vue +56 -56
- package/dist/elements/gallery/_global.d.ts +18 -18
- package/dist/elements/heading/Heading.vue +44 -44
- package/dist/elements/heading/_global.d.ts +42 -42
- package/dist/elements/heading/icon.svg +2 -2
- package/dist/elements/horizontalLine/HorizontalLine.vue +6 -6
- package/dist/elements/horizontalLine/_global.d.ts +17 -17
- package/dist/elements/image/Image.vue +15 -15
- package/dist/elements/image/ImageElement.vue +80 -80
- package/dist/elements/image/_global.d.ts +18 -18
- package/dist/elements/lineBreak/LineBreak.vue +3 -3
- package/dist/elements/lineBreak/_global.d.ts +18 -18
- package/dist/elements/link/BlockLink.vue +108 -108
- package/dist/elements/link/Link.vue +92 -92
- package/dist/elements/link/dependency/_global.d.ts +47 -47
- package/dist/elements/link/reference/_global.d.ts +49 -49
- package/dist/elements/list/List.vue +58 -58
- package/dist/elements/list/_global.d.ts +50 -50
- package/dist/elements/math/_global.d.ts +72 -72
- package/dist/elements/math/_global.ts +3 -3
- package/dist/elements/math/components/BlockMath.vue +30 -30
- package/dist/elements/math/components/InlinerMath.vue +65 -65
- package/dist/elements/math/components/Katex.vue +88 -88
- package/dist/elements/math/components/MathGroup.vue +41 -41
- package/dist/elements/paragraph/Paragraph.vue +25 -25
- package/dist/elements/paragraph/_global.d.ts +27 -27
- package/dist/elements/paragraph/icon.svg +3 -3
- package/dist/elements/problem/_global.d.ts +112 -112
- package/dist/elements/problem/assets/actions/answer.svg +2 -2
- package/dist/elements/problem/assets/actions/check.svg +2 -2
- package/dist/elements/problem/assets/actions/generate.svg +2 -2
- package/dist/elements/problem/assets/actions/hint.svg +2 -2
- package/dist/elements/problem/assets/actions/note.svg +2 -2
- package/dist/elements/problem/assets/actions/solution.svg +2 -2
- package/dist/elements/problem/assets/icon.svg +2 -2
- package/dist/elements/problem/components/Problem.vue +22 -22
- package/dist/elements/problem/components/ProblemButton.vue +20 -20
- package/dist/elements/problem/components/ProblemContainer.vue +8 -8
- package/dist/elements/problem/components/ProblemContent.vue +356 -356
- package/dist/elements/problem/components/ProblemExpander.vue +7 -7
- package/dist/elements/problem/components/ProblemExpanderSection.vue +57 -57
- package/dist/elements/problem/components/ProblemHeader.vue +100 -100
- package/dist/elements/problem/components/Problems.vue +83 -83
- package/dist/elements/problem/components/SubProblem.vue +14 -14
- package/dist/elements/problem/components/expanders/Check.vue +153 -153
- package/dist/elements/problem/components/expanders/Checks.vue +146 -146
- package/dist/elements/problem/components/expanders/DefaultPlusSections.vue +38 -38
- package/dist/elements/problem/components/expanders/Hint.vue +26 -26
- package/dist/elements/table/Table.vue +100 -100
- package/dist/elements/table/_global.d.ts +36 -36
- package/dist/elements/video/Video.vue +110 -110
- package/dist/elements/video/_global.d.ts +18 -18
- package/package.json +4 -4
- package/types.d.ts +4 -4
|
@@ -1,270 +1,270 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
import {
|
|
3
|
-
onBeforeUnmount,
|
|
4
|
-
onMounted,
|
|
5
|
-
ref,
|
|
6
|
-
useTemplateRef,
|
|
7
|
-
watch,
|
|
8
|
-
watchEffect,
|
|
9
|
-
} from 'vue';
|
|
10
|
-
import { useFloating, autoUpdate, offset, shift } from '@floating-ui/vue';
|
|
11
|
-
import type { BlockSchema, ProseElement } from '@jsprose/core';
|
|
12
|
-
|
|
13
|
-
import { useProseContext } from '../../composables/context.js';
|
|
14
|
-
import { useElementIcon } from '../../composables/elementIcon.js';
|
|
15
|
-
import {
|
|
16
|
-
useIsAnchor,
|
|
17
|
-
useJumpToAnchor,
|
|
18
|
-
useResolveAnchor,
|
|
19
|
-
} from '../../composables/anchor.js';
|
|
20
|
-
import AsideMenu from './AsideMenu.vue';
|
|
21
|
-
import { useFormatTextState } from '../../composables/formatText.js';
|
|
22
|
-
|
|
23
|
-
const { element } = defineProps<{ element: ProseElement<BlockSchema> }>();
|
|
24
|
-
const { EruditIcon, EruditTransition } = useProseContext();
|
|
25
|
-
const elementIcon = await useElementIcon(element);
|
|
26
|
-
|
|
27
|
-
const formatTextState = useFormatTextState();
|
|
28
|
-
// Reset quote state making sure any opened quote does not leak outside
|
|
29
|
-
formatTextState.quote = 'closed';
|
|
30
|
-
|
|
31
|
-
const blockElement = useTemplateRef('block');
|
|
32
|
-
const asideElement = useTemplateRef('aside');
|
|
33
|
-
const asideMenuElement = useTemplateRef('asideMenu');
|
|
34
|
-
const hover = ref(false);
|
|
35
|
-
const menuVisible = ref(false);
|
|
36
|
-
|
|
37
|
-
const { floatingStyles: floatingMenuStyle } = useFloating(
|
|
38
|
-
asideElement,
|
|
39
|
-
asideMenuElement,
|
|
40
|
-
{
|
|
41
|
-
whileElementsMounted: autoUpdate,
|
|
42
|
-
placement: 'right-start',
|
|
43
|
-
strategy: 'fixed',
|
|
44
|
-
middleware: [offset(10), shift({ rootBoundary: 'viewport' })],
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const outsideClickHandler = (e: MouseEvent) => {
|
|
49
|
-
if (!menuVisible.value) return;
|
|
50
|
-
const aside = asideElement.value;
|
|
51
|
-
if (!aside) return;
|
|
52
|
-
if (aside.contains(e.target as Node)) return;
|
|
53
|
-
menuVisible.value = false;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const isAnchor = useIsAnchor(element);
|
|
57
|
-
const jumpToAnchor = useJumpToAnchor();
|
|
58
|
-
const resolveAnchor = useResolveAnchor();
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Waits for the element's position to be stable in the viewport before resolving the anchor.
|
|
62
|
-
* Stability is defined as the element remaining in the same position for some duration.
|
|
63
|
-
* If the element moves during this period, the timer is reset.
|
|
64
|
-
*
|
|
65
|
-
* @param element - The DOM element to monitor for position stability
|
|
66
|
-
* @param onStable - Callback to execute once the element is stable
|
|
67
|
-
* @param onCleanup - Vue's cleanup callback to register cleanup handlers
|
|
68
|
-
*/
|
|
69
|
-
const waitForStablePosition = (
|
|
70
|
-
element: HTMLElement,
|
|
71
|
-
onStable: () => void,
|
|
72
|
-
onCleanup: (fn: () => void) => void,
|
|
73
|
-
) => {
|
|
74
|
-
const stableDuration = 300;
|
|
75
|
-
|
|
76
|
-
let lastPosition = { top: 0, left: 0 };
|
|
77
|
-
let stableTimer: number | undefined;
|
|
78
|
-
let animationFrameId: number | undefined;
|
|
79
|
-
let resolved = false;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Continuously checks if the element's position has changed.
|
|
83
|
-
* If a change is detected, the stability timer is reset.
|
|
84
|
-
* Uses requestAnimationFrame for efficient, synchronized position monitoring.
|
|
85
|
-
*/
|
|
86
|
-
const checkStability = () => {
|
|
87
|
-
if (resolved) return;
|
|
88
|
-
|
|
89
|
-
const rect = element.getBoundingClientRect();
|
|
90
|
-
const currentPosition = { top: rect.top, left: rect.left };
|
|
91
|
-
|
|
92
|
-
// Detect position changes and reset stability timer
|
|
93
|
-
if (
|
|
94
|
-
lastPosition.top !== currentPosition.top ||
|
|
95
|
-
lastPosition.left !== currentPosition.left
|
|
96
|
-
) {
|
|
97
|
-
lastPosition = currentPosition;
|
|
98
|
-
clearTimeout(stableTimer);
|
|
99
|
-
|
|
100
|
-
// Start new 200ms countdown for stability
|
|
101
|
-
stableTimer = window.setTimeout(() => {
|
|
102
|
-
resolved = true;
|
|
103
|
-
onStable();
|
|
104
|
-
}, stableDuration);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Continue monitoring on next frame
|
|
108
|
-
animationFrameId = requestAnimationFrame(checkStability);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// Initialize tracking with current position
|
|
112
|
-
const initialRect = element.getBoundingClientRect();
|
|
113
|
-
lastPosition = { top: initialRect.top, left: initialRect.left };
|
|
114
|
-
|
|
115
|
-
// Start initial stability timer
|
|
116
|
-
stableTimer = window.setTimeout(() => {
|
|
117
|
-
resolved = true;
|
|
118
|
-
cancelAnimationFrame(animationFrameId!);
|
|
119
|
-
onStable();
|
|
120
|
-
}, stableDuration);
|
|
121
|
-
|
|
122
|
-
// Begin position monitoring
|
|
123
|
-
animationFrameId = requestAnimationFrame(checkStability);
|
|
124
|
-
|
|
125
|
-
// Cleanup timers and animation frames when effect is disposed
|
|
126
|
-
onCleanup(() => {
|
|
127
|
-
clearTimeout(stableTimer);
|
|
128
|
-
if (animationFrameId !== undefined) {
|
|
129
|
-
cancelAnimationFrame(animationFrameId);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
watch(menuVisible, (visible) => {
|
|
135
|
-
if (visible) {
|
|
136
|
-
document.addEventListener('click', outsideClickHandler);
|
|
137
|
-
} else {
|
|
138
|
-
document.removeEventListener('click', outsideClickHandler);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
onMounted(() => {
|
|
143
|
-
watchEffect(async (onCleanup) => {
|
|
144
|
-
if (isAnchor.value) {
|
|
145
|
-
jumpToAnchor(blockElement.value!);
|
|
146
|
-
waitForStablePosition(
|
|
147
|
-
blockElement.value!,
|
|
148
|
-
() => {
|
|
149
|
-
jumpToAnchor(blockElement.value!);
|
|
150
|
-
resolveAnchor();
|
|
151
|
-
},
|
|
152
|
-
onCleanup,
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
hover.value = blockElement.value!.matches(':hover');
|
|
158
|
-
|
|
159
|
-
blockElement.value!.addEventListener('mouseenter', () => {
|
|
160
|
-
hover.value = true;
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
blockElement.value!.addEventListener('mouseleave', () => {
|
|
164
|
-
hover.value = false;
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
onBeforeUnmount(() => {
|
|
169
|
-
document.removeEventListener('click', outsideClickHandler);
|
|
170
|
-
});
|
|
171
|
-
</script>
|
|
172
|
-
|
|
173
|
-
<template>
|
|
174
|
-
<div
|
|
175
|
-
ref="block"
|
|
176
|
-
:id="element.id"
|
|
177
|
-
:class="[$style.block, 'scroll-mt-big pr-(--proseAsideWidth)']"
|
|
178
|
-
>
|
|
179
|
-
<div :class="[$style.blockAbove, 'h-(--proseGap)']"></div>
|
|
180
|
-
|
|
181
|
-
<div class="relative">
|
|
182
|
-
<!-- Block Aside -->
|
|
183
|
-
<aside
|
|
184
|
-
ref="aside"
|
|
185
|
-
@click="menuVisible = !menuVisible"
|
|
186
|
-
class="group/aside absolute top-0 left-0 h-full w-(--proseAsideWidth)
|
|
187
|
-
cursor-pointer"
|
|
188
|
-
>
|
|
189
|
-
<!-- Aside Background -->
|
|
190
|
-
<div
|
|
191
|
-
class="micro:rounded-sm group-hocus/aside:bg-bg-accent absolute top-0
|
|
192
|
-
left-0 h-full w-full bg-transparent transition-[background]"
|
|
193
|
-
></div>
|
|
194
|
-
<!-- Aside Icon -->
|
|
195
|
-
<div class="sticky top-0">
|
|
196
|
-
<EruditTransition mode="out-in">
|
|
197
|
-
<div v-if="hover" :key="elementIcon">
|
|
198
|
-
<EruditIcon
|
|
199
|
-
:name="elementIcon"
|
|
200
|
-
class="text-text-dimmed group-hocus/aside:text-text m-auto
|
|
201
|
-
mt-0.5 aspect-square w-[80%] transition-[color]"
|
|
202
|
-
/>
|
|
203
|
-
</div>
|
|
204
|
-
</EruditTransition>
|
|
205
|
-
</div>
|
|
206
|
-
<!-- Aside Menu -->
|
|
207
|
-
<EruditTransition>
|
|
208
|
-
<div
|
|
209
|
-
ref="asideMenu"
|
|
210
|
-
:style="floatingMenuStyle"
|
|
211
|
-
v-if="menuVisible"
|
|
212
|
-
@click.stop
|
|
213
|
-
class="z-10 cursor-auto"
|
|
214
|
-
>
|
|
215
|
-
<AsideMenu :element />
|
|
216
|
-
</div>
|
|
217
|
-
</EruditTransition>
|
|
218
|
-
</aside>
|
|
219
|
-
|
|
220
|
-
<!-- Block Content -->
|
|
221
|
-
<main class="relative ml-(--proseAsideWidth)">
|
|
222
|
-
<slot></slot>
|
|
223
|
-
<!-- Anchor Overlay -->
|
|
224
|
-
<EruditTransition>
|
|
225
|
-
<div v-if="isAnchor">
|
|
226
|
-
<div
|
|
227
|
-
class="bg-brand animate-fade-out pointer-events-none absolute
|
|
228
|
-
-top-(--overlayPadding) -right-(--overlayPadding)
|
|
229
|
-
-bottom-(--overlayPadding) -left-(--overlayPadding) touch-none
|
|
230
|
-
rounded opacity-0 shadow-[0_0_18px_1px_var(--color-brand)]
|
|
231
|
-
[--overlayPadding:5px]"
|
|
232
|
-
></div>
|
|
233
|
-
</div>
|
|
234
|
-
</EruditTransition>
|
|
235
|
-
</main>
|
|
236
|
-
</div>
|
|
237
|
-
|
|
238
|
-
<div :class="[$style.blockBelow, 'h-(--proseGap)']"></div>
|
|
239
|
-
</div>
|
|
240
|
-
</template>
|
|
241
|
-
|
|
242
|
-
<style module>
|
|
243
|
-
/* Always hide corresponding block gaps for frist/last blocks. */
|
|
244
|
-
|
|
245
|
-
.block:first-child > .blockAbove {
|
|
246
|
-
display: none !important;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.block:last-child > .blockBelow {
|
|
250
|
-
display: none !important;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/* Hiding bottom block gap by default, to compensate next block's top gap. */
|
|
254
|
-
|
|
255
|
-
.blockBelow {
|
|
256
|
-
display: none;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/* When hover/focus show block bottom gap to increase hoverable area and hide next block's top gap so no gap duplication happens. */
|
|
260
|
-
|
|
261
|
-
.block:hover:has(+ .block) > .blockBelow,
|
|
262
|
-
.block:focus:has(+ .block) > .blockBelow {
|
|
263
|
-
display: block;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
.block:hover + .block > .blockAbove,
|
|
267
|
-
.block:focus + .block > .blockAbove {
|
|
268
|
-
display: none;
|
|
269
|
-
}
|
|
270
|
-
</style>
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import {
|
|
3
|
+
onBeforeUnmount,
|
|
4
|
+
onMounted,
|
|
5
|
+
ref,
|
|
6
|
+
useTemplateRef,
|
|
7
|
+
watch,
|
|
8
|
+
watchEffect,
|
|
9
|
+
} from 'vue';
|
|
10
|
+
import { useFloating, autoUpdate, offset, shift } from '@floating-ui/vue';
|
|
11
|
+
import type { BlockSchema, ProseElement } from '@jsprose/core';
|
|
12
|
+
|
|
13
|
+
import { useProseContext } from '../../composables/context.js';
|
|
14
|
+
import { useElementIcon } from '../../composables/elementIcon.js';
|
|
15
|
+
import {
|
|
16
|
+
useIsAnchor,
|
|
17
|
+
useJumpToAnchor,
|
|
18
|
+
useResolveAnchor,
|
|
19
|
+
} from '../../composables/anchor.js';
|
|
20
|
+
import AsideMenu from './AsideMenu.vue';
|
|
21
|
+
import { useFormatTextState } from '../../composables/formatText.js';
|
|
22
|
+
|
|
23
|
+
const { element } = defineProps<{ element: ProseElement<BlockSchema> }>();
|
|
24
|
+
const { EruditIcon, EruditTransition } = useProseContext();
|
|
25
|
+
const elementIcon = await useElementIcon(element);
|
|
26
|
+
|
|
27
|
+
const formatTextState = useFormatTextState();
|
|
28
|
+
// Reset quote state making sure any opened quote does not leak outside
|
|
29
|
+
formatTextState.quote = 'closed';
|
|
30
|
+
|
|
31
|
+
const blockElement = useTemplateRef('block');
|
|
32
|
+
const asideElement = useTemplateRef('aside');
|
|
33
|
+
const asideMenuElement = useTemplateRef('asideMenu');
|
|
34
|
+
const hover = ref(false);
|
|
35
|
+
const menuVisible = ref(false);
|
|
36
|
+
|
|
37
|
+
const { floatingStyles: floatingMenuStyle } = useFloating(
|
|
38
|
+
asideElement,
|
|
39
|
+
asideMenuElement,
|
|
40
|
+
{
|
|
41
|
+
whileElementsMounted: autoUpdate,
|
|
42
|
+
placement: 'right-start',
|
|
43
|
+
strategy: 'fixed',
|
|
44
|
+
middleware: [offset(10), shift({ rootBoundary: 'viewport' })],
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const outsideClickHandler = (e: MouseEvent) => {
|
|
49
|
+
if (!menuVisible.value) return;
|
|
50
|
+
const aside = asideElement.value;
|
|
51
|
+
if (!aside) return;
|
|
52
|
+
if (aside.contains(e.target as Node)) return;
|
|
53
|
+
menuVisible.value = false;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const isAnchor = useIsAnchor(element);
|
|
57
|
+
const jumpToAnchor = useJumpToAnchor();
|
|
58
|
+
const resolveAnchor = useResolveAnchor();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Waits for the element's position to be stable in the viewport before resolving the anchor.
|
|
62
|
+
* Stability is defined as the element remaining in the same position for some duration.
|
|
63
|
+
* If the element moves during this period, the timer is reset.
|
|
64
|
+
*
|
|
65
|
+
* @param element - The DOM element to monitor for position stability
|
|
66
|
+
* @param onStable - Callback to execute once the element is stable
|
|
67
|
+
* @param onCleanup - Vue's cleanup callback to register cleanup handlers
|
|
68
|
+
*/
|
|
69
|
+
const waitForStablePosition = (
|
|
70
|
+
element: HTMLElement,
|
|
71
|
+
onStable: () => void,
|
|
72
|
+
onCleanup: (fn: () => void) => void,
|
|
73
|
+
) => {
|
|
74
|
+
const stableDuration = 300;
|
|
75
|
+
|
|
76
|
+
let lastPosition = { top: 0, left: 0 };
|
|
77
|
+
let stableTimer: number | undefined;
|
|
78
|
+
let animationFrameId: number | undefined;
|
|
79
|
+
let resolved = false;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Continuously checks if the element's position has changed.
|
|
83
|
+
* If a change is detected, the stability timer is reset.
|
|
84
|
+
* Uses requestAnimationFrame for efficient, synchronized position monitoring.
|
|
85
|
+
*/
|
|
86
|
+
const checkStability = () => {
|
|
87
|
+
if (resolved) return;
|
|
88
|
+
|
|
89
|
+
const rect = element.getBoundingClientRect();
|
|
90
|
+
const currentPosition = { top: rect.top, left: rect.left };
|
|
91
|
+
|
|
92
|
+
// Detect position changes and reset stability timer
|
|
93
|
+
if (
|
|
94
|
+
lastPosition.top !== currentPosition.top ||
|
|
95
|
+
lastPosition.left !== currentPosition.left
|
|
96
|
+
) {
|
|
97
|
+
lastPosition = currentPosition;
|
|
98
|
+
clearTimeout(stableTimer);
|
|
99
|
+
|
|
100
|
+
// Start new 200ms countdown for stability
|
|
101
|
+
stableTimer = window.setTimeout(() => {
|
|
102
|
+
resolved = true;
|
|
103
|
+
onStable();
|
|
104
|
+
}, stableDuration);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Continue monitoring on next frame
|
|
108
|
+
animationFrameId = requestAnimationFrame(checkStability);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Initialize tracking with current position
|
|
112
|
+
const initialRect = element.getBoundingClientRect();
|
|
113
|
+
lastPosition = { top: initialRect.top, left: initialRect.left };
|
|
114
|
+
|
|
115
|
+
// Start initial stability timer
|
|
116
|
+
stableTimer = window.setTimeout(() => {
|
|
117
|
+
resolved = true;
|
|
118
|
+
cancelAnimationFrame(animationFrameId!);
|
|
119
|
+
onStable();
|
|
120
|
+
}, stableDuration);
|
|
121
|
+
|
|
122
|
+
// Begin position monitoring
|
|
123
|
+
animationFrameId = requestAnimationFrame(checkStability);
|
|
124
|
+
|
|
125
|
+
// Cleanup timers and animation frames when effect is disposed
|
|
126
|
+
onCleanup(() => {
|
|
127
|
+
clearTimeout(stableTimer);
|
|
128
|
+
if (animationFrameId !== undefined) {
|
|
129
|
+
cancelAnimationFrame(animationFrameId);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
watch(menuVisible, (visible) => {
|
|
135
|
+
if (visible) {
|
|
136
|
+
document.addEventListener('click', outsideClickHandler);
|
|
137
|
+
} else {
|
|
138
|
+
document.removeEventListener('click', outsideClickHandler);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
onMounted(() => {
|
|
143
|
+
watchEffect(async (onCleanup) => {
|
|
144
|
+
if (isAnchor.value) {
|
|
145
|
+
jumpToAnchor(blockElement.value!);
|
|
146
|
+
waitForStablePosition(
|
|
147
|
+
blockElement.value!,
|
|
148
|
+
() => {
|
|
149
|
+
jumpToAnchor(blockElement.value!);
|
|
150
|
+
resolveAnchor();
|
|
151
|
+
},
|
|
152
|
+
onCleanup,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
hover.value = blockElement.value!.matches(':hover');
|
|
158
|
+
|
|
159
|
+
blockElement.value!.addEventListener('mouseenter', () => {
|
|
160
|
+
hover.value = true;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
blockElement.value!.addEventListener('mouseleave', () => {
|
|
164
|
+
hover.value = false;
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
onBeforeUnmount(() => {
|
|
169
|
+
document.removeEventListener('click', outsideClickHandler);
|
|
170
|
+
});
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<template>
|
|
174
|
+
<div
|
|
175
|
+
ref="block"
|
|
176
|
+
:id="element.id"
|
|
177
|
+
:class="[$style.block, 'scroll-mt-big pr-(--proseAsideWidth)']"
|
|
178
|
+
>
|
|
179
|
+
<div :class="[$style.blockAbove, 'h-(--proseGap)']"></div>
|
|
180
|
+
|
|
181
|
+
<div class="relative">
|
|
182
|
+
<!-- Block Aside -->
|
|
183
|
+
<aside
|
|
184
|
+
ref="aside"
|
|
185
|
+
@click="menuVisible = !menuVisible"
|
|
186
|
+
class="group/aside absolute top-0 left-0 h-full w-(--proseAsideWidth)
|
|
187
|
+
cursor-pointer"
|
|
188
|
+
>
|
|
189
|
+
<!-- Aside Background -->
|
|
190
|
+
<div
|
|
191
|
+
class="micro:rounded-sm group-hocus/aside:bg-bg-accent absolute top-0
|
|
192
|
+
left-0 h-full w-full bg-transparent transition-[background]"
|
|
193
|
+
></div>
|
|
194
|
+
<!-- Aside Icon -->
|
|
195
|
+
<div class="sticky top-0">
|
|
196
|
+
<EruditTransition mode="out-in">
|
|
197
|
+
<div v-if="hover" :key="elementIcon">
|
|
198
|
+
<EruditIcon
|
|
199
|
+
:name="elementIcon"
|
|
200
|
+
class="text-text-dimmed group-hocus/aside:text-text m-auto
|
|
201
|
+
mt-0.5 aspect-square w-[80%] transition-[color]"
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
</EruditTransition>
|
|
205
|
+
</div>
|
|
206
|
+
<!-- Aside Menu -->
|
|
207
|
+
<EruditTransition>
|
|
208
|
+
<div
|
|
209
|
+
ref="asideMenu"
|
|
210
|
+
:style="floatingMenuStyle"
|
|
211
|
+
v-if="menuVisible"
|
|
212
|
+
@click.stop
|
|
213
|
+
class="z-10 cursor-auto"
|
|
214
|
+
>
|
|
215
|
+
<AsideMenu :element />
|
|
216
|
+
</div>
|
|
217
|
+
</EruditTransition>
|
|
218
|
+
</aside>
|
|
219
|
+
|
|
220
|
+
<!-- Block Content -->
|
|
221
|
+
<main class="relative ml-(--proseAsideWidth)">
|
|
222
|
+
<slot></slot>
|
|
223
|
+
<!-- Anchor Overlay -->
|
|
224
|
+
<EruditTransition>
|
|
225
|
+
<div v-if="isAnchor">
|
|
226
|
+
<div
|
|
227
|
+
class="bg-brand animate-fade-out pointer-events-none absolute
|
|
228
|
+
-top-(--overlayPadding) -right-(--overlayPadding)
|
|
229
|
+
-bottom-(--overlayPadding) -left-(--overlayPadding) touch-none
|
|
230
|
+
rounded opacity-0 shadow-[0_0_18px_1px_var(--color-brand)]
|
|
231
|
+
[--overlayPadding:5px]"
|
|
232
|
+
></div>
|
|
233
|
+
</div>
|
|
234
|
+
</EruditTransition>
|
|
235
|
+
</main>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<div :class="[$style.blockBelow, 'h-(--proseGap)']"></div>
|
|
239
|
+
</div>
|
|
240
|
+
</template>
|
|
241
|
+
|
|
242
|
+
<style module>
|
|
243
|
+
/* Always hide corresponding block gaps for frist/last blocks. */
|
|
244
|
+
|
|
245
|
+
.block:first-child > .blockAbove {
|
|
246
|
+
display: none !important;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.block:last-child > .blockBelow {
|
|
250
|
+
display: none !important;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Hiding bottom block gap by default, to compensate next block's top gap. */
|
|
254
|
+
|
|
255
|
+
.blockBelow {
|
|
256
|
+
display: none;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* When hover/focus show block bottom gap to increase hoverable area and hide next block's top gap so no gap duplication happens. */
|
|
260
|
+
|
|
261
|
+
.block:hover:has(+ .block) > .blockBelow,
|
|
262
|
+
.block:focus:has(+ .block) > .blockBelow {
|
|
263
|
+
display: block;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.block:hover + .block > .blockAbove,
|
|
267
|
+
.block:focus + .block > .blockAbove {
|
|
268
|
+
display: none;
|
|
269
|
+
}
|
|
270
|
+
</style>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
import type { InlinerSchema, ProseElement } from '@jsprose/core';
|
|
3
|
-
|
|
4
|
-
defineProps<{ element: ProseElement<InlinerSchema> }>();
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<span>
|
|
9
|
-
<slot></slot>
|
|
10
|
-
</span>
|
|
11
|
-
</template>
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { InlinerSchema, ProseElement } from '@jsprose/core';
|
|
3
|
+
|
|
4
|
+
defineProps<{ element: ProseElement<InlinerSchema> }>();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<span>
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</span>
|
|
11
|
+
</template>
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
.pswp__icn {
|
|
2
|
-
width: 40px;
|
|
3
|
-
height: 40px;
|
|
4
|
-
padding: 3px;
|
|
5
|
-
background: color-mix(in srgb, light-dark(#d7d7d7, #3c3c3c), transparent 30%);
|
|
6
|
-
border-radius: 50%;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
:root {
|
|
10
|
-
.pswp {
|
|
11
|
-
--pswp-bg: var(--color-bg-main);
|
|
12
|
-
--pswp-placeholder-bg: transparent;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
:root[data-theme='light'] {
|
|
17
|
-
.pswp {
|
|
18
|
-
--pswp-icon-color: #3f3f3f;
|
|
19
|
-
--pswp-icon-color-secondary: white;
|
|
20
|
-
--pswp-icon-stroke-color: #3f3f3f;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
1
|
+
.pswp__icn {
|
|
2
|
+
width: 40px;
|
|
3
|
+
height: 40px;
|
|
4
|
+
padding: 3px;
|
|
5
|
+
background: color-mix(in srgb, light-dark(#d7d7d7, #3c3c3c), transparent 30%);
|
|
6
|
+
border-radius: 50%;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
.pswp {
|
|
11
|
+
--pswp-bg: var(--color-bg-main);
|
|
12
|
+
--pswp-placeholder-bg: transparent;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
:root[data-theme='light'] {
|
|
17
|
+
.pswp {
|
|
18
|
+
--pswp-icon-color: #3f3f3f;
|
|
19
|
+
--pswp-icon-color-secondary: white;
|
|
20
|
+
--pswp-icon-stroke-color: #3f3f3f;
|
|
21
|
+
}
|
|
22
|
+
}
|