add resize handles between 3 main app columns
This commit is contained in:
parent
313956dd99
commit
928b6d47c8
10 changed files with 372 additions and 9 deletions
96
src/resizer/distributors.js
Normal file
96
src/resizer/distributors.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
class FixedDistributor {
|
||||
constructor(container, items, handleIndex, direction, sizer) {
|
||||
this.item = items[handleIndex + direction];
|
||||
this.beforeOffset = sizer.getItemOffset(this.item);
|
||||
this.sizer = sizer;
|
||||
}
|
||||
|
||||
resize(offset) {
|
||||
const itemSize = offset - this.beforeOffset;
|
||||
this.sizer.setItemSize(this.item, itemSize);
|
||||
return itemSize;
|
||||
}
|
||||
|
||||
finish(_offset) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CollapseDistributor extends FixedDistributor {
|
||||
constructor(container, items, handleIndex, direction, sizer) {
|
||||
super(container, items, handleIndex, direction, sizer);
|
||||
const style = getComputedStyle(this.item);
|
||||
this.minWidth = parseInt(style.minWidth, 10); //auto becomes NaN
|
||||
}
|
||||
|
||||
resize(offset) {
|
||||
let newWidth = offset - this.sizer.getItemOffset(this.item);
|
||||
if (this.minWidth > 0) {
|
||||
if (offset < this.minWidth + 50) {
|
||||
this.item.classList.add("collapsed");
|
||||
newWidth = this.minWidth;
|
||||
}
|
||||
else {
|
||||
this.item.classList.remove("collapsed");
|
||||
}
|
||||
}
|
||||
super.resize(newWidth);
|
||||
}
|
||||
}
|
||||
|
||||
class PercentageDistributor {
|
||||
|
||||
constructor(container, items, handleIndex, direction, sizer) {
|
||||
this.container = container;
|
||||
this.totalSize = sizer.getTotalSize();
|
||||
this.sizer = sizer;
|
||||
|
||||
this.beforeItems = items.slice(0, handleIndex);
|
||||
this.afterItems = items.slice(handleIndex);
|
||||
const percentages = PercentageDistributor._getPercentages(sizer, items);
|
||||
this.beforePercentages = percentages.slice(0, handleIndex);
|
||||
this.afterPercentages = percentages.slice(handleIndex);
|
||||
}
|
||||
|
||||
resize(offset) {
|
||||
const percent = offset / this.totalSize;
|
||||
const beforeSum =
|
||||
this.beforePercentages.reduce((total, p) => total + p, 0);
|
||||
const beforePercentages =
|
||||
this.beforePercentages.map(p => (p / beforeSum) * percent);
|
||||
const afterSum =
|
||||
this.afterPercentages.reduce((total, p) => total + p, 0);
|
||||
const afterPercentages =
|
||||
this.afterPercentages.map(p => (p / afterSum) * (1 - percent));
|
||||
|
||||
this.beforeItems.forEach((item, index) => {
|
||||
this.sizer.setItemPercentage(item, beforePercentages[index]);
|
||||
});
|
||||
this.afterItems.forEach((item, index) => {
|
||||
this.sizer.setItemPercentage(item, afterPercentages[index]);
|
||||
});
|
||||
}
|
||||
|
||||
finish(_offset) {
|
||||
|
||||
}
|
||||
|
||||
static _getPercentages(sizer, items) {
|
||||
const percentages = items.map(i => sizer.getItemPercentage(i));
|
||||
const setPercentages = percentages.filter(p => p !== null);
|
||||
const unsetCount = percentages.length - setPercentages.length;
|
||||
const setTotal = setPercentages.reduce((total, p) => total + p, 0);
|
||||
const implicitPercentage = (1 - setTotal) / unsetCount;
|
||||
return percentages.map(p => p === null ? implicitPercentage : p);
|
||||
}
|
||||
|
||||
static setPercentage(el, percent) {
|
||||
el.style.flexGrow = Math.round(percent * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FixedDistributor,
|
||||
CollapseDistributor,
|
||||
PercentageDistributor,
|
||||
};
|
70
src/resizer/event.js
Normal file
70
src/resizer/event.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import {Sizer} from "./sizer";
|
||||
|
||||
/*
|
||||
classNames:
|
||||
// class on resize-handle
|
||||
handle: string
|
||||
// class on resize-handle
|
||||
reverse: string
|
||||
// class on resize-handle
|
||||
vertical: string
|
||||
// class on container
|
||||
resizing: string
|
||||
*/
|
||||
|
||||
function makeResizeable(container, classNames, distributorCtor, sizerCtor = Sizer) {
|
||||
|
||||
function isResizeHandle(el) {
|
||||
return el && el.classList.contains(classNames.handle);
|
||||
}
|
||||
|
||||
function handleMouseDown(event) {
|
||||
const target = event.target;
|
||||
if (!isResizeHandle(target) || target.parentElement !== container) {
|
||||
return;
|
||||
}
|
||||
// prevent starting a drag operation
|
||||
event.preventDefault();
|
||||
// mark as currently resizing
|
||||
if (classNames.resizing) {
|
||||
container.classList.add(classNames.resizing);
|
||||
}
|
||||
|
||||
const resizeHandle = event.target;
|
||||
const vertical = resizeHandle.classList.contains(classNames.vertical);
|
||||
const reverse = resizeHandle.classList.contains(classNames.reverse);
|
||||
const direction = reverse ? 0 : -1;
|
||||
|
||||
const sizer = new sizerCtor(container, vertical, reverse);
|
||||
|
||||
const items = Array.from(container.children).filter(el => {
|
||||
return !isResizeHandle(el) && (
|
||||
isResizeHandle(el.previousElementSibling) ||
|
||||
isResizeHandle(el.nextElementSibling));
|
||||
});
|
||||
const prevItem = resizeHandle.previousElementSibling;
|
||||
const handleIndex = items.indexOf(prevItem) + 1;
|
||||
const distributor = new distributorCtor(container, items, handleIndex, direction, sizer);
|
||||
|
||||
const onMouseMove = (event) => {
|
||||
const offset = sizer.offsetFromEvent(event);
|
||||
distributor.resize(offset);
|
||||
};
|
||||
|
||||
const body = document.body;
|
||||
const onMouseUp = (event) => {
|
||||
if (classNames.resizing) {
|
||||
container.classList.remove(classNames.resizing);
|
||||
}
|
||||
const offset = sizer.offsetFromEvent(event);
|
||||
distributor.finish(offset);
|
||||
body.removeEventListener("mouseup", onMouseUp, false);
|
||||
body.removeEventListener("mousemove", onMouseMove, false);
|
||||
};
|
||||
body.addEventListener("mouseup", onMouseUp, false);
|
||||
body.addEventListener("mousemove", onMouseMove, false);
|
||||
}
|
||||
container.addEventListener("mousedown", handleMouseDown, false);
|
||||
}
|
||||
|
||||
module.exports = {makeResizeable};
|
10
src/resizer/index.js
Normal file
10
src/resizer/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import {Sizer} from "./sizer";
|
||||
import {FixedDistributor, PercentageDistributor} from "./distributors";
|
||||
import {makeResizeable} from "./event";
|
||||
|
||||
module.exports = {
|
||||
makeResizeable,
|
||||
Sizer,
|
||||
FixedDistributor,
|
||||
PercentageDistributor,
|
||||
};
|
39
src/resizer/room.js
Normal file
39
src/resizer/room.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import {Sizer} from "./sizer";
|
||||
import {FixedDistributor} from "./distributors";
|
||||
|
||||
class RoomSizer extends Sizer {
|
||||
setItemSize(item, size) {
|
||||
const isString = typeof size === "string";
|
||||
const cl = item.classList;
|
||||
if (isString) {
|
||||
item.style.flex = null;
|
||||
if (size === "show-content") {
|
||||
cl.add("show-content");
|
||||
cl.remove("show-available");
|
||||
item.style.maxHeight = null;
|
||||
}
|
||||
} else {
|
||||
cl.add("show-available");
|
||||
//item.style.flex = `0 1 ${Math.round(size)}px`;
|
||||
item.style.maxHeight = `${Math.round(size)}px`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RoomDistributor extends FixedDistributor {
|
||||
resize(offset) {
|
||||
const itemSize = offset - this.sizer.getItemOffset(this.item);
|
||||
|
||||
if (itemSize > this.item.scrollHeight) {
|
||||
this.sizer.setItemSize(this.item, "show-content");
|
||||
} else {
|
||||
this.sizer.setItemSize(this.item, itemSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RoomSizer,
|
||||
RoomDistributor,
|
||||
};
|
70
src/resizer/sizer.js
Normal file
70
src/resizer/sizer.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
class Sizer {
|
||||
constructor(container, vertical, reverse) {
|
||||
this.container = container;
|
||||
this.reverse = reverse;
|
||||
this.vertical = vertical;
|
||||
}
|
||||
|
||||
getItemPercentage(item) {
|
||||
/*
|
||||
const flexGrow = window.getComputedStyle(item).flexGrow;
|
||||
if (flexGrow === "") {
|
||||
return null;
|
||||
}
|
||||
return parseInt(flexGrow) / 1000;
|
||||
*/
|
||||
const style = window.getComputedStyle(item);
|
||||
const sizeStr = this.vertical ? style.height : style.width;
|
||||
const size = parseInt(sizeStr, 10);
|
||||
return size / this.getTotalSize();
|
||||
}
|
||||
|
||||
setItemPercentage(item, percent) {
|
||||
item.style.flexGrow = Math.round(percent * 1000);
|
||||
}
|
||||
|
||||
/** returns how far the edge of the item is from the edge of the container */
|
||||
getItemOffset(item) {
|
||||
const offset = (this.vertical ? item.offsetTop : item.offsetLeft) - this._getOffset();
|
||||
if (this.reverse) {
|
||||
return this.getTotalSize() - (offset + this.getItemSize(item));
|
||||
} else {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
/** returns the width/height of an item in the container */
|
||||
getItemSize(item) {
|
||||
return this.vertical ? item.offsetHeight : item.offsetWidth;
|
||||
}
|
||||
|
||||
/** returns the width/height of the container */
|
||||
getTotalSize() {
|
||||
return this.vertical ? this.container.offsetHeight : this.container.offsetWidth;
|
||||
}
|
||||
|
||||
/** container offset to offsetParent */
|
||||
_getOffset() {
|
||||
return this.vertical ? this.container.offsetTop : this.container.offsetLeft;
|
||||
}
|
||||
|
||||
setItemSize(item, size) {
|
||||
if (this.vertical) {
|
||||
item.style.height = `${Math.round(size)}px`;
|
||||
} else {
|
||||
item.style.width = `${Math.round(size)}px`;
|
||||
}
|
||||
}
|
||||
|
||||
/** returns the position of cursor at event relative to the edge of the container */
|
||||
offsetFromEvent(event) {
|
||||
const pos = this.vertical ? event.pageY : event.pageX;
|
||||
if (this.reverse) {
|
||||
return (this._getOffset() + this.getTotalSize()) - pos;
|
||||
} else {
|
||||
return pos - this._getOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {Sizer};
|
Loading…
Add table
Add a link
Reference in a new issue