mikuru 1.0.34 → 1.0.36

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 (44) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +25 -1
  3. package/components/MikuruAccordion.mikuru +156 -0
  4. package/components/MikuruCarousel.mikuru +144 -6
  5. package/components/MikuruCheckbox.mikuru +84 -0
  6. package/components/MikuruCodeBlock.mikuru +238 -4
  7. package/components/MikuruCombobox.mikuru +226 -0
  8. package/components/MikuruFooter.mikuru +121 -0
  9. package/components/MikuruHeader.mikuru +165 -0
  10. package/components/MikuruSelect.mikuru +106 -0
  11. package/components/MikuruSideMenu.mikuru +188 -0
  12. package/components/MikuruTabs.mikuru +163 -0
  13. package/components/MikuruTextInput.mikuru +83 -0
  14. package/components/MikuruTextarea.mikuru +86 -0
  15. package/components/MikuruVideoPlayer.mikuru +8 -4
  16. package/dist/cli/create.js +5 -1
  17. package/dist/cli/create.js.map +1 -1
  18. package/dist/cli/templates.d.ts +1 -1
  19. package/dist/cli/templates.js +3 -2
  20. package/dist/cli/templates.js.map +1 -1
  21. package/dist/cli.js +1 -1
  22. package/package.json +81 -1
  23. package/templates/video-player/_gitignore +3 -0
  24. package/templates/video-player/index.html +13 -0
  25. package/templates/video-player/package.json +19 -0
  26. package/templates/video-player/public/favicon.svg +4 -0
  27. package/templates/video-player/src/App.mikuru +92 -0
  28. package/templates/video-player/src/css-env.d.ts +1 -0
  29. package/templates/video-player/src/main.ts +10 -0
  30. package/templates/video-player/src/mikuru-env.d.ts +1 -0
  31. package/templates/video-player/src/style.css +132 -0
  32. package/templates/video-player/tsconfig.json +11 -0
  33. package/templates/video-player/vite.config.ts +6 -0
  34. package/types/components/MikuruAccordion.d.ts +18 -0
  35. package/types/components/MikuruCarousel.d.ts +2 -0
  36. package/types/components/MikuruCheckbox.d.ts +13 -0
  37. package/types/components/MikuruCombobox.d.ts +21 -0
  38. package/types/components/MikuruFooter.d.ts +19 -0
  39. package/types/components/MikuruHeader.d.ts +22 -0
  40. package/types/components/MikuruSelect.d.ts +21 -0
  41. package/types/components/MikuruSideMenu.d.ts +22 -0
  42. package/types/components/MikuruTabs.d.ts +18 -0
  43. package/types/components/MikuruTextInput.d.ts +16 -0
  44. package/types/components/MikuruTextarea.d.ts +16 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.36 - 2026-05-17
4
+
5
+ - Stabilized `MikuruVideoPlayer` settings interactions so playback speed and keyboard skip selections defer UI updates out of the click stack, matching the other media controls and avoiding recursive update freezes.
6
+
7
+ ## 1.0.35 - 2026-05-17
8
+
9
+ - Added package-exported tabs, accordion, form controls, select, combobox, header, footer, and side menu components with typed exports and dogfood coverage.
10
+ - Added a `video-player` create template that imports `MikuruVideoPlayer` from the package and demonstrates quality options, controls, and media events.
11
+ - Improved `MikuruCarousel` with CSS-mask arrow icons, optional thumbnail navigation, hidden thumbnail scrollbars, centered active thumbnails, and a 20-image dogfood gallery case.
12
+ - Updated `MikuruSideMenu` collapse controls to use icon buttons.
13
+ - Added lightweight VS Code-style syntax highlighting to `MikuruCodeBlock` for Mikuru/markup, JavaScript/TypeScript, JSON, and CSS snippets.
14
+
3
15
  ## 1.0.34 - 2026-05-16
4
16
 
5
17
  - Stabilized package component internals so repeated mounts and parent rerenders no longer recreate equivalent derived arrays, Sets, or style objects unnecessarily.
package/README.md CHANGED
@@ -29,12 +29,19 @@ Use the `basic` template when you want a small component composition example:
29
29
  npx mikuru create my-basic-app -t basic
30
30
  ```
31
31
 
32
+ Use the `video-player` template when you want a Vite app that imports the package-provided `MikuruVideoPlayer` component:
33
+
34
+ ```sh
35
+ npx mikuru create my-video-app -t video-player
36
+ ```
37
+
32
38
  List available templates:
33
39
 
34
40
  ```sh
35
41
  npx mikuru --list-templates
36
42
  starter - minimal Vite app
37
43
  basic - component composition example
44
+ video-player - MikuruVideoPlayer media app
38
45
  ```
39
46
 
40
47
  Run a dry-run to preview the target, template, and files without writing them:
@@ -197,6 +204,7 @@ The package also provides the `mikuru` binary:
197
204
  ```sh
198
205
  npx mikuru create my-app
199
206
  npx mikuru create my-basic-app -t basic
207
+ npx mikuru create my-video-app -t video-player
200
208
  npx mikuru --list-templates
201
209
  ```
202
210
 
@@ -274,12 +282,18 @@ The package also includes original Mikuru components:
274
282
  - `MikuruAudioPlayer.mikuru`: audio playback with configurable control visibility, live mode, seeking, skip controls, volume, and mute.
275
283
  - `MikuruImageViewer.mikuru`: image zoom, pan, rotate, reset, and fullscreen controls.
276
284
  - `MikuruModal.mikuru`: accessible modal shell with backdrop, Escape close, slots, and close events.
277
- - `MikuruCarousel.mikuru`: image carousel with arrows, dots, keyboard navigation, and optional autoplay.
285
+ - `MikuruCarousel.mikuru`: image carousel with arrows, dots, keyboard navigation, optional autoplay, and optional thumbnail navigation.
278
286
  - `MikuruToast.mikuru`: fixed notification stack with timed auto-dismiss, dismiss events, and tone variants.
279
287
  - `MikuruDropdown.mikuru`: menu button with outside-click close, Escape handling, and select events.
280
288
  - `MikuruToolTip.mikuru`: hover/focus tooltip with configurable placement.
281
289
  - `MikuruProgress.mikuru`: determinate and indeterminate progress indicator.
282
290
  - `MikuruCodeBlock.mikuru`: code display with language label, line numbers, and copy action.
291
+ - `MikuruTabs.mikuru`: accessible tab list with controlled `m-model`, keyboard navigation, and slot/fallback panels.
292
+ - `MikuruAccordion.mikuru`: single or multiple disclosure panels with controlled `m-model` and slot/fallback content.
293
+ - `MikuruTextInput.mikuru`, `MikuruTextarea.mikuru`, and `MikuruCheckbox.mikuru`: form controls that emit `update:modelValue`.
294
+ - `MikuruSelect.mikuru`: labeled select control with normalized string/object options.
295
+ - `MikuruCombobox.mikuru`: searchable single-select combobox with outside-click and Escape close.
296
+ - `MikuruHeader.mikuru`, `MikuruFooter.mikuru`, and `MikuruSideMenu.mikuru`: app shell layout primitives with normalized navigation items and selection events.
283
297
 
284
298
  They can be imported from the package:
285
299
 
@@ -295,6 +309,16 @@ import MikuruDropdown from "mikuru/components/MikuruDropdown";
295
309
  import MikuruToolTip from "mikuru/components/MikuruToolTip";
296
310
  import MikuruProgress from "mikuru/components/MikuruProgress";
297
311
  import MikuruCodeBlock from "mikuru/components/MikuruCodeBlock";
312
+ import MikuruTabs from "mikuru/components/MikuruTabs";
313
+ import MikuruAccordion from "mikuru/components/MikuruAccordion";
314
+ import MikuruTextInput from "mikuru/components/MikuruTextInput";
315
+ import MikuruTextarea from "mikuru/components/MikuruTextarea";
316
+ import MikuruCheckbox from "mikuru/components/MikuruCheckbox";
317
+ import MikuruSelect from "mikuru/components/MikuruSelect";
318
+ import MikuruCombobox from "mikuru/components/MikuruCombobox";
319
+ import MikuruHeader from "mikuru/components/MikuruHeader";
320
+ import MikuruFooter from "mikuru/components/MikuruFooter";
321
+ import MikuruSideMenu from "mikuru/components/MikuruSideMenu";
298
322
  </script>
299
323
  ```
300
324
 
@@ -0,0 +1,156 @@
1
+ <template>
2
+ <section class="mikuru-accordion">
3
+ <details
4
+ m-for="item in normalizedItems"
5
+ :key="item.value"
6
+ class="accordion-item"
7
+ :class="{ open: isOpen(item.value) }"
8
+ @toggle="handleToggle(item, $event)"
9
+ >
10
+ <summary
11
+ class="accordion-trigger"
12
+ :aria-disabled="item.disabled ? 'true' : 'false'"
13
+ @click="handleSummaryClick(item, $event)"
14
+ >
15
+ <span>{{ item.label }}</span>
16
+ <span aria-hidden="true">{{ isOpen(item.value) ? "-" : "+" }}</span>
17
+ </summary>
18
+ <div class="accordion-panel">
19
+ <slot :item="item">{{ item.panel }}</slot>
20
+ </div>
21
+ </details>
22
+ </section>
23
+ </template>
24
+
25
+ <script>
26
+ import { ref, watch } from "mikuru";
27
+
28
+ const {
29
+ items = [],
30
+ modelValue = "",
31
+ multiple = false
32
+ } = defineProps();
33
+
34
+ const emit = defineEmits(["update:modelValue", "change"]);
35
+ const normalizedItems = ref([]);
36
+ const openValues = ref([]);
37
+ let itemsSignature = "";
38
+
39
+ watch(items, syncItems, { immediate: true });
40
+ watch(modelValue, syncOpenValues, { immediate: true });
41
+
42
+ function syncItems() {
43
+ const source = Array.isArray(items.value) ? items.value : [];
44
+ const nextItems = source.map((item, index) => {
45
+ if (typeof item === "string") {
46
+ return { label: item, value: item, panel: "", disabled: false };
47
+ }
48
+ return {
49
+ label: item.label || `Item ${index + 1}`,
50
+ value: item.value ?? item.label ?? index,
51
+ panel: item.panel || "",
52
+ disabled: Boolean(item.disabled)
53
+ };
54
+ });
55
+ const nextSignature = nextItems
56
+ .map((item) => `${item.value}\u0000${item.label}\u0000${item.panel}\u0000${item.disabled}`)
57
+ .join("\u0001");
58
+ if (nextSignature === itemsSignature) return;
59
+ itemsSignature = nextSignature;
60
+ normalizedItems.value = nextItems;
61
+ syncOpenValues();
62
+ }
63
+
64
+ function syncOpenValues() {
65
+ const source = multiple.value
66
+ ? (Array.isArray(modelValue.value) ? modelValue.value : [])
67
+ : (modelValue.value === "" || modelValue.value == null ? [] : [modelValue.value]);
68
+ const availableValues = normalizedItems.value.map((item) => item.value);
69
+ openValues.value = source.filter((value) => availableValues.some((itemValue) => Object.is(itemValue, value)));
70
+ }
71
+
72
+ function isOpen(value) {
73
+ return openValues.value.some((itemValue) => Object.is(itemValue, value));
74
+ }
75
+
76
+ function handleSummaryClick(item, event) {
77
+ if (!item.disabled) return;
78
+ event.preventDefault();
79
+ }
80
+
81
+ function handleToggle(item, event) {
82
+ if (item.disabled) {
83
+ event.target.open = false;
84
+ return;
85
+ }
86
+ if (event.target.open === isOpen(item.value)) return;
87
+ toggleItem(item);
88
+ }
89
+
90
+ function toggleItem(item) {
91
+ let nextValues;
92
+ if (multiple.value) {
93
+ nextValues = isOpen(item.value)
94
+ ? openValues.value.filter((value) => !Object.is(value, item.value))
95
+ : [...openValues.value, item.value];
96
+ } else {
97
+ nextValues = isOpen(item.value) ? [] : [item.value];
98
+ }
99
+ openValues.value = nextValues;
100
+ const payload = multiple.value ? nextValues : nextValues[0] ?? "";
101
+ emit("update:modelValue", payload);
102
+ emit("change", payload);
103
+ }
104
+ </script>
105
+
106
+ <style scoped>
107
+ .mikuru-accordion {
108
+ display: grid;
109
+ overflow: hidden;
110
+ border: 1px solid #e2e8f0;
111
+ border-radius: 8px;
112
+ background: #ffffff;
113
+ }
114
+
115
+ .accordion-item + .accordion-item {
116
+ border-top: 1px solid #e2e8f0;
117
+ }
118
+
119
+ .accordion-trigger {
120
+ display: flex;
121
+ width: 100%;
122
+ align-items: center;
123
+ justify-content: space-between;
124
+ gap: 12px;
125
+ border: 0;
126
+ padding: 12px 14px;
127
+ color: #0f172a;
128
+ background: #ffffff;
129
+ text-align: left;
130
+ font: inherit;
131
+ cursor: pointer;
132
+ list-style: none;
133
+ }
134
+
135
+ .accordion-trigger::-webkit-details-marker {
136
+ display: none;
137
+ }
138
+
139
+ .accordion-trigger:hover,
140
+ .accordion-trigger:focus-visible,
141
+ .accordion-item.open .accordion-trigger {
142
+ background: #f8fafc;
143
+ outline: none;
144
+ }
145
+
146
+ .accordion-trigger[aria-disabled="true"] {
147
+ color: #94a3b8;
148
+ cursor: not-allowed;
149
+ }
150
+
151
+ .accordion-panel {
152
+ padding: 0 14px 14px;
153
+ color: #475569;
154
+ line-height: 1.5;
155
+ }
156
+ </style>
@@ -22,10 +22,10 @@
22
22
  </div>
23
23
 
24
24
  <button class="carousel-arrow previous" type="button" @click="previous" aria-label="Previous slide">
25
-
25
+ <span class="carousel-arrow-icon icon-previous" aria-hidden="true"></span>
26
26
  </button>
27
27
  <button class="carousel-arrow next" type="button" @click="next" aria-label="Next slide">
28
-
28
+ <span class="carousel-arrow-icon icon-next" aria-hidden="true"></span>
29
29
  </button>
30
30
  </div>
31
31
 
@@ -42,6 +42,31 @@
42
42
  ></button>
43
43
  </div>
44
44
  </div>
45
+
46
+ <div m-if="showThumbnails" class="carousel-thumbnail-shell">
47
+ <button class="thumbnail-scroll previous" type="button" @click="scrollThumbnails(-1)" aria-label="Scroll thumbnails left">
48
+ <span class="carousel-arrow-icon icon-previous" aria-hidden="true"></span>
49
+ </button>
50
+
51
+ <div ref="thumbnailTrackEl" class="carousel-thumbnails" role="tablist" aria-label="Carousel thumbnails">
52
+ <button
53
+ m-for="slide in slides"
54
+ :key="slide.id"
55
+ type="button"
56
+ class="carousel-thumbnail"
57
+ :class="{ active: slide.index === activeIndex }"
58
+ :aria-label="slide.label"
59
+ :aria-selected="slide.index === activeIndex"
60
+ @click="goToSlide(slide.index)"
61
+ >
62
+ <img :src="slide.thumbnail" :alt="slide.alt" />
63
+ </button>
64
+ </div>
65
+
66
+ <button class="thumbnail-scroll next" type="button" @click="scrollThumbnails(1)" aria-label="Scroll thumbnails right">
67
+ <span class="carousel-arrow-icon icon-next" aria-hidden="true"></span>
68
+ </button>
69
+ </div>
45
70
  </section>
46
71
  </template>
47
72
 
@@ -53,6 +78,7 @@ const {
53
78
  title = "Mikuru Carousel",
54
79
  autoplay = false,
55
80
  interval = 5000,
81
+ thumbnails = false,
56
82
  emptyTitle = "No slides",
57
83
  emptyMessage = "Add images to show the carousel."
58
84
  } = defineProps({
@@ -60,18 +86,21 @@ const {
60
86
  title: String,
61
87
  autoplay: Boolean,
62
88
  interval: Number,
89
+ thumbnails: Boolean,
63
90
  emptyTitle: String,
64
91
  emptyMessage: String
65
92
  });
66
93
 
67
94
  const activeIndex = ref(0);
68
95
  const slides = ref([]);
96
+ const thumbnailTrackEl = ref(null);
69
97
  let slidesSignature = "";
70
98
  let timer = null;
71
99
  let mounted = false;
72
100
 
73
101
  const slideCount = computed(() => slides.value.length);
74
102
  const isEmpty = computed(() => slideCount.value === 0);
103
+ const showThumbnails = computed(() => thumbnails.value && slideCount.value > 0);
75
104
  const trackStyle = computed(() => `transform: translateX(-${activeIndex.value * 100}%)`);
76
105
  const positionLabel = computed(() => {
77
106
  if (slideCount.value === 0) return "0 / 0";
@@ -87,10 +116,12 @@ function syncSlides() {
87
116
  const alt = typeof item === "string" ? "" : item.alt || item.title || `Slide ${index + 1}`;
88
117
  const slideTitle = typeof item === "string" ? `Slide ${index + 1}` : item.title || `Slide ${index + 1}`;
89
118
  const caption = typeof item === "string" ? "" : item.caption || "";
119
+ const thumbnail = typeof item === "string" ? item : item.thumbnail || item.src;
90
120
  return {
91
121
  id: `${src}-${index}`,
92
122
  index,
93
123
  src,
124
+ thumbnail,
94
125
  alt,
95
126
  title: slideTitle,
96
127
  caption,
@@ -98,7 +129,7 @@ function syncSlides() {
98
129
  };
99
130
  });
100
131
  const nextSignature = nextSlides
101
- .map((slide) => `${slide.id}\u0000${slide.alt}\u0000${slide.title}\u0000${slide.caption}`)
132
+ .map((slide) => `${slide.id}\u0000${slide.thumbnail}\u0000${slide.alt}\u0000${slide.title}\u0000${slide.caption}`)
102
133
  .join("\u0001");
103
134
  if (nextSignature === slidesSignature) return;
104
135
  slidesSignature = nextSignature;
@@ -128,6 +159,7 @@ function clampIndex(index) {
128
159
 
129
160
  function goToSlide(index) {
130
161
  activeIndex.value = clampIndex(index);
162
+ centerActiveThumbnail();
131
163
  restartAutoplay();
132
164
  }
133
165
 
@@ -139,6 +171,18 @@ function next() {
139
171
  goToSlide(activeIndex.value + 1);
140
172
  }
141
173
 
174
+ function scrollThumbnails(direction) {
175
+ goToSlide(activeIndex.value + direction);
176
+ }
177
+
178
+ function centerActiveThumbnail() {
179
+ window.setTimeout(() => {
180
+ const track = thumbnailTrackEl.value;
181
+ const activeThumbnail = track?.querySelector?.(".carousel-thumbnail.active");
182
+ activeThumbnail?.scrollIntoView?.({ behavior: "smooth", block: "nearest", inline: "center" });
183
+ }, 0);
184
+ }
185
+
142
186
  function startAutoplay() {
143
187
  stopAutoplay();
144
188
  if (!autoplay.value || slideCount.value <= 1) return;
@@ -250,13 +294,29 @@ function handleKeydown(event) {
250
294
  border-radius: 999px;
251
295
  color: #111827;
252
296
  background: #ffffff;
253
- font: inherit;
254
- font-size: 1.8rem;
255
- line-height: 1;
297
+ padding: 0;
256
298
  transform: translateY(-50%);
257
299
  cursor: pointer;
258
300
  }
259
301
 
302
+ .carousel-arrow-icon {
303
+ display: block;
304
+ width: 18px;
305
+ height: 18px;
306
+ background: currentColor;
307
+ mask-position: center;
308
+ mask-repeat: no-repeat;
309
+ mask-size: contain;
310
+ }
311
+
312
+ .icon-previous {
313
+ mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M169.4 297.4C156.9 309.9 156.9 330.2 169.4 342.7L361.4 534.7C373.9 547.2 394.2 547.2 406.7 534.7C419.2 522.2 419.2 501.9 406.7 489.4L237.3 320L406.6 150.6C419.1 138.1 419.1 117.8 406.6 105.3C394.1 92.8 373.8 92.8 361.3 105.3L169.3 297.3z"/></svg>');
314
+ }
315
+
316
+ .icon-next {
317
+ mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M471.1 297.4C483.6 309.9 483.6 330.2 471.1 342.7L279.1 534.7C266.6 547.2 246.3 547.2 233.8 534.7C221.3 522.2 221.3 501.9 233.8 489.4L403.2 320L233.9 150.6C221.4 138.1 221.4 117.8 233.9 105.3C246.4 92.8 266.7 92.8 279.2 105.3L471.2 297.3z"/></svg>');
318
+ }
319
+
260
320
  .previous {
261
321
  left: 12px;
262
322
  }
@@ -293,4 +353,82 @@ function handleKeydown(event) {
293
353
  width: 24px;
294
354
  background: #111827;
295
355
  }
356
+
357
+ .carousel-thumbnail-shell {
358
+ position: relative;
359
+ display: grid;
360
+ align-items: center;
361
+ padding: 0 42px;
362
+ }
363
+
364
+ .carousel-thumbnails {
365
+ display: grid;
366
+ grid-auto-columns: minmax(86px, 1fr);
367
+ grid-auto-flow: column;
368
+ gap: 8px;
369
+ overflow-x: auto;
370
+ scrollbar-width: none;
371
+ padding-bottom: 2px;
372
+ }
373
+
374
+ .carousel-thumbnails::-webkit-scrollbar {
375
+ display: none;
376
+ }
377
+
378
+ .carousel-thumbnail {
379
+ display: block;
380
+ aspect-ratio: 1;
381
+ min-width: 86px;
382
+ overflow: hidden;
383
+ border: 2px solid transparent;
384
+ border-radius: 7px;
385
+ padding: 0;
386
+ background: #e2e8f0;
387
+ cursor: pointer;
388
+ }
389
+
390
+ .carousel-thumbnail:hover,
391
+ .carousel-thumbnail:focus-visible,
392
+ .carousel-thumbnail.active {
393
+ border-color: #111827;
394
+ outline: none;
395
+ }
396
+
397
+ .carousel-thumbnail img {
398
+ display: block;
399
+ width: 100%;
400
+ height: 100%;
401
+ object-fit: cover;
402
+ }
403
+
404
+ .thumbnail-scroll {
405
+ position: absolute;
406
+ top: 50%;
407
+ z-index: 1;
408
+ display: grid;
409
+ width: 32px;
410
+ height: 32px;
411
+ place-items: center;
412
+ border: 1px solid #cbd5e1;
413
+ border-radius: 999px;
414
+ color: #111827;
415
+ background: #ffffff;
416
+ box-shadow: 0 8px 24px rgb(15 23 42 / 18%);
417
+ padding: 0;
418
+ transform: translateY(-50%);
419
+ cursor: pointer;
420
+ }
421
+
422
+ .thumbnail-scroll.previous {
423
+ left: 0;
424
+ }
425
+
426
+ .thumbnail-scroll.next {
427
+ right: 0;
428
+ }
429
+
430
+ .thumbnail-scroll .carousel-arrow-icon {
431
+ width: 14px;
432
+ height: 14px;
433
+ }
296
434
  </style>
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <label class="mikuru-checkbox">
3
+ <input
4
+ type="checkbox"
5
+ :checked="checked"
6
+ :value="value"
7
+ :disabled="disabled"
8
+ @change="updateChecked($event)"
9
+ />
10
+ <span>
11
+ <span class="checkbox-label">{{ label }}</span>
12
+ <small m-if="description">{{ description }}</small>
13
+ </span>
14
+ </label>
15
+ </template>
16
+
17
+ <script>
18
+ import { computed } from "mikuru";
19
+
20
+ const {
21
+ label = "Checkbox",
22
+ description = "",
23
+ modelValue = false,
24
+ value = "on",
25
+ disabled = false
26
+ } = defineProps();
27
+
28
+ const emit = defineEmits(["update:modelValue", "change"]);
29
+
30
+ const checked = computed(() => {
31
+ if (Array.isArray(modelValue.value)) {
32
+ return modelValue.value.some((item) => Object.is(item, value.value));
33
+ }
34
+ return Boolean(modelValue.value);
35
+ });
36
+
37
+ function updateChecked(event) {
38
+ let nextValue;
39
+ if (Array.isArray(modelValue.value)) {
40
+ nextValue = event.target.checked
41
+ ? [...modelValue.value, value.value]
42
+ : modelValue.value.filter((item) => !Object.is(item, value.value));
43
+ } else {
44
+ nextValue = event.target.checked;
45
+ }
46
+ emit("update:modelValue", nextValue);
47
+ emit("change", nextValue);
48
+ }
49
+ </script>
50
+
51
+ <style scoped>
52
+ .mikuru-checkbox {
53
+ display: inline-grid;
54
+ grid-template-columns: auto 1fr;
55
+ gap: 9px;
56
+ align-items: start;
57
+ color: #0f172a;
58
+ font: inherit;
59
+ cursor: pointer;
60
+ }
61
+
62
+ .mikuru-checkbox input {
63
+ width: 18px;
64
+ height: 18px;
65
+ margin: 2px 0 0;
66
+ accent-color: #2563eb;
67
+ }
68
+
69
+ .mikuru-checkbox input:disabled {
70
+ cursor: not-allowed;
71
+ }
72
+
73
+ .checkbox-label {
74
+ display: block;
75
+ font-weight: 650;
76
+ }
77
+
78
+ .mikuru-checkbox small {
79
+ display: block;
80
+ margin-top: 2px;
81
+ color: #64748b;
82
+ line-height: 1.35;
83
+ }
84
+ </style>