@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,16 @@
1
+ //#region src/react/Frame.d.ts
2
+ export declare function FrameShell({ url, className, preload, title, poster, posterSize, scale }: {
3
+ url: string;
4
+ className?: string;
5
+ preload?: boolean;
6
+ title?: string;
7
+ poster?: string;
8
+ /** The pane's size in stage pixels, which names the captured poster. */
9
+ posterSize?: readonly [number, number];
10
+ /**
11
+ * Render the page at 1/scale of the pane and shrink it to fit: 0.5 shows a small pane the
12
+ * desktop layout it would get at twice the size.
13
+ */
14
+ scale?: number;
15
+ }): import("react").JSX.Element;
16
+ //#endregion
@@ -0,0 +1,174 @@
1
+ import { useDeck, useSlideOptional } from "./context.js";
2
+ import { POSTER_DEFAULT_SIZE, posterName, resolvePoster } from "../core/poster.js";
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/react/Frame.tsx
6
+ /**
7
+ * An embedded page inside a slide, with manners: it loads when its slide is shown (data-src),
8
+ * unloads when the slide is left, and sits behind a shield until you mean to interact — one
9
+ * click, or the F-hint on it — so wheel and arrow keys keep driving the deck until then. While
10
+ * it has the keyboard a "Done" button hands control back; leaving the slide does the same.
11
+ *
12
+ * Until the page has loaded — and wherever it never will (print, overview, the next-slide box,
13
+ * the scroll view) — a poster stands in: the one the deck names, else the screenshot `deck
14
+ * export` captured for this URL at this pane's size, else a card with the address.
15
+ *
16
+ * And it can always be loaded again, which it could not before. The loader in `SlideFrame` gives
17
+ * an iframe its `src` once — often from the prefetch, a slide ahead of arriving — and then skips
18
+ * any frame whose `src` already matches, so a page that failed up there stayed failed for the rest
19
+ * of the talk. Worse, a browser's own error page fires `load` like any other document, so nothing
20
+ * in here could tell a dead frame from a live one. Hence a Reload control that is always one click
21
+ * away, and a timeout for the page that never finishes at all.
22
+ */
23
+ const preconnected = /* @__PURE__ */ new Set();
24
+ function FrameShell({ url, className, preload = false, title, poster, posterSize, scale }) {
25
+ const deck = useDeck();
26
+ const [live, setLive] = useState(false);
27
+ const [loaded, setLoaded] = useState(false);
28
+ const [posterState, setPosterState] = useState("pending");
29
+ const [attempt, setAttempt] = useState(0);
30
+ /** Bumped to throw the iframe element away and start again with a clean one. */
31
+ const [nonce, setNonce] = useState(0);
32
+ const ref = useRef(null);
33
+ const slide = useSlideOptional();
34
+ const active = slide?.active ?? true;
35
+ const preview = slide?.preview ?? false;
36
+ const [w, h] = posterSize ?? POSTER_DEFAULT_SIZE;
37
+ const posterSrc = resolvePoster(poster ?? posterName(url, w, h), deck.base);
38
+ const zoom = scale && scale > 0 && scale !== 1 ? scale : 1;
39
+ useEffect(() => {
40
+ try {
41
+ const origin = new URL(url, window.location.href).origin;
42
+ if (origin === window.location.origin || preconnected.has(origin)) return;
43
+ preconnected.add(origin);
44
+ const link = document.createElement("link");
45
+ link.rel = "preconnect";
46
+ link.href = origin;
47
+ link.crossOrigin = "anonymous";
48
+ document.head.appendChild(link);
49
+ } catch {}
50
+ }, [url]);
51
+ useEffect(() => {
52
+ const el = ref.current;
53
+ if (!el) return;
54
+ const on = () => setLive(true);
55
+ el.addEventListener("deck:interact", on);
56
+ return () => el.removeEventListener("deck:interact", on);
57
+ }, []);
58
+ useEffect(() => {
59
+ if (!active) {
60
+ setLive(false);
61
+ setLoaded(false);
62
+ setAttempt(0);
63
+ }
64
+ }, [active]);
65
+ /**
66
+ * Load the page again, properly.
67
+ *
68
+ * Dropping `src` and putting the same address back does NOT reload a frame: removing the
69
+ * attribute leaves the document where it is, and assigning an address a frame already has is
70
+ * not a navigation — measured, it makes no request at all, which is why pressing Reload looked
71
+ * like pressing nothing. Two things are needed, and both are cheap:
72
+ *
73
+ * · a new element, so there is no document to keep and no address to match, and
74
+ * · a different address, so nothing cached — least of all a cached failure — can be handed
75
+ * back instead of a fresh load.
76
+ */
77
+ const reload = useCallback(() => {
78
+ setLoaded(false);
79
+ setAttempt((n) => n + 1);
80
+ setNonce((n) => n + 1);
81
+ }, []);
82
+ useEffect(() => {
83
+ if (!nonce) return;
84
+ const el = ref.current;
85
+ if (!el) return;
86
+ el.setAttribute("src", `${url}${url.includes("?") ? "&" : "?"}deck-reload=${Date.now()}`);
87
+ }, [nonce, url]);
88
+ useEffect(() => {
89
+ if (preview || !active || loaded || attempt >= 2) return;
90
+ const id = setTimeout(reload, 12e3);
91
+ return () => clearTimeout(id);
92
+ }, [
93
+ preview,
94
+ active,
95
+ loaded,
96
+ attempt,
97
+ reload
98
+ ]);
99
+ const touch = typeof window !== "undefined" && window.matchMedia?.("(pointer: coarse)").matches;
100
+ return /* @__PURE__ */ jsxs("div", {
101
+ className: [
102
+ "deck-frame",
103
+ live ? "is-live" : "",
104
+ loaded ? "is-loaded" : "",
105
+ preview ? "is-preview" : "",
106
+ className
107
+ ].filter(Boolean).join(" "),
108
+ children: [
109
+ /* @__PURE__ */ jsx("iframe", {
110
+ ref,
111
+ "data-src": url,
112
+ "data-preload": preload ? "" : void 0,
113
+ title: title ?? url,
114
+ allow: "fullscreen; geolocation; clipboard-write; autoplay",
115
+ style: zoom !== 1 ? {
116
+ width: `${100 / zoom}%`,
117
+ height: `${100 / zoom}%`,
118
+ maxWidth: "none",
119
+ maxHeight: "none",
120
+ transform: `scale(${zoom})`,
121
+ transformOrigin: "0 0"
122
+ } : void 0,
123
+ onLoad: (e) => {
124
+ if (e.currentTarget.getAttribute("src")) setLoaded(true);
125
+ }
126
+ }, nonce),
127
+ !loaded && posterState !== "missing" ? /* @__PURE__ */ jsx("img", {
128
+ className: "deck-frame-poster",
129
+ src: posterSrc,
130
+ alt: "",
131
+ "aria-hidden": true,
132
+ onLoad: () => setPosterState("ok"),
133
+ onError: () => setPosterState("missing")
134
+ }) : null,
135
+ !loaded && posterState === "missing" ? /* @__PURE__ */ jsx("div", {
136
+ className: "deck-frame-placeholder",
137
+ "aria-hidden": true,
138
+ children: /* @__PURE__ */ jsx("span", { children: url })
139
+ }) : null,
140
+ preview ? null : /* @__PURE__ */ jsx("button", {
141
+ type: "button",
142
+ className: "deck-frame-reload",
143
+ "data-hint": "reload",
144
+ title: "Load this page again",
145
+ onClick: (event) => {
146
+ event.stopPropagation();
147
+ reload();
148
+ },
149
+ children: "Reload"
150
+ }),
151
+ preview ? null : live ? /* @__PURE__ */ jsx("button", {
152
+ type: "button",
153
+ className: "deck-frame-done",
154
+ onClick: () => {
155
+ setLive(false);
156
+ document.activeElement?.blur?.();
157
+ window.focus();
158
+ },
159
+ children: "Done · back to the slides"
160
+ }) : /* @__PURE__ */ jsx("button", {
161
+ type: "button",
162
+ className: "deck-frame-shield",
163
+ "data-hint": "interact",
164
+ onClick: () => {
165
+ setLive(true);
166
+ setTimeout(() => ref.current?.focus(), 0);
167
+ },
168
+ children: /* @__PURE__ */ jsx("span", { children: touch ? "Tap to interact" : "Click to interact · F to focus with the keyboard" })
169
+ })
170
+ ]
171
+ });
172
+ }
173
+ //#endregion
174
+ export { FrameShell };
@@ -0,0 +1,3 @@
1
+ //#region src/react/Hints.d.ts
2
+ export declare function HintLayer(): import("react").ReactPortal | null;
3
+ //#endregion
@@ -0,0 +1,201 @@
1
+ import { useDeck } from "./context.js";
2
+ import { useActions, useUi } from "./hooks.js";
3
+ import { useCallback, useEffect, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/react/Hints.tsx
7
+ /**
8
+ * ─────────────────────────────────────────────────────────────────────────────
9
+ * HINTS — vim's `f`: label every clickable thing, type the label to use it
10
+ * ─────────────────────────────────────────────────────────────────────────────
11
+ *
12
+ * Press F and every link, button, embedded page, image preview and input on the current slide
13
+ * (plus the deck's own controls) gets a short home-row label. Type it and the target is used:
14
+ * links open (external ones in a new tab), buttons click, an embedded page takes the keyboard,
15
+ * an image opens in the lightbox. Escape or F again leaves. Labels are one letter when there
16
+ * are few targets and two when there are many, as in Vimium.
17
+ * ─────────────────────────────────────────────────────────────────────────────
18
+ */
19
+ const SELECTOR = [
20
+ "a[href]",
21
+ "button:not([disabled])",
22
+ "[role='button']",
23
+ "iframe",
24
+ "input:not([type='hidden']):not([disabled])",
25
+ "select",
26
+ "textarea",
27
+ "summary",
28
+ "video[controls]",
29
+ "audio[controls]",
30
+ "[data-hint]",
31
+ ".deck-image[data-preview]"
32
+ ].join(",");
33
+ const LETTERS = "asdfghjkl";
34
+ function labelsFor(n) {
35
+ if (n <= 9) return [...LETTERS].slice(0, n);
36
+ const out = [];
37
+ for (const a of LETTERS) for (const b of LETTERS) out.push(a + b);
38
+ return out.slice(0, n);
39
+ }
40
+ function kindOf(el) {
41
+ const tag = el.tagName;
42
+ if (tag === "A") return "link";
43
+ if (tag === "IFRAME") return "iframe";
44
+ if (tag === "INPUT" || tag === "SELECT" || tag === "TEXTAREA") return "input";
45
+ if (tag === "BUTTON" || el.getAttribute("role") === "button") return "button";
46
+ return "other";
47
+ }
48
+ function visible(el) {
49
+ const r = el.getBoundingClientRect();
50
+ if (r.width < 4 || r.height < 4) return false;
51
+ if (r.bottom < 0 || r.right < 0 || r.top > window.innerHeight || r.left > window.innerWidth) return false;
52
+ const cs = getComputedStyle(el);
53
+ if (cs.visibility === "hidden" || cs.display === "none") return false;
54
+ return cs.opacity !== "0" || el.hasAttribute("data-hint");
55
+ }
56
+ function HintLayer() {
57
+ const deck = useDeck();
58
+ const on = useUi((s) => s.hints);
59
+ const actions = useActions();
60
+ const [targets, setTargets] = useState([]);
61
+ const [typed, setTyped] = useState("");
62
+ const collect = useCallback(() => {
63
+ const roots = [];
64
+ const slide = document.querySelector(".deck-slide.is-present");
65
+ if (slide) roots.push(slide);
66
+ for (const extra of document.querySelectorAll(".deck-controls, .deck-theme-menu, .deck-audience, .deck-presenter-tools, .deck-room-panel")) roots.push(extra);
67
+ const seen = /* @__PURE__ */ new Set();
68
+ const els = [];
69
+ for (const root of roots) for (const el of root.querySelectorAll(SELECTOR)) {
70
+ if (seen.has(el) || !visible(el) || el.closest(".deck-hints")) continue;
71
+ if (el.tagName !== "A" && el.closest("a[href]")) continue;
72
+ if (el.tagName === "IFRAME" && el.closest(".deck-frame:not(.is-live)")) continue;
73
+ seen.add(el);
74
+ els.push(el);
75
+ }
76
+ els.sort((a, b) => {
77
+ const ra = a.getBoundingClientRect();
78
+ const rb = b.getBoundingClientRect();
79
+ return ra.top - rb.top || ra.left - rb.left;
80
+ });
81
+ const labels = labelsFor(els.length);
82
+ setTargets(els.map((el, i) => ({
83
+ el,
84
+ label: labels[i],
85
+ rect: el.getBoundingClientRect(),
86
+ kind: kindOf(el)
87
+ })));
88
+ }, []);
89
+ useEffect(() => {
90
+ if (!on) {
91
+ setTargets([]);
92
+ setTyped("");
93
+ return;
94
+ }
95
+ collect();
96
+ const again = () => collect();
97
+ window.addEventListener("resize", again);
98
+ window.addEventListener("scroll", again, true);
99
+ const unsub = deck.nav.subscribe(again);
100
+ return () => {
101
+ window.removeEventListener("resize", again);
102
+ window.removeEventListener("scroll", again, true);
103
+ unsub();
104
+ };
105
+ }, [
106
+ on,
107
+ collect,
108
+ deck.nav
109
+ ]);
110
+ const activate = useCallback((t) => {
111
+ actions.toggleHints(false);
112
+ const el = t.el;
113
+ if (t.kind === "iframe") {
114
+ el.dispatchEvent(new CustomEvent("deck:interact", { bubbles: true }));
115
+ el.focus();
116
+ return;
117
+ }
118
+ if (t.kind === "link") {
119
+ const a = el;
120
+ if (a.target === "_blank" || a.origin && a.origin !== window.location.origin) window.open(a.href, "_blank", "noopener");
121
+ else a.click();
122
+ return;
123
+ }
124
+ if (t.kind === "input") {
125
+ el.focus();
126
+ return;
127
+ }
128
+ el.click();
129
+ }, [actions]);
130
+ useEffect(() => {
131
+ if (!on) return;
132
+ const onKey = (e) => {
133
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
134
+ if (e.key === "Escape") {
135
+ e.preventDefault();
136
+ e.stopPropagation();
137
+ actions.toggleHints(false);
138
+ return;
139
+ }
140
+ if (e.key === "Backspace") {
141
+ e.preventDefault();
142
+ e.stopPropagation();
143
+ setTyped((t) => t.slice(0, -1));
144
+ return;
145
+ }
146
+ if (e.key.length !== 1) return;
147
+ const key = e.key.toLowerCase();
148
+ if (!LETTERS.includes(key)) {
149
+ e.preventDefault();
150
+ e.stopPropagation();
151
+ return;
152
+ }
153
+ e.preventDefault();
154
+ e.stopPropagation();
155
+ setTyped((prev) => {
156
+ const next = prev + key;
157
+ const hit = targets.find((t) => t.label === next);
158
+ if (hit) {
159
+ queueMicrotask(() => activate(hit));
160
+ return "";
161
+ }
162
+ return targets.some((t) => t.label.startsWith(next)) ? next : "";
163
+ });
164
+ };
165
+ window.addEventListener("keydown", onKey, true);
166
+ return () => window.removeEventListener("keydown", onKey, true);
167
+ }, [
168
+ on,
169
+ targets,
170
+ activate,
171
+ actions
172
+ ]);
173
+ if (!on || typeof document === "undefined") return null;
174
+ return createPortal(/* @__PURE__ */ jsxs("div", {
175
+ className: "deck-hints",
176
+ "aria-live": "polite",
177
+ children: [targets.map((t) => {
178
+ const match = typed && t.label.startsWith(typed);
179
+ const dim = typed && !match;
180
+ return /* @__PURE__ */ jsx("span", {
181
+ className: [
182
+ "deck-hint",
183
+ `kind-${t.kind}`,
184
+ match ? "is-match" : "",
185
+ dim ? "is-dim" : ""
186
+ ].filter(Boolean).join(" "),
187
+ style: {
188
+ left: Math.max(2, t.rect.left),
189
+ top: Math.max(2, t.rect.top - 10)
190
+ },
191
+ onClick: () => activate(t),
192
+ children: match ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("b", { children: typed }), t.label.slice(typed.length)] }) : t.label
193
+ }, t.label);
194
+ }), targets.length === 0 ? /* @__PURE__ */ jsx("span", {
195
+ className: "deck-hint-empty",
196
+ children: "Nothing to focus on this slide"
197
+ }) : null]
198
+ }), document.body);
199
+ }
200
+ //#endregion
201
+ export { HintLayer };
@@ -0,0 +1,54 @@
1
+ import { useActions, useUi } from "./hooks.js";
2
+ import { Suspense, lazy } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/react/Lightbox.tsx
5
+ const SketchCanvas = lazy(() => import("../sketch/Canvas.js"));
6
+ /** A full-screen overlay for an image, a video or a web page (reveal.js 5.2's lightbox). */
7
+ function Lightbox() {
8
+ const box = useUi((s) => s.lightbox);
9
+ const actions = useActions();
10
+ if (!box) return null;
11
+ return /* @__PURE__ */ jsxs("div", {
12
+ className: "deck-lightbox",
13
+ role: "dialog",
14
+ "aria-label": "Preview",
15
+ onClick: actions.closeLightbox,
16
+ children: [/* @__PURE__ */ jsx("button", {
17
+ type: "button",
18
+ className: "deck-lightbox-close",
19
+ "aria-label": "Close",
20
+ onClick: actions.closeLightbox,
21
+ children: "×"
22
+ }), /* @__PURE__ */ jsxs("div", {
23
+ className: "deck-lightbox-media",
24
+ "data-fit": box.fit,
25
+ onClick: (e) => e.stopPropagation(),
26
+ children: [
27
+ box.kind === "image" ? /* @__PURE__ */ jsx("img", {
28
+ src: box.src,
29
+ alt: ""
30
+ }) : null,
31
+ box.kind === "video" ? /* @__PURE__ */ jsx("video", {
32
+ src: box.src,
33
+ controls: true,
34
+ autoPlay: true,
35
+ playsInline: true
36
+ }) : null,
37
+ box.kind === "iframe" ? /* @__PURE__ */ jsx("iframe", {
38
+ src: box.src,
39
+ title: "Preview",
40
+ allow: "fullscreen; autoplay"
41
+ }) : null,
42
+ box.kind === "sketch" ? /* @__PURE__ */ jsx(Suspense, {
43
+ fallback: /* @__PURE__ */ jsx("div", {
44
+ className: "deck-sketch-loading",
45
+ children: "Opening the canvas…"
46
+ }),
47
+ children: /* @__PURE__ */ jsx(SketchCanvas, { id: box.src })
48
+ }) : null
49
+ ]
50
+ })]
51
+ });
52
+ }
53
+ //#endregion
54
+ export { Lightbox };
@@ -0,0 +1,93 @@
1
+ import { key } from "../core/navigation.js";
2
+ import { useDeck } from "./context.js";
3
+ import { useActions, useNav } from "./hooks.js";
4
+ import { SlideFrame } from "./SlideFrame.js";
5
+ import { useEffect } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/react/Overview.tsx
8
+ /**
9
+ * The overview: every column as a row of its stack, each slide live and scaled down, the current
10
+ * one ringed. Click a tile to go there; Esc or O leaves. Slides render in preview mode so nothing
11
+ * plays until it is chosen.
12
+ */
13
+ function Overview() {
14
+ const deck = useDeck();
15
+ const { overview, h, v } = useNav((s) => ({
16
+ overview: s.overview,
17
+ h: s.h,
18
+ v: s.v
19
+ }));
20
+ const actions = useActions();
21
+ useEffect(() => {
22
+ if (!overview) return;
23
+ document.querySelector(".deck-overview .is-current")?.scrollIntoView({
24
+ block: "center",
25
+ inline: "center"
26
+ });
27
+ }, [
28
+ overview,
29
+ h,
30
+ v
31
+ ]);
32
+ if (!overview) return null;
33
+ const tileW = 320;
34
+ const scale = tileW / deck.config.width;
35
+ const tileH = deck.config.height * scale;
36
+ return /* @__PURE__ */ jsx("div", {
37
+ className: "deck-overview",
38
+ role: "dialog",
39
+ "aria-label": "Slide overview",
40
+ onClick: () => actions.toggleOverview(false),
41
+ children: /* @__PURE__ */ jsx("div", {
42
+ className: "deck-overview-grid",
43
+ onClick: (e) => e.stopPropagation(),
44
+ children: deck.columns.map((col) => /* @__PURE__ */ jsx("div", {
45
+ className: "deck-overview-column",
46
+ children: col.slides.map((meta, cv) => {
47
+ const options = deck.options.get(key(col.h, cv));
48
+ const isCurrent = col.h === h && cv === v;
49
+ return /* @__PURE__ */ jsxs("button", {
50
+ type: "button",
51
+ className: ["deck-overview-tile", isCurrent ? "is-current" : ""].join(" "),
52
+ style: {
53
+ width: tileW,
54
+ height: tileH
55
+ },
56
+ onClick: () => {
57
+ actions.goto(col.h, cv);
58
+ actions.toggleOverview(false);
59
+ },
60
+ "aria-label": meta.title ?? `Slide ${col.h + 1}.${cv + 1}`,
61
+ children: [/* @__PURE__ */ jsx("div", {
62
+ className: "deck-overview-canvas",
63
+ style: {
64
+ width: deck.config.width,
65
+ height: deck.config.height,
66
+ transform: `scale(${scale})`
67
+ },
68
+ children: /* @__PURE__ */ jsx(SlideFrame, {
69
+ meta,
70
+ options,
71
+ h: col.h,
72
+ v: cv,
73
+ active: false,
74
+ click: 0,
75
+ preview: true,
76
+ className: "is-tile"
77
+ })
78
+ }), /* @__PURE__ */ jsxs("span", {
79
+ className: "deck-overview-label",
80
+ children: [
81
+ col.h + 1,
82
+ cv ? `.${cv + 1}` : "",
83
+ meta.title ? ` · ${meta.title}` : ""
84
+ ]
85
+ })]
86
+ }, `${col.h},${cv}`);
87
+ })
88
+ }, col.h))
89
+ })
90
+ });
91
+ }
92
+ //#endregion
93
+ export { Overview };
@@ -0,0 +1,128 @@
1
+ import { key } from "../core/navigation.js";
2
+ import { useDeck } from "./context.js";
3
+ import { BackgroundLayer } from "./Backgrounds.js";
4
+ import { SlideFrame, loadSlide } from "./SlideFrame.js";
5
+ import { useEffect, useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/react/PrintView.tsx
8
+ /**
9
+ * `?print-pdf`: every slide laid out as a page at the authored size, one page per click step when
10
+ * `pdfSeparateFragments` is on, notes printed in a box or on a separate page when `showNotes` is
11
+ * set. Print to PDF from the browser, or let `deck export` drive a headless Chromium over it.
12
+ */
13
+ function PrintView() {
14
+ const deck = useDeck();
15
+ const [ready, setReady] = useState(false);
16
+ const [totals, setTotals] = useState({});
17
+ const [zoom, setZoom] = useState(1);
18
+ useEffect(() => {
19
+ const fit = () => setZoom(Math.min(1, (window.innerWidth - 32) / deck.config.width));
20
+ fit();
21
+ window.addEventListener("resize", fit);
22
+ return () => window.removeEventListener("resize", fit);
23
+ }, [deck.config.width]);
24
+ const flat = deck.columns.flatMap((c) => c.slides.map((meta, v) => ({
25
+ meta,
26
+ h: c.h,
27
+ v
28
+ })));
29
+ useEffect(() => {
30
+ let alive = true;
31
+ const grace = setTimeout(() => alive && setReady(true), 15e3);
32
+ Promise.allSettled(flat.map((s) => loadSlide(s.meta))).then(() => {
33
+ if (!alive) return;
34
+ clearTimeout(grace);
35
+ setTimeout(() => alive && setReady(true), 300);
36
+ });
37
+ return () => {
38
+ alive = false;
39
+ clearTimeout(grace);
40
+ };
41
+ }, [flat.length]);
42
+ useEffect(() => deck.nav.subscribe((s) => setTotals(s.totals)), [deck.nav]);
43
+ useEffect(() => {
44
+ document.documentElement.classList.add("deck-print");
45
+ const style = document.createElement("style");
46
+ style.textContent = `@page { size: ${deck.config.width}px ${deck.config.height}px; margin: 0; }`;
47
+ document.head.appendChild(style);
48
+ return () => {
49
+ document.documentElement.classList.remove("deck-print");
50
+ style.remove();
51
+ };
52
+ }, [deck.config.width, deck.config.height]);
53
+ const pages = [];
54
+ for (const s of flat) {
55
+ const k = key(s.h, s.v);
56
+ const total = Math.min(totals[k] ?? 0, deck.config.pdfSeparateFragments ? Number.POSITIVE_INFINITY : 0);
57
+ const steps = deck.config.pdfSeparateFragments ? total + 1 : 1;
58
+ for (let c = 0; c < steps; c++) pages.push({
59
+ key: `${k}:${c}`,
60
+ h: s.h,
61
+ v: s.v,
62
+ click: deck.config.pdfSeparateFragments ? c : totals[k] ?? 0
63
+ });
64
+ }
65
+ return /* @__PURE__ */ jsxs("div", {
66
+ className: "deck-print-pages",
67
+ "data-ready": ready || void 0,
68
+ style: {
69
+ width: deck.config.width,
70
+ zoom
71
+ },
72
+ children: [pages.map((p) => {
73
+ const meta = deck.columns[p.h].slides[p.v];
74
+ const options = deck.options.get(key(p.h, p.v));
75
+ return /* @__PURE__ */ jsxs("div", {
76
+ className: "deck-print-page",
77
+ style: {
78
+ width: deck.config.width,
79
+ height: deck.config.height
80
+ },
81
+ children: [
82
+ /* @__PURE__ */ jsx(BackgroundLayer, {
83
+ bg: options.background,
84
+ active: false
85
+ }),
86
+ /* @__PURE__ */ jsx(SlideFrame, {
87
+ meta,
88
+ options,
89
+ h: p.h,
90
+ v: p.v,
91
+ active: false,
92
+ click: p.click,
93
+ preview: true,
94
+ className: "is-print"
95
+ }),
96
+ deck.config.showNotes === true && meta.hasNotes ? /* @__PURE__ */ jsx(PrintNotes, { meta }) : null
97
+ ]
98
+ }, p.key);
99
+ }), deck.config.showNotes === "separate-page" ? pages.filter((p) => p.click === 0 && deck.columns[p.h].slides[p.v].hasNotes).map((p) => /* @__PURE__ */ jsx("div", {
100
+ className: "deck-print-page deck-print-notes-page",
101
+ style: {
102
+ width: deck.config.width,
103
+ height: deck.config.height
104
+ },
105
+ children: /* @__PURE__ */ jsx(PrintNotes, {
106
+ meta: deck.columns[p.h].slides[p.v],
107
+ full: true
108
+ })
109
+ }, `notes:${p.key}`)) : null]
110
+ });
111
+ }
112
+ function PrintNotes({ meta, full = false }) {
113
+ const [Notes, setNotes] = useState(null);
114
+ useEffect(() => {
115
+ let alive = true;
116
+ meta.loadNotes?.().then((m) => alive && setNotes(() => m.default));
117
+ return () => {
118
+ alive = false;
119
+ };
120
+ }, [meta]);
121
+ if (!Notes) return null;
122
+ return /* @__PURE__ */ jsxs("aside", {
123
+ className: ["deck-print-notes", full ? "is-full" : ""].join(" "),
124
+ children: [full && meta.title ? /* @__PURE__ */ jsx("h3", { children: meta.title }) : null, /* @__PURE__ */ jsx(Notes, {})]
125
+ });
126
+ }
127
+ //#endregion
128
+ export { PrintView };