@cocreate/position 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +1 -1
- package/package.json +5 -4
- package/src/index.js +34 -1
- package/src/position.js +154 -21
package/CONTRIBUTING.md
CHANGED
|
@@ -13,7 +13,7 @@ Fork the [project on Github](https://github.com/CoCreate-app/CoCreate-position)
|
|
|
13
13
|
```
|
|
14
14
|
git position https://github.com/contributor/CoCreate-position.git
|
|
15
15
|
cd CoCreate-position
|
|
16
|
-
git remote add upstream https://
|
|
16
|
+
git remote add upstream https://gitlab.com/cocreatejs/CoCreate-position.git
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Create a Topic Branch
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/position",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Make any element draggable and movable. Customizable via attributes, great for popups, alerts, multitasking and displaying multiple views.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"position",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "git+https://
|
|
25
|
+
"url": "git+https://gitlab.com/cocreatejs/CoCreate-position.git"
|
|
26
26
|
},
|
|
27
27
|
"author": "CoCreate LLC",
|
|
28
28
|
"license": "MIT",
|
|
@@ -36,10 +36,11 @@
|
|
|
36
36
|
},
|
|
37
37
|
"main": "./src/index.js",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cocreate/observer": "^1.
|
|
39
|
+
"@cocreate/observer": "^1.23.0",
|
|
40
|
+
"@cocreate/utils": "^1.54.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@cocreate/webpack": "^1.
|
|
43
|
+
"@cocreate/webpack": "^1.9.0"
|
|
43
44
|
},
|
|
44
45
|
"allowScripts": {
|
|
45
46
|
"@cocreate/observer@1.19.0": true,
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import observer from "@cocreate/observer";
|
|
2
|
+
import { queryElements } from "@cocreate/utils";
|
|
2
3
|
import position from "./position";
|
|
3
4
|
import "./index.css";
|
|
4
5
|
|
|
6
|
+
const positionTriggers = new WeakSet();
|
|
7
|
+
|
|
5
8
|
function init() {
|
|
6
9
|
let elements = document.querySelectorAll("[moveable], [positionable]");
|
|
7
10
|
initElements(elements);
|
|
11
|
+
initPositionTriggers(document.querySelectorAll("[position-query]"));
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
function initElements(elements) {
|
|
@@ -12,7 +16,26 @@ function initElements(elements) {
|
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
function initElement(element) {
|
|
15
|
-
|
|
19
|
+
if (!element.position)
|
|
20
|
+
element.position = new position(element, element.parentElement);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function initPositionTriggers(elements) {
|
|
24
|
+
for (const trigger of elements) {
|
|
25
|
+
if (positionTriggers.has(trigger)) continue;
|
|
26
|
+
positionTriggers.add(trigger);
|
|
27
|
+
trigger.addEventListener("click", () => {
|
|
28
|
+
requestAnimationFrame(() => {
|
|
29
|
+
const targets = queryElements({ element: trigger, prefix: "position" });
|
|
30
|
+
const anchors = queryElements({ element: trigger, prefix: "position-anchor" });
|
|
31
|
+
const anchor = anchors[0] || trigger;
|
|
32
|
+
for (const target of targets) {
|
|
33
|
+
initElement(target);
|
|
34
|
+
target.position.place(anchor);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
16
39
|
}
|
|
17
40
|
|
|
18
41
|
observer.init({
|
|
@@ -24,6 +47,16 @@ observer.init({
|
|
|
24
47
|
}
|
|
25
48
|
});
|
|
26
49
|
|
|
50
|
+
observer.init({
|
|
51
|
+
name: "positionQueryAddedNodes",
|
|
52
|
+
types: ["addedNodes", "attributes"],
|
|
53
|
+
selector: "[position-query]",
|
|
54
|
+
attributeFilter: ["position-query"],
|
|
55
|
+
callback: (mutation) => {
|
|
56
|
+
initPositionTriggers([mutation.target]);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
27
60
|
observer.init({
|
|
28
61
|
name: "positionAttributes",
|
|
29
62
|
types: ["attributes"],
|
package/src/position.js
CHANGED
|
@@ -37,6 +37,18 @@ function position(el, container, options) {
|
|
|
37
37
|
this._selectElement()
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
const INTERACTIVE_SELECTOR = [
|
|
41
|
+
"a",
|
|
42
|
+
"button",
|
|
43
|
+
"input",
|
|
44
|
+
"select",
|
|
45
|
+
"textarea",
|
|
46
|
+
"label",
|
|
47
|
+
"summary",
|
|
48
|
+
"[contenteditable]:not([contenteditable='false'])",
|
|
49
|
+
"[moveable-ignore]"
|
|
50
|
+
].join(",");
|
|
51
|
+
|
|
40
52
|
position.prototype = {
|
|
41
53
|
constructor: position,
|
|
42
54
|
|
|
@@ -58,38 +70,60 @@ position.prototype = {
|
|
|
58
70
|
if (this.dragHandle)
|
|
59
71
|
positionableEl = this.dragHandle
|
|
60
72
|
|
|
73
|
+
this._documentMouseMove = function (e) {
|
|
74
|
+
if (!self.clickedInfo?.isMoving) return;
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
self._onMove(e);
|
|
77
|
+
};
|
|
78
|
+
this._documentMouseUp = function (e) {
|
|
79
|
+
if (!self.clickedInfo?.isMoving) return;
|
|
80
|
+
self._onUp(e);
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
};
|
|
83
|
+
this._documentTouchMove = function (e) {
|
|
84
|
+
if (!self.clickedInfo?.isMoving || !e.touches.length) return;
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
self._onMove(e.touches[0]);
|
|
87
|
+
};
|
|
88
|
+
this._documentTouchEnd = function (e) {
|
|
89
|
+
if (!self.clickedInfo?.isMoving) return;
|
|
90
|
+
self._onUp(e.changedTouches?.[0]);
|
|
91
|
+
};
|
|
92
|
+
this._documentBlur = function () {
|
|
93
|
+
self._onUp();
|
|
94
|
+
};
|
|
95
|
+
|
|
61
96
|
positionableEl.addEventListener('touchstart', function (e) {
|
|
62
97
|
self._selectElement(e.detail.element)
|
|
63
98
|
self._onDown(e.touches[0]);
|
|
99
|
+
if (self.clickedInfo?.isMoving) self._bindDocumentDrag();
|
|
64
100
|
})
|
|
65
101
|
|
|
66
102
|
positionableEl.addEventListener('mousedown', function (e) {
|
|
67
103
|
self._selectElement(e.detail.element)
|
|
68
104
|
self._onDown(e);
|
|
105
|
+
if (self.clickedInfo?.isMoving) self._bindDocumentDrag();
|
|
69
106
|
})
|
|
70
107
|
|
|
71
|
-
|
|
72
|
-
if (self.clickedInfo && self.clickedInfo.isMoving) {
|
|
73
|
-
e.preventDefault();
|
|
74
|
-
self._onMove(e);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
positionableEl.addEventListener('mouseup', function (e) {
|
|
79
|
-
if (self.clickedInfo && self.clickedInfo.isMoving) {
|
|
80
|
-
self._onUp(e);
|
|
81
|
-
e.preventDefault();
|
|
82
|
-
}
|
|
83
|
-
}, true);
|
|
84
|
-
|
|
85
|
-
positionableEl.addEventListener('touchmove', function (e) {
|
|
86
|
-
self._onMove(e.touches[0]);
|
|
87
|
-
}, { passive: false });
|
|
108
|
+
},
|
|
88
109
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
110
|
+
_bindDocumentDrag: function () {
|
|
111
|
+
this._unbindDocumentDrag();
|
|
112
|
+
document.addEventListener('mousemove', this._documentMouseMove);
|
|
113
|
+
document.addEventListener('mouseup', this._documentMouseUp, true);
|
|
114
|
+
document.addEventListener('touchmove', this._documentTouchMove, { passive: false });
|
|
115
|
+
document.addEventListener('touchend', this._documentTouchEnd, true);
|
|
116
|
+
document.addEventListener('touchcancel', this._documentTouchEnd, true);
|
|
117
|
+
window.addEventListener('blur', this._documentBlur);
|
|
118
|
+
},
|
|
92
119
|
|
|
120
|
+
_unbindDocumentDrag: function () {
|
|
121
|
+
document.removeEventListener('mousemove', this._documentMouseMove);
|
|
122
|
+
document.removeEventListener('mouseup', this._documentMouseUp, true);
|
|
123
|
+
document.removeEventListener('touchmove', this._documentTouchMove);
|
|
124
|
+
document.removeEventListener('touchend', this._documentTouchEnd, true);
|
|
125
|
+
document.removeEventListener('touchcancel', this._documentTouchEnd, true);
|
|
126
|
+
window.removeEventListener('blur', this._documentBlur);
|
|
93
127
|
},
|
|
94
128
|
|
|
95
129
|
_isMovable() {
|
|
@@ -99,7 +133,105 @@ position.prototype = {
|
|
|
99
133
|
return this.point.x > 0 && this.point.x < width && this.point.y > 0 && this.point.y < this.dragAreaHeight;
|
|
100
134
|
},
|
|
101
135
|
|
|
136
|
+
place: function (anchor = this.el, options = {}) {
|
|
137
|
+
if (!anchor?.getBoundingClientRect) return;
|
|
138
|
+
|
|
139
|
+
const placement = options.placement ||
|
|
140
|
+
anchor.getAttribute("position-placement") ||
|
|
141
|
+
this.el.getAttribute("position-placement") ||
|
|
142
|
+
"bottom-start";
|
|
143
|
+
const offset = Number(options.offset ??
|
|
144
|
+
anchor.getAttribute("position-offset") ??
|
|
145
|
+
this.el.getAttribute("position-offset") ?? 8);
|
|
146
|
+
const padding = Number(options.padding ??
|
|
147
|
+
anchor.getAttribute("position-padding") ??
|
|
148
|
+
this.el.getAttribute("position-padding") ?? 8);
|
|
149
|
+
const collision = (options.collision ||
|
|
150
|
+
anchor.getAttribute("position-collision") ||
|
|
151
|
+
this.el.getAttribute("position-collision") ||
|
|
152
|
+
"flip shift").split(/[\s,]+/);
|
|
153
|
+
|
|
154
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
155
|
+
const elementRect = this.el.getBoundingClientRect();
|
|
156
|
+
const parentRect = this.container.getBoundingClientRect();
|
|
157
|
+
const isFixed = getComputedStyle(this.el).position === "fixed";
|
|
158
|
+
const boundary = isFixed
|
|
159
|
+
? { left: 0, top: 0, right: innerWidth, bottom: innerHeight }
|
|
160
|
+
: parentRect;
|
|
161
|
+
let [side, align = "center"] = placement.split("-");
|
|
162
|
+
|
|
163
|
+
const getCoordinates = (currentSide) => {
|
|
164
|
+
let left;
|
|
165
|
+
let top;
|
|
166
|
+
if (currentSide === "top" || currentSide === "bottom") {
|
|
167
|
+
top = currentSide === "top"
|
|
168
|
+
? anchorRect.top - elementRect.height - offset
|
|
169
|
+
: anchorRect.bottom + offset;
|
|
170
|
+
left = align === "start"
|
|
171
|
+
? anchorRect.left
|
|
172
|
+
: align === "end"
|
|
173
|
+
? anchorRect.right - elementRect.width
|
|
174
|
+
: anchorRect.left + (anchorRect.width - elementRect.width) / 2;
|
|
175
|
+
} else {
|
|
176
|
+
left = currentSide === "left"
|
|
177
|
+
? anchorRect.left - elementRect.width - offset
|
|
178
|
+
: anchorRect.right + offset;
|
|
179
|
+
top = align === "start"
|
|
180
|
+
? anchorRect.top
|
|
181
|
+
: align === "end"
|
|
182
|
+
? anchorRect.bottom - elementRect.height
|
|
183
|
+
: anchorRect.top + (anchorRect.height - elementRect.height) / 2;
|
|
184
|
+
}
|
|
185
|
+
return { left, top };
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
let coordinates = getCoordinates(side);
|
|
189
|
+
if (collision.includes("flip")) {
|
|
190
|
+
const opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
191
|
+
const overflows =
|
|
192
|
+
(side === "top" && coordinates.top < boundary.top + padding) ||
|
|
193
|
+
(side === "bottom" && coordinates.top + elementRect.height > boundary.bottom - padding) ||
|
|
194
|
+
(side === "left" && coordinates.left < boundary.left + padding) ||
|
|
195
|
+
(side === "right" && coordinates.left + elementRect.width > boundary.right - padding);
|
|
196
|
+
if (overflows) {
|
|
197
|
+
side = opposite[side] || side;
|
|
198
|
+
coordinates = getCoordinates(side);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (collision.includes("shift")) {
|
|
203
|
+
coordinates.left = Math.min(
|
|
204
|
+
Math.max(coordinates.left, boundary.left + padding),
|
|
205
|
+
Math.max(boundary.left + padding, boundary.right - elementRect.width - padding)
|
|
206
|
+
);
|
|
207
|
+
coordinates.top = Math.min(
|
|
208
|
+
Math.max(coordinates.top, boundary.top + padding),
|
|
209
|
+
Math.max(boundary.top + padding, boundary.bottom - elementRect.height - padding)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.el.style.right = "auto";
|
|
214
|
+
this.el.style.bottom = "auto";
|
|
215
|
+
this.el.style.left = (isFixed
|
|
216
|
+
? coordinates.left
|
|
217
|
+
: coordinates.left - parentRect.left + this.container.scrollLeft) + "px";
|
|
218
|
+
this.el.style.top = (isFixed
|
|
219
|
+
? coordinates.top
|
|
220
|
+
: coordinates.top - parentRect.top + this.container.scrollTop) + "px";
|
|
221
|
+
this.isSnap = false;
|
|
222
|
+
this.__setRectInfo();
|
|
223
|
+
this.createEvent("element-positioned");
|
|
224
|
+
},
|
|
225
|
+
|
|
102
226
|
_onDown: function (e) {
|
|
227
|
+
// A title-bar style drag handle may contain controls. Those controls
|
|
228
|
+
// must retain their native click, focus, and file-picker behavior.
|
|
229
|
+
const interactive = e.target?.closest?.(INTERACTIVE_SELECTOR);
|
|
230
|
+
if (this.dragHandle && interactive && this.dragHandle.contains(interactive)) {
|
|
231
|
+
this.clickedInfo = null;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
103
235
|
this.__getBoundStatus(e);
|
|
104
236
|
|
|
105
237
|
this.clickedInfo = {
|
|
@@ -121,6 +253,7 @@ position.prototype = {
|
|
|
121
253
|
},
|
|
122
254
|
|
|
123
255
|
_onUp: function (e) {
|
|
256
|
+
this._unbindDocumentDrag();
|
|
124
257
|
if (!this.clickedInfo) {
|
|
125
258
|
return;
|
|
126
259
|
}
|
|
@@ -346,4 +479,4 @@ position.prototype = {
|
|
|
346
479
|
},
|
|
347
480
|
}
|
|
348
481
|
|
|
349
|
-
export default position;
|
|
482
|
+
export default position;
|