Anonymous
Щелкните событие на кнопки, не работающие в раскрывающемся Shadow Dom
Сообщение
Anonymous » 16 июл 2025, 13:12
Я делаю компонент и столкнулся с проблемой, которую я не могу думать, и нахожу какие -либо рабочие решения. Когда что -то находится в раскрывающемся спине, их события тоже не указаны, но когда они выходят за пределы выпадения в заголовок, они работают. Я использую библиотеку компонентов системы проектирования кальцита для действий кальцита, но я попытался использовать DIV/кнопки, чтобы убедиться, что это не проблема. Я тоже пытался поиграть с указательными событиями, но это тоже не сработало. />
https://codepen.io/keeron1/pen/ggpjbe
Код: Выделить всё
class Floater extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: "open"
});
// Wait for Calcite's to be defined before injecting HTML
customElements.whenDefined("calcite-action").then(() => {
// Inject HTML template
const tpl = document.getElementById("floater-template");
const clone = tpl.content.cloneNode(true);
this.shadowRoot.appendChild(clone);
this._init();
});
}
// Public API
get position() {
return this.getAttribute("position") || "top-left";
}
set position(v) {
this.setAttribute("position", v);
}
get isDraggable() {
if (!this.hasAttribute("draggable")) return true
return this.getAttribute("draggable") === "true" ? true : false
}
set isDraggable(v) {
this.setAttribute("draggable", v);
}
get isResizable() {
return this.getAttribute("resizable") || "none"
}
set isResizable(v) {
this.setAttribute("resizable", v);
}
get isHandleEnabled() {
if (!this.hasAttribute("handle-enabled")) return false
return this.getAttribute("handle-enabled") === "true" ? true : false
}
set isHandleEnabled(v) {
this.setAttribute("handle-enabled", v);
}
_init() {
// Elements
this.floaterCont = this.shadowRoot.querySelector(".floater-container")
this.floater = this.shadowRoot.querySelector(".floater");
// Heading
this.heading = this.shadowRoot.querySelector(".floater-heading")
this.hTitle = this.shadowRoot.querySelector(".floater-heading-title");
this.originalhTitle = this.hTitle?.outerHTML || null;
this.headingEnd = this.shadowRoot.querySelector(".floater-heading-end")
this.headingEndSlot = this.shadowRoot.querySelector('slot[name="heading-end"]');
this.closeButton = this.shadowRoot.querySelector(".floater-close");
this.originalCloseButton = this.closeButton?.outerHTML || null
// Dropdown
this.headingDropdown = this.shadowRoot.querySelector(".floater-heading-dropdown")
this.originalHeadingDropdown = this.headingDropdown?.outerHTML || null
this.headingDropdownAction = this.shadowRoot.querySelector(".floater-heading-dropdown-action")
this.headingDropdownItems = this.shadowRoot.querySelector(".floater-heading-dropdown-items")
this.headingEndDropdownSlot = this.shadowRoot.querySelector('slot[name="dropdown-heading-end"]')
// Content
this.floaterContent = this.shadowRoot.querySelector(".floater-content");
this.contentSlot = this.shadowRoot.querySelector('slot:not([name])');
this.test = this.shadowRoot.querySelector(".floater-test")
// Attributes
this.isDragging = false;
this.dragOffsetX = 0; // Distance between cursor and left of the component
this.dragOffsetY = 0; // Distance between cursor and top of the component
this.lastWindowWH = {
width: null,
height: null
}; // Window width and height
requestAnimationFrame(() => {
this._updateTitle();
this._updateClose();
this._setScale();
this._updateDraggable();
this._updateResizable();
window.addEventListener("resize", this._onResize);
this.closeButton?.addEventListener("click", this._close);
this.test?.addEventListener("click", this._testClick)
this._setStartingPosition();
this.lastWindowWH.width = window.innerWidth;
this.lastWindowWH.height = window.innerHeight;
});
}
// Trigger on component created (not used since we need to wait for calcite action)
connectedCallback() {}
// Trigger on component delete
disconnectedCallback() {
window.removeEventListener("resize", this._onResize)
if (this.isDraggable) this.heading.removeEventListener("pointerdown", this._onDown)
if (this.closeButton) this.closeButton.removeEventListener("click", this._close)
if (this.test) this.test.removeEventListener("click", this._testClick)
}
static get observedAttributes() {
return ["title", "close-disabled", "scale", "draggable", "resizable", "handle-enabled"];
}
// Trigger when attribute changes
attributeChangedCallback(name, oldValue, newValue) {
if (name === "title") this._updateTitle();
if (name === "close-disabled") this._updateClose();
if (name === "scale") this._setScale();
if (name === "draggable") this._updateDraggable();
if (name === "resizable") this._updateResizable();
if (name === "handle-enabled") this._updateHandle();
}
_testClick = () => {
if (!this.isHandleEnabled) this.isHandleEnabled = true
else this.isHandleEnabled = false
}
_updateHandle = () => {
if (this.isHandleEnabled) this._sendHeadingItemsToDropdown();
else this._sendDropdownItemsToHeading();
}
_sendHeadingItemsToDropdown = () => {
if (!this.hasAttribute("close-disabled"))
this.headingDropdownItems.insertBefore(this.closeButton, this.headingEndDropdownSlot)
const endSlot = this.headingEndSlot.assignedElements()
if (endSlot.length === 0) return;
endSlot.forEach(element => {
element.setAttribute("slot", "dropdown-heading-end");
});
}
_sendDropdownItemsToHeading = () => {
if (!this.hasAttribute("close-disabled")) this.headingEnd.append(this.closeButton)
const endSlot = this.headingEndDropdownSlot.assignedElements()
if (endSlot.length === 0) return;
endSlot.forEach(element => {
element.setAttribute("slot", "heading-end");
});
}
_close = () => {
console.log("closed btn clicked")
this.dispatchEvent(new CustomEvent("floaterClose", {
bubbles: true,
composed: true
}));
if (!this.hasAttribute("manual-close"))
this.remove();
}
_onResize = () => {
// Could be improved by saving the floater's left value before it gets pushed, so
// that when the window could grow it could stick to that value
const winWidth = window.innerWidth
const winHeight = window.innerHeight
// Calculate window delta
const deltaX = winWidth - this.lastWindowWH.width;
const deltaY = winHeight - this.lastWindowWH.height;
// Get floater's current properties
const floaterRect = this.floater.getBoundingClientRect();
const currentTop = floaterRect.top
const currentLeft = floaterRect.left
const fw = this.floater.offsetWidth;
const fh = this.floater.offsetHeight;
// Remove inital position class
this.floaterCont.classList.remove("top-left", "top-right", "bottom-left", "bottom-right", "center");
let newTop = currentTop + deltaY;
let newLeft = currentLeft + deltaX;
// Horizontal nudge only on shrink AND if closer to the right edge
if (deltaX < 0) {
const distRight = winWidth - fw - currentLeft;
if (distRight {
if (this.isDraggable) {
this.heading.classList.add("draggable")
this.heading.addEventListener("pointerdown", this._onDown)
} else {
this.heading.classList.remove("draggable")
this.heading.removeEventListener("pointerdown", this._onDown)
}
}
_updateResizable = () => {
this.floaterContent.classList.remove("resize-horizontal", "resize-vertical", "resize-both");
switch (this.isResizable) {
case "horizontal":
this.floaterContent.classList.add("resize-horizontal")
break
case "vertical":
this.floaterContent.classList.add("resize-vertical")
break
case "both":
this.floaterContent.classList.add("resize-both")
break
}
}
_updateTitle = () => {
const titleAtr = this.getAttribute("title") || "";
this.hTitle = this._handleElementLife(titleAtr, this.hTitle, this.originalhTitle, this.heading)
}
_updateClose = () => {
const disabled = this.hasAttribute("close-disabled");
if (disabled) {
this.closeButton.removeEventListener("click", this._close);
this.closeButton.remove();
this.closeButton = null;
return;
}
// Add the element
if (!this.closeButton && this.originalCloseButton) {
const temp = document.createElement("div");
temp.innerHTML = this.originalCloseButton;
this.closeButton = temp.firstElementChild;
this.closeButton.addEventListener("click", this._close);
this.closeButton.scale = this.getAttribute("scale") || "s"
if (this.isHandleEnabled) this.headingDropdownItems.insertBefore(this.closeButton, this.headingEndDropdownSlot)
else this.headingEnd.append(this.closeButton);
}
}
_setStartingPosition = () => {
switch (this.position) {
case "center":
this.floaterCont.classList.add("center")
break;
case "top-left":
this.floaterCont.classList.add("top-left")
break;
case "top-right":
this.floaterCont.classList.add("top-right")
break;
case "bottom-left":
this.floaterCont.classList.add("bottom-left")
break;
case "bottom-right":
this.floaterCont.classList.add("bottom-right")
break;
}
}
_setScale = () => {
let scaleAtr = this.getAttribute("scale") || "s";
if (this.closeButton) this.closeButton.scale = scaleAtr
if (this.headingDropdownAction) this.headingDropdownAction.scale = scaleAtr
}
// Handle floater movement
_onDown = (e) => {
if (e.target.closest(".floater-heading-end")) return;
if (this.headingEndSlot.assignedElements().some(el => el === e.target)) return
e.preventDefault();
this.isDragging = true;
// capture the pointer so we don't lose events
this.setPointerCapture(e.pointerId);
// Compute position based on visual location
const rect = this.floater.getBoundingClientRect();
this.dragOffsetX = e.clientX - rect.left;
this.dragOffsetY = e.clientY - rect.top;
this.addEventListener("pointermove", this._onMove);
this.addEventListener("pointerup", this._onUp);
};
_onMove = (e) => {
if (!this.isDragging) return;
// Remove previous
this.floaterCont.classList.remove("top-left", "top-right", "bottom-left", "bottom-right", "center");
// New positions
let newTop = e.clientY - this.dragOffsetY;
let newLeft = e.clientX - this.dragOffsetX;
const vw = window.innerWidth;
const vh = window.innerHeight;
const fw = this.floater.offsetWidth;
const fh = this.floater.offsetHeight;
// Clamp to viewport
newTop = Math.max(0, Math.min(vh - fh, newTop));
newLeft = Math.max(0, Math.min(vw - fw, newLeft));
// Remove container offset
const contRect = this.floaterCont.getBoundingClientRect();
newTop -= contRect.top;
newLeft -= contRect.left;
this.floater.style.top = `${newTop}px`;
this.floater.style.left = `${newLeft}px`;
}
_onUp = (e) => {
this.isDragging = false;
this.releasePointerCapture(e.pointerId);
this.removeEventListener("mousemove", this._onMove);
this.removeEventListener("mouseup", this._onUp);
}
}
if (!customElements.get("custom-floater"))
customElements.define("custom-floater", Floater);< /code>
:host {
--floater-offset-x: 0px;
--floater-offset-y: 0px;
--floater-min-size-x: auto;
--floater-max-size-x: none;
--floater-min-size-y: auto;
--floater-max-size-y: none;
--floater-content-padding: var(--calcite-spacing-md);
--floater-opacity: 1;
--floater-content-opacity: 1;
}
.floater-container {
position: fixed;
z-index: var(--calcite-z-index-modal);
top: var(--floater-offset-y, 0);
left: var(--floater-offset-x, 0);
pointer-events: none;
}
/* Starting positions */
.floater-container.center {
inset: 0;
>.floater {
top: calc(50% + var(--floater-offset-y, 0px));
left: calc(50% + var(--floater-offset-x, 0px));
transform: translate(-50%, -50%);
}
}
.floater-container.top-right {
top: var(--floater-offset-y, 0);
right: var(--floater-offset-x, 0);
left: auto;
>.floater {
top: var(--calcite-spacing-md);
right: var(--calcite-spacing-md);
}
}
.floater-container.top-left>.floater {
top: var(--calcite-spacing-md);
left: var(--calcite-spacing-md);
}
.floater-container.bottom-left {
bottom: var(--floater-offset-y, 0);
left: var(--floater-offset-x, 0);
top: auto;
>.floater {
bottom: var(--calcite-spacing-md);
left: var(--calcite-spacing-md);
}
}
.floater-container.bottom-right {
bottom: var(--floater-offset-y, 0);
right: var(--floater-offset-x, 0);
top: auto;
left: auto;
>.floater {
bottom: var(--calcite-spacing-md);
right: var(--calcite-spacing-md);
}
}
/* End of starting positions */
.floater {
display: flex;
flex-direction: column;
max-height: 100vh;
position: absolute;
pointer-events: all;
box-sizing: border-box;
border-radius: 0.25rem;
background-color: var(--floater-background-color);
font-family: var(--calcite-sans-family);
box-shadow: var(--calcite-shadow-sm);
opacity: var(--floater-opacity);
}
.floater-heading {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex: 0 0 auto;
border-bottom: 1px solid var(--calcite-color-border-3);
user-select: none;
}
.floater-heading.draggable {
cursor: move;
}
.floater-heading-title {
padding: var(--calcite-spacing-xs) var(--calcite-spacing-md-plus);
font-weight: var(--calcite-font-weight-medium);
font-size: var(--calcite-font-size-0);
color: var(--calcite-color-text-1);
}
.floater-heading-end {
margin-left: auto;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
/* Dropdown menu */
.floater-heading-dropdown-action {
height: 100%;
}
.floater-heading-dropdown-items {
pointer-events: all;
visibility: hidden;
position: absolute;
top: auto;
right: 0;
background: var(--calcite-color-background);
padding: 0.5rem;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
z-index: 100;
border-radius: 4px;
cursor: default;
overflow-y: auto;
height: 100%;
max-height: 300px;
/* Firefox */
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-color);
}
slot[name="dropdown-heading-end"] {
display: flex;
flex-direction: column-reverse;
}
/* Chrome, Edge, Safari */
@supports selector(::-webkit-scrollbar) {
.floater-heading-dropdown-items {
/* Override rules */
scrollbar-width: auto;
scrollbar-color: auto;
}
.floater-heading-dropdown-items::-webkit-scrollbar {
width: var(--scrollbar-width);
}
.floater-heading-dropdown-items::-webkit-scrollbar-thumb {
background: var(--scrollbar-background-color);
border-radius: var(--scrollbar-border-radius);
}
}
.floater-heading-dropdown:hover .floater-heading-dropdown-items {
visibility: visible;
}
/* End of Dropdown menu */
/* .floater-close{ */
/* height: 100%; */
/* width: auto; */
/* background-color: transparent; */
/* border: 0; */
/* cursor: pointer; */
/* } */
.floater-content {
box-sizing: border-box;
padding: var(--floater-content-padding);
overflow: auto;
flex: 1 1 auto;
max-width: var(--floater-max-size-x);
min-width: var(--floater-min-size-x);
max-height: var(--floater-max-size-y);
min-height: var(--floater-min-size-y);
opacity: var(--floater-content-opacity);
}
.floater-content.resize-horizontal {
resize: horizontal;
}
.floater-content.resize-vertical {
resize: vertical;
}
.floater-content.resize-both {
resize: both;
}
/* Hide the resize handle */
.floater-content.resize-horizontal::-webkit-resizer,
.floater-content.resize-vertical::-webkit-resizer,
.floater-content.resize-both::-webkit-resizer {
display: none;
}
.floater-content.resize-horizontal::-moz-resizer,
.floater-content.resize-vertical::-moz-resizer,
.floater-content.resize-both::-moz-resizer {
display: none;
}
test
This is some placeholder content
Подробнее здесь:
https://stackoverflow.com/questions/797 ... shadow-dom
1752660728
Anonymous
Я делаю компонент и столкнулся с проблемой, которую я не могу думать, и нахожу какие -либо рабочие решения. Когда что -то находится в раскрывающемся спине, их события тоже не указаны, но когда они выходят за пределы выпадения в заголовок, они работают. Я использую библиотеку компонентов системы проектирования кальцита для действий кальцита, но я попытался использовать DIV/кнопки, чтобы убедиться, что это не проблема. Я тоже пытался поиграть с указательными событиями, но это тоже не сработало. />https://codepen.io/keeron1/pen/ggpjbe [code]class Floater extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); // Wait for Calcite's to be defined before injecting HTML customElements.whenDefined("calcite-action").then(() => { // Inject HTML template const tpl = document.getElementById("floater-template"); const clone = tpl.content.cloneNode(true); this.shadowRoot.appendChild(clone); this._init(); }); } // Public API get position() { return this.getAttribute("position") || "top-left"; } set position(v) { this.setAttribute("position", v); } get isDraggable() { if (!this.hasAttribute("draggable")) return true return this.getAttribute("draggable") === "true" ? true : false } set isDraggable(v) { this.setAttribute("draggable", v); } get isResizable() { return this.getAttribute("resizable") || "none" } set isResizable(v) { this.setAttribute("resizable", v); } get isHandleEnabled() { if (!this.hasAttribute("handle-enabled")) return false return this.getAttribute("handle-enabled") === "true" ? true : false } set isHandleEnabled(v) { this.setAttribute("handle-enabled", v); } _init() { // Elements this.floaterCont = this.shadowRoot.querySelector(".floater-container") this.floater = this.shadowRoot.querySelector(".floater"); // Heading this.heading = this.shadowRoot.querySelector(".floater-heading") this.hTitle = this.shadowRoot.querySelector(".floater-heading-title"); this.originalhTitle = this.hTitle?.outerHTML || null; this.headingEnd = this.shadowRoot.querySelector(".floater-heading-end") this.headingEndSlot = this.shadowRoot.querySelector('slot[name="heading-end"]'); this.closeButton = this.shadowRoot.querySelector(".floater-close"); this.originalCloseButton = this.closeButton?.outerHTML || null // Dropdown this.headingDropdown = this.shadowRoot.querySelector(".floater-heading-dropdown") this.originalHeadingDropdown = this.headingDropdown?.outerHTML || null this.headingDropdownAction = this.shadowRoot.querySelector(".floater-heading-dropdown-action") this.headingDropdownItems = this.shadowRoot.querySelector(".floater-heading-dropdown-items") this.headingEndDropdownSlot = this.shadowRoot.querySelector('slot[name="dropdown-heading-end"]') // Content this.floaterContent = this.shadowRoot.querySelector(".floater-content"); this.contentSlot = this.shadowRoot.querySelector('slot:not([name])'); this.test = this.shadowRoot.querySelector(".floater-test") // Attributes this.isDragging = false; this.dragOffsetX = 0; // Distance between cursor and left of the component this.dragOffsetY = 0; // Distance between cursor and top of the component this.lastWindowWH = { width: null, height: null }; // Window width and height requestAnimationFrame(() => { this._updateTitle(); this._updateClose(); this._setScale(); this._updateDraggable(); this._updateResizable(); window.addEventListener("resize", this._onResize); this.closeButton?.addEventListener("click", this._close); this.test?.addEventListener("click", this._testClick) this._setStartingPosition(); this.lastWindowWH.width = window.innerWidth; this.lastWindowWH.height = window.innerHeight; }); } // Trigger on component created (not used since we need to wait for calcite action) connectedCallback() {} // Trigger on component delete disconnectedCallback() { window.removeEventListener("resize", this._onResize) if (this.isDraggable) this.heading.removeEventListener("pointerdown", this._onDown) if (this.closeButton) this.closeButton.removeEventListener("click", this._close) if (this.test) this.test.removeEventListener("click", this._testClick) } static get observedAttributes() { return ["title", "close-disabled", "scale", "draggable", "resizable", "handle-enabled"]; } // Trigger when attribute changes attributeChangedCallback(name, oldValue, newValue) { if (name === "title") this._updateTitle(); if (name === "close-disabled") this._updateClose(); if (name === "scale") this._setScale(); if (name === "draggable") this._updateDraggable(); if (name === "resizable") this._updateResizable(); if (name === "handle-enabled") this._updateHandle(); } _testClick = () => { if (!this.isHandleEnabled) this.isHandleEnabled = true else this.isHandleEnabled = false } _updateHandle = () => { if (this.isHandleEnabled) this._sendHeadingItemsToDropdown(); else this._sendDropdownItemsToHeading(); } _sendHeadingItemsToDropdown = () => { if (!this.hasAttribute("close-disabled")) this.headingDropdownItems.insertBefore(this.closeButton, this.headingEndDropdownSlot) const endSlot = this.headingEndSlot.assignedElements() if (endSlot.length === 0) return; endSlot.forEach(element => { element.setAttribute("slot", "dropdown-heading-end"); }); } _sendDropdownItemsToHeading = () => { if (!this.hasAttribute("close-disabled")) this.headingEnd.append(this.closeButton) const endSlot = this.headingEndDropdownSlot.assignedElements() if (endSlot.length === 0) return; endSlot.forEach(element => { element.setAttribute("slot", "heading-end"); }); } _close = () => { console.log("closed btn clicked") this.dispatchEvent(new CustomEvent("floaterClose", { bubbles: true, composed: true })); if (!this.hasAttribute("manual-close")) this.remove(); } _onResize = () => { // Could be improved by saving the floater's left value before it gets pushed, so // that when the window could grow it could stick to that value const winWidth = window.innerWidth const winHeight = window.innerHeight // Calculate window delta const deltaX = winWidth - this.lastWindowWH.width; const deltaY = winHeight - this.lastWindowWH.height; // Get floater's current properties const floaterRect = this.floater.getBoundingClientRect(); const currentTop = floaterRect.top const currentLeft = floaterRect.left const fw = this.floater.offsetWidth; const fh = this.floater.offsetHeight; // Remove inital position class this.floaterCont.classList.remove("top-left", "top-right", "bottom-left", "bottom-right", "center"); let newTop = currentTop + deltaY; let newLeft = currentLeft + deltaX; // Horizontal nudge only on shrink AND if closer to the right edge if (deltaX < 0) { const distRight = winWidth - fw - currentLeft; if (distRight { if (this.isDraggable) { this.heading.classList.add("draggable") this.heading.addEventListener("pointerdown", this._onDown) } else { this.heading.classList.remove("draggable") this.heading.removeEventListener("pointerdown", this._onDown) } } _updateResizable = () => { this.floaterContent.classList.remove("resize-horizontal", "resize-vertical", "resize-both"); switch (this.isResizable) { case "horizontal": this.floaterContent.classList.add("resize-horizontal") break case "vertical": this.floaterContent.classList.add("resize-vertical") break case "both": this.floaterContent.classList.add("resize-both") break } } _updateTitle = () => { const titleAtr = this.getAttribute("title") || ""; this.hTitle = this._handleElementLife(titleAtr, this.hTitle, this.originalhTitle, this.heading) } _updateClose = () => { const disabled = this.hasAttribute("close-disabled"); if (disabled) { this.closeButton.removeEventListener("click", this._close); this.closeButton.remove(); this.closeButton = null; return; } // Add the element if (!this.closeButton && this.originalCloseButton) { const temp = document.createElement("div"); temp.innerHTML = this.originalCloseButton; this.closeButton = temp.firstElementChild; this.closeButton.addEventListener("click", this._close); this.closeButton.scale = this.getAttribute("scale") || "s" if (this.isHandleEnabled) this.headingDropdownItems.insertBefore(this.closeButton, this.headingEndDropdownSlot) else this.headingEnd.append(this.closeButton); } } _setStartingPosition = () => { switch (this.position) { case "center": this.floaterCont.classList.add("center") break; case "top-left": this.floaterCont.classList.add("top-left") break; case "top-right": this.floaterCont.classList.add("top-right") break; case "bottom-left": this.floaterCont.classList.add("bottom-left") break; case "bottom-right": this.floaterCont.classList.add("bottom-right") break; } } _setScale = () => { let scaleAtr = this.getAttribute("scale") || "s"; if (this.closeButton) this.closeButton.scale = scaleAtr if (this.headingDropdownAction) this.headingDropdownAction.scale = scaleAtr } // Handle floater movement _onDown = (e) => { if (e.target.closest(".floater-heading-end")) return; if (this.headingEndSlot.assignedElements().some(el => el === e.target)) return e.preventDefault(); this.isDragging = true; // capture the pointer so we don't lose events this.setPointerCapture(e.pointerId); // Compute position based on visual location const rect = this.floater.getBoundingClientRect(); this.dragOffsetX = e.clientX - rect.left; this.dragOffsetY = e.clientY - rect.top; this.addEventListener("pointermove", this._onMove); this.addEventListener("pointerup", this._onUp); }; _onMove = (e) => { if (!this.isDragging) return; // Remove previous this.floaterCont.classList.remove("top-left", "top-right", "bottom-left", "bottom-right", "center"); // New positions let newTop = e.clientY - this.dragOffsetY; let newLeft = e.clientX - this.dragOffsetX; const vw = window.innerWidth; const vh = window.innerHeight; const fw = this.floater.offsetWidth; const fh = this.floater.offsetHeight; // Clamp to viewport newTop = Math.max(0, Math.min(vh - fh, newTop)); newLeft = Math.max(0, Math.min(vw - fw, newLeft)); // Remove container offset const contRect = this.floaterCont.getBoundingClientRect(); newTop -= contRect.top; newLeft -= contRect.left; this.floater.style.top = `${newTop}px`; this.floater.style.left = `${newLeft}px`; } _onUp = (e) => { this.isDragging = false; this.releasePointerCapture(e.pointerId); this.removeEventListener("mousemove", this._onMove); this.removeEventListener("mouseup", this._onUp); } } if (!customElements.get("custom-floater")) customElements.define("custom-floater", Floater);< /code> :host { --floater-offset-x: 0px; --floater-offset-y: 0px; --floater-min-size-x: auto; --floater-max-size-x: none; --floater-min-size-y: auto; --floater-max-size-y: none; --floater-content-padding: var(--calcite-spacing-md); --floater-opacity: 1; --floater-content-opacity: 1; } .floater-container { position: fixed; z-index: var(--calcite-z-index-modal); top: var(--floater-offset-y, 0); left: var(--floater-offset-x, 0); pointer-events: none; } /* Starting positions */ .floater-container.center { inset: 0; >.floater { top: calc(50% + var(--floater-offset-y, 0px)); left: calc(50% + var(--floater-offset-x, 0px)); transform: translate(-50%, -50%); } } .floater-container.top-right { top: var(--floater-offset-y, 0); right: var(--floater-offset-x, 0); left: auto; >.floater { top: var(--calcite-spacing-md); right: var(--calcite-spacing-md); } } .floater-container.top-left>.floater { top: var(--calcite-spacing-md); left: var(--calcite-spacing-md); } .floater-container.bottom-left { bottom: var(--floater-offset-y, 0); left: var(--floater-offset-x, 0); top: auto; >.floater { bottom: var(--calcite-spacing-md); left: var(--calcite-spacing-md); } } .floater-container.bottom-right { bottom: var(--floater-offset-y, 0); right: var(--floater-offset-x, 0); top: auto; left: auto; >.floater { bottom: var(--calcite-spacing-md); right: var(--calcite-spacing-md); } } /* End of starting positions */ .floater { display: flex; flex-direction: column; max-height: 100vh; position: absolute; pointer-events: all; box-sizing: border-box; border-radius: 0.25rem; background-color: var(--floater-background-color); font-family: var(--calcite-sans-family); box-shadow: var(--calcite-shadow-sm); opacity: var(--floater-opacity); } .floater-heading { display: flex; flex-direction: row; flex-wrap: nowrap; flex: 0 0 auto; border-bottom: 1px solid var(--calcite-color-border-3); user-select: none; } .floater-heading.draggable { cursor: move; } .floater-heading-title { padding: var(--calcite-spacing-xs) var(--calcite-spacing-md-plus); font-weight: var(--calcite-font-weight-medium); font-size: var(--calcite-font-size-0); color: var(--calcite-color-text-1); } .floater-heading-end { margin-left: auto; display: flex; flex-direction: row; flex-wrap: nowrap; } /* Dropdown menu */ .floater-heading-dropdown-action { height: 100%; } .floater-heading-dropdown-items { pointer-events: all; visibility: hidden; position: absolute; top: auto; right: 0; background: var(--calcite-color-background); padding: 0.5rem; box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2); z-index: 100; border-radius: 4px; cursor: default; overflow-y: auto; height: 100%; max-height: 300px; /* Firefox */ scrollbar-width: thin; scrollbar-color: var(--scrollbar-color); } slot[name="dropdown-heading-end"] { display: flex; flex-direction: column-reverse; } /* Chrome, Edge, Safari */ @supports selector(::-webkit-scrollbar) { .floater-heading-dropdown-items { /* Override rules */ scrollbar-width: auto; scrollbar-color: auto; } .floater-heading-dropdown-items::-webkit-scrollbar { width: var(--scrollbar-width); } .floater-heading-dropdown-items::-webkit-scrollbar-thumb { background: var(--scrollbar-background-color); border-radius: var(--scrollbar-border-radius); } } .floater-heading-dropdown:hover .floater-heading-dropdown-items { visibility: visible; } /* End of Dropdown menu */ /* .floater-close{ */ /* height: 100%; */ /* width: auto; */ /* background-color: transparent; */ /* border: 0; */ /* cursor: pointer; */ /* } */ .floater-content { box-sizing: border-box; padding: var(--floater-content-padding); overflow: auto; flex: 1 1 auto; max-width: var(--floater-max-size-x); min-width: var(--floater-min-size-x); max-height: var(--floater-max-size-y); min-height: var(--floater-min-size-y); opacity: var(--floater-content-opacity); } .floater-content.resize-horizontal { resize: horizontal; } .floater-content.resize-vertical { resize: vertical; } .floater-content.resize-both { resize: both; } /* Hide the resize handle */ .floater-content.resize-horizontal::-webkit-resizer, .floater-content.resize-vertical::-webkit-resizer, .floater-content.resize-both::-webkit-resizer { display: none; } .floater-content.resize-horizontal::-moz-resizer, .floater-content.resize-vertical::-moz-resizer, .floater-content.resize-both::-moz-resizer { display: none; } test This is some placeholder content [/code] Подробнее здесь: [url]https://stackoverflow.com/questions/79703237/click-event-on-buttons-not-working-in-dropdown-shadow-dom[/url]