index.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import _extends from "@babel/runtime/helpers/esm/extends";
  3. import { createVNode as _createVNode } from "vue";
  4. import { defineComponent, shallowRef, reactive, watch, onMounted, getCurrentInstance, computed, onUnmounted, onUpdated } from 'vue';
  5. import classNames from '../_util/classNames';
  6. import ResizeObserver from '../vc-resize-observer';
  7. import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
  8. import { withInstall } from '../_util/type';
  9. import { addObserveTarget, removeObserveTarget, getTargetRect, getFixedTop, getFixedBottom } from './utils';
  10. import useConfigInject from '../config-provider/hooks/useConfigInject';
  11. import omit from '../_util/omit';
  12. import useStyle from './style';
  13. function getDefaultTarget() {
  14. return typeof window !== 'undefined' ? window : null;
  15. }
  16. var AffixStatus;
  17. (function (AffixStatus) {
  18. AffixStatus[AffixStatus["None"] = 0] = "None";
  19. AffixStatus[AffixStatus["Prepare"] = 1] = "Prepare";
  20. })(AffixStatus || (AffixStatus = {}));
  21. // Affix
  22. export const affixProps = () => ({
  23. /**
  24. * 距离窗口顶部达到指定偏移量后触发
  25. */
  26. offsetTop: Number,
  27. /** 距离窗口底部达到指定偏移量后触发 */
  28. offsetBottom: Number,
  29. /** 设置 Affix 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 */
  30. target: {
  31. type: Function,
  32. default: getDefaultTarget
  33. },
  34. prefixCls: String,
  35. /** 固定状态改变时触发的回调函数 */
  36. onChange: Function,
  37. onTestUpdatePosition: Function
  38. });
  39. const Affix = defineComponent({
  40. compatConfig: {
  41. MODE: 3
  42. },
  43. name: 'AAffix',
  44. inheritAttrs: false,
  45. props: affixProps(),
  46. setup(props, _ref) {
  47. let {
  48. slots,
  49. emit,
  50. expose,
  51. attrs
  52. } = _ref;
  53. const placeholderNode = shallowRef();
  54. const fixedNode = shallowRef();
  55. const state = reactive({
  56. affixStyle: undefined,
  57. placeholderStyle: undefined,
  58. status: AffixStatus.None,
  59. lastAffix: false,
  60. prevTarget: null,
  61. timeout: null
  62. });
  63. const currentInstance = getCurrentInstance();
  64. const offsetTop = computed(() => {
  65. return props.offsetBottom === undefined && props.offsetTop === undefined ? 0 : props.offsetTop;
  66. });
  67. const offsetBottom = computed(() => props.offsetBottom);
  68. const measure = () => {
  69. const {
  70. status,
  71. lastAffix
  72. } = state;
  73. const {
  74. target
  75. } = props;
  76. if (status !== AffixStatus.Prepare || !fixedNode.value || !placeholderNode.value || !target) {
  77. return;
  78. }
  79. const targetNode = target();
  80. if (!targetNode) {
  81. return;
  82. }
  83. const newState = {
  84. status: AffixStatus.None
  85. };
  86. const placeholderRect = getTargetRect(placeholderNode.value);
  87. if (placeholderRect.top === 0 && placeholderRect.left === 0 && placeholderRect.width === 0 && placeholderRect.height === 0) {
  88. return;
  89. }
  90. const targetRect = getTargetRect(targetNode);
  91. const fixedTop = getFixedTop(placeholderRect, targetRect, offsetTop.value);
  92. const fixedBottom = getFixedBottom(placeholderRect, targetRect, offsetBottom.value);
  93. if (placeholderRect.top === 0 && placeholderRect.left === 0 && placeholderRect.width === 0 && placeholderRect.height === 0) {
  94. return;
  95. }
  96. if (fixedTop !== undefined) {
  97. const width = `${placeholderRect.width}px`;
  98. const height = `${placeholderRect.height}px`;
  99. newState.affixStyle = {
  100. position: 'fixed',
  101. top: fixedTop,
  102. width,
  103. height
  104. };
  105. newState.placeholderStyle = {
  106. width,
  107. height
  108. };
  109. } else if (fixedBottom !== undefined) {
  110. const width = `${placeholderRect.width}px`;
  111. const height = `${placeholderRect.height}px`;
  112. newState.affixStyle = {
  113. position: 'fixed',
  114. bottom: fixedBottom,
  115. width,
  116. height
  117. };
  118. newState.placeholderStyle = {
  119. width,
  120. height
  121. };
  122. }
  123. newState.lastAffix = !!newState.affixStyle;
  124. if (lastAffix !== newState.lastAffix) {
  125. emit('change', newState.lastAffix);
  126. }
  127. // update state
  128. _extends(state, newState);
  129. };
  130. const prepareMeasure = () => {
  131. _extends(state, {
  132. status: AffixStatus.Prepare,
  133. affixStyle: undefined,
  134. placeholderStyle: undefined
  135. });
  136. // Test if `updatePosition` called
  137. if (process.env.NODE_ENV === 'test') {
  138. emit('testUpdatePosition');
  139. }
  140. };
  141. const updatePosition = throttleByAnimationFrame(() => {
  142. prepareMeasure();
  143. });
  144. const lazyUpdatePosition = throttleByAnimationFrame(() => {
  145. const {
  146. target
  147. } = props;
  148. const {
  149. affixStyle
  150. } = state;
  151. // Check position change before measure to make Safari smooth
  152. if (target && affixStyle) {
  153. const targetNode = target();
  154. if (targetNode && placeholderNode.value) {
  155. const targetRect = getTargetRect(targetNode);
  156. const placeholderRect = getTargetRect(placeholderNode.value);
  157. const fixedTop = getFixedTop(placeholderRect, targetRect, offsetTop.value);
  158. const fixedBottom = getFixedBottom(placeholderRect, targetRect, offsetBottom.value);
  159. if (fixedTop !== undefined && affixStyle.top === fixedTop || fixedBottom !== undefined && affixStyle.bottom === fixedBottom) {
  160. return;
  161. }
  162. }
  163. }
  164. // Directly call prepare measure since it's already throttled.
  165. prepareMeasure();
  166. });
  167. expose({
  168. updatePosition,
  169. lazyUpdatePosition
  170. });
  171. watch(() => props.target, val => {
  172. const newTarget = (val === null || val === void 0 ? void 0 : val()) || null;
  173. if (state.prevTarget !== newTarget) {
  174. removeObserveTarget(currentInstance);
  175. if (newTarget) {
  176. addObserveTarget(newTarget, currentInstance);
  177. // Mock Event object.
  178. updatePosition();
  179. }
  180. state.prevTarget = newTarget;
  181. }
  182. });
  183. watch(() => [props.offsetTop, props.offsetBottom], updatePosition);
  184. onMounted(() => {
  185. const {
  186. target
  187. } = props;
  188. if (target) {
  189. // [Legacy] Wait for parent component ref has its value.
  190. // We should use target as directly element instead of function which makes element check hard.
  191. state.timeout = setTimeout(() => {
  192. addObserveTarget(target(), currentInstance);
  193. // Mock Event object.
  194. updatePosition();
  195. });
  196. }
  197. });
  198. onUpdated(() => {
  199. measure();
  200. });
  201. onUnmounted(() => {
  202. clearTimeout(state.timeout);
  203. removeObserveTarget(currentInstance);
  204. updatePosition.cancel();
  205. // https://github.com/ant-design/ant-design/issues/22683
  206. lazyUpdatePosition.cancel();
  207. });
  208. const {
  209. prefixCls
  210. } = useConfigInject('affix', props);
  211. const [wrapSSR, hashId] = useStyle(prefixCls);
  212. return () => {
  213. var _a;
  214. const {
  215. affixStyle,
  216. placeholderStyle,
  217. status
  218. } = state;
  219. const className = classNames({
  220. [prefixCls.value]: affixStyle,
  221. [hashId.value]: true
  222. });
  223. const restProps = omit(props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target', 'onChange', 'onTestUpdatePosition']);
  224. return wrapSSR(_createVNode(ResizeObserver, {
  225. "onResize": updatePosition
  226. }, {
  227. default: () => [_createVNode("div", _objectSpread(_objectSpread(_objectSpread({}, restProps), attrs), {}, {
  228. "ref": placeholderNode,
  229. "data-measure-status": status
  230. }), [affixStyle && _createVNode("div", {
  231. "style": placeholderStyle,
  232. "aria-hidden": "true"
  233. }, null), _createVNode("div", {
  234. "class": className,
  235. "ref": fixedNode,
  236. "style": affixStyle
  237. }, [(_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots)])])]
  238. }));
  239. };
  240. }
  241. });
  242. export default withInstall(Affix);