animot-presenter 0.5.19 → 0.5.21

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.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Auto-layout pass for ContainerElement. Walks a slide's elements list,
3
+ * finds the container by id, and rewrites the position (and optionally
4
+ * size, when align=stretch) of every child id listed in childIds.
5
+ *
6
+ * This is the same algorithm the editor uses; running it in the presenter
7
+ * keeps embedded JSONs visually identical to what the user authored, and
8
+ * gives us a single source of truth for layout math.
9
+ */
10
+ import type { CanvasElement } from '../types';
11
+ export declare function relayoutContainer(elements: CanvasElement[], containerId: string): CanvasElement[];
12
+ export declare function relayoutAllContainers(elements: CanvasElement[]): CanvasElement[];
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Auto-layout pass for ContainerElement. Walks a slide's elements list,
3
+ * finds the container by id, and rewrites the position (and optionally
4
+ * size, when align=stretch) of every child id listed in childIds.
5
+ *
6
+ * This is the same algorithm the editor uses; running it in the presenter
7
+ * keeps embedded JSONs visually identical to what the user authored, and
8
+ * gives us a single source of truth for layout math.
9
+ */
10
+ function resolvePadding(p) {
11
+ if (typeof p === 'number')
12
+ return { top: p, right: p, bottom: p, left: p };
13
+ return p;
14
+ }
15
+ export function relayoutContainer(elements, containerId) {
16
+ const container = elements.find((el) => el.id === containerId);
17
+ if (!container || container.type !== 'container')
18
+ return elements;
19
+ const c = container;
20
+ const pad = resolvePadding(c.padding);
21
+ const innerW = Math.max(0, c.size.width - pad.left - pad.right);
22
+ const innerH = Math.max(0, c.size.height - pad.top - pad.bottom);
23
+ const isRow = c.direction === 'row';
24
+ const innerMain = isRow ? innerW : innerH;
25
+ const innerCross = isRow ? innerH : innerW;
26
+ const mainOriginAbs = (isRow ? c.position.x + pad.left : c.position.y + pad.top);
27
+ const crossOriginAbs = (isRow ? c.position.y + pad.top : c.position.x + pad.left);
28
+ const childMap = new Map(elements.map((el) => [el.id, el]));
29
+ const children = c.childIds
30
+ .map((id) => childMap.get(id))
31
+ .filter((el) => el !== undefined);
32
+ if (children.length === 0)
33
+ return elements;
34
+ const mainSizes = children.map((ch) => (isRow ? ch.size.width : ch.size.height));
35
+ const totalChildMain = mainSizes.reduce((a, b) => a + b, 0);
36
+ const totalGap = c.gap * Math.max(0, children.length - 1);
37
+ const usedMain = totalChildMain + totalGap;
38
+ const freeMain = Math.max(0, innerMain - usedMain);
39
+ let mainCursor = 0;
40
+ let extraGap = 0;
41
+ switch (c.justify) {
42
+ case 'start':
43
+ mainCursor = 0;
44
+ break;
45
+ case 'center':
46
+ mainCursor = freeMain / 2;
47
+ break;
48
+ case 'end':
49
+ mainCursor = freeMain;
50
+ break;
51
+ case 'space-between':
52
+ mainCursor = 0;
53
+ extraGap = children.length > 1 ? freeMain / (children.length - 1) : 0;
54
+ break;
55
+ case 'space-around':
56
+ extraGap = children.length > 0 ? freeMain / children.length : 0;
57
+ mainCursor = extraGap / 2;
58
+ break;
59
+ case 'space-evenly':
60
+ extraGap = freeMain / (children.length + 1);
61
+ mainCursor = extraGap;
62
+ break;
63
+ }
64
+ const updates = new Map();
65
+ for (let i = 0; i < children.length; i++) {
66
+ const ch = children[i];
67
+ const mainSize = isRow ? ch.size.width : ch.size.height;
68
+ const intrinsicCross = isRow ? ch.size.height : ch.size.width;
69
+ let crossOffset = 0;
70
+ let crossSize = intrinsicCross;
71
+ switch (c.align) {
72
+ case 'start':
73
+ crossOffset = 0;
74
+ break;
75
+ case 'center':
76
+ crossOffset = (innerCross - intrinsicCross) / 2;
77
+ break;
78
+ case 'end':
79
+ crossOffset = innerCross - intrinsicCross;
80
+ break;
81
+ case 'stretch':
82
+ crossOffset = 0;
83
+ crossSize = innerCross;
84
+ break;
85
+ }
86
+ const x = isRow ? mainOriginAbs + mainCursor : crossOriginAbs + crossOffset;
87
+ const y = isRow ? crossOriginAbs + crossOffset : mainOriginAbs + mainCursor;
88
+ const w = isRow ? mainSize : crossSize;
89
+ const h = isRow ? crossSize : mainSize;
90
+ updates.set(ch.id, { position: { x, y }, size: { width: w, height: h } });
91
+ mainCursor += mainSize + c.gap + extraGap;
92
+ }
93
+ return elements.map((el) => {
94
+ const up = updates.get(el.id);
95
+ if (!up)
96
+ return el;
97
+ return { ...el, position: up.position, size: up.size ?? el.size };
98
+ });
99
+ }
100
+ export function relayoutAllContainers(elements) {
101
+ let result = elements;
102
+ for (const el of elements) {
103
+ if (el.type === 'container') {
104
+ result = relayoutContainer(result, el.id);
105
+ }
106
+ }
107
+ return result;
108
+ }
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import type { ContainerElement } from '../types';
3
+
4
+ interface Props {
5
+ element: ContainerElement;
6
+ }
7
+ let { element }: Props = $props();
8
+
9
+ const fill = $derived(element.fillColor || 'transparent');
10
+ const borderCss = $derived(
11
+ element.borderWidth && element.borderColor
12
+ ? `${element.borderWidth}px solid ${element.borderColor}`
13
+ : 'none'
14
+ );
15
+ </script>
16
+
17
+ <div class="animot-container-element"
18
+ style:width="100%"
19
+ style:height="100%"
20
+ style:background={fill}
21
+ style:border={borderCss}
22
+ style:border-radius="{element.borderRadius ?? 0}px"
23
+ ></div>
24
+
25
+ <style>
26
+ .animot-container-element {
27
+ box-sizing: border-box;
28
+ pointer-events: none;
29
+ }
30
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { ContainerElement } from '../types';
2
+ interface Props {
3
+ element: ContainerElement;
4
+ }
5
+ declare const Container: import("svelte").Component<Props, {}, "">;
6
+ type Container = ReturnType<typeof Container>;
7
+ export default Container;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,34 @@
1
- export type ElementType = 'code' | 'text' | 'arrow' | 'image' | 'shape' | 'counter' | 'chart' | 'icon' | 'svg' | 'motionPath' | 'video' | 'progress';
1
+ export type ElementType = 'code' | 'text' | 'arrow' | 'image' | 'shape' | 'counter' | 'chart' | 'icon' | 'svg' | 'motionPath' | 'video' | 'progress' | 'container';
2
+ /**
3
+ * Per-element keyframe — animates state at a specific time WITHIN a slide's
4
+ * display window. Layered on top of slide-to-slide morphing: morphing handles
5
+ * between-slide transitions, keyframes handle within-slide animation.
6
+ */
7
+ export interface Keyframe {
8
+ id: string;
9
+ time: number;
10
+ easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'spring';
11
+ position?: Position;
12
+ size?: Size;
13
+ rotation?: number;
14
+ opacity?: number;
15
+ skewX?: number;
16
+ skewY?: number;
17
+ tiltX?: number;
18
+ tiltY?: number;
19
+ borderRadius?: number;
20
+ fontSize?: number;
21
+ fillColor?: string;
22
+ strokeColor?: string;
23
+ strokeWidth?: number;
24
+ backgroundColor?: string;
25
+ color?: string;
26
+ blur?: number;
27
+ brightness?: number;
28
+ contrast?: number;
29
+ saturate?: number;
30
+ grayscale?: number;
31
+ }
2
32
  export type ShapeType = 'rectangle' | 'circle' | 'triangle' | 'ellipse' | 'star' | 'hexagon';
3
33
  export interface Position {
4
34
  x: number;
@@ -57,6 +87,11 @@ export interface BaseElement {
57
87
  name?: string;
58
88
  locked?: boolean;
59
89
  groupId?: string;
90
+ /** Auto-layout container parent. When set, position/size is computed
91
+ * by the container's flex layout pass. */
92
+ containerId?: string;
93
+ /** Per-element keyframes that play DURING this slide. Sorted by time. */
94
+ keyframes?: Keyframe[];
60
95
  position: Position;
61
96
  size: Size;
62
97
  rotation: number;
@@ -414,7 +449,30 @@ export interface ProgressElement extends BaseElement {
414
449
  };
415
450
  animationDuration?: number;
416
451
  }
417
- export type CanvasElement = CodeElement | TextElement | ArrowElement | ImageElement | VideoElement | ShapeElement | CounterElement | ChartElement | IconElement | SvgElement | MotionPathElement | ProgressElement;
452
+ /**
453
+ * Auto-layout container. Holds a list of child element ids and arranges them
454
+ * via flex-style rules. Children remain independently selectable; layout
455
+ * pass writes computed position+size onto each child.
456
+ */
457
+ export interface ContainerElement extends BaseElement {
458
+ type: 'container';
459
+ childIds: string[];
460
+ direction: 'row' | 'column';
461
+ gap: number;
462
+ padding: number | {
463
+ top: number;
464
+ right: number;
465
+ bottom: number;
466
+ left: number;
467
+ };
468
+ align: 'start' | 'center' | 'end' | 'stretch';
469
+ justify: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';
470
+ fillColor?: string;
471
+ borderColor?: string;
472
+ borderWidth?: number;
473
+ borderRadius?: number;
474
+ }
475
+ export type CanvasElement = CodeElement | TextElement | ArrowElement | ImageElement | VideoElement | ShapeElement | CounterElement | ChartElement | IconElement | SvgElement | MotionPathElement | ProgressElement | ContainerElement;
418
476
  export type ParticleShape = 'circle' | 'square' | 'star' | 'triangle';
419
477
  export interface ParticlesConfig {
420
478
  enabled: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "animot-presenter",
3
- "version": "0.5.19",
3
+ "version": "0.5.21",
4
4
  "description": "Embed animated presentations anywhere. Works with vanilla JS, React, Vue, Angular, Svelte, and any frontend framework. Morphing animations, code highlighting, charts, particles, and more.",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",