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()">
|
onfocus="showIndicatorDropdown()" oninput="filterIndicatorTypes()">
|
||||||
<input type="hidden" id="newi_type" value="{% if indicator_types %}{{ indicator_types[0] }}{% endif %}">
|
<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;">
|
<div id="indicator_dropdown" class="indicator-dropdown" style="display: none;">
|
||||||
|
<!-- Regular indicators -->
|
||||||
{% for i_type in indicator_types %}
|
{% for i_type in indicator_types %}
|
||||||
|
{% if not i_type.startswith('CDL_') %}
|
||||||
<div class="indicator-option" data-value="{{i_type}}"
|
<div class="indicator-option" data-value="{{i_type}}"
|
||||||
onmouseover="showIndicatorTooltip('{{i_type}}')"
|
onmouseover="showIndicatorTooltip('{{i_type}}'); hidePatternSubmenu();"
|
||||||
onclick="selectIndicatorType('{{i_type}}')">
|
onclick="selectIndicatorType('{{i_type}}')">
|
||||||
{{i_type}}
|
{{i_type}}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% 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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -122,6 +131,19 @@
|
||||||
<div id="tooltip_svg" style="text-align: center;"></div>
|
<div id="tooltip_svg" style="text-align: center;"></div>
|
||||||
</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>
|
<style>
|
||||||
.indicator-dropdown {
|
.indicator-dropdown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -152,6 +174,45 @@
|
||||||
border-bottom: none;
|
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 {
|
.indicator-tooltip {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -334,6 +395,8 @@ const indicatorInfo = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let submenuTimeout = null;
|
||||||
|
|
||||||
function showIndicatorDropdown() {
|
function showIndicatorDropdown() {
|
||||||
document.getElementById('indicator_dropdown').style.display = 'block';
|
document.getElementById('indicator_dropdown').style.display = 'block';
|
||||||
// Don't show tooltip until hovering over an option
|
// Don't show tooltip until hovering over an option
|
||||||
|
|
@ -343,16 +406,122 @@ function hideIndicatorDropdown() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.getElementById('indicator_dropdown').style.display = 'none';
|
document.getElementById('indicator_dropdown').style.display = 'none';
|
||||||
document.getElementById('indicator_tooltip').style.display = 'none';
|
document.getElementById('indicator_tooltip').style.display = 'none';
|
||||||
|
hidePatternSubmenu();
|
||||||
}, 200);
|
}, 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() {
|
function filterIndicatorTypes() {
|
||||||
const search = document.getElementById('newi_type_search').value.toLowerCase();
|
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 => {
|
options.forEach(opt => {
|
||||||
|
if (!opt.closest('.indicator-submenu')) {
|
||||||
const text = opt.textContent.toLowerCase();
|
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) {
|
function selectIndicatorType(type) {
|
||||||
|
|
@ -363,12 +532,17 @@ function selectIndicatorType(type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showIndicatorTooltip(type) {
|
function showIndicatorTooltip(type) {
|
||||||
|
// Clear any pending submenu hide
|
||||||
|
if (submenuTimeout) {
|
||||||
|
clearTimeout(submenuTimeout);
|
||||||
|
submenuTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
const info = indicatorInfo[type];
|
const info = indicatorInfo[type];
|
||||||
const tooltip = document.getElementById('indicator_tooltip');
|
const tooltip = document.getElementById('indicator_tooltip');
|
||||||
const title = document.getElementById('tooltip_title');
|
const title = document.getElementById('tooltip_title');
|
||||||
const desc = document.getElementById('tooltip_description');
|
const desc = document.getElementById('tooltip_description');
|
||||||
const svg = document.getElementById('tooltip_svg');
|
const svg = document.getElementById('tooltip_svg');
|
||||||
const dropdown = document.getElementById('indicator_dropdown');
|
|
||||||
|
|
||||||
if (info) {
|
if (info) {
|
||||||
title.textContent = type;
|
title.textContent = type;
|
||||||
|
|
@ -380,28 +554,39 @@ function showIndicatorTooltip(type) {
|
||||||
svg.innerHTML = '';
|
svg.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get dropdown position and place tooltip to the right of it
|
// Determine which element to position relative to
|
||||||
const dropdownRect = dropdown.getBoundingClientRect();
|
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.display = 'block';
|
||||||
tooltip.style.position = 'fixed';
|
tooltip.style.position = 'fixed';
|
||||||
tooltip.style.left = (dropdownRect.right + 10) + 'px';
|
tooltip.style.left = (refRect.right + 10) + 'px';
|
||||||
tooltip.style.top = dropdownRect.top + 'px';
|
tooltip.style.top = refRect.top + 'px';
|
||||||
|
|
||||||
// If tooltip would go off right edge of screen, put it to the left instead
|
// If tooltip would go off right edge of screen, put it to the left instead
|
||||||
const tooltipWidth = 280; // matches CSS width
|
const tooltipWidth = 280; // matches CSS width
|
||||||
if (dropdownRect.right + 10 + tooltipWidth > window.innerWidth) {
|
if (refRect.right + 10 + tooltipWidth > window.innerWidth) {
|
||||||
tooltip.style.left = (dropdownRect.left - tooltipWidth - 10) + 'px';
|
tooltip.style.left = (refRect.left - tooltipWidth - 10) + 'px';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close dropdown when clicking outside
|
// Close dropdown when clicking outside
|
||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
const wrapper = document.querySelector('.indicator-type-wrapper');
|
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_dropdown').style.display = 'none';
|
||||||
document.getElementById('indicator_tooltip').style.display = 'none';
|
document.getElementById('indicator_tooltip').style.display = 'none';
|
||||||
|
document.getElementById('patterns_submenu').style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue