@pixodesk/svg-animator-vue 1.0.35 → 1.0.39
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/README.md +15 -9
- package/dist/index.cjs +72 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -36
- package/dist/index.d.ts +63 -36
- package/dist/index.js +73 -52
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +654 -303
- package/dist/index.umd.js.map +1 -1
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,12 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
2
|
import { PropType, VNode } from 'vue';
|
|
3
|
-
import { PxAnimatedSvgDocument,
|
|
3
|
+
import { PxAnimatedSvgDocument, PxTimelinePatch, PxDiagnostic, PxDiagnosticKind, PxAnimatorHandle, PxStartOn, PxOutAction } from '@pixodesk/svg-animator-web';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/** Pauses the animation at its current state. */
|
|
11
|
-
pause(): void;
|
|
12
|
-
/** Stops the animation and resets it to its initial state. */
|
|
13
|
-
cancel(): void;
|
|
14
|
-
/** Jumps to the end of the animation and holds the final state. */
|
|
15
|
-
finish(): void;
|
|
16
|
-
/** Changes the speed of the animation. 1 is normal, 2 is double, -1 is reverse. */
|
|
17
|
-
setPlaybackRate(rate: number): void;
|
|
18
|
-
/** Returns the current playback time in milliseconds. */
|
|
19
|
-
getCurrentTime(): number | null;
|
|
20
|
-
/** Jumps to a specific time (in milliseconds) in the animation. */
|
|
21
|
-
setCurrentTime(time: number): void;
|
|
22
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* The imperative handle the template ref exposes — core's `PxAnimatorHandle` under this
|
|
7
|
+
* package's name (review §9). One definition for React, Vue and React Native.
|
|
8
|
+
*/
|
|
9
|
+
type VueAnimatorApi = PxAnimatorHandle;
|
|
23
10
|
/**
|
|
24
11
|
* Vue component for rendering and controlling Pixodesk SVG animations.
|
|
25
12
|
*
|
|
@@ -52,10 +39,10 @@ declare const PixodeskSvgAnimator: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
|
52
39
|
type: PropType<PxAnimatedSvgDocument>;
|
|
53
40
|
required: true;
|
|
54
41
|
};
|
|
55
|
-
|
|
56
|
-
type: PropType<
|
|
42
|
+
timeline: {
|
|
43
|
+
type: PropType<PxTimelinePatch | string>;
|
|
57
44
|
};
|
|
58
|
-
|
|
45
|
+
resetTimeline: {
|
|
59
46
|
type: BooleanConstructor;
|
|
60
47
|
default: undefined;
|
|
61
48
|
};
|
|
@@ -89,17 +76,27 @@ declare const PixodeskSvgAnimator: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
|
89
76
|
time: {
|
|
90
77
|
type: NumberConstructor;
|
|
91
78
|
};
|
|
79
|
+
onWarn: {
|
|
80
|
+
type: PropType<(diagnostic: PxDiagnostic) => void>;
|
|
81
|
+
};
|
|
82
|
+
onError: {
|
|
83
|
+
type: PropType<(diagnostic: PxDiagnostic) => void>;
|
|
84
|
+
};
|
|
85
|
+
silent: {
|
|
86
|
+
type: PropType<boolean | ReadonlyArray<PxDiagnosticKind>>;
|
|
87
|
+
default: undefined;
|
|
88
|
+
};
|
|
92
89
|
}>, () => VNode<vue.RendererNode, vue.RendererElement, {
|
|
93
90
|
[key: string]: any;
|
|
94
|
-
}> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, ("
|
|
91
|
+
}> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, ("finish" | "play" | "pause" | "cancel" | "stop" | "remove")[], "finish" | "play" | "pause" | "cancel" | "stop" | "remove", vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
95
92
|
doc: {
|
|
96
93
|
type: PropType<PxAnimatedSvgDocument>;
|
|
97
94
|
required: true;
|
|
98
95
|
};
|
|
99
|
-
|
|
100
|
-
type: PropType<
|
|
96
|
+
timeline: {
|
|
97
|
+
type: PropType<PxTimelinePatch | string>;
|
|
101
98
|
};
|
|
102
|
-
|
|
99
|
+
resetTimeline: {
|
|
103
100
|
type: BooleanConstructor;
|
|
104
101
|
default: undefined;
|
|
105
102
|
};
|
|
@@ -133,18 +130,29 @@ declare const PixodeskSvgAnimator: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
|
133
130
|
time: {
|
|
134
131
|
type: NumberConstructor;
|
|
135
132
|
};
|
|
133
|
+
onWarn: {
|
|
134
|
+
type: PropType<(diagnostic: PxDiagnostic) => void>;
|
|
135
|
+
};
|
|
136
|
+
onError: {
|
|
137
|
+
type: PropType<(diagnostic: PxDiagnostic) => void>;
|
|
138
|
+
};
|
|
139
|
+
silent: {
|
|
140
|
+
type: PropType<boolean | ReadonlyArray<PxDiagnosticKind>>;
|
|
141
|
+
default: undefined;
|
|
142
|
+
};
|
|
136
143
|
}>> & Readonly<{
|
|
144
|
+
onFinish?: ((...args: any[]) => any) | undefined;
|
|
137
145
|
onPlay?: ((...args: any[]) => any) | undefined;
|
|
138
|
-
onStop?: ((...args: any[]) => any) | undefined;
|
|
139
146
|
onPause?: ((...args: any[]) => any) | undefined;
|
|
140
147
|
onCancel?: ((...args: any[]) => any) | undefined;
|
|
141
|
-
|
|
148
|
+
onStop?: ((...args: any[]) => any) | undefined;
|
|
142
149
|
onRemove?: ((...args: any[]) => any) | undefined;
|
|
143
150
|
}>, {
|
|
144
|
-
autoplay: boolean;
|
|
145
151
|
play: boolean;
|
|
146
|
-
resetDocDefaults: boolean;
|
|
147
152
|
pause: boolean;
|
|
153
|
+
autoplay: boolean;
|
|
154
|
+
resetTimeline: boolean;
|
|
155
|
+
silent: boolean | readonly PxDiagnosticKind[];
|
|
148
156
|
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
|
149
157
|
|
|
150
158
|
/**
|
|
@@ -176,34 +184,53 @@ declare const PixodeskSvgAnimator: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
|
176
184
|
* - `'mouseOver'` — plays on hover
|
|
177
185
|
* - `'click'` — plays on click, toggles on second click
|
|
178
186
|
* - `'scrollIntoView'` — plays when the element enters the viewport
|
|
187
|
+
* - `'programmatic'` — **not supported here.** This wrapper exposes no `play()`, so there is
|
|
188
|
+
* nothing to wait for and the animation never starts. The prop keeps the
|
|
189
|
+
* shared `PxStartOn` type (one enum per wire key), so the value type-checks
|
|
190
|
+
* — it simply has no effect. Use `PixodeskSvgAnimator` (the JSON player)
|
|
191
|
+
* when you need to start an animation from code.
|
|
179
192
|
* @prop outAction - What happens when the trigger ends (hover/scroll out, second click):
|
|
180
193
|
* - `'continue'` — keeps playing (default)
|
|
181
194
|
* - `'pause'` — pauses at the current frame
|
|
182
195
|
* - `'reset'` — resets to the beginning
|
|
196
|
+
* - `'reverse'` — **acts as `'continue'` here.** A CSS class toggle cannot run keyframes
|
|
197
|
+
* backwards. Accepted so the prop keeps the shared `PxOutAction` type.
|
|
198
|
+
* @prop scrollIntoViewThreshold - For `'scrollIntoView'`: how much of the element must be
|
|
199
|
+
* visible (0–1) before it starts, and below which the out action applies. Defaults to the
|
|
200
|
+
* wire default (`0`, any pixel) — the same as the JSON player, not a private `0.1`.
|
|
183
201
|
*/
|
|
184
202
|
declare const PixodeskSvgCssAnimator: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
185
203
|
startOn: {
|
|
186
|
-
type: PropType<
|
|
204
|
+
type: PropType<PxStartOn>;
|
|
187
205
|
default: string;
|
|
188
206
|
};
|
|
189
207
|
outAction: {
|
|
190
|
-
type: PropType<
|
|
208
|
+
type: PropType<PxOutAction>;
|
|
191
209
|
default: string;
|
|
192
210
|
};
|
|
211
|
+
scrollIntoViewThreshold: {
|
|
212
|
+
type: NumberConstructor;
|
|
213
|
+
default: 0;
|
|
214
|
+
};
|
|
193
215
|
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
|
194
216
|
[key: string]: any;
|
|
195
217
|
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
196
218
|
startOn: {
|
|
197
|
-
type: PropType<
|
|
219
|
+
type: PropType<PxStartOn>;
|
|
198
220
|
default: string;
|
|
199
221
|
};
|
|
200
222
|
outAction: {
|
|
201
|
-
type: PropType<
|
|
223
|
+
type: PropType<PxOutAction>;
|
|
202
224
|
default: string;
|
|
203
225
|
};
|
|
226
|
+
scrollIntoViewThreshold: {
|
|
227
|
+
type: NumberConstructor;
|
|
228
|
+
default: 0;
|
|
229
|
+
};
|
|
204
230
|
}>> & Readonly<{}>, {
|
|
205
|
-
startOn:
|
|
206
|
-
outAction:
|
|
231
|
+
startOn: PxStartOn;
|
|
232
|
+
outAction: PxOutAction;
|
|
233
|
+
scrollIntoViewThreshold: number;
|
|
207
234
|
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
|
208
235
|
|
|
209
236
|
export { PixodeskSvgAnimator, PixodeskSvgCssAnimator, type VueAnimatorApi };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var Vue = window.Vue; var PixodeskAnimatorWeb = window.PixodeskAnimator;
|
|
2
2
|
|
|
3
3
|
// src/PixodeskSvgAnimator.ts
|
|
4
|
-
import { camelCaseToKebabWordIfNeeded, createAnimator, generateNewIds, getNormalizedProps, STYLE_ATTR_NAMES, applyAnimatorConfig,
|
|
4
|
+
import { camelCaseToKebabWordIfNeeded, createAnimator, createDiagnostics, generateNewIds, getNormalizedProps, STYLE_ATTR_NAMES, applyAnimatorConfig, foldTimelineOverride, getAnimatorConfig, PxControlMode, resolveControlMode, controlModeTakesOverTrigger, PxDiagnosticKind, progressToTimeMs, DEFAULT_DURATION_MS } from "@pixodesk/svg-animator-web";
|
|
5
5
|
import {
|
|
6
6
|
computed,
|
|
7
7
|
defineComponent,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
watch
|
|
13
13
|
} from "vue";
|
|
14
14
|
var VUE_PROP = { ref: "ref" };
|
|
15
|
-
function createVueAdapter(elementRefs) {
|
|
15
|
+
function createVueAdapter(elementRefs, diag) {
|
|
16
16
|
const warnedSelectors = /* @__PURE__ */ new Set();
|
|
17
17
|
const adapter = {
|
|
18
18
|
isConnected: () => true,
|
|
@@ -21,7 +21,7 @@ function createVueAdapter(elementRefs) {
|
|
|
21
21
|
const element = elementRefs.get(id);
|
|
22
22
|
if (!element && !warnedSelectors.has(id)) {
|
|
23
23
|
warnedSelectors.add(id);
|
|
24
|
-
|
|
24
|
+
diag.warn(PxDiagnosticKind.host, 'setAttribute: No elements found for id "' + id + '"');
|
|
25
25
|
}
|
|
26
26
|
if (element) {
|
|
27
27
|
element.setAttribute(attrName, value);
|
|
@@ -33,19 +33,19 @@ function createVueAdapter(elementRefs) {
|
|
|
33
33
|
};
|
|
34
34
|
return adapter;
|
|
35
35
|
}
|
|
36
|
-
function applyDocOverrides(doc, props, compMode) {
|
|
37
|
-
const {
|
|
38
|
-
const patch =
|
|
39
|
-
const fullPatch = compMode
|
|
36
|
+
function applyDocOverrides(doc, props, compMode, diag) {
|
|
37
|
+
const { timeline, resetTimeline, duration, delay, iterations, startOn } = props;
|
|
38
|
+
const patch = foldTimelineOverride(timeline, { duration, delay, iterations, startOn });
|
|
39
|
+
const fullPatch = controlModeTakesOverTrigger(compMode) ? {
|
|
40
40
|
...patch ?? {},
|
|
41
41
|
timeline: {
|
|
42
42
|
...patch?.timeline ?? {},
|
|
43
43
|
trigger: { ...patch?.timeline?.trigger ?? {}, startOn: "programmatic" }
|
|
44
44
|
}
|
|
45
45
|
} : patch;
|
|
46
|
-
if (fullPatch !== void 0 ||
|
|
47
|
-
const applied = applyAnimatorConfig(doc, fullPatch ?? {}, { resetDefaults: !!
|
|
48
|
-
for (const w of applied.warnings)
|
|
46
|
+
if (fullPatch !== void 0 || resetTimeline) {
|
|
47
|
+
const applied = applyAnimatorConfig(doc, fullPatch ?? {}, { resetDefaults: !!resetTimeline });
|
|
48
|
+
for (const w of applied.warnings) diag.warn(PxDiagnosticKind.usage, "timeline override: " + w);
|
|
49
49
|
doc = applied.doc;
|
|
50
50
|
}
|
|
51
51
|
return doc;
|
|
@@ -55,9 +55,9 @@ function calcSeekMs(doc, props) {
|
|
|
55
55
|
const animator = getAnimatorConfig(doc) || {};
|
|
56
56
|
if (props.progress !== void 0) {
|
|
57
57
|
const iterationsValue = props.iterations ?? animator.iterations;
|
|
58
|
-
const iterationsCount = typeof iterationsValue === "number" && iterationsValue >= 1 ? iterationsValue : 1;
|
|
59
|
-
const singleDuration = props.duration ?? animator.duration ??
|
|
60
|
-
seekMs = props.progress
|
|
58
|
+
const iterationsCount = iterationsValue === "infinite" ? Infinity : typeof iterationsValue === "number" && iterationsValue >= 1 ? iterationsValue : 1;
|
|
59
|
+
const singleDuration = props.duration ?? animator.duration ?? DEFAULT_DURATION_MS;
|
|
60
|
+
seekMs = progressToTimeMs(props.progress, singleDuration, iterationsCount);
|
|
61
61
|
}
|
|
62
62
|
if (props.time !== void 0) seekMs = props.time;
|
|
63
63
|
return seekMs;
|
|
@@ -67,10 +67,10 @@ var PixodeskSvgAnimator = defineComponent({
|
|
|
67
67
|
props: {
|
|
68
68
|
// -- Source
|
|
69
69
|
doc: { type: Object, required: true },
|
|
70
|
-
// -- Playback override:
|
|
71
|
-
// plus the four shortcuts people reach for most.
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
// -- Playback override: the document's `timeline` block as a patch (or a JSON string),
|
|
71
|
+
// plus the four shortcuts people reach for most. The same names as React / RN.
|
|
72
|
+
timeline: { type: [Object, String] },
|
|
73
|
+
resetTimeline: { type: Boolean, default: void 0 },
|
|
74
74
|
duration: { type: Number },
|
|
75
75
|
delay: { type: Number },
|
|
76
76
|
iterations: { type: [Number, String] },
|
|
@@ -81,21 +81,41 @@ var PixodeskSvgAnimator = defineComponent({
|
|
|
81
81
|
pause: { type: Boolean, default: void 0 },
|
|
82
82
|
// -- Controlled time
|
|
83
83
|
progress: { type: Number },
|
|
84
|
-
time: { type: Number }
|
|
84
|
+
time: { type: Number },
|
|
85
|
+
// -- Diagnostics (API review §5).
|
|
86
|
+
// Function PROPS, not emits, on purpose: an emit handler always exists, so wiring
|
|
87
|
+
// these to `emit` would permanently suppress the console fallback for anyone who
|
|
88
|
+
// never listens. As props, "not given" really is undefined and the console still
|
|
89
|
+
// speaks by default — the same contract as React and React Native.
|
|
90
|
+
onWarn: { type: Function },
|
|
91
|
+
onError: { type: Function },
|
|
92
|
+
// `silent` takes `true` or just the kinds to quiet, so `platform` chatter can be
|
|
93
|
+
// silenced while `document` problems still speak.
|
|
94
|
+
silent: { type: [Boolean, Array], default: void 0 }
|
|
85
95
|
},
|
|
86
96
|
emits: ["play", "stop", "pause", "cancel", "finish", "remove"],
|
|
87
97
|
setup(props, { expose, emit }) {
|
|
88
98
|
const elementRefs = /* @__PURE__ */ new Map();
|
|
89
99
|
const apiRef = shallowRef(null);
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
const makeDiag = () => createDiagnostics(
|
|
101
|
+
{ onWarn: props.onWarn, onError: props.onError, silent: props.silent },
|
|
102
|
+
"[PixodeskSvgAnimator]"
|
|
103
|
+
);
|
|
104
|
+
const resolvedMode = computed(() => resolveControlMode({
|
|
105
|
+
progress: props.progress,
|
|
106
|
+
time: props.time,
|
|
107
|
+
play: props.play,
|
|
108
|
+
pause: props.pause,
|
|
109
|
+
autoplay: props.autoplay
|
|
110
|
+
}));
|
|
111
|
+
const compMode = computed(() => resolvedMode.value.mode);
|
|
112
|
+
watch(resolvedMode, (r) => {
|
|
113
|
+
const diag = makeDiag();
|
|
114
|
+
for (const w of r.warnings) diag.warn(PxDiagnosticKind.usage, w);
|
|
115
|
+
}, { immediate: true });
|
|
96
116
|
const resolvedDoc = computed(() => {
|
|
97
117
|
let doc = generateNewIds(props.doc);
|
|
98
|
-
return applyDocOverrides(doc, props, compMode.value);
|
|
118
|
+
return applyDocOverrides(doc, props, compMode.value, makeDiag());
|
|
99
119
|
});
|
|
100
120
|
function renderNode(node) {
|
|
101
121
|
if (!node) return null;
|
|
@@ -112,32 +132,27 @@ var PixodeskSvgAnimator = defineComponent({
|
|
|
112
132
|
};
|
|
113
133
|
}
|
|
114
134
|
const childVNodes = children?.map((child) => renderNode(child)).filter(Boolean);
|
|
115
|
-
|
|
135
|
+
const text = typeof node.textContent === "string" ? node.textContent : void 0;
|
|
136
|
+
return h(type, normProps, childVNodes?.length ? childVNodes : text);
|
|
116
137
|
}
|
|
117
138
|
function createApi() {
|
|
118
139
|
destroyApi();
|
|
119
140
|
const doc = resolvedDoc.value;
|
|
120
141
|
if (!doc) return;
|
|
121
|
-
|
|
142
|
+
apiRef.value = createAnimator({
|
|
143
|
+
doc,
|
|
144
|
+
adapter: createVueAdapter(elementRefs, makeDiag()),
|
|
122
145
|
onPlay: () => emit("play"),
|
|
123
|
-
onPause: () =>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
emit("stop");
|
|
134
|
-
},
|
|
135
|
-
onRemove: () => {
|
|
136
|
-
emit("remove");
|
|
137
|
-
emit("stop");
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
apiRef.value = createAnimator({ data: doc, adapter: createVueAdapter(elementRefs), callbacks });
|
|
146
|
+
onPause: () => emit("pause"),
|
|
147
|
+
onCancel: () => emit("cancel"),
|
|
148
|
+
onFinish: () => emit("finish"),
|
|
149
|
+
onRemove: () => emit("remove"),
|
|
150
|
+
onStop: () => emit("stop"),
|
|
151
|
+
// The player's own diagnostics reach the same handlers as the component's.
|
|
152
|
+
onWarn: props.onWarn,
|
|
153
|
+
onError: props.onError,
|
|
154
|
+
silent: props.silent
|
|
155
|
+
});
|
|
141
156
|
syncPlayState();
|
|
142
157
|
applySeek();
|
|
143
158
|
}
|
|
@@ -146,19 +161,19 @@ var PixodeskSvgAnimator = defineComponent({
|
|
|
146
161
|
apiRef.value = null;
|
|
147
162
|
}
|
|
148
163
|
function syncPlayState() {
|
|
149
|
-
if (compMode.value !==
|
|
164
|
+
if (compMode.value !== PxControlMode.play) return;
|
|
150
165
|
if (props.play && !props.pause) {
|
|
151
166
|
apiRef.value?.play();
|
|
152
167
|
} else if (props.pause) {
|
|
153
168
|
apiRef.value?.pause();
|
|
154
169
|
} else if (props.play === false) {
|
|
155
|
-
apiRef.value?.
|
|
170
|
+
apiRef.value?.pause();
|
|
156
171
|
} else {
|
|
157
172
|
apiRef.value?.play();
|
|
158
173
|
}
|
|
159
174
|
}
|
|
160
175
|
function applySeek() {
|
|
161
|
-
if (compMode.value !==
|
|
176
|
+
if (compMode.value !== PxControlMode.fixedTime) return;
|
|
162
177
|
const doc = resolvedDoc.value;
|
|
163
178
|
if (!doc) return;
|
|
164
179
|
const seekMs = calcSeekMs(doc, props);
|
|
@@ -182,7 +197,9 @@ var PixodeskSvgAnimator = defineComponent({
|
|
|
182
197
|
finish: () => apiRef.value?.finish(),
|
|
183
198
|
setPlaybackRate: (rate) => apiRef.value?.setPlaybackRate(rate),
|
|
184
199
|
getCurrentTime: () => apiRef.value?.getCurrentTime() ?? null,
|
|
185
|
-
setCurrentTime: (time) => apiRef.value?.setCurrentTime(time)
|
|
200
|
+
setCurrentTime: (time) => apiRef.value?.setCurrentTime(time),
|
|
201
|
+
getCurrentProgress: () => apiRef.value?.getCurrentProgress() ?? null,
|
|
202
|
+
setCurrentProgress: (progress) => apiRef.value?.setCurrentProgress(progress)
|
|
186
203
|
};
|
|
187
204
|
expose(publicApi);
|
|
188
205
|
return () => {
|
|
@@ -194,13 +211,15 @@ var PixodeskSvgAnimator = defineComponent({
|
|
|
194
211
|
var PixodeskSvgAnimator_default = PixodeskSvgAnimator;
|
|
195
212
|
|
|
196
213
|
// src/PixodeskSvgCssAnimator.ts
|
|
214
|
+
import { PX_TRIGGER_DEFAULTS } from "@pixodesk/svg-animator-web";
|
|
197
215
|
import { computed as computed2, defineComponent as defineComponent2, h as h2, onMounted as onMounted2, onUnmounted as onUnmounted2, ref as ref2, useAttrs } from "vue";
|
|
198
216
|
var PixodeskSvgCssAnimator = defineComponent2({
|
|
199
217
|
name: "PixodeskSvgCssAnimator",
|
|
200
218
|
inheritAttrs: false,
|
|
201
219
|
props: {
|
|
202
220
|
startOn: { type: String, default: "load" },
|
|
203
|
-
outAction: { type: String, default: "continue" }
|
|
221
|
+
outAction: { type: String, default: "continue" },
|
|
222
|
+
scrollIntoViewThreshold: { type: Number, default: PX_TRIGGER_DEFAULTS.scrollIntoViewThreshold }
|
|
204
223
|
},
|
|
205
224
|
setup(props, { slots }) {
|
|
206
225
|
const attrs = useAttrs();
|
|
@@ -215,11 +234,13 @@ var PixodeskSvgCssAnimator = defineComponent2({
|
|
|
215
234
|
const el = divRef.value;
|
|
216
235
|
if (!el) return;
|
|
217
236
|
const outState = props.outAction === "reset" ? "idle" : props.outAction === "pause" ? "paused" : "playing";
|
|
237
|
+
const threshold = props.scrollIntoViewThreshold;
|
|
238
|
+
const visible = (entry) => threshold > 0 ? entry.intersectionRatio >= threshold : entry.isIntersecting;
|
|
218
239
|
const observer = new IntersectionObserver(
|
|
219
240
|
([entry]) => {
|
|
220
|
-
state.value = entry
|
|
241
|
+
state.value = visible(entry) ? "playing" : outState;
|
|
221
242
|
},
|
|
222
|
-
{ threshold
|
|
243
|
+
{ threshold }
|
|
223
244
|
);
|
|
224
245
|
observer.observe(el);
|
|
225
246
|
observerCleanup = () => observer.disconnect();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/PixodeskSvgAnimator.ts","../src/PixodeskSvgCssAnimator.ts"],"sourcesContent":["/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { PxAnimatedSvgDocument, PxAnimatorAPI, PxAnimatorConfigPatch, PxNode, PxPlatformAdapter, PxTimelineEngineExtra, PxTrigger } from '@pixodesk/svg-animator-web';\nimport { camelCaseToKebabWordIfNeeded, createAnimator, FillMode, generateNewIds, getNormalizedProps, STYLE_ATTR_NAMES, applyAnimatorConfig, foldAnimatorConfigShortcuts, getAnimatorConfig } from '@pixodesk/svg-animator-web';\nimport {\n computed, defineComponent, h, onMounted, onUnmounted, ref, shallowRef, type PropType, type VNode,\n watch,\n} from 'vue';\n\n\n// -- Public types -----------------------------------------------------------\n\n/** Vue's own prop names on the vnode — not wire keys. See the React twin. */\nconst VUE_PROP = { ref: 'ref' } as const;\n\nexport interface VueAnimatorApi {\n /** Returns true if the animation is currently running. */\n isPlaying(): boolean;\n\n /** Starts or resumes the animation. */\n play(): void;\n\n /** Pauses the animation at its current state. */\n pause(): void;\n\n /** Stops the animation and resets it to its initial state. */\n cancel(): void;\n\n /** Jumps to the end of the animation and holds the final state. */\n finish(): void;\n\n /** Changes the speed of the animation. 1 is normal, 2 is double, -1 is reverse. */\n setPlaybackRate(rate: number): void;\n\n /** Returns the current playback time in milliseconds. */\n getCurrentTime(): number | null;\n\n /** Jumps to a specific time (in milliseconds) in the animation. */\n setCurrentTime(time: number): void;\n}\n\n\n// -- Internal types ---------------------------------------------------------\n\nenum CompMode {\n static = 'static',\n autoplay = 'autoplay',\n play = 'play',\n fixedTime = 'fixedTime'\n}\n\n\n// -- Vue ↔ Animator bridge --------------------------------------------------\n\n/**\n * Creates a platform adapter that routes animator attribute updates\n * to the corresponding Vue-managed DOM element refs.\n */\nfunction createVueAdapter(elementRefs: Map<string, Element>) {\n const warnedSelectors = new Set<string>();\n\n const adapter: PxPlatformAdapter = {\n isConnected: () => true,\n setAttribute: (id, attrName, value) => {\n attrName = camelCaseToKebabWordIfNeeded(attrName);\n\n const element = elementRefs.get(id);\n\n if (!element && !warnedSelectors.has(id)) {\n warnedSelectors.add(id);\n console.warn('setAttribute: No elements found for id \"' + id + '\"');\n }\n\n if (element) {\n element.setAttribute(attrName, value);\n if (STYLE_ATTR_NAMES.has(attrName)) {\n (element as HTMLElement).style[attrName as any] = value;\n }\n }\n },\n };\n return adapter;\n}\n\n// FIXME: add model validation (e.g. isElementFileJson check)\n\n\n// -- Helper: apply doc overrides --------------------------------------------\n\ninterface DocOverrideProps {\n /** Per-instance override of the document's `animator` config — the same shape as\n * `animator` in SCHEMA.md, deep-merged; `null` at a slot deletes it. Also accepts a\n * JSON string. Replaces the former flat mode/fill/direction/frameRate/outAction props. */\n config?: PxAnimatorConfigPatch | string;\n /** Start from the player's defaults instead of the document's playback settings. */\n resetDocDefaults?: boolean;\n duration?: number;\n delay?: number;\n iterations?: number | 'infinite';\n startOn?: 'load' | 'mouseOver' | 'click' | 'scrollIntoView' | 'programmatic';\n progress?: number;\n time?: number;\n}\n\nfunction applyDocOverrides(\n doc: PxAnimatedSvgDocument,\n props: DocOverrideProps,\n compMode: CompMode,\n): PxAnimatedSvgDocument {\n\n // ONE patch, applied ONCE: the props, plus the component's own need to take the trigger\n // over in the non-autoplay control modes.\n //\n // This replaces three hand-rolled spread blocks that wrote the FLAT runtime keys. On a\n // wire-format document — which is what every writer emits — `flattenAnimatorTimeline`\n // overwrote them from `timeline` immediately afterwards, so the overrides were silently\n // discarded. See PLAYBACK-OVERRIDE-PLAN.md §1.1.\n const { config, resetDocDefaults, duration, delay, iterations, startOn } = props;\n const patch = foldAnimatorConfigShortcuts(config, { duration, delay, iterations, startOn });\n const fullPatch: any = compMode !== CompMode.autoplay\n ? {\n ...(patch ?? {}),\n timeline: {\n ...((patch as any)?.timeline ?? {}),\n trigger: { ...((patch as any)?.timeline?.trigger ?? {}), startOn: 'programmatic' },\n },\n }\n : patch;\n\n if (fullPatch !== undefined || resetDocDefaults) {\n const applied = applyAnimatorConfig(doc, fullPatch ?? {}, { resetDefaults: !!resetDocDefaults });\n for (const w of applied.warnings) console.warn('[PixodeskSvgAnimator] config override:', w);\n doc = applied.doc;\n }\n\n return doc;\n}\n\n/**\n * Controlled-time mode: absolute seek target in ms. `progress` is a fraction\n * (0–1) of the WHOLE timeline (duration × iterations); `time` is absolute.\n * Applied through the animator API (setCurrentTime) so scrubbing does NOT\n * recreate the animator.\n */\nfunction calcSeekMs(doc: PxAnimatedSvgDocument, props: DocOverrideProps): number | undefined {\n let seekMs: number | undefined;\n // The FLAT runtime view, so this works on a wire-format document too — reading\n // `doc.animator.duration` directly finds nothing there.\n const animator = getAnimatorConfig(doc) || {};\n if (props.progress !== undefined) {\n const iterationsValue = props.iterations ?? animator.iterations;\n const iterationsCount = typeof iterationsValue === 'number' && iterationsValue >= 1 ? iterationsValue : 1;\n const singleDuration = props.duration ?? animator.duration ?? 1000; // engine default duration\n seekMs = props.progress * singleDuration * iterationsCount;\n }\n if (props.time !== undefined) seekMs = props.time;\n return seekMs;\n}\n\n\n// -- Main public component --------------------------------------------------\n\n/**\n * Vue component for rendering and controlling Pixodesk SVG animations.\n *\n * Supports four mutually-exclusive control modes:\n *\n * 1. **Autoplay** – uses triggers from the animation document.\n * ```vue\n * <PixodeskSvgAnimator :doc=\"animation\" autoplay />\n * ```\n *\n * 2. **Declarative play/pause** – controlled via boolean props.\n * ```vue\n * <PixodeskSvgAnimator :doc=\"animation\" play :pause=\"false\" />\n * ```\n *\n * 3. **Imperative** – exposes a ref-based API for full programmatic control.\n * ```vue\n * <PixodeskSvgAnimator :doc=\"animation\" ref=\"animator\" />\n * <button @click=\"$refs.animator.play()\">Play</button>\n * ```\n *\n * 4. **Controlled time** – renders a single frame at a given time.\n * ```vue\n * <PixodeskSvgAnimator :doc=\"animation\" :progress=\"0.5\" />\n * <PixodeskSvgAnimator :doc=\"animation\" :time=\"500\" />\n * ```\n */\nconst PixodeskSvgAnimator = defineComponent({\n name: 'PixodeskSvgAnimator',\n\n props: {\n // -- Source\n doc: { type: Object as PropType<PxAnimatedSvgDocument>, required: true },\n\n // -- Playback override: one object spelled exactly like `animator` in the file,\n // plus the four shortcuts people reach for most.\n config: { type: [Object, String] as PropType<PxAnimatorConfigPatch | string> },\n resetDocDefaults: { type: Boolean, default: undefined },\n duration: { type: Number },\n delay: { type: Number },\n iterations: { type: [Number, String] as PropType<number | 'infinite'> },\n startOn: { type: String as PropType<'load' | 'mouseOver' | 'click' | 'scrollIntoView' | 'programmatic'> },\n\n // -- Declarative control\n autoplay: { type: Boolean, default: undefined },\n play: { type: Boolean, default: undefined },\n pause: { type: Boolean, default: undefined },\n\n // -- Controlled time\n progress: { type: Number },\n time: { type: Number },\n },\n\n emits: ['play', 'stop', 'pause', 'cancel', 'finish', 'remove'],\n\n setup(props, { expose, emit }) {\n const elementRefs = new Map<string, Element>();\n const apiRef = shallowRef<PxAnimatorAPI | null>(null);\n\n // -- Determine control mode ---------------------------------------------\n\n const compMode = computed<CompMode>(() => {\n if (props.autoplay) return CompMode.autoplay;\n if (props.progress !== undefined || props.time !== undefined) return CompMode.fixedTime;\n if (props.play !== undefined || props.pause !== undefined) return CompMode.play;\n return CompMode.static;\n });\n\n // -- Prepare the document with overrides --------------------------------\n\n const resolvedDoc = computed(() => {\n let doc = generateNewIds(props.doc);\n return applyDocOverrides(doc, props, compMode.value);\n });\n\n // -- Render the SVG node tree -------------------------------------------\n\n function renderNode(node: PxNode | undefined): VNode | null {\n if (!node) return null;\n\n const { type, animate, meta, children, ...attrs } = node;\n const normProps = getNormalizedProps(attrs);\n\n // Capture a ref to each element with an id.\n if (node.id) {\n const nodeId = node.id;\n normProps[VUE_PROP.ref] = (el: Element | null) => {\n if (el) {\n elementRefs.set(nodeId, el);\n } else {\n elementRefs.delete(nodeId);\n }\n };\n }\n\n const childVNodes = children?.map(child => renderNode(child)).filter(Boolean) as VNode[] | undefined;\n return h(type, normProps, childVNodes);\n }\n\n // -- Animator lifecycle -------------------------------------------------\n\n function createApi() {\n destroyApi();\n const doc = resolvedDoc.value;\n if (!doc) return;\n\n // Route animator lifecycle events to Vue component events.\n // `stop` fires alongside any event that halts playback.\n const callbacks = {\n onPlay: () => emit('play'),\n onPause: () => { emit('pause'); emit('stop'); },\n onCancel: () => { emit('cancel'); emit('stop'); },\n onFinish: () => { emit('finish'); emit('stop'); },\n onRemove: () => { emit('remove'); emit('stop'); },\n };\n\n apiRef.value = createAnimator({ data: doc, adapter: createVueAdapter(elementRefs), callbacks });\n\n // (Re)apply the declarative control state to the fresh animator —\n // covers both the initial mount (e.g. `:play=\"true\"` from the\n // start) and doc swaps.\n syncPlayState();\n applySeek();\n }\n\n function destroyApi() {\n apiRef.value?.destroy();\n apiRef.value = null;\n }\n\n /** Declarative play/pause → animator calls. */\n function syncPlayState() {\n if (compMode.value !== CompMode.play) return;\n if (props.play && !props.pause) {\n apiRef.value?.play();\n } else if (props.pause) {\n apiRef.value?.pause();\n } else if (props.play === false) {\n // explicit play=false → jump to the end state\n apiRef.value?.finish();\n } else {\n // pause-only usage: pause switched off → resume\n apiRef.value?.play();\n }\n }\n\n /** Controlled-time mode: seek through the animator API (no recreate). */\n function applySeek() {\n if (compMode.value !== CompMode.fixedTime) return;\n const doc = resolvedDoc.value;\n if (!doc) return;\n const seekMs = calcSeekMs(doc, props);\n if (seekMs !== undefined) {\n apiRef.value?.setCurrentTime(seekMs);\n apiRef.value?.pause();\n }\n }\n\n // Create the animator once DOM refs are available.\n onMounted(() => createApi());\n\n // Recreate the animator when the resolved doc changes.\n // `flush: 'post'` — the animator must be created AFTER the DOM is\n // patched, otherwise the new root element isn't in the document yet\n // and trigger setup fails (autoplay would never resume after a doc swap).\n watch(resolvedDoc, () => createApi(), { flush: 'post' });\n\n // Sync declarative play/pause props with the animator.\n watch([compMode, () => props.play, () => props.pause], () => syncPlayState());\n\n // Scrubbing progress/time only seeks — the animator is NOT recreated.\n watch([compMode, () => props.progress, () => props.time], () => applySeek());\n\n onUnmounted(() => {\n destroyApi();\n });\n\n // -- Expose imperative API ----------------------------------------------\n\n const publicApi: VueAnimatorApi = {\n isPlaying: () => apiRef.value?.isPlaying() || false,\n play: () => apiRef.value?.play(),\n pause: () => apiRef.value?.pause(),\n cancel: () => apiRef.value?.cancel(),\n finish: () => apiRef.value?.finish(),\n setPlaybackRate: (rate: number) => apiRef.value?.setPlaybackRate(rate),\n getCurrentTime: () => apiRef.value?.getCurrentTime() ?? null,\n setCurrentTime: (time: number) => apiRef.value?.setCurrentTime(time),\n };\n\n expose(publicApi);\n\n // -- Render -------------------------------------------------------------\n\n return () => {\n const doc = resolvedDoc.value;\n return doc ? renderNode(doc) : null;\n };\n },\n});\n\nexport default PixodeskSvgAnimator;\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { OutAction, StartOn } from \"@pixodesk/svg-animator-web\";\nimport { computed, defineComponent, h, onMounted, onUnmounted, ref, useAttrs, type PropType } from 'vue';\n\n\ntype AnimState = 'idle' | 'paused' | 'playing';\n\n\n/**\n * Controls playback of a SVG+CSS animated file by toggling class names on a wrapper div.\n *\n * Intended for use with SVG files exported from the Pixodesk editor using the\n * **CSS Keyframes** flavor (no `<script>` tag). Import the SVG as a Vue component\n * via `vite-svg-loader` and pass it as the default slot:\n *\n * ```vue\n * <script setup>\n * import AnimationSvg from './animation.svg'; // vite-svg-loader\n * </script>\n *\n * <template>\n * <PixodeskSvgCssAnimator startOn=\"mouseOver\" outAction=\"pause\">\n * <AnimationSvg />\n * </PixodeskSvgCssAnimator>\n * </template>\n * ```\n *\n * The wrapper div carries one of three animation states via CSS class names:\n * - *(no class)* — idle, animation not started\n * - `px-anim-enabled` — started but paused\n * - `px-anim-enabled px-anim-playing` — actively playing\n *\n * @prop startOn - What triggers the animation to start:\n * - `'load'` — plays immediately on mount (default)\n * - `'mouseOver'` — plays on hover\n * - `'click'` — plays on click, toggles on second click\n * - `'scrollIntoView'` — plays when the element enters the viewport\n * @prop outAction - What happens when the trigger ends (hover/scroll out, second click):\n * - `'continue'` — keeps playing (default)\n * - `'pause'` — pauses at the current frame\n * - `'reset'` — resets to the beginning\n */\nconst PixodeskSvgCssAnimator = defineComponent({\n name: 'PixodeskSvgCssAnimator',\n\n inheritAttrs: false,\n\n props: {\n startOn: { type: String as PropType<StartOn>, default: 'load' },\n outAction: { type: String as PropType<OutAction>, default: 'continue' },\n },\n\n setup(props, { slots }) {\n const attrs = useAttrs();\n const state = ref<AnimState>(props.startOn === 'load' ? 'playing' : 'idle');\n const divRef = ref<HTMLDivElement | null>(null);\n\n const goOut = () => {\n state.value =\n props.outAction === 'reset' ? 'idle' :\n props.outAction === 'pause' ? 'paused' : 'playing';\n };\n\n let observerCleanup: (() => void) | undefined;\n\n onMounted(() => {\n if (props.startOn !== 'scrollIntoView') return;\n const el = divRef.value;\n if (!el) return;\n const outState: AnimState =\n props.outAction === 'reset' ? 'idle' :\n props.outAction === 'pause' ? 'paused' : 'playing';\n const observer = new IntersectionObserver(\n ([entry]) => { state.value = entry.isIntersecting ? 'playing' : outState; },\n { threshold: 0.1 }\n );\n observer.observe(el);\n observerCleanup = () => observer.disconnect();\n });\n\n onUnmounted(() => observerCleanup?.());\n\n const animClass = computed(() =>\n state.value === 'playing' ? 'px-anim-enabled px-anim-playing' :\n state.value === 'paused' ? 'px-anim-enabled' : ''\n );\n\n const handlers = computed(() =>\n props.startOn === 'mouseOver' ? {\n onMouseenter: () => { state.value = 'playing'; },\n onMouseleave: goOut,\n } :\n props.startOn === 'click' ? {\n onClick: () => state.value === 'playing' ? goOut() : (state.value = 'playing'),\n } : {}\n );\n\n return () => h('div', {\n ref: divRef,\n ...attrs,\n class: [attrs.class, animClass.value],\n ...handlers.value,\n }, slots.default?.());\n },\n});\n\nexport default PixodeskSvgCssAnimator;\n"],"mappings":"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","names":["computed","defineComponent","h","onMounted","onUnmounted","ref"]}
|
|
1
|
+
{"version":3,"sources":["../src/PixodeskSvgAnimator.ts","../src/PixodeskSvgCssAnimator.ts"],"sourcesContent":["/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { PxAnimatedSvgDocument, PxAnimatorAPI, PxNode, PxPlatformAdapter, PxTimelineEngineExtra, PxTimelinePatch, PxTrigger } from '@pixodesk/svg-animator-web';\nimport { camelCaseToKebabWordIfNeeded, createAnimator, createDiagnostics, generateNewIds, getNormalizedProps, STYLE_ATTR_NAMES, applyAnimatorConfig, foldTimelineOverride, getAnimatorConfig, PxControlMode, resolveControlMode, controlModeTakesOverTrigger, PxDiagnosticKind, progressToTimeMs, DEFAULT_DURATION_MS, type PxAnimatorHandle, type PxControlProps, type PxPlaybackOverrideProps, type PxDiagnostic, type PxDiagnostics } from '@pixodesk/svg-animator-web';\nimport {\n computed, defineComponent, h, onMounted, onUnmounted, ref, shallowRef, type PropType, type VNode,\n watch,\n} from 'vue';\n\n\n// -- Public types -----------------------------------------------------------\n\n/** Vue's own prop names on the vnode — not wire keys. See the React twin. */\nconst VUE_PROP = { ref: 'ref' } as const;\n\n/**\n * The imperative handle the template ref exposes — core's `PxAnimatorHandle` under this\n * package's name (review §9). One definition for React, Vue and React Native.\n */\nexport type VueAnimatorApi = PxAnimatorHandle;\n\n\n// -- Internal types ---------------------------------------------------------\n\n// The control mode is core's `PxControlMode`, shared with React and React Native\n// (API review §1/§7) — one precedence rule, one set of conflict warnings.\n\n\n// -- Vue ↔ Animator bridge --------------------------------------------------\n\n/**\n * Creates a platform adapter that routes animator attribute updates\n * to the corresponding Vue-managed DOM element refs.\n */\nfunction createVueAdapter(elementRefs: Map<string, Element>, diag: PxDiagnostics) {\n const warnedSelectors = new Set<string>();\n\n const adapter: PxPlatformAdapter = {\n isConnected: () => true,\n setAttribute: (id, attrName, value) => {\n attrName = camelCaseToKebabWordIfNeeded(attrName);\n\n const element = elementRefs.get(id);\n\n if (!element && !warnedSelectors.has(id)) {\n warnedSelectors.add(id);\n diag.warn(PxDiagnosticKind.host, 'setAttribute: No elements found for id \"' + id + '\"');\n }\n\n if (element) {\n element.setAttribute(attrName, value);\n if (STYLE_ATTR_NAMES.has(attrName)) {\n (element as HTMLElement).style[attrName as any] = value;\n }\n }\n },\n };\n return adapter;\n}\n\n// FIXME: add model validation (e.g. isElementFileJson check)\n\n\n// -- Helper: apply doc overrides --------------------------------------------\n\n/**\n * What `applyDocOverrides` / `calcSeekMs` read off the props — core's shared shapes, not a local\n * copy (review §9). The runtime `props: {…}` block below stays Vue's own, because Vue needs\n * runtime prop declarations; this is only the TypeScript view of the same members.\n */\ntype DocOverrideProps = PxPlaybackOverrideProps & Pick<PxControlProps, 'progress' | 'time'>;\n\nfunction applyDocOverrides(\n doc: PxAnimatedSvgDocument,\n props: DocOverrideProps,\n compMode: PxControlMode,\n diag: PxDiagnostics,\n): PxAnimatedSvgDocument {\n\n // ONE patch, applied ONCE: the props, plus the component's own need to take the trigger\n // over in the non-autoplay control modes.\n //\n // This replaces three hand-rolled spread blocks that wrote the FLAT runtime keys. On a\n // wire-format document — which is what every writer emits — `flattenAnimatorTimeline`\n // overwrote them from `timeline` immediately afterwards, so the overrides were silently\n // discarded. See PLAYBACK-OVERRIDE-PLAN.md §1.1.\n const { timeline, resetTimeline, duration, delay, iterations, startOn } = props;\n const patch = foldTimelineOverride(timeline, { duration, delay, iterations, startOn });\n const fullPatch: any = controlModeTakesOverTrigger(compMode)\n ? {\n ...(patch ?? {}),\n timeline: {\n ...((patch as any)?.timeline ?? {}),\n trigger: { ...((patch as any)?.timeline?.trigger ?? {}), startOn: 'programmatic' },\n },\n }\n : patch;\n\n if (fullPatch !== undefined || resetTimeline) {\n const applied = applyAnimatorConfig(doc, fullPatch ?? {}, { resetDefaults: !!resetTimeline });\n for (const w of applied.warnings) diag.warn(PxDiagnosticKind.usage, 'timeline override: ' + w);\n doc = applied.doc;\n }\n\n return doc;\n}\n\n/**\n * Controlled-time mode: absolute seek target in ms. `progress` is a fraction\n * (0–1) of the WHOLE timeline (duration × iterations — ONE iteration when\n * endless); `time` is absolute. Applied through the animator API\n * (setCurrentTime) so scrubbing does NOT recreate the animator.\n */\nfunction calcSeekMs(doc: PxAnimatedSvgDocument, props: DocOverrideProps): number | undefined {\n let seekMs: number | undefined;\n // The FLAT runtime view, so this works on a wire-format document too — reading\n // `doc.animator.duration` directly finds nothing there.\n const animator = getAnimatorConfig(doc) || {};\n if (props.progress !== undefined) {\n // ONE rule for progress → time (core's `progressToTimeMs`): the same mapping\n // `getCurrentProgress` reads back, so a prop of 0.5 and a read of 0.5 agree.\n const iterationsValue = props.iterations ?? animator.iterations;\n const iterationsCount = iterationsValue === 'infinite' ? Infinity\n : (typeof iterationsValue === 'number' && iterationsValue >= 1 ? iterationsValue : 1);\n const singleDuration = props.duration ?? animator.duration ?? DEFAULT_DURATION_MS;\n seekMs = progressToTimeMs(props.progress, singleDuration, iterationsCount);\n }\n if (props.time !== undefined) seekMs = props.time;\n return seekMs;\n}\n\n\n// -- Main public component --------------------------------------------------\n\n/**\n * Vue component for rendering and controlling Pixodesk SVG animations.\n *\n * Supports four mutually-exclusive control modes:\n *\n * 1. **Autoplay** – uses triggers from the animation document.\n * ```vue\n * <PixodeskSvgAnimator :doc=\"animation\" autoplay />\n * ```\n *\n * 2. **Declarative play/pause** – controlled via boolean props.\n * ```vue\n * <PixodeskSvgAnimator :doc=\"animation\" play :pause=\"false\" />\n * ```\n *\n * 3. **Imperative** – exposes a ref-based API for full programmatic control.\n * ```vue\n * <PixodeskSvgAnimator :doc=\"animation\" ref=\"animator\" />\n * <button @click=\"$refs.animator.play()\">Play</button>\n * ```\n *\n * 4. **Controlled time** – renders a single frame at a given time.\n * ```vue\n * <PixodeskSvgAnimator :doc=\"animation\" :progress=\"0.5\" />\n * <PixodeskSvgAnimator :doc=\"animation\" :time=\"500\" />\n * ```\n */\nconst PixodeskSvgAnimator = defineComponent({\n name: 'PixodeskSvgAnimator',\n\n props: {\n // -- Source\n doc: { type: Object as PropType<PxAnimatedSvgDocument>, required: true },\n\n // -- Playback override: the document's `timeline` block as a patch (or a JSON string),\n // plus the four shortcuts people reach for most. The same names as React / RN.\n timeline: { type: [Object, String] as PropType<PxTimelinePatch | string> },\n resetTimeline: { type: Boolean, default: undefined },\n duration: { type: Number },\n delay: { type: Number },\n iterations: { type: [Number, String] as PropType<number | 'infinite'> },\n startOn: { type: String as PropType<'load' | 'mouseOver' | 'click' | 'scrollIntoView' | 'programmatic'> },\n\n // -- Declarative control\n autoplay: { type: Boolean, default: undefined },\n play: { type: Boolean, default: undefined },\n pause: { type: Boolean, default: undefined },\n\n // -- Controlled time\n progress: { type: Number },\n time: { type: Number },\n\n // -- Diagnostics (API review §5).\n // Function PROPS, not emits, on purpose: an emit handler always exists, so wiring\n // these to `emit` would permanently suppress the console fallback for anyone who\n // never listens. As props, \"not given\" really is undefined and the console still\n // speaks by default — the same contract as React and React Native.\n onWarn: { type: Function as PropType<(diagnostic: PxDiagnostic) => void> },\n onError: { type: Function as PropType<(diagnostic: PxDiagnostic) => void> },\n // `silent` takes `true` or just the kinds to quiet, so `platform` chatter can be\n // silenced while `document` problems still speak.\n silent: { type: [Boolean, Array] as PropType<boolean | ReadonlyArray<PxDiagnosticKind>>, default: undefined },\n },\n\n emits: ['play', 'stop', 'pause', 'cancel', 'finish', 'remove'],\n\n setup(props, { expose, emit }) {\n const elementRefs = new Map<string, Element>();\n const apiRef = shallowRef<PxAnimatorAPI | null>(null);\n\n /**\n * The diagnostics channel, built from the CURRENT props each time (API review §5).\n * Not hoisted into a constant wrapper: `(m, d) => props.onWarn?.(m, d)` would always be\n * a function, so the channel would think a handler exists and the console fallback\n * would never fire for anyone who passed nothing.\n */\n const makeDiag = (): PxDiagnostics => createDiagnostics(\n { onWarn: props.onWarn, onError: props.onError, silent: props.silent },\n '[PixodeskSvgAnimator]',\n );\n\n // -- Determine control mode ---------------------------------------------\n\n // ONE control-mode rule, decided in core (API review §1/§7). The template ref is not an\n // input: it is exposed in every mode and never changes which one is chosen.\n const resolvedMode = computed(() => resolveControlMode({\n progress: props.progress, time: props.time,\n play: props.play, pause: props.pause, autoplay: props.autoplay,\n }));\n const compMode = computed<PxControlMode>(() => resolvedMode.value.mode);\n\n // Warn where the mode is COMPUTED, not inside `applyDocOverrides` — that runs again on\n // every doc recompute and would repeat the same sentence.\n watch(resolvedMode, r => {\n const diag = makeDiag();\n for (const w of r.warnings) diag.warn(PxDiagnosticKind.usage, w);\n }, { immediate: true });\n\n // -- Prepare the document with overrides --------------------------------\n\n const resolvedDoc = computed(() => {\n let doc = generateNewIds(props.doc);\n return applyDocOverrides(doc, props, compMode.value, makeDiag());\n });\n\n // -- Render the SVG node tree -------------------------------------------\n\n function renderNode(node: PxNode | undefined): VNode | null {\n if (!node) return null;\n\n const { type, animate, meta, children, ...attrs } = node;\n const normProps = getNormalizedProps(attrs);\n\n // Capture a ref to each element with an id.\n if (node.id) {\n const nodeId = node.id;\n normProps[VUE_PROP.ref] = (el: Element | null) => {\n if (el) {\n elementRefs.set(nodeId, el);\n } else {\n elementRefs.delete(nodeId);\n }\n };\n }\n\n const childVNodes = children?.map(child => renderNode(child)).filter(Boolean) as VNode[] | undefined;\n // Text content: a node's own `textContent` renders only when it has no child nodes — a\n // line <tspan> can carry both, and then its children are the styled spans (the React\n // Native renderer's rule). `getNormalizedProps` strips `textContent` from the attributes.\n const text = typeof node.textContent === 'string' ? node.textContent : undefined;\n return h(type, normProps, childVNodes?.length ? childVNodes : text);\n }\n\n // -- Animator lifecycle -------------------------------------------------\n\n function createApi() {\n destroyApi();\n const doc = resolvedDoc.value;\n if (!doc) return;\n\n // Route animator lifecycle events to Vue component events, INLINE under the same\n // names every surface uses (review §9). `stop` fires alongside any event that halts\n // playback — that is the PLAYER's rule, so `onStop` is passed through, not re-derived.\n apiRef.value = createAnimator({\n doc,\n adapter: createVueAdapter(elementRefs, makeDiag()),\n onPlay: () => emit('play'),\n onPause: () => emit('pause'),\n onCancel: () => emit('cancel'),\n onFinish: () => emit('finish'),\n onRemove: () => emit('remove'),\n onStop: () => emit('stop'),\n // The player's own diagnostics reach the same handlers as the component's.\n onWarn: props.onWarn,\n onError: props.onError,\n silent: props.silent,\n });\n\n // (Re)apply the declarative control state to the fresh animator —\n // covers both the initial mount (e.g. `:play=\"true\"` from the\n // start) and doc swaps.\n syncPlayState();\n applySeek();\n }\n\n function destroyApi() {\n apiRef.value?.destroy();\n apiRef.value = null;\n }\n\n /** Declarative play/pause → animator calls. */\n function syncPlayState() {\n if (compMode.value !== PxControlMode.play) return;\n if (props.play && !props.pause) {\n apiRef.value?.play();\n } else if (props.pause) {\n apiRef.value?.pause();\n } else if (props.play === false) {\n // explicit play=false → hold where it is (review §8). This used to `finish()`;\n // a boolean whose `false` means \"jump to the end\" is not what anyone guesses.\n apiRef.value?.pause();\n } else {\n // pause-only usage: pause switched off → resume\n apiRef.value?.play();\n }\n }\n\n /** Controlled-time mode: seek through the animator API (no recreate). */\n function applySeek() {\n if (compMode.value !== PxControlMode.fixedTime) return;\n const doc = resolvedDoc.value;\n if (!doc) return;\n const seekMs = calcSeekMs(doc, props);\n if (seekMs !== undefined) {\n apiRef.value?.setCurrentTime(seekMs);\n apiRef.value?.pause();\n }\n }\n\n // Create the animator once DOM refs are available.\n onMounted(() => createApi());\n\n // Recreate the animator when the resolved doc changes.\n // `flush: 'post'` — the animator must be created AFTER the DOM is\n // patched, otherwise the new root element isn't in the document yet\n // and trigger setup fails (autoplay would never resume after a doc swap).\n watch(resolvedDoc, () => createApi(), { flush: 'post' });\n\n // Sync declarative play/pause props with the animator.\n watch([compMode, () => props.play, () => props.pause], () => syncPlayState());\n\n // Scrubbing progress/time only seeks — the animator is NOT recreated.\n watch([compMode, () => props.progress, () => props.time], () => applySeek());\n\n onUnmounted(() => {\n destroyApi();\n });\n\n // -- Expose imperative API ----------------------------------------------\n\n const publicApi: VueAnimatorApi = {\n isPlaying: () => apiRef.value?.isPlaying() || false,\n play: () => apiRef.value?.play(),\n pause: () => apiRef.value?.pause(),\n cancel: () => apiRef.value?.cancel(),\n finish: () => apiRef.value?.finish(),\n setPlaybackRate: (rate: number) => apiRef.value?.setPlaybackRate(rate),\n getCurrentTime: () => apiRef.value?.getCurrentTime() ?? null,\n setCurrentTime: (time: number) => apiRef.value?.setCurrentTime(time),\n getCurrentProgress: () => apiRef.value?.getCurrentProgress() ?? null,\n setCurrentProgress: (progress: number) => apiRef.value?.setCurrentProgress(progress),\n };\n\n expose(publicApi);\n\n // -- Render -------------------------------------------------------------\n\n return () => {\n const doc = resolvedDoc.value;\n return doc ? renderNode(doc) : null;\n };\n },\n});\n\nexport default PixodeskSvgAnimator;\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { PX_TRIGGER_DEFAULTS, PxOutAction, PxStartOn } from \"@pixodesk/svg-animator-web\";\nimport { computed, defineComponent, h, onMounted, onUnmounted, ref, useAttrs, type PropType } from 'vue';\n\n\ntype AnimState = 'idle' | 'paused' | 'playing';\n\n\n/**\n * Controls playback of a SVG+CSS animated file by toggling class names on a wrapper div.\n *\n * Intended for use with SVG files exported from the Pixodesk editor using the\n * **CSS Keyframes** flavor (no `<script>` tag). Import the SVG as a Vue component\n * via `vite-svg-loader` and pass it as the default slot:\n *\n * ```vue\n * <script setup>\n * import AnimationSvg from './animation.svg'; // vite-svg-loader\n * </script>\n *\n * <template>\n * <PixodeskSvgCssAnimator startOn=\"mouseOver\" outAction=\"pause\">\n * <AnimationSvg />\n * </PixodeskSvgCssAnimator>\n * </template>\n * ```\n *\n * The wrapper div carries one of three animation states via CSS class names:\n * - *(no class)* — idle, animation not started\n * - `px-anim-enabled` — started but paused\n * - `px-anim-enabled px-anim-playing` — actively playing\n *\n * @prop startOn - What triggers the animation to start:\n * - `'load'` — plays immediately on mount (default)\n * - `'mouseOver'` — plays on hover\n * - `'click'` — plays on click, toggles on second click\n * - `'scrollIntoView'` — plays when the element enters the viewport\n * - `'programmatic'` — **not supported here.** This wrapper exposes no `play()`, so there is\n * nothing to wait for and the animation never starts. The prop keeps the\n * shared `PxStartOn` type (one enum per wire key), so the value type-checks\n * — it simply has no effect. Use `PixodeskSvgAnimator` (the JSON player)\n * when you need to start an animation from code.\n * @prop outAction - What happens when the trigger ends (hover/scroll out, second click):\n * - `'continue'` — keeps playing (default)\n * - `'pause'` — pauses at the current frame\n * - `'reset'` — resets to the beginning\n * - `'reverse'` — **acts as `'continue'` here.** A CSS class toggle cannot run keyframes\n * backwards. Accepted so the prop keeps the shared `PxOutAction` type.\n * @prop scrollIntoViewThreshold - For `'scrollIntoView'`: how much of the element must be\n * visible (0–1) before it starts, and below which the out action applies. Defaults to the\n * wire default (`0`, any pixel) — the same as the JSON player, not a private `0.1`.\n */\nconst PixodeskSvgCssAnimator = defineComponent({\n name: 'PixodeskSvgCssAnimator',\n\n inheritAttrs: false,\n\n props: {\n startOn: { type: String as PropType<PxStartOn>, default: 'load' },\n outAction: { type: String as PropType<PxOutAction>, default: 'continue' },\n scrollIntoViewThreshold: { type: Number, default: PX_TRIGGER_DEFAULTS.scrollIntoViewThreshold },\n },\n\n setup(props, { slots }) {\n const attrs = useAttrs();\n const state = ref<AnimState>(props.startOn === 'load' ? 'playing' : 'idle');\n const divRef = ref<HTMLDivElement | null>(null);\n\n const goOut = () => {\n state.value =\n props.outAction === 'reset' ? 'idle' :\n props.outAction === 'pause' ? 'paused' : 'playing';\n };\n\n let observerCleanup: (() => void) | undefined;\n\n onMounted(() => {\n if (props.startOn !== 'scrollIntoView') return;\n const el = divRef.value;\n if (!el) return;\n const outState: AnimState =\n props.outAction === 'reset' ? 'idle' :\n props.outAction === 'pause' ? 'paused' : 'playing';\n // `isIntersecting` is true at ONE visible pixel, so with a threshold above 0 it could\n // never report \"out\" — read the ratio against the threshold instead (review §13).\n const threshold = props.scrollIntoViewThreshold;\n const visible = (entry: IntersectionObserverEntry): boolean =>\n threshold > 0 ? entry.intersectionRatio >= threshold : entry.isIntersecting;\n const observer = new IntersectionObserver(\n ([entry]) => { state.value = visible(entry) ? 'playing' : outState; },\n { threshold }\n );\n observer.observe(el);\n observerCleanup = () => observer.disconnect();\n });\n\n onUnmounted(() => observerCleanup?.());\n\n const animClass = computed(() =>\n state.value === 'playing' ? 'px-anim-enabled px-anim-playing' :\n state.value === 'paused' ? 'px-anim-enabled' : ''\n );\n\n const handlers = computed(() =>\n props.startOn === 'mouseOver' ? {\n onMouseenter: () => { state.value = 'playing'; },\n onMouseleave: goOut,\n } :\n props.startOn === 'click' ? {\n onClick: () => state.value === 'playing' ? goOut() : (state.value = 'playing'),\n } : {}\n );\n\n return () => h('div', {\n ref: divRef,\n ...attrs,\n class: [attrs.class, animClass.value],\n ...handlers.value,\n }, slots.default?.());\n },\n});\n\nexport default PixodeskSvgCssAnimator;\n"],"mappings":"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","names":["computed","defineComponent","h","onMounted","onUnmounted","ref"]}
|