@jsenv/dom 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_dom.js +283 -145
- package/package.json +1 -1
package/dist/jsenv_dom.js
CHANGED
|
@@ -1908,6 +1908,8 @@ const pxPropertySet = new Set([
|
|
|
1908
1908
|
"paddingRight",
|
|
1909
1909
|
"paddingBottom",
|
|
1910
1910
|
"paddingLeft",
|
|
1911
|
+
"outlineWidth",
|
|
1912
|
+
"outlineOffset",
|
|
1911
1913
|
"borderWidth",
|
|
1912
1914
|
"borderTopWidth",
|
|
1913
1915
|
"borderRightWidth",
|
|
@@ -5417,6 +5419,31 @@ const getScrollContainerSet = (element) => {
|
|
|
5417
5419
|
return scrollContainerSet;
|
|
5418
5420
|
};
|
|
5419
5421
|
|
|
5422
|
+
/**
|
|
5423
|
+
* Rounds a CSS pixel value to the nearest physical pixel boundary for the current display.
|
|
5424
|
+
*
|
|
5425
|
+
* At zoom levels other than 100%, `devicePixelRatio` is not an integer (e.g. 1.25, 1.5),
|
|
5426
|
+
* so fractional CSS pixel values from `getBoundingClientRect()` may not align to the physical
|
|
5427
|
+
* pixel grid. Setting `top`/`left` to such values causes the browser to interpolate across
|
|
5428
|
+
* pixels, resulting in blurry rendering or misalignment with adjacent elements.
|
|
5429
|
+
*
|
|
5430
|
+
* Snapping to the physical grid ensures the value falls exactly on a pixel boundary.
|
|
5431
|
+
*
|
|
5432
|
+
* @param {number} value - A CSS pixel value (e.g. from getBoundingClientRect or scroll offset).
|
|
5433
|
+
* @returns {number} The nearest physical-pixel-aligned CSS pixel value.
|
|
5434
|
+
* @example
|
|
5435
|
+
* // At devicePixelRatio 1.25, snapToPixel(154.4) → 154.4 (already on grid)
|
|
5436
|
+
* // At devicePixelRatio 1.25, snapToPixel(154.3) → 154.4
|
|
5437
|
+
*/
|
|
5438
|
+
const snapToPixel = (value) => {
|
|
5439
|
+
return Math.round(value * devicePixelRatio) / devicePixelRatio;
|
|
5440
|
+
};
|
|
5441
|
+
|
|
5442
|
+
// Round a CSS-pixel value to the nearest physical pixel boundary.
|
|
5443
|
+
// At zoom levels other than 100%, devicePixelRatio is not an integer (e.g. 1.25, 1.5),
|
|
5444
|
+
// so CSS pixels don't align 1:1 with physical pixels. Rounding to the physical grid
|
|
5445
|
+
// ensures the browser can render the element without sub-pixel blurring.
|
|
5446
|
+
|
|
5420
5447
|
const getBorderSizes = (element) => {
|
|
5421
5448
|
const {
|
|
5422
5449
|
borderLeftWidth,
|
|
@@ -5424,11 +5451,12 @@ const getBorderSizes = (element) => {
|
|
|
5424
5451
|
borderTopWidth,
|
|
5425
5452
|
borderBottomWidth,
|
|
5426
5453
|
} = window.getComputedStyle(element, null);
|
|
5454
|
+
|
|
5427
5455
|
return {
|
|
5428
|
-
left: parseFloat(borderLeftWidth),
|
|
5429
|
-
right: parseFloat(borderRightWidth),
|
|
5430
|
-
top: parseFloat(borderTopWidth),
|
|
5431
|
-
bottom: parseFloat(borderBottomWidth),
|
|
5456
|
+
left: snapToPixel(parseFloat(borderLeftWidth)),
|
|
5457
|
+
right: snapToPixel(parseFloat(borderRightWidth)),
|
|
5458
|
+
top: snapToPixel(parseFloat(borderTopWidth)),
|
|
5459
|
+
bottom: snapToPixel(parseFloat(borderBottomWidth)),
|
|
5432
5460
|
};
|
|
5433
5461
|
};
|
|
5434
5462
|
|
|
@@ -5780,8 +5808,8 @@ const measureScrollbar = (scrollableElement) => {
|
|
|
5780
5808
|
const scrollbarHeight = scrollDiv.offsetHeight - scrollDiv.clientHeight;
|
|
5781
5809
|
scrollableElement.removeChild(scrollDiv);
|
|
5782
5810
|
return [
|
|
5783
|
-
hasXScrollbar ? scrollbarWidth : 0,
|
|
5784
|
-
hasYScrollbar ? scrollbarHeight : 0,
|
|
5811
|
+
hasXScrollbar ? snapToPixel(scrollbarWidth) : 0,
|
|
5812
|
+
hasYScrollbar ? snapToPixel(scrollbarHeight) : 0,
|
|
5785
5813
|
];
|
|
5786
5814
|
};
|
|
5787
5815
|
|
|
@@ -6094,6 +6122,18 @@ const scrollIntoViewWithStickyAwareness = (
|
|
|
6094
6122
|
}
|
|
6095
6123
|
};
|
|
6096
6124
|
|
|
6125
|
+
const getPaddingSizes = (element) => {
|
|
6126
|
+
const { paddingLeft, paddingRight, paddingTop, paddingBottom } =
|
|
6127
|
+
window.getComputedStyle(element, null);
|
|
6128
|
+
|
|
6129
|
+
return {
|
|
6130
|
+
left: snapToPixel(parseFloat(paddingLeft)),
|
|
6131
|
+
right: snapToPixel(parseFloat(paddingRight)),
|
|
6132
|
+
top: snapToPixel(parseFloat(paddingTop)),
|
|
6133
|
+
bottom: snapToPixel(parseFloat(paddingBottom)),
|
|
6134
|
+
};
|
|
6135
|
+
};
|
|
6136
|
+
|
|
6097
6137
|
/**
|
|
6098
6138
|
* Prevents scrolling on all scrollable containers that are ancestors of (or
|
|
6099
6139
|
* siblings preceding) `element`. Used when an overlay (popover, dialog) is
|
|
@@ -6130,11 +6170,10 @@ const trapScrollInside = (element) => {
|
|
|
6130
6170
|
return;
|
|
6131
6171
|
}
|
|
6132
6172
|
const [scrollbarWidth, scrollbarHeight] = measureScrollbar(el);
|
|
6133
|
-
const
|
|
6134
|
-
const paddingTop = parseInt(getStyle(el, "padding-top"), 0);
|
|
6173
|
+
const { right, bottom } = getPaddingSizes(el);
|
|
6135
6174
|
const removeScrollLockStyles = setStyles(el, {
|
|
6136
|
-
"padding-right": `${
|
|
6137
|
-
"padding-
|
|
6175
|
+
"padding-right": `${right + scrollbarWidth}px`,
|
|
6176
|
+
"padding-bottom": `${bottom + scrollbarHeight}px`,
|
|
6138
6177
|
"overflow": "hidden",
|
|
6139
6178
|
});
|
|
6140
6179
|
cleanupCallbackSet.add(removeScrollLockStyles);
|
|
@@ -9575,6 +9614,10 @@ const stickyAsRelativeCoords = (
|
|
|
9575
9614
|
return [leftPosition, topPosition];
|
|
9576
9615
|
};
|
|
9577
9616
|
|
|
9617
|
+
// Minimum fraction of element width/height that must be visible on the preferred side
|
|
9618
|
+
// before flipping to the opposite side. Prevents flickering near the flip threshold.
|
|
9619
|
+
const MIN_CONTENT_VISIBILITY_RATIO = 0.6;
|
|
9620
|
+
|
|
9578
9621
|
/**
|
|
9579
9622
|
* Tracks how much of an element is visible within its scrollable parent and within the
|
|
9580
9623
|
* document viewport. Calls update() on initialization and whenever visibility changes
|
|
@@ -9876,49 +9919,56 @@ const visibleRectEffect = (element, update) => {
|
|
|
9876
9919
|
};
|
|
9877
9920
|
|
|
9878
9921
|
/**
|
|
9879
|
-
* Places element
|
|
9922
|
+
* Places element relative to anchor with independent control of horizontal and vertical axes.
|
|
9880
9923
|
*
|
|
9881
|
-
*
|
|
9882
|
-
*
|
|
9883
|
-
*
|
|
9884
|
-
*
|
|
9885
|
-
*
|
|
9886
|
-
*
|
|
9887
|
-
* ```
|
|
9924
|
+
* Horizontal axis — positionX / positionXFixed (left → right):
|
|
9925
|
+
* "to-the-left" element.right = anchor.left (sits entirely to the left of anchor)
|
|
9926
|
+
* "left-aligned" element.left = anchor.left (left edges aligned)
|
|
9927
|
+
* "center" element centered horizontally over anchor (default)
|
|
9928
|
+
* "right-aligned" element.right = anchor.right (right edges aligned)
|
|
9929
|
+
* "to-the-right" element.left = anchor.right (sits entirely to the right of anchor)
|
|
9888
9930
|
*
|
|
9889
|
-
*
|
|
9890
|
-
*
|
|
9891
|
-
* -
|
|
9892
|
-
*
|
|
9893
|
-
* -
|
|
9894
|
-
*
|
|
9895
|
-
*
|
|
9896
|
-
*
|
|
9897
|
-
*
|
|
9898
|
-
*
|
|
9931
|
+
* Vertical axis — positionY / positionYFixed (top → bottom):
|
|
9932
|
+
* "above" element.bottom = anchor.top (sits above, no overlap)
|
|
9933
|
+
* "above-overlap" element.bottom = anchor.bottom (sits above, overlapping anchor)
|
|
9934
|
+
* "center" element centered vertically over anchor
|
|
9935
|
+
* "below-overlap" element.top = anchor.top (sits below, overlapping anchor)
|
|
9936
|
+
* "below" element.top = anchor.bottom (sits below, no overlap) (default)
|
|
9937
|
+
*
|
|
9938
|
+
* positionX / positionY attempt the requested placement and automatically flip to the
|
|
9939
|
+
* logical opposite when the element does not fit in the viewport:
|
|
9940
|
+
* above ↔ below, above-overlap ↔ below-overlap
|
|
9941
|
+
*
|
|
9942
|
+
* positionXFixed / positionYFixed skip the fit check entirely.
|
|
9943
|
+
*
|
|
9944
|
+
* The resolved X and Y are persisted as data-position-x-current / data-position-y-current
|
|
9945
|
+
* on the element so subsequent calls start from the last resolved position (avoids
|
|
9946
|
+
* flickering when the element is near the flip threshold). Fixed axes are not persisted.
|
|
9899
9947
|
*
|
|
9900
9948
|
* @param {HTMLElement} element - The element to position (must be document-relative)
|
|
9901
9949
|
* @param {HTMLElement} anchor - The anchor element to position against
|
|
9902
9950
|
* @param {object} [options]
|
|
9903
|
-
* @param {string} [options.
|
|
9904
|
-
*
|
|
9905
|
-
*
|
|
9906
|
-
*
|
|
9907
|
-
* the last resolved position is persisted as data-position-current to avoid flickering.
|
|
9908
|
-
* @param {string} [options.position] - Force a specific position, skipping the fit-check.
|
|
9951
|
+
* @param {string} [options.positionX="center"] - Preferred X placement, with viewport fallback.
|
|
9952
|
+
* @param {string} [options.positionY="below"] - Preferred Y placement, with viewport fallback.
|
|
9953
|
+
* @param {string} [options.positionXFixed] - Force X placement, skipping the fit-check.
|
|
9954
|
+
* @param {string} [options.positionYFixed] - Force Y placement, skipping the fit-check.
|
|
9909
9955
|
* @param {number} [options.alignToViewportEdgeWhenAnchorNearEdge=0] - Snap to viewport left
|
|
9910
9956
|
* edge when anchor is within this many px of the left edge and element is wider than anchor.
|
|
9911
9957
|
* @param {number} [options.minLeft=0] - Minimum left coordinate (document-relative).
|
|
9912
|
-
* @returns {{
|
|
9958
|
+
* @returns {{ positionX, positionY, left, top, width, height, anchorLeft, anchorTop, anchorRight, anchorBottom, spaceLeft, spaceRight, spaceAbove, spaceBelow }}
|
|
9913
9959
|
*/
|
|
9914
9960
|
const pickPositionRelativeTo = (
|
|
9915
9961
|
element,
|
|
9916
9962
|
anchor,
|
|
9917
9963
|
{
|
|
9918
|
-
|
|
9919
|
-
|
|
9964
|
+
positionX = "center",
|
|
9965
|
+
positionY = "below",
|
|
9966
|
+
positionXFixed,
|
|
9967
|
+
positionYFixed,
|
|
9920
9968
|
alignToViewportEdgeWhenAnchorNearEdge = 0,
|
|
9921
9969
|
minLeft = 0,
|
|
9970
|
+
spacing = 0,
|
|
9971
|
+
viewportSpacing = 0,
|
|
9922
9972
|
} = {},
|
|
9923
9973
|
) => {
|
|
9924
9974
|
|
|
@@ -9933,72 +9983,160 @@ const pickPositionRelativeTo = (
|
|
|
9933
9983
|
top: elementTop,
|
|
9934
9984
|
bottom: elementBottom,
|
|
9935
9985
|
} = elementRect;
|
|
9936
|
-
const
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
bottom: anchorBottom,
|
|
9941
|
-
} = anchorRect;
|
|
9986
|
+
const anchorLeft = snapToPixel(anchorRect.left);
|
|
9987
|
+
const anchorTop = snapToPixel(anchorRect.top);
|
|
9988
|
+
const anchorRight = snapToPixel(anchorRect.right);
|
|
9989
|
+
const anchorBottom = snapToPixel(anchorRect.bottom);
|
|
9942
9990
|
const elementWidth = elementRight - elementLeft;
|
|
9943
9991
|
const elementHeight = elementBottom - elementTop;
|
|
9944
9992
|
const anchorWidth = anchorRight - anchorLeft;
|
|
9945
9993
|
const anchorHeight = anchorBottom - anchorTop;
|
|
9946
9994
|
|
|
9947
|
-
// Determine the active position: position wins, then data-position-current (last resolved),
|
|
9948
|
-
// then data-position-try attribute (user preference), then positionTry param
|
|
9949
|
-
let activePosition;
|
|
9950
|
-
if (position) {
|
|
9951
|
-
activePosition = position;
|
|
9952
|
-
} else {
|
|
9953
|
-
const positionCurrentFromAttribute = element.getAttribute(
|
|
9954
|
-
"data-position-current",
|
|
9955
|
-
);
|
|
9956
|
-
const positionTryFromAttribute = element.getAttribute("data-position-try");
|
|
9957
|
-
activePosition =
|
|
9958
|
-
positionCurrentFromAttribute || positionTryFromAttribute || positionTry;
|
|
9959
|
-
}
|
|
9960
|
-
|
|
9961
9995
|
const spaceAbove = anchorTop;
|
|
9962
9996
|
const spaceBelow = viewportHeight - anchorBottom;
|
|
9997
|
+
const spaceLeft = anchorLeft;
|
|
9998
|
+
const spaceRight = viewportWidth - anchorRight;
|
|
9999
|
+
|
|
10000
|
+
// Resolve active X and Y, and whether each is fixed (no flip fallback)
|
|
10001
|
+
let activeX;
|
|
10002
|
+
let activeY;
|
|
10003
|
+
const xIsFixed = Boolean(positionXFixed);
|
|
10004
|
+
const yIsFixed = Boolean(positionYFixed);
|
|
10005
|
+
const hasStoredY = Boolean(element.getAttribute("data-position-y-current"));
|
|
10006
|
+
const hasStoredX = Boolean(element.getAttribute("data-position-x-current"));
|
|
10007
|
+
if (xIsFixed) {
|
|
10008
|
+
activeX = positionXFixed;
|
|
10009
|
+
} else {
|
|
10010
|
+
const storedX = element.getAttribute("data-position-x-current");
|
|
10011
|
+
activeX = storedX ?? positionX;
|
|
10012
|
+
}
|
|
10013
|
+
if (yIsFixed) {
|
|
10014
|
+
activeY = positionYFixed;
|
|
10015
|
+
} else {
|
|
10016
|
+
const storedY = element.getAttribute("data-position-y-current");
|
|
10017
|
+
activeY = storedY ?? positionY;
|
|
10018
|
+
}
|
|
9963
10019
|
|
|
9964
|
-
// Resolve
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
10020
|
+
// Resolve final Y
|
|
10021
|
+
let finalY;
|
|
10022
|
+
{
|
|
10023
|
+
const oppositeY = {
|
|
10024
|
+
"above": "below",
|
|
10025
|
+
"below": "above",
|
|
10026
|
+
"above-overlap": "below-overlap",
|
|
10027
|
+
"below-overlap": "above-overlap",
|
|
10028
|
+
};
|
|
10029
|
+
// Compute effective space for a given Y value
|
|
10030
|
+
const spaceFor = (y) => {
|
|
10031
|
+
if (y === "above") {
|
|
10032
|
+
return spaceAbove - spacing - viewportSpacing;
|
|
10033
|
+
}
|
|
10034
|
+
if (y === "above-overlap") {
|
|
10035
|
+
return spaceAbove + anchorHeight - viewportSpacing;
|
|
10036
|
+
}
|
|
10037
|
+
if (y === "below") {
|
|
10038
|
+
return spaceBelow - spacing - viewportSpacing;
|
|
10039
|
+
}
|
|
10040
|
+
if (y === "below-overlap") {
|
|
10041
|
+
return spaceBelow + anchorHeight - viewportSpacing;
|
|
10042
|
+
}
|
|
10043
|
+
return Infinity; // center
|
|
10044
|
+
};
|
|
10045
|
+
if (yIsFixed || activeY === "center") {
|
|
10046
|
+
finalY = activeY;
|
|
10047
|
+
} else if (!hasStoredY) {
|
|
10048
|
+
// Never positioned before — pick the best side from scratch.
|
|
10049
|
+
const preferred = positionY;
|
|
10050
|
+
const opposite = oppositeY[preferred];
|
|
10051
|
+
const preferredFits = spaceFor(preferred) >= elementHeight;
|
|
10052
|
+
const oppositeFits = spaceFor(opposite) >= elementHeight;
|
|
10053
|
+
if (preferredFits) {
|
|
10054
|
+
// Preferred fits completely — use it (even if opposite also fits)
|
|
10055
|
+
finalY = preferred;
|
|
10056
|
+
} else if (oppositeFits) {
|
|
10057
|
+
// Only opposite fits completely — flip
|
|
10058
|
+
finalY = opposite;
|
|
10059
|
+
} else {
|
|
10060
|
+
// Neither fits completely — use whichever meets the minimum ratio
|
|
10061
|
+
const preferredMeetsRatio =
|
|
10062
|
+
spaceFor(preferred) / elementHeight >= MIN_CONTENT_VISIBILITY_RATIO;
|
|
10063
|
+
finalY = preferredMeetsRatio ? preferred : opposite;
|
|
10064
|
+
}
|
|
9980
10065
|
} else {
|
|
9981
|
-
|
|
10066
|
+
// Previously positioned — stay as long as current side meets minimum ratio
|
|
10067
|
+
const currentFitsEnough =
|
|
10068
|
+
spaceFor(activeY) / elementHeight >= MIN_CONTENT_VISIBILITY_RATIO;
|
|
10069
|
+
if (currentFitsEnough) {
|
|
10070
|
+
finalY = activeY;
|
|
10071
|
+
} else {
|
|
10072
|
+
finalY = oppositeY[activeY];
|
|
10073
|
+
}
|
|
9982
10074
|
}
|
|
9983
|
-
}
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
10075
|
+
}
|
|
10076
|
+
|
|
10077
|
+
// Resolve final X
|
|
10078
|
+
let finalX;
|
|
10079
|
+
{
|
|
10080
|
+
const oppositeX = {
|
|
10081
|
+
"to-the-left": "to-the-right",
|
|
10082
|
+
"to-the-right": "to-the-left",
|
|
10083
|
+
"left-aligned": "right-aligned",
|
|
10084
|
+
"right-aligned": "left-aligned",
|
|
10085
|
+
};
|
|
10086
|
+
// Compute effective space for a given X value
|
|
10087
|
+
const spaceFor = (x) => {
|
|
10088
|
+
if (x === "to-the-left") {
|
|
10089
|
+
return spaceLeft - spacing - viewportSpacing;
|
|
10090
|
+
}
|
|
10091
|
+
if (x === "left-aligned") {
|
|
10092
|
+
return viewportWidth - anchorLeft - viewportSpacing;
|
|
10093
|
+
}
|
|
10094
|
+
if (x === "right-aligned") {
|
|
10095
|
+
return anchorRight - viewportSpacing;
|
|
10096
|
+
}
|
|
10097
|
+
if (x === "to-the-right") {
|
|
10098
|
+
return spaceRight - spacing - viewportSpacing;
|
|
10099
|
+
}
|
|
10100
|
+
return Infinity; // center
|
|
10101
|
+
};
|
|
10102
|
+
if (xIsFixed || activeX === "center") {
|
|
10103
|
+
finalX = activeX;
|
|
10104
|
+
} else if (!hasStoredX) {
|
|
10105
|
+
// Never positioned before — pick the best side from scratch.
|
|
10106
|
+
const preferred = positionX;
|
|
10107
|
+
const opposite = oppositeX[preferred];
|
|
10108
|
+
const preferredFits = spaceFor(preferred) >= elementWidth;
|
|
10109
|
+
const oppositeFits = spaceFor(opposite) >= elementWidth;
|
|
10110
|
+
if (preferredFits) {
|
|
10111
|
+
finalX = preferred;
|
|
10112
|
+
} else if (oppositeFits) {
|
|
10113
|
+
finalX = opposite;
|
|
10114
|
+
} else {
|
|
10115
|
+
const preferredMeetsRatio =
|
|
10116
|
+
spaceFor(preferred) / elementWidth >= MIN_CONTENT_VISIBILITY_RATIO;
|
|
10117
|
+
finalX = preferredMeetsRatio ? preferred : opposite;
|
|
10118
|
+
}
|
|
9988
10119
|
} else {
|
|
9989
|
-
|
|
10120
|
+
// Previously positioned — stay as long as current side meets minimum ratio
|
|
10121
|
+
const currentFitsEnough =
|
|
10122
|
+
spaceFor(activeX) / elementWidth >= MIN_CONTENT_VISIBILITY_RATIO;
|
|
10123
|
+
if (currentFitsEnough) {
|
|
10124
|
+
finalX = activeX;
|
|
10125
|
+
} else {
|
|
10126
|
+
finalX = oppositeX[activeX];
|
|
10127
|
+
}
|
|
9990
10128
|
}
|
|
9991
10129
|
}
|
|
9992
10130
|
|
|
9993
10131
|
// Calculate horizontal position (viewport-relative)
|
|
9994
10132
|
let elementPositionLeft;
|
|
9995
10133
|
{
|
|
9996
|
-
if (
|
|
9997
|
-
elementPositionLeft = anchorLeft - elementWidth;
|
|
9998
|
-
} else if (
|
|
9999
|
-
elementPositionLeft =
|
|
10000
|
-
} else {
|
|
10001
|
-
//
|
|
10134
|
+
if (finalX === "to-the-left") {
|
|
10135
|
+
elementPositionLeft = anchorLeft - elementWidth - spacing;
|
|
10136
|
+
} else if (finalX === "left-aligned") {
|
|
10137
|
+
elementPositionLeft = anchorLeft;
|
|
10138
|
+
} else if (finalX === "center") {
|
|
10139
|
+
// Complex logic handles wide anchors and viewport-edge snapping
|
|
10002
10140
|
const anchorIsWiderThanViewport = anchorWidth > viewportWidth;
|
|
10003
10141
|
if (anchorIsWiderThanViewport) {
|
|
10004
10142
|
const anchorLeftIsVisible = anchorLeft >= 0;
|
|
@@ -10027,65 +10165,85 @@ const pickPositionRelativeTo = (
|
|
|
10027
10165
|
}
|
|
10028
10166
|
}
|
|
10029
10167
|
}
|
|
10168
|
+
} else if (finalX === "right-aligned") {
|
|
10169
|
+
elementPositionLeft = anchorRight - elementWidth;
|
|
10170
|
+
} else {
|
|
10171
|
+
// "to-the-right"
|
|
10172
|
+
elementPositionLeft = anchorRight + spacing;
|
|
10030
10173
|
}
|
|
10031
|
-
// Constrain horizontal position to viewport boundaries
|
|
10032
|
-
if (elementPositionLeft <
|
|
10033
|
-
elementPositionLeft =
|
|
10034
|
-
} else if (
|
|
10035
|
-
elementPositionLeft
|
|
10174
|
+
// Constrain horizontal position to viewport boundaries (with viewportSpacing margin)
|
|
10175
|
+
if (elementPositionLeft < viewportSpacing) {
|
|
10176
|
+
elementPositionLeft = viewportSpacing;
|
|
10177
|
+
} else if (
|
|
10178
|
+
elementPositionLeft + elementWidth >
|
|
10179
|
+
viewportWidth - viewportSpacing
|
|
10180
|
+
) {
|
|
10181
|
+
elementPositionLeft = viewportWidth - viewportSpacing - elementWidth;
|
|
10036
10182
|
}
|
|
10037
10183
|
}
|
|
10038
10184
|
|
|
10039
10185
|
// Calculate vertical position (viewport-relative)
|
|
10040
10186
|
let elementPositionTop;
|
|
10041
10187
|
{
|
|
10042
|
-
if (
|
|
10188
|
+
if (finalY === "above") {
|
|
10189
|
+
// top is always anchorTop - elementHeight - spacing — max-height truncates if needed.
|
|
10190
|
+
const idealTop = anchorTop - elementHeight - spacing;
|
|
10191
|
+
elementPositionTop =
|
|
10192
|
+
idealTop < viewportSpacing ? viewportSpacing : idealTop;
|
|
10193
|
+
} else if (finalY === "above-overlap") {
|
|
10194
|
+
const idealTop = anchorBottom - elementHeight;
|
|
10195
|
+
elementPositionTop =
|
|
10196
|
+
idealTop < viewportSpacing ? viewportSpacing : idealTop;
|
|
10197
|
+
} else if (finalY === "center") {
|
|
10043
10198
|
elementPositionTop = anchorTop + anchorHeight / 2 - elementHeight / 2;
|
|
10044
|
-
} else if (
|
|
10045
|
-
const idealTop =
|
|
10199
|
+
} else if (finalY === "below-overlap") {
|
|
10200
|
+
const idealTop = anchorTop;
|
|
10046
10201
|
elementPositionTop =
|
|
10047
10202
|
idealTop % 1 === 0 ? idealTop : Math.floor(idealTop) + 1;
|
|
10048
10203
|
} else {
|
|
10049
|
-
// "
|
|
10050
|
-
|
|
10051
|
-
|
|
10204
|
+
// "below"
|
|
10205
|
+
// top is always anchorBottom + spacing — max-height (via --space-available) truncates
|
|
10206
|
+
// the element height so it doesn't overflow the viewport bottom.
|
|
10207
|
+
const idealTop = anchorBottom + spacing;
|
|
10208
|
+
elementPositionTop =
|
|
10209
|
+
idealTop % 1 === 0 ? idealTop : Math.floor(idealTop) + 1;
|
|
10052
10210
|
}
|
|
10053
10211
|
}
|
|
10054
10212
|
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
if (vertPart && horzPart) {
|
|
10060
|
-
finalPosition = `${vertPart}-${horzPart}`;
|
|
10061
|
-
} else if (vertPart) {
|
|
10062
|
-
finalPosition = vertPart;
|
|
10063
|
-
} else if (horzPart) {
|
|
10064
|
-
finalPosition = horzPart;
|
|
10065
|
-
} else {
|
|
10066
|
-
finalPosition = "center";
|
|
10067
|
-
}
|
|
10213
|
+
// Persist resolved X/Y so subsequent calls start from here (avoids flickering).
|
|
10214
|
+
// Fixed axes are not persisted.
|
|
10215
|
+
if (!xIsFixed) {
|
|
10216
|
+
element.setAttribute("data-position-x-current", finalX);
|
|
10068
10217
|
}
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
// (avoids flickering between positions when the element is near the threshold).
|
|
10072
|
-
// position is not persisted — it is always explicit.
|
|
10073
|
-
|
|
10074
|
-
if (!position) {
|
|
10075
|
-
element.setAttribute("data-position-current", finalPosition);
|
|
10218
|
+
if (!yIsFixed) {
|
|
10219
|
+
element.setAttribute("data-position-y-current", finalY);
|
|
10076
10220
|
}
|
|
10077
10221
|
|
|
10078
10222
|
// Get document scroll for final coordinate conversion
|
|
10079
10223
|
const { scrollLeft, scrollTop } = document.documentElement;
|
|
10080
|
-
const elementDocumentLeft = elementPositionLeft + scrollLeft;
|
|
10081
|
-
const elementDocumentTop = elementPositionTop + scrollTop;
|
|
10224
|
+
const elementDocumentLeft = snapToPixel(elementPositionLeft + scrollLeft);
|
|
10225
|
+
const elementDocumentTop = snapToPixel(elementPositionTop + scrollTop);
|
|
10082
10226
|
const anchorDocumentLeft = anchorLeft + scrollLeft;
|
|
10083
10227
|
const anchorDocumentTop = anchorTop + scrollTop;
|
|
10084
10228
|
const anchorDocumentRight = anchorRight + scrollLeft;
|
|
10085
10229
|
const anchorDocumentBottom = anchorBottom + scrollTop;
|
|
10086
10230
|
|
|
10231
|
+
// For overlap variants the element starts at the anchor edge (not past it),
|
|
10232
|
+
// so the usable space includes the anchor dimension.
|
|
10233
|
+
// spacing (gap between anchor and element) and viewportSpacing are subtracted
|
|
10234
|
+
// so callers get the net usable space directly.
|
|
10235
|
+
const effectiveSpaceAbove =
|
|
10236
|
+
(finalY === "above-overlap" ? spaceAbove + anchorHeight : spaceAbove) -
|
|
10237
|
+
(finalY === "above" ? spacing : 0) -
|
|
10238
|
+
viewportSpacing;
|
|
10239
|
+
const effectiveSpaceBelow =
|
|
10240
|
+
(finalY === "below-overlap" ? spaceBelow + anchorHeight : spaceBelow) -
|
|
10241
|
+
(finalY === "below" ? spacing : 0) -
|
|
10242
|
+
viewportSpacing;
|
|
10243
|
+
|
|
10087
10244
|
return {
|
|
10088
|
-
|
|
10245
|
+
positionX: finalX,
|
|
10246
|
+
positionY: finalY,
|
|
10089
10247
|
left: elementDocumentLeft,
|
|
10090
10248
|
top: elementDocumentTop,
|
|
10091
10249
|
width: elementWidth,
|
|
@@ -10094,19 +10252,10 @@ const pickPositionRelativeTo = (
|
|
|
10094
10252
|
anchorTop: anchorDocumentTop,
|
|
10095
10253
|
anchorRight: anchorDocumentRight,
|
|
10096
10254
|
anchorBottom: anchorDocumentBottom,
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
// Decompose position flags
|
|
10102
|
-
const decomposePosition = (pos) => {
|
|
10103
|
-
return {
|
|
10104
|
-
isTop: pos === "top" || pos === "top-left" || pos === "top-right",
|
|
10105
|
-
isBottom:
|
|
10106
|
-
pos === "bottom" || pos === "bottom-left" || pos === "bottom-right",
|
|
10107
|
-
isLeft: pos === "left" || pos === "top-left" || pos === "bottom-left",
|
|
10108
|
-
isRight: pos === "right" || pos === "top-right" || pos === "bottom-right",
|
|
10109
|
-
isCenter: pos === "center",
|
|
10255
|
+
spaceLeft: spaceLeft - viewportSpacing,
|
|
10256
|
+
spaceRight: spaceRight - viewportSpacing,
|
|
10257
|
+
spaceAbove: effectiveSpaceAbove,
|
|
10258
|
+
spaceBelow: effectiveSpaceBelow,
|
|
10110
10259
|
};
|
|
10111
10260
|
};
|
|
10112
10261
|
|
|
@@ -11882,17 +12031,6 @@ const getWidthWithoutTransition = (element) =>
|
|
|
11882
12031
|
const getHeightWithoutTransition = (element) =>
|
|
11883
12032
|
getHeight$1(element, transitionStyleController);
|
|
11884
12033
|
|
|
11885
|
-
const getPaddingSizes = (element) => {
|
|
11886
|
-
const { paddingLeft, paddingRight, paddingTop, paddingBottom } =
|
|
11887
|
-
window.getComputedStyle(element, null);
|
|
11888
|
-
return {
|
|
11889
|
-
left: parseFloat(paddingLeft),
|
|
11890
|
-
right: parseFloat(paddingRight),
|
|
11891
|
-
top: parseFloat(paddingTop),
|
|
11892
|
-
bottom: parseFloat(paddingBottom),
|
|
11893
|
-
};
|
|
11894
|
-
};
|
|
11895
|
-
|
|
11896
12034
|
const getInnerHeight = (element) => {
|
|
11897
12035
|
// Always subtract paddings and borders to get the content height
|
|
11898
12036
|
const paddingSizes = getPaddingSizes(element);
|
|
@@ -13233,4 +13371,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
|
|
|
13233
13371
|
};
|
|
13234
13372
|
};
|
|
13235
13373
|
|
|
13236
|
-
export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, canInterceptKeys, captureScrollState, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findFocusable, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isSameColor, isScrollable, measureScrollbar, mergeOneStyle, mergeTwoStyles, normalizeStyles, parseStyle, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
|
|
13374
|
+
export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, canInterceptKeys, captureScrollState, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findFocusable, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isSameColor, isScrollable, measureScrollbar, mergeOneStyle, mergeTwoStyles, normalizeStyle, normalizeStyles, parseStyle, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, snapToPixel, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
|