diff --git a/docs/content/1.getting-started/2.usage.md b/docs/content/1.getting-started/2.usage.md index 4d109c8..d3b2704 100644 --- a/docs/content/1.getting-started/2.usage.md +++ b/docs/content/1.getting-started/2.usage.md @@ -97,6 +97,11 @@ Whether to allow the primary panel to be collapsed on enter key on divider or wh Defaults to `false` :: +::field{name="dragToToggle" type="boolean"} +Whether dragging beyond the collapse threshold should collapse or expand the primary panel. + Defaults to `true` +:: + ::field{name="collapseThreshold" type="number"} How far to drag beyond the minSize to collapse/expand the primary panel. :: @@ -209,7 +214,9 @@ By setting `sizeUnit` to `px`, the component will use pixel values for `minSize` By setting `collapsible` to `true`, you can allow the primary panel to be collapsed by either dragging the divider the `collapseThreshold` beyond the `minSize` or using Enter when focussed on the divider. -Both `minSize` and `collapsibleThreshold` have to be defined. +Set `drag-to-toggle` to `false` to disable collapsing or expanding when dragging beyond the collapse threshold. Enter-key toggling and programmatic collapse or expand still remain available. + +Both `minSize` and `collapseThreshold` have to be defined. ::code-preview :example-collapsible diff --git a/packages/vue-split-panel/src/SplitPanel.vue b/packages/vue-split-panel/src/SplitPanel.vue index 682f2fe..f344876 100644 --- a/packages/vue-split-panel/src/SplitPanel.vue +++ b/packages/vue-split-panel/src/SplitPanel.vue @@ -16,6 +16,7 @@ const props = withDefaults(defineProps(), { sizeUnit: "%", direction: "ltr", collapsible: false, + dragToToggle: true, collapsedSize: 0, transitionDuration: 0, transitionTimingFunctionCollapse: "cubic-bezier(0.4, 0, 0.6, 1)", @@ -69,6 +70,7 @@ const { handleKeydown } = useKeyboard(sizePercentage, collapsed, { const { isDragging, handleDblClick } = usePointer(collapsed, sizePercentage, sizePixels, { collapseThreshold: () => props.collapseThreshold, collapsible: () => props.collapsible, + dragToToggle: () => props.dragToToggle, direction: () => props.direction, disabled: () => props.disabled, orientation: () => props.orientation, diff --git a/packages/vue-split-panel/src/composables/use-pointer.test.ts b/packages/vue-split-panel/src/composables/use-pointer.test.ts index b9d8423..615ad36 100644 --- a/packages/vue-split-panel/src/composables/use-pointer.test.ts +++ b/packages/vue-split-panel/src/composables/use-pointer.test.ts @@ -44,6 +44,7 @@ describe("usePointer", () => { options = { disabled: ref(false), collapsible: ref(true), + dragToToggle: ref(true), primary: ref("start"), orientation: ref("horizontal"), direction: ref("ltr"), @@ -195,6 +196,39 @@ describe("usePointer", () => { expect(collapsed.value).toBe(false); }); + it("should not collapse when dragToToggle is false", async () => { + options.dragToToggle = ref(false); + usePointer(collapsed, sizePercentage, sizePixels, options); + + mockDragX.value = 30; // Below minSize (50) - collapseThreshold (10) = 40 + await nextTick(); + + expect(collapsed.value).toBe(false); + }); + + it("should not expand when dragToToggle is false", async () => { + collapsed.value = true; + options.dragToToggle = ref(false); + options.collapseThreshold = ref(15); + usePointer(collapsed, sizePercentage, sizePixels, options); + + mockDragX.value = 20; // Above collapseThreshold (15) + await nextTick(); + + expect(collapsed.value).toBe(true); + }); + + it("should still update sizePercentage when dragToToggle is false", async () => { + options.dragToToggle = ref(false); + usePointer(collapsed, sizePercentage, sizePixels, options); + + mockDragX.value = 100; + await nextTick(); + + expect(collapsed.value).toBe(false); + expect(sizePercentage.value).toBe(25); + }); + it("should snap to snap points within threshold", async () => { options.snapPixels = computed(() => [100, 200, 300]); options.snapThreshold = ref(8); @@ -205,6 +239,19 @@ describe("usePointer", () => { expect(sizePercentage.value).toBe(50); // 200/400 * 100 = 50% }); + it("should still snap to snap points when dragToToggle is false", async () => { + options.dragToToggle = ref(false); + options.snapPixels = computed(() => [100, 200, 300]); + options.snapThreshold = ref(8); + usePointer(collapsed, sizePercentage, sizePixels, options); + + mockDragX.value = 195; + await nextTick(); + + expect(collapsed.value).toBe(false); + expect(sizePercentage.value).toBe(50); + }); + it("should not snap when outside snap threshold", async () => { options.snapPixels = computed(() => [100, 200, 300]); options.snapThreshold = ref(5); diff --git a/packages/vue-split-panel/src/composables/use-pointer.ts b/packages/vue-split-panel/src/composables/use-pointer.ts index b0216fb..fb6d6e9 100644 --- a/packages/vue-split-panel/src/composables/use-pointer.ts +++ b/packages/vue-split-panel/src/composables/use-pointer.ts @@ -8,6 +8,7 @@ import { pixelsToPercentage } from "../utils/pixels-to-percentage"; export interface UsePointerOptions { disabled: MaybeRefOrGetter; collapsible: MaybeRefOrGetter; + dragToToggle: MaybeRefOrGetter; primary: MaybeRefOrGetter; orientation: MaybeRefOrGetter; direction: MaybeRefOrGetter; @@ -47,7 +48,11 @@ export const usePointer = ( newPositionInPixels = options.componentSize.value - newPositionInPixels; } - if (toValue(options.collapsible) && toValue(options.collapseThreshold) !== undefined) { + if ( + toValue(options.collapsible) && + toValue(options.dragToToggle) && + toValue(options.collapseThreshold) !== undefined + ) { let threshold: number; if (thresholdLocation === "collapse") diff --git a/packages/vue-split-panel/src/types.ts b/packages/vue-split-panel/src/types.ts index 841c8dd..308ecd6 100644 --- a/packages/vue-split-panel/src/types.ts +++ b/packages/vue-split-panel/src/types.ts @@ -58,6 +58,12 @@ export interface SplitPanelProps { */ collapsible?: boolean; + /** + * Whether dragging past the collapse threshold should collapse or expand the primary panel + * @default true + */ + dragToToggle?: boolean; + /** How far to drag beyond the minSize to collapse/expand the primary panel */ collapseThreshold?: number;