animot-presenter 0.6.3 → 0.6.4
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/AnimotPresenter.svelte +378 -55
- package/dist/cdn/animot-presenter.css +1 -1
- package/dist/cdn/animot-presenter.esm.js +9856 -7624
- package/dist/cdn/animot-presenter.min.js +14 -10
- package/dist/styles/presenter.css +38 -0
- package/dist/types.d.ts +33 -2
- package/dist/utils/freehand.d.ts +26 -0
- package/dist/utils/freehand.js +70 -0
- package/dist/utils/path-morph.d.ts +89 -0
- package/dist/utils/path-morph.js +420 -0
- package/dist/utils/svg-path-edit.d.ts +37 -0
- package/dist/utils/svg-path-edit.js +279 -0
- package/package.json +3 -1
|
@@ -217,6 +217,44 @@
|
|
|
217
217
|
0% { opacity: 1; } 100% { opacity: 0; }
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/* Freehand draw element */
|
|
221
|
+
.animot-draw-element { display: block; width: 100%; height: 100%; overflow: visible; }
|
|
222
|
+
|
|
223
|
+
/* Sticky note element */
|
|
224
|
+
.animot-sticky-element {
|
|
225
|
+
position: relative;
|
|
226
|
+
width: 100%;
|
|
227
|
+
height: 100%;
|
|
228
|
+
display: flex;
|
|
229
|
+
box-sizing: border-box;
|
|
230
|
+
overflow: hidden;
|
|
231
|
+
}
|
|
232
|
+
.animot-sticky-sheen {
|
|
233
|
+
position: absolute;
|
|
234
|
+
inset: 0;
|
|
235
|
+
pointer-events: none;
|
|
236
|
+
background: linear-gradient(135deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0) 45%, rgba(0, 0, 0, 0.06));
|
|
237
|
+
}
|
|
238
|
+
.animot-sticky-fold {
|
|
239
|
+
position: absolute;
|
|
240
|
+
right: 0;
|
|
241
|
+
bottom: 0;
|
|
242
|
+
width: 22px;
|
|
243
|
+
height: 22px;
|
|
244
|
+
pointer-events: none;
|
|
245
|
+
background: linear-gradient(135deg, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.18) 50%);
|
|
246
|
+
border-top-left-radius: 4px;
|
|
247
|
+
box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.12);
|
|
248
|
+
}
|
|
249
|
+
.animot-sticky-text {
|
|
250
|
+
position: relative;
|
|
251
|
+
z-index: 1;
|
|
252
|
+
width: 100%;
|
|
253
|
+
line-height: 1.3;
|
|
254
|
+
white-space: pre-wrap;
|
|
255
|
+
word-break: break-word;
|
|
256
|
+
}
|
|
257
|
+
|
|
220
258
|
/* SVG path-trace animations (Icon + Svg elements) */
|
|
221
259
|
.animot-svg-element.icon-anim-draw path,
|
|
222
260
|
.animot-svg-element.icon-anim-draw circle,
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ElementType = 'code' | 'text' | 'arrow' | 'image' | 'shape' | 'counter' | 'chart' | 'icon' | 'svg' | 'motionPath' | 'video' | 'progress' | 'container';
|
|
1
|
+
export type ElementType = 'code' | 'text' | 'arrow' | 'image' | 'shape' | 'counter' | 'chart' | 'icon' | 'svg' | 'motionPath' | 'video' | 'progress' | 'container' | 'draw' | 'sticky';
|
|
2
2
|
/**
|
|
3
3
|
* Per-element keyframe — animates state at a specific time WITHIN a slide's
|
|
4
4
|
* display window. Layered on top of slide-to-slide morphing: morphing handles
|
|
@@ -28,6 +28,7 @@ export interface Keyframe {
|
|
|
28
28
|
contrast?: number;
|
|
29
29
|
saturate?: number;
|
|
30
30
|
grayscale?: number;
|
|
31
|
+
path?: string;
|
|
31
32
|
}
|
|
32
33
|
export type ShapeType = 'rectangle' | 'circle' | 'triangle' | 'ellipse' | 'star' | 'hexagon';
|
|
33
34
|
export interface Position {
|
|
@@ -118,6 +119,8 @@ export interface BaseElement {
|
|
|
118
119
|
depth?: number;
|
|
119
120
|
motionPathId?: string;
|
|
120
121
|
motionPathConfig?: MotionPathConfig;
|
|
122
|
+
/** Solid color for the shape-morph transition only (render-time override). */
|
|
123
|
+
morphColor?: string;
|
|
121
124
|
blur?: number;
|
|
122
125
|
brightness?: number;
|
|
123
126
|
contrast?: number;
|
|
@@ -416,6 +419,34 @@ export interface SvgElement extends BaseElement {
|
|
|
416
419
|
viewBox?: string;
|
|
417
420
|
animation?: SvgPathAnimationConfig;
|
|
418
421
|
}
|
|
422
|
+
export type DrawBrush = 'pen' | 'marker' | 'highlighter';
|
|
423
|
+
export interface DrawElement extends BaseElement {
|
|
424
|
+
type: 'draw';
|
|
425
|
+
points: number[][];
|
|
426
|
+
color: string;
|
|
427
|
+
opacity: number;
|
|
428
|
+
brush: DrawBrush;
|
|
429
|
+
strokeWidth: number;
|
|
430
|
+
thinning?: number;
|
|
431
|
+
smoothing?: number;
|
|
432
|
+
streamline?: number;
|
|
433
|
+
taperStart?: boolean;
|
|
434
|
+
taperEnd?: boolean;
|
|
435
|
+
}
|
|
436
|
+
export interface StickyElement extends BaseElement {
|
|
437
|
+
type: 'sticky';
|
|
438
|
+
text: string;
|
|
439
|
+
bgColor: string;
|
|
440
|
+
textColor: string;
|
|
441
|
+
fontSize: number;
|
|
442
|
+
fontFamily: string;
|
|
443
|
+
fontWeight: number;
|
|
444
|
+
textAlign: 'left' | 'center' | 'right';
|
|
445
|
+
padding: number;
|
|
446
|
+
borderRadius: number;
|
|
447
|
+
opacity: number;
|
|
448
|
+
shadow?: boolean;
|
|
449
|
+
}
|
|
419
450
|
export interface MotionPathElement extends BaseElement {
|
|
420
451
|
type: 'motionPath';
|
|
421
452
|
points: PathPoint[];
|
|
@@ -480,7 +511,7 @@ export interface ContainerElement extends BaseElement {
|
|
|
480
511
|
borderWidth?: number;
|
|
481
512
|
borderRadius?: number;
|
|
482
513
|
}
|
|
483
|
-
export type CanvasElement = CodeElement | TextElement | ArrowElement | ImageElement | VideoElement | ShapeElement | CounterElement | ChartElement | IconElement | SvgElement | MotionPathElement | ProgressElement | ContainerElement;
|
|
514
|
+
export type CanvasElement = CodeElement | TextElement | ArrowElement | ImageElement | VideoElement | ShapeElement | CounterElement | ChartElement | IconElement | SvgElement | MotionPathElement | ProgressElement | ContainerElement | DrawElement | StickyElement;
|
|
484
515
|
export type ParticleShape = 'circle' | 'square' | 'star' | 'triangle';
|
|
485
516
|
export interface ParticlesConfig {
|
|
486
517
|
enabled: boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DrawElement } from '../types';
|
|
2
|
+
export interface StrokeOptions {
|
|
3
|
+
size: number;
|
|
4
|
+
thinning: number;
|
|
5
|
+
smoothing: number;
|
|
6
|
+
streamline: number;
|
|
7
|
+
taperStart: boolean;
|
|
8
|
+
taperEnd: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveStrokeOptions(el: Pick<DrawElement, 'brush' | 'strokeWidth' | 'thinning' | 'smoothing' | 'streamline' | 'taperStart' | 'taperEnd'>): StrokeOptions;
|
|
11
|
+
/** Compute the axis-aligned bounding box of raw input points. */
|
|
12
|
+
export declare function pointsBounds(points: number[][]): {
|
|
13
|
+
minX: number;
|
|
14
|
+
minY: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Build the renderable path + viewBox for a draw element. Points are assumed
|
|
20
|
+
* to already be in a 0,0-origin local space; the viewBox spans their bounds
|
|
21
|
+
* (plus stroke padding) so the element box scales the stroke on resize.
|
|
22
|
+
*/
|
|
23
|
+
export declare function drawElementToPath(el: Pick<DrawElement, 'points' | 'brush' | 'strokeWidth' | 'thinning' | 'smoothing' | 'streamline' | 'taperStart' | 'taperEnd'>): {
|
|
24
|
+
d: string;
|
|
25
|
+
viewBox: string;
|
|
26
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import getStroke from 'perfect-freehand';
|
|
2
|
+
const BRUSH_DEFAULTS = {
|
|
3
|
+
// Tapered, pressure-responsive — feels like an ink pen.
|
|
4
|
+
pen: { thinning: 0.6, smoothing: 0.5, streamline: 0.5, taperStart: true, taperEnd: true },
|
|
5
|
+
// Flat, even width — a chisel marker.
|
|
6
|
+
marker: { thinning: 0.1, smoothing: 0.55, streamline: 0.5, taperStart: false, taperEnd: false },
|
|
7
|
+
// Even width like marker; the translucency comes from element opacity.
|
|
8
|
+
highlighter: { thinning: 0, smoothing: 0.6, streamline: 0.5, taperStart: false, taperEnd: false }
|
|
9
|
+
};
|
|
10
|
+
export function resolveStrokeOptions(el) {
|
|
11
|
+
const d = BRUSH_DEFAULTS[el.brush] ?? BRUSH_DEFAULTS.pen;
|
|
12
|
+
return {
|
|
13
|
+
size: el.strokeWidth,
|
|
14
|
+
thinning: el.thinning ?? d.thinning ?? 0.5,
|
|
15
|
+
smoothing: el.smoothing ?? d.smoothing ?? 0.5,
|
|
16
|
+
streamline: el.streamline ?? d.streamline ?? 0.5,
|
|
17
|
+
taperStart: el.taperStart ?? d.taperStart ?? false,
|
|
18
|
+
taperEnd: el.taperEnd ?? d.taperEnd ?? false
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Turn a perfect-freehand outline polygon into an SVG path `d` string. */
|
|
22
|
+
function outlineToPath(points) {
|
|
23
|
+
if (points.length === 0)
|
|
24
|
+
return '';
|
|
25
|
+
const d = points.reduce((acc, [x0, y0], i, arr) => {
|
|
26
|
+
const [x1, y1] = arr[(i + 1) % arr.length];
|
|
27
|
+
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
|
|
28
|
+
return acc;
|
|
29
|
+
}, ['M', points[0][0], points[0][1], 'Q']);
|
|
30
|
+
d.push('Z');
|
|
31
|
+
return d.join(' ');
|
|
32
|
+
}
|
|
33
|
+
/** Compute the axis-aligned bounding box of raw input points. */
|
|
34
|
+
export function pointsBounds(points) {
|
|
35
|
+
if (points.length === 0)
|
|
36
|
+
return { minX: 0, minY: 0, width: 0, height: 0 };
|
|
37
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
38
|
+
for (const [x, y] of points) {
|
|
39
|
+
if (x < minX)
|
|
40
|
+
minX = x;
|
|
41
|
+
if (y < minY)
|
|
42
|
+
minY = y;
|
|
43
|
+
if (x > maxX)
|
|
44
|
+
maxX = x;
|
|
45
|
+
if (y > maxY)
|
|
46
|
+
maxY = y;
|
|
47
|
+
}
|
|
48
|
+
return { minX, minY, width: maxX - minX, height: maxY - minY };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the renderable path + viewBox for a draw element. Points are assumed
|
|
52
|
+
* to already be in a 0,0-origin local space; the viewBox spans their bounds
|
|
53
|
+
* (plus stroke padding) so the element box scales the stroke on resize.
|
|
54
|
+
*/
|
|
55
|
+
export function drawElementToPath(el) {
|
|
56
|
+
const opts = resolveStrokeOptions(el);
|
|
57
|
+
const stroke = getStroke(el.points, {
|
|
58
|
+
size: opts.size,
|
|
59
|
+
thinning: opts.thinning,
|
|
60
|
+
smoothing: opts.smoothing,
|
|
61
|
+
streamline: opts.streamline,
|
|
62
|
+
start: { taper: opts.taperStart ? opts.size * 4 : 0, cap: !opts.taperStart },
|
|
63
|
+
end: { taper: opts.taperEnd ? opts.size * 4 : 0, cap: !opts.taperEnd }
|
|
64
|
+
});
|
|
65
|
+
const d = outlineToPath(stroke);
|
|
66
|
+
const b = pointsBounds(el.points);
|
|
67
|
+
const pad = opts.size;
|
|
68
|
+
const vb = `${b.minX - pad} ${b.minY - pad} ${b.width + pad * 2} ${b.height + pad * 2}`;
|
|
69
|
+
return { d, viewBox: vb };
|
|
70
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ShapeType, CanvasElement } from '../types';
|
|
2
|
+
/** Build a `d` string for a shape type, fitted to a w×h box (origin 0,0). */
|
|
3
|
+
export declare function shapeToPath(type: ShapeType, w: number, h: number, borderRadius?: number): string;
|
|
4
|
+
/**
|
|
5
|
+
* Extract drawable path `d` strings from raw SVG markup. Returns an array of
|
|
6
|
+
* subpath `d` strings (one per primitive). Empty when nothing is drawable.
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractPaths(svgContent: string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Like extractPaths, but pairs each subpath with its own fill color so a
|
|
11
|
+
* multi-color SVG morphs per-piece (one `<path>` per subpath) instead of
|
|
12
|
+
* flattening to a single representative color. Primitives without their own
|
|
13
|
+
* fill inherit the root `<svg fill="…">` (default black, matching the
|
|
14
|
+
* browser), which is exactly the black outline strokes on icon SVGs.
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractPathsWithFill(svgContent: string): Array<{
|
|
17
|
+
d: string;
|
|
18
|
+
fill: string;
|
|
19
|
+
}>;
|
|
20
|
+
/** Single combined `d` (all subpaths concatenated) — for simple morphs. */
|
|
21
|
+
export declare function extractCombinedPath(svgContent: string): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Pick a representative fill color from SVG markup — the first non-"none"
|
|
24
|
+
* `fill` attribute. Used to color the flattened morph path (which loses
|
|
25
|
+
* per-subpath fills). Returns null if none found.
|
|
26
|
+
*/
|
|
27
|
+
export declare function extractFill(svgContent: string): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Re-fit a path `d` from its own viewBox into a target W×H box (origin 0,0).
|
|
30
|
+
* Morph stops must share a coordinate space or flubber produces scale jumps;
|
|
31
|
+
* authoring normalizes every stop into the element's box via this. Parses to
|
|
32
|
+
* absolute cubics, scales anchors + handles, re-serializes.
|
|
33
|
+
*/
|
|
34
|
+
export declare function fitPathToBox(d: string, sourceViewBox: string, targetW: number, targetH: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve any vector element (shape / svg / draw) to its current geometry as
|
|
37
|
+
* a path `d` plus the viewBox that `d` lives in. Single source of truth for
|
|
38
|
+
* both the morph engine and the "snapshot current" authoring action.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveElementPath(el: CanvasElement): {
|
|
41
|
+
d: string;
|
|
42
|
+
viewBox: string;
|
|
43
|
+
} | null;
|
|
44
|
+
/**
|
|
45
|
+
* Like resolveElementPath but keeps subpaths SEPARATE, each with its own fill,
|
|
46
|
+
* so multi-color SVGs morph per-piece. Shapes/draws return a single part.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveElementPathParts(el: CanvasElement): {
|
|
49
|
+
parts: Array<{
|
|
50
|
+
d: string;
|
|
51
|
+
fill: string;
|
|
52
|
+
}>;
|
|
53
|
+
viewBox: string;
|
|
54
|
+
} | null;
|
|
55
|
+
export type PathInterpolator = (t: number) => string;
|
|
56
|
+
/** One morphing piece: a `d` interpolator plus the color it lerps across. */
|
|
57
|
+
export interface MorphPart {
|
|
58
|
+
interp: PathInterpolator;
|
|
59
|
+
fromColor: string;
|
|
60
|
+
toColor: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build per-piece morph parts between two fill-tagged subpath lists. Each `d`
|
|
64
|
+
* MUST already be in the same coordinate space (caller fits the source into
|
|
65
|
+
* the target viewBox). Picks the right flubber routine by count so 1↔many and
|
|
66
|
+
* many↔1 map correctly, and pairs colors per piece so multi-color SVGs keep
|
|
67
|
+
* their colors instead of flattening to one.
|
|
68
|
+
*/
|
|
69
|
+
export declare function makeMorphParts(from: Array<{
|
|
70
|
+
d: string;
|
|
71
|
+
fill: string;
|
|
72
|
+
}>, to: Array<{
|
|
73
|
+
d: string;
|
|
74
|
+
fill: string;
|
|
75
|
+
}>): MorphPart[];
|
|
76
|
+
/**
|
|
77
|
+
* Build an interpolator between two `d` strings. Handles differing subpath
|
|
78
|
+
* counts via flubber's many-to-many interpolation, falling back to a simple
|
|
79
|
+
* single-path interpolate. Always returns a function (t:0..1) => d.
|
|
80
|
+
*/
|
|
81
|
+
export declare function makePathInterpolator(fromD: string, toD: string): PathInterpolator;
|
|
82
|
+
/**
|
|
83
|
+
* Build a single interpolator that morphs through an ordered list of `d`
|
|
84
|
+
* stops. `t` runs 0..1 across the whole sequence; the segment is picked by
|
|
85
|
+
* scaling `t` across (stops.length - 1) and interpolating within it. Used by
|
|
86
|
+
* the within-slide multi-target morph (circle → star → hexagon → …).
|
|
87
|
+
* When `loop` is true the sequence wraps back to the first stop.
|
|
88
|
+
*/
|
|
89
|
+
export declare function makeSequenceInterpolator(stops: string[], loop?: boolean): PathInterpolator;
|