sard-uniapp 1.25.4 → 1.25.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/components/accordion/accordion.vue +1 -1
  3. package/components/accordion-item/accordion-item.vue +1 -1
  4. package/components/action-sheet/action-sheet.vue +1 -1
  5. package/components/alert/alert.vue +1 -1
  6. package/components/avatar/avatar.vue +1 -1
  7. package/components/avatar-group/avatar-group.vue +1 -1
  8. package/components/back-top/back-top.vue +1 -1
  9. package/components/badge/badge.vue +1 -1
  10. package/components/button/button.vue +1 -1
  11. package/components/calendar/calendar.vue +1 -1
  12. package/components/calendar-input/calendar-input.vue +2 -2
  13. package/components/calendar-popout/calendar-popout.vue +2 -2
  14. package/components/card/card.vue +1 -1
  15. package/components/cascader/README.md +89 -18
  16. package/components/cascader/cascader.d.ts +6 -6
  17. package/components/cascader/cascader.vue +201 -182
  18. package/components/cascader/common.d.ts +32 -7
  19. package/components/cascader/common.js +39 -23
  20. package/components/cascader/index.d.ts +1 -1
  21. package/components/cascader/index.scss +40 -4
  22. package/components/cascader/useCascaderTabs.d.ts +12 -0
  23. package/components/cascader/useCascaderTabs.js +59 -0
  24. package/components/cascader/useCascaderTree.d.ts +66 -0
  25. package/components/cascader/useCascaderTree.js +194 -0
  26. package/components/cascader/variables.scss +9 -2
  27. package/components/cascader-input/README.md +26 -13
  28. package/components/cascader-input/cascader-input.d.ts +7 -2
  29. package/components/cascader-input/cascader-input.vue +70 -16
  30. package/components/cascader-input/common.d.ts +356 -30
  31. package/components/cascader-popout/README.md +6 -0
  32. package/components/cascader-popout/cascader-popout.d.ts +5 -1
  33. package/components/cascader-popout/cascader-popout.vue +16 -4
  34. package/components/cascader-popout/common.d.ts +169 -17
  35. package/components/check-icon/check-icon.vue +1 -1
  36. package/components/checkbox/checkbox.d.ts +2 -2
  37. package/components/checkbox/checkbox.vue +1 -1
  38. package/components/checkbox-group/checkbox-group.vue +1 -1
  39. package/components/checkbox-input/checkbox-input.vue +2 -2
  40. package/components/checkbox-popout/checkbox-popout.vue +2 -2
  41. package/components/col/col.vue +1 -1
  42. package/components/collapse/collapse.vue +1 -1
  43. package/components/config/index.js +4 -1
  44. package/components/cool-icon/cool-icon.vue +1 -1
  45. package/components/count-down/count-down.vue +1 -1
  46. package/components/crop-image/crop-image.vue +1 -1
  47. package/components/crop-image-agent/crop-image-agent.vue +1 -1
  48. package/components/datetime-picker/datetime-picker.vue +1 -1
  49. package/components/datetime-picker-input/datetime-picker-input.vue +2 -2
  50. package/components/datetime-picker-popout/datetime-picker-popout.vue +2 -2
  51. package/components/datetime-range-picker/datetime-range-picker.vue +1 -1
  52. package/components/datetime-range-picker-input/datetime-range-picker-input.vue +2 -2
  53. package/components/datetime-range-picker-popout/datetime-range-picker-popout.vue +2 -2
  54. package/components/dialog/dialog.vue +2 -2
  55. package/components/dialog-agent/dialog-agent.vue +2 -2
  56. package/components/divider/divider.vue +1 -1
  57. package/components/dnd/dnd.vue +1 -1
  58. package/components/dnd-handle/dnd-handle.vue +1 -1
  59. package/components/dnd-item/dnd-item.vue +1 -1
  60. package/components/dropdown/dropdown.vue +1 -1
  61. package/components/dropdown-item/dropdown-item.vue +7 -2
  62. package/components/empty/empty.vue +1 -1
  63. package/components/fab/fab.vue +1 -1
  64. package/components/fab-item/fab-item.vue +1 -1
  65. package/components/floating-bubble/floating-bubble.vue +1 -1
  66. package/components/floating-panel/floating-panel.vue +1 -1
  67. package/components/form/form.vue +1 -1
  68. package/components/form-item/form-item.vue +1 -1
  69. package/components/form-item-plain/form-item-plain.vue +1 -1
  70. package/components/form-plain/form-plain.vue +1 -1
  71. package/components/grid/grid.vue +1 -1
  72. package/components/grid-item/grid-item.vue +2 -2
  73. package/components/icon/icon.vue +1 -1
  74. package/components/icon/sari.scss +17 -17
  75. package/components/image/image.vue +10 -4
  76. package/components/indexes/indexes.vue +1 -1
  77. package/components/indexes-anchor/indexes-anchor.vue +1 -1
  78. package/components/input/input.vue +1 -1
  79. package/components/keyboard/keyboard.vue +1 -1
  80. package/components/list/list.vue +1 -1
  81. package/components/list-item/list-item.vue +1 -1
  82. package/components/load-more/load-more.vue +1 -1
  83. package/components/loading/loading.vue +1 -1
  84. package/components/locale/lang/ar-SA.d.ts +2 -0
  85. package/components/locale/lang/ar-SA.js +2 -0
  86. package/components/locale/lang/en-US.d.ts +2 -0
  87. package/components/locale/lang/en-US.js +2 -0
  88. package/components/locale/lang/vi-VN.d.ts +2 -0
  89. package/components/locale/lang/vi-VN.js +2 -0
  90. package/components/locale/lang/zh-CN.d.ts +2 -0
  91. package/components/locale/lang/zh-CN.js +2 -0
  92. package/components/marquee/marquee.vue +1 -1
  93. package/components/menu/menu.vue +1 -1
  94. package/components/navbar/navbar.vue +2 -2
  95. package/components/navbar-item/navbar-item.vue +1 -1
  96. package/components/navbar-pit/navbar-pit.vue +1 -1
  97. package/components/notice-bar/notice-bar.vue +1 -1
  98. package/components/notify/notify.vue +2 -1
  99. package/components/notify-agent/notify-agent.vue +1 -1
  100. package/components/overlay/overlay.vue +1 -1
  101. package/components/pagination/pagination.vue +1 -1
  102. package/components/password-input/password-input.vue +1 -1
  103. package/components/picker/picker.vue +1 -1
  104. package/components/picker-input/picker-input.vue +2 -2
  105. package/components/picker-item/picker-item.vue +1 -1
  106. package/components/picker-popout/picker-popout.vue +2 -2
  107. package/components/popout/popout.vue +1 -1
  108. package/components/popout-input/popout-input.vue +1 -1
  109. package/components/popover/popover.vue +3 -2
  110. package/components/popover-reference/popover-reference.vue +1 -1
  111. package/components/popup/README.md +1 -0
  112. package/components/popup/common.d.ts +28 -0
  113. package/components/popup/index.scss +4 -2
  114. package/components/popup/popup.d.ts +1 -0
  115. package/components/popup/popup.vue +82 -16
  116. package/components/popup/variables.scss +6 -0
  117. package/components/progress-bar/progress-bar.vue +1 -1
  118. package/components/progress-circle/progress-circle.vue +1 -1
  119. package/components/pull-down-refresh/pull-down-refresh.vue +1 -1
  120. package/components/qrcode/qrcode.vue +6 -5
  121. package/components/radio/radio.vue +1 -1
  122. package/components/radio-group/radio-group.vue +1 -1
  123. package/components/radio-input/radio-input.vue +2 -2
  124. package/components/radio-popout/radio-popout.vue +2 -2
  125. package/components/rate/rate.vue +1 -1
  126. package/components/read-more/read-more.vue +1 -1
  127. package/components/resize-sensor/index.scss +7 -0
  128. package/components/resize-sensor/resize-sensor.vue +10 -3
  129. package/components/result/result.vue +1 -1
  130. package/components/row/row.vue +1 -1
  131. package/components/scroll-list/scroll-list.vue +1 -1
  132. package/components/scroll-spy/scroll-spy.vue +1 -1
  133. package/components/scroll-spy-anchor/scroll-spy-anchor.vue +1 -1
  134. package/components/search/search.vue +1 -1
  135. package/components/segmented/segmented.vue +1 -1
  136. package/components/segmented-item/segmented-item.vue +1 -1
  137. package/components/share-sheet/share-sheet.vue +2 -1
  138. package/components/sidebar/sidebar.vue +1 -1
  139. package/components/sidebar-item/sidebar-item.vue +1 -1
  140. package/components/signature/signature.vue +1 -1
  141. package/components/skeleton/skeleton.vue +1 -1
  142. package/components/skeleton-avatar/skeleton-avatar.vue +1 -1
  143. package/components/skeleton-block/skeleton-block.vue +1 -1
  144. package/components/skeleton-paragraph/skeleton-paragraph.vue +1 -1
  145. package/components/skeleton-title/skeleton-title.vue +1 -1
  146. package/components/slider/slider.vue +1 -1
  147. package/components/space/space.vue +1 -1
  148. package/components/status-bar/status-bar.vue +1 -1
  149. package/components/step/step.vue +1 -1
  150. package/components/stepper/stepper.vue +1 -1
  151. package/components/steps/steps.vue +1 -1
  152. package/components/sticky/sticky.vue +1 -1
  153. package/components/sticky-box/sticky-box.vue +1 -1
  154. package/components/swipe-action/swipe-action.vue +1 -1
  155. package/components/swiper-dot/swiper-dot.vue +1 -1
  156. package/components/switch/switch.vue +1 -1
  157. package/components/tab/tab.vue +1 -1
  158. package/components/tabbar/tabbar.vue +1 -1
  159. package/components/tabbar-item/tabbar-item.vue +1 -1
  160. package/components/tabbar-pit/tabbar-pit.vue +1 -1
  161. package/components/table/table.vue +1 -1
  162. package/components/table-cell/table-cell.vue +1 -1
  163. package/components/table-fixation/table-fixation.vue +1 -1
  164. package/components/table-row/table-row.vue +1 -1
  165. package/components/tabs/tabs.vue +1 -1
  166. package/components/tag/tag.vue +1 -1
  167. package/components/timeline/timeline.vue +1 -1
  168. package/components/timeline-item/timeline-item.vue +1 -1
  169. package/components/toast/toast.vue +2 -1
  170. package/components/toast-agent/toast-agent.vue +1 -1
  171. package/components/tree/README.md +0 -2
  172. package/components/tree/tree.d.ts +1 -1
  173. package/components/tree/tree.vue +36 -81
  174. package/components/tree-node/tree-node.vue +8 -4
  175. package/components/upload/upload.vue +1 -1
  176. package/components/upload-preview/upload-preview.vue +1 -1
  177. package/components/waterfall/waterfall.vue +1 -1
  178. package/components/waterfall-item/waterfall-item.vue +1 -1
  179. package/components/waterfall-load/waterfall-load.vue +1 -1
  180. package/components/watermark/watermark.vue +6 -8
  181. package/package.json +2 -1
  182. package/use/index.d.ts +1 -0
  183. package/use/index.js +1 -0
  184. package/use/useLockScroll.d.ts +8 -0
  185. package/use/useLockScroll.js +88 -0
  186. package/utils/file.d.ts +6 -0
  187. package/utils/file.js +33 -11
  188. package/utils/index.d.ts +2 -0
  189. package/utils/index.js +2 -0
  190. package/utils/log.d.ts +1 -0
  191. package/utils/log.js +4 -0
  192. package/utils/router.js +2 -1
  193. package/utils/system.d.ts +2 -0
  194. package/utils/system.js +6 -1
  195. package/utils/tree.d.ts +18 -0
  196. package/utils/tree.js +94 -0
  197. package/components/tree/utils.d.ts +0 -5
  198. package/components/tree/utils.js +0 -29
@@ -5,15 +5,7 @@
5
5
  <slot name="top" :tab-index="currentTab"></slot>
6
6
 
7
7
  <view :class="bem.e('container')">
8
- <view
9
- :class="bem.e('wrapper')"
10
- :style="
11
- stringifyStyle({
12
- transform: `translateX(${-Number(currentTab) * 100}%)`,
13
- transitionDuration: renderedPane ? null : '0s',
14
- })
15
- "
16
- >
8
+ <view :class="bem.e('wrapper')" :style="wrapperStyle">
17
9
  <view
18
10
  v-for="(panel, panelIndex) in panels"
19
11
  :key="panelIndex"
@@ -22,54 +14,78 @@
22
14
  <view :class="bem.e('options')">
23
15
  <scroll-view scroll-y trap-scroll :class="bem.e('scroll')">
24
16
  <view
25
- v-for="(option, optionIndex) in panel.options"
17
+ v-for="(node, optionIndex) in panel.nodes"
26
18
  :key="optionIndex"
27
19
  :class="
28
20
  classNames(
29
21
  bem.e('option'),
30
- bem.em(
31
- 'option',
32
- 'selected',
33
- panel.selected &&
34
- panel.selected[mergedFieldKeys.value] ===
35
- option[mergedFieldKeys.value],
36
- ),
37
- bem.em(
38
- 'option',
39
- 'disabled',
40
- option[mergedFieldKeys.disabled],
41
- ),
22
+ bem.em('option', 'selected', node.selected),
23
+ bem.em('option', 'checked', node.checked),
24
+ bem.em('option', 'disabled', node.disabled),
42
25
  )
43
26
  "
44
- @click="onOptionClick(option, panelIndex)"
27
+ @click="onNodeClick(node, panelIndex)"
45
28
  >
29
+ <view
30
+ v-if="multiple"
31
+ :class="bem.e('selection')"
32
+ @click.stop="onCheckClick(node)"
33
+ >
34
+ <sar-checkbox
35
+ readonly
36
+ :checked="node.checked"
37
+ :indeterminate="node.indeterminate"
38
+ :disabled="node.disabled"
39
+ />
40
+ </view>
41
+ <view
42
+ :class="
43
+ classNames(
44
+ bem.e('option-loading'),
45
+ bem.em(
46
+ 'option-loading',
47
+ 'show',
48
+ node.loadStatus === 'loading',
49
+ ),
50
+ )
51
+ "
52
+ >
53
+ <sar-loading />
54
+ </view>
46
55
  <view :class="bem.e('option-label')">
47
- {{
48
- labelRender
49
- ? labelRender(option)
50
- : option[mergedFieldKeys.label]
51
- }}
56
+ {{ node.label }}
52
57
  </view>
53
- <view :class="bem.e('option-icon')">
58
+ <view v-if="!multiple" :class="bem.e('option-icon')">
54
59
  <sar-icon family="sari" name="success" />
55
60
  </view>
56
61
  </view>
57
62
  </scroll-view>
58
- <view
59
- :class="
60
- classNames(
61
- bem.e('loading-wrapper'),
62
- bem.em('loading-wrapper', 'show', panel.options.length === 0),
63
- )
64
- "
65
- >
66
- <view :class="bem.e('loading')">
67
- <sar-loading />
68
- </view>
69
- </view>
70
63
  </view>
71
64
  </view>
72
65
  </view>
66
+
67
+ <view v-if="lazy && !!load" :class="loadStatusWrapperClass">
68
+ <view :class="bem.e('load-status')">
69
+ <sar-loading
70
+ v-if="loadStatus === 'loading'"
71
+ :class="bem.e('loading')"
72
+ />
73
+ <text
74
+ v-else-if="loadStatus === 'error'"
75
+ :class="bem.e('error')"
76
+ @click="initialize"
77
+ >
78
+ {{ t('error') }}
79
+ </text>
80
+ <text
81
+ v-else-if="loadStatus === 'loaded' && treeData.length === 0"
82
+ :class="bem.e('empty')"
83
+ @click="initialize"
84
+ >
85
+ {{ t('noData') }}
86
+ </text>
87
+ </view>
88
+ </view>
73
89
  </view>
74
90
  </view>
75
91
  </template>
@@ -77,34 +93,34 @@
77
93
  <script>
78
94
  import { mergeDefaults as _mergeDefaults, defineComponent as _defineComponent } from "vue";
79
95
  import { computed, ref, watch } from "vue";
80
- import {
81
- classNames,
82
- stringifyStyle,
83
- createBem,
84
- isEmptyBinding,
85
- isEmptyArray
86
- } from "../../utils";
87
- import { useTranslate } from "../locale";
96
+ import { classNames, stringifyStyle, createBem } from "../../utils";
88
97
  import SarTabs from "../tabs/tabs.vue";
89
98
  import SarIcon from "../icon/icon.vue";
90
99
  import SarLoading from "../loading/loading.vue";
100
+ import SarCheckbox from "../checkbox/checkbox.vue";
91
101
  import {
92
102
  defaultFieldKeys,
93
- getSelectedOptionsByValue,
94
103
  defaultCascaderProps
95
104
  } from "./common";
105
+ import { useCascaderTree } from "./useCascaderTree";
106
+ import { useCascaderTabs } from "./useCascaderTabs";
107
+ import { useTranslate } from "../locale";
96
108
  /**
97
109
  * @property {string} rootClass 组件根元素类名,默认值:-。
98
110
  * @property {StyleValue} rootStyle 组件根元素样式,默认值:-。
99
- * @property {string | number | (string | number)[]} modelValue 选中项的值,默认值:-。
111
+ * @property {CascaderValue} modelValue 选中项的值,默认值:-。
100
112
  * @property {CascaderOption[]} options 可选项数据源,默认值:[]。
101
113
  * @property {CascaderFieldKeys} fieldKeys 自定义 `options` 中的字段,默认值:defaultFieldKeys。
102
114
  * @property {string} hintText 未选中时的提示文案,默认值:'请选择'。
103
115
  * @property {(option: CascaderOption) => string} labelRender 自定义可选项渲染,默认值:-。
104
116
  * @property {boolean} changeOnSelect 点击每级选项都会触发变化,默认值:false。
105
117
  * @property {boolean} allLevels 是否绑定所有级别的值,而不单单是最后一级,默认值:false。
106
- * @event {(value: string | number | (string | number)[], selectedOptions: CascaderOption[]) => void} update 全部选项选择完成后触发
107
- * @event {(value: string | number | (string | number)[], selectedOptions: CascaderOption[]) => void} change 全部选项选择完成后触发
118
+ * @property {boolean} multiple 是否多选,默认值:false。
119
+ * @property {boolean} checkStrictly 是否严格的遵守父子节点不互相关联(用于多选),默认值:false。
120
+ * @property {boolean} lazy 是否懒加载子节点,需与 load 方法结合使用,默认值:false。
121
+ * @property {(node?: CascaderStateNode) => Promise<CascaderOption[]> | CascaderOption[]} load 加载子节点的方法,仅当 lazy 属性为true 时生效,默认值:-。
122
+ * @event {(value: CascaderValue, selectedOptions: CascaderOption[]) => void} update 全部选项选择完成后触发
123
+ * @event {(value: CascaderValue, selectedOptions: CascaderOption[]) => void} change 全部选项选择完成后触发
108
124
  * @event {(option: CascaderOption, tabIndex: number) => void} select 选中某一项时触发
109
125
  */
110
126
  export default _defineComponent({
@@ -112,6 +128,7 @@ export default _defineComponent({
112
128
  SarTabs,
113
129
  SarIcon,
114
130
  SarLoading,
131
+ SarCheckbox,
115
132
  },
116
133
  ...{
117
134
  options: {
@@ -121,7 +138,7 @@ export default _defineComponent({
121
138
  },
122
139
  __name: "cascader",
123
140
  props: _mergeDefaults({
124
- rootStyle: { type: [Boolean, null, String, Object, Array], required: false, skipCheck: true },
141
+ rootStyle: { type: null, required: false },
125
142
  rootClass: { type: String, required: false },
126
143
  modelValue: { type: [String, Number, Array], required: false },
127
144
  options: { type: Array, required: false },
@@ -129,7 +146,11 @@ export default _defineComponent({
129
146
  hintText: { type: String, required: false },
130
147
  labelRender: { type: Function, required: false },
131
148
  changeOnSelect: { type: Boolean, required: false },
132
- allLevels: { type: Boolean, required: false }
149
+ allLevels: { type: Boolean, required: false },
150
+ multiple: { type: Boolean, required: false },
151
+ checkStrictly: { type: Boolean, required: false },
152
+ lazy: { type: Boolean, required: false },
153
+ load: { type: Function, required: false }
133
154
  }, defaultCascaderProps),
134
155
  emits: ["update:model-value", "change", "select"],
135
156
  setup(__props, { expose: __expose, emit: __emit }) {
@@ -138,92 +159,6 @@ export default _defineComponent({
138
159
  const emit = __emit;
139
160
  const bem = createBem("cascader");
140
161
  const { t } = useTranslate("cascader");
141
- const updatePanels = () => {
142
- let nextPanels;
143
- if (isEmptyBinding(tempValue) || isEmptyArray(tempValue)) {
144
- nextPanels = [
145
- {
146
- options: props.options || [],
147
- selected: null
148
- }
149
- ];
150
- } else {
151
- const selectedOptions = getSelectedOptionsByValue(
152
- props.options || [],
153
- tempValue,
154
- mergedFieldKeys.value
155
- );
156
- if (selectedOptions && selectedOptions.length > 0) {
157
- let nextOptions = props.options;
158
- nextPanels = selectedOptions.map((option) => {
159
- const panel = {
160
- options: nextOptions || [],
161
- selected: option
162
- };
163
- nextOptions = option[mergedFieldKeys.value.children];
164
- return panel;
165
- });
166
- if (nextOptions) {
167
- nextPanels.push({
168
- options: nextOptions,
169
- selected: null
170
- });
171
- }
172
- } else {
173
- nextPanels = [
174
- {
175
- options: props.options || [],
176
- selected: null
177
- }
178
- ];
179
- }
180
- }
181
- panels.value = nextPanels;
182
- currentTab.value = nextPanels.length - 1;
183
- if (!renderedPane.value) {
184
- setTimeout(() => {
185
- renderedPane.value = true;
186
- }, 30);
187
- }
188
- };
189
- const isLastOption = (option) => {
190
- return !Array.isArray(option[mergedFieldKeys.value.children]);
191
- };
192
- const onOptionClick = (option, panelIndex) => {
193
- if (option.disabled) {
194
- return;
195
- }
196
- let nextPanels = panels.value.slice();
197
- nextPanels[panelIndex].selected = option;
198
- const selectBack = panelIndex < nextPanels.length - 1;
199
- if (selectBack) {
200
- nextPanels = nextPanels.slice(0, panelIndex + 1);
201
- }
202
- const isLast = isLastOption(option);
203
- if (!isLast) {
204
- const nextPanel = {
205
- options: option[mergedFieldKeys.value.children],
206
- selected: null
207
- };
208
- nextPanels.push(nextPanel);
209
- }
210
- currentTab.value = isLast ? panelIndex : nextPanels.length - 1;
211
- if (props.allLevels) {
212
- tempValue = nextPanels.map((panel) => panel.selected).filter(Boolean).map((option2) => option2[mergedFieldKeys.value.value]);
213
- } else {
214
- tempValue = option[mergedFieldKeys.value.value];
215
- }
216
- panels.value = nextPanels;
217
- emit("select", option, panelIndex);
218
- if (isLast || props.changeOnSelect) {
219
- const selectedOptions = nextPanels.map((panel) => panel.selected).filter(Boolean);
220
- emit("update:model-value", tempValue, selectedOptions);
221
- emit("change", tempValue, selectedOptions);
222
- }
223
- };
224
- const innerPaceholder = computed(() => {
225
- return props.hintText || t("pleaseSelect");
226
- });
227
162
  const mergedFieldKeys = computed(() => {
228
163
  return Object.assign(
229
164
  {},
@@ -231,47 +166,121 @@ export default _defineComponent({
231
166
  props.fieldKeys
232
167
  );
233
168
  });
234
- let tempValue = props.modelValue;
235
- const currentTab = ref(0);
236
- const renderedPane = ref(false);
237
- const panels = ref([]);
238
- const tabList = computed(() => {
239
- return panels.value.map((panel) => {
240
- const { selected } = panel;
241
- const label = selected ? selected[mergedFieldKeys.value.label] : innerPaceholder.value;
242
- return {
243
- title: label
244
- };
245
- });
169
+ const innerValue = ref();
170
+ const {
171
+ treeData,
172
+ originalTreeData,
173
+ loadStatus,
174
+ legacyLoadChildren,
175
+ toStateNodes,
176
+ setSelectedByNode,
177
+ updateChecked,
178
+ setCheckedByNode,
179
+ isLeaf,
180
+ getCheckedLeaves,
181
+ getCheckedNodes,
182
+ getAncestors,
183
+ initialize
184
+ } = useCascaderTree(props, {
185
+ fieldKeys: mergedFieldKeys,
186
+ innerValue
246
187
  });
247
- watch(
248
- () => props.modelValue,
249
- () => {
250
- if (Array.isArray(props.modelValue)) {
251
- if (props.modelValue.length > 0) {
252
- if (props.modelValue.every(
253
- (item, index) => panels.value[index].selected?.[mergedFieldKeys.value.value] === item
254
- )) {
255
- return;
188
+ initialize();
189
+ const { renderedPane, panels, currentTab, tabList } = useCascaderTabs(props, {
190
+ treeData
191
+ });
192
+ const triggerMultipleChange = () => {
193
+ const nodes = props.checkStrictly ? getCheckedNodes() : getCheckedLeaves();
194
+ const nextValue = props.allLevels ? nodes.map((node) => getAncestors(node).map((node2) => node2.value)) : nodes.map((node) => node.value);
195
+ innerValue.value = nextValue;
196
+ const options = nodes.map(
197
+ (node) => getAncestors(node).map((node2) => node2.option)
198
+ );
199
+ emit("update:model-value", nextValue, options);
200
+ emit("change", nextValue, options);
201
+ };
202
+ const triggerSingleChange = (node) => {
203
+ const nextValue = props.allLevels ? panels.value.map((panel) => panel.selected).filter(Boolean).map((node2) => node2.value) : node.value;
204
+ innerValue.value = nextValue;
205
+ const options = panels.value.map((panel) => panel.selected).filter(Boolean).map((node2) => node2.option);
206
+ emit("update:model-value", nextValue, options);
207
+ emit("change", nextValue, options);
208
+ };
209
+ const onCheckClick = (node) => {
210
+ if (node.disabled) return;
211
+ setCheckedByNode(node, !node.checked);
212
+ triggerMultipleChange();
213
+ };
214
+ const onNodeClick = async (node, panelIndex) => {
215
+ if (node.disabled) return;
216
+ if (node.loadStatus === "loading") {
217
+ return;
218
+ }
219
+ if (props.lazy && props.load && !node.isLeaf && node.loadStatus === "idle") {
220
+ try {
221
+ node.loadStatus = "loading";
222
+ const treeNodes = await props.load(node) || [];
223
+ node.loadStatus = "loaded";
224
+ node.children = toStateNodes(treeNodes, node);
225
+ updateChecked(innerValue.value);
226
+ node.option.children = treeNodes;
227
+ originalTreeData.value = [...originalTreeData.value];
228
+ if (node.children.length === 0) {
229
+ node.isLeaf = true;
230
+ } else {
231
+ if (node.checked) {
232
+ setCheckedByNode(node, true);
256
233
  }
257
234
  }
258
- } else {
259
- if (!isEmptyBinding(props.modelValue)) {
260
- if (panels.value.some(
261
- (panel) => panel.selected?.[mergedFieldKeys.value.value] === props.modelValue
262
- )) {
263
- return;
235
+ } catch {
236
+ node.loadStatus = "idle";
237
+ return;
238
+ }
239
+ }
240
+ setSelectedByNode(node);
241
+ const isLast = isLeaf(node);
242
+ if (!isLast) {
243
+ currentTab.value = panels.value.length - 1;
244
+ }
245
+ if (!isLast && node.children && node.children.length === 0) {
246
+ node.loadStatus = "loading";
247
+ }
248
+ const proxyOption = new Proxy(node.option, {
249
+ set(target, p, newValue) {
250
+ if (p === mergedFieldKeys.value.children) {
251
+ legacyLoadChildren.value = true;
252
+ node.loadStatus = "loaded";
253
+ node.children = toStateNodes(newValue, node);
254
+ if (node.children.length === 0) {
255
+ node.isLeaf = true;
256
+ }
257
+ const isLast2 = isLeaf(node);
258
+ if (!isLast2) {
259
+ currentTab.value = panels.value.length - 1;
264
260
  }
265
261
  }
262
+ return Reflect.set(target, p, newValue);
263
+ }
264
+ });
265
+ emit("select", proxyOption, panelIndex);
266
+ if (props.multiple) {
267
+ if (isLast) {
268
+ setCheckedByNode(node, !node.checked);
269
+ triggerMultipleChange();
270
+ }
271
+ } else {
272
+ if (isLast || props.changeOnSelect) {
273
+ triggerSingleChange(node);
266
274
  }
267
- tempValue = props.modelValue;
268
- updatePanels();
269
275
  }
270
- );
276
+ };
271
277
  watch(
272
- () => props.options,
273
- () => {
274
- updatePanels();
278
+ () => props.modelValue,
279
+ (value) => {
280
+ if (value === innerValue.value) return;
281
+ innerValue.value = value;
282
+ updateChecked(value);
283
+ currentTab.value = panels.value.length - 1;
275
284
  },
276
285
  {
277
286
  immediate: true
@@ -283,15 +292,25 @@ export default _defineComponent({
283
292
  const cascaderStyle = computed(() => {
284
293
  return stringifyStyle(props.rootStyle);
285
294
  });
286
- const __returned__ = { props, emit, bem, t, updatePanels, isLastOption, onOptionClick, innerPaceholder, mergedFieldKeys, get tempValue() {
287
- return tempValue;
288
- }, set tempValue(v) {
289
- tempValue = v;
290
- }, currentTab, renderedPane, panels, tabList, cascaderClass, cascaderStyle, get classNames() {
295
+ const wrapperStyle = computed(() => {
296
+ return stringifyStyle({
297
+ transform: `translateX(${-Number(currentTab.value) * 100}%)`,
298
+ transitionDuration: renderedPane.value ? null : "0s"
299
+ });
300
+ });
301
+ const loadStatusWrapperClass = computed(() => {
302
+ return classNames(
303
+ bem.e("load-status-wrapper"),
304
+ bem.em(
305
+ "load-status-wrapper",
306
+ "show",
307
+ loadStatus.value !== "loaded" || treeData.value.length === 0
308
+ )
309
+ );
310
+ });
311
+ const __returned__ = { props, emit, bem, t, mergedFieldKeys, innerValue, treeData, originalTreeData, loadStatus, legacyLoadChildren, toStateNodes, setSelectedByNode, updateChecked, setCheckedByNode, isLeaf, getCheckedLeaves, getCheckedNodes, getAncestors, initialize, renderedPane, panels, currentTab, tabList, triggerMultipleChange, triggerSingleChange, onCheckClick, onNodeClick, cascaderClass, cascaderStyle, wrapperStyle, loadStatusWrapperClass, get classNames() {
291
312
  return classNames;
292
- }, get stringifyStyle() {
293
- return stringifyStyle;
294
- }, SarTabs, SarIcon, SarLoading };
313
+ }, SarTabs, SarIcon, SarLoading, SarCheckbox };
295
314
  return __returned__;
296
315
  }
297
316
  });
@@ -1,27 +1,49 @@
1
- import { type StyleValue } from 'vue';
1
+ import { InjectionKey, type StyleValue } from 'vue';
2
2
  export interface CascaderFieldKeys {
3
3
  label?: string;
4
4
  value?: string;
5
5
  disabled?: string;
6
6
  children?: string;
7
+ isLeaf?: string;
7
8
  }
8
9
  export interface CascaderOption {
9
10
  label?: string;
10
11
  value?: string | number;
11
12
  disabled?: boolean;
12
13
  children?: CascaderOption[];
14
+ isLeaf?: boolean;
13
15
  [key: PropertyKey]: any;
14
16
  }
17
+ export interface CascaderStateNode {
18
+ label: string;
19
+ value: string | number;
20
+ key: string | number;
21
+ disabled: boolean;
22
+ children?: CascaderStateNode[];
23
+ parent: CascaderStateNode | null;
24
+ isLeaf: boolean;
25
+ loadStatus: 'idle' | 'loading' | 'loaded';
26
+ depth: number;
27
+ indeterminate: boolean;
28
+ checked: boolean;
29
+ selected: boolean;
30
+ option: CascaderOption;
31
+ }
32
+ export type CascaderValue = string | number | (string | number)[] | (string | number)[][];
15
33
  export interface CascaderProps {
16
34
  rootStyle?: StyleValue;
17
35
  rootClass?: string;
18
- modelValue?: string | number | (string | number)[];
36
+ modelValue?: CascaderValue;
19
37
  options?: CascaderOption[];
20
38
  fieldKeys?: CascaderFieldKeys;
21
39
  hintText?: string;
22
40
  labelRender?: (option: CascaderOption) => string;
23
41
  changeOnSelect?: boolean;
24
42
  allLevels?: boolean;
43
+ multiple?: boolean;
44
+ checkStrictly?: boolean;
45
+ lazy?: boolean;
46
+ load?: (node?: CascaderStateNode) => Promise<CascaderOption[]> | CascaderOption[];
25
47
  }
26
48
  export declare const defaultCascaderProps: {
27
49
  options: () => never[];
@@ -32,13 +54,16 @@ export interface CascaderSlots {
32
54
  }): any;
33
55
  }
34
56
  export interface CascaderEmits {
35
- (e: 'update:model-value', value: string | number | (string | number)[], selectedOptions: any[]): void;
36
- (e: 'change', value: string | number | (string | number)[], selectedOptions: any[]): void;
57
+ (e: 'update:model-value', value: CascaderValue, selectedOptions: any[]): void;
58
+ (e: 'change', value: CascaderValue, selectedOptions: any[]): void;
37
59
  (e: 'select', option: any, tabIndex: number): void;
38
60
  }
39
61
  export interface CascaderPanel {
40
- options: CascaderOption[];
41
- selected: CascaderOption | null;
62
+ nodes: CascaderStateNode[];
63
+ selected: CascaderStateNode | null;
42
64
  }
43
65
  export declare const defaultFieldKeys: CascaderFieldKeys;
44
- export declare function getSelectedOptionsByValue(options: CascaderOption[], value: string | number | (string | number)[], fieldKeys: Required<CascaderFieldKeys>): CascaderOption[] | undefined;
66
+ export declare function getSelectedOptionsByValue(options: CascaderOption[], value: CascaderValue, fieldKeys: Required<CascaderFieldKeys>, multiple?: boolean): CascaderOption[] | CascaderOption[][] | undefined;
67
+ export declare const cascaderOptionsContextSymbol: InjectionKey<{
68
+ set: (options: CascaderOption[]) => void;
69
+ }>;
@@ -1,3 +1,4 @@
1
+ import { isNullish } from '../../utils';
1
2
  export const defaultCascaderProps = {
2
3
  options: () => [],
3
4
  };
@@ -6,35 +7,50 @@ export const defaultFieldKeys = {
6
7
  value: 'value',
7
8
  disabled: 'disabled',
8
9
  children: 'children',
10
+ isLeaf: 'isLeaf',
9
11
  };
10
- export function getSelectedOptionsByValue(options, value, fieldKeys) {
11
- if (Array.isArray(value)) {
12
- const selectedOptions = [];
13
- let list = options;
14
- for (const item of value) {
15
- const option = list.find((option) => option[fieldKeys.value] === item);
16
- if (!option)
17
- break;
18
- selectedOptions.push(option);
19
- list = option[fieldKeys.children];
20
- if (!Array.isArray(list))
21
- break;
12
+ export function getSelectedOptionsByValue(options, value, fieldKeys, multiple) {
13
+ // 多选
14
+ if (multiple) {
15
+ if (Array.isArray(value)) {
16
+ return value
17
+ .map((item) => getSelectedOptionsByValue(options, item, fieldKeys))
18
+ .filter((item) => Array.isArray(item) ? item.length !== 0 : !isNullish(item));
22
19
  }
23
- return selectedOptions;
24
20
  }
21
+ // 单选
25
22
  else {
26
- for (const option of options) {
27
- // 优先在子结点中查找,找到后再向上回溯路径
28
- // 这样可以处理存在重复值场景时候更偏向于更深层次的选项
29
- if (Array.isArray(option[fieldKeys.children])) {
30
- const selectedOptions = getSelectedOptionsByValue(option[fieldKeys.children], value, fieldKeys);
31
- if (selectedOptions) {
32
- return [option, ...selectedOptions];
33
- }
23
+ // 全路径
24
+ if (Array.isArray(value)) {
25
+ const selectedOptions = [];
26
+ let list = options;
27
+ for (const item of value) {
28
+ const option = list.find((option) => option[fieldKeys.value] === item);
29
+ if (!option)
30
+ break;
31
+ selectedOptions.push(option);
32
+ list = option[fieldKeys.children];
33
+ if (!Array.isArray(list))
34
+ break;
34
35
  }
35
- if (option[fieldKeys.value] === value) {
36
- return [option];
36
+ return selectedOptions;
37
+ }
38
+ // 最后一级
39
+ else {
40
+ for (const option of options) {
41
+ // 优先在子结点中查找,找到后再向上回溯路径
42
+ // 这样可以处理存在重复值场景时候更偏向于更深层次的选项
43
+ if (Array.isArray(option[fieldKeys.children])) {
44
+ const selectedOptions = getSelectedOptionsByValue(option[fieldKeys.children], value, fieldKeys, multiple);
45
+ if (selectedOptions) {
46
+ return [option, ...selectedOptions];
47
+ }
48
+ }
49
+ if (option[fieldKeys.value] === value) {
50
+ return [option];
51
+ }
37
52
  }
38
53
  }
39
54
  }
40
55
  }
56
+ export const cascaderOptionsContextSymbol = Symbol('cascader-options');
@@ -1 +1 @@
1
- export type { CascaderFieldKeys, CascaderOption, CascaderProps, CascaderSlots, CascaderEmits, } from './common';
1
+ export type { CascaderFieldKeys, CascaderOption, CascaderProps, CascaderSlots, CascaderEmits, CascaderValue, CascaderStateNode, } from './common';