@vuetify/nightly 3.9.2-master.2025-07-22 → 3.9.2-master.2025-07-24

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 (237) hide show
  1. package/CHANGELOG.md +32 -3
  2. package/dist/_component-variables-labs.sass +2 -1
  3. package/dist/json/attributes.json +3014 -2750
  4. package/dist/json/importMap-labs.json +34 -22
  5. package/dist/json/importMap.json +168 -168
  6. package/dist/json/tags.json +81 -0
  7. package/dist/json/web-types.json +6647 -5536
  8. package/dist/vuetify-labs.cjs +765 -23
  9. package/dist/vuetify-labs.css +4300 -3958
  10. package/dist/vuetify-labs.d.ts +6519 -844
  11. package/dist/vuetify-labs.esm.js +766 -24
  12. package/dist/vuetify-labs.esm.js.map +1 -1
  13. package/dist/vuetify-labs.js +765 -23
  14. package/dist/vuetify-labs.min.css +2 -2
  15. package/dist/vuetify.cjs +50 -16
  16. package/dist/vuetify.cjs.map +1 -1
  17. package/dist/vuetify.css +5769 -5762
  18. package/dist/vuetify.d.ts +96 -82
  19. package/dist/vuetify.esm.js +50 -16
  20. package/dist/vuetify.esm.js.map +1 -1
  21. package/dist/vuetify.js +50 -16
  22. package/dist/vuetify.js.map +1 -1
  23. package/dist/vuetify.min.css +2 -2
  24. package/dist/vuetify.min.js +276 -274
  25. package/dist/vuetify.min.js.map +1 -1
  26. package/lib/components/VBottomSheet/VBottomSheet.d.ts +6 -6
  27. package/lib/components/VCarousel/VCarousel.d.ts +7 -13
  28. package/lib/components/VCarousel/VCarousel.js.map +1 -1
  29. package/lib/components/VColorPicker/VColorPicker.css +1 -0
  30. package/lib/components/VColorPicker/VColorPicker.sass +1 -0
  31. package/lib/components/VDataTable/VDataTableHeaders.js +0 -1
  32. package/lib/components/VDataTable/VDataTableHeaders.js.map +1 -1
  33. package/lib/components/VDialog/VDialog.d.ts +6 -6
  34. package/lib/components/VFileInput/VFileInput.css +3 -0
  35. package/lib/components/VFileInput/VFileInput.sass +3 -0
  36. package/lib/components/VOverlay/VOverlay.js +1 -0
  37. package/lib/components/VOverlay/VOverlay.js.map +1 -1
  38. package/lib/components/VOverlay/scrollStrategies.d.ts +1 -0
  39. package/lib/components/VOverlay/scrollStrategies.js +6 -4
  40. package/lib/components/VOverlay/scrollStrategies.js.map +1 -1
  41. package/lib/components/VProgressLinear/VProgressLinear.css +4 -1
  42. package/lib/components/VProgressLinear/VProgressLinear.js +2 -1
  43. package/lib/components/VProgressLinear/VProgressLinear.js.map +1 -1
  44. package/lib/components/VProgressLinear/VProgressLinear.sass +5 -2
  45. package/lib/components/VRangeSlider/VRangeSlider.d.ts +13 -0
  46. package/lib/components/VSlider/VSlider.d.ts +13 -0
  47. package/lib/components/VSlider/VSlider.js +3 -1
  48. package/lib/components/VSlider/VSlider.js.map +1 -1
  49. package/lib/components/VSlider/VSliderThumb.d.ts +13 -0
  50. package/lib/components/VSlider/VSliderThumb.js +2 -0
  51. package/lib/components/VSlider/VSliderThumb.js.map +1 -1
  52. package/lib/components/VSlider/slider.d.ts +6 -0
  53. package/lib/components/VSlider/slider.js +2 -0
  54. package/lib/components/VSlider/slider.js.map +1 -1
  55. package/lib/components/VTextField/VTextField.js +2 -2
  56. package/lib/components/VTextField/VTextField.js.map +1 -1
  57. package/lib/components/VTreeview/VTreeview.d.ts +7 -7
  58. package/lib/components/VTreeview/VTreeview.js +0 -1
  59. package/lib/components/VTreeview/VTreeview.js.map +1 -1
  60. package/lib/components/VTreeview/VTreeviewChildren.d.ts +13 -0
  61. package/lib/components/VTreeview/VTreeviewChildren.js +2 -1
  62. package/lib/components/VTreeview/VTreeviewChildren.js.map +1 -1
  63. package/lib/components/index.js +1 -1
  64. package/lib/components/index.js.map +1 -1
  65. package/lib/composables/date/adapters/vuetify.js +8 -2
  66. package/lib/composables/date/adapters/vuetify.js.map +1 -1
  67. package/lib/entry-bundler.js +1 -1
  68. package/lib/framework.d.ts +59 -55
  69. package/lib/framework.js +1 -1
  70. package/lib/iconsets/fa.js +9 -1
  71. package/lib/iconsets/fa.js.map +1 -1
  72. package/lib/iconsets/fa4.js +9 -1
  73. package/lib/iconsets/fa4.js.map +1 -1
  74. package/lib/iconsets/md.js +9 -1
  75. package/lib/iconsets/md.js.map +1 -1
  76. package/lib/iconsets/mdi-svg.js +9 -1
  77. package/lib/iconsets/mdi-svg.js.map +1 -1
  78. package/lib/iconsets/mdi.js +9 -1
  79. package/lib/iconsets/mdi.js.map +1 -1
  80. package/lib/labs/VFileUpload/VFileUploadItem.js +1 -1
  81. package/lib/labs/VFileUpload/VFileUploadItem.js.map +1 -1
  82. package/lib/labs/VStepperVertical/VStepperVerticalItem.css +1 -0
  83. package/lib/labs/VStepperVertical/VStepperVerticalItem.sass +1 -0
  84. package/lib/labs/VVideo/VVideo.css +319 -0
  85. package/lib/labs/VVideo/VVideo.d.ts +6932 -0
  86. package/lib/labs/VVideo/VVideo.js +424 -0
  87. package/lib/labs/VVideo/VVideo.js.map +1 -0
  88. package/lib/labs/VVideo/VVideo.sass +301 -0
  89. package/lib/labs/VVideo/VVideoControls.d.ts +3524 -0
  90. package/lib/labs/VVideo/VVideoControls.js +232 -0
  91. package/lib/labs/VVideo/VVideoControls.js.map +1 -0
  92. package/lib/labs/VVideo/VVideoVolume.d.ts +3088 -0
  93. package/lib/labs/VVideo/VVideoVolume.js +90 -0
  94. package/lib/labs/VVideo/VVideoVolume.js.map +1 -0
  95. package/lib/labs/VVideo/_variables.scss +58 -0
  96. package/lib/labs/VVideo/index.d.ts +3 -0
  97. package/lib/labs/VVideo/index.js +4 -0
  98. package/lib/labs/VVideo/index.js.map +1 -0
  99. package/lib/labs/components.d.ts +1 -0
  100. package/lib/labs/components.js +1 -0
  101. package/lib/labs/components.js.map +1 -1
  102. package/lib/locale/af.d.ts +11 -0
  103. package/lib/locale/af.js +11 -0
  104. package/lib/locale/af.js.map +1 -1
  105. package/lib/locale/ar.d.ts +11 -0
  106. package/lib/locale/ar.js +11 -0
  107. package/lib/locale/ar.js.map +1 -1
  108. package/lib/locale/az.d.ts +11 -0
  109. package/lib/locale/az.js +11 -0
  110. package/lib/locale/az.js.map +1 -1
  111. package/lib/locale/bg.d.ts +11 -0
  112. package/lib/locale/bg.js +11 -0
  113. package/lib/locale/bg.js.map +1 -1
  114. package/lib/locale/ca.d.ts +11 -0
  115. package/lib/locale/ca.js +11 -0
  116. package/lib/locale/ca.js.map +1 -1
  117. package/lib/locale/ckb.d.ts +11 -0
  118. package/lib/locale/ckb.js +11 -0
  119. package/lib/locale/ckb.js.map +1 -1
  120. package/lib/locale/cs.d.ts +11 -0
  121. package/lib/locale/cs.js +11 -0
  122. package/lib/locale/cs.js.map +1 -1
  123. package/lib/locale/da.d.ts +11 -0
  124. package/lib/locale/da.js +11 -0
  125. package/lib/locale/da.js.map +1 -1
  126. package/lib/locale/de.d.ts +11 -0
  127. package/lib/locale/de.js +11 -0
  128. package/lib/locale/de.js.map +1 -1
  129. package/lib/locale/el.d.ts +11 -0
  130. package/lib/locale/el.js +11 -0
  131. package/lib/locale/el.js.map +1 -1
  132. package/lib/locale/en.d.ts +11 -0
  133. package/lib/locale/en.js +11 -0
  134. package/lib/locale/en.js.map +1 -1
  135. package/lib/locale/es.d.ts +11 -0
  136. package/lib/locale/es.js +11 -0
  137. package/lib/locale/es.js.map +1 -1
  138. package/lib/locale/et.d.ts +11 -0
  139. package/lib/locale/et.js +11 -0
  140. package/lib/locale/et.js.map +1 -1
  141. package/lib/locale/fa.d.ts +11 -0
  142. package/lib/locale/fa.js +11 -0
  143. package/lib/locale/fa.js.map +1 -1
  144. package/lib/locale/fi.d.ts +11 -0
  145. package/lib/locale/fi.js +11 -0
  146. package/lib/locale/fi.js.map +1 -1
  147. package/lib/locale/fr.d.ts +11 -0
  148. package/lib/locale/fr.js +11 -0
  149. package/lib/locale/fr.js.map +1 -1
  150. package/lib/locale/he.d.ts +11 -0
  151. package/lib/locale/he.js +11 -0
  152. package/lib/locale/he.js.map +1 -1
  153. package/lib/locale/hr.d.ts +11 -0
  154. package/lib/locale/hr.js +11 -0
  155. package/lib/locale/hr.js.map +1 -1
  156. package/lib/locale/hu.d.ts +11 -0
  157. package/lib/locale/hu.js +11 -0
  158. package/lib/locale/hu.js.map +1 -1
  159. package/lib/locale/id.d.ts +11 -0
  160. package/lib/locale/id.js +11 -0
  161. package/lib/locale/id.js.map +1 -1
  162. package/lib/locale/it.d.ts +11 -0
  163. package/lib/locale/it.js +11 -0
  164. package/lib/locale/it.js.map +1 -1
  165. package/lib/locale/ja.d.ts +11 -0
  166. package/lib/locale/ja.js +11 -0
  167. package/lib/locale/ja.js.map +1 -1
  168. package/lib/locale/km.d.ts +11 -0
  169. package/lib/locale/km.js +11 -0
  170. package/lib/locale/km.js.map +1 -1
  171. package/lib/locale/ko.d.ts +11 -0
  172. package/lib/locale/ko.js +11 -0
  173. package/lib/locale/ko.js.map +1 -1
  174. package/lib/locale/lt.d.ts +11 -0
  175. package/lib/locale/lt.js +11 -0
  176. package/lib/locale/lt.js.map +1 -1
  177. package/lib/locale/lv.d.ts +11 -0
  178. package/lib/locale/lv.js +11 -0
  179. package/lib/locale/lv.js.map +1 -1
  180. package/lib/locale/nl.d.ts +11 -0
  181. package/lib/locale/nl.js +11 -0
  182. package/lib/locale/nl.js.map +1 -1
  183. package/lib/locale/no.d.ts +11 -0
  184. package/lib/locale/no.js +11 -0
  185. package/lib/locale/no.js.map +1 -1
  186. package/lib/locale/pl.d.ts +11 -0
  187. package/lib/locale/pl.js +11 -0
  188. package/lib/locale/pl.js.map +1 -1
  189. package/lib/locale/pt.d.ts +11 -0
  190. package/lib/locale/pt.js +11 -0
  191. package/lib/locale/pt.js.map +1 -1
  192. package/lib/locale/ro.d.ts +11 -0
  193. package/lib/locale/ro.js +11 -0
  194. package/lib/locale/ro.js.map +1 -1
  195. package/lib/locale/ru.d.ts +11 -0
  196. package/lib/locale/ru.js +11 -0
  197. package/lib/locale/ru.js.map +1 -1
  198. package/lib/locale/sk.d.ts +11 -0
  199. package/lib/locale/sk.js +11 -0
  200. package/lib/locale/sk.js.map +1 -1
  201. package/lib/locale/sl.d.ts +11 -0
  202. package/lib/locale/sl.js +11 -0
  203. package/lib/locale/sl.js.map +1 -1
  204. package/lib/locale/sr-Cyrl.d.ts +11 -0
  205. package/lib/locale/sr-Cyrl.js +11 -0
  206. package/lib/locale/sr-Cyrl.js.map +1 -1
  207. package/lib/locale/sr-Latn.d.ts +11 -0
  208. package/lib/locale/sr-Latn.js +11 -0
  209. package/lib/locale/sr-Latn.js.map +1 -1
  210. package/lib/locale/sv.d.ts +11 -0
  211. package/lib/locale/sv.js +11 -0
  212. package/lib/locale/sv.js.map +1 -1
  213. package/lib/locale/th.d.ts +11 -0
  214. package/lib/locale/th.js +11 -0
  215. package/lib/locale/th.js.map +1 -1
  216. package/lib/locale/tr.d.ts +11 -0
  217. package/lib/locale/tr.js +11 -0
  218. package/lib/locale/tr.js.map +1 -1
  219. package/lib/locale/uk.d.ts +11 -0
  220. package/lib/locale/uk.js +11 -0
  221. package/lib/locale/uk.js.map +1 -1
  222. package/lib/locale/vi.d.ts +11 -0
  223. package/lib/locale/vi.js +11 -0
  224. package/lib/locale/vi.js.map +1 -1
  225. package/lib/locale/zh-Hans.d.ts +11 -0
  226. package/lib/locale/zh-Hans.js +11 -0
  227. package/lib/locale/zh-Hans.js.map +1 -1
  228. package/lib/locale/zh-Hant.d.ts +11 -0
  229. package/lib/locale/zh-Hant.js +11 -0
  230. package/lib/locale/zh-Hant.js.map +1 -1
  231. package/lib/util/index.d.ts +1 -0
  232. package/lib/util/index.js +1 -0
  233. package/lib/util/index.js.map +1 -1
  234. package/lib/util/timeUtils.d.ts +1 -0
  235. package/lib/util/timeUtils.js +4 -0
  236. package/lib/util/timeUtils.js.map +1 -0
  237. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Vuetify v3.9.2-master.2025-07-22
2
+ * Vuetify v3.9.2-master.2025-07-24
3
3
  * Forged by John Leider
4
4
  * Released under the MIT License.
5
5
  */
@@ -1660,6 +1660,10 @@
1660
1660
  vm.render = render;
1661
1661
  }
1662
1662
 
1663
+ function formatTime(seconds) {
1664
+ return [Math.floor(seconds % 60), Math.floor(seconds / 60 % 60), Math.floor(seconds / 60 / 60 % 60)].filter((x, i) => i < 2 || x > 0).reverse().map(String).map((x, i) => i > 0 ? x.padStart(2, '0') : x).join(':');
1665
+ }
1666
+
1663
1667
  // Utilities
1664
1668
 
1665
1669
  // Types
@@ -2180,6 +2184,17 @@
2180
2184
  option: 'Option',
2181
2185
  plus: 'plus',
2182
2186
  shortcut: 'Keyboard shortcut: {0}'
2187
+ },
2188
+ video: {
2189
+ play: 'Play',
2190
+ pause: 'Pause',
2191
+ seek: 'Seek',
2192
+ volume: 'Volume',
2193
+ showVolume: 'Show volume control',
2194
+ mute: 'Mute',
2195
+ unmute: 'Unmute',
2196
+ enterFullscreen: 'Full screen',
2197
+ exitFullscreen: 'Exit full screen'
2183
2198
  }
2184
2199
  };
2185
2200
 
@@ -4262,7 +4277,7 @@
4262
4277
 
4263
4278
  // Types
4264
4279
 
4265
- const allowedVariants$2 = ['elevated', 'flat', 'tonal', 'outlined', 'text', 'plain'];
4280
+ const allowedVariants$4 = ['elevated', 'flat', 'tonal', 'outlined', 'text', 'plain'];
4266
4281
  function genOverlays(isClickable, name) {
4267
4282
  return vue.createElementVNode(vue.Fragment, null, [isClickable && vue.createElementVNode("span", {
4268
4283
  "key": "overlay",
@@ -4277,7 +4292,7 @@
4277
4292
  variant: {
4278
4293
  type: String,
4279
4294
  default: 'elevated',
4280
- validator: v => allowedVariants$2.includes(v)
4295
+ validator: v => allowedVariants$4.includes(v)
4281
4296
  }
4282
4297
  }, 'variant');
4283
4298
  function useVariant(props) {
@@ -4701,7 +4716,15 @@
4701
4716
  arrowdown: 'mdi-arrow-down',
4702
4717
  arrowleft: 'mdi-arrow-left',
4703
4718
  arrowright: 'mdi-arrow-right',
4704
- backspace: 'mdi-backspace'
4719
+ backspace: 'mdi-backspace',
4720
+ play: 'mdi-play',
4721
+ pause: 'mdi-pause',
4722
+ fullscreen: 'mdi-fullscreen',
4723
+ fullscreenExit: 'mdi-fullscreen-exit',
4724
+ volumeHigh: 'mdi-volume-high',
4725
+ volumeMedium: 'mdi-volume-medium',
4726
+ volumeLow: 'mdi-volume-low',
4727
+ volumeOff: 'mdi-volume-variant-off'
4705
4728
  };
4706
4729
  const mdi = {
4707
4730
  // Not using mergeProps here, functional components merge props by default (?)
@@ -5280,7 +5303,8 @@
5280
5303
  'v-progress-linear--reverse': isReversed.value,
5281
5304
  'v-progress-linear--rounded': props.rounded,
5282
5305
  'v-progress-linear--rounded-bar': props.roundedBar,
5283
- 'v-progress-linear--striped': props.striped
5306
+ 'v-progress-linear--striped': props.striped,
5307
+ 'v-progress-linear--clickable': props.clickable
5284
5308
  }, roundedClasses.value, themeClasses.value, rtlClasses.value, props.class]),
5285
5309
  "style": vue.normalizeStyle([{
5286
5310
  bottom: props.location === 'bottom' ? 0 : undefined,
@@ -10830,11 +10854,12 @@
10830
10854
  function onScroll(e) {
10831
10855
  data.isActive.value = false;
10832
10856
  }
10833
- bindScroll(data.targetEl.value ?? data.contentEl.value, onScroll);
10857
+ bindScroll(data.target.value ?? data.contentEl.value, onScroll);
10834
10858
  }
10835
10859
  function blockScrollStrategy(data, props) {
10836
10860
  const offsetParent = data.root.value?.offsetParent;
10837
- const scrollElements = [...new Set([...getScrollParents(data.targetEl.value, props.contained ? offsetParent : undefined), ...getScrollParents(data.contentEl.value, props.contained ? offsetParent : undefined)])].filter(el => !el.classList.contains('v-overlay-scroll-blocked'));
10861
+ const target = Array.isArray(data.target.value) ? document.elementFromPoint(...data.target.value) : data.target.value;
10862
+ const scrollElements = [...new Set([...getScrollParents(target, props.contained ? offsetParent : undefined), ...getScrollParents(data.contentEl.value, props.contained ? offsetParent : undefined)])].filter(el => !el.classList.contains('v-overlay-scroll-blocked'));
10838
10863
  const scrollbarWidth = window.innerWidth - document.documentElement.offsetWidth;
10839
10864
  const scrollableParent = (el => hasScrollbar(el) && el)(offsetParent || document.documentElement);
10840
10865
  if (scrollableParent) {
@@ -10881,7 +10906,7 @@
10881
10906
  }
10882
10907
  ric = (typeof requestIdleCallback === 'undefined' ? cb => cb() : requestIdleCallback)(() => {
10883
10908
  scope.run(() => {
10884
- bindScroll(data.targetEl.value ?? data.contentEl.value, e => {
10909
+ bindScroll(data.target.value ?? data.contentEl.value, e => {
10885
10910
  if (slow) {
10886
10911
  // If the position calculation is slow,
10887
10912
  // defer updates until scrolling is finished.
@@ -10906,7 +10931,8 @@
10906
10931
  }
10907
10932
 
10908
10933
  /** @private */
10909
- function bindScroll(el, onScroll) {
10934
+ function bindScroll(target, onScroll) {
10935
+ const el = Array.isArray(target) ? document.elementFromPoint(...target) : target;
10910
10936
  const scrollElements = [document, ...getScrollParents(el)];
10911
10937
  scrollElements.forEach(el => {
10912
10938
  el.addEventListener('scroll', onScroll, {
@@ -11588,6 +11614,7 @@
11588
11614
  root,
11589
11615
  contentEl,
11590
11616
  targetEl,
11617
+ target,
11591
11618
  isActive,
11592
11619
  updateLocation
11593
11620
  });
@@ -11983,7 +12010,7 @@
11983
12010
 
11984
12011
  // Types
11985
12012
 
11986
- const allowedVariants$1 = ['underlined', 'outlined', 'filled', 'solo', 'solo-inverted', 'solo-filled', 'plain'];
12013
+ const allowedVariants$3 = ['underlined', 'outlined', 'filled', 'solo', 'solo-inverted', 'solo-filled', 'plain'];
11987
12014
  const makeVFieldProps = propsFactory({
11988
12015
  appendInnerIcon: IconValue,
11989
12016
  bgColor: String,
@@ -12017,7 +12044,7 @@
12017
12044
  variant: {
12018
12045
  type: String,
12019
12046
  default: 'filled',
12020
- validator: v => allowedVariants$1.includes(v)
12047
+ validator: v => allowedVariants$3.includes(v)
12021
12048
  },
12022
12049
  'onClick:clear': EventProp(),
12023
12050
  'onClick:appendInner': EventProp(),
@@ -12365,7 +12392,7 @@
12365
12392
  if (!isFocused.value) focus();
12366
12393
  vue.nextTick(() => {
12367
12394
  if (inputRef.value !== document.activeElement) {
12368
- vue.nextTick(() => inputRef.value?.focus());
12395
+ inputRef.value?.focus();
12369
12396
  }
12370
12397
  });
12371
12398
  }
@@ -12468,7 +12495,7 @@
12468
12495
  "placeholder": props.placeholder,
12469
12496
  "size": 1,
12470
12497
  "type": props.type,
12471
- "onFocus": onFocus,
12498
+ "onFocus": focus,
12472
12499
  "onBlur": blur
12473
12500
  }, slotProps, inputAttrs), null), [[Intersect, {
12474
12501
  handler: onIntersect
@@ -16179,6 +16206,7 @@
16179
16206
  validator: v => ['vertical', 'horizontal'].includes(v)
16180
16207
  },
16181
16208
  reverse: Boolean,
16209
+ noKeyboard: Boolean,
16182
16210
  ...makeRoundedProps(),
16183
16211
  ...makeElevationProps({
16184
16212
  elevation: 2
@@ -16382,6 +16410,7 @@
16382
16410
  min,
16383
16411
  max,
16384
16412
  mousePressed,
16413
+ noKeyboard: vue.toRef(() => props.noKeyboard),
16385
16414
  numTicks,
16386
16415
  onSliderMousedown,
16387
16416
  onSliderTouchstart,
@@ -16434,6 +16463,7 @@
16434
16463
  default: true
16435
16464
  },
16436
16465
  name: String,
16466
+ noKeyboard: Boolean,
16437
16467
  ...makeComponentProps()
16438
16468
  }, 'VSliderThumb');
16439
16469
  const VSliderThumb = genericComponent()({
@@ -16496,6 +16526,7 @@
16496
16526
  if (step.value) return [1, 2, 3];else return [1, 5, 10];
16497
16527
  });
16498
16528
  function parseKeydown(e, value) {
16529
+ if (props.noKeyboard) return;
16499
16530
  if (!relevantKeys.includes(e.key)) return;
16500
16531
  e.preventDefault();
16501
16532
  const _step = step.value || 0.1;
@@ -16731,7 +16762,8 @@
16731
16762
  trackContainerRef,
16732
16763
  position,
16733
16764
  hasLabels,
16734
- readonly
16765
+ readonly,
16766
+ noKeyboard
16735
16767
  } = useSlider({
16736
16768
  props,
16737
16769
  steps,
@@ -16807,6 +16839,7 @@
16807
16839
  "ref": thumbContainerRef,
16808
16840
  "aria-describedby": messagesId.value,
16809
16841
  "focused": isFocused.value,
16842
+ "noKeyboard": noKeyboard.value,
16810
16843
  "min": min.value,
16811
16844
  "max": max.value,
16812
16845
  "modelValue": model.value,
@@ -17525,7 +17558,13 @@
17525
17558
  return weeks;
17526
17559
  }
17527
17560
  function startOfWeek(date, locale, firstDayOfWeek) {
17528
- const day = firstDayOfWeek ?? weekInfo(locale)?.firstDay ?? 0;
17561
+ let day = (firstDayOfWeek ?? weekInfo(locale)?.firstDay ?? 0) % 7;
17562
+
17563
+ // prevent infinite loop
17564
+ if (![0, 1, 2, 3, 4, 5, 6].includes(day)) {
17565
+ consoleWarn('Invalid firstDayOfWeek, expected discrete number in range [0-6]');
17566
+ day = 0;
17567
+ }
17529
17568
  const d = new Date(date);
17530
17569
  while (d.getDay() !== day) {
17531
17570
  d.setDate(d.getDate() - 1);
@@ -21143,7 +21182,6 @@
21143
21182
  "appendIcon": appendIcon.value,
21144
21183
  "onClick:append": () => selectAll(!allSelected.value)
21145
21184
  }, {
21146
- ...slots,
21147
21185
  chip: props => vue.createVNode(VChip, {
21148
21186
  "onClick": props.item.raw?.sortable ? () => toggleSort(props.item.raw) : undefined,
21149
21187
  "onMousedown": e => {
@@ -23878,7 +23916,7 @@
23878
23916
 
23879
23917
  // Types
23880
23918
 
23881
- const allowedVariants = ['default', 'accordion', 'inset', 'popout'];
23919
+ const allowedVariants$2 = ['default', 'accordion', 'inset', 'popout'];
23882
23920
  const makeVExpansionPanelsProps = propsFactory({
23883
23921
  flat: Boolean,
23884
23922
  ...makeGroupProps(),
@@ -23889,7 +23927,7 @@
23889
23927
  variant: {
23890
23928
  type: String,
23891
23929
  default: 'default',
23892
- validator: v => allowedVariants.includes(v)
23930
+ validator: v => allowedVariants$2.includes(v)
23893
23931
  }
23894
23932
  }, 'VExpansionPanels');
23895
23933
  const VExpansionPanels = genericComponent()({
@@ -30263,6 +30301,7 @@
30263
30301
  // Types
30264
30302
 
30265
30303
  const makeVTreeviewChildrenProps = propsFactory({
30304
+ fluid: Boolean,
30266
30305
  disabled: Boolean,
30267
30306
  loadChildren: Function,
30268
30307
  loadingIcon: {
@@ -30344,7 +30383,7 @@
30344
30383
  depth,
30345
30384
  isLast,
30346
30385
  isLastGroup: props.isLastGroup,
30347
- leafLinks: !props.hideActions,
30386
+ leafLinks: !props.hideActions && !props.fluid,
30348
30387
  separateRoots: props.separateRoots,
30349
30388
  parentIndentLines: props.parentIndentLines,
30350
30389
  variant: props.indentLinesVariant
@@ -30461,7 +30500,6 @@
30461
30500
  return flat;
30462
30501
  }
30463
30502
  const makeVTreeviewProps = propsFactory({
30464
- fluid: Boolean,
30465
30503
  openAll: Boolean,
30466
30504
  indentLines: [Boolean, String],
30467
30505
  search: String,
@@ -31753,7 +31791,7 @@
31753
31791
  "style": props.style
31754
31792
  }), {
31755
31793
  ...slots,
31756
- title: () => props?.title ?? props.file?.name,
31794
+ title: slots.title ?? (() => props?.title ?? props.file?.name),
31757
31795
  prepend: slotProps => vue.createElementVNode(vue.Fragment, null, [!slots.prepend ? vue.createVNode(VAvatar, {
31758
31796
  "icon": props.fileIcon,
31759
31797
  "image": preview.value,
@@ -33180,6 +33218,707 @@
33180
33218
  }
33181
33219
  });
33182
33220
 
33221
+ // Types
33222
+
33223
+ const makeVVideoVolumeProps = propsFactory({
33224
+ inline: Boolean,
33225
+ label: String,
33226
+ direction: {
33227
+ type: String,
33228
+ default: 'vertical'
33229
+ },
33230
+ modelValue: {
33231
+ type: Number,
33232
+ default: 0
33233
+ },
33234
+ menuProps: Object,
33235
+ sliderProps: Object,
33236
+ onClick: EventProp(),
33237
+ ...makeComponentProps()
33238
+ }, 'VVideoVolume');
33239
+ const VVideoVolume = genericComponent()({
33240
+ name: 'VVideoVolume',
33241
+ props: makeVVideoVolumeProps(),
33242
+ emits: {
33243
+ 'update:modelValue': val => true
33244
+ },
33245
+ setup(props, _ref) {
33246
+ let {
33247
+ attrs
33248
+ } = _ref;
33249
+ const {
33250
+ t
33251
+ } = useLocale();
33252
+ const volume = useProxiedModel(props, 'modelValue');
33253
+ const volumeIcon = vue.toRef(() => volume.value > 70 ? '$volumeHigh' : volume.value > 40 ? '$volumeMedium' : volume.value > 10 ? '$volumeLow' : '$volumeOff');
33254
+ const containerRef = vue.ref();
33255
+ useRender(() => {
33256
+ const sliderDefaults = {
33257
+ hideDetails: true,
33258
+ step: 5,
33259
+ thumbSize: 16
33260
+ };
33261
+ return vue.createElementVNode("div", {
33262
+ "class": vue.normalizeClass(['v-video-volume', {
33263
+ 'v-video-volume--inline': props.inline
33264
+ }, props.class]),
33265
+ "style": vue.normalizeStyle(props.style),
33266
+ "ref": containerRef
33267
+ }, [vue.withDirectives(vue.createVNode(VIconBtn, vue.mergeProps({
33268
+ "icon": volumeIcon.value,
33269
+ "aria-label": props.label,
33270
+ "onClick": props.onClick
33271
+ }, attrs), {
33272
+ default: () => [vue.createVNode(VIcon, null, null), !props.inline && vue.createVNode(VMenu, {
33273
+ "offset": "8",
33274
+ "activator": "parent",
33275
+ "attach": containerRef.value,
33276
+ "location": props.menuProps?.location ?? 'top center',
33277
+ "closeOnContentClick": false
33278
+ }, {
33279
+ default: () => [vue.createElementVNode("div", {
33280
+ "class": vue.normalizeClass(['v-video-volume__menu', `v-video-volume__menu--${props.direction}`])
33281
+ }, [vue.createVNode(VSlider, vue.mergeProps({
33282
+ "direction": props.direction,
33283
+ "aria-label": t('$vuetify.video.volume'),
33284
+ "modelValue": volume.value,
33285
+ "onUpdate:modelValue": v => volume.value = v
33286
+ }, sliderDefaults, props.sliderProps), null)])]
33287
+ })]
33288
+ }), [[vue.resolveDirective("tooltip"), props.label, 'top']]), props.inline && vue.createVNode(VSlider, vue.mergeProps({
33289
+ "class": "v-video-volume-inline__slider",
33290
+ "minWidth": "50",
33291
+ "aria-label": t('$vuetify.video.volume'),
33292
+ "modelValue": volume.value,
33293
+ "onUpdate:modelValue": v => volume.value = v,
33294
+ "onKeydown": e => {
33295
+ e.stopPropagation();
33296
+ }
33297
+ }, sliderDefaults, props.sliderProps), null)]);
33298
+ });
33299
+ }
33300
+ });
33301
+
33302
+ // Types
33303
+
33304
+ const allowedVariants$1 = ['hidden', 'default', 'tube', 'mini'];
33305
+ const makeVVideoControlsProps = propsFactory({
33306
+ color: String,
33307
+ backgroundColor: String,
33308
+ trackColor: String,
33309
+ playing: Boolean,
33310
+ hidePlay: Boolean,
33311
+ hideVolume: Boolean,
33312
+ hideFullscreen: Boolean,
33313
+ fullscreen: Boolean,
33314
+ floating: Boolean,
33315
+ splitTime: Boolean,
33316
+ pills: Boolean,
33317
+ detached: Boolean,
33318
+ progress: {
33319
+ type: Number,
33320
+ default: 0
33321
+ },
33322
+ duration: {
33323
+ type: Number,
33324
+ default: 0
33325
+ },
33326
+ volume: [Number, String],
33327
+ variant: {
33328
+ type: String,
33329
+ default: 'default',
33330
+ validator: v => allowedVariants$1.includes(v)
33331
+ },
33332
+ volumeProps: Object,
33333
+ ...makeDensityProps(),
33334
+ ...makeElevationProps(),
33335
+ ...makeThemeProps()
33336
+ }, 'VVideoControls');
33337
+ const VVideoControls = genericComponent()({
33338
+ name: 'VVideoControls',
33339
+ props: makeVVideoControlsProps(),
33340
+ emits: {
33341
+ 'update:playing': val => true,
33342
+ 'update:progress': val => true,
33343
+ 'update:volume': val => true,
33344
+ skip: val => true,
33345
+ 'click:fullscreen': () => true
33346
+ },
33347
+ setup(props, _ref) {
33348
+ let {
33349
+ emit,
33350
+ slots
33351
+ } = _ref;
33352
+ const {
33353
+ t
33354
+ } = useLocale();
33355
+ const {
33356
+ themeClasses
33357
+ } = provideTheme(props);
33358
+ const {
33359
+ densityClasses
33360
+ } = useDensity(props);
33361
+ const {
33362
+ elevationClasses
33363
+ } = useElevation(props);
33364
+ const {
33365
+ backgroundColorClasses,
33366
+ backgroundColorStyles
33367
+ } = useBackgroundColor(() => {
33368
+ const fallbackBackground = props.detached ? 'surface' : undefined;
33369
+ return props.backgroundColor ?? fallbackBackground;
33370
+ });
33371
+ const playing = useProxiedModel(props, 'playing');
33372
+ const progress = useProxiedModel(props, 'progress');
33373
+ const volume = useProxiedModel(props, 'volume', 0, v => Number(v ?? 0));
33374
+ const lastVolume = vue.shallowRef();
33375
+ const currentTime = vue.computed(() => {
33376
+ const secondsElapsed = Math.round(props.progress / 100 * props.duration);
33377
+ return {
33378
+ elapsed: formatTime(secondsElapsed),
33379
+ remaining: formatTime(props.duration - secondsElapsed),
33380
+ total: formatTime(props.duration)
33381
+ };
33382
+ });
33383
+ const labels = vue.computed(() => {
33384
+ const playIconLocaleKey = playing.value ? 'pause' : 'play';
33385
+ const volumeIconLocaleKey = props.volumeProps?.inline ? volume.value ? 'mute' : 'unmute' : 'showVolume';
33386
+ const fullscreenIconLocaleKey = props.fullscreen ? 'exitFullscreen' : 'enterFullscreen';
33387
+ return {
33388
+ seek: t('$vuetify.video.seek'),
33389
+ volume: t('$vuetify.video.volume'),
33390
+ playAction: t(`$vuetify.video.${playIconLocaleKey}`),
33391
+ volumeAction: t(`$vuetify.video.${volumeIconLocaleKey}`),
33392
+ fullscreenAction: t(`$vuetify.video.${fullscreenIconLocaleKey}`)
33393
+ };
33394
+ });
33395
+ function play() {
33396
+ playing.value = true;
33397
+ }
33398
+ function pause() {
33399
+ playing.value = false;
33400
+ }
33401
+ function skipTo(v) {
33402
+ progress.value = v;
33403
+ }
33404
+ function toggleMuted() {
33405
+ if (volume.value) {
33406
+ lastVolume.value = volume.value;
33407
+ volume.value = 0;
33408
+ } else {
33409
+ volume.value = lastVolume.value ?? 100;
33410
+ }
33411
+ }
33412
+ function toggleFullscreen() {
33413
+ emit('click:fullscreen');
33414
+ }
33415
+ useRender(() => {
33416
+ const sizes = props.pills ? [42, 36, 30] : [32, 28, 24];
33417
+ const innerDefaults = {
33418
+ VIconBtn: {
33419
+ size: props.density === 'compact' ? sizes[2] : props.density === 'comfortable' ? sizes[1] : sizes[0],
33420
+ iconSize: props.density === 'compact' ? 20 : props.density === 'comfortable' ? 24 : 26,
33421
+ variant: 'text',
33422
+ color: props.color
33423
+ },
33424
+ VSlider: {
33425
+ thumbSize: props.variant === 'tube' ? 10 : 16,
33426
+ hideDetails: true
33427
+ }
33428
+ };
33429
+ const regularBtnSize = innerDefaults.VIconBtn.size;
33430
+ const playBtnSize = props.pills ? regularBtnSize + 8 : regularBtnSize;
33431
+ const pillClasses = ['v-video-control__pill', props.pills ? elevationClasses.value : []];
33432
+ const slotProps = {
33433
+ play,
33434
+ pause,
33435
+ playing: playing.value,
33436
+ progress: progress.value,
33437
+ currentTime: currentTime.value,
33438
+ skipTo,
33439
+ volume,
33440
+ toggleMuted,
33441
+ fullscreen: props.fullscreen,
33442
+ toggleFullscreen,
33443
+ labels: labels.value
33444
+ };
33445
+ return vue.createElementVNode("div", {
33446
+ "class": vue.normalizeClass(['v-video-controls', `v-video-controls--variant-${props.variant}`, {
33447
+ 'v-video-controls--pills': props.pills
33448
+ }, {
33449
+ 'v-video-controls--detached': props.detached
33450
+ }, {
33451
+ 'v-video-controls--floating': props.floating
33452
+ }, {
33453
+ 'v-video-controls--split-time': props.splitTime
33454
+ }, backgroundColorClasses.value, props.detached && !props.pills ? elevationClasses.value : [], densityClasses.value, themeClasses.value]),
33455
+ "style": vue.normalizeStyle([backgroundColorStyles.value, {
33456
+ '--v-video-controls-pill-height': `${regularBtnSize}px`
33457
+ }])
33458
+ }, [vue.createVNode(VDefaultsProvider, {
33459
+ "defaults": innerDefaults
33460
+ }, {
33461
+ default: () => [slots.default?.(slotProps) ?? vue.createElementVNode(vue.Fragment, null, [props.variant !== 'mini' && vue.createElementVNode(vue.Fragment, null, [!props.hidePlay && vue.createElementVNode("div", {
33462
+ "class": vue.normalizeClass([pillClasses, 'v-video__action-play'])
33463
+ }, [vue.withDirectives(vue.createVNode(VIconBtn, {
33464
+ "icon": playing.value ? '$pause' : '$play',
33465
+ "size": playBtnSize,
33466
+ "aria-label": labels.value.playAction,
33467
+ "onClick": () => playing.value = !playing.value
33468
+ }, null), [[vue.resolveDirective("tooltip"), labels.value.playAction, 'top']])]), slots.prepend && vue.createElementVNode("div", {
33469
+ "class": vue.normalizeClass(pillClasses)
33470
+ }, [slots.prepend(slotProps)]), props.splitTime ? vue.createElementVNode("span", {
33471
+ "class": vue.normalizeClass([pillClasses, 'v-video__time'])
33472
+ }, [currentTime.value.elapsed]) : props.variant !== 'default' ? vue.createElementVNode("span", {
33473
+ "class": vue.normalizeClass([pillClasses, 'v-video__time'])
33474
+ }, [currentTime.value.elapsed, vue.createTextVNode(" / "), currentTime.value.total]) : '', vue.createVNode(VSlider, {
33475
+ "modelValue": props.progress,
33476
+ "noKeyboard": true,
33477
+ "color": props.trackColor ?? props.color,
33478
+ "trackColor": props.variant === 'tube' ? 'white' : undefined,
33479
+ "class": "v-video__track",
33480
+ "thumbLabel": "always",
33481
+ "aria-label": labels.value.seek,
33482
+ "onUpdate:modelValue": skipTo
33483
+ }, {
33484
+ 'thumb-label': () => currentTime.value.elapsed
33485
+ }), props.variant === 'tube' && vue.createVNode(VSpacer, null, null), props.splitTime ? vue.createElementVNode("span", {
33486
+ "class": vue.normalizeClass([pillClasses, 'v-video__time'])
33487
+ }, [currentTime.value.remaining]) : '']), props.variant === 'mini' && vue.createElementVNode(vue.Fragment, null, [vue.createVNode(VSpacer, null, null), slots.prepend && vue.createElementVNode("div", {
33488
+ "class": vue.normalizeClass(pillClasses)
33489
+ }, [slots.prepend(slotProps)]), !props.hidePlay && vue.createElementVNode("div", {
33490
+ "class": vue.normalizeClass([pillClasses, 'v-video__action-play'])
33491
+ }, [vue.withDirectives(vue.createVNode(VIconBtn, {
33492
+ "icon": playing.value ? '$pause' : '$play',
33493
+ "size": playBtnSize,
33494
+ "aria-label": labels.value.playAction,
33495
+ "onClick": () => playing.value = !playing.value
33496
+ }, null), [[vue.resolveDirective("tooltip"), labels.value.playAction, 'top']])])]), (!props.hideVolume || !props.hideFullscreen || slots.append) && vue.createElementVNode("div", {
33497
+ "class": vue.normalizeClass(pillClasses)
33498
+ }, [!props.hideVolume && vue.createVNode(VVideoVolume, vue.mergeProps({
33499
+ "key": "volume-control",
33500
+ "sliderProps": {
33501
+ color: props.color
33502
+ },
33503
+ "modelValue": volume.value,
33504
+ "label": labels.value.volumeAction,
33505
+ "onUpdate:modelValue": v => volume.value = v,
33506
+ "onClick": () => props.volumeProps?.inline && toggleMuted()
33507
+ }, props.volumeProps), null), slots.append?.(slotProps), !props.hideFullscreen && vue.withDirectives(vue.createVNode(VIconBtn, {
33508
+ "icon": props.fullscreen ? '$fullscreenExit' : '$fullscreen',
33509
+ "aria-label": labels.value.fullscreenAction,
33510
+ "onClick": toggleFullscreen
33511
+ }, null), [[vue.resolveDirective("tooltip"), labels.value.fullscreenAction, 'top']])]), props.variant === 'mini' && vue.createVNode(VSpacer, null, null)])]
33512
+ })]);
33513
+ });
33514
+ return {
33515
+ toggleMuted
33516
+ };
33517
+ }
33518
+ });
33519
+
33520
+ // Types
33521
+
33522
+ const allowedVariants = ['background', 'player'];
33523
+ const makeVVideoProps = propsFactory({
33524
+ autoplay: Boolean,
33525
+ muted: Boolean,
33526
+ eager: Boolean,
33527
+ src: String,
33528
+ type: String,
33529
+ // e.g. video/mp4
33530
+ image: String,
33531
+ hideOverlay: Boolean,
33532
+ noFullscreen: Boolean,
33533
+ startAt: [Number, String],
33534
+ variant: {
33535
+ type: String,
33536
+ default: 'player',
33537
+ validator: v => allowedVariants.includes(v)
33538
+ },
33539
+ controlsTransition: {
33540
+ type: [Boolean, String, Object],
33541
+ component: VFadeTransition
33542
+ },
33543
+ controlsVariant: {
33544
+ type: String,
33545
+ default: 'default'
33546
+ },
33547
+ controlsProps: {
33548
+ type: Object
33549
+ },
33550
+ rounded: [Boolean, Number, String, Array],
33551
+ ...makeComponentProps(),
33552
+ ...makeDensityProps(),
33553
+ ...makeDimensionProps(),
33554
+ ...makeThemeProps(),
33555
+ ...omit(makeVVideoControlsProps(), ['fullscreen', 'variant'])
33556
+ }, 'VVideo');
33557
+ const VVideo = genericComponent()({
33558
+ name: 'VVideo',
33559
+ inheritAttrs: false,
33560
+ props: makeVVideoProps(),
33561
+ emits: {
33562
+ loaded: element => true,
33563
+ 'update:playing': val => true,
33564
+ 'update:progress': val => true,
33565
+ 'update:volume': val => true
33566
+ },
33567
+ setup(props, _ref) {
33568
+ let {
33569
+ attrs,
33570
+ emit,
33571
+ slots
33572
+ } = _ref;
33573
+ const {
33574
+ themeClasses
33575
+ } = provideTheme(props);
33576
+ const {
33577
+ densityClasses
33578
+ } = useDensity(props);
33579
+ const {
33580
+ dimensionStyles
33581
+ } = useDimension(props);
33582
+ const {
33583
+ elevationClasses
33584
+ } = useElevation(props);
33585
+ const {
33586
+ ssr
33587
+ } = useDisplay();
33588
+ const roundedForContainer = vue.toRef(() => Array.isArray(props.rounded) ? props.rounded[0] : props.rounded);
33589
+ const roundedForControls = vue.toRef(() => Array.isArray(props.rounded) ? props.rounded.at(-1) : props.rounded ?? false);
33590
+ const {
33591
+ roundedClasses: roundedContainerClasses
33592
+ } = useRounded(roundedForContainer);
33593
+ const {
33594
+ roundedClasses: roundedControlsClasses
33595
+ } = useRounded(roundedForControls);
33596
+ const containerRef = vue.ref();
33597
+ const videoRef = vue.ref();
33598
+ const controlsRef = vue.ref();
33599
+ const playing = useProxiedModel(props, 'playing');
33600
+ const progress = useProxiedModel(props, 'progress');
33601
+ const volume = useProxiedModel(props, 'volume', 0, v => Number(v ?? 0));
33602
+ const fullscreen = vue.shallowRef(false);
33603
+ const waiting = vue.shallowRef(false);
33604
+ const triggered = vue.shallowRef(false);
33605
+ const startAfterLoad = vue.shallowRef(false);
33606
+ const state = vue.shallowRef(props.autoplay ? 'loading' : 'idle');
33607
+ const duration = vue.shallowRef(0);
33608
+ const fullscreenEnabled = vue.toRef(() => !props.noFullscreen && !String(attrs.controlsList ?? '').includes('nofullscreen'));
33609
+ function onTimeupdate() {
33610
+ const {
33611
+ currentTime,
33612
+ duration
33613
+ } = videoRef.value;
33614
+ progress.value = duration === 0 ? 0 : 100 * currentTime / duration;
33615
+ }
33616
+ async function onTriggered() {
33617
+ await vue.nextTick();
33618
+ if (!videoRef.value) return;
33619
+ videoRef.value.addEventListener('timeupdate', onTimeupdate);
33620
+ videoRef.value.volume = volume.value / 100;
33621
+ if (state.value !== 'loaded') {
33622
+ state.value = 'loading';
33623
+ }
33624
+ }
33625
+ function onVideoLoaded() {
33626
+ state.value = 'loaded';
33627
+ duration.value = videoRef.value.duration;
33628
+ const startTime = Number(props.startAt ?? 0);
33629
+ if (startTime && startTime <= duration.value) {
33630
+ videoRef.value.currentTime = startTime;
33631
+ progress.value = duration.value === 0 ? 0 : 100 * startTime / duration.value;
33632
+ }
33633
+ if (startAfterLoad.value) {
33634
+ setTimeout(() => playing.value = true, 100);
33635
+ }
33636
+ emit('loaded', videoRef.value);
33637
+ }
33638
+ function onClick() {
33639
+ if (state.value !== 'loaded') {
33640
+ triggered.value = true;
33641
+ startAfterLoad.value = !startAfterLoad.value;
33642
+ }
33643
+ }
33644
+ function onKeydown(e) {
33645
+ if (!videoRef.value || e.ctrlKey) return;
33646
+ if (e.key.startsWith('Arrow')) {
33647
+ e.preventDefault();
33648
+ }
33649
+ switch (true) {
33650
+ case e.key === ' ':
33651
+ {
33652
+ if (!['A', 'BUTTON'].includes(e.target?.tagName)) {
33653
+ e.preventDefault();
33654
+ playing.value = !playing.value;
33655
+ }
33656
+ break;
33657
+ }
33658
+ case e.key === 'ArrowRight':
33659
+ {
33660
+ const step = 10 * (e.shiftKey ? 6 : 1);
33661
+ videoRef.value.currentTime = Math.min(videoRef.value.currentTime + step, duration.value);
33662
+ // TODO: show skip indicator
33663
+ break;
33664
+ }
33665
+ case e.key === 'ArrowLeft':
33666
+ {
33667
+ const step = 10 * (e.shiftKey ? 6 : 1);
33668
+ videoRef.value.currentTime = Math.max(videoRef.value.currentTime - step, 0);
33669
+ // TODO: show skip indicator
33670
+ break;
33671
+ }
33672
+ case createRange(10).map(String).includes(e.key):
33673
+ {
33674
+ skipTo(Number(e.key) * 10);
33675
+ break;
33676
+ }
33677
+ case e.key === 'ArrowUp':
33678
+ {
33679
+ volume.value = Math.min(volume.value + 10, 100);
33680
+ // TODO: show volume change indicator
33681
+ break;
33682
+ }
33683
+ case e.key === 'ArrowDown':
33684
+ {
33685
+ volume.value = Math.max(volume.value - 10, 0);
33686
+ // TODO: show volume change indicator
33687
+ break;
33688
+ }
33689
+ case e.key === 'm':
33690
+ {
33691
+ controlsRef.value?.toggleMuted();
33692
+ break;
33693
+ }
33694
+ case e.key === 'f':
33695
+ {
33696
+ toggleFullscreen();
33697
+ break;
33698
+ }
33699
+ }
33700
+ }
33701
+ function skipTo(v) {
33702
+ if (!videoRef.value) return;
33703
+ progress.value = v;
33704
+ videoRef.value.currentTime = duration.value * v / 100;
33705
+ }
33706
+ vue.watch(() => props.src, v => {
33707
+ progress.value = 0;
33708
+ });
33709
+ vue.watch(playing, v => {
33710
+ if (!videoRef.value) return;
33711
+ if (v) {
33712
+ videoRef.value.play();
33713
+ } else {
33714
+ videoRef.value.pause();
33715
+ }
33716
+ });
33717
+ vue.watch(volume, v => {
33718
+ if (!videoRef.value) return;
33719
+ videoRef.value.volume = v / 100;
33720
+ });
33721
+ vue.watch(triggered, () => onTriggered(), {
33722
+ once: true
33723
+ });
33724
+ vue.watch(() => props.eager, v => v && (triggered.value = true), {
33725
+ immediate: true
33726
+ });
33727
+ vue.onMounted(() => {
33728
+ if (props.autoplay && !ssr) {
33729
+ triggered.value = true;
33730
+ startAfterLoad.value = true;
33731
+ }
33732
+ });
33733
+ vue.onBeforeUnmount(() => {
33734
+ videoRef.value?.removeEventListener('timeupdate', onTimeupdate);
33735
+ });
33736
+ function focusSlider() {
33737
+ const container = videoRef.value?.closest('.v-video');
33738
+ const innerSlider = container?.querySelector('[role="slider"]');
33739
+ innerSlider?.focus();
33740
+ }
33741
+ function fullscreenExitShortcut(e) {
33742
+ if (['ESC', 'f'].includes(e.key)) {
33743
+ toggleFullscreen();
33744
+ document.body.removeEventListener('keydown', fullscreenExitShortcut);
33745
+ }
33746
+ }
33747
+ async function toggleFullscreen() {
33748
+ if (!fullscreenEnabled.value || !document.fullscreenEnabled) {
33749
+ return;
33750
+ }
33751
+ if (document.fullscreenElement) {
33752
+ document.exitFullscreen();
33753
+ onFullscreenExit();
33754
+ } else {
33755
+ await containerRef.value?.requestFullscreen();
33756
+ document.body.addEventListener('keydown', fullscreenExitShortcut);
33757
+ document.addEventListener('fullscreenchange', onFullscreenExit);
33758
+ fullscreen.value = true;
33759
+ }
33760
+ }
33761
+ function onFullscreenExit() {
33762
+ // event fires with a delay after requestFullscreen(), ignore first run
33763
+ if (document.fullscreenElement) return;
33764
+ focusSlider();
33765
+ fullscreen.value = false;
33766
+ document.body.removeEventListener('keydown', fullscreenExitShortcut);
33767
+ document.removeEventListener('fullscreenchange', onFullscreenExit);
33768
+ }
33769
+ function onVideoClick(e) {
33770
+ e.preventDefault();
33771
+ if (state.value === 'loaded') {
33772
+ playing.value = !playing.value;
33773
+ focusSlider();
33774
+ }
33775
+ }
33776
+ function onDoubleClick(e) {
33777
+ e.preventDefault();
33778
+ toggleFullscreen();
33779
+ }
33780
+ let lastTap = 0;
33781
+ function onTouchend(e) {
33782
+ const now = performance.now();
33783
+ if (now - lastTap < 500) {
33784
+ e.preventDefault();
33785
+ toggleFullscreen();
33786
+ } else {
33787
+ lastTap = now;
33788
+ }
33789
+ }
33790
+ useRender(() => {
33791
+ const showControls = state.value === 'loaded' && props.variant === 'player' && props.controlsVariant !== 'hidden';
33792
+ const posterTransition = props.variant === 'background' ? 'poster-fade-out' : 'fade-transition';
33793
+ const overlayProps = {
33794
+ contained: true,
33795
+ persistent: true,
33796
+ contentClass: 'v-video__overlay-fill'
33797
+ };
33798
+ const controlsProps = {
33799
+ ...VVideoControls.filterProps(omit(props, ['variant', 'rounded', 'hideVolume'])),
33800
+ rounded: Array.isArray(props.rounded) ? props.rounded.at(-1) : props.rounded,
33801
+ fullscreen: fullscreen.value,
33802
+ hideVolume: props.hideVolume || props.muted,
33803
+ hideFullscreen: props.hideFullscreen || !fullscreenEnabled.value,
33804
+ density: props.density,
33805
+ variant: props.controlsVariant,
33806
+ playing: playing.value,
33807
+ progress: progress.value,
33808
+ duration: duration.value,
33809
+ volume: volume.value,
33810
+ ...props.controlsProps
33811
+ };
33812
+ const controlsEventHandlers = {
33813
+ onSkip: v => skipTo(v),
33814
+ 'onClick:fullscreen': () => toggleFullscreen(),
33815
+ 'onUpdate:playing': v => playing.value = v,
33816
+ 'onUpdate:progress': v => skipTo(v),
33817
+ 'onUpdate:volume': v => volume.value = v,
33818
+ onClick: e => e.stopPropagation()
33819
+ };
33820
+ const controlslist = [attrs.controlslist, props.noFullscreen ? 'nofullscreen' : ''].filter(Boolean).join(' ');
33821
+ const loadingIndicator = vue.createVNode(VProgressCircular, {
33822
+ "indeterminate": true,
33823
+ "color": props.color,
33824
+ "width": "3",
33825
+ "size": Math.min(100, Number(props.height) / 2 || 50)
33826
+ }, null);
33827
+ const overlayPlayIcon = vue.createVNode(VIconBtn, {
33828
+ "icon": "$play",
33829
+ "size": "80",
33830
+ "color": "#fff",
33831
+ "variant": "outlined",
33832
+ "iconSize": "50",
33833
+ "class": "v-video__center-icon"
33834
+ }, null);
33835
+ return vue.createElementVNode("div", {
33836
+ "ref": containerRef,
33837
+ "class": vue.normalizeClass(['v-video', `v-video--variant-${props.variant}`, `v-video--${state.value}`, {
33838
+ 'v-video--playing': playing.value
33839
+ }, themeClasses.value, densityClasses.value, roundedContainerClasses.value, props.class]),
33840
+ "style": vue.normalizeStyle([props.variant === 'background' ? [] : pick(dimensionStyles.value, ['width', 'min-width', 'max-width']), props.style]),
33841
+ "onKeydown": onKeydown,
33842
+ "onClick": onClick
33843
+ }, [vue.createElementVNode("div", {
33844
+ "class": vue.normalizeClass(['v-video__content', elevationClasses.value]),
33845
+ "style": vue.normalizeStyle([props.variant === 'background' ? [] : dimensionStyles.value])
33846
+ }, [(props.eager || triggered.value) && vue.createElementVNode("video", vue.mergeProps({
33847
+ "key": "video-element",
33848
+ "class": ['v-video__video', roundedContainerClasses.value]
33849
+ }, omit(attrs, ['controlslist', 'class', 'style']), {
33850
+ "controlslist": controlslist,
33851
+ "autoplay": props.autoplay,
33852
+ "muted": props.muted,
33853
+ "playsinline": true,
33854
+ "ref": videoRef,
33855
+ "onLoadeddata": onVideoLoaded,
33856
+ "onPlay": () => playing.value = true,
33857
+ "onPause": () => playing.value = false,
33858
+ "onWaiting": () => waiting.value = true,
33859
+ "onPlaying": () => waiting.value = false,
33860
+ "onClick": onVideoClick,
33861
+ "onDblclick": onDoubleClick,
33862
+ "onTouchend": onTouchend
33863
+ }), [slots.sources?.() ?? vue.createElementVNode("source", {
33864
+ "src": props.src,
33865
+ "type": props.type
33866
+ }, null)]), props.variant === 'player' && !props.hideOverlay && vue.createVNode(VOverlay, vue.mergeProps({
33867
+ "key": "pause-overlay",
33868
+ "modelValue": state.value === 'loaded',
33869
+ "opacity": "0"
33870
+ }, overlayProps), {
33871
+ default: () => [vue.createVNode(VSpacer, null, null), vue.createVNode(MaybeTransition, {
33872
+ "name": "fade-transition"
33873
+ }, {
33874
+ default: () => [!playing.value && overlayPlayIcon]
33875
+ }), vue.createVNode(VSpacer, null, null)]
33876
+ }), props.variant === 'player' && !!slots.header ? vue.createElementVNode("div", {
33877
+ "key": "header",
33878
+ "class": "v-video__header"
33879
+ }, [slots.header()]) : '', vue.createVNode(VOverlay, vue.mergeProps({
33880
+ "key": "poster-overlay",
33881
+ "modelValue": state.value !== 'loaded',
33882
+ "transition": posterTransition
33883
+ }, overlayProps), {
33884
+ default: () => [vue.createVNode(VImg, {
33885
+ "cover": true,
33886
+ "src": props.image
33887
+ }, {
33888
+ default: () => [vue.createElementVNode("div", {
33889
+ "class": vue.normalizeClass(['v-video__overlay-fill', ...roundedContainerClasses.value])
33890
+ }, [overlayPlayIcon])]
33891
+ })]
33892
+ }), vue.createVNode(VOverlay, vue.mergeProps({
33893
+ "key": "loading-overlay",
33894
+ "modelValue": state.value === 'loading' || waiting.value,
33895
+ "opacity": ".1"
33896
+ }, overlayProps), {
33897
+ default: () => [loadingIndicator]
33898
+ })]), vue.createVNode(MaybeTransition, {
33899
+ "key": "actions",
33900
+ "transition": props.controlsTransition
33901
+ }, {
33902
+ default: () => [showControls && vue.createVNode(VVideoControls, vue.mergeProps({
33903
+ "ref": controlsRef,
33904
+ "class": roundedControlsClasses.value
33905
+ }, controlsProps, controlsEventHandlers), {
33906
+ default: slots.controls,
33907
+ prepend: slots.prepend,
33908
+ append: slots.append
33909
+ })]
33910
+ })]);
33911
+ });
33912
+ return {
33913
+ video: videoRef,
33914
+ ...forwardRefs({
33915
+ skipTo,
33916
+ toggleFullscreen
33917
+ }, controlsRef)
33918
+ };
33919
+ }
33920
+ });
33921
+
33183
33922
  var components = /*#__PURE__*/Object.freeze({
33184
33923
  __proto__: null,
33185
33924
  VAlert: VAlert,
@@ -33370,6 +34109,9 @@
33370
34109
  VTreeviewGroup: VTreeviewGroup,
33371
34110
  VTreeviewItem: VTreeviewItem,
33372
34111
  VValidation: VValidation,
34112
+ VVideo: VVideo,
34113
+ VVideoControls: VVideoControls,
34114
+ VVideoVolume: VVideoVolume,
33373
34115
  VVirtualScroll: VVirtualScroll,
33374
34116
  VWindow: VWindow,
33375
34117
  VWindowItem: VWindowItem
@@ -33692,7 +34434,7 @@
33692
34434
  };
33693
34435
  });
33694
34436
  }
33695
- const version$1 = "3.9.2-master.2025-07-22";
34437
+ const version$1 = "3.9.2-master.2025-07-24";
33696
34438
  createVuetify$1.version = version$1;
33697
34439
 
33698
34440
  // Vue's inject() can only be used in setup
@@ -33990,7 +34732,7 @@
33990
34732
 
33991
34733
  /* eslint-disable local-rules/sort-imports */
33992
34734
 
33993
- const version = "3.9.2-master.2025-07-22";
34735
+ const version = "3.9.2-master.2025-07-24";
33994
34736
 
33995
34737
  /* eslint-disable local-rules/sort-imports */
33996
34738