Align.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import { nextTick, defineComponent, ref, computed, onMounted, onUpdated, watch, onUnmounted } from 'vue';
  2. import { alignElement, alignPoint } from 'dom-align';
  3. import addEventListener from '../vc-util/Dom/addEventListener';
  4. import { cloneElement } from '../_util/vnode';
  5. import isVisible from '../vc-util/Dom/isVisible';
  6. import { isSamePoint, restoreFocus, monitorResize } from './util';
  7. import useBuffer from './hooks/useBuffer';
  8. import isEqual from 'lodash-es/isEqual';
  9. export const alignProps = {
  10. align: Object,
  11. target: [Object, Function],
  12. onAlign: Function,
  13. monitorBufferTime: Number,
  14. monitorWindowResize: Boolean,
  15. disabled: Boolean
  16. };
  17. function getElement(func) {
  18. if (typeof func !== 'function') return null;
  19. return func();
  20. }
  21. function getPoint(point) {
  22. if (typeof point !== 'object' || !point) return null;
  23. return point;
  24. }
  25. export default defineComponent({
  26. compatConfig: {
  27. MODE: 3
  28. },
  29. name: 'Align',
  30. props: alignProps,
  31. emits: ['align'],
  32. setup(props, _ref) {
  33. let {
  34. expose,
  35. slots
  36. } = _ref;
  37. const cacheRef = ref({});
  38. const nodeRef = ref();
  39. const [forceAlign, cancelForceAlign] = useBuffer(() => {
  40. const {
  41. disabled: latestDisabled,
  42. target: latestTarget,
  43. align: latestAlign,
  44. onAlign: latestOnAlign
  45. } = props;
  46. if (!latestDisabled && latestTarget && nodeRef.value) {
  47. const source = nodeRef.value;
  48. let result;
  49. const element = getElement(latestTarget);
  50. const point = getPoint(latestTarget);
  51. cacheRef.value.element = element;
  52. cacheRef.value.point = point;
  53. cacheRef.value.align = latestAlign;
  54. // IE lose focus after element realign
  55. // We should record activeElement and restore later
  56. const {
  57. activeElement
  58. } = document;
  59. // We only align when element is visible
  60. if (element && isVisible(element)) {
  61. result = alignElement(source, element, latestAlign);
  62. } else if (point) {
  63. result = alignPoint(source, point, latestAlign);
  64. }
  65. restoreFocus(activeElement, source);
  66. if (latestOnAlign && result) {
  67. latestOnAlign(source, result);
  68. }
  69. return true;
  70. }
  71. return false;
  72. }, computed(() => props.monitorBufferTime));
  73. // ===================== Effect =====================
  74. // Listen for target updated
  75. const resizeMonitor = ref({
  76. cancel: () => {}
  77. });
  78. // Listen for source updated
  79. const sourceResizeMonitor = ref({
  80. cancel: () => {}
  81. });
  82. const goAlign = () => {
  83. const target = props.target;
  84. const element = getElement(target);
  85. const point = getPoint(target);
  86. if (nodeRef.value !== sourceResizeMonitor.value.element) {
  87. sourceResizeMonitor.value.cancel();
  88. sourceResizeMonitor.value.element = nodeRef.value;
  89. sourceResizeMonitor.value.cancel = monitorResize(nodeRef.value, forceAlign);
  90. }
  91. if (cacheRef.value.element !== element || !isSamePoint(cacheRef.value.point, point) || !isEqual(cacheRef.value.align, props.align)) {
  92. forceAlign();
  93. // Add resize observer
  94. if (resizeMonitor.value.element !== element) {
  95. resizeMonitor.value.cancel();
  96. resizeMonitor.value.element = element;
  97. resizeMonitor.value.cancel = monitorResize(element, forceAlign);
  98. }
  99. }
  100. };
  101. onMounted(() => {
  102. nextTick(() => {
  103. goAlign();
  104. });
  105. });
  106. onUpdated(() => {
  107. nextTick(() => {
  108. goAlign();
  109. });
  110. });
  111. // Listen for disabled change
  112. watch(() => props.disabled, disabled => {
  113. if (!disabled) {
  114. forceAlign();
  115. } else {
  116. cancelForceAlign();
  117. }
  118. }, {
  119. immediate: true,
  120. flush: 'post'
  121. });
  122. // Listen for window resize
  123. const winResizeRef = ref(null);
  124. watch(() => props.monitorWindowResize, monitorWindowResize => {
  125. if (monitorWindowResize) {
  126. if (!winResizeRef.value) {
  127. winResizeRef.value = addEventListener(window, 'resize', forceAlign);
  128. }
  129. } else if (winResizeRef.value) {
  130. winResizeRef.value.remove();
  131. winResizeRef.value = null;
  132. }
  133. }, {
  134. flush: 'post'
  135. });
  136. onUnmounted(() => {
  137. resizeMonitor.value.cancel();
  138. sourceResizeMonitor.value.cancel();
  139. if (winResizeRef.value) winResizeRef.value.remove();
  140. cancelForceAlign();
  141. });
  142. expose({
  143. forceAlign: () => forceAlign(true)
  144. });
  145. return () => {
  146. const child = slots === null || slots === void 0 ? void 0 : slots.default();
  147. if (child) {
  148. return cloneElement(child[0], {
  149. ref: nodeRef
  150. }, true, true);
  151. }
  152. return null;
  153. };
  154. }
  155. });