@marimo-team/islands 0.23.15-dev11 → 0.23.15-dev13
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/{code-visibility-HO1U02IJ.js → code-visibility-5ORyMumO.js} +922 -839
- package/dist/main.js +970 -965
- package/dist/{reveal-component-BuQ2GKAM.js → reveal-component-BFBqn3Kj.js} +613 -601
- package/package.json +1 -1
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +29 -0
- package/src/components/editor/renderers/slides-layout/types.ts +4 -0
- package/src/components/slides/reveal-component.tsx +33 -4
- package/src/components/slides/slide-form.tsx +72 -0
package/package.json
CHANGED
|
@@ -42,6 +42,23 @@ describe("SlidesLayoutPlugin validator", () => {
|
|
|
42
42
|
}).success,
|
|
43
43
|
).toBe(false);
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
it("accepts each valid deck vertical alignment", () => {
|
|
47
|
+
for (const verticalAlign of ["top", "center", "bottom"]) {
|
|
48
|
+
expect(
|
|
49
|
+
SlidesLayoutPlugin.validator.safeParse({ deck: { verticalAlign } })
|
|
50
|
+
.success,
|
|
51
|
+
).toBe(true);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("rejects an unknown deck vertical alignment", () => {
|
|
56
|
+
expect(
|
|
57
|
+
SlidesLayoutPlugin.validator.safeParse({
|
|
58
|
+
deck: { verticalAlign: "middle" },
|
|
59
|
+
}).success,
|
|
60
|
+
).toBe(false);
|
|
61
|
+
});
|
|
45
62
|
});
|
|
46
63
|
|
|
47
64
|
describe("SlidesLayoutPlugin deserializeLayout", () => {
|
|
@@ -268,6 +285,18 @@ const BACKWARDS_COMPAT_SNAPSHOTS: BackwardsCompatCase[] = [
|
|
|
268
285
|
],
|
|
269
286
|
},
|
|
270
287
|
},
|
|
288
|
+
{
|
|
289
|
+
// `verticalAlign` was added to DeckConfig.
|
|
290
|
+
label: "deck.verticalAlign round-trips through validate + (de)serialize",
|
|
291
|
+
input: {
|
|
292
|
+
cells: [{}],
|
|
293
|
+
deck: { transition: "fade", verticalAlign: "top" },
|
|
294
|
+
},
|
|
295
|
+
expected: {
|
|
296
|
+
deck: { transition: "fade", verticalAlign: "top" },
|
|
297
|
+
cellIds: ["a"],
|
|
298
|
+
},
|
|
299
|
+
},
|
|
271
300
|
];
|
|
272
301
|
|
|
273
302
|
describe("SlidesLayoutPlugin backwards compatibility", () => {
|
|
@@ -23,8 +23,12 @@ const DeckTransitionSchema = z.enum([
|
|
|
23
23
|
]);
|
|
24
24
|
export type DeckTransition = z.infer<typeof DeckTransitionSchema>;
|
|
25
25
|
|
|
26
|
+
const DeckVerticalAlignSchema = z.enum(["top", "center", "bottom"]);
|
|
27
|
+
export type DeckVerticalAlign = z.infer<typeof DeckVerticalAlignSchema>;
|
|
28
|
+
|
|
26
29
|
const DeckConfigSchema = z.looseObject({
|
|
27
30
|
transition: DeckTransitionSchema.optional(),
|
|
31
|
+
verticalAlign: DeckVerticalAlignSchema.optional(),
|
|
28
32
|
});
|
|
29
33
|
export type DeckConfig = z.infer<typeof DeckConfigSchema>;
|
|
30
34
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
type CSSProperties,
|
|
4
5
|
startTransition,
|
|
5
6
|
useEffect,
|
|
6
7
|
useMemo,
|
|
@@ -26,6 +27,7 @@ import { Logger } from "@/utils/Logger";
|
|
|
26
27
|
import "./slides.css";
|
|
27
28
|
import "./reveal-slides.css";
|
|
28
29
|
import type {
|
|
30
|
+
DeckVerticalAlign,
|
|
29
31
|
SlideConfig,
|
|
30
32
|
SlidesLayout,
|
|
31
33
|
SlideType,
|
|
@@ -39,6 +41,7 @@ import {
|
|
|
39
41
|
type ComposedSubslide,
|
|
40
42
|
} from "./compose-slides";
|
|
41
43
|
import {
|
|
44
|
+
DEFAULT_DECK_VERTICAL_ALIGN,
|
|
42
45
|
DEFAULT_DECK_TRANSITION,
|
|
43
46
|
DEFAULT_SLIDE_TYPE,
|
|
44
47
|
SlideSidebar,
|
|
@@ -292,16 +295,38 @@ export function useParkedPreview(options: {
|
|
|
292
295
|
};
|
|
293
296
|
}
|
|
294
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Margin style that positions a slide's content vertically within the
|
|
300
|
+
* full-height slide. The content is a flex item, so the vertical margins decide
|
|
301
|
+
* where the free space lands: `auto` on both sides centers it, while pinning one
|
|
302
|
+
* side to `0` pushes content to the top or bottom. The horizontal `20px` keeps
|
|
303
|
+
* content off the slide edges regardless of alignment.
|
|
304
|
+
*/
|
|
305
|
+
function resolveSlideContentStyle(
|
|
306
|
+
verticalAlign: DeckVerticalAlign | undefined,
|
|
307
|
+
): CSSProperties {
|
|
308
|
+
switch (verticalAlign ?? DEFAULT_DECK_VERTICAL_ALIGN) {
|
|
309
|
+
case "top":
|
|
310
|
+
return { margin: "0 20px auto" };
|
|
311
|
+
case "bottom":
|
|
312
|
+
return { margin: "auto 20px 0" };
|
|
313
|
+
default:
|
|
314
|
+
return { margin: "auto 20px" };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
295
318
|
const SubslideView = ({
|
|
296
319
|
subslide,
|
|
297
320
|
resolveShowCode,
|
|
298
321
|
isEditable,
|
|
299
322
|
slideConfigs,
|
|
323
|
+
contentStyle,
|
|
300
324
|
}: {
|
|
301
325
|
subslide: ComposedSubslide<RuntimeCell>;
|
|
302
326
|
resolveShowCode: (cellId: CellId) => boolean;
|
|
303
327
|
isEditable: boolean;
|
|
304
328
|
slideConfigs: ReadonlyMap<CellId, SlideConfig>;
|
|
329
|
+
contentStyle: CSSProperties;
|
|
305
330
|
}) => {
|
|
306
331
|
const { slideLevel, cumulativeByBlock } = buildSubslideNotes(
|
|
307
332
|
subslide,
|
|
@@ -321,9 +346,7 @@ const SubslideView = ({
|
|
|
321
346
|
? "mo-slide-content flex flex-col gap-3"
|
|
322
347
|
: "mo-slide-content"
|
|
323
348
|
}
|
|
324
|
-
style={
|
|
325
|
-
margin: "auto 20px",
|
|
326
|
-
}}
|
|
349
|
+
style={contentStyle}
|
|
327
350
|
>
|
|
328
351
|
{subslide.blocks.map((block, i) => {
|
|
329
352
|
const rendered = block.cells.map((cell) => {
|
|
@@ -514,6 +537,10 @@ const RevealSlidesComponent = ({
|
|
|
514
537
|
);
|
|
515
538
|
|
|
516
539
|
const deckTransition = layout.deck?.transition ?? DEFAULT_DECK_TRANSITION;
|
|
540
|
+
const slideContentStyle = resolveSlideContentStyle(
|
|
541
|
+
layout.deck?.verticalAlign,
|
|
542
|
+
);
|
|
543
|
+
|
|
517
544
|
// Reveal's Notes plugin iframes the deck for the current/upcoming-slide
|
|
518
545
|
// previews. We load the same URL but as a read-only kiosk client with the
|
|
519
546
|
// app chrome hidden, which `<SlidesLayoutRenderer>` interprets the same as
|
|
@@ -704,6 +731,7 @@ const RevealSlidesComponent = ({
|
|
|
704
731
|
resolveShowCode={resolveShowCode}
|
|
705
732
|
isEditable={isEditable}
|
|
706
733
|
slideConfigs={layout.cells}
|
|
734
|
+
contentStyle={slideContentStyle}
|
|
707
735
|
/>
|
|
708
736
|
);
|
|
709
737
|
}
|
|
@@ -717,6 +745,7 @@ const RevealSlidesComponent = ({
|
|
|
717
745
|
resolveShowCode={resolveShowCode}
|
|
718
746
|
isEditable={isEditable}
|
|
719
747
|
slideConfigs={layout.cells}
|
|
748
|
+
contentStyle={slideContentStyle}
|
|
720
749
|
/>
|
|
721
750
|
);
|
|
722
751
|
})}
|
|
@@ -743,7 +772,7 @@ const RevealSlidesComponent = ({
|
|
|
743
772
|
? "mo-slide-content flex flex-col gap-3"
|
|
744
773
|
: "mo-slide-content"
|
|
745
774
|
}
|
|
746
|
-
style={
|
|
775
|
+
style={slideContentStyle}
|
|
747
776
|
>
|
|
748
777
|
<ParkedPreviewContent
|
|
749
778
|
cell={parkedPreviewCell}
|
|
@@ -23,6 +23,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
|
23
23
|
import type { CellId } from "@/core/cells/ids";
|
|
24
24
|
import { cn } from "@/utils/cn";
|
|
25
25
|
import type {
|
|
26
|
+
DeckVerticalAlign,
|
|
26
27
|
DeckTransition,
|
|
27
28
|
SlidesLayout,
|
|
28
29
|
SlideType,
|
|
@@ -37,6 +38,7 @@ import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
|
37
38
|
|
|
38
39
|
export const DEFAULT_SLIDE_TYPE: SlideType = "slide";
|
|
39
40
|
export const DEFAULT_DECK_TRANSITION: DeckTransition = "slide";
|
|
41
|
+
export const DEFAULT_DECK_VERTICAL_ALIGN: DeckVerticalAlign = "center";
|
|
40
42
|
const COLLAPSED_CONFIG_WIDTH = 36;
|
|
41
43
|
const slideConfigOpenAtom = atomWithStorage<boolean>(
|
|
42
44
|
"marimo:slides:config-open",
|
|
@@ -118,6 +120,30 @@ const DECK_TRANSITION_OPTIONS: DeckTransitionOption[] = [
|
|
|
118
120
|
{ value: "zoom", label: "Zoom", description: "Zoom into the next slide." },
|
|
119
121
|
];
|
|
120
122
|
|
|
123
|
+
interface DeckVerticalAlignOption {
|
|
124
|
+
value: DeckVerticalAlign;
|
|
125
|
+
label: string;
|
|
126
|
+
description: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const DECK_VERTICAL_ALIGN_OPTIONS: DeckVerticalAlignOption[] = [
|
|
130
|
+
{
|
|
131
|
+
value: "center",
|
|
132
|
+
label: "Center",
|
|
133
|
+
description: "Vertically center each slide's content.",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
value: "top",
|
|
137
|
+
label: "Top",
|
|
138
|
+
description: "Align content to the top, like the cell view.",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
value: "bottom",
|
|
142
|
+
label: "Bottom",
|
|
143
|
+
description: "Align content to the bottom of each slide.",
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
121
147
|
const SlidesForm = ({
|
|
122
148
|
layout,
|
|
123
149
|
setLayout,
|
|
@@ -300,6 +326,12 @@ const DeckConfigForm = ({
|
|
|
300
326
|
(opt) => opt.value === currentTransition,
|
|
301
327
|
)?.description;
|
|
302
328
|
|
|
329
|
+
const currentVerticalAlign: DeckVerticalAlign =
|
|
330
|
+
layout.deck?.verticalAlign ?? DEFAULT_DECK_VERTICAL_ALIGN;
|
|
331
|
+
const activeVerticalAlignDescription = DECK_VERTICAL_ALIGN_OPTIONS.find(
|
|
332
|
+
(opt) => opt.value === currentVerticalAlign,
|
|
333
|
+
)?.description;
|
|
334
|
+
|
|
303
335
|
const handleTransitionChange = (value: DeckTransition) => {
|
|
304
336
|
setLayout({
|
|
305
337
|
...layout,
|
|
@@ -307,6 +339,13 @@ const DeckConfigForm = ({
|
|
|
307
339
|
});
|
|
308
340
|
};
|
|
309
341
|
|
|
342
|
+
const handleVerticalAlignChange = (value: DeckVerticalAlign) => {
|
|
343
|
+
setLayout({
|
|
344
|
+
...layout,
|
|
345
|
+
deck: { ...layout.deck, verticalAlign: value },
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
|
|
310
349
|
return (
|
|
311
350
|
<div className="flex flex-col gap-3">
|
|
312
351
|
<div className="flex flex-col gap-1.5">
|
|
@@ -337,6 +376,39 @@ const DeckConfigForm = ({
|
|
|
337
376
|
<p className="text-xs text-foreground/70">{activeDescription}</p>
|
|
338
377
|
)}
|
|
339
378
|
</div>
|
|
379
|
+
<div className="flex flex-col gap-1.5">
|
|
380
|
+
<label
|
|
381
|
+
htmlFor="deck-vertical-align"
|
|
382
|
+
className="font-semibold text-sm text-foreground"
|
|
383
|
+
>
|
|
384
|
+
Vertical alignment
|
|
385
|
+
</label>
|
|
386
|
+
<Select
|
|
387
|
+
value={currentVerticalAlign}
|
|
388
|
+
onValueChange={(value) =>
|
|
389
|
+
handleVerticalAlignChange(value as DeckVerticalAlign)
|
|
390
|
+
}
|
|
391
|
+
>
|
|
392
|
+
<SelectTrigger
|
|
393
|
+
id="deck-vertical-align"
|
|
394
|
+
aria-label="Vertical alignment"
|
|
395
|
+
>
|
|
396
|
+
<SelectValue />
|
|
397
|
+
</SelectTrigger>
|
|
398
|
+
<SelectContent>
|
|
399
|
+
{DECK_VERTICAL_ALIGN_OPTIONS.map(({ value, label }) => (
|
|
400
|
+
<SelectItem key={value} value={value}>
|
|
401
|
+
{label}
|
|
402
|
+
</SelectItem>
|
|
403
|
+
))}
|
|
404
|
+
</SelectContent>
|
|
405
|
+
</Select>
|
|
406
|
+
{activeVerticalAlignDescription && (
|
|
407
|
+
<p className="text-xs text-foreground/70">
|
|
408
|
+
{activeVerticalAlignDescription}
|
|
409
|
+
</p>
|
|
410
|
+
)}
|
|
411
|
+
</div>
|
|
340
412
|
</div>
|
|
341
413
|
);
|
|
342
414
|
};
|