mikuru 1.0.33 → 1.0.34
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 +7 -0
- package/components/MikuruAudioPlayer.mikuru +5 -4
- package/components/MikuruCarousel.mikuru +31 -15
- package/components/MikuruCodeBlock.mikuru +9 -5
- package/components/MikuruDropdown.mikuru +15 -4
- package/components/MikuruImageViewer.mikuru +1 -3
- package/components/MikuruProgress.mikuru +1 -3
- package/components/MikuruToast.mikuru +16 -8
- package/components/MikuruVideoPlayer.mikuru +33 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.34 - 2026-05-16
|
|
4
|
+
|
|
5
|
+
- Stabilized package component internals so repeated mounts and parent rerenders no longer recreate equivalent derived arrays, Sets, or style objects unnecessarily.
|
|
6
|
+
- Hardened `MikuruCarousel`, `MikuruDropdown`, `MikuruToast`, `MikuruCodeBlock`, and `MikuruVideoPlayer` against recursive update loops when parents pass freshly-created array props with unchanged contents.
|
|
7
|
+
- Switched package component style bindings in `MikuruImageViewer`, `MikuruProgress`, and `MikuruVideoPlayer` to stable string styles to avoid avoidable reactive object churn.
|
|
8
|
+
- Updated `MikuruAudioPlayer` timeline class derivation to return stable class strings.
|
|
9
|
+
|
|
3
10
|
## 1.0.33 - 2026-05-16
|
|
4
11
|
|
|
5
12
|
- Added `MikuruVideoPlayer` sizing props for width, height, and aspect ratio.
|
|
@@ -111,10 +111,11 @@ const showSkipControl = computed(() => hasControl("skip"));
|
|
|
111
111
|
const showMuteControl = computed(() => hasControl("mute"));
|
|
112
112
|
const showVolumeControl = computed(() => hasControl("volume"));
|
|
113
113
|
const showTimeline = computed(() => showSeekControl.value || showTimeControl.value);
|
|
114
|
-
const timelineClass = computed(() =>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
const timelineClass = computed(() => {
|
|
115
|
+
if (showSeekControl.value && !showTimeControl.value) return "timeline-seek-only";
|
|
116
|
+
if (showTimeControl.value && !showSeekControl.value) return "timeline-time-only";
|
|
117
|
+
return "";
|
|
118
|
+
});
|
|
118
119
|
const showControls = computed(() => (
|
|
119
120
|
showPlayControl.value ||
|
|
120
121
|
showSkipControl.value ||
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<div class="carousel-track" :style="trackStyle">
|
|
5
5
|
<article
|
|
6
6
|
class="carousel-slide"
|
|
7
|
-
m-for="slide in
|
|
7
|
+
m-for="slide in slides"
|
|
8
8
|
:key="slide.id"
|
|
9
9
|
:aria-label="slide.label"
|
|
10
10
|
>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<span>{{ positionLabel }}</span>
|
|
34
34
|
<div class="carousel-dots" role="tablist" aria-label="Carousel slides">
|
|
35
35
|
<button
|
|
36
|
-
m-for="slide in
|
|
36
|
+
m-for="slide in slides"
|
|
37
37
|
:key="slide.id"
|
|
38
38
|
type="button"
|
|
39
39
|
:class="{ active: slide.index === activeIndex }"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
</template>
|
|
47
47
|
|
|
48
48
|
<script>
|
|
49
|
-
import { computed, onMounted, onUnmounted, ref } from "mikuru";
|
|
49
|
+
import { computed, onMounted, onUnmounted, ref, watch } from "mikuru";
|
|
50
50
|
|
|
51
51
|
const {
|
|
52
52
|
images = [],
|
|
@@ -65,11 +65,24 @@ const {
|
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
const activeIndex = ref(0);
|
|
68
|
+
const slides = ref([]);
|
|
69
|
+
let slidesSignature = "";
|
|
68
70
|
let timer = null;
|
|
71
|
+
let mounted = false;
|
|
69
72
|
|
|
70
|
-
const
|
|
73
|
+
const slideCount = computed(() => slides.value.length);
|
|
74
|
+
const isEmpty = computed(() => slideCount.value === 0);
|
|
75
|
+
const trackStyle = computed(() => `transform: translateX(-${activeIndex.value * 100}%)`);
|
|
76
|
+
const positionLabel = computed(() => {
|
|
77
|
+
if (slideCount.value === 0) return "0 / 0";
|
|
78
|
+
return `${activeIndex.value + 1} / ${slideCount.value}`;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
watch(images, syncSlides, { immediate: true });
|
|
82
|
+
|
|
83
|
+
function syncSlides() {
|
|
71
84
|
const source = Array.isArray(images.value) ? images.value : [];
|
|
72
|
-
|
|
85
|
+
const nextSlides = source.map((item, index) => {
|
|
73
86
|
const src = typeof item === "string" ? item : item.src;
|
|
74
87
|
const alt = typeof item === "string" ? "" : item.alt || item.title || `Slide ${index + 1}`;
|
|
75
88
|
const slideTitle = typeof item === "string" ? `Slide ${index + 1}` : item.title || `Slide ${index + 1}`;
|
|
@@ -84,22 +97,25 @@ const normalizedSlides = computed(() => {
|
|
|
84
97
|
label: `${slideTitle}, ${index + 1} of ${source.length}`
|
|
85
98
|
};
|
|
86
99
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
}
|
|
100
|
+
const nextSignature = nextSlides
|
|
101
|
+
.map((slide) => `${slide.id}\u0000${slide.alt}\u0000${slide.title}\u0000${slide.caption}`)
|
|
102
|
+
.join("\u0001");
|
|
103
|
+
if (nextSignature === slidesSignature) return;
|
|
104
|
+
slidesSignature = nextSignature;
|
|
105
|
+
slides.value = nextSlides;
|
|
106
|
+
activeIndex.value = clampIndex(activeIndex.value);
|
|
107
|
+
if (mounted) {
|
|
108
|
+
startAutoplay();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
97
111
|
|
|
98
112
|
onMounted(() => {
|
|
113
|
+
mounted = true;
|
|
99
114
|
startAutoplay();
|
|
100
115
|
});
|
|
101
116
|
|
|
102
117
|
onUnmounted(() => {
|
|
118
|
+
mounted = false;
|
|
103
119
|
stopAutoplay();
|
|
104
120
|
});
|
|
105
121
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</template>
|
|
11
11
|
|
|
12
12
|
<script>
|
|
13
|
-
import { computed, ref } from "mikuru";
|
|
13
|
+
import { computed, ref, watch } from "mikuru";
|
|
14
14
|
|
|
15
15
|
const {
|
|
16
16
|
code = "",
|
|
@@ -23,12 +23,16 @@ const {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
const copied = ref(false);
|
|
26
|
+
const lines = ref([]);
|
|
26
27
|
const languageLabel = computed(() => language.value || "text");
|
|
27
28
|
const copyLabel = computed(() => copied.value ? "Copied" : "Copy");
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
text
|
|
31
|
-
|
|
29
|
+
|
|
30
|
+
watch(code, () => {
|
|
31
|
+
lines.value = code.value.split("\n").map((text, index) => ({
|
|
32
|
+
number: index + 1,
|
|
33
|
+
text
|
|
34
|
+
}));
|
|
35
|
+
}, { immediate: true });
|
|
32
36
|
|
|
33
37
|
async function copyCode() {
|
|
34
38
|
if (!navigator.clipboard) return;
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
</template>
|
|
30
30
|
|
|
31
31
|
<script>
|
|
32
|
-
import {
|
|
32
|
+
import { onMounted, onUnmounted, ref, watch } from "mikuru";
|
|
33
33
|
|
|
34
34
|
const {
|
|
35
35
|
label = "Menu",
|
|
@@ -42,9 +42,14 @@ const {
|
|
|
42
42
|
const emit = defineEmits(["select"]);
|
|
43
43
|
const rootEl = ref(null);
|
|
44
44
|
const isOpen = ref(false);
|
|
45
|
-
const normalizedItems =
|
|
45
|
+
const normalizedItems = ref([]);
|
|
46
|
+
let itemsSignature = "";
|
|
47
|
+
|
|
48
|
+
watch(items, syncItems, { immediate: true });
|
|
49
|
+
|
|
50
|
+
function syncItems() {
|
|
46
51
|
const source = Array.isArray(items.value) ? items.value : [];
|
|
47
|
-
|
|
52
|
+
const nextItems = source.map((item, index) => {
|
|
48
53
|
if (typeof item === "string") {
|
|
49
54
|
return { label: item, value: item, description: "", disabled: false };
|
|
50
55
|
}
|
|
@@ -55,7 +60,13 @@ const normalizedItems = computed(() => {
|
|
|
55
60
|
disabled: Boolean(item.disabled)
|
|
56
61
|
};
|
|
57
62
|
});
|
|
58
|
-
|
|
63
|
+
const nextSignature = nextItems
|
|
64
|
+
.map((item) => `${item.value}\u0000${item.label}\u0000${item.description}\u0000${item.disabled}`)
|
|
65
|
+
.join("\u0001");
|
|
66
|
+
if (nextSignature === itemsSignature) return;
|
|
67
|
+
itemsSignature = nextSignature;
|
|
68
|
+
normalizedItems.value = nextItems;
|
|
69
|
+
}
|
|
59
70
|
|
|
60
71
|
onMounted(() => {
|
|
61
72
|
document.addEventListener("pointerdown", handleDocumentPointer);
|
|
@@ -82,9 +82,7 @@ const label = computed(() => caption.value || alt.value);
|
|
|
82
82
|
const captionText = computed(() => caption.value || alt.value);
|
|
83
83
|
const zoomLabel = computed(() => `${Math.round(zoom.value * 100)}%`);
|
|
84
84
|
const fullscreenLabel = computed(() => isFullscreen.value ? "Exit fullscreen" : "Enter fullscreen");
|
|
85
|
-
const imageStyle = computed(() => ({
|
|
86
|
-
transform: `translate(${offsetX.value}px, ${offsetY.value}px) scale(${zoom.value}) rotate(${rotation.value}deg)`
|
|
87
|
-
}));
|
|
85
|
+
const imageStyle = computed(() => `transform: translate(${offsetX.value}px, ${offsetY.value}px) scale(${zoom.value}) rotate(${rotation.value}deg)`);
|
|
88
86
|
|
|
89
87
|
onMounted(() => {
|
|
90
88
|
document.addEventListener("fullscreenchange", syncFullscreen);
|
|
@@ -36,9 +36,7 @@ const safeMax = computed(() => max.value > 0 ? max.value : 100);
|
|
|
36
36
|
const clampedValue = computed(() => Math.min(Math.max(value.value, 0), safeMax.value));
|
|
37
37
|
const percent = computed(() => Math.round((clampedValue.value / safeMax.value) * 100));
|
|
38
38
|
const percentLabel = computed(() => `${percent.value}%`);
|
|
39
|
-
const fillStyle = computed(() =>
|
|
40
|
-
width: indeterminate.value ? "45%" : `${percent.value}%`
|
|
41
|
-
}));
|
|
39
|
+
const fillStyle = computed(() => `width: ${indeterminate.value ? "45%" : `${percent.value}%`}`);
|
|
42
40
|
</script>
|
|
43
41
|
|
|
44
42
|
<style scoped>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
</template>
|
|
18
18
|
|
|
19
19
|
<script>
|
|
20
|
-
import { computed, onMounted, onUnmounted, watch } from "mikuru";
|
|
20
|
+
import { computed, onMounted, onUnmounted, ref, watch } from "mikuru";
|
|
21
21
|
|
|
22
22
|
const {
|
|
23
23
|
toasts = [],
|
|
@@ -32,9 +32,14 @@ const {
|
|
|
32
32
|
const emit = defineEmits(["dismiss"]);
|
|
33
33
|
const timers = new Map();
|
|
34
34
|
const positionClass = computed(() => `position-${position.value}`);
|
|
35
|
-
const normalizedToasts =
|
|
35
|
+
const normalizedToasts = ref([]);
|
|
36
|
+
let toastsSignature = "";
|
|
37
|
+
|
|
38
|
+
watch([toasts, duration], syncToasts, { immediate: true });
|
|
39
|
+
|
|
40
|
+
function syncToasts() {
|
|
36
41
|
const source = Array.isArray(toasts.value) ? toasts.value : [];
|
|
37
|
-
|
|
42
|
+
const nextToasts = source.map((toast, index) => {
|
|
38
43
|
const id = toast.id ?? index;
|
|
39
44
|
const tone = toast.tone || "info";
|
|
40
45
|
const toastDuration = typeof toast.duration === "number" ? toast.duration : duration.value;
|
|
@@ -47,7 +52,14 @@ const normalizedToasts = computed(() => {
|
|
|
47
52
|
duration: toastDuration
|
|
48
53
|
};
|
|
49
54
|
});
|
|
50
|
-
|
|
55
|
+
const nextSignature = nextToasts
|
|
56
|
+
.map((toast) => `${toast.id}\u0000${toast.title}\u0000${toast.message}\u0000${toast.toneClass}\u0000${toast.duration}`)
|
|
57
|
+
.join("\u0001");
|
|
58
|
+
if (nextSignature === toastsSignature) return;
|
|
59
|
+
toastsSignature = nextSignature;
|
|
60
|
+
normalizedToasts.value = nextToasts;
|
|
61
|
+
syncToastTimers();
|
|
62
|
+
}
|
|
51
63
|
|
|
52
64
|
onMounted(() => {
|
|
53
65
|
syncToastTimers();
|
|
@@ -57,10 +69,6 @@ onUnmounted(() => {
|
|
|
57
69
|
clearToastTimers();
|
|
58
70
|
});
|
|
59
71
|
|
|
60
|
-
watch(toasts, () => {
|
|
61
|
-
syncToastTimers();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
72
|
function dismissToast(id) {
|
|
65
73
|
clearToastTimer(id);
|
|
66
74
|
emit("dismiss", id);
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
</template>
|
|
145
145
|
|
|
146
146
|
<script>
|
|
147
|
-
import { computed, onBeforeUnmount, onMounted, onUnmounted, ref } from "mikuru";
|
|
147
|
+
import { computed, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from "mikuru";
|
|
148
148
|
|
|
149
149
|
const {
|
|
150
150
|
src,
|
|
@@ -202,19 +202,40 @@ const controlsVisible = ref(true);
|
|
|
202
202
|
const settingsEl = ref(null);
|
|
203
203
|
const allControls = ["play", "seek", "time", "mute", "volume", "settings", "fullscreen"];
|
|
204
204
|
const liveHiddenControls = ["seek", "time"];
|
|
205
|
-
const activeControls =
|
|
205
|
+
const activeControls = ref(new Set(allControls));
|
|
206
|
+
const normalizedQualityOptions = ref([]);
|
|
207
|
+
let controlsSignature = "";
|
|
208
|
+
let qualityOptionsSignature = "";
|
|
209
|
+
|
|
210
|
+
watch(controls, syncControls, { immediate: true });
|
|
211
|
+
watch([qualityOptions, src, poster], syncQualityOptions, { immediate: true });
|
|
212
|
+
|
|
213
|
+
function syncControls() {
|
|
206
214
|
const configuredControls = Array.isArray(controls.value) ? controls.value : allControls;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
215
|
+
const nextSignature = configuredControls.join("\u0001");
|
|
216
|
+
if (nextSignature === controlsSignature) return;
|
|
217
|
+
controlsSignature = nextSignature;
|
|
218
|
+
activeControls.value = new Set(configuredControls);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function syncQualityOptions() {
|
|
210
222
|
const source = Array.isArray(qualityOptions.value) ? qualityOptions.value : [];
|
|
211
223
|
const options = source
|
|
212
224
|
.map((option, index) => normalizeQualityOption(option, index))
|
|
213
225
|
.filter(Boolean);
|
|
214
|
-
|
|
226
|
+
const nextOptions = options.length > 0
|
|
215
227
|
? options
|
|
216
228
|
: [{ id: "auto", label: "Auto", src: src.value, poster: poster.value }];
|
|
217
|
-
|
|
229
|
+
const nextSignature = nextOptions
|
|
230
|
+
.map((option) => `${option.id}\u0000${option.label}\u0000${option.src}\u0000${option.poster}`)
|
|
231
|
+
.join("\u0001");
|
|
232
|
+
if (nextSignature === qualityOptionsSignature) return;
|
|
233
|
+
qualityOptionsSignature = nextSignature;
|
|
234
|
+
normalizedQualityOptions.value = nextOptions;
|
|
235
|
+
if (!nextOptions.some((option) => option.id === selectedQualityId.value)) {
|
|
236
|
+
selectedQualityId.value = nextOptions[0]?.id || "auto";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
218
239
|
const selectedQuality = computed(() =>
|
|
219
240
|
normalizedQualityOptions.value.find((option) => option.id === selectedQualityId.value) ?? normalizedQualityOptions.value[0]
|
|
220
241
|
);
|
|
@@ -223,15 +244,15 @@ const selectedVideoSrc = computed(() => selectedQuality.value?.src || src.value)
|
|
|
223
244
|
const selectedPoster = computed(() => selectedQuality.value?.poster ?? poster.value);
|
|
224
245
|
const playerStyle = computed(() => {
|
|
225
246
|
const normalizedWidth = normalizeCssSize(width.value);
|
|
226
|
-
return normalizedWidth ?
|
|
247
|
+
return normalizedWidth ? `width: ${normalizedWidth}` : "";
|
|
227
248
|
});
|
|
228
249
|
const screenStyle = computed(() => {
|
|
229
250
|
const normalizedHeight = normalizeCssSize(height.value);
|
|
230
251
|
const normalizedAspectRatio = normalizeAspectRatio(aspectRatio.value);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
252
|
+
const declarations = [];
|
|
253
|
+
if (normalizedHeight) declarations.push(`height: ${normalizedHeight}`);
|
|
254
|
+
if (normalizedAspectRatio) declarations.push(`aspect-ratio: ${normalizedAspectRatio}`);
|
|
255
|
+
return declarations.join("; ");
|
|
235
256
|
});
|
|
236
257
|
const isLive = computed(() => live.value === true);
|
|
237
258
|
const safeDuration = computed(() => duration.value > 0 ? duration.value : 0);
|