@rainbow-oh/yee-c 0.14.3 → 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/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 +73 -7
- 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/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 +96 -10
- 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/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 +81 -2
- package/src/version.ts +1 -1
package/package.json
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;
|
|
@@ -139,7 +139,15 @@ const getPanelSize = (
|
|
|
139
139
|
return panelsRect;
|
|
140
140
|
};
|
|
141
141
|
|
|
142
|
-
const Wrapper = ({
|
|
142
|
+
const Wrapper = ({
|
|
143
|
+
children,
|
|
144
|
+
itemsSize,
|
|
145
|
+
itemTransition,
|
|
146
|
+
animation,
|
|
147
|
+
onDraggingChange,
|
|
148
|
+
onClick,
|
|
149
|
+
...props
|
|
150
|
+
}: any) => {
|
|
143
151
|
const wrappered = [] as React.ReactElement[];
|
|
144
152
|
const refs = useRef<
|
|
145
153
|
Record<
|
|
@@ -171,6 +179,8 @@ const Wrapper = ({ children, itemsSize, onClick, ...props }: any) => {
|
|
|
171
179
|
style: {
|
|
172
180
|
...style, // If there is a border or other size-related design, it may cause calculation issues
|
|
173
181
|
flexBasis: currentSize,
|
|
182
|
+
// animation 模式下受控尺寸变更走 flex-basis 过渡;拖拽期间为 undefined 保证跟手
|
|
183
|
+
transition: itemTransition,
|
|
174
184
|
},
|
|
175
185
|
min,
|
|
176
186
|
max,
|
|
@@ -212,15 +222,55 @@ const Wrapper = ({ children, itemsSize, onClick, ...props }: any) => {
|
|
|
212
222
|
flexable={flexable}
|
|
213
223
|
expandable={_expandable}
|
|
214
224
|
onClick={onClick}
|
|
225
|
+
onDraggingChange={onDraggingChange}
|
|
215
226
|
key={`handler-${n}`}
|
|
216
227
|
/>,
|
|
217
228
|
);
|
|
218
229
|
}
|
|
219
230
|
});
|
|
220
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
|
+
|
|
221
270
|
return (
|
|
222
271
|
<SplitterWrapperCtx.Provider value={{ refs }}>
|
|
223
272
|
{wrappered}
|
|
273
|
+
{exiting}
|
|
224
274
|
</SplitterWrapperCtx.Provider>
|
|
225
275
|
);
|
|
226
276
|
};
|
|
@@ -236,6 +286,7 @@ const Splitter = React.forwardRef(
|
|
|
236
286
|
children,
|
|
237
287
|
bordered,
|
|
238
288
|
onResize,
|
|
289
|
+
animation,
|
|
239
290
|
...rest
|
|
240
291
|
} = props;
|
|
241
292
|
|
|
@@ -245,6 +296,15 @@ const Splitter = React.forwardRef(
|
|
|
245
296
|
|
|
246
297
|
const [records, setRecords] = useState<PanelRect[]>([]);
|
|
247
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
|
+
|
|
248
308
|
const handleClick = (index: number, action: 'expand' | 'collapse') => {
|
|
249
309
|
const c = index - 1;
|
|
250
310
|
|
|
@@ -303,15 +363,31 @@ const Splitter = React.forwardRef(
|
|
|
303
363
|
}
|
|
304
364
|
}, [layout]);
|
|
305
365
|
|
|
366
|
+
const prevPanelCountRef = React.useRef(-1);
|
|
367
|
+
|
|
306
368
|
React.useEffect(() => {
|
|
307
369
|
if (totalSize) {
|
|
308
370
|
const panelsSize = getPanelSize(children, {
|
|
309
371
|
totalSize: totalSize,
|
|
310
372
|
layout,
|
|
311
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
|
+
}
|
|
312
388
|
setRecords(panelsSize);
|
|
313
389
|
}
|
|
314
|
-
}, [children, totalSize, layout]);
|
|
390
|
+
}, [children, totalSize, layout, animation]);
|
|
315
391
|
|
|
316
392
|
return (
|
|
317
393
|
<div className={cls} {...rest} ref={componentRef}>
|
|
@@ -323,6 +399,9 @@ const Splitter = React.forwardRef(
|
|
|
323
399
|
totalSize={totalSize}
|
|
324
400
|
itemsSize={records}
|
|
325
401
|
onClick={handleClick}
|
|
402
|
+
animation={animation}
|
|
403
|
+
itemTransition={itemTransition}
|
|
404
|
+
onDraggingChange={setDragging}
|
|
326
405
|
>
|
|
327
406
|
{children}
|
|
328
407
|
</Wrapper>
|