animot-presenter 0.5.11 → 0.5.13

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,15 @@
1
+ /**
2
+ * Chart helpers — series normalization, color resolution, value formatting.
3
+ * Mirrors the editor app's helper, minus the brand-kit palette path (the
4
+ * presenter is kit-agnostic; `useKitPalette: true` simply falls through to
5
+ * `element.colors`).
6
+ */
7
+ import type { ChartElement, ChartSeries, ChartDataPoint, ChartValueFormat } from '../types';
8
+ export declare function normalizeSeries(element: ChartElement): ChartSeries[];
9
+ export declare function paletteFor(element: ChartElement): string[];
10
+ export declare function resolveColor(element: ChartElement, seriesIndex: number, pointIndex: number, point: ChartDataPoint | undefined, series: ChartSeries | undefined): string;
11
+ export declare function formatValue(value: number, fmt?: ChartValueFormat): string;
12
+ export declare function computeYRange(element: ChartElement): {
13
+ min: number;
14
+ max: number;
15
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Chart helpers — series normalization, color resolution, value formatting.
3
+ * Mirrors the editor app's helper, minus the brand-kit palette path (the
4
+ * presenter is kit-agnostic; `useKitPalette: true` simply falls through to
5
+ * `element.colors`).
6
+ */
7
+ export function normalizeSeries(element) {
8
+ if (element.series && element.series.length > 0)
9
+ return element.series;
10
+ return [{ id: 'default', name: '', data: element.data ?? [] }];
11
+ }
12
+ export function paletteFor(element) {
13
+ return element.colors && element.colors.length > 0 ? element.colors : ['#6366f1'];
14
+ }
15
+ export function resolveColor(element, seriesIndex, pointIndex, point, series) {
16
+ if (point?.color)
17
+ return point.color;
18
+ if (series?.color)
19
+ return series.color;
20
+ const palette = paletteFor(element);
21
+ const seriesCount = element.series?.length ?? 0;
22
+ const idx = seriesCount > 1 ? seriesIndex : pointIndex;
23
+ return palette[idx % palette.length];
24
+ }
25
+ export function formatValue(value, fmt) {
26
+ const { prefix = '', suffix = '', decimals = 0, abbreviate = false } = fmt ?? {};
27
+ let n = value;
28
+ let suffixOverride = '';
29
+ if (abbreviate) {
30
+ const abs = Math.abs(n);
31
+ if (abs >= 1_000_000_000) {
32
+ n = n / 1_000_000_000;
33
+ suffixOverride = 'B';
34
+ }
35
+ else if (abs >= 1_000_000) {
36
+ n = n / 1_000_000;
37
+ suffixOverride = 'M';
38
+ }
39
+ else if (abs >= 1_000) {
40
+ n = n / 1_000;
41
+ suffixOverride = 'K';
42
+ }
43
+ }
44
+ const rounded = decimals > 0 ? n.toFixed(decimals) : Math.round(n).toString();
45
+ return `${prefix}${rounded}${suffixOverride}${suffix}`;
46
+ }
47
+ export function computeYRange(element) {
48
+ const series = normalizeSeries(element);
49
+ let min = Infinity;
50
+ let max = -Infinity;
51
+ if (element.chartType === 'bar' && (element.barLayout ?? 'grouped') === 'stacked' && series.length > 1) {
52
+ const labels = series[0].data.map((d) => d.label);
53
+ for (let i = 0; i < labels.length; i++) {
54
+ let posSum = 0;
55
+ let negSum = 0;
56
+ for (const s of series) {
57
+ const v = s.data[i]?.value ?? 0;
58
+ if (v >= 0)
59
+ posSum += v;
60
+ else
61
+ negSum += v;
62
+ }
63
+ if (posSum > max)
64
+ max = posSum;
65
+ if (negSum < min)
66
+ min = negSum;
67
+ }
68
+ }
69
+ else {
70
+ for (const s of series) {
71
+ for (const p of s.data) {
72
+ if (p.value < min)
73
+ min = p.value;
74
+ if (p.value > max)
75
+ max = p.value;
76
+ }
77
+ }
78
+ }
79
+ if (!isFinite(min))
80
+ min = 0;
81
+ if (!isFinite(max))
82
+ max = 1;
83
+ if (min > 0)
84
+ min = 0;
85
+ const userMin = element.yAxis?.min;
86
+ const userMax = element.yAxis?.max;
87
+ if (userMin !== undefined)
88
+ min = userMin;
89
+ if (userMax !== undefined)
90
+ max = userMax;
91
+ if (max === min)
92
+ max = min + 1;
93
+ return { min, max };
94
+ }
@@ -10,53 +10,88 @@
10
10
  * • gradientShift — animates background-position on a multi-color gradient
11
11
  * • rgbSplit — chromatic-aberration-style R/B channel offset (drop-shadow)
12
12
  */
13
- /** Build a CSS clip-path that matches a shape silhouette. Returns null for
14
- * rectangles with no border-radius (no clipping needed). */
15
- function shapeClipPath(shape) {
13
+ // Hexagon and star are rendered by Shape.svelte as REGULAR polygons inscribed
14
+ // in min(w, h) and centered. Triangle and ellipse stretch to fill the box.
15
+ // Compute clip-path / mask coords from the actual element size so the overlay
16
+ // matches the visible silhouette rather than stretching with the bounding box.
17
+ function regularPolygonPoints(sides, w, h) {
18
+ const cx = w / 2, cy = h / 2;
19
+ const r = Math.min(w, h) / 2;
20
+ return Array.from({ length: sides }, (_, i) => {
21
+ const a = (i * Math.PI * 2) / sides - Math.PI / 2;
22
+ return { x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) };
23
+ });
24
+ }
25
+ function regularStarPoints(w, h) {
26
+ const cx = w / 2, cy = h / 2;
27
+ const outerR = Math.min(w, h) / 2;
28
+ const innerR = outerR * 0.4;
29
+ return Array.from({ length: 10 }, (_, i) => {
30
+ const a = (i * Math.PI / 5) - Math.PI / 2;
31
+ const r = i % 2 === 0 ? outerR : innerR;
32
+ return { x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) };
33
+ });
34
+ }
35
+ function ptsToClipPath(pts) {
36
+ return `polygon(${pts.map(p => `${p.x}px ${p.y}px`).join(', ')})`;
37
+ }
38
+ function ptsToSvgPolygon(pts) {
39
+ return pts.map(p => `${p.x},${p.y}`).join(' ');
40
+ }
41
+ function shapeClipPath(shape, w, h) {
42
+ const cx = w / 2, cy = h / 2;
16
43
  switch (shape.type) {
17
- case 'circle': return 'circle(50% at 50% 50%)';
18
- case 'ellipse': return 'ellipse(50% 50% at 50% 50%)';
19
- case 'triangle': return 'polygon(50% 0%, 0% 100%, 100% 100%)';
20
- case 'hexagon': return 'polygon(50% 0%, 93.3% 25%, 93.3% 75%, 50% 100%, 6.7% 75%, 6.7% 25%)';
21
- case 'star': return 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)';
44
+ case 'circle': {
45
+ const r = Math.min(w, h) / 2;
46
+ return `circle(${r}px at ${cx}px ${cy}px)`;
47
+ }
48
+ case 'ellipse':
49
+ return `ellipse(${w / 2}px ${h / 2}px at ${cx}px ${cy}px)`;
50
+ case 'triangle':
51
+ return `polygon(${cx}px 0px, 0px ${h}px, ${w}px ${h}px)`;
52
+ case 'hexagon':
53
+ return ptsToClipPath(regularPolygonPoints(6, w, h));
54
+ case 'star':
55
+ return ptsToClipPath(regularStarPoints(w, h));
22
56
  case 'rectangle':
23
57
  return shape.borderRadius && shape.borderRadius > 0
24
58
  ? `inset(0 round ${shape.borderRadius}px)`
25
59
  : null;
26
60
  }
27
61
  }
28
- // SVG mask for the gradient border ring. We stroke the shape silhouette in a
29
- // 100×100 viewBox with `preserveAspectRatio=none` so it stretches to the
30
- // element's box, and `vector-effect=non-scaling-stroke` so the stroke stays a
31
- // constant pixel width regardless of element size or aspect ratio. The stroke
32
- // is centered on the path, so half of it falls outside the silhouette — the
33
- // caller's clip-path trims that outer half away, leaving an inner ring of
34
- // `borderWidth` px against the silhouette edge. We therefore set stroke-width
35
- // to `borderWidth * 2`.
36
- function silhouetteMaskUrl(shapeType, borderWidth) {
62
+ // SVG mask for the gradient border ring. ViewBox matches the element's actual
63
+ // pixel size so polygon coords map 1:1, and `vector-effect=non-scaling-stroke`
64
+ // keeps the stroke a constant pixel width. The caller pairs this with the
65
+ // silhouette clip-path: stroke is centered on the path, so half falls outside
66
+ // and gets trimmed leaving an inner ring of `borderWidth` px. We therefore
67
+ // set stroke-width to `borderWidth * 2`.
68
+ function silhouetteMaskUrl(shapeType, borderWidth, w, h) {
37
69
  const sw = borderWidth * 2;
38
70
  const common = `vector-effect="non-scaling-stroke" fill="none" stroke="#fff" stroke-width="${sw}"`;
71
+ const cx = w / 2, cy = h / 2;
39
72
  let inner = '';
40
73
  switch (shapeType) {
41
- case 'circle':
42
- inner = `<circle cx="50" cy="50" r="50" ${common}/>`;
74
+ case 'circle': {
75
+ const r = Math.min(w, h) / 2;
76
+ inner = `<circle cx="${cx}" cy="${cy}" r="${r}" ${common}/>`;
43
77
  break;
78
+ }
44
79
  case 'ellipse':
45
- inner = `<ellipse cx="50" cy="50" rx="50" ry="50" ${common}/>`;
80
+ inner = `<ellipse cx="${cx}" cy="${cy}" rx="${w / 2}" ry="${h / 2}" ${common}/>`;
46
81
  break;
47
82
  case 'triangle':
48
- inner = `<polygon points="50,0 0,100 100,100" ${common}/>`;
83
+ inner = `<polygon points="${cx},0 0,${h} ${w},${h}" ${common}/>`;
49
84
  break;
50
85
  case 'hexagon':
51
- inner = `<polygon points="50,0 93.3,25 93.3,75 50,100 6.7,75 6.7,25" ${common}/>`;
86
+ inner = `<polygon points="${ptsToSvgPolygon(regularPolygonPoints(6, w, h))}" ${common}/>`;
52
87
  break;
53
88
  case 'star':
54
- inner = `<polygon points="50,0 61,35 98,35 68,57 79,91 50,70 21,91 32,57 2,35 39,35" ${common}/>`;
89
+ inner = `<polygon points="${ptsToSvgPolygon(regularStarPoints(w, h))}" ${common}/>`;
55
90
  break;
56
91
  default:
57
- inner = `<rect x="0" y="0" width="100" height="100" ${common}/>`;
92
+ inner = `<rect x="0" y="0" width="${w}" height="${h}" ${common}/>`;
58
93
  }
59
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="none">${inner}</svg>`;
94
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none">${inner}</svg>`;
60
95
  return `url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}")`;
61
96
  }
62
97
  function effectiveCycle(requested, slideDuration) {
@@ -75,6 +110,7 @@ export function decorations(node, params) {
75
110
  // silhouette. By putting the gradient on a clipped child, the wrapper
76
111
  // stays unclipped and the glow halo renders correctly outside the shape.
77
112
  let gradientEl = null;
113
+ let resizeObs = null;
78
114
  let originalBoxShadow = '';
79
115
  let originalFilter = '';
80
116
  let savedOriginal = false;
@@ -101,6 +137,10 @@ export function decorations(node, params) {
101
137
  if (raf)
102
138
  cancelAnimationFrame(raf);
103
139
  raf = 0;
140
+ if (resizeObs) {
141
+ resizeObs.disconnect();
142
+ resizeObs = null;
143
+ }
104
144
  restoreOriginalStyles();
105
145
  }
106
146
  function run() {
@@ -112,10 +152,35 @@ export function decorations(node, params) {
112
152
  if (!anyEnabled)
113
153
  return;
114
154
  saveOriginalStyles();
115
- // Compute the shape clip-path once applied to shimmer + gradient
116
- // children individually (NOT the wrapper) so the wrapper's glow filter
117
- // halo can render outside the silhouette without being clipped.
118
- const shapeClip = params.shape ? shapeClipPath(params.shape) : null;
155
+ // Shape clip-path / silhouette mask are SIZE-AWARE: hexagon and star are
156
+ // rendered as REGULAR polygons inscribed in min(w, h), so % coords would
157
+ // stretch on non-square boxes and miss the actual shape. We compute in
158
+ // pixels from the element's current size and refresh on resize.
159
+ const useSilhouetteMask = !!(params.shape && params.shape.type !== 'rectangle');
160
+ const borderWidth = params.config?.gradientShift?.borderWidth ?? 3;
161
+ function applyShapeOverlays() {
162
+ if (!params.shape)
163
+ return;
164
+ const w = node.offsetWidth;
165
+ const h = node.offsetHeight;
166
+ if (w <= 0 || h <= 0)
167
+ return;
168
+ const clip = shapeClipPath(params.shape, w, h);
169
+ if (shimmerEl && clip)
170
+ shimmerEl.style.clipPath = clip;
171
+ if (gradientEl && useSilhouetteMask) {
172
+ if (clip)
173
+ gradientEl.style.clipPath = clip;
174
+ const url = silhouetteMaskUrl(params.shape.type, borderWidth, w, h);
175
+ gradientEl.style.webkitMaskImage = url;
176
+ gradientEl.style.maskImage = url;
177
+ }
178
+ }
179
+ // Initial computation. Falls back to null when the element has no shape
180
+ // info (text/code/icon/image/rect) — those use static rules.
181
+ const shapeClip = params.shape
182
+ ? shapeClipPath(params.shape, node.offsetWidth || 1, node.offsetHeight || 1)
183
+ : null;
119
184
  // Shimmer: a child overlay with the diagonal stripe gradient. Clipped
120
185
  // to the shape silhouette individually so circle/hexagon/etc. shapes
121
186
  // don't get rectangular shimmer.
@@ -161,7 +226,6 @@ export function decorations(node, params) {
161
226
  const colors = cfg.gradientShift.colors ?? ['#7c3aed', '#06b6d4', '#ec4899', '#7c3aed'];
162
227
  const angle = cfg.gradientShift.angle ?? 135;
163
228
  const direction = cfg.gradientShift.direction ?? 'forward';
164
- const borderWidth = cfg.gradientShift.borderWidth ?? 3;
165
229
  gradientEl = document.createElement('div');
166
230
  gradientEl.className = 'animot-gradient-shift';
167
231
  Object.assign(gradientEl.style, {
@@ -170,19 +234,13 @@ export function decorations(node, params) {
170
234
  pointerEvents: 'none',
171
235
  zIndex: '0'
172
236
  });
173
- const useSilhouetteMask = params.shape && params.shape.type !== 'rectangle';
174
237
  if (useSilhouetteMask) {
175
- const svgMask = silhouetteMaskUrl(params.shape.type, borderWidth);
176
- gradientEl.style.webkitMaskImage = svgMask;
177
- gradientEl.style.maskImage = svgMask;
178
238
  gradientEl.style.webkitMaskRepeat = 'no-repeat';
179
239
  gradientEl.style.maskRepeat = 'no-repeat';
180
240
  gradientEl.style.webkitMaskSize = '100% 100%';
181
241
  gradientEl.style.maskSize = '100% 100%';
182
- // Trim the outer half of the stroke so the visible ring sits
183
- // flush with the silhouette edge instead of bleeding outside.
184
- if (shapeClip)
185
- gradientEl.style.clipPath = shapeClip;
242
+ // applyShapeOverlays() below sets the actual maskImage + clipPath
243
+ // using current pixel dimensions; ResizeObserver keeps it in sync.
186
244
  }
187
245
  else {
188
246
  gradientEl.style.boxSizing = 'border-box';
@@ -208,6 +266,14 @@ export function decorations(node, params) {
208
266
  // content (text, code block, etc.) instead of covering it.
209
267
  node.insertBefore(gradientEl, node.firstChild);
210
268
  }
269
+ // Apply size-aware clip-path / silhouette mask now, then keep them in
270
+ // sync with element resizes (drag-resize, parent transform changes,
271
+ // initial layout passes that report 0×0 then resolve later).
272
+ if (params.shape) {
273
+ applyShapeOverlays();
274
+ resizeObs = new ResizeObserver(() => applyShapeOverlays());
275
+ resizeObs.observe(node);
276
+ }
211
277
  const start = performance.now();
212
278
  function tick(now) {
213
279
  // Re-alias cfg as non-null inside the tick closure — TS loses the
package/package.json CHANGED
@@ -1,84 +1,84 @@
1
- {
2
- "name": "animot-presenter",
3
- "version": "0.5.11",
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
- "type": "module",
6
- "svelte": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "svelte": "./dist/index.js",
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
13
- },
14
- "./element": {
15
- "import": "./dist/cdn/animot-presenter.esm.js",
16
- "require": "./dist/cdn/animot-presenter.min.js"
17
- },
18
- "./cdn": {
19
- "import": "./dist/cdn/animot-presenter.esm.js",
20
- "require": "./dist/cdn/animot-presenter.min.js"
21
- },
22
- "./styles": "./dist/styles/presenter.css"
23
- },
24
- "files": [
25
- "dist",
26
- "!dist/**/*.test.*",
27
- "!dist/**/*.spec.*"
28
- ],
29
- "scripts": {
30
- "dev": "vite dev",
31
- "build": "npm run build:svelte && npm run build:element",
32
- "build:svelte": "svelte-kit sync && svelte-package -o dist",
33
- "build:element": "vite build --config vite.element.config.ts",
34
- "package": "npm run build",
35
- "preview": "vite preview",
36
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
37
- "prepublishOnly": "npm run build"
38
- },
39
- "peerDependencies": {
40
- "svelte": "^5.0.0"
41
- },
42
- "peerDependenciesMeta": {
43
- "svelte": {
44
- "optional": true
45
- }
46
- },
47
- "devDependencies": {
48
- "@sveltejs/adapter-auto": "^3.0.0",
49
- "@sveltejs/kit": "^2.0.0",
50
- "@sveltejs/package": "^2.0.0",
51
- "@sveltejs/vite-plugin-svelte": "^4.0.0",
52
- "@types/canvas-confetti": "^1.9.0",
53
- "svelte": "^5.0.0",
54
- "svelte-check": "^4.0.0",
55
- "typescript": "^5.0.0",
56
- "vite": "^5.0.0"
57
- },
58
- "dependencies": {
59
- "@animotion/motion": "^2.0.1",
60
- "canvas-confetti": "^1.9.4",
61
- "shiki": "^1.0.0"
62
- },
63
- "keywords": [
64
- "svelte",
65
- "react",
66
- "vue",
67
- "angular",
68
- "web-component",
69
- "animation",
70
- "presentation",
71
- "slides",
72
- "morphing",
73
- "animot",
74
- "code-animation",
75
- "typewriter",
76
- "charts",
77
- "particles"
78
- ],
79
- "license": "BUSL-1.1",
80
- "repository": {
81
- "type": "git",
82
- "url": "https://github.com/beeblock/animot-presenter"
83
- }
84
- }
1
+ {
2
+ "name": "animot-presenter",
3
+ "version": "0.5.13",
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
+ "type": "module",
6
+ "svelte": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "svelte": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./element": {
15
+ "import": "./dist/cdn/animot-presenter.esm.js",
16
+ "require": "./dist/cdn/animot-presenter.min.js"
17
+ },
18
+ "./cdn": {
19
+ "import": "./dist/cdn/animot-presenter.esm.js",
20
+ "require": "./dist/cdn/animot-presenter.min.js"
21
+ },
22
+ "./styles": "./dist/styles/presenter.css"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "!dist/**/*.test.*",
27
+ "!dist/**/*.spec.*"
28
+ ],
29
+ "scripts": {
30
+ "dev": "vite dev",
31
+ "build": "npm run build:svelte && npm run build:element",
32
+ "build:svelte": "svelte-kit sync && svelte-package -o dist",
33
+ "build:element": "vite build --config vite.element.config.ts",
34
+ "package": "npm run build",
35
+ "preview": "vite preview",
36
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
37
+ "prepublishOnly": "npm run build"
38
+ },
39
+ "peerDependencies": {
40
+ "svelte": "^5.0.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "svelte": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@sveltejs/adapter-auto": "^3.0.0",
49
+ "@sveltejs/kit": "^2.0.0",
50
+ "@sveltejs/package": "^2.0.0",
51
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
52
+ "@types/canvas-confetti": "^1.9.0",
53
+ "svelte": "^5.0.0",
54
+ "svelte-check": "^4.0.0",
55
+ "typescript": "^5.0.0",
56
+ "vite": "^5.0.0"
57
+ },
58
+ "dependencies": {
59
+ "@animotion/motion": "^2.0.1",
60
+ "canvas-confetti": "^1.9.4",
61
+ "shiki": "^1.0.0"
62
+ },
63
+ "keywords": [
64
+ "svelte",
65
+ "react",
66
+ "vue",
67
+ "angular",
68
+ "web-component",
69
+ "animation",
70
+ "presentation",
71
+ "slides",
72
+ "morphing",
73
+ "animot",
74
+ "code-animation",
75
+ "typewriter",
76
+ "charts",
77
+ "particles"
78
+ ],
79
+ "license": "BUSL-1.1",
80
+ "repository": {
81
+ "type": "git",
82
+ "url": "https://github.com/beeblock/animot-presenter"
83
+ }
84
+ }