mikuru 1.0.28 → 1.0.29
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.29 - 2026-05-15
|
|
4
|
+
|
|
5
|
+
- Fixed `MikuruVideoPlayer` and `MikuruAudioPlayer` unmount handling so media events fired while a modal or conditional branch is being removed no longer update component refs recursively.
|
|
6
|
+
- Handled aborted `play()` promises during media player teardown to avoid uncaught browser `AbortError` / `DOMException` noise when closing a sample video quickly.
|
|
7
|
+
|
|
3
8
|
## 1.0.28 - 2026-05-15
|
|
4
9
|
|
|
5
10
|
- Added package-exported Mikuru video player, audio player, image viewer, modal, carousel, toast, dropdown, tooltip, progress, and code block components with custom controls, template refs, lifecycle cleanup, keyboard-accessible seeking/navigation, volume, mute, playback rate, stop, fullscreen, close/select/dismiss events, progress states, and copy actions.
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
</template>
|
|
50
50
|
|
|
51
51
|
<script>
|
|
52
|
-
import { computed, onMounted, ref } from "mikuru";
|
|
52
|
+
import { computed, onBeforeUnmount, onMounted, ref } from "mikuru";
|
|
53
53
|
|
|
54
54
|
const {
|
|
55
55
|
src,
|
|
@@ -76,16 +76,23 @@ const initials = computed(() => {
|
|
|
76
76
|
const words = title.value.split(" ").filter(Boolean);
|
|
77
77
|
return words.slice(0, 2).map((word) => word[0]).join("").toUpperCase() || "M";
|
|
78
78
|
});
|
|
79
|
+
let isDisposed = false;
|
|
79
80
|
|
|
80
81
|
onMounted(() => {
|
|
81
82
|
applyAudioSettings();
|
|
82
83
|
});
|
|
83
84
|
|
|
85
|
+
onBeforeUnmount(() => {
|
|
86
|
+
isDisposed = true;
|
|
87
|
+
});
|
|
88
|
+
|
|
84
89
|
function getMedia() {
|
|
90
|
+
if (isDisposed) return null;
|
|
85
91
|
return mediaEl.value;
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
function syncMedia() {
|
|
95
|
+
if (isDisposed) return;
|
|
89
96
|
const media = getMedia();
|
|
90
97
|
if (!media) return;
|
|
91
98
|
currentTime.value = media.currentTime || 0;
|
|
@@ -95,6 +102,7 @@ function syncMedia() {
|
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
function applyAudioSettings() {
|
|
105
|
+
if (isDisposed) return;
|
|
98
106
|
const media = getMedia();
|
|
99
107
|
if (!media) return;
|
|
100
108
|
media.volume = volume.value;
|
|
@@ -102,10 +110,12 @@ function applyAudioSettings() {
|
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
function markPlaying() {
|
|
113
|
+
if (isDisposed) return;
|
|
105
114
|
isPlaying.value = true;
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
function markPaused() {
|
|
118
|
+
if (isDisposed) return;
|
|
109
119
|
isPlaying.value = false;
|
|
110
120
|
}
|
|
111
121
|
|
|
@@ -113,13 +123,20 @@ async function togglePlayback() {
|
|
|
113
123
|
const media = getMedia();
|
|
114
124
|
if (!media) return;
|
|
115
125
|
if (media.paused) {
|
|
116
|
-
|
|
126
|
+
try {
|
|
127
|
+
await media.play();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (!isDisposed && error?.name !== "AbortError") {
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
117
133
|
return;
|
|
118
134
|
}
|
|
119
135
|
media.pause();
|
|
120
136
|
}
|
|
121
137
|
|
|
122
138
|
function seek(event) {
|
|
139
|
+
if (isDisposed) return;
|
|
123
140
|
const media = getMedia();
|
|
124
141
|
const nextTime = Number(event.target.value);
|
|
125
142
|
currentTime.value = nextTime;
|
|
@@ -129,6 +146,7 @@ function seek(event) {
|
|
|
129
146
|
}
|
|
130
147
|
|
|
131
148
|
function setVolume(event) {
|
|
149
|
+
if (isDisposed) return;
|
|
132
150
|
const nextVolume = Number(event.target.value);
|
|
133
151
|
volume.value = nextVolume;
|
|
134
152
|
muted.value = nextVolume === 0;
|
|
@@ -136,6 +154,7 @@ function setVolume(event) {
|
|
|
136
154
|
}
|
|
137
155
|
|
|
138
156
|
function toggleMute() {
|
|
157
|
+
if (isDisposed) return;
|
|
139
158
|
muted.value = !muted.value;
|
|
140
159
|
applyAudioSettings();
|
|
141
160
|
}
|
|
@@ -149,6 +168,7 @@ function skipForward() {
|
|
|
149
168
|
}
|
|
150
169
|
|
|
151
170
|
function skipBy(offset) {
|
|
171
|
+
if (isDisposed) return;
|
|
152
172
|
const media = getMedia();
|
|
153
173
|
if (!media) return;
|
|
154
174
|
const nextTime = Math.min(Math.max(media.currentTime + offset, 0), safeDuration.value || media.currentTime + offset);
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
</template>
|
|
93
93
|
|
|
94
94
|
<script>
|
|
95
|
-
import { computed, onMounted, onUnmounted, ref } from "mikuru";
|
|
95
|
+
import { computed, onBeforeUnmount, onMounted, onUnmounted, ref } from "mikuru";
|
|
96
96
|
|
|
97
97
|
const {
|
|
98
98
|
src,
|
|
@@ -130,21 +130,30 @@ const seekStyle = computed(() => "--progress: " + seekProgress.value + "%");
|
|
|
130
130
|
const playLabel = computed(() => isPlaying.value ? "Pause video" : "Play video");
|
|
131
131
|
const muteLabel = computed(() => muted.value ? "Unmute video" : "Mute video");
|
|
132
132
|
const rates = [1, 1.25, 1.5, 2, 0.75];
|
|
133
|
+
let isDisposed = false;
|
|
133
134
|
|
|
134
135
|
onMounted(() => {
|
|
135
136
|
applyAudioSettings();
|
|
136
137
|
document.addEventListener("fullscreenchange", updateFullscreen);
|
|
137
138
|
});
|
|
138
139
|
|
|
140
|
+
onBeforeUnmount(() => {
|
|
141
|
+
isDisposed = true;
|
|
142
|
+
isSeeking.value = false;
|
|
143
|
+
pointerInside.value = false;
|
|
144
|
+
});
|
|
145
|
+
|
|
139
146
|
onUnmounted(() => {
|
|
140
147
|
document.removeEventListener("fullscreenchange", updateFullscreen);
|
|
141
148
|
});
|
|
142
149
|
|
|
143
150
|
function getMedia() {
|
|
151
|
+
if (isDisposed) return null;
|
|
144
152
|
return mediaEl.value;
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
function syncMedia() {
|
|
156
|
+
if (isDisposed) return;
|
|
148
157
|
const media = getMedia();
|
|
149
158
|
if (!media) return;
|
|
150
159
|
currentTime.value = media.currentTime || 0;
|
|
@@ -155,6 +164,7 @@ function syncMedia() {
|
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
function applyAudioSettings() {
|
|
167
|
+
if (isDisposed) return;
|
|
158
168
|
const media = getMedia();
|
|
159
169
|
if (!media) return;
|
|
160
170
|
media.volume = volume.value;
|
|
@@ -163,16 +173,19 @@ function applyAudioSettings() {
|
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
function markPlaying() {
|
|
176
|
+
if (isDisposed) return;
|
|
166
177
|
isPlaying.value = true;
|
|
167
178
|
controlsVisible.value = pointerInside.value;
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
function markPaused() {
|
|
182
|
+
if (isDisposed) return;
|
|
171
183
|
isPlaying.value = false;
|
|
172
184
|
controlsVisible.value = true;
|
|
173
185
|
}
|
|
174
186
|
|
|
175
187
|
function updateFullscreen() {
|
|
188
|
+
if (isDisposed) return;
|
|
176
189
|
isFullscreen.value = document.fullscreenElement === screenEl.value || document.fullscreenElement === mediaEl.value;
|
|
177
190
|
}
|
|
178
191
|
|
|
@@ -180,7 +193,13 @@ async function togglePlayback() {
|
|
|
180
193
|
const media = getMedia();
|
|
181
194
|
if (!media) return;
|
|
182
195
|
if (media.paused) {
|
|
183
|
-
|
|
196
|
+
try {
|
|
197
|
+
await media.play();
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (!isDisposed && error?.name !== "AbortError") {
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
184
203
|
return;
|
|
185
204
|
}
|
|
186
205
|
media.pause();
|
|
@@ -194,6 +213,7 @@ function getPointerTime(event) {
|
|
|
194
213
|
}
|
|
195
214
|
|
|
196
215
|
function seekTo(nextTime) {
|
|
216
|
+
if (isDisposed) return;
|
|
197
217
|
const media = getMedia();
|
|
198
218
|
currentTime.value = nextTime;
|
|
199
219
|
if (media) {
|
|
@@ -206,6 +226,7 @@ function seekFromPointer(event) {
|
|
|
206
226
|
}
|
|
207
227
|
|
|
208
228
|
function startSeek(event) {
|
|
229
|
+
if (isDisposed) return;
|
|
209
230
|
isSeeking.value = true;
|
|
210
231
|
controlsVisible.value = true;
|
|
211
232
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
@@ -213,11 +234,13 @@ function startSeek(event) {
|
|
|
213
234
|
}
|
|
214
235
|
|
|
215
236
|
function dragSeek(event) {
|
|
237
|
+
if (isDisposed) return;
|
|
216
238
|
if (!isSeeking.value) return;
|
|
217
239
|
seekFromPointer(event);
|
|
218
240
|
}
|
|
219
241
|
|
|
220
242
|
function endSeek(event) {
|
|
243
|
+
if (isDisposed) return;
|
|
221
244
|
if (!isSeeking.value) return;
|
|
222
245
|
isSeeking.value = false;
|
|
223
246
|
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
@@ -225,17 +248,20 @@ function endSeek(event) {
|
|
|
225
248
|
}
|
|
226
249
|
|
|
227
250
|
function showControls() {
|
|
251
|
+
if (isDisposed) return;
|
|
228
252
|
pointerInside.value = true;
|
|
229
253
|
controlsVisible.value = true;
|
|
230
254
|
}
|
|
231
255
|
|
|
232
256
|
function hideControls() {
|
|
257
|
+
if (isDisposed) return;
|
|
233
258
|
pointerInside.value = false;
|
|
234
259
|
if (!isPlaying.value || isSeeking.value) return;
|
|
235
260
|
controlsVisible.value = false;
|
|
236
261
|
}
|
|
237
262
|
|
|
238
263
|
function seekWithKeyboard(event) {
|
|
264
|
+
if (isDisposed) return;
|
|
239
265
|
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight" && event.key !== "Home" && event.key !== "End") {
|
|
240
266
|
return;
|
|
241
267
|
}
|
|
@@ -256,6 +282,7 @@ function seekWithKeyboard(event) {
|
|
|
256
282
|
}
|
|
257
283
|
|
|
258
284
|
function stopPlayback() {
|
|
285
|
+
if (isDisposed) return;
|
|
259
286
|
const media = getMedia();
|
|
260
287
|
if (!media) return;
|
|
261
288
|
media.pause();
|
|
@@ -265,6 +292,7 @@ function stopPlayback() {
|
|
|
265
292
|
}
|
|
266
293
|
|
|
267
294
|
function setVolume(event) {
|
|
295
|
+
if (isDisposed) return;
|
|
268
296
|
const nextVolume = Number(event.target.value);
|
|
269
297
|
volume.value = nextVolume;
|
|
270
298
|
muted.value = nextVolume === 0;
|
|
@@ -272,17 +300,20 @@ function setVolume(event) {
|
|
|
272
300
|
}
|
|
273
301
|
|
|
274
302
|
function toggleMute() {
|
|
303
|
+
if (isDisposed) return;
|
|
275
304
|
muted.value = !muted.value;
|
|
276
305
|
applyAudioSettings();
|
|
277
306
|
}
|
|
278
307
|
|
|
279
308
|
function cycleRate() {
|
|
309
|
+
if (isDisposed) return;
|
|
280
310
|
const currentIndex = rates.indexOf(playbackRate.value);
|
|
281
311
|
playbackRate.value = rates[(currentIndex + 1) % rates.length];
|
|
282
312
|
applyAudioSettings();
|
|
283
313
|
}
|
|
284
314
|
|
|
285
315
|
function toggleFullscreen() {
|
|
316
|
+
if (isDisposed) return;
|
|
286
317
|
if (document.fullscreenElement) {
|
|
287
318
|
document.exitFullscreen?.();
|
|
288
319
|
return;
|