+
+
+
+
Draggable
Resizable
Mirrored
+
Bounded
Responsive
+
Prevent Collision
+
Vertical Compact
Row Height: Col nums:
+ Margin x: Margin y:
-
+
@@ -104,10 +127,10 @@
//var eventBus = require('./eventBus');
let testLayout = [
- {"x":0,"y":0,"w":2,"h":2,"i":"0", resizable: true, draggable: true, static: false},
+ {"x":0,"y":0,"w":2,"h":2,"i":"0", resizable: true, draggable: true, static: false, minY: 0, maxY: 2},
{"x":2,"y":0,"w":2,"h":4,"i":"1", resizable: null, draggable: null, static: true},
- {"x":4,"y":0,"w":2,"h":5,"i":"2", resizable: false, draggable: false, static: false},
- {"x":6,"y":0,"w":2,"h":3,"i":"3", resizable: false, draggable: false, static: false},
+ {"x":4,"y":0,"w":2,"h":5,"i":"2", resizable: false, draggable: false, static: false, minX: 4, maxX: 4, minW: 2, maxW: 2, preserveAspectRatio: true},
+ {"x":6,"y":0,"w":2,"h":3,"i":"3", resizable: false, draggable: false, static: false, preserveAspectRatio: true},
{"x":8,"y":0,"w":2,"h":3,"i":"4", resizable: false, draggable: false, static: false},
{"x":10,"y":0,"w":2,"h":3,"i":"5", resizable: false, draggable: false, static: false},
{"x":0,"y":5,"w":2,"h":5,"i":"6", resizable: false, draggable: false, static: false},
@@ -115,7 +138,7 @@
{"x":4,"y":5,"w":2,"h":5,"i":"8", resizable: false, draggable: false, static: false},
{"x":6,"y":3,"w":2,"h":4,"i":"9", resizable: false, draggable: false, static: true},
{"x":8,"y":4,"w":2,"h":4,"i":"10", resizable: false, draggable: false, static: false},
- {"x":10,"y":4,"w":2,"h":4,"i":"11", resizable: false, draggable: false, static: false},
+ {"x":10,"y":4,"w":2,"h":4,"i":"11", resizable: false, draggable: false, static: false, minY: 4},
{"x":0,"y":10,"w":2,"h":5,"i":"12", resizable: false, draggable: false, static: false},
{"x":2,"y":10,"w":2,"h":5,"i":"13", resizable: false, draggable: false, static: false},
{"x":4,"y":8,"w":2,"h":4,"i":"14", resizable: false, draggable: false, static: false},
@@ -126,10 +149,17 @@
{"x":2,"y":6,"w":2,"h":2,"i":"19", resizable: false, draggable: false, static: false}
];
+ /*let testLayout = [
+ { x: 0, y: 0, w: 2, h: 2, i: "0" },
+ { x: 2, y: 0, w: 2, h: 2, i: "1" },
+ { x: 4, y: 0, w: 2, h: 2, i: "2" },
+ { x: 6, y: 0, w: 2, h: 2, i: "3" },
+ { x: 8, y: 0, w: 2, h: 2, i: "4" },
+ ];*/
+
export default {
name: 'app',
components: {
- // ResponsiveGridLayout,
GridLayout,
GridItem,
TestElement,
@@ -143,9 +173,16 @@
resizable: true,
mirrored: false,
responsive: true,
+ bounded: false,
+ transformScale: 1,
+ preventCollision: false,
+ compact: true,
+ restoreOnDrag: true,
rowHeight: 30,
colNum: 12,
- index: 0
+ index: 0,
+ marginX: 10,
+ marginY: 10,
}
},
mounted: function () {
@@ -165,9 +202,22 @@
width -= 20;
document.getElementById("content").style.width = width+"px";
},
- removeItem: function(item) {
- //console.log("### REMOVE " + item.i);
- this.layout.splice(this.layout.indexOf(item), 1);
+ scaleHalf: function() {
+ this.transformScale = 0.5
+ document.getElementById("grid-layout").style.transform = "scale(0.5)";
+ },
+ scaleThreeQuarters: function() {
+ this.transformScale = 0.75
+ document.getElementById("grid-layout").style.transform = "scale(0.75)";
+ },
+ scaleIdentity: function() {
+ this.transformScale = 1
+ document.getElementById("grid-layout").style.transform = "scale(1)";
+ },
+ removeItem: function(i) {
+ console.log("### REMOVE " + i);
+ const index = this.layout.map(item => item.i).indexOf(i);
+ this.layout.splice(index, 1);
},
addItem: function() {
// let self = this;
@@ -176,6 +226,20 @@
this.index++;
this.layout.push(item);
},
+ addItemDynamically: function() {
+ const x = (this.layout.length * 2) % (this.colNum || 12);
+ const y = this.layout.length + (this.colNum || 12);
+ console.log("X=" + x + " Y=" + y)
+ let item = {
+ x: x,
+ y: y,
+ w: 2,
+ h: 2,
+ i: this.index+"",
+ }
+ this.index++;
+ this.layout.push(item);
+ },
move: function(i, newX, newY){
console.log("MOVE i=" + i + ", X=" + newX + ", Y=" + newY);
},
@@ -188,6 +252,9 @@
resized: function(i, newH, newW, newHPx, newWPx){
console.log("### RESIZED i=" + i + ", H=" + newH + ", W=" + newW + ", H(px)=" + newHPx + ", W(px)=" + newWPx);
},
+ containerResized: function(i, newH, newW, newHPx, newWPx){
+ console.log("### CONTAINER RESIZED i=" + i + ", H=" + newH + ", W=" + newW + ", H(px)=" + newHPx + ", W(px)=" + newWPx);
+ },
/**
* Add change direction button
*/
@@ -218,6 +285,9 @@
layoutUpdatedEvent: function(newLayout){
console.log("Updated layout: ", newLayout)
},
+ breakpointChangedEvent: function(newBreakpoint, newLayout){
+ console.log("breakpoint changed breakpoint=", newBreakpoint, ", layout: ", newLayout );
+ }
},
}
@@ -252,7 +322,7 @@
}*/
-
diff --git a/src/components/TestElement.vue b/src/components/TestElement.vue
index 2868a4b0..1e82e6b0 100644
--- a/src/components/TestElement.vue
+++ b/src/components/TestElement.vue
@@ -1,9 +1,18 @@
-
- {{text}}
-
+
+
+ {{text}}
+
+ x
+
\ No newline at end of file
+
diff --git a/src/components/index.js b/src/components/index.js
index 326d702d..36816c40 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,20 +1,34 @@
-import Vue from "vue";
import GridItem from './GridItem.vue';
import GridLayout from './GridLayout.vue';
// import ResponsiveGridLayout from './ResponsiveGridLayout.vue';
-
const VueGridLayout = {
// ResponsiveGridLayout,
GridLayout,
GridItem
}
-// module.exports = VueGridLayout;
+export function install(Vue) {
+ if (install.installed) return;
+ install.installed = true;
+ Object.keys(VueGridLayout).forEach(name => {
+ Vue.component(name, VueGridLayout[name]);
+ });
+}
+
+const plugin = {
+ install,
+};
-Object.keys(VueGridLayout).forEach(name => {
- Vue.component(name, VueGridLayout[name]);
-});
+let GlobalVue = null;
+if (typeof window !== 'undefined') {
+ GlobalVue = window.Vue;
+} else if (typeof global !== 'undefined') {
+ GlobalVue = global.Vue;
+}
+if (GlobalVue) {
+ GlobalVue.use(plugin);
+}
export default VueGridLayout;
-export { GridLayout, GridItem };
\ No newline at end of file
+export { GridLayout, GridItem };
diff --git a/src/helpers/utils.js b/src/helpers/utils.js
index a24e07b0..4cadea9a 100644
--- a/src/helpers/utils.js
+++ b/src/helpers/utils.js
@@ -75,9 +75,10 @@ export function collides(l1: LayoutItem, l2: LayoutItem): boolean {
* @param {Array} layout Layout.
* @param {Boolean} verticalCompact Whether or not to compact the layout
* vertically.
+ * @param {Object} minPositions
* @return {Array} Compacted Layout.
*/
-export function compact(layout: Layout, verticalCompact: Boolean): Layout {
+export function compact(layout: Layout, verticalCompact: Boolean, minPositions): Layout {
// Statics go in the compareWith array right away so items flow around them.
const compareWith = getStatics(layout);
// We go through the items by row and column.
@@ -90,7 +91,7 @@ export function compact(layout: Layout, verticalCompact: Boolean): Layout {
// Don't move static elements
if (!l.static) {
- l = compactItem(compareWith, l, verticalCompact);
+ l = compactItem(compareWith, l, verticalCompact, minPositions);
// Add to comparison array. We only collide with items before this one.
// Statics are already in this array.
@@ -110,12 +111,17 @@ export function compact(layout: Layout, verticalCompact: Boolean): Layout {
/**
* Compact an item in the layout.
*/
-export function compactItem(compareWith: Layout, l: LayoutItem, verticalCompact: boolean): LayoutItem {
+export function compactItem(compareWith: Layout, l: LayoutItem, verticalCompact: boolean, minPositions): LayoutItem {
if (verticalCompact) {
// Move the element up as far as it can go without colliding.
while (l.y > 0 && !getFirstCollision(compareWith, l)) {
l.y--;
}
+ } else if (minPositions) {
+ const minY = minPositions[l.i].y;
+ while (l.y > minY && !getFirstCollision(compareWith, l)) {
+ l.y--;
+ }
}
// Move it down, and keep moving it down if it's colliding.
@@ -206,12 +212,15 @@ export function getStatics(layout: Layout): Array
{
* @param {Boolean} [isUserAction] If true, designates that the item we're moving is
* being dragged/resized by th euser.
*/
-export function moveElement(layout: Layout, l: LayoutItem, x: Number, y: Number, isUserAction: Boolean): Layout {
+export function moveElement(layout: Layout, l: LayoutItem, x: Number, y: Number, isUserAction: Boolean, preventCollision: Boolean): Layout {
if (l.static) return layout;
// Short-circuit if nothing to do.
//if (l.y === y && l.x === x) return layout;
+ const oldX = l.x;
+ const oldY = l.y;
+
const movingUp = y && l.y > y;
// This is quite a bit faster than extending the object
if (typeof x === 'number') l.x = x;
@@ -226,6 +235,13 @@ export function moveElement(layout: Layout, l: LayoutItem, x: Number, y: Number,
if (movingUp) sorted = sorted.reverse();
const collisions = getAllCollisions(sorted, l);
+ if (preventCollision && collisions.length) {
+ l.x = oldX;
+ l.y = oldY;
+ l.moved = false;
+ return layout;
+ }
+
// Move each item that collides away from this element.
for (let i = 0, len = collisions.length; i < len; i++) {
const collision = collisions[i];
@@ -261,6 +277,7 @@ export function moveElement(layout: Layout, l: LayoutItem, x: Number, y: Number,
export function moveElementAwayFromCollision(layout: Layout, collidesWith: LayoutItem,
itemToMove: LayoutItem, isUserAction: ?boolean): Layout {
+ const preventCollision = false // we're already colliding
// If there is enough space above the collision to put this element, move it there.
// We only do this on the main collision as this can get funky in cascades and cause
// unwanted swapping behavior.
@@ -275,13 +292,13 @@ export function moveElementAwayFromCollision(layout: Layout, collidesWith: Layou
};
fakeItem.y = Math.max(collidesWith.y - itemToMove.h, 0);
if (!getFirstCollision(layout, fakeItem)) {
- return moveElement(layout, itemToMove, undefined, fakeItem.y);
+ return moveElement(layout, itemToMove, undefined, fakeItem.y, preventCollision);
}
}
// Previously this was optimized to move below the collision directly, but this can cause problems
// with cascading moves, as an item may actually leapflog a collision and cause a reversal in order.
- return moveElement(layout, itemToMove, undefined, itemToMove.y + 1);
+ return moveElement(layout, itemToMove, undefined, itemToMove.y + 1, preventCollision);
}
/**
@@ -369,9 +386,14 @@ export function setTopRight(top, right, width, height): Object {
*/
export function sortLayoutItemsByRowCol(layout: Layout): Layout {
return [].concat(layout).sort(function(a, b) {
+ if (a.y === b.y && a.x === b.x) {
+ return 0;
+ }
+
if (a.y > b.y || (a.y === b.y && a.x > b.x)) {
return 1;
}
+
return -1;
});
}
@@ -446,6 +468,7 @@ export function synchronizeLayoutWithChildren(initialLayout: Layout, children: A
export function validateLayout(layout: Layout, contextName: string): void {
contextName = contextName || "Layout";
const subProps = ['x', 'y', 'w', 'h'];
+ let keyArr = [];
if (!Array.isArray(layout)) throw new Error(contextName + " must be an array!");
for (let i = 0, len = layout.length; i < len; i++) {
const item = layout[i];
@@ -454,11 +477,20 @@ export function validateLayout(layout: Layout, contextName: string): void {
throw new Error('VueGridLayout: ' + contextName + '[' + i + '].' + subProps[j] + ' must be a number!');
}
}
- if (item.i && typeof item.i !== 'string') {
- // number is also ok, so comment the error
- // TODO confirm if commenting the line below doesn't cause unexpected problems
- // throw new Error('VueGridLayout: ' + contextName + '[' + i + '].i must be a string!');
+
+ if (item.i === undefined || item.i === null) {
+ throw new Error('VueGridLayout: ' + contextName + '[' + i + '].i cannot be null!');
+ }
+
+ if (typeof item.i !== 'number' && typeof item.i !== 'string') {
+ throw new Error('VueGridLayout: ' + contextName + '[' + i + '].i must be a string or number!');
}
+
+ if (keyArr.indexOf(item.i) >= 0) {
+ throw new Error('VueGridLayout: ' + contextName + '[' + i + '].i must be unique!');
+ }
+ keyArr.push(item.i);
+
if (item.static !== undefined && typeof item.static !== 'boolean') {
throw new Error('VueGridLayout: ' + contextName + '[' + i + '].static must be a boolean!');
}
diff --git a/test/unit/GridItem.spec.js b/test/unit/GridItem.spec.js
new file mode 100644
index 00000000..562ac962
--- /dev/null
+++ b/test/unit/GridItem.spec.js
@@ -0,0 +1,23 @@
+import { shallowMount } from '@vue/test-utils'
+import GridLayout from '../../src/components/GridLayout.vue'
+
+let layout
+
+describe('GridLayout test', () => {
+ beforeAll(()=>{
+ let testLayout = [{"x":0,"y":0,"w":2,"h":2,"i":"0", resizable: true, draggable: true, static: false, minY: 0, maxY: 2}];
+ layout = JSON.parse(JSON.stringify(testLayout))
+ })
+
+ describe('Interface test', () => {
+ it('should render correct contents', () => {
+ const wrapper = shallowMount(GridLayout, {
+ propsData: {
+ layout: layout
+ }
+ })
+ const grid = wrapper.findAll('.vue-grid-layout');
+ expect(grid.selector).toEqual('.vue-grid-layout');
+ })
+ })
+})
diff --git a/test/unit/utils.spec.js b/test/unit/utils.spec.js
new file mode 100644
index 00000000..ad26733c
--- /dev/null
+++ b/test/unit/utils.spec.js
@@ -0,0 +1,313 @@
+// @flow
+/* eslint-env jest */
+
+import {
+ bottom,
+ collides,
+ compact,
+ moveElement,
+ sortLayoutItemsByRowCol,
+ validateLayout
+ } from "../../src/helpers/utils";
+
+ describe("bottom", () => {
+ it("Handles an empty layout as input", () => {
+ expect(bottom([])).toEqual(0);
+ });
+
+ it("Returns the bottom coordinate of the layout", () => {
+ expect(
+ bottom([
+ { i: "1", x: 0, y: 1, w: 1, h: 1 },
+ { i: "2", x: 1, y: 2, w: 1, h: 1 }
+ ])
+ ).toEqual(3);
+ });
+ });
+
+ describe("sortLayoutItemsByRowCol", () => {
+ it("should sort by top to bottom right", () => {
+ const layout = [
+ { x: 1, y: 1, w: 1, h: 1, i: "2" },
+ { x: 1, y: 0, w: 1, h: 1, i: "1" },
+ { x: 0, y: 1, w: 2, h: 2, i: "3" }
+ ];
+ expect(sortLayoutItemsByRowCol(layout)).toEqual([
+ { x: 1, y: 0, w: 1, h: 1, i: "1" },
+ { x: 0, y: 1, w: 2, h: 2, i: "3" },
+ { x: 1, y: 1, w: 1, h: 1, i: "2" }
+ ]);
+ });
+ });
+
+ describe("collides", () => {
+ it("Returns whether the layout items collide", () => {
+ expect(
+ collides(
+ { i: "1", x: 0, y: 1, w: 1, h: 1 },
+ { i: "2", x: 1, y: 2, w: 1, h: 1 }
+ )
+ ).toEqual(false);
+ expect(
+ collides(
+ { i: "1", x: 0, y: 1, w: 1, h: 1 },
+ { i: "2", x: 0, y: 1, w: 1, h: 1 }
+ )
+ ).toEqual(true);
+ });
+ });
+
+ describe("validateLayout", () => {
+ it("Validates an empty layout", () => {
+ validateLayout([]);
+ });
+ it("Validates a populated layout", () => {
+ validateLayout([
+ { i: "1", x: 0, y: 1, w: 1, h: 1 },
+ { i: "2", x: 1, y: 2, w: 1, h: 1 }
+ ]);
+ });
+ it("Throws errors on invalid input", () => {
+ expect(() => {
+ validateLayout([
+ { i: "1", x: 0, y: 1, w: 1, h: 1 },
+ { i: "2", x: 1, y: 2, w: 1 }
+ ]);
+ }).toThrowError(/layout\[1\]\.h must be a number!/i);
+ });
+ });
+
+ describe("moveElement", () => {
+ function compactAndMove(
+ layout,
+ layoutItem,
+ x,
+ y,
+ isUserAction,
+ preventCollision
+ ) {
+ return compact(
+ moveElement(
+ layout,
+ layoutItem,
+ x,
+ y,
+ isUserAction,
+ preventCollision
+ )
+ );
+ }
+
+ it("Does not change layout when colliding on no rearrangement mode", () => {
+ const layout = [
+ { i: "1", x: 0, y: 1, w: 1, h: 1, moved: false },
+ { i: "2", x: 1, y: 2, w: 1, h: 1, moved: false }
+ ];
+ const layoutItem = layout[0];
+ expect(
+ moveElement(
+ layout,
+ layoutItem,
+ 1,
+ 2, // x, y
+ true,
+ true // isUserAction, preventCollision
+ )
+ ).toEqual([
+ { i: "1", x: 0, y: 1, w: 1, h: 1, moved: false },
+ { i: "2", x: 1, y: 2, w: 1, h: 1, moved: false }
+ ]);
+ });
+
+ it("Does change layout when colliding in rearrangement mode", () => {
+ const layout = [
+ { i: "1", x: 0, y: 0, w: 1, h: 1, moved: false },
+ { i: "2", x: 1, y: 0, w: 1, h: 1, moved: false }
+ ];
+ const layoutItem = layout[0];
+ expect(
+ moveElement(
+ layout,
+ layoutItem,
+ 1,
+ 0, // x, y
+ true,
+ false // isUserAction, preventCollision
+ )
+ ).toEqual([
+ { i: "1", x: 1, y: 0, w: 1, h: 1, moved: true },
+ { i: "2", x: 1, y: 1, w: 1, h: 1, moved: true }
+ ]);
+ });
+
+ it("Moves elements out of the way without causing panel jumps when compaction is vertical", () => {
+ const layout = [
+ { x: 0, y: 0, w: 1, h: 10, i: "A" },
+ { x: 0, y: 10, w: 1, h: 1, i: "B" },
+ { x: 0, y: 11, w: 1, h: 1, i: "C" }
+ ];
+ // move A down slightly so it collides with C; can cause C to jump above B.
+ // We instead want B to jump above A (it has the room)
+ const itemA = layout[0];
+ expect(
+ compactAndMove(
+ layout,
+ itemA,
+ 0,
+ 1, // x, y
+ true,
+ false // isUserAction, preventCollision
+ )
+ ).toEqual([
+ expect.objectContaining({ x: 0, y: 1, w: 1, h: 10, i: "A" }),
+ expect.objectContaining({ x: 0, y: 0, w: 1, h: 1, i: "B" }),
+ expect.objectContaining({ x: 0, y: 11, w: 1, h: 1, i: "C" })
+ ]);
+ });
+
+ it("Calculates the correct collision when moving large object far", () => {
+ const layout = [
+ { x: 0, y: 0, w: 1, h: 10, i: "A" },
+ { x: 0, y: 10, w: 1, h: 1, i: "B" },
+ { x: 0, y: 11, w: 1, h: 1, i: "C" }
+ ];
+ // Move A down by 2. This should move B above, but since we don't compact in between,
+ // C should move below.
+ const itemA = layout[0];
+ expect(
+ moveElement(
+ layout,
+ itemA,
+ 0,
+ 2, // x, y
+ true,
+ false // isUserAction, preventCollision
+ )
+ ).toEqual([
+ expect.objectContaining({ x: 0, y: 2, w: 1, h: 10, i: "A" }),
+ expect.objectContaining({ x: 0, y: 1, w: 1, h: 1, i: "B" }),
+ expect.objectContaining({ x: 0, y: 12, w: 1, h: 1, i: "C" })
+ ]);
+ });
+
+ it("Moves elements out of the way without causing panel jumps when compaction is vertical", () => {
+ const layout = [
+ { x: 0, y: 0, w: 1, h: 1, i: "A" },
+ { x: 1, y: 0, w: 1, h: 1, i: "B" },
+ { x: 0, y: 1, w: 2, h: 2, i: "C" }
+ ];
+ // move A over slightly so it collides with B; can cause C to jump above B
+ // this test will check that that does not happen
+ const itemA = layout[0];
+ expect(
+ moveElement(
+ layout,
+ itemA,
+ 1,
+ 0, // x, y
+ true,
+ false // isUserAction, preventCollision
+ )
+ ).toEqual([
+ { x: 1, y: 0, w: 1, h: 1, i: "A", moved: true },
+ { x: 1, y: 1, w: 1, h: 1, i: "B", moved: true },
+ { x: 0, y: 2, w: 2, h: 2, i: "C", moved: true }
+ ]);
+ });
+
+ it("Moves one element to another should cause moving down panels, vert compact", () => {
+ // | A | B |
+ // |C| D |
+ const layout = [
+ { x: 0, y: 0, w: 2, h: 1, i: "A" },
+ { x: 2, y: 0, w: 2, h: 1, i: "B" },
+ { x: 0, y: 1, w: 1, h: 1, i: "C" },
+ { x: 1, y: 1, w: 3, h: 1, i: "D" }
+ ];
+ // move B left slightly so it collides with A; can cause C to jump above A
+ // this test will check that that does not happen
+ const itemB = layout[1];
+ expect(
+ compactAndMove(
+ layout,
+ itemB,
+ 1,
+ 0, // x, y
+ true,
+ false // isUserAction, preventCollision
+ )
+ ).toEqual([
+ expect.objectContaining({ x: 0, y: 1, w: 2, h: 1, i: "A" }),
+ expect.objectContaining({ x: 1, y: 0, w: 2, h: 1, i: "B" }),
+ expect.objectContaining({ x: 0, y: 2, w: 1, h: 1, i: "C" }),
+ expect.objectContaining({ x: 1, y: 2, w: 3, h: 1, i: "D" })
+ ]);
+ });
+
+ it("Moves one element to another should cause moving down panels, vert compact", () => {
+ // | A |
+ // |B|C|
+ // | |
+ //
+ // Moving C above A should not move B above A
+ const layout = [
+ { x: 0, y: 0, w: 2, h: 1, i: "A" },
+ { x: 0, y: 1, w: 1, h: 1, i: "B" },
+ { x: 1, y: 1, w: 1, h: 2, i: "C" }
+ ];
+ // Move C up.
+ const itemB = layout[2];
+ expect(
+ compactAndMove(
+ layout,
+ itemB,
+ 1,
+ 0, // x, y
+ true,
+ false // isUserAction, preventCollision
+ )
+ ).toEqual([
+ expect.objectContaining({ x: 0, y: 2, w: 2, h: 1, i: "A" }),
+ expect.objectContaining({ x: 0, y: 3, w: 1, h: 1, i: "B" }),
+ expect.objectContaining({ x: 1, y: 0, w: 1, h: 2, i: "C" })
+ ]);
+ });
+ });
+
+ describe("compact vertical", () => {
+ it("Removes empty vertical space above item", () => {
+ const layout = [{ i: "1", x: 0, y: 1, w: 1, h: 1 }];
+ expect(compact(layout, true)).toEqual([
+ { i: "1", x: 0, y: 0, w: 1, h: 1, moved: false }
+ ]);
+ });
+
+ it("Resolve collision by moving item further down in array", () => {
+ const layout = [
+ { x: 0, y: 0, w: 1, h: 5, i: "1" },
+ { x: 0, y: 1, w: 1, h: 1, i: "2" }
+ ];
+ expect(compact(layout, true)).toEqual([
+ { x: 0, y: 0, w: 1, h: 5, i: "1", moved: false },
+ { x: 0, y: 5, w: 1, h: 1, i: "2", moved: false }
+ ]);
+ });
+
+ it("Handles recursive collision by moving new collisions out of the way before moving item down", () => {
+ const layout = [
+ { x: 0, y: 0, w: 2, h: 5, i: "1" },
+ { x: 0, y: 0, w: 10, h: 1, i: "2" },
+ { x: 5, y: 1, w: 1, h: 1, i: "3" },
+ { x: 5, y: 2, w: 1, h: 1, i: "4" },
+ { x: 5, y: 3, w: 1, h: 1, i: "5", static: true }
+ ];
+
+ expect(compact(layout, true)).toEqual([
+ { x: 0, y: 0, w: 2, h: 5, i: "1", moved: false },
+ { x: 0, y: 5, w: 10, h: 1, i: "2", moved: false },
+ { x: 5, y: 0, w: 1, h: 1, i: "3", moved: false },
+ { x: 5, y: 1, w: 1, h: 1, i: "4", moved: false },
+ { x: 5, y: 3, w: 1, h: 1, i: "5", moved: false, static: true }
+ ]);
+ });
+ });
diff --git a/vue.config.js b/vue.config.js
index 66a070f8..59f22cbb 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -1,11 +1,24 @@
+// https://medium.com/js-dojo/how-to-reduce-your-vue-js-bundle-size-with-webpack-3145bf5019b7
+// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+const webpack = require('webpack');
+const PACKAGE = require('./package.json');
+
+const banner = PACKAGE.name + ' - ' + PACKAGE.version + ' | ' +
+ '(c) 2015, ' + new Date().getFullYear() + ' ' + PACKAGE.author + ' | ' +
+ PACKAGE.homepage;
+
module.exports = {
configureWebpack: {
output: {
library: "VueGridLayout",
libraryExport: 'default'
},
+ plugins: [
+ // new BundleAnalyzerPlugin(),
+ new webpack.BannerPlugin(banner)
+ ],
},
css: {
extract: false
- }
-}
\ No newline at end of file
+ },
+}
diff --git a/website/TODOS.md b/website/TODOS.md
new file mode 100644
index 00000000..2d56732e
--- /dev/null
+++ b/website/TODOS.md
@@ -0,0 +1,9 @@
+# vue-grid-layout
+
+
+https://github.com/wearebraid/vueformulate.com/tree/master/docs
+
+
+# emit responsiveLayoutUpdatedEvent?
+
+https://github.com/jbaysolutions/vue-grid-layout/compare/master...wzquyin:master
diff --git a/website/docs/.vuepress/Autocomplete.js b/website/docs/.vuepress/Autocomplete.js
new file mode 100644
index 00000000..47320d04
--- /dev/null
+++ b/website/docs/.vuepress/Autocomplete.js
@@ -0,0 +1,15 @@
+// import MyFormulateAutocomplete from './components/MyFormulateAutocomplete'
+//
+// export default function (formulateInstance) {
+// formulateInstance.extend({
+// components: {
+// MyFormulateAutocomplete
+// },
+// library: {
+// autocomplete: {
+// classification: 'text',
+// component: 'MyFormulateAutocomplete'
+// }
+// }
+// })
+// }
diff --git a/website/docs/.vuepress/components/Example01Basic.vue b/website/docs/.vuepress/components/Example01Basic.vue
new file mode 100644
index 00000000..537b41d0
--- /dev/null
+++ b/website/docs/.vuepress/components/Example01Basic.vue
@@ -0,0 +1,132 @@
+
+
+
+ {{itemTitle(item)}}
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example02Events.vue b/website/docs/.vuepress/components/Example02Events.vue
new file mode 100644
index 00000000..55aa1e50
--- /dev/null
+++ b/website/docs/.vuepress/components/Example02Events.vue
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+ {{item.i}}
+
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example03MultipleGrids.vue b/website/docs/.vuepress/components/Example03MultipleGrids.vue
new file mode 100644
index 00000000..e734cb40
--- /dev/null
+++ b/website/docs/.vuepress/components/Example03MultipleGrids.vue
@@ -0,0 +1,207 @@
+
+
+
+
Grid #1
+
+
+ {{item.i}}
+
+
+
+
+
Grid #2
+
+
+ {{item.i}}
+
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example04AllowIgnore.vue b/website/docs/.vuepress/components/Example04AllowIgnore.vue
new file mode 100644
index 00000000..8497b955
--- /dev/null
+++ b/website/docs/.vuepress/components/Example04AllowIgnore.vue
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
+ {{item.i}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example05Mirrored.vue b/website/docs/.vuepress/components/Example05Mirrored.vue
new file mode 100644
index 00000000..ff14c0c5
--- /dev/null
+++ b/website/docs/.vuepress/components/Example05Mirrored.vue
@@ -0,0 +1,133 @@
+
+
+ Draggable
+ Resizable
+ Mirrored
+
+
+
+ {{item.i}}
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example06Responsive.vue b/website/docs/.vuepress/components/Example06Responsive.vue
new file mode 100644
index 00000000..6ea37bac
--- /dev/null
+++ b/website/docs/.vuepress/components/Example06Responsive.vue
@@ -0,0 +1,156 @@
+
+
+
+ Displayed as
[x, y, w, h]
:
+
+
+ {{item.i}}: [{{item.x}}, {{item.y}}, {{item.w}}, {{item.h}}]
+
+
+
+
+
Draggable
+
Resizable
+
Responsive
+
+
+
+
+ {{item.i}}
+
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example07PreventCollision.vue b/website/docs/.vuepress/components/Example07PreventCollision.vue
new file mode 100644
index 00000000..5ce50888
--- /dev/null
+++ b/website/docs/.vuepress/components/Example07PreventCollision.vue
@@ -0,0 +1,128 @@
+
+
+
+
+ {{item.i}}
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example08ResponsivePredefinedLayouts.vue b/website/docs/.vuepress/components/Example08ResponsivePredefinedLayouts.vue
new file mode 100644
index 00000000..b124a51f
--- /dev/null
+++ b/website/docs/.vuepress/components/Example08ResponsivePredefinedLayouts.vue
@@ -0,0 +1,159 @@
+
+
+
+
+ {{item.i}}
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example09DynamicAddRemove.vue b/website/docs/.vuepress/components/Example09DynamicAddRemove.vue
new file mode 100644
index 00000000..89c6cb01
--- /dev/null
+++ b/website/docs/.vuepress/components/Example09DynamicAddRemove.vue
@@ -0,0 +1,168 @@
+
+
+
+ Displayed as
[x, y, w, h]
:
+
+
+ {{item.i}}: [{{item.x}}, {{item.y}}, {{item.w}}, {{item.h}}]
+
+
+
+
+
Draggable
+
Resizable
+
+
+ {{item.i}}
+ x
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example10DragFromOutside.vue b/website/docs/.vuepress/components/Example10DragFromOutside.vue
new file mode 100644
index 00000000..3fe1f2f2
--- /dev/null
+++ b/website/docs/.vuepress/components/Example10DragFromOutside.vue
@@ -0,0 +1,234 @@
+
+
+
+
+ Displayed as
[x, y, w, h]
:
+
+
+ {{ item.i }}: [{{ item.x }}, {{ item.y }}, {{ item.w }}, {{ item.h }}]
+
+
+
+
+
+
Droppable Element (Drag me!)
+
+
+
+ {{ item.i }}
+
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/Example11Bounded.vue b/website/docs/.vuepress/components/Example11Bounded.vue
new file mode 100644
index 00000000..b81d4e98
--- /dev/null
+++ b/website/docs/.vuepress/components/Example11Bounded.vue
@@ -0,0 +1,153 @@
+
+
+
+ Displayed as
[x, y, w, h]
:
+
+
+ {{item.i}}: [{{item.x}}, {{item.y}}, {{item.w}}, {{item.h}}]
+
+
+
+
+
Draggable
+
Resizable
+
Bounded
+
+
+
+
+ {{item.i}}
+
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/ExampleStylingGridLines.vue b/website/docs/.vuepress/components/ExampleStylingGridLines.vue
new file mode 100644
index 00000000..b78efaef
--- /dev/null
+++ b/website/docs/.vuepress/components/ExampleStylingGridLines.vue
@@ -0,0 +1,168 @@
+
+
+
+
+ {{ itemTitle(item) }}
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/ExampleStylingPlaceholder.vue b/website/docs/.vuepress/components/ExampleStylingPlaceholder.vue
new file mode 100644
index 00000000..4f29f511
--- /dev/null
+++ b/website/docs/.vuepress/components/ExampleStylingPlaceholder.vue
@@ -0,0 +1,138 @@
+
+
+
+
+ {{itemTitle(item)}}
+
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/HomeFooter.vue b/website/docs/.vuepress/components/HomeFooter.vue
new file mode 100644
index 00000000..cdac91e1
--- /dev/null
+++ b/website/docs/.vuepress/components/HomeFooter.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/components/HomepageGrid.vue b/website/docs/.vuepress/components/HomepageGrid.vue
new file mode 100644
index 00000000..ffc69c75
--- /dev/null
+++ b/website/docs/.vuepress/components/HomepageGrid.vue
@@ -0,0 +1,217 @@
+
+
+
+
+

+
+
API to generate image and PDF documents
+
+
+
+
+ {{ itemTitle(item) }}
+
+
+
+
+
+
+
diff --git a/website/docs/.vuepress/config.js b/website/docs/.vuepress/config.js
new file mode 100644
index 00000000..96243a04
--- /dev/null
+++ b/website/docs/.vuepress/config.js
@@ -0,0 +1,173 @@
+const description = 'A draggable and resizable grid layout, as a Vue component.'
+const title = 'Vue Grid Layout - ️A grid layout system for Vue.js'
+
+module.exports = {
+ base: "/vue-grid-layout/",
+ locales: {
+ '/': {
+ lang: 'en-US',
+ title: 'Vue Grid Layout - ️A grid layout system for Vue.js',
+ description: 'A draggable and resizable grid layout, as a Vue component.'
+ },
+ '/zh/': {
+ lang: 'zh-CN',
+ title: 'Vue Grid Layout -️ 适用Vue.js的栅格布局系统',
+ description: '可拖动和可调整大小栅格布局的Vue组件。'
+ }
+ },
+ head: [
+ ['link', { rel: 'icon', href: `/favicon.ico` }],
+ ['link', { rel: "apple-touch-icon", sizes: "180x180", href: "https://jbaysolutions.github.io/vue-grid-layout/assets/favicon/apple-touch-icon.png"}],
+ // ['script', { src: 'https://cdn.jsdelivr.net/npm/vue-grid-layout@2.3.9/dist/vue-grid-layout.umd.min.js' }]
+ [
+ 'script',
+ {
+ async: true,
+ src: 'https://www.googletagmanager.com/gtag/js?id=G-9JZJJHRV8R',
+ },
+ ],
+ [
+ 'script',
+ {},
+ [
+ "window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-9JZJJHRV8R');",
+ ],
+ ],
+ ],
+ port: 8081,
+ theme: '@vuepress/vue',
+ themeConfig: {
+ smoothScroll: true,
+ logo: '/assets/img/logo.png',
+ repo: 'jbaysolutions/vue-grid-layout',
+ docsDir: 'website/docs',
+ editLinks: true,
+ algolia: {
+ apiKey: '2f143d1edd24605564065dd02bf0a22b',
+ indexName: 'vue_grid_layout'
+ },
+ locales: {
+ '/': {
+ selectText: 'Languages',
+ label: 'English',
+ ariaLabel: 'Select language',
+ sidebar: {
+ '/guide/': [
+ {
+ title: "Guide",
+ collapsable: false,
+ children: [
+ '',
+ 'usage',
+ 'properties',
+ 'events',
+ 'styling',
+ ]
+ },
+ {
+ title: "Examples",
+ collapsable: false,
+ children: [
+ '01-basic',
+ '02-events',
+ '03-multiple-grids',
+ '04-allow-ignore',
+ '05-mirrored',
+ '06-responsive',
+ '07-prevent-collision',
+ '08-responsive-predefined-layouts',
+ '09-dynamic-add-remove',
+ '10-drag-from-outside',
+ '11-bounded',
+ ]
+ }
+ ]
+ },
+ nav: [
+ {text: 'Home', link: '/'},
+ {text: 'Guide', link: '/guide/'},
+ {text: 'Changelog', link: '/changelog/'}
+ ],
+ searchPlaceholder: 'Search...',
+ editLinkText: 'Help improve this page!',
+ lastUpdated: 'Last Updated'
+ },
+ '/zh/': {
+ selectText: '选择语言',
+ label: '简体中文',
+ ariaLabel: '选择语言',
+ sidebar: {
+ '/zh/guide/': [
+ {
+ title: "首页",
+ collapsable: false,
+ children: [
+ '',
+ 'usage',
+ 'properties',
+ 'events',
+ 'styling',
+ ]
+ },
+ {
+ title: "例子",
+ collapsable: false,
+ children: [
+ '01-basic',
+ '02-events',
+ '03-multiple-grids',
+ '04-allow-ignore',
+ '05-mirrored',
+ '06-responsive',
+ '07-prevent-collision',
+ '08-responsive-predefined-layouts',
+ '09-dynamic-add-remove',
+ '10-drag-from-outside',
+ '11-bounded',
+ ]
+ }
+ ]
+ },
+ nav: [
+ {text: '首页', link: '/zh/'},
+ {text: '指南', link: '/zh/guide/'},
+ {text: '更新日志', link: '/zh/changelog/'}
+ ],
+ searchPlaceholder: '搜索...',
+ editLinkText: '帮助改善此页面!',
+ lastUpdated: '最后更新时间'
+ }
+ }
+ },
+ plugins: [
+ '@vuepress/back-to-top',
+ /*['@vuepress/google-analytics', {
+ ga: 'UA-37288388-24' // UA-00000000-0
+ }],*/
+ ['seo', {
+ title: $page => `${$page.title} — Vue Grid Layout`,
+ // image: () => 'https://jbaysolutions.github.io/vue-grid-layout/assets/img/og.jpg',
+ siteTitle: (_, $site) => $site.title,
+ description: $page => $page.frontmatter.description || description,
+ author: (_, $site) => $site.themeConfig.author,
+ tags: $page => $page.frontmatter.tags,
+ twitterCard: _ => 'summary_large_image',
+ type: () => 'article',
+ url: (_, $site, path) => ($site.themeConfig.domain || '') + path,
+ publishedAt: $page => $page.frontmatter.date && new Date($page.frontmatter.date),
+ modifiedAt: $page => $page.lastUpdated && new Date($page.lastUpdated),
+ }],
+ ['vuepress-plugin-serve', {
+ port: 8080,
+ staticOptions: {
+ dotfiles: 'allow',
+ },
+ /*beforeServer(app, server) {
+ app.get('/path/to/my/custom', function(req, res) {
+ res.json({ custom: 'response' })
+ })
+ },*/
+ }],
+ ],
+ dest: 'public',
+}
diff --git a/website/docs/.vuepress/enhanceApp.js b/website/docs/.vuepress/enhanceApp.js
new file mode 100644
index 00000000..5d373fd5
--- /dev/null
+++ b/website/docs/.vuepress/enhanceApp.js
@@ -0,0 +1,31 @@
+/**
+ * App level enhancements. Read more here:
+ * https://vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements
+ */
+// import pageComponents from '@internal/page-components'
+import Autocomplete from './Autocomplete'
+// import ArticleCard from './components/ArticleCard'
+// import GithubButton from 'vue-github-button'
+// import VTooltip from 'v-tooltip'
+
+// import '../../node_modules/@braid/vue-formulate/themes/snow/snow.scss'
+
+export default ({ Vue }) => {
+ /*Vue.prototype.$gridlayout = {
+ async load () {
+ await import('vue-grid-layout');
+ console.log("LOADED!")
+ },
+ };*/
+ /*Vue.use(VueFormulate, {
+ plugins: [ Autocomplete ]
+ })*/
+
+ // Vue.use(VTooltip)
+
+ // for (const [name, component] of Object.entries(pageComponents)) {
+ // Vue.component(name, component)
+ // }
+ // Vue.component('github-button', GithubButton)
+ // Vue.component('ArticleCard', ArticleCard)
+}
diff --git a/website/docs/.vuepress/public/assets/favicon/apple-touch-icon.png b/website/docs/.vuepress/public/assets/favicon/apple-touch-icon.png
new file mode 100644
index 00000000..95100f18
Binary files /dev/null and b/website/docs/.vuepress/public/assets/favicon/apple-touch-icon.png differ
diff --git a/website/docs/.vuepress/public/assets/img/docsfold-logo-sm.png b/website/docs/.vuepress/public/assets/img/docsfold-logo-sm.png
new file mode 100644
index 00000000..de1e938c
Binary files /dev/null and b/website/docs/.vuepress/public/assets/img/docsfold-logo-sm.png differ
diff --git a/website/docs/.vuepress/public/assets/img/logo-jbay.png b/website/docs/.vuepress/public/assets/img/logo-jbay.png
new file mode 100644
index 00000000..a78f9140
Binary files /dev/null and b/website/docs/.vuepress/public/assets/img/logo-jbay.png differ
diff --git a/website/docs/.vuepress/public/assets/img/logo.png b/website/docs/.vuepress/public/assets/img/logo.png
new file mode 100644
index 00000000..159fda76
Binary files /dev/null and b/website/docs/.vuepress/public/assets/img/logo.png differ
diff --git a/examples/01-basic.html b/website/docs/.vuepress/public/examples/01-basic.html
similarity index 100%
rename from examples/01-basic.html
rename to website/docs/.vuepress/public/examples/01-basic.html
diff --git a/examples/01-basic.js b/website/docs/.vuepress/public/examples/01-basic.js
similarity index 100%
rename from examples/01-basic.js
rename to website/docs/.vuepress/public/examples/01-basic.js
diff --git a/examples/02-events.html b/website/docs/.vuepress/public/examples/02-events.html
similarity index 97%
rename from examples/02-events.html
rename to website/docs/.vuepress/public/examples/02-events.html
index e083a215..95116cdb 100644
--- a/examples/02-events.html
+++ b/website/docs/.vuepress/public/examples/02-events.html
@@ -60,6 +60,7 @@ Vue Grid Layout Example 2 - Move and resize events
@resize="resizeEvent"
@move="moveEvent"
@resized="resizedEvent"
+ @container-resized="containerResizedEvent"
@moved="movedEvent"
>
{{item.i}}
diff --git a/examples/02-events.js b/website/docs/.vuepress/public/examples/02-events.js
similarity index 92%
rename from examples/02-events.js
rename to website/docs/.vuepress/public/examples/02-events.js
index 230daadd..15f50522 100644
--- a/examples/02-events.js
+++ b/website/docs/.vuepress/public/examples/02-events.js
@@ -58,6 +58,11 @@ new Vue({
console.log(msg);
},
+ containerResizedEvent: function(i, newH, newW, newHPx, newWPx){
+ var msg = "CONTAINER RESIZED i=" + i + ", H=" + newH + ", W=" + newW + ", H(px)=" + newHPx + ", W(px)=" + newWPx;
+ this.eventLog.push(msg);
+ console.log(msg);
+ },
/**
*
* @param i the item id/index
diff --git a/examples/03-multiple-grids.html b/website/docs/.vuepress/public/examples/03-multiple-grids.html
similarity index 100%
rename from examples/03-multiple-grids.html
rename to website/docs/.vuepress/public/examples/03-multiple-grids.html
diff --git a/examples/04-allow-ignore.html b/website/docs/.vuepress/public/examples/04-allow-ignore.html
similarity index 100%
rename from examples/04-allow-ignore.html
rename to website/docs/.vuepress/public/examples/04-allow-ignore.html
diff --git a/examples/05-mirrored.html b/website/docs/.vuepress/public/examples/05-mirrored.html
similarity index 98%
rename from examples/05-mirrored.html
rename to website/docs/.vuepress/public/examples/05-mirrored.html
index 3e66e470..ea5eee08 100644
--- a/examples/05-mirrored.html
+++ b/website/docs/.vuepress/public/examples/05-mirrored.html
@@ -45,7 +45,6 @@ Vue Grid Layout Example 5 - Mirrored grid layout
:is-mirrored="mirrored"
:vertical-compact="true"
:use-css-transforms="true"
- :right-to-left="true"
>
Vue Grid Layout Example 5 - Mirrored grid layout
});