@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,173 @@
1
+ //#region src/renderers/contract.d.ts
2
+ /**
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * THE RENDERER CONTRACT
5
+ * ─────────────────────────────────────────────────────────────────────────────
6
+ *
7
+ * A slide is HTML. Nothing about that requires the thing drawn inside it to be React, and this is
8
+ * the seam where every other framework gets in: a renderer knows how to put one framework's
9
+ * component into an element and how to take it out again, and the engine knows nothing else about
10
+ * it. Astro's islands are the same idea — static page, small pieces of framework, each with its own
11
+ * runtime bundled separately so a Vue slide never ships React.
12
+ *
13
+ * WHY A MARKER AND NOT A SNIFF
14
+ *
15
+ * The obvious design is for each renderer to recognise its own components. It cannot: a React
16
+ * component and a Solid component are both plain functions, and no amount of looking at one tells
17
+ * you which runtime it was written for. Svelte and Angular do not even have a virtual DOM — their
18
+ * reactivity is produced by their compilers, so there is nothing at runtime to recognise.
19
+ *
20
+ * So the build says so, not the engine. A `.vue` file goes through the Vue plugin, which stamps
21
+ * `DECK_RENDERER` onto what it exports; an author can say `framework="vue"` by hand. `owns()` is
22
+ * the last resort, for the renderers that genuinely can tell — a web component is a tag name with a
23
+ * hyphen in it, and that is unambiguous.
24
+ *
25
+ * WHAT A RENDERER MUST SURVIVE
26
+ *
27
+ * A deck shows the same slide in several places at once: the stage, the presenter's current-slide
28
+ * box, an overview tile, the audience's mirror, a print page. So `mount` is called more than once
29
+ * for the same component, on different elements, and may be torn down and mounted again as the
30
+ * presenter moves. Keep per-mount state on the mount, never in the module.
31
+ */
32
+ /** Whatever a framework calls a component. The engine never looks inside one. */
33
+ export type IslandComponent = unknown;
34
+ export type IslandProps = Record<string, unknown>;
35
+ /**
36
+ * Stamped onto a component by a framework's build plugin (or by hand) to say which renderer owns
37
+ * it. Checked before `owns()`, because a stamp is knowledge and a sniff is a guess.
38
+ */
39
+ export declare const DECK_RENDERER: unique symbol;
40
+ /** A component carrying its renderer's name. */
41
+ export interface MarkedComponent {
42
+ [DECK_RENDERER]?: string;
43
+ }
44
+ export interface RenderContext {
45
+ /**
46
+ * The component's children, already rendered to HTML.
47
+ *
48
+ * Cross-framework children are HTML and not a live tree, deliberately: a React subtree cannot
49
+ * keep its state inside a Vue component, and pretending otherwise would produce the kind of bug
50
+ * nobody can explain. Markdown inside an island arrives as markup; interactivity inside an
51
+ * island belongs to that island's framework.
52
+ */
53
+ slot?: string;
54
+ /** The deck's colour scheme at mount, for frameworks that theme once rather than reactively. */
55
+ scheme?: "light" | "dark";
56
+ /**
57
+ * False while printing, exporting, or where the viewer asked for reduced motion. A renderer that
58
+ * starts a loop should not start one.
59
+ */
60
+ animate: boolean;
61
+ }
62
+ /** A live island. The engine holds this and calls it when the slide moves on. */
63
+ export interface IslandMount {
64
+ /** Undo everything: unmount, drop listeners, stop loops. Must be safe to call twice. */
65
+ destroy(): void;
66
+ /**
67
+ * New props without a teardown. Optional — the engine remounts when a renderer cannot update,
68
+ * which is correct but loses the island's internal state, so implement this where it is cheap.
69
+ */
70
+ update?(props: IslandProps): void;
71
+ }
72
+ export interface DeckRenderer {
73
+ /** `framework="…"` names this. Lowercase, hyphenated: `react`, `web-components`, `vue`. */
74
+ readonly name: string;
75
+ /**
76
+ * Last-resort recognition, for renderers that can genuinely tell. Return false when unsure —
77
+ * a wrong yes is worse than no answer, because it takes the component away from the renderer
78
+ * that did know.
79
+ */
80
+ owns?(component: IslandComponent): boolean;
81
+ /** Put the component in the element and keep it alive. */
82
+ mount(host: Element, component: IslandComponent, props: IslandProps, context: RenderContext): IslandMount;
83
+ /**
84
+ * HTML for a surface that will never hydrate — a print page, an export frame, a thumbnail.
85
+ * Optional: without it such a surface shows the island's fallback instead.
86
+ */
87
+ ssr?(component: IslandComponent, props: IslandProps, context: RenderContext): string | Promise<string>;
88
+ /**
89
+ * The renderer whose components the deck runtime can draw directly, without an island. There is
90
+ * exactly one — React today — and marking it lets `<Island>` keep such components inside the
91
+ * React tree, where the deck's own context (clicks, theme, the live room) still reaches them.
92
+ */
93
+ readonly native?: boolean;
94
+ }
95
+ /**
96
+ * Add a renderer. Registration order is `owns()` order, so a renderer that recognises broadly
97
+ * should be registered after one that recognises precisely. Registering a name twice replaces the
98
+ * first, which is what a dev server doing hot reload needs.
99
+ */
100
+ export declare function registerRenderer(renderer: DeckRenderer): void;
101
+ export declare function registeredRenderers(): readonly DeckRenderer[];
102
+ export declare function rendererNamed(name: string): DeckRenderer | null;
103
+ /** The renderer the deck runtime draws directly, if one is registered. */
104
+ export declare function nativeRenderer(): DeckRenderer | null;
105
+ /** Forget every renderer. For tests; nothing in the engine calls this. */
106
+ export declare function clearRenderers(): void;
107
+ /**
108
+ * WHY A FRAMEWORK MUST BE ABLE TO ARRIVE LATE
109
+ *
110
+ * Registering a renderer imports it, and importing it imports its framework. Measured on the
111
+ * showcase deck, registering Vue eagerly put 64 kB (25 kB gzipped) of Vue runtime into the first
112
+ * chunk — paid on slide one, by every viewer, for one island nineteen slides away. Ten frameworks
113
+ * would make that a quarter of a megabyte of runtimes a given deck never runs.
114
+ *
115
+ * So a renderer may be registered as a name and an import instead. Resolution by name
116
+ * (`framework="vue"`) and by stamp (`DECK_RENDERER`) both work on a string, so the engine can know
117
+ * which renderer a component needs before that renderer's code exists in the page — which is the
118
+ * whole trick, and the reason the contract went to a marker rather than a sniff.
119
+ *
120
+ * The cost is deliberate and small: `owns()` cannot run for a renderer that has not loaded, so a
121
+ * deferred renderer's components must be named or stamped. A web component is recognised by its tag
122
+ * and React is the native one, so neither is affected.
123
+ */
124
+ export type RendererLoader = () => Promise<DeckRenderer | {
125
+ default: DeckRenderer;
126
+ }>;
127
+ /**
128
+ * Register a renderer by name, loading it only when a slide actually needs it.
129
+ *
130
+ * registerLazyRenderer("vue", () => import("@kadal/deck-vue").then((m) => m.vueRenderer));
131
+ *
132
+ * An eager registration of the same name wins: it is already here, so there is nothing to wait for.
133
+ */
134
+ export declare function registerLazyRenderer(name: string, load: RendererLoader): void;
135
+ /** Names that are registered, loaded or not. */
136
+ export declare function knownRendererNames(): string[];
137
+ /**
138
+ * The renderer called `name`, loading it if that is what it takes. Null when no such renderer is
139
+ * registered, or when its import failed — a failed import is reported once and then retried on the
140
+ * next ask, because a deck that lost its network on slide three should recover on slide four.
141
+ */
142
+ export declare function loadRenderer(name: string): Promise<DeckRenderer | null>;
143
+ export type Resolution = {
144
+ renderer: DeckRenderer;
145
+ how: "named" | "marked" | "owned";
146
+ } |
147
+ /** A renderer by this name is registered but not loaded. `loadRenderer(name)` finishes the job. */
148
+ {
149
+ renderer: null;
150
+ how: "deferred";
151
+ name: string;
152
+ } | {
153
+ renderer: null;
154
+ how: "unknown";
155
+ reason: string;
156
+ };
157
+ /**
158
+ * Which renderer draws this component, and on what grounds.
159
+ *
160
+ * The order is deliberate: what the author said, then what the build stamped, then what a renderer
161
+ * can prove about the component itself. `how` is carried out of here so a failure can say which
162
+ * step it got to rather than "no renderer".
163
+ */
164
+ export declare function resolveRenderer(component: IslandComponent, framework?: string): Resolution;
165
+ /**
166
+ * Resolution, including the renderers that have to be fetched first.
167
+ *
168
+ * This is what the engine calls. The synchronous `resolveRenderer` above stays exactly as honest as
169
+ * it was — it reports what is in the page right now — and this one takes its `deferred` answer and
170
+ * finishes it.
171
+ */
172
+ export declare function resolveRendererAsync(component: IslandComponent, framework?: string): Promise<Resolution>;
173
+ //#endregion
@@ -0,0 +1,152 @@
1
+ //#region src/renderers/contract.ts
2
+ /**
3
+ * Stamped onto a component by a framework's build plugin (or by hand) to say which renderer owns
4
+ * it. Checked before `owns()`, because a stamp is knowledge and a sniff is a guess.
5
+ */
6
+ const DECK_RENDERER = Symbol.for("deck.renderer");
7
+ const registry = [];
8
+ /**
9
+ * Add a renderer. Registration order is `owns()` order, so a renderer that recognises broadly
10
+ * should be registered after one that recognises precisely. Registering a name twice replaces the
11
+ * first, which is what a dev server doing hot reload needs.
12
+ */
13
+ function registerRenderer(renderer) {
14
+ const at = registry.findIndex((r) => r.name === renderer.name);
15
+ if (at >= 0) registry[at] = renderer;
16
+ else registry.push(renderer);
17
+ }
18
+ function registeredRenderers() {
19
+ return registry;
20
+ }
21
+ function rendererNamed(name) {
22
+ return registry.find((r) => r.name === name) ?? null;
23
+ }
24
+ /** The renderer the deck runtime draws directly, if one is registered. */
25
+ function nativeRenderer() {
26
+ return registry.find((r) => r.native) ?? null;
27
+ }
28
+ /** Forget every renderer. For tests; nothing in the engine calls this. */
29
+ function clearRenderers() {
30
+ registry.length = 0;
31
+ deferred.clear();
32
+ }
33
+ const deferred = /* @__PURE__ */ new Map();
34
+ const loading = /* @__PURE__ */ new Map();
35
+ /**
36
+ * Register a renderer by name, loading it only when a slide actually needs it.
37
+ *
38
+ * registerLazyRenderer("vue", () => import("@kadal/deck-vue").then((m) => m.vueRenderer));
39
+ *
40
+ * An eager registration of the same name wins: it is already here, so there is nothing to wait for.
41
+ */
42
+ function registerLazyRenderer(name, load) {
43
+ deferred.set(name, load);
44
+ }
45
+ /** Names that are registered, loaded or not. */
46
+ function knownRendererNames() {
47
+ return [.../* @__PURE__ */ new Set([...registry.map((r) => r.name), ...deferred.keys()])];
48
+ }
49
+ /**
50
+ * The renderer called `name`, loading it if that is what it takes. Null when no such renderer is
51
+ * registered, or when its import failed — a failed import is reported once and then retried on the
52
+ * next ask, because a deck that lost its network on slide three should recover on slide four.
53
+ */
54
+ function loadRenderer(name) {
55
+ const here = rendererNamed(name);
56
+ if (here) return Promise.resolve(here);
57
+ const inFlight = loading.get(name);
58
+ if (inFlight) return inFlight;
59
+ const load = deferred.get(name);
60
+ if (!load) return Promise.resolve(null);
61
+ const promise = load().then((module) => {
62
+ const renderer = module && typeof module === "object" && "default" in module ? module.default : module;
63
+ if (!renderer || typeof renderer.mount !== "function") throw new Error(`the module registered for “${name}” did not export a renderer`);
64
+ registerRenderer(renderer);
65
+ return renderer;
66
+ }).catch((error) => {
67
+ console.error(`[deck] could not load the “${name}” renderer:`, error);
68
+ return null;
69
+ }).finally(() => {
70
+ loading.delete(name);
71
+ });
72
+ loading.set(name, promise);
73
+ return promise;
74
+ }
75
+ /**
76
+ * Which renderer draws this component, and on what grounds.
77
+ *
78
+ * The order is deliberate: what the author said, then what the build stamped, then what a renderer
79
+ * can prove about the component itself. `how` is carried out of here so a failure can say which
80
+ * step it got to rather than "no renderer".
81
+ */
82
+ function resolveRenderer(component, framework) {
83
+ if (framework) {
84
+ const named = rendererNamed(framework);
85
+ if (named) return {
86
+ renderer: named,
87
+ how: "named"
88
+ };
89
+ if (deferred.has(framework)) return {
90
+ renderer: null,
91
+ how: "deferred",
92
+ name: framework
93
+ };
94
+ const known = knownRendererNames();
95
+ return {
96
+ renderer: null,
97
+ how: "unknown",
98
+ reason: `no renderer named “${framework}” is registered${known.length ? ` (have: ${known.join(", ")})` : " — none are"}`
99
+ };
100
+ }
101
+ const marked = component?.[DECK_RENDERER];
102
+ if (typeof marked === "string") {
103
+ const stamped = rendererNamed(marked);
104
+ if (stamped) return {
105
+ renderer: stamped,
106
+ how: "marked"
107
+ };
108
+ if (deferred.has(marked)) return {
109
+ renderer: null,
110
+ how: "deferred",
111
+ name: marked
112
+ };
113
+ return {
114
+ renderer: null,
115
+ how: "unknown",
116
+ reason: `this component was built for “${marked}”, which is not registered`
117
+ };
118
+ }
119
+ for (const renderer of registry) try {
120
+ if (renderer.owns?.(component)) return {
121
+ renderer,
122
+ how: "owned"
123
+ };
124
+ } catch {}
125
+ return {
126
+ renderer: null,
127
+ how: "unknown",
128
+ reason: "no renderer claimed it — pass framework=\"…\", or have the build stamp DECK_RENDERER on it"
129
+ };
130
+ }
131
+ /**
132
+ * Resolution, including the renderers that have to be fetched first.
133
+ *
134
+ * This is what the engine calls. The synchronous `resolveRenderer` above stays exactly as honest as
135
+ * it was — it reports what is in the page right now — and this one takes its `deferred` answer and
136
+ * finishes it.
137
+ */
138
+ async function resolveRendererAsync(component, framework) {
139
+ const now = resolveRenderer(component, framework);
140
+ if (now.how !== "deferred") return now;
141
+ const renderer = await loadRenderer(now.name);
142
+ return renderer ? {
143
+ renderer,
144
+ how: framework ? "named" : "marked"
145
+ } : {
146
+ renderer: null,
147
+ how: "unknown",
148
+ reason: `the “${now.name}” renderer is registered but could not be loaded`
149
+ };
150
+ }
151
+ //#endregion
152
+ export { DECK_RENDERER, clearRenderers, knownRendererNames, loadRenderer, nativeRenderer, registerLazyRenderer, registerRenderer, registeredRenderers, rendererNamed, resolveRenderer, resolveRendererAsync };
@@ -0,0 +1,12 @@
1
+ import { DECK_RENDERER, DeckRenderer, IslandComponent, IslandMount, IslandProps, MarkedComponent, RenderContext, RendererLoader, Resolution, clearRenderers, knownRendererNames, loadRenderer, nativeRenderer, registerLazyRenderer, registerRenderer, registeredRenderers, rendererNamed, resolveRenderer, resolveRendererAsync } from "./contract.js";
2
+ import { ClientStrategy, Island, IslandLoader, IslandOwnProps } from "./Island.js";
3
+ import { reactRenderer } from "./react.js";
4
+ import { webComponentsRenderer } from "./web-components.js";
5
+ //#region src/renderers/index.d.ts
6
+ /**
7
+ * The two that ship with the engine, in `owns()` order: custom elements can recognise their own
8
+ * (a hyphenated tag is unambiguous), React cannot and does not try.
9
+ */
10
+ export declare function registerBuiltinRenderers(): void;
11
+ //#endregion
12
+ export { type ClientStrategy, DECK_RENDERER, type DeckRenderer, Island, type IslandComponent, type IslandLoader, type IslandMount, type IslandOwnProps, type IslandProps, type MarkedComponent, type RenderContext, type RendererLoader, type Resolution, clearRenderers, knownRendererNames, loadRenderer, nativeRenderer, reactRenderer, registerLazyRenderer, registerRenderer, registeredRenderers, rendererNamed, resolveRenderer, resolveRendererAsync, webComponentsRenderer };
@@ -0,0 +1,16 @@
1
+ import { DECK_RENDERER, clearRenderers, knownRendererNames, loadRenderer, nativeRenderer, registerLazyRenderer, registerRenderer, registeredRenderers, rendererNamed, resolveRenderer, resolveRendererAsync } from "./contract.js";
2
+ import { Island } from "./Island.js";
3
+ import { reactRenderer } from "./react.js";
4
+ import { webComponentsRenderer } from "./web-components.js";
5
+ //#region src/renderers/index.ts
6
+ /**
7
+ * The two that ship with the engine, in `owns()` order: custom elements can recognise their own
8
+ * (a hyphenated tag is unambiguous), React cannot and does not try.
9
+ */
10
+ function registerBuiltinRenderers() {
11
+ registerRenderer(webComponentsRenderer);
12
+ registerRenderer(reactRenderer);
13
+ }
14
+ registerBuiltinRenderers();
15
+ //#endregion
16
+ export { DECK_RENDERER, Island, clearRenderers, knownRendererNames, loadRenderer, nativeRenderer, reactRenderer, registerBuiltinRenderers, registerLazyRenderer, registerRenderer, registeredRenderers, rendererNamed, resolveRenderer, resolveRendererAsync, webComponentsRenderer };
@@ -0,0 +1,16 @@
1
+ import { DeckRenderer } from "./contract.js";
2
+ //#region src/renderers/react.d.ts
3
+ /**
4
+ * React — the framework the deck runtime itself draws with.
5
+ *
6
+ * `native: true` is the whole point of this file. `<Island>` sees it and renders the component
7
+ * directly into the tree instead of mounting it, so a React component on a slide keeps the deck's
8
+ * context: the click it should be revealed at, the theme, the live room, the glossary. Through an
9
+ * island it would get a second React root, and context does not cross a root boundary — the
10
+ * component would render, and every hook that made it part of a deck would quietly return nothing.
11
+ *
12
+ * `mount` exists anyway, for the day the host surface is not React: a React island inside a Vue
13
+ * deck is a root, and then a root is the right answer.
14
+ */
15
+ export declare const reactRenderer: DeckRenderer;
16
+ //#endregion
@@ -0,0 +1,32 @@
1
+ import { createElement } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ //#region src/renderers/react.tsx
4
+ /**
5
+ * React — the framework the deck runtime itself draws with.
6
+ *
7
+ * `native: true` is the whole point of this file. `<Island>` sees it and renders the component
8
+ * directly into the tree instead of mounting it, so a React component on a slide keeps the deck's
9
+ * context: the click it should be revealed at, the theme, the live room, the glossary. Through an
10
+ * island it would get a second React root, and context does not cross a root boundary — the
11
+ * component would render, and every hook that made it part of a deck would quietly return nothing.
12
+ *
13
+ * `mount` exists anyway, for the day the host surface is not React: a React island inside a Vue
14
+ * deck is a root, and then a root is the right answer.
15
+ */
16
+ const reactRenderer = {
17
+ name: "react",
18
+ native: true,
19
+ mount(host, component, props, context) {
20
+ const root = createRoot(host);
21
+ const render = (p) => root.render(createElement(component, p, context.slot ? createElement("div", { dangerouslySetInnerHTML: { __html: context.slot } }) : void 0));
22
+ render(props);
23
+ return {
24
+ destroy() {
25
+ queueMicrotask(() => root.unmount());
26
+ },
27
+ update: render
28
+ };
29
+ }
30
+ };
31
+ //#endregion
32
+ export { reactRenderer };
@@ -0,0 +1,4 @@
1
+ import { DeckRenderer } from "./contract.js";
2
+ //#region src/renderers/web-components.d.ts
3
+ export declare const webComponentsRenderer: DeckRenderer;
4
+ //#endregion
@@ -0,0 +1,103 @@
1
+ //#region src/renderers/web-components.ts
2
+ /**
3
+ * Custom elements — the renderer with no framework behind it.
4
+ *
5
+ * This one is first on purpose. Every other renderer will be a framework with opinions about
6
+ * mounting, and it is easy to write a contract that quietly assumes the shape of the framework you
7
+ * already have. Custom elements assume nothing: a tag name, an element, properties, `remove()`. If
8
+ * the contract fits here it is a contract rather than React with extra steps.
9
+ *
10
+ * An author writes either the tag name:
11
+ *
12
+ * <Island component="my-chart" props={{ data }} />
13
+ *
14
+ * or the class, which is defined on first use:
15
+ *
16
+ * <Island component={MyChart} props={{ data }} />
17
+ */
18
+ /** Tag names must contain a hyphen; that is the whole rule, and it makes recognition reliable. */
19
+ function isCustomElementName(value) {
20
+ return typeof value === "string" && value.includes("-") && !/\s/.test(value);
21
+ }
22
+ function isCustomElementClass(value) {
23
+ if (typeof value !== "function") return false;
24
+ if (typeof HTMLElement === "undefined") return false;
25
+ return value === HTMLElement || value.prototype instanceof HTMLElement;
26
+ }
27
+ let anonymous = 0;
28
+ const named = /* @__PURE__ */ new WeakMap();
29
+ /** The tag for a class: its own `tagName`, one it is already defined under, or a fresh one. */
30
+ function tagFor(ctor) {
31
+ const existing = named.get(ctor);
32
+ if (existing) return existing;
33
+ const declared = ctor.tagName;
34
+ let tag = typeof declared === "string" && declared.includes("-") ? declared : "";
35
+ if (!tag) tag = `deck-island-${++anonymous}`;
36
+ const already = customElements.get(tag);
37
+ if (!already) customElements.define(tag, ctor);
38
+ else if (already !== ctor) {
39
+ tag = `deck-island-${++anonymous}`;
40
+ customElements.define(tag, ctor);
41
+ }
42
+ named.set(ctor, tag);
43
+ return tag;
44
+ }
45
+ /**
46
+ * A property where the element has one, an attribute where it does not.
47
+ *
48
+ * This is the custom-element convention rather than a preference: rich values (arrays, objects,
49
+ * functions) only survive as properties, and attributes only carry strings. `false` removes the
50
+ * attribute instead of writing "false", which is what every element in HTML means by a boolean.
51
+ */
52
+ function apply(el, props) {
53
+ for (const [key, value] of Object.entries(props)) {
54
+ if (key === "children") continue;
55
+ if (key in el || typeof value === "object" || typeof value === "function") try {
56
+ el[key] = value;
57
+ continue;
58
+ } catch {}
59
+ if (value === false || value === null || value === void 0) el.removeAttribute(key);
60
+ else if (value === true) el.setAttribute(key, "");
61
+ else el.setAttribute(key, String(value));
62
+ }
63
+ }
64
+ /**
65
+ * `>` does not strictly need escaping inside a quoted attribute, and is escaped anyway: this string
66
+ * is concatenated into markup that may be read somewhere less forgiving than a browser.
67
+ */
68
+ function escapeAttribute(value) {
69
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
70
+ }
71
+ const webComponentsRenderer = {
72
+ name: "web-components",
73
+ owns(component) {
74
+ return isCustomElementName(component) || isCustomElementClass(component);
75
+ },
76
+ mount(host, component, props, context) {
77
+ const tag = isCustomElementName(component) ? component : tagFor(component);
78
+ const el = document.createElement(tag);
79
+ if (context.slot) el.innerHTML = context.slot;
80
+ if (context.animate === false) el.setAttribute("data-still", "");
81
+ apply(el, props);
82
+ host.replaceChildren(el);
83
+ return {
84
+ destroy() {
85
+ el.remove();
86
+ },
87
+ update(next) {
88
+ apply(el, next);
89
+ }
90
+ };
91
+ },
92
+ /**
93
+ * The element's markup without a browser to upgrade it. A custom element that has not been
94
+ * defined is an inert tag with its attributes — which is exactly what a print page or an export
95
+ * frame should show, as long as the element's styling does not depend on being upgraded.
96
+ */
97
+ ssr(component, props, context) {
98
+ const tag = isCustomElementName(component) ? component : typeof component === "function" ? tagFor(component) : "deck-island-unknown";
99
+ return `<${tag}${Object.entries(props).filter(([k, v]) => k !== "children" && typeof v !== "object" && typeof v !== "function").map(([k, v]) => v === true ? ` ${k}=""` : v === false || v == null ? "" : ` ${k}="${escapeAttribute(String(v))}"`).join("")}>${context.slot ?? ""}</${tag}>`;
100
+ }
101
+ };
102
+ //#endregion
103
+ export { webComponentsRenderer };
@@ -0,0 +1,49 @@
1
+ import { useUi } from "../react/hooks.js";
2
+ import { getScene } from "./render.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import { Excalidraw } from "@excalidraw/excalidraw";
5
+ import "@excalidraw/excalidraw/index.css";
6
+ //#region src/sketch/Canvas.tsx
7
+ /**
8
+ * The real Excalidraw canvas, in view mode, for the lightbox: pan, zoom and point at a part of a
9
+ * sketch during the talk. Loaded only when a sketch is opened — the package is 2.7 MB.
10
+ */
11
+ function SketchCanvas({ id }) {
12
+ const scene = getScene(id);
13
+ const scheme = useUi((s) => s.colorScheme);
14
+ if (!scene) return /* @__PURE__ */ jsx("div", {
15
+ className: "deck-sketch-missing",
16
+ children: "This sketch has not been drawn yet."
17
+ });
18
+ return /* @__PURE__ */ jsx("div", {
19
+ className: "deck-sketch-canvas",
20
+ children: /* @__PURE__ */ jsx(Excalidraw, {
21
+ initialData: {
22
+ elements: scene.elements,
23
+ files: scene.files,
24
+ appState: { viewBackgroundColor: "#fbfbf9" },
25
+ scrollToContent: true
26
+ },
27
+ viewModeEnabled: true,
28
+ zenModeEnabled: true,
29
+ theme: scheme,
30
+ detectScroll: false,
31
+ handleKeyboardGlobally: false,
32
+ autoFocus: true,
33
+ UIOptions: {
34
+ canvasActions: {
35
+ changeViewBackgroundColor: false,
36
+ clearCanvas: false,
37
+ export: false,
38
+ loadScene: false,
39
+ saveAsImage: false,
40
+ saveToActiveFile: false,
41
+ toggleTheme: false
42
+ },
43
+ tools: { image: false }
44
+ }
45
+ })
46
+ });
47
+ }
48
+ //#endregion
49
+ export { SketchCanvas as default };