@kadal/deck 0.0.0-stage → 0.1.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.
Files changed (157) hide show
  1. package/LICENSE +661 -0
  2. package/LICENSING.md +40 -0
  3. package/README.md +170 -2
  4. package/dist/_virtual/_rolldown/runtime.js +13 -0
  5. package/dist/audience/index.d.ts +14 -0
  6. package/dist/audience/index.js +680 -0
  7. package/dist/backgrounds/index.d.ts +65 -0
  8. package/dist/backgrounds/index.js +604 -0
  9. package/dist/backgrounds/shared.d.ts +7 -0
  10. package/dist/backgrounds/shared.js +89 -0
  11. package/dist/cli/bin.d.ts +1 -0
  12. package/dist/cli/bin.js +8 -0
  13. package/dist/cli/host.js +111 -0
  14. package/dist/cli/index.d.ts +4 -0
  15. package/dist/cli/index.js +140 -0
  16. package/dist/cli/login.js +56 -0
  17. package/dist/cli/publish.js +159 -0
  18. package/dist/cli/vite.js +39 -0
  19. package/dist/compiler/code-meta.d.ts +33 -0
  20. package/dist/compiler/code-meta.js +81 -0
  21. package/dist/compiler/index.d.ts +8 -0
  22. package/dist/compiler/index.js +8 -0
  23. package/dist/compiler/magic-move.d.ts +26 -0
  24. package/dist/compiler/magic-move.js +33 -0
  25. package/dist/compiler/plain-text.js +7 -0
  26. package/dist/compiler/rehype-code.d.ts +18 -0
  27. package/dist/compiler/rehype-code.js +66 -0
  28. package/dist/compiler/rehype-glossary.d.ts +35 -0
  29. package/dist/compiler/rehype-glossary.js +211 -0
  30. package/dist/compiler/slots.d.ts +3 -0
  31. package/dist/compiler/slots.js +36 -0
  32. package/dist/compiler/split.d.ts +63 -0
  33. package/dist/compiler/split.js +195 -0
  34. package/dist/components/Background.d.ts +15 -0
  35. package/dist/components/Background.js +24 -0
  36. package/dist/components/Click.d.ts +54 -0
  37. package/dist/components/Click.js +129 -0
  38. package/dist/components/Code.d.ts +27 -0
  39. package/dist/components/Code.js +124 -0
  40. package/dist/components/Motion.d.ts +25 -0
  41. package/dist/components/Motion.js +45 -0
  42. package/dist/components/Sketch.d.ts +44 -0
  43. package/dist/components/Sketch.js +141 -0
  44. package/dist/components/Term.d.ts +63 -0
  45. package/dist/components/Term.js +492 -0
  46. package/dist/components/bling.d.ts +75 -0
  47. package/dist/components/bling.js +146 -0
  48. package/dist/components/content.d.ts +232 -0
  49. package/dist/components/content.js +637 -0
  50. package/dist/components/index.d.ts +10 -0
  51. package/dist/components/index.js +88 -0
  52. package/dist/core/clicks.d.ts +45 -0
  53. package/dist/core/clicks.js +75 -0
  54. package/dist/core/hash.d.ts +33 -0
  55. package/dist/core/hash.js +49 -0
  56. package/dist/core/keyboard.d.ts +17 -0
  57. package/dist/core/keyboard.js +198 -0
  58. package/dist/core/layout.d.ts +16 -0
  59. package/dist/core/layout.js +25 -0
  60. package/dist/core/model.d.ts +259 -0
  61. package/dist/core/model.js +377 -0
  62. package/dist/core/navigation.d.ts +74 -0
  63. package/dist/core/navigation.js +244 -0
  64. package/dist/core/poster.js +30 -0
  65. package/dist/core/store.d.ts +20 -0
  66. package/dist/core/store.js +64 -0
  67. package/dist/core/touch.js +27 -0
  68. package/dist/draw/DrawingLayer.js +315 -0
  69. package/dist/draw/strokes.js +188 -0
  70. package/dist/export/index.d.ts +68 -0
  71. package/dist/export/index.js +251 -0
  72. package/dist/export/run.js +81 -0
  73. package/dist/glossary/model.d.ts +81 -0
  74. package/dist/glossary/model.js +156 -0
  75. package/dist/glossary/parse.d.ts +27 -0
  76. package/dist/glossary/parse.js +90 -0
  77. package/dist/index.d.ts +31 -0
  78. package/dist/index.js +30 -0
  79. package/dist/live/LiveProvider.d.ts +20 -0
  80. package/dist/live/LiveProvider.js +450 -0
  81. package/dist/live/analytics.d.ts +15 -0
  82. package/dist/live/analytics.js +103 -0
  83. package/dist/live/client.d.ts +51 -0
  84. package/dist/live/client.js +193 -0
  85. package/dist/live/components.d.ts +64 -0
  86. package/dist/live/components.js +471 -0
  87. package/dist/live/context.d.ts +15 -0
  88. package/dist/live/context.js +28 -0
  89. package/dist/live/index.d.ts +7 -0
  90. package/dist/live/index.js +7 -0
  91. package/dist/live/protocol.d.ts +284 -0
  92. package/dist/live/protocol.js +42 -0
  93. package/dist/live/turnstile.js +49 -0
  94. package/dist/presenter/index.d.ts +20 -0
  95. package/dist/presenter/index.js +1211 -0
  96. package/dist/react/AutoAnimate.js +92 -0
  97. package/dist/react/Backgrounds.d.ts +11 -0
  98. package/dist/react/Backgrounds.js +154 -0
  99. package/dist/react/ConfettiLayer.js +85 -0
  100. package/dist/react/Deck.d.ts +35 -0
  101. package/dist/react/Deck.js +983 -0
  102. package/dist/react/Frame.d.ts +16 -0
  103. package/dist/react/Frame.js +174 -0
  104. package/dist/react/Hints.d.ts +3 -0
  105. package/dist/react/Hints.js +201 -0
  106. package/dist/react/Lightbox.js +54 -0
  107. package/dist/react/Overview.js +93 -0
  108. package/dist/react/PrintView.js +128 -0
  109. package/dist/react/ScrollView.js +128 -0
  110. package/dist/react/SlideFrame.d.ts +51 -0
  111. package/dist/react/SlideFrame.js +323 -0
  112. package/dist/react/Stage.js +151 -0
  113. package/dist/react/ThemeMenu.d.ts +5 -0
  114. package/dist/react/ThemeMenu.js +124 -0
  115. package/dist/react/Zoom.js +23 -0
  116. package/dist/react/chrome.js +339 -0
  117. package/dist/react/context.d.ts +165 -0
  118. package/dist/react/context.js +19 -0
  119. package/dist/react/hooks.d.ts +33 -0
  120. package/dist/react/hooks.js +70 -0
  121. package/dist/react/layouts.d.ts +21 -0
  122. package/dist/react/layouts.js +104 -0
  123. package/dist/react/mdx.d.ts +4 -0
  124. package/dist/react/mdx.js +33 -0
  125. package/dist/react/terms.d.ts +6 -0
  126. package/dist/react/terms.js +66 -0
  127. package/dist/renderers/Island.d.ts +57 -0
  128. package/dist/renderers/Island.js +223 -0
  129. package/dist/renderers/contract.d.ts +173 -0
  130. package/dist/renderers/contract.js +152 -0
  131. package/dist/renderers/index.d.ts +12 -0
  132. package/dist/renderers/index.js +16 -0
  133. package/dist/renderers/react.d.ts +16 -0
  134. package/dist/renderers/react.js +32 -0
  135. package/dist/renderers/web-components.d.ts +4 -0
  136. package/dist/renderers/web-components.js +103 -0
  137. package/dist/sketch/Canvas.js +49 -0
  138. package/dist/sketch/dsl.d.ts +131 -0
  139. package/dist/sketch/dsl.js +281 -0
  140. package/dist/sketch/index.d.ts +4 -0
  141. package/dist/sketch/index.js +3 -0
  142. package/dist/sketch/palette.d.ts +21 -0
  143. package/dist/sketch/palette.js +66 -0
  144. package/dist/sketch/render.d.ts +43 -0
  145. package/dist/sketch/render.js +198 -0
  146. package/dist/themes/base.css +1704 -0
  147. package/dist/themes/bling.css +592 -0
  148. package/dist/themes/glossary.css +317 -0
  149. package/dist/themes/live.css +1557 -0
  150. package/dist/themes/minimal.css +83 -0
  151. package/dist/themes/rla.css +92 -0
  152. package/dist/themes/sketch.css +121 -0
  153. package/dist/themes/stack.css +62 -0
  154. package/dist/vite/index.d.ts +15 -0
  155. package/dist/vite/index.js +320 -0
  156. package/package.json +159 -4
  157. package/tools/verify-island.mjs +150 -0
@@ -0,0 +1,128 @@
1
+ import { key } from "../core/navigation.js";
2
+ import { useDeck } from "./context.js";
3
+ import { useNav } from "./hooks.js";
4
+ import { BackgroundLayer } from "./Backgrounds.js";
5
+ import { SlideFrame } from "./SlideFrame.js";
6
+ import { useEffect, useRef, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/react/ScrollView.tsx
9
+ /**
10
+ * The scroll view (reveal.js 5): the deck flattened into one page you read by scrolling, every
11
+ * slide scaled to the viewport width, clicks revealed as you scroll through a slide's stretch of
12
+ * page. Switched on by `view: scroll`, `?view=scroll`, or a narrow viewport. The custom scrollbar
13
+ * marks each slide's segment.
14
+ */
15
+ function ScrollView() {
16
+ const deck = useDeck();
17
+ const root = useRef(null);
18
+ const [vw, setVw] = useState(() => typeof window === "undefined" ? 1920 : window.innerWidth);
19
+ const [vh, setVh] = useState(() => typeof window === "undefined" ? 1080 : window.innerHeight);
20
+ const [clicks, setClicks] = useState({});
21
+ const [current, setCurrent] = useState(0);
22
+ const totals = useNav((s) => s.totals);
23
+ useEffect(() => {
24
+ const onResize = () => {
25
+ setVw(window.innerWidth);
26
+ setVh(window.innerHeight);
27
+ };
28
+ window.addEventListener("resize", onResize);
29
+ return () => window.removeEventListener("resize", onResize);
30
+ }, []);
31
+ const scale = vw / deck.config.width;
32
+ const slideH = deck.config.height * scale;
33
+ const flat = deck.columns.flatMap((c) => c.slides.map((meta, v) => ({
34
+ meta,
35
+ h: c.h,
36
+ v
37
+ })));
38
+ const segment = (k) => Math.max(slideH, vh) + (totals[k] ?? 0) * vh * .6;
39
+ useEffect(() => {
40
+ const el = root.current;
41
+ if (!el) return;
42
+ const onScroll = () => {
43
+ const top = el.scrollTop;
44
+ let y = 0;
45
+ const next = {};
46
+ let cur = 0;
47
+ flat.forEach((s, i) => {
48
+ const k = key(s.h, s.v);
49
+ const seg = segment(k);
50
+ const t = totals[k] ?? 0;
51
+ if (top >= y - vh / 2 && top < y + seg - vh / 2) cur = i;
52
+ const progress = Math.min(1, Math.max(0, (top - y + vh * .35) / Math.max(1, seg - Math.max(slideH, vh) + vh * .6)));
53
+ next[k] = t ? Math.min(t, Math.floor(progress * (t + 1))) : 0;
54
+ y += seg;
55
+ });
56
+ setClicks(next);
57
+ setCurrent(cur);
58
+ };
59
+ onScroll();
60
+ el.addEventListener("scroll", onScroll, { passive: true });
61
+ return () => el.removeEventListener("scroll", onScroll);
62
+ }, [
63
+ flat.length,
64
+ vh,
65
+ slideH,
66
+ totals
67
+ ]);
68
+ useEffect(() => {
69
+ const s = flat[current];
70
+ if (s) deck.nav.update((st) => st.h === s.h && st.v === s.v ? st : {
71
+ ...st,
72
+ h: s.h,
73
+ v: s.v,
74
+ click: 0,
75
+ direction: "none"
76
+ });
77
+ }, [
78
+ current,
79
+ deck.nav,
80
+ flat
81
+ ]);
82
+ return /* @__PURE__ */ jsxs("div", {
83
+ ref: root,
84
+ className: "deck-scroll",
85
+ "data-snap": deck.config.scrollSnap || void 0,
86
+ children: [flat.map((s, i) => {
87
+ const k = key(s.h, s.v);
88
+ const options = deck.options.get(k);
89
+ return /* @__PURE__ */ jsx("section", {
90
+ className: ["deck-scroll-segment", i === current ? "is-current" : ""].join(" "),
91
+ style: { height: segment(k) },
92
+ children: /* @__PURE__ */ jsxs("div", {
93
+ className: "deck-scroll-sticky",
94
+ style: { height: Math.min(vh, slideH) },
95
+ children: [/* @__PURE__ */ jsx(BackgroundLayer, {
96
+ bg: options.background,
97
+ active: i === current
98
+ }), /* @__PURE__ */ jsx("div", {
99
+ className: "deck-scroll-canvas",
100
+ style: {
101
+ width: deck.config.width,
102
+ height: deck.config.height,
103
+ transform: `scale(${scale})`
104
+ },
105
+ children: /* @__PURE__ */ jsx(SlideFrame, {
106
+ meta: s.meta,
107
+ options,
108
+ h: s.h,
109
+ v: s.v,
110
+ active: i === current,
111
+ click: clicks[k] ?? 0,
112
+ className: "is-scroll"
113
+ })
114
+ })]
115
+ })
116
+ }, k);
117
+ }), deck.config.scrollProgress !== false ? /* @__PURE__ */ jsx("div", {
118
+ className: "deck-scrollbar",
119
+ "data-mode": deck.config.scrollProgress === true ? "always" : "auto",
120
+ children: flat.map((s, i) => /* @__PURE__ */ jsx("span", {
121
+ className: i === current ? "is-current" : "",
122
+ style: { flexGrow: 1 + (totals[key(s.h, s.v)] ?? 0) * .5 }
123
+ }, key(s.h, s.v)))
124
+ }) : null]
125
+ });
126
+ }
127
+ //#endregion
128
+ export { ScrollView };
@@ -0,0 +1,51 @@
1
+ import { SlideMeta, SlideOptions } from "../core/model.js";
2
+ import { key } from "../core/navigation.js";
3
+ import { ComponentType, ReactNode } from "react";
4
+ //#region src/react/SlideFrame.d.ts
5
+ /**
6
+ * One mounted slide: loads its module lazily, gives it a click registry, wraps it in its layout,
7
+ * and reports its click total to the navigation store. Neighbouring slides are mounted hidden so
8
+ * their media is ready (reveal.js's `viewDistance`); the presenter's next-slide preview, the
9
+ * overview and print pages mount slides in `preview` mode, where nothing autoplays.
10
+ */
11
+ export interface SlideFrameProps {
12
+ meta: SlideMeta;
13
+ options: SlideOptions;
14
+ h: number;
15
+ v: number;
16
+ active: boolean;
17
+ /** The click to render; defaults to the nav click when active, all clicks when past, 0 when future. */
18
+ click: number;
19
+ preview?: boolean;
20
+ /**
21
+ * When to give embedded pages their `src`: `lazy` (the default) only while the slide is the
22
+ * active one; `load` also on a mirror that is never "active" — the audience's copy — a beat
23
+ * later at a random moment, so a room full of phones does not hit the embedded site at once.
24
+ */
25
+ frames?: "lazy" | "load";
26
+ /** The slide a press lands on next: its embedded pages load now, hidden, and stay loaded. */
27
+ prefetch?: boolean;
28
+ /**
29
+ * Whether the slide's glossary terms answer to the pointer and to reveals from the room. On by
30
+ * default wherever the slide is real; a preview is inert — except the audience's mirror, which
31
+ * is a preview in every other respect and turns this on.
32
+ */
33
+ terms?: boolean;
34
+ className?: string;
35
+ style?: React.CSSProperties;
36
+ children?: ReactNode;
37
+ }
38
+ /**
39
+ * A slide's module, once.
40
+ *
41
+ * A REJECTION is deliberately not kept. The cache used to hold whatever the first attempt
42
+ * returned, so a slide that failed once failed for the life of the window: navigating back to it
43
+ * handed out the same rejected promise, and the only way out was to close the window and open a
44
+ * new one. A chunk can fail for reasons that pass — a dropped connection, or a window left open
45
+ * across a deploy, which is the usual one.
46
+ */
47
+ export declare function loadSlide(meta: SlideMeta): Promise<{
48
+ default: ComponentType<Record<string, unknown>>;
49
+ }>;
50
+ export declare function SlideFrame({ meta, options, h, v, active, click, preview, frames, prefetch, terms, className, style, children }: SlideFrameProps): import("react").JSX.Element;
51
+ //#endregion
@@ -0,0 +1,323 @@
1
+ import { setTotal } from "../core/navigation.js";
2
+ import { SlideContext, useDeck } from "./context.js";
3
+ import { createClickRegistry } from "../core/clicks.js";
4
+ import { layoutFor } from "./layouts.js";
5
+ import { MDXProvider } from "./mdx.js";
6
+ import { Component, useEffect, useMemo, useRef, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/react/SlideFrame.tsx
9
+ const modules = /* @__PURE__ */ new Map();
10
+ const magicModules = /* @__PURE__ */ new Map();
11
+ /**
12
+ * A slide's module, once.
13
+ *
14
+ * A REJECTION is deliberately not kept. The cache used to hold whatever the first attempt
15
+ * returned, so a slide that failed once failed for the life of the window: navigating back to it
16
+ * handed out the same rejected promise, and the only way out was to close the window and open a
17
+ * new one. A chunk can fail for reasons that pass — a dropped connection, or a window left open
18
+ * across a deploy, which is the usual one.
19
+ */
20
+ function loadSlide(meta) {
21
+ const cached = modules.get(meta.index);
22
+ if (cached) return cached;
23
+ const pending = meta.load().catch((error) => {
24
+ if (modules.get(meta.index) === pending) modules.delete(meta.index);
25
+ throw error;
26
+ });
27
+ modules.set(meta.index, pending);
28
+ return pending;
29
+ }
30
+ /**
31
+ * The shape of "this chunk is not on the server any more": the window was open when a new build
32
+ * went out, so the name it is asking for no longer exists and the host answers with the index
33
+ * page instead. Safari words it as a MIME type, Chrome and Firefox as a failed import.
34
+ */
35
+ const STALE_BUILD = /MIME type|dynamically imported module|Importing a module script failed/i;
36
+ /**
37
+ * Reload once to pick up the new build — never twice in a minute, or a slide that is genuinely
38
+ * broken becomes a window that reloads for ever.
39
+ */
40
+ function recoverFromStaleBuild() {
41
+ try {
42
+ const key = "deck.stale-reload";
43
+ const last = Number(sessionStorage.getItem(key) ?? 0);
44
+ if (Date.now() - last < 6e4) return false;
45
+ sessionStorage.setItem(key, String(Date.now()));
46
+ } catch {
47
+ return false;
48
+ }
49
+ window.location.reload();
50
+ return true;
51
+ }
52
+ function loadMagic(meta) {
53
+ if (!meta.loadMagic) return null;
54
+ const cached = magicModules.get(meta.index);
55
+ if (cached) return cached;
56
+ const pending = meta.loadMagic().catch((error) => {
57
+ if (magicModules.get(meta.index) === pending) magicModules.delete(meta.index);
58
+ throw error;
59
+ });
60
+ magicModules.set(meta.index, pending);
61
+ return pending;
62
+ }
63
+ /** One broken slide shows its error in place instead of taking the whole deck down. */
64
+ var SlideBoundary = class extends Component {
65
+ state = { error: null };
66
+ static getDerivedStateFromError(e) {
67
+ return { error: e instanceof Error ? e.message : String(e) };
68
+ }
69
+ componentDidCatch(_e, _info) {}
70
+ componentDidUpdate(prev) {
71
+ if (prev.resetKey !== this.props.resetKey && this.state.error) this.setState({ error: null });
72
+ }
73
+ render() {
74
+ if (this.state.error) return /* @__PURE__ */ jsxs("div", {
75
+ className: "deck-slide-error",
76
+ children: [/* @__PURE__ */ jsxs("strong", { children: [
77
+ "Slide ",
78
+ this.props.index + 1,
79
+ " failed to render"
80
+ ] }), /* @__PURE__ */ jsx("pre", { children: this.state.error })]
81
+ });
82
+ return this.props.children;
83
+ }
84
+ };
85
+ /**
86
+ * One embedded page at a time, per host.
87
+ *
88
+ * The slide you are on loads its page first and the slide AHEAD waits for it, because two loads of
89
+ * the same host at once is how a small server gets pushed past a limit — and the frame that loses
90
+ * is left showing an error page for the rest of the talk, which nothing out here can detect. It
91
+ * adds up faster than it looks: the presenter view and the share window each load their own copy,
92
+ * so one arrival at a live slide could be four requests landing together.
93
+ */
94
+ const loadingHosts = /* @__PURE__ */ new Set();
95
+ function hostOf(src) {
96
+ try {
97
+ return new URL(src, window.location.href).origin;
98
+ } catch {
99
+ return src;
100
+ }
101
+ }
102
+ function startFrame(frame, src) {
103
+ const host = hostOf(src);
104
+ loadingHosts.add(host);
105
+ const free = () => {
106
+ loadingHosts.delete(host);
107
+ frame.removeEventListener("load", free);
108
+ frame.removeEventListener("error", free);
109
+ };
110
+ frame.addEventListener("load", free);
111
+ frame.addEventListener("error", free);
112
+ setTimeout(free, 1e4);
113
+ frame.setAttribute("src", src);
114
+ }
115
+ /** The slide ahead: start when the host is quiet, or once waiting has stopped being worth it. */
116
+ function startFrameWhenHostIsFree(frame, src, timers, waited = 0) {
117
+ if (!loadingHosts.has(hostOf(src)) || waited >= 9e3) {
118
+ startFrame(frame, src);
119
+ return;
120
+ }
121
+ timers.push(setTimeout(() => startFrameWhenHostIsFree(frame, src, timers, waited + 600), 600));
122
+ }
123
+ function SlideFrame({ meta, options, h, v, active, click, preview = false, frames = "lazy", prefetch = false, terms = !preview, className, style, children }) {
124
+ const deck = useDeck();
125
+ const [Component, setComponent] = useState(null);
126
+ const [magic, setMagic] = useState(null);
127
+ const [failed, setFailed] = useState(null);
128
+ const registry = useMemo(() => createClickRegistry(options.clicksStart), [options.clicksStart, meta.index]);
129
+ const root = useRef(null);
130
+ useEffect(() => {
131
+ let alive = true;
132
+ loadSlide(meta).then((m) => alive && setComponent(() => m.default)).catch((error) => {
133
+ if (!alive) return;
134
+ const message = error instanceof Error ? error.message : String(error);
135
+ if (STALE_BUILD.test(message) && deck.mode !== "presenter" && recoverFromStaleBuild()) return;
136
+ setFailed(message);
137
+ });
138
+ const mm = loadMagic(meta);
139
+ if (mm) mm.then((m) => alive && setMagic(m.default)).catch(() => {});
140
+ return () => {
141
+ alive = false;
142
+ };
143
+ }, [meta, deck.mode]);
144
+ useEffect(() => {
145
+ if (preview) return;
146
+ const report = (total) => {
147
+ const t = options.clicks ?? total;
148
+ deck.nav.update((s) => setTotal(s, h, v, t));
149
+ };
150
+ report(registry.total());
151
+ return registry.onChange(report);
152
+ }, [
153
+ registry,
154
+ deck.nav,
155
+ h,
156
+ v,
157
+ options.clicks,
158
+ preview
159
+ ]);
160
+ useEffect(() => {
161
+ const el = root.current;
162
+ if (!el) return;
163
+ const timers = [];
164
+ for (const node of el.querySelectorAll("[data-src]")) {
165
+ const isIframe = node.tagName === "IFRAME";
166
+ const ahead = prefetch && !preview;
167
+ if (isIframe && !active && !ahead && frames !== "load" && node.dataset.preload === void 0) continue;
168
+ const src = node.dataset.src;
169
+ if (!src || node.getAttribute("src") === src) continue;
170
+ if (!isIframe) {
171
+ node.setAttribute("src", src);
172
+ continue;
173
+ }
174
+ const frame = node;
175
+ if (active && !preview) timers.push(setTimeout(() => startFrame(frame, src), 750));
176
+ else if (!active && ahead) timers.push(setTimeout(() => startFrameWhenHostIsFree(frame, src, timers), 1500));
177
+ else if (frames === "load") timers.push(setTimeout(() => startFrame(frame, src), 300 + Math.random() * 2200));
178
+ }
179
+ if (preview) return () => timers.forEach(clearTimeout);
180
+ const media = el.querySelectorAll("video, audio");
181
+ if (active) {
182
+ for (const m of media) if (deck.config.autoPlayMedia === true || deck.config.autoPlayMedia !== false && m.dataset.autoplay !== void 0) m.play().catch(() => {});
183
+ } else {
184
+ for (const m of media) {
185
+ if (m.dataset.ignore !== void 0) continue;
186
+ m.pause();
187
+ if (m.dataset.autoplay !== void 0) m.currentTime = 0;
188
+ }
189
+ if (!prefetch) {
190
+ for (const f of el.querySelectorAll("iframe[data-src]")) if (f.dataset.preload === void 0 && f.getAttribute("src")) f.removeAttribute("src");
191
+ }
192
+ }
193
+ return () => timers.forEach(clearTimeout);
194
+ }, [
195
+ active,
196
+ preview,
197
+ frames,
198
+ prefetch,
199
+ Component,
200
+ deck.config.autoPlayMedia
201
+ ]);
202
+ useEffect(() => {
203
+ if (!deck.config.autoFit || !Component) return;
204
+ const host = root.current;
205
+ if (!host) return;
206
+ const el = host.querySelector(".deck-layout");
207
+ if (!el || /deck-layout-(image|iframe|full|none)/.test(el.className)) return;
208
+ let raf = 0;
209
+ const fit = () => {
210
+ el.classList.remove("is-fitted");
211
+ el.style.removeProperty("--deck-fit");
212
+ const need = el.scrollHeight;
213
+ const have = el.clientHeight;
214
+ if (need > have + 2) {
215
+ el.style.setProperty("--deck-fit", Math.max(.45, have / need * .985).toFixed(3));
216
+ el.classList.add("is-fitted");
217
+ }
218
+ };
219
+ const schedule = () => {
220
+ cancelAnimationFrame(raf);
221
+ raf = requestAnimationFrame(fit);
222
+ };
223
+ schedule();
224
+ document.fonts?.ready.then(schedule).catch(() => {});
225
+ const onLoad = (e) => {
226
+ if (e.target?.tagName === "IMG") schedule();
227
+ };
228
+ host.addEventListener("load", onLoad, true);
229
+ const ro = new ResizeObserver(schedule);
230
+ ro.observe(host);
231
+ return () => {
232
+ cancelAnimationFrame(raf);
233
+ host.removeEventListener("load", onLoad, true);
234
+ ro.disconnect();
235
+ };
236
+ }, [
237
+ Component,
238
+ click,
239
+ active,
240
+ preview,
241
+ deck.config.autoFit
242
+ ]);
243
+ const value = useMemo(() => ({
244
+ meta,
245
+ options,
246
+ h,
247
+ v,
248
+ active,
249
+ click,
250
+ registry,
251
+ magic,
252
+ preview,
253
+ terms
254
+ }), [
255
+ meta,
256
+ options,
257
+ h,
258
+ v,
259
+ active,
260
+ click,
261
+ registry,
262
+ magic,
263
+ preview,
264
+ terms
265
+ ]);
266
+ const Layout = layoutFor(options.layout, deck.components);
267
+ const stateClasses = options.state.map((s) => `state-${s}`).join(" ");
268
+ return /* @__PURE__ */ jsx(SlideContext.Provider, {
269
+ value,
270
+ children: /* @__PURE__ */ jsxs("section", {
271
+ ref: root,
272
+ className: [
273
+ "deck-slide",
274
+ `layout-${options.layout}`,
275
+ options.className,
276
+ stateClasses,
277
+ className
278
+ ].filter(Boolean).join(" "),
279
+ "data-h": h,
280
+ "data-v": v,
281
+ "data-index": meta.index,
282
+ "data-id": meta.id,
283
+ "data-layout": options.layout,
284
+ "data-active": active ? "true" : void 0,
285
+ "data-click": click,
286
+ "data-color-scheme": options.colorScheme ?? void 0,
287
+ "aria-hidden": active ? void 0 : true,
288
+ style: {
289
+ ...style,
290
+ ...options.zoom !== 1 ? { zoom: options.zoom } : null
291
+ },
292
+ children: [failed ? /* @__PURE__ */ jsxs("div", {
293
+ className: "deck-slide-error",
294
+ children: [
295
+ /* @__PURE__ */ jsxs("strong", { children: [
296
+ "Slide ",
297
+ meta.index + 1,
298
+ " failed to load"
299
+ ] }),
300
+ /* @__PURE__ */ jsx("pre", { children: failed }),
301
+ /* @__PURE__ */ jsx("div", {
302
+ className: "deck-slide-error-actions",
303
+ children: /* @__PURE__ */ jsx("button", {
304
+ type: "button",
305
+ onClick: () => window.location.reload(),
306
+ children: "Reload the deck"
307
+ })
308
+ })
309
+ ]
310
+ }) : Component ? /* @__PURE__ */ jsx(SlideBoundary, {
311
+ index: meta.index,
312
+ resetKey: Component,
313
+ children: /* @__PURE__ */ jsx(MDXProvider, { children: /* @__PURE__ */ jsx(Layout, {
314
+ frontmatter: meta.frontmatter,
315
+ options,
316
+ children: /* @__PURE__ */ jsx(Component, { frontmatter: meta.frontmatter })
317
+ }) })
318
+ }) : /* @__PURE__ */ jsx("div", { className: "deck-slide-loading" }), children]
319
+ })
320
+ });
321
+ }
322
+ //#endregion
323
+ export { SlideFrame, loadMagic, loadSlide };
@@ -0,0 +1,151 @@
1
+ import { key } from "../core/navigation.js";
2
+ import { useDeck } from "./context.js";
3
+ import { useLayout, useNav } from "./hooks.js";
4
+ import { transitionMs } from "./Backgrounds.js";
5
+ import { SlideFrame } from "./SlideFrame.js";
6
+ import { animateFrom, snapshot } from "./AutoAnimate.js";
7
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ //#region src/react/Stage.tsx
10
+ /**
11
+ * The stage: the authored canvas, scaled to the viewport, holding the slides near the current one.
12
+ * The current slide is `present`; its neighbours are mounted hidden within `viewDistance` so their
13
+ * media is ready; the slide just left stays mounted as `leaving` for the length of the transition.
14
+ * Transitions and directions are CSS classes the base stylesheet animates.
15
+ */
16
+ function Stage() {
17
+ const deck = useDeck();
18
+ const layout = useLayout();
19
+ const { h, v, click, direction, seq, overview } = useNav((s) => ({
20
+ h: s.h,
21
+ v: s.v,
22
+ click: s.click,
23
+ direction: s.direction,
24
+ seq: s.seq,
25
+ overview: s.overview
26
+ }));
27
+ const stageRef = useRef(null);
28
+ const [leaving, setLeaving] = useState(null);
29
+ const lastPos = useRef({
30
+ h,
31
+ v
32
+ });
33
+ const lastClick = useRef(click);
34
+ const autoSnap = useRef(null);
35
+ const lastOptions = useRef(deck.options.get(key(h, v)) ?? null);
36
+ const current = deck.options.get(key(h, v)) ?? null;
37
+ const transition = current?.transition ?? deck.config.transition;
38
+ const speed = current?.transitionSpeed ?? deck.config.transitionSpeed;
39
+ const distance = typeof window !== "undefined" && window.innerWidth < 600 ? deck.config.mobileViewDistance : deck.config.viewDistance;
40
+ useLayoutEffect(() => {
41
+ const prev = lastPos.current;
42
+ if (prev.h === h && prev.v === v) {
43
+ lastClick.current = click;
44
+ return;
45
+ }
46
+ const leavingClick = lastClick.current;
47
+ lastClick.current = click;
48
+ const prevOptions = lastOptions.current;
49
+ const prevEl = stageRef.current?.querySelector(`.deck-slide[data-h="${prev.h}"][data-v="${prev.v}"]`);
50
+ const canAuto = deck.config.autoAnimate && prevOptions?.autoAnimate && current?.autoAnimate && (prevOptions.autoAnimateId ?? null) === (current.autoAnimateId ?? null) && Math.abs(prev.h - h) + Math.abs(prev.v - v) === 1;
51
+ autoSnap.current = canAuto && prevEl ? snapshot(prevEl) : null;
52
+ setLeaving({
53
+ h: prev.h,
54
+ v: prev.v,
55
+ seq,
56
+ click: leavingClick
57
+ });
58
+ lastPos.current = {
59
+ h,
60
+ v
61
+ };
62
+ lastOptions.current = current;
63
+ }, [
64
+ h,
65
+ v,
66
+ click,
67
+ seq,
68
+ current,
69
+ deck.config.autoAnimate
70
+ ]);
71
+ useEffect(() => {
72
+ if (!leaving) return;
73
+ const t = setTimeout(() => setLeaving(null), transitionMs(speed) + 50);
74
+ return () => clearTimeout(t);
75
+ }, [leaving, speed]);
76
+ useEffect(() => {
77
+ const snap = autoSnap.current;
78
+ if (!snap) return;
79
+ autoSnap.current = null;
80
+ const el = stageRef.current?.querySelector(`.deck-slide[data-h="${h}"][data-v="${v}"]`);
81
+ if (!el) return;
82
+ const frame = requestAnimationFrame(() => {
83
+ animateFrom(snap, el, {
84
+ duration: current?.autoAnimate ? deck.config.autoAnimateDuration : 0,
85
+ easing: deck.config.autoAnimateEasing,
86
+ unmatched: deck.config.autoAnimateUnmatched,
87
+ scale: layout.scale
88
+ });
89
+ });
90
+ return () => cancelAnimationFrame(frame);
91
+ }, [
92
+ h,
93
+ v,
94
+ seq
95
+ ]);
96
+ const frames = [];
97
+ deck.columns.forEach((col, ch) => {
98
+ col.slides.forEach((_s, cv) => {
99
+ const dh = ch - h;
100
+ if (Math.abs(dh) > distance) return;
101
+ if (dh !== 0 && cv > 0 && !(leaving && leaving.h === ch && leaving.v === cv)) return;
102
+ const isPresent = ch === h && cv === v;
103
+ const isLeaving = !!leaving && leaving.h === ch && leaving.v === cv && !isPresent;
104
+ if (!isPresent && !isLeaving && ch === h && Math.abs(cv - v) > 1) return;
105
+ const role = isPresent ? "present" : isLeaving ? "leaving" : dh < 0 ? "past" : dh > 0 ? "future" : cv < v ? "above" : "below";
106
+ frames.push({
107
+ h: ch,
108
+ v: cv,
109
+ role
110
+ });
111
+ });
112
+ });
113
+ const autoAnimating = !!leaving && autoSnapWas(leaving, current);
114
+ const stageStyle = {
115
+ width: deck.config.width,
116
+ height: deck.config.height,
117
+ transform: `translate(${layout.left}px, ${layout.top}px) scale(${layout.scale})`
118
+ };
119
+ return /* @__PURE__ */ jsx("div", {
120
+ ref: stageRef,
121
+ className: "deck-stage",
122
+ style: stageStyle,
123
+ "data-transition": autoAnimating || seq === 0 ? "none" : transition,
124
+ "data-speed": speed,
125
+ "data-direction": direction,
126
+ "data-overview": overview || void 0,
127
+ children: frames.map((f) => {
128
+ const meta = deck.columns[f.h].slides[f.v];
129
+ const options = deck.options.get(key(f.h, f.v));
130
+ const isPresent = f.role === "present";
131
+ const total = deck.nav.get().totals[key(f.h, f.v)] ?? 0;
132
+ const frameClick = isPresent ? click : f.role === "leaving" ? leaving?.click ?? 0 : f.role === "past" || f.role === "above" ? total : 0;
133
+ const prefetch = f.role === "below" && f.v === v + 1 || f.role === "future" && f.h === h + 1;
134
+ return /* @__PURE__ */ jsx(SlideFrame, {
135
+ meta,
136
+ options,
137
+ h: f.h,
138
+ v: f.v,
139
+ active: isPresent,
140
+ click: frameClick,
141
+ prefetch,
142
+ className: `is-${f.role}`
143
+ }, `${f.h},${f.v}`);
144
+ })
145
+ });
146
+ }
147
+ function autoSnapWas(_leaving, current) {
148
+ return !!current?.autoAnimate;
149
+ }
150
+ //#endregion
151
+ export { Stage };
@@ -0,0 +1,5 @@
1
+ //#region src/react/ThemeMenu.d.ts
2
+ export declare function ThemeMenu({ compact }: {
3
+ compact?: boolean;
4
+ }): import("react").JSX.Element;
5
+ //#endregion