flowsvg 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +54 -1
  2. package/PATTERNS.md +71 -4
  3. package/README.md +148 -438
  4. package/README.zh-CN.md +148 -438
  5. package/dist/adapters/react.d.ts +55 -0
  6. package/dist/adapters/react.js +138 -0
  7. package/dist/adapters/vue.d.ts +158 -0
  8. package/dist/adapters/vue.js +177 -0
  9. package/dist/adapters/web-component.d.ts +111 -0
  10. package/dist/adapters/web-component.js +425 -0
  11. package/dist/{player-controls.d.ts → browser/controls.d.ts} +7 -3
  12. package/dist/{player-controls.js → browser/controls.js} +23 -31
  13. package/dist/browser/export-controls.d.ts +32 -0
  14. package/dist/browser/export-controls.js +151 -0
  15. package/dist/browser/player.d.ts +77 -0
  16. package/dist/browser/player.js +412 -0
  17. package/dist/browser/select-control.d.ts +33 -0
  18. package/dist/browser/select-control.js +171 -0
  19. package/dist/browser.d.ts +5 -69
  20. package/dist/browser.js +3 -353
  21. package/dist/core/color.d.ts +3 -0
  22. package/dist/core/color.js +18 -0
  23. package/dist/core/diagram.d.ts +40 -0
  24. package/dist/core/diagram.js +481 -0
  25. package/dist/core/edge-geometry.d.ts +45 -0
  26. package/dist/core/edge-geometry.js +609 -0
  27. package/dist/{core.d.ts → core/model.d.ts} +37 -61
  28. package/dist/core/model.js +1 -0
  29. package/dist/core/node-content-measurement.d.ts +34 -0
  30. package/dist/core/node-content-measurement.js +66 -0
  31. package/dist/{node-content-paint.d.ts → core/node-content-paint.d.ts} +2 -2
  32. package/dist/{node-content-paint.js → core/node-content-paint.js} +11 -20
  33. package/dist/{node-content.d.ts → core/node-content.d.ts} +18 -1
  34. package/dist/{node-content.js → core/node-content.js} +30 -6
  35. package/dist/core/svg-number.d.ts +5 -0
  36. package/dist/core/svg-number.js +7 -0
  37. package/dist/{theme.d.ts → core/theme.d.ts} +6 -0
  38. package/dist/core/theme.js +17 -0
  39. package/dist/{timeline.d.ts → core/timeline.d.ts} +11 -5
  40. package/dist/{timeline.js → core/timeline.js} +30 -22
  41. package/dist/core/validation.d.ts +11 -0
  42. package/dist/core/validation.js +399 -0
  43. package/dist/export/browser.d.ts +9 -0
  44. package/dist/export/browser.js +172 -0
  45. package/dist/export/captions.d.ts +18 -0
  46. package/dist/export/captions.js +44 -0
  47. package/dist/export/plan.d.ts +25 -0
  48. package/dist/export/plan.js +94 -0
  49. package/dist/export/types.d.ts +64 -0
  50. package/dist/export/types.js +9 -0
  51. package/dist/export/worker-source.d.ts +1 -0
  52. package/dist/export/worker-source.js +1 -0
  53. package/dist/export.d.ts +3 -0
  54. package/dist/export.js +2 -0
  55. package/dist/index.d.ts +7 -1
  56. package/dist/index.js +3 -1
  57. package/dist/node/cli.d.ts +2 -0
  58. package/dist/node/cli.js +72 -0
  59. package/dist/node/gif.d.ts +16 -0
  60. package/dist/node/gif.js +184 -0
  61. package/dist/node/html.d.ts +19 -0
  62. package/dist/node/html.js +61 -0
  63. package/dist/node/output.d.ts +2 -0
  64. package/dist/node/output.js +22 -0
  65. package/dist/node.d.ts +6 -0
  66. package/dist/node.js +3 -0
  67. package/dist/portable/LICENSE.esbuild.txt +21 -0
  68. package/dist/portable/LICENSE.flowsvg.txt +21 -0
  69. package/dist/portable/LICENSE.gifenc.txt +21 -0
  70. package/dist/portable/export.js +5 -0
  71. package/dist/portable/manifest.json +36 -0
  72. package/dist/portable/player.js +2 -0
  73. package/dist/portable/styles.css +227 -0
  74. package/dist/react.d.ts +3 -48
  75. package/dist/react.js +2 -126
  76. package/dist/styles.css +194 -14
  77. package/dist/vue.d.ts +3 -132
  78. package/dist/vue.js +2 -161
  79. package/dist/web-component.d.ts +3 -103
  80. package/dist/web-component.js +2 -396
  81. package/docs/images/entity-graph.png +0 -0
  82. package/docs/images/facts-timeline.png +0 -0
  83. package/docs/images/facts-timeline.zh-CN.png +0 -0
  84. package/docs/images/flowsvg-wordmark.gif +0 -0
  85. package/docs/images/node-content.png +0 -0
  86. package/docs/images/numbered-list.png +0 -0
  87. package/docs/images/numbered-list.zh-CN.png +0 -0
  88. package/docs/images/quick-start.png +0 -0
  89. package/docs/images/record-tags.png +0 -0
  90. package/package.json +38 -6
  91. package/dist/core.js +0 -1149
  92. package/dist/theme.js +0 -6
  93. /package/dist/{player-accessibility.d.ts → browser/accessibility.d.ts} +0 -0
  94. /package/dist/{player-accessibility.js → browser/accessibility.js} +0 -0
  95. /package/dist/{player-lifecycle.d.ts → browser/lifecycle.d.ts} +0 -0
  96. /package/dist/{player-lifecycle.js → browser/lifecycle.js} +0 -0
  97. /package/dist/{svg-dom.d.ts → browser/svg-dom.d.ts} +0 -0
  98. /package/dist/{svg-dom.js → browser/svg-dom.js} +0 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * React adapter for component lifecycle, props, events and imperative controls.
3
+ * Playback/rendering reuse browser; imports do not create DOM, and the player is created only on mount.
4
+ */
5
+ import { type CSSProperties } from "react";
6
+ import { type PlayerErrorDetail, type PlayerState } from "../browser/player.js";
7
+ import type { DiagramDocument, ThemeName } from "../core/model.js";
8
+ /** React document, visual options and callbacks; document reference changes trigger a reload. */
9
+ export interface FlowPlayerProps {
10
+ document: DiagramDocument;
11
+ /** Positive playback multiplier; omit to use the player default. */
12
+ speed?: number;
13
+ /** Defaults to false. Changes update live loop mode without rebuilding the player. */
14
+ loop?: boolean;
15
+ theme?: ThemeName;
16
+ controls?: boolean;
17
+ /** Defaults to false. Enable GIF downloads when controls are visible; export is independent of playback. */
18
+ gifExport?: boolean;
19
+ /** Defaults to false: hover marks borders only. True also highlights backgrounds, including groups. */
20
+ hoverFill?: boolean;
21
+ reducedMotion?: boolean;
22
+ /** Initial position in milliseconds, applied once; use seek afterward, and document reloads reset to zero. */
23
+ initialTimeMs?: number;
24
+ className?: string;
25
+ style?: CSSProperties;
26
+ onStepChange?: (state: PlayerState) => void;
27
+ onTimeUpdate?: (state: PlayerState) => void;
28
+ onPlayStateChange?: (state: PlayerState) => void;
29
+ onLoopChange?: (state: PlayerState) => void;
30
+ onError?: (detail: PlayerErrorDetail) => void;
31
+ }
32
+ /** Imperative controls for a mounted component; calls before mounting or after unmounting throw a not-mounted error. */
33
+ export interface FlowPlayerHandle {
34
+ /** Resume from the current position when playback can advance. */
35
+ play(): void;
36
+ /** Pause while retaining the current frame. */
37
+ pause(): void;
38
+ /** Seek in milliseconds, clamping to the timeline; non-finite values become zero. */
39
+ seek(timeMs: number): void;
40
+ /** Pause and jump to a beat boundary; backward from inside a beat goes to that beat's start. */
41
+ step(direction: 1 | -1): void;
42
+ /** Set a finite positive multiplier; ignore invalid values. */
43
+ setSpeed(speed: number): void;
44
+ /** Toggle whole-timeline playback without changing the current position or play state. */
45
+ setLoop(loop: boolean): void;
46
+ /** Reset/pause at zero on success; emit an error and retain playback state if validation fails. */
47
+ load(document: DiagramDocument): void;
48
+ /** Read the current browser-player snapshot. */
49
+ getState(): PlayerState;
50
+ }
51
+ /**
52
+ * SSR renders only a host container; an effect mounts the browser player and unmount cleanup releases it.
53
+ * Document reference changes call load; visual/control options rebuild the instance, with initial positioning applied once.
54
+ */
55
+ export declare const FlowPlayer: import("react").ForwardRefExoticComponent<FlowPlayerProps & import("react").RefAttributes<FlowPlayerHandle>>;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * React adapter for component lifecycle, props, events and imperative controls.
3
+ * Playback/rendering reuse browser; imports do not create DOM, and the player is created only on mount.
4
+ */
5
+ import { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from "react";
6
+ import { createPlayer } from "../browser/player.js";
7
+ /**
8
+ * SSR renders only a host container; an effect mounts the browser player and unmount cleanup releases it.
9
+ * Document reference changes call load; visual/control options rebuild the instance, with initial positioning applied once.
10
+ */
11
+ export const FlowPlayer = forwardRef(function FlowPlayer(props, ref) {
12
+ const hostRef = useRef(null);
13
+ const playerRef = useRef(null);
14
+ const eventCleanupRef = useRef(null);
15
+ const callbacksRef = useRef({});
16
+ const documentRef = useRef(null);
17
+ const optionsRef = useRef(null);
18
+ const initialTimeRef = useRef(props.initialTimeMs);
19
+ const initialAppliedRef = useRef(false);
20
+ // Listeners read current callbacks through a stable ref so callback identity changes do not rebuild the player.
21
+ callbacksRef.current = {
22
+ onStepChange: props.onStepChange,
23
+ onTimeUpdate: props.onTimeUpdate,
24
+ onPlayStateChange: props.onPlayStateChange,
25
+ onLoopChange: props.onLoopChange,
26
+ onError: props.onError
27
+ };
28
+ useImperativeHandle(ref, () => ({
29
+ play: () => requirePlayer(playerRef).play(),
30
+ pause: () => requirePlayer(playerRef).pause(),
31
+ seek: (timeMs) => requirePlayer(playerRef).seek(timeMs),
32
+ step: (direction) => requirePlayer(playerRef).step(direction),
33
+ setSpeed: (speed) => requirePlayer(playerRef).setSpeed(speed),
34
+ setLoop: (loop) => requirePlayer(playerRef).setLoop(loop),
35
+ load: (document) => requirePlayer(playerRef).load(document),
36
+ getState: () => requirePlayer(playerRef).getState()
37
+ }), []);
38
+ useEffect(() => {
39
+ const host = hostRef.current;
40
+ if (!host)
41
+ return;
42
+ const nextOptions = {
43
+ theme: props.theme,
44
+ controls: props.controls,
45
+ gifExport: props.gifExport,
46
+ hoverFill: props.hoverFill,
47
+ reducedMotion: props.reducedMotion
48
+ };
49
+ const existing = playerRef.current;
50
+ const optionsChanged = !sameOptions(optionsRef.current, nextOptions);
51
+ if (existing && !optionsChanged && documentRef.current !== props.document) {
52
+ existing.load(props.document);
53
+ documentRef.current = props.document;
54
+ }
55
+ else if (!existing || optionsChanged) {
56
+ // Detach old callbacks before replacement so old lifecycle events cannot reach the new component state.
57
+ eventCleanupRef.current?.();
58
+ existing?.destroy();
59
+ const player = createPlayer(host, props.document, { ...nextOptions, loop: props.loop ?? existing?.getState().loop });
60
+ // A new instance needs current props even when the separate speed effect has no changed dependency.
61
+ if (props.speed !== undefined)
62
+ player.setSpeed(props.speed);
63
+ eventCleanupRef.current = attachPlayerEvents(player, callbacksRef);
64
+ playerRef.current = player;
65
+ documentRef.current = props.document;
66
+ optionsRef.current = nextOptions;
67
+ if (!initialAppliedRef.current && initialTimeRef.current !== undefined) {
68
+ player.seek(initialTimeRef.current);
69
+ }
70
+ initialAppliedRef.current = true;
71
+ return;
72
+ }
73
+ optionsRef.current = nextOptions;
74
+ }, [props.document, props.theme, props.controls, props.gifExport, props.reducedMotion, props.hoverFill]);
75
+ useEffect(() => {
76
+ const player = playerRef.current;
77
+ if (player && props.speed !== undefined)
78
+ player.setSpeed(props.speed);
79
+ }, [props.speed]);
80
+ useEffect(() => {
81
+ playerRef.current?.setLoop(props.loop ?? false);
82
+ }, [props.loop]);
83
+ useEffect(() => () => {
84
+ eventCleanupRef.current?.();
85
+ eventCleanupRef.current = null;
86
+ playerRef.current?.destroy();
87
+ playerRef.current = null;
88
+ documentRef.current = null;
89
+ optionsRef.current = null;
90
+ initialAppliedRef.current = false;
91
+ }, []);
92
+ const className = props.className ? `flowsvg ${props.className}` : "flowsvg";
93
+ return createElement("div", { ref: hostRef, className, style: props.style });
94
+ });
95
+ function requirePlayer(playerRef) {
96
+ if (!playerRef.current)
97
+ throw new Error("FlowPlayer is not mounted");
98
+ return playerRef.current;
99
+ }
100
+ function sameOptions(previous, next) {
101
+ if (!previous)
102
+ return false;
103
+ return previous.theme === next.theme
104
+ && previous.controls === next.controls
105
+ && previous.gifExport === next.gifExport
106
+ && previous.hoverFill === next.hoverFill
107
+ && previous.reducedMotion === next.reducedMotion;
108
+ }
109
+ /** Translate browser event details to component callbacks; share cleanup between replacement and unmount. */
110
+ function attachPlayerEvents(player, callbacksRef) {
111
+ const onStepChange = (event) => {
112
+ callbacksRef.current.onStepChange?.(event.detail);
113
+ };
114
+ const onTimeUpdate = (event) => {
115
+ callbacksRef.current.onTimeUpdate?.(event.detail);
116
+ };
117
+ const onPlayStateChange = (event) => {
118
+ callbacksRef.current.onPlayStateChange?.(event.detail);
119
+ };
120
+ const onLoopChange = (event) => {
121
+ callbacksRef.current.onLoopChange?.(event.detail);
122
+ };
123
+ const onError = (event) => {
124
+ callbacksRef.current.onError?.(event.detail);
125
+ };
126
+ player.element.addEventListener("stepchange", onStepChange);
127
+ player.element.addEventListener("timeupdate", onTimeUpdate);
128
+ player.element.addEventListener("playstatechange", onPlayStateChange);
129
+ player.element.addEventListener("loopchange", onLoopChange);
130
+ player.element.addEventListener("error", onError);
131
+ return () => {
132
+ player.element.removeEventListener("stepchange", onStepChange);
133
+ player.element.removeEventListener("timeupdate", onTimeUpdate);
134
+ player.element.removeEventListener("playstatechange", onPlayStateChange);
135
+ player.element.removeEventListener("loopchange", onLoopChange);
136
+ player.element.removeEventListener("error", onError);
137
+ };
138
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Vue adapter for component lifecycle, props, events and imperative controls.
3
+ * Playback/rendering reuse browser; imports do not create DOM, and the player is created only on mount.
4
+ */
5
+ import { type CSSProperties, type PropType } from "vue";
6
+ import { type PlayerErrorDetail, type PlayerState } from "../browser/player.js";
7
+ import type { DiagramDocument, ThemeName } from "../core/model.js";
8
+ /** Vue document, visual options and callbacks; document reference changes trigger a reload. */
9
+ export interface FlowPlayerProps {
10
+ document: DiagramDocument;
11
+ /** Positive playback multiplier; omit to use the player default. */
12
+ speed?: number;
13
+ /** Defaults to false. Changes update live loop mode without rebuilding the player. */
14
+ loop?: boolean;
15
+ theme?: ThemeName;
16
+ controls?: boolean;
17
+ /** Defaults to false. Enable GIF downloads when controls are visible; export is independent of playback. */
18
+ gifExport?: boolean;
19
+ /** Defaults to false: hover marks borders only. True also highlights backgrounds, including groups. */
20
+ hoverFill?: boolean;
21
+ reducedMotion?: boolean;
22
+ /** Initial position in milliseconds, applied once; use seek afterward, and document reloads reset to zero. */
23
+ initialTimeMs?: number;
24
+ className?: string;
25
+ style?: CSSProperties;
26
+ onStepChange?: (state: PlayerState) => void;
27
+ onTimeUpdate?: (state: PlayerState) => void;
28
+ onPlayStateChange?: (state: PlayerState) => void;
29
+ onLoopChange?: (state: PlayerState) => void;
30
+ onError?: (detail: PlayerErrorDetail) => void;
31
+ }
32
+ /** Imperative controls for a mounted component; calls before mounting or after unmounting throw a not-mounted error. */
33
+ export interface FlowPlayerHandle {
34
+ /** Resume from the current position when playback can advance. */
35
+ play(): void;
36
+ /** Pause while retaining the current frame. */
37
+ pause(): void;
38
+ /** Seek in milliseconds, clamping to the timeline; non-finite values become zero. */
39
+ seek(timeMs: number): void;
40
+ /** Pause and jump to a beat boundary; backward from inside a beat goes to that beat's start. */
41
+ step(direction: 1 | -1): void;
42
+ /** Set a finite positive multiplier; ignore invalid values. */
43
+ setSpeed(speed: number): void;
44
+ /** Toggle whole-timeline playback without changing the current position or play state. */
45
+ setLoop(loop: boolean): void;
46
+ /** Reset/pause at zero on success; emit an error and retain playback state if validation fails. */
47
+ load(document: DiagramDocument): void;
48
+ /** Read the current browser-player snapshot. */
49
+ getState(): PlayerState;
50
+ }
51
+ /**
52
+ * SSR renders only the host; onMounted creates the player and onBeforeUnmount releases it.
53
+ * Document reference changes call load; option changes rebuild, and exposed methods delegate to the browser instance.
54
+ */
55
+ export declare const FlowPlayer: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
56
+ readonly document: {
57
+ readonly type: PropType<DiagramDocument>;
58
+ readonly required: true;
59
+ };
60
+ readonly speed: {
61
+ readonly type: NumberConstructor;
62
+ readonly required: false;
63
+ };
64
+ readonly loop: {
65
+ readonly type: PropType<boolean | undefined>;
66
+ readonly default: undefined;
67
+ };
68
+ readonly theme: {
69
+ readonly type: PropType<ThemeName>;
70
+ readonly required: false;
71
+ };
72
+ readonly gifExport: {
73
+ readonly type: PropType<boolean | undefined>;
74
+ readonly default: undefined;
75
+ };
76
+ readonly controls: {
77
+ readonly type: PropType<boolean | undefined>;
78
+ readonly default: undefined;
79
+ };
80
+ readonly hoverFill: {
81
+ readonly type: PropType<boolean | undefined>;
82
+ readonly default: undefined;
83
+ };
84
+ readonly reducedMotion: {
85
+ readonly type: PropType<boolean | undefined>;
86
+ readonly default: undefined;
87
+ };
88
+ readonly initialTimeMs: {
89
+ readonly type: NumberConstructor;
90
+ readonly required: false;
91
+ };
92
+ readonly className: {
93
+ readonly type: StringConstructor;
94
+ readonly required: false;
95
+ };
96
+ readonly style: {
97
+ readonly type: PropType<CSSProperties>;
98
+ readonly required: false;
99
+ };
100
+ readonly onStepChange: {
101
+ readonly type: PropType<(state: PlayerState) => void>;
102
+ readonly required: false;
103
+ };
104
+ readonly onTimeUpdate: {
105
+ readonly type: PropType<(state: PlayerState) => void>;
106
+ readonly required: false;
107
+ };
108
+ readonly onPlayStateChange: {
109
+ readonly type: PropType<(state: PlayerState) => void>;
110
+ readonly required: false;
111
+ };
112
+ readonly onLoopChange: {
113
+ readonly type: PropType<(state: PlayerState) => void>;
114
+ readonly required: false;
115
+ };
116
+ readonly onError: {
117
+ readonly type: PropType<(detail: PlayerErrorDetail) => void>;
118
+ readonly required: false;
119
+ };
120
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
121
+ [key: string]: any;
122
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
123
+ stepchange: (_state: PlayerState) => true;
124
+ timeupdate: (_state: PlayerState) => true;
125
+ playstatechange: (_state: PlayerState) => true;
126
+ loopchange: (_state: PlayerState) => true;
127
+ error: (_detail: PlayerErrorDetail) => true;
128
+ }, string, import("vue").PublicProps, Readonly<{
129
+ readonly document: DiagramDocument;
130
+ } & {
131
+ readonly style?: CSSProperties | undefined;
132
+ readonly theme?: ThemeName | undefined;
133
+ readonly reducedMotion?: boolean | undefined;
134
+ readonly speed?: number | undefined;
135
+ readonly controls?: boolean | undefined;
136
+ readonly loop?: boolean | undefined;
137
+ readonly gifExport?: boolean | undefined;
138
+ readonly hoverFill?: boolean | undefined;
139
+ readonly initialTimeMs?: number | undefined;
140
+ readonly className?: string | undefined;
141
+ readonly onStepChange?: ((state: PlayerState) => void) | undefined;
142
+ readonly onTimeUpdate?: ((state: PlayerState) => void) | undefined;
143
+ readonly onPlayStateChange?: ((state: PlayerState) => void) | undefined;
144
+ readonly onLoopChange?: ((state: PlayerState) => void) | undefined;
145
+ readonly onError?: ((detail: PlayerErrorDetail) => void) | undefined;
146
+ } & {
147
+ onError?: ((_detail: PlayerErrorDetail) => any) | undefined;
148
+ onTimeupdate?: ((_state: PlayerState) => any) | undefined;
149
+ onPlaystatechange?: ((_state: PlayerState) => any) | undefined;
150
+ onStepchange?: ((_state: PlayerState) => any) | undefined;
151
+ onLoopchange?: ((_state: PlayerState) => any) | undefined;
152
+ }>, {
153
+ readonly reducedMotion: boolean | undefined;
154
+ readonly controls: boolean | undefined;
155
+ readonly loop: boolean | undefined;
156
+ readonly gifExport: boolean | undefined;
157
+ readonly hoverFill: boolean | undefined;
158
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}>;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Vue adapter for component lifecycle, props, events and imperative controls.
3
+ * Playback/rendering reuse browser; imports do not create DOM, and the player is created only on mount.
4
+ */
5
+ import { defineComponent, h, onBeforeUnmount, onMounted, ref, watch } from "vue";
6
+ import { createPlayer } from "../browser/player.js";
7
+ const propsOptions = {
8
+ document: { type: Object, required: true },
9
+ speed: { type: Number, required: false },
10
+ loop: { type: Boolean, default: undefined },
11
+ theme: { type: String, required: false },
12
+ gifExport: { type: Boolean, default: undefined },
13
+ controls: { type: Boolean, default: undefined },
14
+ hoverFill: { type: Boolean, default: undefined },
15
+ reducedMotion: { type: Boolean, default: undefined },
16
+ initialTimeMs: { type: Number, required: false },
17
+ className: { type: String, required: false },
18
+ style: { type: Object, required: false },
19
+ onStepChange: { type: Function, required: false },
20
+ onTimeUpdate: { type: Function, required: false },
21
+ onPlayStateChange: { type: Function, required: false },
22
+ onLoopChange: { type: Function, required: false },
23
+ onError: { type: Function, required: false }
24
+ };
25
+ /**
26
+ * SSR renders only the host; onMounted creates the player and onBeforeUnmount releases it.
27
+ * Document reference changes call load; option changes rebuild, and exposed methods delegate to the browser instance.
28
+ */
29
+ export const FlowPlayer = defineComponent({
30
+ name: "FlowPlayer",
31
+ inheritAttrs: false,
32
+ props: propsOptions,
33
+ emits: {
34
+ stepchange: (_state) => true,
35
+ timeupdate: (_state) => true,
36
+ playstatechange: (_state) => true,
37
+ loopchange: (_state) => true,
38
+ error: (_detail) => true
39
+ },
40
+ setup(props, { attrs, emit, expose }) {
41
+ const hostRef = ref(null);
42
+ let player;
43
+ let cleanupEvents;
44
+ let optionsSnapshot;
45
+ let mounted = false;
46
+ let initialApplied = false;
47
+ const requirePlayer = () => {
48
+ if (!player)
49
+ throw new Error("FlowPlayer is not mounted");
50
+ return player;
51
+ };
52
+ expose({
53
+ play: () => requirePlayer().play(),
54
+ pause: () => requirePlayer().pause(),
55
+ seek: (timeMs) => requirePlayer().seek(timeMs),
56
+ step: (direction) => requirePlayer().step(direction),
57
+ setSpeed: (speed) => requirePlayer().setSpeed(speed),
58
+ setLoop: (loop) => requirePlayer().setLoop(loop),
59
+ load: (document) => requirePlayer().load(document),
60
+ getState: () => requirePlayer().getState()
61
+ });
62
+ const currentOptions = () => ({
63
+ theme: props.theme,
64
+ controls: props.controls,
65
+ gifExport: props.gifExport,
66
+ hoverFill: props.hoverFill,
67
+ reducedMotion: props.reducedMotion
68
+ });
69
+ const attachEvents = (next) => {
70
+ const onStepChange = (event) => {
71
+ const state = event.detail;
72
+ emit("stepchange", state);
73
+ props.onStepChange?.(state);
74
+ };
75
+ const onTimeUpdate = (event) => {
76
+ const state = event.detail;
77
+ emit("timeupdate", state);
78
+ props.onTimeUpdate?.(state);
79
+ };
80
+ const onPlayStateChange = (event) => {
81
+ const state = event.detail;
82
+ emit("playstatechange", state);
83
+ props.onPlayStateChange?.(state);
84
+ };
85
+ const onLoopChange = (event) => {
86
+ const state = event.detail;
87
+ emit("loopchange", state);
88
+ props.onLoopChange?.(state);
89
+ };
90
+ const onError = (event) => {
91
+ const detail = event.detail;
92
+ // Vue's error event already invokes the onError prop; an explicit callback would duplicate delivery.
93
+ emit("error", detail);
94
+ };
95
+ next.element.addEventListener("stepchange", onStepChange);
96
+ next.element.addEventListener("timeupdate", onTimeUpdate);
97
+ next.element.addEventListener("playstatechange", onPlayStateChange);
98
+ next.element.addEventListener("loopchange", onLoopChange);
99
+ next.element.addEventListener("error", onError);
100
+ return () => {
101
+ next.element.removeEventListener("stepchange", onStepChange);
102
+ next.element.removeEventListener("timeupdate", onTimeUpdate);
103
+ next.element.removeEventListener("playstatechange", onPlayStateChange);
104
+ next.element.removeEventListener("loopchange", onLoopChange);
105
+ next.element.removeEventListener("error", onError);
106
+ };
107
+ };
108
+ // Keep initialApplied across option rebuilds so initialTimeMs is not reapplied.
109
+ const mount = (previousLoop) => {
110
+ const host = hostRef.value;
111
+ if (!host || player)
112
+ return;
113
+ const next = createPlayer(host, props.document, { ...currentOptions(), loop: props.loop ?? previousLoop });
114
+ player = next;
115
+ optionsSnapshot = currentOptions();
116
+ cleanupEvents = attachEvents(next);
117
+ if (props.speed !== undefined)
118
+ next.setSpeed(props.speed);
119
+ if (!initialApplied && props.initialTimeMs !== undefined)
120
+ next.seek(props.initialTimeMs);
121
+ initialApplied = true;
122
+ };
123
+ // Detach event forwarding first so destruction does not call an unmounted component.
124
+ const unmount = () => {
125
+ cleanupEvents?.();
126
+ cleanupEvents = undefined;
127
+ player?.destroy();
128
+ player = undefined;
129
+ optionsSnapshot = undefined;
130
+ };
131
+ const rebuild = () => {
132
+ if (!mounted || !player)
133
+ return;
134
+ const previousLoop = player.getState().loop;
135
+ unmount();
136
+ mount(previousLoop);
137
+ };
138
+ onMounted(() => {
139
+ mounted = true;
140
+ mount();
141
+ });
142
+ watch(() => props.document, (next, previous) => {
143
+ if (!mounted || next === previous || !player)
144
+ return;
145
+ player.load(next);
146
+ });
147
+ watch(() => [props.theme, props.controls, props.gifExport, props.reducedMotion, props.hoverFill], () => {
148
+ const next = currentOptions();
149
+ if (!mounted || sameOptions(optionsSnapshot, next))
150
+ return;
151
+ rebuild();
152
+ });
153
+ watch(() => props.speed, (speed) => {
154
+ if (player && speed !== undefined)
155
+ player.setSpeed(speed);
156
+ });
157
+ watch(() => props.loop, (loop) => player?.setLoop(loop ?? false));
158
+ onBeforeUnmount(() => {
159
+ mounted = false;
160
+ unmount();
161
+ });
162
+ return () => h("div", {
163
+ ref: hostRef,
164
+ class: ["flowsvg", props.className, attrs.class],
165
+ style: [props.style, attrs.style]
166
+ });
167
+ }
168
+ });
169
+ function sameOptions(previous, next) {
170
+ if (!previous)
171
+ return false;
172
+ return previous.theme === next.theme
173
+ && previous.controls === next.controls
174
+ && previous.gifExport === next.gifExport
175
+ && previous.hoverFill === next.hoverFill
176
+ && previous.reducedMotion === next.reducedMotion;
177
+ }
@@ -0,0 +1,111 @@
1
+ import type { DiagramDocument, EvaluateOptions, ThemeName } from "../core/model.js";
2
+ import { type PlayerErrorDetail, type PlayerState } from "../browser/player.js";
3
+ /** Default custom-element name registered by {@link defineFlowPlayer}. */
4
+ export declare const FLOW_PLAYER_TAG = "flowsvg-player";
5
+ type ElementBase = typeof HTMLElement;
6
+ declare const HTMLElementBase: ElementBase;
7
+ /** Declarative options; autoplay defaults to false, speed to 1 and controls to true. */
8
+ export interface FlowPlayerElementOptions extends EvaluateOptions {
9
+ autoplay?: boolean;
10
+ /** Defaults to false; loop is a live playback preference, independent of GIF loopCount. */
11
+ loop?: boolean;
12
+ controls?: boolean;
13
+ /** Defaults to false. Enable GIF download controls; ignored when controls is false. */
14
+ gifExport?: boolean;
15
+ /** Defaults to false; true enables background feedback as well as borders on hover. */
16
+ hoverFill?: boolean;
17
+ speed?: number;
18
+ }
19
+ /** Pre-mount errors may lack player state; mounted errors include available state and the Error. */
20
+ export type FlowPlayerElementErrorDetail = Partial<PlayerErrorDetail> & {
21
+ error: Error;
22
+ };
23
+ /**
24
+ * Custom-element adapter for attribute conversion, URL requests and playback/error events.
25
+ * The document property may be assigned before connection; disconnect releases the player/requests but retains the document.
26
+ * Rendering/timeline behavior delegates to createPlayer; registration requires an explicit defineFlowPlayer call.
27
+ */
28
+ export declare class FlowPlayerElement extends HTMLElementBase {
29
+ static readonly observedAttributes: readonly ["document", "src", "autoplay", "controls", "speed", "theme", "reduced-motion", "hover-fill", "gif-export", "loop"];
30
+ private player;
31
+ private playerHost;
32
+ private sourceAbort;
33
+ private requestToken;
34
+ private connected;
35
+ private manuallyDestroyed;
36
+ private currentDocument;
37
+ private controlsOverride;
38
+ private readonly forwardedEvents;
39
+ /** Current object document; assigning undefined clears it, cancels requests and unmounts the player. */
40
+ get document(): DiagramDocument | undefined;
41
+ set document(value: DiagramDocument | undefined);
42
+ /** Document URL resolved against ownerDocument.baseURI; changes cancel obsolete loads. */
43
+ get src(): string | undefined;
44
+ set src(value: string | undefined);
45
+ /** Whether to autoplay; false pauses a mounted player. */
46
+ get autoplay(): boolean;
47
+ set autoplay(value: boolean);
48
+ /** Live loop mode, reflected by the loop attribute; changes retain time and play state. */
49
+ get loop(): boolean;
50
+ set loop(value: boolean);
51
+ /** Controls default on; an explicit false is retained in HTML as controls="false". */
52
+ get controls(): boolean;
53
+ set controls(value: boolean);
54
+ /** Positive speed multiplier; invalid attribute text reads as 1, while invalid property assignments are ignored. */
55
+ get speed(): number;
56
+ set speed(value: number);
57
+ /** Explicit light/dark theme; the player falls back to the document theme or light when unset. */
58
+ get theme(): ThemeName | undefined;
59
+ set theme(value: ThemeName | undefined);
60
+ /** undefined follows the system preference; true/false explicitly override it via reduced-motion. */
61
+ get reducedMotion(): boolean | undefined;
62
+ set reducedMotion(value: boolean | undefined);
63
+ /** Hover paints only borders by default. The hover-fill attribute enables background and content feedback. */
64
+ get hoverFill(): boolean;
65
+ set hoverFill(value: boolean);
66
+ /** Opt-in GIF controls, reflected by the gif-export attribute; defaults to false. */
67
+ get gifExport(): boolean;
68
+ set gifExport(value: boolean);
69
+ connectedCallback(): void;
70
+ disconnectedCallback(): void;
71
+ attributeChangedCallback(name: string, _oldValue: string | null, value: string | null): void;
72
+ /** Forward controls to the mounted player; calls are inert when no player is mounted. */
73
+ play(): void;
74
+ /** Pause the mounted player, retaining its current frame; do nothing before mounting. */
75
+ pause(): void;
76
+ /** Seek in milliseconds using browser-player clamping; do nothing before mounting. */
77
+ seek(timeMs: number): void;
78
+ /** Pause and jump to a beat boundary; backward from inside a beat goes to that beat's start. */
79
+ step(direction: 1 | -1): void;
80
+ /** Set the speed property, retaining it for future mounts; ignore non-finite or non-positive values. */
81
+ setSpeed(value: number): void;
82
+ /** Set loop mode and retain it for future mounts. */
83
+ setLoop(value: boolean): void;
84
+ /** Return undefined before mounting or after destruction. */
85
+ getState(): PlayerState | undefined;
86
+ /** Explicitly release the player and requests; the manual-destruction flag resets on the next connection. */
87
+ destroy(): void;
88
+ private setDocument;
89
+ private loadDocumentAttribute;
90
+ /** AbortController cancels network work; the token also rejects obsolete results already received but not yet applied. */
91
+ private loadSource;
92
+ /** Restore an existing playback state during option rebuilds; autoplay applies only to a fresh mount. */
93
+ private mountPlayer;
94
+ /** Rebuild for option changes and restore time/playback; document replacement instead follows load's reset-to-zero rule. */
95
+ private rebuildForOptions;
96
+ /** Detach forwarding before destruction so teardown cannot forward events from the old instance. */
97
+ private unmountPlayer;
98
+ private forwardPlayerEvents;
99
+ private removeForwardedEvents;
100
+ private playerOptions;
101
+ private applyAutoplay;
102
+ private cancelSourceLoad;
103
+ private dispatchError;
104
+ }
105
+ /**
106
+ * Register explicitly; repeated calls for the default name return the existing constructor, or undefined without customElements.
107
+ * Load the module with a DOM for real registration, since the HTMLElement base is fixed at module evaluation.
108
+ * @throws {DOMException} From the registry for invalid names or reusing the constructor under another name.
109
+ */
110
+ export declare function defineFlowPlayer(tagName?: string): CustomElementConstructor | undefined;
111
+ export {};