@cocreate/position 1.16.0 → 2.0.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/.github/FUNDING.yml +1 -1
- package/.github/workflows/automated.yml +18 -7
- package/CHANGELOG.md +309 -275
- package/CONTRIBUTING.md +6 -6
- package/README.md +210 -85
- package/demo/index.html +474 -46
- package/docs/index.html +7 -7
- package/package.json +21 -16
- package/src/autoScroll.js +213 -0
- package/src/draggable/engine.js +698 -0
- package/src/draggable.js +64 -0
- package/src/eventOperator.js +15 -0
- package/src/helpers/events.js +34 -0
- package/src/index.css +19 -0
- package/src/index.js +232 -71
- package/src/position.js +443 -482
- package/src/snappable.js +572 -0
- package/test/auto-scroll.browser.cjs +307 -0
- package/webpack.config.js +1 -0
package/src/position.js
CHANGED
|
@@ -1,482 +1,443 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
_ghostProcess: function (info) {
|
|
446
|
-
this.ghostEl.style.left = info.x + 'px';
|
|
447
|
-
this.ghostEl.style.top = info.y + 'px';
|
|
448
|
-
this.ghostEl.style.width = info.w + 'px';
|
|
449
|
-
this.ghostEl.style.height = info.h + 'px';
|
|
450
|
-
|
|
451
|
-
if (info.type === "show") {
|
|
452
|
-
if (this.ghostEl.style.display === 'none') {
|
|
453
|
-
this.ghostEl.style.display = 'block'
|
|
454
|
-
}
|
|
455
|
-
this.ghostEl.style.opacity = 0.2;
|
|
456
|
-
} else {
|
|
457
|
-
this.ghostEl.style.opacity = 0;
|
|
458
|
-
if (this.ghostEl.style.display !== 'none') {
|
|
459
|
-
this.ghostEl.style.display = 'none'
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return;
|
|
463
|
-
},
|
|
464
|
-
|
|
465
|
-
_selectElement: function (el) {
|
|
466
|
-
this._releaseSelect();
|
|
467
|
-
this.container['selectedPositionElement'] = this.el
|
|
468
|
-
if (this.el) {
|
|
469
|
-
this.createEvent('elementSelected');
|
|
470
|
-
this.el.style.zIndex = this.container.positionIndex += 1;
|
|
471
|
-
this.el.classList.add('selectedPosition');
|
|
472
|
-
}
|
|
473
|
-
},
|
|
474
|
-
|
|
475
|
-
_releaseSelect: function () {
|
|
476
|
-
let el = this.container['selectedPositionElement']
|
|
477
|
-
if (el)
|
|
478
|
-
el.classList.remove("selectedPosition");
|
|
479
|
-
},
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
export default position;
|
|
1
|
+
import observer from "@cocreate/observer";
|
|
2
|
+
import { queryElements } from "@cocreate/utils";
|
|
3
|
+
import { relayEvents } from "@cocreate/utils/iframe";
|
|
4
|
+
import { resolveEventOperator } from "./eventOperator.js";
|
|
5
|
+
import { dispatch } from "./helpers/events.js";
|
|
6
|
+
|
|
7
|
+
const placementStates = new WeakMap();
|
|
8
|
+
const triggerElements = new WeakMap();
|
|
9
|
+
const positionEventBindings = new WeakMap();
|
|
10
|
+
const positionFrames = new WeakMap();
|
|
11
|
+
const pendingPositionEvents = new WeakMap();
|
|
12
|
+
const positionConfigSelector = "[position-query], [position-events]";
|
|
13
|
+
|
|
14
|
+
function getQueryAttribute(element, name) {
|
|
15
|
+
return element?.getAttribute?.(name) ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getPositionEvents(value) {
|
|
19
|
+
const aliases = { hover: ["mouseover", "mousemove", "mouseout"] };
|
|
20
|
+
return [...new Set((value || "").split(",")
|
|
21
|
+
.flatMap(event => aliases[event.trim().toLowerCase()] || [event.trim()])
|
|
22
|
+
.filter(Boolean))];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function initPositionTargets(elements) {
|
|
26
|
+
for (const element of elements) attachPlacement(element);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function attachPlacement(element) {
|
|
30
|
+
if (!element?.matches) return;
|
|
31
|
+
const position = element.position || (element.position = {
|
|
32
|
+
el: element,
|
|
33
|
+
container: element.parentElement
|
|
34
|
+
});
|
|
35
|
+
position.el = element;
|
|
36
|
+
position.place = (anchor = element, options = {}) => place(element, anchor, options);
|
|
37
|
+
position.unplace = () => {
|
|
38
|
+
unplace(element);
|
|
39
|
+
element.hidden = true;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getEventSnapshot(event, trigger) {
|
|
44
|
+
const detail = event?.detail && typeof event.detail === "object" ? event.detail : {};
|
|
45
|
+
const originalEvent = detail.originalEvent || event;
|
|
46
|
+
const target = detail.target || originalEvent?.target || event?.target || null;
|
|
47
|
+
const cursor = detail.cursor || (
|
|
48
|
+
Number.isFinite(originalEvent?.clientX) && Number.isFinite(originalEvent?.clientY)
|
|
49
|
+
? { x: originalEvent.clientX, y: originalEvent.clientY }
|
|
50
|
+
: null
|
|
51
|
+
);
|
|
52
|
+
const sourceDocument = detail.document ||
|
|
53
|
+
(target?.nodeType === 9 ? target : target?.ownerDocument) ||
|
|
54
|
+
originalEvent?.view?.document || null;
|
|
55
|
+
const relayTarget = detail.originalEvent ? event.target : null;
|
|
56
|
+
const pointer = {};
|
|
57
|
+
for (const key of ["pointerId", "pointerType", "isPrimary", "button", "buttons", "pressure", "tiltX", "tiltY", "twist", "clientX", "clientY", "pageX", "pageY", "screenX", "screenY"]) {
|
|
58
|
+
if (originalEvent?.[key] !== undefined) pointer[key] = originalEvent[key];
|
|
59
|
+
}
|
|
60
|
+
const keyboard = {};
|
|
61
|
+
for (const key of ["key", "code", "repeat", "altKey", "ctrlKey", "metaKey", "shiftKey"]) {
|
|
62
|
+
if (originalEvent?.[key] !== undefined) keyboard[key] = originalEvent[key];
|
|
63
|
+
}
|
|
64
|
+
const input = {};
|
|
65
|
+
for (const key of ["inputType", "data", "dataTransfer"]) {
|
|
66
|
+
if (originalEvent?.[key] !== undefined) input[key] = originalEvent[key];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
type: event?.type || originalEvent?.type || "",
|
|
71
|
+
target,
|
|
72
|
+
relatedTarget: detail.relatedTarget || originalEvent?.relatedTarget || null,
|
|
73
|
+
relayTarget,
|
|
74
|
+
frameElement: sourceDocument?.defaultView?.frameElement ||
|
|
75
|
+
(relayTarget?.tagName === "IFRAME" ? relayTarget : null),
|
|
76
|
+
currentTarget: trigger,
|
|
77
|
+
document: sourceDocument,
|
|
78
|
+
timeStamp: originalEvent?.timeStamp ?? event?.timeStamp ?? null,
|
|
79
|
+
cursor,
|
|
80
|
+
pointer,
|
|
81
|
+
keyboard,
|
|
82
|
+
input,
|
|
83
|
+
detail,
|
|
84
|
+
originalEvent
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getRectSnapshot(rect) {
|
|
89
|
+
if (!rect) return null;
|
|
90
|
+
return {
|
|
91
|
+
x: rect.left,
|
|
92
|
+
y: rect.top,
|
|
93
|
+
top: rect.top,
|
|
94
|
+
right: rect.right,
|
|
95
|
+
bottom: rect.bottom,
|
|
96
|
+
left: rect.left,
|
|
97
|
+
width: rect.width,
|
|
98
|
+
height: rect.height
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function updatePositionContext(element, context) {
|
|
103
|
+
attachPlacement(element);
|
|
104
|
+
Object.assign(element.position, {
|
|
105
|
+
element: context.queryElement,
|
|
106
|
+
trigger: context.trigger,
|
|
107
|
+
event: context.event,
|
|
108
|
+
query: context.query,
|
|
109
|
+
queryElements: context.queryElements,
|
|
110
|
+
box: {
|
|
111
|
+
element,
|
|
112
|
+
type: context.boxType,
|
|
113
|
+
rect: context.rect || null
|
|
114
|
+
},
|
|
115
|
+
active: context.active
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function place(element, anchor, options = {}) {
|
|
120
|
+
if (!anchor?.getBoundingClientRect) return;
|
|
121
|
+
const anchorDocument = anchor.ownerDocument ||
|
|
122
|
+
anchor.startContainer?.ownerDocument || anchor.commonAncestorContainer?.ownerDocument;
|
|
123
|
+
if (!anchorDocument) return;
|
|
124
|
+
const targetDocument = element.ownerDocument;
|
|
125
|
+
const boxType = options.box || getQueryAttribute(element, "position-box") || "element";
|
|
126
|
+
const rect = getPositionBox(anchor, boxType, targetDocument);
|
|
127
|
+
if (!rect) return false;
|
|
128
|
+
element.hidden = false;
|
|
129
|
+
|
|
130
|
+
let state = placementStates.get(element);
|
|
131
|
+
if (!state) placementStates.set(element, state = {});
|
|
132
|
+
state.options = options;
|
|
133
|
+
if (state.anchor !== anchor || state.boxType !== boxType) {
|
|
134
|
+
unplace(element);
|
|
135
|
+
state.anchor = anchor;
|
|
136
|
+
state.boxType = boxType;
|
|
137
|
+
state.documents = getDocumentChain(anchorDocument, targetDocument);
|
|
138
|
+
state.update = () => {
|
|
139
|
+
if (state.frame) return;
|
|
140
|
+
const view = targetDocument.defaultView;
|
|
141
|
+
state.frame = view.requestAnimationFrame(() => {
|
|
142
|
+
state.frame = null;
|
|
143
|
+
const currentAnchor = state.anchor;
|
|
144
|
+
const isConnected = currentAnchor?.isConnected ??
|
|
145
|
+
currentAnchor?.startContainer?.isConnected ??
|
|
146
|
+
currentAnchor?.commonAncestorContainer?.isConnected;
|
|
147
|
+
if (!isConnected || !place(element, currentAnchor, state.options))
|
|
148
|
+
element.hidden = true;
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
for (const document of state.documents) {
|
|
152
|
+
document.addEventListener("scroll", state.update, true);
|
|
153
|
+
document.defaultView?.addEventListener("resize", state.update);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const view = targetDocument.defaultView;
|
|
158
|
+
const isFixed = view.getComputedStyle(element).position === "fixed";
|
|
159
|
+
const offsetParent = element.offsetParent;
|
|
160
|
+
const parentRect = offsetParent?.getBoundingClientRect();
|
|
161
|
+
element.style.right = "auto";
|
|
162
|
+
element.style.bottom = "auto";
|
|
163
|
+
element.style.left = (isFixed || !parentRect
|
|
164
|
+
? rect.left
|
|
165
|
+
: rect.left - parentRect.left - offsetParent.clientLeft + offsetParent.scrollLeft) + "px";
|
|
166
|
+
element.style.top = (isFixed || !parentRect
|
|
167
|
+
? rect.top
|
|
168
|
+
: rect.top - parentRect.top - offsetParent.clientTop + offsetParent.scrollTop) + "px";
|
|
169
|
+
element.style.width = `${rect.width}px`;
|
|
170
|
+
element.style.height = `${rect.height}px`;
|
|
171
|
+
if (element.position) {
|
|
172
|
+
element.position.isSnap = false;
|
|
173
|
+
if (options.context) {
|
|
174
|
+
updatePositionContext(element, {
|
|
175
|
+
...options.context,
|
|
176
|
+
rect: getRectSnapshot(rect),
|
|
177
|
+
active: true
|
|
178
|
+
});
|
|
179
|
+
} else {
|
|
180
|
+
updatePositionContext(element, {
|
|
181
|
+
trigger: null,
|
|
182
|
+
event: null,
|
|
183
|
+
query: { selector: null, elements: [anchor], element: anchor },
|
|
184
|
+
queryElement: anchor,
|
|
185
|
+
queryElements: [anchor],
|
|
186
|
+
boxType,
|
|
187
|
+
rect: getRectSnapshot(rect),
|
|
188
|
+
active: true
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
dispatch(element, "element-positioned");
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function mapRectToDocument(rect, sourceDocument, targetDocument) {
|
|
197
|
+
let document = sourceDocument;
|
|
198
|
+
if (!document || !targetDocument) return null;
|
|
199
|
+
while (document !== targetDocument) {
|
|
200
|
+
const frame = document.defaultView?.frameElement;
|
|
201
|
+
if (!frame) return null;
|
|
202
|
+
const frameRect = frame.getBoundingClientRect();
|
|
203
|
+
const scaleX = frame.offsetWidth ? frameRect.width / frame.offsetWidth : 1;
|
|
204
|
+
const scaleY = frame.offsetHeight ? frameRect.height / frame.offsetHeight : 1;
|
|
205
|
+
rect = {
|
|
206
|
+
left: frameRect.left + (frame.clientLeft + rect.left) * scaleX,
|
|
207
|
+
top: frameRect.top + (frame.clientTop + rect.top) * scaleY,
|
|
208
|
+
right: frameRect.left + (frame.clientLeft + rect.right) * scaleX,
|
|
209
|
+
bottom: frameRect.top + (frame.clientTop + rect.bottom) * scaleY,
|
|
210
|
+
width: rect.width * scaleX,
|
|
211
|
+
height: rect.height * scaleY
|
|
212
|
+
};
|
|
213
|
+
document = frame.ownerDocument;
|
|
214
|
+
}
|
|
215
|
+
return rect;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getDocumentChain(sourceDocument, targetDocument) {
|
|
219
|
+
const documents = [];
|
|
220
|
+
let document = sourceDocument;
|
|
221
|
+
while (document) {
|
|
222
|
+
documents.push(document);
|
|
223
|
+
if (document === targetDocument) return documents;
|
|
224
|
+
const frame = document.defaultView?.frameElement;
|
|
225
|
+
if (!frame) break;
|
|
226
|
+
document = frame.ownerDocument;
|
|
227
|
+
}
|
|
228
|
+
return documents;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getRectInDocument(target, targetDocument) {
|
|
232
|
+
const sourceDocument = target.ownerDocument ||
|
|
233
|
+
target.startContainer?.ownerDocument || target.commonAncestorContainer?.ownerDocument;
|
|
234
|
+
return mapRectToDocument(target.getBoundingClientRect(), sourceDocument, targetDocument);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function getElementBoxInDocument(element, targetDocument) {
|
|
238
|
+
const rect = element.getBoundingClientRect();
|
|
239
|
+
const style = element.ownerDocument.defaultView.getComputedStyle(element);
|
|
240
|
+
const margin = name => Number.parseFloat(style[name]) || 0;
|
|
241
|
+
const left = rect.left - margin("marginLeft");
|
|
242
|
+
const top = rect.top - margin("marginTop");
|
|
243
|
+
const right = rect.right + margin("marginRight");
|
|
244
|
+
const bottom = rect.bottom + margin("marginBottom");
|
|
245
|
+
return mapRectToDocument({ left, top, right, bottom, width: right - left, height: bottom - top },
|
|
246
|
+
element.ownerDocument, targetDocument);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getTextBoxInDocument(element, targetDocument) {
|
|
250
|
+
const document = element.ownerDocument;
|
|
251
|
+
const walker = document.createTreeWalker(element, document.defaultView.NodeFilter.SHOW_TEXT);
|
|
252
|
+
const rects = [];
|
|
253
|
+
let textNode;
|
|
254
|
+
while ((textNode = walker.nextNode())) {
|
|
255
|
+
if (!textNode.nodeValue) continue;
|
|
256
|
+
const range = document.createRange();
|
|
257
|
+
range.selectNodeContents(textNode);
|
|
258
|
+
for (const rect of range.getClientRects()) {
|
|
259
|
+
if (rect.width || rect.height) rects.push(rect);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (!rects.length) return null;
|
|
263
|
+
const left = Math.min(...rects.map(rect => rect.left));
|
|
264
|
+
const top = Math.min(...rects.map(rect => rect.top));
|
|
265
|
+
const right = Math.max(...rects.map(rect => rect.right));
|
|
266
|
+
const bottom = Math.max(...rects.map(rect => rect.bottom));
|
|
267
|
+
return mapRectToDocument({ left, top, right, bottom, width: right - left, height: bottom - top },
|
|
268
|
+
document, targetDocument);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function getPositionBox(anchor, type, targetDocument) {
|
|
272
|
+
if (anchor.nodeType === 1) {
|
|
273
|
+
if (String(type).toLowerCase() === "text") return getTextBoxInDocument(anchor, targetDocument);
|
|
274
|
+
return getElementBoxInDocument(anchor, targetDocument);
|
|
275
|
+
}
|
|
276
|
+
return getRectInDocument(anchor, targetDocument);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function deepest(elements) {
|
|
280
|
+
return elements.reduce((current, element) =>
|
|
281
|
+
!current || current.contains(element) ? element : current, null);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function placeFromTrigger(trigger) {
|
|
285
|
+
const event = pendingPositionEvents.get(trigger) || null;
|
|
286
|
+
pendingPositionEvents.delete(trigger);
|
|
287
|
+
const anchorQuery = getQueryAttribute(trigger, "position-query");
|
|
288
|
+
const elementQuery = getQueryAttribute(trigger, "position-element");
|
|
289
|
+
const previousElements = triggerElements.get(trigger) || new Set();
|
|
290
|
+
if (!elementQuery) {
|
|
291
|
+
for (const element of previousElements) clearPlacement(element);
|
|
292
|
+
triggerElements.delete(trigger);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const eventQuery = resolveEventOperator(anchorQuery, event);
|
|
296
|
+
const selectionOperator = eventQuery.handled ? null : anchorQuery?.match(/\$selection/);
|
|
297
|
+
let anchor;
|
|
298
|
+
let matchedElements = [];
|
|
299
|
+
if (eventQuery.handled) {
|
|
300
|
+
matchedElements = eventQuery.value
|
|
301
|
+
? queryElements({ element: eventQuery.value, selector: eventQuery.selector })
|
|
302
|
+
: [];
|
|
303
|
+
anchor = matchedElements[0];
|
|
304
|
+
} else if (selectionOperator) {
|
|
305
|
+
const prefix = anchorQuery.slice(0, selectionOperator.index).trim();
|
|
306
|
+
const sources = prefix ? queryElements({ element: trigger, selector: prefix }) : [trigger];
|
|
307
|
+
matchedElements = sources;
|
|
308
|
+
for (const source of sources) {
|
|
309
|
+
const sourceDocument = source.nodeType === 9
|
|
310
|
+
? source
|
|
311
|
+
: source.contentDocument || source.ownerDocument;
|
|
312
|
+
const selection = sourceDocument?.getSelection?.();
|
|
313
|
+
if (selection && !selection.isCollapsed && selection.rangeCount) {
|
|
314
|
+
anchor = selection.getRangeAt(0).cloneRange();
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
matchedElements = anchorQuery
|
|
320
|
+
? queryElements({ element: trigger, selector: anchorQuery })
|
|
321
|
+
: [trigger];
|
|
322
|
+
anchor = anchorQuery?.includes(":hover") ? deepest(matchedElements) : matchedElements[0];
|
|
323
|
+
}
|
|
324
|
+
const elements = queryElements({ element: trigger, selector: elementQuery });
|
|
325
|
+
const currentElements = new Set(elements);
|
|
326
|
+
for (const element of previousElements) {
|
|
327
|
+
if (!currentElements.has(element)) clearPlacement(element);
|
|
328
|
+
}
|
|
329
|
+
triggerElements.set(trigger, currentElements);
|
|
330
|
+
if (!elementQuery) return;
|
|
331
|
+
const options = { box: getQueryAttribute(trigger, "position-box") || "element" };
|
|
332
|
+
const context = {
|
|
333
|
+
trigger,
|
|
334
|
+
event,
|
|
335
|
+
query: { selector: anchorQuery, elements: matchedElements, element: anchor },
|
|
336
|
+
queryElement: anchor,
|
|
337
|
+
queryElements: matchedElements,
|
|
338
|
+
boxType: options.box,
|
|
339
|
+
rect: null,
|
|
340
|
+
active: Boolean(anchor)
|
|
341
|
+
};
|
|
342
|
+
options.context = context;
|
|
343
|
+
for (const element of elements) {
|
|
344
|
+
updatePositionContext(element, context);
|
|
345
|
+
if (!anchor || !element.position.place(anchor, options)) clearPlacement(element);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function clearPlacement(element) {
|
|
350
|
+
if (!element) return;
|
|
351
|
+
element.position?.unplace?.();
|
|
352
|
+
element.hidden = true;
|
|
353
|
+
if (element.position) element.position.active = false;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function schedulePositionUpdate(element, event) {
|
|
357
|
+
pendingPositionEvents.set(element, event);
|
|
358
|
+
if (positionFrames.has(element)) return;
|
|
359
|
+
const frame = element.ownerDocument.defaultView.requestAnimationFrame(() => {
|
|
360
|
+
positionFrames.delete(element);
|
|
361
|
+
placeFromTrigger(element);
|
|
362
|
+
});
|
|
363
|
+
positionFrames.set(element, frame);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function initPositionEvents(element) {
|
|
367
|
+
const value = getQueryAttribute(element, "position-events")?.trim() || "";
|
|
368
|
+
const existing = positionEventBindings.get(element);
|
|
369
|
+
if (!element.hasAttribute("position-query")) {
|
|
370
|
+
existing?.remove();
|
|
371
|
+
positionEventBindings.delete(element);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const key = value;
|
|
375
|
+
if (existing?.key === key) return;
|
|
376
|
+
existing?.remove();
|
|
377
|
+
const events = getPositionEvents(value);
|
|
378
|
+
if (!events.length) {
|
|
379
|
+
positionEventBindings.delete(element);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const update = event => schedulePositionUpdate(element, getEventSnapshot(event, element));
|
|
383
|
+
for (const event of events) element.addEventListener(event, update, true);
|
|
384
|
+
positionEventBindings.set(element, {
|
|
385
|
+
key,
|
|
386
|
+
remove: () => events.forEach(event => element.removeEventListener(event, update, true))
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function unplace(element) {
|
|
391
|
+
const state = placementStates.get(element);
|
|
392
|
+
if (!state?.update) return;
|
|
393
|
+
for (const document of state.documents || []) {
|
|
394
|
+
document.removeEventListener("scroll", state.update, true);
|
|
395
|
+
document.defaultView?.removeEventListener("resize", state.update);
|
|
396
|
+
}
|
|
397
|
+
if (state.frame) element.ownerDocument.defaultView?.cancelAnimationFrame(state.frame);
|
|
398
|
+
state.anchor = null;
|
|
399
|
+
state.boxType = null;
|
|
400
|
+
state.documents = null;
|
|
401
|
+
state.update = null;
|
|
402
|
+
state.frame = null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function initTriggers(elements) {
|
|
406
|
+
for (const element of elements) initPositionEvents(element);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function init() {
|
|
410
|
+
refresh(document.documentElement);
|
|
411
|
+
relayEvents(document.querySelectorAll("iframe"));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function refresh(node) {
|
|
415
|
+
if (node?.nodeType !== 1) return;
|
|
416
|
+
initTriggers([node]);
|
|
417
|
+
initTriggers(node.querySelectorAll(positionConfigSelector));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
observer.init({
|
|
421
|
+
name: "positionIframeEventRelayAddedNodes",
|
|
422
|
+
types: ["addedNodes"],
|
|
423
|
+
selector: "iframe",
|
|
424
|
+
callback: mutation => relayEvents([mutation.target])
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
observer.init({
|
|
428
|
+
name: "positionPlacementAddedNodes",
|
|
429
|
+
types: ["addedNodes"],
|
|
430
|
+
selector: positionConfigSelector,
|
|
431
|
+
callback: mutation => refresh(mutation.target)
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
observer.init({
|
|
435
|
+
name: "positionPlacementAttributes",
|
|
436
|
+
types: ["attributes"],
|
|
437
|
+
attributeFilter: ["position-query", "position-element", "position-events", "position-box"],
|
|
438
|
+
callback: mutation => refresh(mutation.target)
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
init();
|
|
442
|
+
|
|
443
|
+
export default { init, refresh, initPositionTargets };
|