Refactor floating panel styling hooks

This commit is contained in:
thePR0M3TH3AN committed 2025-10-14 09:32:28 -04:00
1 parent ae932cb968
commit e614acdee2
10 files changed
+268 -57

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
const DEFAULT_STATE = {
placement: "bottom",
alignment: "start",
strategy: "fixed",
direction: "ltr",
mode: "fallback",
fallbackTop: null,
fallbackLeft: null,
};
const FALLBACK_TOP_VAR = "--floating-fallback-top";
const FALLBACK_LEFT_VAR = "--floating-fallback-left";
function toCssPixel(value) {
if (Number.isFinite(value)) {
return `${value}px`;
}
if (typeof value === "string" && value.trim() !== "") {
return value;
}
return "auto";
}
function normalizeDirection(value) {
return value === "rtl" ? "rtl" : "ltr";
}
function ensureDatasetValue(element, key, value) {
if (!element) {
return;
}
if (value === undefined || value === null || value === "") {
delete element.dataset[key];
return;
}
element.dataset[key] = value;
}
export function createFloatingPanelStyles(panel) {
const safePanel = panel || null;
const state = { ...DEFAULT_STATE };
if (safePanel) {
safePanel.dataset.floatingPanel = "true";
safePanel.style.setProperty(FALLBACK_TOP_VAR, "auto");
safePanel.style.setProperty(FALLBACK_LEFT_VAR, "auto");
}
const api = {
element: safePanel,
setPlacement(value) {
state.placement = typeof value === "string" && value ? value : DEFAULT_STATE.placement;
ensureDatasetValue(safePanel, "floatingPlacement", state.placement);
return state.placement;
},
getPlacement() {
return state.placement;
},
setAlignment(value) {
state.alignment = typeof value === "string" && value ? value : DEFAULT_STATE.alignment;
ensureDatasetValue(safePanel, "floatingAlignment", state.alignment);
return state.alignment;
},
getAlignment() {
return state.alignment;
},
setStrategy(value) {
state.strategy = value === "absolute" ? "absolute" : DEFAULT_STATE.strategy;
ensureDatasetValue(safePanel, "floatingStrategy", state.strategy);
return state.strategy;
},
getStrategy() {
return state.strategy;
},
setDirection(value) {
state.direction = normalizeDirection(value);
ensureDatasetValue(safePanel, "floatingDir", state.direction);
return state.direction;
},
getDirection() {
return state.direction;
},
setMode(value) {
state.mode = value === "anchor" ? "anchor" : DEFAULT_STATE.mode;
ensureDatasetValue(safePanel, "floatingMode", state.mode);
return state.mode;
},
getMode() {
return state.mode;
},
setFallbackPosition({ top, left }) {
state.fallbackTop = Number.isFinite(top) ? Number(top) : null;
state.fallbackLeft = Number.isFinite(left) ? Number(left) : null;
if (safePanel) {
safePanel.style.setProperty(FALLBACK_TOP_VAR, toCssPixel(state.fallbackTop));
safePanel.style.setProperty(FALLBACK_LEFT_VAR, toCssPixel(state.fallbackLeft));
}
return { ...api.getFallbackPosition() };
},
getFallbackPosition() {
return {
top: state.fallbackTop,
left: state.fallbackLeft,
};
},
teardown() {
if (!safePanel) {
return;
}
safePanel.style.removeProperty(FALLBACK_TOP_VAR);
safePanel.style.removeProperty(FALLBACK_LEFT_VAR);
safePanel.style.removeProperty("right");
safePanel.style.removeProperty("bottom");
safePanel.style.removeProperty("position");
delete safePanel.dataset.floatingPanel;
delete safePanel.dataset.floatingPlacement;
delete safePanel.dataset.floatingAlignment;
delete safePanel.dataset.floatingStrategy;
delete safePanel.dataset.floatingDir;
delete safePanel.dataset.floatingMode;
state.placement = DEFAULT_STATE.placement;
state.alignment = DEFAULT_STATE.alignment;
state.strategy = DEFAULT_STATE.strategy;
state.direction = DEFAULT_STATE.direction;
state.mode = DEFAULT_STATE.mode;
state.fallbackTop = DEFAULT_STATE.fallbackTop;
state.fallbackLeft = DEFAULT_STATE.fallbackLeft;
},
};
api.setPlacement(state.placement);
api.setAlignment(state.alignment);
api.setStrategy(state.strategy);
api.setDirection(state.direction);
api.setMode(state.mode);
return api;
}
export default createFloatingPanelStyles;
+18 -36
View File
@@ -1,3 +1,5 @@
import { createFloatingPanelStyles } from "./floatingPanelStyles.js";
const DEFAULT_OPTIONS = {
placement: "bottom",
alignment: "start",
@@ -9,20 +11,6 @@ const DEFAULT_OPTIONS = {
onUpdate: null,
};
const PANEL_DATA_KEYS = [
"floatingPanel",
"floatingPlacement",
"floatingAlignment",
"floatingStrategy",
"floatingDir",
"floatingMode",
];
const PANEL_STYLE_PROPS = [
"--floating-fallback-top",
"--floating-fallback-left",
];
const OPPOSITE_PLACEMENT = {
top: "bottom",
bottom: "top",
@@ -213,9 +201,10 @@ export function positionFloatingPanel(anchor, panel, options = {}) {
const documentRef = safeAnchor.ownerDocument || safePanel.ownerDocument || globalThis.document;
const windowRef = documentRef?.defaultView || globalThis;
const { styles: providedStyles, ...optionOverrides } = options || {};
const config = {
...DEFAULT_OPTIONS,
...options,
...optionOverrides,
};
config.placement = normalizePlacement(config.placement);
config.alignment = normalizeAlignment(config.alignment);
@@ -235,20 +224,21 @@ export function positionFloatingPanel(anchor, panel, options = {}) {
cleanup: [],
};
const styles =
providedStyles && providedStyles.element === safePanel
? providedStyles
: createFloatingPanelStyles(safePanel);
safeAnchor.dataset.floatingAnchor = "true";
safePanel.dataset.floatingPanel = "true";
safePanel.dataset.floatingAlignment = config.alignment;
safePanel.dataset.floatingStrategy = config.strategy;
safePanel.dataset.floatingDir = rtl ? "rtl" : "ltr";
safePanel.dataset.floatingMode = anchorSupported ? "anchor" : "fallback";
styles.setAlignment(config.alignment);
styles.setStrategy(config.strategy);
styles.setDirection(rtl ? "rtl" : "ltr");
styles.setMode(anchorSupported ? "anchor" : "fallback");
styles.setPlacement(config.placement);
const applyPosition = ({ top, left, placement }) => {
safePanel.dataset.floatingPlacement = placement;
safePanel.style.position = config.strategy;
safePanel.style.removeProperty("right");
safePanel.style.removeProperty("bottom");
safePanel.style.setProperty("--floating-fallback-top", `${top}px`);
safePanel.style.setProperty("--floating-fallback-left", `${left}px`);
styles.setPlacement(placement);
styles.setFallbackPosition({ top, left });
if (typeof config.onUpdate === "function") {
config.onUpdate({ top, left, placement, alignment: config.alignment, rtl });
}
@@ -395,18 +385,10 @@ export function positionFloatingPanel(anchor, panel, options = {}) {
// Ignore cleanup errors to avoid breaking consumer teardown flows.
}
}
PANEL_STYLE_PROPS.forEach((prop) => {
safePanel.style.removeProperty(prop);
});
safePanel.style.removeProperty("right");
safePanel.style.removeProperty("bottom");
safePanel.style.removeProperty("position");
safePanel.style.position = "";
PANEL_DATA_KEYS.forEach((key) => {
delete safePanel.dataset[key];
});
styles.teardown();
delete safeAnchor.dataset.floatingAnchor;
},
styles,
};
}