Add categorized indicator dropdown with expandable Patterns submenu
- Group candlestick patterns under expandable "Patterns" category - Regular indicators shown directly in dropdown - Patterns submenu expands on hover with all CDL_* indicators - Fixed submenu positioning outside scrollable container - Tooltip shows pattern description and SVG when hovering items Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4ab7a5023d
commit
8f362a1379
|
|
@ -24,15 +24,24 @@
|
|||
onfocus="showIndicatorDropdown()" oninput="filterIndicatorTypes()">
|
||||
<input type="hidden" id="newi_type" value="{% if indicator_types %}{{ indicator_types[0] }}{% endif %}">
|
||||
|
||||
<!-- Custom dropdown -->
|
||||
<!-- Custom dropdown with categories -->
|
||||
<div id="indicator_dropdown" class="indicator-dropdown" style="display: none;">
|
||||
<!-- Regular indicators -->
|
||||
{% for i_type in indicator_types %}
|
||||
{% if not i_type.startswith('CDL_') %}
|
||||
<div class="indicator-option" data-value="{{i_type}}"
|
||||
onmouseover="showIndicatorTooltip('{{i_type}}')"
|
||||
onmouseover="showIndicatorTooltip('{{i_type}}'); hidePatternSubmenu();"
|
||||
onclick="selectIndicatorType('{{i_type}}')">
|
||||
{{i_type}}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Patterns category -->
|
||||
<div id="patterns_category" class="indicator-category" onmouseover="showPatternSubmenu()" onmouseout="hidePatternSubmenuDelayed()">
|
||||
<span onmouseover="showCategoryTooltip()">Patterns</span>
|
||||
<span class="category-arrow">▶</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -122,6 +131,19 @@
|
|||
<div id="tooltip_svg" style="text-align: center;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Patterns submenu - placed outside dropdown to avoid scroll issues -->
|
||||
<div id="patterns_submenu" class="indicator-submenu" onmouseover="keepPatternSubmenuOpen()" onmouseout="hidePatternSubmenuDelayed()">
|
||||
{% for i_type in indicator_types %}
|
||||
{% if i_type.startswith('CDL_') %}
|
||||
<div class="indicator-option" data-value="{{i_type}}"
|
||||
onmouseover="showIndicatorTooltip('{{i_type}}')"
|
||||
onclick="selectIndicatorType('{{i_type}}')">
|
||||
{{i_type}}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.indicator-dropdown {
|
||||
position: absolute;
|
||||
|
|
@ -152,6 +174,45 @@
|
|||
border-bottom: none;
|
||||
}
|
||||
|
||||
.indicator-category {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
background: #f8f8f8;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.indicator-category:hover {
|
||||
background-color: #3E3AF2;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.category-arrow {
|
||||
font-size: 10px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.indicator-submenu {
|
||||
display: none;
|
||||
position: fixed;
|
||||
min-width: 200px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.indicator-submenu .indicator-option {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.indicator-tooltip {
|
||||
display: none;
|
||||
position: fixed;
|
||||
|
|
@ -334,6 +395,8 @@ const indicatorInfo = {
|
|||
}
|
||||
};
|
||||
|
||||
let submenuTimeout = null;
|
||||
|
||||
function showIndicatorDropdown() {
|
||||
document.getElementById('indicator_dropdown').style.display = 'block';
|
||||
// Don't show tooltip until hovering over an option
|
||||
|
|
@ -343,16 +406,122 @@ function hideIndicatorDropdown() {
|
|||
setTimeout(() => {
|
||||
document.getElementById('indicator_dropdown').style.display = 'none';
|
||||
document.getElementById('indicator_tooltip').style.display = 'none';
|
||||
hidePatternSubmenu();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function showPatternSubmenu() {
|
||||
if (submenuTimeout) {
|
||||
clearTimeout(submenuTimeout);
|
||||
submenuTimeout = null;
|
||||
}
|
||||
|
||||
const category = document.getElementById('patterns_category');
|
||||
const submenu = document.getElementById('patterns_submenu');
|
||||
const categoryRect = category.getBoundingClientRect();
|
||||
|
||||
// Position submenu to the right of the category
|
||||
submenu.style.display = 'block';
|
||||
submenu.style.left = (categoryRect.right + 2) + 'px';
|
||||
submenu.style.top = categoryRect.top + 'px';
|
||||
|
||||
// If submenu would go off right edge, position to the left instead
|
||||
const submenuWidth = submenu.offsetWidth;
|
||||
if (categoryRect.right + 2 + submenuWidth > window.innerWidth) {
|
||||
submenu.style.left = (categoryRect.left - submenuWidth - 2) + 'px';
|
||||
}
|
||||
|
||||
// If submenu would go off bottom, adjust top
|
||||
const submenuHeight = submenu.offsetHeight;
|
||||
if (categoryRect.top + submenuHeight > window.innerHeight) {
|
||||
submenu.style.top = (window.innerHeight - submenuHeight - 10) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
function keepPatternSubmenuOpen() {
|
||||
if (submenuTimeout) {
|
||||
clearTimeout(submenuTimeout);
|
||||
submenuTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
function showCategoryTooltip() {
|
||||
const tooltip = document.getElementById('indicator_tooltip');
|
||||
const title = document.getElementById('tooltip_title');
|
||||
const desc = document.getElementById('tooltip_description');
|
||||
const svg = document.getElementById('tooltip_svg');
|
||||
const submenu = document.getElementById('patterns_submenu');
|
||||
|
||||
title.textContent = 'Candlestick Patterns';
|
||||
desc.textContent = 'Technical analysis patterns based on candlestick formations. These patterns can signal potential reversals or continuations in price trends. Hover over a pattern to see its description and visual representation.';
|
||||
svg.innerHTML = '';
|
||||
|
||||
// Position tooltip to the right of the submenu
|
||||
const submenuRect = submenu.getBoundingClientRect();
|
||||
tooltip.style.display = 'block';
|
||||
tooltip.style.position = 'fixed';
|
||||
tooltip.style.left = (submenuRect.right + 10) + 'px';
|
||||
tooltip.style.top = submenuRect.top + 'px';
|
||||
|
||||
const tooltipWidth = 280;
|
||||
if (submenuRect.right + 10 + tooltipWidth > window.innerWidth) {
|
||||
tooltip.style.left = (submenuRect.left - tooltipWidth - 10) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
function hidePatternSubmenu() {
|
||||
document.getElementById('patterns_submenu').style.display = 'none';
|
||||
}
|
||||
|
||||
function hidePatternSubmenuDelayed() {
|
||||
submenuTimeout = setTimeout(() => {
|
||||
hidePatternSubmenu();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function filterIndicatorTypes() {
|
||||
const search = document.getElementById('newi_type_search').value.toLowerCase();
|
||||
const options = document.querySelectorAll('.indicator-option');
|
||||
const dropdown = document.getElementById('indicator_dropdown');
|
||||
const options = dropdown.querySelectorAll('.indicator-option');
|
||||
const category = dropdown.querySelector('.indicator-category');
|
||||
const submenuOptions = document.querySelectorAll('#patterns_submenu .indicator-option');
|
||||
|
||||
let hasRegularMatch = false;
|
||||
let hasPatternMatch = false;
|
||||
|
||||
// Filter regular indicators
|
||||
options.forEach(opt => {
|
||||
if (!opt.closest('.indicator-submenu')) {
|
||||
const text = opt.textContent.toLowerCase();
|
||||
opt.style.display = text.includes(search) ? 'block' : 'none';
|
||||
const matches = text.includes(search);
|
||||
opt.style.display = matches ? 'block' : 'none';
|
||||
if (matches) hasRegularMatch = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Filter pattern indicators and check if any match
|
||||
submenuOptions.forEach(opt => {
|
||||
const text = opt.textContent.toLowerCase();
|
||||
const matches = text.includes(search);
|
||||
opt.style.display = matches ? 'block' : 'none';
|
||||
if (matches) hasPatternMatch = true;
|
||||
});
|
||||
|
||||
// Show/hide the Patterns category based on search
|
||||
if (category) {
|
||||
if (search === '') {
|
||||
// No search - show category normally
|
||||
category.style.display = 'flex';
|
||||
hidePatternSubmenu();
|
||||
} else if (hasPatternMatch) {
|
||||
// Search matches patterns - show category and expand submenu
|
||||
category.style.display = 'flex';
|
||||
document.getElementById('patterns_submenu').style.display = 'block';
|
||||
} else {
|
||||
// No pattern matches - hide category
|
||||
category.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectIndicatorType(type) {
|
||||
|
|
@ -363,12 +532,17 @@ function selectIndicatorType(type) {
|
|||
}
|
||||
|
||||
function showIndicatorTooltip(type) {
|
||||
// Clear any pending submenu hide
|
||||
if (submenuTimeout) {
|
||||
clearTimeout(submenuTimeout);
|
||||
submenuTimeout = null;
|
||||
}
|
||||
|
||||
const info = indicatorInfo[type];
|
||||
const tooltip = document.getElementById('indicator_tooltip');
|
||||
const title = document.getElementById('tooltip_title');
|
||||
const desc = document.getElementById('tooltip_description');
|
||||
const svg = document.getElementById('tooltip_svg');
|
||||
const dropdown = document.getElementById('indicator_dropdown');
|
||||
|
||||
if (info) {
|
||||
title.textContent = type;
|
||||
|
|
@ -380,28 +554,39 @@ function showIndicatorTooltip(type) {
|
|||
svg.innerHTML = '';
|
||||
}
|
||||
|
||||
// Get dropdown position and place tooltip to the right of it
|
||||
const dropdownRect = dropdown.getBoundingClientRect();
|
||||
// Determine which element to position relative to
|
||||
let referenceElement;
|
||||
if (type.startsWith('CDL_')) {
|
||||
// For pattern indicators, position relative to submenu
|
||||
referenceElement = document.getElementById('patterns_submenu');
|
||||
} else {
|
||||
// For regular indicators, position relative to dropdown
|
||||
referenceElement = document.getElementById('indicator_dropdown');
|
||||
}
|
||||
|
||||
// Position tooltip to the right of the dropdown
|
||||
const refRect = referenceElement.getBoundingClientRect();
|
||||
|
||||
// Position tooltip to the right of the reference element
|
||||
tooltip.style.display = 'block';
|
||||
tooltip.style.position = 'fixed';
|
||||
tooltip.style.left = (dropdownRect.right + 10) + 'px';
|
||||
tooltip.style.top = dropdownRect.top + 'px';
|
||||
tooltip.style.left = (refRect.right + 10) + 'px';
|
||||
tooltip.style.top = refRect.top + 'px';
|
||||
|
||||
// If tooltip would go off right edge of screen, put it to the left instead
|
||||
const tooltipWidth = 280; // matches CSS width
|
||||
if (dropdownRect.right + 10 + tooltipWidth > window.innerWidth) {
|
||||
tooltip.style.left = (dropdownRect.left - tooltipWidth - 10) + 'px';
|
||||
if (refRect.right + 10 + tooltipWidth > window.innerWidth) {
|
||||
tooltip.style.left = (refRect.left - tooltipWidth - 10) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
const wrapper = document.querySelector('.indicator-type-wrapper');
|
||||
if (wrapper && !wrapper.contains(e.target)) {
|
||||
const submenu = document.getElementById('patterns_submenu');
|
||||
if (wrapper && !wrapper.contains(e.target) && !submenu.contains(e.target)) {
|
||||
document.getElementById('indicator_dropdown').style.display = 'none';
|
||||
document.getElementById('indicator_tooltip').style.display = 'none';
|
||||
document.getElementById('patterns_submenu').style.display = 'none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue