antoniandre / splitpanes

A Vue 3 (and 2) reliable, simple and touch-ready panes splitter / resizer.
https://antoniandre.github.io/splitpanes
MIT License
1.91k stars 169 forks source link

Adding more than one panel at once can cause incorrect/reversed panel order, resize behavior #202

Open Billiam opened 9 months ago

Billiam commented 9 months ago

When adding two or more panes at once, the internal order of panes can be reversed, leading to reversed splitter behavior (ex: drag left, splitter goes right), and a bunch of other weird issues.

When adding a new pane, Pane.onPaneAdd finds the position of the pane's dom element in the parent node.

This works when only one new panel is being added.

In my case, I have 3 panels, two of which are hidden when the page width is too small:

<Splitpanes>
  <Pane v-if="largePage"></Pane>
  <Pane v-if="largePage"></Pane>
  <Pane></Pane
</Splitpanes>

when largePage changes to true, both of the first two panes are added to the dom, and whenPaneAdd is fired twice by the Pane component's mount hook.

I don't think there's a guaranteed order for this, behavior, so the middle pane in my case fires first.

At this time, the this.panes array contains only one pane.

it finds the index correctly (1), but assumes that the rest of the pane array is otherwise complete, and inserts it into the panes array at position 1, with index 1, after the existing pane.

Then, whenPaneAdd fires for the left-most pane, and adds it at position 1

Now the pane orders are [0, 2, 1]. The indexes are then updated with:

this.panes.forEach((p, i) => (p.index = i))

And dragging the splitter between the second and third panes gives reversed behavior.

As a workaround, I'm doing the following:

Extending Pane to add the pane's ID to the dom element:

<template>
  <div class="splitpanes__pane" :data-uid="_.uid" @click="onPaneClick($event, _.uid)" :style="style">
    <slot/>
  </div>
</template>
<script>
import { Pane } from 'splitpanes'

export default {
  name: 'ModPane',
  extends: Pane
}
</script>

Extending Splitpanes to change the ordering behavior


<script>
import { Splitpanes } from 'splitpanes'

export default {
  name: 'ModSplitpanes',
  extends: Splitpanes,
  methods: {
    onPaneAdd (pane) {
      const paneEls = Array.from(pane.$el.parentNode.children).filter(el => {
        return el.className.includes('splitpanes__pane')
      })
// create a map of UIDs to index positions in the dom
      const indexMap = Object.fromEntries(paneEls.map((paneEl, index) => {
        return [paneEl.getAttribute('data-uid'), index]
      }))

      const index = indexMap[pane._.uid]
      const min = parseFloat(pane.minSize)
      const max = parseFloat(pane.maxSize)
// push instead of splice, since they'll be sorted
      this.panes.push({
        id: pane._.uid,
        index,
        min: isNaN(min) ? 0 : min,
        max: isNaN(max) ? 100 : max,
        size: pane.size === null ? null : parseFloat(pane.size),
        givenSize: pane.size,
        update: pane.update
      })

// Redo indexes after insertion for other shifted panes
      this.panes.forEach(p => (p.index = indexMap[p.id]))
// sort panes by index afterward
      this.panes.sort((a, b) => a.index - b.index)

      if (this.ready) {
        this.$nextTick(() => {
          // 2. Add the splitter.
          this.redoSplitters()

          // 3. Resize the panes.
          this.resetPaneSizes({ addedPane: this.panes[index] })

          // 4. Fire `pane-add` event.
          this.$emit('pane-add', { index, panes: this.panes.map(pane => ({ min: pane.min, max: pane.max, size: pane.size })) })
        })
      }
    }
}
</script>