11<script lang="ts" setup>
22import type { SplitPanelProps } from ' ./types' ;
3- import { clamp , useDraggable , useElementSize , useResizeObserver } from ' @vueuse/core' ;
4- import { computed , onMounted , ref , useTemplateRef , watch } from ' vue' ;
5- import { closestNumber } from ' ./utils/closest-number' ;
6- import { percentageToPixels } from ' ./utils/percentage-to-pixels' ;
7- import { pixelsToPercentage } from ' ./utils/pixels-to-percentage' ;
3+ import { ref , useTemplateRef , watch } from ' vue' ;
4+ import { useGridTemplate } from ' ./composables/use-grid-template' ;
5+ import { useKeyboard } from ' ./composables/use-keyboard' ;
6+ import { usePointer } from ' ./composables/use-pointer' ;
7+ import { useResize } from ' ./composables/use-resize' ;
8+ import { useSizes } from ' ./composables/use-sizes' ;
89
910const props = withDefaults (defineProps <SplitPanelProps >(), {
1011 orientation: ' horizontal' ,
@@ -25,80 +26,80 @@ const emits = defineEmits<{
2526 transitionend: [event : TransitionEvent ];
2627}>();
2728
28- const panelEl = useTemplateRef (' split-panel' );
29- const dividerEl = useTemplateRef (' divider' );
30-
31- const { width : componentWidth, height : componentHeight } = useElementSize (panelEl );
32- const componentSize = computed (() => props .orientation === ' horizontal' ? componentWidth .value : componentHeight .value );
33-
34- const { width : dividerWidth, height : dividerHeight } = useElementSize (dividerEl );
35- const dividerSize = computed (() => props .orientation === ' horizontal' ? dividerWidth .value : dividerHeight .value );
36-
3729/** Size of the primary panel in either percentages or pixels as defined by the sizeUnit property */
3830const size = defineModel <number >(' size' , { default: 50 });
3931
40- const sizePercentage = computed ({
41- get() {
42- if (props .sizeUnit === ' %' ) return size .value ;
43- return pixelsToPercentage (componentSize .value , size .value );
44- },
45- set(newValue : number ) {
46- if (props .sizeUnit === ' %' ) {
47- size .value = newValue ;
48- }
49- else {
50- size .value = percentageToPixels (componentSize .value , newValue );
51- }
52- },
53- });
54-
55- const sizePixels = computed ({
56- get() {
57- if (props .sizeUnit === ' px' ) return size .value ;
58- return percentageToPixels (componentSize .value , size .value );
59- },
60- set(newValue : number ) {
61- if (props .sizeUnit === ' px' ) {
62- size .value = newValue ;
63- }
64- else {
65- size .value = pixelsToPercentage (componentSize .value , newValue );
66- }
67- },
68- });
32+ /** Whether the primary column is collapsed or not */
33+ const collapsed = defineModel <boolean >(' collapsed' , { default: false });
6934
70- const minSizePercentage = computed (() => {
71- if ( props . minSize === undefined ) return ;
35+ const panelEl = useTemplateRef ( ' split-panel ' );
36+ const dividerEl = useTemplateRef ( ' divider ' ) ;
7237
73- if (props .sizeUnit === ' %' ) return props .minSize ;
74- return pixelsToPercentage (componentSize .value , props .minSize );
75- });
38+ let expandedSizePercentage = 0 ;
7639
77- const minSizePixels = computed (() => {
78- if (props .minSize === undefined ) return ;
40+ const collapseTransitionState = ref <null | ' expanding' | ' collapsing' >(null );
7941
80- if (props .sizeUnit === ' px' ) return props .minSize ;
81- return percentageToPixels (componentSize .value , props .minSize );
42+ const {
43+ sizePercentage,
44+ sizePixels,
45+ maxSizePercentage,
46+ minSizePercentage,
47+ minSizePixels,
48+ componentSize,
49+ dividerSize,
50+ snapPixels,
51+ } = useSizes (size , {
52+ disabled : () => props .disabled ,
53+ collapsible : () => props .collapsible ,
54+ primary : () => props .primary ,
55+ orientation : () => props .orientation ,
56+ sizeUnit : () => props .sizeUnit ,
57+ minSize : () => props .minSize ,
58+ maxSize : () => props .maxSize ,
59+ snapPoints : () => props .snapPoints ,
60+ panelEl ,
61+ dividerEl ,
8262});
8363
84- const maxSizePercentage = computed (() => {
85- if ( props . maxSize === undefined ) return ;
86-
87- if ( props . sizeUnit === ' % ' ) return props .maxSize ;
88- return pixelsToPercentage ( componentSize . value , props .maxSize );
64+ const { handleKeydown } = useKeyboard ( sizePercentage , collapsed , {
65+ disabled : () => props . disabled ,
66+ collapsible : () => props . collapsible ,
67+ primary : () => props .primary ,
68+ orientation : () => props .orientation ,
8969});
9070
91- const snapPixels = computed (() => {
92- if (props .sizeUnit === ' px' ) return props .snapPoints ;
93- return props .snapPoints .map ((snapPercentage ) => percentageToPixels (componentSize .value , snapPercentage ));
71+ const { isDragging, handleDblClick } = usePointer (collapsed , sizePercentage , sizePixels , {
72+ collapseThreshold : () => props .collapseThreshold ,
73+ collapsible : () => props .collapsible ,
74+ direction : () => props .direction ,
75+ disabled : () => props .disabled ,
76+ orientation : () => props .orientation ,
77+ primary : () => props .primary ,
78+ snapThreshold : () => props .snapThreshold ,
79+ panelEl ,
80+ dividerEl ,
81+ minSizePixels ,
82+ componentSize ,
83+ snapPixels ,
9484});
9585
96- let expandedSizePercentage = 0 ;
97-
98- /** Whether the primary column is collapsed or not */
99- const collapsed = defineModel <boolean >(' collapsed' , { default: false });
86+ const { gridTemplate } = useGridTemplate ({
87+ collapsed ,
88+ direction : () => props .direction ,
89+ dividerSize ,
90+ maxSizePercentage ,
91+ minSizePercentage ,
92+ orientation : () => props .orientation ,
93+ primary : () => props .primary ,
94+ sizePercentage ,
95+ });
10096
101- const collapseTransitionState = ref <null | ' expanding' | ' collapsing' >(null );
97+ useResize (sizePercentage , {
98+ sizePixels ,
99+ panelEl ,
100+ orientation : () => props .orientation ,
101+ primary : () => props .primary ,
102+ });
102103
103104const onTransitionEnd = (event : TransitionEvent ) => {
104105 collapseTransitionState .value = null ;
@@ -117,155 +118,6 @@ watch(collapsed, (newCollapsed) => {
117118 }
118119});
119120
120- let cachedSizePixels = 0 ;
121-
122- onMounted (() => {
123- cachedSizePixels = sizePixels .value ;
124- });
125-
126- const { x : dividerX, y : dividerY, isDragging } = useDraggable (dividerEl , { containerElement: panelEl });
127-
128- let hasToggledDuringCurrentDrag = false ;
129-
130- watch ([dividerX , dividerY ], ([newX , newY ]) => {
131- if (props .disabled ) return ;
132-
133- let newPositionInPixels = props .orientation === ' horizontal' ? newX : newY ;
134-
135- if (props .primary === ' end' ) {
136- newPositionInPixels = componentSize .value - newPositionInPixels ;
137- }
138-
139- if (props .collapsible && minSizePixels .value !== undefined && props .collapseThreshold !== undefined && hasToggledDuringCurrentDrag === false ) {
140- const collapseThreshold = minSizePixels .value - (props .collapseThreshold ?? 0 );
141- const expandThreshold = (props .collapseThreshold ?? 0 );
142-
143- if (newPositionInPixels < collapseThreshold && collapsed .value === false ) {
144- collapsed .value = true ;
145- hasToggledDuringCurrentDrag = true ;
146- }
147- else if (newPositionInPixels > expandThreshold && collapsed .value === true ) {
148- collapsed .value = false ;
149- hasToggledDuringCurrentDrag = true ;
150- }
151- }
152-
153- for (let snapPoint of snapPixels .value ) {
154- if (props .direction === ' rtl' && props .orientation === ' horizontal' ) {
155- snapPoint = componentSize .value - snapPoint ;
156- }
157-
158- if (
159- newPositionInPixels >= snapPoint - props .snapThreshold
160- && newPositionInPixels <= snapPoint + props .snapThreshold
161- ) {
162- newPositionInPixels = snapPoint ;
163- }
164- }
165-
166- sizePercentage .value = clamp (pixelsToPercentage (componentSize .value , newPositionInPixels ), 0 , 100 );
167- });
168-
169- watch (isDragging , (newDragging ) => {
170- if (newDragging === false ) hasToggledDuringCurrentDrag = false ;
171- });
172-
173- watch (sizePixels , (newPixels , oldPixels ) => {
174- if (newPixels === oldPixels ) return ;
175- cachedSizePixels = newPixels ;
176- });
177-
178- useResizeObserver (panelEl , (entries ) => {
179- const entry = entries [0 ];
180- const { width, height } = entry .contentRect ;
181- const size = props .orientation === ' horizontal' ? width : height ;
182-
183- if (props .primary ) {
184- sizePercentage .value = pixelsToPercentage (size , cachedSizePixels );
185- }
186- });
187-
188- const handleKeydown = (event : KeyboardEvent ) => {
189- if (props .disabled ) return ;
190-
191- if ([' ArrowLeft' , ' ArrowRight' , ' ArrowUp' , ' ArrowDown' , ' Home' , ' End' , ' Enter' ].includes (event .key )) {
192- event .preventDefault ();
193-
194- let newPosition = sizePercentage .value ;
195-
196- const increment = (event .shiftKey ? 10 : 1 ) * (props .primary === ' end' ? - 1 : 1 );
197-
198- if (
199- (event .key === ' ArrowLeft' && props .orientation === ' horizontal' )
200- || (event .key === ' ArrowUp' && props .orientation === ' vertical' )
201- ) {
202- newPosition -= increment ;
203- }
204-
205- if (
206- (event .key === ' ArrowRight' && props .orientation === ' horizontal' )
207- || (event .key === ' ArrowDown' && props .orientation === ' vertical' )
208- ) {
209- newPosition += increment ;
210- }
211-
212- if (event .key === ' Home' ) {
213- newPosition = props .primary === ' end' ? 100 : 0 ;
214- }
215-
216- if (event .key === ' End' ) {
217- newPosition = props .primary === ' end' ? 0 : 100 ;
218- }
219-
220- if (event .key === ' Enter' && props .collapsible ) {
221- collapsed .value = ! collapsed .value ;
222- }
223-
224- sizePercentage .value = clamp (newPosition , 0 , 100 );
225- }
226- };
227-
228- const handleDblClick = () => {
229- const closest = closestNumber (snapPixels .value , sizePixels .value );
230-
231- if (closest !== undefined ) {
232- sizePixels .value = closest ;
233- }
234- };
235-
236- const gridTemplate = computed (() => {
237- let primary: string ;
238-
239- if (collapsed .value ) {
240- primary = ' 0' ;
241- }
242- else if (minSizePercentage .value !== undefined && maxSizePercentage .value !== undefined ) {
243- primary = ` clamp(0%, clamp(${minSizePercentage .value }%, ${sizePercentage .value }%, ${maxSizePercentage .value }%), calc(100% - ${dividerSize .value }px)) ` ;
244- }
245- else {
246- primary = ` clamp(0%, ${sizePercentage .value }%, calc(100% - ${dividerSize .value }px)) ` ;
247- }
248-
249- const secondary = ' auto' ;
250-
251- if (! props .primary || props .primary === ' start' ) {
252- if (props .direction === ' ltr' || props .orientation === ' vertical' ) {
253- return ` ${primary } ${dividerSize .value }px ${secondary } ` ;
254- }
255- else {
256- return ` ${secondary } ${dividerSize .value }px ${primary } ` ;
257- }
258- }
259- else {
260- if (props .direction === ' ltr' || props .orientation === ' vertical' ) {
261- return ` ${secondary } ${dividerSize .value }px ${primary } ` ;
262- }
263- else {
264- return ` ${primary } ${dividerSize .value }px ${secondary } ` ;
265- }
266- }
267- });
268-
269121const collapse = () => collapsed .value = true ;
270122const expand = () => collapsed .value = false ;
271123const toggle = (val : boolean ) => collapsed .value = val ;
0 commit comments