Cascader.mjs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import { ref, watch, nextTick, defineComponent, createVNode as _createVNode } from "vue";
  2. import { extend, truthProp, numericProp, makeArrayProp, makeStringProp, createNamespace, HAPTICS_FEEDBACK } from "../utils/index.mjs";
  3. import { useRefs } from "../composables/use-refs.mjs";
  4. import { Tab } from "../tab/index.mjs";
  5. import { Tabs } from "../tabs/index.mjs";
  6. import { Icon } from "../icon/index.mjs";
  7. const [name, bem, t] = createNamespace("cascader");
  8. const cascaderProps = {
  9. title: String,
  10. options: makeArrayProp(),
  11. closeable: truthProp,
  12. swipeable: truthProp,
  13. closeIcon: makeStringProp("cross"),
  14. showHeader: truthProp,
  15. modelValue: numericProp,
  16. fieldNames: Object,
  17. placeholder: String,
  18. activeColor: String
  19. };
  20. var stdin_default = defineComponent({
  21. name,
  22. props: cascaderProps,
  23. emits: ["close", "change", "finish", "clickTab", "update:modelValue"],
  24. setup(props, {
  25. slots,
  26. emit
  27. }) {
  28. const tabs = ref([]);
  29. const activeTab = ref(0);
  30. const [selectedElementRefs, setSelectedElementRefs] = useRefs();
  31. const {
  32. text: textKey,
  33. value: valueKey,
  34. children: childrenKey
  35. } = extend({
  36. text: "text",
  37. value: "value",
  38. children: "children"
  39. }, props.fieldNames);
  40. const getSelectedOptionsByValue = (options, value) => {
  41. for (const option of options) {
  42. if (option[valueKey] === value) {
  43. return [option];
  44. }
  45. if (option[childrenKey]) {
  46. const selectedOptions = getSelectedOptionsByValue(option[childrenKey], value);
  47. if (selectedOptions) {
  48. return [option, ...selectedOptions];
  49. }
  50. }
  51. }
  52. };
  53. const updateTabs = () => {
  54. const {
  55. options,
  56. modelValue
  57. } = props;
  58. if (modelValue !== void 0) {
  59. const selectedOptions = getSelectedOptionsByValue(options, modelValue);
  60. if (selectedOptions) {
  61. let optionsCursor = options;
  62. tabs.value = selectedOptions.map((option) => {
  63. const tab = {
  64. options: optionsCursor,
  65. selected: option
  66. };
  67. const next = optionsCursor.find((item) => item[valueKey] === option[valueKey]);
  68. if (next) {
  69. optionsCursor = next[childrenKey];
  70. }
  71. return tab;
  72. });
  73. if (optionsCursor) {
  74. tabs.value.push({
  75. options: optionsCursor,
  76. selected: null
  77. });
  78. }
  79. nextTick(() => {
  80. activeTab.value = tabs.value.length - 1;
  81. });
  82. return;
  83. }
  84. }
  85. tabs.value = [{
  86. options,
  87. selected: null
  88. }];
  89. };
  90. const onSelect = (option, tabIndex) => {
  91. if (option.disabled) {
  92. return;
  93. }
  94. tabs.value[tabIndex].selected = option;
  95. if (tabs.value.length > tabIndex + 1) {
  96. tabs.value = tabs.value.slice(0, tabIndex + 1);
  97. }
  98. if (option[childrenKey]) {
  99. const nextTab = {
  100. options: option[childrenKey],
  101. selected: null
  102. };
  103. if (tabs.value[tabIndex + 1]) {
  104. tabs.value[tabIndex + 1] = nextTab;
  105. } else {
  106. tabs.value.push(nextTab);
  107. }
  108. nextTick(() => {
  109. activeTab.value++;
  110. });
  111. }
  112. const selectedOptions = tabs.value.map((tab) => tab.selected).filter(Boolean);
  113. emit("update:modelValue", option[valueKey]);
  114. const params = {
  115. value: option[valueKey],
  116. tabIndex,
  117. selectedOptions
  118. };
  119. emit("change", params);
  120. if (!option[childrenKey]) {
  121. emit("finish", params);
  122. }
  123. };
  124. const onClose = () => emit("close");
  125. const onClickTab = ({
  126. name: name2,
  127. title
  128. }) => emit("clickTab", name2, title);
  129. const renderHeader = () => props.showHeader ? _createVNode("div", {
  130. "class": bem("header")
  131. }, [_createVNode("h2", {
  132. "class": bem("title")
  133. }, [slots.title ? slots.title() : props.title]), props.closeable ? _createVNode(Icon, {
  134. "name": props.closeIcon,
  135. "class": [bem("close-icon"), HAPTICS_FEEDBACK],
  136. "onClick": onClose
  137. }, null) : null]) : null;
  138. const renderOption = (option, selectedOption, tabIndex) => {
  139. const {
  140. disabled
  141. } = option;
  142. const selected = !!(selectedOption && option[valueKey] === selectedOption[valueKey]);
  143. const color = option.color || (selected ? props.activeColor : void 0);
  144. const Text = slots.option ? slots.option({
  145. option,
  146. selected
  147. }) : _createVNode("span", null, [option[textKey]]);
  148. return _createVNode("li", {
  149. "ref": selected ? setSelectedElementRefs(tabIndex) : void 0,
  150. "role": "menuitemradio",
  151. "class": [bem("option", {
  152. selected,
  153. disabled
  154. }), option.className],
  155. "style": {
  156. color
  157. },
  158. "tabindex": disabled ? void 0 : selected ? 0 : -1,
  159. "aria-checked": selected,
  160. "aria-disabled": disabled || void 0,
  161. "onClick": () => onSelect(option, tabIndex)
  162. }, [Text, selected ? _createVNode(Icon, {
  163. "name": "success",
  164. "class": bem("selected-icon")
  165. }, null) : null]);
  166. };
  167. const renderOptions = (options, selectedOption, tabIndex) => _createVNode("ul", {
  168. "role": "menu",
  169. "class": bem("options")
  170. }, [options.map((option) => renderOption(option, selectedOption, tabIndex))]);
  171. const renderTab = (tab, tabIndex) => {
  172. const {
  173. options,
  174. selected
  175. } = tab;
  176. const placeholder = props.placeholder || t("select");
  177. const title = selected ? selected[textKey] : placeholder;
  178. return _createVNode(Tab, {
  179. "title": title,
  180. "titleClass": bem("tab", {
  181. unselected: !selected
  182. })
  183. }, {
  184. default: () => {
  185. var _a, _b;
  186. return [(_a = slots["options-top"]) == null ? void 0 : _a.call(slots, {
  187. tabIndex
  188. }), renderOptions(options, selected, tabIndex), (_b = slots["options-bottom"]) == null ? void 0 : _b.call(slots, {
  189. tabIndex
  190. })];
  191. }
  192. });
  193. };
  194. const renderTabs = () => _createVNode(Tabs, {
  195. "active": activeTab.value,
  196. "onUpdate:active": ($event) => activeTab.value = $event,
  197. "shrink": true,
  198. "animated": true,
  199. "class": bem("tabs"),
  200. "color": props.activeColor,
  201. "swipeable": props.swipeable,
  202. "onClickTab": onClickTab
  203. }, {
  204. default: () => [tabs.value.map(renderTab)]
  205. });
  206. const scrollIntoView = (el) => {
  207. const scrollParent = el.parentElement;
  208. if (scrollParent) {
  209. scrollParent.scrollTop = el.offsetTop - (scrollParent.offsetHeight - el.offsetHeight) / 2;
  210. }
  211. };
  212. updateTabs();
  213. watch(activeTab, (value) => {
  214. const el = selectedElementRefs.value[value];
  215. if (el) scrollIntoView(el);
  216. });
  217. watch(() => props.options, updateTabs, {
  218. deep: true
  219. });
  220. watch(() => props.modelValue, (value) => {
  221. if (value !== void 0) {
  222. const values = tabs.value.map((tab) => {
  223. var _a;
  224. return (_a = tab.selected) == null ? void 0 : _a[valueKey];
  225. });
  226. if (values.includes(value)) {
  227. return;
  228. }
  229. }
  230. updateTabs();
  231. });
  232. return () => _createVNode("div", {
  233. "class": bem()
  234. }, [renderHeader(), renderTabs()]);
  235. }
  236. });
  237. export {
  238. cascaderProps,
  239. stdin_default as default
  240. };