diff --git a/web/css/autocomplete-plus.css b/web/css/autocomplete-plus.css index deafdc0..fdba432 100644 --- a/web/css/autocomplete-plus.css +++ b/web/css/autocomplete-plus.css @@ -43,7 +43,7 @@ --comfy-hover-bg ); /* Use a hover variable if available, or adjust */ - color: var(--desc-text); /* Adjust text color on hover if needed */ + color: var(--descrip-text); /* Adjust text color on hover if needed */ } /* Selected item style */ @@ -68,9 +68,10 @@ top: 0; left: 0; display: none; - width: 800px; + width: fit-content; background-color: var(--comfy-input-bg); color: var(--input-text); + overflow-y: auto; } #similar-tags-header { @@ -80,7 +81,7 @@ padding: 8px 12px; border-bottom: 1px solid var(--border-color); background-color: var(--comfy-menu-bg); - color: var(--desc-text); + color: var(--descrip-text); } .similar-tags-header-tag-name { @@ -89,45 +90,48 @@ #similar-tags-list { display: grid; - padding: 5px; - border: 1px solid var(--border-color); - border-radius: 4px; box-shadow: 0 2px 8px rgb(0 0 0 / 30%); grid-auto-rows: auto; grid-template-columns: max-content 1fr auto; - overflow-y: auto; } .similar-tag-item { display: contents; - padding: 5px 10px; /* Adjusted padding */ - border-bottom: 1px solid var(--border-color); /* Separator line */ cursor: pointer; - white-space: nowrap; /* Prevent wrapping */ } -.similar-tag-item:last-child { - border-bottom: none; +.similar-tag-item span { + align-content: center; + padding: 4px 8px; + border-bottom: 1px solid var(--border-color); } -.similar-tag-item:hover { - background-color: var(--comfy-hover-bg); +.similar-tag-item:nth-child(even) span { + background-color: var(--comfy-menu-bg); +} + +.similar-tag-item:nth-child(odd) span { + background-color: var(--comfy-input-bg); +} + +.similar-tag-item:hover span { + background-color: var(--p-form-field-filled-hover-background); } .similar-tag-name { + padding: 8px; white-space: nowrap; } .similar-tag-alias { overflow: hidden; + color: var(--descrip-text); text-overflow: ellipsis; white-space: nowrap; } .similar-tag-similarity { - border-radius: 10px; - background-color: var(--comfy-menu-bg); - color: var(--desc-text); + color: var(--descrip-text); text-align: right; white-space: nowrap; } diff --git a/web/js/similar-tags.js b/web/js/similar-tags.js index c9efb39..4a39e15 100644 --- a/web/js/similar-tags.js +++ b/web/js/similar-tags.js @@ -25,7 +25,7 @@ class SimilarTagsUI { this.root.appendChild(this.header); // Create a tbody for the tags - this.tagsContainer = document.createElement('ul'); + this.tagsContainer = document.createElement('div'); this.tagsContainer.id = 'similar-tags-list'; this.root.appendChild(this.tagsContainer); @@ -87,6 +87,10 @@ class SimilarTagsUI { * @param {Array<{tag: string, similarity: number, count: number, alias?: string[]}>} similarTags */ updateContent(similarTags) { + this.root.style.left = 0; + this.root.style.top = 0; + this.root.style.maxWidth = `${window.innerWidth / 2}px`; + this.root.style.maxHeight = `${window.innerHeight / 2}px`; this.tagsContainer.innerHTML = ''; // Update header with current tag @@ -125,10 +129,9 @@ class SimilarTagsUI { * @returns {HTMLTableRowElement} The tag row element */ createTagElement(tagData) { - const tagRow = document.createElement('li'); + const tagRow = document.createElement('div'); tagRow.className = 'similar-tag-item'; tagRow.dataset.tag = tagData.tag; - tagRow.style.cursor = 'pointer'; // Tag name cell const tagNameCell = document.createElement('span'); @@ -138,11 +141,11 @@ class SimilarTagsUI { // Alias cell (middle column) const aliasCell = document.createElement('span'); aliasCell.className = 'similar-tag-alias'; - + // Display alias if available if (tagData.alias && tagData.alias.length > 0) { let aliasText = tagData.alias.join(', '); - aliasCell.textContent = aliasText; + aliasCell.textContent = `${aliasText}`; aliasCell.title = tagData.alias.join(', '); // Full alias on hover } @@ -173,98 +176,42 @@ class SimilarTagsUI { * @param {HTMLElement} inputElement The input element to position */ updatePosition(inputElement) { - // Reset position for accurate measurement - this.root.style.maxHeight = ''; - this.root.style.maxWidth = ''; + const margin = 10; - // Get the bounds of the input element - const inputRect = inputElement.getBoundingClientRect(); - - // Get viewport dimensions - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - const mergin = 10; - - // Get ComfyUI canvas scale if available - const scale = window.app?.canvas?.ds?.scale ?? 1.0; // Note: scale is not currently used in positioning logic - - // Make element briefly visible for measurement but not actually showing + // Measure the element size without causing reflow this.root.style.visibility = 'hidden'; + this.root.style.position = 'absolute'; // Ensure position is absolute for measurement this.root.style.display = 'block'; + this.root.style.left = '-9999px'; + this.root.style.top = '-9999px'; + this.root.style.maxWidth = ''; // Reset max dimensions before measuring + this.root.style.maxHeight = ''; const elemRect = this.root.getBoundingClientRect(); + // Hide it again after measurement this.root.style.display = 'none'; this.root.style.visibility = 'visible'; + this.root.style.position = ''; // Reset position style + this.root.style.left = ''; + this.root.style.top = ''; - let [left, top] = [0, 0]; + // Get the optimal placement area + const placementArea = this.#getOptimalPlacementArea(inputElement.getBoundingClientRect(), elemRect.width, elemRect.height, margin); - // Determine initial position based on setting - if (settingValues.similarTagsDisplayPosition === 'vertical') { - // --- Vertical Positioning --- - // Calculate available space above and below the input - const spaceAbove = inputRect.top; - const spaceBelow = viewportHeight - inputRect.bottom; - - // Determine if we should place above or below based on which has more space - const placeAbove = spaceAbove > spaceBelow; - - if (placeAbove) { - // Place above the input with appropriate margin - top = inputRect.top - elemRect.height - mergin; - // Adjust max height to not exceed available space - this.root.style.maxHeight = `${spaceAbove - (mergin * 2)}px`; - } else { - // Place below the input with appropriate margin - top = inputRect.bottom + mergin; - // Adjust max height to not exceed available space - this.root.style.maxHeight = `${spaceBelow - (mergin * 2)}px`; - } + // Calculate final styles, fitting the element within the placement area + const finalMaxWidth = Math.min(elemRect.width, placementArea.width); + const finalMaxHeight = Math.min(elemRect.height, placementArea.height); - // Ensure horizontal alignment within viewport if placed vertically - left = Math.max(mergin, Math.min(viewportWidth - elemRect.width - mergin, left)); - // Align left edge with input where possible - left = inputRect.left; + // Adjust position if the element is smaller than the area (e.g., center or align based on mode) + // For simplicity, we'll just use the calculated top-left corner of the area for now. + // More sophisticated alignment could be added here if needed. + let finalLeft = placementArea.x; + let finalTop = placementArea.y; - } else { - // --- Horizontal Positioning (Default) --- - // Initial position: to the right of the textarea - left = inputRect.right + mergin; - top = inputRect.top; - - // Check if we have enough space to the right - const rightSpace = viewportWidth - left; - if (rightSpace < elemRect.width && left > viewportWidth / 2) { // Only move left if it fits better - // Not enough space to the right, try placing it to the left - left = inputRect.left - elemRect.width - mergin; - } - - // Fallback to vertical if horizontal doesn't fit well (e.g., goes off left) - if (left < 0) { - left = inputRect.left; - top = inputRect.bottom + mergin; - const bottomSpace = viewportHeight - top; - if (bottomSpace < elemRect.height) { - top = inputRect.top - elemRect.height - mergin; - } - } - // Ensure vertical alignment within viewport if placed horizontally - top = Math.max(mergin, Math.min(viewportHeight - elemRect.height - mergin, top)); - } - - // Ensure the element stays within viewport (final check for both cases) - // left = Math.max(mergin, Math.min(viewportWidth - elw - mergin, left)); - // top = Math.max(mergin, Math.min(viewportHeight - elemHeight - mergin, top)); - - // Update element position - this.root.style.left = `${left}px`; - this.root.style.top = `${top}px`; - - // Set max dimensions to ensure scrolling if content is too large - // Adjust max height based on final top position - const maxHeight = viewportHeight - top - 20; - this.root.style.maxHeight = `${maxHeight}px`; - // Adjust max width based on final left position (less critical usually) - const maxWidth = Math.min(elemRect.width, viewportWidth - left - 20); - this.root.style.maxWidth = `${maxWidth}px`; + // Apply Styles + this.root.style.left = `${finalLeft}px`; + this.root.style.top = `${finalTop}px`; + this.root.style.maxWidth = `${finalMaxWidth}px`; + this.root.style.maxHeight = `${finalMaxHeight}px`; } /** @@ -289,6 +236,76 @@ class SimilarTagsUI { isVisible() { return this.root.style.display !== 'none'; } + + + /** + * Calculates the optimal placement area for the panel based on available space. + * @param {DOMRect} inputRect - Bounding rectangle of the input element. + * @param {number} elemWidth - Width of the panel element. + * @param {number} elemHeight - Height of the panel element. + * @param {number} margin - Margin around the element. + * @returns {{ x: number, y: number, width: number, height: number }} The calculated placement area. + */ + #getOptimalPlacementArea(inputRect, elemWidth, elemHeight, margin) { + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + // Calculate available space around the input element + const spaceAbove = inputRect.top - margin; + const spaceBelow = viewportHeight - inputRect.bottom - margin; + const spaceLeft = inputRect.left - margin; + const spaceRight = viewportWidth - inputRect.right - margin; + + const isVertical = settingValues.similarTagsDisplayPosition === 'vertical'; + + let area = { x: 0, y: 0, width: 0, height: 0 }; + + if (isVertical) { + // --- Vertical Placement --- + // Determine available width first (usually aligned with input) + area.width = viewportWidth - margin * 2; // Max available width within viewport margins + area.x = margin; // Default x position + + // Decide whether to place above or below + if (spaceAbove >= elemHeight || spaceAbove > spaceBelow) { + // Place Above + area.y = inputRect.top - margin - Math.min(elemHeight, spaceAbove); // Position below the available space top edge + area.height = Math.min(elemHeight, spaceAbove); // Fit height within available space + } else { + // Place Below + area.y = inputRect.bottom + margin; + area.height = Math.min(elemHeight, spaceBelow); + } + // Adjust x and width to align with input if possible, while staying in viewport + area.x = Math.max(margin, inputRect.left); + area.width = Math.min(elemWidth, viewportWidth - area.x - margin); + } else { + // --- Horizontal Placement --- + // Determine available height first (usually aligned with input top) + area.height = viewportHeight - margin * 2; // Max available height within viewport margins + area.y = margin; // Default y position + + // Decide whether to place left or right + if (spaceLeft >= elemWidth || spaceLeft > spaceRight) { + // Place Left + area.x = inputRect.left - margin - Math.min(elemWidth, spaceLeft); + area.width = Math.min(elemWidth, spaceLeft); + } else { + // Place Right + area.x = inputRect.right + margin; + area.width = Math.min(elemWidth, spaceRight); + } + // Adjust y and height to align with input top if possible, while staying in viewport + area.y = Math.max(margin, inputRect.top); + area.height = Math.min(elemHeight, viewportHeight - area.y - margin); + } + + // Ensure dimensions are non-negative + area.width = Math.max(0, area.width); + area.height = Math.max(0, area.height); + + return area; + } } // --- Helper Functions --- @@ -528,7 +545,7 @@ export class SimilarTagsEventHandler { // If the focus is not within the similar tags UI, hide it. if (similarTagsUI && !similarTagsElement.contains(activeElement)) { - // similarTagsUI.hide(); + similarTagsUI.hide(); } }, 150); // Delay in milliseconds (adjust if necessary) } @@ -543,7 +560,7 @@ export class SimilarTagsEventHandler { similarTagsUI.hide(); } } - + // Show similar tags on Ctrl+Space if (settingValues.enableSimilarTags) { if (event.key === ' ' && event.ctrlKey) {