Skip to content

Commit b85902b

Browse files
feat: add support for a min collapsed size (#50)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent e53a9ed commit b85902b

File tree

10 files changed

+114
-21
lines changed

10 files changed

+114
-21
lines changed

docs/content/1.getting-started/2.usage.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ import { SplitPanel } from '@directus/vue-split-panel';
9797
How far to drag beyond the minSize to collapse/expand the primary panel.
9898
::
9999

100+
::field{name="collapsedSize" type="number"}
101+
How much of the collapsed panel is visible in its collapsed state.
102+
::
103+
100104
::field{name="transitionDuration" type="number"}
101105
Duration of the collapse/expand transition in ms.
102106
Defaults to `0`

packages/vue-split-panel/playground/src/App.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const collapsed = ref(false);
1313
v-model:collapsed="collapsed"
1414
collapsible
1515
:collapse-threshold="100"
16+
:collapsed-size="50"
1617
:min-size="250"
1718
:max-size="400"
1819
size-unit="px"

packages/vue-split-panel/src/SplitPanel.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const props = withDefaults(defineProps<SplitPanelProps>(), {
1616
sizeUnit: '%',
1717
direction: 'ltr',
1818
collapsible: false,
19+
collapsedSize: 0,
1920
transitionDuration: 0,
2021
transitionTimingFunctionCollapse: 'cubic-bezier(0.4, 0, 0.6, 1)',
2122
transitionTimingFunctionExpand: 'cubic-bezier(0, 0, 0.2, 1)',
@@ -41,6 +42,7 @@ const {
4142
componentSize,
4243
dividerSize,
4344
snapPixels,
45+
collapsedSizePercentage,
4446
} = useSizes(size, {
4547
disabled: () => props.disabled,
4648
collapsible: () => props.collapsible,
@@ -50,6 +52,7 @@ const {
5052
minSize: () => props.minSize,
5153
maxSize: () => props.maxSize,
5254
snapPoints: () => props.snapPoints,
55+
collapsedSize: () => props.collapsedSize,
5356
panelEl,
5457
dividerEl,
5558
});
@@ -87,6 +90,7 @@ const { gridTemplate } = useGridTemplate({
8790
orientation: () => props.orientation,
8891
primary: () => props.primary,
8992
sizePercentage,
93+
collapsedSizePercentage,
9094
});
9195
9296
useResize(sizePercentage, {
@@ -105,7 +109,7 @@ const {
105109
} = useCollapse(
106110
collapsed,
107111
sizePercentage,
108-
{ transitionDuration: () => props.transitionDuration },
112+
{ transitionDuration: () => props.transitionDuration, collapsedSize: () => props.collapsedSize },
109113
);
110114
111115
defineExpose({ collapse, expand, toggle });

packages/vue-split-panel/src/composables/use-collapse.test.ts

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ describe('useCollapse', () => {
66
it('should return expected methods and properties', () => {
77
const collapsed = ref(false);
88
const sizePercentage = ref(50);
9-
const options = { transitionDuration: 300 };
9+
const options = { transitionDuration: 300, collapsedSize: 0 };
1010

1111
const result = useCollapse(collapsed, sizePercentage, options);
1212

@@ -26,7 +26,7 @@ describe('useCollapse', () => {
2626
it('should set collapsed to true', () => {
2727
const collapsed = ref(false);
2828
const sizePercentage = ref(50);
29-
const options = { transitionDuration: 300 };
29+
const options = { transitionDuration: 300, collapsedSize: 0 };
3030

3131
const { collapse } = useCollapse(collapsed, sizePercentage, options);
3232

@@ -40,7 +40,7 @@ describe('useCollapse', () => {
4040
it('should set collapsed to false', () => {
4141
const collapsed = ref(true);
4242
const sizePercentage = ref(0);
43-
const options = { transitionDuration: 300 };
43+
const options = { transitionDuration: 300, collapsedSize: 0 };
4444

4545
const { expand } = useCollapse(collapsed, sizePercentage, options);
4646

@@ -54,7 +54,7 @@ describe('useCollapse', () => {
5454
it('should set collapsed to the provided value', () => {
5555
const collapsed = ref(false);
5656
const sizePercentage = ref(50);
57-
const options = { transitionDuration: 300 };
57+
const options = { transitionDuration: 300, collapsedSize: 0 };
5858

5959
const { toggle } = useCollapse(collapsed, sizePercentage, options);
6060

@@ -67,10 +67,10 @@ describe('useCollapse', () => {
6767
});
6868

6969
describe('collapsed watcher behavior', () => {
70-
it('should store size and set to 0 when collapsing', async () => {
70+
it('should store size and set to collapsedSize when collapsing', async () => {
7171
const collapsed = ref(false);
7272
const sizePercentage = ref(75);
73-
const options = { transitionDuration: 300 };
73+
const options = { transitionDuration: 300, collapsedSize: 0 };
7474

7575
const { collapseTransitionState } = useCollapse(collapsed, sizePercentage, options);
7676

@@ -84,7 +84,7 @@ describe('useCollapse', () => {
8484
it('should restore size when expanding', async () => {
8585
const collapsed = ref(false);
8686
const sizePercentage = ref(60);
87-
const options = { transitionDuration: 300 };
87+
const options = { transitionDuration: 300, collapsedSize: 0 };
8888

8989
const { collapseTransitionState } = useCollapse(collapsed, sizePercentage, options);
9090

@@ -104,7 +104,7 @@ describe('useCollapse', () => {
104104
it('should preserve original size through multiple collapse/expand cycles', async () => {
105105
const collapsed = ref(false);
106106
const sizePercentage = ref(42);
107-
const options = { transitionDuration: 300 };
107+
const options = { transitionDuration: 300, collapsedSize: 0 };
108108

109109
useCollapse(collapsed, sizePercentage, options);
110110

@@ -130,7 +130,7 @@ describe('useCollapse', () => {
130130
it('should handle size changes between collapse cycles', async () => {
131131
const collapsed = ref(false);
132132
const sizePercentage = ref(30);
133-
const options = { transitionDuration: 300 };
133+
const options = { transitionDuration: 300, collapsedSize: 0 };
134134

135135
useCollapse(collapsed, sizePercentage, options);
136136

@@ -163,7 +163,7 @@ describe('useCollapse', () => {
163163
it('should start with null transition state', () => {
164164
const collapsed = ref(false);
165165
const sizePercentage = ref(50);
166-
const options = { transitionDuration: 300 };
166+
const options = { transitionDuration: 300, collapsedSize: 0 };
167167

168168
const { collapseTransitionState } = useCollapse(collapsed, sizePercentage, options);
169169

@@ -173,7 +173,7 @@ describe('useCollapse', () => {
173173
it('should set collapsing state when collapsed becomes true', async () => {
174174
const collapsed = ref(false);
175175
const sizePercentage = ref(50);
176-
const options = { transitionDuration: 300 };
176+
const options = { transitionDuration: 300, collapsedSize: 0 };
177177

178178
const { collapseTransitionState } = useCollapse(collapsed, sizePercentage, options);
179179

@@ -186,7 +186,7 @@ describe('useCollapse', () => {
186186
it('should set expanding state when collapsed becomes false', async () => {
187187
const collapsed = ref(true);
188188
const sizePercentage = ref(0);
189-
const options = { transitionDuration: 300 };
189+
const options = { transitionDuration: 300, collapsedSize: 0 };
190190

191191
const { collapseTransitionState } = useCollapse(collapsed, sizePercentage, options);
192192

@@ -201,7 +201,7 @@ describe('useCollapse', () => {
201201
it('should return CSS transition duration', () => {
202202
const collapsed = ref(false);
203203
const sizePercentage = ref(50);
204-
const options = { transitionDuration: 500 };
204+
const options = { transitionDuration: 500, collapsedSize: 0 };
205205

206206
const { transitionDurationCss } = useCollapse(collapsed, sizePercentage, options);
207207

@@ -212,7 +212,7 @@ describe('useCollapse', () => {
212212
const collapsed = ref(false);
213213
const sizePercentage = ref(50);
214214
const transitionDuration = ref(300);
215-
const options = { transitionDuration };
215+
const options = { transitionDuration, collapsedSize: 0 };
216216

217217
const { transitionDurationCss } = useCollapse(collapsed, sizePercentage, options);
218218

@@ -227,7 +227,7 @@ describe('useCollapse', () => {
227227
it('should handle rapid collapse/expand operations', async () => {
228228
const collapsed = ref(false);
229229
const sizePercentage = ref(65);
230-
const options = { transitionDuration: 300 };
230+
const options = { transitionDuration: 300, collapsedSize: 0 };
231231

232232
const { collapseTransitionState } = useCollapse(collapsed, sizePercentage, options);
233233

@@ -247,7 +247,7 @@ describe('useCollapse', () => {
247247
it('should work with methods triggering state changes', async () => {
248248
const collapsed = ref(false);
249249
const sizePercentage = ref(45);
250-
const options = { transitionDuration: 300 };
250+
const options = { transitionDuration: 300, collapsedSize: 0 };
251251

252252
const { collapse, expand, toggle, collapseTransitionState } = useCollapse(collapsed, sizePercentage, options);
253253

@@ -276,7 +276,7 @@ describe('useCollapse', () => {
276276
it('should work with zero initial size', async () => {
277277
const collapsed = ref(false);
278278
const sizePercentage = ref(0);
279-
const options = { transitionDuration: 300 };
279+
const options = { transitionDuration: 300, collapsedSize: 0 };
280280

281281
useCollapse(collapsed, sizePercentage, options);
282282

@@ -290,5 +290,51 @@ describe('useCollapse', () => {
290290
await nextTick();
291291
expect(sizePercentage.value).toBe(0);
292292
});
293+
294+
it('should use custom collapsedSize value', async () => {
295+
const collapsed = ref(false);
296+
const sizePercentage = ref(60);
297+
const options = { transitionDuration: 300, collapsedSize: 10 };
298+
299+
const { collapseTransitionState } = useCollapse(collapsed, sizePercentage, options);
300+
301+
// Collapse to custom size
302+
collapsed.value = true;
303+
await nextTick();
304+
expect(sizePercentage.value).toBe(10);
305+
expect(collapseTransitionState.value).toBe('collapsing');
306+
307+
// Expand should restore original size
308+
collapsed.value = false;
309+
await nextTick();
310+
expect(sizePercentage.value).toBe(60);
311+
expect(collapseTransitionState.value).toBe('expanding');
312+
});
313+
314+
it('should support reactive collapsedSize value', async () => {
315+
const collapsed = ref(false);
316+
const sizePercentage = ref(70);
317+
const collapsedSize = ref(5);
318+
const options = { transitionDuration: 300, collapsedSize };
319+
320+
useCollapse(collapsed, sizePercentage, options);
321+
322+
// Collapse with initial collapsedSize
323+
collapsed.value = true;
324+
await nextTick();
325+
expect(sizePercentage.value).toBe(5);
326+
327+
// Change collapsedSize while collapsed
328+
collapsedSize.value = 15;
329+
330+
// Expand and collapse again with new collapsedSize
331+
collapsed.value = false;
332+
await nextTick();
333+
expect(sizePercentage.value).toBe(70);
334+
335+
collapsed.value = true;
336+
await nextTick();
337+
expect(sizePercentage.value).toBe(15);
338+
});
293339
});
294340
});

packages/vue-split-panel/src/composables/use-collapse.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { computed, toValue, watch } from 'vue';
44

55
export interface UseCollapseOptions {
66
transitionDuration: MaybeRefOrGetter<number>;
7+
collapsedSize: MaybeRefOrGetter<number>;
78
}
89

910
export const useCollapse = (collapsed: Ref<boolean>, sizePercentage: Ref<number>, options: UseCollapseOptions) => {
@@ -16,7 +17,7 @@ export const useCollapse = (collapsed: Ref<boolean>, sizePercentage: Ref<number>
1617
watch(collapsed, (newCollapsed) => {
1718
if (newCollapsed === true) {
1819
expandedSizePercentage = sizePercentage.value;
19-
sizePercentage.value = 0;
20+
sizePercentage.value = toValue(options.collapsedSize);
2021
collapseTransitionState.value = 'collapsing';
2122
}
2223
else {

packages/vue-split-panel/src/composables/use-grid-template.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe('useGridTemplate', () => {
1111
maxSizePercentage: computed(() => {}) as ComputedRef<number | undefined>,
1212
sizePercentage: computed(() => 50),
1313
dividerSize: computed(() => 4),
14+
collapsedSizePercentage: computed(() => 0),
1415
primary: 'start',
1516
direction: 'ltr',
1617
orientation: 'horizontal',
@@ -21,7 +22,28 @@ describe('useGridTemplate', () => {
2122
const options = createOptions({ collapsed: ref(true) });
2223
const { gridTemplate } = useGridTemplate(options);
2324

24-
expect(gridTemplate.value).toBe('0 4px auto');
25+
expect(gridTemplate.value).toBe('0% 4px auto');
26+
});
27+
28+
it('uses custom collapsedSizePercentage when collapsed', () => {
29+
const options = createOptions({
30+
collapsed: ref(true),
31+
collapsedSizePercentage: computed(() => 10),
32+
});
33+
const { gridTemplate } = useGridTemplate(options);
34+
35+
expect(gridTemplate.value).toBe('10% 4px auto');
36+
});
37+
38+
it('uses custom collapsedSizePercentage with end primary', () => {
39+
const options = createOptions({
40+
collapsed: ref(true),
41+
collapsedSizePercentage: computed(() => 15),
42+
primary: 'end',
43+
});
44+
const { gridTemplate } = useGridTemplate(options);
45+
46+
expect(gridTemplate.value).toBe('auto 4px 15%');
2547
});
2648

2749
it('returns basic clamp template when no min/max constraints', () => {

packages/vue-split-panel/src/composables/use-grid-template.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ export interface UseGridTemplateOptions {
1111
primary: MaybeRefOrGetter<Primary | undefined>;
1212
direction: MaybeRefOrGetter<Direction>;
1313
orientation: MaybeRefOrGetter<Orientation>;
14+
collapsedSizePercentage: ComputedRef<number>;
1415
}
1516

1617
export const useGridTemplate = (options: UseGridTemplateOptions) => {
1718
const gridTemplate = computed(() => {
1819
let primary: string;
1920

2021
if (options.collapsed.value) {
21-
primary = '0';
22+
primary = `${options.collapsedSizePercentage.value}%`;
2223
}
2324
else if (options.minSizePercentage.value !== undefined && options.maxSizePercentage.value !== undefined) {
2425
primary = `clamp(0%, clamp(${options.minSizePercentage.value}%, ${options.sizePercentage.value}%, ${options.maxSizePercentage.value}%), calc(100% - ${options.dividerSize.value}px))`;

packages/vue-split-panel/src/composables/use-sizes.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('useSizes', () => {
3232
snapPoints: [25, 50, 75],
3333
panelEl: mockPanelEl,
3434
dividerEl: mockDividerEl,
35+
collapsedSize: 0,
3536
};
3637
});
3738

packages/vue-split-panel/src/composables/use-sizes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface UseSizesOptions {
1717
snapPoints: MaybeRefOrGetter<number[]>;
1818
panelEl: MaybeComputedElementRef;
1919
dividerEl: MaybeComputedElementRef;
20+
collapsedSize: MaybeRefOrGetter<number>;
2021
}
2122

2223
export const useSizes = (size: Ref<number>, options: UseSizesOptions) => {
@@ -73,6 +74,11 @@ export const useSizes = (size: Ref<number>, options: UseSizesOptions) => {
7374
return pixelsToPercentage(componentSize.value, toValue(options.maxSize)!);
7475
});
7576

77+
const collapsedSizePercentage = computed(() => {
78+
if (toValue(options.sizeUnit) === '%') return toValue(options.collapsedSize);
79+
return pixelsToPercentage(componentSize.value, toValue(options.collapsedSize)!);
80+
});
81+
7682
const snapPixels = computed(() => {
7783
if (toValue(options.sizeUnit) === 'px') return toValue(options.snapPoints);
7884
return toValue(options.snapPoints).map((snapPercentage) => percentageToPixels(componentSize.value, snapPercentage));
@@ -87,5 +93,6 @@ export const useSizes = (size: Ref<number>, options: UseSizesOptions) => {
8793
minSizePixels,
8894
maxSizePercentage,
8995
snapPixels,
96+
collapsedSizePercentage,
9097
};
9198
};

packages/vue-split-panel/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export interface SplitPanelProps {
6161
/** How far to drag beyond the minSize to collapse/expand the primary panel */
6262
collapseThreshold?: number;
6363

64+
/**
65+
* How much of the panel content is visible when the panel is collapsed
66+
* @default 0
67+
*/
68+
collapsedSize?: number;
69+
6470
/**
6571
* How long should the collapse/expand state transition for in ms
6672
* @default 0

0 commit comments

Comments
 (0)