@slidev-react/client 0.2.5

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.
Files changed (131) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README.md +16 -0
  4. package/package.json +44 -0
  5. package/src/addons/AddonProvider.tsx +25 -0
  6. package/src/addons/g2/G2Chart.tsx +370 -0
  7. package/src/addons/g2/chartPresets.ts +43 -0
  8. package/src/addons/g2/chartThemeTokens.ts +124 -0
  9. package/src/addons/g2/index.ts +36 -0
  10. package/src/addons/g2/style.css +31 -0
  11. package/src/addons/insight/Insight.tsx +10 -0
  12. package/src/addons/insight/InsightAddonProvider.tsx +20 -0
  13. package/src/addons/insight/SpotlightLayout.tsx +11 -0
  14. package/src/addons/insight/index.ts +17 -0
  15. package/src/addons/insight/style.css +34 -0
  16. package/src/addons/mermaid/MermaidDiagram.tsx +379 -0
  17. package/src/addons/mermaid/index.ts +10 -0
  18. package/src/addons/registry.test.ts +28 -0
  19. package/src/addons/registry.ts +61 -0
  20. package/src/addons/types.ts +6 -0
  21. package/src/app/App.tsx +125 -0
  22. package/src/app/README.md +18 -0
  23. package/src/app/providers/SlidesNavigationProvider.tsx +82 -0
  24. package/src/app/usePresentationBootstrap.ts +85 -0
  25. package/src/features/presentation/PresentationStatus.tsx +514 -0
  26. package/src/features/presentation/PrintSlidesView.tsx +350 -0
  27. package/src/features/presentation/browser.ts +5 -0
  28. package/src/features/presentation/draw/DrawOverlay.tsx +170 -0
  29. package/src/features/presentation/draw/DrawProvider.tsx +394 -0
  30. package/src/features/presentation/draw/persistence.test.ts +80 -0
  31. package/src/features/presentation/draw/persistence.ts +54 -0
  32. package/src/features/presentation/exportArtifacts.test.ts +48 -0
  33. package/src/features/presentation/exportArtifacts.ts +6 -0
  34. package/src/features/presentation/location.test.ts +73 -0
  35. package/src/features/presentation/location.ts +113 -0
  36. package/src/features/presentation/navigation/KeyboardController.tsx +73 -0
  37. package/src/features/presentation/navigation/PresentationNavbar.tsx +162 -0
  38. package/src/features/presentation/navigation/ShortcutsHelpOverlay.test.tsx +24 -0
  39. package/src/features/presentation/navigation/ShortcutsHelpOverlay.tsx +111 -0
  40. package/src/features/presentation/navigation/keyboardShortcuts.test.ts +74 -0
  41. package/src/features/presentation/navigation/keyboardShortcuts.ts +221 -0
  42. package/src/features/presentation/navigation/useSlidesNavigation.ts +15 -0
  43. package/src/features/presentation/overview/NotesOverview.tsx +200 -0
  44. package/src/features/presentation/overview/QuickOverview.tsx +126 -0
  45. package/src/features/presentation/path.ts +137 -0
  46. package/src/features/presentation/presenter/FlowTimelinePreview.test.tsx +54 -0
  47. package/src/features/presentation/presenter/FlowTimelinePreview.tsx +274 -0
  48. package/src/features/presentation/presenter/PresenterModeView.tsx +93 -0
  49. package/src/features/presentation/presenter/PresenterShell.tsx +286 -0
  50. package/src/features/presentation/presenter/PresenterSidePreview.tsx +68 -0
  51. package/src/features/presentation/presenter/PresenterTopProgress.tsx +28 -0
  52. package/src/features/presentation/presenter/SpeakerNotesPanel.tsx +51 -0
  53. package/src/features/presentation/presenter/StandaloneModeView.tsx +36 -0
  54. package/src/features/presentation/presenter/persistence.test.ts +26 -0
  55. package/src/features/presentation/presenter/persistence.ts +31 -0
  56. package/src/features/presentation/presenter/presentationSyncBridge.test.ts +87 -0
  57. package/src/features/presentation/presenter/presentationSyncBridge.ts +82 -0
  58. package/src/features/presentation/presenter/stage.ts +15 -0
  59. package/src/features/presentation/presenter/types.ts +30 -0
  60. package/src/features/presentation/presenter/useFullscreen.ts +58 -0
  61. package/src/features/presentation/presenter/useIdleCursor.ts +37 -0
  62. package/src/features/presentation/presenter/usePresentationFlowRuntime.ts +238 -0
  63. package/src/features/presentation/presenter/usePresenterChromeRuntime.ts +358 -0
  64. package/src/features/presentation/presenter/usePresenterSessionState.ts +226 -0
  65. package/src/features/presentation/presenter/useWakeLock.ts +110 -0
  66. package/src/features/presentation/recordingFilename.test.ts +46 -0
  67. package/src/features/presentation/recordingFilename.ts +56 -0
  68. package/src/features/presentation/reveal/Reveal.tsx +119 -0
  69. package/src/features/presentation/reveal/RevealContext.tsx +29 -0
  70. package/src/features/presentation/reveal/useRevealStep.ts +35 -0
  71. package/src/features/presentation/session.test.ts +122 -0
  72. package/src/features/presentation/session.ts +124 -0
  73. package/src/features/presentation/stage/SlidePreviewSurface.tsx +92 -0
  74. package/src/features/presentation/stage/SlideStage.tsx +159 -0
  75. package/src/features/presentation/stage/slideSurface.ts +71 -0
  76. package/src/features/presentation/stage/slideViewport.tsx +47 -0
  77. package/src/features/presentation/sync/adapters/broadcastChannelTransport.ts +40 -0
  78. package/src/features/presentation/sync/adapters/websocketTransport.ts +128 -0
  79. package/src/features/presentation/sync/model/presence.test.ts +42 -0
  80. package/src/features/presentation/sync/model/presence.ts +33 -0
  81. package/src/features/presentation/sync/model/replication.test.ts +72 -0
  82. package/src/features/presentation/sync/model/replication.ts +113 -0
  83. package/src/features/presentation/sync/model/status.test.ts +52 -0
  84. package/src/features/presentation/sync/model/status.ts +33 -0
  85. package/src/features/presentation/types.ts +1 -0
  86. package/src/features/presentation/usePresentationRecorder.ts +194 -0
  87. package/src/features/presentation/usePresentationSync.ts +423 -0
  88. package/src/index.ts +7 -0
  89. package/src/main.tsx +12 -0
  90. package/src/theme/ThemeProvider.test.ts +36 -0
  91. package/src/theme/ThemeProvider.tsx +79 -0
  92. package/src/theme/__mocks__/active-theme.ts +3 -0
  93. package/src/theme/base.css +14 -0
  94. package/src/theme/components.css +231 -0
  95. package/src/theme/index.css +11 -0
  96. package/src/theme/layouts/center.tsx +9 -0
  97. package/src/theme/layouts/cover.tsx +9 -0
  98. package/src/theme/layouts/default.tsx +5 -0
  99. package/src/theme/layouts/defaultLayouts.ts +20 -0
  100. package/src/theme/layouts/helpers.tsx +12 -0
  101. package/src/theme/layouts/image-right.tsx +21 -0
  102. package/src/theme/layouts/immersive.tsx +9 -0
  103. package/src/theme/layouts/resolveLayout.ts +9 -0
  104. package/src/theme/layouts/section.tsx +9 -0
  105. package/src/theme/layouts/statement.tsx +9 -0
  106. package/src/theme/layouts/two-cols.tsx +21 -0
  107. package/src/theme/layouts/types.ts +1 -0
  108. package/src/theme/layouts.css +133 -0
  109. package/src/theme/mark.css +379 -0
  110. package/src/theme/print.css +106 -0
  111. package/src/theme/prose.css +263 -0
  112. package/src/theme/registry.test.ts +21 -0
  113. package/src/theme/registry.ts +40 -0
  114. package/src/theme/tokens.css +148 -0
  115. package/src/theme/transitions.css +141 -0
  116. package/src/theme/types.ts +9 -0
  117. package/src/theme/useResolvedLayout.ts +24 -0
  118. package/src/types/generated-slides.d.ts +7 -0
  119. package/src/types/mdx-components.ts +7 -0
  120. package/src/types/plantuml-encoder.d.ts +7 -0
  121. package/src/ui/diagrams/PlantUmlDiagram.tsx +33 -0
  122. package/src/ui/mdx/MagicMoveDemo.tsx +114 -0
  123. package/src/ui/mdx/index.ts +21 -0
  124. package/src/ui/primitives/Annotate.test.tsx +64 -0
  125. package/src/ui/primitives/Annotate.tsx +82 -0
  126. package/src/ui/primitives/Badge.tsx +5 -0
  127. package/src/ui/primitives/Callout.tsx +24 -0
  128. package/src/ui/primitives/ChromeIconButton.tsx +58 -0
  129. package/src/ui/primitives/ChromePanel.tsx +79 -0
  130. package/src/ui/primitives/ChromeTag.tsx +70 -0
  131. package/src/ui/primitives/FormSelect.tsx +51 -0
@@ -0,0 +1,82 @@
1
+ import type { DrawStroke } from "../draw/DrawProvider";
2
+ import type { PresentationCursorState, PresentationSharedState } from "../types";
3
+
4
+ export interface LocalPresentationSyncState {
5
+ page: number;
6
+ cue: number;
7
+ cueTotal: number;
8
+ timer: number;
9
+ cursor: PresentationCursorState | null;
10
+ drawings: Record<string, DrawStroke[]>;
11
+ drawingsRevision: number;
12
+ }
13
+
14
+ export interface RemotePresentationPatchEffects {
15
+ remoteTimer?: number;
16
+ remoteCursor?: PresentationCursorState | null;
17
+ slideClicks?: {
18
+ slideId: string;
19
+ clicks: number;
20
+ };
21
+ slideClicksTotal?: {
22
+ slideId: string;
23
+ clicksTotal: number;
24
+ };
25
+ remoteDrawings?: {
26
+ revision: number;
27
+ strokesBySlideId: Record<string, DrawStroke[]>;
28
+ };
29
+ }
30
+
31
+ export function buildPresentationSharedState(
32
+ localState: LocalPresentationSyncState,
33
+ ): PresentationSharedState {
34
+ return {
35
+ ...localState,
36
+ lastUpdate: 0,
37
+ };
38
+ }
39
+
40
+ export function mapRemotePresentationPatch({
41
+ patch,
42
+ remotePage,
43
+ currentPage,
44
+ resolveSlideId,
45
+ }: {
46
+ patch: Partial<PresentationSharedState>;
47
+ remotePage: number;
48
+ currentPage: number;
49
+ resolveSlideId: (index: number) => string | null;
50
+ }): RemotePresentationPatchEffects {
51
+ const remoteSlideId = resolveSlideId(remotePage);
52
+ const effects: RemotePresentationPatchEffects = {};
53
+
54
+ if (typeof patch.timer === "number") effects.remoteTimer = patch.timer;
55
+
56
+ if ("cursor" in patch) {
57
+ effects.remoteCursor = remotePage === currentPage ? (patch.cursor ?? null) : null;
58
+ }
59
+
60
+ if (remoteSlideId && typeof patch.cueTotal === "number") {
61
+ effects.slideClicksTotal = {
62
+ slideId: remoteSlideId,
63
+ clicksTotal: patch.cueTotal,
64
+ };
65
+ }
66
+
67
+ if (remoteSlideId && typeof patch.cue === "number") {
68
+ effects.slideClicks = {
69
+ slideId: remoteSlideId,
70
+ clicks: patch.cue,
71
+ };
72
+ }
73
+
74
+ if (patch.drawings) {
75
+ effects.remoteDrawings = {
76
+ revision: typeof patch.drawingsRevision === "number" ? patch.drawingsRevision : Date.now(),
77
+ strokesBySlideId: patch.drawings,
78
+ };
79
+ }
80
+
81
+ return effects;
82
+ }
@@ -0,0 +1,15 @@
1
+ import type { SlidesViewport } from "@slidev-react/core/slides/viewport";
2
+
3
+ export const OVERVIEW_STAGE_WIDTH = 320;
4
+
5
+ export function resolveOverviewStageMetrics(viewport: SlidesViewport) {
6
+ const stageWidth = viewport.width;
7
+ const stageHeight = viewport.height;
8
+
9
+ return {
10
+ stageWidth,
11
+ stageHeight,
12
+ overviewStageHeight: Math.round((OVERVIEW_STAGE_WIDTH * stageHeight) / stageWidth),
13
+ overviewStageScale: OVERVIEW_STAGE_WIDTH / stageWidth,
14
+ };
15
+ }
@@ -0,0 +1,30 @@
1
+ import type { LayoutName } from "@slidev-react/core/slides/layout";
2
+ import type { SlideComponent } from "@slidev-react/core/slides/slide";
3
+ import type { SlidesViewport } from "@slidev-react/core/slides/viewport";
4
+ import type { TransitionName } from "@slidev-react/core/slides/transition";
5
+
6
+ export interface CompiledSlide {
7
+ id: string;
8
+ component: SlideComponent;
9
+ meta: {
10
+ title?: string;
11
+ layout?: LayoutName;
12
+ class?: string;
13
+ background?: string;
14
+ transition?: TransitionName;
15
+ clicks?: number;
16
+ notes?: string;
17
+ src?: string;
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Deck-wide visual configuration that flows through the presenter component tree.
23
+ * Groups the repeatedly passed `slides*` props to reduce prop drilling.
24
+ */
25
+ export interface SlidesConfig {
26
+ slidesViewport: SlidesViewport;
27
+ slidesLayout?: LayoutName;
28
+ slidesBackground?: string;
29
+ slidesTransition?: TransitionName;
30
+ }
@@ -0,0 +1,58 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+
3
+ function isFullscreenSupported() {
4
+ return (
5
+ typeof document !== "undefined" &&
6
+ typeof document.documentElement.requestFullscreen === "function"
7
+ );
8
+ }
9
+
10
+ export function useFullscreen() {
11
+ const [active, setActive] = useState(
12
+ () => typeof document !== "undefined" && document.fullscreenElement !== null,
13
+ );
14
+
15
+ useEffect(() => {
16
+ if (typeof document === "undefined") return;
17
+
18
+ const onFullscreenChange = () => {
19
+ setActive(document.fullscreenElement !== null);
20
+ };
21
+
22
+ document.addEventListener("fullscreenchange", onFullscreenChange);
23
+ return () => {
24
+ document.removeEventListener("fullscreenchange", onFullscreenChange);
25
+ };
26
+ }, []);
27
+
28
+ const enter = useCallback(async () => {
29
+ if (!isFullscreenSupported()) return;
30
+ await document.documentElement.requestFullscreen();
31
+ }, []);
32
+
33
+ const exit = useCallback(async () => {
34
+ if (typeof document === "undefined" || !document.fullscreenElement) return;
35
+ await document.exitFullscreen();
36
+ }, []);
37
+
38
+ const toggle = useCallback(async () => {
39
+ if (typeof document === "undefined") return;
40
+
41
+ if (document.fullscreenElement) {
42
+ await exit();
43
+ return;
44
+ }
45
+
46
+ await enter();
47
+ }, [enter, exit]);
48
+
49
+ return {
50
+ supported: isFullscreenSupported(),
51
+ active,
52
+ enter,
53
+ exit,
54
+ toggle,
55
+ };
56
+ }
57
+
58
+ export type FullscreenRuntime = ReturnType<typeof useFullscreen>;
@@ -0,0 +1,37 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ export function useIdleCursor({ enabled, idleMs = 2500 }: { enabled: boolean; idleMs?: number }) {
4
+ const [hidden, setHidden] = useState(false);
5
+
6
+ useEffect(() => {
7
+ if (!enabled) {
8
+ setHidden(false);
9
+ return;
10
+ }
11
+
12
+ let idleTimer = window.setTimeout(() => {
13
+ setHidden(true);
14
+ }, idleMs);
15
+
16
+ const markActive = () => {
17
+ setHidden(false);
18
+ window.clearTimeout(idleTimer);
19
+ idleTimer = window.setTimeout(() => {
20
+ setHidden(true);
21
+ }, idleMs);
22
+ };
23
+
24
+ window.addEventListener("pointermove", markActive, { passive: true });
25
+ window.addEventListener("pointerdown", markActive, { passive: true });
26
+ window.addEventListener("keydown", markActive);
27
+
28
+ return () => {
29
+ window.clearTimeout(idleTimer);
30
+ window.removeEventListener("pointermove", markActive);
31
+ window.removeEventListener("pointerdown", markActive);
32
+ window.removeEventListener("keydown", markActive);
33
+ };
34
+ }, [enabled, idleMs]);
35
+
36
+ return hidden;
37
+ }
@@ -0,0 +1,238 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { resolveCueTotal } from "@slidev-react/core/presentation/flow/cue";
3
+ import {
4
+ canAdvanceFlow,
5
+ canRetreatFlow,
6
+ clampCueIndex,
7
+ resolveAdvanceFlow,
8
+ resolveRetreatFlow,
9
+ } from "@slidev-react/core/presentation/flow/navigation";
10
+ import { type RevealContextValue } from "../reveal/RevealContext";
11
+ import type { CompiledSlide } from "./types";
12
+
13
+ interface SlidesNavigationLike {
14
+ currentIndex: number;
15
+ total: number;
16
+ goTo: (index: number) => void;
17
+ }
18
+
19
+ function resolveMaxCueStep(stepCounts: Map<number, number> | undefined) {
20
+ if (!stepCounts || stepCounts.size === 0) return 0;
21
+
22
+ let max = 0;
23
+ for (const step of stepCounts.keys()) {
24
+ if (step > max) max = step;
25
+ }
26
+
27
+ return max;
28
+ }
29
+
30
+ export function usePresentationFlowRuntime({
31
+ slides,
32
+ navigation,
33
+ }: {
34
+ slides: CompiledSlide[];
35
+ navigation: SlidesNavigationLike;
36
+ }) {
37
+ const currentSlide = slides[navigation.currentIndex];
38
+ const revealStepCountsRef = useRef<Record<string, Map<number, number>>>({});
39
+ const [clicksBySlideId, setClicksBySlideId] = useState<Record<string, number>>({});
40
+ const [clicksTotalBySlideId, setClicksTotalBySlideId] = useState<Record<string, number>>({});
41
+ const clicksBySlideIdRef = useRef(clicksBySlideId);
42
+ const clicksTotalBySlideIdRef = useRef(clicksTotalBySlideId);
43
+ const slideClicksConfig = useMemo(
44
+ () =>
45
+ Object.fromEntries(
46
+ slides.map((slide) => [slide.id, slide.meta.clicks ?? 0] as const),
47
+ ) as Record<string, number>,
48
+ [slides],
49
+ );
50
+
51
+ useEffect(() => {
52
+ clicksBySlideIdRef.current = clicksBySlideId;
53
+ }, [clicksBySlideId]);
54
+
55
+ useEffect(() => {
56
+ clicksTotalBySlideIdRef.current = clicksTotalBySlideId;
57
+ }, [clicksTotalBySlideId]);
58
+
59
+ const setSlideClicks = useCallback(
60
+ (slideId: string, next: number) => {
61
+ setClicksBySlideId((prev) => {
62
+ const total = resolveCueTotal({
63
+ configuredCues: slideClicksConfig[slideId],
64
+ detectedCues: clicksTotalBySlideIdRef.current[slideId],
65
+ });
66
+ const clamped = clampCueIndex(next, total);
67
+ if ((prev[slideId] ?? 0) === clamped) return prev;
68
+
69
+ const updated = {
70
+ ...prev,
71
+ [slideId]: clamped,
72
+ };
73
+ clicksBySlideIdRef.current = updated;
74
+ return updated;
75
+ });
76
+ },
77
+ [slideClicksConfig],
78
+ );
79
+
80
+ const setSlideClicksTotal = useCallback(
81
+ (slideId: string, nextTotal: number) => {
82
+ const safeTotal = resolveCueTotal({
83
+ configuredCues: slideClicksConfig[slideId],
84
+ detectedCues: nextTotal,
85
+ });
86
+
87
+ setClicksTotalBySlideId((prev) => {
88
+ if (prev[slideId] === safeTotal) return prev;
89
+
90
+ const updated = {
91
+ ...prev,
92
+ [slideId]: safeTotal,
93
+ };
94
+ clicksTotalBySlideIdRef.current = updated;
95
+ return updated;
96
+ });
97
+
98
+ setClicksBySlideId((prev) => {
99
+ const clamped = clampCueIndex(prev[slideId] ?? 0, safeTotal);
100
+ if ((prev[slideId] ?? 0) === clamped) return prev;
101
+
102
+ const updated = {
103
+ ...prev,
104
+ [slideId]: clamped,
105
+ };
106
+ clicksBySlideIdRef.current = updated;
107
+ return updated;
108
+ });
109
+ },
110
+ [slideClicksConfig],
111
+ );
112
+
113
+ const registerRevealStep = useCallback(
114
+ (step: number) => {
115
+ const slideId = currentSlide.id;
116
+ const normalizedStep = Math.max(Math.floor(step), 1);
117
+ const slideSteps = revealStepCountsRef.current[slideId] ?? new Map<number, number>();
118
+ revealStepCountsRef.current[slideId] = slideSteps;
119
+ slideSteps.set(normalizedStep, (slideSteps.get(normalizedStep) ?? 0) + 1);
120
+ setSlideClicksTotal(slideId, resolveMaxCueStep(slideSteps));
121
+
122
+ return () => {
123
+ const steps = revealStepCountsRef.current[slideId];
124
+ if (!steps) return;
125
+
126
+ const nextCount = (steps.get(normalizedStep) ?? 1) - 1;
127
+ if (nextCount <= 0) steps.delete(normalizedStep);
128
+ else steps.set(normalizedStep, nextCount);
129
+
130
+ if (steps.size === 0) delete revealStepCountsRef.current[slideId];
131
+
132
+ setSlideClicksTotal(slideId, resolveMaxCueStep(steps));
133
+ };
134
+ },
135
+ [currentSlide.id, setSlideClicksTotal],
136
+ );
137
+
138
+ const currentClicks = clicksBySlideId[currentSlide.id] ?? 0;
139
+ const currentClicksTotal = resolveCueTotal({
140
+ configuredCues: currentSlide.meta.clicks,
141
+ detectedCues: clicksTotalBySlideId[currentSlide.id],
142
+ });
143
+
144
+ const goToSlideAtStart = useCallback(
145
+ (index: number) => {
146
+ const targetSlide = slides[index];
147
+ if (!targetSlide) return;
148
+
149
+ setSlideClicks(targetSlide.id, 0);
150
+ navigation.goTo(index);
151
+ },
152
+ [navigation, setSlideClicks, slides],
153
+ );
154
+
155
+ const advanceReveal = useCallback(() => {
156
+ const nextState = resolveAdvanceFlow({
157
+ currentCueIndex: currentClicks,
158
+ currentCueTotal: currentClicksTotal,
159
+ currentPageIndex: navigation.currentIndex,
160
+ totalPages: navigation.total,
161
+ });
162
+ if (!nextState) return;
163
+
164
+ const targetSlide = slides[nextState.pageIndex];
165
+ if (!targetSlide) return;
166
+
167
+ setSlideClicks(targetSlide.id, nextState.cueIndex);
168
+ if (nextState.pageIndex !== navigation.currentIndex) navigation.goTo(nextState.pageIndex);
169
+ }, [currentClicks, currentClicksTotal, navigation, setSlideClicks, slides]);
170
+
171
+ const retreatReveal = useCallback(() => {
172
+ const previousSlideId = slides[navigation.currentIndex - 1]?.id ?? "";
173
+ const nextState = resolveRetreatFlow({
174
+ currentCueIndex: currentClicks,
175
+ currentPageIndex: navigation.currentIndex,
176
+ previousCueIndex: clicksBySlideIdRef.current[previousSlideId],
177
+ previousCueTotal: resolveCueTotal({
178
+ configuredCues: slideClicksConfig[previousSlideId],
179
+ detectedCues: clicksTotalBySlideIdRef.current[previousSlideId],
180
+ }),
181
+ });
182
+ if (!nextState) return;
183
+
184
+ const targetSlide = slides[nextState.pageIndex];
185
+ if (!targetSlide) return;
186
+
187
+ setSlideClicks(targetSlide.id, nextState.cueIndex);
188
+ if (nextState.pageIndex !== navigation.currentIndex) navigation.goTo(nextState.pageIndex);
189
+ }, [currentClicks, navigation, setSlideClicks, slideClicksConfig, slides]);
190
+
191
+ const revealContextValue = useMemo<RevealContextValue>(
192
+ () => ({
193
+ slideId: currentSlide.id,
194
+ clicks: currentClicks,
195
+ clicksTotal: currentClicksTotal,
196
+ setClicks: (next) => setSlideClicks(currentSlide.id, next),
197
+ registerStep: registerRevealStep,
198
+ advance: advanceReveal,
199
+ retreat: retreatReveal,
200
+ canAdvance: canAdvanceFlow({
201
+ currentCueIndex: currentClicks,
202
+ currentCueTotal: currentClicksTotal,
203
+ currentPageIndex: navigation.currentIndex,
204
+ totalPages: navigation.total,
205
+ }),
206
+ canRetreat: canRetreatFlow({
207
+ currentCueIndex: currentClicks,
208
+ currentPageIndex: navigation.currentIndex,
209
+ }),
210
+ }),
211
+ [
212
+ advanceReveal,
213
+ currentClicks,
214
+ currentClicksTotal,
215
+ currentSlide.id,
216
+ navigation.currentIndex,
217
+ navigation.total,
218
+ registerRevealStep,
219
+ retreatReveal,
220
+ setSlideClicks,
221
+ ],
222
+ );
223
+
224
+ return {
225
+ currentClicks,
226
+ currentClicksTotal,
227
+ canPrev: revealContextValue.canRetreat,
228
+ canNext: revealContextValue.canAdvance,
229
+ revealContextValue,
230
+ setSlideClicks,
231
+ setSlideClicksTotal,
232
+ goToSlideAtStart,
233
+ advanceReveal,
234
+ retreatReveal,
235
+ }
236
+ }
237
+
238
+ export type PresentationFlowRuntime = ReturnType<typeof usePresentationFlowRuntime>