@chenzy-design/core 0.3.1 → 0.4.1

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 (283) hide show
  1. package/dist/ai-chat-dialogue.d.ts +300 -0
  2. package/dist/ai-chat-dialogue.d.ts.map +1 -0
  3. package/dist/ai-chat-dialogue.js +699 -0
  4. package/dist/ai-chat-dialogue.js.map +1 -0
  5. package/dist/ai-chat-dialogue.test.d.ts +2 -0
  6. package/dist/ai-chat-dialogue.test.d.ts.map +1 -0
  7. package/dist/ai-chat-dialogue.test.js +338 -0
  8. package/dist/ai-chat-dialogue.test.js.map +1 -0
  9. package/dist/ai-chat-input.d.ts +191 -0
  10. package/dist/ai-chat-input.d.ts.map +1 -0
  11. package/dist/ai-chat-input.js +274 -0
  12. package/dist/ai-chat-input.js.map +1 -0
  13. package/dist/ai-chat-input.test.d.ts +2 -0
  14. package/dist/ai-chat-input.test.d.ts.map +1 -0
  15. package/dist/ai-chat-input.test.js +339 -0
  16. package/dist/ai-chat-input.test.js.map +1 -0
  17. package/dist/audio-player.d.ts +154 -0
  18. package/dist/audio-player.d.ts.map +1 -0
  19. package/dist/audio-player.js +254 -0
  20. package/dist/audio-player.js.map +1 -0
  21. package/dist/audio-player.test.d.ts +2 -0
  22. package/dist/audio-player.test.d.ts.map +1 -0
  23. package/dist/audio-player.test.js +275 -0
  24. package/dist/audio-player.test.js.map +1 -0
  25. package/dist/back-top.d.ts +4 -1
  26. package/dist/back-top.d.ts.map +1 -1
  27. package/dist/back-top.js +5 -2
  28. package/dist/back-top.js.map +1 -1
  29. package/dist/back-top.test.js +3 -2
  30. package/dist/back-top.test.js.map +1 -1
  31. package/dist/calendar.d.ts +132 -44
  32. package/dist/calendar.d.ts.map +1 -1
  33. package/dist/calendar.js +413 -70
  34. package/dist/calendar.js.map +1 -1
  35. package/dist/calendar.test.js +160 -76
  36. package/dist/calendar.test.js.map +1 -1
  37. package/dist/chat.d.ts +172 -0
  38. package/dist/chat.d.ts.map +1 -0
  39. package/dist/chat.js +210 -0
  40. package/dist/chat.js.map +1 -0
  41. package/dist/chat.test.d.ts +2 -0
  42. package/dist/chat.test.d.ts.map +1 -0
  43. package/dist/chat.test.js +181 -0
  44. package/dist/chat.test.js.map +1 -0
  45. package/dist/code-highlight.d.ts +30 -0
  46. package/dist/code-highlight.d.ts.map +1 -0
  47. package/dist/code-highlight.js +37 -0
  48. package/dist/code-highlight.js.map +1 -0
  49. package/dist/code-highlight.test.d.ts +2 -0
  50. package/dist/code-highlight.test.d.ts.map +1 -0
  51. package/dist/code-highlight.test.js +36 -0
  52. package/dist/code-highlight.test.js.map +1 -0
  53. package/dist/collapsible.d.ts +41 -0
  54. package/dist/collapsible.d.ts.map +1 -0
  55. package/dist/collapsible.js +33 -0
  56. package/dist/collapsible.js.map +1 -0
  57. package/dist/collapsible.test.d.ts +2 -0
  58. package/dist/collapsible.test.d.ts.map +1 -0
  59. package/dist/collapsible.test.js +57 -0
  60. package/dist/collapsible.test.js.map +1 -0
  61. package/dist/color-value.d.ts +60 -0
  62. package/dist/color-value.d.ts.map +1 -0
  63. package/dist/color-value.js +165 -0
  64. package/dist/color-value.js.map +1 -0
  65. package/dist/color-value.test.d.ts +2 -0
  66. package/dist/color-value.test.d.ts.map +1 -0
  67. package/dist/color-value.test.js +104 -0
  68. package/dist/color-value.test.js.map +1 -0
  69. package/dist/config-provider.d.ts +76 -37
  70. package/dist/config-provider.d.ts.map +1 -1
  71. package/dist/config-provider.js +107 -32
  72. package/dist/config-provider.js.map +1 -1
  73. package/dist/config-provider.test.js +123 -60
  74. package/dist/config-provider.test.js.map +1 -1
  75. package/dist/cropper.d.ts +164 -0
  76. package/dist/cropper.d.ts.map +1 -0
  77. package/dist/cropper.js +841 -0
  78. package/dist/cropper.js.map +1 -0
  79. package/dist/cropper.test.d.ts +2 -0
  80. package/dist/cropper.test.d.ts.map +1 -0
  81. package/dist/cropper.test.js +259 -0
  82. package/dist/cropper.test.js.map +1 -0
  83. package/dist/drag-move.d.ts +119 -0
  84. package/dist/drag-move.d.ts.map +1 -0
  85. package/dist/drag-move.js +237 -0
  86. package/dist/drag-move.js.map +1 -0
  87. package/dist/drag-move.test.d.ts +2 -0
  88. package/dist/drag-move.test.d.ts.map +1 -0
  89. package/dist/drag-move.test.js +304 -0
  90. package/dist/drag-move.test.js.map +1 -0
  91. package/dist/editable.d.ts +24 -4
  92. package/dist/editable.d.ts.map +1 -1
  93. package/dist/editable.js +18 -0
  94. package/dist/editable.js.map +1 -1
  95. package/dist/editable.test.js +55 -2
  96. package/dist/editable.test.js.map +1 -1
  97. package/dist/ellipsis.d.ts +13 -0
  98. package/dist/ellipsis.d.ts.map +1 -1
  99. package/dist/ellipsis.js +36 -0
  100. package/dist/ellipsis.js.map +1 -1
  101. package/dist/ellipsis.test.js +37 -1
  102. package/dist/ellipsis.test.js.map +1 -1
  103. package/dist/form-path.d.ts +19 -0
  104. package/dist/form-path.d.ts.map +1 -0
  105. package/dist/form-path.js +97 -0
  106. package/dist/form-path.js.map +1 -0
  107. package/dist/form-path.test.d.ts +2 -0
  108. package/dist/form-path.test.d.ts.map +1 -0
  109. package/dist/form-path.test.js +57 -0
  110. package/dist/form-path.test.js.map +1 -0
  111. package/dist/form-validate.d.ts +13 -0
  112. package/dist/form-validate.d.ts.map +1 -0
  113. package/dist/form-validate.js +204 -0
  114. package/dist/form-validate.js.map +1 -0
  115. package/dist/form.d.ts +91 -18
  116. package/dist/form.d.ts.map +1 -1
  117. package/dist/form.js +170 -115
  118. package/dist/form.js.map +1 -1
  119. package/dist/form.test.js +349 -76
  120. package/dist/form.test.js.map +1 -1
  121. package/dist/highlight.d.ts +42 -32
  122. package/dist/highlight.d.ts.map +1 -1
  123. package/dist/highlight.js +67 -41
  124. package/dist/highlight.js.map +1 -1
  125. package/dist/highlight.test.js +73 -32
  126. package/dist/highlight.test.js.map +1 -1
  127. package/dist/hotkeys.d.ts +151 -0
  128. package/dist/hotkeys.d.ts.map +1 -0
  129. package/dist/hotkeys.js +188 -0
  130. package/dist/hotkeys.js.map +1 -0
  131. package/dist/hotkeys.test.d.ts +2 -0
  132. package/dist/hotkeys.test.d.ts.map +1 -0
  133. package/dist/hotkeys.test.js +170 -0
  134. package/dist/hotkeys.test.js.map +1 -0
  135. package/dist/index.d.ts +33 -17
  136. package/dist/index.d.ts.map +1 -1
  137. package/dist/index.js +32 -13
  138. package/dist/index.js.map +1 -1
  139. package/dist/input.d.ts +34 -0
  140. package/dist/input.d.ts.map +1 -0
  141. package/dist/input.js +54 -0
  142. package/dist/input.js.map +1 -0
  143. package/dist/input.test.d.ts +2 -0
  144. package/dist/input.test.d.ts.map +1 -0
  145. package/dist/input.test.js +56 -0
  146. package/dist/input.test.js.map +1 -0
  147. package/dist/markdown-render.d.ts +49 -0
  148. package/dist/markdown-render.d.ts.map +1 -0
  149. package/dist/markdown-render.js +61 -0
  150. package/dist/markdown-render.js.map +1 -0
  151. package/dist/markdown-render.test.d.ts +2 -0
  152. package/dist/markdown-render.test.d.ts.map +1 -0
  153. package/dist/markdown-render.test.js +106 -0
  154. package/dist/markdown-render.test.js.map +1 -0
  155. package/dist/notification.d.ts +40 -31
  156. package/dist/notification.d.ts.map +1 -1
  157. package/dist/notification.js +86 -70
  158. package/dist/notification.js.map +1 -1
  159. package/dist/notification.test.js +62 -39
  160. package/dist/notification.test.js.map +1 -1
  161. package/dist/numeral.d.ts +32 -0
  162. package/dist/numeral.d.ts.map +1 -0
  163. package/dist/numeral.js +67 -0
  164. package/dist/numeral.js.map +1 -0
  165. package/dist/numeral.test.d.ts +2 -0
  166. package/dist/numeral.test.d.ts.map +1 -0
  167. package/dist/numeral.test.js +59 -0
  168. package/dist/numeral.test.js.map +1 -0
  169. package/dist/overflow-list.d.ts +9 -40
  170. package/dist/overflow-list.d.ts.map +1 -1
  171. package/dist/overflow-list.js +9 -70
  172. package/dist/overflow-list.js.map +1 -1
  173. package/dist/overflow-list.test.js +1 -122
  174. package/dist/overflow-list.test.js.map +1 -1
  175. package/dist/pagination.d.ts +17 -0
  176. package/dist/pagination.d.ts.map +1 -1
  177. package/dist/pagination.js +48 -0
  178. package/dist/pagination.js.map +1 -1
  179. package/dist/pagination.test.js +40 -1
  180. package/dist/pagination.test.js.map +1 -1
  181. package/dist/pincode.d.ts +79 -0
  182. package/dist/pincode.d.ts.map +1 -0
  183. package/dist/pincode.js +119 -0
  184. package/dist/pincode.js.map +1 -0
  185. package/dist/pincode.test.d.ts +2 -0
  186. package/dist/pincode.test.d.ts.map +1 -0
  187. package/dist/pincode.test.js +151 -0
  188. package/dist/pincode.test.js.map +1 -0
  189. package/dist/progress.d.ts +32 -30
  190. package/dist/progress.d.ts.map +1 -1
  191. package/dist/progress.js +257 -64
  192. package/dist/progress.js.map +1 -1
  193. package/dist/progress.test.js +50 -47
  194. package/dist/progress.test.js.map +1 -1
  195. package/dist/resizable.d.ts +200 -0
  196. package/dist/resizable.d.ts.map +1 -0
  197. package/dist/resizable.js +329 -0
  198. package/dist/resizable.js.map +1 -0
  199. package/dist/resizable.test.d.ts +2 -0
  200. package/dist/resizable.test.d.ts.map +1 -0
  201. package/dist/resizable.test.js +302 -0
  202. package/dist/resizable.test.js.map +1 -0
  203. package/dist/scroll-list.d.ts +69 -62
  204. package/dist/scroll-list.d.ts.map +1 -1
  205. package/dist/scroll-list.js +103 -144
  206. package/dist/scroll-list.js.map +1 -1
  207. package/dist/scroll-list.test.js +100 -110
  208. package/dist/scroll-list.test.js.map +1 -1
  209. package/dist/sidebar.d.ts +46 -0
  210. package/dist/sidebar.d.ts.map +1 -0
  211. package/dist/sidebar.js +65 -0
  212. package/dist/sidebar.js.map +1 -0
  213. package/dist/sidebar.test.d.ts +2 -0
  214. package/dist/sidebar.test.d.ts.map +1 -0
  215. package/dist/sidebar.test.js +88 -0
  216. package/dist/sidebar.test.js.map +1 -0
  217. package/dist/spin.d.ts +5 -5
  218. package/dist/spin.d.ts.map +1 -1
  219. package/dist/spin.js +13 -39
  220. package/dist/spin.js.map +1 -1
  221. package/dist/spin.test.js +2 -26
  222. package/dist/spin.test.js.map +1 -1
  223. package/dist/toast.d.ts +33 -30
  224. package/dist/toast.d.ts.map +1 -1
  225. package/dist/toast.js +77 -72
  226. package/dist/toast.js.map +1 -1
  227. package/dist/toast.test.js +63 -37
  228. package/dist/toast.test.js.map +1 -1
  229. package/dist/tree.d.ts +22 -5
  230. package/dist/tree.d.ts.map +1 -1
  231. package/dist/tree.js +61 -9
  232. package/dist/tree.js.map +1 -1
  233. package/dist/tree.test.js +72 -1
  234. package/dist/tree.test.js.map +1 -1
  235. package/dist/user-guide.d.ts +73 -0
  236. package/dist/user-guide.d.ts.map +1 -0
  237. package/dist/user-guide.js +95 -0
  238. package/dist/user-guide.js.map +1 -0
  239. package/dist/user-guide.test.d.ts +2 -0
  240. package/dist/user-guide.test.d.ts.map +1 -0
  241. package/dist/user-guide.test.js +109 -0
  242. package/dist/user-guide.test.js.map +1 -0
  243. package/dist/video-player.d.ts +196 -0
  244. package/dist/video-player.d.ts.map +1 -0
  245. package/dist/video-player.js +461 -0
  246. package/dist/video-player.js.map +1 -0
  247. package/dist/video-player.test.d.ts +2 -0
  248. package/dist/video-player.test.d.ts.map +1 -0
  249. package/dist/video-player.test.js +435 -0
  250. package/dist/video-player.test.js.map +1 -0
  251. package/package.json +17 -1
  252. package/dist/banner.d.ts +0 -19
  253. package/dist/banner.d.ts.map +0 -1
  254. package/dist/banner.js +0 -20
  255. package/dist/banner.js.map +0 -1
  256. package/dist/banner.test.d.ts +0 -2
  257. package/dist/banner.test.d.ts.map +0 -1
  258. package/dist/banner.test.js +0 -29
  259. package/dist/banner.test.js.map +0 -1
  260. package/dist/breakpoints.d.ts +0 -30
  261. package/dist/breakpoints.d.ts.map +0 -1
  262. package/dist/breakpoints.js +0 -45
  263. package/dist/breakpoints.js.map +0 -1
  264. package/dist/breakpoints.test.d.ts +0 -2
  265. package/dist/breakpoints.test.d.ts.map +0 -1
  266. package/dist/breakpoints.test.js +0 -46
  267. package/dist/breakpoints.test.js.map +0 -1
  268. package/dist/lottie-icon.d.ts +0 -88
  269. package/dist/lottie-icon.d.ts.map +0 -1
  270. package/dist/lottie-icon.js +0 -84
  271. package/dist/lottie-icon.js.map +0 -1
  272. package/dist/lottie-icon.test.d.ts +0 -2
  273. package/dist/lottie-icon.test.d.ts.map +0 -1
  274. package/dist/lottie-icon.test.js +0 -88
  275. package/dist/lottie-icon.test.js.map +0 -1
  276. package/dist/sider.d.ts +0 -26
  277. package/dist/sider.d.ts.map +0 -1
  278. package/dist/sider.js +0 -55
  279. package/dist/sider.js.map +0 -1
  280. package/dist/sider.test.d.ts +0 -2
  281. package/dist/sider.test.d.ts.map +0 -1
  282. package/dist/sider.test.js +0 -29
  283. package/dist/sider.test.js.map +0 -1
@@ -0,0 +1,196 @@
1
+ /**
2
+ * video-player — framework-agnostic state machine for a native <video> player,
3
+ * ported 1:1 from Semi Design's VideoPlayerFoundation + VideoProgressFoundation.
4
+ *
5
+ * ZERO framework deps. The render layer injects an adapter (getVideo /
6
+ * getVideoWrapper + notify callbacks + state setters) and forwards native media
7
+ * events (timeupdate / durationchange / progress / ended / error / canplay /
8
+ * waiting / stalled / leavepictureinpicture) and document keydown /
9
+ * fullscreenchange into the foundation methods. All time/volume clamping,
10
+ * play-rate/quality/route switching, the keyboard map (Space / ← / →) and the
11
+ * progress-bar drag→time math live here so any MVVM layer can reuse them.
12
+ *
13
+ * The foundation NEVER reads the DOM except through the adapter's getVideo /
14
+ * getVideoWrapper accessors (which return the live element or null), matching
15
+ * Semi. No document.* is touched here beyond fullscreen APIs invoked through
16
+ * the video wrapper the adapter hands us — event (un)registration is delegated
17
+ * to the render layer, so this module stays library-agnostic and SSR-safe.
18
+ *
19
+ * See specs Semi videoPlayer.foundation.js / progressFoundation.js.
20
+ */
21
+ /** A localized string bundle the foundation needs for transient notifications. */
22
+ export interface VideoPlayerLocale {
23
+ /** shown while the media is buffering (native `waiting`) */
24
+ loading: string;
25
+ /** shown when playback stalls (native `stalled`) */
26
+ stall: string;
27
+ /** mirror-on notification */
28
+ mirror: string;
29
+ /** mirror-off notification */
30
+ cancelMirror: string;
31
+ /** rate-change notification template, contains `${rate}` */
32
+ rateChange: string;
33
+ /** quality-change notification template, contains `${quality}` */
34
+ qualityChange: string;
35
+ /** route-change notification template, contains `${route}` */
36
+ routeChange: string;
37
+ }
38
+ /** A `{ label, value }` option for the playback-rate menu. */
39
+ export interface PlaybackRateOption {
40
+ label: string;
41
+ value: number;
42
+ }
43
+ /** A `{ label, value }` option for the quality / route menus. */
44
+ export interface LabeledOption {
45
+ label: string;
46
+ value: string;
47
+ }
48
+ /** A chapter marker on the progress track. */
49
+ export interface VideoMarker {
50
+ /** start time in seconds */
51
+ start: number;
52
+ /** chapter title */
53
+ title?: string;
54
+ }
55
+ /**
56
+ * Adapter the render layer implements. Mirrors Semi's VideoPlayerAdapter:
57
+ * two element accessors, a set of `notify*` callbacks (fired outward to the
58
+ * host's `onXxx` props) and a set of `set*` state mutators (fired inward to
59
+ * the render layer's reactive state).
60
+ */
61
+ export interface VideoPlayerAdapter {
62
+ getVideo: () => HTMLVideoElement | null;
63
+ getVideoWrapper: () => HTMLElement | null;
64
+ notifyPlay: () => void;
65
+ notifyPause: () => void;
66
+ notifyQualityChange: (quality: string) => void;
67
+ notifyRateChange: (rate: number) => void;
68
+ notifyRouteChange: (route: string) => void;
69
+ notifyVolumeChange: (volume: number) => void;
70
+ setBufferedValue: (bufferedValue: number) => void;
71
+ setCurrentTime: (currentTime: number) => void;
72
+ setIsError: (isError: boolean) => void;
73
+ setIsMirror: (isMirror: boolean) => void;
74
+ setIsPlaying: (isPlaying: boolean) => void;
75
+ setMuted: (muted: boolean) => void;
76
+ setNotificationContent: (content: string) => void;
77
+ setPlaybackRate: (rate: number) => void;
78
+ setQuality: (quality: string) => void;
79
+ setRoute: (route: string) => void;
80
+ setShowControls: (showControls: boolean) => void;
81
+ setShowNotification: (showNotification: boolean) => void;
82
+ setTotalTime: (totalTime: number) => void;
83
+ setVolume: (volume: number) => void;
84
+ }
85
+ /** Reactive state the foundation reads back through `getState`. */
86
+ export interface VideoPlayerState {
87
+ isPlaying: boolean;
88
+ muted: boolean;
89
+ volume: number;
90
+ currentTime: number;
91
+ isMirror: boolean;
92
+ }
93
+ /** Host props the foundation reads through `getProps`. */
94
+ export interface VideoPlayerProps {
95
+ volume: number;
96
+ muted: boolean;
97
+ seekTime: number;
98
+ controlsList: string[];
99
+ }
100
+ export interface VideoPlayerFoundationOptions {
101
+ adapter: VideoPlayerAdapter;
102
+ getState: () => VideoPlayerState;
103
+ getProps: () => VideoPlayerProps;
104
+ /** how long (ms) auto-hide controls waits after the last mouse move (Semi: 3000) */
105
+ controlsHideDelay?: number;
106
+ /** how long (ms) a transient notification stays up (Semi: 1000) */
107
+ notificationDuration?: number;
108
+ }
109
+ /** Default control items, in Semi's order. */
110
+ export declare const DEFAULT_CONTROLS_LIST: string[];
111
+ /** Semi's default 6-tier playback-rate menu (2.0 → 0.75). */
112
+ export declare const DEFAULT_PLAYBACK_RATE_LIST: PlaybackRateOption[];
113
+ export declare const VIDEO_PLAYER_NUMBERS: {
114
+ readonly DEFAULT_VOLUME: 100;
115
+ readonly DEFAULT_SEEK_TIME: 10;
116
+ readonly DEFAULT_VOLUME_STEP: 10;
117
+ readonly DEFAULT_PLAYBACK_RATE: 1;
118
+ };
119
+ /**
120
+ * Create the VideoPlayer foundation. Returns an object of methods the render
121
+ * layer wires to DOM events + UI controls. Pure w.r.t. framework; the only
122
+ * side effects are through the injected adapter, timers, and fullscreen /
123
+ * PiP browser APIs reached via the elements the adapter returns.
124
+ */
125
+ export declare function createVideoPlayerFoundation(options: VideoPlayerFoundationOptions): {
126
+ init: () => void;
127
+ destroy: () => void;
128
+ clearTimer: () => void;
129
+ shouldShowControlItem: (name: string) => boolean;
130
+ checkFullScreen: () => boolean;
131
+ handleVideoPlay: () => void;
132
+ handleVideoPause: () => void;
133
+ handleCanPlay: () => void;
134
+ handleWaiting: (locale: VideoPlayerLocale) => void;
135
+ handleStalled: (locale: VideoPlayerLocale) => void;
136
+ handleProgress: () => void;
137
+ handleEnded: () => void;
138
+ handleTimeUpdate: () => void;
139
+ handleDurationChange: () => void;
140
+ handleError: () => void;
141
+ handleLeavePictureInPicture: () => void;
142
+ handleMouseMove: () => void;
143
+ handleMouseEnterWrapper: () => void;
144
+ handleMouseLeaveWrapper: () => void;
145
+ handlePlay: () => void;
146
+ handlePause: () => void;
147
+ handlePlayOrPause: () => void;
148
+ handleTimeChange: (value: number) => void;
149
+ handleVolumeChange: (value: number) => void;
150
+ handleVolumeSilent: () => void;
151
+ handleFullscreen: () => void;
152
+ handleFullscreenChange: () => void;
153
+ handleMirror: (locale: VideoPlayerLocale) => void;
154
+ handlePictureInPicture: () => void;
155
+ handleRateChange: (rate: PlaybackRateOption, locale: VideoPlayerLocale) => void;
156
+ handleQualityChange: (quality: LabeledOption, locale: VideoPlayerLocale) => void;
157
+ handleRouteChange: (route: LabeledOption, locale: VideoPlayerLocale) => void;
158
+ handleTemporaryNotification: (content: string) => void;
159
+ handleBodyKeyDown: (e: KeyboardEvent) => void;
160
+ };
161
+ export type VideoPlayerFoundation = ReturnType<typeof createVideoPlayerFoundation>;
162
+ /** Clamp a pointer X to a [0,1] fraction of a slider rect, then to a value. */
163
+ export declare function progressValueFromPointer(clientX: number, rect: {
164
+ left: number;
165
+ width: number;
166
+ }, max: number): {
167
+ percentage: number;
168
+ value: number;
169
+ offset: number;
170
+ };
171
+ /**
172
+ * Width (as CSS length) of the `value` overlay within one marker segment.
173
+ * Returns `'0%'` before the segment, a clamped `calc(100% - 2px)` past it, and
174
+ * a percentage inside it — matching Semi's getValueWidth.
175
+ */
176
+ export declare function segmentValueWidth(marker: {
177
+ start: number;
178
+ end: number;
179
+ }, value: number): string;
180
+ /**
181
+ * Build contiguous `[start, end)` marker segments across the whole duration
182
+ * from Semi-style chapter markers (each with only a `start`). The last segment
183
+ * runs to `totalTime`. When there are no markers a single full-width segment is
184
+ * returned so the progress bar always has exactly one track to render.
185
+ */
186
+ export declare function buildMarkerSegments(markers: VideoMarker[], totalTime: number): Array<{
187
+ start: number;
188
+ end: number;
189
+ title?: string;
190
+ }>;
191
+ /**
192
+ * Format seconds as `MM:SS` / `H:MM:SS`(严格对齐 Semi utils.formatTime):
193
+ * NaN/非法 → '00:00';≥1h → `H:MM:SS`;否则 `MM:SS`(分钟也补零)。
194
+ */
195
+ export declare function formatTimeDisplay(seconds: number): string;
196
+ //# sourceMappingURL=video-player.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-player.d.ts","sourceRoot":"","sources":["../src/video-player.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,461 @@
1
+ /**
2
+ * video-player — framework-agnostic state machine for a native <video> player,
3
+ * ported 1:1 from Semi Design's VideoPlayerFoundation + VideoProgressFoundation.
4
+ *
5
+ * ZERO framework deps. The render layer injects an adapter (getVideo /
6
+ * getVideoWrapper + notify callbacks + state setters) and forwards native media
7
+ * events (timeupdate / durationchange / progress / ended / error / canplay /
8
+ * waiting / stalled / leavepictureinpicture) and document keydown /
9
+ * fullscreenchange into the foundation methods. All time/volume clamping,
10
+ * play-rate/quality/route switching, the keyboard map (Space / ← / →) and the
11
+ * progress-bar drag→time math live here so any MVVM layer can reuse them.
12
+ *
13
+ * The foundation NEVER reads the DOM except through the adapter's getVideo /
14
+ * getVideoWrapper accessors (which return the live element or null), matching
15
+ * Semi. No document.* is touched here beyond fullscreen APIs invoked through
16
+ * the video wrapper the adapter hands us — event (un)registration is delegated
17
+ * to the render layer, so this module stays library-agnostic and SSR-safe.
18
+ *
19
+ * See specs Semi videoPlayer.foundation.js / progressFoundation.js.
20
+ */
21
+ /** Default control items, in Semi's order. */
22
+ export const DEFAULT_CONTROLS_LIST = [
23
+ 'play',
24
+ 'next',
25
+ 'time',
26
+ 'volume',
27
+ 'playbackRate',
28
+ 'quality',
29
+ 'route',
30
+ 'mirror',
31
+ 'fullscreen',
32
+ 'pictureInPicture',
33
+ ];
34
+ /** Semi's default 6-tier playback-rate menu (2.0 → 0.75). */
35
+ export const DEFAULT_PLAYBACK_RATE_LIST = [
36
+ { label: '2.0x', value: 2 },
37
+ { label: '1.5x', value: 1.5 },
38
+ { label: '1.25x', value: 1.25 },
39
+ { label: '1.0x', value: 1 },
40
+ { label: '0.75x', value: 0.75 },
41
+ ];
42
+ export const VIDEO_PLAYER_NUMBERS = {
43
+ DEFAULT_VOLUME: 100,
44
+ DEFAULT_SEEK_TIME: 10,
45
+ DEFAULT_VOLUME_STEP: 10,
46
+ DEFAULT_PLAYBACK_RATE: 1,
47
+ };
48
+ /**
49
+ * Create the VideoPlayer foundation. Returns an object of methods the render
50
+ * layer wires to DOM events + UI controls. Pure w.r.t. framework; the only
51
+ * side effects are through the injected adapter, timers, and fullscreen /
52
+ * PiP browser APIs reached via the elements the adapter returns.
53
+ */
54
+ export function createVideoPlayerFoundation(options) {
55
+ const { adapter, getState, getProps } = options;
56
+ const controlsHideDelay = options.controlsHideDelay ?? 3000;
57
+ const notificationDuration = options.notificationDuration ?? 1000;
58
+ let controlsTimer = null;
59
+ let notificationTimer = null;
60
+ let mouseMoveThrottleTs = 0;
61
+ let scrollPosition = null;
62
+ function clearTimer() {
63
+ if (controlsTimer !== null) {
64
+ clearTimeout(controlsTimer);
65
+ controlsTimer = null;
66
+ }
67
+ }
68
+ function clearNotificationTimer() {
69
+ if (notificationTimer !== null) {
70
+ clearTimeout(notificationTimer);
71
+ notificationTimer = null;
72
+ }
73
+ }
74
+ // --- controls auto-hide (Semi throttles mousemove at 200ms) ---
75
+ function handleMouseMove() {
76
+ const now = Date.now();
77
+ if (now - mouseMoveThrottleTs < 200)
78
+ return;
79
+ mouseMoveThrottleTs = now;
80
+ adapter.setShowControls(true);
81
+ clearTimer();
82
+ controlsTimer = setTimeout(() => {
83
+ adapter.setShowControls(false);
84
+ }, controlsHideDelay);
85
+ }
86
+ function handleMouseEnterWrapper() {
87
+ adapter.setShowControls(true);
88
+ }
89
+ function handleMouseLeaveWrapper() {
90
+ if (getState().isPlaying) {
91
+ adapter.setShowControls(false);
92
+ }
93
+ }
94
+ // --- native media events → state ---
95
+ function handleVideoPlay() {
96
+ adapter.setIsPlaying(true);
97
+ adapter.notifyPlay();
98
+ }
99
+ function handleVideoPause() {
100
+ adapter.setIsPlaying(false);
101
+ adapter.notifyPause();
102
+ }
103
+ function handleCanPlay() {
104
+ adapter.setShowNotification(false);
105
+ }
106
+ function handleWaiting(locale) {
107
+ adapter.setNotificationContent(locale.loading);
108
+ adapter.setShowNotification(true);
109
+ }
110
+ function handleStalled(locale) {
111
+ adapter.setNotificationContent(locale.stall);
112
+ adapter.setShowNotification(true);
113
+ }
114
+ function handleProgress() {
115
+ const video = adapter.getVideo();
116
+ if (video && video.buffered.length > 0) {
117
+ const bufferedEnd = video.buffered.end(video.buffered.length - 1);
118
+ adapter.setBufferedValue(bufferedEnd);
119
+ }
120
+ }
121
+ function handleEnded() {
122
+ adapter.setIsPlaying(false);
123
+ adapter.setShowControls(true);
124
+ }
125
+ function handleTimeUpdate() {
126
+ const video = adapter.getVideo();
127
+ if (!video)
128
+ return;
129
+ adapter.setCurrentTime(video.currentTime);
130
+ }
131
+ function handleDurationChange() {
132
+ const video = adapter.getVideo();
133
+ if (!video)
134
+ return;
135
+ adapter.setTotalTime(video.duration);
136
+ }
137
+ function handleError() {
138
+ adapter.setIsError(true);
139
+ }
140
+ // --- play / pause ---
141
+ function handlePlay() {
142
+ const video = adapter.getVideo();
143
+ if (video) {
144
+ // native onPlay → handleVideoPlay updates isPlaying
145
+ void video.play();
146
+ }
147
+ }
148
+ function handlePause() {
149
+ const video = adapter.getVideo();
150
+ if (video) {
151
+ video.pause();
152
+ }
153
+ }
154
+ function handlePlayOrPause() {
155
+ const video = adapter.getVideo();
156
+ if (!video)
157
+ return;
158
+ if (video.paused)
159
+ handlePlay();
160
+ else
161
+ handlePause();
162
+ }
163
+ // --- time / seek (with NaN guard, matching Semi) ---
164
+ function handleTimeChange(value) {
165
+ const video = adapter.getVideo();
166
+ if (!video)
167
+ return;
168
+ if (!Number.isNaN(value)) {
169
+ video.currentTime = value;
170
+ adapter.setCurrentTime(value);
171
+ }
172
+ }
173
+ // --- volume (0–100 floor + mute derivation, matching Semi) ---
174
+ function handleVolumeChange(value) {
175
+ const video = adapter.getVideo();
176
+ if (!video)
177
+ return;
178
+ const volume = Math.floor(value > 0 ? value : 0);
179
+ video.volume = volume / 100;
180
+ adapter.setVolume(volume);
181
+ adapter.setMuted(volume === 0);
182
+ // NB: Semi's handleVolumeChange does not fire notifyVolumeChange; the
183
+ // onVolumeChange callback is wired by the render layer to the slider's
184
+ // own change event. Kept faithful — no notify here.
185
+ }
186
+ /** Toggle mute: restore previous volume when muted, else drop to 0. */
187
+ function handleVolumeSilent() {
188
+ const video = adapter.getVideo();
189
+ if (!video)
190
+ return;
191
+ const { volume, muted } = getState();
192
+ if (muted) {
193
+ video.volume = volume / 100;
194
+ adapter.setVolume(volume);
195
+ adapter.setMuted(false);
196
+ }
197
+ else {
198
+ video.volume = 0;
199
+ adapter.setMuted(true);
200
+ }
201
+ }
202
+ // --- fullscreen ---
203
+ function checkFullScreen() {
204
+ const videoWrapper = adapter.getVideoWrapper();
205
+ if (!videoWrapper)
206
+ return false;
207
+ const doc = document;
208
+ const el = videoWrapper;
209
+ return !!(doc.fullscreenElement === videoWrapper ||
210
+ doc.webkitFullscreenElement === videoWrapper ||
211
+ doc.mozFullScreenElement === videoWrapper ||
212
+ doc.msFullscreenElement === videoWrapper ||
213
+ el.webkitDisplayingFullscreen // iOS Safari
214
+ );
215
+ }
216
+ function handleFullscreen() {
217
+ const videoWrapper = adapter.getVideoWrapper();
218
+ if (!videoWrapper)
219
+ return;
220
+ if (checkFullScreen()) {
221
+ void document.exitFullscreen();
222
+ }
223
+ else {
224
+ scrollPosition = { x: window.scrollX, y: window.scrollY };
225
+ void videoWrapper.requestFullscreen();
226
+ }
227
+ }
228
+ function handleFullscreenChange() {
229
+ if (checkFullScreen()) {
230
+ document.addEventListener('mousemove', handleMouseMove);
231
+ }
232
+ else {
233
+ // Esc or button exit both land here; restore the pre-fullscreen scroll.
234
+ if (scrollPosition) {
235
+ const pos = scrollPosition;
236
+ setTimeout(() => {
237
+ window.scrollTo(pos.x, pos.y);
238
+ scrollPosition = null;
239
+ }, 0);
240
+ }
241
+ document.removeEventListener('mousemove', handleMouseMove);
242
+ }
243
+ }
244
+ // --- mirror ---
245
+ function handleMirror(locale) {
246
+ const { isMirror } = getState();
247
+ const next = !isMirror;
248
+ adapter.setIsMirror(next);
249
+ handleTemporaryNotification(next ? locale.mirror : locale.cancelMirror);
250
+ }
251
+ // --- picture-in-picture ---
252
+ function handlePictureInPicture() {
253
+ const video = adapter.getVideo();
254
+ if (!video)
255
+ return;
256
+ void video.requestPictureInPicture();
257
+ }
258
+ function handleLeavePictureInPicture() {
259
+ const video = adapter.getVideo();
260
+ if (!video)
261
+ return;
262
+ adapter.setIsPlaying(!video.paused);
263
+ }
264
+ // --- rate / quality / route ---
265
+ function handleRateChange(rate, locale) {
266
+ const video = adapter.getVideo();
267
+ if (!video)
268
+ return;
269
+ video.playbackRate = rate.value;
270
+ adapter.setPlaybackRate(rate.value);
271
+ adapter.notifyRateChange(rate.value);
272
+ handleTemporaryNotification(locale.rateChange.replace('${rate}', rate.label));
273
+ }
274
+ function handleQualityChange(quality, locale) {
275
+ adapter.setQuality(quality.value);
276
+ adapter.notifyQualityChange(quality.value);
277
+ handleTemporaryNotification(locale.qualityChange.replace('${quality}', quality.label));
278
+ restorePlayPosition();
279
+ }
280
+ function handleRouteChange(route, locale) {
281
+ adapter.setRoute(route.value);
282
+ adapter.notifyRouteChange(route.value);
283
+ handleTemporaryNotification(locale.routeChange.replace('${route}', route.label));
284
+ restorePlayPosition();
285
+ }
286
+ /** After a src swap, seek back to the previous position and resume if it was playing. */
287
+ function restorePlayPosition() {
288
+ const video = adapter.getVideo();
289
+ if (!video)
290
+ return;
291
+ const wasPlaying = !video.paused;
292
+ const currentTime = video.currentTime;
293
+ const handleLoaded = () => {
294
+ video.currentTime = currentTime;
295
+ if (wasPlaying)
296
+ void video.play();
297
+ video.removeEventListener('loadeddata', handleLoaded);
298
+ };
299
+ video.addEventListener('loadeddata', handleLoaded);
300
+ }
301
+ // --- transient notification ---
302
+ function handleTemporaryNotification(content) {
303
+ adapter.setNotificationContent(content);
304
+ adapter.setShowNotification(true);
305
+ clearNotificationTimer();
306
+ notificationTimer = setTimeout(() => {
307
+ adapter.setShowNotification(false);
308
+ notificationTimer = null;
309
+ }, notificationDuration);
310
+ }
311
+ // --- keyboard (Space play/pause, ←/→ seek by seekTime) ---
312
+ function handleBodyKeyDown(e) {
313
+ const videoWrapper = adapter.getVideoWrapper();
314
+ // Only respond when focus is inside the player, so we don't hijack keys
315
+ // from other interactive elements on the page (Semi behavior).
316
+ if (videoWrapper && !videoWrapper.contains(document.activeElement)) {
317
+ return;
318
+ }
319
+ const { currentTime } = getState();
320
+ const { seekTime } = getProps();
321
+ if (e.key === ' ') {
322
+ handlePlayOrPause();
323
+ }
324
+ else if (e.key === 'ArrowLeft') {
325
+ handleTimeChange(currentTime - seekTime);
326
+ }
327
+ else if (e.key === 'ArrowRight') {
328
+ handleTimeChange(currentTime + seekTime);
329
+ }
330
+ }
331
+ // --- lifecycle ---
332
+ function init() {
333
+ const { volume, muted } = getProps();
334
+ const video = adapter.getVideo();
335
+ if (video) {
336
+ adapter.setTotalTime(video.duration);
337
+ handleVolumeChange(muted ? 0 : volume);
338
+ }
339
+ }
340
+ function destroy() {
341
+ clearTimer();
342
+ clearNotificationTimer();
343
+ document.removeEventListener('mousemove', handleMouseMove);
344
+ }
345
+ function shouldShowControlItem(name) {
346
+ return getProps().controlsList.includes(name);
347
+ }
348
+ return {
349
+ init,
350
+ destroy,
351
+ clearTimer,
352
+ shouldShowControlItem,
353
+ checkFullScreen,
354
+ // media events
355
+ handleVideoPlay,
356
+ handleVideoPause,
357
+ handleCanPlay,
358
+ handleWaiting,
359
+ handleStalled,
360
+ handleProgress,
361
+ handleEnded,
362
+ handleTimeUpdate,
363
+ handleDurationChange,
364
+ handleError,
365
+ handleLeavePictureInPicture,
366
+ // controls
367
+ handleMouseMove,
368
+ handleMouseEnterWrapper,
369
+ handleMouseLeaveWrapper,
370
+ handlePlay,
371
+ handlePause,
372
+ handlePlayOrPause,
373
+ handleTimeChange,
374
+ handleVolumeChange,
375
+ handleVolumeSilent,
376
+ handleFullscreen,
377
+ handleFullscreenChange,
378
+ handleMirror,
379
+ handlePictureInPicture,
380
+ handleRateChange,
381
+ handleQualityChange,
382
+ handleRouteChange,
383
+ handleTemporaryNotification,
384
+ handleBodyKeyDown,
385
+ };
386
+ }
387
+ // ---------------------------------------------------------------------------
388
+ // Progress-bar drag → time math (ported from VideoProgressFoundation).
389
+ // Pure geometry helpers the render layer calls on pointer events; no DOM
390
+ // beyond the ClientRect the caller passes in.
391
+ // ---------------------------------------------------------------------------
392
+ /** Clamp a pointer X to a [0,1] fraction of a slider rect, then to a value. */
393
+ export function progressValueFromPointer(clientX, rect, max) {
394
+ const offset = clientX - rect.left;
395
+ const total = rect.width;
396
+ const percentage = total > 0 ? Math.min(Math.max(offset / total, 0), 1) : 0;
397
+ const value = percentage * max;
398
+ return { percentage, value, offset: offset - rect.width / 2 };
399
+ }
400
+ /**
401
+ * Width (as CSS length) of the `value` overlay within one marker segment.
402
+ * Returns `'0%'` before the segment, a clamped `calc(100% - 2px)` past it, and
403
+ * a percentage inside it — matching Semi's getValueWidth.
404
+ */
405
+ export function segmentValueWidth(marker, value) {
406
+ const { start, end } = marker;
407
+ if (value > end)
408
+ return 'calc(100% - 2px)';
409
+ if (value < start)
410
+ return '0%';
411
+ if (end === start)
412
+ return '0%';
413
+ return `${((value - start) / (end - start)) * 100}%`;
414
+ }
415
+ /**
416
+ * Build contiguous `[start, end)` marker segments across the whole duration
417
+ * from Semi-style chapter markers (each with only a `start`). The last segment
418
+ * runs to `totalTime`. When there are no markers a single full-width segment is
419
+ * returned so the progress bar always has exactly one track to render.
420
+ */
421
+ export function buildMarkerSegments(markers, totalTime) {
422
+ const total = Number.isFinite(totalTime) && totalTime > 0 ? totalTime : 0;
423
+ if (!markers || markers.length === 0) {
424
+ return [{ start: 0, end: total }];
425
+ }
426
+ const sorted = [...markers]
427
+ .filter((m) => Number.isFinite(m.start) && m.start >= 0)
428
+ .sort((a, b) => a.start - b.start);
429
+ if (sorted.length === 0)
430
+ return [{ start: 0, end: total }];
431
+ return sorted.map((m, i) => {
432
+ const next = sorted[i + 1];
433
+ const end = next ? next.start : total;
434
+ const seg = {
435
+ start: m.start,
436
+ end: Math.max(end, m.start),
437
+ };
438
+ if (m.title !== undefined)
439
+ seg.title = m.title;
440
+ return seg;
441
+ });
442
+ }
443
+ /**
444
+ * Format seconds as `MM:SS` / `H:MM:SS`(严格对齐 Semi utils.formatTime):
445
+ * NaN/非法 → '00:00';≥1h → `H:MM:SS`;否则 `MM:SS`(分钟也补零)。
446
+ */
447
+ export function formatTimeDisplay(seconds) {
448
+ if (!Number.isFinite(seconds) || seconds < 0)
449
+ return '00:00';
450
+ const total = Math.floor(seconds);
451
+ const h = Math.floor(total / 3600);
452
+ const m = Math.floor((total % 3600) / 60);
453
+ const s = total % 60;
454
+ const ss = String(s).padStart(2, '0');
455
+ const mm = String(m).padStart(2, '0');
456
+ if (h > 0) {
457
+ return `${h}:${mm}:${ss}`;
458
+ }
459
+ return `${mm}:${ss}`;
460
+ }
461
+ //# sourceMappingURL=video-player.js.map