@rainbow-oh/yee-c 0.14.2 → 0.14.4
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.
- package/dist/cjs/Form/index.d.ts +1 -1
- package/dist/cjs/Splitter/demo/animation.d.ts +3 -0
- package/dist/cjs/Splitter/demo/animation.js +43 -0
- package/dist/cjs/Splitter/hooks/useMove.d.ts +1 -1
- package/dist/cjs/Splitter/hooks/useMove.js +3 -1
- package/dist/cjs/Splitter/interface.d.ts +6 -0
- package/dist/cjs/Splitter/splitter-handler.d.ts +3 -1
- package/dist/cjs/Splitter/splitter-handler.js +3 -2
- package/dist/cjs/Splitter/splitter.js +79 -11
- package/dist/cjs/style/themes.css +1 -1
- package/dist/cjs/style/variables.css +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Form/index.d.ts +1 -1
- package/dist/esm/Splitter/demo/animation.d.ts +3 -0
- package/dist/esm/Splitter/demo/animation.js +51 -0
- package/dist/esm/Splitter/hooks/useMove.d.ts +1 -1
- package/dist/esm/Splitter/hooks/useMove.js +3 -1
- package/dist/esm/Splitter/interface.d.ts +6 -0
- package/dist/esm/Splitter/splitter-handler.d.ts +3 -1
- package/dist/esm/Splitter/splitter-handler.js +3 -2
- package/dist/esm/Splitter/splitter.js +107 -14
- package/dist/esm/style/themes.css +1 -1
- package/dist/esm/style/variables.css +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Form/index.ts +1 -0
- package/src/Splitter/demo/animation.tsx +29 -0
- package/src/Splitter/hooks/useMove.ts +3 -0
- package/src/Splitter/index.en-US.md +2 -0
- package/src/Splitter/index.zh-CN.md +2 -0
- package/src/Splitter/interface.ts +6 -0
- package/src/Splitter/splitter-handler.tsx +4 -1
- package/src/Splitter/splitter.tsx +93 -6
- package/src/version.ts +1 -1
package/package.json
CHANGED
package/src/Form/index.ts
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Button, Splitter } from '@rainbow-oh/yee-c';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
export default () => {
|
|
5
|
+
const [open, setOpen] = React.useState(false);
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div>
|
|
9
|
+
<div style={{ marginBottom: 8 }}>
|
|
10
|
+
<Button type="primary" onClick={() => setOpen((v) => !v)}>
|
|
11
|
+
{open ? 'Close Panel' : 'Open Panel'}
|
|
12
|
+
</Button>
|
|
13
|
+
</div>
|
|
14
|
+
<div style={{ height: 300 }}>
|
|
15
|
+
<Splitter animation>
|
|
16
|
+
<Splitter.Item min="30%">
|
|
17
|
+
<div style={{ padding: 16, backgroundColor: '#f0f0f0' }}>Content Panel</div>
|
|
18
|
+
</Splitter.Item>
|
|
19
|
+
{open ? (
|
|
20
|
+
// 条件渲染的面板:挂载时从 0 展开到目标尺寸,卸载时过渡收合到 0 后移除
|
|
21
|
+
<Splitter.Item size="40%" min="200px" max="60%">
|
|
22
|
+
<div style={{ padding: 16, backgroundColor: '#e0e0e0' }}>Assistant Panel</div>
|
|
23
|
+
</Splitter.Item>
|
|
24
|
+
) : null}
|
|
25
|
+
</Splitter>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -5,6 +5,7 @@ export default function useMove(
|
|
|
5
5
|
handler: React.RefObject<HTMLDivElement | null>,
|
|
6
6
|
direction: 'horizontal' | 'vertical',
|
|
7
7
|
onMove: (dis: number) => void,
|
|
8
|
+
onMovingChange?: (moving: boolean) => void,
|
|
8
9
|
) {
|
|
9
10
|
const [moving, setMoving] = React.useState(false);
|
|
10
11
|
|
|
@@ -20,6 +21,7 @@ export default function useMove(
|
|
|
20
21
|
let x = e.pageX;
|
|
21
22
|
let y = e.pageY;
|
|
22
23
|
setMoving(true);
|
|
24
|
+
onMovingChange?.(true);
|
|
23
25
|
|
|
24
26
|
const onMouseMove = debounce((e: MouseEvent) => {
|
|
25
27
|
document.body.style.userSelect = 'none';
|
|
@@ -38,6 +40,7 @@ export default function useMove(
|
|
|
38
40
|
|
|
39
41
|
const onMouseUp = () => {
|
|
40
42
|
setMoving(false);
|
|
43
|
+
onMovingChange?.(false);
|
|
41
44
|
window.removeEventListener('mousemove', onMouseMove);
|
|
42
45
|
window.removeEventListener('mouseup', onMouseUp);
|
|
43
46
|
ele.removeEventListener('mouseup', onMouseUp);
|
|
@@ -21,6 +21,7 @@ Split panels for layout.
|
|
|
21
21
|
<code src="./demo/size.tsx" title="Size Control" description="Control panel size"></code>
|
|
22
22
|
<code src="./demo/nested.tsx" title="Nested" description="Nested splitters"></code>
|
|
23
23
|
<code src="./demo/bordered.tsx" title="Bordered" description="Splitter with borders"></code>
|
|
24
|
+
<code src="./demo/animation.tsx" title="Animation" description="With animation enabled, conditionally rendered panels mount/unmount and programmatic size changes transition smoothly; dragging stays real-time"></code>
|
|
24
25
|
|
|
25
26
|
## API
|
|
26
27
|
|
|
@@ -36,6 +37,7 @@ Split panels for layout.
|
|
|
36
37
|
| bordered | `boolean` | Display border or not | - |
|
|
37
38
|
| itemPadding | `number \| string` | Item padding | - |
|
|
38
39
|
| onResize | `(sizes: number[]) => void` | Panel size change event | - |
|
|
40
|
+
| animation | `boolean` | Animate programmatic size changes, collapsible expand/collapse and panel mount/unmount with a flex-basis transition; dragging always tracks the pointer in real time | `false` |
|
|
39
41
|
| data-* | `string` | - | Supports all data-* attributes, forwarded to root DOM element |
|
|
40
42
|
|
|
41
43
|
### SplitterItemProps
|
|
@@ -21,6 +21,7 @@ toc: 'content'
|
|
|
21
21
|
<code src="./demo/size.tsx" title="尺寸控制" description="控制面板尺寸"></code>
|
|
22
22
|
<code src="./demo/nested.tsx" title="嵌套" description="嵌套分割面板"></code>
|
|
23
23
|
<code src="./demo/bordered.tsx" title="边框" description="带边框的分割面板"></code>
|
|
24
|
+
<code src="./demo/animation.tsx" title="过渡动画" description="开启 animation 后,条件渲染面板的挂载/卸载与受控尺寸变更有平滑过渡动画;拖拽缩放始终实时跟手"></code>
|
|
24
25
|
|
|
25
26
|
## API
|
|
26
27
|
|
|
@@ -36,6 +37,7 @@ toc: 'content'
|
|
|
36
37
|
| bordered | `boolean` | 是否显示边框 | - |
|
|
37
38
|
| itemPadding | `number \| string` | 项目内边距 | - |
|
|
38
39
|
| onResize | `(sizes: number[]) => void` | 面板尺寸变化事件 | - |
|
|
40
|
+
| animation | `boolean` | 受控尺寸变更、collapsible 收展及面板挂载/卸载时的 flex-basis 过渡动画;拖拽缩放始终实时跟手,不受影响 | `false` |
|
|
39
41
|
| data-* | `string` | - | 支持所有 data-* 属性,透传到组件根 DOM 元素 |
|
|
40
42
|
|
|
41
43
|
### SplitterItemProps
|
|
@@ -82,4 +82,10 @@ export interface SplitterProps extends DataAttributeProps {
|
|
|
82
82
|
* panel size change event
|
|
83
83
|
* */
|
|
84
84
|
onResize?: (sizes: PanelRect[]) => void;
|
|
85
|
+
/**
|
|
86
|
+
* animate programmatic size changes and panel mount/unmount with a
|
|
87
|
+
* flex-basis transition; dragging always tracks the pointer in real time
|
|
88
|
+
* @default false
|
|
89
|
+
*/
|
|
90
|
+
animation?: boolean;
|
|
85
91
|
}
|
|
@@ -18,6 +18,8 @@ interface SplitterHandlerProps {
|
|
|
18
18
|
size: { startSize: number; endSize: number },
|
|
19
19
|
eles: { start: HTMLDivElement; end: HTMLDivElement },
|
|
20
20
|
) => void;
|
|
21
|
+
/** 拖拽开始/结束回调(animation 模式下拖拽期间需关闭 flex-basis 过渡) */
|
|
22
|
+
onDraggingChange?: (dragging: boolean) => void;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const SplitterHandler = ({
|
|
@@ -28,6 +30,7 @@ const SplitterHandler = ({
|
|
|
28
30
|
records,
|
|
29
31
|
setRecords,
|
|
30
32
|
onClick,
|
|
33
|
+
onDraggingChange,
|
|
31
34
|
}: SplitterHandlerProps) => {
|
|
32
35
|
// eslint-disable-line @typescript-eslint/no-unused-vars
|
|
33
36
|
const { start, end } = collapsible
|
|
@@ -91,7 +94,7 @@ const SplitterHandler = ({
|
|
|
91
94
|
});
|
|
92
95
|
};
|
|
93
96
|
|
|
94
|
-
useMove(handler, layout, onMove);
|
|
97
|
+
useMove(handler, layout, onMove, onDraggingChange);
|
|
95
98
|
|
|
96
99
|
const handleClick = (action: 'collapse' | 'expand') => {
|
|
97
100
|
const eles = refs.current;
|
|
@@ -35,12 +35,15 @@ const getPanelSize = (
|
|
|
35
35
|
},
|
|
36
36
|
) => {
|
|
37
37
|
const panelsRect = [] as PanelRect[];
|
|
38
|
-
|
|
38
|
+
// toArray 剔除 null/undefined/boolean(如 `{open ? <Item/> : null}` 条件面板)。
|
|
39
|
+
// Children.count 会把 null 也计为一面板,导致 auto 面板被平分出错误尺寸。
|
|
40
|
+
const items = React.Children.toArray(children);
|
|
41
|
+
const count = items.length;
|
|
39
42
|
|
|
40
43
|
let remainingSize = totalSize;
|
|
41
44
|
let remainingItem = count;
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
items.forEach((child) => {
|
|
44
47
|
if (React.isValidElement(child)) {
|
|
45
48
|
const { style, size, defaultSize, min, max } = child.props || ({} as any);
|
|
46
49
|
|
|
@@ -111,6 +114,8 @@ const getPanelSize = (
|
|
|
111
114
|
|
|
112
115
|
panelsRect.forEach((item) => {
|
|
113
116
|
if (item.type === 'default') {
|
|
117
|
+
// currentSize 缺失会导致 Wrapper 注入的 flexBasis 为空,defaultSize 失效(宽度由内容撑开)
|
|
118
|
+
item.currentSize = item.size;
|
|
114
119
|
remainingSize -= item.size || 0;
|
|
115
120
|
remainingItem--;
|
|
116
121
|
} else if (item.type === 'percent') {
|
|
@@ -124,7 +129,8 @@ const getPanelSize = (
|
|
|
124
129
|
if (remainingItem > 0) {
|
|
125
130
|
panelsRect.forEach((item) => {
|
|
126
131
|
if (item.type === 'auto') {
|
|
127
|
-
|
|
132
|
+
// 显式尺寸总和超出 totalSize 时 remainingSize 为负,负 flexBasis 会被浏览器丢弃导致面板回退内容尺寸
|
|
133
|
+
item.size = Math.max(0, remainingSize / remainingItem);
|
|
128
134
|
item.currentSize = item.size;
|
|
129
135
|
}
|
|
130
136
|
});
|
|
@@ -133,7 +139,15 @@ const getPanelSize = (
|
|
|
133
139
|
return panelsRect;
|
|
134
140
|
};
|
|
135
141
|
|
|
136
|
-
const Wrapper = ({
|
|
142
|
+
const Wrapper = ({
|
|
143
|
+
children,
|
|
144
|
+
itemsSize,
|
|
145
|
+
itemTransition,
|
|
146
|
+
animation,
|
|
147
|
+
onDraggingChange,
|
|
148
|
+
onClick,
|
|
149
|
+
...props
|
|
150
|
+
}: any) => {
|
|
137
151
|
const wrappered = [] as React.ReactElement[];
|
|
138
152
|
const refs = useRef<
|
|
139
153
|
Record<
|
|
@@ -145,9 +159,10 @@ const Wrapper = ({ children, itemsSize, onClick, ...props }: any) => {
|
|
|
145
159
|
}
|
|
146
160
|
>
|
|
147
161
|
>({});
|
|
162
|
+
// 与 getPanelSize 同口径:toArray 剔除 null,index/handler 判断不受条件渲染干扰
|
|
148
163
|
const arr = React.Children.toArray(children);
|
|
149
164
|
|
|
150
|
-
|
|
165
|
+
arr.forEach((child, index) => {
|
|
151
166
|
if (React.isValidElement(child)) {
|
|
152
167
|
const n = index + 1;
|
|
153
168
|
const { style } = child.props || ({} as any);
|
|
@@ -164,6 +179,8 @@ const Wrapper = ({ children, itemsSize, onClick, ...props }: any) => {
|
|
|
164
179
|
style: {
|
|
165
180
|
...style, // If there is a border or other size-related design, it may cause calculation issues
|
|
166
181
|
flexBasis: currentSize,
|
|
182
|
+
// animation 模式下受控尺寸变更走 flex-basis 过渡;拖拽期间为 undefined 保证跟手
|
|
183
|
+
transition: itemTransition,
|
|
167
184
|
},
|
|
168
185
|
min,
|
|
169
186
|
max,
|
|
@@ -178,6 +195,7 @@ const Wrapper = ({ children, itemsSize, onClick, ...props }: any) => {
|
|
|
178
195
|
},
|
|
179
196
|
}),
|
|
180
197
|
);
|
|
198
|
+
|
|
181
199
|
const next = arr[index + 1];
|
|
182
200
|
|
|
183
201
|
if (!next || !React.isValidElement(next)) {
|
|
@@ -204,15 +222,55 @@ const Wrapper = ({ children, itemsSize, onClick, ...props }: any) => {
|
|
|
204
222
|
flexable={flexable}
|
|
205
223
|
expandable={_expandable}
|
|
206
224
|
onClick={onClick}
|
|
225
|
+
onDraggingChange={onDraggingChange}
|
|
207
226
|
key={`handler-${n}`}
|
|
208
227
|
/>,
|
|
209
228
|
);
|
|
210
229
|
}
|
|
211
230
|
});
|
|
212
231
|
|
|
232
|
+
// ── 出场动画:children 数量减少(条件渲染的面板卸载)时,保留上一次渲染的
|
|
233
|
+
// 尾部面板克隆,flex-basis 过渡到 0 后再真正移除,避免右栏瞬间消失导致跳变。
|
|
234
|
+
const prevCountRef = React.useRef(arr.length);
|
|
235
|
+
const prevClonesRef = React.useRef<React.ReactElement[]>([]);
|
|
236
|
+
const [exiting, setExiting] = React.useState<React.ReactElement<any>[]>([]);
|
|
237
|
+
|
|
238
|
+
React.useEffect(() => {
|
|
239
|
+
const prevCount = prevCountRef.current;
|
|
240
|
+
prevCountRef.current = arr.length;
|
|
241
|
+
if (!animation || arr.length >= prevCount) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// 上一帧克隆里已带原 flex-basis(本帧 records 已不含被移除面板)
|
|
245
|
+
const removed = prevClonesRef.current.slice(arr.length);
|
|
246
|
+
if (!removed.length) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
setExiting(removed);
|
|
250
|
+
// 第二阶段:下一帧 flex-basis 归 0,触发过渡
|
|
251
|
+
const raf = requestAnimationFrame(() => {
|
|
252
|
+
setExiting((list) =>
|
|
253
|
+
list.map((el) => React.cloneElement(el, { style: { ...el.props.style, flexBasis: 0 } })),
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
const timer = window.setTimeout(() => setExiting([]), 320);
|
|
257
|
+
return () => {
|
|
258
|
+
// 过渡期间面板重新打开:立即丢弃残留克隆
|
|
259
|
+
cancelAnimationFrame(raf);
|
|
260
|
+
window.clearTimeout(timer);
|
|
261
|
+
setExiting([]);
|
|
262
|
+
};
|
|
263
|
+
}, [arr.length, animation]);
|
|
264
|
+
|
|
265
|
+
// 记录本帧克隆,供下一次渲染的移除检测读取(声明在其后:上方 effect 读到的是旧值)
|
|
266
|
+
React.useEffect(() => {
|
|
267
|
+
prevClonesRef.current = wrappered;
|
|
268
|
+
});
|
|
269
|
+
|
|
213
270
|
return (
|
|
214
271
|
<SplitterWrapperCtx.Provider value={{ refs }}>
|
|
215
272
|
{wrappered}
|
|
273
|
+
{exiting}
|
|
216
274
|
</SplitterWrapperCtx.Provider>
|
|
217
275
|
);
|
|
218
276
|
};
|
|
@@ -228,6 +286,7 @@ const Splitter = React.forwardRef(
|
|
|
228
286
|
children,
|
|
229
287
|
bordered,
|
|
230
288
|
onResize,
|
|
289
|
+
animation,
|
|
231
290
|
...rest
|
|
232
291
|
} = props;
|
|
233
292
|
|
|
@@ -237,6 +296,15 @@ const Splitter = React.forwardRef(
|
|
|
237
296
|
|
|
238
297
|
const [records, setRecords] = useState<PanelRect[]>([]);
|
|
239
298
|
|
|
299
|
+
// animation 模式:拖拽期间关掉面板的 flex-basis 过渡(onMove 直接写 DOM,过渡会导致跟不
|
|
300
|
+
// 上鼠标);其余程序性尺寸变更(受控 size、collapsible 收展、面板增减)都走过渡。
|
|
301
|
+
const [dragging, setDragging] = React.useState(false);
|
|
302
|
+
const itemTransition = animation
|
|
303
|
+
? dragging
|
|
304
|
+
? undefined
|
|
305
|
+
: 'flex-basis 0.28s cubic-bezier(0.22, 0.61, 0.36, 1)'
|
|
306
|
+
: undefined;
|
|
307
|
+
|
|
240
308
|
const handleClick = (index: number, action: 'expand' | 'collapse') => {
|
|
241
309
|
const c = index - 1;
|
|
242
310
|
|
|
@@ -295,15 +363,31 @@ const Splitter = React.forwardRef(
|
|
|
295
363
|
}
|
|
296
364
|
}, [layout]);
|
|
297
365
|
|
|
366
|
+
const prevPanelCountRef = React.useRef(-1);
|
|
367
|
+
|
|
298
368
|
React.useEffect(() => {
|
|
299
369
|
if (totalSize) {
|
|
300
370
|
const panelsSize = getPanelSize(children, {
|
|
301
371
|
totalSize: totalSize,
|
|
302
372
|
layout,
|
|
303
373
|
});
|
|
374
|
+
// animation 模式下面板新增(条件渲染的面板挂载)时先以 0 尺寸入库,下一帧再落到
|
|
375
|
+
// 目标尺寸,配合 flex-basis 过渡播放入场动画;否则新面板瞬间撑开、旧面板瞬间收窄。
|
|
376
|
+
const prevCount = prevPanelCountRef.current;
|
|
377
|
+
prevPanelCountRef.current = panelsSize.length;
|
|
378
|
+
if (animation && prevCount >= 0 && panelsSize.length > prevCount) {
|
|
379
|
+
panelsSize.slice(prevCount).forEach((item) => {
|
|
380
|
+
item.currentSize = 0;
|
|
381
|
+
});
|
|
382
|
+
setRecords(panelsSize);
|
|
383
|
+
const raf = requestAnimationFrame(() => {
|
|
384
|
+
setRecords(getPanelSize(children, { totalSize: totalSize, layout }));
|
|
385
|
+
});
|
|
386
|
+
return () => cancelAnimationFrame(raf);
|
|
387
|
+
}
|
|
304
388
|
setRecords(panelsSize);
|
|
305
389
|
}
|
|
306
|
-
}, [children, totalSize, layout]);
|
|
390
|
+
}, [children, totalSize, layout, animation]);
|
|
307
391
|
|
|
308
392
|
return (
|
|
309
393
|
<div className={cls} {...rest} ref={componentRef}>
|
|
@@ -315,6 +399,9 @@ const Splitter = React.forwardRef(
|
|
|
315
399
|
totalSize={totalSize}
|
|
316
400
|
itemsSize={records}
|
|
317
401
|
onClick={handleClick}
|
|
402
|
+
animation={animation}
|
|
403
|
+
itemTransition={itemTransition}
|
|
404
|
+
onDraggingChange={setDragging}
|
|
318
405
|
>
|
|
319
406
|
{children}
|
|
320
407
|
</Wrapper>
|