@honeydeck/honeydeck 0.4.0 → 0.6.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/AGENTS.md +4 -4
- package/DEVELOPMENT.md +6 -4
- package/Readme.md +15 -15
- package/SPEC.md +5 -4
- package/docs/browser-frame.md +38 -0
- package/docs/components.md +16 -57
- package/docs/configuration.md +13 -0
- package/docs/customization.md +2 -0
- package/docs/deeper-dive.md +32 -7
- package/docs/getting-started.md +4 -2
- package/docs/index.json +258 -0
- package/docs/keyboard.md +35 -0
- package/docs/list-style.md +53 -0
- package/docs/local-development.md +3 -1
- package/docs/mermaid.md +2 -0
- package/docs/mobile.md +2 -0
- package/docs/navigation.md +3 -1
- package/docs/notes.md +40 -0
- package/docs/pdf-export.md +6 -2
- package/docs/presenter-mode.md +8 -3
- package/docs/reveal-group.md +60 -0
- package/docs/reveal-with.md +39 -0
- package/docs/reveal.md +35 -0
- package/docs/skills.md +5 -3
- package/docs/slides.md +2 -0
- package/docs/slidev-migration.md +5 -0
- package/docs/steps-and-reveals.md +145 -8
- package/docs/timeline-steps.md +50 -0
- package/package.json +6 -2
- package/skills/SPEC.md +6 -6
- package/skills/honeydeck/SKILL.md +9 -9
- package/skills/slidev-migration/SKILL.md +7 -6
- package/src/SPEC.md +8 -3
- package/src/cli/SPEC.md +3 -2
- package/src/cli/pdf.ts +11 -4
- package/src/remark/SPEC.md +102 -2
- package/src/remark/code-utils.ts +151 -0
- package/src/remark/shiki-code-blocks.ts +329 -136
- package/src/remark/step-numbering.ts +408 -103
- package/src/runtime/Deck.tsx +133 -116
- package/src/runtime/EffectiveColorModeContext.tsx +37 -0
- package/src/runtime/SPEC.md +21 -8
- package/src/runtime/SlideCanvas.tsx +19 -16
- package/src/runtime/SlideScaleContext.tsx +23 -0
- package/src/runtime/components/CodeBlock.tsx +19 -202
- package/src/runtime/components/CodeBlockCopyButton.tsx +64 -0
- package/src/runtime/components/CodeBlockShared.ts +17 -0
- package/src/runtime/components/Fade.tsx +51 -0
- package/src/runtime/components/FadeGroup.tsx +175 -0
- package/src/runtime/components/FadeWith.tsx +54 -0
- package/src/runtime/components/MagicCodeBlock.tsx +223 -0
- package/src/runtime/components/NavBar.tsx +1 -1
- package/src/runtime/components/NormalCodeBlock.tsx +128 -0
- package/src/runtime/components/Reveal.tsx +27 -27
- package/src/runtime/components/RevealGroup.tsx +143 -41
- package/src/runtime/components/RevealWith.tsx +63 -0
- package/src/runtime/components/SPEC.md +115 -10
- package/src/runtime/components/TimelineReveal.tsx +81 -0
- package/src/runtime/components/index.ts +13 -5
- package/src/runtime/components/timelineVisibility.ts +45 -0
- package/src/runtime/index.ts +9 -1
- package/src/runtime/navigation.ts +6 -4
- package/src/runtime/presentationApi.ts +449 -0
- package/src/runtime/views/PresenterCastButton.tsx +39 -0
- package/src/runtime/views/PresenterView.tsx +21 -4
- package/src/runtime/views/SPEC.md +7 -5
- package/src/theme/base.css +67 -2
- package/src/vite-plugin/SPEC.md +20 -2
- package/src/vite-plugin/index.ts +16 -2
- package/src/vite-plugin/splitter.ts +1 -0
- package/src/vite-plugin/virtual-modules.ts +16 -6
package/src/runtime/Deck.tsx
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - Overlay with `<OverviewView>` when overview mode is toggled
|
|
14
14
|
* - `<NavBar>` always present (auto-hides on desktop, visible on touch)
|
|
15
15
|
* - `useSwipeNav` for touch devices
|
|
16
|
-
* - `useSync` for BroadcastChannel
|
|
16
|
+
* - `useSync` for BroadcastChannel fallback and `usePresentationReceiverSync` for Presentation API receiver sync
|
|
17
17
|
* - Manual color mode override (system / light / dark) via NavBar
|
|
18
18
|
*
|
|
19
19
|
* ### Viewport scaling
|
|
@@ -41,12 +41,18 @@ import type { ColorMode } from "./components/ColorModeCycleButton.tsx";
|
|
|
41
41
|
import { ErrorBoundary } from "./components/ErrorBoundary.tsx";
|
|
42
42
|
import { NavBar } from "./components/NavBar.tsx";
|
|
43
43
|
import { SlideNumberBadge } from "./components/SlideNumberBadge.tsx";
|
|
44
|
+
import {
|
|
45
|
+
type EffectiveColorMode,
|
|
46
|
+
EffectiveColorModeProvider,
|
|
47
|
+
} from "./EffectiveColorModeContext.tsx";
|
|
44
48
|
import { rememberSlideRoute } from "./lastSlideRoute.ts";
|
|
45
49
|
import {
|
|
46
50
|
closeOverview,
|
|
47
51
|
toggleOverview as toggleOverviewRoute,
|
|
48
52
|
} from "./navigation.ts";
|
|
53
|
+
import { usePresentationReceiverSync } from "./presentationApi.ts";
|
|
49
54
|
import { useRoute } from "./router.ts";
|
|
55
|
+
import { SlideScaleProvider } from "./SlideScaleContext.tsx";
|
|
50
56
|
import {
|
|
51
57
|
BASE_HEIGHT,
|
|
52
58
|
BASE_WIDTH,
|
|
@@ -94,6 +100,8 @@ export function Deck() {
|
|
|
94
100
|
if (c === "light" || c === "dark") return c;
|
|
95
101
|
return "system";
|
|
96
102
|
});
|
|
103
|
+
const [effectiveColorMode, setEffectiveColorMode] =
|
|
104
|
+
useState<EffectiveColorMode>("light");
|
|
97
105
|
|
|
98
106
|
const route = useRoute();
|
|
99
107
|
const pointerLayout = usePointerLayout();
|
|
@@ -105,9 +113,9 @@ export function Deck() {
|
|
|
105
113
|
// ── Color mode: apply data-honeydeck-color-mode to <html> ──────────────────
|
|
106
114
|
useLayoutEffect(() => {
|
|
107
115
|
function applyMode(darkFromSystem: boolean) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
);
|
|
116
|
+
const mode = resolveEffectiveColorMode(colorMode, darkFromSystem);
|
|
117
|
+
applyHoneydeckColorMode(mode);
|
|
118
|
+
setEffectiveColorMode(mode);
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
@@ -147,11 +155,14 @@ export function Deck() {
|
|
|
147
155
|
});
|
|
148
156
|
}, [route]);
|
|
149
157
|
|
|
150
|
-
// ──
|
|
158
|
+
// ── Audience sync: BroadcastChannel + Presentation API receiver ──────
|
|
151
159
|
useSync({
|
|
152
160
|
enabled: route.view === "slide" || route.view === "overview",
|
|
153
161
|
isPresenter: false,
|
|
154
162
|
});
|
|
163
|
+
usePresentationReceiverSync({
|
|
164
|
+
enabled: route.view === "slide" || route.view === "overview",
|
|
165
|
+
});
|
|
155
166
|
|
|
156
167
|
const resetZoom = useCallback(() => {
|
|
157
168
|
setSlideZoom(1);
|
|
@@ -263,7 +274,8 @@ export function Deck() {
|
|
|
263
274
|
route.view === "slide" || route.view === "overview"
|
|
264
275
|
? { ...route, slide: currentSlide, step: currentStep }
|
|
265
276
|
: route;
|
|
266
|
-
const
|
|
277
|
+
const activeSlideScale = scale * slideZoom;
|
|
278
|
+
const slideTransform = `translate(${slidePan.x}px, ${slidePan.y}px) scale(${activeSlideScale})`;
|
|
267
279
|
const showSlideNumbers = config.showSlideNumbers === true;
|
|
268
280
|
const disableSlideTextSelection =
|
|
269
281
|
route.view === "slide" &&
|
|
@@ -275,129 +287,134 @@ export function Deck() {
|
|
|
275
287
|
// ---------------------------------------------------------------------------
|
|
276
288
|
|
|
277
289
|
return (
|
|
278
|
-
<
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
ref={stageRef}
|
|
282
|
-
className={`absolute inset-0 ${disableSlideTextSelection ? "select-none" : ""}`}
|
|
283
|
-
>
|
|
284
|
-
{/* ── Stage backdrop: themed bg at slide size, prevents flicker ──── */}
|
|
290
|
+
<EffectiveColorModeProvider mode={effectiveColorMode}>
|
|
291
|
+
<div className="fixed inset-0 overflow-hidden bg-black">
|
|
292
|
+
{/* ── Sizing container: fills viewport for scale calc ──────── */}
|
|
285
293
|
<div
|
|
286
|
-
|
|
287
|
-
className=
|
|
294
|
+
ref={stageRef}
|
|
295
|
+
className={`absolute inset-0 ${disableSlideTextSelection ? "select-none" : ""}`}
|
|
288
296
|
>
|
|
297
|
+
{/* ── Stage backdrop: themed bg at slide size, prevents flicker ──── */}
|
|
289
298
|
<div
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
height: BASE_HEIGHT,
|
|
294
|
-
transform: `scale(${scale})`,
|
|
295
|
-
transformOrigin: "center center",
|
|
296
|
-
}}
|
|
297
|
-
/>
|
|
298
|
-
</div>
|
|
299
|
-
|
|
300
|
-
{/* ── All slides (only current is visible) ──────────────────────── */}
|
|
301
|
-
{slideData.map((data, i) => {
|
|
302
|
-
const slideNumber = i + 1;
|
|
303
|
-
const isCurrent = slideNumber === currentSlide;
|
|
304
|
-
const { Component, stepCount, title, frontmatter, layoutName } = data;
|
|
305
|
-
const LayoutComponent = resolveLayout(layoutName);
|
|
306
|
-
|
|
307
|
-
return (
|
|
299
|
+
aria-hidden="true"
|
|
300
|
+
className="absolute inset-0 flex items-center justify-center"
|
|
301
|
+
>
|
|
308
302
|
<div
|
|
309
|
-
|
|
310
|
-
aria-hidden={!isCurrent}
|
|
311
|
-
className={`absolute inset-0 flex items-center justify-center ${
|
|
312
|
-
isCurrent
|
|
313
|
-
? "opacity-100 visible pointer-events-auto z-1"
|
|
314
|
-
: "opacity-0 invisible pointer-events-none z-0"
|
|
315
|
-
}`}
|
|
303
|
+
className="shrink-0 bg-background"
|
|
316
304
|
style={{
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
305
|
+
width: BASE_WIDTH,
|
|
306
|
+
height: BASE_HEIGHT,
|
|
307
|
+
transform: `scale(${scale})`,
|
|
308
|
+
transformOrigin: "center center",
|
|
320
309
|
}}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
{/* ── All slides (only current is visible) ──────────────────────── */}
|
|
314
|
+
{slideData.map((data, i) => {
|
|
315
|
+
const slideNumber = i + 1;
|
|
316
|
+
const isCurrent = slideNumber === currentSlide;
|
|
317
|
+
const { Component, stepCount, title, frontmatter, layoutName } =
|
|
318
|
+
data;
|
|
319
|
+
const LayoutComponent = resolveLayout(layoutName);
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<div
|
|
323
|
+
key={data.id}
|
|
324
|
+
aria-hidden={!isCurrent}
|
|
325
|
+
className={`absolute inset-0 flex items-center justify-center ${
|
|
326
|
+
isCurrent
|
|
327
|
+
? "opacity-100 visible pointer-events-auto z-1"
|
|
328
|
+
: "opacity-0 invisible pointer-events-none z-0"
|
|
329
|
+
}`}
|
|
330
|
+
style={{
|
|
331
|
+
transition: enableTransition
|
|
332
|
+
? `opacity 200ms ease, visibility 0s ${isCurrent ? "0s" : "200ms"}`
|
|
333
|
+
: "none",
|
|
334
|
+
}}
|
|
325
335
|
>
|
|
326
|
-
<
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
width: BASE_WIDTH,
|
|
330
|
-
height: BASE_HEIGHT,
|
|
331
|
-
transform: slideTransform,
|
|
332
|
-
transformOrigin: "center center",
|
|
333
|
-
}}
|
|
336
|
+
<TimelineProvider
|
|
337
|
+
stepIndex={isCurrent ? currentStep : 0}
|
|
338
|
+
stepCount={stepCount}
|
|
334
339
|
>
|
|
335
|
-
<
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
+
<SlideScaleProvider scale={activeSlideScale}>
|
|
341
|
+
<div
|
|
342
|
+
className="honeydeck-slide-canvas shrink-0 relative overflow-hidden box-border"
|
|
343
|
+
style={{
|
|
344
|
+
width: BASE_WIDTH,
|
|
345
|
+
height: BASE_HEIGHT,
|
|
346
|
+
transform: slideTransform,
|
|
347
|
+
transformOrigin: "center center",
|
|
348
|
+
}}
|
|
340
349
|
>
|
|
341
|
-
<
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
350
|
+
<ErrorBoundary slideNumber={slideNumber}>
|
|
351
|
+
<LayoutComponent
|
|
352
|
+
title={title || null}
|
|
353
|
+
frontmatter={frontmatter}
|
|
354
|
+
rawChildren={<Component />}
|
|
355
|
+
>
|
|
356
|
+
<Component />
|
|
357
|
+
</LayoutComponent>
|
|
358
|
+
</ErrorBoundary>
|
|
359
|
+
</div>
|
|
360
|
+
</SlideScaleProvider>
|
|
361
|
+
</TimelineProvider>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
})}
|
|
365
|
+
|
|
366
|
+
{showSlideNumbers && (
|
|
367
|
+
<div className="absolute inset-0 z-10 flex items-center justify-center pointer-events-none">
|
|
368
|
+
<div
|
|
369
|
+
className="honeydeck-slide-number-layer shrink-0 relative"
|
|
370
|
+
style={{
|
|
371
|
+
width: BASE_WIDTH,
|
|
372
|
+
height: BASE_HEIGHT,
|
|
373
|
+
transform: slideTransform,
|
|
374
|
+
transformOrigin: "center center",
|
|
375
|
+
}}
|
|
376
|
+
>
|
|
377
|
+
<SlideNumberBadge slide={currentSlide} />
|
|
378
|
+
</div>
|
|
346
379
|
</div>
|
|
347
|
-
)
|
|
348
|
-
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
349
382
|
|
|
350
|
-
{
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
</div>
|
|
363
|
-
</div>
|
|
383
|
+
{/* ── Overview overlay ──────────────────────────────────────────── */}
|
|
384
|
+
{isOverview && (
|
|
385
|
+
<OverviewView
|
|
386
|
+
currentSlide={currentSlide}
|
|
387
|
+
currentStep={currentStep}
|
|
388
|
+
onClose={() =>
|
|
389
|
+
closeOverview(controlRoute, {
|
|
390
|
+
slideCount: slideData.length,
|
|
391
|
+
getStepCount,
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
/>
|
|
364
395
|
)}
|
|
365
|
-
</div>
|
|
366
396
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
onToggleOverview={toggleOverview}
|
|
389
|
-
onSetColorMode={setColorMode}
|
|
390
|
-
isZoomed={slideZoom > 1}
|
|
391
|
-
onResetZoom={resetZoom}
|
|
392
|
-
toggleSignal={navBarToggleSignal}
|
|
393
|
-
showTextSelectionToggle={pointerLayout.isTouchDevice}
|
|
394
|
-
isTextSelectionEnabled={slideTextSelectionEnabled}
|
|
395
|
-
onToggleTextSelection={() =>
|
|
396
|
-
setSlideTextSelectionEnabled((value) => !value)
|
|
397
|
-
}
|
|
398
|
-
/>
|
|
399
|
-
)}
|
|
400
|
-
</div>
|
|
397
|
+
{/* ── Navigation bar ────────────────────────────────────────────── */}
|
|
398
|
+
{/* GAP-06: showSlideNumbers wired from config */}
|
|
399
|
+
{!isOverview && (
|
|
400
|
+
<NavBarWithHover
|
|
401
|
+
route={controlRoute}
|
|
402
|
+
isOverview={isOverview}
|
|
403
|
+
colorMode={colorMode}
|
|
404
|
+
onToggleOverview={toggleOverview}
|
|
405
|
+
onSetColorMode={setColorMode}
|
|
406
|
+
isZoomed={slideZoom > 1}
|
|
407
|
+
onResetZoom={resetZoom}
|
|
408
|
+
toggleSignal={navBarToggleSignal}
|
|
409
|
+
showTextSelectionToggle={pointerLayout.isTouchDevice}
|
|
410
|
+
isTextSelectionEnabled={slideTextSelectionEnabled}
|
|
411
|
+
onToggleTextSelection={() =>
|
|
412
|
+
setSlideTextSelectionEnabled((value) => !value)
|
|
413
|
+
}
|
|
414
|
+
/>
|
|
415
|
+
)}
|
|
416
|
+
</div>
|
|
417
|
+
</EffectiveColorModeProvider>
|
|
401
418
|
);
|
|
402
419
|
}
|
|
403
420
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createContext, type ReactNode, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
export type EffectiveColorMode = "light" | "dark";
|
|
4
|
+
|
|
5
|
+
const EffectiveColorModeContext = createContext<EffectiveColorMode | null>(
|
|
6
|
+
null,
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
type EffectiveColorModeProviderProps = {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
mode: EffectiveColorMode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function EffectiveColorModeProvider({
|
|
15
|
+
children,
|
|
16
|
+
mode,
|
|
17
|
+
}: EffectiveColorModeProviderProps) {
|
|
18
|
+
return (
|
|
19
|
+
<EffectiveColorModeContext.Provider value={mode}>
|
|
20
|
+
{children}
|
|
21
|
+
</EffectiveColorModeContext.Provider>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function readDocumentEffectiveColorMode(): EffectiveColorMode {
|
|
26
|
+
if (typeof document === "undefined") return "light";
|
|
27
|
+
return document.documentElement.getAttribute("data-honeydeck-color-mode") ===
|
|
28
|
+
"dark"
|
|
29
|
+
? "dark"
|
|
30
|
+
: "light";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useEffectiveColorMode(): EffectiveColorMode {
|
|
34
|
+
return (
|
|
35
|
+
useContext(EffectiveColorModeContext) ?? readDocumentEffectiveColorMode()
|
|
36
|
+
);
|
|
37
|
+
}
|
package/src/runtime/SPEC.md
CHANGED
|
@@ -6,23 +6,36 @@
|
|
|
6
6
|
|
|
7
7
|
### Concept
|
|
8
8
|
|
|
9
|
-
The **timeline** is a first-class Honeydeck concept. Each slide has a local timeline of steps. Code walkthroughs, reveal components, and statically registered custom component steps all hook into the same timeline.
|
|
9
|
+
The **timeline** is a first-class Honeydeck concept. Each slide has a local timeline of steps. Code walkthroughs, Magic Code blocks, reveal/fade components, and statically registered custom component steps all hook into the same timeline.
|
|
10
10
|
|
|
11
11
|
Fenced code blocks join the timeline when their metadata uses `{group|group}` step syntax. The first code group is the block's baseline active highlight and consumes no timeline step. Each later group consumes one timeline step.
|
|
12
12
|
|
|
13
|
+
Magic Code blocks join the same timeline. Each inner code fence contributes its normal code highlight states; Honeydeck advances through those highlight states before morphing to the next inner code fence. Magic Code step counting is `sum(inner fence highlight groups) - 1`.
|
|
14
|
+
|
|
13
15
|
### Timeline State
|
|
14
16
|
|
|
15
17
|
- Initial state: `stepIndex = 0` (no reveal or custom step content active)
|
|
16
18
|
- Stepped code blocks show their first metadata group immediately as their baseline state whenever the block is visible
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
+
- Magic Code blocks show their first inner code fence and its first metadata group immediately whenever the block is visible
|
|
20
|
+
- First reveal/fade/custom timeline entry activates at `stepIndex = 1`
|
|
21
|
+
- For code walkthroughs and Magic Code inner code states, the second and later metadata groups activate at their assigned timeline steps
|
|
19
22
|
- Timeline entries are determined by document order (top-to-bottom)
|
|
20
|
-
-
|
|
23
|
+
- Each authored `<Reveal>` or `<Fade>` adds one step to the slide timeline. Honeydeck injects an internal `at={n}` prop during compilation to connect each component to its assigned timeline step; `at` is not a user-facing API for step-producing components, and author-authored `at` values are build errors.
|
|
24
|
+
- `<Reveal>` content is visible when `stepIndex >= at`; `<Fade>` content is visible when `stepIndex < at`.
|
|
25
|
+
- `<Reveal name="...">` may name that reveal's assigned step for same-slide `<RevealWith target="...">` or `<FadeWith target="...">` synchronization. Reveal names are slide-local, literal, non-empty strings.
|
|
26
|
+
- `<RevealWith>` and `<FadeWith>` never add timeline steps. They sync with an existing step resolved either from `target="name"` on a same-slide `<Reveal>`, from numeric `target={n}`, or from literal numeric `at={n}` targeting an existing 1-based slide-local step.
|
|
27
|
+
- Reveal/fade components reserve hidden layout space by default; with `ephemeral`, hidden content renders `null` and reserves no space while presenter future previews still render a muted ghost.
|
|
21
28
|
- Timeline entries are flat within each slide, even when authored with nested
|
|
22
29
|
components. A parent `<Reveal>` or `<RevealGroup>` target consumes its step
|
|
23
|
-
first, then any nested
|
|
24
|
-
inside it are appended to the same slide timeline before the next
|
|
25
|
-
timeline target.
|
|
30
|
+
first, then any nested reveal/fade group, reveal/fade, or code walkthrough
|
|
31
|
+
steps inside it are appended to the same slide timeline before the next
|
|
32
|
+
sibling timeline target. `<RevealGroup listRevealMode="nested">` also treats
|
|
33
|
+
nested list items in direct child lists as timeline targets in depth-first
|
|
34
|
+
document order.
|
|
35
|
+
- `<RevealWith>` and `<FadeWith>` must not contain nested timeline producers because they do not add steps themselves. Target them at sibling timeline steps instead.
|
|
36
|
+
- `<Fade>` and `<FadeGroup>` targets must not contain nested timeline producers
|
|
37
|
+
because a faded parent would hide later nested steps. Put fade components
|
|
38
|
+
inside reveal targets instead.
|
|
26
39
|
- Custom React components can participate by wrapping their usage in
|
|
27
40
|
`<TimelineSteps steps={N}>`. The wrapper must be visible in slide MDX so the
|
|
28
41
|
compiler can reserve those steps at build time.
|
|
@@ -113,7 +126,7 @@ Reference page routes intentionally do not encode slide or step. During one brow
|
|
|
113
126
|
| `↓` / `s` | Next slide; in overview, timeline navigation is disabled: arrow keys move overview selection and WASD are no-ops |
|
|
114
127
|
| `↑` / `w` | Previous slide; in overview, timeline navigation is disabled: arrow keys move overview selection and WASD are no-ops |
|
|
115
128
|
| `o` | Toggle overview mode |
|
|
116
|
-
| `p` | Open presenter mode (
|
|
129
|
+
| `p` | Open presenter mode (same tab) |
|
|
117
130
|
| `f` | Toggle fullscreen |
|
|
118
131
|
| `Escape` | Exit overview; in reference pages, return to slides; browser-native Escape handles fullscreen exit |
|
|
119
132
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* so that surrounding layout can measure and position it correctly.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { SlideScaleProvider } from "./SlideScaleContext.tsx";
|
|
13
14
|
import {
|
|
14
15
|
BASE_HEIGHT,
|
|
15
16
|
BASE_WIDTH,
|
|
@@ -72,23 +73,25 @@ export function SlideCanvas({
|
|
|
72
73
|
stepCount={stepCount}
|
|
73
74
|
showFutureSteps={showFutureSteps}
|
|
74
75
|
>
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<LayoutComponent
|
|
85
|
-
title={title || null}
|
|
86
|
-
frontmatter={frontmatter}
|
|
87
|
-
rawChildren={<Component />}
|
|
76
|
+
<SlideScaleProvider scale={scale}>
|
|
77
|
+
<div
|
|
78
|
+
className="honeydeck-slide-canvas absolute top-0 left-0 overflow-hidden box-border"
|
|
79
|
+
style={{
|
|
80
|
+
width: BASE_WIDTH,
|
|
81
|
+
height: BASE_HEIGHT,
|
|
82
|
+
transform: `scale(${scale})`,
|
|
83
|
+
transformOrigin: "top left",
|
|
84
|
+
}}
|
|
88
85
|
>
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
<LayoutComponent
|
|
87
|
+
title={title || null}
|
|
88
|
+
frontmatter={frontmatter}
|
|
89
|
+
rawChildren={<Component />}
|
|
90
|
+
>
|
|
91
|
+
<Component />
|
|
92
|
+
</LayoutComponent>
|
|
93
|
+
</div>
|
|
94
|
+
</SlideScaleProvider>
|
|
92
95
|
</TimelineProvider>
|
|
93
96
|
</div>
|
|
94
97
|
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createContext, type ReactNode, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
const SlideScaleContext = createContext(1);
|
|
4
|
+
|
|
5
|
+
type SlideScaleProviderProps = {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
scale: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function SlideScaleProvider({
|
|
11
|
+
children,
|
|
12
|
+
scale,
|
|
13
|
+
}: SlideScaleProviderProps) {
|
|
14
|
+
return (
|
|
15
|
+
<SlideScaleContext.Provider value={scale}>
|
|
16
|
+
{children}
|
|
17
|
+
</SlideScaleContext.Provider>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useSlideScale(): number {
|
|
22
|
+
return useContext(SlideScaleContext);
|
|
23
|
+
}
|