@codecademy/gamut
68.2.268.2.3-alpha.eb3f22.0
dist/PopoverContainer/utils.js~
dist/PopoverContainer/utils.jsModified+44−24
Index: package/dist/PopoverContainer/utils.js
===================================================================
--- package/dist/PopoverContainer/utils.js
+++ package/dist/PopoverContainer/utils.js
@@ -1,5 +1,28 @@
import { percentageOrAbsolute as percent } from '@codecademy/variance';
+/**
+ * Mirrors placement on the inline axis when the target is RTL so e.g. `bottom-left`
+ * uses the same geometry as `bottom-right` in LTR.
+ */
+export const mirrorAlignment = (alignment, isRtl) => {
+ if (!isRtl) return alignment;
+ switch (alignment) {
+ case 'top-left':
+ return 'top-right';
+ case 'top-right':
+ return 'top-left';
+ case 'bottom-left':
+ return 'bottom-right';
+ case 'bottom-right':
+ return 'bottom-left';
+ case 'left':
+ return 'right';
+ case 'right':
+ return 'left';
+ default:
+ return alignment;
+ }
+};
const getWindowDimensions = () => ({
height: window.innerHeight || document.documentElement.clientHeight,
width: window.innerWidth || document.documentElement.clientWidth
});
@@ -137,21 +160,18 @@
/**
* Computes the absolute position styles for a popover relative to a target element.
*
- * Returns two style objects:
- * - `styles`: position edge values (left/right/top/bottom) passed as variance props so
- * they are converted to logical properties (inset-inline-start etc.) when
- * `useLogicalProperties` is enabled. Corner and edge alignments automatically flip
- * sides in RTL layouts as a result.
- * - `physicalStyles`: applied as an inline `style` prop, bypassing logical conversion.
- * Used for transforms (CSS transforms have no logical equivalent — `translate(-100%, 0)`
- * always shifts physically left) and for centered alignments whose `left` value is a
- * physical screen coordinate that must not flip in RTL.
+ * When `isRtl` is true, {@link mirrorAlignment} maps the requested placement to the
+ * mirrored corner/edge on the inline axis before computing offsets (same viewport math
+ * as LTR).
*
- * When `invertAxis` is set and `isRtl` is true, the x-transform coefficient is negated
- * so that the shift moves toward the target rather than away from it after logical
- * positions have flipped the element to the opposite physical side.
+ * Returns two fragments that callers merge into inline `style` (they are not Gamut variance
+ * props on the host, so nothing here is swapped to logical properties via `system.positioning`).
+ *
+ * - `styles`: corner/edge inset lengths (`left` / `right` / `top` / `bottom`).
+ * - `dirNeutralStyles`: transforms/coords not further remapped for RTL/logical placement
+ * after {@link mirrorAlignment}; merged after `styles`.
*/
export const getPosition = ({
alignment,
container,
@@ -160,8 +180,9 @@
y = 0,
invertAxis,
isRtl = false
}) => {
+ const layoutAlignment = mirrorAlignment(alignment, isRtl);
const {
top,
left,
bottom,
@@ -170,28 +191,27 @@
width
} = container;
const xOffset = width + offset + x;
const yOffset = height + offset + y;
- const alignments = alignment.split('-');
+ const alignments = layoutAlignment.split('-');
const styles = {};
- const physicalStyles = {};
+ const dirNeutralStyles = {};
if (alignments.length === 1) {
const [direction] = alignments;
const isVertical = direction === 'top' || direction === 'bottom';
if (isVertical) {
- // Center x is a physical screen coordinate — this should not flip in RTL.
- physicalStyles.left = left + width / 2;
- physicalStyles.transform = 'translate(-50%, 0)';
+ // Center x uses viewport/layout coords — stays literal after mirrorAlignment under RTL.
+ dirNeutralStyles.left = left + width / 2;
+ dirNeutralStyles.transform = 'translate(-50%, 0)';
} else {
styles.top = top + height / 2;
- physicalStyles.transform = 'translate(0, -50%)';
+ dirNeutralStyles.transform = 'translate(0, -50%)';
}
} else {
const coef = AXIS[invertAxis ?? 'none'];
const [yAxis, xAxis] = alignments;
- // Negate x coefficient in RTL so invertAxis shifts toward the target.
- const xCoef = isRtl ? -coef[xAxis] : coef[xAxis];
- physicalStyles.transform = `translate(${percent(xCoef)}, ${percent(coef[yAxis])})`;
+ const xCoef = coef[xAxis];
+ dirNeutralStyles.transform = `translate(${percent(xCoef)}, ${percent(coef[yAxis])})`;
}
const alignmentOffsets = {
left: {
position: 'right',
@@ -209,18 +229,18 @@
position: 'top',
value: top + yOffset
}
};
- alignments.forEach(alignment => {
+ alignments.forEach(edge => {
const {
position,
value
- } = alignmentOffsets[alignment];
+ } = alignmentOffsets[edge];
styles[position] = value;
});
return {
styles,
- physicalStyles: Object.keys(physicalStyles).length ? physicalStyles : undefined
+ dirNeutralStyles: Object.keys(dirNeutralStyles).length ? dirNeutralStyles : undefined
};
};
export const getContainers = (target, inline = false, scroll) => {
const viewport = target.getBoundingClientRect();