VOverlay.mjs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. import { withDirectives as _withDirectives, resolveDirective as _resolveDirective, vShow as _vShow, Fragment as _Fragment, createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
  2. // Styles
  3. import "./VOverlay.css";
  4. // Composables
  5. import { makeLocationStrategyProps, useLocationStrategies } from "./locationStrategies.mjs";
  6. import { makeScrollStrategyProps, useScrollStrategies } from "./scrollStrategies.mjs";
  7. import { makeActivatorProps, useActivator } from "./useActivator.mjs";
  8. import { useBackgroundColor } from "../../composables/color.mjs";
  9. import { makeComponentProps } from "../../composables/component.mjs";
  10. import { makeDimensionProps, useDimension } from "../../composables/dimensions.mjs";
  11. import { useHydration } from "../../composables/hydration.mjs";
  12. import { makeLazyProps, useLazy } from "../../composables/lazy.mjs";
  13. import { useRtl } from "../../composables/locale.mjs";
  14. import { useProxiedModel } from "../../composables/proxiedModel.mjs";
  15. import { useBackButton, useRouter } from "../../composables/router.mjs";
  16. import { useScopeId } from "../../composables/scopeId.mjs";
  17. import { useStack } from "../../composables/stack.mjs";
  18. import { useTeleport } from "../../composables/teleport.mjs";
  19. import { makeThemeProps, provideTheme } from "../../composables/theme.mjs";
  20. import { useToggleScope } from "../../composables/toggleScope.mjs";
  21. import { makeTransitionProps, MaybeTransition } from "../../composables/transition.mjs"; // Directives
  22. import { ClickOutside } from "../../directives/click-outside/index.mjs"; // Utilities
  23. import { computed, mergeProps, ref, Teleport, toRef, Transition, watch } from 'vue';
  24. import { animate, convertToUnit, genericComponent, getScrollParent, IN_BROWSER, propsFactory, standardEasing, useRender } from "../../util/index.mjs"; // Types
  25. function Scrim(props) {
  26. const {
  27. modelValue,
  28. color,
  29. ...rest
  30. } = props;
  31. return _createVNode(Transition, {
  32. "name": "fade-transition",
  33. "appear": true
  34. }, {
  35. default: () => [props.modelValue && _createVNode("div", _mergeProps({
  36. "class": ['v-overlay__scrim', props.color.backgroundColorClasses.value],
  37. "style": props.color.backgroundColorStyles.value
  38. }, rest), null)]
  39. });
  40. }
  41. export const makeVOverlayProps = propsFactory({
  42. absolute: Boolean,
  43. attach: [Boolean, String, Object],
  44. closeOnBack: {
  45. type: Boolean,
  46. default: true
  47. },
  48. contained: Boolean,
  49. contentClass: null,
  50. contentProps: null,
  51. disabled: Boolean,
  52. noClickAnimation: Boolean,
  53. modelValue: Boolean,
  54. persistent: Boolean,
  55. scrim: {
  56. type: [Boolean, String],
  57. default: true
  58. },
  59. zIndex: {
  60. type: [Number, String],
  61. default: 2000
  62. },
  63. ...makeActivatorProps(),
  64. ...makeComponentProps(),
  65. ...makeDimensionProps(),
  66. ...makeLazyProps(),
  67. ...makeLocationStrategyProps(),
  68. ...makeScrollStrategyProps(),
  69. ...makeThemeProps(),
  70. ...makeTransitionProps()
  71. }, 'VOverlay');
  72. export const VOverlay = genericComponent()({
  73. name: 'VOverlay',
  74. directives: {
  75. ClickOutside
  76. },
  77. inheritAttrs: false,
  78. props: {
  79. _disableGlobalStack: Boolean,
  80. ...makeVOverlayProps()
  81. },
  82. emits: {
  83. 'click:outside': e => true,
  84. 'update:modelValue': value => true,
  85. afterLeave: () => true
  86. },
  87. setup(props, _ref) {
  88. let {
  89. slots,
  90. attrs,
  91. emit
  92. } = _ref;
  93. const model = useProxiedModel(props, 'modelValue');
  94. const isActive = computed({
  95. get: () => model.value,
  96. set: v => {
  97. if (!(v && props.disabled)) model.value = v;
  98. }
  99. });
  100. const {
  101. teleportTarget
  102. } = useTeleport(computed(() => props.attach || props.contained));
  103. const {
  104. themeClasses
  105. } = provideTheme(props);
  106. const {
  107. rtlClasses,
  108. isRtl
  109. } = useRtl();
  110. const {
  111. hasContent,
  112. onAfterLeave
  113. } = useLazy(props, isActive);
  114. const scrimColor = useBackgroundColor(computed(() => {
  115. return typeof props.scrim === 'string' ? props.scrim : null;
  116. }));
  117. const {
  118. globalTop,
  119. localTop,
  120. stackStyles
  121. } = useStack(isActive, toRef(props, 'zIndex'), props._disableGlobalStack);
  122. const {
  123. activatorEl,
  124. activatorRef,
  125. activatorEvents,
  126. contentEvents,
  127. scrimEvents
  128. } = useActivator(props, {
  129. isActive,
  130. isTop: localTop
  131. });
  132. const {
  133. dimensionStyles
  134. } = useDimension(props);
  135. const isMounted = useHydration();
  136. const {
  137. scopeId
  138. } = useScopeId();
  139. watch(() => props.disabled, v => {
  140. if (v) isActive.value = false;
  141. });
  142. const root = ref();
  143. const contentEl = ref();
  144. const {
  145. contentStyles,
  146. updateLocation
  147. } = useLocationStrategies(props, {
  148. isRtl,
  149. contentEl,
  150. activatorEl,
  151. isActive
  152. });
  153. useScrollStrategies(props, {
  154. root,
  155. contentEl,
  156. activatorEl,
  157. isActive,
  158. updateLocation
  159. });
  160. function onClickOutside(e) {
  161. emit('click:outside', e);
  162. if (!props.persistent) isActive.value = false;else animateClick();
  163. }
  164. function closeConditional() {
  165. return isActive.value && globalTop.value;
  166. }
  167. IN_BROWSER && watch(isActive, val => {
  168. if (val) {
  169. window.addEventListener('keydown', onKeydown);
  170. } else {
  171. window.removeEventListener('keydown', onKeydown);
  172. }
  173. }, {
  174. immediate: true
  175. });
  176. function onKeydown(e) {
  177. if (e.key === 'Escape' && globalTop.value) {
  178. if (!props.persistent) {
  179. isActive.value = false;
  180. if (contentEl.value?.contains(document.activeElement)) {
  181. activatorEl.value?.focus();
  182. }
  183. } else animateClick();
  184. }
  185. }
  186. const router = useRouter();
  187. useToggleScope(() => props.closeOnBack, () => {
  188. useBackButton(router, next => {
  189. if (globalTop.value && isActive.value) {
  190. next(false);
  191. if (!props.persistent) isActive.value = false;else animateClick();
  192. } else {
  193. next();
  194. }
  195. });
  196. });
  197. const top = ref();
  198. watch(() => isActive.value && (props.absolute || props.contained) && teleportTarget.value == null, val => {
  199. if (val) {
  200. const scrollParent = getScrollParent(root.value);
  201. if (scrollParent && scrollParent !== document.scrollingElement) {
  202. top.value = scrollParent.scrollTop;
  203. }
  204. }
  205. });
  206. // Add a quick "bounce" animation to the content
  207. function animateClick() {
  208. if (props.noClickAnimation) return;
  209. contentEl.value && animate(contentEl.value, [{
  210. transformOrigin: 'center'
  211. }, {
  212. transform: 'scale(1.03)'
  213. }, {
  214. transformOrigin: 'center'
  215. }], {
  216. duration: 150,
  217. easing: standardEasing
  218. });
  219. }
  220. useRender(() => _createVNode(_Fragment, null, [slots.activator?.({
  221. isActive: isActive.value,
  222. props: mergeProps({
  223. ref: activatorRef
  224. }, activatorEvents.value, props.activatorProps)
  225. }), isMounted.value && hasContent.value && _createVNode(Teleport, {
  226. "disabled": !teleportTarget.value,
  227. "to": teleportTarget.value
  228. }, {
  229. default: () => [_createVNode("div", _mergeProps({
  230. "class": ['v-overlay', {
  231. 'v-overlay--absolute': props.absolute || props.contained,
  232. 'v-overlay--active': isActive.value,
  233. 'v-overlay--contained': props.contained
  234. }, themeClasses.value, rtlClasses.value, props.class],
  235. "style": [stackStyles.value, {
  236. top: convertToUnit(top.value)
  237. }, props.style],
  238. "ref": root
  239. }, scopeId, attrs), [_createVNode(Scrim, _mergeProps({
  240. "color": scrimColor,
  241. "modelValue": isActive.value && !!props.scrim
  242. }, scrimEvents.value), null), _createVNode(MaybeTransition, {
  243. "appear": true,
  244. "persisted": true,
  245. "transition": props.transition,
  246. "target": activatorEl.value,
  247. "onAfterLeave": () => {
  248. onAfterLeave();
  249. emit('afterLeave');
  250. }
  251. }, {
  252. default: () => [_withDirectives(_createVNode("div", _mergeProps({
  253. "ref": contentEl,
  254. "class": ['v-overlay__content', props.contentClass],
  255. "style": [dimensionStyles.value, contentStyles.value]
  256. }, contentEvents.value, props.contentProps), [slots.default?.({
  257. isActive
  258. })]), [[_vShow, isActive.value], [_resolveDirective("click-outside"), {
  259. handler: onClickOutside,
  260. closeConditional,
  261. include: () => [activatorEl.value]
  262. }]])]
  263. })])]
  264. })]));
  265. return {
  266. activatorEl,
  267. animateClick,
  268. contentEl,
  269. globalTop,
  270. localTop,
  271. updateLocation
  272. };
  273. }
  274. });
  275. //# sourceMappingURL=VOverlay.mjs.map