@deephaven/golden-layout 1.23.1-legacy-table-plugin.bbf55c8.0 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,14 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
4
4
  import $ from 'jquery';
5
5
  import AbstractContentItem from "./AbstractContentItem.js";
6
6
  import { animFrame } from "../utils/index.js";
7
- import { Splitter } from "../controls/index.js";
7
+ import { IntersectionSplitter, Splitter } from "../controls/index.js";
8
+ /**
9
+ * A single 2D intersection handle. `parentSplitterIndex` is the "bar" splitter
10
+ * owned by this RowOrColumn; `stemOwner`/`stemSplitterIndex` identify the
11
+ * perpendicular "stem" splitter that crosses it, which may live arbitrarily
12
+ * deep in this item's subtree. `junctionAtNearEdge` records which end of the
13
+ * stem meets the bar.
14
+ */
8
15
  export default class RowOrColumn extends AbstractContentItem {
9
16
  constructor(isColumn, layoutManager, config, parent) {
10
17
  super(layoutManager, config, parent, $('<div class="lm_item lm_' + (isColumn ? 'column' : 'row') + '"></div>'));
@@ -13,6 +20,7 @@ export default class RowOrColumn extends AbstractContentItem {
13
20
  _defineProperty(this, "childElementContainer", void 0);
14
21
  _defineProperty(this, "parent", void 0);
15
22
  _defineProperty(this, "_splitter", []);
23
+ _defineProperty(this, "_intersectionSplitter", []);
16
24
  _defineProperty(this, "_splitterSize", void 0);
17
25
  _defineProperty(this, "_splitterGrabSize", void 0);
18
26
  _defineProperty(this, "_isColumn", void 0);
@@ -20,6 +28,12 @@ export default class RowOrColumn extends AbstractContentItem {
20
28
  _defineProperty(this, "_splitterPosition", null);
21
29
  _defineProperty(this, "_splitterMinPosition", null);
22
30
  _defineProperty(this, "_splitterMaxPosition", null);
31
+ _defineProperty(this, "_isIntersectionDragging", false);
32
+ _defineProperty(this, "_activeFourWayPartner", null);
33
+ _defineProperty(this, "_activeFourWaySharedCenterMin", null);
34
+ _defineProperty(this, "_activeFourWaySharedCenterMax", null);
35
+ _defineProperty(this, "_activeFourWayBaseCenter", null);
36
+ _defineProperty(this, "_activeFourWayPartnerBaseCenter", null);
23
37
  this.parent = parent;
24
38
  this.isRow = !isColumn;
25
39
  this.isColumn = isColumn;
@@ -150,6 +164,7 @@ export default class RowOrColumn extends AbstractContentItem {
150
164
  if (this.contentItems.length > 0) {
151
165
  this._calculateRelativeSizes();
152
166
  this._setAbsoluteSizes();
167
+ this._scheduleIntersectionRefresh();
153
168
  }
154
169
  this.emitBubblingEvent('stateChanged');
155
170
  this.emit('resize');
@@ -167,6 +182,16 @@ export default class RowOrColumn extends AbstractContentItem {
167
182
  for (i = 0; i < this.contentItems.length - 1; i++) {
168
183
  this.contentItems[i].element.after(this._createSplitter(i).element);
169
184
  }
185
+
186
+ // Initialise children eagerly so their splitters exist before we attach
187
+ // intersection handles to them. _$init is idempotent so the outer
188
+ // callDownwards('_$init') traversal will skip them as no-ops.
189
+ for (i = 0; i < this.contentItems.length; i++) {
190
+ if (this.contentItems[i].isInitialised !== true) {
191
+ this.contentItems[i]._$init();
192
+ }
193
+ }
194
+ this._refreshIntersectionSplitters();
170
195
  }
171
196
 
172
197
  /**
@@ -473,6 +498,14 @@ export default class RowOrColumn extends AbstractContentItem {
473
498
  * @param {lm.controls.Splitter} splitter
474
499
  */
475
500
  _onSplitterDragStop(splitter) {
501
+ this._applySplitterDragStop(splitter);
502
+ this._scheduleSetSize();
503
+ }
504
+
505
+ /**
506
+ * Applies drag-stop updates for one splitter without scheduling layout.
507
+ */
508
+ _applySplitterDragStop(splitter) {
476
509
  var _items$before$element2, _items$after$element$2, _this$_splitterPositi, _items$before$config$2, _items$after$config$t;
477
510
  var items = this._getItemsForSplitter(splitter);
478
511
  var sizeBefore = (_items$before$element2 = items.before.element[this._dimension]()) !== null && _items$before$element2 !== void 0 ? _items$before$element2 : 0;
@@ -485,7 +518,628 @@ export default class RowOrColumn extends AbstractContentItem {
485
518
  top: 0,
486
519
  left: 0
487
520
  });
521
+ }
522
+
523
+ /**
524
+ * Schedules a full descendant size update on the next animation frame.
525
+ */
526
+ _scheduleSetSize() {
488
527
  animFrame(this.callDownwards.bind(this, 'setSize', undefined, undefined, undefined));
528
+ // setSize only propagates downwards, so it repositions intersection handles
529
+ // owned by this item and its descendants. A handle that sits at a crossing
530
+ // is owned by the parent RowOrColumn (it depends on a child splitter's
531
+ // position), so ancestors must be refreshed too or their handles drift out
532
+ // of sync with the lines after a drag.
533
+ this._scheduleAncestorIntersectionRefresh();
534
+ }
535
+
536
+ /**
537
+ * Schedule intersection handle refresh after layout and browser positioning settle.
538
+ */
539
+ _scheduleIntersectionRefresh() {
540
+ animFrame(() => {
541
+ animFrame(this._refreshIntersectionSplitters.bind(this));
542
+ });
543
+ }
544
+
545
+ /**
546
+ * Schedule an intersection handle refresh on every RowOrColumn ancestor so
547
+ * crossing handles stay aligned after a drag that only resized descendants.
548
+ */
549
+ _scheduleAncestorIntersectionRefresh() {
550
+ var ancestor = this.parent;
551
+ while (ancestor != null) {
552
+ if (ancestor instanceof RowOrColumn) {
553
+ ancestor._scheduleIntersectionRefresh();
554
+ }
555
+ ancestor = ancestor.parent;
556
+ }
557
+ }
558
+
559
+ // ============================================================================
560
+ // Intersection Splitter Methods - Support for 2D grid resizing
561
+ // ============================================================================
562
+
563
+ /**
564
+ * Create intersection splitters at the crossing points between this
565
+ * RowOrColumn's splitters and the splitters of any perpendicular child.
566
+ *
567
+ * Each handle is appended into this RowOrColumn's container and positioned
568
+ * with JS (via `_positionIntersectionSplitter`) during refresh so it stays
569
+ * aligned as the layout changes. Handles are keyed by their splitter indices
570
+ * so existing ones are reused rather than recreated.
571
+ *
572
+ * @returns The set of crossing keys that currently exist, so callers can
573
+ * sweep handles whose crossing no longer exists.
574
+ */
575
+ _createIntersectionSplitters() {
576
+ this.childElementContainer.css('position', 'relative');
577
+ var ensuredKeys = new Set();
578
+ for (var parentSplitterIndex = 0; parentSplitterIndex < this._splitter.length; parentSplitterIndex++) {
579
+ var beforeItem = this.contentItems[parentSplitterIndex];
580
+ var afterItem = this.contentItems[parentSplitterIndex + 1];
581
+
582
+ // A splitter "bar" is crossed by perpendicular splitter lines reaching its
583
+ // shared edge from either side. Those lines can be nested arbitrarily deep
584
+ // (e.g. a row inside a column inside the adjacent row), so walk each
585
+ // adjacent subtree down to the touching edge. The before item meets the
586
+ // bar at its far edge, the after item at its near edge.
587
+ var stems = [...this._collectEdgeStemSplitters(beforeItem, false, 'b'), ...this._collectEdgeStemSplitters(afterItem, true, 'a')];
588
+ for (var i = 0; i < stems.length; i++) {
589
+ var stem = stems[i];
590
+ var key = parentSplitterIndex + ':' + stem.path;
591
+ ensuredKeys.add(key);
592
+ this._ensureIntersectionSplitter(key, parentSplitterIndex, stem.stemOwner, stem.stemSplitterIndex, stem.junctionAtNearEdge);
593
+ }
594
+ }
595
+ return ensuredKeys;
596
+ }
597
+
598
+ /**
599
+ * Collect every perpendicular splitter line within `item`'s subtree that
600
+ * reaches the shared edge with one of this row/column's splitter bars, so a
601
+ * crossing handle can be created for it. Lines can be nested arbitrarily deep,
602
+ * so descend until the edge is no longer shared.
603
+ *
604
+ * @param item The subtree root to search.
605
+ * @param nearEdge true when the bar sits at the start of `item` along the bar
606
+ * main axis (junction at the near end), false when at the end.
607
+ * @param path Dot/colon-delimited string uniquely identifying this stem within
608
+ * the layout tree, used as part of the intersection handle key.
609
+ * @returns Array of stem descriptors for each crossing found in the subtree.
610
+ */
611
+ _collectEdgeStemSplitters(item, nearEdge, path) {
612
+ if (!(item instanceof RowOrColumn)) {
613
+ return [];
614
+ }
615
+ if (item._isColumn !== this._isColumn) {
616
+ // Perpendicular to the bar: every splitter here crosses it, and every
617
+ // child spans the full cross extent so all share the edge - recurse into
618
+ // each to pick up deeper crossings.
619
+ var results = [];
620
+ for (var i = 0; i < item._splitter.length; i++) {
621
+ results.push({
622
+ stemOwner: item,
623
+ stemSplitterIndex: i,
624
+ junctionAtNearEdge: nearEdge,
625
+ path: path + ':' + i
626
+ });
627
+ }
628
+ for (var _i6 = 0; _i6 < item.contentItems.length; _i6++) {
629
+ results.push(...this._collectEdgeStemSplitters(item.contentItems[_i6], nearEdge, path + '.' + _i6));
630
+ }
631
+ return results;
632
+ }
633
+
634
+ // Parallel to the bar: only the child at the shared edge can reach it.
635
+ var edgeIndex = nearEdge ? 0 : item.contentItems.length - 1;
636
+ return this._collectEdgeStemSplitters(item.contentItems[edgeIndex], nearEdge, path + '.' + edgeIndex);
637
+ }
638
+
639
+ /**
640
+ * Recreate intersection splitters based on current splitter topology.
641
+ * This keeps handles aligned and present after layout tree mutations.
642
+ */
643
+ _refreshIntersectionSplitters() {
644
+ var previousCount = this._intersectionSplitter.length;
645
+ var ensuredKeys = this._createIntersectionSplitters();
646
+
647
+ // Sweep handles whose crossing no longer exists after a topology change.
648
+ this._intersectionSplitter = this._intersectionSplitter.filter(record => {
649
+ if (ensuredKeys.has(record.key)) {
650
+ return true;
651
+ }
652
+ record.splitter._$destroy();
653
+ return false;
654
+ });
655
+ for (var i = 0; i < this._intersectionSplitter.length; i++) {
656
+ this._positionIntersectionSplitter(this._intersectionSplitter[i]);
657
+ }
658
+
659
+ // If handles were added/removed due to topology change, run one more pass
660
+ // on the next frame to settle post-layout positions.
661
+ if (this._intersectionSplitter.length !== previousCount) {
662
+ animFrame(() => {
663
+ for (var _i7 = 0; _i7 < this._intersectionSplitter.length; _i7++) {
664
+ this._positionIntersectionSplitter(this._intersectionSplitter[_i7]);
665
+ }
666
+ });
667
+ }
668
+ }
669
+
670
+ /**
671
+ * Destroy all previously created intersection splitters.
672
+ */
673
+ _destroyIntersectionSplitters() {
674
+ for (var i = 0; i < this._intersectionSplitter.length; i++) {
675
+ this._intersectionSplitter[i].splitter._$destroy();
676
+ }
677
+ this._intersectionSplitter = [];
678
+ }
679
+
680
+ /**
681
+ * Tear down splitters (including intersection handles and their document-level
682
+ * drag listeners) before delegating to the base destroy logic.
683
+ */
684
+ _$destroy() {
685
+ this._destroyIntersectionSplitters();
686
+ for (var i = 0; i < this._splitter.length; i++) {
687
+ this._splitter[i]._$destroy();
688
+ }
689
+ this._splitter = [];
690
+ AbstractContentItem.prototype._$destroy.call(this);
691
+ }
692
+
693
+ /**
694
+ * Whether any golden-layout drag that should suppress intersection hover
695
+ * affordances is in progress. Covers a 1D splitter drag or a panel drag
696
+ * (both signalled by `lm_dragging` on the body) and the 2D intersection drag
697
+ * itself (`lm_intersection_dragging`). During these the handle must not light
698
+ * up its cross as the pointer passes over a crossing.
699
+ *
700
+ * @returns True when a golden-layout drag is active.
701
+ */
702
+ static _isAnyDragInProgress() {
703
+ return $(document.body).hasClass('lm_dragging') || $(document.body).hasClass('lm_intersection_dragging');
704
+ }
705
+
706
+ /**
707
+ * Create a single intersection splitter anchored in this row/column overlay
708
+ * at the given coordinates.
709
+ *
710
+ * @param key Unique key identifying this crossing (parent splitter index plus
711
+ * the stem's tree path), used to reuse an existing handle instead
712
+ * of creating a duplicate.
713
+ * @param parentSplitterIndex Index into this row/column's `_splitter` array of
714
+ * the "bar" splitter that owns the crossing.
715
+ * @param stemOwner The RowOrColumn (possibly a descendant) that owns the
716
+ * perpendicular "stem" splitter crossing the bar.
717
+ * @param stemSplitterIndex Index into `stemOwner._splitter` of the stem
718
+ * splitter.
719
+ * @param junctionAtNearEdge True when the stem meets the bar at the stem
720
+ * owner's near edge, false at its far edge.
721
+ */
722
+ _ensureIntersectionSplitter(key, parentSplitterIndex, stemOwner, stemSplitterIndex, junctionAtNearEdge) {
723
+ var existing = this._intersectionSplitter.find(item => item.key === key);
724
+ if (existing != null) {
725
+ existing.parentSplitterIndex = parentSplitterIndex;
726
+ existing.stemOwner = stemOwner;
727
+ existing.stemSplitterIndex = stemSplitterIndex;
728
+ existing.junctionAtNearEdge = junctionAtNearEdge;
729
+ return;
730
+ }
731
+ var intersectionSplitter = new IntersectionSplitter(this._splitterSize, this._splitterGrabSize);
732
+
733
+ // Handlers close over the record so reuse (which mutates the record in
734
+ // place) keeps them pointed at the current crossing.
735
+ var record = {
736
+ splitter: intersectionSplitter,
737
+ key,
738
+ parentSplitterIndex,
739
+ stemOwner,
740
+ stemSplitterIndex,
741
+ junctionAtNearEdge
742
+ };
743
+ intersectionSplitter.on('dragStart', () => this._onIntersectionSplitterDragStart(record));
744
+ intersectionSplitter.on('drag', (offsetX, offsetY) => this._onIntersectionSplitterDrag(record, offsetX, offsetY));
745
+ intersectionSplitter.on('dragStop', () => this._onIntersectionSplitterDragStop(record));
746
+
747
+ // Highlight both perpendicular lines while hovering the grab area, mirroring
748
+ // the active line affordance used for 1D splitter drags.
749
+ var hoverPartner = null;
750
+ intersectionSplitter.element.on('mouseenter', () => {
751
+ // Ignore hover state changes while any drag is in progress. This covers
752
+ // our own active 2D drag (crossing other handles must not transfer or pin
753
+ // highlights) as well as 1D golden-layout splitter or panel drags, which
754
+ // would otherwise flash the cross highlight as the pointer passes over a
755
+ // crossing mid-drag.
756
+ if (this._isIntersectionDragging || RowOrColumn._isAnyDragInProgress()) {
757
+ return;
758
+ }
759
+ this._setIntersectionHighlight(record, true);
760
+ hoverPartner = this._findNearFourWayPartner(record);
761
+ if (hoverPartner != null) {
762
+ this._setIntersectionHighlight(hoverPartner, true);
763
+ }
764
+ });
765
+ intersectionSplitter.element.on('mouseleave', () => {
766
+ if (this._isIntersectionDragging || $(document.body).hasClass('lm_intersection_dragging')) {
767
+ return;
768
+ }
769
+ this._setIntersectionHighlight(record, false);
770
+ if (hoverPartner != null) {
771
+ this._setIntersectionHighlight(hoverPartner, false);
772
+ hoverPartner = null;
773
+ }
774
+ });
775
+ intersectionSplitter.element.css({
776
+ position: 'absolute',
777
+ left: 0,
778
+ top: 0,
779
+ transform: 'translate(-50%, -50%)',
780
+ zIndex: 60
781
+ });
782
+ this.childElementContainer.append(intersectionSplitter.element);
783
+ this._intersectionSplitter.push(record);
784
+ }
785
+
786
+ /**
787
+ * Move an intersection handle to the current centre of its crossing.
788
+ *
789
+ * @param record The intersection handle record to reposition.
790
+ */
791
+ _positionIntersectionSplitter(record) {
792
+ var position = this._getIntersectionPosition(record);
793
+ if (position == null) {
794
+ return;
795
+ }
796
+ record.splitter.element.css({
797
+ left: position.left,
798
+ top: position.top
799
+ });
800
+ }
801
+
802
+ /**
803
+ * Compute intersection coordinates (the centre of the crossing) relative to
804
+ * this row/column container.
805
+ *
806
+ * Uses `getBoundingClientRect` for the container and both splitter elements
807
+ * rather than jQuery `.position()`. `.position()` is relative to each
808
+ * element's offset parent, which varies with nesting and `position: relative`
809
+ * on intermediate items, so adding those values together mis-places the
810
+ * handle at some crossings. Rect-based deltas are independent of the offset
811
+ * parent chain and always land on the visual crossing.
812
+ *
813
+ * @param record The intersection handle record to locate.
814
+ * @returns The `{ left, top }` crossing centre relative to this row/column
815
+ * container, or null when the geometry is unavailable.
816
+ */
817
+ _getIntersectionPosition(record) {
818
+ var _record$stemOwner;
819
+ var parentSplitter = this._splitter[record.parentSplitterIndex];
820
+ var childSplitter = (_record$stemOwner = record.stemOwner) === null || _record$stemOwner === void 0 ? void 0 : _record$stemOwner._splitter[record.stemSplitterIndex];
821
+ var container = this.childElementContainer[0];
822
+ var parentEl = parentSplitter === null || parentSplitter === void 0 ? void 0 : parentSplitter.element[0];
823
+ var childEl = childSplitter === null || childSplitter === void 0 ? void 0 : childSplitter.element[0];
824
+ if (container == null || parentEl == null || childEl == null) {
825
+ return null;
826
+ }
827
+ var containerRect = container.getBoundingClientRect();
828
+ var parentRect = parentEl.getBoundingClientRect();
829
+ var childRect = childEl.getBoundingClientRect();
830
+ var parentCenterX = parentRect.left + parentRect.width / 2 - containerRect.left;
831
+ var parentCenterY = parentRect.top + parentRect.height / 2 - containerRect.top;
832
+ var childCenterX = childRect.left + childRect.width / 2 - containerRect.left;
833
+ var childCenterY = childRect.top + childRect.height / 2 - containerRect.top;
834
+
835
+ // The parent splitter runs along the cross axis (the "bar") and the child
836
+ // splitter along the main axis (the "stem"); take each line's centre.
837
+ if (this._isColumn) {
838
+ return {
839
+ left: childCenterX,
840
+ top: parentCenterY
841
+ };
842
+ }
843
+ return {
844
+ left: parentCenterX,
845
+ top: childCenterY
846
+ };
847
+ }
848
+
849
+ /**
850
+ * Toggle the active-line highlight on both splitters that meet at an
851
+ * intersection. Reuses the standard `.lm_dragging` line style so the 2D
852
+ * affordance is visually identical to the existing 1D drag affordance, and
853
+ * adds `.lm_intersection_line` to lift the lines above pane content so an
854
+ * offset junction renders cleanly instead of being clipped by a neighbour.
855
+ *
856
+ * @param record The intersection handle whose two perpendicular lines to
857
+ * toggle.
858
+ * @param highlighted True to add the highlight, false to remove it.
859
+ */
860
+ _setIntersectionHighlight(record, highlighted) {
861
+ var _record$stemOwner2;
862
+ var parentSplitter = this._splitter[record.parentSplitterIndex];
863
+ var childSplitter = (_record$stemOwner2 = record.stemOwner) === null || _record$stemOwner2 === void 0 ? void 0 : _record$stemOwner2._splitter[record.stemSplitterIndex];
864
+ parentSplitter === null || parentSplitter === void 0 || parentSplitter.element.toggleClass('lm_dragging', highlighted);
865
+ parentSplitter === null || parentSplitter === void 0 || parentSplitter.element.toggleClass('lm_intersection_line', highlighted);
866
+ childSplitter === null || childSplitter === void 0 || childSplitter.element.toggleClass('lm_dragging', highlighted);
867
+ childSplitter === null || childSplitter === void 0 || childSplitter.element.toggleClass('lm_intersection_line', highlighted);
868
+ }
869
+
870
+ /**
871
+ * Find a nearby sibling intersection on the same parent splitter so a
872
+ * near-aligned 4-way corner can be dragged as one.
873
+ *
874
+ * @param record The intersection handle to find a partner for.
875
+ * @returns The closest sibling handle within tolerance, or null if none.
876
+ */
877
+ _findNearFourWayPartner(record) {
878
+ // getBoundingClientRect() returns zeros in jsdom; skip partner detection
879
+ // when geometry is unavailable so unit tests remain deterministic.
880
+ var container = this.childElementContainer[0];
881
+ var containerRect = container === null || container === void 0 ? void 0 : container.getBoundingClientRect();
882
+ if (containerRect == null || containerRect.width === 0 && containerRect.height === 0) {
883
+ return null;
884
+ }
885
+ var base = this._getIntersectionPosition(record);
886
+ if (base == null) {
887
+ return null;
888
+ }
889
+
890
+ // Keep this intentionally conservative for now; can be made configurable.
891
+ var tolerancePx = 8;
892
+ var best = null;
893
+ var bestDist = Number.POSITIVE_INFINITY;
894
+ for (var i = 0; i < this._intersectionSplitter.length; i++) {
895
+ var candidate = this._intersectionSplitter[i];
896
+ if (candidate.key === record.key || candidate.parentSplitterIndex !== record.parentSplitterIndex) {
897
+ continue;
898
+ }
899
+ var position = this._getIntersectionPosition(candidate);
900
+ if (position == null) {
901
+ continue;
902
+ }
903
+ var dx = Math.abs(position.left - base.left);
904
+ var dy = Math.abs(position.top - base.top);
905
+ if (dx > tolerancePx || dy > tolerancePx) {
906
+ continue;
907
+ }
908
+ var dist = dx + dy;
909
+ if (dist < bestDist) {
910
+ bestDist = dist;
911
+ best = candidate;
912
+ }
913
+ }
914
+ return best;
915
+ }
916
+
917
+ /**
918
+ * Clamp and apply one splitter visual drag offset on the owner's active axis.
919
+ *
920
+ * @param owner The RowOrColumn that owns the splitter and its min/max bounds.
921
+ * @param splitter The splitter whose visual position to move.
922
+ * @param offset The desired pixel offset along the owner's active axis;
923
+ * clamped to the owner's min/max drag range before applying.
924
+ */
925
+ _setSplitterDragOffset(owner, splitter, offset) {
926
+ var min = owner._splitterMinPosition;
927
+ var max = owner._splitterMaxPosition;
928
+ if (min == null || max == null) {
929
+ return;
930
+ }
931
+ var clamped = Math.max(min, Math.min(max, offset));
932
+ owner._splitterPosition = clamped;
933
+ splitter.element.css(owner._isColumn ? 'top' : 'left', clamped);
934
+ }
935
+
936
+ /**
937
+ * Get the splitter line center on the owner's active drag axis.
938
+ *
939
+ * @param owner The RowOrColumn that determines the active axis.
940
+ * @param splitter The splitter to measure.
941
+ * @returns The line centre in client coordinates on the active axis, or null
942
+ * when the element is unavailable.
943
+ */
944
+ _getSplitterAxisCenter(owner, splitter) {
945
+ var element = splitter.element[0];
946
+ if (element == null) {
947
+ return null;
948
+ }
949
+ var rect = element.getBoundingClientRect();
950
+ return owner._isColumn ? rect.top + rect.height / 2 : rect.left + rect.width / 2;
951
+ }
952
+
953
+ /**
954
+ * Invoked when an intersection splitter's DragListener fires dragStart.
955
+ * Calculates movement bounds for both axes (via the existing 1D logic) so the
956
+ * drag stays within valid ranges, and highlights both perpendicular lines.
957
+ *
958
+ * @param record The intersection handle that started dragging.
959
+ */
960
+ _onIntersectionSplitterDragStart(record) {
961
+ var parentSplitter = this._splitter[record.parentSplitterIndex];
962
+ var childSplitter = record.stemOwner._splitter[record.stemSplitterIndex];
963
+ var partner = this._findNearFourWayPartner(record);
964
+ var partnerSplitter = partner ? partner.stemOwner._splitter[partner.stemSplitterIndex] : null;
965
+
966
+ // Reuse the existing 1D splitter drag logic to compute bounds for each axis.
967
+ this._onSplitterDragStart(parentSplitter);
968
+ record.stemOwner._onSplitterDragStart(childSplitter);
969
+ this._activeFourWaySharedCenterMin = null;
970
+ this._activeFourWaySharedCenterMax = null;
971
+ this._activeFourWayBaseCenter = null;
972
+ this._activeFourWayPartnerBaseCenter = null;
973
+ if (partner != null && partnerSplitter != null) {
974
+ partner.stemOwner._onSplitterDragStart(partnerSplitter);
975
+ var baseCenter = this._getSplitterAxisCenter(record.stemOwner, childSplitter);
976
+ var partnerBaseCenter = this._getSplitterAxisCenter(partner.stemOwner, partnerSplitter);
977
+ var recordMin = record.stemOwner._splitterMinPosition;
978
+ var recordMax = record.stemOwner._splitterMaxPosition;
979
+ var partnerMin = partner.stemOwner._splitterMinPosition;
980
+ var partnerMax = partner.stemOwner._splitterMaxPosition;
981
+ if (recordMin != null && recordMax != null && partnerMin != null && partnerMax != null && baseCenter != null && partnerBaseCenter != null) {
982
+ var recordCenterMin = baseCenter + recordMin;
983
+ var recordCenterMax = baseCenter + recordMax;
984
+ var partnerCenterMin = partnerBaseCenter + partnerMin;
985
+ var partnerCenterMax = partnerBaseCenter + partnerMax;
986
+ var sharedCenterMin = Math.max(recordCenterMin, partnerCenterMin);
987
+ var sharedCenterMax = Math.min(recordCenterMax, partnerCenterMax);
988
+ if (sharedCenterMin <= sharedCenterMax) {
989
+ this._activeFourWayPartner = partner;
990
+ this._activeFourWaySharedCenterMin = sharedCenterMin;
991
+ this._activeFourWaySharedCenterMax = sharedCenterMax;
992
+ this._activeFourWayBaseCenter = baseCenter;
993
+ this._activeFourWayPartnerBaseCenter = partnerBaseCenter;
994
+ } else {
995
+ this._activeFourWayPartner = null;
996
+ }
997
+ } else {
998
+ this._activeFourWayPartner = null;
999
+ }
1000
+ } else {
1001
+ this._activeFourWayPartner = null;
1002
+ }
1003
+ this._isIntersectionDragging = true;
1004
+ this._setIntersectionHighlight(record, true);
1005
+ if (this._activeFourWayPartner != null) {
1006
+ this._setIntersectionHighlight(this._activeFourWayPartner, true);
1007
+ }
1008
+ $(document.body).addClass('lm_intersection_dragging');
1009
+ }
1010
+
1011
+ /**
1012
+ * Invoked when an intersection splitter's DragListener fires drag. Moves both
1013
+ * splitter lines by delegating to the existing 1D logic, which clamps each
1014
+ * axis to its own valid range. The lines moving form the 2D drag affordance.
1015
+ *
1016
+ * The stem line spans the full extent of its owner along the parent axis, so
1017
+ * when the parent line moves the junction would otherwise detach. The stem is
1018
+ * stretched to follow the parent line while its far tip stays anchored.
1019
+ *
1020
+ * The stretch is applied with a CSS `transform: scale(...)` about the far tip
1021
+ * rather than by changing the line's `width`/`height`/`top`/`left`. Splitter
1022
+ * lines are real in-flow elements (floated / `position: relative`), so
1023
+ * mutating their box size reflows sibling panes and headers (tabs jump,
1024
+ * content shifts, gaps appear). A transform is painted without affecting
1025
+ * layout, so the affordance stretches cleanly even for deeply nested grids.
1026
+ *
1027
+ * @param record The intersection handle being dragged.
1028
+ * @param offsetX Horizontal pixels moved from the drag origin. Can be negative.
1029
+ * @param offsetY Vertical pixels moved from the drag origin. Can be negative.
1030
+ */
1031
+ _onIntersectionSplitterDrag(record, offsetX, offsetY) {
1032
+ var _this$_splitterPositi2, _record$stemOwner$ele;
1033
+ var parentSplitter = this._splitter[record.parentSplitterIndex];
1034
+ var childSplitter = record.stemOwner._splitter[record.stemSplitterIndex];
1035
+ var partner = this._activeFourWayPartner;
1036
+ var partnerSplitter = partner ? partner.stemOwner._splitter[partner.stemSplitterIndex] : null;
1037
+ this._onSplitterDrag(parentSplitter, offsetX, offsetY);
1038
+ if (partner != null && partnerSplitter != null) {
1039
+ // Keep both stems aligned by applying one shared clamped offset across the
1040
+ // intersection axis for both stem owners.
1041
+ var desiredStemOffset = record.stemOwner._isColumn ? offsetY : offsetX;
1042
+ var sharedCenterMin = this._activeFourWaySharedCenterMin;
1043
+ var sharedCenterMax = this._activeFourWaySharedCenterMax;
1044
+ var baseCenter = this._activeFourWayBaseCenter;
1045
+ var partnerBaseCenter = this._activeFourWayPartnerBaseCenter;
1046
+ if (sharedCenterMin != null && sharedCenterMax != null && baseCenter != null && partnerBaseCenter != null) {
1047
+ var desiredCenter = baseCenter + desiredStemOffset;
1048
+ var sharedCenter = Math.max(sharedCenterMin, Math.min(sharedCenterMax, desiredCenter));
1049
+ this._setSplitterDragOffset(record.stemOwner, childSplitter, sharedCenter - baseCenter);
1050
+ this._setSplitterDragOffset(partner.stemOwner, partnerSplitter, sharedCenter - partnerBaseCenter);
1051
+ }
1052
+ } else {
1053
+ record.stemOwner._onSplitterDrag(childSplitter, offsetX, offsetY);
1054
+ }
1055
+
1056
+ // Scale the stem line along the parent axis so its junction tip tracks the
1057
+ // parent line while its far tip stays put. `_splitterPosition` is the
1058
+ // parent's clamped offset; the stem owner extent gives the line's length.
1059
+ var shift = (_this$_splitterPositi2 = this._splitterPosition) !== null && _this$_splitterPositi2 !== void 0 ? _this$_splitterPositi2 : 0;
1060
+ var fullLength = (_record$stemOwner$ele = record.stemOwner.element[this._dimension]()) !== null && _record$stemOwner$ele !== void 0 ? _record$stemOwner$ele : 0;
1061
+ var newLength = record.junctionAtNearEdge ? fullLength - shift : fullLength + shift;
1062
+ // Keep the stretched stem visible and non-inverted even when the parent
1063
+ // splitter's clamped shift exceeds the stem owner's span.
1064
+ var minVisibleLength = Math.max(this._splitterSize, 1);
1065
+ var safeLength = Math.max(newLength, minVisibleLength);
1066
+ var scale = fullLength > 0 ? safeLength / fullLength : 1;
1067
+
1068
+ // Anchor the far tip: scale about the edge opposite the junction.
1069
+ var farEdge = record.junctionAtNearEdge ? this._isColumn ? 'bottom' : 'right' : this._isColumn ? 'top' : 'left';
1070
+ childSplitter.element.css({
1071
+ 'transform-origin': farEdge,
1072
+ transform: this._isColumn ? "scaleY(".concat(scale, ")") : "scaleX(".concat(scale, ")")
1073
+ });
1074
+ if (partner != null && partnerSplitter != null) {
1075
+ var _this$_splitterPositi3, _partner$stemOwner$el;
1076
+ var partnerShift = (_this$_splitterPositi3 = this._splitterPosition) !== null && _this$_splitterPositi3 !== void 0 ? _this$_splitterPositi3 : 0;
1077
+ var partnerFullLength = (_partner$stemOwner$el = partner.stemOwner.element[this._dimension]()) !== null && _partner$stemOwner$el !== void 0 ? _partner$stemOwner$el : 0;
1078
+ var partnerNewLength = partner.junctionAtNearEdge ? partnerFullLength - partnerShift : partnerFullLength + partnerShift;
1079
+ var partnerMinVisibleLength = Math.max(this._splitterSize, 1);
1080
+ var partnerSafeLength = Math.max(partnerNewLength, partnerMinVisibleLength);
1081
+ var partnerScale = partnerFullLength > 0 ? partnerSafeLength / partnerFullLength : 1;
1082
+ var partnerFarEdge = partner.junctionAtNearEdge ? this._isColumn ? 'bottom' : 'right' : this._isColumn ? 'top' : 'left';
1083
+ partnerSplitter.element.css({
1084
+ 'transform-origin': partnerFarEdge,
1085
+ transform: this._isColumn ? "scaleY(".concat(partnerScale, ")") : "scaleX(".concat(partnerScale, ")")
1086
+ });
1087
+ }
1088
+ }
1089
+
1090
+ /**
1091
+ * Invoked when an intersection splitter's DragListener fires dragStop.
1092
+ * Applies both axis updates atomically (via the existing 1D logic), clears the
1093
+ * highlight unless the pointer is still over the handle, then relayouts once.
1094
+ *
1095
+ * @param record The intersection handle that finished dragging.
1096
+ */
1097
+ _onIntersectionSplitterDragStop(record) {
1098
+ var parentSplitter = this._splitter[record.parentSplitterIndex];
1099
+ var childSplitter = record.stemOwner._splitter[record.stemSplitterIndex];
1100
+ var partner = this._activeFourWayPartner;
1101
+ var partnerSplitter = partner ? partner.stemOwner._splitter[partner.stemSplitterIndex] : null;
1102
+ this._applySplitterDragStop(parentSplitter);
1103
+
1104
+ // In 4-way mode, enforce one final shared stem offset before applying stop
1105
+ // so both corners land perfectly aligned on drop.
1106
+ if (partner != null && partnerSplitter != null && this._activeFourWaySharedCenterMin != null && this._activeFourWaySharedCenterMax != null && this._activeFourWayBaseCenter != null && this._activeFourWayPartnerBaseCenter != null) {
1107
+ var _record$stemOwner$_sp;
1108
+ var baseOffset = (_record$stemOwner$_sp = record.stemOwner._splitterPosition) !== null && _record$stemOwner$_sp !== void 0 ? _record$stemOwner$_sp : 0;
1109
+ var desiredCenter = this._activeFourWayBaseCenter + baseOffset;
1110
+ var sharedCenter = Math.max(this._activeFourWaySharedCenterMin, Math.min(this._activeFourWaySharedCenterMax, desiredCenter));
1111
+ this._setSplitterDragOffset(record.stemOwner, childSplitter, sharedCenter - this._activeFourWayBaseCenter);
1112
+ this._setSplitterDragOffset(partner.stemOwner, partnerSplitter, sharedCenter - this._activeFourWayPartnerBaseCenter);
1113
+ }
1114
+ record.stemOwner._applySplitterDragStop(childSplitter);
1115
+ if (partner != null && partnerSplitter != null) {
1116
+ partner.stemOwner._applySplitterDragStop(partnerSplitter);
1117
+ }
1118
+
1119
+ // Clear the stretch transform applied during drag so the stem line falls
1120
+ // back to its CSS full-extent size once the layout is reapplied.
1121
+ childSplitter.element.css({
1122
+ transform: '',
1123
+ 'transform-origin': ''
1124
+ });
1125
+ if (partnerSplitter != null) {
1126
+ partnerSplitter.element.css({
1127
+ transform: '',
1128
+ 'transform-origin': ''
1129
+ });
1130
+ }
1131
+ this._isIntersectionDragging = false;
1132
+ this._setIntersectionHighlight(record, false);
1133
+ if (partner != null) {
1134
+ this._setIntersectionHighlight(partner, false);
1135
+ }
1136
+ this._activeFourWayPartner = null;
1137
+ this._activeFourWaySharedCenterMin = null;
1138
+ this._activeFourWaySharedCenterMax = null;
1139
+ this._activeFourWayBaseCenter = null;
1140
+ this._activeFourWayPartnerBaseCenter = null;
1141
+ $(document.body).removeClass('lm_intersection_dragging');
1142
+ this._scheduleSetSize();
489
1143
  }
490
1144
  }
491
1145
  //# sourceMappingURL=RowOrColumn.js.map