@particle-academy/fancy-slides 0.8.0 → 0.10.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
@@ -940,7 +940,18 @@ function SlideElementHost({
940
940
  touchAction: canMove ? "none" : void 0,
941
941
  ...buildAnimation ? buildEnterStyle(buildAnimation, buildDelay) : null
942
942
  };
943
- const inner = renderInner({ element, theme, slideWidthPx, editing, selected, onContentChange, paraReveal }) ?? renderElement?.(element, slideWidthPx) ?? elementPlaceholder(element);
943
+ const rendered = renderInner({ element, theme, slideWidthPx, editing, selected, onContentChange, paraReveal }) ?? renderElement?.(element, slideWidthPx) ?? elementPlaceholder(element);
944
+ const inner = element.href && !editing ? /* @__PURE__ */ jsxRuntime.jsx(
945
+ "a",
946
+ {
947
+ href: element.href,
948
+ target: "_blank",
949
+ rel: "noreferrer",
950
+ style: { display: "block", width: "100%", height: "100%", color: "inherit", textDecoration: "inherit" },
951
+ "data-fancy-slides-href": "",
952
+ children: rendered
953
+ }
954
+ ) : rendered;
944
955
  return /* @__PURE__ */ jsxRuntime.jsxs(
945
956
  "div",
946
957
  {
@@ -1782,6 +1793,7 @@ function useDeckState({ value, onChange, onOp }) {
1782
1793
  apply,
1783
1794
  setTitle: (title) => apply({ kind: "deck_set_title", title }),
1784
1795
  applyTheme: (theme) => apply({ kind: "deck_apply_theme", theme }),
1796
+ setDeck: (deck) => apply({ kind: "deck_set", deck }),
1785
1797
  addSlide: (index, partial) => {
1786
1798
  const id = partial?.id ?? slideId();
1787
1799
  const slide = {
@@ -1836,6 +1848,8 @@ function reduce(deck, op) {
1836
1848
  return { ...deck, title: op.title };
1837
1849
  case "deck_apply_theme":
1838
1850
  return { ...deck, theme: op.theme };
1851
+ case "deck_set":
1852
+ return op.deck;
1839
1853
  case "slide_add": {
1840
1854
  const slides = [...deck.slides];
1841
1855
  slides.splice(Math.max(0, Math.min(slides.length, op.index)), 0, op.slide);
@@ -2284,7 +2298,7 @@ function ElementInspector({ element, onPatch, onDelete, onLockToggle, slide, onS
2284
2298
  /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.Tabs.Panels, { children: [
2285
2299
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "style", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(StyleSection, { element, onPatch }) }) }),
2286
2300
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "build", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(AnimateSection, { animation: element.animation, onSetAnimation, isText: element.type === "text" }) }) }),
2287
- /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "layout", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(LayoutSection, { element, onPatch }) }) }),
2301
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "layout", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(LayoutSection, { element, onPatch, siblings: slide?.elements ?? [] }) }) }),
2288
2302
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Tabs.Panel, { value: "advanced", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Card, { padding: "md", className: "!bg-white dark:!bg-zinc-950", children: /* @__PURE__ */ jsxRuntime.jsx(AdvancedSection, { element, onPatch }) }) })
2289
2303
  ] })
2290
2304
  ] }) })
@@ -2489,7 +2503,10 @@ function buildLabel(element) {
2489
2503
  }
2490
2504
  return `${element.type} #${element.id.slice(-6)}`;
2491
2505
  }
2492
- function LayoutSection({ element, onPatch }) {
2506
+ function LayoutSection({ element, onPatch, siblings }) {
2507
+ const zs = siblings.map((e) => e.z ?? 0);
2508
+ const bringToFront = () => onPatch({ z: (zs.length ? Math.max(...zs) : 0) + 1 });
2509
+ const sendToBack = () => onPatch({ z: (zs.length ? Math.min(...zs) : 0) - 1 });
2493
2510
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2494
2511
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
2495
2512
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { label: "X", type: "number", value: String(roundFrac(element.x)), onChange: (e) => onPatch({ x: clamp2(parseFloat(e.target.value), 0, 1) }) }),
@@ -2499,7 +2516,26 @@ function LayoutSection({ element, onPatch }) {
2499
2516
  ] }),
2500
2517
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Separator, {}),
2501
2518
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Slider, { label: "Rotation", value: element.rotation ?? 0, onValueChange: (v) => onPatch({ rotation: Number(v) }), min: -180, max: 180 }),
2502
- /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { label: "Z-index", type: "number", value: String(element.z ?? 0), onChange: (e) => onPatch({ z: parseInt(e.target.value, 10) || 0 }) })
2519
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2", children: [
2520
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { label: "Z-index", type: "number", value: String(element.z ?? 0), onChange: (e) => onPatch({ z: parseInt(e.target.value, 10) || 0 }), className: "flex-1" }),
2521
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "sm", variant: "ghost", onClick: bringToFront, "aria-label": "Bring to front", children: "Front" }),
2522
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "sm", variant: "ghost", onClick: sendToBack, "aria-label": "Send to back", children: "Back" })
2523
+ ] }),
2524
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Separator, {}),
2525
+ /* @__PURE__ */ jsxRuntime.jsx(
2526
+ reactFancy.Input,
2527
+ {
2528
+ label: "Link (href)",
2529
+ value: element.href ?? "",
2530
+ placeholder: "https://\u2026",
2531
+ onChange: (e) => onPatch({ href: e.target.value || void 0 })
2532
+ }
2533
+ ),
2534
+ /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.Text, { size: "xs", className: "!text-zinc-500", children: [
2535
+ "Makes the whole element a click target in the viewer (opens a new tab) and exports as a pptx hyperlink. For links inside text, use markdown ",
2536
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "[label](url)" }),
2537
+ "."
2538
+ ] })
2503
2539
  ] });
2504
2540
  }
2505
2541
  function AdvancedSection({ element, onPatch }) {
@@ -3321,12 +3357,84 @@ function DeckEditor({
3321
3357
  );
3322
3358
  }
3323
3359
 
3360
+ // src/types.ts
3361
+ var SCHEMA_VERSION = 1;
3362
+
3363
+ // src/utils/serialize.ts
3364
+ var ELEMENT_TYPES = ["text", "image", "chart", "code", "table", "shape", "embed"];
3365
+ function validateDeck(deck) {
3366
+ const errors = [];
3367
+ const d = deck;
3368
+ if (!d || typeof d !== "object") {
3369
+ return { ok: false, errors: ["deck is not an object"] };
3370
+ }
3371
+ if (typeof d.id !== "string" || !d.id) errors.push("deck.id must be a non-empty string");
3372
+ if (typeof d.title !== "string") errors.push("deck.title must be a string");
3373
+ if (!d.theme || typeof d.theme !== "object") errors.push("deck.theme must be an object");
3374
+ if (!Array.isArray(d.slides)) {
3375
+ errors.push("deck.slides must be an array");
3376
+ return { ok: errors.length === 0, errors };
3377
+ }
3378
+ d.slides.forEach((slide, si) => {
3379
+ if (!slide || typeof slide !== "object") {
3380
+ errors.push(`slides[${si}] is not an object`);
3381
+ return;
3382
+ }
3383
+ if (typeof slide.id !== "string" || !slide.id) errors.push(`slides[${si}].id must be a non-empty string`);
3384
+ if (!Array.isArray(slide.elements)) {
3385
+ errors.push(`slides[${si}].elements must be an array`);
3386
+ return;
3387
+ }
3388
+ slide.elements.forEach((el, ei) => {
3389
+ const where = `slides[${si}].elements[${ei}]`;
3390
+ if (!el || typeof el !== "object") {
3391
+ errors.push(`${where} is not an object`);
3392
+ return;
3393
+ }
3394
+ if (typeof el.id !== "string" || !el.id) errors.push(`${where}.id must be a non-empty string`);
3395
+ if (!ELEMENT_TYPES.includes(el.type)) errors.push(`${where}.type "${el.type}" is not a known element type`);
3396
+ for (const k of ["x", "y", "w", "h"]) {
3397
+ const v = el[k];
3398
+ if (typeof v !== "number" || v < 0 || v > 1) errors.push(`${where}.${k} must be a number in 0..1`);
3399
+ }
3400
+ });
3401
+ });
3402
+ return { ok: errors.length === 0, errors };
3403
+ }
3404
+ function migrateDeck(deck) {
3405
+ let d = deck;
3406
+ d.version ?? 1;
3407
+ return d.version === SCHEMA_VERSION ? d : { ...d, version: SCHEMA_VERSION };
3408
+ }
3409
+ function serializeDeck(deck, pretty = false) {
3410
+ const stamped = deck.version === SCHEMA_VERSION ? deck : { ...deck, version: SCHEMA_VERSION };
3411
+ return JSON.stringify(stamped, null, pretty ? 2 : void 0);
3412
+ }
3413
+ function parseDeck(input) {
3414
+ let raw;
3415
+ if (typeof input === "string") {
3416
+ try {
3417
+ raw = JSON.parse(input);
3418
+ } catch (e) {
3419
+ throw new Error(`parseDeck: invalid JSON \u2014 ${e.message}`);
3420
+ }
3421
+ } else {
3422
+ raw = input;
3423
+ }
3424
+ const result = validateDeck(raw);
3425
+ if (!result.ok) {
3426
+ throw new Error(`parseDeck: invalid deck \u2014 ${result.errors.join("; ")}`);
3427
+ }
3428
+ return migrateDeck(raw);
3429
+ }
3430
+
3324
3431
  exports.DeckEditor = DeckEditor;
3325
3432
  exports.EditorToolbar = EditorToolbar;
3326
3433
  exports.ElementInspector = ElementInspector;
3327
3434
  exports.ImageElementRenderer = ImageElementRenderer;
3328
3435
  exports.PRESENTATION_EDITOR_ACTIONS = PRESENTATION_EDITOR_ACTIONS;
3329
3436
  exports.PresenterView = PresenterView;
3437
+ exports.SCHEMA_VERSION = SCHEMA_VERSION;
3330
3438
  exports.ShapeElementRenderer = ShapeElementRenderer;
3331
3439
  exports.Slide = Slide;
3332
3440
  exports.SlideRail = SlideRail;
@@ -3345,11 +3453,14 @@ exports.defaultTheme = defaultTheme;
3345
3453
  exports.defineTheme = defineTheme;
3346
3454
  exports.elementId = elementId;
3347
3455
  exports.isByParagraph = isByParagraph;
3456
+ exports.migrateDeck = migrateDeck;
3348
3457
  exports.nextId = nextId;
3349
3458
  exports.normalizeSlideMarkdown = normalizeSlideMarkdown;
3350
3459
  exports.paragraphReveals = paragraphReveals;
3460
+ exports.parseDeck = parseDeck;
3351
3461
  exports.reduceDeck = reduce;
3352
3462
  exports.resolveTheme = resolveTheme;
3463
+ exports.serializeDeck = serializeDeck;
3353
3464
  exports.slideId = slideId;
3354
3465
  exports.splitParagraphs = splitParagraphs;
3355
3466
  exports.stepDelays = stepDelays;
@@ -3359,6 +3470,7 @@ exports.useIsDarkSlide = useIsDarkSlide;
3359
3470
  exports.useSlideContext = useSlideContext;
3360
3471
  exports.useSlideKeyboard = useSlideKeyboard;
3361
3472
  exports.useSlideTheme = useSlideTheme;
3473
+ exports.validateDeck = validateDeck;
3362
3474
  exports.visibleElementIds = visibleElementIds;
3363
3475
  exports.vividTheme = vividTheme;
3364
3476
  //# sourceMappingURL=index.cjs.map