项目原始demo,不改动
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 
 
 

302 line
7.9 KiB

  1. // Styles
  2. import "../../../src/components/VDialog/VDialog.sass"; // Components
  3. import { VThemeProvider } from '../VThemeProvider'; // Mixins
  4. import Activatable from '../../mixins/activatable';
  5. import Dependent from '../../mixins/dependent';
  6. import Detachable from '../../mixins/detachable';
  7. import Overlayable from '../../mixins/overlayable';
  8. import Returnable from '../../mixins/returnable';
  9. import Stackable from '../../mixins/stackable';
  10. import Toggleable from '../../mixins/toggleable'; // Directives
  11. import ClickOutside from '../../directives/click-outside'; // Helpers
  12. import mixins from '../../util/mixins';
  13. import { removed } from '../../util/console';
  14. import { convertToUnit, keyCodes } from '../../util/helpers';
  15. const baseMixins = mixins(Activatable, Dependent, Detachable, Overlayable, Returnable, Stackable, Toggleable);
  16. /* @vue/component */
  17. export default baseMixins.extend({
  18. name: 'v-dialog',
  19. directives: {
  20. ClickOutside
  21. },
  22. props: {
  23. dark: Boolean,
  24. disabled: Boolean,
  25. fullscreen: Boolean,
  26. light: Boolean,
  27. maxWidth: {
  28. type: [String, Number],
  29. default: 'none'
  30. },
  31. noClickAnimation: Boolean,
  32. origin: {
  33. type: String,
  34. default: 'center center'
  35. },
  36. persistent: Boolean,
  37. retainFocus: {
  38. type: Boolean,
  39. default: true
  40. },
  41. scrollable: Boolean,
  42. transition: {
  43. type: [String, Boolean],
  44. default: 'dialog-transition'
  45. },
  46. width: {
  47. type: [String, Number],
  48. default: 'auto'
  49. }
  50. },
  51. data() {
  52. return {
  53. activatedBy: null,
  54. animate: false,
  55. animateTimeout: -1,
  56. isActive: !!this.value,
  57. stackMinZIndex: 200
  58. };
  59. },
  60. computed: {
  61. classes() {
  62. return {
  63. [`v-dialog ${this.contentClass}`.trim()]: true,
  64. 'v-dialog--active': this.isActive,
  65. 'v-dialog--persistent': this.persistent,
  66. 'v-dialog--fullscreen': this.fullscreen,
  67. 'v-dialog--scrollable': this.scrollable,
  68. 'v-dialog--animated': this.animate
  69. };
  70. },
  71. contentClasses() {
  72. return {
  73. 'v-dialog__content': true,
  74. 'v-dialog__content--active': this.isActive
  75. };
  76. },
  77. hasActivator() {
  78. return Boolean(!!this.$slots.activator || !!this.$scopedSlots.activator);
  79. }
  80. },
  81. watch: {
  82. isActive(val) {
  83. if (val) {
  84. this.show();
  85. this.hideScroll();
  86. } else {
  87. this.removeOverlay();
  88. this.unbind();
  89. }
  90. },
  91. fullscreen(val) {
  92. if (!this.isActive) return;
  93. if (val) {
  94. this.hideScroll();
  95. this.removeOverlay(false);
  96. } else {
  97. this.showScroll();
  98. this.genOverlay();
  99. }
  100. }
  101. },
  102. created() {
  103. /* istanbul ignore next */
  104. if (this.$attrs.hasOwnProperty('full-width')) {
  105. removed('full-width', this);
  106. }
  107. },
  108. beforeMount() {
  109. this.$nextTick(() => {
  110. this.isBooted = this.isActive;
  111. this.isActive && this.show();
  112. });
  113. },
  114. beforeDestroy() {
  115. if (typeof window !== 'undefined') this.unbind();
  116. },
  117. methods: {
  118. animateClick() {
  119. this.animate = false; // Needed for when clicking very fast
  120. // outside of the dialog
  121. this.$nextTick(() => {
  122. this.animate = true;
  123. window.clearTimeout(this.animateTimeout);
  124. this.animateTimeout = window.setTimeout(() => this.animate = false, 150);
  125. });
  126. },
  127. closeConditional(e) {
  128. const target = e.target; // Ignore the click if the dialog is closed or destroyed,
  129. // if it was on an element inside the content,
  130. // if it was dragged onto the overlay (#6969),
  131. // or if this isn't the topmost dialog (#9907)
  132. return !(this._isDestroyed || !this.isActive || this.$refs.content.contains(target) || this.overlay && target && !this.overlay.$el.contains(target)) && this.activeZIndex >= this.getMaxZIndex();
  133. },
  134. hideScroll() {
  135. if (this.fullscreen) {
  136. document.documentElement.classList.add('overflow-y-hidden');
  137. } else {
  138. Overlayable.options.methods.hideScroll.call(this);
  139. }
  140. },
  141. show() {
  142. !this.fullscreen && !this.hideOverlay && this.genOverlay();
  143. this.$nextTick(() => {
  144. this.$refs.content.focus();
  145. this.bind();
  146. });
  147. },
  148. bind() {
  149. window.addEventListener('focusin', this.onFocusin);
  150. },
  151. unbind() {
  152. window.removeEventListener('focusin', this.onFocusin);
  153. },
  154. onClickOutside(e) {
  155. this.$emit('click:outside', e);
  156. if (this.persistent) {
  157. this.noClickAnimation || this.animateClick();
  158. } else {
  159. this.isActive = false;
  160. }
  161. },
  162. onKeydown(e) {
  163. if (e.keyCode === keyCodes.esc && !this.getOpenDependents().length) {
  164. if (!this.persistent) {
  165. this.isActive = false;
  166. const activator = this.getActivator();
  167. this.$nextTick(() => activator && activator.focus());
  168. } else if (!this.noClickAnimation) {
  169. this.animateClick();
  170. }
  171. }
  172. this.$emit('keydown', e);
  173. },
  174. // On focus change, wrap focus to stay inside the dialog
  175. // https://github.com/vuetifyjs/vuetify/issues/6892
  176. onFocusin(e) {
  177. if (!e || !this.retainFocus) return;
  178. const target = e.target;
  179. if (!!target && // It isn't the document or the dialog body
  180. ![document, this.$refs.content].includes(target) && // It isn't inside the dialog body
  181. !this.$refs.content.contains(target) && // We're the topmost dialog
  182. this.activeZIndex >= this.getMaxZIndex() && // It isn't inside a dependent element (like a menu)
  183. !this.getOpenDependentElements().some(el => el.contains(target)) // So we must have focused something outside the dialog and its children
  184. ) {
  185. // Find and focus the first available element inside the dialog
  186. const focusable = this.$refs.content.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  187. focusable.length && focusable[0].focus();
  188. }
  189. },
  190. genContent() {
  191. return this.showLazyContent(() => [this.$createElement(VThemeProvider, {
  192. props: {
  193. root: true,
  194. light: this.light,
  195. dark: this.dark
  196. }
  197. }, [this.$createElement('div', {
  198. class: this.contentClasses,
  199. attrs: {
  200. role: 'document',
  201. tabindex: this.isActive ? 0 : undefined,
  202. ...this.getScopeIdAttrs()
  203. },
  204. on: {
  205. keydown: this.onKeydown
  206. },
  207. style: {
  208. zIndex: this.activeZIndex
  209. },
  210. ref: 'content'
  211. }, [this.genTransition()])])]);
  212. },
  213. genTransition() {
  214. const content = this.genInnerContent();
  215. if (!this.transition) return content;
  216. return this.$createElement('transition', {
  217. props: {
  218. name: this.transition,
  219. origin: this.origin,
  220. appear: true
  221. }
  222. }, [content]);
  223. },
  224. genInnerContent() {
  225. const data = {
  226. class: this.classes,
  227. ref: 'dialog',
  228. directives: [{
  229. name: 'click-outside',
  230. value: this.onClickOutside,
  231. args: {
  232. closeConditional: this.closeConditional,
  233. include: this.getOpenDependentElements
  234. }
  235. }, {
  236. name: 'show',
  237. value: this.isActive
  238. }],
  239. style: {
  240. transformOrigin: this.origin
  241. }
  242. };
  243. if (!this.fullscreen) {
  244. data.style = { ...data.style,
  245. maxWidth: this.maxWidth === 'none' ? undefined : convertToUnit(this.maxWidth),
  246. width: this.width === 'auto' ? undefined : convertToUnit(this.width)
  247. };
  248. }
  249. return this.$createElement('div', data, this.getContentSlot());
  250. }
  251. },
  252. render(h) {
  253. return h('div', {
  254. staticClass: 'v-dialog__container',
  255. class: {
  256. 'v-dialog__container--attached': this.attach === '' || this.attach === true || this.attach === 'attach'
  257. },
  258. attrs: {
  259. role: 'dialog'
  260. }
  261. }, [this.genActivator(), this.genContent()]);
  262. }
  263. });
  264. //# sourceMappingURL=VDialog.js.map