DOMWrap.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
  6. var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
  7. var _extends2 = require('babel-runtime/helpers/extends');
  8. var _extends3 = _interopRequireDefault(_extends2);
  9. var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
  10. var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
  11. var _vueTypes = require('../_util/vue-types');
  12. var _vueTypes2 = _interopRequireDefault(_vueTypes);
  13. var _resizeObserverPolyfill = require('resize-observer-polyfill');
  14. var _resizeObserverPolyfill2 = _interopRequireDefault(_resizeObserverPolyfill);
  15. var _SubMenu = require('./SubMenu');
  16. var _SubMenu2 = _interopRequireDefault(_SubMenu);
  17. var _BaseMixin = require('../_util/BaseMixin');
  18. var _BaseMixin2 = _interopRequireDefault(_BaseMixin);
  19. var _util = require('./util');
  20. var _vnode = require('../_util/vnode');
  21. var _propsUtil = require('../_util/props-util');
  22. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  23. var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
  24. var MENUITEM_OVERFLOWED_CLASSNAME = 'menuitem-overflowed';
  25. var FLOAT_PRECISION_ADJUST = 0.5;
  26. // Fix ssr
  27. if (canUseDOM) {
  28. require('mutationobserver-shim');
  29. }
  30. var DOMWrap = {
  31. name: 'DOMWrap',
  32. mixins: [_BaseMixin2['default']],
  33. data: function data() {
  34. this.resizeObserver = null;
  35. this.mutationObserver = null;
  36. // original scroll size of the list
  37. this.originalTotalWidth = 0;
  38. // copy of overflowed items
  39. this.overflowedItems = [];
  40. // cache item of the original items (so we can track the size and order)
  41. this.menuItemSizes = [];
  42. return {
  43. lastVisibleIndex: undefined
  44. };
  45. },
  46. mounted: function mounted() {
  47. var _this = this;
  48. this.$nextTick(function () {
  49. _this.setChildrenWidthAndResize();
  50. if (_this.level === 1 && _this.mode === 'horizontal') {
  51. var menuUl = _this.$el;
  52. if (!menuUl) {
  53. return;
  54. }
  55. _this.resizeObserver = new _resizeObserverPolyfill2['default'](function (entries) {
  56. entries.forEach(_this.setChildrenWidthAndResize);
  57. });
  58. [].slice.call(menuUl.children).concat(menuUl).forEach(function (el) {
  59. _this.resizeObserver.observe(el);
  60. });
  61. if (typeof MutationObserver !== 'undefined') {
  62. _this.mutationObserver = new MutationObserver(function () {
  63. _this.resizeObserver.disconnect();
  64. [].slice.call(menuUl.children).concat(menuUl).forEach(function (el) {
  65. _this.resizeObserver.observe(el);
  66. });
  67. _this.setChildrenWidthAndResize();
  68. });
  69. _this.mutationObserver.observe(menuUl, {
  70. attributes: false,
  71. childList: true,
  72. subTree: false
  73. });
  74. }
  75. }
  76. });
  77. },
  78. beforeDestroy: function beforeDestroy() {
  79. if (this.resizeObserver) {
  80. this.resizeObserver.disconnect();
  81. }
  82. if (this.mutationObserver) {
  83. this.mutationObserver.disconnect();
  84. }
  85. },
  86. methods: {
  87. // get all valid menuItem nodes
  88. getMenuItemNodes: function getMenuItemNodes() {
  89. var prefixCls = this.$props.prefixCls;
  90. var ul = this.$el;
  91. if (!ul) {
  92. return [];
  93. }
  94. // filter out all overflowed indicator placeholder
  95. return [].slice.call(ul.children).filter(function (node) {
  96. return node.className.split(' ').indexOf(prefixCls + '-overflowed-submenu') < 0;
  97. });
  98. },
  99. getOverflowedSubMenuItem: function getOverflowedSubMenuItem(keyPrefix, overflowedItems, renderPlaceholder) {
  100. var h = this.$createElement;
  101. var _$props = this.$props,
  102. overflowedIndicator = _$props.overflowedIndicator,
  103. level = _$props.level,
  104. mode = _$props.mode,
  105. prefixCls = _$props.prefixCls,
  106. theme = _$props.theme;
  107. if (level !== 1 || mode !== 'horizontal') {
  108. return null;
  109. }
  110. // put all the overflowed item inside a submenu
  111. // with a title of overflow indicator ('...')
  112. var copy = this.$slots['default'][0];
  113. var _getPropsData = (0, _propsUtil.getPropsData)(copy),
  114. title = _getPropsData.title,
  115. rest = (0, _objectWithoutProperties3['default'])(_getPropsData, ['title']); // eslint-disable-line no-unused-vars
  116. var events = (0, _propsUtil.getEvents)(copy);
  117. var style = {};
  118. var key = keyPrefix + '-overflowed-indicator';
  119. var eventKey = keyPrefix + '-overflowed-indicator';
  120. if (overflowedItems.length === 0 && renderPlaceholder !== true) {
  121. style = {
  122. display: 'none'
  123. };
  124. } else if (renderPlaceholder) {
  125. style = {
  126. visibility: 'hidden',
  127. // prevent from taking normal dom space
  128. position: 'absolute'
  129. };
  130. key = key + '-placeholder';
  131. eventKey = eventKey + '-placeholder';
  132. }
  133. var popupClassName = theme ? prefixCls + '-' + theme : '';
  134. var props = {};
  135. var on = {};
  136. _util.menuAllProps.props.forEach(function (k) {
  137. if (rest[k] !== undefined) {
  138. props[k] = rest[k];
  139. }
  140. });
  141. _util.menuAllProps.on.forEach(function (k) {
  142. if (events[k] !== undefined) {
  143. on[k] = events[k];
  144. }
  145. });
  146. var subMenuProps = {
  147. props: (0, _extends3['default'])({
  148. title: overflowedIndicator,
  149. popupClassName: popupClassName
  150. }, props, {
  151. eventKey: eventKey,
  152. disabled: false
  153. }),
  154. 'class': prefixCls + '-overflowed-submenu',
  155. key: key,
  156. style: style,
  157. on: on
  158. };
  159. return h(
  160. _SubMenu2['default'],
  161. subMenuProps,
  162. [overflowedItems]
  163. );
  164. },
  165. // memorize rendered menuSize
  166. setChildrenWidthAndResize: function setChildrenWidthAndResize() {
  167. if (this.mode !== 'horizontal') {
  168. return;
  169. }
  170. var ul = this.$el;
  171. if (!ul) {
  172. return;
  173. }
  174. var ulChildrenNodes = ul.children;
  175. if (!ulChildrenNodes || ulChildrenNodes.length === 0) {
  176. return;
  177. }
  178. var lastOverflowedIndicatorPlaceholder = ul.children[ulChildrenNodes.length - 1];
  179. // need last overflowed indicator for calculating length;
  180. (0, _util.setStyle)(lastOverflowedIndicatorPlaceholder, 'display', 'inline-block');
  181. var menuItemNodes = this.getMenuItemNodes();
  182. // reset display attribute for all hidden elements caused by overflow to calculate updated width
  183. // and then reset to original state after width calculation
  184. var overflowedItems = menuItemNodes.filter(function (c) {
  185. return c.className.split(' ').indexOf(MENUITEM_OVERFLOWED_CLASSNAME) >= 0;
  186. });
  187. overflowedItems.forEach(function (c) {
  188. (0, _util.setStyle)(c, 'display', 'inline-block');
  189. });
  190. this.menuItemSizes = menuItemNodes.map(function (c) {
  191. return (0, _util.getWidth)(c);
  192. });
  193. overflowedItems.forEach(function (c) {
  194. (0, _util.setStyle)(c, 'display', 'none');
  195. });
  196. this.overflowedIndicatorWidth = (0, _util.getWidth)(ul.children[ul.children.length - 1]);
  197. this.originalTotalWidth = this.menuItemSizes.reduce(function (acc, cur) {
  198. return acc + cur;
  199. }, 0);
  200. this.handleResize();
  201. // prevent the overflowed indicator from taking space;
  202. (0, _util.setStyle)(lastOverflowedIndicatorPlaceholder, 'display', 'none');
  203. },
  204. handleResize: function handleResize() {
  205. var _this2 = this;
  206. if (this.mode !== 'horizontal') {
  207. return;
  208. }
  209. var ul = this.$el;
  210. if (!ul) {
  211. return;
  212. }
  213. var width = (0, _util.getWidth)(ul);
  214. this.overflowedItems = [];
  215. var currentSumWidth = 0;
  216. // index for last visible child in horizontal mode
  217. var lastVisibleIndex = void 0;
  218. // float number comparison could be problematic
  219. // e.g. 0.1 + 0.2 > 0.3 =====> true
  220. // thus using FLOAT_PRECISION_ADJUST as buffer to help the situation
  221. if (this.originalTotalWidth > width + FLOAT_PRECISION_ADJUST) {
  222. lastVisibleIndex = -1;
  223. this.menuItemSizes.forEach(function (liWidth) {
  224. currentSumWidth += liWidth;
  225. if (currentSumWidth + _this2.overflowedIndicatorWidth <= width) {
  226. lastVisibleIndex += 1;
  227. }
  228. });
  229. }
  230. this.setState({ lastVisibleIndex: lastVisibleIndex });
  231. },
  232. renderChildren: function renderChildren(children) {
  233. var _this3 = this;
  234. // need to take care of overflowed items in horizontal mode
  235. var lastVisibleIndex = this.$data.lastVisibleIndex;
  236. var className = (0, _propsUtil.getClass)(this);
  237. return (children || []).reduce(function (acc, childNode, index) {
  238. var item = childNode;
  239. var eventKey = (0, _propsUtil.getPropsData)(childNode).eventKey;
  240. if (_this3.mode === 'horizontal') {
  241. var overflowed = _this3.getOverflowedSubMenuItem(eventKey, []);
  242. if (lastVisibleIndex !== undefined && className[_this3.prefixCls + '-root'] !== -1) {
  243. if (index > lastVisibleIndex) {
  244. item = (0, _vnode.cloneElement)(childNode,
  245. // 这里修改 eventKey 是为了防止隐藏状态下还会触发 openkeys 事件
  246. {
  247. style: { display: 'none' },
  248. props: { eventKey: eventKey + '-hidden' },
  249. 'class': MENUITEM_OVERFLOWED_CLASSNAME
  250. });
  251. }
  252. if (index === lastVisibleIndex + 1) {
  253. _this3.overflowedItems = children.slice(lastVisibleIndex + 1).map(function (c) {
  254. return (0, _vnode.cloneElement)(c,
  255. // children[index].key will become '.$key' in clone by default,
  256. // we have to overwrite with the correct key explicitly
  257. {
  258. key: (0, _propsUtil.getPropsData)(c).eventKey,
  259. props: { mode: 'vertical-left' }
  260. });
  261. });
  262. overflowed = _this3.getOverflowedSubMenuItem(eventKey, _this3.overflowedItems);
  263. }
  264. }
  265. var ret = [].concat((0, _toConsumableArray3['default'])(acc), [overflowed, item]);
  266. if (index === children.length - 1) {
  267. // need a placeholder for calculating overflowed indicator width
  268. ret.push(_this3.getOverflowedSubMenuItem(eventKey, [], true));
  269. }
  270. return ret;
  271. }
  272. return [].concat((0, _toConsumableArray3['default'])(acc), [item]);
  273. }, []);
  274. }
  275. },
  276. render: function render() {
  277. var h = arguments[0];
  278. var Tag = this.$props.tag;
  279. var tagProps = {
  280. on: (0, _propsUtil.getListeners)(this)
  281. };
  282. return h(
  283. Tag,
  284. tagProps,
  285. [this.renderChildren(this.$slots['default'])]
  286. );
  287. }
  288. };
  289. DOMWrap.props = {
  290. mode: _vueTypes2['default'].oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']),
  291. prefixCls: _vueTypes2['default'].string,
  292. level: _vueTypes2['default'].number,
  293. theme: _vueTypes2['default'].string,
  294. overflowedIndicator: _vueTypes2['default'].node,
  295. visible: _vueTypes2['default'].bool,
  296. hiddenClassName: _vueTypes2['default'].string,
  297. tag: _vueTypes2['default'].string.def('div')
  298. };
  299. exports['default'] = DOMWrap;