@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.
- package/css/goldenlayout-base.css +1 -1
- package/css/goldenlayout-base.css.map +1 -1
- package/dist/controls/IntersectionSplitter.d.ts +49 -0
- package/dist/controls/IntersectionSplitter.d.ts.map +1 -0
- package/dist/controls/IntersectionSplitter.js +86 -0
- package/dist/controls/IntersectionSplitter.js.map +1 -0
- package/dist/controls/index.d.ts +1 -0
- package/dist/controls/index.d.ts.map +1 -1
- package/dist/controls/index.js +1 -0
- package/dist/controls/index.js.map +1 -1
- package/dist/items/RowOrColumn.d.ts +189 -0
- package/dist/items/RowOrColumn.d.ts.map +1 -1
- package/dist/items/RowOrColumn.js +655 -1
- package/dist/items/RowOrColumn.js.map +1 -1
- package/package.json +3 -3
- package/scss/goldenlayout-base.scss +30 -0
|
@@ -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
|