mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			760 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			760 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // The programming goals of Split.js are to deliver readable, understandable and
 | |
| // maintainable code, while at the same time manually optimizing for tiny minified file size,
 | |
| // browser compatibility without additional requirements, graceful fallback (IE8 is supported)
 | |
| // and very few assumptions about the user's page layout.
 | |
| const global = window
 | |
| const { document } = global
 | |
| 
 | |
| // Save a couple long function names that are used frequently.
 | |
| // This optimization saves around 400 bytes.
 | |
| const addEventListener = 'addEventListener'
 | |
| const removeEventListener = 'removeEventListener'
 | |
| const getBoundingClientRect = 'getBoundingClientRect'
 | |
| const gutterStartDragging = '_a'
 | |
| const aGutterSize = '_b'
 | |
| const bGutterSize = '_c'
 | |
| const HORIZONTAL = 'horizontal'
 | |
| const NOOP = () => false
 | |
| 
 | |
| // Figure out if we're in IE8 or not. IE8 will still render correctly,
 | |
| // but will be static instead of draggable.
 | |
| const isIE8 = global.attachEvent && !global[addEventListener]
 | |
| 
 | |
| // Helper function determines which prefixes of CSS calc we need.
 | |
| // We only need to do this once on startup, when this anonymous function is called.
 | |
| //
 | |
| // Tests -webkit, -moz and -o prefixes. Modified from StackOverflow:
 | |
| // http://stackoverflow.com/questions/16625140/js-feature-detection-to-detect-the-usage-of-webkit-calc-over-calc/16625167#16625167
 | |
| const calc = `${['', '-webkit-', '-moz-', '-o-']
 | |
|     .filter(prefix => {
 | |
|         const el = document.createElement('div')
 | |
|         el.style.cssText = `width:${prefix}calc(9px)`
 | |
| 
 | |
|         return !!el.style.length
 | |
|     })
 | |
|     .shift()}calc`
 | |
| 
 | |
| // Helper function checks if its argument is a string-like type
 | |
| const isString = v => typeof v === 'string' || v instanceof String
 | |
| 
 | |
| // Helper function allows elements and string selectors to be used
 | |
| // interchangeably. In either case an element is returned. This allows us to
 | |
| // do `Split([elem1, elem2])` as well as `Split(['#id1', '#id2'])`.
 | |
| const elementOrSelector = el => {
 | |
|     if (isString(el)) {
 | |
|         const ele = document.querySelector(el)
 | |
|         if (!ele) {
 | |
|             throw new Error(`Selector ${el} did not match a DOM element`)
 | |
|         }
 | |
|         return ele
 | |
|     }
 | |
| 
 | |
|     return el
 | |
| }
 | |
| 
 | |
| // Helper function gets a property from the properties object, with a default fallback
 | |
| const getOption = (options, propName, def) => {
 | |
|     const value = options[propName]
 | |
|     if (value !== undefined) {
 | |
|         return value
 | |
|     }
 | |
|     return def
 | |
| }
 | |
| 
 | |
| const getGutterSize = (gutterSize, isFirst, isLast, gutterAlign) => {
 | |
|     if (isFirst) {
 | |
|         if (gutterAlign === 'end') {
 | |
|             return 0
 | |
|         }
 | |
|         if (gutterAlign === 'center') {
 | |
|             return gutterSize / 2
 | |
|         }
 | |
|     } else if (isLast) {
 | |
|         if (gutterAlign === 'start') {
 | |
|             return 0
 | |
|         }
 | |
|         if (gutterAlign === 'center') {
 | |
|             return gutterSize / 2
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return gutterSize
 | |
| }
 | |
| 
 | |
| // Default options
 | |
| const defaultGutterFn = (i, gutterDirection) => {
 | |
|     const gut = document.createElement('div')
 | |
|     gut.className = `gutter gutter-${gutterDirection}`
 | |
|     return gut
 | |
| }
 | |
| 
 | |
| const defaultElementStyleFn = (dim, size, gutSize) => {
 | |
|     const style = {}
 | |
| 
 | |
|     if (!isString(size)) {
 | |
|         if (!isIE8) {
 | |
|             style[dim] = `${calc}(${size}% - ${gutSize}px)`
 | |
|         } else {
 | |
|             style[dim] = `${size}%`
 | |
|         }
 | |
|     } else {
 | |
|         style[dim] = size
 | |
|     }
 | |
| 
 | |
|     return style
 | |
| }
 | |
| 
 | |
| const defaultGutterStyleFn = (dim, gutSize) => ({ [dim]: `${gutSize}px` })
 | |
| 
 | |
| // The main function to initialize a split. Split.js thinks about each pair
 | |
| // of elements as an independant pair. Dragging the gutter between two elements
 | |
| // only changes the dimensions of elements in that pair. This is key to understanding
 | |
| // how the following functions operate, since each function is bound to a pair.
 | |
| //
 | |
| // A pair object is shaped like this:
 | |
| //
 | |
| // {
 | |
| //     a: DOM element,
 | |
| //     b: DOM element,
 | |
| //     aMin: Number,
 | |
| //     bMin: Number,
 | |
| //     dragging: Boolean,
 | |
| //     parent: DOM element,
 | |
| //     direction: 'horizontal' | 'vertical'
 | |
| // }
 | |
| //
 | |
| // The basic sequence:
 | |
| //
 | |
| // 1. Set defaults to something sane. `options` doesn't have to be passed at all.
 | |
| // 2. Initialize a bunch of strings based on the direction we're splitting.
 | |
| //    A lot of the behavior in the rest of the library is paramatized down to
 | |
| //    rely on CSS strings and classes.
 | |
| // 3. Define the dragging helper functions, and a few helpers to go with them.
 | |
| // 4. Loop through the elements while pairing them off. Every pair gets an
 | |
| //    `pair` object and a gutter.
 | |
| // 5. Actually size the pair elements, insert gutters and attach event listeners.
 | |
| const Split = (idsOption, options = {}) => {
 | |
|     let ids = idsOption
 | |
|     let dimension
 | |
|     let clientAxis
 | |
|     let position
 | |
|     let positionEnd
 | |
|     let clientSize
 | |
|     let elements
 | |
| 
 | |
|     // Allow HTMLCollection to be used as an argument when supported
 | |
|     if (Array.from) {
 | |
|         ids = Array.from(ids)
 | |
|     }
 | |
| 
 | |
|     // All DOM elements in the split should have a common parent. We can grab
 | |
|     // the first elements parent and hope users read the docs because the
 | |
|     // behavior will be whacky otherwise.
 | |
|     const firstElement = elementOrSelector(ids[0])
 | |
|     const parent = firstElement.parentNode
 | |
|     const parentFlexDirection = getComputedStyle
 | |
|         ? getComputedStyle(parent).flexDirection
 | |
|         : null
 | |
| 
 | |
|     // Set default options.sizes to equal percentages of the parent element.
 | |
|     let sizes = getOption(options, 'sizes') || ids.map(() => 100 / ids.length)
 | |
| 
 | |
|     // Standardize minSize to an array if it isn't already. This allows minSize
 | |
|     // to be passed as a number.
 | |
|     const minSize = getOption(options, 'minSize', 100)
 | |
|     const minSizes = Array.isArray(minSize) ? minSize : ids.map(() => minSize)
 | |
| 
 | |
|     // Get other options
 | |
|     const expandToMin = getOption(options, 'expandToMin', false)
 | |
|     const gutterSize = getOption(options, 'gutterSize', 10)
 | |
|     const gutterAlign = getOption(options, 'gutterAlign', 'center')
 | |
|     const snapOffset = getOption(options, 'snapOffset', 30)
 | |
|     const dragInterval = getOption(options, 'dragInterval', 1)
 | |
|     const direction = getOption(options, 'direction', HORIZONTAL)
 | |
|     const cursor = getOption(
 | |
|         options,
 | |
|         'cursor',
 | |
|         direction === HORIZONTAL ? 'col-resize' : 'row-resize',
 | |
|     )
 | |
|     const gutter = getOption(options, 'gutter', defaultGutterFn)
 | |
|     const elementStyle = getOption(
 | |
|         options,
 | |
|         'elementStyle',
 | |
|         defaultElementStyleFn,
 | |
|     )
 | |
|     const gutterStyle = getOption(options, 'gutterStyle', defaultGutterStyleFn)
 | |
| 
 | |
|     // 2. Initialize a bunch of strings based on the direction we're splitting.
 | |
|     // A lot of the behavior in the rest of the library is paramatized down to
 | |
|     // rely on CSS strings and classes.
 | |
|     if (direction === HORIZONTAL) {
 | |
|         dimension = 'width'
 | |
|         clientAxis = 'clientX'
 | |
|         position = 'left'
 | |
|         positionEnd = 'right'
 | |
|         clientSize = 'clientWidth'
 | |
|     } else if (direction === 'vertical') {
 | |
|         dimension = 'height'
 | |
|         clientAxis = 'clientY'
 | |
|         position = 'top'
 | |
|         positionEnd = 'bottom'
 | |
|         clientSize = 'clientHeight'
 | |
|     }
 | |
| 
 | |
|     // 3. Define the dragging helper functions, and a few helpers to go with them.
 | |
|     // Each helper is bound to a pair object that contains its metadata. This
 | |
|     // also makes it easy to store references to listeners that that will be
 | |
|     // added and removed.
 | |
|     //
 | |
|     // Even though there are no other functions contained in them, aliasing
 | |
|     // this to self saves 50 bytes or so since it's used so frequently.
 | |
|     //
 | |
|     // The pair object saves metadata like dragging state, position and
 | |
|     // event listener references.
 | |
| 
 | |
|     function setElementSize(el, size, gutSize, i) {
 | |
|         // Split.js allows setting sizes via numbers (ideally), or if you must,
 | |
|         // by string, like '300px'. This is less than ideal, because it breaks
 | |
|         // the fluid layout that `calc(% - px)` provides. You're on your own if you do that,
 | |
|         // make sure you calculate the gutter size by hand.
 | |
|         const style = elementStyle(dimension, size, gutSize, i)
 | |
| 
 | |
|         Object.keys(style).forEach(prop => {
 | |
|             // eslint-disable-next-line no-param-reassign
 | |
|             el.style[prop] = style[prop]
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     function setGutterSize(gutterElement, gutSize, i) {
 | |
|         const style = gutterStyle(dimension, gutSize, i)
 | |
| 
 | |
|         Object.keys(style).forEach(prop => {
 | |
|             // eslint-disable-next-line no-param-reassign
 | |
|             gutterElement.style[prop] = style[prop]
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     function getSizes() {
 | |
|         return elements.map(element => element.size)
 | |
|     }
 | |
| 
 | |
|     // Supports touch events, but not multitouch, so only the first
 | |
|     // finger `touches[0]` is counted.
 | |
|     function getMousePosition(e) {
 | |
|         if ('touches' in e) return e.touches[0][clientAxis]
 | |
|         return e[clientAxis]
 | |
|     }
 | |
| 
 | |
|     // Actually adjust the size of elements `a` and `b` to `offset` while dragging.
 | |
|     // calc is used to allow calc(percentage + gutterpx) on the whole split instance,
 | |
|     // which allows the viewport to be resized without additional logic.
 | |
|     // Element a's size is the same as offset. b's size is total size - a size.
 | |
|     // Both sizes are calculated from the initial parent percentage,
 | |
|     // then the gutter size is subtracted.
 | |
|     function adjust(offset) {
 | |
|         const a = elements[this.a]
 | |
|         const b = elements[this.b]
 | |
|         const percentage = a.size + b.size
 | |
| 
 | |
|         a.size = (offset / this.size) * percentage
 | |
|         b.size = percentage - (offset / this.size) * percentage
 | |
| 
 | |
|         setElementSize(a.element, a.size, this[aGutterSize], a.i)
 | |
|         setElementSize(b.element, b.size, this[bGutterSize], b.i)
 | |
|     }
 | |
| 
 | |
|     // drag, where all the magic happens. The logic is really quite simple:
 | |
|     //
 | |
|     // 1. Ignore if the pair is not dragging.
 | |
|     // 2. Get the offset of the event.
 | |
|     // 3. Snap offset to min if within snappable range (within min + snapOffset).
 | |
|     // 4. Actually adjust each element in the pair to offset.
 | |
|     //
 | |
|     // ---------------------------------------------------------------------
 | |
|     // |    | <- a.minSize               ||              b.minSize -> |    |
 | |
|     // |    |  | <- this.snapOffset      ||     this.snapOffset -> |  |    |
 | |
|     // |    |  |                         ||                        |  |    |
 | |
|     // |    |  |                         ||                        |  |    |
 | |
|     // ---------------------------------------------------------------------
 | |
|     // | <- this.start                                        this.size -> |
 | |
|     function drag(e) {
 | |
|         let offset
 | |
|         const a = elements[this.a]
 | |
|         const b = elements[this.b]
 | |
| 
 | |
|         if (!this.dragging) return
 | |
| 
 | |
|         // Get the offset of the event from the first side of the
 | |
|         // pair `this.start`. Then offset by the initial position of the
 | |
|         // mouse compared to the gutter size.
 | |
|         offset =
 | |
|             getMousePosition(e) -
 | |
|             this.start +
 | |
|             (this[aGutterSize] - this.dragOffset)
 | |
| 
 | |
|         if (dragInterval > 1) {
 | |
|             offset = Math.round(offset / dragInterval) * dragInterval
 | |
|         }
 | |
| 
 | |
|         // If within snapOffset of min or max, set offset to min or max.
 | |
|         // snapOffset buffers a.minSize and b.minSize, so logic is opposite for both.
 | |
|         // Include the appropriate gutter sizes to prevent overflows.
 | |
|         if (offset <= a.minSize + snapOffset + this[aGutterSize]) {
 | |
|             offset = a.minSize + this[aGutterSize]
 | |
|         } else if (
 | |
|             offset >=
 | |
|             this.size - (b.minSize + snapOffset + this[bGutterSize])
 | |
|         ) {
 | |
|             offset = this.size - (b.minSize + this[bGutterSize])
 | |
|         }
 | |
| 
 | |
|         // Actually adjust the size.
 | |
|         adjust.call(this, offset)
 | |
| 
 | |
|         // Call the drag callback continously. Don't do anything too intensive
 | |
|         // in this callback.
 | |
|         getOption(options, 'onDrag', NOOP)()
 | |
|     }
 | |
| 
 | |
|     // Cache some important sizes when drag starts, so we don't have to do that
 | |
|     // continously:
 | |
|     //
 | |
|     // `size`: The total size of the pair. First + second + first gutter + second gutter.
 | |
|     // `start`: The leading side of the first element.
 | |
|     //
 | |
|     // ------------------------------------------------
 | |
|     // |      aGutterSize -> |||                      |
 | |
|     // |                     |||                      |
 | |
|     // |                     |||                      |
 | |
|     // |                     ||| <- bGutterSize       |
 | |
|     // ------------------------------------------------
 | |
|     // | <- start                             size -> |
 | |
|     function calculateSizes() {
 | |
|         // Figure out the parent size minus padding.
 | |
|         const a = elements[this.a].element
 | |
|         const b = elements[this.b].element
 | |
| 
 | |
|         const aBounds = a[getBoundingClientRect]()
 | |
|         const bBounds = b[getBoundingClientRect]()
 | |
| 
 | |
|         this.size =
 | |
|             aBounds[dimension] +
 | |
|             bBounds[dimension] +
 | |
|             this[aGutterSize] +
 | |
|             this[bGutterSize]
 | |
|         this.start = aBounds[position]
 | |
|         this.end = aBounds[positionEnd]
 | |
|     }
 | |
| 
 | |
|     function innerSize(element) {
 | |
|         // Return nothing if getComputedStyle is not supported (< IE9)
 | |
|         // Or if parent element has no layout yet
 | |
|         if (!getComputedStyle) return null
 | |
| 
 | |
|         const computedStyle = getComputedStyle(element)
 | |
|         let size = element[clientSize]
 | |
| 
 | |
|         if (size === 0) return null
 | |
| 
 | |
|         if (direction === HORIZONTAL) {
 | |
|             size -=
 | |
|                 parseFloat(computedStyle.paddingLeft) +
 | |
|                 parseFloat(computedStyle.paddingRight)
 | |
|         } else {
 | |
|             size -=
 | |
|                 parseFloat(computedStyle.paddingTop) +
 | |
|                 parseFloat(computedStyle.paddingBottom)
 | |
|         }
 | |
| 
 | |
|         return size
 | |
|     }
 | |
| 
 | |
|     // When specifying percentage sizes that are less than the computed
 | |
|     // size of the element minus the gutter, the lesser percentages must be increased
 | |
|     // (and decreased from the other elements) to make space for the pixels
 | |
|     // subtracted by the gutters.
 | |
|     function trimToMin(sizesToTrim) {
 | |
|         // Try to get inner size of parent element.
 | |
|         // If it's no supported, return original sizes.
 | |
|         const parentSize = innerSize(parent)
 | |
|         if (parentSize === null) {
 | |
|             return sizesToTrim
 | |
|         }
 | |
| 
 | |
|         // Keep track of the excess pixels, the amount of pixels over the desired percentage
 | |
|         // Also keep track of the elements with pixels to spare, to decrease after if needed
 | |
|         let excessPixels = 0
 | |
|         const toSpare = []
 | |
| 
 | |
|         const pixelSizes = sizesToTrim.map((size, i) => {
 | |
|             // Convert requested percentages to pixel sizes
 | |
|             const pixelSize = (parentSize * size) / 100
 | |
|             const elementGutterSize = getGutterSize(
 | |
|                 gutterSize,
 | |
|                 i === 0,
 | |
|                 i === sizesToTrim.length - 1,
 | |
|                 gutterAlign,
 | |
|             )
 | |
|             const elementMinSize = minSizes[i] + elementGutterSize
 | |
| 
 | |
|             // If element is too smal, increase excess pixels by the difference
 | |
|             // and mark that it has no pixels to spare
 | |
|             if (pixelSize < elementMinSize) {
 | |
|                 excessPixels += elementMinSize - pixelSize
 | |
|                 toSpare.push(0)
 | |
|                 return elementMinSize
 | |
|             }
 | |
| 
 | |
|             // Otherwise, mark the pixels it has to spare and return it's original size
 | |
|             toSpare.push(pixelSize - elementMinSize)
 | |
|             return pixelSize
 | |
|         })
 | |
| 
 | |
|         // If nothing was adjusted, return the original sizes
 | |
|         if (excessPixels === 0) {
 | |
|             return sizesToTrim
 | |
|         }
 | |
| 
 | |
|         return pixelSizes.map((pixelSize, i) => {
 | |
|             let newPixelSize = pixelSize
 | |
| 
 | |
|             // While there's still pixels to take, and there's enough pixels to spare,
 | |
|             // take as many as possible up to the total excess pixels
 | |
|             if (excessPixels > 0 && toSpare[i] - excessPixels > 0) {
 | |
|                 const takenPixels = Math.min(
 | |
|                     excessPixels,
 | |
|                     toSpare[i] - excessPixels,
 | |
|                 )
 | |
| 
 | |
|                 // Subtract the amount taken for the next iteration
 | |
|                 excessPixels -= takenPixels
 | |
|                 newPixelSize = pixelSize - takenPixels
 | |
|             }
 | |
| 
 | |
|             // Return the pixel size adjusted as a percentage
 | |
|             return (newPixelSize / parentSize) * 100
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     // stopDragging is very similar to startDragging in reverse.
 | |
|     function stopDragging() {
 | |
|         const self = this
 | |
|         const a = elements[self.a].element
 | |
|         const b = elements[self.b].element
 | |
| 
 | |
|         if (self.dragging) {
 | |
|             getOption(options, 'onDragEnd', NOOP)(getSizes())
 | |
|         }
 | |
| 
 | |
|         self.dragging = false
 | |
| 
 | |
|         // Remove the stored event listeners. This is why we store them.
 | |
|         global[removeEventListener]('mouseup', self.stop)
 | |
|         global[removeEventListener]('touchend', self.stop)
 | |
|         global[removeEventListener]('touchcancel', self.stop)
 | |
|         global[removeEventListener]('mousemove', self.move)
 | |
|         global[removeEventListener]('touchmove', self.move)
 | |
| 
 | |
|         // Clear bound function references
 | |
|         self.stop = null
 | |
|         self.move = null
 | |
| 
 | |
|         a[removeEventListener]('selectstart', NOOP)
 | |
|         a[removeEventListener]('dragstart', NOOP)
 | |
|         b[removeEventListener]('selectstart', NOOP)
 | |
|         b[removeEventListener]('dragstart', NOOP)
 | |
| 
 | |
|         a.style.userSelect = ''
 | |
|         a.style.webkitUserSelect = ''
 | |
|         a.style.MozUserSelect = ''
 | |
|         a.style.pointerEvents = ''
 | |
| 
 | |
|         b.style.userSelect = ''
 | |
|         b.style.webkitUserSelect = ''
 | |
|         b.style.MozUserSelect = ''
 | |
|         b.style.pointerEvents = ''
 | |
| 
 | |
|         self.gutter.style.cursor = ''
 | |
|         self.parent.style.cursor = ''
 | |
|         document.body.style.cursor = ''
 | |
|     }
 | |
| 
 | |
|     // startDragging calls `calculateSizes` to store the inital size in the pair object.
 | |
|     // It also adds event listeners for mouse/touch events,
 | |
|     // and prevents selection while dragging so avoid the selecting text.
 | |
|     function startDragging(e) {
 | |
|         // Right-clicking can't start dragging.
 | |
|         if ('button' in e && e.button !== 0) {
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         // Alias frequently used variables to save space. 200 bytes.
 | |
|         const self = this
 | |
|         const a = elements[self.a].element
 | |
|         const b = elements[self.b].element
 | |
| 
 | |
|         // Call the onDragStart callback.
 | |
|         if (!self.dragging) {
 | |
|             getOption(options, 'onDragStart', NOOP)(getSizes())
 | |
|         }
 | |
| 
 | |
|         // Don't actually drag the element. We emulate that in the drag function.
 | |
|         e.preventDefault()
 | |
| 
 | |
|         // Set the dragging property of the pair object.
 | |
|         self.dragging = true
 | |
| 
 | |
|         // Create two event listeners bound to the same pair object and store
 | |
|         // them in the pair object.
 | |
|         self.move = drag.bind(self)
 | |
|         self.stop = stopDragging.bind(self)
 | |
| 
 | |
|         // All the binding. `window` gets the stop events in case we drag out of the elements.
 | |
|         global[addEventListener]('mouseup', self.stop)
 | |
|         global[addEventListener]('touchend', self.stop)
 | |
|         global[addEventListener]('touchcancel', self.stop)
 | |
|         global[addEventListener]('mousemove', self.move)
 | |
|         global[addEventListener]('touchmove', self.move)
 | |
| 
 | |
|         // Disable selection. Disable!
 | |
|         a[addEventListener]('selectstart', NOOP)
 | |
|         a[addEventListener]('dragstart', NOOP)
 | |
|         b[addEventListener]('selectstart', NOOP)
 | |
|         b[addEventListener]('dragstart', NOOP)
 | |
| 
 | |
|         a.style.userSelect = 'none'
 | |
|         a.style.webkitUserSelect = 'none'
 | |
|         a.style.MozUserSelect = 'none'
 | |
|         a.style.pointerEvents = 'none'
 | |
| 
 | |
|         b.style.userSelect = 'none'
 | |
|         b.style.webkitUserSelect = 'none'
 | |
|         b.style.MozUserSelect = 'none'
 | |
|         b.style.pointerEvents = 'none'
 | |
| 
 | |
|         // Set the cursor at multiple levels
 | |
|         self.gutter.style.cursor = cursor
 | |
|         self.parent.style.cursor = cursor
 | |
|         document.body.style.cursor = cursor
 | |
| 
 | |
|         // Cache the initial sizes of the pair.
 | |
|         calculateSizes.call(self)
 | |
| 
 | |
|         // Determine the position of the mouse compared to the gutter
 | |
|         self.dragOffset = getMousePosition(e) - self.end
 | |
|     }
 | |
| 
 | |
|     // adjust sizes to ensure percentage is within min size and gutter.
 | |
|     sizes = trimToMin(sizes)
 | |
| 
 | |
|     // 5. Create pair and element objects. Each pair has an index reference to
 | |
|     // elements `a` and `b` of the pair (first and second elements).
 | |
|     // Loop through the elements while pairing them off. Every pair gets a
 | |
|     // `pair` object and a gutter.
 | |
|     //
 | |
|     // Basic logic:
 | |
|     //
 | |
|     // - Starting with the second element `i > 0`, create `pair` objects with
 | |
|     //   `a = i - 1` and `b = i`
 | |
|     // - Set gutter sizes based on the _pair_ being first/last. The first and last
 | |
|     //   pair have gutterSize / 2, since they only have one half gutter, and not two.
 | |
|     // - Create gutter elements and add event listeners.
 | |
|     // - Set the size of the elements, minus the gutter sizes.
 | |
|     //
 | |
|     // -----------------------------------------------------------------------
 | |
|     // |     i=0     |         i=1         |        i=2       |      i=3     |
 | |
|     // |             |                     |                  |              |
 | |
|     // |           pair 0                pair 1             pair 2           |
 | |
|     // |             |                     |                  |              |
 | |
|     // -----------------------------------------------------------------------
 | |
|     const pairs = []
 | |
|     elements = ids.map((id, i) => {
 | |
|         // Create the element object.
 | |
|         const element = {
 | |
|             element: elementOrSelector(id),
 | |
|             size: sizes[i],
 | |
|             minSize: minSizes[i],
 | |
|             i,
 | |
|         }
 | |
| 
 | |
|         let pair
 | |
| 
 | |
|         if (i > 0) {
 | |
|             // Create the pair object with its metadata.
 | |
|             pair = {
 | |
|                 a: i - 1,
 | |
|                 b: i,
 | |
|                 dragging: false,
 | |
|                 direction,
 | |
|                 parent,
 | |
|             }
 | |
| 
 | |
|             pair[aGutterSize] = getGutterSize(
 | |
|                 gutterSize,
 | |
|                 i - 1 === 0,
 | |
|                 false,
 | |
|                 gutterAlign,
 | |
|             )
 | |
|             pair[bGutterSize] = getGutterSize(
 | |
|                 gutterSize,
 | |
|                 false,
 | |
|                 i === ids.length - 1,
 | |
|                 gutterAlign,
 | |
|             )
 | |
| 
 | |
|             // if the parent has a reverse flex-direction, switch the pair elements.
 | |
|             if (
 | |
|                 parentFlexDirection === 'row-reverse' ||
 | |
|                 parentFlexDirection === 'column-reverse'
 | |
|             ) {
 | |
|                 const temp = pair.a
 | |
|                 pair.a = pair.b
 | |
|                 pair.b = temp
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Determine the size of the current element. IE8 is supported by
 | |
|         // staticly assigning sizes without draggable gutters. Assigns a string
 | |
|         // to `size`.
 | |
|         //
 | |
|         // IE9 and above
 | |
|         if (!isIE8) {
 | |
|             // Create gutter elements for each pair.
 | |
|             if (i > 0) {
 | |
|                 const gutterElement = gutter(i, direction, element.element)
 | |
|                 setGutterSize(gutterElement, gutterSize, i)
 | |
| 
 | |
|                 // Save bound event listener for removal later
 | |
|                 pair[gutterStartDragging] = startDragging.bind(pair)
 | |
| 
 | |
|                 // Attach bound event listener
 | |
|                 gutterElement[addEventListener](
 | |
|                     'mousedown',
 | |
|                     pair[gutterStartDragging],
 | |
|                 )
 | |
|                 gutterElement[addEventListener](
 | |
|                     'touchstart',
 | |
|                     pair[gutterStartDragging],
 | |
|                 )
 | |
| 
 | |
|                 parent.insertBefore(gutterElement, element.element)
 | |
| 
 | |
|                 pair.gutter = gutterElement
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         setElementSize(
 | |
|             element.element,
 | |
|             element.size,
 | |
|             getGutterSize(
 | |
|                 gutterSize,
 | |
|                 i === 0,
 | |
|                 i === ids.length - 1,
 | |
|                 gutterAlign,
 | |
|             ),
 | |
|         )
 | |
| 
 | |
|         // After the first iteration, and we have a pair object, append it to the
 | |
|         // list of pairs.
 | |
|         if (i > 0) {
 | |
|             pairs.push(pair)
 | |
|         }
 | |
| 
 | |
|         return element
 | |
|     })
 | |
| 
 | |
|     function adjustToMin(element) {
 | |
|         const isLast = element.i === pairs.length
 | |
|         const pair = isLast ? pairs[element.i - 1] : pairs[element.i]
 | |
| 
 | |
|         calculateSizes.call(pair)
 | |
| 
 | |
|         const size = isLast
 | |
|             ? pair.size - element.minSize - pair[bGutterSize]
 | |
|             : element.minSize + pair[aGutterSize]
 | |
| 
 | |
|         adjust.call(pair, size)
 | |
|     }
 | |
| 
 | |
|     elements.forEach(element => {
 | |
|         const computedSize = element.element[getBoundingClientRect]()[dimension]
 | |
| 
 | |
|         if (computedSize < element.minSize) {
 | |
|             if (expandToMin) {
 | |
|                 adjustToMin(element)
 | |
|             } else {
 | |
|                 // eslint-disable-next-line no-param-reassign
 | |
|                 element.minSize = computedSize
 | |
|             }
 | |
|         }
 | |
|     })
 | |
| 
 | |
|     function setSizes(newSizes) {
 | |
|         const trimmed = trimToMin(newSizes)
 | |
|         trimmed.forEach((newSize, i) => {
 | |
|             if (i > 0) {
 | |
|                 const pair = pairs[i - 1]
 | |
| 
 | |
|                 const a = elements[pair.a]
 | |
|                 const b = elements[pair.b]
 | |
| 
 | |
|                 a.size = trimmed[i - 1]
 | |
|                 b.size = newSize
 | |
| 
 | |
|                 setElementSize(a.element, a.size, pair[aGutterSize])
 | |
|                 setElementSize(b.element, b.size, pair[bGutterSize])
 | |
|             }
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     function destroy(preserveStyles, preserveGutter) {
 | |
|         pairs.forEach(pair => {
 | |
|             if (preserveGutter !== true) {
 | |
|                 pair.parent.removeChild(pair.gutter)
 | |
|             } else {
 | |
|                 pair.gutter[removeEventListener](
 | |
|                     'mousedown',
 | |
|                     pair[gutterStartDragging],
 | |
|                 )
 | |
|                 pair.gutter[removeEventListener](
 | |
|                     'touchstart',
 | |
|                     pair[gutterStartDragging],
 | |
|                 )
 | |
|             }
 | |
| 
 | |
|             if (preserveStyles !== true) {
 | |
|                 const style = elementStyle(
 | |
|                     dimension,
 | |
|                     pair.a.size,
 | |
|                     pair[aGutterSize],
 | |
|                 )
 | |
| 
 | |
|                 Object.keys(style).forEach(prop => {
 | |
|                     elements[pair.a].element.style[prop] = ''
 | |
|                     elements[pair.b].element.style[prop] = ''
 | |
|                 })
 | |
|             }
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     if (isIE8) {
 | |
|         return {
 | |
|             setSizes,
 | |
|             destroy,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return {
 | |
|         setSizes,
 | |
|         getSizes,
 | |
|         collapse(i) {
 | |
|             adjustToMin(elements[i])
 | |
|         },
 | |
|         destroy,
 | |
|         parent,
 | |
|         pairs,
 | |
|     }
 | |
| }
 | |
| 
 | |
| export default Split
 |