@jsenv/dom 0.9.1 → 0.9.3
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/dist/jsenv_dom.js +408 -370
- package/package.json +2 -2
package/dist/jsenv_dom.js
CHANGED
|
@@ -385,7 +385,8 @@ const areSameRGBA = (first, second) => {
|
|
|
385
385
|
};
|
|
386
386
|
const resolveCSSColor = (color, element) => {
|
|
387
387
|
const rgba = parseCSSColor(color, element);
|
|
388
|
-
|
|
388
|
+
const colorString = stringifyCSSColor(rgba);
|
|
389
|
+
return colorString;
|
|
389
390
|
};
|
|
390
391
|
|
|
391
392
|
/**
|
|
@@ -440,12 +441,27 @@ const parseCSSColor = (color, element) => {
|
|
|
440
441
|
return color;
|
|
441
442
|
}
|
|
442
443
|
|
|
444
|
+
// oklab(L a b) and oklab(L a b / alpha)
|
|
445
|
+
if (color.startsWith("oklab(")) {
|
|
446
|
+
const oklabMatch = color.match(
|
|
447
|
+
/^oklab\(\s*([\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)(?:\s*\/\s*([\d.]+))?\s*\)$/,
|
|
448
|
+
);
|
|
449
|
+
if (oklabMatch) {
|
|
450
|
+
const L = parseFloat(oklabMatch[1]);
|
|
451
|
+
const a = parseFloat(oklabMatch[2]);
|
|
452
|
+
const b = parseFloat(oklabMatch[3]);
|
|
453
|
+
const alpha = oklabMatch[4] !== undefined ? parseFloat(oklabMatch[4]) : 1;
|
|
454
|
+
const [r, g, bChannel] = oklabToRgb(L, a, b);
|
|
455
|
+
return [r, g, bChannel, alpha];
|
|
456
|
+
}
|
|
457
|
+
return color;
|
|
458
|
+
}
|
|
459
|
+
|
|
443
460
|
// Pass through CSS color functions we don't handle
|
|
444
461
|
if (
|
|
445
462
|
color.startsWith("lch(") ||
|
|
446
463
|
color.startsWith("oklch(") ||
|
|
447
464
|
color.startsWith("lab(") ||
|
|
448
|
-
color.startsWith("oklab(") ||
|
|
449
465
|
color.startsWith("hwb(") ||
|
|
450
466
|
color.includes("color-contrast(")
|
|
451
467
|
) {
|
|
@@ -505,6 +521,27 @@ const parseCSSColor = (color, element) => {
|
|
|
505
521
|
const rgba = convertColorToRgba(resolvedColor);
|
|
506
522
|
return rgba;
|
|
507
523
|
};
|
|
524
|
+
const oklabToRgb = (L, a, b) => {
|
|
525
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
526
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
527
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
528
|
+
const l = l_ * l_ * l_;
|
|
529
|
+
const m = m_ * m_ * m_;
|
|
530
|
+
const s = s_ * s_ * s_;
|
|
531
|
+
const rLinear = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
532
|
+
const gLinear = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
533
|
+
const bLinear = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
|
|
534
|
+
const toSrgb = (linear) => {
|
|
535
|
+
const clamped = linear < 0 ? 0 : linear > 1 ? 1 : linear;
|
|
536
|
+
const srgb =
|
|
537
|
+
clamped <= 0.0031308
|
|
538
|
+
? 12.92 * clamped
|
|
539
|
+
: 1.055 * Math.pow(clamped, 1 / 2.4) - 0.055;
|
|
540
|
+
return Math.round(srgb * 255);
|
|
541
|
+
};
|
|
542
|
+
return [toSrgb(rLinear), toSrgb(gLinear), toSrgb(bLinear)];
|
|
543
|
+
};
|
|
544
|
+
|
|
508
545
|
/**
|
|
509
546
|
* Converts HSL color to RGB
|
|
510
547
|
* @param {number} h - Hue (0-360)
|
|
@@ -637,12 +674,10 @@ const stringifyCSSColor = (value) => {
|
|
|
637
674
|
}
|
|
638
675
|
const rgba = value;
|
|
639
676
|
const [r, g, b, a = 1] = rgba;
|
|
640
|
-
|
|
641
677
|
// Validate RGB values
|
|
642
678
|
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
|
|
643
679
|
return null;
|
|
644
680
|
}
|
|
645
|
-
|
|
646
681
|
// Validate alpha value
|
|
647
682
|
if (a < 0 || a > 1) {
|
|
648
683
|
return null;
|
|
@@ -660,10 +695,7 @@ const stringifyCSSColor = (value) => {
|
|
|
660
695
|
return name;
|
|
661
696
|
}
|
|
662
697
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
// Use rgb() for opaque colors, rgba() for transparent
|
|
666
|
-
if (a === 1) {
|
|
698
|
+
// Use rgb() for opaque colors, rgba() for transparent
|
|
667
699
|
return `rgb(${rInt}, ${gInt}, ${bInt})`;
|
|
668
700
|
}
|
|
669
701
|
if (a === 0 && rInt === 0 && gInt === 0 && bInt === 0) {
|
|
@@ -4034,12 +4066,6 @@ const performArrowNavigation = (
|
|
|
4034
4066
|
}
|
|
4035
4067
|
|
|
4036
4068
|
const onTargetToFocus = (targetToFocus) => {
|
|
4037
|
-
console.debug(
|
|
4038
|
-
`Arrow navigation: ${event.key} from`,
|
|
4039
|
-
activeElement,
|
|
4040
|
-
"to",
|
|
4041
|
-
targetToFocus,
|
|
4042
|
-
);
|
|
4043
4069
|
event.preventDefault();
|
|
4044
4070
|
markFocusNav(event);
|
|
4045
4071
|
targetToFocus.focus();
|
|
@@ -6299,23 +6325,52 @@ const getDragCoordinates = (
|
|
|
6299
6325
|
return [leftRelativeToScrollContainer, topRelativeToScrollContainer];
|
|
6300
6326
|
};
|
|
6301
6327
|
|
|
6302
|
-
const
|
|
6303
|
-
const
|
|
6328
|
+
const installImportMetaCssBuild = (importMeta) => {
|
|
6329
|
+
const IMPORT_META_CSS_BUILD = "jsenv_import_meta_css_build";
|
|
6330
|
+
|
|
6331
|
+
if (importMeta.css === IMPORT_META_CSS_BUILD) {
|
|
6332
|
+
return;
|
|
6333
|
+
}
|
|
6334
|
+
|
|
6335
|
+
const stylesheetMap = new Map();
|
|
6336
|
+
const adopt = (url, value) => {
|
|
6337
|
+
const stylesheet = new CSSStyleSheet({ baseUrl: importMeta.url });
|
|
6338
|
+
stylesheet.replaceSync(value);
|
|
6339
|
+
stylesheetMap.set(url, stylesheet);
|
|
6340
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
|
|
6341
|
+
};
|
|
6342
|
+
const update = (url, value) => {
|
|
6343
|
+
stylesheetMap.get(url).replaceSync(value);
|
|
6344
|
+
};
|
|
6345
|
+
const remove = (url) => {
|
|
6346
|
+
const stylesheet = stylesheetMap.get(url);
|
|
6347
|
+
document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
|
|
6348
|
+
(s) => s !== stylesheet,
|
|
6349
|
+
);
|
|
6350
|
+
stylesheetMap.delete(url);
|
|
6351
|
+
};
|
|
6304
6352
|
|
|
6305
|
-
|
|
6306
|
-
// eslint-disable-next-line accessor-pairs
|
|
6353
|
+
const currentCssSourceMap = new Map();
|
|
6307
6354
|
Object.defineProperty(importMeta, "css", {
|
|
6308
6355
|
configurable: true,
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6356
|
+
get() {
|
|
6357
|
+
return IMPORT_META_CSS_BUILD;
|
|
6358
|
+
},
|
|
6359
|
+
set([value, url]) {
|
|
6360
|
+
if (value === undefined) {
|
|
6361
|
+
if (stylesheetMap.has(url)) {
|
|
6362
|
+
remove(url);
|
|
6363
|
+
currentCssSourceMap.delete(url);
|
|
6364
|
+
}
|
|
6365
|
+
return;
|
|
6366
|
+
}
|
|
6367
|
+
if (!stylesheetMap.has(url)) {
|
|
6368
|
+
adopt(url, value);
|
|
6369
|
+
currentCssSourceMap.set(url, value);
|
|
6370
|
+
} else if (currentCssSourceMap.get(url) !== value) {
|
|
6371
|
+
update(url, value);
|
|
6372
|
+
currentCssSourceMap.set(url, value);
|
|
6373
|
+
}
|
|
6319
6374
|
},
|
|
6320
6375
|
});
|
|
6321
6376
|
};
|
|
@@ -6479,7 +6534,16 @@ const isolateInteractions = (elements) => {
|
|
|
6479
6534
|
};
|
|
6480
6535
|
};
|
|
6481
6536
|
|
|
6482
|
-
|
|
6537
|
+
installImportMetaCssBuild(import.meta);/**
|
|
6538
|
+
* Drag Gesture System
|
|
6539
|
+
*
|
|
6540
|
+
* TODO: rename moveX/moveY en juste x/y
|
|
6541
|
+
* puisque move c'est perturbant sachant que c'est drag + scroll
|
|
6542
|
+
* et que drag c'est juste la partie mouvement de la souris
|
|
6543
|
+
*
|
|
6544
|
+
* donc juste x/y ca seras surement mieux
|
|
6545
|
+
*
|
|
6546
|
+
*/
|
|
6483
6547
|
const createDragGestureController = (options = {}) => {
|
|
6484
6548
|
const {
|
|
6485
6549
|
name,
|
|
@@ -6488,17 +6552,18 @@ const createDragGestureController = (options = {}) => {
|
|
|
6488
6552
|
onDrag,
|
|
6489
6553
|
onRelease,
|
|
6490
6554
|
threshold = 5,
|
|
6491
|
-
direction: defaultDirection = {
|
|
6555
|
+
direction: defaultDirection = {
|
|
6556
|
+
x: true,
|
|
6557
|
+
y: true
|
|
6558
|
+
},
|
|
6492
6559
|
documentInteractions = "auto",
|
|
6493
6560
|
backdrop = true,
|
|
6494
|
-
backdropZIndex = 999999
|
|
6561
|
+
backdropZIndex = 999999
|
|
6495
6562
|
} = options;
|
|
6496
|
-
|
|
6497
6563
|
const dragGestureController = {
|
|
6498
6564
|
grab: null,
|
|
6499
|
-
gravViaPointer: null
|
|
6565
|
+
gravViaPointer: null
|
|
6500
6566
|
};
|
|
6501
|
-
|
|
6502
6567
|
const grab = ({
|
|
6503
6568
|
element,
|
|
6504
6569
|
direction = defaultDirection,
|
|
@@ -6508,7 +6573,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
6508
6573
|
cursor = "grabbing",
|
|
6509
6574
|
scrollContainer = document.documentElement,
|
|
6510
6575
|
layoutScrollableLeft: scrollableLeftAtGrab = 0,
|
|
6511
|
-
layoutScrollableTop: scrollableTopAtGrab = 0
|
|
6576
|
+
layoutScrollableTop: scrollableTopAtGrab = 0
|
|
6512
6577
|
} = {}) => {
|
|
6513
6578
|
if (!element) {
|
|
6514
6579
|
throw new Error("element is required");
|
|
@@ -6516,7 +6581,6 @@ const createDragGestureController = (options = {}) => {
|
|
|
6516
6581
|
if (!direction.x && !direction.y) {
|
|
6517
6582
|
return null;
|
|
6518
6583
|
}
|
|
6519
|
-
|
|
6520
6584
|
const [publishBeforeDrag, addBeforeDragCallback] = createPubSub();
|
|
6521
6585
|
const [publishDrag, addDragCallback] = createPubSub();
|
|
6522
6586
|
const [publishRelease, addReleaseCallback] = createPubSub();
|
|
@@ -6526,13 +6590,15 @@ const createDragGestureController = (options = {}) => {
|
|
|
6526
6590
|
if (onRelease) {
|
|
6527
6591
|
addReleaseCallback(onRelease);
|
|
6528
6592
|
}
|
|
6529
|
-
|
|
6530
6593
|
const scrollLeftAtGrab = scrollContainer.scrollLeft;
|
|
6531
6594
|
const scrollTopAtGrab = scrollContainer.scrollTop;
|
|
6532
6595
|
const leftAtGrab = scrollLeftAtGrab + scrollableLeftAtGrab;
|
|
6533
6596
|
const topAtGrab = scrollTopAtGrab + scrollableTopAtGrab;
|
|
6534
6597
|
const createLayout = (x, y) => {
|
|
6535
|
-
const {
|
|
6598
|
+
const {
|
|
6599
|
+
scrollLeft,
|
|
6600
|
+
scrollTop
|
|
6601
|
+
} = scrollContainer;
|
|
6536
6602
|
const left = scrollableLeftAtGrab + x;
|
|
6537
6603
|
const top = scrollableTopAtGrab + y;
|
|
6538
6604
|
const scrollableLeft = left - scrollLeft;
|
|
@@ -6552,42 +6618,38 @@ const createDragGestureController = (options = {}) => {
|
|
|
6552
6618
|
top,
|
|
6553
6619
|
// Delta since grab (number representing how much we dragged)
|
|
6554
6620
|
xDelta: left - leftAtGrab,
|
|
6555
|
-
yDelta: top - topAtGrab
|
|
6621
|
+
yDelta: top - topAtGrab
|
|
6556
6622
|
};
|
|
6557
6623
|
return layoutProps;
|
|
6558
6624
|
};
|
|
6559
|
-
|
|
6560
|
-
const grabLayout = createLayout(
|
|
6561
|
-
grabX + scrollContainer.scrollLeft,
|
|
6562
|
-
grabY + scrollContainer.scrollTop,
|
|
6563
|
-
);
|
|
6625
|
+
const grabLayout = createLayout(grabX + scrollContainer.scrollLeft, grabY + scrollContainer.scrollTop);
|
|
6564
6626
|
const gestureInfo = {
|
|
6565
6627
|
name,
|
|
6566
6628
|
direction,
|
|
6567
6629
|
started: !threshold,
|
|
6568
6630
|
status: "grabbed",
|
|
6569
|
-
|
|
6570
6631
|
element,
|
|
6571
6632
|
scrollContainer,
|
|
6572
|
-
grabX,
|
|
6573
|
-
|
|
6633
|
+
grabX,
|
|
6634
|
+
// x grab coordinate (excluding scroll)
|
|
6635
|
+
grabY,
|
|
6636
|
+
// y grab coordinate (excluding scroll)
|
|
6574
6637
|
grabLayout,
|
|
6575
6638
|
leftAtGrab,
|
|
6576
6639
|
topAtGrab,
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
dragY: grabY,
|
|
6640
|
+
dragX: grabX,
|
|
6641
|
+
// coordinate of the last drag (excluding scroll of the scrollContainer)
|
|
6642
|
+
dragY: grabY,
|
|
6643
|
+
// coordinate of the last drag (excluding scroll of the scrollContainer)
|
|
6580
6644
|
layout: grabLayout,
|
|
6581
|
-
|
|
6582
6645
|
isGoingUp: undefined,
|
|
6583
6646
|
isGoingDown: undefined,
|
|
6584
6647
|
isGoingLeft: undefined,
|
|
6585
6648
|
isGoingRight: undefined,
|
|
6586
|
-
|
|
6587
6649
|
// metadata about interaction sources
|
|
6588
6650
|
grabEvent: event,
|
|
6589
6651
|
dragEvent: null,
|
|
6590
|
-
releaseEvent: null
|
|
6652
|
+
releaseEvent: null
|
|
6591
6653
|
};
|
|
6592
6654
|
definePropertyAsReadOnly(gestureInfo, "name");
|
|
6593
6655
|
definePropertyAsReadOnly(gestureInfo, "direction");
|
|
@@ -6598,7 +6660,6 @@ const createDragGestureController = (options = {}) => {
|
|
|
6598
6660
|
definePropertyAsReadOnly(gestureInfo, "leftAtGrab");
|
|
6599
6661
|
definePropertyAsReadOnly(gestureInfo, "topAtGrab");
|
|
6600
6662
|
definePropertyAsReadOnly(gestureInfo, "grabEvent");
|
|
6601
|
-
|
|
6602
6663
|
document_interactions: {
|
|
6603
6664
|
if (documentInteractions === "manual") {
|
|
6604
6665
|
break document_interactions;
|
|
@@ -6611,23 +6672,18 @@ const createDragGestureController = (options = {}) => {
|
|
|
6611
6672
|
2. Break the visual feedback (inconsistent cursors, hover states)
|
|
6612
6673
|
3. Cause unwanted scrolling (keyboard shortcuts, wheel events in restricted directions)
|
|
6613
6674
|
4. Create accessibility issues (focus jumping, screen reader confusion)
|
|
6614
|
-
|
|
6615
|
-
STRATEGY: Create a controlled interaction environment by:
|
|
6675
|
+
STRATEGY: Create a controlled interaction environment by:
|
|
6616
6676
|
1. VISUAL CONTROL: Use a backdrop to unify cursor appearance and block pointer events
|
|
6617
6677
|
2. INTERACTION ISOLATION: Make non-dragged elements inert to prevent interference
|
|
6618
6678
|
3. FOCUS MANAGEMENT: Control focus location and prevent focus changes during drag
|
|
6619
6679
|
4. SELECTIVE SCROLLING: Allow scrolling only in directions supported by the drag gesture
|
|
6620
|
-
|
|
6621
|
-
IMPLEMENTATION:
|
|
6680
|
+
IMPLEMENTATION:
|
|
6622
6681
|
*/
|
|
6623
6682
|
|
|
6624
6683
|
// 1. INTERACTION ISOLATION: Make everything except the dragged element inert
|
|
6625
6684
|
// This prevents keyboard events, pointer interactions, and screen reader navigation
|
|
6626
6685
|
// on non-relevant elements during the drag operation
|
|
6627
|
-
const cleanupInert = isolateInteractions([
|
|
6628
|
-
element,
|
|
6629
|
-
...Array.from(document.querySelectorAll("[data-droppable]")),
|
|
6630
|
-
]);
|
|
6686
|
+
const cleanupInert = isolateInteractions([element, ...Array.from(document.querySelectorAll("[data-droppable]"))]);
|
|
6631
6687
|
addReleaseCallback(() => {
|
|
6632
6688
|
cleanupInert();
|
|
6633
6689
|
});
|
|
@@ -6644,14 +6700,14 @@ const createDragGestureController = (options = {}) => {
|
|
|
6644
6700
|
// Handle wheel events on backdrop for directionally-constrained drag gestures
|
|
6645
6701
|
// (e.g., table column resize should only allow horizontal scrolling)
|
|
6646
6702
|
if (!direction.x || !direction.y) {
|
|
6647
|
-
backdropElement.onwheel =
|
|
6703
|
+
backdropElement.onwheel = e => {
|
|
6648
6704
|
e.preventDefault();
|
|
6649
6705
|
const scrollX = direction.x ? e.deltaX : 0;
|
|
6650
6706
|
const scrollY = direction.y ? e.deltaY : 0;
|
|
6651
6707
|
scrollContainer.scrollBy({
|
|
6652
6708
|
left: scrollX,
|
|
6653
6709
|
top: scrollY,
|
|
6654
|
-
behavior: "auto"
|
|
6710
|
+
behavior: "auto"
|
|
6655
6711
|
});
|
|
6656
6712
|
};
|
|
6657
6713
|
}
|
|
@@ -6662,23 +6718,25 @@ const createDragGestureController = (options = {}) => {
|
|
|
6662
6718
|
}
|
|
6663
6719
|
|
|
6664
6720
|
// 3. FOCUS MANAGEMENT: Control and stabilize focus during drag
|
|
6665
|
-
const {
|
|
6721
|
+
const {
|
|
6722
|
+
activeElement
|
|
6723
|
+
} = document;
|
|
6666
6724
|
const focusableElement = findFocusable(element);
|
|
6667
6725
|
// Focus the dragged element (or document.body as fallback) to establish clear focus context
|
|
6668
6726
|
// This also ensure any keydown event listened by the currently focused element
|
|
6669
6727
|
// won't be available during drag
|
|
6670
6728
|
const elementToFocus = focusableElement || document.body;
|
|
6671
6729
|
elementToFocus.focus({
|
|
6672
|
-
preventScroll: true
|
|
6730
|
+
preventScroll: true
|
|
6673
6731
|
});
|
|
6674
6732
|
addReleaseCallback(() => {
|
|
6675
6733
|
// Restore original focus on release
|
|
6676
6734
|
activeElement.focus({
|
|
6677
|
-
preventScroll: true
|
|
6735
|
+
preventScroll: true
|
|
6678
6736
|
});
|
|
6679
6737
|
});
|
|
6680
6738
|
// Prevent Tab navigation entirely (focus should stay stable)
|
|
6681
|
-
const onkeydown =
|
|
6739
|
+
const onkeydown = e => {
|
|
6682
6740
|
if (e.key === "Tab") {
|
|
6683
6741
|
e.preventDefault();
|
|
6684
6742
|
return;
|
|
@@ -6691,27 +6749,16 @@ const createDragGestureController = (options = {}) => {
|
|
|
6691
6749
|
|
|
6692
6750
|
// 4. SELECTIVE SCROLLING: Allow keyboard scrolling only in supported directions
|
|
6693
6751
|
{
|
|
6694
|
-
const onDocumentKeydown =
|
|
6752
|
+
const onDocumentKeydown = keyboardEvent => {
|
|
6695
6753
|
// Vertical scrolling keys - prevent if vertical movement not supported
|
|
6696
|
-
if (
|
|
6697
|
-
keyboardEvent.key === "ArrowUp" ||
|
|
6698
|
-
keyboardEvent.key === "ArrowDown" ||
|
|
6699
|
-
keyboardEvent.key === " " ||
|
|
6700
|
-
keyboardEvent.key === "PageUp" ||
|
|
6701
|
-
keyboardEvent.key === "PageDown" ||
|
|
6702
|
-
keyboardEvent.key === "Home" ||
|
|
6703
|
-
keyboardEvent.key === "End"
|
|
6704
|
-
) {
|
|
6754
|
+
if (keyboardEvent.key === "ArrowUp" || keyboardEvent.key === "ArrowDown" || keyboardEvent.key === " " || keyboardEvent.key === "PageUp" || keyboardEvent.key === "PageDown" || keyboardEvent.key === "Home" || keyboardEvent.key === "End") {
|
|
6705
6755
|
if (!direction.y) {
|
|
6706
6756
|
keyboardEvent.preventDefault();
|
|
6707
6757
|
}
|
|
6708
6758
|
return;
|
|
6709
6759
|
}
|
|
6710
6760
|
// Horizontal scrolling keys - prevent if horizontal movement not supported
|
|
6711
|
-
if (
|
|
6712
|
-
keyboardEvent.key === "ArrowLeft" ||
|
|
6713
|
-
keyboardEvent.key === "ArrowRight"
|
|
6714
|
-
) {
|
|
6761
|
+
if (keyboardEvent.key === "ArrowLeft" || keyboardEvent.key === "ArrowRight") {
|
|
6715
6762
|
if (!direction.x) {
|
|
6716
6763
|
keyboardEvent.preventDefault();
|
|
6717
6764
|
}
|
|
@@ -6728,36 +6775,38 @@ const createDragGestureController = (options = {}) => {
|
|
|
6728
6775
|
// Set up scroll event handling to adjust drag position when scrolling occurs
|
|
6729
6776
|
{
|
|
6730
6777
|
let isHandlingScroll = false;
|
|
6731
|
-
const handleScroll =
|
|
6778
|
+
const handleScroll = scrollEvent => {
|
|
6732
6779
|
if (isHandlingScroll) {
|
|
6733
6780
|
return;
|
|
6734
6781
|
}
|
|
6735
6782
|
isHandlingScroll = true;
|
|
6736
|
-
drag(gestureInfo.dragX, gestureInfo.dragY, {
|
|
6783
|
+
drag(gestureInfo.dragX, gestureInfo.dragY, {
|
|
6784
|
+
event: scrollEvent
|
|
6785
|
+
});
|
|
6737
6786
|
isHandlingScroll = false;
|
|
6738
6787
|
};
|
|
6739
|
-
const scrollEventReceiver =
|
|
6740
|
-
scrollContainer === document.documentElement
|
|
6741
|
-
? document
|
|
6742
|
-
: scrollContainer;
|
|
6788
|
+
const scrollEventReceiver = scrollContainer === document.documentElement ? document : scrollContainer;
|
|
6743
6789
|
scrollEventReceiver.addEventListener("scroll", handleScroll, {
|
|
6744
|
-
passive: true
|
|
6790
|
+
passive: true
|
|
6745
6791
|
});
|
|
6746
6792
|
addReleaseCallback(() => {
|
|
6747
6793
|
scrollEventReceiver.removeEventListener("scroll", handleScroll, {
|
|
6748
|
-
passive: true
|
|
6794
|
+
passive: true
|
|
6749
6795
|
});
|
|
6750
6796
|
});
|
|
6751
6797
|
}
|
|
6752
|
-
|
|
6753
6798
|
const determineDragData = ({
|
|
6754
6799
|
dragX,
|
|
6755
6800
|
dragY,
|
|
6756
6801
|
dragEvent,
|
|
6757
|
-
isRelease = false
|
|
6802
|
+
isRelease = false
|
|
6758
6803
|
}) => {
|
|
6759
6804
|
// === ÉTAT INITIAL (au moment du grab) ===
|
|
6760
|
-
const {
|
|
6805
|
+
const {
|
|
6806
|
+
grabX,
|
|
6807
|
+
grabY,
|
|
6808
|
+
grabLayout
|
|
6809
|
+
} = gestureInfo;
|
|
6761
6810
|
// === CE QUI EST DEMANDÉ (où on veut aller) ===
|
|
6762
6811
|
// Calcul de la direction basé sur le mouvement précédent
|
|
6763
6812
|
// (ne tient pas compte du mouvement final une fois les contraintes appliquées)
|
|
@@ -6769,56 +6818,38 @@ const createDragGestureController = (options = {}) => {
|
|
|
6769
6818
|
const isGoingRight = dragX > currentDragX;
|
|
6770
6819
|
const isGoingUp = dragY < currentDragY;
|
|
6771
6820
|
const isGoingDown = dragY > currentDragY;
|
|
6772
|
-
|
|
6773
|
-
const
|
|
6774
|
-
? scrollContainer.scrollLeft + (dragX - grabX)
|
|
6775
|
-
: grabLayout.scrollLeft;
|
|
6776
|
-
const layoutYRequested = direction.y
|
|
6777
|
-
? scrollContainer.scrollTop + (dragY - grabY)
|
|
6778
|
-
: grabLayout.scrollTop;
|
|
6821
|
+
const layoutXRequested = direction.x ? scrollContainer.scrollLeft + (dragX - grabX) : grabLayout.scrollLeft;
|
|
6822
|
+
const layoutYRequested = direction.y ? scrollContainer.scrollTop + (dragY - grabY) : grabLayout.scrollTop;
|
|
6779
6823
|
const layoutRequested = createLayout(layoutXRequested, layoutYRequested);
|
|
6780
6824
|
const currentLayout = gestureInfo.layout;
|
|
6781
6825
|
let layout;
|
|
6782
|
-
if (
|
|
6783
|
-
layoutRequested.x === currentLayout.x &&
|
|
6784
|
-
layoutRequested.y === currentLayout.y
|
|
6785
|
-
) {
|
|
6826
|
+
if (layoutRequested.x === currentLayout.x && layoutRequested.y === currentLayout.y) {
|
|
6786
6827
|
layout = currentLayout;
|
|
6787
6828
|
} else {
|
|
6788
6829
|
// === APPLICATION DES CONTRAINTES ===
|
|
6789
6830
|
let layoutConstrained = layoutRequested;
|
|
6790
6831
|
const limitLayout = (left, top) => {
|
|
6791
|
-
layoutConstrained = createLayout(
|
|
6792
|
-
left === undefined
|
|
6793
|
-
? layoutConstrained.x
|
|
6794
|
-
: left - scrollableLeftAtGrab,
|
|
6795
|
-
top === undefined ? layoutConstrained.y : top - scrollableTopAtGrab,
|
|
6796
|
-
);
|
|
6832
|
+
layoutConstrained = createLayout(left === undefined ? layoutConstrained.x : left - scrollableLeftAtGrab, top === undefined ? layoutConstrained.y : top - scrollableTopAtGrab);
|
|
6797
6833
|
};
|
|
6798
|
-
|
|
6799
6834
|
publishBeforeDrag(layoutRequested, currentLayout, limitLayout, {
|
|
6800
6835
|
dragEvent,
|
|
6801
|
-
isRelease
|
|
6836
|
+
isRelease
|
|
6802
6837
|
});
|
|
6803
6838
|
// === ÉTAT FINAL ===
|
|
6804
6839
|
layout = layoutConstrained;
|
|
6805
6840
|
}
|
|
6806
|
-
|
|
6807
6841
|
const dragData = {
|
|
6808
6842
|
dragX,
|
|
6809
6843
|
dragY,
|
|
6810
6844
|
layout,
|
|
6811
|
-
|
|
6812
6845
|
isGoingLeft,
|
|
6813
6846
|
isGoingRight,
|
|
6814
6847
|
isGoingUp,
|
|
6815
6848
|
isGoingDown,
|
|
6816
|
-
|
|
6817
6849
|
status: isRelease ? "released" : "dragging",
|
|
6818
6850
|
dragEvent: isRelease ? gestureInfo.dragEvent : dragEvent,
|
|
6819
|
-
releaseEvent: isRelease ? dragEvent : null
|
|
6851
|
+
releaseEvent: isRelease ? dragEvent : null
|
|
6820
6852
|
};
|
|
6821
|
-
|
|
6822
6853
|
if (isRelease) {
|
|
6823
6854
|
return dragData;
|
|
6824
6855
|
}
|
|
@@ -6843,18 +6874,19 @@ const createDragGestureController = (options = {}) => {
|
|
|
6843
6874
|
}
|
|
6844
6875
|
return dragData;
|
|
6845
6876
|
};
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6877
|
+
const drag = (dragX = gestureInfo.dragX,
|
|
6878
|
+
// Scroll container relative X coordinate
|
|
6879
|
+
dragY = gestureInfo.dragY,
|
|
6880
|
+
// Scroll container relative Y coordinate
|
|
6881
|
+
{
|
|
6882
|
+
event = new CustomEvent("programmatic"),
|
|
6883
|
+
isRelease = false
|
|
6884
|
+
} = {}) => {
|
|
6853
6885
|
const dragData = determineDragData({
|
|
6854
6886
|
dragX,
|
|
6855
6887
|
dragY,
|
|
6856
6888
|
dragEvent: event,
|
|
6857
|
-
isRelease
|
|
6889
|
+
isRelease
|
|
6858
6890
|
});
|
|
6859
6891
|
const startedPrevious = gestureInfo.started;
|
|
6860
6892
|
const layoutPrevious = gestureInfo.layout;
|
|
@@ -6864,25 +6896,24 @@ const createDragGestureController = (options = {}) => {
|
|
|
6864
6896
|
onDragStart?.(gestureInfo);
|
|
6865
6897
|
}
|
|
6866
6898
|
const someLayoutChange = gestureInfo.layout !== layoutPrevious;
|
|
6867
|
-
publishDrag(
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
someLayoutChange,
|
|
6874
|
-
);
|
|
6899
|
+
publishDrag(gestureInfo,
|
|
6900
|
+
// we still publish drag event even when unchanged
|
|
6901
|
+
// because UI might need to adjust when document scrolls
|
|
6902
|
+
// even if nothing truly changes visually the element
|
|
6903
|
+
// can decide to stick to the scroll for example
|
|
6904
|
+
someLayoutChange);
|
|
6875
6905
|
};
|
|
6876
|
-
|
|
6877
6906
|
const release = ({
|
|
6878
6907
|
event = new CustomEvent("programmatic"),
|
|
6879
6908
|
releaseX = gestureInfo.dragX,
|
|
6880
|
-
releaseY = gestureInfo.dragY
|
|
6909
|
+
releaseY = gestureInfo.dragY
|
|
6881
6910
|
} = {}) => {
|
|
6882
|
-
drag(releaseX, releaseY, {
|
|
6911
|
+
drag(releaseX, releaseY, {
|
|
6912
|
+
event,
|
|
6913
|
+
isRelease: true
|
|
6914
|
+
});
|
|
6883
6915
|
publishRelease(gestureInfo);
|
|
6884
6916
|
};
|
|
6885
|
-
|
|
6886
6917
|
onGrab?.(gestureInfo);
|
|
6887
6918
|
const dragGesture = {
|
|
6888
6919
|
gestureInfo,
|
|
@@ -6890,12 +6921,11 @@ const createDragGestureController = (options = {}) => {
|
|
|
6890
6921
|
addDragCallback,
|
|
6891
6922
|
addReleaseCallback,
|
|
6892
6923
|
drag,
|
|
6893
|
-
release
|
|
6924
|
+
release
|
|
6894
6925
|
};
|
|
6895
6926
|
return dragGesture;
|
|
6896
6927
|
};
|
|
6897
6928
|
dragGestureController.grab = grab;
|
|
6898
|
-
|
|
6899
6929
|
const initDragByPointer = (grabEvent, dragOptions, initializer) => {
|
|
6900
6930
|
if (grabEvent.button !== undefined && grabEvent.button !== 0) {
|
|
6901
6931
|
return null;
|
|
@@ -6905,8 +6935,11 @@ const createDragGestureController = (options = {}) => {
|
|
|
6905
6935
|
// target is a text node
|
|
6906
6936
|
return null;
|
|
6907
6937
|
}
|
|
6908
|
-
const mouseEventCoords =
|
|
6909
|
-
const {
|
|
6938
|
+
const mouseEventCoords = mouseEvent => {
|
|
6939
|
+
const {
|
|
6940
|
+
clientX,
|
|
6941
|
+
clientY
|
|
6942
|
+
} = mouseEvent;
|
|
6910
6943
|
return [clientX, clientY];
|
|
6911
6944
|
};
|
|
6912
6945
|
const [grabX, grabY] = mouseEventCoords(grabEvent);
|
|
@@ -6914,37 +6947,39 @@ const createDragGestureController = (options = {}) => {
|
|
|
6914
6947
|
grabX,
|
|
6915
6948
|
grabY,
|
|
6916
6949
|
event: grabEvent,
|
|
6917
|
-
...dragOptions
|
|
6950
|
+
...dragOptions
|
|
6918
6951
|
});
|
|
6919
|
-
const dragViaPointer =
|
|
6952
|
+
const dragViaPointer = dragEvent => {
|
|
6920
6953
|
const [mouseDragX, mouseDragY] = mouseEventCoords(dragEvent);
|
|
6921
6954
|
dragGesture.drag(mouseDragX, mouseDragY, {
|
|
6922
|
-
event: dragEvent
|
|
6955
|
+
event: dragEvent
|
|
6923
6956
|
});
|
|
6924
6957
|
};
|
|
6925
|
-
const releaseViaPointer =
|
|
6958
|
+
const releaseViaPointer = mouseupEvent => {
|
|
6926
6959
|
const [mouseReleaseX, mouseReleaseY] = mouseEventCoords(mouseupEvent);
|
|
6927
6960
|
dragGesture.release({
|
|
6928
6961
|
event: mouseupEvent,
|
|
6929
6962
|
releaseX: mouseReleaseX,
|
|
6930
|
-
releaseY: mouseReleaseY
|
|
6963
|
+
releaseY: mouseReleaseY
|
|
6931
6964
|
});
|
|
6932
6965
|
};
|
|
6933
6966
|
dragGesture.dragViaPointer = dragViaPointer;
|
|
6934
6967
|
dragGesture.releaseViaPointer = releaseViaPointer;
|
|
6935
6968
|
const cleanup = initializer({
|
|
6936
6969
|
onMove: dragViaPointer,
|
|
6937
|
-
onRelease: releaseViaPointer
|
|
6970
|
+
onRelease: releaseViaPointer
|
|
6938
6971
|
});
|
|
6939
6972
|
dragGesture.addReleaseCallback(() => {
|
|
6940
6973
|
cleanup();
|
|
6941
6974
|
});
|
|
6942
6975
|
return dragGesture;
|
|
6943
6976
|
};
|
|
6944
|
-
|
|
6945
6977
|
const grabViaPointer = (grabEvent, options) => {
|
|
6946
6978
|
if (grabEvent.type === "pointerdown") {
|
|
6947
|
-
return initDragByPointer(grabEvent, options, ({
|
|
6979
|
+
return initDragByPointer(grabEvent, options, ({
|
|
6980
|
+
onMove,
|
|
6981
|
+
onRelease
|
|
6982
|
+
}) => {
|
|
6948
6983
|
const target = grabEvent.target;
|
|
6949
6984
|
target.setPointerCapture(grabEvent.pointerId);
|
|
6950
6985
|
target.addEventListener("lostpointercapture", onRelease);
|
|
@@ -6961,11 +6996,12 @@ const createDragGestureController = (options = {}) => {
|
|
|
6961
6996
|
});
|
|
6962
6997
|
}
|
|
6963
6998
|
if (grabEvent.type === "mousedown") {
|
|
6964
|
-
console.warn(
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6999
|
+
console.warn(`Received "mousedown" event, "pointerdown" events are recommended to perform drag gestures.`);
|
|
7000
|
+
return initDragByPointer(grabEvent, options, ({
|
|
7001
|
+
onMove,
|
|
7002
|
+
onRelease
|
|
7003
|
+
}) => {
|
|
7004
|
+
const onPointerUp = pointerEvent => {
|
|
6969
7005
|
// <button disabled> for example does not emit mouseup if we release mouse over it
|
|
6970
7006
|
// -> we add "pointerup" to catch mouseup occuring on disabled element
|
|
6971
7007
|
if (pointerEvent.pointerType === "mouse") {
|
|
@@ -6982,54 +7018,43 @@ const createDragGestureController = (options = {}) => {
|
|
|
6982
7018
|
};
|
|
6983
7019
|
});
|
|
6984
7020
|
}
|
|
6985
|
-
throw new Error(
|
|
6986
|
-
`Unsupported "${grabEvent.type}" evenet passed to grabViaPointer. "pointerdown" was expected.`,
|
|
6987
|
-
);
|
|
7021
|
+
throw new Error(`Unsupported "${grabEvent.type}" evenet passed to grabViaPointer. "pointerdown" was expected.`);
|
|
6988
7022
|
};
|
|
6989
7023
|
dragGestureController.grabViaPointer = grabViaPointer;
|
|
6990
|
-
|
|
6991
7024
|
return dragGestureController;
|
|
6992
7025
|
};
|
|
6993
|
-
|
|
6994
|
-
const dragAfterThreshold = (
|
|
6995
|
-
grabEvent,
|
|
6996
|
-
dragGestureInitializer,
|
|
6997
|
-
threshold,
|
|
6998
|
-
) => {
|
|
7026
|
+
const dragAfterThreshold = (grabEvent, dragGestureInitializer, threshold) => {
|
|
6999
7027
|
const significantDragGestureController = createDragGestureController({
|
|
7000
7028
|
threshold,
|
|
7001
7029
|
// allow interaction for this intermediate gesture:
|
|
7002
7030
|
// user should still be able to scroll or interact with the document
|
|
7003
7031
|
// only once the gesture is significant we take control
|
|
7004
7032
|
documentInteractions: "manual",
|
|
7005
|
-
onDragStart:
|
|
7033
|
+
onDragStart: gestureInfo => {
|
|
7006
7034
|
significantDragGesture.release(); // kill that gesture
|
|
7007
7035
|
const dragGesture = dragGestureInitializer();
|
|
7008
7036
|
dragGesture.dragViaPointer(gestureInfo.dragEvent);
|
|
7009
|
-
}
|
|
7037
|
+
}
|
|
7038
|
+
});
|
|
7039
|
+
const significantDragGesture = significantDragGestureController.grabViaPointer(grabEvent, {
|
|
7040
|
+
element: grabEvent.target
|
|
7010
7041
|
});
|
|
7011
|
-
const significantDragGesture =
|
|
7012
|
-
significantDragGestureController.grabViaPointer(grabEvent, {
|
|
7013
|
-
element: grabEvent.target,
|
|
7014
|
-
});
|
|
7015
7042
|
};
|
|
7016
|
-
|
|
7017
7043
|
const definePropertyAsReadOnly = (object, propertyName) => {
|
|
7018
7044
|
Object.defineProperty(object, propertyName, {
|
|
7019
7045
|
writable: false,
|
|
7020
|
-
value: object[propertyName]
|
|
7046
|
+
value: object[propertyName]
|
|
7021
7047
|
});
|
|
7022
7048
|
};
|
|
7023
|
-
|
|
7024
|
-
import.meta.css = /* css */ `
|
|
7049
|
+
import.meta.css = [/* css */`
|
|
7025
7050
|
.navi_drag_gesture_backdrop {
|
|
7026
7051
|
position: fixed;
|
|
7027
7052
|
inset: 0;
|
|
7028
7053
|
user-select: none;
|
|
7029
7054
|
}
|
|
7030
|
-
|
|
7055
|
+
`, "/src/interaction/drag/drag_gesture.js"];
|
|
7031
7056
|
|
|
7032
|
-
|
|
7057
|
+
installImportMetaCssBuild(import.meta);const setupConstraintFeedbackLine = () => {
|
|
7033
7058
|
const constraintFeedbackLine = createConstraintFeedbackLine();
|
|
7034
7059
|
|
|
7035
7060
|
// Track last known mouse position for constraint feedback line during scroll
|
|
@@ -7037,16 +7062,17 @@ installImportMetaCss(import.meta);const setupConstraintFeedbackLine = () => {
|
|
|
7037
7062
|
let lastMouseY = null;
|
|
7038
7063
|
|
|
7039
7064
|
// Internal function to update constraint feedback line
|
|
7040
|
-
const onDrag =
|
|
7041
|
-
const {
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7065
|
+
const onDrag = gestureInfo => {
|
|
7066
|
+
const {
|
|
7067
|
+
grabEvent,
|
|
7068
|
+
dragEvent
|
|
7069
|
+
} = gestureInfo;
|
|
7070
|
+
if (grabEvent.type === "programmatic" ||
|
|
7071
|
+
// dragEvent can be null when only mousedown without yet any mousemove
|
|
7072
|
+
!dragEvent || dragEvent.type === "programmatic") {
|
|
7046
7073
|
// programmatic drag
|
|
7047
7074
|
return;
|
|
7048
7075
|
}
|
|
7049
|
-
|
|
7050
7076
|
const mouseX = dragEvent.clientX;
|
|
7051
7077
|
const mouseY = dragEvent.clientY;
|
|
7052
7078
|
// Use last known position if current position not available (e.g., during scroll)
|
|
@@ -7059,7 +7085,6 @@ installImportMetaCss(import.meta);const setupConstraintFeedbackLine = () => {
|
|
|
7059
7085
|
// Store current mouse position for potential use during scroll
|
|
7060
7086
|
lastMouseX = mouseX;
|
|
7061
7087
|
lastMouseY = mouseY;
|
|
7062
|
-
|
|
7063
7088
|
const grabClientX = grabEvent.clientX;
|
|
7064
7089
|
const grabClientY = grabEvent.clientY;
|
|
7065
7090
|
|
|
@@ -7088,25 +7113,21 @@ installImportMetaCss(import.meta);const setupConstraintFeedbackLine = () => {
|
|
|
7088
7113
|
constraintFeedbackLine.style.opacity = `${maxOpacity * opacityFactor}`;
|
|
7089
7114
|
constraintFeedbackLine.setAttribute("data-visible", "");
|
|
7090
7115
|
};
|
|
7091
|
-
|
|
7092
7116
|
return {
|
|
7093
7117
|
onDrag,
|
|
7094
7118
|
onRelease: () => {
|
|
7095
7119
|
constraintFeedbackLine.remove();
|
|
7096
|
-
}
|
|
7120
|
+
}
|
|
7097
7121
|
};
|
|
7098
7122
|
};
|
|
7099
|
-
|
|
7100
7123
|
const createConstraintFeedbackLine = () => {
|
|
7101
7124
|
const line = document.createElement("div");
|
|
7102
7125
|
line.className = "navi_constraint_feedback_line";
|
|
7103
|
-
line.title =
|
|
7104
|
-
"Constraint feedback - shows distance between mouse and moving grab point";
|
|
7126
|
+
line.title = "Constraint feedback - shows distance between mouse and moving grab point";
|
|
7105
7127
|
document.body.appendChild(line);
|
|
7106
7128
|
return line;
|
|
7107
7129
|
};
|
|
7108
|
-
|
|
7109
|
-
import.meta.css = /* css */ `
|
|
7130
|
+
import.meta.css = [/* css */`
|
|
7110
7131
|
.navi_constraint_feedback_line {
|
|
7111
7132
|
position: fixed;
|
|
7112
7133
|
z-index: 9998;
|
|
@@ -7120,16 +7141,17 @@ import.meta.css = /* css */ `
|
|
|
7120
7141
|
.navi_constraint_feedback_line[data-visible] {
|
|
7121
7142
|
visibility: visible;
|
|
7122
7143
|
}
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
installImportMetaCss(import.meta);const MARKER_SIZE = 12;
|
|
7144
|
+
`, "/src/interaction/drag/constraint_feedback_line.js"];
|
|
7126
7145
|
|
|
7146
|
+
installImportMetaCssBuild(import.meta);// Keep visual markers (debug markers, obstacle markers, constraint feedback line) in DOM after drag ends
|
|
7147
|
+
const MARKER_SIZE = 12;
|
|
7127
7148
|
let currentDebugMarkers = [];
|
|
7128
7149
|
let currentConstraintMarkers = [];
|
|
7129
7150
|
let currentReferenceElementMarker = null;
|
|
7130
7151
|
let currentElementMarker = null;
|
|
7131
|
-
|
|
7132
|
-
|
|
7152
|
+
const setupDragDebugMarkers = (dragGesture, {
|
|
7153
|
+
referenceElement
|
|
7154
|
+
}) => {
|
|
7133
7155
|
// Clean up any existing persistent markers from previous drag gestures
|
|
7134
7156
|
{
|
|
7135
7157
|
// Remove any existing markers from previous gestures
|
|
@@ -7138,29 +7160,27 @@ const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
|
7138
7160
|
container.innerHTML = ""; // Clear all markers efficiently
|
|
7139
7161
|
}
|
|
7140
7162
|
}
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7163
|
+
const {
|
|
7164
|
+
direction,
|
|
7165
|
+
scrollContainer
|
|
7166
|
+
} = dragGesture.gestureInfo;
|
|
7144
7167
|
return {
|
|
7145
|
-
onConstraints: (
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7168
|
+
onConstraints: (constraints, {
|
|
7169
|
+
left,
|
|
7170
|
+
top,
|
|
7171
|
+
right,
|
|
7172
|
+
bottom,
|
|
7173
|
+
autoScrollArea
|
|
7174
|
+
}) => {
|
|
7149
7175
|
// Schedule removal of previous markers if they exist
|
|
7150
7176
|
const previousDebugMarkers = [...currentDebugMarkers];
|
|
7151
7177
|
const previousConstraintMarkers = [...currentConstraintMarkers];
|
|
7152
7178
|
const previousReferenceElementMarker = currentReferenceElementMarker;
|
|
7153
7179
|
const previousElementMarker = currentElementMarker;
|
|
7154
|
-
|
|
7155
|
-
if (
|
|
7156
|
-
previousDebugMarkers.length > 0 ||
|
|
7157
|
-
previousConstraintMarkers.length > 0 ||
|
|
7158
|
-
previousReferenceElementMarker ||
|
|
7159
|
-
previousElementMarker
|
|
7160
|
-
) {
|
|
7180
|
+
if (previousDebugMarkers.length > 0 || previousConstraintMarkers.length > 0 || previousReferenceElementMarker || previousElementMarker) {
|
|
7161
7181
|
setTimeout(() => {
|
|
7162
|
-
previousDebugMarkers.forEach(
|
|
7163
|
-
previousConstraintMarkers.forEach(
|
|
7182
|
+
previousDebugMarkers.forEach(marker => marker.remove());
|
|
7183
|
+
previousConstraintMarkers.forEach(marker => marker.remove());
|
|
7164
7184
|
if (previousReferenceElementMarker) {
|
|
7165
7185
|
previousReferenceElementMarker.remove();
|
|
7166
7186
|
}
|
|
@@ -7189,7 +7209,7 @@ const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
|
7189
7209
|
bottom,
|
|
7190
7210
|
scrollContainer,
|
|
7191
7211
|
label: elementLabel,
|
|
7192
|
-
color: elementColor
|
|
7212
|
+
color: elementColor
|
|
7193
7213
|
});
|
|
7194
7214
|
|
|
7195
7215
|
// Create reference element marker if reference element exists
|
|
@@ -7199,52 +7219,47 @@ const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
|
7199
7219
|
top,
|
|
7200
7220
|
right,
|
|
7201
7221
|
bottom,
|
|
7202
|
-
scrollContainer
|
|
7222
|
+
scrollContainer
|
|
7203
7223
|
});
|
|
7204
7224
|
}
|
|
7205
7225
|
|
|
7206
7226
|
// Collect all markers to be created, then merge duplicates
|
|
7207
7227
|
const markersToCreate = [];
|
|
7208
|
-
|
|
7209
7228
|
{
|
|
7210
7229
|
if (direction.x) {
|
|
7211
7230
|
markersToCreate.push({
|
|
7212
|
-
name: autoScrollArea.paddingLeft
|
|
7213
|
-
? `autoscroll.left + padding(${autoScrollArea.paddingLeft})`
|
|
7214
|
-
: "autoscroll.left",
|
|
7231
|
+
name: autoScrollArea.paddingLeft ? `autoscroll.left + padding(${autoScrollArea.paddingLeft})` : "autoscroll.left",
|
|
7215
7232
|
x: autoScrollArea.left,
|
|
7216
7233
|
y: 0,
|
|
7217
|
-
color: "0 128 0",
|
|
7218
|
-
|
|
7234
|
+
color: "0 128 0",
|
|
7235
|
+
// green
|
|
7236
|
+
side: "left"
|
|
7219
7237
|
});
|
|
7220
7238
|
markersToCreate.push({
|
|
7221
|
-
name: autoScrollArea.paddingRight
|
|
7222
|
-
? `autoscroll.right + padding(${autoScrollArea.paddingRight})`
|
|
7223
|
-
: "autoscroll.right",
|
|
7239
|
+
name: autoScrollArea.paddingRight ? `autoscroll.right + padding(${autoScrollArea.paddingRight})` : "autoscroll.right",
|
|
7224
7240
|
x: autoScrollArea.right,
|
|
7225
7241
|
y: 0,
|
|
7226
|
-
color: "0 128 0",
|
|
7227
|
-
|
|
7242
|
+
color: "0 128 0",
|
|
7243
|
+
// green
|
|
7244
|
+
side: "right"
|
|
7228
7245
|
});
|
|
7229
7246
|
}
|
|
7230
7247
|
if (direction.y) {
|
|
7231
7248
|
markersToCreate.push({
|
|
7232
|
-
name: autoScrollArea.paddingTop
|
|
7233
|
-
? `autoscroll.top + padding(${autoScrollArea.paddingTop})`
|
|
7234
|
-
: "autoscroll.top",
|
|
7249
|
+
name: autoScrollArea.paddingTop ? `autoscroll.top + padding(${autoScrollArea.paddingTop})` : "autoscroll.top",
|
|
7235
7250
|
x: 0,
|
|
7236
7251
|
y: autoScrollArea.top,
|
|
7237
|
-
color: "255 0 0",
|
|
7238
|
-
|
|
7252
|
+
color: "255 0 0",
|
|
7253
|
+
// red
|
|
7254
|
+
side: "top"
|
|
7239
7255
|
});
|
|
7240
7256
|
markersToCreate.push({
|
|
7241
|
-
name: autoScrollArea.paddingBottom
|
|
7242
|
-
? `autoscroll.bottom + padding(${autoScrollArea.paddingBottom})`
|
|
7243
|
-
: "autoscroll.bottom",
|
|
7257
|
+
name: autoScrollArea.paddingBottom ? `autoscroll.bottom + padding(${autoScrollArea.paddingBottom})` : "autoscroll.bottom",
|
|
7244
7258
|
x: 0,
|
|
7245
7259
|
y: autoScrollArea.bottom,
|
|
7246
|
-
color: "255 165 0",
|
|
7247
|
-
|
|
7260
|
+
color: "255 165 0",
|
|
7261
|
+
// orange
|
|
7262
|
+
side: "bottom"
|
|
7248
7263
|
});
|
|
7249
7264
|
}
|
|
7250
7265
|
}
|
|
@@ -7252,7 +7267,9 @@ const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
|
7252
7267
|
// Process each constraint individually to preserve names
|
|
7253
7268
|
for (const constraint of constraints) {
|
|
7254
7269
|
if (constraint.type === "bounds") {
|
|
7255
|
-
const {
|
|
7270
|
+
const {
|
|
7271
|
+
bounds
|
|
7272
|
+
} = constraint;
|
|
7256
7273
|
|
|
7257
7274
|
// Create individual markers for each bound with constraint name
|
|
7258
7275
|
if (direction.x) {
|
|
@@ -7261,8 +7278,9 @@ const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
|
7261
7278
|
name: `${constraint.name}.left`,
|
|
7262
7279
|
x: bounds.left,
|
|
7263
7280
|
y: 0,
|
|
7264
|
-
color: "128 0 128",
|
|
7265
|
-
|
|
7281
|
+
color: "128 0 128",
|
|
7282
|
+
// purple
|
|
7283
|
+
side: "left"
|
|
7266
7284
|
});
|
|
7267
7285
|
}
|
|
7268
7286
|
if (bounds.right !== undefined) {
|
|
@@ -7272,8 +7290,9 @@ const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
|
7272
7290
|
name: `${constraint.name}.right`,
|
|
7273
7291
|
x: bounds.right,
|
|
7274
7292
|
y: 0,
|
|
7275
|
-
color: "128 0 128",
|
|
7276
|
-
|
|
7293
|
+
color: "128 0 128",
|
|
7294
|
+
// purple
|
|
7295
|
+
side: "right"
|
|
7277
7296
|
});
|
|
7278
7297
|
}
|
|
7279
7298
|
}
|
|
@@ -7283,8 +7302,9 @@ const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
|
7283
7302
|
name: `${constraint.name}.top`,
|
|
7284
7303
|
x: 0,
|
|
7285
7304
|
y: bounds.top,
|
|
7286
|
-
color: "128 0 128",
|
|
7287
|
-
|
|
7305
|
+
color: "128 0 128",
|
|
7306
|
+
// purple
|
|
7307
|
+
side: "top"
|
|
7288
7308
|
});
|
|
7289
7309
|
}
|
|
7290
7310
|
if (bounds.bottom !== undefined) {
|
|
@@ -7294,37 +7314,28 @@ const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
|
7294
7314
|
name: `${constraint.name}.bottom`,
|
|
7295
7315
|
x: 0,
|
|
7296
7316
|
y: bounds.bottom,
|
|
7297
|
-
color: "128 0 128",
|
|
7298
|
-
|
|
7317
|
+
color: "128 0 128",
|
|
7318
|
+
// purple
|
|
7319
|
+
side: "bottom"
|
|
7299
7320
|
});
|
|
7300
7321
|
}
|
|
7301
7322
|
}
|
|
7302
7323
|
} else if (constraint.type === "obstacle") {
|
|
7303
|
-
const obstacleMarker = createObstacleMarker(
|
|
7304
|
-
constraint,
|
|
7305
|
-
scrollContainer,
|
|
7306
|
-
);
|
|
7324
|
+
const obstacleMarker = createObstacleMarker(constraint, scrollContainer);
|
|
7307
7325
|
currentConstraintMarkers.push(obstacleMarker);
|
|
7308
7326
|
}
|
|
7309
7327
|
}
|
|
7310
7328
|
|
|
7311
7329
|
// Create markers with merging for overlapping positions
|
|
7312
|
-
const createdMarkers = createMergedMarkers(
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
);
|
|
7316
|
-
currentDebugMarkers.push(
|
|
7317
|
-
...createdMarkers.filter((m) => m.type !== "constraint"),
|
|
7318
|
-
);
|
|
7319
|
-
currentConstraintMarkers.push(
|
|
7320
|
-
...createdMarkers.filter((m) => m.type === "constraint"),
|
|
7321
|
-
);
|
|
7330
|
+
const createdMarkers = createMergedMarkers(markersToCreate, scrollContainer);
|
|
7331
|
+
currentDebugMarkers.push(...createdMarkers.filter(m => m.type !== "constraint"));
|
|
7332
|
+
currentConstraintMarkers.push(...createdMarkers.filter(m => m.type === "constraint"));
|
|
7322
7333
|
},
|
|
7323
7334
|
onRelease: () => {
|
|
7324
7335
|
{
|
|
7325
7336
|
return;
|
|
7326
7337
|
}
|
|
7327
|
-
}
|
|
7338
|
+
}
|
|
7328
7339
|
};
|
|
7329
7340
|
};
|
|
7330
7341
|
|
|
@@ -7343,8 +7354,9 @@ const getMarkersContainer = () => {
|
|
|
7343
7354
|
// Convert document-relative coordinates to viewport coordinates for marker positioning
|
|
7344
7355
|
// Takes the scroll container into account for proper positioning relative to the container
|
|
7345
7356
|
const getDebugMarkerPos = (x, y, scrollContainer, side = null) => {
|
|
7346
|
-
const {
|
|
7347
|
-
|
|
7357
|
+
const {
|
|
7358
|
+
documentElement
|
|
7359
|
+
} = document;
|
|
7348
7360
|
const leftWithoutScroll = x - scrollContainer.scrollLeft;
|
|
7349
7361
|
const topWithoutScroll = y - scrollContainer.scrollTop;
|
|
7350
7362
|
let baseX;
|
|
@@ -7376,7 +7388,6 @@ const getDebugMarkerPos = (x, y, scrollContainer, side = null) => {
|
|
|
7376
7388
|
// For obstacles and other markers: use converted coordinates directly
|
|
7377
7389
|
return [baseX, baseY];
|
|
7378
7390
|
};
|
|
7379
|
-
|
|
7380
7391
|
const createMergedMarkers = (markersToCreate, scrollContainer) => {
|
|
7381
7392
|
const mergedMarkers = [];
|
|
7382
7393
|
const positionMap = new Map();
|
|
@@ -7384,7 +7395,6 @@ const createMergedMarkers = (markersToCreate, scrollContainer) => {
|
|
|
7384
7395
|
// Group markers by position and side
|
|
7385
7396
|
for (const marker of markersToCreate) {
|
|
7386
7397
|
const key = `${marker.x},${marker.y},${marker.side}`;
|
|
7387
|
-
|
|
7388
7398
|
if (!positionMap.has(key)) {
|
|
7389
7399
|
positionMap.set(key, []);
|
|
7390
7400
|
}
|
|
@@ -7402,43 +7412,36 @@ const createMergedMarkers = (markersToCreate, scrollContainer) => {
|
|
|
7402
7412
|
} else {
|
|
7403
7413
|
// Multiple markers at same position - merge labels
|
|
7404
7414
|
const firstMarker = markers[0];
|
|
7405
|
-
const combinedName = markers.map(
|
|
7415
|
+
const combinedName = markers.map(m => m.name).join(" + ");
|
|
7406
7416
|
|
|
7407
7417
|
// Use the first marker's color, or mix colors if needed
|
|
7408
|
-
const domMarker = createDebugMarker(
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
scrollContainer,
|
|
7414
|
-
);
|
|
7415
|
-
domMarker.type = markers.some((m) => m.name.includes("Bound"))
|
|
7416
|
-
? "constraint"
|
|
7417
|
-
: "visible";
|
|
7418
|
+
const domMarker = createDebugMarker({
|
|
7419
|
+
...firstMarker,
|
|
7420
|
+
name: combinedName
|
|
7421
|
+
}, scrollContainer);
|
|
7422
|
+
domMarker.type = markers.some(m => m.name.includes("Bound")) ? "constraint" : "visible";
|
|
7418
7423
|
mergedMarkers.push(domMarker);
|
|
7419
7424
|
}
|
|
7420
7425
|
}
|
|
7421
|
-
|
|
7422
7426
|
return mergedMarkers;
|
|
7423
7427
|
};
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7428
|
+
const createDebugMarker = ({
|
|
7429
|
+
name,
|
|
7430
|
+
x,
|
|
7431
|
+
y,
|
|
7432
|
+
color = "255 0 0",
|
|
7433
|
+
side
|
|
7434
|
+
}, scrollContainer) => {
|
|
7429
7435
|
// Convert coordinates from document-relative to viewport
|
|
7430
7436
|
const [viewportX, viewportY] = getDebugMarkerPos(x, y, scrollContainer, side);
|
|
7431
|
-
|
|
7432
7437
|
const marker = document.createElement("div");
|
|
7433
7438
|
marker.className = `navi_debug_marker`;
|
|
7434
7439
|
marker.setAttribute(`data-${side}`, "");
|
|
7435
7440
|
// Set the color as a CSS custom property
|
|
7436
7441
|
marker.style.setProperty("--marker-color", `rgb(${color})`);
|
|
7437
7442
|
// Position markers exactly at the boundary coordinates
|
|
7438
|
-
marker.style.left =
|
|
7439
|
-
|
|
7440
|
-
marker.style.top =
|
|
7441
|
-
side === "bottom" ? `${viewportY - MARKER_SIZE}px` : `${viewportY}px`;
|
|
7443
|
+
marker.style.left = side === "right" ? `${viewportX - MARKER_SIZE}px` : `${viewportX}px`;
|
|
7444
|
+
marker.style.top = side === "bottom" ? `${viewportY - MARKER_SIZE}px` : `${viewportY}px`;
|
|
7442
7445
|
marker.title = name;
|
|
7443
7446
|
|
|
7444
7447
|
// Add label
|
|
@@ -7446,7 +7449,6 @@ const createDebugMarker = (
|
|
|
7446
7449
|
label.className = `navi_debug_marker_label`;
|
|
7447
7450
|
label.textContent = name;
|
|
7448
7451
|
marker.appendChild(label);
|
|
7449
|
-
|
|
7450
7452
|
const container = getMarkersContainer();
|
|
7451
7453
|
container.appendChild(marker);
|
|
7452
7454
|
return marker;
|
|
@@ -7456,13 +7458,7 @@ const createObstacleMarker = (obstacleObj, scrollContainer) => {
|
|
|
7456
7458
|
const height = obstacleObj.bounds.bottom - obstacleObj.bounds.top;
|
|
7457
7459
|
|
|
7458
7460
|
// Convert document-relative coordinates to viewport coordinates
|
|
7459
|
-
const [x, y] = getDebugMarkerPos(
|
|
7460
|
-
obstacleObj.bounds.left,
|
|
7461
|
-
obstacleObj.bounds.top,
|
|
7462
|
-
scrollContainer,
|
|
7463
|
-
"obstacle",
|
|
7464
|
-
);
|
|
7465
|
-
|
|
7461
|
+
const [x, y] = getDebugMarkerPos(obstacleObj.bounds.left, obstacleObj.bounds.top, scrollContainer, "obstacle");
|
|
7466
7462
|
const marker = document.createElement("div");
|
|
7467
7463
|
marker.className = "navi_obstacle_marker";
|
|
7468
7464
|
marker.style.left = `${x}px`;
|
|
@@ -7476,12 +7472,10 @@ const createObstacleMarker = (obstacleObj, scrollContainer) => {
|
|
|
7476
7472
|
label.className = "navi_obstacle_marker_label";
|
|
7477
7473
|
label.textContent = obstacleObj.name;
|
|
7478
7474
|
marker.appendChild(label);
|
|
7479
|
-
|
|
7480
7475
|
const container = getMarkersContainer();
|
|
7481
7476
|
container.appendChild(marker);
|
|
7482
7477
|
return marker;
|
|
7483
7478
|
};
|
|
7484
|
-
|
|
7485
7479
|
const createElementMarker = ({
|
|
7486
7480
|
left,
|
|
7487
7481
|
top,
|
|
@@ -7489,13 +7483,12 @@ const createElementMarker = ({
|
|
|
7489
7483
|
bottom,
|
|
7490
7484
|
scrollContainer,
|
|
7491
7485
|
label = "Element",
|
|
7492
|
-
color = "0, 200, 0"
|
|
7486
|
+
color = "0, 200, 0" // Default green color
|
|
7493
7487
|
}) => {
|
|
7494
7488
|
const width = right - left;
|
|
7495
7489
|
const height = bottom - top;
|
|
7496
7490
|
// Convert document-relative coordinates to viewport coordinates
|
|
7497
7491
|
const [x, y] = getDebugMarkerPos(left, top, scrollContainer, "element");
|
|
7498
|
-
|
|
7499
7492
|
const marker = document.createElement("div");
|
|
7500
7493
|
marker.className = "navi_element_marker";
|
|
7501
7494
|
marker.style.left = `${x}px`;
|
|
@@ -7513,24 +7506,21 @@ const createElementMarker = ({
|
|
|
7513
7506
|
labelEl.className = "navi_element_marker_label";
|
|
7514
7507
|
labelEl.textContent = label;
|
|
7515
7508
|
marker.appendChild(labelEl);
|
|
7516
|
-
|
|
7517
7509
|
const container = getMarkersContainer();
|
|
7518
7510
|
container.appendChild(marker);
|
|
7519
7511
|
return marker;
|
|
7520
7512
|
};
|
|
7521
|
-
|
|
7522
7513
|
const createReferenceElementMarker = ({
|
|
7523
7514
|
left,
|
|
7524
7515
|
top,
|
|
7525
7516
|
right,
|
|
7526
7517
|
bottom,
|
|
7527
|
-
scrollContainer
|
|
7518
|
+
scrollContainer
|
|
7528
7519
|
}) => {
|
|
7529
7520
|
const width = right - left;
|
|
7530
7521
|
const height = bottom - top;
|
|
7531
7522
|
// Convert document-relative coordinates to viewport coordinates
|
|
7532
7523
|
const [x, y] = getDebugMarkerPos(left, top, scrollContainer, "reference");
|
|
7533
|
-
|
|
7534
7524
|
const marker = document.createElement("div");
|
|
7535
7525
|
marker.className = "navi_reference_element_marker";
|
|
7536
7526
|
marker.style.left = `${x}px`;
|
|
@@ -7544,13 +7534,11 @@ const createReferenceElementMarker = ({
|
|
|
7544
7534
|
label.className = "navi_reference_element_marker_label";
|
|
7545
7535
|
label.textContent = "Reference Element";
|
|
7546
7536
|
marker.appendChild(label);
|
|
7547
|
-
|
|
7548
7537
|
const container = getMarkersContainer();
|
|
7549
7538
|
container.appendChild(marker);
|
|
7550
7539
|
return marker;
|
|
7551
7540
|
};
|
|
7552
|
-
|
|
7553
|
-
import.meta.css = /* css */ `
|
|
7541
|
+
import.meta.css = [/* css */`
|
|
7554
7542
|
.navi_debug_markers_container {
|
|
7555
7543
|
position: fixed;
|
|
7556
7544
|
top: 0;
|
|
@@ -7735,7 +7723,7 @@ import.meta.css = /* css */ `
|
|
|
7735
7723
|
border-radius: 3px;
|
|
7736
7724
|
pointer-events: none;
|
|
7737
7725
|
}
|
|
7738
|
-
|
|
7726
|
+
`, "/src/interaction/drag/drag_debug_markers.js"];
|
|
7739
7727
|
|
|
7740
7728
|
const initDragConstraints = (
|
|
7741
7729
|
dragGesture,
|
|
@@ -8899,46 +8887,72 @@ const getWidth = (element) => {
|
|
|
8899
8887
|
return width;
|
|
8900
8888
|
};
|
|
8901
8889
|
|
|
8902
|
-
|
|
8903
|
-
|
|
8890
|
+
installImportMetaCssBuild(import.meta);/**
|
|
8891
|
+
* Position Sticky Polyfill
|
|
8892
|
+
*
|
|
8893
|
+
* This module provides a workaround for position:sticky limitations when used with
|
|
8894
|
+
* overflow:auto/hidden parent elements (see https://github.com/w3c/csswg-drafts/issues/865).
|
|
8895
|
+
*
|
|
8896
|
+
* How it works:
|
|
8897
|
+
* 1. Creates a placeholder clone of the sticky element to maintain document flow
|
|
8898
|
+
* 2. Positions the real element using fixed positioning relative to viewport
|
|
8899
|
+
* 3. Adjusts position on scroll to emulate position:sticky behavior
|
|
8900
|
+
* 4. Handles parent boundary detection to keep element within its container
|
|
8901
|
+
* 5. Updates dimensions on resize and DOM changes
|
|
8902
|
+
*
|
|
8903
|
+
* Usage:
|
|
8904
|
+
* ```
|
|
8905
|
+
* const cleanup = initPositionSticky(element);
|
|
8906
|
+
* // Later when no longer needed
|
|
8907
|
+
* cleanup();
|
|
8908
|
+
* ```
|
|
8909
|
+
*
|
|
8910
|
+
* The element should have a CSS "top" value specified (e.g., top: 10px).
|
|
8911
|
+
*/
|
|
8912
|
+
import.meta.css = [/* css */`
|
|
8904
8913
|
[data-position-sticky-placeholder] {
|
|
8905
8914
|
position: static !important;
|
|
8906
8915
|
width: auto !important;
|
|
8907
8916
|
height: auto !important;
|
|
8908
8917
|
opacity: 0 !important;
|
|
8909
8918
|
}
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
const initPositionSticky = (element) => {
|
|
8919
|
+
`, "/src/position/position_sticky.js"];
|
|
8920
|
+
const initPositionSticky = element => {
|
|
8913
8921
|
const computedStyle = getComputedStyle(element);
|
|
8914
8922
|
const topCssValue = computedStyle.top;
|
|
8915
8923
|
const top = parseFloat(topCssValue);
|
|
8916
|
-
|
|
8917
|
-
|
|
8924
|
+
const leftCssValue = computedStyle.left;
|
|
8925
|
+
const left = parseFloat(leftCssValue);
|
|
8926
|
+
const hasTop = !isNaN(top);
|
|
8927
|
+
const hasLeft = !isNaN(left);
|
|
8928
|
+
if (!hasTop && !hasLeft) {
|
|
8929
|
+
return () => {}; // Early return if no valid top or left value
|
|
8918
8930
|
}
|
|
8919
8931
|
|
|
8920
8932
|
// Skip polyfill if native position:sticky would work (no overflow:auto/hidden parents)
|
|
8921
8933
|
const scrollContainerSet = getScrollContainerSet(element);
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
break;
|
|
8935
|
-
}
|
|
8934
|
+
// Determine per-axis whether an intermediate container blocks native sticky.
|
|
8935
|
+
// Native sticky fails only when there is a scroll container between the element
|
|
8936
|
+
// and the document with overflow set on that axis.
|
|
8937
|
+
let xScrollContainer = null; // first intermediate container blocking horizontal sticky
|
|
8938
|
+
let yScrollContainer = null; // first intermediate container blocking vertical sticky
|
|
8939
|
+
for (const scrollContainer of scrollContainerSet) {
|
|
8940
|
+
if (scrollContainer === document.documentElement) {
|
|
8941
|
+
break;
|
|
8942
|
+
}
|
|
8943
|
+
const style = getComputedStyle(scrollContainer);
|
|
8944
|
+
if (xScrollContainer === null && (style.overflowX === "auto" || style.overflowX === "hidden" || style.overflowX === "scroll")) {
|
|
8945
|
+
xScrollContainer = scrollContainer;
|
|
8936
8946
|
}
|
|
8937
|
-
if (
|
|
8938
|
-
|
|
8947
|
+
if (yScrollContainer === null && (style.overflowY === "auto" || style.overflowY === "hidden" || style.overflowY === "scroll")) {
|
|
8948
|
+
yScrollContainer = scrollContainer;
|
|
8939
8949
|
}
|
|
8940
8950
|
}
|
|
8941
|
-
|
|
8951
|
+
const needsPolyfillX = hasLeft && xScrollContainer !== null;
|
|
8952
|
+
const needsPolyfillY = hasTop && yScrollContainer !== null;
|
|
8953
|
+
if (!needsPolyfillX && !needsPolyfillY) {
|
|
8954
|
+
return () => {}; // Native sticky will work fine on both axes
|
|
8955
|
+
}
|
|
8942
8956
|
const cleanupCallbackSet = new Set();
|
|
8943
8957
|
const cleanup = () => {
|
|
8944
8958
|
for (const cleanupCallback of cleanupCallbackSet) {
|
|
@@ -8946,7 +8960,6 @@ const initPositionSticky = (element) => {
|
|
|
8946
8960
|
}
|
|
8947
8961
|
cleanupCallbackSet.clear();
|
|
8948
8962
|
};
|
|
8949
|
-
|
|
8950
8963
|
const parentElement = element.parentElement;
|
|
8951
8964
|
const createPlaceholderClone = () => {
|
|
8952
8965
|
const clone = element.cloneNode(true);
|
|
@@ -8954,16 +8967,13 @@ const initPositionSticky = (element) => {
|
|
|
8954
8967
|
clone.removeAttribute("data-sticky");
|
|
8955
8968
|
return clone;
|
|
8956
8969
|
};
|
|
8957
|
-
|
|
8958
8970
|
let placeholder = createPlaceholderClone();
|
|
8959
8971
|
parentElement.insertBefore(placeholder, element);
|
|
8960
8972
|
cleanupCallbackSet.add(() => {
|
|
8961
8973
|
placeholder.remove();
|
|
8962
8974
|
});
|
|
8963
|
-
|
|
8964
8975
|
let width = getWidth(element);
|
|
8965
8976
|
let height = getHeight(element);
|
|
8966
|
-
|
|
8967
8977
|
const updateSize = () => {
|
|
8968
8978
|
const newPlaceholder = createPlaceholderClone();
|
|
8969
8979
|
parentElement.replaceChild(newPlaceholder, placeholder);
|
|
@@ -8972,84 +8982,114 @@ const initPositionSticky = (element) => {
|
|
|
8972
8982
|
height = getHeight(placeholder);
|
|
8973
8983
|
updatePosition();
|
|
8974
8984
|
};
|
|
8975
|
-
|
|
8976
8985
|
const updatePosition = () => {
|
|
8977
8986
|
// Ensure placeholder dimensions match element
|
|
8978
8987
|
setStyles(placeholder, {
|
|
8979
8988
|
width: `${width}px`,
|
|
8980
|
-
height: `${height}px
|
|
8989
|
+
height: `${height}px`
|
|
8981
8990
|
});
|
|
8982
|
-
|
|
8983
8991
|
const placeholderRect = placeholder.getBoundingClientRect();
|
|
8984
8992
|
const parentRect = parentElement.getBoundingClientRect();
|
|
8985
8993
|
|
|
8986
|
-
//
|
|
8987
|
-
|
|
8988
|
-
|
|
8994
|
+
// The CSS `top`/`left` values are offsets from the scroll container's edge.
|
|
8995
|
+
// getBoundingClientRect() always returns viewport coordinates (already accounting
|
|
8996
|
+
// for scroll position of all ancestors), so to convert the CSS offset to a
|
|
8997
|
+
// viewport threshold we add the scroll container's own viewport position.
|
|
8998
|
+
//
|
|
8999
|
+
// Example: main starts at viewport x=250, left=0 → leftThreshold=250.
|
|
9000
|
+
// After scrolling main 670px: placeholderRect.left = 250-670 = -420.
|
|
9001
|
+
// -420 <= 250 → stuck → element.style.left = 250px (main's left edge). ✓
|
|
9002
|
+
//
|
|
9003
|
+
// If no intermediate scroll container exists, use 0 (document/viewport edge).
|
|
9004
|
+
const yContainerRect = yScrollContainer ? yScrollContainer.getBoundingClientRect() : {
|
|
9005
|
+
top: 0
|
|
9006
|
+
};
|
|
9007
|
+
const xContainerRect = xScrollContainer ? xScrollContainer.getBoundingClientRect() : {
|
|
9008
|
+
left: 0
|
|
9009
|
+
};
|
|
9010
|
+
const topThreshold = yContainerRect.top + top;
|
|
9011
|
+
const leftThreshold = xContainerRect.left + left;
|
|
8989
9012
|
|
|
8990
|
-
//
|
|
9013
|
+
// ── Vertical (top) ──────────────────────────────────────────────────────
|
|
8991
9014
|
let topPosition;
|
|
8992
|
-
let
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
// Adjust to stay within parent
|
|
9006
|
-
topPosition = parentBottom - height;
|
|
9015
|
+
let isStuckVertically = false;
|
|
9016
|
+
if (hasTop) {
|
|
9017
|
+
if (placeholderRect.top <= topThreshold) {
|
|
9018
|
+
topPosition = topThreshold;
|
|
9019
|
+
isStuckVertically = true;
|
|
9020
|
+
// Don't go beyond parent's bottom boundary
|
|
9021
|
+
const parentBottom = parentRect.bottom;
|
|
9022
|
+
const elementBottom = topThreshold + height;
|
|
9023
|
+
if (elementBottom > parentBottom) {
|
|
9024
|
+
topPosition = parentBottom - height;
|
|
9025
|
+
}
|
|
9026
|
+
} else {
|
|
9027
|
+
topPosition = placeholderRect.top;
|
|
9007
9028
|
}
|
|
9008
9029
|
} else {
|
|
9009
|
-
// Element should be at its natural position in the flow
|
|
9010
9030
|
topPosition = placeholderRect.top;
|
|
9011
9031
|
}
|
|
9012
9032
|
|
|
9033
|
+
// ── Horizontal (left) ───────────────────────────────────────────────────
|
|
9034
|
+
let leftPosition;
|
|
9035
|
+
let isStuckHorizontally = false;
|
|
9036
|
+
if (hasLeft) {
|
|
9037
|
+
if (placeholderRect.left <= leftThreshold) {
|
|
9038
|
+
leftPosition = leftThreshold;
|
|
9039
|
+
isStuckHorizontally = true;
|
|
9040
|
+
// Don't go beyond parent's right boundary
|
|
9041
|
+
const parentRight = parentRect.right;
|
|
9042
|
+
const elementRight = leftThreshold + width;
|
|
9043
|
+
if (elementRight > parentRight) {
|
|
9044
|
+
leftPosition = parentRight - width;
|
|
9045
|
+
}
|
|
9046
|
+
} else {
|
|
9047
|
+
leftPosition = placeholderRect.left;
|
|
9048
|
+
}
|
|
9049
|
+
} else {
|
|
9050
|
+
leftPosition = placeholderRect.left;
|
|
9051
|
+
}
|
|
9013
9052
|
element.style.top = `${topPosition}px`;
|
|
9053
|
+
element.style.left = `${Math.round(leftPosition)}px`;
|
|
9014
9054
|
element.style.width = `${width}px`;
|
|
9015
9055
|
element.style.height = `${height}px`;
|
|
9016
9056
|
|
|
9017
9057
|
// Set attribute for potential styling
|
|
9018
|
-
if (
|
|
9058
|
+
if (isStuckVertically || isStuckHorizontally) {
|
|
9019
9059
|
element.setAttribute("data-sticky", "");
|
|
9020
9060
|
} else {
|
|
9021
9061
|
element.removeAttribute("data-sticky");
|
|
9022
9062
|
}
|
|
9023
9063
|
};
|
|
9024
|
-
|
|
9025
9064
|
{
|
|
9026
9065
|
const restorePositionStyle = forceStyles(element, {
|
|
9027
9066
|
"position": "fixed",
|
|
9028
9067
|
"z-index": 1,
|
|
9029
|
-
"will-change": "transform"
|
|
9068
|
+
"will-change": "transform" // Hint for hardware acceleration
|
|
9030
9069
|
});
|
|
9031
9070
|
cleanupCallbackSet.add(restorePositionStyle);
|
|
9032
9071
|
}
|
|
9033
|
-
|
|
9034
9072
|
updatePosition();
|
|
9035
|
-
|
|
9036
9073
|
{
|
|
9037
9074
|
const handleScroll = () => {
|
|
9038
9075
|
updatePosition();
|
|
9039
9076
|
};
|
|
9040
9077
|
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9078
|
+
// Listen on all scroll containers (including document) since the element
|
|
9079
|
+
// uses position:fixed and any ancestor scroll changes its apparent position.
|
|
9080
|
+
const listenTargets = new Set(scrollContainerSet);
|
|
9081
|
+
listenTargets.add(document.documentElement);
|
|
9082
|
+
for (const scrollTarget of listenTargets) {
|
|
9083
|
+
scrollTarget.addEventListener("scroll", handleScroll, {
|
|
9084
|
+
passive: true
|
|
9044
9085
|
});
|
|
9045
9086
|
cleanupCallbackSet.add(() => {
|
|
9046
|
-
|
|
9047
|
-
passive: true
|
|
9087
|
+
scrollTarget.removeEventListener("scroll", handleScroll, {
|
|
9088
|
+
passive: true
|
|
9048
9089
|
});
|
|
9049
9090
|
});
|
|
9050
9091
|
}
|
|
9051
9092
|
}
|
|
9052
|
-
|
|
9053
9093
|
{
|
|
9054
9094
|
let animationFrame = null;
|
|
9055
9095
|
const resizeObserver = new ResizeObserver(() => {
|
|
@@ -9068,7 +9108,6 @@ const initPositionSticky = (element) => {
|
|
|
9068
9108
|
animationFrame = null;
|
|
9069
9109
|
});
|
|
9070
9110
|
}
|
|
9071
|
-
|
|
9072
9111
|
{
|
|
9073
9112
|
const mutationObserver = new MutationObserver(() => {
|
|
9074
9113
|
updateSize();
|
|
@@ -9076,13 +9115,12 @@ const initPositionSticky = (element) => {
|
|
|
9076
9115
|
mutationObserver.observe(element, {
|
|
9077
9116
|
childList: true,
|
|
9078
9117
|
subtree: true,
|
|
9079
|
-
characterData: true
|
|
9118
|
+
characterData: true
|
|
9080
9119
|
});
|
|
9081
9120
|
cleanupCallbackSet.add(() => {
|
|
9082
9121
|
mutationObserver.disconnect();
|
|
9083
9122
|
});
|
|
9084
9123
|
}
|
|
9085
|
-
|
|
9086
9124
|
return cleanup;
|
|
9087
9125
|
};
|
|
9088
9126
|
|