Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/content/1.getting-started/2.usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ import { SplitPanel } from '@directus/vue-split-panel';
How far to drag beyond the minSize to collapse/expand the primary panel.
::

::field{name="collapsedSize" type="number"}
How much of the collapsed panel is visible in its collapsed state.
::

::field{name="transitionDuration" type="number"}
Duration of the collapse/expand transition in ms.
Defaults to `0`
Expand Down
1 change: 1 addition & 0 deletions packages/vue-split-panel/playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const collapsed = ref(false);
v-model:collapsed="collapsed"
collapsible
:collapse-threshold="100"
:collapsed-size="50"
:min-size="250"
:max-size="400"
size-unit="px"
Expand Down
6 changes: 5 additions & 1 deletion packages/vue-split-panel/src/SplitPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const props = withDefaults(defineProps<SplitPanelProps>(), {
sizeUnit: '%',
direction: 'ltr',
collapsible: false,
collapsedSize: 0,
transitionDuration: 0,
transitionTimingFunctionCollapse: 'cubic-bezier(0.4, 0, 0.6, 1)',
transitionTimingFunctionExpand: 'cubic-bezier(0, 0, 0.2, 1)',
Expand All @@ -41,6 +42,7 @@ const {
componentSize,
dividerSize,
snapPixels,
collapsedSizePercentage,
} = useSizes(size, {
disabled: () => props.disabled,
collapsible: () => props.collapsible,
Expand All @@ -50,6 +52,7 @@ const {
minSize: () => props.minSize,
maxSize: () => props.maxSize,
snapPoints: () => props.snapPoints,
collapsedSize: () => props.collapsedSize,
panelEl,
dividerEl,
});
Expand Down Expand Up @@ -87,6 +90,7 @@ const { gridTemplate } = useGridTemplate({
orientation: () => props.orientation,
primary: () => props.primary,
sizePercentage,
collapsedSizePercentage,
});

useResize(sizePercentage, {
Expand All @@ -105,7 +109,7 @@ const {
} = useCollapse(
collapsed,
sizePercentage,
{ transitionDuration: () => props.transitionDuration },
{ transitionDuration: () => props.transitionDuration, collapsedSize: () => props.collapsedSize },
);

defineExpose({ collapse, expand, toggle });
Expand Down
80 changes: 63 additions & 17 deletions packages/vue-split-panel/src/composables/use-collapse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('useCollapse', () => {
it('should return expected methods and properties', () => {
const collapsed = ref(false);
const sizePercentage = ref(50);
const options = { transitionDuration: 300 };
const options = { transitionDuration: 300, collapsedSize: 0 };

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

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

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

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

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

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

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

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

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

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

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

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

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

useCollapse(collapsed, sizePercentage, options);

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

useCollapse(collapsed, sizePercentage, options);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

useCollapse(collapsed, sizePercentage, options);

Expand All @@ -290,5 +290,51 @@ describe('useCollapse', () => {
await nextTick();
expect(sizePercentage.value).toBe(0);
});

it('should use custom collapsedSize value', async () => {
const collapsed = ref(false);
const sizePercentage = ref(60);
const options = { transitionDuration: 300, collapsedSize: 10 };

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

// Collapse to custom size
collapsed.value = true;
await nextTick();
expect(sizePercentage.value).toBe(10);
expect(collapseTransitionState.value).toBe('collapsing');

// Expand should restore original size
collapsed.value = false;
await nextTick();
expect(sizePercentage.value).toBe(60);
expect(collapseTransitionState.value).toBe('expanding');
});

it('should support reactive collapsedSize value', async () => {
const collapsed = ref(false);
const sizePercentage = ref(70);
const collapsedSize = ref(5);
const options = { transitionDuration: 300, collapsedSize };

useCollapse(collapsed, sizePercentage, options);

// Collapse with initial collapsedSize
collapsed.value = true;
await nextTick();
expect(sizePercentage.value).toBe(5);

// Change collapsedSize while collapsed
collapsedSize.value = 15;

// Expand and collapse again with new collapsedSize
collapsed.value = false;
await nextTick();
expect(sizePercentage.value).toBe(70);

collapsed.value = true;
await nextTick();
expect(sizePercentage.value).toBe(15);
});
});
});
3 changes: 2 additions & 1 deletion packages/vue-split-panel/src/composables/use-collapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { computed, toValue, watch } from 'vue';

export interface UseCollapseOptions {
transitionDuration: MaybeRefOrGetter<number>;
collapsedSize: MaybeRefOrGetter<number>;
}

export const useCollapse = (collapsed: Ref<boolean>, sizePercentage: Ref<number>, options: UseCollapseOptions) => {
Expand All @@ -16,7 +17,7 @@ export const useCollapse = (collapsed: Ref<boolean>, sizePercentage: Ref<number>
watch(collapsed, (newCollapsed) => {
if (newCollapsed === true) {
expandedSizePercentage = sizePercentage.value;
sizePercentage.value = 0;
sizePercentage.value = toValue(options.collapsedSize);
collapseTransitionState.value = 'collapsing';
}
else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('useGridTemplate', () => {
maxSizePercentage: computed(() => {}) as ComputedRef<number | undefined>,
sizePercentage: computed(() => 50),
dividerSize: computed(() => 4),
collapsedSizePercentage: computed(() => 0),
primary: 'start',
direction: 'ltr',
orientation: 'horizontal',
Expand All @@ -21,7 +22,28 @@ describe('useGridTemplate', () => {
const options = createOptions({ collapsed: ref(true) });
const { gridTemplate } = useGridTemplate(options);

expect(gridTemplate.value).toBe('0 4px auto');
expect(gridTemplate.value).toBe('0% 4px auto');
});

it('uses custom collapsedSizePercentage when collapsed', () => {
const options = createOptions({
collapsed: ref(true),
collapsedSizePercentage: computed(() => 10),
});
const { gridTemplate } = useGridTemplate(options);

expect(gridTemplate.value).toBe('10% 4px auto');
});

it('uses custom collapsedSizePercentage with end primary', () => {
const options = createOptions({
collapsed: ref(true),
collapsedSizePercentage: computed(() => 15),
primary: 'end',
});
const { gridTemplate } = useGridTemplate(options);

expect(gridTemplate.value).toBe('auto 4px 15%');
});

it('returns basic clamp template when no min/max constraints', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ export interface UseGridTemplateOptions {
primary: MaybeRefOrGetter<Primary | undefined>;
direction: MaybeRefOrGetter<Direction>;
orientation: MaybeRefOrGetter<Orientation>;
collapsedSizePercentage: ComputedRef<number>;
}

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

if (options.collapsed.value) {
primary = '0';
primary = `${options.collapsedSizePercentage.value}%`;
}
else if (options.minSizePercentage.value !== undefined && options.maxSizePercentage.value !== undefined) {
primary = `clamp(0%, clamp(${options.minSizePercentage.value}%, ${options.sizePercentage.value}%, ${options.maxSizePercentage.value}%), calc(100% - ${options.dividerSize.value}px))`;
Expand Down
1 change: 1 addition & 0 deletions packages/vue-split-panel/src/composables/use-sizes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('useSizes', () => {
snapPoints: [25, 50, 75],
panelEl: mockPanelEl,
dividerEl: mockDividerEl,
collapsedSize: 0,
};
});

Expand Down
7 changes: 7 additions & 0 deletions packages/vue-split-panel/src/composables/use-sizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface UseSizesOptions {
snapPoints: MaybeRefOrGetter<number[]>;
panelEl: MaybeComputedElementRef;
dividerEl: MaybeComputedElementRef;
collapsedSize: MaybeRefOrGetter<number>;
}

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

const collapsedSizePercentage = computed(() => {
if (toValue(options.sizeUnit) === '%') return toValue(options.collapsedSize);
return pixelsToPercentage(componentSize.value, toValue(options.collapsedSize)!);
});

const snapPixels = computed(() => {
if (toValue(options.sizeUnit) === 'px') return toValue(options.snapPoints);
return toValue(options.snapPoints).map((snapPercentage) => percentageToPixels(componentSize.value, snapPercentage));
Expand All @@ -87,5 +93,6 @@ export const useSizes = (size: Ref<number>, options: UseSizesOptions) => {
minSizePixels,
maxSizePercentage,
snapPixels,
collapsedSizePercentage,
};
};
6 changes: 6 additions & 0 deletions packages/vue-split-panel/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ export interface SplitPanelProps {
/** How far to drag beyond the minSize to collapse/expand the primary panel */
collapseThreshold?: number;

/**
* How much of the panel content is visible when the panel is collapsed
* @default 0
*/
collapsedSize?: number;

/**
* How long should the collapse/expand state transition for in ms
* @default 0
Expand Down