@particle-academy/fancy-slides 0.6.1 → 0.8.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.cjs CHANGED
@@ -477,6 +477,18 @@ var BUILD_KEYFRAMES = `
477
477
  }
478
478
  }
479
479
  `;
480
+
481
+ // src/components/elements/TextElement/editor-preset.ts
482
+ var PRESENTATION_EDITOR_ACTIONS = [
483
+ { icon: "B", label: "Bold", command: "bold" },
484
+ { icon: "I", label: "Italic", command: "italic" },
485
+ { icon: "H", label: "Heading", command: "formatBlock", commandArg: "<h2>" },
486
+ { icon: "P", label: "Paragraph", command: "formatBlock", commandArg: "<p>" },
487
+ { icon: "\u2022", label: "Bullet list", command: "insertUnorderedList" }
488
+ ];
489
+ function normalizeSlideMarkdown(md) {
490
+ return md.replace(/\r\n/g, "\n").replace(/\n{2,}/g, "\n").replace(/[ \t]+$/gm, "").trim();
491
+ }
480
492
  function TextElementRenderer({
481
493
  element,
482
494
  theme,
@@ -515,19 +527,58 @@ function TextElementRenderer({
515
527
  overflow: "hidden"
516
528
  };
517
529
  if (editing && selected) {
518
- return /* @__PURE__ */ jsxRuntime.jsx(
519
- "textarea",
530
+ const fontPx = Math.round((style.fontSize ?? 28) * scale);
531
+ const lh = style.lineHeight ?? 1.4;
532
+ const editScope = scopeId;
533
+ return /* @__PURE__ */ jsxRuntime.jsxs(
534
+ "div",
520
535
  {
521
- value: element.content,
522
- onChange: (e) => onContentChange?.(e.target.value),
536
+ "data-fs-edit-scope": editScope,
523
537
  style: {
524
- ...css,
525
- whiteSpace: "pre-wrap",
526
- resize: "none",
527
- border: "none",
538
+ width: "100%",
539
+ height: "100%",
528
540
  pointerEvents: "auto",
529
- cursor: "text"
530
- }
541
+ cursor: "text",
542
+ textAlign: style.align ?? "left",
543
+ color: style.color ?? t.colors?.text,
544
+ fontFamily: style.fontFamily ?? t.fonts?.body
545
+ },
546
+ onPointerDown: (e) => e.stopPropagation(),
547
+ children: [
548
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
549
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor] {
550
+ border: none; background: transparent; border-radius: 0;
551
+ height: 100%; display: flex; flex-direction: column; overflow: hidden;
552
+ }
553
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-toolbar] {
554
+ background: rgba(244,244,245,0.85); border-radius: 6px 6px 0 0;
555
+ padding: 2px 4px;
556
+ }
557
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] {
558
+ flex: 1; min-height: 0; padding: 4px 2px; overflow: auto;
559
+ font-size: ${fontPx}px; line-height: ${lh};
560
+ }
561
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] :is(p, ul, ol, li) { font-size: inherit; margin: 0; }
562
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] :where(p, li) + :where(p, li, ul, ol) { margin-top: 0.4em; }
563
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] h1 { font-size: 1.6em; font-weight: 700; margin: 0; }
564
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] h2 { font-size: 1.35em; font-weight: 700; margin: 0; }
565
+ [data-fs-edit-scope="${editScope}"] [data-react-fancy-editor-content] h3 { font-size: 1.15em; font-weight: 600; margin: 0; }
566
+ ` }),
567
+ /* @__PURE__ */ jsxRuntime.jsxs(
568
+ reactFancy.Editor,
569
+ {
570
+ value: element.content,
571
+ onChange: (md) => onContentChange?.(normalizeSlideMarkdown(md)),
572
+ outputFormat: "markdown",
573
+ lineSpacing: lh,
574
+ children: [
575
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Editor.Toolbar, { actions: PRESENTATION_EDITOR_ACTIONS }),
576
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Editor.Content, {})
577
+ ]
578
+ },
579
+ element.id
580
+ )
581
+ ]
531
582
  }
532
583
  );
533
584
  }
@@ -2199,10 +2250,10 @@ function EditorToolbar({
2199
2250
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tooltip, { content: "Present (F)", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { color: "violet", size: "sm", icon: "play", onClick: onPresent, children: "Present" }) }) })
2200
2251
  ] });
2201
2252
  }
2202
- function ElementInspector({ element, onPatch, onDelete, onLockToggle, slide, onSetTransition, onSetBackground, onSetAnimation, onSetElementAnimation }) {
2253
+ function ElementInspector({ element, onPatch, onDelete, onLockToggle, slide, onSetTransition, onSetBackground, onSetLayout, onSetAnimation, onSetElementAnimation }) {
2203
2254
  if (!element) {
2204
2255
  if (slide) {
2205
- return /* @__PURE__ */ jsxRuntime.jsx(SlideSettings, { slide, onSetTransition, onSetBackground, onSetElementAnimation });
2256
+ return /* @__PURE__ */ jsxRuntime.jsx(SlideSettings, { slide, onSetTransition, onSetBackground, onSetLayout, onSetElementAnimation });
2206
2257
  }
2207
2258
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fs-inspector flex h-full flex-col border-l border-zinc-200 bg-zinc-50 p-4 dark:border-zinc-800 dark:bg-zinc-900", children: [
2208
2259
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h3", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Inspector" }),
@@ -2239,10 +2290,26 @@ function ElementInspector({ element, onPatch, onDelete, onLockToggle, slide, onS
2239
2290
  ] }) })
2240
2291
  ] });
2241
2292
  }
2293
+ var SLIDE_LAYOUTS = [
2294
+ { value: "blank", label: "Blank" },
2295
+ { value: "title", label: "Title" },
2296
+ { value: "title-content", label: "Title + content" },
2297
+ { value: "two-column", label: "Two column" },
2298
+ { value: "section-divider", label: "Section divider" },
2299
+ { value: "image-text", label: "Image + text" },
2300
+ { value: "text-image", label: "Text + image" },
2301
+ { value: "quote", label: "Quote" }
2302
+ ];
2303
+ function backgroundMode(bg) {
2304
+ if (bg?.gradient) return "gradient";
2305
+ if (bg?.image) return "image";
2306
+ return "color";
2307
+ }
2242
2308
  function SlideSettings({
2243
2309
  slide,
2244
2310
  onSetTransition,
2245
2311
  onSetBackground,
2312
+ onSetLayout,
2246
2313
  onSetElementAnimation
2247
2314
  }) {
2248
2315
  const transition = slide.transition;
@@ -2251,6 +2318,7 @@ function SlideSettings({
2251
2318
  const merged = { kind, duration: transition?.duration, direction: transition?.direction, ...next };
2252
2319
  onSetTransition?.(merged.kind === "none" ? { kind: "none" } : merged);
2253
2320
  };
2321
+ const bgMode = backgroundMode(slide.background);
2254
2322
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fs-inspector flex h-full w-full flex-col border-l border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900", children: [
2255
2323
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between border-b border-zinc-200 px-3 py-2 dark:border-zinc-800", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2256
2324
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h3", size: "xs", className: "!font-mono !uppercase !tracking-wider !text-zinc-500", children: "slide" }),
@@ -2260,6 +2328,19 @@ function SlideSettings({
2260
2328
  ] })
2261
2329
  ] }) }),
2262
2330
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto p-3", children: [
2331
+ onSetLayout && /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "mb-3 !bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2332
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h4", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Layout" }),
2333
+ /* @__PURE__ */ jsxRuntime.jsx(
2334
+ reactFancy.Select,
2335
+ {
2336
+ label: "Preset",
2337
+ list: SLIDE_LAYOUTS,
2338
+ value: slide.layout ?? "blank",
2339
+ onValueChange: (v) => onSetLayout(v)
2340
+ }
2341
+ ),
2342
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Text, { size: "xs", className: "!text-zinc-500", children: "The layout hint the deck commits to \u2014 carried through to the pptx export's slide layout." })
2343
+ ] }) }),
2263
2344
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2264
2345
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h4", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Transition" }),
2265
2346
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2303,13 +2384,63 @@ function SlideSettings({
2303
2384
  ] }) }),
2304
2385
  onSetBackground && /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "mt-3 !bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2305
2386
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h4", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Background" }),
2306
- /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, { label: "Color", children: /* @__PURE__ */ jsxRuntime.jsx(
2387
+ /* @__PURE__ */ jsxRuntime.jsx(
2388
+ reactFancy.Select,
2389
+ {
2390
+ label: "Type",
2391
+ list: [
2392
+ { value: "color", label: "Solid color" },
2393
+ { value: "gradient", label: "Gradient" },
2394
+ { value: "image", label: "Image" }
2395
+ ],
2396
+ value: bgMode,
2397
+ onValueChange: (v) => {
2398
+ if (v === "color") onSetBackground({ color: slide.background?.color ?? "#ffffff" });
2399
+ else if (v === "gradient") onSetBackground({ gradient: slide.background?.gradient ?? "linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)" });
2400
+ else onSetBackground({ image: slide.background?.image ?? "", imageFit: slide.background?.imageFit ?? "cover", color: slide.background?.color });
2401
+ }
2402
+ }
2403
+ ),
2404
+ bgMode === "color" && /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, { label: "Color", children: /* @__PURE__ */ jsxRuntime.jsx(
2307
2405
  reactFancy.ColorPicker,
2308
2406
  {
2309
2407
  value: slide.background?.color ?? "#ffffff",
2310
- onChange: (c) => onSetBackground({ ...slide.background, color: c })
2408
+ onChange: (c) => onSetBackground({ color: c })
2409
+ }
2410
+ ) }),
2411
+ bgMode === "gradient" && /* @__PURE__ */ jsxRuntime.jsx(
2412
+ reactFancy.Textarea,
2413
+ {
2414
+ label: "CSS gradient",
2415
+ value: slide.background?.gradient ?? "",
2416
+ onValueChange: (v) => onSetBackground({ gradient: v }),
2417
+ rows: 2
2311
2418
  }
2312
- ) })
2419
+ ),
2420
+ bgMode === "image" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2421
+ /* @__PURE__ */ jsxRuntime.jsx(
2422
+ reactFancy.Textarea,
2423
+ {
2424
+ label: "Image URL",
2425
+ value: slide.background?.image ?? "",
2426
+ onValueChange: (v) => onSetBackground({ ...slide.background, image: v }),
2427
+ rows: 2
2428
+ }
2429
+ ),
2430
+ /* @__PURE__ */ jsxRuntime.jsx(
2431
+ reactFancy.Select,
2432
+ {
2433
+ label: "Fit",
2434
+ list: [
2435
+ { value: "cover", label: "Cover" },
2436
+ { value: "contain", label: "Contain" },
2437
+ { value: "fill", label: "Fill (stretch)" }
2438
+ ],
2439
+ value: slide.background?.imageFit ?? "cover",
2440
+ onValueChange: (v) => onSetBackground({ ...slide.background, imageFit: v })
2441
+ }
2442
+ )
2443
+ ] })
2313
2444
  ] }) }),
2314
2445
  onSetElementAnimation && /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "mt-3 !bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(BuildOrderList, { slide, onSetElementAnimation }) })
2315
2446
  ] })
@@ -2550,6 +2681,24 @@ function TextStyleControls({ element, onPatch }) {
2550
2681
  onValueChange: (v) => setStyle({ align: v })
2551
2682
  }
2552
2683
  ),
2684
+ /* @__PURE__ */ jsxRuntime.jsx(
2685
+ reactFancy.Select,
2686
+ {
2687
+ label: "Vertical align",
2688
+ list: [
2689
+ { value: "top", label: "Top" },
2690
+ { value: "middle", label: "Middle" },
2691
+ { value: "bottom", label: "Bottom" }
2692
+ ],
2693
+ value: s.verticalAlign ?? "top",
2694
+ onValueChange: (v) => setStyle({ verticalAlign: v })
2695
+ }
2696
+ ),
2697
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { label: "Line height", type: "number", value: String(s.lineHeight ?? 1.4), onChange: (e) => setStyle({ lineHeight: parseFloat(e.target.value) || 1.4 }) }),
2698
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
2699
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Switch, { label: "Italic", checked: !!s.italic, onCheckedChange: (v) => setStyle({ italic: v }) }),
2700
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Switch, { label: "Underline", checked: !!s.underline, onCheckedChange: (v) => setStyle({ underline: v }) })
2701
+ ] }),
2553
2702
  /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, { label: "Color", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ColorPicker, { value: s.color ?? "#0f172a", onChange: (c) => setStyle({ color: c }) }) })
2554
2703
  ] });
2555
2704
  }
@@ -2632,6 +2781,7 @@ function ShapeStyleControls({ element, onPatch }) {
2632
2781
  /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, { label: "Fill", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ColorPicker, { value: element.fill ?? "#ffffff", onChange: (c) => onPatch({ fill: c }) }) }),
2633
2782
  /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, { label: "Stroke", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ColorPicker, { value: element.stroke ?? "#0f172a", onChange: (c) => onPatch({ stroke: c }) }) }),
2634
2783
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Slider, { label: "Stroke width", value: element.strokeWidth ?? 2, onValueChange: (v) => onPatch({ strokeWidth: Number(v) }), min: 0, max: 20, step: 0.5 }),
2784
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Switch, { label: "Dashed stroke", checked: !!element.dashed, onCheckedChange: (v) => onPatch({ dashed: v }) }),
2635
2785
  (element.shape === "rounded-rect" || element.shape === "rect") && /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Slider, { label: "Corner radius", value: element.radius ?? 0, onValueChange: (v) => onPatch({ radius: Number(v) }), min: 0, max: 40 })
2636
2786
  ] });
2637
2787
  }
@@ -2651,7 +2801,8 @@ function CodeStyleControls({ element, onPatch }) {
2651
2801
  value: element.codeTheme ?? "auto",
2652
2802
  onValueChange: (v) => onPatch({ codeTheme: v })
2653
2803
  }
2654
- )
2804
+ ),
2805
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Switch, { label: "Line numbers", checked: element.lineNumbers ?? true, onCheckedChange: (v) => onPatch({ lineNumbers: v }) })
2655
2806
  ] });
2656
2807
  }
2657
2808
  function ChartStyleControls({ element, onPatch }) {
@@ -3132,7 +3283,7 @@ function DeckEditor({
3132
3283
  slide,
3133
3284
  theme: deck.theme,
3134
3285
  editing: true,
3135
- onElementContentChange: (eid, content) => ops.updateElement(slide.id, eid, { content }),
3286
+ onElementContentChange: (eid, content) => ops.updateElement(slide.id, eid, { content, format: "markdown" }),
3136
3287
  onElementSelect: setElementIdSelected,
3137
3288
  selectedElementId: elementIdSelected,
3138
3289
  onElementMove: (eid, x, y) => ops.moveElement(slide.id, eid, x, y),
@@ -3158,6 +3309,7 @@ function DeckEditor({
3158
3309
  onLockToggle: (locked) => slide && elementIdSelected && ops.updateElement(slide.id, elementIdSelected, { locked }),
3159
3310
  onSetTransition: (transition) => slide && ops.setTransition(slide.id, transition),
3160
3311
  onSetBackground: (background) => slide && ops.setBackground(slide.id, background),
3312
+ onSetLayout: (layout) => slide && ops.setLayout(slide.id, layout),
3161
3313
  onSetAnimation: (animation) => slide && elementIdSelected && ops.setAnimation(slide.id, elementIdSelected, animation),
3162
3314
  onSetElementAnimation: (eid, animation) => slide && ops.setAnimation(slide.id, eid, animation)
3163
3315
  }
@@ -3173,6 +3325,7 @@ exports.DeckEditor = DeckEditor;
3173
3325
  exports.EditorToolbar = EditorToolbar;
3174
3326
  exports.ElementInspector = ElementInspector;
3175
3327
  exports.ImageElementRenderer = ImageElementRenderer;
3328
+ exports.PRESENTATION_EDITOR_ACTIONS = PRESENTATION_EDITOR_ACTIONS;
3176
3329
  exports.PresenterView = PresenterView;
3177
3330
  exports.ShapeElementRenderer = ShapeElementRenderer;
3178
3331
  exports.Slide = Slide;
@@ -3193,6 +3346,7 @@ exports.defineTheme = defineTheme;
3193
3346
  exports.elementId = elementId;
3194
3347
  exports.isByParagraph = isByParagraph;
3195
3348
  exports.nextId = nextId;
3349
+ exports.normalizeSlideMarkdown = normalizeSlideMarkdown;
3196
3350
  exports.paragraphReveals = paragraphReveals;
3197
3351
  exports.reduceDeck = reduce;
3198
3352
  exports.resolveTheme = resolveTheme;