diff --git a/.changeset/splitter-collapse-intent.md b/.changeset/splitter-collapse-intent.md
new file mode 100644
index 000000000..bd9735a8b
--- /dev/null
+++ b/.changeset/splitter-collapse-intent.md
@@ -0,0 +1,5 @@
+---
+"@vuetify/v0": minor
+---
+
+feat(Splitter): defer drag collapse/expand to pointer release with a pending intent — dragging a collapsible panel past its `minSize` no longer collapses instantly. While dragging, the panel now pins at its `minSize` (or `collapsedSize` when opening a collapsed panel) and arms a pending intent; the collapse/expand only commits on release, and dragging back out cancels it. `SplitterHandle` exposes the armed state through a `pending` slot prop (`'collapse' | 'expand' | null`) and a matching `data-pending` attribute so consumers can render a "release to hide/open" affordance. Keyboard and programmatic resize keep their existing instant behavior.
diff --git a/apps/playground/src/components/playground/app/PlaygroundSplitterHandle.vue b/apps/playground/src/components/playground/app/PlaygroundSplitterHandle.vue
index d4e293217..6cbd65fc4 100644
--- a/apps/playground/src/components/playground/app/PlaygroundSplitterHandle.vue
+++ b/apps/playground/src/components/playground/app/PlaygroundSplitterHandle.vue
@@ -53,23 +53,25 @@
-
+
+
+
+ {{ pending === 'collapse' ? 'Release to hide' : 'Release to open' }}
+
diff --git a/packages/0/src/components/Splitter/SplitterHandle.vue b/packages/0/src/components/Splitter/SplitterHandle.vue
index 69894cbc6..d61df9bff 100644
--- a/packages/0/src/components/Splitter/SplitterHandle.vue
+++ b/packages/0/src/components/Splitter/SplitterHandle.vue
@@ -29,7 +29,7 @@
// Types
import type { AtomProps } from '#v0/components/Atom'
- import type { SplitterOrientation } from './SplitterRoot.vue'
+ import type { SplitterIntentMode, SplitterOrientation } from './SplitterRoot.vue'
export interface SplitterHandleProps extends AtomProps {
disabled?: boolean
@@ -42,6 +42,7 @@
isDragging: boolean
isDisabled: boolean
state: SplitterHandleState
+ pending: SplitterIntentMode | null
attrs: {
'role': 'separator'
'tabindex': 0 | -1
@@ -55,6 +56,7 @@
'data-state': SplitterHandleState
'data-orientation': SplitterOrientation
'data-disabled': true | undefined
+ 'data-pending': SplitterIntentMode | undefined
'style': Record
'onPointerdown': (e: PointerEvent) => void
'onPointerenter': () => void
@@ -103,6 +105,15 @@
return 'inactive'
})
+ // Armed collapse/expand intent for either panel adjacent to this handle (null until past threshold)
+ const pending = toRef((): SplitterIntentMode | null => {
+ const intent = splitter.pending.value
+ if (!intent) return null
+ const before = splitter.panel(ticket.index)
+ const after = splitter.panel(ticket.index + 1)
+ return intent.id === before?.id || intent.id === after?.id ? intent.mode : null
+ })
+
// aria-valuenow: size of the panel before this handle (0-100), rounded for AT
/* v8 ignore next -- defensive: the handle's index always points to a registered panel */
const valuenow = toRef(() => Math.round(splitter.panel(ticket.index)?.size ?? 0))
@@ -260,6 +271,7 @@
isDragging: splitter.draggingHandle.value === ticket.index,
isDisabled: isDisabled.value,
state: state.value,
+ pending: pending.value,
attrs: {
'role': 'separator',
'tabindex': isDisabled.value ? -1 : 0,
@@ -273,6 +285,7 @@
'data-state': state.value,
'data-orientation': splitter.orientation.value,
'data-disabled': isDisabled.value || undefined,
+ 'data-pending': pending.value ?? undefined,
'style': { 'touch-action': 'none' },
'onPointerdown': onPointerDown,
'onPointerenter': onPointerEnter,
diff --git a/packages/0/src/components/Splitter/SplitterRoot.vue b/packages/0/src/components/Splitter/SplitterRoot.vue
index 1059e8d6d..7730a038a 100644
--- a/packages/0/src/components/Splitter/SplitterRoot.vue
+++ b/packages/0/src/components/Splitter/SplitterRoot.vue
@@ -34,10 +34,18 @@
import type { AtomExpose, AtomProps } from '#v0/components/Atom'
import type { RegistryContext } from '#v0/composables/createRegistry'
import type { SelectionContext, SelectionTicket, SelectionTicketInput } from '#v0/composables/createSelection'
+ import type { ID } from '#v0/types'
import type { Ref } from 'vue'
export type SplitterOrientation = 'horizontal' | 'vertical'
+ export type SplitterIntentMode = 'collapse' | 'expand'
+
+ export interface SplitterPendingIntent {
+ id: ID
+ mode: SplitterIntentMode
+ }
+
export interface SplitterPanelInput extends SelectionTicketInput {
size: number
minSize: number
@@ -56,6 +64,7 @@
handles: RegistryContext
dragging: Readonly[>
draggingHandle: Readonly][>
+ pending: Readonly][>
rootEl: Readonly][>
panel: (index: number) => SplitterPanelTicket | undefined
resize: (index: number, delta: number, options?: { emit?: boolean }) => void
@@ -116,9 +125,28 @@
const rootEl = toRef(() => toElement(rootAtom.value?.element) ?? null)
const draggingHandle = shallowRef(null)
const dragging = toRef(() => !isNull(draggingHandle.value))
- const expandAccum = new Map()
+
+ // Keyboard / programmatic resize keeps the legacy instant collapse + accumulate-to-expand behavior.
+ const expandAccum = new Map()
const EXPAND_THRESHOLD = 10
+ // Pointer drag instead arms an intent at the min/collapsed boundary and only commits on release,
+ // so the panel resists going smaller and the user can back out before letting go.
+ const intentAccum = new Map()
+ const pending = shallowRef(null)
+ const INTENT_THRESHOLD = 5
+
+ function arm (id: ID, mode: SplitterIntentMode, delta: number) {
+ const accum = (intentAccum.get(id) ?? 0) + Math.abs(delta)
+ intentAccum.set(id, accum)
+ if (accum >= INTENT_THRESHOLD) pending.value = { id, mode }
+ }
+
+ function disarm (id: ID) {
+ intentAccum.delete(id)
+ if (pending.value?.id === id) pending.value = null
+ }
+
const panels = createSelection({
multiple: true,
enroll: true,
@@ -173,40 +201,68 @@
let size = clamp(before.size + delta, lower, upper)
- // Collapse snap: dragging a collapsible panel below minSize snaps to collapsedSize
const beforeCollapsed = !toValue(before.isSelected)
+ const drag = dragging.value
+
+ // Collapse boundary for the leading panel. While dragging, pin at minSize and arm an intent
+ // instead of collapsing outright; keyboard/programmatic resize keeps the legacy instant snap.
if (before.collapsible && !beforeCollapsed && size <= before.minSize && delta < 0) {
- size = before.collapsedSize
- before.unselect()
- expandAccum.set(before.id, 0)
- } else if (before.collapsible && beforeCollapsed && delta > 0) {
- const accum = (expandAccum.get(before.id) ?? 0) + delta
- expandAccum.set(before.id, accum)
- if (accum >= EXPAND_THRESHOLD) {
- size = clamp(accum, before.collapsedSize, before.maxSize)
- before.select()
- expandAccum.delete(before.id)
+ if (drag) {
+ size = before.minSize
+ arm(before.id, 'collapse', delta)
} else {
size = before.collapsedSize
+ before.unselect()
+ expandAccum.set(before.id, 0)
+ }
+ } else if (before.collapsible && beforeCollapsed && delta > 0) {
+ // Expand boundary for the leading panel. While dragging, hold at collapsedSize and arm intent.
+ if (drag) {
+ size = before.collapsedSize
+ arm(before.id, 'expand', delta)
+ } else {
+ const accum = (expandAccum.get(before.id) ?? 0) + delta
+ expandAccum.set(before.id, accum)
+ if (accum >= EXPAND_THRESHOLD) {
+ size = clamp(accum, before.collapsedSize, before.maxSize)
+ before.select()
+ expandAccum.delete(before.id)
+ } else {
+ size = before.collapsedSize
+ }
}
+ } else if (drag) {
+ disarm(before.id)
}
const afterSize = total - size
const afterCollapsed = !toValue(after.isSelected)
if (after.collapsible && !afterCollapsed && afterSize <= after.minSize && delta > 0) {
- size = total - after.collapsedSize
- after.unselect()
- expandAccum.set(after.id, 0)
- } else if (after.collapsible && afterCollapsed && delta < 0) {
- const accum = (expandAccum.get(after.id) ?? 0) + Math.abs(delta)
- expandAccum.set(after.id, accum)
- if (accum >= EXPAND_THRESHOLD) {
- size = total - clamp(accum, after.collapsedSize, after.maxSize)
- after.select()
- expandAccum.delete(after.id)
+ if (drag) {
+ size = total - after.minSize
+ arm(after.id, 'collapse', delta)
} else {
size = total - after.collapsedSize
+ after.unselect()
+ expandAccum.set(after.id, 0)
+ }
+ } else if (after.collapsible && afterCollapsed && delta < 0) {
+ if (drag) {
+ size = total - after.collapsedSize
+ arm(after.id, 'expand', delta)
+ } else {
+ const accum = (expandAccum.get(after.id) ?? 0) + Math.abs(delta)
+ expandAccum.set(after.id, accum)
+ if (accum >= EXPAND_THRESHOLD) {
+ size = total - clamp(accum, after.collapsedSize, after.maxSize)
+ after.select()
+ expandAccum.delete(after.id)
+ } else {
+ size = total - after.collapsedSize
+ }
}
+ } else if (drag) {
+ disarm(after.id)
}
before.size = size
@@ -323,8 +379,24 @@
function onEndDrag () {
if (isNull(draggingHandle.value)) return
+
+ const handleIndex = draggingHandle.value
+ const intent = pending.value
draggingHandle.value = null
+ pending.value = null
expandAccum.clear()
+ intentAccum.clear()
+
+ if (intent) {
+ const ticket = panels.get(intent.id)
+ if (ticket) {
+ const neighborIndex = ticket.index === handleIndex ? handleIndex + 1 : handleIndex
+ if (intent.mode === 'collapse') collapse(ticket.index, neighborIndex)
+ else expand(ticket.index, neighborIndex)
+ return
+ }
+ }
+
emitLayout()
}
@@ -339,6 +411,7 @@
handles,
dragging,
draggingHandle,
+ pending,
rootEl,
panel,
resize,
diff --git a/packages/0/src/components/Splitter/index.test.ts b/packages/0/src/components/Splitter/index.test.ts
index 987410f33..6842e8041 100644
--- a/packages/0/src/components/Splitter/index.test.ts
+++ b/packages/0/src/components/Splitter/index.test.ts
@@ -1491,7 +1491,7 @@ describe('splitter', () => {
})
describe('drag expand from collapsed', () => {
- it('should expand a collapsed panel when dragged past threshold', async () => {
+ it('should hold a collapsed panel closed during drag and expand on release', async () => {
const onLayout = vi.fn()
const wrapper = twoPanel({
onLayout,
@@ -1527,7 +1527,7 @@ describe('splitter', () => {
}))
await nextTick()
- // Drag past EXPAND_THRESHOLD (10%) — move 120px = 12%
+ // Drag out 12% — past INTENT_THRESHOLD, arms an expand intent
document.dispatchEvent(new PointerEvent('pointermove', {
clientX: 120,
clientY: 50,
@@ -1536,12 +1536,20 @@ describe('splitter', () => {
await new Promise(resolve => requestAnimationFrame(resolve))
await nextTick()
- // Panel should be expanded and track cursor (12%), not snap to minSize (15%)
+ // Still collapsed and pinned at collapsedSize while dragging — only intent is armed
+ expect(panels[0].vm.isCollapsed).toBe(true)
+ expect(panels[0].vm.size).toBe(0)
+ expect(handle.attributes('data-pending')).toBe('expand')
+
+ // Release commits the expand
+ document.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }))
+ await nextTick()
+
expect(panels[0].vm.isCollapsed).toBe(false)
- expect(panels[0].vm.size).toBe(12)
+ expect(panels[0].vm.size).toBeGreaterThan(0)
})
- it('should allow panel to stay below minSize during drag', async () => {
+ it('should not expand when released before the intent threshold', async () => {
const wrapper = twoPanel({
panels: [
{ defaultSize: 50, collapsible: true, collapsedSize: 0, minSize: 15 },
@@ -1550,7 +1558,6 @@ describe('splitter', () => {
})
await nextTick()
- // Collapse, then expand via drag just past threshold
const panels = wrapper.findAllComponents(SplitterPanel as any)
panels[0].vm.collapse()
await nextTick()
@@ -1571,28 +1578,279 @@ describe('splitter', () => {
}))
await nextTick()
- // Drag to 11% — just past threshold, below minSize (15%)
+ // Drag out only 3% — below INTENT_THRESHOLD (5%), never arms
+ document.dispatchEvent(new PointerEvent('pointermove', {
+ clientX: 30,
+ clientY: 50,
+ bubbles: true,
+ }))
+ await new Promise(resolve => requestAnimationFrame(resolve))
+ await nextTick()
+
+ expect(handle.attributes('data-pending')).toBeUndefined()
+
+ document.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }))
+ await nextTick()
+
+ // Stays collapsed
+ expect(panels[0].vm.isCollapsed).toBe(true)
+ expect(panels[0].vm.size).toBe(0)
+ })
+
+ it('should hold a collapsed trailing panel closed during drag and expand on release', async () => {
+ const wrapper = twoPanel({
+ panels: [
+ { defaultSize: 50 },
+ { defaultSize: 50, collapsible: true, collapsedSize: 0, minSize: 15 },
+ ],
+ })
+ await nextTick()
+
+ // Collapse the trailing panel
+ const panels = wrapper.findAllComponents(SplitterPanel as any)
+ panels[1].vm.collapse()
+ await nextTick()
+ expect(panels[1].vm.isCollapsed).toBe(true)
+ expect(panels[1].vm.size).toBe(0)
+
+ const handle = wrapper.findComponent(SplitterHandle as any)
+ const handleEl = handle.element as HTMLElement
+ handleEl.setPointerCapture = vi.fn()
+
+ const rootEl = wrapper.element as HTMLElement
+ Object.defineProperty(rootEl, 'offsetWidth', { value: 1000, configurable: true })
+
+ // Start drag at the collapsed boundary (100%)
+ handleEl.dispatchEvent(new PointerEvent('pointerdown', {
+ button: 0,
+ clientX: 1000,
+ clientY: 50,
+ bubbles: true,
+ pointerId: 1,
+ }))
+ await nextTick()
+
+ // Drag out 12% — past INTENT_THRESHOLD, arms an expand intent for the trailing panel
+ document.dispatchEvent(new PointerEvent('pointermove', {
+ clientX: 880,
+ clientY: 50,
+ bubbles: true,
+ }))
+ await new Promise(resolve => requestAnimationFrame(resolve))
+ await nextTick()
+
+ // Still collapsed and pinned at collapsedSize while dragging — only intent is armed
+ expect(panels[1].vm.isCollapsed).toBe(true)
+ expect(panels[1].vm.size).toBe(0)
+ expect(handle.attributes('data-pending')).toBe('expand')
+
+ // Release commits the expand
+ document.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }))
+ await nextTick()
+
+ expect(panels[1].vm.isCollapsed).toBe(false)
+ expect(panels[1].vm.size).toBeGreaterThan(0)
+ })
+ })
+
+ describe('drag collapse intent', () => {
+ it('should pin at minSize while dragging and collapse on release', async () => {
+ const wrapper = twoPanel({
+ panels: [
+ { defaultSize: 50, collapsible: true, collapsedSize: 0, minSize: 15 },
+ { defaultSize: 50 },
+ ],
+ })
+ await nextTick()
+
+ const panels = wrapper.findAllComponents(SplitterPanel as any)
+ const handle = wrapper.findComponent(SplitterHandle as any)
+ const handleEl = handle.element as HTMLElement
+ handleEl.setPointerCapture = vi.fn()
+
+ const rootEl = wrapper.element as HTMLElement
+ Object.defineProperty(rootEl, 'offsetWidth', { value: 1000, configurable: true })
+
+ // Start drag at the 50% boundary
+ handleEl.dispatchEvent(new PointerEvent('pointerdown', {
+ button: 0,
+ clientX: 500,
+ clientY: 50,
+ bubbles: true,
+ pointerId: 1,
+ }))
+ await nextTick()
+
+ // Drag well below minSize (toward 5%)
document.dispatchEvent(new PointerEvent('pointermove', {
- clientX: 110,
+ clientX: 50,
clientY: 50,
bubbles: true,
}))
await new Promise(resolve => requestAnimationFrame(resolve))
await nextTick()
+ // Pinned at minSize, still expanded, intent armed
expect(panels[0].vm.isCollapsed).toBe(false)
- expect(panels[0].vm.size).toBe(11)
+ expect(panels[0].vm.size).toBe(15)
+ expect(handle.attributes('data-pending')).toBe('collapse')
+
+ // Release commits the collapse
+ document.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }))
+ await nextTick()
+
+ expect(panels[0].vm.isCollapsed).toBe(true)
+ expect(panels[0].vm.size).toBe(0)
+ })
+
+ it('should cancel the collapse when dragged back out before release', async () => {
+ const wrapper = twoPanel({
+ panels: [
+ { defaultSize: 50, collapsible: true, collapsedSize: 0, minSize: 15 },
+ { defaultSize: 50 },
+ ],
+ })
+ await nextTick()
+
+ const panels = wrapper.findAllComponents(SplitterPanel as any)
+ const handle = wrapper.findComponent(SplitterHandle as any)
+ const handleEl = handle.element as HTMLElement
+ handleEl.setPointerCapture = vi.fn()
- // Continue drag to 13% — still below minSize, should track cursor
+ const rootEl = wrapper.element as HTMLElement
+ Object.defineProperty(rootEl, 'offsetWidth', { value: 1000, configurable: true })
+
+ handleEl.dispatchEvent(new PointerEvent('pointerdown', {
+ button: 0,
+ clientX: 500,
+ clientY: 50,
+ bubbles: true,
+ pointerId: 1,
+ }))
+ await nextTick()
+
+ // Drag below minSize — arms collapse intent
+ document.dispatchEvent(new PointerEvent('pointermove', {
+ clientX: 50,
+ clientY: 50,
+ bubbles: true,
+ }))
+ await new Promise(resolve => requestAnimationFrame(resolve))
+ await nextTick()
+ expect(handle.attributes('data-pending')).toBe('collapse')
+
+ // Drag back out above minSize — disarms
+ document.dispatchEvent(new PointerEvent('pointermove', {
+ clientX: 350,
+ clientY: 50,
+ bubbles: true,
+ }))
+ await new Promise(resolve => requestAnimationFrame(resolve))
+ await nextTick()
+ expect(handle.attributes('data-pending')).toBeUndefined()
+
+ document.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }))
+ await nextTick()
+
+ // Stays expanded — never collapsed
+ expect(panels[0].vm.isCollapsed).toBe(false)
+ expect(panels[0].vm.size).toBeGreaterThanOrEqual(15)
+ })
+
+ it('should not collapse when released without arming the intent', async () => {
+ const wrapper = twoPanel({
+ panels: [
+ { defaultSize: 16, collapsible: true, collapsedSize: 0, minSize: 15 },
+ { defaultSize: 84 },
+ ],
+ })
+ await nextTick()
+
+ const panels = wrapper.findAllComponents(SplitterPanel as any)
+ const handle = wrapper.findComponent(SplitterHandle as any)
+ const handleEl = handle.element as HTMLElement
+ handleEl.setPointerCapture = vi.fn()
+
+ const rootEl = wrapper.element as HTMLElement
+ Object.defineProperty(rootEl, 'offsetWidth', { value: 1000, configurable: true })
+
+ // Start at the 16% boundary
+ handleEl.dispatchEvent(new PointerEvent('pointerdown', {
+ button: 0,
+ clientX: 160,
+ clientY: 50,
+ bubbles: true,
+ pointerId: 1,
+ }))
+ await nextTick()
+
+ // Nudge ~1% below min — pins at min but accumulates under INTENT_THRESHOLD
document.dispatchEvent(new PointerEvent('pointermove', {
- clientX: 130,
+ clientX: 148,
clientY: 50,
bubbles: true,
}))
await new Promise(resolve => requestAnimationFrame(resolve))
await nextTick()
- expect(panels[0].vm.size).toBe(13)
+ expect(panels[0].vm.size).toBe(15)
+ expect(handle.attributes('data-pending')).toBeUndefined()
+
+ document.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }))
+ await nextTick()
+
+ // Released without intent — pinned at min, not collapsed
+ expect(panels[0].vm.isCollapsed).toBe(false)
+ expect(panels[0].vm.size).toBe(15)
+ })
+
+ it('should pin the trailing panel at minSize while dragging and collapse on release', async () => {
+ const wrapper = twoPanel({
+ panels: [
+ { defaultSize: 50 },
+ { defaultSize: 50, collapsible: true, collapsedSize: 0, minSize: 15 },
+ ],
+ })
+ await nextTick()
+
+ const panels = wrapper.findAllComponents(SplitterPanel as any)
+ const handle = wrapper.findComponent(SplitterHandle as any)
+ const handleEl = handle.element as HTMLElement
+ handleEl.setPointerCapture = vi.fn()
+
+ const rootEl = wrapper.element as HTMLElement
+ Object.defineProperty(rootEl, 'offsetWidth', { value: 1000, configurable: true })
+
+ // Start drag at the 50% boundary
+ handleEl.dispatchEvent(new PointerEvent('pointerdown', {
+ button: 0,
+ clientX: 500,
+ clientY: 50,
+ bubbles: true,
+ pointerId: 1,
+ }))
+ await nextTick()
+
+ // Drag well past the trailing panel's minSize (toward 95%)
+ document.dispatchEvent(new PointerEvent('pointermove', {
+ clientX: 950,
+ clientY: 50,
+ bubbles: true,
+ }))
+ await new Promise(resolve => requestAnimationFrame(resolve))
+ await nextTick()
+
+ // Trailing panel pinned at minSize, still expanded, intent armed
+ expect(panels[1].vm.isCollapsed).toBe(false)
+ expect(panels[1].vm.size).toBe(15)
+ expect(handle.attributes('data-pending')).toBe('collapse')
+
+ // Release commits the collapse
+ document.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }))
+ await nextTick()
+
+ expect(panels[1].vm.isCollapsed).toBe(true)
+ expect(panels[1].vm.size).toBe(0)
})
})
@@ -1855,5 +2113,27 @@ describe('splitter', () => {
expect(panels[1]!.vm.isCollapsed).toBe(true)
expect(panels[1]!.vm.size).toBe(5)
})
+
+ it('should snap the leading panel to collapsedSize when ArrowLeft drives it below minSize', async () => {
+ const wrapper = twoPanel({
+ panels: [
+ // Left panel is collapsible — driving its size below minSize triggers the instant snap
+ { defaultSize: 50, collapsible: true, collapsedSize: 0, minSize: 20 },
+ { defaultSize: 50, minSize: 0, maxSize: 100 },
+ ],
+ })
+ await nextTick()
+
+ // ArrowLeft = -1 delta on the leading panel; keyboard resize keeps the instant collapse
+ const handle = wrapper.findComponent(SplitterHandle as any)
+ for (let i = 0; i < 35; i++) {
+ await handle.trigger('keydown', { key: 'ArrowLeft' })
+ }
+ await nextTick()
+
+ const panels = wrapper.findAllComponents(SplitterPanel as any)
+ expect(panels[0]!.vm.isCollapsed).toBe(true)
+ expect(panels[0]!.vm.size).toBe(0)
+ })
})
})
diff --git a/packages/0/src/components/Splitter/index.ts b/packages/0/src/components/Splitter/index.ts
index c32025507..96f91631f 100644
--- a/packages/0/src/components/Splitter/index.ts
+++ b/packages/0/src/components/Splitter/index.ts
@@ -3,7 +3,7 @@ export { default as SplitterRoot } from './SplitterRoot.vue'
export { default as SplitterPanel } from './SplitterPanel.vue'
export { default as SplitterHandle } from './SplitterHandle.vue'
-export type { SplitterContext, SplitterOrientation, SplitterPanelInput, SplitterPanelTicket, SplitterRootExpose, SplitterRootProps, SplitterRootSlotProps } from './SplitterRoot.vue'
+export type { SplitterContext, SplitterIntentMode, SplitterOrientation, SplitterPanelInput, SplitterPanelTicket, SplitterPendingIntent, SplitterRootExpose, SplitterRootProps, SplitterRootSlotProps } from './SplitterRoot.vue'
export type { SplitterPanelExpose, SplitterPanelProps, SplitterPanelSlotProps } from './SplitterPanel.vue'
export type { SplitterHandleProps, SplitterHandleSlotProps, SplitterHandleState } from './SplitterHandle.vue'
]