antd-mobile 5.40.0 → 5.41.0-alpha.1

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 (66) hide show
  1. package/2x/bundle/antd-mobile.cjs.development.js +225 -85
  2. package/2x/bundle/antd-mobile.cjs.js +6 -6
  3. package/2x/bundle/antd-mobile.es.development.js +225 -85
  4. package/2x/bundle/antd-mobile.es.js +2218 -2144
  5. package/2x/bundle/antd-mobile.umd.development.js +225 -85
  6. package/2x/bundle/antd-mobile.umd.js +7 -7
  7. package/2x/bundle/style.css +4 -1
  8. package/2x/cjs/components/calendar/calendar.d.ts +4 -1
  9. package/2x/cjs/components/calendar/calendar.js +18 -11
  10. package/2x/cjs/components/date-picker/date-picker-week-utils.js +2 -1
  11. package/2x/cjs/components/ellipsis/ellipsis.d.ts +3 -0
  12. package/2x/cjs/components/ellipsis/ellipsis.js +10 -2
  13. package/2x/cjs/components/swiper/swiper.js +1 -1
  14. package/2x/cjs/components/tabs/tabs.js +45 -13
  15. package/2x/cjs/components/virtual-input/virtual-input.css +4 -1
  16. package/2x/cjs/components/virtual-input/virtual-input.d.ts +6 -0
  17. package/2x/cjs/components/virtual-input/virtual-input.js +105 -8
  18. package/2x/es/components/calendar/calendar.d.ts +4 -1
  19. package/2x/es/components/calendar/calendar.js +19 -12
  20. package/2x/es/components/date-picker/date-picker-week-utils.js +2 -1
  21. package/2x/es/components/ellipsis/ellipsis.d.ts +3 -0
  22. package/2x/es/components/ellipsis/ellipsis.js +10 -2
  23. package/2x/es/components/swiper/swiper.js +1 -1
  24. package/2x/es/components/tabs/tabs.js +44 -12
  25. package/2x/es/components/virtual-input/virtual-input.css +4 -1
  26. package/2x/es/components/virtual-input/virtual-input.d.ts +6 -0
  27. package/2x/es/components/virtual-input/virtual-input.js +105 -8
  28. package/2x/package.json +3 -3
  29. package/bundle/antd-mobile.cjs.development.js +225 -85
  30. package/bundle/antd-mobile.cjs.js +6 -6
  31. package/bundle/antd-mobile.compatible.umd.js +1 -1
  32. package/bundle/antd-mobile.es.development.js +225 -85
  33. package/bundle/antd-mobile.es.js +2218 -2144
  34. package/bundle/antd-mobile.umd.development.js +225 -85
  35. package/bundle/antd-mobile.umd.js +7 -7
  36. package/bundle/style.css +1 -1
  37. package/cjs/components/calendar/calendar.d.ts +4 -1
  38. package/cjs/components/calendar/calendar.js +18 -11
  39. package/cjs/components/date-picker/date-picker-week-utils.js +2 -1
  40. package/cjs/components/ellipsis/ellipsis.d.ts +3 -0
  41. package/cjs/components/ellipsis/ellipsis.js +10 -2
  42. package/cjs/components/swiper/swiper.js +1 -1
  43. package/cjs/components/tabs/tabs.js +45 -13
  44. package/cjs/components/virtual-input/virtual-input.css +3 -1
  45. package/cjs/components/virtual-input/virtual-input.d.ts +6 -0
  46. package/cjs/components/virtual-input/virtual-input.js +105 -8
  47. package/es/components/calendar/calendar.d.ts +4 -1
  48. package/es/components/calendar/calendar.js +19 -12
  49. package/es/components/date-picker/date-picker-week-utils.js +2 -1
  50. package/es/components/ellipsis/ellipsis.d.ts +3 -0
  51. package/es/components/ellipsis/ellipsis.js +10 -2
  52. package/es/components/swiper/swiper.js +1 -1
  53. package/es/components/tabs/tabs.js +44 -12
  54. package/es/components/virtual-input/virtual-input.css +3 -1
  55. package/es/components/virtual-input/virtual-input.d.ts +6 -0
  56. package/es/components/virtual-input/virtual-input.js +105 -8
  57. package/package.json +3 -3
  58. package/umd/antd-mobile.js +1 -1
  59. package/2x/cjs/components/ellipsis/~ellipsis.d.ts +0 -15
  60. package/2x/cjs/components/ellipsis/~ellipsis.js +0 -161
  61. package/2x/es/components/ellipsis/~ellipsis.d.ts +0 -15
  62. package/2x/es/components/ellipsis/~ellipsis.js +0 -151
  63. package/cjs/components/ellipsis/~ellipsis.d.ts +0 -15
  64. package/cjs/components/ellipsis/~ellipsis.js +0 -161
  65. package/es/components/ellipsis/~ellipsis.d.ts +0 -15
  66. package/es/components/ellipsis/~ellipsis.js +0 -151
@@ -1,16 +1,16 @@
1
- import React, { isValidElement, useRef } from 'react';
1
+ import { animated, useSpring } from '@react-spring/web';
2
+ import { useIsomorphicLayoutEffect, useThrottleFn } from 'ahooks';
2
3
  import classNames from 'classnames';
3
- import { useSpring, animated } from '@react-spring/web';
4
- import { withNativeProps } from '../../utils/native-props';
5
- import { usePropsValue } from '../../utils/use-props-value';
4
+ import React, { isValidElement, useEffect, useRef } from 'react';
6
5
  import { bound } from '../../utils/bound';
7
- import { useThrottleFn, useIsomorphicLayoutEffect } from 'ahooks';
6
+ import { withNativeProps } from '../../utils/native-props';
7
+ import { ShouldRender } from '../../utils/should-render';
8
+ import { traverseReactNode } from '../../utils/traverse-react-node';
9
+ import { useIsomorphicUpdateLayoutEffect } from '../../utils/use-isomorphic-update-layout-effect';
8
10
  import { useMutationEffect } from '../../utils/use-mutation-effect';
11
+ import { usePropsValue } from '../../utils/use-props-value';
9
12
  import { useResizeEffect } from '../../utils/use-resize-effect';
10
13
  import { mergeProps } from '../../utils/with-default-props';
11
- import { useIsomorphicUpdateLayoutEffect } from '../../utils/use-isomorphic-update-layout-effect';
12
- import { ShouldRender } from '../../utils/should-render';
13
- import { traverseReactNode } from '../../utils/traverse-react-node';
14
14
  const classPrefix = `adm-tabs`;
15
15
  export const Tab = () => {
16
16
  return null;
@@ -25,6 +25,7 @@ export const Tabs = p => {
25
25
  const props = mergeProps(defaultProps, p);
26
26
  const tabListContainerRef = useRef(null);
27
27
  const activeLineRef = useRef(null);
28
+ const tabRefs = useRef({});
28
29
  const keyToIndexRecord = {};
29
30
  let firstActiveKey = null;
30
31
  const panes = [];
@@ -166,7 +167,7 @@ export const Tabs = p => {
166
167
  });
167
168
  const {
168
169
  run: updateMask
169
- } = useThrottleFn((immediate = false) => {
170
+ } = useThrottleFn(immediate => {
170
171
  const container = tabListContainerRef.current;
171
172
  if (!container) return;
172
173
  const scrollLeft = container.scrollLeft;
@@ -197,6 +198,34 @@ export const Tabs = p => {
197
198
  useIsomorphicLayoutEffect(() => {
198
199
  updateMask(true);
199
200
  }, []);
201
+ const handleKeyDown = e => {
202
+ const keys = Object.keys(keyToIndexRecord);
203
+ const currentIndex = keyToIndexRecord[activeKey];
204
+ const isNext = isRTL ? e.key === 'ArrowLeft' : e.key === 'ArrowRight';
205
+ const isPrev = isRTL ? e.key === 'ArrowRight' : e.key === 'ArrowLeft';
206
+ const offsetDirection = isNext ? 1 : -1;
207
+ const findNextEnabledTab = (startIndex, direction) => {
208
+ const length = keys.length;
209
+ for (let i = 0; i < length; i++) {
210
+ const index = (startIndex + direction * (i + 1) + length) % length;
211
+ const key = keys[index];
212
+ const pane = panes.find(p => p.key === key);
213
+ if (!(pane === null || pane === void 0 ? void 0 : pane.props.disabled)) return key;
214
+ }
215
+ return keys[startIndex];
216
+ };
217
+ const currentKey = findNextEnabledTab(currentIndex, offsetDirection);
218
+ if (isNext || isPrev) {
219
+ e.preventDefault();
220
+ setActiveKey(currentKey);
221
+ }
222
+ };
223
+ useEffect(() => {
224
+ var _a;
225
+ if (activeKey && tabRefs.current[activeKey]) {
226
+ (_a = tabRefs.current[activeKey]) === null || _a === void 0 ? void 0 : _a.focus();
227
+ }
228
+ }, [activeKey]);
200
229
  return withNativeProps(props, React.createElement("div", {
201
230
  className: classPrefix,
202
231
  style: {
@@ -219,6 +248,7 @@ export const Tabs = p => {
219
248
  ref: tabListContainerRef,
220
249
  scrollLeft: scrollLeft,
221
250
  onScroll: updateMask,
251
+ onKeyDown: handleKeyDown,
222
252
  role: 'tablist'
223
253
  }, React.createElement(animated.div, {
224
254
  ref: activeLineRef,
@@ -233,6 +263,10 @@ export const Tabs = p => {
233
263
  [`${classPrefix}-tab-wrapper-stretch`]: props.stretch
234
264
  })
235
265
  }, React.createElement("div", {
266
+ role: 'tab',
267
+ "aria-selected": pane.key === activeKey,
268
+ tabIndex: pane.key === activeKey ? 0 : -1,
269
+ ref: el => tabRefs.current[pane.key] = el,
236
270
  onClick: () => {
237
271
  const {
238
272
  key
@@ -246,9 +280,7 @@ export const Tabs = p => {
246
280
  className: classNames(`${classPrefix}-tab`, {
247
281
  [`${classPrefix}-tab-active`]: pane.key === activeKey,
248
282
  [`${classPrefix}-tab-disabled`]: pane.props.disabled
249
- }),
250
- role: 'tab',
251
- "aria-selected": pane.key === activeKey
283
+ })
252
284
  }, pane.props.title)))))), panes.map(pane => {
253
285
  if (pane.props.children === undefined) {
254
286
  return null;
@@ -73,7 +73,6 @@
73
73
  }
74
74
 
75
75
  .adm-virtual-input:focus .adm-virtual-input-caret {
76
- display: block;
77
76
  animation-name: adm-caret-blink;
78
77
  animation-duration: 1s;
79
78
  animation-timing-function: linear;
@@ -84,6 +83,10 @@
84
83
  color: var(--disabled-color);
85
84
  }
86
85
 
86
+ .adm-virtual-input-caret-dragging .adm-virtual-input-caret {
87
+ animation: none !important;
88
+ }
89
+
87
90
  @keyframes adm-caret-blink {
88
91
  from {
89
92
  opacity: 1;
@@ -3,6 +3,10 @@ import React from 'react';
3
3
  import { NativeProps } from '../../utils/native-props';
4
4
  import type { InputProps } from '../input';
5
5
  import { NumberKeyboardProps } from '../number-keyboard';
6
+ export declare type Cursor = {
7
+ movable?: boolean;
8
+ onMove?: (position: number) => void;
9
+ };
6
10
  export declare type VirtualInputProps = {
7
11
  onFocus?: () => void;
8
12
  onBlur?: () => void;
@@ -10,6 +14,7 @@ export declare type VirtualInputProps = {
10
14
  keyboard?: ReactElement<NumberKeyboardProps>;
11
15
  clearable?: boolean;
12
16
  onClear?: () => void;
17
+ cursor?: Cursor;
13
18
  } & Pick<InputProps, 'value' | 'onChange' | 'placeholder' | 'disabled' | 'clearIcon'> & NativeProps<'--font-size' | '--color' | '--placeholder-color' | '--disabled-color' | '--text-align' | '--caret-width' | '--caret-color'>;
14
19
  export declare type VirtualInputRef = {
15
20
  focus: () => void;
@@ -22,6 +27,7 @@ export declare const VirtualInput: React.ForwardRefExoticComponent<{
22
27
  keyboard?: ReactElement<NumberKeyboardProps, string | React.JSXElementConstructor<any>> | undefined;
23
28
  clearable?: boolean | undefined;
24
29
  onClear?: (() => void) | undefined;
30
+ cursor?: Cursor | undefined;
25
31
  } & Pick<InputProps, "value" | "onChange" | "disabled" | "placeholder" | "clearIcon"> & {
26
32
  className?: string | undefined;
27
33
  style?: (React.CSSProperties & Partial<Record<"--color" | "--font-size" | "--placeholder-color" | "--text-align" | "--disabled-color" | "--caret-width" | "--caret-color", string>>) | undefined;
@@ -8,7 +8,10 @@ import { mergeProp, mergeProps } from '../../utils/with-default-props';
8
8
  import { useConfig } from '../config-provider';
9
9
  const classPrefix = 'adm-virtual-input';
10
10
  const defaultProps = {
11
- defaultValue: ''
11
+ defaultValue: '',
12
+ cursor: {
13
+ movable: false
14
+ }
12
15
  };
13
16
  export const VirtualInput = forwardRef((props, ref) => {
14
17
  const {
@@ -21,6 +24,13 @@ export const VirtualInput = forwardRef((props, ref) => {
21
24
  const contentRef = useRef(null);
22
25
  const [hasFocus, setHasFocus] = useState(false);
23
26
  const [caretPosition, setCaretPosition] = useState(value.length); // 光标位置,从 0 开始,如值是 2 则表示光标在顺序下标为 2 的数字之前
27
+ const keyboardDataRef = useRef({}); // 临时记录虚拟键盘输入,在下次更新时用于判断光标位置如何调整
28
+ const touchDataRef = useRef(); // 记录上一次 touch 时的坐标位置
29
+ const charRef = useRef(null); // 第一个字符的 DOM
30
+ const charWidthRef = useRef(0); // 单个字符宽度
31
+ const caretRef = useRef(null); // 光标的 DOM
32
+ const [isCaretDragging, setIsCaretDragging] = useState(false);
33
+ const touchMoveTimeoutRef = useRef();
24
34
  const clearIcon = mergeProp(React.createElement(CloseCircleFill, null), componentConfig.clearIcon, props.clearIcon);
25
35
  function scrollToEnd() {
26
36
  const root = rootRef.current;
@@ -32,6 +42,24 @@ export const VirtualInput = forwardRef((props, ref) => {
32
42
  if (!content) return;
33
43
  content.scrollLeft = content.clientWidth;
34
44
  }
45
+ useEffect(() => {
46
+ // 记录单个字符的宽度,用于光标移动时的计算
47
+ if (charRef.current) {
48
+ charWidthRef.current = charRef.current.getBoundingClientRect().width;
49
+ }
50
+ }, [value]);
51
+ useEffect(() => {
52
+ // 经过外部受控逻辑后,再调整光标位置,如果受控逻辑改动了值则光标放到最后
53
+ if (value === keyboardDataRef.current.newValue) {
54
+ if (keyboardDataRef.current.mode === 'input') {
55
+ setCaretPosition(c => c + 1);
56
+ } else if (keyboardDataRef.current.mode === 'delete') {
57
+ setCaretPosition(c => c - 1);
58
+ }
59
+ } else {
60
+ setCaretPosition(value.length);
61
+ }
62
+ }, [value]);
35
63
  useIsomorphicLayoutEffect(() => {
36
64
  scrollToEnd();
37
65
  }, [value]);
@@ -65,16 +93,24 @@ export const VirtualInput = forwardRef((props, ref) => {
65
93
  onInput: v => {
66
94
  var _a, _b;
67
95
  const newValue = value.substring(0, caretPosition) + v + value.substring(caretPosition);
96
+ // 临时记录,用于后续光标位置
97
+ keyboardDataRef.current = {
98
+ newValue,
99
+ mode: 'input'
100
+ };
68
101
  setValue(newValue);
69
- setCaretPosition(c => c + 1);
70
102
  (_b = (_a = keyboard.props).onInput) === null || _b === void 0 ? void 0 : _b.call(_a, v);
71
103
  },
72
104
  onDelete: () => {
73
105
  var _a, _b;
74
106
  if (caretPosition === 0) return;
75
107
  const newValue = value.substring(0, caretPosition - 1) + value.substring(caretPosition);
108
+ // 临时记录,用于后续光标位置
109
+ keyboardDataRef.current = {
110
+ newValue,
111
+ mode: 'delete'
112
+ };
76
113
  setValue(newValue);
77
- setCaretPosition(caretPosition - 1);
78
114
  (_b = (_a = keyboard.props).onDelete) === null || _b === void 0 ? void 0 : _b.call(_a);
79
115
  },
80
116
  visible: hasFocus,
@@ -94,23 +130,80 @@ export const VirtualInput = forwardRef((props, ref) => {
94
130
  });
95
131
  // 点击输入框时,将光标置于最后
96
132
  const setCaretPositionToEnd = () => {
97
- setCaretPosition(value.length);
133
+ var _a, _b;
134
+ if (caretPosition !== value.length) {
135
+ setCaretPosition(value.length);
136
+ (_b = (_a = mergedProps.cursor) === null || _a === void 0 ? void 0 : _a.onMove) === null || _b === void 0 ? void 0 : _b.call(_a, value.length);
137
+ }
98
138
  };
99
139
  // 点击单个字符时,根据点击位置置于字符前或后
100
140
  const changeCaretPosition = index => e => {
141
+ var _a, _b, _c;
142
+ if (mergedProps.disabled || !((_a = mergedProps.cursor) === null || _a === void 0 ? void 0 : _a.movable)) return;
101
143
  e.stopPropagation();
102
144
  const rect = e.target.getBoundingClientRect();
103
145
  const midX = rect.left + rect.width / 2;
104
146
  const clickX = e.clientX;
105
147
  // 点击区域是否偏右
106
148
  const isRight = clickX > midX;
107
- setCaretPosition(isRight ? index + 1 : index);
149
+ const newCaretPosition = isRight ? index + 1 : index;
150
+ setCaretPosition(newCaretPosition);
151
+ (_c = (_b = mergedProps.cursor) === null || _b === void 0 ? void 0 : _b.onMove) === null || _c === void 0 ? void 0 : _c.call(_b, newCaretPosition);
152
+ };
153
+ // 在光标附近 touchmove 时也可以调整光标位置
154
+ const handleTouchStart = e => {
155
+ var _a;
156
+ if (mergedProps.disabled || !((_a = mergedProps.cursor) === null || _a === void 0 ? void 0 : _a.movable)) return;
157
+ if (!caretRef.current) return;
158
+ const touch = e.touches[0];
159
+ const caretRect = caretRef.current.getBoundingClientRect();
160
+ const distance = Math.abs(touch.clientX - (caretRect.left + caretRect.width / 2));
161
+ if (distance < 20) {
162
+ // 20px 阈值可调整
163
+ touchDataRef.current = {
164
+ startX: touch.clientX,
165
+ startCaretPosition: caretPosition
166
+ };
167
+ } else {
168
+ touchDataRef.current = null;
169
+ }
170
+ };
171
+ const handleTouchMove = e => {
172
+ var _a, _b, _c;
173
+ if (!touchDataRef.current || !((_a = mergedProps.cursor) === null || _a === void 0 ? void 0 : _a.movable)) return;
174
+ setIsCaretDragging(true);
175
+ const touch = e.touches[0];
176
+ const deltaX = touch.clientX - touchDataRef.current.startX;
177
+ const charWidth = charWidthRef.current;
178
+ const moveChars = Math.round(deltaX / charWidth);
179
+ let newCaretPosition = touchDataRef.current.startCaretPosition + moveChars;
180
+ // 边界处理
181
+ newCaretPosition = Math.max(0, Math.min(newCaretPosition, value.length));
182
+ setCaretPosition(newCaretPosition);
183
+ (_c = (_b = mergedProps.cursor) === null || _b === void 0 ? void 0 : _b.onMove) === null || _c === void 0 ? void 0 : _c.call(_b, newCaretPosition);
184
+ // 防止 touchend 不触发
185
+ if (touchMoveTimeoutRef.current) {
186
+ clearTimeout(touchMoveTimeoutRef.current);
187
+ }
188
+ touchMoveTimeoutRef.current = setTimeout(() => {
189
+ setIsCaretDragging(false);
190
+ touchMoveTimeoutRef.current = null;
191
+ }, 500);
192
+ };
193
+ const handleTouchEnd = () => {
194
+ touchDataRef.current = null;
195
+ setIsCaretDragging(false);
196
+ if (touchMoveTimeoutRef.current) {
197
+ clearTimeout(touchMoveTimeoutRef.current);
198
+ touchMoveTimeoutRef.current = null;
199
+ }
108
200
  };
109
201
  const chars = (value + '').split('');
110
202
  return withNativeProps(mergedProps, React.createElement("div", {
111
203
  ref: rootRef,
112
204
  className: classNames(classPrefix, {
113
- [`${classPrefix}-disabled`]: mergedProps.disabled
205
+ [`${classPrefix}-disabled`]: mergedProps.disabled,
206
+ [`${classPrefix}-caret-dragging`]: isCaretDragging
114
207
  }),
115
208
  tabIndex: mergedProps.disabled ? undefined : 0,
116
209
  role: 'textbox',
@@ -122,13 +215,18 @@ export const VirtualInput = forwardRef((props, ref) => {
122
215
  ref: contentRef,
123
216
  "aria-disabled": mergedProps.disabled,
124
217
  "aria-label": mergedProps.placeholder,
125
- onClick: setCaretPositionToEnd
218
+ onClick: setCaretPositionToEnd,
219
+ onTouchStart: handleTouchStart,
220
+ onTouchMove: handleTouchMove,
221
+ onTouchEnd: handleTouchEnd
126
222
  }, chars.slice(0, caretPosition).map((i, index) => React.createElement("span", {
223
+ ref: index === 0 ? charRef : undefined,
127
224
  key: index,
128
225
  onClick: changeCaretPosition(index)
129
226
  }, i)), React.createElement("div", {
130
227
  className: `${classPrefix}-caret-container`
131
228
  }, hasFocus && React.createElement("div", {
229
+ ref: caretRef,
132
230
  className: `${classPrefix}-caret`
133
231
  })), chars.slice(caretPosition).map((i, index) => React.createElement("span", {
134
232
  key: index,
@@ -139,7 +237,6 @@ export const VirtualInput = forwardRef((props, ref) => {
139
237
  var _a;
140
238
  e.stopPropagation();
141
239
  setValue('');
142
- setCaretPosition(0);
143
240
  (_a = mergedProps.onClear) === null || _a === void 0 ? void 0 : _a.call(mergedProps);
144
241
  },
145
242
  role: 'button',
package/2x/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antd-mobile",
3
- "version": "5.40.0",
3
+ "version": "5.41.0-alpha.1",
4
4
  "homepage": "https://github.com/ant-design/ant-design-mobile#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/ant-design/ant-design-mobile/issues"
@@ -53,8 +53,8 @@
53
53
  "use-sync-external-store": "^1.2.0"
54
54
  },
55
55
  "peerDependencies": {
56
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
57
- "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
56
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
57
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
58
58
  },
59
59
  "GravityCDN": "./umd/antd-mobile.js",
60
60
  "size-limit": [