@cocreate/position 1.13.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 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://github.com/CoCreate-app/CoCreate-position.git
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.13.0",
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://github.com/CoCreate-app/CoCreate-position.git"
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.21.0"
39
+ "@cocreate/observer": "^1.23.0",
40
+ "@cocreate/utils": "^1.54.0"
40
41
  },
41
42
  "devDependencies": {
42
- "@cocreate/webpack": "^1.7.0"
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
- element.position = new position(element, element.parentElement);
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
- positionableEl.addEventListener('mousemove', function (e) {
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
- positionableEl.addEventListener('touchend', function (e) {
90
- self._onUp(e.touches[0]);
91
- }, { passive: false });
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;