VSnackbar.mjs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import { mergeProps as _mergeProps, resolveDirective as _resolveDirective, createVNode as _createVNode } from "vue";
  2. // Styles
  3. import "./VSnackbar.css";
  4. // Components
  5. import { VDefaultsProvider } from "../VDefaultsProvider/index.mjs";
  6. import { VOverlay } from "../VOverlay/index.mjs";
  7. import { makeVOverlayProps } from "../VOverlay/VOverlay.mjs";
  8. import { VProgressLinear } from "../VProgressLinear/index.mjs"; // Composables
  9. import { useLayout } from "../../composables/index.mjs";
  10. import { forwardRefs } from "../../composables/forwardRefs.mjs";
  11. import { VuetifyLayoutKey } from "../../composables/layout.mjs";
  12. import { makeLocationProps } from "../../composables/location.mjs";
  13. import { makePositionProps, usePosition } from "../../composables/position.mjs";
  14. import { useProxiedModel } from "../../composables/proxiedModel.mjs";
  15. import { makeRoundedProps, useRounded } from "../../composables/rounded.mjs";
  16. import { useScopeId } from "../../composables/scopeId.mjs";
  17. import { makeThemeProps, provideTheme } from "../../composables/theme.mjs";
  18. import { useToggleScope } from "../../composables/toggleScope.mjs";
  19. import { genOverlays, makeVariantProps, useVariant } from "../../composables/variant.mjs"; // Utilities
  20. import { computed, inject, mergeProps, nextTick, onMounted, onScopeDispose, ref, shallowRef, watch, watchEffect } from 'vue';
  21. import { genericComponent, omit, propsFactory, refElement, useRender } from "../../util/index.mjs"; // Types
  22. function useCountdown(milliseconds) {
  23. const time = shallowRef(milliseconds());
  24. let timer = -1;
  25. function clear() {
  26. clearInterval(timer);
  27. }
  28. function reset() {
  29. clear();
  30. nextTick(() => time.value = milliseconds());
  31. }
  32. function start(el) {
  33. const style = el ? getComputedStyle(el) : {
  34. transitionDuration: 0.2
  35. };
  36. const interval = parseFloat(style.transitionDuration) * 1000 || 200;
  37. clear();
  38. if (time.value <= 0) return;
  39. const startTime = performance.now();
  40. timer = window.setInterval(() => {
  41. const elapsed = performance.now() - startTime + interval;
  42. time.value = Math.max(milliseconds() - elapsed, 0);
  43. if (time.value <= 0) clear();
  44. }, interval);
  45. }
  46. onScopeDispose(clear);
  47. return {
  48. clear,
  49. time,
  50. start,
  51. reset
  52. };
  53. }
  54. export const makeVSnackbarProps = propsFactory({
  55. multiLine: Boolean,
  56. text: String,
  57. timer: [Boolean, String],
  58. timeout: {
  59. type: [Number, String],
  60. default: 5000
  61. },
  62. vertical: Boolean,
  63. ...makeLocationProps({
  64. location: 'bottom'
  65. }),
  66. ...makePositionProps(),
  67. ...makeRoundedProps(),
  68. ...makeVariantProps(),
  69. ...makeThemeProps(),
  70. ...omit(makeVOverlayProps({
  71. transition: 'v-snackbar-transition'
  72. }), ['persistent', 'noClickAnimation', 'scrim', 'scrollStrategy'])
  73. }, 'VSnackbar');
  74. export const VSnackbar = genericComponent()({
  75. name: 'VSnackbar',
  76. props: makeVSnackbarProps(),
  77. emits: {
  78. 'update:modelValue': v => true
  79. },
  80. setup(props, _ref) {
  81. let {
  82. slots
  83. } = _ref;
  84. const isActive = useProxiedModel(props, 'modelValue');
  85. const {
  86. positionClasses
  87. } = usePosition(props);
  88. const {
  89. scopeId
  90. } = useScopeId();
  91. const {
  92. themeClasses
  93. } = provideTheme(props);
  94. const {
  95. colorClasses,
  96. colorStyles,
  97. variantClasses
  98. } = useVariant(props);
  99. const {
  100. roundedClasses
  101. } = useRounded(props);
  102. const countdown = useCountdown(() => Number(props.timeout));
  103. const overlay = ref();
  104. const timerRef = ref();
  105. const isHovering = shallowRef(false);
  106. const startY = shallowRef(0);
  107. const mainStyles = ref();
  108. const hasLayout = inject(VuetifyLayoutKey, undefined);
  109. useToggleScope(() => !!hasLayout, () => {
  110. const layout = useLayout();
  111. watchEffect(() => {
  112. mainStyles.value = layout.mainStyles.value;
  113. });
  114. });
  115. watch(isActive, startTimeout);
  116. watch(() => props.timeout, startTimeout);
  117. onMounted(() => {
  118. if (isActive.value) startTimeout();
  119. });
  120. let activeTimeout = -1;
  121. function startTimeout() {
  122. countdown.reset();
  123. window.clearTimeout(activeTimeout);
  124. const timeout = Number(props.timeout);
  125. if (!isActive.value || timeout === -1) return;
  126. const element = refElement(timerRef.value);
  127. countdown.start(element);
  128. activeTimeout = window.setTimeout(() => {
  129. isActive.value = false;
  130. }, timeout);
  131. }
  132. function clearTimeout() {
  133. countdown.reset();
  134. window.clearTimeout(activeTimeout);
  135. }
  136. function onPointerenter() {
  137. isHovering.value = true;
  138. clearTimeout();
  139. }
  140. function onPointerleave() {
  141. isHovering.value = false;
  142. startTimeout();
  143. }
  144. function onTouchstart(event) {
  145. startY.value = event.touches[0].clientY;
  146. }
  147. function onTouchend(event) {
  148. if (Math.abs(startY.value - event.changedTouches[0].clientY) > 50) {
  149. isActive.value = false;
  150. }
  151. }
  152. function onAfterLeave() {
  153. if (isHovering.value) onPointerleave();
  154. }
  155. const locationClasses = computed(() => {
  156. return props.location.split(' ').reduce((acc, loc) => {
  157. acc[`v-snackbar--${loc}`] = true;
  158. return acc;
  159. }, {});
  160. });
  161. useRender(() => {
  162. const overlayProps = VOverlay.filterProps(props);
  163. const hasContent = !!(slots.default || slots.text || props.text);
  164. return _createVNode(VOverlay, _mergeProps({
  165. "ref": overlay,
  166. "class": ['v-snackbar', {
  167. 'v-snackbar--active': isActive.value,
  168. 'v-snackbar--multi-line': props.multiLine && !props.vertical,
  169. 'v-snackbar--timer': !!props.timer,
  170. 'v-snackbar--vertical': props.vertical
  171. }, locationClasses.value, positionClasses.value, props.class],
  172. "style": [mainStyles.value, props.style]
  173. }, overlayProps, {
  174. "modelValue": isActive.value,
  175. "onUpdate:modelValue": $event => isActive.value = $event,
  176. "contentProps": mergeProps({
  177. class: ['v-snackbar__wrapper', themeClasses.value, colorClasses.value, roundedClasses.value, variantClasses.value],
  178. style: [colorStyles.value],
  179. onPointerenter,
  180. onPointerleave
  181. }, overlayProps.contentProps),
  182. "persistent": true,
  183. "noClickAnimation": true,
  184. "scrim": false,
  185. "scrollStrategy": "none",
  186. "_disableGlobalStack": true,
  187. "onTouchstartPassive": onTouchstart,
  188. "onTouchend": onTouchend,
  189. "onAfterLeave": onAfterLeave
  190. }, scopeId), {
  191. default: () => [genOverlays(false, 'v-snackbar'), props.timer && !isHovering.value && _createVNode("div", {
  192. "key": "timer",
  193. "class": "v-snackbar__timer"
  194. }, [_createVNode(VProgressLinear, {
  195. "ref": timerRef,
  196. "color": typeof props.timer === 'string' ? props.timer : 'info',
  197. "max": props.timeout,
  198. "model-value": countdown.time.value
  199. }, null)]), hasContent && _createVNode("div", {
  200. "key": "content",
  201. "class": "v-snackbar__content",
  202. "role": "status",
  203. "aria-live": "polite"
  204. }, [slots.text?.() ?? props.text, slots.default?.()]), slots.actions && _createVNode(VDefaultsProvider, {
  205. "defaults": {
  206. VBtn: {
  207. variant: 'text',
  208. ripple: false,
  209. slim: true
  210. }
  211. }
  212. }, {
  213. default: () => [_createVNode("div", {
  214. "class": "v-snackbar__actions"
  215. }, [slots.actions({
  216. isActive
  217. })])]
  218. })],
  219. activator: slots.activator
  220. });
  221. });
  222. return forwardRefs({}, overlay);
  223. }
  224. });
  225. //# sourceMappingURL=VSnackbar.mjs.map