@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.
Files changed (2) hide show
  1. package/dist/jsenv_dom.js +408 -370
  2. 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
- return stringifyCSSColor(rgba);
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 installImportMetaCss = (importMeta) => {
6303
- const stylesheet = new CSSStyleSheet({ baseUrl: importMeta.url });
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
- let called = false;
6306
- // eslint-disable-next-line accessor-pairs
6353
+ const currentCssSourceMap = new Map();
6307
6354
  Object.defineProperty(importMeta, "css", {
6308
6355
  configurable: true,
6309
- set(value) {
6310
- if (called) {
6311
- throw new Error("import.meta.css setter can only be called once");
6312
- }
6313
- called = true;
6314
- stylesheet.replaceSync(value);
6315
- document.adoptedStyleSheets = [
6316
- ...document.adoptedStyleSheets,
6317
- stylesheet,
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
- installImportMetaCss(import.meta);
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 = { x: true, y: true },
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 { scrollLeft, scrollTop } = scrollContainer;
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, // x grab coordinate (excluding scroll)
6573
- grabY, // y grab coordinate (excluding scroll)
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
- dragX: grabX, // coordinate of the last drag (excluding scroll of the scrollContainer)
6579
- dragY: grabY, // coordinate of the last drag (excluding scroll of the scrollContainer)
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 = (e) => {
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 { activeElement } = document;
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 = (e) => {
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 = (keyboardEvent) => {
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 = (scrollEvent) => {
6778
+ const handleScroll = scrollEvent => {
6732
6779
  if (isHandlingScroll) {
6733
6780
  return;
6734
6781
  }
6735
6782
  isHandlingScroll = true;
6736
- drag(gestureInfo.dragX, gestureInfo.dragY, { event: scrollEvent });
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 { grabX, grabY, grabLayout } = gestureInfo;
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 layoutXRequested = direction.x
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
- const drag = (
6848
- dragX = gestureInfo.dragX, // Scroll container relative X coordinate
6849
- dragY = gestureInfo.dragY, // Scroll container relative Y coordinate
6850
- { event = new CustomEvent("programmatic"), isRelease = false } = {},
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
- gestureInfo,
6869
- // we still publish drag event even when unchanged
6870
- // because UI might need to adjust when document scrolls
6871
- // even if nothing truly changes visually the element
6872
- // can decide to stick to the scroll for example
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, { event, isRelease: true });
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 = (mouseEvent) => {
6909
- const { clientX, clientY } = mouseEvent;
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 = (dragEvent) => {
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 = (mouseupEvent) => {
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, ({ onMove, onRelease }) => {
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
- `Received "mousedown" event, "pointerdown" events are recommended to perform drag gestures.`,
6966
- );
6967
- return initDragByPointer(grabEvent, options, ({ onMove, onRelease }) => {
6968
- const onPointerUp = (pointerEvent) => {
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: (gestureInfo) => {
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
- installImportMetaCss(import.meta);const setupConstraintFeedbackLine = () => {
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 = (gestureInfo) => {
7041
- const { grabEvent, dragEvent } = gestureInfo;
7042
- if (
7043
- grabEvent.type === "programmatic" ||
7044
- dragEvent.type === "programmatic"
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
- const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
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
- const { direction, scrollContainer } = dragGesture.gestureInfo;
7143
-
7163
+ const {
7164
+ direction,
7165
+ scrollContainer
7166
+ } = dragGesture.gestureInfo;
7144
7167
  return {
7145
- onConstraints: (
7146
- constraints,
7147
- { left, top, right, bottom, autoScrollArea },
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((marker) => marker.remove());
7163
- previousConstraintMarkers.forEach((marker) => marker.remove());
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", // green
7218
- side: "left",
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", // green
7227
- side: "right",
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", // red
7238
- side: "top",
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", // orange
7247
- side: "bottom",
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 { bounds } = constraint;
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", // purple
7265
- side: "left",
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", // purple
7276
- side: "right",
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", // purple
7287
- side: "top",
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", // purple
7298
- side: "bottom",
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
- markersToCreate,
7314
- scrollContainer,
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 { documentElement } = document;
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((m) => m.name).join(" + ");
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
- ...firstMarker,
7411
- name: combinedName,
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
- const createDebugMarker = (
7426
- { name, x, y, color = "255 0 0", side },
7427
- scrollContainer,
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
- side === "right" ? `${viewportX - MARKER_SIZE}px` : `${viewportX}px`;
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", // Default green color
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
- installImportMetaCss(import.meta);
8903
- import.meta.css = /* css */ `
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
- if (isNaN(top)) {
8917
- return () => {}; // Early return if no valid top value
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
- let hasOverflowHiddenOrAuto = false;
8924
- for (const scrollContainer of scrollContainerSet) {
8925
- const scrollContainerComputedStyle = getComputedStyle(scrollContainer);
8926
- const overflowX = scrollContainerComputedStyle.overflowX;
8927
- if (overflowX === "auto" || overflowX === "hidden") {
8928
- hasOverflowHiddenOrAuto = true;
8929
- break;
8930
- }
8931
- const overflowY = scrollContainerComputedStyle.overflowY;
8932
- if (overflowY === "auto" || overflowY === "hidden") {
8933
- hasOverflowHiddenOrAuto = true;
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 (!hasOverflowHiddenOrAuto) {
8938
- return () => {}; // Native sticky will work fine
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
- // Calculate left position in viewport coordinates (fixed positioning)
8987
- const leftPosition = placeholderRect.left;
8988
- element.style.left = `${Math.round(leftPosition)}px`;
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
- // Determine if element should be sticky or at its natural position
9013
+ // ── Vertical (top) ──────────────────────────────────────────────────────
8991
9014
  let topPosition;
8992
- let isStuck = false;
8993
-
8994
- // Check if we need to stick the element
8995
- if (placeholderRect.top <= top) {
8996
- // Element should be stuck at "top" position in the viewport
8997
- topPosition = top;
8998
- isStuck = true;
8999
-
9000
- // But make sure it doesn't go beyond parent's bottom boundary
9001
- const parentBottom = parentRect.bottom;
9002
- const elementBottom = top + height;
9003
-
9004
- if (elementBottom > parentBottom) {
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 (isStuck) {
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", // Hint for hardware acceleration
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
- for (const scrollContainer of scrollContainerSet) {
9042
- scrollContainer.addEventListener("scroll", handleScroll, {
9043
- passive: true,
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
- scrollContainer.removeEventListener("scroll", handleScroll, {
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