@particle-academy/fancy-slides 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -2,6 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, CSSProperties } from 'react';
3
3
  import { h as Slide$1, o as Theme, j as SlideElement, D as Deck, d as DeckOp, g as ShapeKind, l as SlideTransition, i as SlideBackground, E as ElementAnimation, m as TextElement, I as ImageElement, S as ShapeElement, k as SlideLayout } from './types-9BbelJX1.cjs';
4
4
  export { A as AnimationEffect, a as AnimationTrigger, C as ChartElement, b as CodeElement, c as DeckActivity, e as ElementBase, f as EmbedElement, T as TableElement, n as TextStyle, p as ThemeColors, q as ThemeFonts, r as TransitionKind } from './types-9BbelJX1.cjs';
5
+ import { EditorAction } from '@particle-academy/react-fancy';
5
6
 
6
7
  interface SlideProps {
7
8
  /** The slide to render. */
@@ -181,6 +182,16 @@ interface SlideThumbnailProps {
181
182
  * presenter view, and anywhere else a deck wants to show its slides as
182
183
  * thumbnails. Re-uses the shared <Slide> so the layout matches the viewer
183
184
  * exactly — no second rendering path.
185
+ *
186
+ * The slide is rendered at its full DESIGN width and the whole thing is
187
+ * CSS-`scale()`d down to the thumbnail size (the same approach fancy-artboard
188
+ * uses for its piece previews). Scaling the rendered output — rather than
189
+ * rendering the slide *at* the thumbnail width — is what makes heavy embedded
190
+ * surfaces (ECharts charts, the fancy-code editor) shrink proportionally:
191
+ * those render at fixed internal font sizes that ignore `slideWidthPx`, so a
192
+ * directly-undersized render leaves them oversized in the thumb. A uniform
193
+ * transform shrinks everything identically, so the thumb is a faithful
194
+ * miniature of the live slide.
184
195
  */
185
196
  declare function SlideThumbnail({ slide, theme, width, active, onClick, onContextMenu, renderElement, className, style, }: SlideThumbnailProps): react_jsx_runtime.JSX.Element;
186
197
 
@@ -474,6 +485,29 @@ interface TextElementRendererProps {
474
485
  */
475
486
  declare function TextElementRenderer({ element, theme, slideWidthPx, editing, selected, onContentChange, paraReveal, }: TextElementRendererProps): react_jsx_runtime.JSX.Element;
476
487
 
488
+ /**
489
+ * Presentation-tuned toolbar preset for the react-fancy `Editor` used to edit
490
+ * slide text inline. Only commands that round-trip cleanly through the editor's
491
+ * `htmlToMarkdown` output are included — bold (`**`), italic (`*`), heading
492
+ * (`## `), and bullet list (`- `). Box-level typography (alignment, color, font
493
+ * size, line height) is NOT here: those are per-element `TextStyle` properties
494
+ * edited in the ElementInspector, and `text-align`/color spans don't survive the
495
+ * markdown the slide content commits to.
496
+ */
497
+ declare const PRESENTATION_EDITOR_ACTIONS: EditorAction[];
498
+ /**
499
+ * Normalize the `Editor`'s markdown output to the *line-based* paragraph model
500
+ * the slide content commits to. The editor emits a blank line (`\n\n`) between
501
+ * `<p>` blocks, but `splitParagraphs` (and the dark-slide pptx writer) treat a
502
+ * single `\n` as one paragraph / build unit, and a blank interior line as its
503
+ * own (phantom) build. Collapsing runs of newlines to a single `\n` keeps
504
+ * "by paragraph" reveals and per-paragraph pptx builds correct — bullets are
505
+ * already one-per-`\n`, so they're unaffected. The round-trip invariant
506
+ * (`content → Editor → normalize → content` preserves the `\n` paragraph count)
507
+ * is covered by a unit test.
508
+ */
509
+ declare function normalizeSlideMarkdown(md: string): string;
510
+
477
511
  interface ImageElementRendererProps {
478
512
  element: ImageElement;
479
513
  }
@@ -637,4 +671,4 @@ type ChartKind = "bar" | "line" | "pie" | "area" | "scatter";
637
671
  type Option = Record<string, unknown>;
638
672
  declare function chartStarterOption(kind: ChartKind): Option;
639
673
 
640
- export { type Build, type BuildStep, type ChartKind$1 as ChartKind, Deck, DeckEditor, type DeckEditorProps, DeckOp, type DeckStateApi, EditorToolbar, type EditorToolbarProps, ElementAnimation, ElementInspector, type ElementInspectorProps, ImageElement, ImageElementRenderer, type ImageElementRendererProps, type ParaReveal, PresenterView, type PresenterViewProps, ShapeElement, ShapeElementRenderer, type ShapeElementRendererProps, ShapeKind, Slide, SlideBackground, type SlideContextValue, Slide$1 as SlideData, SlideElement, type SlideKeyboardOptions, SlideLayout, type SlideProps, SlideRail, type SlideRailProps, SlideThumbnail, type SlideThumbnailProps, SlideTransition, SlideViewer, type SlideViewerProps, SpeakerNotes, type SpeakerNotesProps, TextElement, TextElementRenderer, type TextElementRendererProps, Theme, type UseDeckStateOptions, buildSteps, buildsForStep, builtinThemes, chartStarterOption, collectBuilds, darkTheme, deckId, defaultTheme, defineTheme, elementId, isByParagraph, nextId, paragraphReveals, reduce as reduceDeck, resolveTheme, slideId, splitParagraphs, stepDelays, totalBuildSteps, useDeckState, useIsDarkSlide, useSlideContext, useSlideKeyboard, useSlideTheme, visibleElementIds, vividTheme };
674
+ export { type Build, type BuildStep, type ChartKind$1 as ChartKind, Deck, DeckEditor, type DeckEditorProps, DeckOp, type DeckStateApi, EditorToolbar, type EditorToolbarProps, ElementAnimation, ElementInspector, type ElementInspectorProps, ImageElement, ImageElementRenderer, type ImageElementRendererProps, PRESENTATION_EDITOR_ACTIONS, type ParaReveal, PresenterView, type PresenterViewProps, ShapeElement, ShapeElementRenderer, type ShapeElementRendererProps, ShapeKind, Slide, SlideBackground, type SlideContextValue, Slide$1 as SlideData, SlideElement, type SlideKeyboardOptions, SlideLayout, type SlideProps, SlideRail, type SlideRailProps, SlideThumbnail, type SlideThumbnailProps, SlideTransition, SlideViewer, type SlideViewerProps, SpeakerNotes, type SpeakerNotesProps, TextElement, TextElementRenderer, type TextElementRendererProps, Theme, type UseDeckStateOptions, buildSteps, buildsForStep, builtinThemes, chartStarterOption, collectBuilds, darkTheme, deckId, defaultTheme, defineTheme, elementId, isByParagraph, nextId, normalizeSlideMarkdown, paragraphReveals, reduce as reduceDeck, resolveTheme, slideId, splitParagraphs, stepDelays, totalBuildSteps, useDeckState, useIsDarkSlide, useSlideContext, useSlideKeyboard, useSlideTheme, visibleElementIds, vividTheme };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, CSSProperties } from 'react';
3
3
  import { h as Slide$1, o as Theme, j as SlideElement, D as Deck, d as DeckOp, g as ShapeKind, l as SlideTransition, i as SlideBackground, E as ElementAnimation, m as TextElement, I as ImageElement, S as ShapeElement, k as SlideLayout } from './types-9BbelJX1.js';
4
4
  export { A as AnimationEffect, a as AnimationTrigger, C as ChartElement, b as CodeElement, c as DeckActivity, e as ElementBase, f as EmbedElement, T as TableElement, n as TextStyle, p as ThemeColors, q as ThemeFonts, r as TransitionKind } from './types-9BbelJX1.js';
5
+ import { EditorAction } from '@particle-academy/react-fancy';
5
6
 
6
7
  interface SlideProps {
7
8
  /** The slide to render. */
@@ -181,6 +182,16 @@ interface SlideThumbnailProps {
181
182
  * presenter view, and anywhere else a deck wants to show its slides as
182
183
  * thumbnails. Re-uses the shared <Slide> so the layout matches the viewer
183
184
  * exactly — no second rendering path.
185
+ *
186
+ * The slide is rendered at its full DESIGN width and the whole thing is
187
+ * CSS-`scale()`d down to the thumbnail size (the same approach fancy-artboard
188
+ * uses for its piece previews). Scaling the rendered output — rather than
189
+ * rendering the slide *at* the thumbnail width — is what makes heavy embedded
190
+ * surfaces (ECharts charts, the fancy-code editor) shrink proportionally:
191
+ * those render at fixed internal font sizes that ignore `slideWidthPx`, so a
192
+ * directly-undersized render leaves them oversized in the thumb. A uniform
193
+ * transform shrinks everything identically, so the thumb is a faithful
194
+ * miniature of the live slide.
184
195
  */
185
196
  declare function SlideThumbnail({ slide, theme, width, active, onClick, onContextMenu, renderElement, className, style, }: SlideThumbnailProps): react_jsx_runtime.JSX.Element;
186
197
 
@@ -474,6 +485,29 @@ interface TextElementRendererProps {
474
485
  */
475
486
  declare function TextElementRenderer({ element, theme, slideWidthPx, editing, selected, onContentChange, paraReveal, }: TextElementRendererProps): react_jsx_runtime.JSX.Element;
476
487
 
488
+ /**
489
+ * Presentation-tuned toolbar preset for the react-fancy `Editor` used to edit
490
+ * slide text inline. Only commands that round-trip cleanly through the editor's
491
+ * `htmlToMarkdown` output are included — bold (`**`), italic (`*`), heading
492
+ * (`## `), and bullet list (`- `). Box-level typography (alignment, color, font
493
+ * size, line height) is NOT here: those are per-element `TextStyle` properties
494
+ * edited in the ElementInspector, and `text-align`/color spans don't survive the
495
+ * markdown the slide content commits to.
496
+ */
497
+ declare const PRESENTATION_EDITOR_ACTIONS: EditorAction[];
498
+ /**
499
+ * Normalize the `Editor`'s markdown output to the *line-based* paragraph model
500
+ * the slide content commits to. The editor emits a blank line (`\n\n`) between
501
+ * `<p>` blocks, but `splitParagraphs` (and the dark-slide pptx writer) treat a
502
+ * single `\n` as one paragraph / build unit, and a blank interior line as its
503
+ * own (phantom) build. Collapsing runs of newlines to a single `\n` keeps
504
+ * "by paragraph" reveals and per-paragraph pptx builds correct — bullets are
505
+ * already one-per-`\n`, so they're unaffected. The round-trip invariant
506
+ * (`content → Editor → normalize → content` preserves the `\n` paragraph count)
507
+ * is covered by a unit test.
508
+ */
509
+ declare function normalizeSlideMarkdown(md: string): string;
510
+
477
511
  interface ImageElementRendererProps {
478
512
  element: ImageElement;
479
513
  }
@@ -637,4 +671,4 @@ type ChartKind = "bar" | "line" | "pie" | "area" | "scatter";
637
671
  type Option = Record<string, unknown>;
638
672
  declare function chartStarterOption(kind: ChartKind): Option;
639
673
 
640
- export { type Build, type BuildStep, type ChartKind$1 as ChartKind, Deck, DeckEditor, type DeckEditorProps, DeckOp, type DeckStateApi, EditorToolbar, type EditorToolbarProps, ElementAnimation, ElementInspector, type ElementInspectorProps, ImageElement, ImageElementRenderer, type ImageElementRendererProps, type ParaReveal, PresenterView, type PresenterViewProps, ShapeElement, ShapeElementRenderer, type ShapeElementRendererProps, ShapeKind, Slide, SlideBackground, type SlideContextValue, Slide$1 as SlideData, SlideElement, type SlideKeyboardOptions, SlideLayout, type SlideProps, SlideRail, type SlideRailProps, SlideThumbnail, type SlideThumbnailProps, SlideTransition, SlideViewer, type SlideViewerProps, SpeakerNotes, type SpeakerNotesProps, TextElement, TextElementRenderer, type TextElementRendererProps, Theme, type UseDeckStateOptions, buildSteps, buildsForStep, builtinThemes, chartStarterOption, collectBuilds, darkTheme, deckId, defaultTheme, defineTheme, elementId, isByParagraph, nextId, paragraphReveals, reduce as reduceDeck, resolveTheme, slideId, splitParagraphs, stepDelays, totalBuildSteps, useDeckState, useIsDarkSlide, useSlideContext, useSlideKeyboard, useSlideTheme, visibleElementIds, vividTheme };
674
+ export { type Build, type BuildStep, type ChartKind$1 as ChartKind, Deck, DeckEditor, type DeckEditorProps, DeckOp, type DeckStateApi, EditorToolbar, type EditorToolbarProps, ElementAnimation, ElementInspector, type ElementInspectorProps, ImageElement, ImageElementRenderer, type ImageElementRendererProps, PRESENTATION_EDITOR_ACTIONS, type ParaReveal, PresenterView, type PresenterViewProps, ShapeElement, ShapeElementRenderer, type ShapeElementRendererProps, ShapeKind, Slide, SlideBackground, type SlideContextValue, Slide$1 as SlideData, SlideElement, type SlideKeyboardOptions, SlideLayout, type SlideProps, SlideRail, type SlideRailProps, SlideThumbnail, type SlideThumbnailProps, SlideTransition, SlideViewer, type SlideViewerProps, SpeakerNotes, type SpeakerNotesProps, TextElement, TextElementRenderer, type TextElementRendererProps, Theme, type UseDeckStateOptions, buildSteps, buildsForStep, builtinThemes, chartStarterOption, collectBuilds, darkTheme, deckId, defaultTheme, defineTheme, elementId, isByParagraph, nextId, normalizeSlideMarkdown, paragraphReveals, reduce as reduceDeck, resolveTheme, slideId, splitParagraphs, stepDelays, totalBuildSteps, useDeckState, useIsDarkSlide, useSlideContext, useSlideKeyboard, useSlideTheme, visibleElementIds, vividTheme };
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@ import { defaultElementRegistry } from './chunk-YEJZYKVB.js';
2
2
  import { isDarkColor, SlideContext } from './chunk-WIUXPQAK.js';
3
3
  export { useIsDarkSlide, useSlideContext, useSlideTheme } from './chunk-WIUXPQAK.js';
4
4
  import { useId, useRef, useState, useEffect, useMemo, useCallback } from 'react';
5
- import { ContentRenderer, Text, Action, ContextMenu, Separator, Tooltip, Dropdown, Badge, Heading, Tabs, Card, Select, Input, ColorPicker, Slider, Switch, Textarea } from '@particle-academy/react-fancy';
6
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { Editor, ContentRenderer, Text, Action, ContextMenu, Separator, Tooltip, Dropdown, Badge, Heading, Tabs, Card, Select, Input, ColorPicker, Slider, Switch, Textarea } from '@particle-academy/react-fancy';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
 
8
8
  // src/theme/default-theme.ts
9
9
  var defaultTheme = {
@@ -268,6 +268,18 @@ var BUILD_KEYFRAMES = `
268
268
  }
269
269
  }
270
270
  `;
271
+
272
+ // src/components/elements/TextElement/editor-preset.ts
273
+ var PRESENTATION_EDITOR_ACTIONS = [
274
+ { icon: "B", label: "Bold", command: "bold" },
275
+ { icon: "I", label: "Italic", command: "italic" },
276
+ { icon: "H", label: "Heading", command: "formatBlock", commandArg: "<h2>" },
277
+ { icon: "P", label: "Paragraph", command: "formatBlock", commandArg: "<p>" },
278
+ { icon: "\u2022", label: "Bullet list", command: "insertUnorderedList" }
279
+ ];
280
+ function normalizeSlideMarkdown(md) {
281
+ return md.replace(/\r\n/g, "\n").replace(/\n{2,}/g, "\n").replace(/[ \t]+$/gm, "").trim();
282
+ }
271
283
  function TextElementRenderer({
272
284
  element,
273
285
  theme,
@@ -306,19 +318,58 @@ function TextElementRenderer({
306
318
  overflow: "hidden"
307
319
  };
308
320
  if (editing && selected) {
309
- return /* @__PURE__ */ jsx(
310
- "textarea",
321
+ const fontPx = Math.round((style.fontSize ?? 28) * scale);
322
+ const lh = style.lineHeight ?? 1.4;
323
+ const editScope = scopeId;
324
+ return /* @__PURE__ */ jsxs(
325
+ "div",
311
326
  {
312
- value: element.content,
313
- onChange: (e) => onContentChange?.(e.target.value),
327
+ "data-fs-edit-scope": editScope,
314
328
  style: {
315
- ...css,
316
- whiteSpace: "pre-wrap",
317
- resize: "none",
318
- border: "none",
329
+ width: "100%",
330
+ height: "100%",
319
331
  pointerEvents: "auto",
320
- cursor: "text"
321
- }
332
+ cursor: "text",
333
+ textAlign: style.align ?? "left",
334
+ color: style.color ?? t.colors?.text,
335
+ fontFamily: style.fontFamily ?? t.fonts?.body
336
+ },
337
+ onPointerDown: (e) => e.stopPropagation(),
338
+ children: [
339
+ /* @__PURE__ */ jsx("style", { children: `
340
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor] {
341
+ border: none; background: transparent; border-radius: 0;
342
+ height: 100%; display: flex; flex-direction: column; overflow: hidden;
343
+ }
344
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-toolbar] {
345
+ background: rgba(244,244,245,0.85); border-radius: 6px 6px 0 0;
346
+ padding: 2px 4px;
347
+ }
348
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] {
349
+ flex: 1; min-height: 0; padding: 4px 2px; overflow: auto;
350
+ font-size: ${fontPx}px; line-height: ${lh};
351
+ }
352
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] :is(p, ul, ol, li) { font-size: inherit; margin: 0; }
353
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] :where(p, li) + :where(p, li, ul, ol) { margin-top: 0.4em; }
354
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] h1 { font-size: 1.6em; font-weight: 700; margin: 0; }
355
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] h2 { font-size: 1.35em; font-weight: 700; margin: 0; }
356
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] h3 { font-size: 1.15em; font-weight: 600; margin: 0; }
357
+ ` }),
358
+ /* @__PURE__ */ jsxs(
359
+ Editor,
360
+ {
361
+ value: element.content,
362
+ onChange: (md) => onContentChange?.(normalizeSlideMarkdown(md)),
363
+ outputFormat: "markdown",
364
+ lineSpacing: lh,
365
+ children: [
366
+ /* @__PURE__ */ jsx(Editor.Toolbar, { actions: PRESENTATION_EDITOR_ACTIONS }),
367
+ /* @__PURE__ */ jsx(Editor.Content, {})
368
+ ]
369
+ },
370
+ element.id
371
+ )
372
+ ]
322
373
  }
323
374
  );
324
375
  }
@@ -1407,6 +1458,11 @@ function SlideThumbnail({
1407
1458
  className,
1408
1459
  style
1409
1460
  }) {
1461
+ const resolved = resolveTheme(theme);
1462
+ const ratio = resolved.aspectRatio ?? 16 / 9;
1463
+ const designWidth = resolved.slideWidth ?? 1280;
1464
+ const scale = width / designWidth;
1465
+ const height = width / ratio;
1410
1466
  return /* @__PURE__ */ jsx(
1411
1467
  "div",
1412
1468
  {
@@ -1419,12 +1475,27 @@ function SlideThumbnail({
1419
1475
  boxShadow: active ? "0 0 0 3px rgba(139, 92, 246, 0.2)" : "0 1px 2px rgba(0,0,0,0.05)",
1420
1476
  background: "#ffffff",
1421
1477
  width,
1478
+ height,
1422
1479
  ...style
1423
1480
  },
1424
1481
  onClick,
1425
1482
  onContextMenu,
1426
1483
  "data-fancy-slides-thumbnail": slide.id,
1427
- children: /* @__PURE__ */ jsx(Slide, { slide, theme, width, renderElement })
1484
+ children: /* @__PURE__ */ jsx(
1485
+ "div",
1486
+ {
1487
+ style: {
1488
+ width: designWidth,
1489
+ height: designWidth / ratio,
1490
+ transform: `scale(${scale})`,
1491
+ transformOrigin: "top left",
1492
+ // The thumb owns interaction — charts/code/iframes inside the
1493
+ // scaled slide shouldn't capture clicks.
1494
+ pointerEvents: "none"
1495
+ },
1496
+ children: /* @__PURE__ */ jsx(Slide, { slide, theme, width: designWidth, renderElement })
1497
+ }
1498
+ )
1428
1499
  }
1429
1500
  );
1430
1501
  }
@@ -2861,7 +2932,7 @@ function DeckEditor({
2861
2932
  slide,
2862
2933
  theme: deck.theme,
2863
2934
  editing: true,
2864
- onElementContentChange: (eid, content) => ops.updateElement(slide.id, eid, { content }),
2935
+ onElementContentChange: (eid, content) => ops.updateElement(slide.id, eid, { content, format: "markdown" }),
2865
2936
  onElementSelect: setElementIdSelected,
2866
2937
  selectedElementId: elementIdSelected,
2867
2938
  onElementMove: (eid, x, y) => ops.moveElement(slide.id, eid, x, y),
@@ -2898,6 +2969,6 @@ function DeckEditor({
2898
2969
  );
2899
2970
  }
2900
2971
 
2901
- export { DeckEditor, EditorToolbar, ElementInspector, ImageElementRenderer, PresenterView, ShapeElementRenderer, Slide, SlideRail, SlideThumbnail, SlideViewer, SpeakerNotes, TextElementRenderer, buildSteps, buildsForStep, builtinThemes, chartStarterOption, collectBuilds, darkTheme, deckId, defaultTheme, defineTheme, elementId, isByParagraph, nextId, paragraphReveals, reduce as reduceDeck, resolveTheme, slideId, splitParagraphs, stepDelays, totalBuildSteps, useDeckState, useSlideKeyboard, visibleElementIds, vividTheme };
2972
+ export { DeckEditor, EditorToolbar, ElementInspector, ImageElementRenderer, PRESENTATION_EDITOR_ACTIONS, PresenterView, ShapeElementRenderer, Slide, SlideRail, SlideThumbnail, SlideViewer, SpeakerNotes, TextElementRenderer, buildSteps, buildsForStep, builtinThemes, chartStarterOption, collectBuilds, darkTheme, deckId, defaultTheme, defineTheme, elementId, isByParagraph, nextId, normalizeSlideMarkdown, paragraphReveals, reduce as reduceDeck, resolveTheme, slideId, splitParagraphs, stepDelays, totalBuildSteps, useDeckState, useSlideKeyboard, visibleElementIds, vividTheme };
2902
2973
  //# sourceMappingURL=index.js.map
2903
2974
  //# sourceMappingURL=index.js.map