@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.
- package/LICENSE +661 -0
- package/LICENSING.md +40 -0
- package/README.md +170 -2
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/audience/index.d.ts +14 -0
- package/dist/audience/index.js +680 -0
- package/dist/backgrounds/index.d.ts +65 -0
- package/dist/backgrounds/index.js +604 -0
- package/dist/backgrounds/shared.d.ts +7 -0
- package/dist/backgrounds/shared.js +89 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +8 -0
- package/dist/cli/host.js +111 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.js +140 -0
- package/dist/cli/login.js +56 -0
- package/dist/cli/publish.js +159 -0
- package/dist/cli/vite.js +39 -0
- package/dist/compiler/code-meta.d.ts +33 -0
- package/dist/compiler/code-meta.js +81 -0
- package/dist/compiler/index.d.ts +8 -0
- package/dist/compiler/index.js +8 -0
- package/dist/compiler/magic-move.d.ts +26 -0
- package/dist/compiler/magic-move.js +33 -0
- package/dist/compiler/plain-text.js +7 -0
- package/dist/compiler/rehype-code.d.ts +18 -0
- package/dist/compiler/rehype-code.js +66 -0
- package/dist/compiler/rehype-glossary.d.ts +35 -0
- package/dist/compiler/rehype-glossary.js +211 -0
- package/dist/compiler/slots.d.ts +3 -0
- package/dist/compiler/slots.js +36 -0
- package/dist/compiler/split.d.ts +63 -0
- package/dist/compiler/split.js +195 -0
- package/dist/components/Background.d.ts +15 -0
- package/dist/components/Background.js +24 -0
- package/dist/components/Click.d.ts +54 -0
- package/dist/components/Click.js +129 -0
- package/dist/components/Code.d.ts +27 -0
- package/dist/components/Code.js +124 -0
- package/dist/components/Motion.d.ts +25 -0
- package/dist/components/Motion.js +45 -0
- package/dist/components/Sketch.d.ts +44 -0
- package/dist/components/Sketch.js +141 -0
- package/dist/components/Term.d.ts +63 -0
- package/dist/components/Term.js +492 -0
- package/dist/components/bling.d.ts +75 -0
- package/dist/components/bling.js +146 -0
- package/dist/components/content.d.ts +232 -0
- package/dist/components/content.js +637 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.js +88 -0
- package/dist/core/clicks.d.ts +45 -0
- package/dist/core/clicks.js +75 -0
- package/dist/core/hash.d.ts +33 -0
- package/dist/core/hash.js +49 -0
- package/dist/core/keyboard.d.ts +17 -0
- package/dist/core/keyboard.js +198 -0
- package/dist/core/layout.d.ts +16 -0
- package/dist/core/layout.js +25 -0
- package/dist/core/model.d.ts +259 -0
- package/dist/core/model.js +377 -0
- package/dist/core/navigation.d.ts +74 -0
- package/dist/core/navigation.js +244 -0
- package/dist/core/poster.js +30 -0
- package/dist/core/store.d.ts +20 -0
- package/dist/core/store.js +64 -0
- package/dist/core/touch.js +27 -0
- package/dist/draw/DrawingLayer.js +315 -0
- package/dist/draw/strokes.js +188 -0
- package/dist/export/index.d.ts +68 -0
- package/dist/export/index.js +251 -0
- package/dist/export/run.js +81 -0
- package/dist/glossary/model.d.ts +81 -0
- package/dist/glossary/model.js +156 -0
- package/dist/glossary/parse.d.ts +27 -0
- package/dist/glossary/parse.js +90 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +30 -0
- package/dist/live/LiveProvider.d.ts +20 -0
- package/dist/live/LiveProvider.js +450 -0
- package/dist/live/analytics.d.ts +15 -0
- package/dist/live/analytics.js +103 -0
- package/dist/live/client.d.ts +51 -0
- package/dist/live/client.js +193 -0
- package/dist/live/components.d.ts +64 -0
- package/dist/live/components.js +471 -0
- package/dist/live/context.d.ts +15 -0
- package/dist/live/context.js +28 -0
- package/dist/live/index.d.ts +7 -0
- package/dist/live/index.js +7 -0
- package/dist/live/protocol.d.ts +284 -0
- package/dist/live/protocol.js +42 -0
- package/dist/live/turnstile.js +49 -0
- package/dist/presenter/index.d.ts +20 -0
- package/dist/presenter/index.js +1211 -0
- package/dist/react/AutoAnimate.js +92 -0
- package/dist/react/Backgrounds.d.ts +11 -0
- package/dist/react/Backgrounds.js +154 -0
- package/dist/react/ConfettiLayer.js +85 -0
- package/dist/react/Deck.d.ts +35 -0
- package/dist/react/Deck.js +983 -0
- package/dist/react/Frame.d.ts +16 -0
- package/dist/react/Frame.js +174 -0
- package/dist/react/Hints.d.ts +3 -0
- package/dist/react/Hints.js +201 -0
- package/dist/react/Lightbox.js +54 -0
- package/dist/react/Overview.js +93 -0
- package/dist/react/PrintView.js +128 -0
- package/dist/react/ScrollView.js +128 -0
- package/dist/react/SlideFrame.d.ts +51 -0
- package/dist/react/SlideFrame.js +323 -0
- package/dist/react/Stage.js +151 -0
- package/dist/react/ThemeMenu.d.ts +5 -0
- package/dist/react/ThemeMenu.js +124 -0
- package/dist/react/Zoom.js +23 -0
- package/dist/react/chrome.js +339 -0
- package/dist/react/context.d.ts +165 -0
- package/dist/react/context.js +19 -0
- package/dist/react/hooks.d.ts +33 -0
- package/dist/react/hooks.js +70 -0
- package/dist/react/layouts.d.ts +21 -0
- package/dist/react/layouts.js +104 -0
- package/dist/react/mdx.d.ts +4 -0
- package/dist/react/mdx.js +33 -0
- package/dist/react/terms.d.ts +6 -0
- package/dist/react/terms.js +66 -0
- package/dist/renderers/Island.d.ts +57 -0
- package/dist/renderers/Island.js +223 -0
- package/dist/renderers/contract.d.ts +173 -0
- package/dist/renderers/contract.js +152 -0
- package/dist/renderers/index.d.ts +12 -0
- package/dist/renderers/index.js +16 -0
- package/dist/renderers/react.d.ts +16 -0
- package/dist/renderers/react.js +32 -0
- package/dist/renderers/web-components.d.ts +4 -0
- package/dist/renderers/web-components.js +103 -0
- package/dist/sketch/Canvas.js +49 -0
- package/dist/sketch/dsl.d.ts +131 -0
- package/dist/sketch/dsl.js +281 -0
- package/dist/sketch/index.d.ts +4 -0
- package/dist/sketch/index.js +3 -0
- package/dist/sketch/palette.d.ts +21 -0
- package/dist/sketch/palette.js +66 -0
- package/dist/sketch/render.d.ts +43 -0
- package/dist/sketch/render.js +198 -0
- package/dist/themes/base.css +1704 -0
- package/dist/themes/bling.css +592 -0
- package/dist/themes/glossary.css +317 -0
- package/dist/themes/live.css +1557 -0
- package/dist/themes/minimal.css +83 -0
- package/dist/themes/rla.css +92 -0
- package/dist/themes/sketch.css +121 -0
- package/dist/themes/stack.css +62 -0
- package/dist/vite/index.d.ts +15 -0
- package/dist/vite/index.js +320 -0
- package/package.json +159 -4
- 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 };
|