index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import { Keyframes, unit } from '@ant-design/cssinjs';
  2. import { getStyle as getCheckboxStyle } from '../../checkbox/style';
  3. import { genFocusOutline, resetComponent } from '../../style';
  4. import { genCollapseMotion } from '../../style/motion';
  5. import { genStyleHooks, mergeToken } from '../../theme/internal';
  6. import { genDirectoryStyle } from './directory';
  7. // ============================ Keyframes =============================
  8. const treeNodeFX = new Keyframes('ant-tree-node-fx-do-not-use', {
  9. '0%': {
  10. opacity: 0
  11. },
  12. '100%': {
  13. opacity: 1
  14. }
  15. });
  16. // ============================== Switch ==============================
  17. const getSwitchStyle = (prefixCls, token) => ({
  18. [`.${prefixCls}-switcher-icon`]: {
  19. display: 'inline-block',
  20. fontSize: 10,
  21. verticalAlign: 'baseline',
  22. svg: {
  23. transition: `transform ${token.motionDurationSlow}`
  24. }
  25. }
  26. });
  27. // =============================== Drop ===============================
  28. const getDropIndicatorStyle = (prefixCls, token) => ({
  29. [`.${prefixCls}-drop-indicator`]: {
  30. position: 'absolute',
  31. // it should displayed over the following node
  32. zIndex: 1,
  33. height: 2,
  34. backgroundColor: token.colorPrimary,
  35. borderRadius: 1,
  36. pointerEvents: 'none',
  37. '&:after': {
  38. position: 'absolute',
  39. top: -3,
  40. insetInlineStart: -6,
  41. width: 8,
  42. height: 8,
  43. backgroundColor: 'transparent',
  44. border: `${unit(token.lineWidthBold)} solid ${token.colorPrimary}`,
  45. borderRadius: '50%',
  46. content: '""'
  47. }
  48. }
  49. });
  50. export const genBaseStyle = (prefixCls, token) => {
  51. const {
  52. treeCls,
  53. treeNodeCls,
  54. treeNodePadding,
  55. titleHeight,
  56. indentSize,
  57. nodeSelectedBg,
  58. nodeHoverBg,
  59. colorTextQuaternary,
  60. controlItemBgActiveDisabled
  61. } = token;
  62. return {
  63. [treeCls]: Object.assign(Object.assign({}, resetComponent(token)), {
  64. // fix https://github.com/ant-design/ant-design/issues/50316
  65. ['--rc-virtual-list-scrollbar-bg']: token.colorSplit,
  66. background: token.colorBgContainer,
  67. borderRadius: token.borderRadius,
  68. transition: `background-color ${token.motionDurationSlow}`,
  69. '&-rtl': {
  70. direction: 'rtl'
  71. },
  72. [`&${treeCls}-rtl ${treeCls}-switcher_close ${treeCls}-switcher-icon svg`]: {
  73. transform: 'rotate(90deg)'
  74. },
  75. [`&-focused:not(:hover):not(${treeCls}-active-focused)`]: genFocusOutline(token),
  76. // =================== Virtual List ===================
  77. [`${treeCls}-list-holder-inner`]: {
  78. alignItems: 'flex-start'
  79. },
  80. [`&${treeCls}-block-node`]: {
  81. [`${treeCls}-list-holder-inner`]: {
  82. alignItems: 'stretch',
  83. // >>> Title
  84. [`${treeCls}-node-content-wrapper`]: {
  85. flex: 'auto'
  86. },
  87. // >>> Drag
  88. [`${treeNodeCls}.dragging:after`]: {
  89. position: 'absolute',
  90. inset: 0,
  91. border: `1px solid ${token.colorPrimary}`,
  92. opacity: 0,
  93. animationName: treeNodeFX,
  94. animationDuration: token.motionDurationSlow,
  95. animationPlayState: 'running',
  96. animationFillMode: 'forwards',
  97. content: '""',
  98. pointerEvents: 'none',
  99. borderRadius: token.borderRadius
  100. }
  101. }
  102. },
  103. // ===================== TreeNode =====================
  104. [treeNodeCls]: {
  105. display: 'flex',
  106. alignItems: 'flex-start',
  107. marginBottom: treeNodePadding,
  108. lineHeight: unit(titleHeight),
  109. position: 'relative',
  110. // 非常重要,避免 drop-indicator 在拖拽过程中闪烁
  111. '&:before': {
  112. content: '""',
  113. position: 'absolute',
  114. zIndex: 1,
  115. insetInlineStart: 0,
  116. width: '100%',
  117. top: '100%',
  118. height: treeNodePadding
  119. },
  120. // Disabled
  121. [`&-disabled ${treeCls}-node-content-wrapper`]: {
  122. color: token.colorTextDisabled,
  123. cursor: 'not-allowed',
  124. '&:hover': {
  125. background: 'transparent'
  126. }
  127. },
  128. [`${treeCls}-checkbox-disabled + ${treeCls}-node-selected,&${treeNodeCls}-disabled${treeNodeCls}-selected ${treeCls}-node-content-wrapper`]: {
  129. backgroundColor: controlItemBgActiveDisabled
  130. },
  131. // we can not set pointer-events to none for checkbox in tree
  132. // ref: https://github.com/ant-design/ant-design/issues/39822#issuecomment-2605234058
  133. [`${treeCls}-checkbox-disabled`]: {
  134. pointerEvents: 'unset'
  135. },
  136. // not disable
  137. [`&:not(${treeNodeCls}-disabled)`]: {
  138. // >>> Title
  139. [`${treeCls}-node-content-wrapper`]: {
  140. '&:hover': {
  141. color: token.nodeHoverColor
  142. }
  143. }
  144. },
  145. [`&-active ${treeCls}-node-content-wrapper`]: {
  146. background: token.controlItemBgHover
  147. },
  148. [`&:not(${treeNodeCls}-disabled).filter-node ${treeCls}-title`]: {
  149. color: token.colorPrimary,
  150. fontWeight: token.fontWeightStrong
  151. },
  152. '&-draggable': {
  153. cursor: 'grab',
  154. [`${treeCls}-draggable-icon`]: {
  155. // https://github.com/ant-design/ant-design/issues/41915
  156. flexShrink: 0,
  157. width: titleHeight,
  158. textAlign: 'center',
  159. visibility: 'visible',
  160. color: colorTextQuaternary
  161. },
  162. [`&${treeNodeCls}-disabled ${treeCls}-draggable-icon`]: {
  163. visibility: 'hidden'
  164. }
  165. }
  166. },
  167. // >>> Indent
  168. [`${treeCls}-indent`]: {
  169. alignSelf: 'stretch',
  170. whiteSpace: 'nowrap',
  171. userSelect: 'none',
  172. '&-unit': {
  173. display: 'inline-block',
  174. width: indentSize
  175. }
  176. },
  177. // >>> Drag Handler
  178. [`${treeCls}-draggable-icon`]: {
  179. visibility: 'hidden'
  180. },
  181. // Switcher / Checkbox
  182. [`${treeCls}-switcher, ${treeCls}-checkbox`]: {
  183. marginInlineEnd: token.calc(token.calc(titleHeight).sub(token.controlInteractiveSize)).div(2).equal()
  184. },
  185. // >>> Switcher
  186. [`${treeCls}-switcher`]: Object.assign(Object.assign({}, getSwitchStyle(prefixCls, token)), {
  187. position: 'relative',
  188. flex: 'none',
  189. alignSelf: 'stretch',
  190. width: titleHeight,
  191. textAlign: 'center',
  192. cursor: 'pointer',
  193. userSelect: 'none',
  194. transition: `all ${token.motionDurationSlow}`,
  195. '&-noop': {
  196. cursor: 'unset'
  197. },
  198. '&:before': {
  199. pointerEvents: 'none',
  200. content: '""',
  201. width: titleHeight,
  202. height: titleHeight,
  203. position: 'absolute',
  204. left: {
  205. _skip_check_: true,
  206. value: 0
  207. },
  208. top: 0,
  209. borderRadius: token.borderRadius,
  210. transition: `all ${token.motionDurationSlow}`
  211. },
  212. [`&:not(${treeCls}-switcher-noop):hover:before`]: {
  213. backgroundColor: token.colorBgTextHover
  214. },
  215. [`&_close ${treeCls}-switcher-icon svg`]: {
  216. transform: 'rotate(-90deg)'
  217. },
  218. '&-loading-icon': {
  219. color: token.colorPrimary
  220. },
  221. '&-leaf-line': {
  222. position: 'relative',
  223. zIndex: 1,
  224. display: 'inline-block',
  225. width: '100%',
  226. height: '100%',
  227. // https://github.com/ant-design/ant-design/issues/31884
  228. '&:before': {
  229. position: 'absolute',
  230. top: 0,
  231. insetInlineEnd: token.calc(titleHeight).div(2).equal(),
  232. bottom: token.calc(treeNodePadding).mul(-1).equal(),
  233. marginInlineStart: -1,
  234. borderInlineEnd: `1px solid ${token.colorBorder}`,
  235. content: '""'
  236. },
  237. '&:after': {
  238. position: 'absolute',
  239. width: token.calc(token.calc(titleHeight).div(2).equal()).mul(0.8).equal(),
  240. height: token.calc(titleHeight).div(2).equal(),
  241. borderBottom: `1px solid ${token.colorBorder}`,
  242. content: '""'
  243. }
  244. }
  245. }),
  246. // >>> Title
  247. // add `${treeCls}-checkbox + span` to cover checkbox `${checkboxCls} + span`
  248. [`${treeCls}-node-content-wrapper`]: Object.assign(Object.assign({
  249. position: 'relative',
  250. minHeight: titleHeight,
  251. paddingBlock: 0,
  252. paddingInline: token.paddingXS,
  253. background: 'transparent',
  254. borderRadius: token.borderRadius,
  255. cursor: 'pointer',
  256. transition: `all ${token.motionDurationMid}, border 0s, line-height 0s, box-shadow 0s`
  257. }, getDropIndicatorStyle(prefixCls, token)), {
  258. '&:hover': {
  259. backgroundColor: nodeHoverBg
  260. },
  261. [`&${treeCls}-node-selected`]: {
  262. color: token.nodeSelectedColor,
  263. backgroundColor: nodeSelectedBg
  264. },
  265. // Icon
  266. [`${treeCls}-iconEle`]: {
  267. display: 'inline-block',
  268. width: titleHeight,
  269. height: titleHeight,
  270. textAlign: 'center',
  271. verticalAlign: 'top',
  272. '&:empty': {
  273. display: 'none'
  274. }
  275. }
  276. }),
  277. // https://github.com/ant-design/ant-design/issues/28217
  278. [`${treeCls}-unselectable ${treeCls}-node-content-wrapper:hover`]: {
  279. backgroundColor: 'transparent'
  280. },
  281. [`${treeNodeCls}.drop-container > [draggable]`]: {
  282. boxShadow: `0 0 0 2px ${token.colorPrimary}`
  283. },
  284. // ==================== Show Line =====================
  285. '&-show-line': {
  286. // ================ Indent lines ================
  287. [`${treeCls}-indent-unit`]: {
  288. position: 'relative',
  289. height: '100%',
  290. '&:before': {
  291. position: 'absolute',
  292. top: 0,
  293. insetInlineEnd: token.calc(titleHeight).div(2).equal(),
  294. bottom: token.calc(treeNodePadding).mul(-1).equal(),
  295. borderInlineEnd: `1px solid ${token.colorBorder}`,
  296. content: '""'
  297. },
  298. '&-end:before': {
  299. display: 'none'
  300. }
  301. },
  302. // ============== Cover Background ==============
  303. [`${treeCls}-switcher`]: {
  304. background: 'transparent',
  305. '&-line-icon': {
  306. // https://github.com/ant-design/ant-design/issues/32813
  307. verticalAlign: '-0.15em'
  308. }
  309. }
  310. },
  311. [`${treeNodeCls}-leaf-last ${treeCls}-switcher-leaf-line:before`]: {
  312. top: 'auto !important',
  313. bottom: 'auto !important',
  314. height: `${unit(token.calc(titleHeight).div(2).equal())} !important`
  315. }
  316. })
  317. };
  318. };
  319. // ============================== Merged ==============================
  320. export const genTreeStyle = (prefixCls, token,
  321. /**
  322. * @descCN 是否启用目录树样式
  323. * @descEN Whether to enable directory style
  324. * @default true
  325. */
  326. enableDirectory = true) => {
  327. const treeCls = `.${prefixCls}`;
  328. const treeNodeCls = `${treeCls}-treenode`;
  329. const treeNodePadding = token.calc(token.paddingXS).div(2).equal();
  330. const treeToken = mergeToken(token, {
  331. treeCls,
  332. treeNodeCls,
  333. treeNodePadding
  334. });
  335. return [
  336. // Basic
  337. genBaseStyle(prefixCls, treeToken),
  338. // Directory
  339. enableDirectory && genDirectoryStyle(treeToken)].filter(Boolean);
  340. };
  341. export const initComponentToken = token => {
  342. const {
  343. controlHeightSM,
  344. controlItemBgHover,
  345. controlItemBgActive
  346. } = token;
  347. const titleHeight = controlHeightSM;
  348. return {
  349. titleHeight,
  350. indentSize: titleHeight,
  351. nodeHoverBg: controlItemBgHover,
  352. nodeHoverColor: token.colorText,
  353. nodeSelectedBg: controlItemBgActive,
  354. nodeSelectedColor: token.colorText
  355. };
  356. };
  357. export const prepareComponentToken = token => {
  358. const {
  359. colorTextLightSolid,
  360. colorPrimary
  361. } = token;
  362. return Object.assign(Object.assign({}, initComponentToken(token)), {
  363. directoryNodeSelectedColor: colorTextLightSolid,
  364. directoryNodeSelectedBg: colorPrimary
  365. });
  366. };
  367. export default genStyleHooks('Tree', (token, {
  368. prefixCls
  369. }) => [{
  370. [token.componentCls]: getCheckboxStyle(`${prefixCls}-checkbox`, token)
  371. }, genTreeStyle(prefixCls, token), genCollapseMotion(token)], prepareComponentToken);