-
-
Notifications
You must be signed in to change notification settings - Fork 189
Generate and export types. #247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sumimakito
wants to merge
1
commit into
antoniandre:main
Choose a base branch
from
sumimakito:feat/types
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
+503
−23
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
I recently tried to modify this project with typescript and use them inline in my own project. here is the source code: import {ComputedRef, InjectionKey, Ref} from 'vue';
export type Pane = {
id: number
el: HTMLElement | null
min: number
max: number
givenSize?: number
size: number
index: number
}
export type SplitpanesProps = {
horizontal?: boolean
pushOtherPanes?: boolean
maximizePanes?: boolean
rtl?: boolean
firstSplitter?: boolean
}
export type EmitPaneData = {
max: number; min: number, size: number
}
export type SplitpanesEmits = {
(evt: 'ready'): void
(evt: 'resize', event: MouseEvent | TouchEvent, panes: EmitPaneData[]): void
(evt: 'resized', event: MouseEvent | TouchEvent | undefined, panes: EmitPaneData[], index?: number, prevPane?: Pane, nextPane?: Pane): void
(evt: 'pane-click', event: MouseEvent | TouchEvent, panes: EmitPaneData[], index: number, pane: Pane): void
(evt: 'pane-maximize', event: MouseEvent | TouchEvent, panes: EmitPaneData[], index: number, pane: Pane): void
(evt: 'pane-add', pane: Pane, panes: EmitPaneData[]): void
(evt: 'pane-remove', pane: Pane, panes: EmitPaneData[]): void
(evt: 'splitter-click', event: MouseEvent | TouchEvent, panes: EmitPaneData[], index: number, prevPane?: Pane, nextPane?: Pane): void
(evt: 'splitter-dblclick', event: MouseEvent | TouchEvent, panes: EmitPaneData[], index: number, prevPane?: Pane, nextPane?: Pane): void
}
export type DragInfo = {
x: number,
y: number,
}
export type RequestUpdatePayload = {
uid: number
size?: number
min?: number
max?: number
}
export type ChangedPanes = {
addedPane?: Pane,
removedPane?: Pane
}
export const injectKey = {
panes: Symbol() as InjectionKey<Ref<Pane[]>>,
indexedPanes: Symbol() as InjectionKey<ComputedRef<Record<number, Pane>>>,
horizontal: Symbol() as InjectionKey<ComputedRef<boolean>>,
requestUpdate: Symbol() as InjectionKey<(payload: RequestUpdatePayload) => void>,
onPaneAdd: Symbol() as InjectionKey<(pane: Omit<Pane, 'index'>) => void>,
onPaneRemove: Symbol() as InjectionKey<(uid: number) => void>,
onPaneClick: Symbol() as InjectionKey<(event: MouseEvent | TouchEvent, uid: number) => void>
}<script setup lang="ts">
import {h, ref, computed, onMounted, onBeforeUnmount, nextTick, provide, useSlots, watch} from 'vue'
import {
ChangedPanes,
DragInfo,
injectKey,
Pane,
RequestUpdatePayload, SplitpanesEmits,
SplitpanesProps
} from "./splitPaneTypes.ts";
const emits = defineEmits<
SplitpanesEmits
>()
const props = withDefaults(defineProps<
SplitpanesProps
>(), {
horizontal: false,
pushOtherPanes: true,
maximizePanes: true,
rtl: false,
firstSplitter: false
})
const slots = useSlots()
const panes = ref<Pane[]>([])
// Indexed panes by id (Vue's internal component uid) of Pane components for fast lookup.
// Every time a pane is destroyed this index is recomputed.
const indexedPanes = computed(() => panes.value.reduce((obj, pane) => (obj[pane.id] = pane) && obj, {} as {
[key: number]: Pane
}))
const panesCount = computed(() => panes.value.length)
const containerEl = ref<HTMLElement | null>(null)
const ready = ref(false)
const touch = ref<{
mouseDown: boolean,
dragging: boolean,
activeSplitter?: number,
cursorOffset: number
}>({
mouseDown: false,
dragging: false,
cursorOffset: 0 // Cursor offset within the splitter.
})
const splitterTaps = ref<{
splitter?: number,
timeoutId?: number
}>({ // Used to detect double click on touch devices.
})
const splitpanesClasses = computed(() => ({
[`splitpanes splitpanes--${props.horizontal ? 'horizontal' : 'vertical'}`]: true,
'splitpanes--dragging': touch.value.dragging
}))
const getPanesData = () => {
return panes.value.map(pane => ({
id: pane.id,
size: pane.size,
min: pane.min,
max: pane.max,
}))
}
const getEventExtraData = (dataIndex: number | undefined = undefined): {
index?: number,
prevPane?: Pane,
nextPane?: Pane
} => {
const index: number | undefined = dataIndex ?? touch.value.activeSplitter
if (index !== undefined) return {
index,
prevPane: panes.value[index - (props.firstSplitter ? 1 : 0)],
nextPane: panes.value[index + (props.firstSplitter ? 0 : 1)],
}
return {}
}
// Methods.
// --------------------------------------------------------
const bindEvents = () => {
document.addEventListener('mousemove', onMouseMove, {passive: false})
document.addEventListener('mouseup', onMouseUp)
// Passive: false to prevent scrolling while touch dragging.
if ('ontouchstart' in window) {
document.addEventListener('touchmove', onMouseMove, {passive: false})
document.addEventListener('touchend', onMouseUp)
}
}
const unbindEvents = () => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
if ('ontouchstart' in window) {
document.removeEventListener('touchmove', onMouseMove)
document.removeEventListener('touchend', onMouseUp)
}
}
const onMouseDown = (event: MouseEvent | TouchEvent, splitterIndex: number) => {
if (!(event.target instanceof HTMLElement)) return
// Store the cursor offset within the splitter to keep the cursor in the same position while dragging.
const splitterEl = event.target.closest('.splitpanes__splitter')
if (splitterEl) {
const {left, top} = splitterEl.getBoundingClientRect()
const {clientX, clientY} = (event instanceof TouchEvent) ? event.touches[0] : event
touch.value.cursorOffset = props.horizontal ? (clientY - top) : (clientX - left)
}
bindEvents()
touch.value.mouseDown = true
touch.value.activeSplitter = splitterIndex
}
const onMouseMove = (event: MouseEvent | TouchEvent) => {
if (touch.value.mouseDown) {
// Prevent scrolling while touch dragging (only works with an active event, eg. passive: false).
event.preventDefault()
touch.value.dragging = true
requestAnimationFrame(() => {
const dragInfo = getCurrentMouseDrag(event)
if (!dragInfo) return
calculatePanesSize(dragInfo)
emits('resize', event, getPanesData())
})
}
}
const onMouseUp = (event: MouseEvent | TouchEvent) => {
if (touch.value.dragging) {
window.getSelection()?.removeAllRanges()
const extraData = getEventExtraData()
emits('resized', event, getPanesData(), extraData.index, extraData.prevPane, extraData.nextPane)
}
touch.value.mouseDown = false
touch.value.activeSplitter = undefined
// Keep dragging flag until click event is finished (click happens immediately after mouseup)
// in order to prevent emitting `splitter-click` event if splitter was dragged.
setTimeout(() => {
touch.value.dragging = false
unbindEvents()
}, 100)
}
// If touch device, detect double tap manually (2 taps separated by less than 500ms).
const onSplitterClick = (event: MouseEvent | TouchEvent, splitterIndex: number) => {
if ('ontouchstart' in window) {
event.preventDefault()
// Detect splitter double taps.
if (splitterTaps.value.splitter === splitterIndex) {
clearTimeout(splitterTaps.value.timeoutId)
splitterTaps.value.timeoutId = undefined
onSplitterDblClick(event, splitterIndex)
splitterTaps.value.splitter = undefined // Reset for the next tap check.
} else {
splitterTaps.value.splitter = splitterIndex
// Store the fist tap and wait for the second one.
splitterTaps.value.timeoutId = setTimeout(() => (splitterTaps.value.splitter = undefined), 500)
}
}
if (!touch.value.dragging) {
const {prevPane, nextPane} = getEventExtraData(splitterIndex)
emits('splitter-click', event, getPanesData(), splitterIndex, prevPane, nextPane)
}
}
// On splitter dbl click or dbl tap maximize this pane.
const onSplitterDblClick = (event: MouseEvent | TouchEvent, splitterIndex: number) => {
const {prevPane, nextPane} = getEventExtraData(splitterIndex)
emits('splitter-dblclick', event, getPanesData(), splitterIndex, prevPane, nextPane)
if (props.maximizePanes) {
let totalMinSizes = 0
panes.value = panes.value.map((pane, i) => {
pane.size = i === splitterIndex ? pane.max : pane.min
if (i !== splitterIndex) totalMinSizes += pane.min
return pane
})
const pane = panes.value[splitterIndex]
if (!pane) return
pane.size -= totalMinSizes
emits('pane-maximize', event, getPanesData(), splitterIndex, pane)
emits('resized', event, getPanesData(), splitterIndex, prevPane, nextPane)
}
}
const onPaneClick = (event: MouseEvent | TouchEvent, paneId: number) => {
const pane = indexedPanes.value[paneId]
if (!pane) return
emits('pane-click', event, getPanesData(), paneId, pane)
}
// Get the cursor position relative to the splitpanes container.
const getCurrentMouseDrag = (event: MouseEvent | TouchEvent): DragInfo | undefined => {
if (!containerEl.value) return
const rect = containerEl.value.getBoundingClientRect()
const {clientX, clientY} = (event instanceof TouchEvent) ? event.touches[0] : event
return {
x: (clientX - (props.horizontal ? 0 : touch.value.cursorOffset)) - rect.left,
y: (clientY - (props.horizontal ? touch.value.cursorOffset : 0)) - rect.top
}
}
// Returns the drag percentage of the splitter relative to the container (ranging from 0 to 100%).
const getCurrentDragPercentage = (drag: DragInfo): number => {
if (!containerEl.value) return 0
let dragSize: number = drag[props.horizontal ? 'y' : 'x']
// In the code below 'size' refers to 'width' for vertical and 'height' for horizontal layout.
const containerSize = containerEl.value[props.horizontal ? 'clientHeight' : 'clientWidth']
if (props.rtl && !props.horizontal) dragSize = containerSize - dragSize
return dragSize * 100 / containerSize
}
const calculatePanesSize = (drag: DragInfo) => {
const splitterIndex = touch.value.activeSplitter
if (splitterIndex === undefined) return
let sums = {
prevPanesSize: sumPrevPanesSize(splitterIndex),
nextPanesSize: sumNextPanesSize(splitterIndex),
prevReachedMinPanes: 0,
nextReachedMinPanes: 0
}
const minDrag = 0 + (props.pushOtherPanes ? 0 : sums.prevPanesSize)
const maxDrag = 100 - (props.pushOtherPanes ? 0 : sums.nextPanesSize)
const dragPercentage = Math.max(Math.min(getCurrentDragPercentage(drag), maxDrag), minDrag)
// If not pushing other panes, panes to resize are right before and right after splitter.
let panesToResize = [splitterIndex, splitterIndex + 1]
let paneBefore = panes.value[panesToResize[0]] || null
let paneAfter = panes.value[panesToResize[1]] || null
const paneBeforeMaxReached = paneBefore.max < 100 && (dragPercentage >= (paneBefore.max + sums.prevPanesSize))
const paneAfterMaxReached = paneAfter.max < 100 && (dragPercentage <= 100 - (paneAfter.max + sumNextPanesSize(splitterIndex + 1)))
// Prevent dragging beyond pane max.
if (paneBeforeMaxReached || paneAfterMaxReached) {
if (paneBeforeMaxReached) {
paneBefore.size = paneBefore.max
paneAfter.size = Math.max(100 - paneBefore.max - sums.prevPanesSize - sums.nextPanesSize, 0)
} else {
paneBefore.size = Math.max(100 - paneAfter.max - sums.prevPanesSize - sumNextPanesSize(splitterIndex + 1), 0)
paneAfter.size = paneAfter.max
}
return
}
// When pushOtherPanes = true, find the closest expanded pane on each side of the splitter.
if (props.pushOtherPanes) {
const vars = doPushOtherPanes(sums, dragPercentage)
if (!vars) return // Prevent other calculation.
({sums, panesToResize} = vars)
paneBefore = panes.value[panesToResize[0]] || null
paneAfter = panes.value[panesToResize[1]] || null
}
if (paneBefore !== null) {
paneBefore.size = Math.min(Math.max(dragPercentage - sums.prevPanesSize - sums.prevReachedMinPanes, paneBefore.min), paneBefore.max)
}
if (paneAfter !== null) {
paneAfter.size = Math.min(Math.max(100 - dragPercentage - sums.nextPanesSize - sums.nextReachedMinPanes, paneAfter.min), paneAfter.max)
}
}
const doPushOtherPanes = (
sums: {
prevPanesSize: number,
nextPanesSize: number,
prevReachedMinPanes: number,
nextReachedMinPanes: number
},
dragPercentage: number
) => {
const splitterIndex = touch.value.activeSplitter
if (splitterIndex === undefined) return
const panesToResize = [splitterIndex, splitterIndex + 1]
// Pushing Down.
// Going smaller than the current pane min size: take the previous expanded pane.
if (dragPercentage < sums.prevPanesSize + panes.value[panesToResize[0]].min) {
panesToResize[0] = findPrevExpandedPane(splitterIndex)!.index
sums.prevReachedMinPanes = 0
// If pushing a n-2 or less pane, from splitter, then make sure all in between is at min size.
if (panesToResize[0] < splitterIndex) {
panes.value.forEach((pane, i) => {
if (i > panesToResize[0] && i <= splitterIndex) {
pane.size = pane.min
sums.prevReachedMinPanes += pane.min
}
})
}
sums.prevPanesSize = sumPrevPanesSize(panesToResize[0])
// If nothing else to push down, cancel dragging.
if (panesToResize[0] === undefined) {
sums.prevReachedMinPanes = 0
panes.value[0].size = panes.value[0].min
panes.value.forEach((pane, i) => {
if (i > 0 && i <= splitterIndex) {
pane.size = pane.min
sums.prevReachedMinPanes += pane.min
}
})
panes.value[panesToResize[1]].size = 100 - sums.prevReachedMinPanes - panes.value[0].min - sums.prevPanesSize - sums.nextPanesSize
return null
}
}
// Pushing Up.
// Pushing up beyond min size is reached: take the next expanded pane.
if (dragPercentage > 100 - sums.nextPanesSize - panes.value[panesToResize[1]].min) {
panesToResize[1] = findNextExpandedPane(splitterIndex)!.index
sums.nextReachedMinPanes = 0
// If pushing a n+2 or more pane, from splitter, then make sure all in between is at min size.
if (panesToResize[1] > splitterIndex + 1) {
panes.value.forEach((pane, i) => {
if (i > splitterIndex && i < panesToResize[1]) {
pane.size = pane.min
sums.nextReachedMinPanes += pane.min
}
})
}
sums.nextPanesSize = sumNextPanesSize(panesToResize[1] - 1)
// If nothing else to push up, cancel dragging.
if (panesToResize[1] === undefined) {
sums.nextReachedMinPanes = 0
panes.value.forEach((pane, i) => {
// If pushing a n+2 or more pane, from splitter, then make sure all in between is at min size.
if (i < panesCount.value - 1 && i >= splitterIndex + 1) {
pane.size = pane.min
sums.nextReachedMinPanes += pane.min
}
})
panes.value[panesToResize[0]].size = 100 - sums.prevPanesSize - sumNextPanesSize(panesToResize[0] - 1)
return null
}
}
return {sums, panesToResize}
}
const sumPrevPanesSize = (splitterIndex: number) => {
return panes.value.reduce((total, pane, i) => total + (i < splitterIndex ? pane.size : 0), 0)
}
const sumNextPanesSize = (splitterIndex: number) => {
return panes.value.reduce((total, pane, i) => total + (i > splitterIndex + 1 ? pane.size : 0), 0)
}
// Return the previous pane from siblings which has a size (width for vert or height for horz) of more than 0.
const findPrevExpandedPane = (splitterIndex: number): Pane | undefined => {
return [...panes.value].reverse().find(p => (p.index < splitterIndex && p.size > p.min))
}
// Return the next pane from siblings which has a size (width for vert or height for horz) of more than 0.
const findNextExpandedPane = (splitterIndex: number): Pane | undefined => {
return panes.value.find(p => (p.index > splitterIndex + 1 && p.size > p.min))
}
const checkSplitpanesNodes = () => {
const children = Array.from(containerEl.value?.children || [])
for (const child of children) {
const isPane = child.classList.contains('splitpanes__pane')
const isSplitter = child.classList.contains('splitpanes__splitter')
// Node is not a Pane or a splitter: remove it.
if (!isPane && !isSplitter) {
child.remove()
console.warn('Splitpanes: Only <Pane> elements are allowed at the root of <Splitpanes>. One of your DOM nodes was removed.')
}
}
}
const addSplitter = (paneIndex: number, nextPaneNode: HTMLElement, isVeryFirst = false) => {
const splitterIndex = paneIndex - 1
const elm = document.createElement('div')
elm.classList.add('splitpanes__splitter')
if (!isVeryFirst) {
elm.onmousedown = event => onMouseDown(event, splitterIndex)
if (typeof window !== 'undefined' && 'ontouchstart' in window) {
elm.ontouchstart = event => onMouseDown(event, splitterIndex)
}
elm.onclick = event => onSplitterClick(event, splitterIndex + 1)
}
elm.ondblclick = event => onSplitterDblClick(event, splitterIndex + 1)
nextPaneNode.parentNode?.insertBefore(elm, nextPaneNode)
}
const removeSplitter = (node: HTMLElement) => {
node.onmousedown = null
node.onclick = null
node.ondblclick = null
node.remove()
}
const redoSplitters = () => {
const children = Array.from(containerEl.value?.children ?? [])
for (const el of children) {
if (el.className.includes('splitpanes__splitter') && el instanceof HTMLElement) {
removeSplitter(el)
}
}
let paneIndex = 0
for (const el of children) {
if (el.className.includes('splitpanes__pane') && el instanceof HTMLElement) {
if (!paneIndex && props.firstSplitter) addSplitter(paneIndex, el, true)
else if (paneIndex) addSplitter(paneIndex, el)
paneIndex++
}
}
}
// Called by Pane component on programmatic resize.
const requestUpdate = ({uid, ...args}: RequestUpdatePayload) => {
const pane = indexedPanes.value[uid]
for (const [key, value] of Object.entries(args)) {
// @ts-ignore
pane[key] = value
}
}
const onPaneAdd = (paneData: Omit<Pane, 'index'>) => {
// 1. Add pane to array at the same index it was inserted in the <splitpanes> tag.
let index = -1
Array.from(containerEl.value?.children || []).some(el => {
if (el.className.includes('splitpanes__pane')) index++
return el.isSameNode(paneData.el)
})
const pane = {...paneData, index}
panes.value.splice(index, 0, pane)
// Redo indexes after insertion for other shifted panes.
panes.value.forEach((p, i) => (p.index = i))
if (ready.value) {
nextTick(() => {
// 2. Add the splitter.
redoSplitters()
// 3. Resize the panes (before pane-add so it will contain correct width).
resetPaneSizes({addedPane: panes.value[index]})
// 4. Fire `pane-add` event.
emits('pane-add', pane, getPanesData())
})
}
}
const onPaneRemove = (uid: number) => {
// 1. Remove the pane from array and redo indexes.
const index = panes.value.findIndex(p => p.id === uid)
panes.value[index].el = null // Prevent memory leaks.
const removed = panes.value.splice(index, 1)[0]
panes.value.forEach((p, i) => (p.index = i)) // Redo indexes after removal.
nextTick(() => {
// 2. Remove the splitter.
redoSplitters()
// 3. Fire `pane-remove` event.
emits('pane-remove', removed, getPanesData())
// 4. Resize the panes.
resetPaneSizes({removedPane: {...removed, index}})
})
}
const resetPaneSizes = (changedPanes: ChangedPanes = {}) => {
if (!changedPanes.addedPane && !changedPanes.removedPane) initialPanesSizing()
else if (panes.value.some(pane => pane.givenSize !== undefined || pane.min || pane.max < 100)) equalizeAfterAddOrRemove(changedPanes)
else equalize()
if (ready.value) emits('resized', undefined, getPanesData())
}
const equalize = () => {
const equalSpace = 100 / panesCount.value
let leftToAllocate = 0
const ungrowable = []
const unshrinkable = []
for (const pane of panes.value) {
pane.size = Math.max(Math.min(equalSpace, pane.max), pane.min)
leftToAllocate -= pane.size
if (pane.size >= pane.max) ungrowable.push(pane.id)
if (pane.size <= pane.min) unshrinkable.push(pane.id)
}
if (leftToAllocate > 0.1) readjustSizes(leftToAllocate, ungrowable, unshrinkable)
}
const initialPanesSizing = () => {
let leftToAllocate = 100
const ungrowable = []
const unshrinkable = []
let definedSizes = 0
// Check if pre-allocated space is 100%.
for (const pane of panes.value) {
leftToAllocate -= pane.size
if (pane.givenSize !== undefined) definedSizes++
if (pane.size >= pane.max) ungrowable.push(pane.id)
if (pane.size <= pane.min) unshrinkable.push(pane.id)
}
// Set pane sizes if not set.
let leftToAllocate2 = 100
if (leftToAllocate > 0.1) {
for (const pane of panes.value) {
if (pane.givenSize === undefined) {
pane.size = Math.max(Math.min(leftToAllocate / (panesCount.value - definedSizes), pane.max), pane.min)
}
leftToAllocate2 -= pane.size
}
if (leftToAllocate2 > 0.1) readjustSizes(leftToAllocate2, ungrowable, unshrinkable)
}
}
const equalizeAfterAddOrRemove = ({addedPane}: ChangedPanes = {}) => {
let equalSpace = 100 / panesCount.value
let leftToAllocate = 0
const ungrowable = []
const unshrinkable = []
if (addedPane?.givenSize !== undefined) {
equalSpace = (100 - addedPane.givenSize) / (panesCount.value - 1)
}
// Check if pre-allocated space is 100%.
for (const pane of panes.value) {
leftToAllocate -= pane.size
if (pane.size >= pane.max) ungrowable.push(pane.id)
if (pane.size <= pane.min) unshrinkable.push(pane.id)
}
if (Math.abs(leftToAllocate) < 0.1) return // Ok.
for (const pane of panes.value) {
const addedPaneHasGivenSize = addedPane?.givenSize !== null && addedPane?.id === pane.id
if (!addedPaneHasGivenSize) pane.size = Math.max(Math.min(equalSpace, pane.max), pane.min)
leftToAllocate -= pane.size
if (pane.size >= pane.max) ungrowable.push(pane.id)
if (pane.size <= pane.min) unshrinkable.push(pane.id)
}
if (leftToAllocate > 0.1) readjustSizes(leftToAllocate, ungrowable, unshrinkable)
}
/* const recalculatePaneSizes = ({ addedPane, removedPane } = {}) => {
let leftToAllocate = 100
let equalSpaceToAllocate = leftToAllocate / panesCount.value
let ungrowable = []
let unshrinkable = []
// When adding a pane with no size, apply min-size if defined otherwise divide another pane
// (next or prev) in 2.
// if (addedPane && addedPane.size === null) {
// if (addedPane.min) addedPane.size = addedPane.min
// else {
// const paneToDivide = panes.value[addedPane.index + 1] || panes.value[addedPane.index - 1]
// if (paneToDivide) {
// // @todo: Dividing that pane in 2 could be incorrect if becoming lower than its min size.
// addedPane.size = paneToDivide.size / 2
// paneToDivide.size /= 2
// }
// }
// }
panes.value.forEach((pane, i) => {
// Added pane - reduce the size of the next pane.
if (addedPane && addedPane.index + 1 === i) {
pane.size = Math.max(Math.min(100 - sumPrevPanesSize(i) - sumNextPanesSize(i + 1), pane.max), pane.min)
// @todo: if could not allocate correctly, try to allocate in the next pane straight away,
// then still do the second loop if not correct.
}
// Removed pane - increase the size of the next pane.
else if (removedPane && removedPane.index === i) {
pane.size = Math.max(Math.min(100 - sumPrevPanesSize(i) - sumNextPanesSize(i + 1), pane.max), pane.min)
// @todo: if could not allocate correctly, try to allocate in the next pane straight away,
// then still do the second loop if not correct.
}
// Initial load and on demand recalculation.
else if (!addedPane && !removedPane && pane.size === null) {
pane.size = Math.max(Math.min(equalSpaceToAllocate, pane.max), pane.min)
}
leftToAllocate -= pane.size
if (pane.size >= pane.max) ungrowable.push(pane.id)
if (pane.size <= pane.min) unshrinkable.push(pane.id)
})
// Do one more loop to adjust sizes if still wrong.
// > 0.1: Prevent maths rounding issues due to bytes.
if (Math.abs(leftToAllocate) > 0.1) readjustSizes(leftToAllocate, ungrowable, unshrinkable)
} */
// Second loop to adjust sizes now that we know more about the panes constraints.
const readjustSizes = (leftToAllocate: number, ungrowable: number[], unshrinkable: number[]) => {
let equalSpaceToAllocate
if (leftToAllocate > 0) equalSpaceToAllocate = leftToAllocate / (panesCount.value - ungrowable.length)
else equalSpaceToAllocate = leftToAllocate / (panesCount.value - unshrinkable.length)
panes.value.forEach((pane) => {
if (leftToAllocate > 0 && !ungrowable.includes(pane.id)) {
// Need to diff the size before and after to get the exact allocated space.
const newPaneSize = Math.max(Math.min(pane.size + equalSpaceToAllocate, pane.max), pane.min)
const allocated = newPaneSize - pane.size
leftToAllocate -= allocated
pane.size = newPaneSize
} else if (!unshrinkable.includes(pane.id)) {
// Need to diff the size before and after to get the exact allocated space.
const newPaneSize = Math.max(Math.min(pane.size + equalSpaceToAllocate, pane.max), pane.min)
const allocated = newPaneSize - pane.size
leftToAllocate -= allocated
pane.size = newPaneSize
}
})
if (Math.abs(leftToAllocate) > 0.1) { // > 0.1: Prevent maths rounding issues due to bytes.
// Don't emit on hot reload when Vue destroys panes.
nextTick(() => {
if (ready.value) {
console.warn('Splitpanes: Could not resize panes correctly due to their constraints.')
}
})
}
}
/* const distributeEmptySpace = () => {
let growablePanes = []
let collapsedPanesCount.value = 0
let growableAmount = 0 // Total of how much the current panes can grow to fill blank space.
let spaceToDistribute = 100 - panes.value.reduce((sum, pane) => (sum += pane.size) && sum, 0)
// Do a first loop to determine if we can distribute the new blank space between all the
// expandedPanes, without expanding the collapsed ones.
panes.value.forEach(pane => {
if (pane.size < pane.max) growablePanes.push(pane)
if (!pane.size) collapsedPanesCount.value++
else growableAmount += pane.max - pane.size
})
// If the blank space to distribute is too great for the expanded panes, also expand collapsed ones.
let expandCollapsedPanes = growableAmount < spaceToDistribute
// New space to distribute equally.
let growablePanesCount.value = (growablePanes.length - (expandCollapsedPanes ? 0 : collapsedPanesCount.value))
let equalSpaceToDistribute = spaceToDistribute / growablePanesCount.value
// if (growablePanesCount.value === 1) equalSpace = 100 / panesCount.value
let spaceLeftToDistribute = spaceToDistribute
// Now add the equalSpaceToDistribute to each pane size accordingly.
growablePanes.forEach(pane => {
if (pane.size < pane.max && (pane.size || (!pane.size && expandCollapsedPanes))) {
const newSize = Math.min(pane.size + equalSpaceToDistribute, pane.max)
let allocatedSpace = (newSize - pane.size)
spaceLeftToDistribute -= allocatedSpace
pane.size = newSize
// If the equalSpaceToDistribute is not fully added to the current pane, distribute the remainder
// to the next panes.
// Also fix decimal issue due to bites - E.g. calculating 8.33 and getting 8.3299999999999
if (equalSpaceToDistribute - allocatedSpace > 0.1) equalSpaceToDistribute = spaceLeftToDistribute / (--growablePanesCount.value)
}
})
/* Disabled otherwise will show up on hot reload.
// if there is still space to allocate show warning message.
if (panesCount.value && ~~spaceLeftToDistribute) {
// eslint-disable-next-line no-console
console.warn('Splitpanes: Could not distribute all the empty space between panes due to their constraints.')
} *\/
emitEvent('resized', { index: touch.value.activeSplitter }, true)
} */
// Watchers.
// --------------------------------------------------------
watch(() => props.firstSplitter, () => redoSplitters())
onMounted(() => {
checkSplitpanesNodes()
redoSplitters()
resetPaneSizes()
ready.value = true
emits('ready')
})
// Prevent emitting console warnings on hot reloading.
onBeforeUnmount(() => (ready.value = false))
const render = () => {
return h(
'div',
{ref: containerEl, class: splitpanesClasses.value},
slots.default?.()
)
}
provide(injectKey.panes, panes)
provide(injectKey.indexedPanes, indexedPanes)
provide(injectKey.horizontal, computed(() => props.horizontal))
provide(injectKey.requestUpdate, requestUpdate)
provide(injectKey.onPaneAdd, onPaneAdd)
provide(injectKey.onPaneRemove, onPaneRemove)
provide(injectKey.onPaneClick, onPaneClick)
</script>
<template>
<component :is="render"></component>
</template>
<style>
.splitpanes {
display: flex;
width: 100%;
height: 100%;
}
.splitpanes--vertical {
flex-direction: row;
}
.splitpanes--horizontal {
flex-direction: column;
}
.splitpanes--dragging .splitpanes__pane,
*:has(.splitpanes--dragging) {
user-select: none;
pointer-events: none;
}
.splitpanes__splitter {
touch-action: none;
}
.splitpanes--vertical > .splitpanes__splitter {
min-width: 1px;
cursor: col-resize;
}
.splitpanes--horizontal > .splitpanes__splitter {
min-height: 1px;
cursor: row-resize;
}
splitpanes__pane {
width: 100%;
height: 100%;
overflow: hidden;
}
.splitpanes--vertical .splitpanes__pane {
will-change: width;
}
.splitpanes--horizontal .splitpanes__pane {
will-change: height;
}
.splitpanes--dragging .splitpanes__pane {
transition: none;
}
</style><template>
<div
ref="paneEl"
class="splitpanes__pane"
@click="onPaneClick($event, uid)"
:style="styles">
<slot/>
</div>
</template>
<script setup lang="ts">
import {inject, computed, onMounted, onBeforeUnmount, watch, getCurrentInstance, useTemplateRef} from 'vue'
import {injectKey} from "./splitPaneTypes.ts";
const props = withDefaults(defineProps<{
size?: number | string
minSize?: number | string
maxSize?: number | string
}>(), {
minSize: 0,
maxSize: 100
})
const requestUpdate = inject(injectKey.requestUpdate)!
const onPaneAdd = inject(injectKey.onPaneAdd)!
const horizontal = inject(injectKey.horizontal)!
const onPaneRemove = inject(injectKey.onPaneRemove)!
const onPaneClick = inject(injectKey.onPaneClick)!
const uid = getCurrentInstance()!.uid
const indexedPanes = inject(injectKey.indexedPanes)!
const pane = computed(() => indexedPanes.value[uid])
const stringToNumber = (value: string | number) => {
return typeof value === 'string' ? parseFloat(value) : value
}
const paneEl = useTemplateRef("paneEl")
const sizeNumber = computed(() => {
let value = props.size === undefined ? 0 : stringToNumber(props.size)
value = isNaN(value) ? 0 : value
return Math.max(Math.min(value, maxSizeNumber.value), minSizeNumber.value)
})
const minSizeNumber = computed(() => {
const value = stringToNumber(props.minSize)
return isNaN(value) ? 0 : value
})
const maxSizeNumber = computed(() => {
const value = stringToNumber(props.maxSize)
return isNaN(value) ? 100 : value
})
const styles = computed(() => `${horizontal.value ? 'height' : 'width'}: ${pane.value?.size}%`)
watch(() => sizeNumber.value, size => requestUpdate({uid, size}))
watch(() => minSizeNumber.value, min => requestUpdate({uid, min}))
watch(() => maxSizeNumber.value, max => requestUpdate({uid, max}))
onMounted(() => {
onPaneAdd({
id: uid,
el: paneEl.value,
min: minSizeNumber.value,
max: maxSizeNumber.value,
// The given size (useful to know the user intention).
givenSize: props.size === undefined ? undefined : sizeNumber.value,
size: sizeNumber.value // The computed current size at any time.
})
})
onBeforeUnmount(() => {
onPaneRemove(uid)
})
</script> |
|
This seems really useful, can this be merged @antoniandre ? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This pull request tries to generate, export, and bundle types with the package, because
@types/splitpaneswas last updated on 22 Nov 2023 at2.2.6, which is relatively out-of-date.attw result
Still, after the current change,
attwreports some warnings onnode16, but it should start working correctly for projects setmoduleResolutiontonode10andbundler.