@marimo-team/islands 0.23.3-dev8 → 0.23.3
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/dist/{chat-ui-BLFhPclV.js → chat-ui-DEd_Ndal.js} +82 -82
- package/dist/{html-to-image-XYwXqg2E.js → html-to-image-DBosi5GK.js} +2240 -2214
- package/dist/main.js +2627 -2746
- package/dist/{process-output-BDVjDpbu.js → process-output-k-4WHpxz.js} +1 -1
- package/dist/{reveal-component-CrnLosc4.js → reveal-component-CFuofbBD.js} +827 -561
- package/dist/{slide-Dl7Rf496.js → slide-form-DgMI37ES.js} +1729 -894
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/editor/file-tree/renderers.tsx +1 -1
- package/src/components/editor/output/JsonOutput.tsx +187 -4
- package/src/components/editor/output/__tests__/JsonOutput-mimetype.test.tsx +80 -0
- package/src/components/editor/output/__tests__/json-output.test.ts +185 -2
- package/src/components/editor/renderers/slides-layout/__tests__/compute-slide-cells.test.ts +150 -0
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +298 -0
- package/src/components/editor/renderers/slides-layout/compute-slide-cells.ts +50 -0
- package/src/components/editor/renderers/slides-layout/plugin.tsx +54 -9
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +30 -12
- package/src/components/editor/renderers/slides-layout/types.ts +31 -3
- package/src/components/editor/renderers/types.ts +2 -0
- package/src/components/slides/__tests__/compose-slides.test.ts +433 -0
- package/src/components/slides/compose-slides.ts +337 -0
- package/src/components/slides/minimap.tsx +133 -12
- package/src/components/slides/reveal-component.tsx +337 -74
- package/src/components/slides/reveal-slides.css +33 -1
- package/src/components/slides/slide-form.tsx +347 -0
- package/src/components/ui/radio-group.tsx +5 -3
- package/src/core/cells/types.ts +2 -0
- package/src/core/islands/__tests__/bridge.test.ts +116 -5
- package/src/core/islands/bridge.ts +5 -1
- package/src/core/layout/layout.ts +6 -2
- package/src/core/static/__tests__/export-context.test.ts +122 -0
- package/src/core/static/__tests__/static-state.test.ts +80 -0
- package/src/core/static/export-context.ts +84 -0
- package/src/core/static/static-state.ts +44 -6
- package/src/plugins/core/RenderHTML.tsx +23 -2
- package/src/plugins/core/__test__/RenderHTML.test.ts +86 -1
- package/src/plugins/core/__test__/trusted-url.test.ts +130 -18
- package/src/plugins/core/sanitize.ts +11 -5
- package/src/plugins/core/trusted-url.ts +32 -10
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +29 -1
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +34 -0
- package/src/plugins/impl/panel/__tests__/PanelPlugin.test.ts +35 -2
|
@@ -1,107 +1,370 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
Fragment as ReactFragment,
|
|
9
|
+
} from "react";
|
|
10
|
+
import useEvent from "react-use-event-hook";
|
|
11
|
+
import { ExpandIcon, EyeOffIcon } from "lucide-react";
|
|
12
|
+
import { Deck, Fragment, Slide, Stack } from "@revealjs/react";
|
|
6
13
|
import { Slide as CellOutputSlide } from "@/components/slides/slide";
|
|
7
14
|
import { Button } from "@/components/ui/button";
|
|
8
15
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
9
|
-
import type {
|
|
10
|
-
import type { RevealApi } from "reveal.js";
|
|
16
|
+
import type { RuntimeCell } from "@/core/cells/types";
|
|
17
|
+
import type { RevealApi, RevealConfig } from "reveal.js";
|
|
18
|
+
import { useEventListener } from "@/hooks/useEventListener";
|
|
11
19
|
import { Events } from "@/utils/events";
|
|
12
20
|
import { Logger } from "@/utils/Logger";
|
|
13
|
-
|
|
14
21
|
import "./slides.css";
|
|
15
22
|
import "./reveal-slides.css";
|
|
23
|
+
import type { SlidesLayout } from "../editor/renderers/slides-layout/types";
|
|
24
|
+
import {
|
|
25
|
+
buildSlideIndices,
|
|
26
|
+
composeSlides,
|
|
27
|
+
computeDeckNavigation,
|
|
28
|
+
resolveActiveCellIndex,
|
|
29
|
+
resolveDeckNavigationTarget,
|
|
30
|
+
type ComposedSubslide,
|
|
31
|
+
} from "./compose-slides";
|
|
32
|
+
import {
|
|
33
|
+
DEFAULT_DECK_TRANSITION,
|
|
34
|
+
DEFAULT_SLIDE_TYPE,
|
|
35
|
+
SlideSidebar,
|
|
36
|
+
} from "./slide-form";
|
|
37
|
+
import type { AppMode } from "@/core/mode";
|
|
38
|
+
|
|
39
|
+
const ASPECT_RATIO = 16 / 9;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* reveal.js caches the last visited vertical index on each stack and can
|
|
43
|
+
* resume there on later horizontal navigation. After minimap-driven jumps we
|
|
44
|
+
* want stacks to re-enter from the top instead of reusing stale stack state.
|
|
45
|
+
*/
|
|
46
|
+
function clearPreviousVerticalIndices(deck: RevealApi) {
|
|
47
|
+
const slidesEl = deck.getSlidesElement();
|
|
48
|
+
if (!slidesEl) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const stack of slidesEl.querySelectorAll(
|
|
53
|
+
"section.stack[data-previous-indexv]",
|
|
54
|
+
)) {
|
|
55
|
+
stack.removeAttribute("data-previous-indexv");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const FORWARD_NAV_KEYS = new Set([
|
|
60
|
+
" ",
|
|
61
|
+
"Spacebar",
|
|
62
|
+
"ArrowRight",
|
|
63
|
+
"ArrowDown",
|
|
64
|
+
"PageDown",
|
|
65
|
+
]);
|
|
66
|
+
const BACK_NAV_KEYS = new Set(["ArrowLeft", "ArrowUp", "PageUp"]);
|
|
67
|
+
|
|
68
|
+
function classifyNavKey(event: KeyboardEvent): 1 | -1 | 0 {
|
|
69
|
+
if (FORWARD_NAV_KEYS.has(event.key)) {
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
if (BACK_NAV_KEYS.has(event.key)) {
|
|
73
|
+
return -1;
|
|
74
|
+
}
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function useSlideDimensions(ref: React.RefObject<HTMLDivElement | null>) {
|
|
79
|
+
const [dims, setDims] = useState({ width: 960, height: 540 });
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const el = ref.current;
|
|
83
|
+
if (!el) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const observer = new ResizeObserver((entries) => {
|
|
88
|
+
const { width, height } = entries[0].contentRect;
|
|
89
|
+
if (width <= 0 || height <= 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const fitWidth = Math.min(width, height * ASPECT_RATIO);
|
|
93
|
+
const fitHeight = fitWidth / ASPECT_RATIO;
|
|
94
|
+
setDims({
|
|
95
|
+
width: Math.round(fitWidth),
|
|
96
|
+
height: Math.round(fitHeight),
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
observer.observe(el);
|
|
101
|
+
return () => observer.disconnect();
|
|
102
|
+
}, [ref]);
|
|
103
|
+
|
|
104
|
+
return dims;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Trigger a resize event on the window
|
|
109
|
+
* Vega elements need to be re-measured when the container width changes.
|
|
110
|
+
*/
|
|
111
|
+
function triggerResize(deck: RevealApi | null) {
|
|
112
|
+
if (deck?.getCurrentSlide()?.querySelector(".vega-embed, marimo-vega")) {
|
|
113
|
+
requestAnimationFrame(() => {
|
|
114
|
+
window.dispatchEvent(new Event("resize"));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const SubslideView = ({
|
|
120
|
+
subslide,
|
|
121
|
+
}: {
|
|
122
|
+
subslide: ComposedSubslide<RuntimeCell>;
|
|
123
|
+
}) => (
|
|
124
|
+
<Slide>
|
|
125
|
+
<div className="h-full w-full overflow-auto flex">
|
|
126
|
+
<div className="mo-slide-content" style={{ margin: "auto 20px" }}>
|
|
127
|
+
{subslide.blocks.map((block, i) => {
|
|
128
|
+
const rendered = block.cells.map((cell) => (
|
|
129
|
+
<CellOutputSlide
|
|
130
|
+
key={cell.id}
|
|
131
|
+
cellId={cell.id}
|
|
132
|
+
status={cell.status}
|
|
133
|
+
output={cell.output}
|
|
134
|
+
/>
|
|
135
|
+
));
|
|
136
|
+
if (block.isFragment) {
|
|
137
|
+
return (
|
|
138
|
+
<Fragment key={i} as="div">
|
|
139
|
+
{rendered}
|
|
140
|
+
</Fragment>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return <ReactFragment key={i}>{rendered}</ReactFragment>;
|
|
144
|
+
})}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</Slide>
|
|
148
|
+
);
|
|
16
149
|
|
|
17
150
|
const RevealSlidesComponent = ({
|
|
18
151
|
cellsWithOutput,
|
|
152
|
+
layout,
|
|
153
|
+
setLayout,
|
|
19
154
|
activeIndex,
|
|
20
155
|
onSlideChange,
|
|
21
156
|
deckRef,
|
|
157
|
+
mode,
|
|
158
|
+
configWidth = 300, // px
|
|
22
159
|
}: {
|
|
23
|
-
cellsWithOutput:
|
|
160
|
+
cellsWithOutput: RuntimeCell[];
|
|
161
|
+
layout: SlidesLayout;
|
|
162
|
+
setLayout: (layout: SlidesLayout) => void;
|
|
24
163
|
activeIndex?: number;
|
|
25
164
|
onSlideChange?: (index: number) => void;
|
|
26
165
|
deckRef: React.RefObject<RevealApi | null>;
|
|
166
|
+
mode: AppMode;
|
|
167
|
+
configWidth?: number;
|
|
27
168
|
}) => {
|
|
169
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
170
|
+
const { width, height } = useSlideDimensions(containerRef);
|
|
171
|
+
const activeCell =
|
|
172
|
+
activeIndex != null ? cellsWithOutput[activeIndex] : undefined;
|
|
173
|
+
// Fall back to the first cell while the deck settles on an initial slide.
|
|
174
|
+
// Still `undefined` when the deck is empty (handled below).
|
|
175
|
+
const activeConfigCell = activeCell ?? cellsWithOutput.at(0);
|
|
176
|
+
|
|
177
|
+
const composition = useMemo(
|
|
178
|
+
() =>
|
|
179
|
+
composeSlides({
|
|
180
|
+
cells: cellsWithOutput,
|
|
181
|
+
getType: (cell) =>
|
|
182
|
+
layout.cells.get(cell.id)?.type ?? DEFAULT_SLIDE_TYPE,
|
|
183
|
+
}),
|
|
184
|
+
[cellsWithOutput, layout.cells],
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Skip cells aren't part of the composed deck. When one is selected in the
|
|
188
|
+
// minimap we render a preview over the deck and park reveal on a neighboring
|
|
189
|
+
// real slide; keyboard nav while parked is handled below.
|
|
190
|
+
const skippedPreviewCell =
|
|
191
|
+
activeCell && layout.cells.get(activeCell.id)?.type === "skip"
|
|
192
|
+
? activeCell
|
|
193
|
+
: null;
|
|
194
|
+
|
|
195
|
+
const { cellToTarget, targetToCellIndex } = useMemo(
|
|
196
|
+
() =>
|
|
197
|
+
buildSlideIndices({
|
|
198
|
+
composition,
|
|
199
|
+
cells: cellsWithOutput,
|
|
200
|
+
getId: (c) => c.id,
|
|
201
|
+
}),
|
|
202
|
+
[composition, cellsWithOutput],
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const deckTransition = layout.deck?.transition ?? DEFAULT_DECK_TRANSITION;
|
|
206
|
+
const revealConfig: RevealConfig = useMemo(
|
|
207
|
+
() => ({
|
|
208
|
+
embedded: true,
|
|
209
|
+
width,
|
|
210
|
+
height,
|
|
211
|
+
center: false,
|
|
212
|
+
minScale: 0.2,
|
|
213
|
+
maxScale: 2,
|
|
214
|
+
transition: deckTransition,
|
|
215
|
+
keyboardCondition: (event: KeyboardEvent) => !Events.fromInput(event),
|
|
216
|
+
}),
|
|
217
|
+
[width, height, deckTransition],
|
|
218
|
+
);
|
|
219
|
+
|
|
28
220
|
useEffect(() => {
|
|
29
221
|
const deck = deckRef.current;
|
|
30
|
-
if (deck == null
|
|
222
|
+
if (deck == null) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const target = resolveDeckNavigationTarget({
|
|
226
|
+
activeIndex,
|
|
227
|
+
cells: cellsWithOutput,
|
|
228
|
+
cellToTarget,
|
|
229
|
+
getId: (cell) => cell.id,
|
|
230
|
+
});
|
|
231
|
+
const next = target && computeDeckNavigation(deck.getIndices(), target);
|
|
232
|
+
if (!next) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
deck.slide(next.h, next.v, next.f);
|
|
236
|
+
clearPreviousVerticalIndices(deck);
|
|
237
|
+
}, [activeIndex, cellToTarget, cellsWithOutput, deckRef]);
|
|
238
|
+
|
|
239
|
+
// Forward the deck's current cell to the parent, except while a skipped
|
|
240
|
+
// preview is parked: every reveal.js event during that window is an echo
|
|
241
|
+
// of the programmatic park (possibly with transient indices), so ignoring
|
|
242
|
+
// them keeps `activeCellId` pinned on the skipped cell.
|
|
243
|
+
const reportCurrentCell = useEvent(() => {
|
|
244
|
+
if (skippedPreviewCell != null) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const deck = deckRef.current;
|
|
248
|
+
if (!deck) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const flatIndex = resolveActiveCellIndex(
|
|
252
|
+
targetToCellIndex,
|
|
253
|
+
deck.getIndices(),
|
|
254
|
+
);
|
|
255
|
+
if (flatIndex != null) {
|
|
256
|
+
onSlideChange?.(flatIndex);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// While parked on a skipped preview, step through minimap order instead of
|
|
261
|
+
// letting reveal.js advance from the parked slide the user can't see.
|
|
262
|
+
const handleParkedNavKey = useEvent((event: KeyboardEvent) => {
|
|
263
|
+
if (!skippedPreviewCell || activeIndex == null) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (Events.fromInput(event)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const direction = classifyNavKey(event);
|
|
270
|
+
if (direction === 0) {
|
|
31
271
|
return;
|
|
32
272
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
const nextIndex = activeIndex + direction;
|
|
276
|
+
if (nextIndex < 0 || nextIndex >= cellsWithOutput.length) {
|
|
277
|
+
return;
|
|
36
278
|
}
|
|
37
|
-
|
|
279
|
+
onSlideChange?.(nextIndex);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
useEventListener(document, "keydown", handleParkedNavKey, { capture: true });
|
|
38
283
|
|
|
39
284
|
return (
|
|
40
|
-
<div className="
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
className="
|
|
44
|
-
style={{ height: "100%" }}
|
|
45
|
-
config={{
|
|
46
|
-
embedded: true, // Avoid styles leaking out
|
|
47
|
-
overview: false,
|
|
48
|
-
width: "100%",
|
|
49
|
-
height: "100%", // Both style and config height are needed to ensure the deck is full height
|
|
50
|
-
center: false, // We are handling this manually
|
|
51
|
-
minScale: 1,
|
|
52
|
-
maxScale: 1,
|
|
53
|
-
// Only enable keyboard controls when not focused on an input
|
|
54
|
-
keyboardCondition: (event: KeyboardEvent) => {
|
|
55
|
-
return !Events.fromInput(event);
|
|
56
|
-
},
|
|
57
|
-
}}
|
|
58
|
-
onSlideChange={() => {
|
|
59
|
-
const deck = deckRef.current;
|
|
60
|
-
if (deck) {
|
|
61
|
-
onSlideChange?.(deck.getIndices().h);
|
|
62
|
-
// Trigger resize so vega-embed re-measures container width
|
|
63
|
-
if (
|
|
64
|
-
deck.getCurrentSlide()?.querySelector(".vega-embed, marimo-vega")
|
|
65
|
-
) {
|
|
66
|
-
requestAnimationFrame(() => {
|
|
67
|
-
window.dispatchEvent(new Event("resize"));
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}}
|
|
285
|
+
<div className="flex-1 min-w-0 flex flex-row gap-3">
|
|
286
|
+
<div
|
|
287
|
+
ref={containerRef}
|
|
288
|
+
className="flex-1 min-w-0 flex items-center justify-center overflow-hidden"
|
|
72
289
|
>
|
|
73
|
-
{
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
290
|
+
<div className="group relative" style={{ width, height }}>
|
|
291
|
+
<Deck
|
|
292
|
+
deckRef={deckRef}
|
|
293
|
+
className="aspect-video w-full overflow-hidden border rounded bg-background mo-slides-theme prose-slides"
|
|
294
|
+
config={revealConfig}
|
|
295
|
+
onSlideChange={() => {
|
|
296
|
+
reportCurrentCell();
|
|
297
|
+
const deck = deckRef.current;
|
|
298
|
+
triggerResize(deck);
|
|
299
|
+
}}
|
|
300
|
+
onFragmentShown={reportCurrentCell}
|
|
301
|
+
onFragmentHidden={reportCurrentCell}
|
|
302
|
+
>
|
|
303
|
+
{composition.stacks.map((stack, i) => {
|
|
304
|
+
if (stack.subslides.length === 1) {
|
|
305
|
+
return <SubslideView key={i} subslide={stack.subslides[0]} />;
|
|
306
|
+
}
|
|
307
|
+
return (
|
|
308
|
+
<Stack key={i}>
|
|
309
|
+
{stack.subslides.map((sub, j) => (
|
|
310
|
+
<SubslideView key={j} subslide={sub} />
|
|
311
|
+
))}
|
|
312
|
+
</Stack>
|
|
313
|
+
);
|
|
314
|
+
})}
|
|
315
|
+
</Deck>
|
|
316
|
+
{skippedPreviewCell && (
|
|
317
|
+
<div
|
|
318
|
+
className="absolute inset-0 z-10 border rounded bg-background flex flex-col overflow-hidden"
|
|
319
|
+
aria-label="Skipped in presentation"
|
|
320
|
+
>
|
|
321
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground border-b bg-muted/40">
|
|
322
|
+
<EyeOffIcon className="h-3.5 w-3.5" />
|
|
323
|
+
<span>Skipped in presentation</span>
|
|
324
|
+
</div>
|
|
325
|
+
<div className="flex-1 overflow-auto flex">
|
|
326
|
+
<div
|
|
327
|
+
className="mo-slide-content"
|
|
328
|
+
style={{ margin: "auto 20px" }}
|
|
329
|
+
>
|
|
330
|
+
<CellOutputSlide
|
|
331
|
+
cellId={skippedPreviewCell.id}
|
|
332
|
+
status={skippedPreviewCell.status}
|
|
333
|
+
output={skippedPreviewCell.output}
|
|
334
|
+
/>
|
|
335
|
+
</div>
|
|
82
336
|
</div>
|
|
83
337
|
</div>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
</
|
|
104
|
-
</
|
|
338
|
+
)}
|
|
339
|
+
<Tooltip content="Fullscreen (F)">
|
|
340
|
+
<Button
|
|
341
|
+
data-testid="marimo-plugin-slides-fullscreen"
|
|
342
|
+
variant="ghost"
|
|
343
|
+
size="icon"
|
|
344
|
+
className="absolute top-2 right-2 z-20 opacity-0 group-hover:opacity-70 text-muted-foreground transition-opacity h-7 w-7"
|
|
345
|
+
onClick={() => {
|
|
346
|
+
deckRef.current
|
|
347
|
+
?.getViewportElement()
|
|
348
|
+
?.requestFullscreen()
|
|
349
|
+
.catch((error) => {
|
|
350
|
+
Logger.error("Failed to request fullscreen", error);
|
|
351
|
+
});
|
|
352
|
+
}}
|
|
353
|
+
>
|
|
354
|
+
<ExpandIcon className="h-4 w-4" />
|
|
355
|
+
</Button>
|
|
356
|
+
</Tooltip>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
{mode !== "read" && (
|
|
361
|
+
<SlideSidebar
|
|
362
|
+
configWidth={configWidth}
|
|
363
|
+
layout={layout}
|
|
364
|
+
setLayout={setLayout}
|
|
365
|
+
activeConfigCell={activeConfigCell}
|
|
366
|
+
/>
|
|
367
|
+
)}
|
|
105
368
|
</div>
|
|
106
369
|
);
|
|
107
370
|
};
|
|
@@ -37,6 +37,38 @@
|
|
|
37
37
|
text-align: unset;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
.reveal .slides > section
|
|
40
|
+
.reveal .slides > section,
|
|
41
|
+
.reveal .slides > section > section {
|
|
41
42
|
height: 100%;
|
|
42
43
|
}
|
|
44
|
+
|
|
45
|
+
/* Reveal.js animates slides by rendering past/future neighbors in the layout
|
|
46
|
+
and translating them offscreen. Tailwind v4's preflight forces `[hidden]`
|
|
47
|
+
elements to `display: none !important`, which collapses those neighbors
|
|
48
|
+
and kills the transition. Reasserting `display: block` inside the same
|
|
49
|
+
`@layer base` lets selector specificity beat preflight's `[hidden]` rule. */
|
|
50
|
+
@layer base {
|
|
51
|
+
.reveal .slides > section.past,
|
|
52
|
+
.reveal .slides > section.future,
|
|
53
|
+
.reveal .slides > section > section.past,
|
|
54
|
+
.reveal .slides > section > section.future {
|
|
55
|
+
display: block !important;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Without this, the slides will animate as if from the edge of the screen. We hide this unless fullscreen */
|
|
60
|
+
.reveal-viewport:not(:fullscreen) {
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Reveal slides can contain multiple blocks on a single subslide (the root
|
|
65
|
+
cell plus one or more fragments), so we stack vertically and stretch each
|
|
66
|
+
block to full width so its content stays left-aligned like a single cell
|
|
67
|
+
would. The base `.mo-slide-content` stays a horizontal shrink-wrap flex for
|
|
68
|
+
swiper/minimap, which only ever render one child. */
|
|
69
|
+
.reveal .mo-slide-content {
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
}
|
|
72
|
+
.reveal .mo-slide-content .output {
|
|
73
|
+
margin: 0;
|
|
74
|
+
}
|