VOverlay.mjs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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, onBeforeUnmount, ref, Teleport, toRef, Transition, watch } from 'vue';
  24. import { animate, convertToUnit, genericComponent, getCurrentInstance, 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. opacity: [Number, String],
  53. noClickAnimation: Boolean,
  54. modelValue: Boolean,
  55. persistent: Boolean,
  56. scrim: {
  57. type: [Boolean, String],
  58. default: true
  59. },
  60. zIndex: {
  61. type: [Number, String],
  62. default: 2000
  63. },
  64. ...makeActivatorProps(),
  65. ...makeComponentProps(),
  66. ...makeDimensionProps(),
  67. ...makeLazyProps(),
  68. ...makeLocationStrategyProps(),
  69. ...makeScrollStrategyProps(),
  70. ...makeThemeProps(),
  71. ...makeTransitionProps()
  72. }, 'VOverlay');
  73. export const VOverlay = genericComponent()({
  74. name: 'VOverlay',
  75. directives: {
  76. ClickOutside
  77. },
  78. inheritAttrs: false,
  79. props: {
  80. _disableGlobalStack: Boolean,
  81. ...makeVOverlayProps()
  82. },
  83. emits: {
  84. 'click:outside': e => true,
  85. 'update:modelValue': value => true,
  86. afterEnter: () => true,
  87. afterLeave: () => true
  88. },
  89. setup(props, _ref) {
  90. let {
  91. slots,
  92. attrs,
  93. emit
  94. } = _ref;
  95. const vm = getCurrentInstance('VOverlay');
  96. const root = ref();
  97. const scrimEl = ref();
  98. const contentEl = ref();
  99. const model = useProxiedModel(props, 'modelValue');
  100. const isActive = computed({
  101. get: () => model.value,
  102. set: v => {
  103. if (!(v && props.disabled)) model.value = v;
  104. }
  105. });
  106. const {
  107. themeClasses
  108. } = provideTheme(props);
  109. const {
  110. rtlClasses,
  111. isRtl
  112. } = useRtl();
  113. const {
  114. hasContent,
  115. onAfterLeave: _onAfterLeave
  116. } = useLazy(props, isActive);
  117. const scrimColor = useBackgroundColor(computed(() => {
  118. return typeof props.scrim === 'string' ? props.scrim : null;
  119. }));
  120. const {
  121. globalTop,
  122. localTop,
  123. stackStyles
  124. } = useStack(isActive, toRef(props, 'zIndex'), props._disableGlobalStack);
  125. const {
  126. activatorEl,
  127. activatorRef,
  128. target,
  129. targetEl,
  130. targetRef,
  131. activatorEvents,
  132. contentEvents,
  133. scrimEvents
  134. } = useActivator(props, {
  135. isActive,
  136. isTop: localTop,
  137. contentEl
  138. });
  139. const {
  140. teleportTarget
  141. } = useTeleport(() => {
  142. const target = props.attach || props.contained;
  143. if (target) return target;
  144. const rootNode = activatorEl?.value?.getRootNode() || vm.proxy?.$el?.getRootNode();
  145. if (rootNode instanceof ShadowRoot) return rootNode;
  146. return false;
  147. });
  148. const {
  149. dimensionStyles
  150. } = useDimension(props);
  151. const isMounted = useHydration();
  152. const {
  153. scopeId
  154. } = useScopeId();
  155. watch(() => props.disabled, v => {
  156. if (v) isActive.value = false;
  157. });
  158. const {
  159. contentStyles,
  160. updateLocation
  161. } = useLocationStrategies(props, {
  162. isRtl,
  163. contentEl,
  164. target,
  165. isActive
  166. });
  167. useScrollStrategies(props, {
  168. root,
  169. contentEl,
  170. targetEl,
  171. isActive,
  172. updateLocation
  173. });
  174. function onClickOutside(e) {
  175. emit('click:outside', e);
  176. if (!props.persistent) isActive.value = false;else animateClick();
  177. }
  178. function closeConditional(e) {
  179. return isActive.value && globalTop.value && (
  180. // If using scrim, only close if clicking on it rather than anything opened on top
  181. !props.scrim || e.target === scrimEl.value || e instanceof MouseEvent && e.shadowTarget === scrimEl.value);
  182. }
  183. IN_BROWSER && watch(isActive, val => {
  184. if (val) {
  185. window.addEventListener('keydown', onKeydown);
  186. } else {
  187. window.removeEventListener('keydown', onKeydown);
  188. }
  189. }, {
  190. immediate: true
  191. });
  192. onBeforeUnmount(() => {
  193. if (!IN_BROWSER) return;
  194. window.removeEventListener('keydown', onKeydown);
  195. });
  196. function onKeydown(e) {
  197. if (e.key === 'Escape' && globalTop.value) {
  198. if (!props.persistent) {
  199. isActive.value = false;
  200. if (contentEl.value?.contains(document.activeElement)) {
  201. activatorEl.value?.focus();
  202. }
  203. } else animateClick();
  204. }
  205. }
  206. const router = useRouter();
  207. useToggleScope(() => props.closeOnBack, () => {
  208. useBackButton(router, next => {
  209. if (globalTop.value && isActive.value) {
  210. next(false);
  211. if (!props.persistent) isActive.value = false;else animateClick();
  212. } else {
  213. next();
  214. }
  215. });
  216. });
  217. const top = ref();
  218. watch(() => isActive.value && (props.absolute || props.contained) && teleportTarget.value == null, val => {
  219. if (val) {
  220. const scrollParent = getScrollParent(root.value);
  221. if (scrollParent && scrollParent !== document.scrollingElement) {
  222. top.value = scrollParent.scrollTop;
  223. }
  224. }
  225. });
  226. // Add a quick "bounce" animation to the content
  227. function animateClick() {
  228. if (props.noClickAnimation) return;
  229. contentEl.value && animate(contentEl.value, [{
  230. transformOrigin: 'center'
  231. }, {
  232. transform: 'scale(1.03)'
  233. }, {
  234. transformOrigin: 'center'
  235. }], {
  236. duration: 150,
  237. easing: standardEasing
  238. });
  239. }
  240. function onAfterEnter() {
  241. emit('afterEnter');
  242. }
  243. function onAfterLeave() {
  244. _onAfterLeave();
  245. emit('afterLeave');
  246. }
  247. useRender(() => _createVNode(_Fragment, null, [slots.activator?.({
  248. isActive: isActive.value,
  249. targetRef,
  250. props: mergeProps({
  251. ref: activatorRef
  252. }, activatorEvents.value, props.activatorProps)
  253. }), isMounted.value && hasContent.value && _createVNode(Teleport, {
  254. "disabled": !teleportTarget.value,
  255. "to": teleportTarget.value
  256. }, {
  257. default: () => [_createVNode("div", _mergeProps({
  258. "class": ['v-overlay', {
  259. 'v-overlay--absolute': props.absolute || props.contained,
  260. 'v-overlay--active': isActive.value,
  261. 'v-overlay--contained': props.contained
  262. }, themeClasses.value, rtlClasses.value, props.class],
  263. "style": [stackStyles.value, {
  264. '--v-overlay-opacity': props.opacity,
  265. top: convertToUnit(top.value)
  266. }, props.style],
  267. "ref": root
  268. }, scopeId, attrs), [_createVNode(Scrim, _mergeProps({
  269. "color": scrimColor,
  270. "modelValue": isActive.value && !!props.scrim,
  271. "ref": scrimEl
  272. }, scrimEvents.value), null), _createVNode(MaybeTransition, {
  273. "appear": true,
  274. "persisted": true,
  275. "transition": props.transition,
  276. "target": target.value,
  277. "onAfterEnter": onAfterEnter,
  278. "onAfterLeave": onAfterLeave
  279. }, {
  280. default: () => [_withDirectives(_createVNode("div", _mergeProps({
  281. "ref": contentEl,
  282. "class": ['v-overlay__content', props.contentClass],
  283. "style": [dimensionStyles.value, contentStyles.value]
  284. }, contentEvents.value, props.contentProps), [slots.default?.({
  285. isActive
  286. })]), [[_vShow, isActive.value], [_resolveDirective("click-outside"), {
  287. handler: onClickOutside,
  288. closeConditional,
  289. include: () => [activatorEl.value]
  290. }]])]
  291. })])]
  292. })]));
  293. return {
  294. activatorEl,
  295. scrimEl,
  296. target,
  297. animateClick,
  298. contentEl,
  299. globalTop,
  300. localTop,
  301. updateLocation
  302. };
  303. }
  304. });
  305. //# sourceMappingURL=VOverlay.mjs.map