index.mjs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. import { defineComponent, useSlots, ref, computed, provide, reactive, watch, onBeforeUpdate, onMounted, openBlock, createElementBlock, normalizeClass, unref, Fragment, renderList, createBlock, withCtx, renderSlot, nextTick } from 'vue';
  2. import { isEqual, flattenDeep, cloneDeep } from 'lodash-unified';
  3. import ElCascaderMenu from './menu.mjs';
  4. import Store from './store.mjs';
  5. import Node from './node.mjs';
  6. import { cascaderPanelProps, cascaderPanelEmits, useCascaderConfig } from './config.mjs';
  7. import { sortByOriginalOrder, checkNode, getMenuIndex } from './utils.mjs';
  8. import { CASCADER_PANEL_INJECTION_KEY } from './types.mjs';
  9. import _export_sfc from '../../../_virtual/plugin-vue_export-helper.mjs';
  10. import { unique, castArray } from '../../../utils/arrays.mjs';
  11. import { focusNode, getSibling } from '../../../utils/dom/aria.mjs';
  12. import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
  13. import { UPDATE_MODEL_EVENT, CHANGE_EVENT } from '../../../constants/event.mjs';
  14. import { isEmpty } from '../../../utils/types.mjs';
  15. import { isClient } from '@vueuse/core';
  16. import { scrollIntoView } from '../../../utils/dom/scroll.mjs';
  17. import { getEventCode } from '../../../utils/dom/event.mjs';
  18. import { EVENT_CODE } from '../../../constants/aria.mjs';
  19. const _sfc_main = defineComponent({
  20. ...{
  21. name: "ElCascaderPanel"
  22. },
  23. __name: "index",
  24. props: cascaderPanelProps,
  25. emits: cascaderPanelEmits,
  26. setup(__props, { expose: __expose, emit: __emit }) {
  27. const props = __props;
  28. const emit = __emit;
  29. let manualChecked = false;
  30. const ns = useNamespace("cascader");
  31. const config = useCascaderConfig(props);
  32. const slots = useSlots();
  33. let store;
  34. const initialLoaded = ref(true);
  35. const initialLoadedOnce = ref(false);
  36. const menuList = ref([]);
  37. const checkedValue = ref();
  38. const menus = ref([]);
  39. const expandingNode = ref();
  40. const checkedNodes = ref([]);
  41. const isHoverMenu = computed(() => config.value.expandTrigger === "hover");
  42. const renderLabelFn = computed(() => props.renderLabel || slots.default);
  43. const initStore = () => {
  44. const { options } = props;
  45. const cfg = config.value;
  46. manualChecked = false;
  47. store = new Store(options, cfg);
  48. menus.value = [store.getNodes()];
  49. if (cfg.lazy && isEmpty(props.options)) {
  50. initialLoaded.value = false;
  51. lazyLoad(void 0, (list) => {
  52. if (list) {
  53. store = new Store(list, cfg);
  54. menus.value = [store.getNodes()];
  55. }
  56. initialLoaded.value = true;
  57. syncCheckedValue(false, true);
  58. });
  59. } else {
  60. syncCheckedValue(false, true);
  61. }
  62. };
  63. const lazyLoad = (node, cb) => {
  64. const cfg = config.value;
  65. node = node || new Node({}, cfg, void 0, true);
  66. node.loading = true;
  67. const resolve = (dataList) => {
  68. const _node = node;
  69. const parent = _node.root ? null : _node;
  70. _node.loading = false;
  71. _node.loaded = true;
  72. _node.childrenData = _node.childrenData || [];
  73. dataList && (store == null ? void 0 : store.appendNodes(dataList, parent));
  74. dataList && (cb == null ? void 0 : cb(dataList));
  75. if (node.level === 0) {
  76. initialLoadedOnce.value = true;
  77. }
  78. };
  79. const reject = () => {
  80. node.loading = false;
  81. node.loaded = false;
  82. if (node.level === 0) {
  83. initialLoaded.value = true;
  84. }
  85. };
  86. cfg.lazyLoad(node, resolve, reject);
  87. };
  88. const expandNode = (node, silent) => {
  89. var _a;
  90. const { level } = node;
  91. const newMenus = menus.value.slice(0, level);
  92. let newExpandingNode;
  93. if (node.isLeaf) {
  94. newExpandingNode = node.pathNodes[level - 2];
  95. } else {
  96. newExpandingNode = node;
  97. newMenus.push(node.children);
  98. }
  99. if (((_a = expandingNode.value) == null ? void 0 : _a.uid) !== (newExpandingNode == null ? void 0 : newExpandingNode.uid)) {
  100. expandingNode.value = node;
  101. menus.value = newMenus;
  102. !silent && emit("expand-change", (node == null ? void 0 : node.pathValues) || []);
  103. }
  104. };
  105. const handleCheckChange = (node, checked, emitClose = true) => {
  106. const { checkStrictly, multiple } = config.value;
  107. const oldNode = checkedNodes.value[0];
  108. manualChecked = true;
  109. !multiple && (oldNode == null ? void 0 : oldNode.doCheck(false));
  110. node.doCheck(checked);
  111. calculateCheckedValue();
  112. emitClose && !multiple && !checkStrictly && emit("close");
  113. !emitClose && !multiple && expandParentNode(node);
  114. };
  115. const expandParentNode = (node) => {
  116. if (!node)
  117. return;
  118. node = node.parent;
  119. expandParentNode(node);
  120. node && expandNode(node);
  121. };
  122. const getFlattedNodes = (leafOnly) => store == null ? void 0 : store.getFlattedNodes(leafOnly);
  123. const getCheckedNodes = (leafOnly) => {
  124. var _a;
  125. return (_a = getFlattedNodes(leafOnly)) == null ? void 0 : _a.filter(({ checked }) => checked !== false);
  126. };
  127. const clearCheckedNodes = () => {
  128. checkedNodes.value.forEach((node) => node.doCheck(false));
  129. calculateCheckedValue();
  130. menus.value = menus.value.slice(0, 1);
  131. expandingNode.value = void 0;
  132. emit("expand-change", []);
  133. };
  134. const calculateCheckedValue = () => {
  135. var _a;
  136. const { checkStrictly, multiple } = config.value;
  137. const oldNodes = checkedNodes.value;
  138. const newNodes = getCheckedNodes(!checkStrictly);
  139. const nodes = sortByOriginalOrder(oldNodes, newNodes);
  140. const values = nodes.map((node) => node.valueByOption);
  141. checkedNodes.value = nodes;
  142. checkedValue.value = multiple ? values : (_a = values[0]) != null ? _a : null;
  143. };
  144. const syncCheckedValue = (loaded = false, forced = false) => {
  145. const { modelValue } = props;
  146. const { lazy, multiple, checkStrictly } = config.value;
  147. const leafOnly = !checkStrictly;
  148. if (!initialLoaded.value || manualChecked || !forced && isEqual(modelValue, checkedValue.value))
  149. return;
  150. if (lazy && !loaded) {
  151. const values = unique(
  152. flattenDeep(castArray(modelValue))
  153. );
  154. const nodes = values.map((val) => store == null ? void 0 : store.getNodeByValue(val)).filter((node) => !!node && !node.loaded && !node.loading);
  155. if (nodes.length) {
  156. nodes.forEach((node) => {
  157. lazyLoad(node, () => syncCheckedValue(false, forced));
  158. });
  159. } else {
  160. syncCheckedValue(true, forced);
  161. }
  162. } else {
  163. const values = multiple ? castArray(modelValue) : [modelValue];
  164. const nodes = unique(
  165. values.map(
  166. (val) => store == null ? void 0 : store.getNodeByValue(val, leafOnly)
  167. )
  168. );
  169. syncMenuState(nodes, forced);
  170. checkedValue.value = cloneDeep(modelValue != null ? modelValue : void 0);
  171. }
  172. };
  173. const syncMenuState = (newCheckedNodes, reserveExpandingState = true) => {
  174. const { checkStrictly } = config.value;
  175. const oldNodes = checkedNodes.value;
  176. const newNodes = newCheckedNodes.filter(
  177. (node) => !!node && (checkStrictly || node.isLeaf)
  178. );
  179. const oldExpandingNode = store == null ? void 0 : store.getSameNode(expandingNode.value);
  180. const newExpandingNode = reserveExpandingState && oldExpandingNode || newNodes[0];
  181. if (newExpandingNode) {
  182. newExpandingNode.pathNodes.forEach((node) => expandNode(node, true));
  183. } else {
  184. expandingNode.value = void 0;
  185. }
  186. oldNodes.forEach((node) => node.doCheck(false));
  187. reactive(newNodes).forEach((node) => node.doCheck(true));
  188. checkedNodes.value = newNodes;
  189. nextTick(scrollToExpandingNode);
  190. };
  191. const scrollToExpandingNode = () => {
  192. if (!isClient)
  193. return;
  194. menuList.value.forEach((menu) => {
  195. const menuElement = menu == null ? void 0 : menu.$el;
  196. if (menuElement) {
  197. const container = menuElement.querySelector(
  198. `.${ns.namespace.value}-scrollbar__wrap`
  199. );
  200. let activeNode = menuElement.querySelector(
  201. `.${ns.b("node")}.in-active-path`
  202. );
  203. if (!activeNode) {
  204. const activeElements = menuElement.querySelectorAll(
  205. `.${ns.b("node")}.${ns.is("active")}`
  206. );
  207. activeNode = activeElements[activeElements.length - 1];
  208. }
  209. scrollIntoView(container, activeNode);
  210. }
  211. });
  212. };
  213. const handleKeyDown = (e) => {
  214. const target = e.target;
  215. const code = getEventCode(e);
  216. switch (code) {
  217. case EVENT_CODE.up:
  218. case EVENT_CODE.down: {
  219. e.preventDefault();
  220. const distance = code === EVENT_CODE.up ? -1 : 1;
  221. focusNode(
  222. getSibling(
  223. target,
  224. distance,
  225. `.${ns.b("node")}[tabindex="-1"]`
  226. )
  227. );
  228. break;
  229. }
  230. case EVENT_CODE.left: {
  231. e.preventDefault();
  232. const preMenu = menuList.value[getMenuIndex(target) - 1];
  233. const expandedNode = preMenu == null ? void 0 : preMenu.$el.querySelector(
  234. `.${ns.b("node")}[aria-expanded="true"]`
  235. );
  236. focusNode(expandedNode);
  237. break;
  238. }
  239. case EVENT_CODE.right: {
  240. e.preventDefault();
  241. const nextMenu = menuList.value[getMenuIndex(target) + 1];
  242. const firstNode = nextMenu == null ? void 0 : nextMenu.$el.querySelector(
  243. `.${ns.b("node")}[tabindex="-1"]`
  244. );
  245. focusNode(firstNode);
  246. break;
  247. }
  248. case EVENT_CODE.enter:
  249. case EVENT_CODE.numpadEnter:
  250. checkNode(target);
  251. break;
  252. }
  253. };
  254. provide(
  255. CASCADER_PANEL_INJECTION_KEY,
  256. reactive({
  257. config,
  258. expandingNode,
  259. checkedNodes,
  260. isHoverMenu,
  261. initialLoaded,
  262. renderLabelFn,
  263. lazyLoad,
  264. expandNode,
  265. handleCheckChange
  266. })
  267. );
  268. watch(
  269. config,
  270. (newVal, oldVal) => {
  271. if (isEqual(newVal, oldVal))
  272. return;
  273. initStore();
  274. },
  275. {
  276. immediate: true
  277. }
  278. );
  279. watch(() => props.options, initStore, {
  280. deep: true
  281. });
  282. watch(
  283. () => props.modelValue,
  284. () => {
  285. manualChecked = false;
  286. syncCheckedValue();
  287. },
  288. {
  289. deep: true
  290. }
  291. );
  292. watch(
  293. () => checkedValue.value,
  294. (val) => {
  295. if (!isEqual(val, props.modelValue)) {
  296. emit(UPDATE_MODEL_EVENT, val);
  297. emit(CHANGE_EVENT, val);
  298. }
  299. }
  300. );
  301. const loadLazyRootNodes = () => {
  302. if (initialLoadedOnce.value)
  303. return;
  304. initStore();
  305. };
  306. onBeforeUpdate(() => menuList.value = []);
  307. onMounted(() => !isEmpty(props.modelValue) && syncCheckedValue());
  308. __expose({
  309. menuList,
  310. menus,
  311. checkedNodes,
  312. handleKeyDown,
  313. handleCheckChange,
  314. getFlattedNodes,
  315. getCheckedNodes,
  316. clearCheckedNodes,
  317. calculateCheckedValue,
  318. scrollToExpandingNode,
  319. loadLazyRootNodes
  320. });
  321. return (_ctx, _cache) => {
  322. return openBlock(), createElementBlock(
  323. "div",
  324. {
  325. class: normalizeClass([unref(ns).b("panel"), unref(ns).is("bordered", _ctx.border)]),
  326. onKeydown: handleKeyDown
  327. },
  328. [
  329. (openBlock(true), createElementBlock(
  330. Fragment,
  331. null,
  332. renderList(menus.value, (menu, index) => {
  333. return openBlock(), createBlock(ElCascaderMenu, {
  334. key: index,
  335. ref_for: true,
  336. ref: (item) => menuList.value[index] = item,
  337. index,
  338. nodes: [...menu]
  339. }, {
  340. empty: withCtx(() => [
  341. renderSlot(_ctx.$slots, "empty")
  342. ]),
  343. _: 3
  344. }, 8, ["index", "nodes"]);
  345. }),
  346. 128
  347. ))
  348. ],
  349. 34
  350. );
  351. };
  352. }
  353. });
  354. var CascaderPanel = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "/home/runner/work/element-plus/element-plus/packages/components/cascader-panel/src/index.vue"]]);
  355. export { CascaderPanel as default };
  356. //# sourceMappingURL=index.mjs.map