VDialog.mjs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue";
  2. // Styles
  3. import "./VDialog.css";
  4. // Components
  5. import { VDialogTransition } from "../transitions/index.mjs";
  6. import { VDefaultsProvider } from "../VDefaultsProvider/index.mjs";
  7. import { VOverlay } from "../VOverlay/index.mjs";
  8. import { makeVOverlayProps } from "../VOverlay/VOverlay.mjs"; // Composables
  9. import { forwardRefs } from "../../composables/forwardRefs.mjs";
  10. import { useProxiedModel } from "../../composables/proxiedModel.mjs";
  11. import { useScopeId } from "../../composables/scopeId.mjs"; // Utilities
  12. import { mergeProps, nextTick, onBeforeUnmount, ref, watch } from 'vue';
  13. import { focusableChildren, genericComponent, IN_BROWSER, propsFactory, useRender } from "../../util/index.mjs"; // Types
  14. export const makeVDialogProps = propsFactory({
  15. fullscreen: Boolean,
  16. retainFocus: {
  17. type: Boolean,
  18. default: true
  19. },
  20. scrollable: Boolean,
  21. ...makeVOverlayProps({
  22. origin: 'center center',
  23. scrollStrategy: 'block',
  24. transition: {
  25. component: VDialogTransition
  26. },
  27. zIndex: 2400
  28. })
  29. }, 'VDialog');
  30. export const VDialog = genericComponent()({
  31. name: 'VDialog',
  32. props: makeVDialogProps(),
  33. emits: {
  34. 'update:modelValue': value => true,
  35. afterEnter: () => true,
  36. afterLeave: () => true
  37. },
  38. setup(props, _ref) {
  39. let {
  40. emit,
  41. slots
  42. } = _ref;
  43. const isActive = useProxiedModel(props, 'modelValue');
  44. const {
  45. scopeId
  46. } = useScopeId();
  47. const overlay = ref();
  48. function onFocusin(e) {
  49. const before = e.relatedTarget;
  50. const after = e.target;
  51. if (before !== after && overlay.value?.contentEl &&
  52. // We're the topmost dialog
  53. overlay.value?.globalTop &&
  54. // It isn't the document or the dialog body
  55. ![document, overlay.value.contentEl].includes(after) &&
  56. // It isn't inside the dialog body
  57. !overlay.value.contentEl.contains(after)) {
  58. const focusable = focusableChildren(overlay.value.contentEl);
  59. if (!focusable.length) return;
  60. const firstElement = focusable[0];
  61. const lastElement = focusable[focusable.length - 1];
  62. if (before === firstElement) {
  63. lastElement.focus();
  64. } else {
  65. firstElement.focus();
  66. }
  67. }
  68. }
  69. onBeforeUnmount(() => {
  70. document.removeEventListener('focusin', onFocusin);
  71. });
  72. if (IN_BROWSER) {
  73. watch(() => isActive.value && props.retainFocus, val => {
  74. val ? document.addEventListener('focusin', onFocusin) : document.removeEventListener('focusin', onFocusin);
  75. }, {
  76. immediate: true
  77. });
  78. }
  79. function onAfterEnter() {
  80. emit('afterEnter');
  81. if (overlay.value?.contentEl && !overlay.value.contentEl.contains(document.activeElement)) {
  82. overlay.value.contentEl.focus({
  83. preventScroll: true
  84. });
  85. }
  86. }
  87. function onAfterLeave() {
  88. emit('afterLeave');
  89. }
  90. watch(isActive, async val => {
  91. if (!val) {
  92. await nextTick();
  93. overlay.value.activatorEl?.focus({
  94. preventScroll: true
  95. });
  96. }
  97. });
  98. useRender(() => {
  99. const overlayProps = VOverlay.filterProps(props);
  100. const activatorProps = mergeProps({
  101. 'aria-haspopup': 'dialog'
  102. }, props.activatorProps);
  103. const contentProps = mergeProps({
  104. tabindex: -1
  105. }, props.contentProps);
  106. return _createVNode(VOverlay, _mergeProps({
  107. "ref": overlay,
  108. "class": ['v-dialog', {
  109. 'v-dialog--fullscreen': props.fullscreen,
  110. 'v-dialog--scrollable': props.scrollable
  111. }, props.class],
  112. "style": props.style
  113. }, overlayProps, {
  114. "modelValue": isActive.value,
  115. "onUpdate:modelValue": $event => isActive.value = $event,
  116. "aria-modal": "true",
  117. "activatorProps": activatorProps,
  118. "contentProps": contentProps,
  119. "height": !props.fullscreen ? props.height : undefined,
  120. "width": !props.fullscreen ? props.width : undefined,
  121. "maxHeight": !props.fullscreen ? props.maxHeight : undefined,
  122. "maxWidth": !props.fullscreen ? props.maxWidth : undefined,
  123. "role": "dialog",
  124. "onAfterEnter": onAfterEnter,
  125. "onAfterLeave": onAfterLeave
  126. }, scopeId), {
  127. activator: slots.activator,
  128. default: function () {
  129. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  130. args[_key] = arguments[_key];
  131. }
  132. return _createVNode(VDefaultsProvider, {
  133. "root": "VDialog"
  134. }, {
  135. default: () => [slots.default?.(...args)]
  136. });
  137. }
  138. });
  139. });
  140. return forwardRefs({}, overlay);
  141. }
  142. });
  143. //# sourceMappingURL=VDialog.mjs.map