@mintplayer/ng-bootstrap 21.23.1 → 21.25.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/fesm2022/mintplayer-ng-bootstrap-dock.mjs +198 -6
- package/fesm2022/mintplayer-ng-bootstrap-dock.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-navbar.mjs +56 -2
- package/fesm2022/mintplayer-ng-bootstrap-navbar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-navigation-lock.mjs +172 -70
- package/fesm2022/mintplayer-ng-bootstrap-navigation-lock.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mintplayer-ng-bootstrap-dock.d.ts +31 -5
- package/types/mintplayer-ng-bootstrap-navbar.d.ts +28 -1
- package/types/mintplayer-ng-bootstrap-navigation-lock.d.ts +97 -37
|
@@ -318,12 +318,21 @@ const styles = unsafeCSS(`:host {
|
|
|
318
318
|
padding: 0.5rem 1rem;
|
|
319
319
|
margin: -0.5rem -1rem;
|
|
320
320
|
touch-action: none;
|
|
321
|
+
user-select: none;
|
|
322
|
+
-webkit-user-select: none;
|
|
323
|
+
-webkit-touch-callout: none;
|
|
321
324
|
}
|
|
322
325
|
|
|
323
326
|
.dock-tab:active {
|
|
324
327
|
cursor: grabbing;
|
|
325
328
|
}
|
|
326
329
|
|
|
330
|
+
.dock-tab[data-pressing=true] {
|
|
331
|
+
background-color: var(--bs-secondary-bg-subtle, rgba(0, 0, 0, 0.05));
|
|
332
|
+
transform: scale(1.02);
|
|
333
|
+
transition: background-color 100ms ease-out, transform 100ms ease-out;
|
|
334
|
+
}
|
|
335
|
+
|
|
327
336
|
.dock-stack__pane {
|
|
328
337
|
position: relative;
|
|
329
338
|
display: flex;
|
|
@@ -422,6 +431,13 @@ class MintDockManagerElement extends LitElement {
|
|
|
422
431
|
return [...(super.observedAttributes ?? []), 'layout'];
|
|
423
432
|
}
|
|
424
433
|
static { this.instanceCounter = 0; }
|
|
434
|
+
// Touch tab-drag gesture: a finger that lands on a tab header must be able
|
|
435
|
+
// to scroll the tabstrip natively. We require a stationary hold before
|
|
436
|
+
// arming the drag, so a horizontal swipe scrolls instead of undocking.
|
|
437
|
+
// See docs/prd/dock-touch-long-press-drag.md.
|
|
438
|
+
static { this.TOUCH_LONG_PRESS_MS = 600; }
|
|
439
|
+
static { this.TOUCH_LONG_PRESS_SLOP_PX = 10; }
|
|
440
|
+
static { this.TOUCH_PRESS_FEEDBACK_DELAY_MS = 150; }
|
|
425
441
|
renderSnapMarkersForCorner() {
|
|
426
442
|
if (!this.showSnapMarkers)
|
|
427
443
|
return;
|
|
@@ -1814,13 +1830,18 @@ class MintDockManagerElement extends LitElement {
|
|
|
1814
1830
|
this.pendingTabDragMetrics = null;
|
|
1815
1831
|
}
|
|
1816
1832
|
/**
|
|
1817
|
-
* Pointerdown handler arms a "may become a drag" gesture.
|
|
1818
|
-
*
|
|
1819
|
-
* {@link
|
|
1820
|
-
*
|
|
1821
|
-
*
|
|
1833
|
+
* Pointerdown handler arms a "may become a drag" gesture. Mouse / pen use a
|
|
1834
|
+
* 5 px distance threshold and arm immediately; touch dispatches to
|
|
1835
|
+
* {@link armPaneDragGestureTouch} which requires a 600 ms stationary hold
|
|
1836
|
+
* (so the user can scroll the tabstrip natively without undocking).
|
|
1837
|
+
* Once armed, both paths converge on {@link beginPaneDrag}. All listeners
|
|
1838
|
+
* self-clean on resolve so the gesture stays scoped to a single pointerdown.
|
|
1822
1839
|
*/
|
|
1823
1840
|
armPaneDragGesture(startEvent, path, pane, stackEl) {
|
|
1841
|
+
if (startEvent.pointerType === 'touch') {
|
|
1842
|
+
this.armPaneDragGestureTouch(startEvent, path, pane, stackEl);
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1824
1845
|
if (startEvent.pointerType === 'mouse' && startEvent.button !== 0)
|
|
1825
1846
|
return;
|
|
1826
1847
|
const win = this.windowRef;
|
|
@@ -1857,6 +1878,152 @@ class MintDockManagerElement extends LitElement {
|
|
|
1857
1878
|
win.addEventListener('pointerup', onRelease, true);
|
|
1858
1879
|
win.addEventListener('pointercancel', onRelease, true);
|
|
1859
1880
|
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Touch-specific gesture arming. With `.dock-tab` set to `touch-action:
|
|
1883
|
+
* none`, the browser never arbitrates the gesture itself, so JS owns it
|
|
1884
|
+
* from frame 1. Three outcomes from the pending state:
|
|
1885
|
+
*
|
|
1886
|
+
* - User holds within {@link TOUCH_LONG_PRESS_SLOP_PX} for
|
|
1887
|
+
* {@link TOUCH_LONG_PRESS_MS} → timer fires → drag arms via
|
|
1888
|
+
* {@link beginPaneDrag}.
|
|
1889
|
+
* - User moves past slop and the move is mostly horizontal, and the
|
|
1890
|
+
* strip's `<ul>` is scrollable → enter `scrolling` mode and drive
|
|
1891
|
+
* `ul.scrollLeft` from JS for the rest of the gesture (no drag, no
|
|
1892
|
+
* momentum — direct 1:1 finger follow).
|
|
1893
|
+
* - User moves past slop in any other direction, releases, or
|
|
1894
|
+
* pointercancel fires → abandoned, no drag, no scroll. Releases
|
|
1895
|
+
* under slop fire the synthesized click that drives `tab-activate`.
|
|
1896
|
+
*
|
|
1897
|
+
* `touch-action: pan-x` was the original PRD design but doesn't work:
|
|
1898
|
+
* the policy is frozen at touchstart and `setPointerCapture` doesn't
|
|
1899
|
+
* promote it, so first move after a long-press fires pointercancel and
|
|
1900
|
+
* strands the panel. JS-driven scroll is the only model that holds.
|
|
1901
|
+
*/
|
|
1902
|
+
armPaneDragGestureTouch(startEvent, path, pane, stackEl) {
|
|
1903
|
+
const win = this.windowRef;
|
|
1904
|
+
if (!win)
|
|
1905
|
+
return;
|
|
1906
|
+
const startX = startEvent.clientX;
|
|
1907
|
+
const startY = startEvent.clientY;
|
|
1908
|
+
const pointerId = startEvent.pointerId;
|
|
1909
|
+
let latestX = startX;
|
|
1910
|
+
let latestY = startY;
|
|
1911
|
+
let lastScrollX = startX;
|
|
1912
|
+
let resolved = false;
|
|
1913
|
+
let scrolling = false;
|
|
1914
|
+
let pressFeedbackApplied = false;
|
|
1915
|
+
const tabSpan = startEvent.currentTarget instanceof HTMLElement
|
|
1916
|
+
? startEvent.currentTarget
|
|
1917
|
+
: startEvent.target instanceof HTMLElement
|
|
1918
|
+
? startEvent.target.closest('.dock-tab')
|
|
1919
|
+
: null;
|
|
1920
|
+
const stripUl = stackEl
|
|
1921
|
+
? this.getStackStripEl(stackEl)?.querySelector('ul.nav.nav-tabs') ?? null
|
|
1922
|
+
: null;
|
|
1923
|
+
const cleanup = () => {
|
|
1924
|
+
if (resolved)
|
|
1925
|
+
return;
|
|
1926
|
+
resolved = true;
|
|
1927
|
+
win.clearTimeout(longPressTimeout);
|
|
1928
|
+
win.clearTimeout(pressFeedbackTimeout);
|
|
1929
|
+
win.removeEventListener('pointermove', onMove, true);
|
|
1930
|
+
win.removeEventListener('pointerup', onRelease, true);
|
|
1931
|
+
win.removeEventListener('pointercancel', onCancel, true);
|
|
1932
|
+
if (pressFeedbackApplied && tabSpan) {
|
|
1933
|
+
tabSpan.removeAttribute('data-pressing');
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
const onMove = (event) => {
|
|
1937
|
+
if (resolved || event.pointerId !== pointerId)
|
|
1938
|
+
return;
|
|
1939
|
+
if (scrolling) {
|
|
1940
|
+
const dx = event.clientX - lastScrollX;
|
|
1941
|
+
lastScrollX = event.clientX;
|
|
1942
|
+
if (stripUl)
|
|
1943
|
+
stripUl.scrollLeft -= dx;
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
latestX = event.clientX;
|
|
1947
|
+
latestY = event.clientY;
|
|
1948
|
+
const dx = event.clientX - startX;
|
|
1949
|
+
const dy = event.clientY - startY;
|
|
1950
|
+
if (Math.hypot(dx, dy) <= MintDockManagerElement.TOUCH_LONG_PRESS_SLOP_PX) {
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
// Slop crossed before the long-press fired. If the move is mostly
|
|
1954
|
+
// horizontal and the strip can scroll, take over scroll in JS;
|
|
1955
|
+
// otherwise abandon (no drag — browser native UI is fully suppressed
|
|
1956
|
+
// by `touch-action: none` so no fallback is needed).
|
|
1957
|
+
const stripScrollable = !!stripUl && stripUl.scrollWidth > stripUl.clientWidth;
|
|
1958
|
+
if (Math.abs(dx) > Math.abs(dy) && stripScrollable) {
|
|
1959
|
+
win.clearTimeout(longPressTimeout);
|
|
1960
|
+
win.clearTimeout(pressFeedbackTimeout);
|
|
1961
|
+
if (pressFeedbackApplied && tabSpan) {
|
|
1962
|
+
tabSpan.removeAttribute('data-pressing');
|
|
1963
|
+
pressFeedbackApplied = false;
|
|
1964
|
+
}
|
|
1965
|
+
scrolling = true;
|
|
1966
|
+
lastScrollX = event.clientX;
|
|
1967
|
+
stripUl.scrollLeft -= dx;
|
|
1968
|
+
this.clearPendingTabDragMetrics();
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
cleanup();
|
|
1972
|
+
this.clearPendingTabDragMetrics();
|
|
1973
|
+
};
|
|
1974
|
+
const onRelease = (event) => {
|
|
1975
|
+
if (resolved || event.pointerId !== pointerId)
|
|
1976
|
+
return;
|
|
1977
|
+
cleanup();
|
|
1978
|
+
this.clearPendingTabDragMetrics();
|
|
1979
|
+
};
|
|
1980
|
+
const onCancel = (event) => {
|
|
1981
|
+
if (resolved || event.pointerId !== pointerId)
|
|
1982
|
+
return;
|
|
1983
|
+
cleanup();
|
|
1984
|
+
this.clearPendingTabDragMetrics();
|
|
1985
|
+
};
|
|
1986
|
+
const longPressTimeout = win.setTimeout(() => {
|
|
1987
|
+
if (resolved)
|
|
1988
|
+
return;
|
|
1989
|
+
cleanup();
|
|
1990
|
+
try {
|
|
1991
|
+
this.setPointerCapture(pointerId);
|
|
1992
|
+
}
|
|
1993
|
+
catch {
|
|
1994
|
+
// setPointerCapture throws if the pointer is no longer active
|
|
1995
|
+
// (e.g. timer raced a pointerup we didn't see). Drag will abort
|
|
1996
|
+
// naturally on the next event.
|
|
1997
|
+
}
|
|
1998
|
+
if (typeof navigator !== 'undefined' && typeof navigator.vibrate === 'function') {
|
|
1999
|
+
try {
|
|
2000
|
+
navigator.vibrate(10);
|
|
2001
|
+
}
|
|
2002
|
+
catch {
|
|
2003
|
+
// ignore
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
const synthEvent = new PointerEvent('pointermove', {
|
|
2007
|
+
pointerId,
|
|
2008
|
+
pointerType: 'touch',
|
|
2009
|
+
isPrimary: true,
|
|
2010
|
+
clientX: latestX,
|
|
2011
|
+
clientY: latestY,
|
|
2012
|
+
bubbles: false,
|
|
2013
|
+
cancelable: false,
|
|
2014
|
+
});
|
|
2015
|
+
this.beginPaneDrag(synthEvent, path, pane, stackEl);
|
|
2016
|
+
}, MintDockManagerElement.TOUCH_LONG_PRESS_MS);
|
|
2017
|
+
const pressFeedbackTimeout = win.setTimeout(() => {
|
|
2018
|
+
if (resolved || !tabSpan)
|
|
2019
|
+
return;
|
|
2020
|
+
pressFeedbackApplied = true;
|
|
2021
|
+
tabSpan.setAttribute('data-pressing', 'true');
|
|
2022
|
+
}, MintDockManagerElement.TOUCH_PRESS_FEEDBACK_DELAY_MS);
|
|
2023
|
+
win.addEventListener('pointermove', onMove, true);
|
|
2024
|
+
win.addEventListener('pointerup', onRelease, true);
|
|
2025
|
+
win.addEventListener('pointercancel', onCancel, true);
|
|
2026
|
+
}
|
|
1860
2027
|
beginPaneDrag(event, path, pane, stackEl) {
|
|
1861
2028
|
const { path: sourcePath, floatingIndex, pointerOffsetX, pointerOffsetY, } = this.preparePaneDragSource(path, pane, stackEl, event);
|
|
1862
2029
|
// Capture header bounds for detecting when to convert to floating.
|
|
@@ -2420,7 +2587,32 @@ class MintDockManagerElement extends LitElement {
|
|
|
2420
2587
|
const placeholderButton = placeholderTabId
|
|
2421
2588
|
? allTabButtons.find((b) => b.id === `${placeholderTabId}-header-button`) ?? null
|
|
2422
2589
|
: null;
|
|
2423
|
-
|
|
2590
|
+
// The dragged pane's content is `data-hidden`, but mp-tab-control's strip
|
|
2591
|
+
// re-render is async (its MutationObserver schedules a Lit microtask). When
|
|
2592
|
+
// beginPaneDrag calls updateDraggedFloatingPositionFromPoint synchronously
|
|
2593
|
+
// right after preparePaneDragSource, this runs before that re-render — so
|
|
2594
|
+
// the dragged button is still in the strip's shadow. We must exclude it
|
|
2595
|
+
// explicitly, otherwise the loop counts it as a real target and the
|
|
2596
|
+
// placeholder gets appended past the live tabs (visible on touch
|
|
2597
|
+
// long-press, where the finger doesn't move and the user sees the
|
|
2598
|
+
// mis-positioned placeholder for the duration of the hold).
|
|
2599
|
+
const draggedPane = this.dragState?.pane ?? null;
|
|
2600
|
+
let draggedTabId = null;
|
|
2601
|
+
if (draggedPane) {
|
|
2602
|
+
for (const child of Array.from(stack.children)) {
|
|
2603
|
+
if (child instanceof HTMLElement &&
|
|
2604
|
+
child.classList.contains('dock-tab') &&
|
|
2605
|
+
!child.hasAttribute('data-placeholder') &&
|
|
2606
|
+
child.dataset['pane'] === draggedPane) {
|
|
2607
|
+
draggedTabId = child.dataset['tabId'] ?? null;
|
|
2608
|
+
break;
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
const draggedButton = draggedTabId
|
|
2613
|
+
? allTabButtons.find((b) => b.id === `${draggedTabId}-header-button`) ?? null
|
|
2614
|
+
: null;
|
|
2615
|
+
const targets = allTabButtons.filter((b) => b !== placeholderButton && b !== draggedButton);
|
|
2424
2616
|
if (targets.length === 0) {
|
|
2425
2617
|
return 0;
|
|
2426
2618
|
}
|